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
719d3d2b
Commit
719d3d2b
authored
Jun 23, 2013
by
Han-Wen Nienhuys
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
unionfs: hide types AutoUnionFs, UnionFs, DirCache, CachingFileSystem
parent
3c0c2f7e
Changes
5
Hide whitespace changes
Inline
Side-by-side
Showing
5 changed files
with
107 additions
and
99 deletions
+107
-99
unionfs/autounion.go
unionfs/autounion.go
+43
-35
unionfs/cachingfs.go
unionfs/cachingfs.go
+11
-11
unionfs/create.go
unionfs/create.go
+1
-1
unionfs/dircache.go
unionfs/dircache.go
+11
-11
unionfs/unionfs.go
unionfs/unionfs.go
+41
-41
No files found.
unionfs/autounion.go
View file @
719d3d2b
...
...
@@ -16,8 +16,8 @@ import (
)
type
knownFs
struct
{
*
UnionFs
*
pathfs
.
PathNodeFs
unionFS
pathfs
.
FileSystem
nodeFS
*
pathfs
.
PathNodeFs
}
// Creates unions for all files under a given directory,
...
...
@@ -25,7 +25,7 @@ type knownFs struct {
// D/READONLY symlink.
//
// A union for A/B/C will placed under directory A-B-C.
type
A
utoUnionFs
struct
{
type
a
utoUnionFs
struct
{
pathfs
.
FileSystem
debug
bool
...
...
@@ -64,11 +64,19 @@ const (
_SCAN_CONFIG
=
".scan_config"
)
func
NewAutoUnionFs
(
directory
string
,
options
AutoUnionFsOptions
)
*
AutoUnionFs
{
// A pathfs.FileSystem that we can hookup with MountState and
// FileSystemConnector
type
RootFileSystem
interface
{
SetMountState
(
state
*
fuse
.
MountState
)
SetFileSystemConnector
(
conn
*
fuse
.
FileSystemConnector
)
pathfs
.
FileSystem
}
func
NewAutoUnionFs
(
directory
string
,
options
AutoUnionFsOptions
)
RootFileSystem
{
if
options
.
HideReadonly
{
options
.
HiddenFiles
=
append
(
options
.
HiddenFiles
,
_READONLY
)
}
a
:=
&
A
utoUnionFs
{
a
:=
&
a
utoUnionFs
{
knownFileSystems
:
make
(
map
[
string
]
knownFs
),
nameRootMap
:
make
(
map
[
string
]
string
),
options
:
&
options
,
...
...
@@ -83,18 +91,18 @@ func NewAutoUnionFs(directory string, options AutoUnionFsOptions) *AutoUnionFs {
return
a
}
func
(
fs
*
A
utoUnionFs
)
String
()
string
{
return
fmt
.
Sprintf
(
"
A
utoUnionFs(%s)"
,
fs
.
root
)
func
(
fs
*
a
utoUnionFs
)
String
()
string
{
return
fmt
.
Sprintf
(
"
a
utoUnionFs(%s)"
,
fs
.
root
)
}
func
(
fs
*
A
utoUnionFs
)
OnMount
(
nodeFs
*
pathfs
.
PathNodeFs
)
{
func
(
fs
*
a
utoUnionFs
)
OnMount
(
nodeFs
*
pathfs
.
PathNodeFs
)
{
fs
.
nodeFs
=
nodeFs
if
fs
.
options
.
UpdateOnMount
{
time
.
AfterFunc
(
100
*
time
.
Millisecond
,
func
()
{
fs
.
updateKnownFses
()
})
}
}
func
(
fs
*
A
utoUnionFs
)
addAutomaticFs
(
roots
[]
string
)
{
func
(
fs
*
a
utoUnionFs
)
addAutomaticFs
(
roots
[]
string
)
{
relative
:=
strings
.
TrimLeft
(
strings
.
Replace
(
roots
[
0
],
fs
.
root
,
""
,
-
1
),
"/"
)
name
:=
strings
.
Replace
(
relative
,
"/"
,
"-"
,
-
1
)
...
...
@@ -103,7 +111,7 @@ func (fs *AutoUnionFs) addAutomaticFs(roots []string) {
}
}
func
(
fs
*
A
utoUnionFs
)
createFs
(
name
string
,
roots
[]
string
)
fuse
.
Status
{
func
(
fs
*
a
utoUnionFs
)
createFs
(
name
string
,
roots
[]
string
)
fuse
.
Status
{
fs
.
lock
.
Lock
()
defer
fs
.
lock
.
Unlock
()
...
...
@@ -116,7 +124,7 @@ func (fs *AutoUnionFs) createFs(name string, roots []string) fuse.Status {
}
known
:=
fs
.
knownFileSystems
[
name
]
if
known
.
UnionFs
!=
nil
{
if
known
.
unionFS
!=
nil
{
log
.
Println
(
"Already have a workspace:"
,
name
)
return
fuse
.
EBUSY
}
...
...
@@ -140,12 +148,12 @@ func (fs *AutoUnionFs) createFs(name string, roots []string) fuse.Status {
return
code
}
func
(
fs
*
A
utoUnionFs
)
rmFs
(
name
string
)
(
code
fuse
.
Status
)
{
func
(
fs
*
a
utoUnionFs
)
rmFs
(
name
string
)
(
code
fuse
.
Status
)
{
fs
.
lock
.
Lock
()
defer
fs
.
lock
.
Unlock
()
known
:=
fs
.
knownFileSystems
[
name
]
if
known
.
UnionFs
==
nil
{
if
known
.
unionFS
==
nil
{
return
fuse
.
ENOENT
}
...
...
@@ -160,7 +168,7 @@ func (fs *AutoUnionFs) rmFs(name string) (code fuse.Status) {
return
code
}
func
(
fs
*
A
utoUnionFs
)
addFs
(
name
string
,
roots
[]
string
)
(
code
fuse
.
Status
)
{
func
(
fs
*
a
utoUnionFs
)
addFs
(
name
string
,
roots
[]
string
)
(
code
fuse
.
Status
)
{
if
name
==
_CONFIG
||
name
==
_STATUS
||
name
==
_SCAN_CONFIG
{
log
.
Printf
(
"Illegal name %q for overlay: %v"
,
name
,
roots
)
return
fuse
.
EINVAL
...
...
@@ -168,7 +176,7 @@ func (fs *AutoUnionFs) addFs(name string, roots []string) (code fuse.Status) {
return
fs
.
createFs
(
name
,
roots
)
}
func
(
fs
*
A
utoUnionFs
)
getRoots
(
path
string
)
[]
string
{
func
(
fs
*
a
utoUnionFs
)
getRoots
(
path
string
)
[]
string
{
ro
:=
filepath
.
Join
(
path
,
_READONLY
)
fi
,
err
:=
os
.
Lstat
(
ro
)
fiDir
,
errDir
:=
os
.
Stat
(
ro
)
...
...
@@ -184,7 +192,7 @@ func (fs *AutoUnionFs) getRoots(path string) []string {
return
nil
}
func
(
fs
*
A
utoUnionFs
)
visit
(
path
string
,
fi
os
.
FileInfo
,
err
error
)
error
{
func
(
fs
*
a
utoUnionFs
)
visit
(
path
string
,
fi
os
.
FileInfo
,
err
error
)
error
{
if
fi
!=
nil
&&
fi
.
IsDir
()
{
roots
:=
fs
.
getRoots
(
path
)
if
roots
!=
nil
{
...
...
@@ -194,7 +202,7 @@ func (fs *AutoUnionFs) visit(path string, fi os.FileInfo, err error) error {
return
nil
}
func
(
fs
*
A
utoUnionFs
)
updateKnownFses
()
{
func
(
fs
*
a
utoUnionFs
)
updateKnownFses
()
{
log
.
Println
(
"Looking for new filesystems"
)
// We unroll the first level of entries in the root manually in order
// to allow symbolic links on that level.
...
...
@@ -215,7 +223,7 @@ func (fs *AutoUnionFs) updateKnownFses() {
log
.
Println
(
"Done looking"
)
}
func
(
fs
*
A
utoUnionFs
)
Readlink
(
path
string
,
context
*
fuse
.
Context
)
(
out
string
,
code
fuse
.
Status
)
{
func
(
fs
*
a
utoUnionFs
)
Readlink
(
path
string
,
context
*
fuse
.
Context
)
(
out
string
,
code
fuse
.
Status
)
{
comps
:=
strings
.
Split
(
path
,
string
(
filepath
.
Separator
))
if
comps
[
0
]
==
_STATUS
&&
comps
[
1
]
==
_ROOT
{
return
fs
.
root
,
fuse
.
OK
...
...
@@ -241,13 +249,13 @@ func (fs *AutoUnionFs) Readlink(path string, context *fuse.Context) (out string,
return
""
,
fuse
.
ENOENT
}
func
(
fs
*
AutoUnionFs
)
getUnionFs
(
name
string
)
*
UnionFs
{
func
(
fs
*
autoUnionFs
)
getUnionFs
(
name
string
)
pathfs
.
FileSystem
{
fs
.
lock
.
RLock
()
defer
fs
.
lock
.
RUnlock
()
return
fs
.
knownFileSystems
[
name
]
.
UnionFs
return
fs
.
knownFileSystems
[
name
]
.
unionFS
}
func
(
fs
*
A
utoUnionFs
)
Symlink
(
pointedTo
string
,
linkName
string
,
context
*
fuse
.
Context
)
(
code
fuse
.
Status
)
{
func
(
fs
*
a
utoUnionFs
)
Symlink
(
pointedTo
string
,
linkName
string
,
context
*
fuse
.
Context
)
(
code
fuse
.
Status
)
{
comps
:=
strings
.
Split
(
linkName
,
"/"
)
if
len
(
comps
)
!=
2
{
return
fuse
.
EPERM
...
...
@@ -270,7 +278,7 @@ func (fs *AutoUnionFs) Symlink(pointedTo string, linkName string, context *fuse.
return
fuse
.
EPERM
}
func
(
fs
*
A
utoUnionFs
)
SetDebug
(
b
bool
)
{
func
(
fs
*
a
utoUnionFs
)
SetDebug
(
b
bool
)
{
// Officially, this should use locking, but we don't care
// about race conditions here.
fs
.
debug
=
b
...
...
@@ -279,11 +287,11 @@ func (fs *AutoUnionFs) SetDebug(b bool) {
fs
.
mountState
.
SetDebug
(
b
)
}
func
(
fs
*
A
utoUnionFs
)
hasDebug
()
bool
{
func
(
fs
*
a
utoUnionFs
)
hasDebug
()
bool
{
return
fs
.
debug
}
func
(
fs
*
A
utoUnionFs
)
Unlink
(
path
string
,
context
*
fuse
.
Context
)
(
code
fuse
.
Status
)
{
func
(
fs
*
a
utoUnionFs
)
Unlink
(
path
string
,
context
*
fuse
.
Context
)
(
code
fuse
.
Status
)
{
comps
:=
strings
.
Split
(
path
,
"/"
)
if
len
(
comps
)
!=
2
{
return
fuse
.
EPERM
...
...
@@ -303,11 +311,11 @@ func (fs *AutoUnionFs) Unlink(path string, context *fuse.Context) (code fuse.Sta
}
// Must define this, because ENOSYS will suspend all GetXAttr calls.
func
(
fs
*
A
utoUnionFs
)
GetXAttr
(
name
string
,
attr
string
,
context
*
fuse
.
Context
)
([]
byte
,
fuse
.
Status
)
{
func
(
fs
*
a
utoUnionFs
)
GetXAttr
(
name
string
,
attr
string
,
context
*
fuse
.
Context
)
([]
byte
,
fuse
.
Status
)
{
return
nil
,
fuse
.
ENODATA
}
func
(
fs
*
A
utoUnionFs
)
GetAttr
(
path
string
,
context
*
fuse
.
Context
)
(
*
fuse
.
Attr
,
fuse
.
Status
)
{
func
(
fs
*
a
utoUnionFs
)
GetAttr
(
path
string
,
context
*
fuse
.
Context
)
(
*
fuse
.
Attr
,
fuse
.
Status
)
{
if
path
==
""
||
path
==
_CONFIG
||
path
==
_STATUS
{
a
:=
&
fuse
.
Attr
{
Mode
:
fuse
.
S_IFDIR
|
0755
,
...
...
@@ -368,7 +376,7 @@ func (fs *AutoUnionFs) GetAttr(path string, context *fuse.Context) (*fuse.Attr,
return
nil
,
fuse
.
ENOENT
}
func
(
fs
*
A
utoUnionFs
)
StatusDir
()
(
stream
[]
fuse
.
DirEntry
,
status
fuse
.
Status
)
{
func
(
fs
*
a
utoUnionFs
)
StatusDir
()
(
stream
[]
fuse
.
DirEntry
,
status
fuse
.
Status
)
{
stream
=
make
([]
fuse
.
DirEntry
,
0
,
10
)
stream
=
[]
fuse
.
DirEntry
{
{
Name
:
_VERSION
,
Mode
:
fuse
.
S_IFREG
|
0644
},
...
...
@@ -383,17 +391,17 @@ func (fs *AutoUnionFs) StatusDir() (stream []fuse.DirEntry, status fuse.Status)
// SetMountState stores the MountState, which is necessary for
// retrieving debug data.
func
(
fs
*
A
utoUnionFs
)
SetMountState
(
state
*
fuse
.
MountState
)
{
func
(
fs
*
a
utoUnionFs
)
SetMountState
(
state
*
fuse
.
MountState
)
{
fs
.
mountState
=
state
}
func
(
fs
*
A
utoUnionFs
)
SetFileSystemConnector
(
conn
*
fuse
.
FileSystemConnector
)
{
func
(
fs
*
a
utoUnionFs
)
SetFileSystemConnector
(
conn
*
fuse
.
FileSystemConnector
)
{
fs
.
connector
=
conn
}
func
(
fs
*
A
utoUnionFs
)
DebugData
()
string
{
func
(
fs
*
a
utoUnionFs
)
DebugData
()
string
{
if
fs
.
mountState
==
nil
{
return
"
A
utoUnionFs.mountState not set"
return
"
a
utoUnionFs.mountState not set"
}
setting
:=
fs
.
mountState
.
KernelSettings
()
msg
:=
fmt
.
Sprintf
(
...
...
@@ -411,7 +419,7 @@ func (fs *AutoUnionFs) DebugData() string {
return
msg
}
func
(
fs
*
A
utoUnionFs
)
Open
(
path
string
,
flags
uint32
,
context
*
fuse
.
Context
)
(
fuse
.
File
,
fuse
.
Status
)
{
func
(
fs
*
a
utoUnionFs
)
Open
(
path
string
,
flags
uint32
,
context
*
fuse
.
Context
)
(
fuse
.
File
,
fuse
.
Status
)
{
if
path
==
filepath
.
Join
(
_STATUS
,
_DEBUG
)
{
if
flags
&
fuse
.
O_ANYWRITE
!=
0
{
return
nil
,
fuse
.
EPERM
...
...
@@ -434,7 +442,7 @@ func (fs *AutoUnionFs) Open(path string, flags uint32, context *fuse.Context) (f
return
nil
,
fuse
.
ENOENT
}
func
(
fs
*
A
utoUnionFs
)
Truncate
(
name
string
,
offset
uint64
,
context
*
fuse
.
Context
)
(
code
fuse
.
Status
)
{
func
(
fs
*
a
utoUnionFs
)
Truncate
(
name
string
,
offset
uint64
,
context
*
fuse
.
Context
)
(
code
fuse
.
Status
)
{
if
name
!=
filepath
.
Join
(
_CONFIG
,
_SCAN_CONFIG
)
{
log
.
Println
(
"Huh? Truncating unsupported write file"
,
name
)
return
fuse
.
EPERM
...
...
@@ -442,7 +450,7 @@ func (fs *AutoUnionFs) Truncate(name string, offset uint64, context *fuse.Contex
return
fuse
.
OK
}
func
(
fs
*
A
utoUnionFs
)
OpenDir
(
name
string
,
context
*
fuse
.
Context
)
(
stream
[]
fuse
.
DirEntry
,
status
fuse
.
Status
)
{
func
(
fs
*
a
utoUnionFs
)
OpenDir
(
name
string
,
context
*
fuse
.
Context
)
(
stream
[]
fuse
.
DirEntry
,
status
fuse
.
Status
)
{
switch
name
{
case
_STATUS
:
return
fs
.
StatusDir
()
...
...
@@ -481,6 +489,6 @@ func (fs *AutoUnionFs) OpenDir(name string, context *fuse.Context) (stream []fus
return
stream
,
status
}
func
(
fs
*
A
utoUnionFs
)
StatFs
(
name
string
)
*
fuse
.
StatfsOut
{
func
(
fs
*
a
utoUnionFs
)
StatFs
(
name
string
)
*
fuse
.
StatfsOut
{
return
&
fuse
.
StatfsOut
{}
}
unionfs/cachingfs.go
View file @
719d3d2b
...
...
@@ -35,7 +35,7 @@ type linkResponse struct {
}
// Caches filesystem metadata.
type
C
achingFileSystem
struct
{
type
c
achingFileSystem
struct
{
pathfs
.
FileSystem
attributes
*
TimedCache
...
...
@@ -80,8 +80,8 @@ func readLink(fs pathfs.FileSystem, name string) *linkResponse {
}
}
func
NewCachingFileSystem
(
fs
pathfs
.
FileSystem
,
ttl
time
.
Duration
)
*
Caching
FileSystem
{
c
:=
new
(
C
achingFileSystem
)
func
NewCachingFileSystem
(
fs
pathfs
.
FileSystem
,
ttl
time
.
Duration
)
pathfs
.
FileSystem
{
c
:=
new
(
c
achingFileSystem
)
c
.
FileSystem
=
fs
c
.
attributes
=
NewTimedCache
(
func
(
n
string
)
(
interface
{},
bool
)
{
a
:=
getAttr
(
fs
,
n
)
...
...
@@ -102,13 +102,13 @@ func NewCachingFileSystem(fs pathfs.FileSystem, ttl time.Duration) *CachingFileS
return
c
}
func
(
fs
*
C
achingFileSystem
)
DropCache
()
{
func
(
fs
*
c
achingFileSystem
)
DropCache
()
{
for
_
,
c
:=
range
[]
*
TimedCache
{
fs
.
attributes
,
fs
.
dirs
,
fs
.
links
,
fs
.
xattr
}
{
c
.
DropAll
(
nil
)
}
}
func
(
fs
*
C
achingFileSystem
)
GetAttr
(
name
string
,
context
*
fuse
.
Context
)
(
*
fuse
.
Attr
,
fuse
.
Status
)
{
func
(
fs
*
c
achingFileSystem
)
GetAttr
(
name
string
,
context
*
fuse
.
Context
)
(
*
fuse
.
Attr
,
fuse
.
Status
)
{
if
name
==
_DROP_CACHE
{
return
&
fuse
.
Attr
{
Mode
:
fuse
.
S_IFREG
|
0777
,
...
...
@@ -119,27 +119,27 @@ func (fs *CachingFileSystem) GetAttr(name string, context *fuse.Context) (*fuse.
return
r
.
Attr
,
r
.
Status
}
func
(
fs
*
C
achingFileSystem
)
GetXAttr
(
name
string
,
attr
string
,
context
*
fuse
.
Context
)
([]
byte
,
fuse
.
Status
)
{
func
(
fs
*
c
achingFileSystem
)
GetXAttr
(
name
string
,
attr
string
,
context
*
fuse
.
Context
)
([]
byte
,
fuse
.
Status
)
{
key
:=
name
+
_XATTRSEP
+
attr
r
:=
fs
.
xattr
.
Get
(
key
)
.
(
*
xattrResponse
)
return
r
.
data
,
r
.
Status
}
func
(
fs
*
C
achingFileSystem
)
Readlink
(
name
string
,
context
*
fuse
.
Context
)
(
string
,
fuse
.
Status
)
{
func
(
fs
*
c
achingFileSystem
)
Readlink
(
name
string
,
context
*
fuse
.
Context
)
(
string
,
fuse
.
Status
)
{
r
:=
fs
.
links
.
Get
(
name
)
.
(
*
linkResponse
)
return
r
.
linkContent
,
r
.
Status
}
func
(
fs
*
C
achingFileSystem
)
OpenDir
(
name
string
,
context
*
fuse
.
Context
)
(
stream
[]
fuse
.
DirEntry
,
status
fuse
.
Status
)
{
func
(
fs
*
c
achingFileSystem
)
OpenDir
(
name
string
,
context
*
fuse
.
Context
)
(
stream
[]
fuse
.
DirEntry
,
status
fuse
.
Status
)
{
r
:=
fs
.
dirs
.
Get
(
name
)
.
(
*
dirResponse
)
return
r
.
entries
,
r
.
Status
}
func
(
fs
*
C
achingFileSystem
)
String
()
string
{
return
fmt
.
Sprintf
(
"
C
achingFileSystem(%v)"
,
fs
.
FileSystem
)
func
(
fs
*
c
achingFileSystem
)
String
()
string
{
return
fmt
.
Sprintf
(
"
c
achingFileSystem(%v)"
,
fs
.
FileSystem
)
}
func
(
fs
*
C
achingFileSystem
)
Open
(
name
string
,
flags
uint32
,
context
*
fuse
.
Context
)
(
f
fuse
.
File
,
status
fuse
.
Status
)
{
func
(
fs
*
c
achingFileSystem
)
Open
(
name
string
,
flags
uint32
,
context
*
fuse
.
Context
)
(
f
fuse
.
File
,
status
fuse
.
Status
)
{
if
flags
&
fuse
.
O_ANYWRITE
!=
0
&&
name
==
_DROP_CACHE
{
log
.
Println
(
"Dropping cache for"
,
fs
)
fs
.
DropCache
()
...
...
unionfs/create.go
View file @
719d3d2b
...
...
@@ -6,7 +6,7 @@ import (
"github.com/hanwen/go-fuse/fuse/pathfs"
)
func
NewUnionFsFromRoots
(
roots
[]
string
,
opts
*
UnionFsOptions
,
roCaching
bool
)
(
*
UnionFs
,
error
)
{
func
NewUnionFsFromRoots
(
roots
[]
string
,
opts
*
UnionFsOptions
,
roCaching
bool
)
(
pathfs
.
FileSystem
,
error
)
{
fses
:=
make
([]
pathfs
.
FileSystem
,
0
)
for
i
,
r
:=
range
roots
{
var
fs
pathfs
.
FileSystem
...
...
unionfs/dircache.go
View file @
719d3d2b
...
...
@@ -10,7 +10,7 @@ import (
)
// newDirnameMap reads the contents of the given directory. On error,
// returns a nil map. This forces reloads in the
D
irCache until we
// returns a nil map. This forces reloads in the
d
irCache until we
// succeed.
func
newDirnameMap
(
fs
pathfs
.
FileSystem
,
dir
string
)
map
[
string
]
bool
{
stream
,
code
:=
fs
.
OpenDir
(
dir
,
nil
)
...
...
@@ -33,11 +33,11 @@ func newDirnameMap(fs pathfs.FileSystem, dir string) map[string]bool {
return
result
}
//
D
irCache caches names in a directory for some time.
//
d
irCache caches names in a directory for some time.
//
// If called when the cache is expired, the filenames are read afresh in
// the background.
type
D
irCache
struct
{
type
d
irCache
struct
{
dir
string
ttl
time
.
Duration
fs
pathfs
.
FileSystem
...
...
@@ -49,7 +49,7 @@ type DirCache struct {
updateRunning
bool
}
func
(
c
*
D
irCache
)
setMap
(
newMap
map
[
string
]
bool
)
{
func
(
c
*
d
irCache
)
setMap
(
newMap
map
[
string
]
bool
)
{
c
.
lock
.
Lock
()
defer
c
.
lock
.
Unlock
()
...
...
@@ -59,7 +59,7 @@ func (c *DirCache) setMap(newMap map[string]bool) {
func
()
{
c
.
DropCache
()
})
}
func
(
c
*
D
irCache
)
DropCache
()
{
func
(
c
*
d
irCache
)
DropCache
()
{
c
.
lock
.
Lock
()
defer
c
.
lock
.
Unlock
()
c
.
names
=
nil
...
...
@@ -67,7 +67,7 @@ func (c *DirCache) DropCache() {
// Try to refresh: if another update is already running, do nothing,
// otherwise, read the directory and set it.
func
(
c
*
D
irCache
)
maybeRefresh
()
{
func
(
c
*
d
irCache
)
maybeRefresh
()
{
c
.
lock
.
Lock
()
defer
c
.
lock
.
Unlock
()
if
c
.
updateRunning
{
...
...
@@ -80,7 +80,7 @@ func (c *DirCache) maybeRefresh() {
}()
}
func
(
c
*
D
irCache
)
RemoveEntry
(
name
string
)
{
func
(
c
*
d
irCache
)
RemoveEntry
(
name
string
)
{
c
.
lock
.
Lock
()
defer
c
.
lock
.
Unlock
()
if
c
.
names
==
nil
{
...
...
@@ -91,7 +91,7 @@ func (c *DirCache) RemoveEntry(name string) {
delete
(
c
.
names
,
name
)
}
func
(
c
*
D
irCache
)
AddEntry
(
name
string
)
{
func
(
c
*
d
irCache
)
AddEntry
(
name
string
)
{
c
.
lock
.
Lock
()
defer
c
.
lock
.
Unlock
()
if
c
.
names
==
nil
{
...
...
@@ -102,15 +102,15 @@ func (c *DirCache) AddEntry(name string) {
c
.
names
[
name
]
=
true
}
func
NewDirCache
(
fs
pathfs
.
FileSystem
,
dir
string
,
ttl
time
.
Duration
)
*
D
irCache
{
dc
:=
new
(
D
irCache
)
func
newDirCache
(
fs
pathfs
.
FileSystem
,
dir
string
,
ttl
time
.
Duration
)
*
d
irCache
{
dc
:=
new
(
d
irCache
)
dc
.
dir
=
dir
dc
.
fs
=
fs
dc
.
ttl
=
ttl
return
dc
}
func
(
c
*
D
irCache
)
HasEntry
(
name
string
)
(
mapPresent
bool
,
found
bool
)
{
func
(
c
*
d
irCache
)
HasEntry
(
name
string
)
(
mapPresent
bool
,
found
bool
)
{
c
.
lock
.
RLock
()
defer
c
.
lock
.
RUnlock
()
...
...
unionfs/unionfs.go
View file @
719d3d2b
...
...
@@ -56,14 +56,14 @@ func filePathHash(path string) string {
answer is-deleted queries quickly.
*/
type
UnionFs
struct
{
type
unionFS
struct
{
pathfs
.
FileSystem
// The same, but as interfaces.
fileSystems
[]
pathfs
.
FileSystem
// A file-existence cache.
deletionCache
*
D
irCache
deletionCache
*
d
irCache
// A file -> branch cache.
branchCache
*
TimedCache
...
...
@@ -86,8 +86,8 @@ const (
_DROP_CACHE
=
".drop_cache"
)
func
NewUnionFs
(
fileSystems
[]
pathfs
.
FileSystem
,
options
UnionFsOptions
)
*
UnionFs
{
g
:=
&
UnionFs
{
func
NewUnionFs
(
fileSystems
[]
pathfs
.
FileSystem
,
options
UnionFsOptions
)
pathfs
.
FileSystem
{
g
:=
&
unionFS
{
options
:
&
options
,
fileSystems
:
fileSystems
,
FileSystem
:
pathfs
.
NewDefaultFileSystem
(),
...
...
@@ -100,7 +100,7 @@ func NewUnionFs(fileSystems []pathfs.FileSystem, options UnionFsOptions) *UnionF
return
nil
}
g
.
deletionCache
=
N
ewDirCache
(
writable
,
options
.
DeletionDirName
,
options
.
DeletionCacheTTL
)
g
.
deletionCache
=
n
ewDirCache
(
writable
,
options
.
DeletionDirName
,
options
.
DeletionCacheTTL
)
g
.
branchCache
=
NewTimedCache
(
func
(
n
string
)
(
interface
{},
bool
)
{
return
g
.
getBranchAttrNoCache
(
n
),
true
},
options
.
BranchCacheTTL
)
...
...
@@ -113,7 +113,7 @@ func NewUnionFs(fileSystems []pathfs.FileSystem, options UnionFsOptions) *UnionF
return
g
}
func
(
fs
*
UnionFs
)
OnMount
(
nodeFs
*
pathfs
.
PathNodeFs
)
{
func
(
fs
*
unionFS
)
OnMount
(
nodeFs
*
pathfs
.
PathNodeFs
)
{
fs
.
nodeFs
=
nodeFs
}
...
...
@@ -122,7 +122,7 @@ func (fs *UnionFs) OnMount(nodeFs *pathfs.PathNodeFs) {
// The isDeleted() method tells us if a path has a marker in the deletion store.
// It may return an error code if the store could not be accessed.
func
(
fs
*
UnionFs
)
isDeleted
(
name
string
)
(
deleted
bool
,
code
fuse
.
Status
)
{
func
(
fs
*
unionFS
)
isDeleted
(
name
string
)
(
deleted
bool
,
code
fuse
.
Status
)
{
marker
:=
fs
.
deletionPath
(
name
)
haveCache
,
found
:=
fs
.
deletionCache
.
HasEntry
(
filepath
.
Base
(
marker
))
if
haveCache
{
...
...
@@ -142,7 +142,7 @@ func (fs *UnionFs) isDeleted(name string) (deleted bool, code fuse.Status) {
return
false
,
fuse
.
Status
(
syscall
.
EROFS
)
}
func
(
fs
*
UnionFs
)
createDeletionStore
()
(
code
fuse
.
Status
)
{
func
(
fs
*
unionFS
)
createDeletionStore
()
(
code
fuse
.
Status
)
{
writable
:=
fs
.
fileSystems
[
0
]
fi
,
code
:=
writable
.
GetAttr
(
fs
.
options
.
DeletionDirName
,
nil
)
if
code
==
fuse
.
ENOENT
{
...
...
@@ -159,7 +159,7 @@ func (fs *UnionFs) createDeletionStore() (code fuse.Status) {
return
code
}
func
(
fs
*
UnionFs
)
getBranch
(
name
string
)
branchResult
{
func
(
fs
*
unionFS
)
getBranch
(
name
string
)
branchResult
{
name
=
stripSlash
(
name
)
r
:=
fs
.
branchCache
.
Get
(
name
)
return
r
.
(
branchResult
)
...
...
@@ -175,7 +175,7 @@ func (fs branchResult) String() string {
return
fmt
.
Sprintf
(
"{%v %v branch %d}"
,
fs
.
attr
,
fs
.
code
,
fs
.
branch
)
}
func
(
fs
*
UnionFs
)
getBranchAttrNoCache
(
name
string
)
branchResult
{
func
(
fs
*
unionFS
)
getBranchAttrNoCache
(
name
string
)
branchResult
{
name
=
stripSlash
(
name
)
parent
,
base
:=
path
.
Split
(
name
)
...
...
@@ -213,11 +213,11 @@ func (fs *UnionFs) getBranchAttrNoCache(name string) branchResult {
////////////////
// Deletion.
func
(
fs
*
UnionFs
)
deletionPath
(
name
string
)
string
{
func
(
fs
*
unionFS
)
deletionPath
(
name
string
)
string
{
return
filepath
.
Join
(
fs
.
options
.
DeletionDirName
,
filePathHash
(
name
))
}
func
(
fs
*
UnionFs
)
removeDeletion
(
name
string
)
{
func
(
fs
*
unionFS
)
removeDeletion
(
name
string
)
{
marker
:=
fs
.
deletionPath
(
name
)
fs
.
deletionCache
.
RemoveEntry
(
path
.
Base
(
marker
))
...
...
@@ -231,7 +231,7 @@ func (fs *UnionFs) removeDeletion(name string) {
}
}
func
(
fs
*
UnionFs
)
putDeletion
(
name
string
)
(
code
fuse
.
Status
)
{
func
(
fs
*
unionFS
)
putDeletion
(
name
string
)
(
code
fuse
.
Status
)
{
code
=
fs
.
createDeletionStore
()
if
!
code
.
Ok
()
{
return
code
...
...
@@ -271,7 +271,7 @@ func (fs *UnionFs) putDeletion(name string) (code fuse.Status) {
////////////////
// Promotion.
func
(
fs
*
UnionFs
)
Promote
(
name
string
,
srcResult
branchResult
,
context
*
fuse
.
Context
)
(
code
fuse
.
Status
)
{
func
(
fs
*
unionFS
)
Promote
(
name
string
,
srcResult
branchResult
,
context
*
fuse
.
Context
)
(
code
fuse
.
Status
)
{
writable
:=
fs
.
fileSystems
[
0
]
sourceFs
:=
fs
.
fileSystems
[
srcResult
.
branch
]
...
...
@@ -347,7 +347,7 @@ func (fs *UnionFs) Promote(name string, srcResult branchResult, context *fuse.Co
////////////////////////////////////////////////////////////////
// Below: implement interface for a FileSystem.
func
(
fs
*
UnionFs
)
Link
(
orig
string
,
newName
string
,
context
*
fuse
.
Context
)
(
code
fuse
.
Status
)
{
func
(
fs
*
unionFS
)
Link
(
orig
string
,
newName
string
,
context
*
fuse
.
Context
)
(
code
fuse
.
Status
)
{
origResult
:=
fs
.
getBranch
(
orig
)
code
=
origResult
.
code
if
code
.
Ok
()
&&
origResult
.
branch
>
0
{
...
...
@@ -377,7 +377,7 @@ func (fs *UnionFs) Link(orig string, newName string, context *fuse.Context) (cod
return
code
}
func
(
fs
*
UnionFs
)
Rmdir
(
path
string
,
context
*
fuse
.
Context
)
(
code
fuse
.
Status
)
{
func
(
fs
*
unionFS
)
Rmdir
(
path
string
,
context
*
fuse
.
Context
)
(
code
fuse
.
Status
)
{
r
:=
fs
.
getBranch
(
path
)
if
r
.
code
!=
fuse
.
OK
{
return
r
.
code
...
...
@@ -411,7 +411,7 @@ func (fs *UnionFs) Rmdir(path string, context *fuse.Context) (code fuse.Status)
return
code
}
func
(
fs
*
UnionFs
)
Mkdir
(
path
string
,
mode
uint32
,
context
*
fuse
.
Context
)
(
code
fuse
.
Status
)
{
func
(
fs
*
unionFS
)
Mkdir
(
path
string
,
mode
uint32
,
context
*
fuse
.
Context
)
(
code
fuse
.
Status
)
{
deleted
,
code
:=
fs
.
isDeleted
(
path
)
if
!
code
.
Ok
()
{
return
code
...
...
@@ -448,7 +448,7 @@ func (fs *UnionFs) Mkdir(path string, mode uint32, context *fuse.Context) (code
return
code
}
func
(
fs
*
UnionFs
)
Symlink
(
pointedTo
string
,
linkName
string
,
context
*
fuse
.
Context
)
(
code
fuse
.
Status
)
{
func
(
fs
*
unionFS
)
Symlink
(
pointedTo
string
,
linkName
string
,
context
*
fuse
.
Context
)
(
code
fuse
.
Status
)
{
code
=
fs
.
promoteDirsTo
(
linkName
)
if
code
.
Ok
()
{
code
=
fs
.
fileSystems
[
0
]
.
Symlink
(
pointedTo
,
linkName
,
context
)
...
...
@@ -460,7 +460,7 @@ func (fs *UnionFs) Symlink(pointedTo string, linkName string, context *fuse.Cont
return
code
}
func
(
fs
*
UnionFs
)
Truncate
(
path
string
,
size
uint64
,
context
*
fuse
.
Context
)
(
code
fuse
.
Status
)
{
func
(
fs
*
unionFS
)
Truncate
(
path
string
,
size
uint64
,
context
*
fuse
.
Context
)
(
code
fuse
.
Status
)
{
if
path
==
_DROP_CACHE
{
return
fuse
.
OK
}
...
...
@@ -483,7 +483,7 @@ func (fs *UnionFs) Truncate(path string, size uint64, context *fuse.Context) (co
return
code
}
func
(
fs
*
UnionFs
)
Utimens
(
name
string
,
atime
*
time
.
Time
,
mtime
*
time
.
Time
,
context
*
fuse
.
Context
)
(
code
fuse
.
Status
)
{
func
(
fs
*
unionFS
)
Utimens
(
name
string
,
atime
*
time
.
Time
,
mtime
*
time
.
Time
,
context
*
fuse
.
Context
)
(
code
fuse
.
Status
)
{
name
=
stripSlash
(
name
)
r
:=
fs
.
getBranch
(
name
)
...
...
@@ -503,7 +503,7 @@ func (fs *UnionFs) Utimens(name string, atime *time.Time, mtime *time.Time, cont
return
code
}
func
(
fs
*
UnionFs
)
Chown
(
name
string
,
uid
uint32
,
gid
uint32
,
context
*
fuse
.
Context
)
(
code
fuse
.
Status
)
{
func
(
fs
*
unionFS
)
Chown
(
name
string
,
uid
uint32
,
gid
uint32
,
context
*
fuse
.
Context
)
(
code
fuse
.
Status
)
{
name
=
stripSlash
(
name
)
r
:=
fs
.
getBranch
(
name
)
if
r
.
attr
==
nil
||
r
.
code
!=
fuse
.
OK
{
...
...
@@ -532,7 +532,7 @@ func (fs *UnionFs) Chown(name string, uid uint32, gid uint32, context *fuse.Cont
return
fuse
.
OK
}
func
(
fs
*
UnionFs
)
Chmod
(
name
string
,
mode
uint32
,
context
*
fuse
.
Context
)
(
code
fuse
.
Status
)
{
func
(
fs
*
unionFS
)
Chmod
(
name
string
,
mode
uint32
,
context
*
fuse
.
Context
)
(
code
fuse
.
Status
)
{
name
=
stripSlash
(
name
)
r
:=
fs
.
getBranch
(
name
)
if
r
.
attr
==
nil
{
...
...
@@ -564,7 +564,7 @@ func (fs *UnionFs) Chmod(name string, mode uint32, context *fuse.Context) (code
return
fuse
.
OK
}
func
(
fs
*
UnionFs
)
Access
(
name
string
,
mode
uint32
,
context
*
fuse
.
Context
)
(
code
fuse
.
Status
)
{
func
(
fs
*
unionFS
)
Access
(
name
string
,
mode
uint32
,
context
*
fuse
.
Context
)
(
code
fuse
.
Status
)
{
// We always allow writing.
mode
=
mode
&^
raw
.
W_OK
if
name
==
""
{
...
...
@@ -577,7 +577,7 @@ func (fs *UnionFs) Access(name string, mode uint32, context *fuse.Context) (code
return
fuse
.
ENOENT
}
func
(
fs
*
UnionFs
)
Unlink
(
name
string
,
context
*
fuse
.
Context
)
(
code
fuse
.
Status
)
{
func
(
fs
*
unionFS
)
Unlink
(
name
string
,
context
*
fuse
.
Context
)
(
code
fuse
.
Status
)
{
r
:=
fs
.
getBranch
(
name
)
if
r
.
branch
==
0
{
code
=
fs
.
fileSystems
[
0
]
.
Unlink
(
name
,
context
)
...
...
@@ -594,7 +594,7 @@ func (fs *UnionFs) Unlink(name string, context *fuse.Context) (code fuse.Status)
return
code
}
func
(
fs
*
UnionFs
)
Readlink
(
name
string
,
context
*
fuse
.
Context
)
(
out
string
,
code
fuse
.
Status
)
{
func
(
fs
*
unionFS
)
Readlink
(
name
string
,
context
*
fuse
.
Context
)
(
out
string
,
code
fuse
.
Status
)
{
r
:=
fs
.
getBranch
(
name
)
if
r
.
branch
>=
0
{
return
fs
.
fileSystems
[
r
.
branch
]
.
Readlink
(
name
,
context
)
...
...
@@ -611,7 +611,7 @@ func stripSlash(fn string) string {
return
strings
.
TrimRight
(
fn
,
string
(
filepath
.
Separator
))
}
func
(
fs
*
UnionFs
)
promoteDirsTo
(
filename
string
)
fuse
.
Status
{
func
(
fs
*
unionFS
)
promoteDirsTo
(
filename
string
)
fuse
.
Status
{
dirName
,
_
:=
filepath
.
Split
(
filename
)
dirName
=
stripSlash
(
dirName
)
...
...
@@ -655,7 +655,7 @@ func (fs *UnionFs) promoteDirsTo(filename string) fuse.Status {
return
fuse
.
OK
}
func
(
fs
*
UnionFs
)
Create
(
name
string
,
flags
uint32
,
mode
uint32
,
context
*
fuse
.
Context
)
(
fuseFile
fuse
.
File
,
code
fuse
.
Status
)
{
func
(
fs
*
unionFS
)
Create
(
name
string
,
flags
uint32
,
mode
uint32
,
context
*
fuse
.
Context
)
(
fuseFile
fuse
.
File
,
code
fuse
.
Status
)
{
writable
:=
fs
.
fileSystems
[
0
]
code
=
fs
.
promoteDirsTo
(
name
)
...
...
@@ -677,7 +677,7 @@ func (fs *UnionFs) Create(name string, flags uint32, mode uint32, context *fuse.
return
fuseFile
,
code
}
func
(
fs
*
UnionFs
)
GetAttr
(
name
string
,
context
*
fuse
.
Context
)
(
a
*
fuse
.
Attr
,
s
fuse
.
Status
)
{
func
(
fs
*
unionFS
)
GetAttr
(
name
string
,
context
*
fuse
.
Context
)
(
a
*
fuse
.
Attr
,
s
fuse
.
Status
)
{
_
,
hidden
:=
fs
.
hiddenFiles
[
name
]
if
hidden
{
return
nil
,
fuse
.
ENOENT
...
...
@@ -708,7 +708,7 @@ func (fs *UnionFs) GetAttr(name string, context *fuse.Context) (a *fuse.Attr, s
return
&
fi
,
r
.
code
}
func
(
fs
*
UnionFs
)
GetXAttr
(
name
string
,
attr
string
,
context
*
fuse
.
Context
)
([]
byte
,
fuse
.
Status
)
{
func
(
fs
*
unionFS
)
GetXAttr
(
name
string
,
attr
string
,
context
*
fuse
.
Context
)
([]
byte
,
fuse
.
Status
)
{
if
name
==
_DROP_CACHE
{
return
nil
,
fuse
.
ENODATA
}
...
...
@@ -720,7 +720,7 @@ func (fs *UnionFs) GetXAttr(name string, attr string, context *fuse.Context) ([]
return
nil
,
fuse
.
ENOENT
}
func
(
fs
*
UnionFs
)
OpenDir
(
directory
string
,
context
*
fuse
.
Context
)
(
stream
[]
fuse
.
DirEntry
,
status
fuse
.
Status
)
{
func
(
fs
*
unionFS
)
OpenDir
(
directory
string
,
context
*
fuse
.
Context
)
(
stream
[]
fuse
.
DirEntry
,
status
fuse
.
Status
)
{
dirBranch
:=
fs
.
getBranch
(
directory
)
if
dirBranch
.
branch
<
0
{
return
nil
,
fuse
.
ENOENT
...
...
@@ -812,7 +812,7 @@ func (fs *UnionFs) OpenDir(directory string, context *fuse.Context) (stream []fu
// recursivePromote promotes path, and if a directory, everything
// below that directory. It returns a list of all promoted paths, in
// full, including the path itself.
func
(
fs
*
UnionFs
)
recursivePromote
(
path
string
,
pathResult
branchResult
,
context
*
fuse
.
Context
)
(
names
[]
string
,
code
fuse
.
Status
)
{
func
(
fs
*
unionFS
)
recursivePromote
(
path
string
,
pathResult
branchResult
,
context
*
fuse
.
Context
)
(
names
[]
string
,
code
fuse
.
Status
)
{
names
=
[]
string
{}
if
pathResult
.
branch
>
0
{
code
=
fs
.
Promote
(
path
,
pathResult
,
context
)
...
...
@@ -843,7 +843,7 @@ func (fs *UnionFs) recursivePromote(path string, pathResult branchResult, contex
return
names
,
code
}
func
(
fs
*
UnionFs
)
renameDirectory
(
srcResult
branchResult
,
srcDir
string
,
dstDir
string
,
context
*
fuse
.
Context
)
(
code
fuse
.
Status
)
{
func
(
fs
*
unionFS
)
renameDirectory
(
srcResult
branchResult
,
srcDir
string
,
dstDir
string
,
context
*
fuse
.
Context
)
(
code
fuse
.
Status
)
{
names
:=
[]
string
{}
if
code
.
Ok
()
{
names
,
code
=
fs
.
recursivePromote
(
srcDir
,
srcResult
,
context
)
...
...
@@ -876,7 +876,7 @@ func (fs *UnionFs) renameDirectory(srcResult branchResult, srcDir string, dstDir
return
code
}
func
(
fs
*
UnionFs
)
Rename
(
src
string
,
dst
string
,
context
*
fuse
.
Context
)
(
code
fuse
.
Status
)
{
func
(
fs
*
unionFS
)
Rename
(
src
string
,
dst
string
,
context
*
fuse
.
Context
)
(
code
fuse
.
Status
)
{
srcResult
:=
fs
.
getBranch
(
src
)
code
=
srcResult
.
code
if
code
.
Ok
()
{
...
...
@@ -910,15 +910,15 @@ func (fs *UnionFs) Rename(src string, dst string, context *fuse.Context) (code f
return
code
}
func
(
fs
*
UnionFs
)
DropBranchCache
(
names
[]
string
)
{
func
(
fs
*
unionFS
)
DropBranchCache
(
names
[]
string
)
{
fs
.
branchCache
.
DropAll
(
names
)
}
func
(
fs
*
UnionFs
)
DropDeletionCache
()
{
func
(
fs
*
unionFS
)
DropDeletionCache
()
{
fs
.
deletionCache
.
DropCache
()
}
func
(
fs
*
UnionFs
)
DropSubFsCaches
()
{
func
(
fs
*
unionFS
)
DropSubFsCaches
()
{
for
_
,
fs
:=
range
fs
.
fileSystems
{
a
,
code
:=
fs
.
GetAttr
(
_DROP_CACHE
,
nil
)
if
code
.
Ok
()
&&
a
.
IsRegular
()
{
...
...
@@ -931,7 +931,7 @@ func (fs *UnionFs) DropSubFsCaches() {
}
}
func
(
fs
*
UnionFs
)
Open
(
name
string
,
flags
uint32
,
context
*
fuse
.
Context
)
(
fuseFile
fuse
.
File
,
status
fuse
.
Status
)
{
func
(
fs
*
unionFS
)
Open
(
name
string
,
flags
uint32
,
context
*
fuse
.
Context
)
(
fuseFile
fuse
.
File
,
status
fuse
.
Status
)
{
if
name
==
_DROP_CACHE
{
if
flags
&
fuse
.
O_ANYWRITE
!=
0
{
log
.
Println
(
"Forced cache drop on"
,
fs
)
...
...
@@ -966,7 +966,7 @@ func (fs *UnionFs) Open(name string, flags uint32, context *fuse.Context) (fuseF
return
fuseFile
,
status
}
func
(
fs
*
UnionFs
)
String
()
string
{
func
(
fs
*
unionFS
)
String
()
string
{
names
:=
[]
string
{}
for
_
,
fs
:=
range
fs
.
fileSystems
{
names
=
append
(
names
,
fs
.
String
())
...
...
@@ -974,13 +974,13 @@ func (fs *UnionFs) String() string {
return
fmt
.
Sprintf
(
"UnionFs(%v)"
,
names
)
}
func
(
fs
*
UnionFs
)
StatFs
(
name
string
)
*
fuse
.
StatfsOut
{
func
(
fs
*
unionFS
)
StatFs
(
name
string
)
*
fuse
.
StatfsOut
{
return
fs
.
fileSystems
[
0
]
.
StatFs
(
""
)
}
type
unionFsFile
struct
{
fuse
.
File
ufs
*
UnionFs
ufs
*
unionFS
node
*
fuse
.
Inode
layer
int
}
...
...
@@ -989,7 +989,7 @@ func (fs *unionFsFile) String() string {
return
fmt
.
Sprintf
(
"unionFsFile(%s)"
,
fs
.
File
.
String
())
}
func
(
fs
*
UnionFs
)
newUnionFsFile
(
f
fuse
.
File
,
branch
int
)
*
unionFsFile
{
func
(
fs
*
unionFS
)
newUnionFsFile
(
f
fuse
.
File
,
branch
int
)
*
unionFsFile
{
return
&
unionFsFile
{
File
:
f
,
ufs
:
fs
,
...
...
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