| 1 | //go:build linux |
| 2 | |
| 3 | package host |
| 4 | |
| 5 | import "golang.org/x/sys/unix" |
| 6 | |
| 7 | // walkNoFollowDir is the one descriptor walk behind consoleDir and the root |
| 8 | // migration's floor repair. It opens base, then each name beneath it in |
| 9 | // turn with O_NOFOLLOW|O_DIRECTORY, so no symlink is followed below base. |
| 10 | // check sees every directory on the way (last false) before anything |
| 11 | // beneath it is opened or made, and the final directory (last true) once it |
| 12 | // is open. A missing name is made only when create(last) allows it: Mkdirat |
| 13 | // 0755 then Fchmodat 0755 beneath its checked parent, the parent fsynced, |
| 14 | // and the name opened again with O_NOFOLLOW. It returns the checked final |
| 15 | // directory's descriptor; errors are the system call's or check's own. |
| 16 | func walkNoFollowDir(base string, names []string, create func(last bool) bool, check func(fd int, last bool) error) (int, error) { |
| 17 | if len(names) == 0 { |
| 18 | return -1, unix.EINVAL |
| 19 | } |
| 20 | fd, err := unix.Open(base, unix.O_RDONLY|unix.O_DIRECTORY|unix.O_CLOEXEC, 0) |
| 21 | if err != nil { |
| 22 | return -1, err |
| 23 | } |
| 24 | for i, name := range names { |
| 25 | last := i == len(names)-1 |
| 26 | if err = check(fd, false); err != nil { |
| 27 | unix.Close(fd) |
| 28 | return -1, err |
| 29 | } |
| 30 | next, e := unix.Openat(fd, name, unix.O_RDONLY|unix.O_DIRECTORY|unix.O_NOFOLLOW|unix.O_CLOEXEC, 0) |
| 31 | if e == unix.ENOENT && create(last) { |
| 32 | e = unix.Mkdirat(fd, name, 0o755) |
| 33 | if e == nil { |
| 34 | e = unix.Fchmodat(fd, name, 0o755, 0) |
| 35 | } |
| 36 | if e == nil || e == unix.EEXIST { |
| 37 | if e = unix.Fsync(fd); e == nil { |
| 38 | next, e = unix.Openat(fd, name, unix.O_RDONLY|unix.O_DIRECTORY|unix.O_NOFOLLOW|unix.O_CLOEXEC, 0) |
| 39 | } |
| 40 | } |
| 41 | } |
| 42 | unix.Close(fd) |
| 43 | if e != nil { |
| 44 | return -1, e |
| 45 | } |
| 46 | fd = next |
| 47 | if last { |
| 48 | if err = check(fd, true); err != nil { |
| 49 | unix.Close(fd) |
| 50 | return -1, err |
| 51 | } |
| 52 | } |
| 53 | } |
| 54 | return fd, nil |
| 55 | } |
| 56 | |