Add tests for hard link
parent
1b4a34cc7b
commit
4ee295e334
|
@ -365,3 +365,71 @@ func RunSymlinkInParallelTest(
|
||||||
AssertEq(nil, err)
|
AssertEq(nil, err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Run an ogletest test that checks expectations for parallel calls to
|
||||||
|
// link(2).
|
||||||
|
func RunHardlinkInParallelTest(
|
||||||
|
ctx context.Context,
|
||||||
|
dir string) {
|
||||||
|
// Ensure that we get parallelism for this test.
|
||||||
|
defer runtime.GOMAXPROCS(runtime.GOMAXPROCS(runtime.NumCPU()))
|
||||||
|
|
||||||
|
// Create a file.
|
||||||
|
originalFile := path.Join(dir, "original_file")
|
||||||
|
const contents = "Hello\x00world"
|
||||||
|
|
||||||
|
err := ioutil.WriteFile(originalFile, []byte(contents), 0444)
|
||||||
|
AssertEq(nil, err)
|
||||||
|
|
||||||
|
// Try for awhile to see if anything breaks.
|
||||||
|
const duration = 500 * time.Millisecond
|
||||||
|
startTime := time.Now()
|
||||||
|
for time.Since(startTime) < duration {
|
||||||
|
filename := path.Join(dir, "foo")
|
||||||
|
|
||||||
|
// Set up a function that creates the symlink, ignoring EEXIST errors.
|
||||||
|
worker := func(id byte) (err error) {
|
||||||
|
err = os.Link(originalFile, filename)
|
||||||
|
|
||||||
|
if os.IsExist(err) {
|
||||||
|
err = nil
|
||||||
|
}
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
err = fmt.Errorf("Worker %d: Link: %v", id, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Run several workers in parallel.
|
||||||
|
const numWorkers = 16
|
||||||
|
b := syncutil.NewBundle(ctx)
|
||||||
|
for i := 0; i < numWorkers; i++ {
|
||||||
|
id := byte(i)
|
||||||
|
b.Add(func(ctx context.Context) (err error) {
|
||||||
|
err = worker(id)
|
||||||
|
return
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
err := b.Join()
|
||||||
|
AssertEq(nil, err)
|
||||||
|
|
||||||
|
// The symlink should have been created, once.
|
||||||
|
entries, err := ReadDirPicky(dir)
|
||||||
|
AssertEq(nil, err)
|
||||||
|
AssertEq(2, len(entries))
|
||||||
|
AssertEq("foo", entries[0].Name())
|
||||||
|
AssertEq("original_file", entries[1].Name())
|
||||||
|
|
||||||
|
// Remove the link.
|
||||||
|
err = os.Remove(filename)
|
||||||
|
AssertEq(nil, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Clean up the original file at the end.
|
||||||
|
err = os.Remove(originalFile)
|
||||||
|
AssertEq(nil, err)
|
||||||
|
}
|
||||||
|
|
|
@ -424,6 +424,46 @@ func (fs *memFS) CreateSymlink(
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (fs *memFS) CreateLink(
|
||||||
|
ctx context.Context,
|
||||||
|
op *fuseops.CreateLinkOp) (err error) {
|
||||||
|
fs.mu.Lock()
|
||||||
|
defer fs.mu.Unlock()
|
||||||
|
|
||||||
|
// Grab the parent, which we will update shortly.
|
||||||
|
parent := fs.getInodeOrDie(op.Parent)
|
||||||
|
|
||||||
|
// Ensure that the name doesn't already exist, so we don't wind up with a
|
||||||
|
// duplicate.
|
||||||
|
_, _, exists := parent.LookUpChild(op.Name)
|
||||||
|
if exists {
|
||||||
|
err = fuse.EEXIST
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get the target inode to be linked
|
||||||
|
target := fs.getInodeOrDie(op.Target)
|
||||||
|
|
||||||
|
// Update the attributes
|
||||||
|
now := time.Now()
|
||||||
|
target.attrs.Nlink++
|
||||||
|
target.attrs.Ctime = now
|
||||||
|
|
||||||
|
// Add an entry in the parent.
|
||||||
|
parent.AddChild(op.Target, op.Name, fuseutil.DT_File)
|
||||||
|
|
||||||
|
// Return the response.
|
||||||
|
op.Entry.Child = op.Target
|
||||||
|
op.Entry.Attributes = target.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
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
func (fs *memFS) Rename(
|
func (fs *memFS) Rename(
|
||||||
ctx context.Context,
|
ctx context.Context,
|
||||||
op *fuseops.RenameOp) (err error) {
|
op *fuseops.RenameOp) (err error) {
|
||||||
|
|
|
@ -21,6 +21,7 @@ import (
|
||||||
"os"
|
"os"
|
||||||
"os/user"
|
"os/user"
|
||||||
"path"
|
"path"
|
||||||
|
"reflect"
|
||||||
"runtime"
|
"runtime"
|
||||||
"strconv"
|
"strconv"
|
||||||
"syscall"
|
"syscall"
|
||||||
|
@ -1088,26 +1089,6 @@ func (t *MemFSTest) ReadDirWhileModifying() {
|
||||||
ExpectTrue(namesSeen["qux"])
|
ExpectTrue(namesSeen["qux"])
|
||||||
}
|
}
|
||||||
|
|
||||||
func (t *MemFSTest) HardLinks() {
|
|
||||||
var err error
|
|
||||||
|
|
||||||
// Create a file and a directory.
|
|
||||||
fileName := path.Join(t.Dir, "foo")
|
|
||||||
err = ioutil.WriteFile(fileName, []byte{}, 0400)
|
|
||||||
AssertEq(nil, err)
|
|
||||||
|
|
||||||
dirName := path.Join(t.Dir, "bar")
|
|
||||||
err = os.Mkdir(dirName, 0700)
|
|
||||||
AssertEq(nil, err)
|
|
||||||
|
|
||||||
// Attempt to link each. Neither should work, but for different reasons.
|
|
||||||
err = os.Link(fileName, path.Join(t.Dir, "baz"))
|
|
||||||
ExpectThat(err, Error(HasSubstr("not implemented")))
|
|
||||||
|
|
||||||
err = os.Link(dirName, path.Join(t.Dir, "baz"))
|
|
||||||
ExpectThat(err, Error(HasSubstr("not permitted")))
|
|
||||||
}
|
|
||||||
|
|
||||||
func (t *MemFSTest) CreateSymlink() {
|
func (t *MemFSTest) CreateSymlink() {
|
||||||
var fi os.FileInfo
|
var fi os.FileInfo
|
||||||
var err error
|
var err error
|
||||||
|
@ -1225,6 +1206,202 @@ func (t *MemFSTest) DeleteSymlink() {
|
||||||
ExpectThat(entries, ElementsAre())
|
ExpectThat(entries, ElementsAre())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (t *MemFSTest) CreateHardlink() {
|
||||||
|
var fi os.FileInfo
|
||||||
|
var err error
|
||||||
|
|
||||||
|
// Create a file.
|
||||||
|
fileName := path.Join(t.Dir, "regular_file")
|
||||||
|
const contents = "Hello\x00world"
|
||||||
|
|
||||||
|
err = ioutil.WriteFile(fileName, []byte(contents), 0444)
|
||||||
|
AssertEq(nil, err)
|
||||||
|
|
||||||
|
// Clean up the file at the end.
|
||||||
|
defer func() {
|
||||||
|
err := os.Remove(fileName)
|
||||||
|
AssertEq(nil, err)
|
||||||
|
}()
|
||||||
|
|
||||||
|
// Create a link to the file.
|
||||||
|
linkName := path.Join(t.Dir, "foo")
|
||||||
|
err = os.Link(fileName, linkName)
|
||||||
|
AssertEq(nil, err)
|
||||||
|
|
||||||
|
// Clean up the file at the end.
|
||||||
|
defer func() {
|
||||||
|
err := os.Remove(linkName)
|
||||||
|
AssertEq(nil, err)
|
||||||
|
}()
|
||||||
|
|
||||||
|
// Stat the link.
|
||||||
|
fi, err = os.Lstat(linkName)
|
||||||
|
AssertEq(nil, err)
|
||||||
|
|
||||||
|
ExpectEq("foo", fi.Name())
|
||||||
|
ExpectEq(0444, fi.Mode())
|
||||||
|
|
||||||
|
// Read the parent directory.
|
||||||
|
entries, err := fusetesting.ReadDirPicky(t.Dir)
|
||||||
|
AssertEq(nil, err)
|
||||||
|
AssertEq(2, len(entries))
|
||||||
|
|
||||||
|
fi = entries[0]
|
||||||
|
ExpectEq("foo", fi.Name())
|
||||||
|
ExpectEq(0444, fi.Mode())
|
||||||
|
|
||||||
|
fi = entries[1]
|
||||||
|
ExpectEq("regular_file", fi.Name())
|
||||||
|
ExpectEq(0444, fi.Mode())
|
||||||
|
}
|
||||||
|
|
||||||
|
func (t *MemFSTest) CreateHardlink_AlreadyExists() {
|
||||||
|
var err error
|
||||||
|
|
||||||
|
// Create a file and a directory.
|
||||||
|
fileName := path.Join(t.Dir, "foo")
|
||||||
|
err = ioutil.WriteFile(fileName, []byte{}, 0400)
|
||||||
|
AssertEq(nil, err)
|
||||||
|
|
||||||
|
dirName := path.Join(t.Dir, "bar")
|
||||||
|
err = os.Mkdir(dirName, 0700)
|
||||||
|
AssertEq(nil, err)
|
||||||
|
|
||||||
|
// Create an existing symlink.
|
||||||
|
symlinkName := path.Join(t.Dir, "baz")
|
||||||
|
err = os.Symlink("blah", symlinkName)
|
||||||
|
AssertEq(nil, err)
|
||||||
|
|
||||||
|
// Create another link to the file.
|
||||||
|
hardlinkName := path.Join(t.Dir, "qux")
|
||||||
|
err = os.Link(fileName, hardlinkName)
|
||||||
|
AssertEq(nil, err)
|
||||||
|
|
||||||
|
// Symlinking on top of any of them should fail.
|
||||||
|
names := []string{
|
||||||
|
fileName,
|
||||||
|
dirName,
|
||||||
|
symlinkName,
|
||||||
|
hardlinkName,
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, n := range names {
|
||||||
|
err = os.Link(fileName, n)
|
||||||
|
ExpectThat(err, Error(HasSubstr("exists")))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (t *MemFSTest) DeleteHardlink() {
|
||||||
|
var fi os.FileInfo
|
||||||
|
var err error
|
||||||
|
|
||||||
|
// Create a file.
|
||||||
|
fileName := path.Join(t.Dir, "regular_file")
|
||||||
|
const contents = "Hello\x00world"
|
||||||
|
|
||||||
|
err = ioutil.WriteFile(fileName, []byte(contents), 0444)
|
||||||
|
AssertEq(nil, err)
|
||||||
|
|
||||||
|
// Step #1: We will create and remove a link and verify that
|
||||||
|
// after removal everything is as expected.
|
||||||
|
|
||||||
|
// Create a link to the file.
|
||||||
|
linkName := path.Join(t.Dir, "foo")
|
||||||
|
err = os.Link(fileName, linkName)
|
||||||
|
AssertEq(nil, err)
|
||||||
|
|
||||||
|
// Remove the link.
|
||||||
|
err = os.Remove(linkName)
|
||||||
|
AssertEq(nil, err)
|
||||||
|
|
||||||
|
// Stat the link.
|
||||||
|
fi, err = os.Lstat(linkName)
|
||||||
|
AssertEq(nil, fi)
|
||||||
|
ExpectThat(err, Error(HasSubstr("no such file")))
|
||||||
|
|
||||||
|
// Read the parent directory.
|
||||||
|
entries, err := fusetesting.ReadDirPicky(t.Dir)
|
||||||
|
AssertEq(nil, err)
|
||||||
|
AssertEq(1, len(entries))
|
||||||
|
|
||||||
|
fi = entries[0]
|
||||||
|
ExpectEq("regular_file", fi.Name())
|
||||||
|
ExpectEq(0444, fi.Mode())
|
||||||
|
|
||||||
|
// Step #2: We will create a link and remove the original file subsequently
|
||||||
|
// and verify that after removal everything is as expected.
|
||||||
|
|
||||||
|
// Create a link to the file.
|
||||||
|
linkName = path.Join(t.Dir, "bar")
|
||||||
|
err = os.Link(fileName, linkName)
|
||||||
|
AssertEq(nil, err)
|
||||||
|
|
||||||
|
// Remove the original file.
|
||||||
|
err = os.Remove(fileName)
|
||||||
|
AssertEq(nil, err)
|
||||||
|
|
||||||
|
// Stat the link.
|
||||||
|
fi, err = os.Lstat(linkName)
|
||||||
|
AssertEq(nil, err)
|
||||||
|
ExpectEq("bar", fi.Name())
|
||||||
|
ExpectEq(0444, fi.Mode())
|
||||||
|
|
||||||
|
// Stat the original file.
|
||||||
|
fi, err = os.Lstat(fileName)
|
||||||
|
AssertEq(nil, fi)
|
||||||
|
ExpectThat(err, Error(HasSubstr("no such file")))
|
||||||
|
|
||||||
|
// Read the parent directory.
|
||||||
|
entries, err = fusetesting.ReadDirPicky(t.Dir)
|
||||||
|
AssertEq(nil, err)
|
||||||
|
AssertEq(1, len(entries))
|
||||||
|
|
||||||
|
fi = entries[0]
|
||||||
|
ExpectEq("bar", fi.Name())
|
||||||
|
ExpectEq(0444, fi.Mode())
|
||||||
|
|
||||||
|
// Cleanup.
|
||||||
|
err = os.Remove(linkName)
|
||||||
|
AssertEq(nil, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (t *MemFSTest) ReadHardlink() {
|
||||||
|
var err error
|
||||||
|
|
||||||
|
// Create a file.
|
||||||
|
fileName := path.Join(t.Dir, "regular_file")
|
||||||
|
const contents = "Hello\x00world"
|
||||||
|
|
||||||
|
err = ioutil.WriteFile(fileName, []byte(contents), 0444)
|
||||||
|
AssertEq(nil, err)
|
||||||
|
|
||||||
|
// Clean up the file at the end.
|
||||||
|
defer func() {
|
||||||
|
err := os.Remove(fileName)
|
||||||
|
AssertEq(nil, err)
|
||||||
|
}()
|
||||||
|
|
||||||
|
// Create a link to the file.
|
||||||
|
linkName := path.Join(t.Dir, "foo")
|
||||||
|
err = os.Link(fileName, linkName)
|
||||||
|
AssertEq(nil, err)
|
||||||
|
|
||||||
|
// Clean up the file at the end.
|
||||||
|
defer func() {
|
||||||
|
err := os.Remove(linkName)
|
||||||
|
AssertEq(nil, err)
|
||||||
|
}()
|
||||||
|
|
||||||
|
// Read files.
|
||||||
|
original, err := ioutil.ReadFile(fileName)
|
||||||
|
AssertEq(nil, err)
|
||||||
|
linked, err := ioutil.ReadFile(linkName)
|
||||||
|
AssertEq(nil, err)
|
||||||
|
|
||||||
|
// Check if the bytes are the same.
|
||||||
|
AssertEq(true, reflect.DeepEqual(original, linked))
|
||||||
|
}
|
||||||
|
|
||||||
func (t *MemFSTest) CreateInParallel_NoTruncate() {
|
func (t *MemFSTest) CreateInParallel_NoTruncate() {
|
||||||
fusetesting.RunCreateInParallelTest_NoTruncate(t.Ctx, t.Dir)
|
fusetesting.RunCreateInParallelTest_NoTruncate(t.Ctx, t.Dir)
|
||||||
}
|
}
|
||||||
|
@ -1245,6 +1422,10 @@ func (t *MemFSTest) SymlinkInParallel() {
|
||||||
fusetesting.RunSymlinkInParallelTest(t.Ctx, t.Dir)
|
fusetesting.RunSymlinkInParallelTest(t.Ctx, t.Dir)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (t *MemFSTest) HardlinkInParallel() {
|
||||||
|
fusetesting.RunHardlinkInParallelTest(t.Ctx, t.Dir)
|
||||||
|
}
|
||||||
|
|
||||||
func (t *MemFSTest) RenameWithinDir_File() {
|
func (t *MemFSTest) RenameWithinDir_File() {
|
||||||
var err error
|
var err error
|
||||||
|
|
||||||
|
|
|
@ -445,3 +445,7 @@ func (t *PosixTest) MkdirInParallel() {
|
||||||
func (t *PosixTest) SymlinkInParallel() {
|
func (t *PosixTest) SymlinkInParallel() {
|
||||||
fusetesting.RunSymlinkInParallelTest(t.ctx, t.dir)
|
fusetesting.RunSymlinkInParallelTest(t.ctx, t.dir)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (t *PosixTest) HardlinkInParallel() {
|
||||||
|
fusetesting.RunHardlinkInParallelTest(t.ctx, t.dir)
|
||||||
|
}
|
||||||
|
|
Loading…
Reference in New Issue