Added support for mknod(2) and related calls to fuse_mknod.

For googlecloudplatform/gcsfuse#137.
geesefs-0-30-9
Aaron Jacobs 2015-12-15 10:55:29 +11:00
commit 895b8c4155
6 changed files with 191 additions and 17 deletions

View File

@ -134,6 +134,27 @@ func convertInMessage(
Mode: convertFileMode(in.Mode) | os.ModeDir,
}
case fusekernel.OpMknod:
in := (*fusekernel.MknodIn)(inMsg.Consume(fusekernel.MknodInSize(protocol)))
if in == nil {
err = errors.New("Corrupt OpMknod")
return
}
name := inMsg.ConsumeBytes(inMsg.Len())
i := bytes.IndexByte(name, '\x00')
if i < 0 {
err = errors.New("Corrupt OpMknod")
return
}
name = name[:i]
o = &fuseops.MkNodeOp{
Parent: fuseops.InodeID(inMsg.Header().Nodeid),
Name: string(name),
Mode: convertFileMode(in.Mode),
}
case fusekernel.OpCreate:
in := (*fusekernel.CreateIn)(inMsg.Consume(fusekernel.CreateInSize(protocol)))
if in == nil {
@ -491,6 +512,11 @@ func (c *Connection) kernelResponseForOp(
out := (*fusekernel.EntryOut)(m.Grow(size))
convertChildInodeEntry(&o.Entry, out)
case *fuseops.MkNodeOp:
size := fusekernel.EntryOutSize(c.protocol)
out := (*fusekernel.EntryOut)(m.Grow(size))
convertChildInodeEntry(&o.Entry, out)
case *fuseops.CreateFileOp:
eSize := fusekernel.EntryOutSize(c.protocol)

View File

@ -235,6 +235,33 @@ type MkDirOp struct {
Entry ChildInodeEntry
}
// Create a file inode as a child of an existing directory inode. The kernel
// sends this in response to a mknod(2) call. It may also send it in special
// cases such as an NFS export (cf. https://goo.gl/HiLfnK). It is more typical
// to see CreateFileOp, which is received for an open(2) that creates a file.
//
// The Linux kernel appears to verify the name doesn't already exist (mknod
// calls sys_mknodat calls user_path_create calls filename_create, which
// verifies: http://goo.gl/FZpLu5). But osxfuse may not guarantee this, as with
// mkdir(2). And if names may be created outside of the kernel's control, it
// doesn't matter what the kernel does anyway.
//
// Therefore the file system should return EEXIST if the name already exists.
type MkNodeOp struct {
// The ID of parent directory inode within which to create the child.
Parent InodeID
// The name of the child to create, and the mode with which to create it.
Name string
Mode os.FileMode
// Set by the file system: information about the inode that was created.
//
// The lookup count for the inode is implicitly incremented. See notes on
// ForgetInodeOp for more information.
Entry ChildInodeEntry
}
// Create a file inode and open it.
//
// The kernel sends this when the user asks to open a file with the O_CREAT

View File

@ -41,6 +41,7 @@ type FileSystem interface {
SetInodeAttributes(context.Context, *fuseops.SetInodeAttributesOp) error
ForgetInode(context.Context, *fuseops.ForgetInodeOp) error
MkDir(context.Context, *fuseops.MkDirOp) error
MkNode(context.Context, *fuseops.MkNodeOp) error
CreateFile(context.Context, *fuseops.CreateFileOp) error
CreateSymlink(context.Context, *fuseops.CreateSymlinkOp) error
Rename(context.Context, *fuseops.RenameOp) error
@ -138,6 +139,9 @@ func (s *fileSystemServer) handleOp(
case *fuseops.MkDirOp:
err = s.fs.MkDir(ctx, typed)
case *fuseops.MkNodeOp:
err = s.fs.MkNode(ctx, typed)
case *fuseops.CreateFileOp:
err = s.fs.CreateFile(ctx, typed)

View File

@ -71,6 +71,13 @@ func (fs *NotImplementedFileSystem) MkDir(
return
}
func (fs *NotImplementedFileSystem) MkNode(
ctx context.Context,
op *fuseops.MkNodeOp) (err error) {
err = fuse.ENOSYS
return
}
func (fs *NotImplementedFileSystem) CreateFile(
ctx context.Context,
op *fuseops.CreateFileOp) (err error) {

View File

@ -305,28 +305,37 @@ func (fs *memFS) MkDir(
return
}
func (fs *memFS) CreateFile(
func (fs *memFS) MkNode(
ctx context.Context,
op *fuseops.CreateFileOp) (err error) {
op *fuseops.MkNodeOp) (err error) {
fs.mu.Lock()
defer fs.mu.Unlock()
op.Entry, err = fs.createFile(op.Parent, op.Name, op.Mode)
return
}
// LOCKS_REQUIRED(fs.mu)
func (fs *memFS) createFile(
parentID fuseops.InodeID,
name string,
mode os.FileMode) (entry fuseops.ChildInodeEntry, err error) {
// Grab the parent, which we will update shortly.
parent := fs.getInodeOrDie(op.Parent)
parent := fs.getInodeOrDie(parentID)
// Ensure that the name doesn't already exist, so we don't wind up with a
// duplicate.
_, _, exists := parent.LookUpChild(op.Name)
_, _, exists := parent.LookUpChild(name)
if exists {
err = fuse.EEXIST
return
}
// Set up attributes from the child.
// Set up attributes for the child.
now := time.Now()
childAttrs := fuseops.InodeAttributes{
Nlink: 1,
Mode: op.Mode,
Mode: mode,
Atime: now,
Mtime: now,
Ctime: now,
@ -339,19 +348,27 @@ func (fs *memFS) CreateFile(
childID, child := fs.allocateInode(childAttrs)
// Add an entry in the parent.
parent.AddChild(childID, op.Name, fuseutil.DT_File)
parent.AddChild(childID, name, fuseutil.DT_File)
// Fill in the response entry.
op.Entry.Child = childID
op.Entry.Attributes = child.attrs
entry.Child = childID
entry.Attributes = child.attrs
// We don't spontaneously mutate, so the kernel can cache as long as it wants
// (since it also handles invalidation).
op.Entry.AttributesExpiration = time.Now().Add(365 * 24 * time.Hour)
op.Entry.EntryExpiration = op.Entry.EntryExpiration
entry.AttributesExpiration = time.Now().Add(365 * 24 * time.Hour)
entry.EntryExpiration = entry.AttributesExpiration
// We have nothing interesting to put in the Handle field.
return
}
func (fs *memFS) CreateFile(
ctx context.Context,
op *fuseops.CreateFileOp) (err error) {
fs.mu.Lock()
defer fs.mu.Unlock()
op.Entry, err = fs.createFile(op.Parent, op.Name, op.Mode)
return
}

View File

@ -21,6 +21,7 @@ import (
"os"
"os/user"
"path"
"runtime"
"strconv"
"syscall"
"testing"
@ -87,21 +88,25 @@ func applyUmask(m os.FileMode) os.FileMode {
// Boilerplate
////////////////////////////////////////////////////////////////////////
type MemFSTest struct {
type memFSTest struct {
samples.SampleTest
}
func init() { RegisterTestSuite(&MemFSTest{}) }
func (t *MemFSTest) SetUp(ti *TestInfo) {
func (t *memFSTest) SetUp(ti *TestInfo) {
t.Server = memfs.NewMemFS(currentUid(), currentGid())
t.SampleTest.SetUp(ti)
}
////////////////////////////////////////////////////////////////////////
// Test functions
// Basics
////////////////////////////////////////////////////////////////////////
type MemFSTest struct {
memFSTest
}
func init() { RegisterTestSuite(&MemFSTest{}) }
func (t *MemFSTest) ContentsOfEmptyFileSystem() {
entries, err := fusetesting.ReadDirPicky(t.Dir)
@ -1614,3 +1619,91 @@ func (t *MemFSTest) RenameNonExistentFile() {
err = os.Rename(path.Join(t.Dir, "foo"), path.Join(t.Dir, "bar"))
ExpectThat(err, Error(HasSubstr("no such file")))
}
////////////////////////////////////////////////////////////////////////
// Mknod
////////////////////////////////////////////////////////////////////////
type MknodTest struct {
memFSTest
}
func init() { RegisterTestSuite(&MknodTest{}) }
func (t *MknodTest) File() {
// mknod(2) only works for root on OS X.
if runtime.GOOS == "darwin" {
return
}
var err error
p := path.Join(t.Dir, "foo")
// Create
err = syscall.Mknod(p, syscall.S_IFREG|0641, 0)
AssertEq(nil, err)
// Stat
fi, err := os.Stat(p)
AssertEq(nil, err)
ExpectEq(path.Base(p), fi.Name())
ExpectEq(0, fi.Size())
ExpectEq(os.FileMode(0641), fi.Mode())
// Read
contents, err := ioutil.ReadFile(p)
AssertEq(nil, err)
ExpectEq("", string(contents))
}
func (t *MknodTest) Directory() {
// mknod(2) only works for root on OS X.
if runtime.GOOS == "darwin" {
return
}
var err error
p := path.Join(t.Dir, "foo")
// Quoth `man 2 mknod`: "Under Linux, this call cannot be used to create
// directories."
err = syscall.Mknod(p, syscall.S_IFDIR|0700, 0)
ExpectEq(syscall.EPERM, err)
}
func (t *MknodTest) AlreadyExists() {
// mknod(2) only works for root on OS X.
if runtime.GOOS == "darwin" {
return
}
var err error
p := path.Join(t.Dir, "foo")
// Create (first)
err = ioutil.WriteFile(p, []byte("taco"), 0600)
AssertEq(nil, err)
// Create (second)
err = syscall.Mknod(p, syscall.S_IFREG|0600, 0)
ExpectEq(syscall.EEXIST, err)
// Read
contents, err := ioutil.ReadFile(p)
AssertEq(nil, err)
ExpectEq("taco", string(contents))
}
func (t *MknodTest) NonExistentParent() {
// mknod(2) only works for root on OS X.
if runtime.GOOS == "darwin" {
return
}
var err error
p := path.Join(t.Dir, "foo/bar")
err = syscall.Mknod(p, syscall.S_IFREG|0600, 0)
ExpectEq(syscall.ENOENT, err)
}