diff --git a/examples/count-changes/main.go b/examples/count-changes/main.go index 53e25c4..3e7abfc 100644 --- a/examples/count-changes/main.go +++ b/examples/count-changes/main.go @@ -27,13 +27,18 @@ type server struct { } func queryPopulation(basedir string, update chan<- int) error { + f, err := os.Open(path) + if err != nil { + return err + } + defer f.Close() + h, err := scoutfs.NewQuery(basedir, scoutfs.ByMSeq(scoutfs.InodesEntry{}, scoutfs.InodesEntry{Major: max64, Minor: max32, Ino: max64})) if err != nil { return fmt.Errorf("scoutfs new handle: %v", err) } - defer h.Close() count := 0 for { diff --git a/examples/ino2path/main.go b/examples/ino2path/main.go new file mode 100644 index 0000000..71c42a8 --- /dev/null +++ b/examples/ino2path/main.go @@ -0,0 +1,37 @@ +// Copyright (c) 2018 Versity Software, Inc. +// +// Use of this source code is governed by a BSD-3-Clause license +// that can be found in the LICENSE file in the root of the source +// tree. + +package main + +import ( + "fmt" + "log" + "os" + "strconv" + + scoutfs "github.com/versity/scoutfs-go" +) + +func main() { + if len(os.Args) != 3 || os.Args[1] == "-h" { + fmt.Fprintln(os.Stderr, "usage:", os.Args[0], " ") + os.Exit(1) + } + + f, err := os.Open(os.Args[1]) + if err != nil { + log.Fatalln("error open mount:", err) + } + defer f.Close() + + u, err := strconv.ParseUint(os.Args[2], 10, 64) + if err != nil { + log.Fatalln("error parsing inode:", err) + } + + s, err := scoutfs.InoToPath(f, u) + fmt.Println(s) +} diff --git a/examples/inodestat/main.go b/examples/inodestat/main.go new file mode 100644 index 0000000..964bcc4 --- /dev/null +++ b/examples/inodestat/main.go @@ -0,0 +1,48 @@ +// Copyright (c) 2018 Versity Software, Inc. +// +// Use of this source code is governed by a BSD-3-Clause license +// that can be found in the LICENSE file in the root of the source +// tree. + +package main + +import ( + "fmt" + "log" + "os" + "strconv" + + scoutfs "github.com/versity/scoutfs-go" +) + +func main() { + if len(os.Args) != 3 || os.Args[1] == "-h" { + fmt.Fprintln(os.Stderr, "usage:", os.Args[0], " ") + os.Exit(1) + } + + dirf, err := os.Open(os.Args[1]) + if err != nil { + log.Fatalln("error open mount:", err) + } + defer dirf.Close() + + u, err := strconv.ParseUint(os.Args[2], 10, 64) + if err != nil { + log.Fatalln("error parsing inode:", err) + } + + f, err := scoutfs.OpenByID(dirf, u, os.O_RDONLY) + if err != nil { + log.Fatalln("error open by id:", err) + } + + fi, err := f.Stat() + if err != nil { + log.Fatalln("error stat:", err) + } + + fmt.Println("Name:", fi.Name()) + fmt.Println("Size:", fi.Size()) + fmt.Println("Mode:", fi.Mode()) +} diff --git a/scoutfs.go b/scoutfs.go index 74b054e..d371080 100644 --- a/scoutfs.go +++ b/scoutfs.go @@ -10,7 +10,6 @@ import ( "bytes" "encoding/binary" "os" - "syscall" "unsafe" ) @@ -38,12 +37,9 @@ type Time struct { // Specify query type with By*() option // (only 1 allowed, last one wins) // and specify batching with WithBatchSize() -func NewQuery(path string, opts ...Option) (*Query, error) { - f, err := os.Open(path) - if err != nil { - return nil, err - } - +// An open file within scoutfs is supplied for ioctls +// (usually just the base mount point directory) +func NewQuery(f *os.File, opts ...Option) *Query { q := &Query{ //default batch size is 128 batch: 128, @@ -54,7 +50,7 @@ func NewQuery(path string, opts ...Option) (*Query, error) { opt(q) } - return q, nil + return q } // Option sets various options for NewWalkHandle @@ -146,11 +142,6 @@ func (q *Query) Next() ([]InodesEntry, error) { return inodes, nil } -// Close queryHandle and cleanup -func (q *Query) Close() { - q.fsfd.Close() -} - // StatMore returns scoutfs specific metadata for path func StatMore(path string) (Stat, error) { f, err := os.Open(path) @@ -169,10 +160,40 @@ func StatMore(path string) (Stat, error) { return s, nil } -func scoutfsctl(fd, cmd, ptr uintptr) (int, error) { - count, _, err := syscall.Syscall(syscall.SYS_IOCTL, fd, cmd, ptr) - if err != 0 { - return 0, err +// InoToPath converts an inode number to a path in the filesystem +// An open file within scoutfs is supplied for ioctls +// (usually just the base mount point directory) +func InoToPath(dirfd *os.File, ino uint64) (string, error) { + var res InoPathResult + ip := InoPath{ + Ino: ino, + ResultPtr: uint64(uintptr(unsafe.Pointer(&res))), + ResultSize: uint16(unsafe.Sizeof(res)), } - return int(count), nil + + _, err := scoutfsctl(dirfd.Fd(), IOCINOPATH, uintptr(unsafe.Pointer(&ip))) + if err != nil { + return "", err + } + + return string(res.Path[:res.PathSize]), nil +} + +// OpenByID will open a file by inode returning a typical *os.File +// An open file within scoutfs is supplied for ioctls +// (usually just the base mount point directory) +// The filename is available through the *os.File and is populated with +// the scoutfs InoToPath +func OpenByID(dirfd *os.File, ino uint64, flags int) (*os.File, error) { + name, err := InoToPath(dirfd, ino) + if err != nil { + return nil, err + } + + fd, err := OpenByHandle(dirfd, ino, flags) + if err != nil { + return nil, err + } + + return os.NewFile(fd, name), nil } diff --git a/scoutfsdefs.go b/scoutfsdefs.go index 2ff5e08..15fa476 100644 --- a/scoutfsdefs.go +++ b/scoutfsdefs.go @@ -16,7 +16,7 @@ const ( //IOCQUERYINODES scoutfs ioctl IOCQUERYINODES = 0x40357301 //IOCINOPATH scoutfs ioctl - IOCINOPATH = 0x401c7302 + IOCINOPATH = 0x40227302 //IOCDATAVERSION scoutfs ioctl IOCDATAVERSION = 0x40087304 //IOCRELEASE scoutfs ioctl @@ -30,6 +30,8 @@ const ( QUERYINODESMETASEQ = '\u0000' //QUERYINODESDATASEQ find inodes by data sequence QUERYINODESDATASEQ = '\u0001' + + pathmax = 1024 ) /* pahole for scoutfs_ioctl_walk_inodes_entry @@ -128,11 +130,11 @@ struct scoutfs_ioctl_ino_path { // InoPath ioctl struct type InoPath struct { - Ino uint64 - DirIno uint64 - DirPos uint64 - ResultPtr uint64 - ResultBytes uint64 + Ino uint64 + DirIno uint64 + DirPos uint64 + ResultPtr uint64 + ResultSize uint16 } /* pahole for scoutfs_ioctl_ino_path_result @@ -149,10 +151,10 @@ struct scoutfs_ioctl_ino_path_result { // InoPathResult ioctl struct type InoPathResult struct { - DirIno uint64 - DirPos uint64 - PathBytes uint16 - ResultBytes uint64 + DirIno uint64 + DirPos uint64 + PathSize uint16 + Path [pathmax]byte } /* pahole for scoutfs_ioctl_release @@ -207,7 +209,7 @@ struct scoutfs_ioctl_stat_more { }; */ -//Stat holds scoutfs specific per file metadata +// Stat holds scoutfs specific per file metadata type Stat struct { ValidBytes uint64 MetaSeq uint64 @@ -216,3 +218,37 @@ type Stat struct { OnlineBlocks uint64 OfflineBlocks uint64 } + +/* pahole for scoutfs_fid +struct scoutfs_fid { + __le64 ino; // 0 8 + __le64 parent_ino; // 8 8 + + // size: 16, cachelines: 1, members: 2 + // last cacheline: 16 bytes +}; +*/ + +// FileID for file by ID operations +type FileID struct { + Ino uint64 + ParentIno uint64 +} + +/* pahole for scoutfs_file_handle +struct scoutfs_file_handle { + unsigned int handle_bytes; // 0 4 + int handle_type; // 4 4 + struct scoutfs_fid fid; // 8 16 + + // size: 24, cachelines: 1, members: 3 + // last cacheline: 24 bytes +}; +*/ + +// FileHandle is the scoutfs specific file handle for open by handle operations +type FileHandle struct { + FidSize uint32 + HandleType int32 + FID FileID +} diff --git a/syscall.go b/syscall.go new file mode 100644 index 0000000..5ab616d --- /dev/null +++ b/syscall.go @@ -0,0 +1,69 @@ +package scoutfs + +import ( + "os" + "syscall" + "unsafe" +) + +const ( + // SYS_OPENBYHANDLEAT linux system call + SYS_OPENBYHANDLEAT = 304 + // FILEID_SCOUTFS for scoutfs file handle + FILEID_SCOUTFS = 0x81 +) + +// OpenByHandle is similar to OpenByID, but returns just the file descriptor +// and does not have the added overhead of getting the filename +// An open file within scoutfs is supplied for ioctls +// (usually just the base mount point directory) +func OpenByHandle(dirfd *os.File, ino uint64, flags int) (uintptr, error) { + h := &FileHandle{ + FidSize: uint32(unsafe.Sizeof(FileID{})), + HandleType: FILEID_SCOUTFS, + FID: FileID{Ino: ino}, + } + return openbyhandleat(dirfd.Fd(), h, flags) +} + +func openbyhandleat(dirfd uintptr, handle *FileHandle, flags int) (uintptr, error) { + fd, _, e1 := syscall.Syscall6(SYS_OPENBYHANDLEAT, dirfd, uintptr(unsafe.Pointer(handle)), uintptr(flags), 0, 0, 0) + var err error + if e1 != 0 { + err = errnoErr(e1) + } + return fd, err +} + +func scoutfsctl(fd, cmd, ptr uintptr) (int, error) { + count, _, e1 := syscall.Syscall(syscall.SYS_IOCTL, fd, cmd, ptr) + var err error + if e1 != 0 { + err = errnoErr(e1) + } + return int(count), err +} + +// Do the interface allocations only once for common +// Errno values. +var ( + errEAGAIN error = syscall.EAGAIN + errEINVAL error = syscall.EINVAL + errENOENT error = syscall.ENOENT +) + +// errnoErr returns common boxed Errno values, to prevent +// allocations at runtime. +func errnoErr(e syscall.Errno) error { + switch e { + case 0: + return nil + case syscall.EAGAIN: + return errEAGAIN + case syscall.EINVAL: + return errEINVAL + case syscall.ENOENT: + return errENOENT + } + return e +}