package shares import ( "os" "path/filepath" "sort" "strings" ) // Other services on the host publish their own shares through // drop-in files. nasmon includes the SMB drop-ins into the smb.conf it // generates and keeps smbd / nfs-server running while any of them define a // share, even when nasmon itself has none. const ( DefaultSMBIncludeDir = "/etc/samba/smb.conf.d" DefaultExportsDir = "/etc/exports.d" ) // ExternalShare is a share defined outside nasmon (read-only in the UI). type ExternalShare struct { Kind string // smb | nfs Source string // drop-in file name without extension Name string // SMB share name (empty for NFS) Path string Clients string // NFS clients with options, as written in the file } // IncludeFiles lists the *.conf drop-ins in dir, sorted. A missing dir yields nil. func IncludeFiles(dir string) []string { if dir == "" { return nil } m, _ := filepath.Glob(filepath.Join(dir, "*.conf")) sort.Strings(m) return m } func sourceName(file string) string { return strings.TrimSuffix(filepath.Base(file), filepath.Ext(file)) } // ParseSMBDropIn returns the shares ([section] other than [global], [homes], // [printers]) defined in a Samba config fragment. func ParseSMBDropIn(text, source string) []ExternalShare { var out []ExternalShare var cur *ExternalShare for _, raw := range strings.Split(text, "\n") { line := strings.TrimSpace(raw) if line == "" || line[0] == '#' || line[0] == ';' { continue } if strings.HasPrefix(line, "[") && strings.HasSuffix(line, "]") { name := strings.TrimSpace(line[1 : len(line)-1]) switch strings.ToLower(name) { case "global", "homes", "printers": cur = nil default: out = append(out, ExternalShare{Kind: "smb", Source: source, Name: name}) cur = &out[len(out)-1] } continue } if cur == nil { continue } if k, v, ok := strings.Cut(line, "="); ok && strings.EqualFold(strings.TrimSpace(k), "path") { cur.Path = strings.TrimSpace(v) } } return out } // ParseExportsFile returns the exports in an exports(5) file. func ParseExportsFile(text, source string) []ExternalShare { var out []ExternalShare for _, raw := range strings.Split(text, "\n") { line := strings.TrimSpace(raw) if line == "" || line[0] == '#' { continue } var path, rest string if line[0] == '"' { if end := strings.Index(line[1:], `"`); end >= 0 { path, rest = line[1:end+1], line[end+2:] } } else { path, rest, _ = strings.Cut(line, " ") } if path == "" { continue } out = append(out, ExternalShare{Kind: "nfs", Source: source, Path: path, Clients: strings.TrimSpace(rest)}) } return out } // External reads the SMB drop-ins and the exports.d files of other services. func (m *Manager) External() []ExternalShare { var out []ExternalShare for _, f := range IncludeFiles(m.IncludeDir) { if b, err := os.ReadFile(f); err == nil { out = append(out, ParseSMBDropIn(string(b), sourceName(f))...) } } if m.ExportsDir != "" { files, _ := filepath.Glob(filepath.Join(m.ExportsDir, "*.exports")) sort.Strings(files) for _, f := range files { if filepath.Clean(f) == filepath.Clean(m.Exports) { continue } if b, err := os.ReadFile(f); err == nil { out = append(out, ParseExportsFile(string(b), sourceName(f))...) } } } return out } func countKind(ext []ExternalShare, kind string) int { n := 0 for _, e := range ext { if e.Kind == kind { n++ } } return n }