check / check (push) Canceled after 0s
Below an operand, the walk no longer enters the default excludes: on Linux /proc, /sys, /dev, /run, /tmp, /var/tmp and every mount point of a kernel or memory-backed filesystem, read once from /proc/self/mountinfo; on macOS /dev, /private/tmp, /private/var/tmp, /private/var/vm, /System/Volumes/VM and /System/Volumes/Data. The lists live in the new exclude.go. An operand at or inside one is still walked, also under another operand, as with -x. Each skipped directory gets one stderr line through the warning path, is not counted as skipped, and its records count as outside the scanned operands. --no-default-excludes turns them off; scan --help lists them. Judgement call: an unreadable /proc/self/mountinfo fails the scan. Judgement call: where filesystems are mounted over one another, the last decides. Model: opus-5-5
178 lines
5.0 KiB
Go
178 lines
5.0 KiB
Go
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()
|
|
}
|