//go:build linux package host import ( "encoding/hex" "os" "github.com/carlosframework/platform/internal/hostbox" "golang.org/x/sys/unix" ) // O_PATH pins the no-follow inode without opening a device or reading tenant // bytes. Compare the descriptor and name around the handle lookup. func servingParkPersistentArtifact(parent *os.File, expected servingParkFile) (servingParkPersistentFile, error) { var zero servingParkPersistentFile fd, err := unix.Openat(int(parent.Fd()), expected.Name, unix.O_PATH|unix.O_NOFOLLOW|unix.O_CLOEXEC, 0) if err != nil { return zero, err } defer unix.Close(fd) var st unix.Statx_t if err = unix.Statx(fd, "", unix.AT_EMPTY_PATH|unix.AT_SYMLINK_NOFOLLOW, unix.STATX_BASIC_STATS|unix.STATX_MNT_ID, &st); err != nil { return zero, err } if !servingParkStatxMatches(expected, st) { return zero, hostbox.ErrChanged } var fs unix.Statfs_t if err = unix.Fstatfs(fd, &fs); err != nil { return zero, err } h, mount, err := unix.NameToHandleAt(fd, "", unix.AT_EMPTY_PATH) if err != nil { return zero, err } out := servingParkPersistentFile{Filesystem: fs.Fsid.Val, Type: h.Type(), Handle: hex.EncodeToString(h.Bytes()), Owner: st.Uid, Mode: uint32(st.Mode)} if !out.valid() || mount <= 0 || uint64(mount) != expected.Mount { return zero, hostbox.ErrChanged } var again unix.Statx_t if err = unix.Statx(int(parent.Fd()), expected.Name, unix.AT_SYMLINK_NOFOLLOW, unix.STATX_BASIC_STATS|unix.STATX_MNT_ID, &again); err != nil { return zero, err } if !servingParkStatxMatches(expected, again) || again.Uid != st.Uid || again.Mode != st.Mode { return zero, hostbox.ErrChanged } return out, nil } func servingParkStatxMatches(expected servingParkFile, st unix.Statx_t) bool { 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 }