mirror of
https://github.com/rwinkhart/sys.git
synced 2026-09-02 23:27:31 -04:00
unix: add FileHandle, NewFileHandle, NameToHandleAt, OpenByHandleAt
This adds wrappers around name_to_handle_at and open_by_handle_at.
Requires root (or CAP_DAC_READ_SEARCH, rather) to run tests, which at
least some of our builders have.
bradfitz@go:~/src/golang.org/x/sys/unix$ go test -c && sudo ./unix.test -test.run=OpenBy -test.v=true
=== RUN TestOpenByHandleAt
=== RUN TestOpenByHandleAt/clone=false
=== RUN TestOpenByHandleAt/clone=true
--- PASS: TestOpenByHandleAt (0.00s)
syscall_linux_test.go:546: mountID: 22, handle: size=8, type=1, bytes="\x9e\x1e\b\x00~\x8c\xe5\x9d"
--- PASS: TestOpenByHandleAt/clone=false (0.00s)
syscall_linux_test.go:568: opened fd 3
--- PASS: TestOpenByHandleAt/clone=true (0.00s)
syscall_linux_test.go:568: opened fd 3
PASS
Fixes golang/go#30537
Change-Id: Ia48a8faab2fee665d88a16d81a3a0c1504b129ce
Reviewed-on: https://go-review.googlesource.com/c/sys/+/173357
Run-TryBot: Brad Fitzpatrick <bradfitz@golang.org>
TryBot-Result: Gobot Gobot <gobot@golang.org>
Reviewed-by: Ian Lance Taylor <iant@golang.org>
This commit is contained in:
@@ -1675,6 +1675,69 @@ type fileHandle struct {
|
|||||||
Type int32
|
Type int32
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// FileHandle represents the C struct file_handle used by
|
||||||
|
// name_to_handle_at (see NameToHandleAt) and open_by_handle_at (see
|
||||||
|
// OpenByHandleAt).
|
||||||
|
type FileHandle struct {
|
||||||
|
*fileHandle
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewFileHandle constructs a FileHandle.
|
||||||
|
func NewFileHandle(handleType int32, handle []byte) FileHandle {
|
||||||
|
const hdrSize = unsafe.Sizeof(fileHandle{})
|
||||||
|
buf := make([]byte, hdrSize+uintptr(len(handle)))
|
||||||
|
copy(buf[hdrSize:], handle)
|
||||||
|
fh := (*fileHandle)(unsafe.Pointer(&buf[0]))
|
||||||
|
fh.Type = handleType
|
||||||
|
fh.Bytes = uint32(len(handle))
|
||||||
|
return FileHandle{fh}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (fh *FileHandle) Size() int { return int(fh.fileHandle.Bytes) }
|
||||||
|
func (fh *FileHandle) Type() int32 { return fh.fileHandle.Type }
|
||||||
|
func (fh *FileHandle) Bytes() []byte {
|
||||||
|
n := fh.Size()
|
||||||
|
if n == 0 {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
return (*[1 << 30]byte)(unsafe.Pointer(uintptr(unsafe.Pointer(&fh.fileHandle.Type)) + 4))[:n:n]
|
||||||
|
}
|
||||||
|
|
||||||
|
// NameToHandleAt wraps the name_to_handle_at system call; it obtains
|
||||||
|
// a handle for a path name.
|
||||||
|
func NameToHandleAt(dirfd int, path string, flags int) (handle FileHandle, mountID int, err error) {
|
||||||
|
var mid _C_int
|
||||||
|
// Try first with a small buffer, assuming the handle will
|
||||||
|
// only be 32 bytes.
|
||||||
|
size := uint32(32 + unsafe.Sizeof(fileHandle{}))
|
||||||
|
didResize := false
|
||||||
|
for {
|
||||||
|
buf := make([]byte, size)
|
||||||
|
fh := (*fileHandle)(unsafe.Pointer(&buf[0]))
|
||||||
|
fh.Bytes = size - uint32(unsafe.Sizeof(fileHandle{}))
|
||||||
|
err = nameToHandleAt(dirfd, path, fh, &mid, flags)
|
||||||
|
if err == EOVERFLOW {
|
||||||
|
if didResize {
|
||||||
|
// We shouldn't need to resize more than once
|
||||||
|
return
|
||||||
|
}
|
||||||
|
didResize = true
|
||||||
|
size = fh.Bytes + uint32(unsafe.Sizeof(fileHandle{}))
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
return FileHandle{fh}, int(mid), nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// OpenByHandleAt wraps the open_by_handle_at system call; it opens a
|
||||||
|
// file via a handle as previously returned by NameToHandleAt.
|
||||||
|
func OpenByHandleAt(mountFD int, handle FileHandle, flags int) (fd int, err error) {
|
||||||
|
return openByHandleAt(mountFD, handle.fileHandle, flags)
|
||||||
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Unimplemented
|
* Unimplemented
|
||||||
*/
|
*/
|
||||||
|
|||||||
@@ -7,10 +7,16 @@
|
|||||||
package unix_test
|
package unix_test
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"bufio"
|
||||||
|
"bytes"
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
"io/ioutil"
|
"io/ioutil"
|
||||||
"os"
|
"os"
|
||||||
"runtime"
|
"runtime"
|
||||||
"runtime/debug"
|
"runtime/debug"
|
||||||
|
"strconv"
|
||||||
|
"strings"
|
||||||
"testing"
|
"testing"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
@@ -531,3 +537,72 @@ func TestClockNanosleep(t *testing.T) {
|
|||||||
t.Errorf("ClockNanosleep(CLOCK_THREAD_CPUTIME_ID, 0, %#v, nil) = %v, want EINVAL or EOPNOTSUPP", &rel, err)
|
t.Errorf("ClockNanosleep(CLOCK_THREAD_CPUTIME_ID, 0, %#v, nil) = %v, want EINVAL or EOPNOTSUPP", &rel, err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestOpenByHandleAt(t *testing.T) {
|
||||||
|
h, mountID, err := unix.NameToHandleAt(unix.AT_FDCWD, "syscall_linux_test.go", 0)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("NameToHandleAt: %v", err)
|
||||||
|
}
|
||||||
|
t.Logf("mountID: %v, handle: size=%d, type=%d, bytes=%q", mountID,
|
||||||
|
h.Size(), h.Type(), h.Bytes())
|
||||||
|
mount, err := openMountByID(mountID)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("openMountByID: %v", err)
|
||||||
|
}
|
||||||
|
defer mount.Close()
|
||||||
|
|
||||||
|
for _, clone := range []bool{false, true} {
|
||||||
|
t.Run("clone="+strconv.FormatBool(clone), func(t *testing.T) {
|
||||||
|
if clone {
|
||||||
|
h = unix.NewFileHandle(h.Type(), h.Bytes())
|
||||||
|
}
|
||||||
|
fd, err := unix.OpenByHandleAt(int(mount.Fd()), h, unix.O_RDONLY)
|
||||||
|
if err == unix.EPERM {
|
||||||
|
t.Skipf("skipping OpenByHandleAt without CAP_DAC_READ_SEARCH")
|
||||||
|
}
|
||||||
|
if err == unix.ENOSYS {
|
||||||
|
t.Skipf("name_to_handle_at system call not available")
|
||||||
|
}
|
||||||
|
if err == unix.EOPNOTSUPP {
|
||||||
|
t.Skipf("name_to_handle_at not supported on this filesystem")
|
||||||
|
}
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("OpenByHandleAt: %v", err)
|
||||||
|
}
|
||||||
|
defer unix.Close(fd)
|
||||||
|
|
||||||
|
t.Logf("opened fd %v", fd)
|
||||||
|
f := os.NewFile(uintptr(fd), "")
|
||||||
|
slurp, err := ioutil.ReadAll(f)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
const substr = "Some substring for a test."
|
||||||
|
if !strings.Contains(string(slurp), substr) {
|
||||||
|
t.Errorf("didn't find substring %q in opened file; read %d bytes", substr, len(slurp))
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func openMountByID(mountID int) (f *os.File, err error) {
|
||||||
|
mi, err := os.Open("/proc/self/mountinfo")
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
defer mi.Close()
|
||||||
|
bs := bufio.NewScanner(mi)
|
||||||
|
wantPrefix := []byte(fmt.Sprintf("%v ", mountID))
|
||||||
|
for bs.Scan() {
|
||||||
|
if !bytes.HasPrefix(bs.Bytes(), wantPrefix) {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
fields := strings.Fields(bs.Text())
|
||||||
|
dev := fields[4]
|
||||||
|
return os.Open(dev)
|
||||||
|
}
|
||||||
|
if err := bs.Err(); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return nil, errors.New("mountID not found")
|
||||||
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user