Commit 4ee295e3 authored by Srdjan Rilak's avatar Srdjan Rilak

Add tests for hard link

parent 1b4a34cc
......@@ -365,3 +365,71 @@ func RunSymlinkInParallelTest(
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
}
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(
ctx context.Context,
op *fuseops.RenameOp) (err error) {
......
......@@ -21,6 +21,7 @@ import (
"os"
"os/user"
"path"
"reflect"
"runtime"
"strconv"
"syscall"
......@@ -1088,26 +1089,6 @@ func (t *MemFSTest) ReadDirWhileModifying() {
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() {
var fi os.FileInfo
var err error
......@@ -1225,6 +1206,202 @@ func (t *MemFSTest) DeleteSymlink() {
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() {
fusetesting.RunCreateInParallelTest_NoTruncate(t.Ctx, t.Dir)
}
......@@ -1245,6 +1422,10 @@ func (t *MemFSTest) SymlinkInParallel() {
fusetesting.RunSymlinkInParallelTest(t.Ctx, t.Dir)
}
func (t *MemFSTest) HardlinkInParallel() {
fusetesting.RunHardlinkInParallelTest(t.Ctx, t.Dir)
}
func (t *MemFSTest) RenameWithinDir_File() {
var err error
......
......@@ -445,3 +445,7 @@ func (t *PosixTest) MkdirInParallel() {
func (t *PosixTest) SymlinkInParallel() {
fusetesting.RunSymlinkInParallelTest(t.ctx, t.dir)
}
func (t *PosixTest) HardlinkInParallel() {
fusetesting.RunHardlinkInParallelTest(t.ctx, t.dir)
}
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment