mirror of
https://github.com/golang/sys.git
synced 2026-02-08 11:46:04 +03: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
|
||||
}
|
||||
|
||||
// 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
|
||||
*/
|
||||
|
||||
@@ -7,10 +7,16 @@
|
||||
package unix_test
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"bytes"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"runtime"
|
||||
"runtime/debug"
|
||||
"strconv"
|
||||
"strings"
|
||||
"testing"
|
||||
"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)
|
||||
}
|
||||
}
|
||||
|
||||
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