2013-07-10 11:10:48 +00:00
|
|
|
// +build linux darwin
|
|
|
|
|
|
|
|
/*
|
|
|
|
Copyright 2012 Google Inc.
|
|
|
|
|
|
|
|
Licensed under the Apache License, Version 2.0 (the "License");
|
|
|
|
you may not use this file except in compliance with the License.
|
|
|
|
You may obtain a copy of the License at
|
|
|
|
|
|
|
|
http://www.apache.org/licenses/LICENSE-2.0
|
|
|
|
|
|
|
|
Unless required by applicable law or agreed to in writing, software
|
|
|
|
distributed under the License is distributed on an "AS IS" BASIS,
|
|
|
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
|
|
See the License for the specific language governing permissions and
|
|
|
|
limitations under the License.
|
|
|
|
*/
|
|
|
|
|
|
|
|
package fs
|
|
|
|
|
2013-07-11 00:56:09 +00:00
|
|
|
import (
|
|
|
|
"errors"
|
2013-07-11 06:49:27 +00:00
|
|
|
"io"
|
|
|
|
"io/ioutil"
|
2013-07-11 00:56:09 +00:00
|
|
|
"log"
|
|
|
|
"os"
|
|
|
|
"strings"
|
|
|
|
"sync"
|
2013-07-16 03:44:29 +00:00
|
|
|
"time"
|
2013-07-11 00:56:09 +00:00
|
|
|
|
|
|
|
"camlistore.org/pkg/blobref"
|
2013-07-16 03:44:29 +00:00
|
|
|
"camlistore.org/pkg/readerutil"
|
2013-07-11 06:49:27 +00:00
|
|
|
"camlistore.org/pkg/schema"
|
2013-07-11 00:56:09 +00:00
|
|
|
"camlistore.org/pkg/search"
|
|
|
|
|
|
|
|
"camlistore.org/third_party/code.google.com/p/rsc/fuse"
|
|
|
|
)
|
|
|
|
|
2013-07-16 03:44:29 +00:00
|
|
|
// How often to refresh directory nodes by reading from the blobstore.
|
|
|
|
const populateInterval = 30 * time.Second
|
|
|
|
|
2013-07-11 00:56:09 +00:00
|
|
|
// mutDir is a mutable directory.
|
|
|
|
// Its br is the permanode with camliPath:entname attributes.
|
|
|
|
type mutDir struct {
|
2013-07-11 06:49:27 +00:00
|
|
|
fs *CamliFileSystem
|
|
|
|
permanode *blobref.BlobRef
|
|
|
|
parent *mutDir
|
2013-07-16 03:44:29 +00:00
|
|
|
name string // ent name (base name within parent)
|
2013-07-11 00:56:09 +00:00
|
|
|
|
2013-07-11 06:49:27 +00:00
|
|
|
mu sync.Mutex
|
2013-07-16 03:44:29 +00:00
|
|
|
lastPop time.Time
|
2013-07-11 06:49:27 +00:00
|
|
|
children map[string]fuse.Node
|
2013-07-11 00:56:09 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
func (n *mutDir) Attr() fuse.Attr {
|
|
|
|
return fuse.Attr{
|
|
|
|
Mode: os.ModeDir | 0700,
|
|
|
|
Uid: uint32(os.Getuid()),
|
|
|
|
Gid: uint32(os.Getgid()),
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2013-07-16 03:44:29 +00:00
|
|
|
// populate hits the blobstore to populate map of child nodes.
|
2013-07-11 00:56:09 +00:00
|
|
|
func (n *mutDir) populate() error {
|
2013-07-16 03:44:29 +00:00
|
|
|
n.mu.Lock()
|
|
|
|
defer n.mu.Unlock()
|
|
|
|
|
|
|
|
// Only re-populate if we haven't done so recently.
|
|
|
|
now := time.Now()
|
|
|
|
if n.lastPop.Add(populateInterval).After(now) {
|
2013-07-11 06:49:27 +00:00
|
|
|
return nil
|
|
|
|
}
|
2013-07-16 03:44:29 +00:00
|
|
|
n.lastPop = now
|
2013-07-11 00:56:09 +00:00
|
|
|
|
|
|
|
res, err := n.fs.client.Describe(&search.DescribeRequest{
|
2013-07-11 06:49:27 +00:00
|
|
|
BlobRef: n.permanode,
|
2013-07-11 00:56:09 +00:00
|
|
|
Depth: 3,
|
|
|
|
})
|
|
|
|
if err != nil {
|
|
|
|
log.Println("mutDir.paths:", err)
|
|
|
|
return nil
|
|
|
|
}
|
2013-07-11 06:49:27 +00:00
|
|
|
db := res.Meta[n.permanode.String()]
|
2013-07-11 00:56:09 +00:00
|
|
|
if db == nil {
|
|
|
|
return errors.New("dir blobref not described")
|
|
|
|
}
|
2013-07-11 06:49:27 +00:00
|
|
|
|
|
|
|
// Find all child permanodes and stick them in n.children
|
|
|
|
if n.children == nil {
|
|
|
|
n.children = make(map[string]fuse.Node)
|
|
|
|
}
|
2013-07-11 00:56:09 +00:00
|
|
|
for k, v := range db.Permanode.Attr {
|
|
|
|
const p = "camliPath:"
|
|
|
|
if !strings.HasPrefix(k, p) || len(v) < 1 {
|
|
|
|
continue
|
|
|
|
}
|
|
|
|
name := k[len(p):]
|
|
|
|
childRef := v[0]
|
|
|
|
child := res.Meta[childRef]
|
|
|
|
if child == nil {
|
2013-07-16 03:44:29 +00:00
|
|
|
log.Printf("child not described: %v", childRef)
|
2013-07-11 00:56:09 +00:00
|
|
|
continue
|
|
|
|
}
|
|
|
|
if contentRef := child.Permanode.Attr.Get("camliContent"); contentRef != "" {
|
|
|
|
// This is a file.
|
|
|
|
content := res.Meta[contentRef]
|
|
|
|
if content == nil {
|
2013-07-16 03:44:29 +00:00
|
|
|
log.Printf("child content not described: %v", childRef)
|
2013-07-11 00:56:09 +00:00
|
|
|
continue
|
|
|
|
}
|
|
|
|
if content.CamliType != "file" {
|
2013-07-16 03:44:29 +00:00
|
|
|
log.Printf("child not a file: %v", childRef)
|
2013-07-11 00:56:09 +00:00
|
|
|
continue
|
|
|
|
}
|
2013-07-11 06:49:27 +00:00
|
|
|
n.children[name] = &mutFile{
|
|
|
|
fs: n.fs,
|
2013-07-11 00:56:09 +00:00
|
|
|
permanode: blobref.Parse(childRef),
|
2013-07-11 06:49:27 +00:00
|
|
|
parent: n,
|
2013-07-11 00:56:09 +00:00
|
|
|
name: name,
|
2013-07-11 06:49:27 +00:00
|
|
|
content: blobref.Parse(contentRef),
|
|
|
|
size: content.File.Size,
|
2013-07-11 00:56:09 +00:00
|
|
|
}
|
|
|
|
continue
|
|
|
|
}
|
|
|
|
// This is a directory.
|
2013-07-11 06:49:27 +00:00
|
|
|
n.children[name] = &mutDir{
|
|
|
|
fs: n.fs,
|
|
|
|
permanode: blobref.Parse(childRef),
|
|
|
|
parent: n,
|
|
|
|
name: name,
|
2013-07-11 00:56:09 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
|
|
|
func (n *mutDir) ReadDir(intr fuse.Intr) ([]fuse.Dirent, fuse.Error) {
|
|
|
|
if err := n.populate(); err != nil {
|
|
|
|
log.Println("populate:", err)
|
|
|
|
return nil, fuse.EIO
|
|
|
|
}
|
|
|
|
n.mu.Lock()
|
|
|
|
defer n.mu.Unlock()
|
|
|
|
var ents []fuse.Dirent
|
2013-07-11 06:49:27 +00:00
|
|
|
for name := range n.children {
|
2013-07-11 00:56:09 +00:00
|
|
|
ents = append(ents, fuse.Dirent{
|
|
|
|
Name: name,
|
|
|
|
})
|
|
|
|
}
|
|
|
|
return ents, nil
|
|
|
|
}
|
|
|
|
|
|
|
|
func (n *mutDir) Lookup(name string, intr fuse.Intr) (fuse.Node, fuse.Error) {
|
|
|
|
if err := n.populate(); err != nil {
|
|
|
|
log.Println("populate:", err)
|
|
|
|
return nil, fuse.EIO
|
|
|
|
}
|
|
|
|
n.mu.Lock()
|
|
|
|
defer n.mu.Unlock()
|
2013-07-11 06:49:27 +00:00
|
|
|
if n2 := n.children[name]; n2 != nil {
|
|
|
|
return n2, nil
|
2013-07-11 00:56:09 +00:00
|
|
|
}
|
|
|
|
return nil, fuse.ENOENT
|
|
|
|
}
|
|
|
|
|
2013-07-11 06:49:27 +00:00
|
|
|
func (n *mutDir) Create(req *fuse.CreateRequest, res *fuse.CreateResponse, intr fuse.Intr) (fuse.Node, fuse.Handle, fuse.Error) {
|
2013-07-16 03:44:29 +00:00
|
|
|
child, err := n.creat(req.Name, false)
|
2013-07-11 06:49:27 +00:00
|
|
|
if err != nil {
|
2013-07-16 03:44:29 +00:00
|
|
|
log.Printf("mutDir.Mkdir(%q): %v", req.Name, err)
|
2013-07-11 06:49:27 +00:00
|
|
|
return nil, nil, fuse.EIO
|
|
|
|
}
|
|
|
|
|
2013-07-16 03:44:29 +00:00
|
|
|
// Create and return a file handle.
|
|
|
|
h, ferr := child.(*mutFile).newHandle(nil)
|
|
|
|
if ferr != nil {
|
|
|
|
return nil, nil, ferr
|
2013-07-11 06:49:27 +00:00
|
|
|
}
|
2013-07-16 03:44:29 +00:00
|
|
|
return child, h, nil
|
|
|
|
}
|
2013-07-11 06:49:27 +00:00
|
|
|
|
2013-07-16 03:44:29 +00:00
|
|
|
func (n *mutDir) Mkdir(req *fuse.MkdirRequest, intr fuse.Intr) (fuse.Node, fuse.Error) {
|
|
|
|
child, err := n.creat(req.Name, true)
|
2013-07-11 06:49:27 +00:00
|
|
|
if err != nil {
|
2013-07-16 03:44:29 +00:00
|
|
|
log.Printf("mutDir.Mkdir(%q): %v", req.Name, err)
|
|
|
|
return nil, fuse.EIO
|
2013-07-11 06:49:27 +00:00
|
|
|
}
|
2013-07-16 03:44:29 +00:00
|
|
|
return child, nil
|
|
|
|
}
|
2013-07-11 06:49:27 +00:00
|
|
|
|
2013-07-16 03:44:29 +00:00
|
|
|
func (n *mutDir) creat(name string, isDir bool) (fuse.Node, error) {
|
|
|
|
// Create a Permanode for the file/directory.
|
|
|
|
pr, err := n.fs.client.UploadNewPermanode()
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
2013-07-11 06:49:27 +00:00
|
|
|
}
|
|
|
|
|
2013-07-16 03:44:29 +00:00
|
|
|
// Add a camliPath:name attribute to the directory permanode.
|
|
|
|
claim := schema.NewSetAttributeClaim(n.permanode, "camliPath:"+name, pr.BlobRef.String())
|
|
|
|
_, err = n.fs.client.UploadAndSignBlob(claim)
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
|
|
|
// Add a child node to this node.
|
|
|
|
var child fuse.Node
|
|
|
|
if isDir {
|
|
|
|
child = &mutDir{
|
|
|
|
fs: n.fs,
|
|
|
|
permanode: pr.BlobRef,
|
|
|
|
parent: n,
|
|
|
|
name: name,
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
child = &mutFile{
|
|
|
|
fs: n.fs,
|
|
|
|
permanode: pr.BlobRef,
|
|
|
|
parent: n,
|
|
|
|
name: name,
|
|
|
|
}
|
|
|
|
}
|
2013-07-11 06:49:27 +00:00
|
|
|
n.mu.Lock()
|
|
|
|
if n.children == nil {
|
|
|
|
n.children = make(map[string]fuse.Node)
|
|
|
|
}
|
2013-07-16 03:44:29 +00:00
|
|
|
n.children[name] = child
|
2013-07-11 06:49:27 +00:00
|
|
|
n.mu.Unlock()
|
|
|
|
|
2013-07-16 03:44:29 +00:00
|
|
|
return child, nil
|
|
|
|
}
|
|
|
|
|
|
|
|
func (n *mutDir) Remove(req *fuse.RemoveRequest, intr fuse.Intr) fuse.Error {
|
|
|
|
// Remove the camliPath:name attribute from the directory permanode.
|
|
|
|
claim := schema.NewDelAttributeClaim(n.permanode, "camliPath:"+req.Name)
|
|
|
|
_, err := n.fs.client.UploadAndSignBlob(claim)
|
|
|
|
if err != nil {
|
|
|
|
log.Println("mutDir.Create:", err)
|
|
|
|
return fuse.EIO
|
2013-07-11 06:49:27 +00:00
|
|
|
}
|
2013-07-16 03:44:29 +00:00
|
|
|
// Remove child from map.
|
|
|
|
n.mu.Lock()
|
|
|
|
if n.children != nil {
|
|
|
|
delete(n.children, req.Name)
|
|
|
|
}
|
|
|
|
n.mu.Unlock()
|
|
|
|
return nil
|
2013-07-11 06:49:27 +00:00
|
|
|
}
|
|
|
|
|
2013-07-16 03:44:29 +00:00
|
|
|
// mutFile is a mutable file.
|
2013-07-11 00:56:09 +00:00
|
|
|
type mutFile struct {
|
2013-07-11 06:49:27 +00:00
|
|
|
fs *CamliFileSystem
|
2013-07-11 00:56:09 +00:00
|
|
|
permanode *blobref.BlobRef
|
2013-07-11 06:49:27 +00:00
|
|
|
parent *mutDir
|
2013-07-16 03:44:29 +00:00
|
|
|
name string // ent name (base name within parent)
|
2013-07-11 06:49:27 +00:00
|
|
|
|
|
|
|
mu sync.Mutex
|
|
|
|
content *blobref.BlobRef
|
|
|
|
size int64
|
|
|
|
}
|
|
|
|
|
|
|
|
func (n *mutFile) Attr() fuse.Attr {
|
|
|
|
return fuse.Attr{
|
|
|
|
Mode: 0600, // writable!
|
|
|
|
Uid: uint32(os.Getuid()),
|
|
|
|
Gid: uint32(os.Getgid()),
|
|
|
|
Size: uint64(n.size),
|
|
|
|
// TODO(adg): use the real stuff here
|
|
|
|
Mtime: serverStart,
|
|
|
|
Ctime: serverStart,
|
|
|
|
Crtime: serverStart,
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
func (n *mutFile) setContent(br *blobref.BlobRef, size int64) error {
|
|
|
|
n.mu.Lock()
|
|
|
|
defer n.mu.Unlock()
|
|
|
|
n.content = br
|
|
|
|
n.size = size
|
|
|
|
claim := schema.NewSetAttributeClaim(n.permanode, "camliContent", br.String())
|
|
|
|
_, err := n.fs.client.UploadAndSignBlob(claim)
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
|
|
|
func (n *mutFile) Open(req *fuse.OpenRequest, res *fuse.OpenResponse, intr fuse.Intr) (fuse.Handle, fuse.Error) {
|
|
|
|
log.Printf("mutFile.Open: %v: content: %v", n.permanode, n.content)
|
|
|
|
r, err := schema.NewFileReader(n.fs.fetcher, n.content)
|
|
|
|
if err != nil {
|
|
|
|
log.Printf("mutFile.Open: %v", err)
|
|
|
|
return nil, fuse.EIO
|
|
|
|
}
|
|
|
|
defer r.Close()
|
|
|
|
return n.newHandle(r)
|
|
|
|
}
|
|
|
|
|
2013-07-16 03:44:29 +00:00
|
|
|
func (n *mutFile) Fsync(r *fuse.FsyncRequest, intr fuse.Intr) fuse.Error {
|
|
|
|
// TODO(adg): in the fuse package, plumb through fsync to mutFileHandle
|
|
|
|
// in the same way we did Truncate.
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
2013-07-11 06:49:27 +00:00
|
|
|
func (n *mutFile) newHandle(body io.Reader) (fuse.Handle, fuse.Error) {
|
|
|
|
tmp, err := ioutil.TempFile("", "camli-")
|
|
|
|
if err == nil && body != nil {
|
|
|
|
_, err = io.Copy(tmp, body)
|
|
|
|
}
|
|
|
|
if err != nil {
|
|
|
|
log.Printf("mutFile.newHandle: %v", err)
|
|
|
|
if tmp != nil {
|
|
|
|
tmp.Close()
|
|
|
|
os.Remove(tmp.Name())
|
|
|
|
}
|
|
|
|
return nil, fuse.EIO
|
|
|
|
}
|
|
|
|
return &mutFileHandle{f: n, tmp: tmp}, nil
|
|
|
|
}
|
|
|
|
|
2013-07-16 03:44:29 +00:00
|
|
|
// mutFileHandle represents an open mutable file.
|
|
|
|
// It stores the file contents in a temporary file, and
|
|
|
|
// delegates reads and writes directly to the temporary file.
|
|
|
|
// When the handle is released, it writes the contents of the
|
|
|
|
// temporary file to the blobstore, and instructs the parent
|
|
|
|
// mutFile to update the file permanode.
|
2013-07-11 06:49:27 +00:00
|
|
|
type mutFileHandle struct {
|
|
|
|
f *mutFile
|
|
|
|
tmp *os.File
|
|
|
|
written bool
|
|
|
|
}
|
2013-07-11 00:56:09 +00:00
|
|
|
|
2013-07-11 06:49:27 +00:00
|
|
|
func (h *mutFileHandle) Read(req *fuse.ReadRequest, res *fuse.ReadResponse, intr fuse.Intr) fuse.Error {
|
|
|
|
buf := make([]byte, req.Size)
|
|
|
|
n, err := h.tmp.ReadAt(buf, req.Offset)
|
|
|
|
if err == io.EOF {
|
|
|
|
err = nil
|
|
|
|
}
|
|
|
|
if err != nil {
|
|
|
|
log.Printf("mutFileHandle.Read: %v", err)
|
|
|
|
return fuse.EIO
|
|
|
|
}
|
|
|
|
res.Data = buf[:n]
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
|
|
|
func (h *mutFileHandle) Write(req *fuse.WriteRequest, res *fuse.WriteResponse, intr fuse.Intr) fuse.Error {
|
|
|
|
h.written = true
|
|
|
|
n, err := h.tmp.WriteAt(req.Data, req.Offset)
|
|
|
|
if err != nil {
|
|
|
|
log.Println("mutFileHandle.Write:", err)
|
|
|
|
return fuse.EIO
|
|
|
|
}
|
|
|
|
res.Size = n
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
|
|
|
func (h *mutFileHandle) Release(req *fuse.ReleaseRequest, intr fuse.Intr) fuse.Error {
|
|
|
|
if h.written {
|
|
|
|
_, err := h.tmp.Seek(0, 0)
|
|
|
|
if err != nil {
|
|
|
|
log.Println("mutFileHandle.Release:", err)
|
|
|
|
return fuse.EIO
|
|
|
|
}
|
2013-07-16 03:44:29 +00:00
|
|
|
var n int64
|
|
|
|
br, err := schema.WriteFileFromReader(h.f.fs.client, h.f.name, readerutil.CountingReader{Reader: h.tmp, N: &n})
|
2013-07-11 06:49:27 +00:00
|
|
|
if err != nil {
|
|
|
|
log.Println("mutFileHandle.Release:", err)
|
|
|
|
return fuse.EIO
|
|
|
|
}
|
2013-07-16 03:44:29 +00:00
|
|
|
h.f.setContent(br, n)
|
2013-07-11 06:49:27 +00:00
|
|
|
}
|
|
|
|
h.tmp.Close()
|
|
|
|
os.Remove(h.tmp.Name())
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
|
|
|
func (h *mutFileHandle) Truncate(size uint64, intr fuse.Intr) fuse.Error {
|
|
|
|
h.written = true
|
|
|
|
if err := h.tmp.Truncate(int64(size)); err != nil {
|
|
|
|
log.Println("mutFileHandle.Truncate:", err)
|
|
|
|
return fuse.EIO
|
|
|
|
}
|
|
|
|
return nil
|
2013-07-11 00:56:09 +00:00
|
|
|
}
|