commit
c4e473376f
|
@ -420,6 +420,32 @@ func convertInMessage(
|
|||
Flags: fusekernel.InitFlags(in.Flags),
|
||||
}
|
||||
|
||||
case fusekernel.OpLink:
|
||||
type input fusekernel.LinkIn
|
||||
in := (*input)(inMsg.Consume(unsafe.Sizeof(input{})))
|
||||
if in == nil {
|
||||
err = errors.New("Corrupt OpLink")
|
||||
return
|
||||
}
|
||||
|
||||
name := inMsg.ConsumeBytes(inMsg.Len())
|
||||
i := bytes.IndexByte(name, '\x00')
|
||||
if i < 0 {
|
||||
err = errors.New("Corrupt OpLink")
|
||||
return
|
||||
}
|
||||
name = name[:i]
|
||||
if len(name) == 0 {
|
||||
err = errors.New("Corrupt OpLink (Name not read)")
|
||||
return
|
||||
}
|
||||
|
||||
o = &fuseops.CreateLinkOp{
|
||||
Parent: fuseops.InodeID(inMsg.Header().Nodeid),
|
||||
Name: string(name),
|
||||
Target: fuseops.InodeID(in.Oldnodeid),
|
||||
}
|
||||
|
||||
case fusekernel.OpRemovexattr:
|
||||
buf := inMsg.ConsumeBytes(inMsg.Len())
|
||||
n := len(buf)
|
||||
|
@ -647,6 +673,11 @@ func (c *Connection) kernelResponseForOp(
|
|||
out := (*fusekernel.EntryOut)(m.Grow(size))
|
||||
convertChildInodeEntry(&o.Entry, out)
|
||||
|
||||
case *fuseops.CreateLinkOp:
|
||||
size := int(fusekernel.EntryOutSize(c.protocol))
|
||||
out := (*fusekernel.EntryOut)(m.Grow(size))
|
||||
convertChildInodeEntry(&o.Entry, out)
|
||||
|
||||
case *fuseops.RenameOp:
|
||||
// Empty response
|
||||
|
||||
|
|
|
@ -317,6 +317,26 @@ type CreateSymlinkOp struct {
|
|||
Entry ChildInodeEntry
|
||||
}
|
||||
|
||||
// Create a hard link to an inode. If the name already exists, the file system
|
||||
// should return EEXIST (cf. the notes on CreateFileOp and MkDirOp).
|
||||
type CreateLinkOp struct {
|
||||
// The ID of parent directory inode within which to create the child hard
|
||||
// link.
|
||||
Parent InodeID
|
||||
|
||||
// The name of the new inode.
|
||||
Name string
|
||||
|
||||
// The ID of the target inode.
|
||||
Target InodeID
|
||||
|
||||
// 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
|
||||
}
|
||||
|
||||
////////////////////////////////////////////////////////////////////////
|
||||
// Unlinking
|
||||
////////////////////////////////////////////////////////////////////////
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
|
|
|
@ -43,6 +43,7 @@ type FileSystem interface {
|
|||
MkDir(context.Context, *fuseops.MkDirOp) error
|
||||
MkNode(context.Context, *fuseops.MkNodeOp) error
|
||||
CreateFile(context.Context, *fuseops.CreateFileOp) error
|
||||
CreateLink(context.Context, *fuseops.CreateLinkOp) error
|
||||
CreateSymlink(context.Context, *fuseops.CreateSymlinkOp) error
|
||||
Rename(context.Context, *fuseops.RenameOp) error
|
||||
RmDir(context.Context, *fuseops.RmDirOp) error
|
||||
|
@ -159,6 +160,9 @@ func (s *fileSystemServer) handleOp(
|
|||
case *fuseops.CreateFileOp:
|
||||
err = s.fs.CreateFile(ctx, typed)
|
||||
|
||||
case *fuseops.CreateLinkOp:
|
||||
err = s.fs.CreateLink(ctx, typed)
|
||||
|
||||
case *fuseops.CreateSymlinkOp:
|
||||
err = s.fs.CreateSymlink(ctx, typed)
|
||||
|
||||
|
|
|
@ -92,6 +92,13 @@ func (fs *NotImplementedFileSystem) CreateSymlink(
|
|||
return
|
||||
}
|
||||
|
||||
func (fs *NotImplementedFileSystem) CreateLink(
|
||||
ctx context.Context,
|
||||
op *fuseops.CreateLinkOp) (err error) {
|
||||
err = fuse.ENOSYS
|
||||
return
|
||||
}
|
||||
|
||||
func (fs *NotImplementedFileSystem) Rename(
|
||||
ctx context.Context,
|
||||
op *fuseops.RenameOp) (err error) {
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue