// Command owned judges how the catalogue's directories and archives treat files they did not write (novox/hq // ADR 0422 §8, issue 1024). It refuses, naming the module, the resource and the reason: // // 1. a directory or archive without `unknown`, or with a value but move-out, module or program; // // 2. `move-out` on a path not under ${machine:account-home} or ${machine:agent-home}; // // 3. `knows` with an empty, absolute, `.`/`..`/empty-component or repeated path; // // 4. a path one module declares (a resource's path, or a fact with "home": true) lying inside a directory of // another module, after ${dir:} is resolved within each module and both homes are read as one root. // Archives are not containers for this rule. This holds for ~/.ssh what checks/sshdir held (ADR 0415 §6). // // 5. a directory that is, lies inside or holds a directory a system package owns under /var/lib, or any other // resource inside one (issue 1841: pacman's state directory, placed with "place": ".", was /var/lib/pacman, the // package manager's own database directory). A placed directory is resolved as the mesh resolves it with the // default data root: "." is /var/lib/, "mesh" /var/lib/mesh/, neither /var/lib//. // The system directories are checks/owned/system, captured from a machine (pacman -Qo, /usr/lib/tmpfiles.d); // checks/owned/system-known names the exceptions still to fix, by module, resource and issue. That list only // shrinks: an entry that no longer collides is refused, and with -base one the trunk's copy does not hold. // // go run . [-base ] // // Exits 1 on a finding. package main import ( "encoding/json" "errors" "flag" "fmt" "os" "path" "path/filepath" "regexp" "sort" "strings" ) // The lists of rule 5, relative to the catalogue's root. const ( SystemList = "checks/owned/system" SystemKnown = "checks/owned/system-known" ) // Home is the one root both machine homes are read as. const Home = "${home}" var ( ofDir = regexp.MustCompile(`\$\{dir:([a-z0-9][a-z0-9_-]*)\}`) homeVar = strings.NewReplacer("${machine:account-home}", Home, "${machine:agent-home}", Home) values = map[string]bool{"move-out": true, "module": true, "program": true} ) // Finding is one refusal. type Finding struct { Rule int Module, ID, Why string } type resource struct { ID, Type, Path, Unknown, Place string Knows []string HasKnows bool } type parsed struct { Module string Resources []resource Facts []string // paths of facts with "home": true, under the home } func load(file string) (*parsed, error) { raw, err := os.ReadFile(file) if err != nil { return nil, err } var m struct { Module string `json:"module"` Resources []struct { ID string `json:"id"` Type string `json:"type"` Path string `json:"path"` Place string `json:"place"` Unknown *string `json:"unknown"` Knows []string `json:"knows"` } `json:"resources"` Facts map[string]struct { Path string `json:"path"` Home bool `json:"home"` } `json:"facts"` } if err := json.Unmarshal(raw, &m); err != nil { return nil, fmt.Errorf("%s: %w", file, err) } out := &parsed{Module: m.Module} if out.Module == "" { out.Module = filepath.Base(filepath.Dir(file)) } for _, r := range m.Resources { x := resource{ID: r.ID, Type: r.Type, Path: r.Path, Place: r.Place, Knows: r.Knows, HasKnows: r.Knows != nil} if r.Unknown != nil { x.Unknown = *r.Unknown if x.Unknown == "" { x.Unknown = "(empty)" } } out.Resources = append(out.Resources, x) } keys := make([]string, 0, len(m.Facts)) for k := range m.Facts { keys = append(keys, k) } sort.Strings(keys) for _, k := range keys { if f := m.Facts[k]; f.Home && f.Path != "" { out.Facts = append(out.Facts, f.Path) } } return out, nil } // norm resolves ${dir:} from the module's own resources, reads both homes as one root and cleans the path. func norm(p string, dirs map[string]string) string { for i := 0; i < 8 && ofDir.MatchString(p); i++ { p = ofDir.ReplaceAllStringFunc(p, func(w string) string { if to, ok := dirs[ofDir.FindStringSubmatch(w)[1]]; ok { return to } return w }) } p = homeVar.Replace(p) if p == "" { return "" } return path.Clean(p) } func underHome(p string) bool { return strings.HasPrefix(p, Home+"/") } // badKnows says why a knows entry is refused, or "". func badKnows(k string) string { if k == "" { return "is empty" } if strings.HasPrefix(k, "/") { return "is absolute" } for _, c := range strings.Split(strings.TrimSuffix(k, "/"), "/") { if c == "" || c == "." || c == ".." { return "has an empty, . or .. component" } } return "" } type declared struct { module, id, path string } // Check reads every manifest under root/modules and answers what it refuses. func Check(root string, base []string) ([]Finding, error) { files, err := filepath.Glob(filepath.Join(root, "modules", "*", "module.json")) if err != nil { return nil, err } sort.Strings(files) system, err := readSystem(filepath.Join(root, SystemList)) if err != nil { return nil, err } known, err := readLines(filepath.Join(root, SystemKnown)) if err != nil && !errors.Is(err, os.ErrNotExist) { return nil, err } var out []Finding var paths []declared collided := map[string]bool{} type container struct{ module, path string } var containers []container for _, file := range files { m, err := load(file) if err != nil { return nil, err } dirs := map[string]string{} for _, r := range m.Resources { switch { case r.ID != "" && r.Path != "": dirs[r.ID] = r.Path case r.ID != "" && r.Type == "directory": // A directory the mesh places for the module (`place`): where it lies is the node-engine's, but it is // the module's own, so a path under it belongs to this module and to no other's directory of the same // id (issue 1484: two runs-as modules each keep a .ssh in their own state directory). dirs[r.ID] = "/placed/" + m.Module + "/" + r.ID } } out = append(out, inSystem(m, system, known, collided)...) for _, r := range m.Resources { p := norm(r.Path, dirs) if p != "" { paths = append(paths, declared{m.Module, r.ID, p}) } if r.Type != "directory" && r.Type != "archive" { continue } if r.Type == "directory" && p != "" { containers = append(containers, container{m.Module, p}) } add := func(rule int, why string) { out = append(out, Finding{rule, m.Module, r.ID, why}) } switch { case r.Unknown == "": add(1, "a "+r.Type+" without `unknown` (move-out, module or program)") case !values[r.Unknown]: add(1, fmt.Sprintf("`unknown` is %q, not move-out, module or program", r.Unknown)) case r.Unknown == "move-out" && !underHome(p): add(2, fmt.Sprintf("`move-out` on %q, which is not under ${machine:account-home} or ${machine:agent-home}", r.Path)) } seen := map[string]bool{} for _, k := range r.Knows { if why := badKnows(k); why != "" { add(3, fmt.Sprintf("`knows` entry %q %s", k, why)) continue } c := strings.TrimSuffix(k, "/") if seen[c] { add(3, fmt.Sprintf("`knows` entry %q is repeated", k)) } seen[c] = true } if r.HasKnows && len(r.Knows) == 0 { add(3, "`knows` is empty") } } for _, f := range m.Facts { if p := norm(Home+"/"+f, nil); p != "" { paths = append(paths, declared{m.Module, "fact " + f, p}) } } } for _, k := range known { if !collided[k] { module, id, _ := strings.Cut(k, " ") id, _, _ = strings.Cut(id, " ") out = append(out, Finding{5, module, id, fmt.Sprintf("is listed in %s, and no longer lies in a system "+ "package's directory: take the line out, so the list only shrinks", SystemKnown)}) } } if base != nil { held := map[string]bool{} for _, b := range base { held[b] = true } for _, k := range known { if !held[k] { module, id, _ := strings.Cut(k, " ") id, _, _ = strings.Cut(id, " ") out = append(out, Finding{5, module, id, fmt.Sprintf("is a new line in %s, which the trunk's copy "+ "does not hold: a module's directory is placed outside a system package's, never listed", SystemKnown)}) } } } for _, d := range paths { for _, c := range containers { if c.module != d.module && (d.path == c.path || strings.HasPrefix(d.path, c.path+"/")) { out = append(out, Finding{4, d.module, d.id, fmt.Sprintf("declares %q, inside a directory of %s (%q)", d.path, c.module, c.path)}) } } } return out, nil } // systemDir is one directory a system package owns, and what says so. type systemDir struct{ Path, Source string } // readLines is a list's lines, without comments and blank lines. func readLines(file string) ([]string, error) { raw, err := os.ReadFile(file) if err != nil { return nil, err } var lines []string for _, line := range strings.Split(string(raw), "\n") { if line = strings.TrimSpace(line); line != "" && !strings.HasPrefix(line, "#") { lines = append(lines, line) } } return lines, nil } // readSystem is the list of system directories: " : " per line. Absent or // empty, the check fails: a list that says nothing would let every module through. func readSystem(file string) ([]systemDir, error) { lines, err := readLines(file) if err != nil { return nil, fmt.Errorf("rule 5 reads the system packages' directories from %s: %w", file, err) } if len(lines) == 0 { return nil, fmt.Errorf("%s names no system package's directory, so rule 5 would pass everything", file) } var out []systemDir for _, line := range lines { p, source, _ := strings.Cut(line, " ") if !strings.HasPrefix(p, "/var/lib/") || strings.TrimSpace(source) == "" { return nil, fmt.Errorf("%s: %q is not \"/var/lib/ : \"", file, line) } out = append(out, systemDir{path.Clean(p), strings.TrimSpace(source)}) } return out, nil } // placedAt is where the mesh places a pathless directory with the default data root (mesh-sdk catalogue dirsFor). func placedAt(module string, r resource) string { switch r.Place { case ".": return "/var/lib/" + module case "mesh": return "/var/lib/mesh/" + module } return "/var/lib/" + module + "/" + r.ID } // inSystem is rule 5 for one module: each resource whose resolved path collides with a system package's directory, // unless system-known lists it (then it is marked collided, so a stale line is found). func inSystem(m *parsed, system []systemDir, known []string, collided map[string]bool) []Finding { real := map[string]string{} for _, r := range m.Resources { if r.Type == "directory" && r.Path == "" && r.ID != "" { real[r.ID] = placedAt(m.Module, r) } } for _, r := range m.Resources { if r.ID != "" && r.Path != "" { real[r.ID] = r.Path } } var out []Finding for _, r := range m.Resources { p := "" if r.Path != "" { p = norm(r.Path, real) } else if r.Type == "directory" { p = real[r.ID] } if !strings.HasPrefix(p, "/") { continue } for _, s := range system { inside := p == s.Path || strings.HasPrefix(p, s.Path+"/") holds := r.Type == "directory" && strings.HasPrefix(s.Path, p+"/") if !inside && !holds { continue } listed := "" for _, k := range known { if strings.HasPrefix(k, m.Module+" "+r.ID+" ") || k == m.Module+" "+r.ID { listed = k } } if listed != "" { collided[listed] = true break } how := "is or lies inside" if holds { how = "holds" } out = append(out, Finding{5, m.Module, r.ID, fmt.Sprintf("resolves to %q, which %s %s, the directory of the "+ "system package %s; place it under /var/lib/mesh/%s (\"place\": \"mesh\") or a path of its own", p, how, s.Path, s.Source, m.Module)}) break } } return out } func main() { baseFile := flag.String("base", "", "the trunk's copy of "+SystemKnown+": a line it does not hold is refused") flag.Parse() root := "." if flag.NArg() > 0 { root = flag.Arg(0) } var base []string if *baseFile != "" { lines, err := readLines(*baseFile) if err != nil { fmt.Fprintln(os.Stderr, err) os.Exit(1) } base = append([]string{}, lines...) } found, err := Check(root, base) if err != nil { fmt.Fprintln(os.Stderr, err) os.Exit(1) } for _, f := range found { cite := "novox/hq ADR 0422 §8, issue 1024" if f.Rule == 5 { cite = "issue 1841" } fmt.Printf("%s: %s: %s (rule %d, %s)\n", f.Module, f.ID, f.Why, f.Rule, cite) } if len(found) > 0 { os.Exit(1) } fmt.Println("owned paths: every directory and archive says how it treats unknown files; no module declares inside another's directory, or in a system package's") }