Skip to content
Projects
Groups
Snippets
Help
Loading...
Help
Support
Keyboard shortcuts
?
Submit feedback
Contribute to GitLab
Sign in / Register
Toggle navigation
J
jacobsa-fuse
Project overview
Project overview
Details
Activity
Releases
Repository
Repository
Files
Commits
Branches
Tags
Contributors
Graph
Compare
Issues
0
Issues
0
List
Boards
Labels
Milestones
Merge Requests
0
Merge Requests
0
Analytics
Analytics
Repository
Value Stream
Wiki
Wiki
Snippets
Snippets
Members
Members
Collapse sidebar
Close sidebar
Activity
Graph
Create a new issue
Commits
Issue Boards
Open sidebar
Kirill Smelkov
jacobsa-fuse
Commits
c4e47337
Commit
c4e47337
authored
Dec 01, 2017
by
Aaron Jacobs
Committed by
GitHub
Dec 01, 2017
Browse files
Options
Browse Files
Download
Plain Diff
Merge pull request #36 from sbg/feature/hard-links
Hard links implementation
parents
88e3bc5f
4ee295e3
Changes
8
Show whitespace changes
Inline
Side-by-side
Showing
8 changed files
with
375 additions
and
20 deletions
+375
-20
conversions.go
conversions.go
+31
-0
fuseops/ops.go
fuseops/ops.go
+20
-0
fusetesting/parallel.go
fusetesting/parallel.go
+68
-0
fuseutil/file_system.go
fuseutil/file_system.go
+4
-0
fuseutil/not_implemented_file_system.go
fuseutil/not_implemented_file_system.go
+7
-0
samples/memfs/memfs.go
samples/memfs/memfs.go
+40
-0
samples/memfs/memfs_test.go
samples/memfs/memfs_test.go
+201
-20
samples/memfs/posix_test.go
samples/memfs/posix_test.go
+4
-0
No files found.
conversions.go
View file @
c4e47337
...
...
@@ -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
...
...
fuseops/ops.go
View file @
c4e47337
...
...
@@ -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
////////////////////////////////////////////////////////////////////////
...
...
fusetesting/parallel.go
View file @
c4e47337
...
...
@@ -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
\x00
world"
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
)
}
fuseutil/file_system.go
View file @
c4e47337
...
...
@@ -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
)
...
...
fuseutil/not_implemented_file_system.go
View file @
c4e47337
...
...
@@ -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
)
{
...
...
samples/memfs/memfs.go
View file @
c4e47337
...
...
@@ -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
)
{
...
...
samples/memfs/memfs_test.go
View file @
c4e47337
...
...
@@ -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
\x00
world"
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
\x00
world"
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
\x00
world"
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
...
...
samples/memfs/posix_test.go
View file @
c4e47337
...
...
@@ -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
)
}
Write
Preview
Markdown
is supported
0%
Try again
or
attach a new file
Attach a file
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Cancel
Please
register
or
sign in
to comment