mirror of https://github.com/perkeep/perkeep.git
fs: implement symlinks
Change-Id: Ia2ae00c6e4a5b84239ae4807d31141fdb4f6c220
This commit is contained in:
parent
f709fc930c
commit
dbfbc0c1e5
|
@ -61,6 +61,12 @@ func condSkip(t *testing.T) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func brokenTest(t *testing.T) {
|
||||||
|
if v, _ := strconv.ParseBool(os.Getenv("RUN_BROKEN_TESTS")); !v {
|
||||||
|
t.Skipf("Skipping broken tests without RUN_BROKEN_TESTS=1")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
type mountEnv struct {
|
type mountEnv struct {
|
||||||
t *testing.T
|
t *testing.T
|
||||||
mountPoint string
|
mountPoint string
|
||||||
|
@ -395,10 +401,45 @@ func TestRename(t *testing.T) {
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
func brokenTest(t *testing.T) {
|
func TestSymlink(t *testing.T) {
|
||||||
if v, _ := strconv.ParseBool(os.Getenv("RUN_BROKEN_TESTS")); !v {
|
condSkip(t)
|
||||||
t.Skipf("Skipping broken tests without RUN_BROKEN_TESTS=1")
|
// Do it all once, unmount, re-mount and then check again.
|
||||||
|
// TODO(bradfitz): do this same pattern (unmount and remount) in the other tests.
|
||||||
|
var suffix string
|
||||||
|
var link string
|
||||||
|
const target = "../../some-target" // arbitrary string. some-target is fake.
|
||||||
|
check := func() {
|
||||||
|
fi, err := os.Lstat(link)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("Stat: %v", err)
|
||||||
|
}
|
||||||
|
if fi.Mode()&os.ModeSymlink == 0 {
|
||||||
|
t.Errorf("Mode = %v; want Symlink bit set", fi.Mode())
|
||||||
|
}
|
||||||
|
got, err := os.Readlink(link)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("Readlink: %v", err)
|
||||||
|
}
|
||||||
|
if got != target {
|
||||||
|
t.Errorf("ReadLink = %q; want %q", got, target)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
inEmptyMutDir(t, func(env *mountEnv, rootDir string) {
|
||||||
|
// Save for second test:
|
||||||
|
link = filepath.Join(rootDir, "some-link")
|
||||||
|
suffix = strings.TrimPrefix(link, env.mountPoint)
|
||||||
|
|
||||||
|
if err := os.Symlink(target, link); err != nil {
|
||||||
|
t.Fatalf("Symlink: %v", err)
|
||||||
|
}
|
||||||
|
t.Logf("Checking in first process...")
|
||||||
|
check()
|
||||||
|
})
|
||||||
|
cammountTest(t, func(env *mountEnv) {
|
||||||
|
t.Logf("Checking in second process...")
|
||||||
|
link = env.mountPoint + suffix
|
||||||
|
check()
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestFinderCopy(t *testing.T) {
|
func TestFinderCopy(t *testing.T) {
|
||||||
|
|
|
@ -40,6 +40,14 @@ import (
|
||||||
// How often to refresh directory nodes by reading from the blobstore.
|
// How often to refresh directory nodes by reading from the blobstore.
|
||||||
const populateInterval = 30 * time.Second
|
const populateInterval = 30 * time.Second
|
||||||
|
|
||||||
|
type nodeType int
|
||||||
|
|
||||||
|
const (
|
||||||
|
fileType nodeType = iota
|
||||||
|
dirType
|
||||||
|
symlinkType
|
||||||
|
)
|
||||||
|
|
||||||
// mutDir is a mutable directory.
|
// mutDir is a mutable directory.
|
||||||
// Its br is the permanode with camliPath:entname attributes.
|
// Its br is the permanode with camliPath:entname attributes.
|
||||||
type mutDir struct {
|
type mutDir struct {
|
||||||
|
@ -111,6 +119,18 @@ func (n *mutDir) populate() error {
|
||||||
log.Printf("child not described: %v", childRef)
|
log.Printf("child not described: %v", childRef)
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
if target := child.Permanode.Attr.Get("camliSymlinkTarget"); target != "" {
|
||||||
|
// This is a symlink.
|
||||||
|
n.children[name] = &mutFile{
|
||||||
|
fs: n.fs,
|
||||||
|
permanode: blobref.Parse(childRef),
|
||||||
|
parent: n,
|
||||||
|
name: name,
|
||||||
|
symLink: true,
|
||||||
|
target: target,
|
||||||
|
}
|
||||||
|
continue
|
||||||
|
}
|
||||||
if contentRef := child.Permanode.Attr.Get("camliContent"); contentRef != "" {
|
if contentRef := child.Permanode.Attr.Get("camliContent"); contentRef != "" {
|
||||||
// This is a file.
|
// This is a file.
|
||||||
content := res.Meta[contentRef]
|
content := res.Meta[contentRef]
|
||||||
|
@ -200,7 +220,7 @@ func (n *mutDir) Lookup(name string, intr fuse.Intr) (ret fuse.Node, err fuse.Er
|
||||||
// 2013/07/21 05:26:35 <- &{Create [ID=0x3 Node=0x8 Uid=61652 Gid=5000 Pid=13115] "x" fl=514 mode=-rw-r--r-- fuse.Intr}
|
// 2013/07/21 05:26:35 <- &{Create [ID=0x3 Node=0x8 Uid=61652 Gid=5000 Pid=13115] "x" fl=514 mode=-rw-r--r-- fuse.Intr}
|
||||||
// 2013/07/21 05:26:36 -> 0x3 Create {LookupResponse:{Node:23 Generation:0 EntryValid:1m0s AttrValid:1m0s Attr:{Inode:15976986887557313215 Size:0 Blocks:0 Atime:2013-07-21 05:23:51.537251251 +1200 NZST Mtime:2013-07-21 05:23:51.537251251 +1200 NZST Ctime:2013-07-21 05:23:51.537251251 +1200 NZST Crtime:2013-07-21 05:23:51.537251251 +1200 NZST Mode:-rw------- Nlink:1 Uid:61652 Gid:5000 Rdev:0 Flags:0}} OpenResponse:{Handle:1 Flags:OpenDirectIO}}
|
// 2013/07/21 05:26:36 -> 0x3 Create {LookupResponse:{Node:23 Generation:0 EntryValid:1m0s AttrValid:1m0s Attr:{Inode:15976986887557313215 Size:0 Blocks:0 Atime:2013-07-21 05:23:51.537251251 +1200 NZST Mtime:2013-07-21 05:23:51.537251251 +1200 NZST Ctime:2013-07-21 05:23:51.537251251 +1200 NZST Crtime:2013-07-21 05:23:51.537251251 +1200 NZST Mode:-rw------- Nlink:1 Uid:61652 Gid:5000 Rdev:0 Flags:0}} OpenResponse:{Handle:1 Flags:OpenDirectIO}}
|
||||||
func (n *mutDir) Create(req *fuse.CreateRequest, res *fuse.CreateResponse, intr fuse.Intr) (fuse.Node, fuse.Handle, fuse.Error) {
|
func (n *mutDir) Create(req *fuse.CreateRequest, res *fuse.CreateResponse, intr fuse.Intr) (fuse.Node, fuse.Handle, fuse.Error) {
|
||||||
child, err := n.creat(req.Name, false)
|
child, err := n.creat(req.Name, fileType)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Printf("mutDir.Create(%q): %v", req.Name, err)
|
log.Printf("mutDir.Create(%q): %v", req.Name, err)
|
||||||
return nil, nil, fuse.EIO
|
return nil, nil, fuse.EIO
|
||||||
|
@ -221,7 +241,7 @@ func (n *mutDir) Create(req *fuse.CreateRequest, res *fuse.CreateResponse, intr
|
||||||
}
|
}
|
||||||
|
|
||||||
func (n *mutDir) Mkdir(req *fuse.MkdirRequest, intr fuse.Intr) (fuse.Node, fuse.Error) {
|
func (n *mutDir) Mkdir(req *fuse.MkdirRequest, intr fuse.Intr) (fuse.Node, fuse.Error) {
|
||||||
child, err := n.creat(req.Name, true)
|
child, err := n.creat(req.Name, dirType)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Printf("mutDir.Mkdir(%q): %v", req.Name, err)
|
log.Printf("mutDir.Mkdir(%q): %v", req.Name, err)
|
||||||
return nil, fuse.EIO
|
return nil, fuse.EIO
|
||||||
|
@ -229,7 +249,28 @@ func (n *mutDir) Mkdir(req *fuse.MkdirRequest, intr fuse.Intr) (fuse.Node, fuse.
|
||||||
return child, nil
|
return child, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (n *mutDir) creat(name string, isDir bool) (fuse.Node, error) {
|
// &fuse.SymlinkRequest{Header:fuse.Header{Conn:(*fuse.Conn)(0xc210047180), ID:0x4, Node:0x8, Uid:0xf0d4, Gid:0x1388, Pid:0x7e88}, NewName:"some-link", Target:"../../some-target"}
|
||||||
|
func (n *mutDir) Symlink(req *fuse.SymlinkRequest, intr fuse.Intr) (fuse.Node, fuse.Error) {
|
||||||
|
node, err := n.creat(req.NewName, symlinkType)
|
||||||
|
if err != nil {
|
||||||
|
log.Printf("mutDir.Symlink(%q): %v", req.NewName, err)
|
||||||
|
return nil, fuse.EIO
|
||||||
|
}
|
||||||
|
mf := node.(*mutFile)
|
||||||
|
mf.symLink = true
|
||||||
|
mf.target = req.Target
|
||||||
|
|
||||||
|
claim := schema.NewSetAttributeClaim(mf.permanode, "camliSymlinkTarget", req.Target)
|
||||||
|
_, err = n.fs.client.UploadAndSignBlob(claim)
|
||||||
|
if err != nil {
|
||||||
|
log.Printf("mutDir.Symlink(%q) upload error: %v", req.NewName, err)
|
||||||
|
return nil, fuse.EIO
|
||||||
|
}
|
||||||
|
|
||||||
|
return node, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (n *mutDir) creat(name string, typ nodeType) (fuse.Node, error) {
|
||||||
// Create a Permanode for the file/directory.
|
// Create a Permanode for the file/directory.
|
||||||
pr, err := n.fs.client.UploadNewPermanode()
|
pr, err := n.fs.client.UploadNewPermanode()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
@ -245,20 +286,23 @@ func (n *mutDir) creat(name string, isDir bool) (fuse.Node, error) {
|
||||||
|
|
||||||
// Add a child node to this node.
|
// Add a child node to this node.
|
||||||
var child mutFileOrDir
|
var child mutFileOrDir
|
||||||
if isDir {
|
switch typ {
|
||||||
|
case dirType:
|
||||||
child = &mutDir{
|
child = &mutDir{
|
||||||
fs: n.fs,
|
fs: n.fs,
|
||||||
permanode: pr.BlobRef,
|
permanode: pr.BlobRef,
|
||||||
parent: n,
|
parent: n,
|
||||||
name: name,
|
name: name,
|
||||||
}
|
}
|
||||||
} else {
|
case fileType, symlinkType:
|
||||||
child = &mutFile{
|
child = &mutFile{
|
||||||
fs: n.fs,
|
fs: n.fs,
|
||||||
permanode: pr.BlobRef,
|
permanode: pr.BlobRef,
|
||||||
parent: n,
|
parent: n,
|
||||||
name: name,
|
name: name,
|
||||||
}
|
}
|
||||||
|
default:
|
||||||
|
panic("bogus creat type")
|
||||||
}
|
}
|
||||||
n.mu.Lock()
|
n.mu.Lock()
|
||||||
if n.children == nil {
|
if n.children == nil {
|
||||||
|
@ -350,15 +394,17 @@ func (n *mutDir) Rename(req *fuse.RenameRequest, newDir fuse.Node, intr fuse.Int
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// mutFile is a mutable file.
|
// mutFile is a mutable file, or symlink.
|
||||||
type mutFile struct {
|
type mutFile struct {
|
||||||
fs *CamliFileSystem
|
fs *CamliFileSystem
|
||||||
permanode *blobref.BlobRef
|
permanode *blobref.BlobRef
|
||||||
parent *mutDir
|
parent *mutDir
|
||||||
name string // ent name (base name within parent)
|
name string // ent name (base name within parent)
|
||||||
|
|
||||||
mu sync.Mutex
|
mu sync.Mutex // protects all following fields
|
||||||
content *blobref.BlobRef
|
symLink bool // if true, is a symlink
|
||||||
|
target string // if a symlink
|
||||||
|
content *blobref.BlobRef // if a regular file
|
||||||
size int64
|
size int64
|
||||||
mtime, atime time.Time // if zero, use serverStart
|
mtime, atime time.Time // if zero, use serverStart
|
||||||
}
|
}
|
||||||
|
@ -373,6 +419,7 @@ func (n *mutFile) fullPath() string {
|
||||||
|
|
||||||
func (n *mutFile) Attr() fuse.Attr {
|
func (n *mutFile) Attr() fuse.Attr {
|
||||||
// TODO: don't grab n.mu three+ times in here.
|
// TODO: don't grab n.mu three+ times in here.
|
||||||
|
var mode os.FileMode = 0600 // writable
|
||||||
|
|
||||||
n.mu.Lock()
|
n.mu.Lock()
|
||||||
size := n.size
|
size := n.size
|
||||||
|
@ -381,11 +428,14 @@ func (n *mutFile) Attr() fuse.Attr {
|
||||||
blocks = uint64(size)/512 + 1
|
blocks = uint64(size)/512 + 1
|
||||||
}
|
}
|
||||||
inode := n.permanode.AsUint64()
|
inode := n.permanode.AsUint64()
|
||||||
|
if n.symLink {
|
||||||
|
mode |= os.ModeSymlink
|
||||||
|
}
|
||||||
n.mu.Unlock()
|
n.mu.Unlock()
|
||||||
|
|
||||||
return fuse.Attr{
|
return fuse.Attr{
|
||||||
Inode: inode,
|
Inode: inode,
|
||||||
Mode: 0600, // writable!
|
Mode: mode,
|
||||||
Uid: uint32(os.Getuid()),
|
Uid: uint32(os.Getuid()),
|
||||||
Gid: uint32(os.Getgid()),
|
Gid: uint32(os.Getgid()),
|
||||||
Size: uint64(size),
|
Size: uint64(size),
|
||||||
|
@ -484,6 +534,16 @@ func (n *mutFile) Fsync(r *fuse.FsyncRequest, intr fuse.Intr) fuse.Error {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (n *mutFile) Readlink(req *fuse.ReadlinkRequest, intr fuse.Intr) (string, fuse.Error) {
|
||||||
|
n.mu.Lock()
|
||||||
|
defer n.mu.Unlock()
|
||||||
|
if !n.symLink {
|
||||||
|
log.Printf("mutFile.Readlink on node that's not a symlink?")
|
||||||
|
return "", fuse.EIO
|
||||||
|
}
|
||||||
|
return n.target, nil
|
||||||
|
}
|
||||||
|
|
||||||
func (n *mutFile) Setattr(req *fuse.SetattrRequest, res *fuse.SetattrResponse, intr fuse.Intr) fuse.Error {
|
func (n *mutFile) Setattr(req *fuse.SetattrRequest, res *fuse.SetattrResponse, intr fuse.Intr) fuse.Error {
|
||||||
log.Printf("mutFile.Setattr on %q: %#v", n.fullPath(), req)
|
log.Printf("mutFile.Setattr on %q: %#v", n.fullPath(), req)
|
||||||
// 2013/07/17 19:43:41 mutFile.Setattr on "foo": &fuse.SetattrRequest{Header:fuse.Header{Conn:(*fuse.Conn)(0xc210047180), ID:0x3, Node:0x3d, Uid:0xf0d4, Gid:0x1388, Pid:0x75e8}, Valid:0x30, Handle:0x0, Size:0x0, Atime:time.Time{sec:63509651021, nsec:0x4aec6b8, loc:(*time.Location)(0x47f7600)}, Mtime:time.Time{sec:63509651021, nsec:0x4aec6b8, loc:(*time.Location)(0x47f7600)}, Mode:0x4000000, Uid:0x0, Gid:0x0, Bkuptime:time.Time{sec:62135596800, nsec:0x0, loc:(*time.Location)(0x47f7600)}, Chgtime:time.Time{sec:62135596800, nsec:0x0, loc:(*time.Location)(0x47f7600)}, Crtime:time.Time{sec:0, nsec:0x0, loc:(*time.Location)(nil)}, Flags:0x0}
|
// 2013/07/17 19:43:41 mutFile.Setattr on "foo": &fuse.SetattrRequest{Header:fuse.Header{Conn:(*fuse.Conn)(0xc210047180), ID:0x3, Node:0x3d, Uid:0xf0d4, Gid:0x1388, Pid:0x75e8}, Valid:0x30, Handle:0x0, Size:0x0, Atime:time.Time{sec:63509651021, nsec:0x4aec6b8, loc:(*time.Location)(0x47f7600)}, Mtime:time.Time{sec:63509651021, nsec:0x4aec6b8, loc:(*time.Location)(0x47f7600)}, Mode:0x4000000, Uid:0x0, Gid:0x0, Bkuptime:time.Time{sec:62135596800, nsec:0x0, loc:(*time.Location)(0x47f7600)}, Chgtime:time.Time{sec:62135596800, nsec:0x0, loc:(*time.Location)(0x47f7600)}, Crtime:time.Time{sec:0, nsec:0x0, loc:(*time.Location)(nil)}, Flags:0x0}
|
||||||
|
|
Loading…
Reference in New Issue