github.com/slspeek/camlistore_namedsearch@v0.0.0-20140519202248-ed6f70f7721a/pkg/fs/fs.go (about)

     1  // +build linux darwin
     2  
     3  /*
     4  Copyright 2011 Google Inc.
     5  
     6  Licensed under the Apache License, Version 2.0 (the "License");
     7  you may not use this file except in compliance with the License.
     8  You may obtain a copy of the License at
     9  
    10       http://www.apache.org/licenses/LICENSE-2.0
    11  
    12  Unless required by applicable law or agreed to in writing, software
    13  distributed under the License is distributed on an "AS IS" BASIS,
    14  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    15  See the License for the specific language governing permissions and
    16  limitations under the License.
    17  */
    18  
    19  // Package fs implements a FUSE filesystem for Camlistore and is
    20  // used by the cammount binary.
    21  package fs
    22  
    23  import (
    24  	"fmt"
    25  	"io"
    26  	"log"
    27  	"os"
    28  	"sync"
    29  	"syscall"
    30  	"time"
    31  
    32  	"camlistore.org/pkg/blob"
    33  	"camlistore.org/pkg/client"
    34  	"camlistore.org/pkg/lru"
    35  	"camlistore.org/pkg/schema"
    36  
    37  	"camlistore.org/third_party/bazil.org/fuse"
    38  	fusefs "camlistore.org/third_party/bazil.org/fuse/fs"
    39  )
    40  
    41  var serverStart = time.Now()
    42  
    43  var errNotDir = fuse.Errno(syscall.ENOTDIR)
    44  
    45  type CamliFileSystem struct {
    46  	fetcher blob.Fetcher
    47  	client  *client.Client // or nil, if not doing search queries
    48  	root    fusefs.Node
    49  
    50  	// IgnoreOwners, if true, collapses all file ownership to the
    51  	// uid/gid running the fuse filesystem, and sets all the
    52  	// permissions to 0600/0700.
    53  	IgnoreOwners bool
    54  
    55  	blobToSchema *lru.Cache // ~map[blobstring]*schema.Blob
    56  	nameToBlob   *lru.Cache // ~map[string]blob.Ref
    57  	nameToAttr   *lru.Cache // ~map[string]*fuse.Attr
    58  }
    59  
    60  var _ fusefs.FS = (*CamliFileSystem)(nil)
    61  
    62  func newCamliFileSystem(fetcher blob.Fetcher) *CamliFileSystem {
    63  	return &CamliFileSystem{
    64  		fetcher:      fetcher,
    65  		blobToSchema: lru.New(1024), // arbitrary; TODO: tunable/smarter?
    66  		nameToBlob:   lru.New(1024), // arbitrary: TODO: tunable/smarter?
    67  		nameToAttr:   lru.New(1024), // arbitrary: TODO: tunable/smarter?
    68  	}
    69  }
    70  
    71  // NewDefaultCamliFileSystem returns a filesystem with a generic base, from which
    72  // users can navigate by blobref, tag, date, etc.
    73  func NewDefaultCamliFileSystem(client *client.Client, fetcher blob.Fetcher) *CamliFileSystem {
    74  	if client == nil || fetcher == nil {
    75  		panic("nil argument")
    76  	}
    77  	fs := newCamliFileSystem(fetcher)
    78  	fs.root = &root{fs: fs} // root.go
    79  	fs.client = client
    80  	return fs
    81  }
    82  
    83  // NewRootedCamliFileSystem returns a CamliFileSystem with a node based on a blobref
    84  // as its base.
    85  func NewRootedCamliFileSystem(cli *client.Client, fetcher blob.Fetcher, root blob.Ref) (*CamliFileSystem, error) {
    86  	fs := newCamliFileSystem(fetcher)
    87  	fs.client = cli
    88  
    89  	n, err := fs.newNodeFromBlobRef(root)
    90  
    91  	if err != nil {
    92  		return nil, err
    93  	}
    94  
    95  	fs.root = n
    96  
    97  	return fs, nil
    98  }
    99  
   100  // node implements fuse.Node with a read-only Camli "file" or
   101  // "directory" blob.
   102  type node struct {
   103  	noXattr
   104  	fs      *CamliFileSystem
   105  	blobref blob.Ref
   106  
   107  	pnodeModTime time.Time // optionally set by recent.go; modtime of permanode
   108  
   109  	dmu     sync.Mutex    // guards dirents. acquire before mu.
   110  	dirents []fuse.Dirent // nil until populated once
   111  
   112  	mu      sync.Mutex // guards rest
   113  	attr    fuse.Attr
   114  	meta    *schema.Blob
   115  	lookMap map[string]blob.Ref
   116  }
   117  
   118  func (n *node) Attr() (attr fuse.Attr) {
   119  	_, err := n.schema()
   120  	if err != nil {
   121  		// Hm, can't return it. Just log it I guess.
   122  		log.Printf("error fetching schema superset for %v: %v", n.blobref, err)
   123  	}
   124  	return n.attr
   125  }
   126  
   127  func (n *node) addLookupEntry(name string, ref blob.Ref) {
   128  	n.mu.Lock()
   129  	defer n.mu.Unlock()
   130  	if n.lookMap == nil {
   131  		n.lookMap = make(map[string]blob.Ref)
   132  	}
   133  	n.lookMap[name] = ref
   134  }
   135  
   136  func (n *node) Lookup(name string, intr fusefs.Intr) (fusefs.Node, fuse.Error) {
   137  	if name == ".quitquitquit" {
   138  		// TODO: only in dev mode
   139  		log.Fatalf("Shutting down due to .quitquitquit lookup.")
   140  	}
   141  
   142  	// If we haven't done Readdir yet (dirents isn't set), then force a Readdir
   143  	// call to populate lookMap.
   144  	n.dmu.Lock()
   145  	loaded := n.dirents != nil
   146  	n.dmu.Unlock()
   147  	if !loaded {
   148  		n.ReadDir(nil)
   149  	}
   150  
   151  	n.mu.Lock()
   152  	defer n.mu.Unlock()
   153  	ref, ok := n.lookMap[name]
   154  	if !ok {
   155  		return nil, fuse.ENOENT
   156  	}
   157  	return &node{fs: n.fs, blobref: ref}, nil
   158  }
   159  
   160  func (n *node) schema() (*schema.Blob, error) {
   161  	// TODO: use singleflight library here instead of a lock?
   162  	n.mu.Lock()
   163  	defer n.mu.Unlock()
   164  	if n.meta != nil {
   165  		return n.meta, nil
   166  	}
   167  	blob, err := n.fs.fetchSchemaMeta(n.blobref)
   168  	if err == nil {
   169  		n.meta = blob
   170  		n.populateAttr()
   171  	}
   172  	return blob, err
   173  }
   174  
   175  func isWriteFlags(flags fuse.OpenFlags) bool {
   176  	// TODO read/writeness are not flags, use O_ACCMODE
   177  	return flags&fuse.OpenFlags(os.O_WRONLY|os.O_RDWR|os.O_APPEND|os.O_CREATE) != 0
   178  }
   179  
   180  func (n *node) Open(req *fuse.OpenRequest, res *fuse.OpenResponse, intr fusefs.Intr) (fusefs.Handle, fuse.Error) {
   181  	log.Printf("CAMLI Open on %v: %#v", n.blobref, req)
   182  	if isWriteFlags(req.Flags) {
   183  		return nil, fuse.EPERM
   184  	}
   185  	ss, err := n.schema()
   186  	if err != nil {
   187  		log.Printf("open of %v: %v", n.blobref, err)
   188  		return nil, fuse.EIO
   189  	}
   190  	if ss.Type() == "directory" {
   191  		return n, nil
   192  	}
   193  	fr, err := ss.NewFileReader(n.fs.fetcher)
   194  	if err != nil {
   195  		// Will only happen if ss.Type != "file" or "bytes"
   196  		log.Printf("NewFileReader(%s) = %v", n.blobref, err)
   197  		return nil, fuse.EIO
   198  	}
   199  	return &nodeReader{n: n, fr: fr}, nil
   200  }
   201  
   202  type nodeReader struct {
   203  	n  *node
   204  	fr *schema.FileReader
   205  }
   206  
   207  func (nr *nodeReader) Read(req *fuse.ReadRequest, res *fuse.ReadResponse, intr fusefs.Intr) fuse.Error {
   208  	log.Printf("CAMLI nodeReader READ on %v: %#v", nr.n.blobref, req)
   209  	if req.Offset >= nr.fr.Size() {
   210  		return nil
   211  	}
   212  	size := req.Size
   213  	if int64(size)+req.Offset >= nr.fr.Size() {
   214  		size -= int((int64(size) + req.Offset) - nr.fr.Size())
   215  	}
   216  	buf := make([]byte, size)
   217  	n, err := nr.fr.ReadAt(buf, req.Offset)
   218  	if err == io.EOF {
   219  		err = nil
   220  	}
   221  	if err != nil {
   222  		log.Printf("camli read on %v at %d: %v", nr.n.blobref, req.Offset, err)
   223  		return fuse.EIO
   224  	}
   225  	res.Data = buf[:n]
   226  	return nil
   227  }
   228  
   229  func (nr *nodeReader) Release(req *fuse.ReleaseRequest, intr fusefs.Intr) fuse.Error {
   230  	log.Printf("CAMLI nodeReader RELEASE on %v", nr.n.blobref)
   231  	nr.fr.Close()
   232  	return nil
   233  }
   234  
   235  func (n *node) ReadDir(intr fusefs.Intr) ([]fuse.Dirent, fuse.Error) {
   236  	log.Printf("CAMLI ReadDir on %v", n.blobref)
   237  	n.dmu.Lock()
   238  	defer n.dmu.Unlock()
   239  	if n.dirents != nil {
   240  		return n.dirents, nil
   241  	}
   242  
   243  	ss, err := n.schema()
   244  	if err != nil {
   245  		log.Printf("camli.ReadDir error on %v: %v", n.blobref, err)
   246  		return nil, fuse.EIO
   247  	}
   248  	dr, err := schema.NewDirReader(n.fs.fetcher, ss.BlobRef())
   249  	if err != nil {
   250  		log.Printf("camli.ReadDir error on %v: %v", n.blobref, err)
   251  		return nil, fuse.EIO
   252  	}
   253  	schemaEnts, err := dr.Readdir(-1)
   254  	if err != nil {
   255  		log.Printf("camli.ReadDir error on %v: %v", n.blobref, err)
   256  		return nil, fuse.EIO
   257  	}
   258  	n.dirents = make([]fuse.Dirent, 0)
   259  	for _, sent := range schemaEnts {
   260  		if name := sent.FileName(); name != "" {
   261  			n.addLookupEntry(name, sent.BlobRef())
   262  			n.dirents = append(n.dirents, fuse.Dirent{Name: name})
   263  		}
   264  	}
   265  	return n.dirents, nil
   266  }
   267  
   268  // populateAttr should only be called once n.ss is known to be set and
   269  // non-nil
   270  func (n *node) populateAttr() error {
   271  	meta := n.meta
   272  
   273  	n.attr.Mode = meta.FileMode()
   274  
   275  	if n.fs.IgnoreOwners {
   276  		n.attr.Uid = uint32(os.Getuid())
   277  		n.attr.Gid = uint32(os.Getgid())
   278  		executeBit := n.attr.Mode & 0100
   279  		n.attr.Mode = (n.attr.Mode ^ n.attr.Mode.Perm()) & 0400 & executeBit
   280  	} else {
   281  		n.attr.Uid = uint32(meta.MapUid())
   282  		n.attr.Gid = uint32(meta.MapGid())
   283  	}
   284  
   285  	// TODO: inode?
   286  
   287  	if mt := meta.ModTime(); !mt.IsZero() {
   288  		n.attr.Mtime = mt
   289  	} else {
   290  		n.attr.Mtime = n.pnodeModTime
   291  	}
   292  
   293  	switch meta.Type() {
   294  	case "file":
   295  		n.attr.Size = uint64(meta.PartsSize())
   296  		n.attr.Blocks = 0 // TODO: set?
   297  		n.attr.Mode |= 0400
   298  	case "directory":
   299  		n.attr.Mode |= 0500
   300  	case "symlink":
   301  		n.attr.Mode |= 0400
   302  	default:
   303  		log.Printf("unknown attr ss.Type %q in populateAttr", meta.Type())
   304  	}
   305  	return nil
   306  }
   307  
   308  func (fs *CamliFileSystem) Root() (fusefs.Node, fuse.Error) {
   309  	return fs.root, nil
   310  }
   311  
   312  func (fs *CamliFileSystem) Statfs(req *fuse.StatfsRequest, res *fuse.StatfsResponse, intr fusefs.Intr) fuse.Error {
   313  	// Make some stuff up, just to see if it makes "lsof" happy.
   314  	res.Blocks = 1 << 35
   315  	res.Bfree = 1 << 34
   316  	res.Bavail = 1 << 34
   317  	res.Files = 1 << 29
   318  	res.Ffree = 1 << 28
   319  	res.Namelen = 2048
   320  	res.Bsize = 1024
   321  	return nil
   322  }
   323  
   324  // Errors returned are:
   325  //    os.ErrNotExist -- blob not found
   326  //    os.ErrInvalid -- not JSON or a camli schema blob
   327  func (fs *CamliFileSystem) fetchSchemaMeta(br blob.Ref) (*schema.Blob, error) {
   328  	blobStr := br.String()
   329  	if blob, ok := fs.blobToSchema.Get(blobStr); ok {
   330  		return blob.(*schema.Blob), nil
   331  	}
   332  
   333  	rc, _, err := fs.fetcher.Fetch(br)
   334  	if err != nil {
   335  		return nil, err
   336  	}
   337  	defer rc.Close()
   338  	blob, err := schema.BlobFromReader(br, rc)
   339  	if err != nil {
   340  		log.Printf("Error parsing %s as schema blob: %v", br, err)
   341  		return nil, os.ErrInvalid
   342  	}
   343  	if blob.Type() == "" {
   344  		log.Printf("blob %s is JSON but lacks camliType", br)
   345  		return nil, os.ErrInvalid
   346  	}
   347  	fs.blobToSchema.Add(blobStr, blob)
   348  	return blob, nil
   349  }
   350  
   351  // consolated logic for determining a node to mount based on an arbitrary blobref
   352  func (fs *CamliFileSystem) newNodeFromBlobRef(root blob.Ref) (fusefs.Node, error) {
   353  	blob, err := fs.fetchSchemaMeta(root)
   354  	if err != nil {
   355  		return nil, err
   356  	}
   357  
   358  	switch blob.Type() {
   359  	case "directory":
   360  		n := &node{fs: fs, blobref: root, meta: blob}
   361  		n.populateAttr()
   362  		return n, nil
   363  
   364  	case "permanode":
   365  		// other mutDirs listed in the default fileystem have names and are displayed
   366  		return &mutDir{fs: fs, permanode: root, name: "-"}, nil
   367  	}
   368  
   369  	return nil, fmt.Errorf("Blobref must be of a directory or permanode got a %v", blob.Type())
   370  }
   371  
   372  type notImplementDirNode struct{ noXattr }
   373  
   374  func (notImplementDirNode) Attr() fuse.Attr {
   375  	return fuse.Attr{
   376  		Mode: os.ModeDir | 0000,
   377  		Uid:  uint32(os.Getuid()),
   378  		Gid:  uint32(os.Getgid()),
   379  	}
   380  }
   381  
   382  type staticFileNode string
   383  
   384  func (s staticFileNode) Attr() fuse.Attr {
   385  	return fuse.Attr{
   386  		Mode:   0400,
   387  		Uid:    uint32(os.Getuid()),
   388  		Gid:    uint32(os.Getgid()),
   389  		Size:   uint64(len(s)),
   390  		Mtime:  serverStart,
   391  		Ctime:  serverStart,
   392  		Crtime: serverStart,
   393  	}
   394  }
   395  
   396  func (s staticFileNode) Read(req *fuse.ReadRequest, res *fuse.ReadResponse, intr fusefs.Intr) fuse.Error {
   397  	if req.Offset > int64(len(s)) {
   398  		return nil
   399  	}
   400  	s = s[req.Offset:]
   401  	size := req.Size
   402  	if size > len(s) {
   403  		size = len(s)
   404  	}
   405  	res.Data = make([]byte, size)
   406  	copy(res.Data, s)
   407  	return nil
   408  }
   409  
   410  func (n staticFileNode) Getxattr(*fuse.GetxattrRequest, *fuse.GetxattrResponse, fusefs.Intr) fuse.Error {
   411  	return fuse.ENODATA
   412  }
   413  
   414  func (n staticFileNode) Listxattr(*fuse.ListxattrRequest, *fuse.ListxattrResponse, fusefs.Intr) fuse.Error {
   415  	return nil
   416  }
   417  
   418  func (n staticFileNode) Setxattr(*fuse.SetxattrRequest, fusefs.Intr) fuse.Error {
   419  	return fuse.EPERM
   420  }
   421  
   422  func (n staticFileNode) Removexattr(*fuse.RemovexattrRequest, fusefs.Intr) fuse.Error {
   423  	return fuse.EPERM
   424  }