commit
069fff34a5
|
@ -117,6 +117,16 @@ func Convert(
|
||||||
io = to
|
io = to
|
||||||
co = &to.commonOp
|
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:
|
case *bazilfuse.RemoveRequest:
|
||||||
if typed.Dir {
|
if typed.Dir {
|
||||||
to := &RmDirOp{
|
to := &RmDirOp{
|
||||||
|
|
|
@ -358,6 +358,58 @@ func (o *CreateSymlinkOp) toBazilfuseResponse() (bfResp interface{}) {
|
||||||
// Unlinking
|
// 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
|
// 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
|
// count above one, this means the directory inode should be deleted as well
|
||||||
// once the kernel sends ForgetInodeOp.
|
// once the kernel sends ForgetInodeOp.
|
||||||
|
|
|
@ -47,6 +47,7 @@ type FileSystem interface {
|
||||||
MkDir(*fuseops.MkDirOp) error
|
MkDir(*fuseops.MkDirOp) error
|
||||||
CreateFile(*fuseops.CreateFileOp) error
|
CreateFile(*fuseops.CreateFileOp) error
|
||||||
CreateSymlink(*fuseops.CreateSymlinkOp) error
|
CreateSymlink(*fuseops.CreateSymlinkOp) error
|
||||||
|
Rename(*fuseops.RenameOp) error
|
||||||
RmDir(*fuseops.RmDirOp) error
|
RmDir(*fuseops.RmDirOp) error
|
||||||
Unlink(*fuseops.UnlinkOp) error
|
Unlink(*fuseops.UnlinkOp) error
|
||||||
OpenDir(*fuseops.OpenDirOp) error
|
OpenDir(*fuseops.OpenDirOp) error
|
||||||
|
@ -148,6 +149,9 @@ func (s *fileSystemServer) handleOp(op fuseops.Op) {
|
||||||
case *fuseops.CreateSymlinkOp:
|
case *fuseops.CreateSymlinkOp:
|
||||||
err = s.fs.CreateSymlink(typed)
|
err = s.fs.CreateSymlink(typed)
|
||||||
|
|
||||||
|
case *fuseops.RenameOp:
|
||||||
|
err = s.fs.Rename(typed)
|
||||||
|
|
||||||
case *fuseops.RmDirOp:
|
case *fuseops.RmDirOp:
|
||||||
err = s.fs.RmDir(typed)
|
err = s.fs.RmDir(typed)
|
||||||
|
|
||||||
|
|
|
@ -70,6 +70,12 @@ func (fs *NotImplementedFileSystem) CreateSymlink(
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (fs *NotImplementedFileSystem) Rename(
|
||||||
|
op *fuseops.RenameOp) (err error) {
|
||||||
|
err = fuse.ENOSYS
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
func (fs *NotImplementedFileSystem) RmDir(
|
func (fs *NotImplementedFileSystem) RmDir(
|
||||||
op *fuseops.RmDirOp) (err error) {
|
op *fuseops.RmDirOp) (err error) {
|
||||||
err = fuse.ENOSYS
|
err = fuse.ENOSYS
|
||||||
|
|
|
@ -202,10 +202,14 @@ func (in *inode) Len() (n int) {
|
||||||
// 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: in.isDir()
|
// 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)
|
index, ok := in.findChild(name)
|
||||||
if ok {
|
if ok {
|
||||||
id = in.entries[index].Inode
|
id = in.entries[index].Inode
|
||||||
|
typ = in.entries[index].Type
|
||||||
}
|
}
|
||||||
|
|
||||||
return
|
return
|
||||||
|
@ -274,7 +278,7 @@ func (in *inode) RemoveChild(name string) {
|
||||||
// Serve a ReadDir request.
|
// Serve a ReadDir request.
|
||||||
//
|
//
|
||||||
// REQUIRES: in.isDir()
|
// 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() {
|
if !in.isDir() {
|
||||||
panic("ReadDir called on non-directory.")
|
panic("ReadDir called on non-directory.")
|
||||||
}
|
}
|
||||||
|
|
|
@ -192,7 +192,7 @@ func (fs *memFS) LookUpInode(
|
||||||
inode := fs.getInodeOrDie(op.Parent)
|
inode := fs.getInodeOrDie(op.Parent)
|
||||||
|
|
||||||
// Does the directory have an entry with the given name?
|
// Does the directory have an entry with the given name?
|
||||||
childID, ok := inode.LookUpChild(op.Name)
|
childID, _, ok := inode.LookUpChild(op.Name)
|
||||||
if !ok {
|
if !ok {
|
||||||
err = fuse.ENOENT
|
err = fuse.ENOENT
|
||||||
return
|
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
|
// 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(op.Name)
|
||||||
if exists {
|
if exists {
|
||||||
err = fuse.EEXIST
|
err = fuse.EEXIST
|
||||||
return
|
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
|
// 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(op.Name)
|
||||||
if exists {
|
if exists {
|
||||||
err = fuse.EEXIST
|
err = fuse.EEXIST
|
||||||
return
|
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
|
// 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(op.Name)
|
||||||
if exists {
|
if exists {
|
||||||
err = fuse.EEXIST
|
err = fuse.EEXIST
|
||||||
return
|
return
|
||||||
|
@ -396,6 +396,46 @@ func (fs *memFS) CreateSymlink(
|
||||||
return
|
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(
|
func (fs *memFS) RmDir(
|
||||||
op *fuseops.RmDirOp) (err error) {
|
op *fuseops.RmDirOp) (err error) {
|
||||||
fs.mu.Lock()
|
fs.mu.Lock()
|
||||||
|
@ -405,7 +445,7 @@ func (fs *memFS) RmDir(
|
||||||
parent := fs.getInodeOrDie(op.Parent)
|
parent := fs.getInodeOrDie(op.Parent)
|
||||||
|
|
||||||
// Find the child within the parent.
|
// Find the child within the parent.
|
||||||
childID, ok := parent.LookUpChild(op.Name)
|
childID, _, ok := parent.LookUpChild(op.Name)
|
||||||
if !ok {
|
if !ok {
|
||||||
err = fuse.ENOENT
|
err = fuse.ENOENT
|
||||||
return
|
return
|
||||||
|
@ -438,7 +478,7 @@ func (fs *memFS) Unlink(
|
||||||
parent := fs.getInodeOrDie(op.Parent)
|
parent := fs.getInodeOrDie(op.Parent)
|
||||||
|
|
||||||
// Find the child within the parent.
|
// Find the child within the parent.
|
||||||
childID, ok := parent.LookUpChild(op.Name)
|
childID, _, ok := parent.LookUpChild(op.Name)
|
||||||
if !ok {
|
if !ok {
|
||||||
err = fuse.ENOENT
|
err = fuse.ENOENT
|
||||||
return
|
return
|
||||||
|
@ -482,11 +522,7 @@ func (fs *memFS) ReadDir(
|
||||||
inode := fs.getInodeOrDie(op.Inode)
|
inode := fs.getInodeOrDie(op.Inode)
|
||||||
|
|
||||||
// Serve the request.
|
// Serve the request.
|
||||||
op.Data, err = inode.ReadDir(int(op.Offset), op.Size)
|
op.Data = inode.ReadDir(int(op.Offset), op.Size)
|
||||||
if err != nil {
|
|
||||||
err = fmt.Errorf("inode.ReadDir: %v", err)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
|
@ -1271,3 +1271,380 @@ func (t *MemFSTest) MkdirInParallel() {
|
||||||
func (t *MemFSTest) SymlinkInParallel() {
|
func (t *MemFSTest) SymlinkInParallel() {
|
||||||
fusetesting.RunSymlinkInParallelTest(t.Ctx, t.Dir)
|
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")))
|
||||||
|
}
|
||||||
|
|
Loading…
Reference in New Issue