package guestconfig import ( "sort" "strconv" "strings" ) type MountSpec struct { Source string Target string FSType string Options []string Dump int Pass int } func (m MountSpec) String() string { options := strings.Join(compactStrings(m.Options), ",") if options == "" { options = "defaults" } return strings.Join([]string{ m.Source, m.Target, m.FSType, options, strconv.Itoa(m.Dump), strconv.Itoa(m.Pass), }, " ") } type Builder struct { files map[string][]byte dropTargets map[string]struct{} mounts map[string]MountSpec order []string } func NewBuilder() *Builder { return &Builder{ files: make(map[string][]byte), dropTargets: make(map[string]struct{}), mounts: make(map[string]MountSpec), } } func (b *Builder) WriteFile(path string, data []byte) { if b.files == nil { b.files = make(map[string][]byte) } b.files[path] = append([]byte(nil), data...) } func (b *Builder) DropMountTarget(target string) { target = strings.TrimSpace(target) if target == "" { return } if b.dropTargets == nil { b.dropTargets = make(map[string]struct{}) } b.dropTargets[target] = struct{}{} } func (b *Builder) AddMount(spec MountSpec) { spec.Source = strings.TrimSpace(spec.Source) spec.Target = strings.TrimSpace(spec.Target) spec.FSType = strings.TrimSpace(spec.FSType) spec.Options = compactStrings(spec.Options) if spec.Source == "" || spec.Target == "" || spec.FSType == "" { return } if b.mounts == nil { b.mounts = make(map[string]MountSpec) } if _, exists := b.mounts[spec.Target]; !exists { b.order = append(b.order, spec.Target) } b.mounts[spec.Target] = spec } func (b *Builder) Files() map[string][]byte { if len(b.files) == 0 { return nil } keys := b.FilePaths() out := make(map[string][]byte, len(keys)) for _, path := range keys { out[path] = append([]byte(nil), b.files[path]...) } return out } func (b *Builder) FilePaths() []string { keys := make([]string, 0, len(b.files)) for path := range b.files { keys = append(keys, path) } sort.Strings(keys) return keys } func (b *Builder) RenderFSTab(existing string) string { lines := strings.Split(existing, "\n") out := make([]string, 0, len(lines)+len(b.mounts)) managedTargets := make(map[string]struct{}, len(b.mounts)) for target := range b.mounts { managedTargets[target] = struct{}{} } for _, line := range lines { trimmed := strings.TrimSpace(line) if trimmed == "" { continue } target := mountTarget(trimmed) if target != "" { if _, drop := b.dropTargets[target]; drop { continue } if _, managed := managedTargets[target]; managed { continue } } out = append(out, line) } for _, target := range b.order { out = append(out, b.mounts[target].String()) } return strings.Join(out, "\n") + "\n" } func mountTarget(line string) string { fields := strings.Fields(line) if len(fields) < 2 { return "" } return fields[1] } func compactStrings(values []string) []string { if len(values) == 0 { return nil } out := make([]string, 0, len(values)) seen := make(map[string]struct{}, len(values)) for _, value := range values { value = strings.TrimSpace(value) if value == "" { continue } if _, ok := seen[value]; ok { continue } seen[value] = struct{}{} out = append(out, value) } return out }