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 }