diff --git a/file_system.go b/file_system.go index 083902c..cf6276c 100644 --- a/file_system.go +++ b/file_system.go @@ -159,12 +159,13 @@ type InodeAttributes struct { Size uint64 Mode os.FileMode - // Time information - Atime time.Time - Mtime time.Time - Crtime time.Time + // Time information. See `man 2 stat` for full details. + Atime time.Time // Time of last access + Mtime time.Time // Time of last modification + Ctime time.Time // Time of last modification to inode + Crtime time.Time // Time of creation (OS X only) - // Owner information + // Ownership information Uid uint32 Gid uint32 } @@ -197,6 +198,13 @@ type HandleID uint64 // ReadDirRequest.Offset for details. type DirOffset uint64 +// A header that is included with every request. +type RequestHeader struct { + // Credentials information for the process making the request. + Uid uint32 + Gid uint32 +} + // Information about a child inode within its parent directory. Shared by the // responses for LookUpInode, MkDir, etc. Consumed by the kernel in order to // set up a dcache entry. @@ -209,7 +217,7 @@ type ChildInodeEntry struct { // See comments on type GenerationNumber for more. Generation GenerationNumber - // Current ttributes for the child inode. + // Current attributes for the child inode. Attributes InodeAttributes // The FUSE VFS layer in the kernel maintains a cache of file attributes, @@ -273,15 +281,15 @@ type ChildInodeEntry struct { //////////////////////////////////////////////////////////////////////// type InitRequest struct { - // User and group IDs for the process that is mounting the file system. - Uid uint32 - Gid uint32 + Header RequestHeader } type InitResponse struct { } type LookUpInodeRequest struct { + Header RequestHeader + // The ID of the directory inode to which the child belongs. Parent InodeID @@ -302,6 +310,8 @@ type LookUpInodeResponse struct { } type GetInodeAttributesRequest struct { + Header RequestHeader + // The inode of interest. Inode InodeID } @@ -314,6 +324,8 @@ type GetInodeAttributesResponse struct { } type ForgetInodeRequest struct { + Header RequestHeader + // The inode to be forgotten. The kernel guarantees that the node ID will not // be used in further calls to the file system (unless it is reissued by the // file system). @@ -324,6 +336,8 @@ type ForgetInodeResponse struct { } type MkDirRequest struct { + Header RequestHeader + // The ID of parent directory inode within which to create the child. Parent InodeID @@ -333,10 +347,22 @@ type MkDirRequest struct { } type MkDirResponse struct { + // Information about the inode that was created. + // + // The file system is responsible for initializing and recording (where + // supported) attributes like time information, ownership information, etc. + // + // Ownership information in particular must be set to something reasonable or + // by default root will own everything and unprivileged users won't be able + // to do anything useful. In traditional file systems in the kernel, the + // function inode_init_owner (http://goo.gl/5qavg8) contains the + // standards-compliant logic for this. Entry ChildInodeEntry } type OpenDirRequest struct { + Header RequestHeader + // The ID of the inode to be opened. Inode InodeID @@ -360,6 +386,8 @@ type OpenDirResponse struct { } type ReadDirRequest struct { + Header RequestHeader + // The directory inode that we are reading, and the handle previously // returned by OpenDir when opening that inode. Inode InodeID @@ -449,6 +477,8 @@ type ReadDirResponse struct { } type ReleaseDirHandleRequest struct { + Header RequestHeader + // The handle ID to be released. The kernel guarantees that this ID will not // be used in further calls to the file system (unless it is reissued by the // file system). @@ -459,6 +489,8 @@ type ReleaseDirHandleResponse struct { } type OpenFileRequest struct { + Header RequestHeader + // The ID of the inode to be opened. Inode InodeID @@ -482,6 +514,8 @@ type OpenFileResponse struct { } type ReadFileRequest struct { + Header RequestHeader + // The file inode that we are reading, and the handle previously returned by // OpenFile when opening that inode. Inode InodeID @@ -505,6 +539,8 @@ type ReadFileResponse struct { } type ReleaseFileHandleRequest struct { + Header RequestHeader + // The handle ID to be released. The kernel guarantees that this ID will not // be used in further calls to the file system (unless it is reissued by the // file system). diff --git a/samples/hellofs/hello_fs.go b/samples/hellofs/hello_fs.go index cb5dd9f..9791763 100644 --- a/samples/hellofs/hello_fs.go +++ b/samples/hellofs/hello_fs.go @@ -135,8 +135,8 @@ func (fs *HelloFS) Init( req *fuse.InitRequest) ( resp *fuse.InitResponse, err error) { resp = &fuse.InitResponse{} - fs.Uid = req.Uid - fs.Gid = req.Gid + fs.Uid = req.Header.Uid + fs.Gid = req.Header.Gid return } diff --git a/samples/memfs/fs.go b/samples/memfs/fs.go index ec927b8..27906ac 100644 --- a/samples/memfs/fs.go +++ b/samples/memfs/fs.go @@ -57,9 +57,10 @@ func NewMemFS( inodes: make([]*inode, fuse.RootInodeID+1), } - // Set up the root inode. + // Set up the root inode. Its ownership information will later be modified in + // Init. rootAttrs := fuse.InodeAttributes{ - Mode: 0777 | os.ModeDir, + Mode: 0700 | os.ModeDir, } fs.inodes[fuse.RootInodeID] = newInode(rootAttrs) @@ -70,6 +71,10 @@ func NewMemFS( return fs } +//////////////////////////////////////////////////////////////////////// +// Helpers +//////////////////////////////////////////////////////////////////////// + func (fs *memFS) checkInvariants() { // Check reserved inodes. for i := 0; i < fuse.RootInodeID; i++ { @@ -109,13 +114,6 @@ func (fs *memFS) checkInvariants() { } } -func (fs *memFS) Init( - ctx context.Context, - req *fuse.InitRequest) (resp *fuse.InitResponse, err error) { - resp = &fuse.InitResponse{} - return -} - // Find the given inode and return it with its lock held. Panic if it doesn't // exist. // @@ -171,6 +169,29 @@ func (fs *memFS) allocateInode( return } +//////////////////////////////////////////////////////////////////////// +// FileSystem methods +//////////////////////////////////////////////////////////////////////// + +func (fs *memFS) Init( + ctx context.Context, + req *fuse.InitRequest) (resp *fuse.InitResponse, err error) { + resp = &fuse.InitResponse{} + + fs.mu.RLock() + defer fs.mu.RUnlock() + + // Update the root inode's ownership information to match the credentials of + // the mounting process. + root := fs.getInodeForModifyingOrDie(fuse.RootInodeID) + defer root.mu.Unlock() + + root.attributes.Uid = req.Header.Uid + root.attributes.Gid = req.Header.Gid + + return +} + func (fs *memFS) LookUpInode( ctx context.Context, req *fuse.LookUpInodeRequest) (resp *fuse.LookUpInodeResponse, err error) { @@ -241,15 +262,20 @@ func (fs *memFS) MkDir( parent := fs.getInodeForModifyingOrDie(req.Parent) defer parent.mu.Unlock() - // Allocate a child. + // Set up attributes from the child, using the credientials of the calling + // process as owner (matching inode_init_owner, cf. http://goo.gl/5qavg8). now := fs.clock.Now() childAttrs := fuse.InodeAttributes{ Mode: req.Mode, Atime: now, Mtime: now, + Ctime: now, Crtime: now, + Uid: req.Header.Uid, + Gid: req.Header.Gid, } + // Allocate a child. childID, child := fs.allocateInode(childAttrs) defer child.mu.Unlock() diff --git a/samples/memfs/inode.go b/samples/memfs/inode.go index a22bdc7..f17306e 100644 --- a/samples/memfs/inode.go +++ b/samples/memfs/inode.go @@ -59,6 +59,10 @@ type inode struct { contents []byte // GUARDED_BY(mu) } +//////////////////////////////////////////////////////////////////////// +// Helpers +//////////////////////////////////////////////////////////////////////// + func newInode(attrs fuse.InodeAttributes) (in *inode) { in = &inode{ dir: (attrs.Mode&os.ModeDir != 0), @@ -112,6 +116,10 @@ func (inode *inode) checkInvariants() { } } +//////////////////////////////////////////////////////////////////////// +// Public methods +//////////////////////////////////////////////////////////////////////// + // Find an entry for the given child name and return its inode ID. // // REQUIRES: inode.dir diff --git a/samples/memfs/memfs_test.go b/samples/memfs/memfs_test.go index 9e2dfd8..683413f 100644 --- a/samples/memfs/memfs_test.go +++ b/samples/memfs/memfs_test.go @@ -7,8 +7,11 @@ import ( "io/ioutil" "log" "os" + "os/user" "path" + "strconv" "strings" + "syscall" "testing" "time" @@ -22,6 +25,42 @@ import ( func TestMemFS(t *testing.T) { RunTests(t) } +//////////////////////////////////////////////////////////////////////// +// Helpers +//////////////////////////////////////////////////////////////////////// + +func currentUid() uint32 { + user, err := user.Current() + if err != nil { + panic(err) + } + + uid, err := strconv.ParseUint(user.Uid, 10, 32) + if err != nil { + panic(err) + } + + return uint32(uid) +} + +func currentGid() uint32 { + user, err := user.Current() + if err != nil { + panic(err) + } + + gid, err := strconv.ParseUint(user.Gid, 10, 32) + if err != nil { + panic(err) + } + + return uint32(gid) +} + +func timespecToTime(ts syscall.Timespec) time.Time { + return time.Unix(ts.Sec, ts.Nsec) +} + //////////////////////////////////////////////////////////////////////// // Boilerplate //////////////////////////////////////////////////////////////////////// @@ -94,9 +133,12 @@ func (t *MemFSTest) ContentsOfEmptyFileSystem() { ExpectThat(entries, ElementsAre()) } -func (t *MemFSTest) Mkdir() { +func (t *MemFSTest) Mkdir_OneLevel() { var err error var fi os.FileInfo + var stat *syscall.Stat_t + var entries []os.FileInfo + dirName := path.Join(t.mfs.Dir(), "dir") // Create a directory within the root. @@ -104,11 +146,12 @@ func (t *MemFSTest) Mkdir() { err = os.Mkdir(dirName, 0754) AssertEq(nil, err) - // Simulate time proceeding. + // Simulate time advancing. t.clock.AdvanceTime(time.Second) // Stat the directory. fi, err = os.Stat(dirName) + stat = fi.Sys().(*syscall.Stat_t) AssertEq(nil, err) ExpectEq("dir", fi.Name()) @@ -117,11 +160,83 @@ func (t *MemFSTest) Mkdir() { ExpectEq(0, fi.ModTime().Sub(createTime)) ExpectTrue(fi.IsDir()) + ExpectEq(1, stat.Nlink) + ExpectEq(currentUid(), stat.Uid) + ExpectEq(currentGid(), stat.Gid) + ExpectEq(0, stat.Size) + ExpectEq(0, timespecToTime(stat.Atimespec).Sub(createTime)) + ExpectEq(0, timespecToTime(stat.Mtimespec).Sub(createTime)) + ExpectEq(0, timespecToTime(stat.Ctimespec).Sub(createTime)) + // Read the directory. - entries, err := ioutil.ReadDir(dirName) + entries, err = ioutil.ReadDir(dirName) AssertEq(nil, err) ExpectThat(entries, ElementsAre()) + + // Read the root. + entries, err = ioutil.ReadDir(t.mfs.Dir()) + + AssertEq(nil, err) + AssertEq(1, len(entries)) + + fi = entries[0] + ExpectEq("dir", fi.Name()) + ExpectEq(os.ModeDir|0754, fi.Mode()) +} + +func (t *MemFSTest) Mkdir_TwoLevels() { + var err error + var fi os.FileInfo + var stat *syscall.Stat_t + var entries []os.FileInfo + + // Create a directory within the root. + err = os.Mkdir(path.Join(t.mfs.Dir(), "parent"), 0700) + AssertEq(nil, err) + + // Create a child of that directory. + createTime := t.clock.Now() + err = os.Mkdir(path.Join(t.mfs.Dir(), "parent/dir"), 0754) + AssertEq(nil, err) + + // Simulate time advancing. + t.clock.AdvanceTime(time.Second) + + // Stat the directory. + fi, err = os.Stat(path.Join(t.mfs.Dir(), "parent/dir")) + stat = fi.Sys().(*syscall.Stat_t) + + AssertEq(nil, err) + ExpectEq("dir", fi.Name()) + ExpectEq(0, fi.Size()) + ExpectEq(os.ModeDir|0754, fi.Mode()) + ExpectEq(0, fi.ModTime().Sub(createTime)) + ExpectTrue(fi.IsDir()) + + ExpectEq(1, stat.Nlink) + ExpectEq(currentUid(), stat.Uid) + ExpectEq(currentGid(), stat.Gid) + ExpectEq(0, stat.Size) + ExpectEq(0, timespecToTime(stat.Atimespec).Sub(createTime)) + ExpectEq(0, timespecToTime(stat.Mtimespec).Sub(createTime)) + ExpectEq(0, timespecToTime(stat.Ctimespec).Sub(createTime)) + + // Read the directory. + entries, err = ioutil.ReadDir(path.Join(t.mfs.Dir(), "parent/dir")) + + AssertEq(nil, err) + ExpectThat(entries, ElementsAre()) + + // Read the parent. + entries, err = ioutil.ReadDir(path.Join(t.mfs.Dir(), "parent")) + + AssertEq(nil, err) + AssertEq(1, len(entries)) + + fi = entries[0] + ExpectEq("dir", fi.Name()) + ExpectEq(os.ModeDir|0754, fi.Mode()) } func (t *MemFSTest) Mkdir_AlreadyExists() { @@ -166,6 +281,20 @@ func (t *MemFSTest) Mkdir_IntermediateIsNonExistent() { ExpectThat(err, Error(HasSubstr("no such file or directory"))) } +func (t *MemFSTest) Mkdir_PermissionDenied() { + var err error + + // Create a directory within the root without write permissions. + err = os.Mkdir(path.Join(t.mfs.Dir(), "parent"), 0500) + AssertEq(nil, err) + + // Attempt to create a child of that directory. + err = os.Mkdir(path.Join(t.mfs.Dir(), "parent/dir"), 0754) + + AssertNe(nil, err) + ExpectThat(err, Error(HasSubstr("permission denied"))) +} + func (t *MemFSTest) CreateNewFile_InRoot() { AssertTrue(false, "TODO") } diff --git a/server.go b/server.go index 242c3e2..762795e 100644 --- a/server.go +++ b/server.go @@ -43,6 +43,13 @@ func convertChildInodeEntry( out.EntryValid = in.EntryExpiration.Sub(clock.Now()) } +func convertHeader( + in bazilfuse.Header) (out RequestHeader) { + out.Uid = in.Uid + out.Gid = in.Gid + return +} + // Serve the fuse connection by repeatedly reading requests from the supplied // FUSE connection, responding as dictated by the file system. Return when the // connection is closed or an unexpected error occurs. @@ -82,8 +89,7 @@ func (s *server) handleFuseRequest(fuseReq bazilfuse.Request) { case *bazilfuse.InitRequest: // Convert the request. req := &InitRequest{ - Uid: typed.Header.Uid, - Gid: typed.Header.Gid, + Header: convertHeader(typed.Header), } // Call the file system. @@ -110,6 +116,7 @@ func (s *server) handleFuseRequest(fuseReq bazilfuse.Request) { case *bazilfuse.LookupRequest: // Convert the request. req := &LookUpInodeRequest{ + Header: convertHeader(typed.Header), Parent: InodeID(typed.Header.Node), Name: typed.Name, } @@ -132,7 +139,8 @@ func (s *server) handleFuseRequest(fuseReq bazilfuse.Request) { case *bazilfuse.GetattrRequest: // Convert the request. req := &GetInodeAttributesRequest{ - Inode: InodeID(typed.Header.Node), + Header: convertHeader(typed.Header), + Inode: InodeID(typed.Header.Node), } // Call the file system. @@ -155,6 +163,7 @@ func (s *server) handleFuseRequest(fuseReq bazilfuse.Request) { case *bazilfuse.MkdirRequest: // Convert the request. req := &MkDirRequest{ + Header: convertHeader(typed.Header), Parent: InodeID(typed.Header.Node), Name: typed.Name, Mode: typed.Mode, @@ -180,8 +189,9 @@ func (s *server) handleFuseRequest(fuseReq bazilfuse.Request) { if typed.Dir { // Convert the request. req := &OpenDirRequest{ - Inode: InodeID(typed.Header.Node), - Flags: typed.Flags, + Header: convertHeader(typed.Header), + Inode: InodeID(typed.Header.Node), + Flags: typed.Flags, } // Call the file system. @@ -202,8 +212,9 @@ func (s *server) handleFuseRequest(fuseReq bazilfuse.Request) { } else { // Convert the request. req := &OpenFileRequest{ - Inode: InodeID(typed.Header.Node), - Flags: typed.Flags, + Header: convertHeader(typed.Header), + Inode: InodeID(typed.Header.Node), + Flags: typed.Flags, } // Call the file system. @@ -228,6 +239,7 @@ func (s *server) handleFuseRequest(fuseReq bazilfuse.Request) { if typed.Dir { // Convert the request. req := &ReadDirRequest{ + Header: convertHeader(typed.Header), Inode: InodeID(typed.Header.Node), Handle: HandleID(typed.Handle), Offset: DirOffset(typed.Offset), @@ -252,6 +264,7 @@ func (s *server) handleFuseRequest(fuseReq bazilfuse.Request) { } else { // Convert the request. req := &ReadFileRequest{ + Header: convertHeader(typed.Header), Inode: InodeID(typed.Header.Node), Handle: HandleID(typed.Handle), Offset: typed.Offset, @@ -288,6 +301,7 @@ func convertAttributes(inode InodeID, attr InodeAttributes) bazilfuse.Attr { Mode: attr.Mode, Atime: attr.Atime, Mtime: attr.Mtime, + Ctime: attr.Ctime, Crtime: attr.Crtime, Uid: attr.Uid, Gid: attr.Gid,