Added a rename op.

For GoogleCloudPlatform/gcsfuse#81.
geesefs-0-30-9
Aaron Jacobs 2015-06-25 22:15:31 +10:00
commit 069fff34a5
7 changed files with 502 additions and 13 deletions

View File

@ -117,6 +117,16 @@ func Convert(
io = to
co = &to.commonOp
case *bazilfuse.RenameRequest:
to := &RenameOp{
OldParent: InodeID(typed.Header.Node),
OldName: typed.OldName,
NewParent: InodeID(typed.NewDir),
NewName: typed.NewName,
}
io = to
co = &to.commonOp
case *bazilfuse.RemoveRequest:
if typed.Dir {
to := &RmDirOp{

View File

@ -358,6 +358,58 @@ func (o *CreateSymlinkOp) toBazilfuseResponse() (bfResp interface{}) {
// Unlinking
////////////////////////////////////////////////////////////////////////
// Rename a file or directory, given the IDs of the original parent directory
// and the new one (which may be the same).
//
// In Linux, this is called by vfs_rename (https://goo.gl/eERItT), which is
// called by sys_renameat2 (https://goo.gl/fCC9qC).
//
// The kernel takes care of ensuring that the source and destination are not
// identical (in which case it does nothing), that the rename is not across
// file system boundaries, and that the destination doesn't already exist with
// the wrong type. Some subtleties that the file system must care about:
//
// * If the new name is an existing directory, the file system must ensure it
// is empty before replacing it, returning ENOTEMPTY otherwise. (This is
// per the posix spec: http://goo.gl/4XtT79)
//
// * The rename must be atomic from the point of view of an observer of the
// new name. That is, if the new name already exists, there must be no
// point at which it doesn't exist.
//
// * It is okay for the new name to be modified before the old name is
// removed; these need not be atomic. In fact, the Linux man page
// explicitly says this is likely (cf. https://goo.gl/Y1wVZc).
//
// * Linux bends over backwards (https://goo.gl/pLDn3r) to ensure that
// neither the old nor the new parent can be concurrently modified. But
// it's not clear whether OS X does this, and in any case it doesn't matter
// for file systems that may be modified remotely. Therefore a careful file
// system implementor should probably ensure if possible that the unlink
// step in the "link new name, unlink old name" process doesn't unlink a
// different inode than the one that was linked to the new name. Still,
// posix and the man pages are imprecise about the actual semantics of a
// rename if it's not atomic, so it is probably not disastrous to be loose
// about this.
//
type RenameOp struct {
commonOp
// The old parent directory, and the name of the entry within it to be
// relocated.
OldParent InodeID
OldName string
// The new parent directory, and the name of the entry to be created or
// overwritten within it.
NewParent InodeID
NewName string
}
func (o *RenameOp) toBazilfuseResponse() (bfResp interface{}) {
return
}
// Unlink a directory from its parent. Because directories cannot have a link
// count above one, this means the directory inode should be deleted as well
// once the kernel sends ForgetInodeOp.

View File

@ -47,6 +47,7 @@ type FileSystem interface {
MkDir(*fuseops.MkDirOp) error
CreateFile(*fuseops.CreateFileOp) error
CreateSymlink(*fuseops.CreateSymlinkOp) error
Rename(*fuseops.RenameOp) error
RmDir(*fuseops.RmDirOp) error
Unlink(*fuseops.UnlinkOp) error
OpenDir(*fuseops.OpenDirOp) error
@ -148,6 +149,9 @@ func (s *fileSystemServer) handleOp(op fuseops.Op) {
case *fuseops.CreateSymlinkOp:
err = s.fs.CreateSymlink(typed)
case *fuseops.RenameOp:
err = s.fs.Rename(typed)
case *fuseops.RmDirOp:
err = s.fs.RmDir(typed)

View File

@ -70,6 +70,12 @@ func (fs *NotImplementedFileSystem) CreateSymlink(
return
}
func (fs *NotImplementedFileSystem) Rename(
op *fuseops.RenameOp) (err error) {
err = fuse.ENOSYS
return
}
func (fs *NotImplementedFileSystem) RmDir(
op *fuseops.RmDirOp) (err error) {
err = fuse.ENOSYS

View File

@ -202,10 +202,14 @@ func (in *inode) Len() (n int) {
// Find an entry for the given child name and return its inode ID.
//
// REQUIRES: in.isDir()
func (in *inode) LookUpChild(name string) (id fuseops.InodeID, ok bool) {
func (in *inode) LookUpChild(name string) (
id fuseops.InodeID,
typ fuseutil.DirentType,
ok bool) {
index, ok := in.findChild(name)
if ok {
id = in.entries[index].Inode
typ = in.entries[index].Type
}
return
@ -274,7 +278,7 @@ func (in *inode) RemoveChild(name string) {
// Serve a ReadDir request.
//
// REQUIRES: in.isDir()
func (in *inode) ReadDir(offset int, size int) (data []byte, err error) {
func (in *inode) ReadDir(offset int, size int) (data []byte) {
if !in.isDir() {
panic("ReadDir called on non-directory.")
}

View File

@ -192,7 +192,7 @@ func (fs *memFS) LookUpInode(
inode := fs.getInodeOrDie(op.Parent)
// Does the directory have an entry with the given name?
childID, ok := inode.LookUpChild(op.Name)
childID, _, ok := inode.LookUpChild(op.Name)
if !ok {
err = fuse.ENOENT
return
@ -262,7 +262,7 @@ func (fs *memFS) MkDir(
// 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(op.Name)
if exists {
err = fuse.EEXIST
return
@ -305,7 +305,7 @@ func (fs *memFS) CreateFile(
// 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(op.Name)
if exists {
err = fuse.EEXIST
return
@ -355,7 +355,7 @@ func (fs *memFS) CreateSymlink(
// 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(op.Name)
if exists {
err = fuse.EEXIST
return
@ -396,6 +396,46 @@ func (fs *memFS) CreateSymlink(
return
}
func (fs *memFS) Rename(
op *fuseops.RenameOp) (err error) {
fs.mu.Lock()
defer fs.mu.Unlock()
// Ask the old parent for the child's inode ID and type.
oldParent := fs.getInodeOrDie(op.OldParent)
childID, childType, ok := oldParent.LookUpChild(op.OldName)
if !ok {
err = fuse.ENOENT
return
}
// If the new name exists already in the new parent, make sure it's not a
// non-empty directory, then delete it.
newParent := fs.getInodeOrDie(op.NewParent)
existingID, _, ok := newParent.LookUpChild(op.NewName)
if ok {
existing := fs.getInodeOrDie(existingID)
if existing.isDir() && len(existing.ReadDir(0, 1024)) > 0 {
err = fuse.ENOTEMPTY
return
}
newParent.RemoveChild(op.NewName)
}
// Link the new name.
newParent.AddChild(
childID,
op.NewName,
childType)
// Finally, remove the old name from the old parent.
oldParent.RemoveChild(op.OldName)
return
}
func (fs *memFS) RmDir(
op *fuseops.RmDirOp) (err error) {
fs.mu.Lock()
@ -405,7 +445,7 @@ func (fs *memFS) RmDir(
parent := fs.getInodeOrDie(op.Parent)
// Find the child within the parent.
childID, ok := parent.LookUpChild(op.Name)
childID, _, ok := parent.LookUpChild(op.Name)
if !ok {
err = fuse.ENOENT
return
@ -438,7 +478,7 @@ func (fs *memFS) Unlink(
parent := fs.getInodeOrDie(op.Parent)
// Find the child within the parent.
childID, ok := parent.LookUpChild(op.Name)
childID, _, ok := parent.LookUpChild(op.Name)
if !ok {
err = fuse.ENOENT
return
@ -482,11 +522,7 @@ func (fs *memFS) ReadDir(
inode := fs.getInodeOrDie(op.Inode)
// Serve the request.
op.Data, err = inode.ReadDir(int(op.Offset), op.Size)
if err != nil {
err = fmt.Errorf("inode.ReadDir: %v", err)
return
}
op.Data = inode.ReadDir(int(op.Offset), op.Size)
return
}

View File

@ -1271,3 +1271,380 @@ func (t *MemFSTest) MkdirInParallel() {
func (t *MemFSTest) SymlinkInParallel() {
fusetesting.RunSymlinkInParallelTest(t.Ctx, t.Dir)
}
func (t *MemFSTest) RenameWithinDir_File() {
var err error
// Create a parent directory.
parentPath := path.Join(t.Dir, "parent")
err = os.Mkdir(parentPath, 0700)
AssertEq(nil, err)
// And a file within it.
oldPath := path.Join(parentPath, "foo")
err = ioutil.WriteFile(oldPath, []byte("taco"), 0400)
AssertEq(nil, err)
// Rename it.
newPath := path.Join(parentPath, "bar")
err = os.Rename(oldPath, newPath)
AssertEq(nil, err)
// The old name shouldn't work.
_, err = os.Stat(oldPath)
ExpectTrue(os.IsNotExist(err), "err: %v", err)
_, err = ioutil.ReadFile(oldPath)
ExpectTrue(os.IsNotExist(err), "err: %v", err)
// The new name should.
fi, err := os.Stat(newPath)
AssertEq(nil, err)
ExpectEq(len("taco"), fi.Size())
ExpectEq(os.FileMode(0400), fi.Mode())
contents, err := ioutil.ReadFile(newPath)
AssertEq(nil, err)
ExpectEq("taco", string(contents))
// There should only be the new entry in the directory.
entries, err := fusetesting.ReadDirPicky(parentPath)
AssertEq(nil, err)
AssertEq(1, len(entries))
fi = entries[0]
ExpectEq(path.Base(newPath), fi.Name())
ExpectEq(os.FileMode(0400), fi.Mode())
}
func (t *MemFSTest) RenameWithinDir_Directory() {
var err error
// Create a parent directory.
parentPath := path.Join(t.Dir, "parent")
err = os.Mkdir(parentPath, 0700)
AssertEq(nil, err)
// And a non-empty directory within it.
oldPath := path.Join(parentPath, "foo")
err = os.MkdirAll(path.Join(oldPath, "child"), 0700)
AssertEq(nil, err)
// Rename it.
newPath := path.Join(parentPath, "bar")
err = os.Rename(oldPath, newPath)
AssertEq(nil, err)
// The old name shouldn't work.
_, err = os.Stat(oldPath)
ExpectTrue(os.IsNotExist(err), "err: %v", err)
// The new name should.
fi, err := os.Stat(newPath)
AssertEq(nil, err)
ExpectEq(os.FileMode(0700)|os.ModeDir, fi.Mode())
// There should only be the new entry in the parent.
entries, err := fusetesting.ReadDirPicky(parentPath)
AssertEq(nil, err)
AssertEq(1, len(entries))
fi = entries[0]
ExpectEq(path.Base(newPath), fi.Name())
ExpectEq(os.FileMode(0700)|os.ModeDir, fi.Mode())
// And the child should still be present.
entries, err = fusetesting.ReadDirPicky(newPath)
AssertEq(nil, err)
AssertEq(1, len(entries))
fi = entries[0]
ExpectEq("child", fi.Name())
ExpectEq(os.FileMode(0700)|os.ModeDir, fi.Mode())
}
func (t *MemFSTest) RenameWithinDir_SameName() {
var err error
// Create a parent directory.
parentPath := path.Join(t.Dir, "parent")
err = os.Mkdir(parentPath, 0700)
AssertEq(nil, err)
// And a file within it.
filePath := path.Join(parentPath, "foo")
err = ioutil.WriteFile(filePath, []byte("taco"), 0400)
AssertEq(nil, err)
// Attempt to rename it.
err = os.Rename(filePath, filePath)
AssertEq(nil, err)
// The file should still exist.
contents, err := ioutil.ReadFile(filePath)
AssertEq(nil, err)
ExpectEq("taco", string(contents))
// There should only be the one entry in the directory.
entries, err := fusetesting.ReadDirPicky(parentPath)
AssertEq(nil, err)
AssertEq(1, len(entries))
fi := entries[0]
ExpectEq(path.Base(filePath), fi.Name())
ExpectEq(os.FileMode(0400), fi.Mode())
}
func (t *MemFSTest) RenameAcrossDirs_File() {
var err error
// Create two parent directories.
oldParentPath := path.Join(t.Dir, "old")
newParentPath := path.Join(t.Dir, "new")
err = os.Mkdir(oldParentPath, 0700)
AssertEq(nil, err)
err = os.Mkdir(newParentPath, 0700)
AssertEq(nil, err)
// And a file within the first.
oldPath := path.Join(oldParentPath, "foo")
err = ioutil.WriteFile(oldPath, []byte("taco"), 0400)
AssertEq(nil, err)
// Rename it.
newPath := path.Join(newParentPath, "bar")
err = os.Rename(oldPath, newPath)
AssertEq(nil, err)
// The old name shouldn't work.
_, err = os.Stat(oldPath)
ExpectTrue(os.IsNotExist(err), "err: %v", err)
_, err = ioutil.ReadFile(oldPath)
ExpectTrue(os.IsNotExist(err), "err: %v", err)
// The new name should.
fi, err := os.Stat(newPath)
AssertEq(nil, err)
ExpectEq(len("taco"), fi.Size())
ExpectEq(os.FileMode(0400), fi.Mode())
contents, err := ioutil.ReadFile(newPath)
AssertEq(nil, err)
ExpectEq("taco", string(contents))
// Check the old parent.
entries, err := fusetesting.ReadDirPicky(oldParentPath)
AssertEq(nil, err)
AssertEq(0, len(entries))
// And the new one.
entries, err = fusetesting.ReadDirPicky(newParentPath)
AssertEq(nil, err)
AssertEq(1, len(entries))
fi = entries[0]
ExpectEq(path.Base(newPath), fi.Name())
ExpectEq(os.FileMode(0400), fi.Mode())
}
func (t *MemFSTest) RenameAcrossDirs_Directory() {
var err error
// Create two parent directories.
oldParentPath := path.Join(t.Dir, "old")
newParentPath := path.Join(t.Dir, "new")
err = os.Mkdir(oldParentPath, 0700)
AssertEq(nil, err)
err = os.Mkdir(newParentPath, 0700)
AssertEq(nil, err)
// And a non-empty directory within the first.
oldPath := path.Join(oldParentPath, "foo")
err = os.MkdirAll(path.Join(oldPath, "child"), 0700)
AssertEq(nil, err)
// Rename it.
newPath := path.Join(newParentPath, "bar")
err = os.Rename(oldPath, newPath)
AssertEq(nil, err)
// The old name shouldn't work.
_, err = os.Stat(oldPath)
ExpectTrue(os.IsNotExist(err), "err: %v", err)
// The new name should.
fi, err := os.Stat(newPath)
AssertEq(nil, err)
ExpectEq(os.FileMode(0700)|os.ModeDir, fi.Mode())
// And the child should still be present.
entries, err := fusetesting.ReadDirPicky(newPath)
AssertEq(nil, err)
AssertEq(1, len(entries))
fi = entries[0]
ExpectEq("child", fi.Name())
ExpectEq(os.FileMode(0700)|os.ModeDir, fi.Mode())
// Check the old parent.
entries, err = fusetesting.ReadDirPicky(oldParentPath)
AssertEq(nil, err)
AssertEq(0, len(entries))
// And the new one.
entries, err = fusetesting.ReadDirPicky(newParentPath)
AssertEq(nil, err)
AssertEq(1, len(entries))
fi = entries[0]
ExpectEq(path.Base(newPath), fi.Name())
ExpectEq(os.FileMode(0700)|os.ModeDir, fi.Mode())
}
func (t *MemFSTest) RenameOutOfFileSystem() {
var err error
// Create a file.
oldPath := path.Join(t.Dir, "foo")
err = ioutil.WriteFile(oldPath, []byte("taco"), 0400)
AssertEq(nil, err)
// Attempt to move it out of the file system.
tempDir, err := ioutil.TempDir("", "memfs_test")
AssertEq(nil, err)
defer os.RemoveAll(tempDir)
err = os.Rename(oldPath, path.Join(tempDir, "bar"))
ExpectThat(err, Error(HasSubstr("cross-device")))
}
func (t *MemFSTest) RenameIntoFileSystem() {
var err error
// Create a file outside of our file system.
f, err := ioutil.TempFile("", "memfs_test")
AssertEq(nil, err)
defer f.Close()
oldPath := f.Name()
defer os.Remove(oldPath)
// Attempt to move it into the file system.
err = os.Rename(oldPath, path.Join(t.Dir, "bar"))
ExpectThat(err, Error(HasSubstr("cross-device")))
}
func (t *MemFSTest) RenameOverExistingFile() {
var err error
// Create two files.
oldPath := path.Join(t.Dir, "foo")
err = ioutil.WriteFile(oldPath, []byte("taco"), 0400)
AssertEq(nil, err)
newPath := path.Join(t.Dir, "bar")
err = ioutil.WriteFile(newPath, []byte("burrito"), 0600)
AssertEq(nil, err)
// Rename one over the other.
err = os.Rename(oldPath, newPath)
AssertEq(nil, err)
// Check the file contents.
contents, err := ioutil.ReadFile(newPath)
AssertEq(nil, err)
ExpectEq("taco", string(contents))
// And the parent listing.
entries, err := fusetesting.ReadDirPicky(t.Dir)
AssertEq(nil, err)
AssertEq(1, len(entries))
fi := entries[0]
ExpectEq(path.Base(newPath), fi.Name())
ExpectEq(os.FileMode(0400), fi.Mode())
ExpectEq(len("taco"), fi.Size())
}
func (t *MemFSTest) RenameOverExistingDirectory() {
var err error
// Create two directories, the first non-empty.
oldPath := path.Join(t.Dir, "foo")
err = os.MkdirAll(path.Join(oldPath, "child"), 0700)
AssertEq(nil, err)
newPath := path.Join(t.Dir, "bar")
err = os.Mkdir(newPath, 0600)
AssertEq(nil, err)
// Renaming over the non-empty one shouldn't work.
err = os.Rename(newPath, oldPath)
ExpectThat(err, Error(HasSubstr("not empty")))
// But the other way around should.
err = os.Rename(oldPath, newPath)
AssertEq(nil, err)
// Check the parent listing.
entries, err := fusetesting.ReadDirPicky(t.Dir)
AssertEq(nil, err)
AssertEq(1, len(entries))
fi := entries[0]
ExpectEq(path.Base(newPath), fi.Name())
ExpectEq(os.FileMode(0700)|os.ModeDir, fi.Mode())
// And the directory itself.
entries, err = fusetesting.ReadDirPicky(newPath)
AssertEq(nil, err)
AssertEq(1, len(entries))
fi = entries[0]
ExpectEq("child", fi.Name())
}
func (t *MemFSTest) RenameOverExisting_WrongType() {
var err error
// Create a file and a directory.
filePath := path.Join(t.Dir, "foo")
err = ioutil.WriteFile(filePath, []byte("taco"), 0400)
AssertEq(nil, err)
dirPath := path.Join(t.Dir, "bar")
err = os.Mkdir(dirPath, 0700)
AssertEq(nil, err)
// Renaming one over the other shouldn't work.
err = os.Rename(filePath, dirPath)
ExpectThat(err, Error(HasSubstr("is a directory")))
err = os.Rename(dirPath, filePath)
ExpectThat(err, Error(HasSubstr("not a directory")))
}
func (t *MemFSTest) RenameNonExistentFile() {
var err error
err = os.Rename(path.Join(t.Dir, "foo"), path.Join(t.Dir, "bar"))
ExpectThat(err, Error(HasSubstr("no such file")))
}