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,
|
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:
|
case fusekernel.OpCreate:
|
||||||
in := (*fusekernel.CreateIn)(inMsg.Consume(fusekernel.CreateInSize(protocol)))
|
in := (*fusekernel.CreateIn)(inMsg.Consume(fusekernel.CreateInSize(protocol)))
|
||||||
if in == nil {
|
if in == nil {
|
||||||
|
@ -491,6 +512,11 @@ func (c *Connection) kernelResponseForOp(
|
||||||
out := (*fusekernel.EntryOut)(m.Grow(size))
|
out := (*fusekernel.EntryOut)(m.Grow(size))
|
||||||
convertChildInodeEntry(&o.Entry, out)
|
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:
|
case *fuseops.CreateFileOp:
|
||||||
eSize := fusekernel.EntryOutSize(c.protocol)
|
eSize := fusekernel.EntryOutSize(c.protocol)
|
||||||
|
|
||||||
|
|
|
@ -235,6 +235,33 @@ type MkDirOp struct {
|
||||||
Entry ChildInodeEntry
|
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.
|
// Create a file inode and open it.
|
||||||
//
|
//
|
||||||
// The kernel sends this when the user asks to open a file with the O_CREAT
|
// 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
|
SetInodeAttributes(context.Context, *fuseops.SetInodeAttributesOp) error
|
||||||
ForgetInode(context.Context, *fuseops.ForgetInodeOp) error
|
ForgetInode(context.Context, *fuseops.ForgetInodeOp) error
|
||||||
MkDir(context.Context, *fuseops.MkDirOp) error
|
MkDir(context.Context, *fuseops.MkDirOp) error
|
||||||
|
MkNode(context.Context, *fuseops.MkNodeOp) error
|
||||||
CreateFile(context.Context, *fuseops.CreateFileOp) error
|
CreateFile(context.Context, *fuseops.CreateFileOp) error
|
||||||
CreateSymlink(context.Context, *fuseops.CreateSymlinkOp) error
|
CreateSymlink(context.Context, *fuseops.CreateSymlinkOp) error
|
||||||
Rename(context.Context, *fuseops.RenameOp) error
|
Rename(context.Context, *fuseops.RenameOp) error
|
||||||
|
@ -138,6 +139,9 @@ func (s *fileSystemServer) handleOp(
|
||||||
case *fuseops.MkDirOp:
|
case *fuseops.MkDirOp:
|
||||||
err = s.fs.MkDir(ctx, typed)
|
err = s.fs.MkDir(ctx, typed)
|
||||||
|
|
||||||
|
case *fuseops.MkNodeOp:
|
||||||
|
err = s.fs.MkNode(ctx, typed)
|
||||||
|
|
||||||
case *fuseops.CreateFileOp:
|
case *fuseops.CreateFileOp:
|
||||||
err = s.fs.CreateFile(ctx, typed)
|
err = s.fs.CreateFile(ctx, typed)
|
||||||
|
|
||||||
|
|
|
@ -71,6 +71,13 @@ func (fs *NotImplementedFileSystem) MkDir(
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (fs *NotImplementedFileSystem) MkNode(
|
||||||
|
ctx context.Context,
|
||||||
|
op *fuseops.MkNodeOp) (err error) {
|
||||||
|
err = fuse.ENOSYS
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
func (fs *NotImplementedFileSystem) CreateFile(
|
func (fs *NotImplementedFileSystem) CreateFile(
|
||||||
ctx context.Context,
|
ctx context.Context,
|
||||||
op *fuseops.CreateFileOp) (err error) {
|
op *fuseops.CreateFileOp) (err error) {
|
||||||
|
|
|
@ -305,28 +305,37 @@ func (fs *memFS) MkDir(
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
func (fs *memFS) CreateFile(
|
func (fs *memFS) MkNode(
|
||||||
ctx context.Context,
|
ctx context.Context,
|
||||||
op *fuseops.CreateFileOp) (err error) {
|
op *fuseops.MkNodeOp) (err error) {
|
||||||
fs.mu.Lock()
|
fs.mu.Lock()
|
||||||
defer fs.mu.Unlock()
|
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.
|
// 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
|
// Ensure that the name doesn't already exist, so we don't wind up with a
|
||||||
// duplicate.
|
// duplicate.
|
||||||
_, _, exists := parent.LookUpChild(op.Name)
|
_, _, exists := parent.LookUpChild(name)
|
||||||
if exists {
|
if exists {
|
||||||
err = fuse.EEXIST
|
err = fuse.EEXIST
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// Set up attributes from the child.
|
// Set up attributes for the child.
|
||||||
now := time.Now()
|
now := time.Now()
|
||||||
childAttrs := fuseops.InodeAttributes{
|
childAttrs := fuseops.InodeAttributes{
|
||||||
Nlink: 1,
|
Nlink: 1,
|
||||||
Mode: op.Mode,
|
Mode: mode,
|
||||||
Atime: now,
|
Atime: now,
|
||||||
Mtime: now,
|
Mtime: now,
|
||||||
Ctime: now,
|
Ctime: now,
|
||||||
|
@ -339,19 +348,27 @@ func (fs *memFS) CreateFile(
|
||||||
childID, child := fs.allocateInode(childAttrs)
|
childID, child := fs.allocateInode(childAttrs)
|
||||||
|
|
||||||
// Add an entry in the parent.
|
// 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.
|
// Fill in the response entry.
|
||||||
op.Entry.Child = childID
|
entry.Child = childID
|
||||||
op.Entry.Attributes = child.attrs
|
entry.Attributes = child.attrs
|
||||||
|
|
||||||
// We don't spontaneously mutate, so the kernel can cache as long as it wants
|
// We don't spontaneously mutate, so the kernel can cache as long as it wants
|
||||||
// (since it also handles invalidation).
|
// (since it also handles invalidation).
|
||||||
op.Entry.AttributesExpiration = time.Now().Add(365 * 24 * time.Hour)
|
entry.AttributesExpiration = time.Now().Add(365 * 24 * time.Hour)
|
||||||
op.Entry.EntryExpiration = op.Entry.EntryExpiration
|
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
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -21,6 +21,7 @@ import (
|
||||||
"os"
|
"os"
|
||||||
"os/user"
|
"os/user"
|
||||||
"path"
|
"path"
|
||||||
|
"runtime"
|
||||||
"strconv"
|
"strconv"
|
||||||
"syscall"
|
"syscall"
|
||||||
"testing"
|
"testing"
|
||||||
|
@ -87,21 +88,25 @@ func applyUmask(m os.FileMode) os.FileMode {
|
||||||
// Boilerplate
|
// Boilerplate
|
||||||
////////////////////////////////////////////////////////////////////////
|
////////////////////////////////////////////////////////////////////////
|
||||||
|
|
||||||
type MemFSTest struct {
|
type memFSTest struct {
|
||||||
samples.SampleTest
|
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.Server = memfs.NewMemFS(currentUid(), currentGid())
|
||||||
t.SampleTest.SetUp(ti)
|
t.SampleTest.SetUp(ti)
|
||||||
}
|
}
|
||||||
|
|
||||||
////////////////////////////////////////////////////////////////////////
|
////////////////////////////////////////////////////////////////////////
|
||||||
// Test functions
|
// Basics
|
||||||
////////////////////////////////////////////////////////////////////////
|
////////////////////////////////////////////////////////////////////////
|
||||||
|
|
||||||
|
type MemFSTest struct {
|
||||||
|
memFSTest
|
||||||
|
}
|
||||||
|
|
||||||
|
func init() { RegisterTestSuite(&MemFSTest{}) }
|
||||||
|
|
||||||
func (t *MemFSTest) ContentsOfEmptyFileSystem() {
|
func (t *MemFSTest) ContentsOfEmptyFileSystem() {
|
||||||
entries, err := fusetesting.ReadDirPicky(t.Dir)
|
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"))
|
err = os.Rename(path.Join(t.Dir, "foo"), path.Join(t.Dir, "bar"))
|
||||||
ExpectThat(err, Error(HasSubstr("no such file")))
|
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