//go:build linux package host import "golang.org/x/sys/unix" // walkNoFollowDir is the one descriptor walk behind consoleDir and the root // migration's floor repair. It opens base, then each name beneath it in // turn with O_NOFOLLOW|O_DIRECTORY, so no symlink is followed below base. // check sees every directory on the way (last false) before anything // beneath it is opened or made, and the final directory (last true) once it // is open. A missing name is made only when create(last) allows it: Mkdirat // 0755 then Fchmodat 0755 beneath its checked parent, the parent fsynced, // and the name opened again with O_NOFOLLOW. It returns the checked final // directory's descriptor; errors are the system call's or check's own. func walkNoFollowDir(base string, names []string, create func(last bool) bool, check func(fd int, last bool) error) (int, error) { if len(names) == 0 { return -1, unix.EINVAL } fd, err := unix.Open(base, unix.O_RDONLY|unix.O_DIRECTORY|unix.O_CLOEXEC, 0) if err != nil { return -1, err } for i, name := range names { last := i == len(names)-1 if err = check(fd, false); err != nil { unix.Close(fd) return -1, err } next, e := unix.Openat(fd, name, unix.O_RDONLY|unix.O_DIRECTORY|unix.O_NOFOLLOW|unix.O_CLOEXEC, 0) if e == unix.ENOENT && create(last) { e = unix.Mkdirat(fd, name, 0o755) if e == nil { e = unix.Fchmodat(fd, name, 0o755, 0) } if e == nil || e == unix.EEXIST { if e = unix.Fsync(fd); e == nil { next, e = unix.Openat(fd, name, unix.O_RDONLY|unix.O_DIRECTORY|unix.O_NOFOLLOW|unix.O_CLOEXEC, 0) } } } unix.Close(fd) if e != nil { return -1, e } fd = next if last { if err = check(fd, true); err != nil { unix.Close(fd) return -1, err } } } return fd, nil }