package main // This file holds the default excludes (README "Rules for the walk"): // the directories a scan's walk does not enter below an operand, unless // scan is given --no-default-excludes. import ( "bufio" "errors" "fmt" "io" "os" "slices" "strings" ) // The systems that have default excludes, as runtime.GOOS names them. const ( goosLinux = "linux" goosDarwin = "darwin" ) // mountInfoPath is the file in which Linux lists the mounts the reading // process sees. const mountInfoPath = "/proc/self/mountinfo" // The fields of a line of mountInfoPath that kernelMounts reads, counted // from 0 (see proc(5)): the mount point is the fifth field, and after // the sixth come optional fields, then a field holding "-", then the // filesystem type. const ( mountPointField = 4 optionalFieldsStart = 6 ) // typesPerHelpLine is how many filesystem types the scan command's help // lists on each line. const typesPerHelpLine = 6 // errMountInfoLine reports a line of mountInfoPath without the fields // kernelMounts reads. var errMountInfoLine = errors.New("unexpected line") // defaultExcludes returns the directories that are default excludes on // the system goos names, as runtime.GOOS does: a fixed list on Linux and // on macOS, and none on any other system. On Linux the mount points // kernelMounts finds are default excludes as well. func defaultExcludes(goos string) []string { switch goos { case goosLinux: return []string{"/proc", "/sys", "/dev", "/run", "/tmp", "/var/tmp"} case goosDarwin: // A walk of / reaches everything in /System/Volumes/Data through // the firmlinks, so walking it as well would record each of // those files twice. return []string{ "/dev", "/private/tmp", "/private/var/tmp", "/private/var/vm", "/System/Volumes/VM", "/System/Volumes/Data", } default: return nil } } // kernelFilesystemTypes returns the Linux filesystem types whose mount // points are default excludes: filesystems that the kernel makes up or // that are kept in memory. func kernelFilesystemTypes() []string { return []string{ "proc", "sysfs", "devtmpfs", "devpts", "tmpfs", "ramfs", "cgroup", "cgroup2", "debugfs", "tracefs", "securityfs", "pstore", "bpf", "configfs", "fusectl", "mqueue", "hugetlbfs", "efivarfs", "binfmt_misc", "autofs", "nsfs", "rpc_pipefs", "selinuxfs", } } // findDefaultExcludes returns the default excludes of the system goos // names, each with what the scan says about it on the line it prints // when its walk does not enter it. On Linux it reads the mount points // from mountInfoPath. func findDefaultExcludes(goos string) (map[string]string, error) { excludes := make(map[string]string) for _, dir := range defaultExcludes(goos) { excludes[dir] = "default exclude" } if goos != goosLinux { return excludes, nil } f, err := os.Open(mountInfoPath) if err != nil { return nil, err } defer func() { _ = f.Close() }() mounts, err := kernelMounts(f) if err != nil { return nil, fmt.Errorf("read %s: %w", mountInfoPath, err) } for dir, fsType := range mounts { excludes[dir] = "default exclude, " + fsType + " filesystem" } return excludes, nil } // kernelMounts reads mountinfo, laid out as mountInfoPath is, and // returns the mount point of every filesystem whose type is one of // kernelFilesystemTypes, with that type. Where one filesystem is mounted // over another, a later line names the one on top, so the last line for // a mount point decides. func kernelMounts(mountinfo io.Reader) (map[string]string, error) { types := kernelFilesystemTypes() // The kernel writes a space, tab, newline or backslash in a mount // point as an octal escape. unescape := strings.NewReplacer( `\040`, " ", `\011`, "\t", `\012`, "\n", `\134`, `\`) mounts := make(map[string]string) lines := bufio.NewScanner(mountinfo) for lines.Scan() { fields := strings.Fields(lines.Text()) sep := slices.Index(fields, "-") if sep < optionalFieldsStart || sep+1 >= len(fields) { return nil, fmt.Errorf("%w: %q", errMountInfoLine, lines.Text()) } mountPoint := unescape.Replace(fields[mountPointField]) fsType := fields[sep+1] if slices.Contains(types, fsType) { mounts[mountPoint] = fsType } else { delete(mounts, mountPoint) } } return mounts, lines.Err() } // defaultExcludesHelp describes the default excludes of the system goos // names, for the scan command's help. func defaultExcludesHelp(goos string) string { dirs := defaultExcludes(goos) if len(dirs) == 0 { return "This system has no default excludes." } var help strings.Builder help.WriteString("Below an operand, the walk does not enter these " + "directories, the default\nexcludes, unless --no-default-excludes " + "is given:\n\n") for _, dir := range dirs { fmt.Fprintf(&help, " %s\n", dir) } if goos == goosLinux { help.WriteString("\nnor the mount point of any filesystem of " + "these types:\n\n") types := kernelFilesystemTypes() for line := range slices.Chunk(types, typesPerHelpLine) { fmt.Fprintf(&help, " %s\n", strings.Join(line, ", ")) } } return help.String() }