| 1 | //go:build linux |
| 2 | |
| 3 | package host |
| 4 | |
| 5 | import ( |
| 6 | "encoding/hex" |
| 7 | "os" |
| 8 | |
| 9 | "github.com/carlosframework/platform/internal/hostbox" |
| 10 | "golang.org/x/sys/unix" |
| 11 | ) |
| 12 | |
| 13 | // O_PATH pins the no-follow inode without opening a device or reading tenant |
| 14 | // bytes. Compare the descriptor and name around the handle lookup. |
| 15 | func servingParkPersistentArtifact(parent *os.File, expected servingParkFile) (servingParkPersistentFile, error) { |
| 16 | var zero servingParkPersistentFile |
| 17 | fd, err := unix.Openat(int(parent.Fd()), expected.Name, unix.O_PATH|unix.O_NOFOLLOW|unix.O_CLOEXEC, 0) |
| 18 | if err != nil { |
| 19 | return zero, err |
| 20 | } |
| 21 | defer unix.Close(fd) |
| 22 | var st unix.Statx_t |
| 23 | if err = unix.Statx(fd, "", unix.AT_EMPTY_PATH|unix.AT_SYMLINK_NOFOLLOW, unix.STATX_BASIC_STATS|unix.STATX_MNT_ID, &st); err != nil { |
| 24 | return zero, err |
| 25 | } |
| 26 | if !servingParkStatxMatches(expected, st) { |
| 27 | return zero, hostbox.ErrChanged |
| 28 | } |
| 29 | var fs unix.Statfs_t |
| 30 | if err = unix.Fstatfs(fd, &fs); err != nil { |
| 31 | return zero, err |
| 32 | } |
| 33 | h, mount, err := unix.NameToHandleAt(fd, "", unix.AT_EMPTY_PATH) |
| 34 | if err != nil { |
| 35 | return zero, err |
| 36 | } |
| 37 | out := servingParkPersistentFile{Filesystem: fs.Fsid.Val, Type: h.Type(), Handle: hex.EncodeToString(h.Bytes()), Owner: st.Uid, Mode: uint32(st.Mode)} |
| 38 | if !out.valid() || mount <= 0 || uint64(mount) != expected.Mount { |
| 39 | return zero, hostbox.ErrChanged |
| 40 | } |
| 41 | var again unix.Statx_t |
| 42 | if err = unix.Statx(int(parent.Fd()), expected.Name, unix.AT_SYMLINK_NOFOLLOW, unix.STATX_BASIC_STATS|unix.STATX_MNT_ID, &again); err != nil { |
| 43 | return zero, err |
| 44 | } |
| 45 | if !servingParkStatxMatches(expected, again) || again.Uid != st.Uid || again.Mode != st.Mode { |
| 46 | return zero, hostbox.ErrChanged |
| 47 | } |
| 48 | return out, nil |
| 49 | } |
| 50 | |
| 51 | func servingParkStatxMatches(expected servingParkFile, st unix.Statx_t) bool { |
| 52 | return expected.Present && st.Mask&unix.STATX_MNT_ID != 0 && st.Mode&unix.S_IFMT == unix.S_IFREG && st.Nlink == 1 && unix.Mkdev(st.Dev_major, st.Dev_minor) == expected.Device && st.Ino == expected.Inode && st.Mnt_id == expected.Mount && st.Blocks <= ^uint64(0)/512 && st.Blocks*512 == expected.Bytes |
| 53 | } |
| 54 | |