Fixed permissions issues in memfs, making the package support doing so.

geesefs-0-30-9
Aaron Jacobs 2015-03-03 11:08:34 +11:00
commit 19f7ef25c9
6 changed files with 244 additions and 31 deletions

View File

@ -159,12 +159,13 @@ type InodeAttributes struct {
Size uint64 Size uint64
Mode os.FileMode Mode os.FileMode
// Time information // Time information. See `man 2 stat` for full details.
Atime time.Time Atime time.Time // Time of last access
Mtime time.Time Mtime time.Time // Time of last modification
Crtime time.Time Ctime time.Time // Time of last modification to inode
Crtime time.Time // Time of creation (OS X only)
// Owner information // Ownership information
Uid uint32 Uid uint32
Gid uint32 Gid uint32
} }
@ -197,6 +198,13 @@ type HandleID uint64
// ReadDirRequest.Offset for details. // ReadDirRequest.Offset for details.
type DirOffset uint64 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 // Information about a child inode within its parent directory. Shared by the
// responses for LookUpInode, MkDir, etc. Consumed by the kernel in order to // responses for LookUpInode, MkDir, etc. Consumed by the kernel in order to
// set up a dcache entry. // set up a dcache entry.
@ -209,7 +217,7 @@ type ChildInodeEntry struct {
// See comments on type GenerationNumber for more. // See comments on type GenerationNumber for more.
Generation GenerationNumber Generation GenerationNumber
// Current ttributes for the child inode. // Current attributes for the child inode.
Attributes InodeAttributes Attributes InodeAttributes
// The FUSE VFS layer in the kernel maintains a cache of file attributes, // The FUSE VFS layer in the kernel maintains a cache of file attributes,
@ -273,15 +281,15 @@ type ChildInodeEntry struct {
//////////////////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////////////////
type InitRequest struct { type InitRequest struct {
// User and group IDs for the process that is mounting the file system. Header RequestHeader
Uid uint32
Gid uint32
} }
type InitResponse struct { type InitResponse struct {
} }
type LookUpInodeRequest struct { type LookUpInodeRequest struct {
Header RequestHeader
// The ID of the directory inode to which the child belongs. // The ID of the directory inode to which the child belongs.
Parent InodeID Parent InodeID
@ -302,6 +310,8 @@ type LookUpInodeResponse struct {
} }
type GetInodeAttributesRequest struct { type GetInodeAttributesRequest struct {
Header RequestHeader
// The inode of interest. // The inode of interest.
Inode InodeID Inode InodeID
} }
@ -314,6 +324,8 @@ type GetInodeAttributesResponse struct {
} }
type ForgetInodeRequest struct { type ForgetInodeRequest struct {
Header RequestHeader
// The inode to be forgotten. The kernel guarantees that the node ID will not // 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 // be used in further calls to the file system (unless it is reissued by the
// file system). // file system).
@ -324,6 +336,8 @@ type ForgetInodeResponse struct {
} }
type MkDirRequest struct { type MkDirRequest struct {
Header RequestHeader
// The ID of parent directory inode within which to create the child. // The ID of parent directory inode within which to create the child.
Parent InodeID Parent InodeID
@ -333,10 +347,22 @@ type MkDirRequest struct {
} }
type MkDirResponse 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 Entry ChildInodeEntry
} }
type OpenDirRequest struct { type OpenDirRequest struct {
Header RequestHeader
// The ID of the inode to be opened. // The ID of the inode to be opened.
Inode InodeID Inode InodeID
@ -360,6 +386,8 @@ type OpenDirResponse struct {
} }
type ReadDirRequest struct { type ReadDirRequest struct {
Header RequestHeader
// The directory inode that we are reading, and the handle previously // The directory inode that we are reading, and the handle previously
// returned by OpenDir when opening that inode. // returned by OpenDir when opening that inode.
Inode InodeID Inode InodeID
@ -449,6 +477,8 @@ type ReadDirResponse struct {
} }
type ReleaseDirHandleRequest struct { type ReleaseDirHandleRequest struct {
Header RequestHeader
// The handle ID to be released. The kernel guarantees that this ID will not // 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 // be used in further calls to the file system (unless it is reissued by the
// file system). // file system).
@ -459,6 +489,8 @@ type ReleaseDirHandleResponse struct {
} }
type OpenFileRequest struct { type OpenFileRequest struct {
Header RequestHeader
// The ID of the inode to be opened. // The ID of the inode to be opened.
Inode InodeID Inode InodeID
@ -482,6 +514,8 @@ type OpenFileResponse struct {
} }
type ReadFileRequest struct { type ReadFileRequest struct {
Header RequestHeader
// The file inode that we are reading, and the handle previously returned by // The file inode that we are reading, and the handle previously returned by
// OpenFile when opening that inode. // OpenFile when opening that inode.
Inode InodeID Inode InodeID
@ -505,6 +539,8 @@ type ReadFileResponse struct {
} }
type ReleaseFileHandleRequest struct { type ReleaseFileHandleRequest struct {
Header RequestHeader
// The handle ID to be released. The kernel guarantees that this ID will not // 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 // be used in further calls to the file system (unless it is reissued by the
// file system). // file system).

View File

@ -135,8 +135,8 @@ func (fs *HelloFS) Init(
req *fuse.InitRequest) ( req *fuse.InitRequest) (
resp *fuse.InitResponse, err error) { resp *fuse.InitResponse, err error) {
resp = &fuse.InitResponse{} resp = &fuse.InitResponse{}
fs.Uid = req.Uid fs.Uid = req.Header.Uid
fs.Gid = req.Gid fs.Gid = req.Header.Gid
return return
} }

View File

@ -57,9 +57,10 @@ func NewMemFS(
inodes: make([]*inode, fuse.RootInodeID+1), 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{ rootAttrs := fuse.InodeAttributes{
Mode: 0777 | os.ModeDir, Mode: 0700 | os.ModeDir,
} }
fs.inodes[fuse.RootInodeID] = newInode(rootAttrs) fs.inodes[fuse.RootInodeID] = newInode(rootAttrs)
@ -70,6 +71,10 @@ func NewMemFS(
return fs return fs
} }
////////////////////////////////////////////////////////////////////////
// Helpers
////////////////////////////////////////////////////////////////////////
func (fs *memFS) checkInvariants() { func (fs *memFS) checkInvariants() {
// Check reserved inodes. // Check reserved inodes.
for i := 0; i < fuse.RootInodeID; i++ { 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 // Find the given inode and return it with its lock held. Panic if it doesn't
// exist. // exist.
// //
@ -171,6 +169,29 @@ func (fs *memFS) allocateInode(
return 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( func (fs *memFS) LookUpInode(
ctx context.Context, ctx context.Context,
req *fuse.LookUpInodeRequest) (resp *fuse.LookUpInodeResponse, err error) { req *fuse.LookUpInodeRequest) (resp *fuse.LookUpInodeResponse, err error) {
@ -241,15 +262,20 @@ func (fs *memFS) MkDir(
parent := fs.getInodeForModifyingOrDie(req.Parent) parent := fs.getInodeForModifyingOrDie(req.Parent)
defer parent.mu.Unlock() 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() now := fs.clock.Now()
childAttrs := fuse.InodeAttributes{ childAttrs := fuse.InodeAttributes{
Mode: req.Mode, Mode: req.Mode,
Atime: now, Atime: now,
Mtime: now, Mtime: now,
Ctime: now,
Crtime: now, Crtime: now,
Uid: req.Header.Uid,
Gid: req.Header.Gid,
} }
// Allocate a child.
childID, child := fs.allocateInode(childAttrs) childID, child := fs.allocateInode(childAttrs)
defer child.mu.Unlock() defer child.mu.Unlock()

View File

@ -59,6 +59,10 @@ type inode struct {
contents []byte // GUARDED_BY(mu) contents []byte // GUARDED_BY(mu)
} }
////////////////////////////////////////////////////////////////////////
// Helpers
////////////////////////////////////////////////////////////////////////
func newInode(attrs fuse.InodeAttributes) (in *inode) { func newInode(attrs fuse.InodeAttributes) (in *inode) {
in = &inode{ in = &inode{
dir: (attrs.Mode&os.ModeDir != 0), 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. // Find an entry for the given child name and return its inode ID.
// //
// REQUIRES: inode.dir // REQUIRES: inode.dir

View File

@ -7,8 +7,11 @@ import (
"io/ioutil" "io/ioutil"
"log" "log"
"os" "os"
"os/user"
"path" "path"
"strconv"
"strings" "strings"
"syscall"
"testing" "testing"
"time" "time"
@ -22,6 +25,42 @@ import (
func TestMemFS(t *testing.T) { RunTests(t) } 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 // Boilerplate
//////////////////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////////////////
@ -94,9 +133,12 @@ func (t *MemFSTest) ContentsOfEmptyFileSystem() {
ExpectThat(entries, ElementsAre()) ExpectThat(entries, ElementsAre())
} }
func (t *MemFSTest) Mkdir() { func (t *MemFSTest) Mkdir_OneLevel() {
var err error var err error
var fi os.FileInfo var fi os.FileInfo
var stat *syscall.Stat_t
var entries []os.FileInfo
dirName := path.Join(t.mfs.Dir(), "dir") dirName := path.Join(t.mfs.Dir(), "dir")
// Create a directory within the root. // Create a directory within the root.
@ -104,11 +146,12 @@ func (t *MemFSTest) Mkdir() {
err = os.Mkdir(dirName, 0754) err = os.Mkdir(dirName, 0754)
AssertEq(nil, err) AssertEq(nil, err)
// Simulate time proceeding. // Simulate time advancing.
t.clock.AdvanceTime(time.Second) t.clock.AdvanceTime(time.Second)
// Stat the directory. // Stat the directory.
fi, err = os.Stat(dirName) fi, err = os.Stat(dirName)
stat = fi.Sys().(*syscall.Stat_t)
AssertEq(nil, err) AssertEq(nil, err)
ExpectEq("dir", fi.Name()) ExpectEq("dir", fi.Name())
@ -117,11 +160,83 @@ func (t *MemFSTest) Mkdir() {
ExpectEq(0, fi.ModTime().Sub(createTime)) ExpectEq(0, fi.ModTime().Sub(createTime))
ExpectTrue(fi.IsDir()) 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. // Read the directory.
entries, err := ioutil.ReadDir(dirName) entries, err = ioutil.ReadDir(dirName)
AssertEq(nil, err) AssertEq(nil, err)
ExpectThat(entries, ElementsAre()) 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() { func (t *MemFSTest) Mkdir_AlreadyExists() {
@ -166,6 +281,20 @@ func (t *MemFSTest) Mkdir_IntermediateIsNonExistent() {
ExpectThat(err, Error(HasSubstr("no such file or directory"))) 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() { func (t *MemFSTest) CreateNewFile_InRoot() {
AssertTrue(false, "TODO") AssertTrue(false, "TODO")
} }

View File

@ -43,6 +43,13 @@ func convertChildInodeEntry(
out.EntryValid = in.EntryExpiration.Sub(clock.Now()) 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 // Serve the fuse connection by repeatedly reading requests from the supplied
// FUSE connection, responding as dictated by the file system. Return when the // FUSE connection, responding as dictated by the file system. Return when the
// connection is closed or an unexpected error occurs. // connection is closed or an unexpected error occurs.
@ -82,8 +89,7 @@ func (s *server) handleFuseRequest(fuseReq bazilfuse.Request) {
case *bazilfuse.InitRequest: case *bazilfuse.InitRequest:
// Convert the request. // Convert the request.
req := &InitRequest{ req := &InitRequest{
Uid: typed.Header.Uid, Header: convertHeader(typed.Header),
Gid: typed.Header.Gid,
} }
// Call the file system. // Call the file system.
@ -110,6 +116,7 @@ func (s *server) handleFuseRequest(fuseReq bazilfuse.Request) {
case *bazilfuse.LookupRequest: case *bazilfuse.LookupRequest:
// Convert the request. // Convert the request.
req := &LookUpInodeRequest{ req := &LookUpInodeRequest{
Header: convertHeader(typed.Header),
Parent: InodeID(typed.Header.Node), Parent: InodeID(typed.Header.Node),
Name: typed.Name, Name: typed.Name,
} }
@ -132,7 +139,8 @@ func (s *server) handleFuseRequest(fuseReq bazilfuse.Request) {
case *bazilfuse.GetattrRequest: case *bazilfuse.GetattrRequest:
// Convert the request. // Convert the request.
req := &GetInodeAttributesRequest{ req := &GetInodeAttributesRequest{
Inode: InodeID(typed.Header.Node), Header: convertHeader(typed.Header),
Inode: InodeID(typed.Header.Node),
} }
// Call the file system. // Call the file system.
@ -155,6 +163,7 @@ func (s *server) handleFuseRequest(fuseReq bazilfuse.Request) {
case *bazilfuse.MkdirRequest: case *bazilfuse.MkdirRequest:
// Convert the request. // Convert the request.
req := &MkDirRequest{ req := &MkDirRequest{
Header: convertHeader(typed.Header),
Parent: InodeID(typed.Header.Node), Parent: InodeID(typed.Header.Node),
Name: typed.Name, Name: typed.Name,
Mode: typed.Mode, Mode: typed.Mode,
@ -180,8 +189,9 @@ func (s *server) handleFuseRequest(fuseReq bazilfuse.Request) {
if typed.Dir { if typed.Dir {
// Convert the request. // Convert the request.
req := &OpenDirRequest{ req := &OpenDirRequest{
Inode: InodeID(typed.Header.Node), Header: convertHeader(typed.Header),
Flags: typed.Flags, Inode: InodeID(typed.Header.Node),
Flags: typed.Flags,
} }
// Call the file system. // Call the file system.
@ -202,8 +212,9 @@ func (s *server) handleFuseRequest(fuseReq bazilfuse.Request) {
} else { } else {
// Convert the request. // Convert the request.
req := &OpenFileRequest{ req := &OpenFileRequest{
Inode: InodeID(typed.Header.Node), Header: convertHeader(typed.Header),
Flags: typed.Flags, Inode: InodeID(typed.Header.Node),
Flags: typed.Flags,
} }
// Call the file system. // Call the file system.
@ -228,6 +239,7 @@ func (s *server) handleFuseRequest(fuseReq bazilfuse.Request) {
if typed.Dir { if typed.Dir {
// Convert the request. // Convert the request.
req := &ReadDirRequest{ req := &ReadDirRequest{
Header: convertHeader(typed.Header),
Inode: InodeID(typed.Header.Node), Inode: InodeID(typed.Header.Node),
Handle: HandleID(typed.Handle), Handle: HandleID(typed.Handle),
Offset: DirOffset(typed.Offset), Offset: DirOffset(typed.Offset),
@ -252,6 +264,7 @@ func (s *server) handleFuseRequest(fuseReq bazilfuse.Request) {
} else { } else {
// Convert the request. // Convert the request.
req := &ReadFileRequest{ req := &ReadFileRequest{
Header: convertHeader(typed.Header),
Inode: InodeID(typed.Header.Node), Inode: InodeID(typed.Header.Node),
Handle: HandleID(typed.Handle), Handle: HandleID(typed.Handle),
Offset: typed.Offset, Offset: typed.Offset,
@ -288,6 +301,7 @@ func convertAttributes(inode InodeID, attr InodeAttributes) bazilfuse.Attr {
Mode: attr.Mode, Mode: attr.Mode,
Atime: attr.Atime, Atime: attr.Atime,
Mtime: attr.Mtime, Mtime: attr.Mtime,
Ctime: attr.Ctime,
Crtime: attr.Crtime, Crtime: attr.Crtime,
Uid: attr.Uid, Uid: attr.Uid,
Gid: attr.Gid, Gid: attr.Gid,