Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions unix/linux/types.go
Original file line number Diff line number Diff line change
Expand Up @@ -1047,6 +1047,10 @@ const (
AT_STATX_FORCE_SYNC = C.AT_STATX_FORCE_SYNC
AT_STATX_DONT_SYNC = C.AT_STATX_DONT_SYNC

AT_HANDLE_FID = C.AT_HANDLE_FID
AT_HANDLE_MNT_ID_UNIQUE = C.AT_HANDLE_MNT_ID_UNIQUE
AT_HANDLE_CONNECTABLE = C.AT_HANDLE_CONNECTABLE

AT_RECURSIVE = C.AT_RECURSIVE

AT_SYMLINK_FOLLOW = C.AT_SYMLINK_FOLLOW
Expand Down
30 changes: 27 additions & 3 deletions unix/syscall_linux.go
Original file line number Diff line number Diff line change
Expand Up @@ -2449,8 +2449,28 @@ func (fh *FileHandle) Bytes() []byte {

// NameToHandleAt wraps the name_to_handle_at system call; it obtains
// a handle for a path name.
//
// Deprecated: This method can only return int-sized mount IDs and will thus
// silently ignore [AT_HANDLE_MNT_ID_UNIQUE]. Use [NameToHandleAt64] instead.
func NameToHandleAt(dirfd int, path string, flags int) (handle FileHandle, mountID int, err error) {
var mid _C_int
flags &= ^AT_HANDLE_MNT_ID_UNIQUE
handle, mid, err := NameToHandleAt64(dirfd, path, flags)
return handle, int(mid), err
}

// NameToHandleAt64 is effectively identical to [NameToHandleAt] except that it
// supports returning 64-bit mount IDs with [AT_HANDLE_MNT_ID_UNIQUE].
func NameToHandleAt64(dirfd int, path string, flags int) (handle FileHandle, mountID uint64, err error) {
uniqueMntId := flags&AT_HANDLE_MNT_ID_UNIQUE != 0
var (
midInt _C_int
mid64 uint64
)
midPtr := &midInt
if uniqueMntId {
midPtr = (*_C_int)(unsafe.Pointer(&mid64))
}

// Try first with a small buffer, assuming the handle will
// only be 32 bytes.
size := uint32(32 + unsafe.Sizeof(fileHandle{}))
Expand All @@ -2459,7 +2479,7 @@ func NameToHandleAt(dirfd int, path string, flags int) (handle FileHandle, mount
buf := make([]byte, size)
fh := (*fileHandle)(unsafe.Pointer(&buf[0]))
fh.Bytes = size - uint32(unsafe.Sizeof(fileHandle{}))
err = nameToHandleAt(dirfd, path, fh, &mid, flags)
err = nameToHandleAt(dirfd, path, fh, midPtr, flags)
if err == EOVERFLOW {
if didResize {
// We shouldn't need to resize more than once
Expand All @@ -2472,7 +2492,11 @@ func NameToHandleAt(dirfd int, path string, flags int) (handle FileHandle, mount
if err != nil {
return
}
return FileHandle{fh}, int(mid), nil
mountID = uint64(midInt)
if uniqueMntId {
mountID = mid64
}
return FileHandle{fh}, mountID, nil
}
}

Expand Down
93 changes: 88 additions & 5 deletions unix/syscall_linux_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -913,7 +913,7 @@ func TestClockNanosleep(t *testing.T) {
}

func TestOpenByHandleAt(t *testing.T) {
skipIfNotSupported := func(t *testing.T, name string, err error) {
skipIfNotSupported := func(t *testing.T, name, flags string, err error) {
if err == unix.EPERM {
t.Skipf("skipping %s test without CAP_DAC_READ_SEARCH", name)
}
Expand All @@ -923,15 +923,18 @@ func TestOpenByHandleAt(t *testing.T) {
if err == unix.EOPNOTSUPP {
t.Skipf("%s not supported on this filesystem", name)
}
if err == unix.EINVAL {
t.Skipf("%s flags %s are not supported", name, flags)
}
}

h, mountID, err := unix.NameToHandleAt(unix.AT_FDCWD, "syscall_linux_test.go", 0)
if err != nil {
skipIfNotSupported(t, "name_to_handle_at", err)
skipIfNotSupported(t, "name_to_handle_at", "<none>", err)
t.Fatalf("NameToHandleAt: %v", err)
}
t.Logf("mountID: %v, handle: size=%d, type=%d, bytes=%q", mountID,
h.Size(), h.Type(), h.Bytes())
t.Logf("mountID: %v (%#x), handle: size=%d, type=%d, bytes=%q",
mountID, mountID, h.Size(), h.Type(), h.Bytes())
mount, err := openMountByID(mountID)
if err != nil {
t.Fatalf("openMountByID: %v", err)
Expand All @@ -944,8 +947,8 @@ func TestOpenByHandleAt(t *testing.T) {
h = unix.NewFileHandle(h.Type(), h.Bytes())
}
fd, err := unix.OpenByHandleAt(int(mount.Fd()), h, unix.O_RDONLY)
skipIfNotSupported(t, "open_by_handle_at", err)
if err != nil {
skipIfNotSupported(t, "open_by_handle_at", "O_RDONLY", err)
t.Fatalf("OpenByHandleAt: %v", err)
}
t.Logf("opened fd %v", fd)
Expand All @@ -962,6 +965,86 @@ func TestOpenByHandleAt(t *testing.T) {
}
})
}

t.Run("AT_HANDLE_MNT_ID_UNIQUE", func(t *testing.T) {
// AT_HANDLE_MNT_ID_UNIQUE needs to be implicitly cleared as it would
// produce unusable information (and on pre-AT_HANDLE_MNT_ID_UNIQUE
// implementations, possible stack corruption).
h, mountID, err := unix.NameToHandleAt(unix.AT_FDCWD, ".", unix.AT_HANDLE_MNT_ID_UNIQUE)
if err != nil {
skipIfNotSupported(t, "name_to_handle_at", "AT_HANDLE_MNT_ID_UNIQUE", err)
t.Fatalf("NameToHandleAt: %v", err)
}
t.Logf("mountID: %v (%#x), handle: size=%d, type=%d, bytes=%q",
mountID, mountID, h.Size(), h.Type(), h.Bytes())
if uint64(mountID) >= 1<<31 {
t.Errorf("classic NameToHandleAt must return classic mount id, but got %d (%#x) which is larger than 2^31 (%#x)", mountID, mountID, 1<<31)
}
})
}

func TestNameToHandleAt64(t *testing.T) {
skipIfNotSupported := func(t *testing.T, name, flags string, err error) {
if err == unix.EPERM {
t.Skipf("skipping %s test without CAP_DAC_READ_SEARCH", name)
}
if err == unix.ENOSYS {
t.Skipf("%s system call not available", name)
}
if err == unix.EOPNOTSUPP {
t.Skipf("%s not supported on this filesystem", name)
}
if err == unix.EINVAL {
t.Skipf("%s flags %s are not supported", name, flags)
}
}
t.Run("0", func(t *testing.T) {
h, mountID, err := unix.NameToHandleAt64(unix.AT_FDCWD, ".", 0)
if err != nil {
skipIfNotSupported(t, "name_to_handle_at", "<none>", err)
t.Fatalf("NameToHandleAt64: %v", err)
}
t.Logf("mountID: %v (%#x), handle: size=%d, type=%d, bytes=%q",
mountID, mountID, h.Size(), h.Type(), h.Bytes())
if mountID >= 1<<31 {
t.Errorf("classic mount id %d (%#x) must not be larger than 2^31 (%#x)", mountID, mountID, 1<<31)
}
if len(h.Bytes()) != h.Size() {
t.Errorf("file handle bytes should match expected length %d but got %d", h.Size(), len(h.Bytes()))
}
})
t.Run("AT_HANDLE_FID", func(t *testing.T) {
h, mountID, err := unix.NameToHandleAt64(unix.AT_FDCWD, ".", unix.AT_HANDLE_FID)
if err != nil {
// EOPNOTSUPP is not valid for AT_HANDLE_FID.
if err != unix.EOPNOTSUPP {
skipIfNotSupported(t, "name_to_handle_at", "AT_HANDLE_FID", err)
}
t.Fatalf("NameToHandleAt64: %v", err)
}
t.Logf("mountID: %d (%#x) handle: size=%d type=%d bytes=%q",
mountID, mountID, h.Size(), h.Type(), h.Bytes())
if len(h.Bytes()) != h.Size() {
t.Errorf("file handle bytes should match expected length %d but got %d", h.Size(), len(h.Bytes()))
}
if mountID >= 1<<31 {
t.Fatalf("classic mount id %d (%#x) must not be larger than 2^31 (%#x)", mountID, mountID, 1<<31)
}
})
t.Run("AT_HANDLE_MNT_ID_UNIQUE", func(t *testing.T) {
h, mountID, err := unix.NameToHandleAt64(unix.AT_FDCWD, ".", unix.AT_HANDLE_FID|unix.AT_HANDLE_MNT_ID_UNIQUE)
if err != nil {
t.Skip("name_to_handle_at(AT_HANDLE_FID):", err)
}
t.Logf("mountID: %d (%#x) handle: size=%d type=%d bytes=%q",
mountID, mountID, h.Size(), h.Type(), h.Bytes())
if len(h.Bytes()) != h.Size() {
t.Errorf("file handle bytes should match expected length %d but got %d", h.Size(), len(h.Bytes()))
}
if mountID < 1<<31 {
t.Fatalf("unique mount id %d (%#x) must be at least 2^31 (%#x)", mountID, mountID, 1<<31)
}
})
}

func openMountByID(mountID int) (f *os.File, err error) {
Expand Down
4 changes: 4 additions & 0 deletions unix/ztypes_linux.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.