Skip to content
Projects
Groups
Snippets
Help
Loading...
Help
Support
Keyboard shortcuts
?
Submit feedback
Contribute to GitLab
Sign in / Register
Toggle navigation
G
go-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
go-fuse
Commits
557408bc
Commit
557408bc
authored
Nov 19, 2011
by
Han-Wen Nienhuys
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
Move MemUnionFs to termite.
parent
23d26623
Changes
3
Show whitespace changes
Inline
Side-by-side
Showing
3 changed files
with
1 addition
and
1565 deletions
+1
-1565
unionfs/Makefile
unionfs/Makefile
+1
-2
unionfs/memunionfs.go
unionfs/memunionfs.go
+0
-658
unionfs/memunionfs_test.go
unionfs/memunionfs_test.go
+0
-905
No files found.
unionfs/Makefile
View file @
557408bc
...
@@ -7,8 +7,7 @@ GOFILES=unionfs.go \
...
@@ -7,8 +7,7 @@ GOFILES=unionfs.go \
timedcache.go
\
timedcache.go
\
cachingfs.go
\
cachingfs.go
\
autounion.go
\
autounion.go
\
create.go
\
create.go
memunionfs.go
DEPS
=
../fuse
DEPS
=
../fuse
...
...
unionfs/memunionfs.go
deleted
100644 → 0
View file @
23d26623
package
unionfs
import
(
"fmt"
"github.com/hanwen/go-fuse/fuse"
"log"
"os"
"path/filepath"
"sort"
"strings"
"sync"
"time"
)
var
_
=
log
.
Println
// A unionfs that only uses on-disk backing store for file contents.
type
MemUnionFs
struct
{
fuse
.
DefaultNodeFileSystem
readonly
fuse
.
FileSystem
backingStore
string
connector
*
fuse
.
FileSystemConnector
mutex
sync
.
RWMutex
root
*
memNode
cond
*
sync
.
Cond
nextFree
int
openWritable
int
// All paths that have been renamed or deleted will be marked
// here. After deletion, entries may be recreated, but they
// will be treated as new.
deleted
map
[
string
]
bool
}
type
memNode
struct
{
fuse
.
DefaultFsNode
fs
*
MemUnionFs
// protects mutable data below.
mutex
*
sync
.
RWMutex
backing
string
original
string
changed
bool
link
string
info
os
.
FileInfo
}
type
Result
struct
{
*
os
.
FileInfo
Original
string
Backing
string
Link
string
}
func
(
me
*
MemUnionFs
)
OnMount
(
conn
*
fuse
.
FileSystemConnector
)
{
me
.
connector
=
conn
}
func
(
me
*
MemUnionFs
)
markCloseWrite
()
{
me
.
mutex
.
Lock
()
defer
me
.
mutex
.
Unlock
()
me
.
openWritable
--
if
me
.
openWritable
<
0
{
log
.
Panicf
(
"openWritable Underflow"
)
}
me
.
cond
.
Broadcast
()
}
// Reset drops the state of the filesystem back its original.
func
(
me
*
MemUnionFs
)
Reset
()
{
me
.
mutex
.
Lock
()
defer
me
.
mutex
.
Unlock
()
me
.
root
.
reset
(
""
)
for
path
,
_
:=
range
me
.
deleted
{
parent
,
base
:=
filepath
.
Split
(
path
)
parent
=
stripSlash
(
parent
)
last
,
rest
:=
me
.
connector
.
Node
(
me
.
root
.
Inode
(),
parent
)
if
len
(
rest
)
==
0
{
me
.
connector
.
EntryNotify
(
last
,
base
)
}
}
me
.
deleted
=
make
(
map
[
string
]
bool
,
len
(
me
.
deleted
))
me
.
clearBackingStore
()
}
func
(
me
*
MemUnionFs
)
Reap
()
map
[
string
]
*
Result
{
me
.
mutex
.
Lock
()
defer
me
.
mutex
.
Unlock
()
for
me
.
openWritable
>
0
{
me
.
cond
.
Wait
()
}
m
:=
map
[
string
]
*
Result
{}
for
name
,
_
:=
range
me
.
deleted
{
fi
,
code
:=
me
.
readonly
.
GetAttr
(
name
,
nil
)
if
!
code
.
Ok
()
{
continue
}
m
[
name
]
=
&
Result
{}
if
!
fi
.
IsDirectory
()
{
continue
}
todo
:=
[]
string
{
name
}
for
len
(
todo
)
>
0
{
l
:=
len
(
todo
)
-
1
n
:=
todo
[
l
]
todo
=
todo
[
:
l
]
s
,
_
:=
me
.
readonly
.
OpenDir
(
n
,
nil
)
for
e
:=
range
s
{
full
:=
filepath
.
Join
(
n
,
e
.
Name
)
m
[
full
]
=
&
Result
{}
if
e
.
Mode
&
fuse
.
S_IFDIR
!=
0
{
todo
=
append
(
todo
,
full
)
}
}
}
}
me
.
root
.
reap
(
""
,
m
)
return
m
}
func
(
me
*
MemUnionFs
)
clearBackingStore
()
{
f
,
err
:=
os
.
Open
(
me
.
backingStore
)
if
err
!=
nil
{
return
}
defer
f
.
Close
()
names
,
err
:=
f
.
Readdirnames
(
-
1
)
if
err
!=
nil
{
return
}
for
_
,
n
:=
range
names
{
os
.
Remove
(
filepath
.
Join
(
me
.
backingStore
,
n
))
}
}
func
(
me
*
MemUnionFs
)
Update
(
results
map
[
string
]
*
Result
)
{
del
:=
[]
string
{}
add
:=
[]
string
{}
for
k
,
v
:=
range
results
{
if
v
.
FileInfo
!=
nil
{
add
=
append
(
add
,
k
)
}
else
{
del
=
append
(
del
,
k
)
}
}
sort
.
Strings
(
del
)
for
i
:=
len
(
del
)
-
1
;
i
>=
0
;
i
--
{
n
:=
del
[
i
]
dir
,
base
:=
filepath
.
Split
(
n
)
dir
=
strings
.
TrimRight
(
dir
,
"/"
)
dirNode
,
rest
:=
me
.
connector
.
Node
(
me
.
root
.
Inode
(),
dir
)
if
len
(
rest
)
>
0
{
continue
}
dirNode
.
RmChild
(
base
)
me
.
connector
.
EntryNotify
(
dirNode
,
base
)
}
me
.
mutex
.
Lock
()
notifyNodes
:=
[]
*
fuse
.
Inode
{}
enotifyNodes
:=
[]
*
fuse
.
Inode
{}
enotifyNames
:=
[]
string
{}
sort
.
Strings
(
add
)
for
_
,
n
:=
range
add
{
node
,
rest
:=
me
.
connector
.
Node
(
me
.
root
.
Inode
(),
n
)
if
len
(
rest
)
>
0
{
enotifyNames
=
append
(
enotifyNames
,
rest
[
0
])
enotifyNodes
=
append
(
enotifyNodes
,
node
)
continue
}
notifyNodes
=
append
(
notifyNodes
,
node
)
mn
:=
node
.
FsNode
()
.
(
*
memNode
)
mn
.
original
=
n
mn
.
changed
=
false
r
:=
results
[
n
]
mn
.
info
=
*
r
.
FileInfo
mn
.
link
=
r
.
Link
}
me
.
mutex
.
Unlock
()
for
_
,
n
:=
range
notifyNodes
{
me
.
connector
.
FileNotify
(
n
,
0
,
0
)
}
for
i
,
n
:=
range
enotifyNodes
{
me
.
connector
.
EntryNotify
(
n
,
enotifyNames
[
i
])
}
}
func
(
me
*
MemUnionFs
)
getFilename
()
string
{
id
:=
me
.
nextFree
me
.
nextFree
++
return
fmt
.
Sprintf
(
"%s/%d"
,
me
.
backingStore
,
id
)
}
func
(
me
*
MemUnionFs
)
Root
()
fuse
.
FsNode
{
return
me
.
root
}
func
(
me
*
MemUnionFs
)
newNode
(
isdir
bool
)
*
memNode
{
n
:=
&
memNode
{
fs
:
me
,
mutex
:
&
me
.
mutex
,
}
now
:=
time
.
Nanoseconds
()
n
.
info
.
Mtime_ns
=
now
n
.
info
.
Atime_ns
=
now
n
.
info
.
Ctime_ns
=
now
return
n
}
func
NewMemUnionFs
(
backingStore
string
,
roFs
fuse
.
FileSystem
)
*
MemUnionFs
{
me
:=
&
MemUnionFs
{}
me
.
deleted
=
make
(
map
[
string
]
bool
)
me
.
backingStore
=
backingStore
me
.
readonly
=
roFs
me
.
root
=
me
.
newNode
(
true
)
me
.
root
.
info
.
Mode
=
fuse
.
S_IFDIR
|
0755
me
.
cond
=
sync
.
NewCond
(
&
me
.
mutex
)
return
me
}
func
(
me
*
memNode
)
Deletable
()
bool
{
return
!
me
.
changed
&&
me
.
original
==
""
}
func
(
me
*
memNode
)
StatFs
()
*
fuse
.
StatfsOut
{
backingFs
:=
&
fuse
.
LoopbackFileSystem
{
Root
:
me
.
fs
.
backingStore
}
return
backingFs
.
StatFs
(
""
)
}
func
(
me
*
memNode
)
touch
()
{
me
.
changed
=
true
me
.
info
.
Mtime_ns
=
time
.
Nanoseconds
()
}
func
(
me
*
memNode
)
ctouch
()
{
me
.
changed
=
true
me
.
info
.
Ctime_ns
=
time
.
Nanoseconds
()
}
func
(
me
*
memNode
)
newNode
(
isdir
bool
)
*
memNode
{
n
:=
me
.
fs
.
newNode
(
isdir
)
me
.
Inode
()
.
New
(
isdir
,
n
)
return
n
}
func
(
me
*
memNode
)
Readlink
(
c
*
fuse
.
Context
)
([]
byte
,
fuse
.
Status
)
{
me
.
mutex
.
RLock
()
defer
me
.
mutex
.
RUnlock
()
return
[]
byte
(
me
.
link
),
fuse
.
OK
}
func
(
me
*
memNode
)
Lookup
(
name
string
,
context
*
fuse
.
Context
)
(
fi
*
os
.
FileInfo
,
node
fuse
.
FsNode
,
code
fuse
.
Status
)
{
me
.
mutex
.
RLock
()
defer
me
.
mutex
.
RUnlock
()
return
me
.
lookup
(
name
,
context
)
}
// Must run with mutex held.
func
(
me
*
memNode
)
lookup
(
name
string
,
context
*
fuse
.
Context
)
(
fi
*
os
.
FileInfo
,
node
fuse
.
FsNode
,
code
fuse
.
Status
)
{
if
me
.
original
==
""
&&
me
!=
me
.
fs
.
root
{
return
nil
,
nil
,
fuse
.
ENOENT
}
fn
:=
filepath
.
Join
(
me
.
original
,
name
)
if
_
,
del
:=
me
.
fs
.
deleted
[
fn
];
del
{
return
nil
,
nil
,
fuse
.
ENOENT
}
fi
,
code
=
me
.
fs
.
readonly
.
GetAttr
(
fn
,
context
)
if
!
code
.
Ok
()
{
return
nil
,
nil
,
code
}
child
:=
me
.
newNode
(
fi
.
Mode
&
fuse
.
S_IFDIR
!=
0
)
child
.
info
=
*
fi
child
.
original
=
fn
if
child
.
info
.
Mode
&
fuse
.
S_IFLNK
!=
0
{
child
.
link
,
_
=
me
.
fs
.
readonly
.
Readlink
(
fn
,
context
)
}
me
.
Inode
()
.
AddChild
(
name
,
child
.
Inode
())
return
fi
,
child
,
fuse
.
OK
}
func
(
me
*
memNode
)
Mkdir
(
name
string
,
mode
uint32
,
context
*
fuse
.
Context
)
(
fi
*
os
.
FileInfo
,
newNode
fuse
.
FsNode
,
code
fuse
.
Status
)
{
me
.
mutex
.
Lock
()
defer
me
.
mutex
.
Unlock
()
n
:=
me
.
newNode
(
true
)
n
.
changed
=
true
n
.
info
.
Mode
=
mode
|
fuse
.
S_IFDIR
me
.
Inode
()
.
AddChild
(
name
,
n
.
Inode
())
me
.
touch
()
return
&
n
.
info
,
n
,
fuse
.
OK
}
func
(
me
*
memNode
)
Unlink
(
name
string
,
context
*
fuse
.
Context
)
(
code
fuse
.
Status
)
{
me
.
mutex
.
Lock
()
defer
me
.
mutex
.
Unlock
()
if
me
.
original
!=
""
||
me
==
me
.
fs
.
root
{
me
.
fs
.
deleted
[
filepath
.
Join
(
me
.
original
,
name
)]
=
true
}
ch
:=
me
.
Inode
()
.
RmChild
(
name
)
if
ch
==
nil
{
return
fuse
.
ENOENT
}
me
.
touch
()
return
fuse
.
OK
}
func
(
me
*
memNode
)
Rmdir
(
name
string
,
context
*
fuse
.
Context
)
(
code
fuse
.
Status
)
{
return
me
.
Unlink
(
name
,
context
)
}
func
(
me
*
memNode
)
Symlink
(
name
string
,
content
string
,
context
*
fuse
.
Context
)
(
fi
*
os
.
FileInfo
,
newNode
fuse
.
FsNode
,
code
fuse
.
Status
)
{
me
.
mutex
.
Lock
()
defer
me
.
mutex
.
Unlock
()
n
:=
me
.
newNode
(
false
)
n
.
info
.
Mode
=
fuse
.
S_IFLNK
|
0777
n
.
link
=
content
n
.
changed
=
true
me
.
Inode
()
.
AddChild
(
name
,
n
.
Inode
())
me
.
touch
()
return
&
n
.
info
,
n
,
fuse
.
OK
}
// Expand the original fs as a tree.
func
(
me
*
memNode
)
materializeSelf
()
{
me
.
changed
=
true
if
!
me
.
Inode
()
.
IsDir
()
{
return
}
s
,
_
:=
me
.
fs
.
readonly
.
OpenDir
(
me
.
original
,
nil
)
for
e
:=
range
s
{
me
.
lookup
(
e
.
Name
,
nil
)
}
me
.
original
=
""
}
func
(
me
*
memNode
)
materialize
()
{
me
.
materializeSelf
()
for
_
,
n
:=
range
me
.
Inode
()
.
FsChildren
()
{
if
n
.
IsDir
()
{
n
.
FsNode
()
.
(
*
memNode
)
.
materialize
()
}
}
}
func
(
me
*
memNode
)
Rename
(
oldName
string
,
newParent
fuse
.
FsNode
,
newName
string
,
context
*
fuse
.
Context
)
(
code
fuse
.
Status
)
{
me
.
mutex
.
Lock
()
defer
me
.
mutex
.
Unlock
()
ch
:=
me
.
Inode
()
.
RmChild
(
oldName
)
if
ch
==
nil
{
return
fuse
.
ENOENT
}
if
me
.
original
!=
""
||
me
==
me
.
fs
.
root
{
me
.
fs
.
deleted
[
filepath
.
Join
(
me
.
original
,
oldName
)]
=
true
}
childNode
:=
ch
.
FsNode
()
.
(
*
memNode
)
if
childNode
.
original
!=
""
||
childNode
==
me
.
fs
.
root
{
childNode
.
materialize
()
childNode
.
markChanged
()
}
newParent
.
Inode
()
.
RmChild
(
newName
)
newParent
.
Inode
()
.
AddChild
(
newName
,
ch
)
me
.
touch
()
return
fuse
.
OK
}
func
(
me
*
memNode
)
markChanged
()
{
me
.
changed
=
true
for
_
,
n
:=
range
me
.
Inode
()
.
FsChildren
()
{
n
.
FsNode
()
.
(
*
memNode
)
.
markChanged
()
}
}
func
(
me
*
memNode
)
Link
(
name
string
,
existing
fuse
.
FsNode
,
context
*
fuse
.
Context
)
(
fi
*
os
.
FileInfo
,
newNode
fuse
.
FsNode
,
code
fuse
.
Status
)
{
me
.
Inode
()
.
AddChild
(
name
,
existing
.
Inode
())
fi
,
code
=
existing
.
GetAttr
(
nil
,
context
)
me
.
mutex
.
Lock
()
defer
me
.
mutex
.
Unlock
()
me
.
touch
()
return
fi
,
existing
,
code
}
func
(
me
*
memNode
)
Create
(
name
string
,
flags
uint32
,
mode
uint32
,
context
*
fuse
.
Context
)
(
file
fuse
.
File
,
fi
*
os
.
FileInfo
,
newNode
fuse
.
FsNode
,
code
fuse
.
Status
)
{
me
.
mutex
.
Lock
()
defer
me
.
mutex
.
Unlock
()
n
:=
me
.
newNode
(
false
)
n
.
info
.
Mode
=
mode
|
fuse
.
S_IFREG
n
.
changed
=
true
n
.
backing
=
me
.
fs
.
getFilename
()
f
,
err
:=
os
.
Create
(
n
.
backing
)
if
err
!=
nil
{
log
.
Printf
(
"Backing store error %q: %v"
,
n
.
backing
,
err
)
return
nil
,
nil
,
nil
,
fuse
.
ToStatus
(
err
)
}
me
.
Inode
()
.
AddChild
(
name
,
n
.
Inode
())
me
.
touch
()
me
.
fs
.
openWritable
++
return
n
.
newFile
(
&
fuse
.
LoopbackFile
{
File
:
f
},
true
),
&
n
.
info
,
n
,
fuse
.
OK
}
type
memNodeFile
struct
{
fuse
.
File
writable
bool
node
*
memNode
}
func
(
me
*
memNodeFile
)
String
()
string
{
return
fmt
.
Sprintf
(
"memUfsFile(%s)"
,
me
.
File
.
String
())
}
func
(
me
*
memNodeFile
)
InnerFile
()
fuse
.
File
{
return
me
.
File
}
func
(
me
*
memNodeFile
)
Release
()
{
// Must do the subfile release first, as that may flush data
// to disk.
me
.
File
.
Release
()
if
me
.
writable
{
me
.
node
.
fs
.
markCloseWrite
()
}
}
func
(
me
*
memNodeFile
)
Flush
()
fuse
.
Status
{
code
:=
me
.
File
.
Flush
()
if
me
.
writable
{
// TODO - should this be in Release?
fi
,
_
:=
me
.
File
.
GetAttr
()
me
.
node
.
mutex
.
Lock
()
defer
me
.
node
.
mutex
.
Unlock
()
me
.
node
.
info
.
Size
=
fi
.
Size
me
.
node
.
info
.
Blocks
=
fi
.
Blocks
}
return
code
}
func
(
me
*
memNode
)
newFile
(
f
fuse
.
File
,
writable
bool
)
fuse
.
File
{
return
&
memNodeFile
{
File
:
f
,
writable
:
writable
,
node
:
me
,
}
}
// Must run inside mutex.
func
(
me
*
memNode
)
promote
()
{
if
me
.
backing
==
""
{
me
.
backing
=
me
.
fs
.
getFilename
()
destfs
:=
&
fuse
.
LoopbackFileSystem
{
Root
:
"/"
}
fuse
.
CopyFile
(
me
.
fs
.
readonly
,
destfs
,
me
.
original
,
strings
.
TrimLeft
(
me
.
backing
,
"/"
),
nil
)
me
.
original
=
""
files
:=
me
.
Inode
()
.
Files
(
0
)
for
_
,
f
:=
range
files
{
mf
:=
f
.
File
.
(
*
memNodeFile
)
inner
:=
mf
.
File
osFile
,
err
:=
os
.
Open
(
me
.
backing
)
if
err
!=
nil
{
panic
(
"error opening backing file"
)
}
mf
.
File
=
&
fuse
.
LoopbackFile
{
File
:
osFile
}
inner
.
Flush
()
inner
.
Release
()
}
}
}
func
(
me
*
memNode
)
Open
(
flags
uint32
,
context
*
fuse
.
Context
)
(
file
fuse
.
File
,
code
fuse
.
Status
)
{
me
.
mutex
.
Lock
()
defer
me
.
mutex
.
Unlock
()
if
flags
&
fuse
.
O_ANYWRITE
!=
0
{
me
.
promote
()
me
.
touch
()
}
if
me
.
backing
!=
""
{
f
,
err
:=
os
.
OpenFile
(
me
.
backing
,
int
(
flags
),
0666
)
if
err
!=
nil
{
return
nil
,
fuse
.
ToStatus
(
err
)
}
wr
:=
flags
&
fuse
.
O_ANYWRITE
!=
0
if
wr
{
me
.
fs
.
openWritable
++
}
return
me
.
newFile
(
&
fuse
.
LoopbackFile
{
File
:
f
},
wr
),
fuse
.
OK
}
file
,
code
=
me
.
fs
.
readonly
.
Open
(
me
.
original
,
flags
,
context
)
if
!
code
.
Ok
()
{
return
nil
,
code
}
return
me
.
newFile
(
file
,
false
),
fuse
.
OK
}
func
(
me
*
memNode
)
GetAttr
(
file
fuse
.
File
,
context
*
fuse
.
Context
)
(
fi
*
os
.
FileInfo
,
code
fuse
.
Status
)
{
var
sz
int64
if
file
!=
nil
{
fi
,
code
:=
file
.
GetAttr
()
if
code
.
Ok
()
{
sz
=
fi
.
Size
}
else
{
msg
:=
fmt
.
Sprintf
(
"File.GetAttr(%s) = %v, %v"
,
file
.
String
(),
fi
,
code
)
panic
(
msg
)
}
}
me
.
mutex
.
RLock
()
defer
me
.
mutex
.
RUnlock
()
info
:=
me
.
info
if
file
!=
nil
{
info
.
Size
=
sz
}
return
&
info
,
fuse
.
OK
}
func
(
me
*
memNode
)
Truncate
(
file
fuse
.
File
,
size
uint64
,
context
*
fuse
.
Context
)
(
code
fuse
.
Status
)
{
me
.
mutex
.
Lock
()
defer
me
.
mutex
.
Unlock
()
me
.
promote
()
if
file
!=
nil
{
return
file
.
Truncate
(
size
)
}
me
.
info
.
Size
=
int64
(
size
)
err
:=
os
.
Truncate
(
me
.
backing
,
int64
(
size
))
me
.
touch
()
return
fuse
.
ToStatus
(
err
)
}
func
(
me
*
memNode
)
Utimens
(
file
fuse
.
File
,
atime
uint64
,
mtime
uint64
,
context
*
fuse
.
Context
)
(
code
fuse
.
Status
)
{
me
.
mutex
.
Lock
()
defer
me
.
mutex
.
Unlock
()
me
.
info
.
Atime_ns
=
int64
(
atime
)
me
.
info
.
Mtime_ns
=
int64
(
mtime
)
me
.
ctouch
()
return
fuse
.
OK
}
func
(
me
*
memNode
)
Chmod
(
file
fuse
.
File
,
perms
uint32
,
context
*
fuse
.
Context
)
(
code
fuse
.
Status
)
{
me
.
mutex
.
Lock
()
defer
me
.
mutex
.
Unlock
()
me
.
info
.
Mode
=
(
me
.
info
.
Mode
&^
07777
)
|
perms
me
.
ctouch
()
return
fuse
.
OK
}
func
(
me
*
memNode
)
Chown
(
file
fuse
.
File
,
uid
uint32
,
gid
uint32
,
context
*
fuse
.
Context
)
(
code
fuse
.
Status
)
{
if
context
.
Uid
!=
0
{
return
fuse
.
EPERM
}
me
.
mutex
.
Lock
()
defer
me
.
mutex
.
Unlock
()
me
.
info
.
Uid
=
int
(
uid
)
me
.
info
.
Gid
=
int
(
gid
)
me
.
ctouch
()
return
fuse
.
OK
}
func
(
me
*
memNode
)
OpenDir
(
context
*
fuse
.
Context
)
(
stream
chan
fuse
.
DirEntry
,
code
fuse
.
Status
)
{
me
.
mutex
.
RLock
()
defer
me
.
mutex
.
RUnlock
()
ch
:=
map
[
string
]
uint32
{}
if
me
.
original
!=
""
||
me
==
me
.
fs
.
root
{
stream
,
code
=
me
.
fs
.
readonly
.
OpenDir
(
me
.
original
,
context
)
for
e
:=
range
stream
{
fn
:=
filepath
.
Join
(
me
.
original
,
e
.
Name
)
if
!
me
.
fs
.
deleted
[
fn
]
{
ch
[
e
.
Name
]
=
e
.
Mode
}
}
}
for
k
,
n
:=
range
me
.
Inode
()
.
FsChildren
()
{
ch
[
k
]
=
n
.
FsNode
()
.
(
*
memNode
)
.
info
.
Mode
}
stream
=
make
(
chan
fuse
.
DirEntry
,
len
(
ch
))
for
k
,
v
:=
range
ch
{
stream
<-
fuse
.
DirEntry
{
Name
:
k
,
Mode
:
v
}
}
close
(
stream
)
return
stream
,
fuse
.
OK
}
func
(
me
*
memNode
)
reap
(
path
string
,
results
map
[
string
]
*
Result
)
{
if
me
.
changed
{
info
:=
me
.
info
results
[
path
]
=
&
Result
{
FileInfo
:
&
info
,
Link
:
me
.
link
,
Backing
:
me
.
backing
,
Original
:
me
.
original
,
}
}
for
n
,
ch
:=
range
me
.
Inode
()
.
FsChildren
()
{
p
:=
filepath
.
Join
(
path
,
n
)
ch
.
FsNode
()
.
(
*
memNode
)
.
reap
(
p
,
results
)
}
}
func
(
me
*
memNode
)
reset
(
path
string
)
(
entryNotify
bool
)
{
for
n
,
ch
:=
range
me
.
Inode
()
.
FsChildren
()
{
p
:=
filepath
.
Join
(
path
,
n
)
mn
:=
ch
.
FsNode
()
.
(
*
memNode
)
if
mn
.
reset
(
p
)
{
me
.
Inode
()
.
RmChild
(
n
)
me
.
fs
.
connector
.
EntryNotify
(
me
.
Inode
(),
n
)
}
}
if
me
.
backing
!=
""
||
me
.
original
!=
path
{
return
true
}
if
me
.
changed
{
info
,
code
:=
me
.
fs
.
readonly
.
GetAttr
(
me
.
original
,
nil
)
if
!
code
.
Ok
()
{
return
true
}
me
.
info
=
*
info
me
.
fs
.
connector
.
FileNotify
(
me
.
Inode
(),
-
1
,
0
)
if
me
.
Inode
()
.
IsDir
()
{
me
.
fs
.
connector
.
FileNotify
(
me
.
Inode
(),
0
,
0
)
}
}
return
false
}
unionfs/memunionfs_test.go
deleted
100644 → 0
View file @
23d26623
package
unionfs
import
(
"fmt"
"github.com/hanwen/go-fuse/fuse"
"io/ioutil"
"log"
"os"
"os/exec"
"path/filepath"
"syscall"
"testing"
"time"
)
var
_
=
fmt
.
Print
var
_
=
log
.
Print
var
CheckSuccess
=
fuse
.
CheckSuccess
func
setupMemUfs
(
t
*
testing
.
T
)
(
workdir
string
,
ufs
*
MemUnionFs
,
cleanup
func
())
{
// Make sure system setting does not affect test.
syscall
.
Umask
(
0
)
wd
,
_
:=
ioutil
.
TempDir
(
""
,
""
)
err
:=
os
.
Mkdir
(
wd
+
"/mnt"
,
0700
)
fuse
.
CheckSuccess
(
err
)
err
=
os
.
Mkdir
(
wd
+
"/backing"
,
0700
)
fuse
.
CheckSuccess
(
err
)
os
.
Mkdir
(
wd
+
"/ro"
,
0700
)
fuse
.
CheckSuccess
(
err
)
roFs
:=
fuse
.
NewLoopbackFileSystem
(
wd
+
"/ro"
)
memFs
:=
NewMemUnionFs
(
wd
+
"/backing"
,
roFs
)
// We configure timeouts are smaller, so we can check for
// UnionFs's cache consistency.
opts
:=
&
fuse
.
FileSystemOptions
{
EntryTimeout
:
.5
*
entryTtl
,
AttrTimeout
:
.5
*
entryTtl
,
NegativeTimeout
:
.5
*
entryTtl
,
PortableInodes
:
true
,
}
state
,
conn
,
err
:=
fuse
.
MountNodeFileSystem
(
wd
+
"/mnt"
,
memFs
,
opts
)
CheckSuccess
(
err
)
conn
.
Debug
=
fuse
.
VerboseTest
()
state
.
Debug
=
fuse
.
VerboseTest
()
go
state
.
Loop
()
return
wd
,
memFs
,
func
()
{
state
.
Unmount
()
os
.
RemoveAll
(
wd
)
}
}
func
TestMemUnionFsSymlink
(
t
*
testing
.
T
)
{
wd
,
ufs
,
clean
:=
setupMemUfs
(
t
)
defer
clean
()
err
:=
os
.
Symlink
(
"/foobar"
,
wd
+
"/mnt/link"
)
CheckSuccess
(
err
)
val
,
err
:=
os
.
Readlink
(
wd
+
"/mnt/link"
)
CheckSuccess
(
err
)
if
val
!=
"/foobar"
{
t
.
Errorf
(
"symlink mismatch: %v"
,
val
)
}
r
:=
ufs
.
Reap
()
if
len
(
r
)
!=
2
||
r
[
"link"
]
==
nil
||
r
[
"link"
]
.
Link
!=
"/foobar"
{
t
.
Errorf
(
"expect 1 symlink reap result: %v"
,
r
)
}
}
func
TestMemUnionFsSymlinkPromote
(
t
*
testing
.
T
)
{
wd
,
ufs
,
clean
:=
setupMemUfs
(
t
)
defer
clean
()
err
:=
os
.
Mkdir
(
wd
+
"/ro/subdir"
,
0755
)
CheckSuccess
(
err
)
err
=
os
.
Symlink
(
"/foobar"
,
wd
+
"/mnt/subdir/link"
)
CheckSuccess
(
err
)
r
:=
ufs
.
Reap
()
if
len
(
r
)
!=
2
||
r
[
"subdir"
]
==
nil
||
r
[
"subdir/link"
]
==
nil
||
r
[
"subdir/link"
]
.
Link
!=
"/foobar"
{
t
.
Errorf
(
"expect 1 symlink reap result: %v"
,
r
)
}
}
func
TestMemUnionFsChtimes
(
t
*
testing
.
T
)
{
wd
,
ufs
,
clean
:=
setupMemUfs
(
t
)
defer
clean
()
writeToFile
(
wd
+
"/ro/file"
,
"a"
)
err
:=
os
.
Chtimes
(
wd
+
"/ro/file"
,
42e9
,
43e9
)
CheckSuccess
(
err
)
err
=
os
.
Chtimes
(
wd
+
"/mnt/file"
,
82e9
,
83e9
)
CheckSuccess
(
err
)
fi
,
err
:=
os
.
Lstat
(
wd
+
"/mnt/file"
)
if
fi
.
Atime_ns
!=
82e9
||
fi
.
Mtime_ns
!=
83e9
{
t
.
Error
(
"Incorrect timestamp"
,
fi
)
}
r
:=
ufs
.
Reap
()
if
r
[
"file"
]
==
nil
||
r
[
"file"
]
.
Original
==
""
{
t
.
Errorf
(
"expect 1 file reap result: %v"
,
r
)
}
}
func
TestMemUnionFsChmod
(
t
*
testing
.
T
)
{
wd
,
ufs
,
clean
:=
setupMemUfs
(
t
)
defer
clean
()
ro_fn
:=
wd
+
"/ro/file"
m_fn
:=
wd
+
"/mnt/file"
writeToFile
(
ro_fn
,
"a"
)
err
:=
os
.
Chmod
(
m_fn
,
07070
)
CheckSuccess
(
err
)
fi
,
err
:=
os
.
Lstat
(
m_fn
)
CheckSuccess
(
err
)
if
fi
.
Mode
&
07777
!=
07070
{
t
.
Errorf
(
"Unexpected mode found: %o"
,
fi
.
Mode
)
}
r
:=
ufs
.
Reap
()
if
r
[
"file"
]
==
nil
||
r
[
"file"
]
.
Original
==
""
{
t
.
Errorf
(
"expect 1 file reap result: %v"
,
r
)
}
}
func
TestMemUnionFsChown
(
t
*
testing
.
T
)
{
wd
,
_
,
clean
:=
setupMemUfs
(
t
)
defer
clean
()
ro_fn
:=
wd
+
"/ro/file"
m_fn
:=
wd
+
"/mnt/file"
writeToFile
(
ro_fn
,
"a"
)
err
:=
os
.
Chown
(
m_fn
,
0
,
0
)
code
:=
fuse
.
ToStatus
(
err
)
if
code
!=
fuse
.
EPERM
{
t
.
Error
(
"Unexpected error code"
,
code
,
err
)
}
}
func
TestMemUnionFsDelete
(
t
*
testing
.
T
)
{
wd
,
ufs
,
clean
:=
setupMemUfs
(
t
)
defer
clean
()
writeToFile
(
wd
+
"/ro/file"
,
"a"
)
_
,
err
:=
os
.
Lstat
(
wd
+
"/mnt/file"
)
CheckSuccess
(
err
)
err
=
os
.
Remove
(
wd
+
"/mnt/file"
)
CheckSuccess
(
err
)
_
,
err
=
os
.
Lstat
(
wd
+
"/mnt/file"
)
if
err
==
nil
{
t
.
Fatal
(
"should have disappeared."
)
}
r
:=
ufs
.
Reap
()
if
r
[
"file"
]
==
nil
||
r
[
"file"
]
.
FileInfo
!=
nil
{
t
.
Errorf
(
"expect 1 deletion reap result: %v"
,
r
)
}
}
func
TestMemUnionFsBasic
(
t
*
testing
.
T
)
{
wd
,
_
,
clean
:=
setupMemUfs
(
t
)
defer
clean
()
writeToFile
(
wd
+
"/mnt/rw"
,
"a"
)
writeToFile
(
wd
+
"/ro/ro1"
,
"a"
)
writeToFile
(
wd
+
"/ro/ro2"
,
"b"
)
names
:=
dirNames
(
wd
+
"/mnt"
)
expected
:=
map
[
string
]
bool
{
"rw"
:
true
,
"ro1"
:
true
,
"ro2"
:
true
,
}
checkMapEq
(
t
,
names
,
expected
)
writeToFile
(
wd
+
"/mnt/new"
,
"new contents"
)
contents
:=
readFromFile
(
wd
+
"/mnt/new"
)
if
contents
!=
"new contents"
{
t
.
Errorf
(
"read mismatch: '%v'"
,
contents
)
}
writeToFile
(
wd
+
"/mnt/ro1"
,
"promote me"
)
remove
(
wd
+
"/mnt/new"
)
names
=
dirNames
(
wd
+
"/mnt"
)
checkMapEq
(
t
,
names
,
map
[
string
]
bool
{
"rw"
:
true
,
"ro1"
:
true
,
"ro2"
:
true
,
})
remove
(
wd
+
"/mnt/ro1"
)
names
=
dirNames
(
wd
+
"/mnt"
)
checkMapEq
(
t
,
names
,
map
[
string
]
bool
{
"rw"
:
true
,
"ro2"
:
true
,
})
}
func
TestMemUnionFsPromote
(
t
*
testing
.
T
)
{
wd
,
ufs
,
clean
:=
setupMemUfs
(
t
)
defer
clean
()
err
:=
os
.
Mkdir
(
wd
+
"/ro/subdir"
,
0755
)
CheckSuccess
(
err
)
writeToFile
(
wd
+
"/ro/subdir/file"
,
"content"
)
writeToFile
(
wd
+
"/mnt/subdir/file"
,
"other-content"
)
r
:=
ufs
.
Reap
()
if
r
[
"subdir/file"
]
==
nil
||
r
[
"subdir/file"
]
.
Backing
==
""
{
t
.
Errorf
(
"expect 1 file reap result: %v"
,
r
)
}
}
func
TestMemUnionFsSubdirCreate
(
t
*
testing
.
T
)
{
wd
,
ufs
,
clean
:=
setupMemUfs
(
t
)
defer
clean
()
err
:=
os
.
MkdirAll
(
wd
+
"/ro/subdir/sub2"
,
0755
)
CheckSuccess
(
err
)
writeToFile
(
wd
+
"/mnt/subdir/sub2/file"
,
"other-content"
)
_
,
err
=
os
.
Lstat
(
wd
+
"/mnt/subdir/sub2/file"
)
CheckSuccess
(
err
)
r
:=
ufs
.
Reap
()
if
r
[
"subdir/sub2/file"
]
==
nil
||
r
[
"subdir/sub2/file"
]
.
Backing
==
""
{
t
.
Errorf
(
"expect 1 file reap result: %v"
,
r
)
}
}
func
TestMemUnionFsCreate
(
t
*
testing
.
T
)
{
wd
,
ufs
,
clean
:=
setupMemUfs
(
t
)
defer
clean
()
writeToFile
(
wd
+
"/mnt/file.txt"
,
"hello"
)
_
,
err
:=
os
.
Lstat
(
wd
+
"/mnt/file.txt"
)
CheckSuccess
(
err
)
r
:=
ufs
.
Reap
()
if
r
[
"file.txt"
]
==
nil
||
r
[
"file.txt"
]
.
Backing
==
""
{
t
.
Errorf
(
"expect 1 file reap result: %v"
,
r
)
}
}
func
TestMemUnionFsOpenUndeletes
(
t
*
testing
.
T
)
{
wd
,
_
,
clean
:=
setupMemUfs
(
t
)
defer
clean
()
writeToFile
(
wd
+
"/ro/file"
,
"X"
)
err
:=
os
.
Remove
(
wd
+
"/mnt/file"
)
CheckSuccess
(
err
)
writeToFile
(
wd
+
"/mnt/file"
,
"X"
)
_
,
err
=
os
.
Lstat
(
wd
+
"/mnt/file"
)
}
func
TestMemUnionFsMkdir
(
t
*
testing
.
T
)
{
wd
,
ufs
,
clean
:=
setupMemUfs
(
t
)
defer
clean
()
dirname
:=
wd
+
"/mnt/subdir"
err
:=
os
.
Mkdir
(
dirname
,
0755
)
CheckSuccess
(
err
)
err
=
os
.
Remove
(
dirname
)
CheckSuccess
(
err
)
r
:=
ufs
.
Reap
()
if
len
(
r
)
>
2
||
r
[
""
]
==
nil
||
r
[
"subdir"
]
!=
nil
{
t
.
Errorf
(
"expect 1 file reap result: %v"
,
r
)
}
}
func
TestMemUnionFsMkdirPromote
(
t
*
testing
.
T
)
{
wd
,
ufs
,
clean
:=
setupMemUfs
(
t
)
defer
clean
()
dirname
:=
wd
+
"/ro/subdir/subdir2"
err
:=
os
.
MkdirAll
(
dirname
,
0755
)
CheckSuccess
(
err
)
err
=
os
.
Mkdir
(
wd
+
"/mnt/subdir/subdir2/dir3"
,
0755
)
CheckSuccess
(
err
)
r
:=
ufs
.
Reap
()
if
r
[
"subdir/subdir2/dir3"
]
==
nil
||
r
[
"subdir/subdir2/dir3"
]
.
FileInfo
.
Mode
&
fuse
.
S_IFDIR
==
0
{
t
.
Errorf
(
"expect 1 file reap result: %v"
,
r
)
}
}
func
TestMemUnionFsRmdirMkdir
(
t
*
testing
.
T
)
{
wd
,
_
,
clean
:=
setupMemUfs
(
t
)
defer
clean
()
err
:=
os
.
Mkdir
(
wd
+
"/ro/subdir"
,
0755
)
CheckSuccess
(
err
)
dirname
:=
wd
+
"/mnt/subdir"
err
=
os
.
Remove
(
dirname
)
CheckSuccess
(
err
)
err
=
os
.
Mkdir
(
dirname
,
0755
)
CheckSuccess
(
err
)
}
func
TestMemUnionFsLink
(
t
*
testing
.
T
)
{
wd
,
_
,
clean
:=
setupMemUfs
(
t
)
defer
clean
()
content
:=
"blabla"
fn
:=
wd
+
"/ro/file"
err
:=
ioutil
.
WriteFile
(
fn
,
[]
byte
(
content
),
0666
)
CheckSuccess
(
err
)
err
=
os
.
Link
(
wd
+
"/mnt/file"
,
wd
+
"/mnt/linked"
)
CheckSuccess
(
err
)
fi2
,
err
:=
os
.
Lstat
(
wd
+
"/mnt/linked"
)
CheckSuccess
(
err
)
fi1
,
err
:=
os
.
Lstat
(
wd
+
"/mnt/file"
)
CheckSuccess
(
err
)
if
fi1
.
Ino
!=
fi2
.
Ino
{
t
.
Errorf
(
"inode numbers should be equal for linked files %v, %v"
,
fi1
.
Ino
,
fi2
.
Ino
)
}
c
,
err
:=
ioutil
.
ReadFile
(
wd
+
"/mnt/linked"
)
if
string
(
c
)
!=
content
{
t
.
Errorf
(
"content mismatch got %q want %q"
,
string
(
c
),
content
)
}
}
func
TestMemUnionFsCreateLink
(
t
*
testing
.
T
)
{
wd
,
_
,
clean
:=
setupMemUfs
(
t
)
defer
clean
()
content
:=
"blabla"
fn
:=
wd
+
"/mnt/file"
err
:=
ioutil
.
WriteFile
(
fn
,
[]
byte
(
content
),
0666
)
CheckSuccess
(
err
)
err
=
os
.
Link
(
wd
+
"/mnt/file"
,
wd
+
"/mnt/linked"
)
CheckSuccess
(
err
)
}
func
TestMemUnionFsTruncate
(
t
*
testing
.
T
)
{
wd
,
_
,
clean
:=
setupMemUfs
(
t
)
defer
clean
()
writeToFile
(
wd
+
"/ro/file"
,
"hello"
)
os
.
Truncate
(
wd
+
"/mnt/file"
,
2
)
content
:=
readFromFile
(
wd
+
"/mnt/file"
)
if
content
!=
"he"
{
t
.
Errorf
(
"unexpected content %v"
,
content
)
}
}
func
TestMemUnionFsCopyChmod
(
t
*
testing
.
T
)
{
wd
,
_
,
clean
:=
setupMemUfs
(
t
)
defer
clean
()
contents
:=
"hello"
fn
:=
wd
+
"/mnt/y"
err
:=
ioutil
.
WriteFile
(
fn
,
[]
byte
(
contents
),
0644
)
CheckSuccess
(
err
)
err
=
os
.
Chmod
(
fn
,
0755
)
CheckSuccess
(
err
)
fi
,
err
:=
os
.
Lstat
(
fn
)
CheckSuccess
(
err
)
if
fi
.
Mode
&
0111
==
0
{
t
.
Errorf
(
"1st attr error %o"
,
fi
.
Mode
)
}
time
.
Sleep
(
entryTtl
*
1.1e9
)
fi
,
err
=
os
.
Lstat
(
fn
)
CheckSuccess
(
err
)
if
fi
.
Mode
&
0111
==
0
{
t
.
Errorf
(
"uncached attr error %o"
,
fi
.
Mode
)
}
}
func
TestMemUnionFsTruncateTimestamp
(
t
*
testing
.
T
)
{
wd
,
_
,
clean
:=
setupMemUfs
(
t
)
defer
clean
()
contents
:=
"hello"
fn
:=
wd
+
"/mnt/y"
err
:=
ioutil
.
WriteFile
(
fn
,
[]
byte
(
contents
),
0644
)
CheckSuccess
(
err
)
time
.
Sleep
(
0.2e9
)
truncTs
:=
time
.
Nanoseconds
()
err
=
os
.
Truncate
(
fn
,
3
)
CheckSuccess
(
err
)
fi
,
err
:=
os
.
Lstat
(
fn
)
CheckSuccess
(
err
)
if
abs
(
truncTs
-
fi
.
Mtime_ns
)
>
0.1e9
{
t
.
Error
(
"timestamp drift"
,
truncTs
,
fi
.
Mtime_ns
)
}
}
func
TestMemUnionFsRemoveAll
(
t
*
testing
.
T
)
{
wd
,
_
,
clean
:=
setupMemUfs
(
t
)
defer
clean
()
err
:=
os
.
MkdirAll
(
wd
+
"/ro/dir/subdir"
,
0755
)
CheckSuccess
(
err
)
contents
:=
"hello"
fn
:=
wd
+
"/ro/dir/subdir/y"
err
=
ioutil
.
WriteFile
(
fn
,
[]
byte
(
contents
),
0644
)
CheckSuccess
(
err
)
err
=
os
.
RemoveAll
(
wd
+
"/mnt/dir"
)
if
err
!=
nil
{
t
.
Error
(
"Should delete all"
)
}
for
_
,
f
:=
range
[]
string
{
"dir/subdir/y"
,
"dir/subdir"
,
"dir"
}
{
if
fi
,
_
:=
os
.
Lstat
(
filepath
.
Join
(
wd
,
"mount"
,
f
));
fi
!=
nil
{
t
.
Errorf
(
"file %s should have disappeared: %v"
,
f
,
fi
)
}
}
}
func
TestMemUnionFsRmRf
(
t
*
testing
.
T
)
{
wd
,
_
,
clean
:=
setupMemUfs
(
t
)
defer
clean
()
err
:=
os
.
MkdirAll
(
wd
+
"/ro/dir/subdir"
,
0755
)
CheckSuccess
(
err
)
contents
:=
"hello"
fn
:=
wd
+
"/ro/dir/subdir/y"
err
=
ioutil
.
WriteFile
(
fn
,
[]
byte
(
contents
),
0644
)
CheckSuccess
(
err
)
bin
,
err
:=
exec
.
LookPath
(
"rm"
)
CheckSuccess
(
err
)
cmd
:=
exec
.
Command
(
bin
,
"-rf"
,
wd
+
"/mnt/dir"
)
err
=
cmd
.
Run
()
if
err
!=
nil
{
t
.
Fatal
(
"rm -rf returned error:"
,
err
)
}
for
_
,
f
:=
range
[]
string
{
"dir/subdir/y"
,
"dir/subdir"
,
"dir"
}
{
if
fi
,
_
:=
os
.
Lstat
(
filepath
.
Join
(
wd
,
"mount"
,
f
));
fi
!=
nil
{
t
.
Errorf
(
"file %s should have disappeared: %v"
,
f
,
fi
)
}
}
}
func
TestMemUnionFsDeletedGetAttr
(
t
*
testing
.
T
)
{
wd
,
_
,
clean
:=
setupMemUfs
(
t
)
defer
clean
()
err
:=
ioutil
.
WriteFile
(
wd
+
"/ro/file"
,
[]
byte
(
"blabla"
),
0644
)
CheckSuccess
(
err
)
f
,
err
:=
os
.
Open
(
wd
+
"/mnt/file"
)
CheckSuccess
(
err
)
defer
f
.
Close
()
err
=
os
.
Remove
(
wd
+
"/mnt/file"
)
CheckSuccess
(
err
)
if
fi
,
err
:=
f
.
Stat
();
err
!=
nil
||
!
fi
.
IsRegular
()
{
t
.
Fatalf
(
"stat returned error or non-file: %v %v"
,
err
,
fi
)
}
}
func
TestMemUnionFsDoubleOpen
(
t
*
testing
.
T
)
{
wd
,
_
,
clean
:=
setupMemUfs
(
t
)
defer
clean
()
err
:=
ioutil
.
WriteFile
(
wd
+
"/ro/file"
,
[]
byte
(
"blablabla"
),
0644
)
CheckSuccess
(
err
)
roFile
,
err
:=
os
.
Open
(
wd
+
"/mnt/file"
)
CheckSuccess
(
err
)
defer
roFile
.
Close
()
rwFile
,
err
:=
os
.
OpenFile
(
wd
+
"/mnt/file"
,
os
.
O_WRONLY
|
os
.
O_TRUNC
,
0666
)
CheckSuccess
(
err
)
defer
rwFile
.
Close
()
output
,
err
:=
ioutil
.
ReadAll
(
roFile
)
CheckSuccess
(
err
)
if
len
(
output
)
!=
0
{
t
.
Errorf
(
"After r/w truncation, r/o file should be empty too: %q"
,
string
(
output
))
}
want
:=
"hello"
_
,
err
=
rwFile
.
Write
([]
byte
(
want
))
CheckSuccess
(
err
)
b
:=
make
([]
byte
,
100
)
roFile
.
Seek
(
0
,
0
)
n
,
err
:=
roFile
.
Read
(
b
)
CheckSuccess
(
err
)
b
=
b
[
:
n
]
if
string
(
b
)
!=
"hello"
{
t
.
Errorf
(
"r/w and r/o file are not synchronized: got %q want %q"
,
string
(
b
),
want
)
}
}
func
TestMemUnionFsUpdate
(
t
*
testing
.
T
)
{
wd
,
ufs
,
clean
:=
setupMemUfs
(
t
)
defer
clean
()
err
:=
ioutil
.
WriteFile
(
wd
+
"/ro/file1"
,
[]
byte
(
"blablabla"
),
0644
)
CheckSuccess
(
err
)
_
,
err
=
os
.
Lstat
(
wd
+
"/mnt/file1"
)
CheckSuccess
(
err
)
if
fi
,
_
:=
os
.
Lstat
(
wd
+
"/mnt/file2"
);
fi
!=
nil
{
t
.
Fatal
(
"file2 should not exist"
,
fi
)
}
if
fi
,
_
:=
os
.
Lstat
(
wd
+
"/mnt/symlink"
);
fi
!=
nil
{
t
.
Fatal
(
"symlink should not exist"
,
fi
)
}
err
=
os
.
Remove
(
wd
+
"/ro/file1"
)
CheckSuccess
(
err
)
err
=
ioutil
.
WriteFile
(
wd
+
"/ro/file2"
,
[]
byte
(
"foobar"
),
0644
)
CheckSuccess
(
err
)
err
=
os
.
Symlink
(
"target"
,
wd
+
"/ro/symlink"
)
CheckSuccess
(
err
)
// Still have cached attributes.
fi
,
err
:=
os
.
Lstat
(
wd
+
"/mnt/file1"
)
CheckSuccess
(
err
)
if
fi
,
_
:=
os
.
Lstat
(
wd
+
"/mnt/file2"
);
fi
!=
nil
{
t
.
Fatal
(
"file2 should not exist"
)
}
if
fi
,
_
:=
os
.
Lstat
(
wd
+
"/mnt/symlink"
);
fi
!=
nil
{
t
.
Fatal
(
"symlink should not exist"
,
fi
)
}
roF2
,
err
:=
os
.
Lstat
(
wd
+
"/ro/file2"
)
CheckSuccess
(
err
)
roSymlinkFi
,
err
:=
os
.
Lstat
(
wd
+
"/ro/symlink"
)
CheckSuccess
(
err
)
updates
:=
map
[
string
]
*
Result
{
"file1"
:
&
Result
{
nil
,
""
,
""
,
""
,
},
"file2"
:
&
Result
{
roF2
,
""
,
""
,
""
,
},
"symlink"
:
&
Result
{
roSymlinkFi
,
""
,
""
,
"target"
,
},
}
ufs
.
Update
(
updates
)
// Cached attributes flushed.
if
fi
,
_
:=
os
.
Lstat
(
wd
+
"/mnt/file1"
);
fi
!=
nil
{
t
.
Fatal
(
"file1 should have disappeared"
,
fi
)
}
fi
,
err
=
os
.
Lstat
(
wd
+
"/mnt/file2"
)
CheckSuccess
(
err
)
if
roF2
.
Mtime_ns
!=
fi
.
Mtime_ns
{
t
.
Fatalf
(
"file2 attribute mismatch: got %v want %v"
,
fi
,
roF2
)
}
val
,
err
:=
os
.
Readlink
(
wd
+
"/mnt/symlink"
)
CheckSuccess
(
err
)
if
val
!=
"target"
{
t
.
Error
(
"symlink value got %q want %v"
,
val
,
"target"
)
}
}
func
TestMemUnionFsFdLeak
(
t
*
testing
.
T
)
{
beforeEntries
,
err
:=
ioutil
.
ReadDir
(
"/proc/self/fd"
)
CheckSuccess
(
err
)
wd
,
_
,
clean
:=
setupMemUfs
(
t
)
err
=
ioutil
.
WriteFile
(
wd
+
"/ro/file"
,
[]
byte
(
"blablabla"
),
0644
)
CheckSuccess
(
err
)
contents
,
err
:=
ioutil
.
ReadFile
(
wd
+
"/mnt/file"
)
CheckSuccess
(
err
)
err
=
ioutil
.
WriteFile
(
wd
+
"/mnt/file"
,
contents
,
0644
)
CheckSuccess
(
err
)
clean
()
afterEntries
,
err
:=
ioutil
.
ReadDir
(
"/proc/self/fd"
)
CheckSuccess
(
err
)
if
len
(
afterEntries
)
!=
len
(
beforeEntries
)
{
t
.
Errorf
(
"/proc/self/fd changed size: after %v before %v"
,
len
(
beforeEntries
),
len
(
afterEntries
))
}
}
func
TestMemUnionFsStatFs
(
t
*
testing
.
T
)
{
wd
,
_
,
clean
:=
setupMemUfs
(
t
)
defer
clean
()
s1
:=
syscall
.
Statfs_t
{}
err
:=
syscall
.
Statfs
(
wd
+
"/mnt"
,
&
s1
)
if
err
!=
nil
{
t
.
Fatal
(
"statfs mnt"
,
err
)
}
if
s1
.
Bsize
==
0
{
t
.
Fatal
(
"Expect blocksize > 0"
)
}
}
func
TestMemUnionFsFlushSize
(
t
*
testing
.
T
)
{
wd
,
_
,
clean
:=
setupMemUfs
(
t
)
defer
clean
()
fn
:=
wd
+
"/mnt/file"
f
,
err
:=
os
.
OpenFile
(
fn
,
os
.
O_WRONLY
|
os
.
O_CREATE
,
0644
)
CheckSuccess
(
err
)
fi
,
err
:=
f
.
Stat
()
CheckSuccess
(
err
)
n
,
err
:=
f
.
Write
([]
byte
(
"hello"
))
CheckSuccess
(
err
)
f
.
Close
()
fi
,
err
=
os
.
Lstat
(
fn
)
CheckSuccess
(
err
)
if
fi
.
Size
!=
int64
(
n
)
{
t
.
Errorf
(
"got %d from Stat().Size, want %d"
,
fi
.
Size
,
n
)
}
}
func
TestMemUnionFsFlushRename
(
t
*
testing
.
T
)
{
wd
,
_
,
clean
:=
setupMemUfs
(
t
)
defer
clean
()
err
:=
ioutil
.
WriteFile
(
wd
+
"/mnt/file"
,
[]
byte
(
"x"
),
0644
)
fn
:=
wd
+
"/mnt/tmp"
f
,
err
:=
os
.
OpenFile
(
fn
,
os
.
O_WRONLY
|
os
.
O_CREATE
,
0644
)
CheckSuccess
(
err
)
fi
,
err
:=
f
.
Stat
()
CheckSuccess
(
err
)
n
,
err
:=
f
.
Write
([]
byte
(
"hello"
))
CheckSuccess
(
err
)
f
.
Close
()
dst
:=
wd
+
"/mnt/file"
err
=
os
.
Rename
(
fn
,
dst
)
CheckSuccess
(
err
)
fi
,
err
=
os
.
Lstat
(
dst
)
CheckSuccess
(
err
)
if
fi
.
Size
!=
int64
(
n
)
{
t
.
Errorf
(
"got %d from Stat().Size, want %d"
,
fi
.
Size
,
n
)
}
}
func
TestMemUnionFsTruncGetAttr
(
t
*
testing
.
T
)
{
wd
,
_
,
clean
:=
setupMemUfs
(
t
)
defer
clean
()
c
:=
[]
byte
(
"hello"
)
f
,
err
:=
os
.
OpenFile
(
wd
+
"/mnt/file"
,
os
.
O_CREATE
|
os
.
O_RDWR
|
os
.
O_TRUNC
,
0644
)
CheckSuccess
(
err
)
_
,
err
=
f
.
Write
(
c
)
CheckSuccess
(
err
)
err
=
f
.
Close
()
CheckSuccess
(
err
)
fi
,
err
:=
os
.
Lstat
(
wd
+
"/mnt/file"
)
if
fi
.
Size
!=
int64
(
len
(
c
))
{
t
.
Fatalf
(
"Length mismatch got %d want %d"
,
fi
.
Size
,
len
(
c
))
}
}
func
TestMemUnionFsRenameDirBasic
(
t
*
testing
.
T
)
{
wd
,
ufs
,
clean
:=
setupMemUfs
(
t
)
defer
clean
()
err
:=
os
.
MkdirAll
(
wd
+
"/ro/dir/subdir"
,
0755
)
CheckSuccess
(
err
)
err
=
os
.
Rename
(
wd
+
"/mnt/dir"
,
wd
+
"/mnt/renamed"
)
CheckSuccess
(
err
)
if
fi
,
_
:=
os
.
Lstat
(
wd
+
"/mnt/dir"
);
fi
!=
nil
{
t
.
Fatalf
(
"%s/mnt/dir should have disappeared: %v"
,
wd
,
fi
)
}
if
fi
,
_
:=
os
.
Lstat
(
wd
+
"/mnt/renamed"
);
fi
==
nil
||
!
fi
.
IsDirectory
()
{
t
.
Fatalf
(
"%s/mnt/renamed should be directory: %v"
,
wd
,
fi
)
}
entries
,
err
:=
ioutil
.
ReadDir
(
wd
+
"/mnt/renamed"
)
if
err
!=
nil
||
len
(
entries
)
!=
1
||
entries
[
0
]
.
Name
!=
"subdir"
{
t
.
Errorf
(
"readdir(%s/mnt/renamed) should have one entry: %v, err %v"
,
wd
,
entries
,
err
)
}
r
:=
ufs
.
Reap
()
if
r
[
"dir"
]
==
nil
||
r
[
"dir"
]
.
FileInfo
!=
nil
||
r
[
"renamed/subdir"
]
==
nil
||
!
r
[
"renamed/subdir"
]
.
FileInfo
.
IsDirectory
()
{
t
.
Errorf
(
"Reap should del dir, and add renamed/subdir: %v"
,
r
)
}
if
err
=
os
.
Mkdir
(
wd
+
"/mnt/dir"
,
0755
);
err
!=
nil
{
t
.
Errorf
(
"mkdir should succeed %v"
,
err
)
}
}
func
TestMemUnionFsRenameDirAllSourcesGone
(
t
*
testing
.
T
)
{
wd
,
ufs
,
clean
:=
setupMemUfs
(
t
)
defer
clean
()
err
:=
os
.
MkdirAll
(
wd
+
"/ro/dir"
,
0755
)
CheckSuccess
(
err
)
err
=
ioutil
.
WriteFile
(
wd
+
"/ro/dir/file.txt"
,
[]
byte
{
42
},
0644
)
CheckSuccess
(
err
)
err
=
os
.
Rename
(
wd
+
"/mnt/dir"
,
wd
+
"/mnt/renamed"
)
CheckSuccess
(
err
)
r
:=
ufs
.
Reap
()
if
r
[
"dir"
]
==
nil
||
r
[
"dir"
]
.
FileInfo
!=
nil
||
r
[
"dir/file.txt"
]
==
nil
||
r
[
"dir/file.txt"
]
.
FileInfo
!=
nil
{
t
.
Errorf
(
"Expected 2 deletion entries in %v"
,
r
)
}
}
func
TestMemUnionFsRenameDirWithDeletions
(
t
*
testing
.
T
)
{
wd
,
_
,
clean
:=
setupMemUfs
(
t
)
defer
clean
()
err
:=
os
.
MkdirAll
(
wd
+
"/ro/dir/subdir"
,
0755
)
CheckSuccess
(
err
)
err
=
ioutil
.
WriteFile
(
wd
+
"/ro/dir/file.txt"
,
[]
byte
{
42
},
0644
)
CheckSuccess
(
err
)
err
=
ioutil
.
WriteFile
(
wd
+
"/ro/dir/subdir/file.txt"
,
[]
byte
{
42
},
0644
)
CheckSuccess
(
err
)
if
fi
,
_
:=
os
.
Lstat
(
wd
+
"/mnt/dir/subdir/file.txt"
);
fi
==
nil
||
!
fi
.
IsRegular
()
{
t
.
Fatalf
(
"%s/mnt/dir/subdir/file.txt should be file: %v"
,
wd
,
fi
)
}
err
=
os
.
Remove
(
wd
+
"/mnt/dir/file.txt"
)
CheckSuccess
(
err
)
err
=
os
.
Rename
(
wd
+
"/mnt/dir"
,
wd
+
"/mnt/renamed"
)
CheckSuccess
(
err
)
if
fi
,
_
:=
os
.
Lstat
(
wd
+
"/mnt/dir/subdir/file.txt"
);
fi
!=
nil
{
t
.
Fatalf
(
"%s/mnt/dir/subdir/file.txt should have disappeared: %v"
,
wd
,
fi
)
}
if
fi
,
_
:=
os
.
Lstat
(
wd
+
"/mnt/dir"
);
fi
!=
nil
{
t
.
Fatalf
(
"%s/mnt/dir should have disappeared: %v"
,
wd
,
fi
)
}
if
fi
,
_
:=
os
.
Lstat
(
wd
+
"/mnt/renamed"
);
fi
==
nil
||
!
fi
.
IsDirectory
()
{
t
.
Fatalf
(
"%s/mnt/renamed should be directory: %v"
,
wd
,
fi
)
}
if
fi
,
_
:=
os
.
Lstat
(
wd
+
"/mnt/renamed/file.txt"
);
fi
!=
nil
{
t
.
Fatalf
(
"%s/mnt/renamed/file.txt should have disappeared %#v"
,
wd
,
fi
)
}
if
err
=
os
.
Mkdir
(
wd
+
"/mnt/dir"
,
0755
);
err
!=
nil
{
t
.
Errorf
(
"mkdir should succeed %v"
,
err
)
}
if
fi
,
_
:=
os
.
Lstat
(
wd
+
"/mnt/dir/subdir"
);
fi
!=
nil
{
t
.
Fatalf
(
"%s/mnt/dir/subdir should have disappeared %#v"
,
wd
,
fi
)
}
}
func
TestMemUnionGc
(
t
*
testing
.
T
)
{
wd
,
ufs
,
clean
:=
setupMemUfs
(
t
)
defer
clean
()
writeToFile
(
wd
+
"/mnt/file1"
,
"other-content"
)
writeToFile
(
wd
+
"/mnt/file2"
,
"other-content"
)
err
:=
os
.
Remove
(
wd
+
"/mnt/file1"
)
CheckSuccess
(
err
)
ufs
.
Reset
()
entries
,
err
:=
ioutil
.
ReadDir
(
wd
+
"/backing"
)
CheckSuccess
(
err
)
if
len
(
entries
)
!=
0
{
t
.
Fatalf
(
"should have 1 file after backing store gc: %v"
,
entries
)
}
}
func
testEq
(
t
*
testing
.
T
,
got
interface
{},
want
interface
{},
expectEq
bool
)
{
gots
:=
fmt
.
Sprintf
(
"%v"
,
got
)
wants
:=
fmt
.
Sprintf
(
"%v"
,
want
)
if
(
gots
==
wants
)
!=
expectEq
{
op
:=
"must differ from"
if
expectEq
{
op
=
"want"
}
t
.
Fatalf
(
"Got %s %s %s."
,
gots
,
op
,
wants
)
}
}
func
TestMemUnionResetAttr
(
t
*
testing
.
T
)
{
wd
,
ufs
,
clean
:=
setupMemUfs
(
t
)
defer
clean
()
ioutil
.
WriteFile
(
wd
+
"/ro/fileattr"
,
[]
byte
{
42
},
0644
)
before
,
_
:=
os
.
Lstat
(
wd
+
"/mnt/fileattr"
)
err
:=
os
.
Chmod
(
wd
+
"/mnt/fileattr"
,
0606
)
CheckSuccess
(
err
)
after
,
_
:=
os
.
Lstat
(
wd
+
"/mnt/fileattr"
)
testEq
(
t
,
after
,
before
,
false
)
ufs
.
Reset
()
afterReset
,
_
:=
os
.
Lstat
(
wd
+
"/mnt/fileattr"
)
testEq
(
t
,
afterReset
,
before
,
true
)
}
func
TestMemUnionResetContent
(
t
*
testing
.
T
)
{
wd
,
ufs
,
clean
:=
setupMemUfs
(
t
)
defer
clean
()
ioutil
.
WriteFile
(
wd
+
"/ro/filecontents"
,
[]
byte
{
42
},
0644
)
before
,
_
:=
ioutil
.
ReadFile
(
wd
+
"/mnt/filecontents"
)
ioutil
.
WriteFile
(
wd
+
"/mnt/filecontents"
,
[]
byte
{
43
},
0644
)
after
,
_
:=
ioutil
.
ReadFile
(
wd
+
"/mnt/filecontents"
)
testEq
(
t
,
after
,
before
,
false
)
ufs
.
Reset
()
afterReset
,
err
:=
ioutil
.
ReadFile
(
wd
+
"/mnt/filecontents"
)
CheckSuccess
(
err
)
testEq
(
t
,
afterReset
,
before
,
true
)
}
func
TestMemUnionResetDelete
(
t
*
testing
.
T
)
{
wd
,
ufs
,
clean
:=
setupMemUfs
(
t
)
defer
clean
()
ioutil
.
WriteFile
(
wd
+
"/ro/todelete"
,
[]
byte
{
42
},
0644
)
before
,
_
:=
os
.
Lstat
(
wd
+
"/mnt/todelete"
)
before
.
Ino
=
0
os
.
Remove
(
wd
+
"/mnt/todelete"
)
after
,
_
:=
os
.
Lstat
(
wd
+
"/mnt/todelete"
)
testEq
(
t
,
after
,
before
,
false
)
ufs
.
Reset
()
afterReset
,
_
:=
os
.
Lstat
(
wd
+
"/mnt/todelete"
)
afterReset
.
Ino
=
0
testEq
(
t
,
afterReset
,
before
,
true
)
}
func
clearInodes
(
infos
[]
*
os
.
FileInfo
)
{
for
_
,
i
:=
range
infos
{
i
.
Ino
=
0
}
}
func
TestMemUnionResetDirEntry
(
t
*
testing
.
T
)
{
wd
,
ufs
,
clean
:=
setupMemUfs
(
t
)
defer
clean
()
os
.
Mkdir
(
wd
+
"/ro/dir"
,
0755
)
ioutil
.
WriteFile
(
wd
+
"/ro/dir/todelete"
,
[]
byte
{
42
},
0644
)
before
,
_
:=
ioutil
.
ReadDir
(
wd
+
"/mnt/dir"
)
clearInodes
(
before
)
ioutil
.
WriteFile
(
wd
+
"/mnt/dir/newfile"
,
[]
byte
{
42
},
0644
)
os
.
Remove
(
wd
+
"/mnt/dir/todelete"
)
after
,
_
:=
ioutil
.
ReadDir
(
wd
+
"/mnt/dir"
)
clearInodes
(
after
)
testEq
(
t
,
fuse
.
OsFileInfos
(
after
),
fuse
.
OsFileInfos
(
before
),
false
)
ufs
.
Reset
()
reset
,
_
:=
ioutil
.
ReadDir
(
wd
+
"/mnt/dir"
)
clearInodes
(
reset
)
testEq
(
t
,
fuse
.
OsFileInfos
(
reset
),
fuse
.
OsFileInfos
(
before
),
true
)
}
func
TestMemUnionResetRename
(
t
*
testing
.
T
)
{
wd
,
ufs
,
clean
:=
setupMemUfs
(
t
)
defer
clean
()
os
.
Mkdir
(
wd
+
"/ro/dir"
,
0755
)
ioutil
.
WriteFile
(
wd
+
"/ro/dir/movesrc"
,
[]
byte
{
42
},
0644
)
before
,
_
:=
ioutil
.
ReadDir
(
wd
+
"/mnt/dir"
)
clearInodes
(
before
)
os
.
Rename
(
wd
+
"/mnt/dir/movesrc"
,
wd
+
"/mnt/dir/dest"
)
after
,
_
:=
ioutil
.
ReadDir
(
wd
+
"/mnt/dir"
)
clearInodes
(
after
)
testEq
(
t
,
fuse
.
OsFileInfos
(
after
),
fuse
.
OsFileInfos
(
before
),
false
)
ufs
.
Reset
()
reset
,
_
:=
ioutil
.
ReadDir
(
wd
+
"/mnt/dir"
)
clearInodes
(
reset
)
testEq
(
t
,
fuse
.
OsFileInfos
(
reset
),
fuse
.
OsFileInfos
(
before
),
true
)
}
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