Added support for mknod(2) and related calls to fuse_mknod.
For googlecloudplatform/gcsfuse#137.geesefs-0-30-9
commit
895b8c4155
|
@ -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)
|
||||
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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)
|
||||
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue