Home Download Docs Code Community
     1	//go:build linux
     2	
     3	/*
     4	Copyright 2014 The Perkeep Authors
     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
    20	
    21	import (
    22		"context"
    23		"errors"
    24		"fmt"
    25		"os"
    26		"path/filepath"
    27		"strings"
    28		"sync"
    29		"time"
    30	
    31		"bazil.org/fuse"
    32		"bazil.org/fuse/fs"
    33		"perkeep.org/pkg/blob"
    34		"perkeep.org/pkg/schema"
    35		"perkeep.org/pkg/search"
    36	)
    37	
    38	// roVersionsDir is a fuse directory that represents
    39	// the current state of a permanode directory.
    40	// Unlike roDir, a file within roVersionsDir
    41	// is presented as a directory (roFileVersionsDir) containing
    42	// the different versions of the file (roFileVersion).
    43	// It is read-only.
    44	// Its permanode is the permanode with camliPath:entname attributes.
    45	//
    46	//	TODO: There might be a way to reuse roDir
    47	type roVersionsDir struct {
    48		fs        *CamliFileSystem
    49		permanode blob.Ref
    50		parent    *roVersionsDir // or nil, if the parent is versionsDir
    51		name      string         // ent name (base name within parent)
    52	
    53		mu       sync.Mutex
    54		children map[string]roFileOrDir
    55		xattrs   map[string][]byte
    56	}
    57	
    58	func newROVersionsDir(fs *CamliFileSystem, permanode blob.Ref, name string) *roVersionsDir {
    59		return &roVersionsDir{
    60			fs:        fs,
    61			permanode: permanode,
    62			name:      name,
    63		}
    64	}
    65	
    66	// for debugging
    67	func (n *roVersionsDir) fullPath() string {
    68		if n == nil {
    69			return ""
    70		}
    71		return filepath.Join(n.parent.fullPath(), n.name)
    72	}
    73	
    74	func (n *roVersionsDir) Attr(ctx context.Context, a *fuse.Attr) error {
    75		a.Mode = os.ModeDir | 0500
    76		a.Uid = uint32(os.Getuid())
    77		a.Gid = uint32(os.Getgid())
    78		a.Inode = n.permanode.Sum64()
    79		return nil
    80	}
    81	
    82	// populate hits the blobstore to populate map of child nodes.
    83	func (n *roVersionsDir) populate(ctx context.Context) error {
    84		n.mu.Lock()
    85		defer n.mu.Unlock()
    86	
    87		// Things never change here, so if we've ever populated, we're
    88		// populated.
    89		if n.children != nil {
    90			return nil
    91		}
    92	
    93		Logger.Printf("roVersionsDir.populate(%q)", n.fullPath())
    94	
    95		res, err := n.fs.client.Describe(ctx, &search.DescribeRequest{
    96			BlobRef: n.permanode,
    97			Depth:   3,
    98		})
    99		if err != nil {
   100			Logger.Println("roVersionsDir.paths:", err)
   101			return fmt.Errorf("error while describing permanode: %w", err)
   102		}
   103		db := res.Meta[n.permanode.String()]
   104		if db == nil {
   105			return errors.New("dir blobref not described")
   106		}
   107	
   108		// Find all child permanodes and stick them in n.children
   109		n.children = make(map[string]roFileOrDir)
   110	
   111		for k, v := range db.Permanode.Attr {
   112			const p = "camliPath:"
   113			if !strings.HasPrefix(k, p) || len(v) < 1 {
   114				continue
   115			}
   116			name := k[len(p):]
   117			childRef := v[0]
   118			child := res.Meta[childRef]
   119			if child == nil {
   120				Logger.Printf("child not described: %v", childRef)
   121				continue
   122			}
   123			if child.Permanode == nil {
   124				Logger.Printf("child Permanode is nil: %v", childRef)
   125				continue
   126			}
   127			if target := child.Permanode.Attr.Get("camliSymlinkTarget"); target != "" {
   128				// This is a symlink.
   129				n.children[name] = &roFileVersionsDir{
   130					fs:        n.fs,
   131					permanode: blob.ParseOrZero(childRef),
   132					parent:    n,
   133					name:      name,
   134					symLink:   true,
   135					target:    target,
   136				}
   137			} else if isDir(child.Permanode) {
   138				// This is a directory.
   139				n.children[name] = &roVersionsDir{
   140					fs:        n.fs,
   141					permanode: blob.ParseOrZero(childRef),
   142					parent:    n,
   143					name:      name,
   144				}
   145			} else if contentRef := child.Permanode.Attr.Get("camliContent"); contentRef != "" {
   146				// This is a file.
   147				content := res.Meta[contentRef]
   148				if content == nil {
   149					Logger.Printf("child content not described: %v", childRef)
   150					continue
   151				}
   152				if content.CamliType != "file" {
   153					Logger.Printf("child not a file: %v", childRef)
   154					continue
   155				}
   156				n.children[name] = &roFileVersionsDir{
   157					fs:        n.fs,
   158					permanode: blob.ParseOrZero(childRef),
   159					parent:    n,
   160					name:      name,
   161				}
   162			} else {
   163				// unknown type
   164				continue
   165			}
   166			n.children[name].xattr().load(child.Permanode)
   167		}
   168		return nil
   169	}
   170	
   171	func (n *roVersionsDir) ReadDir(ctx context.Context) ([]fuse.Dirent, error) {
   172		if err := n.populate(ctx); err != nil {
   173			Logger.Println("populate:", err)
   174			return nil, handleEIOorEINTR(err)
   175		}
   176		n.mu.Lock()
   177		defer n.mu.Unlock()
   178		var ents []fuse.Dirent
   179		for name, childNode := range n.children {
   180			var ino uint64
   181			switch v := childNode.(type) {
   182			case *roVersionsDir:
   183				ino = v.permanode.Sum64()
   184			case *roFileVersion:
   185				ino = v.permanode.Sum64()
   186			default:
   187				Logger.Printf("roVersionsDir.ReadDir: unknown child type %T", childNode)
   188			}
   189	
   190			// TODO: figure out what Dirent.Type means.
   191			// fuse.go says "Type uint32 // ?"
   192			dirent := fuse.Dirent{
   193				Name:  name,
   194				Inode: ino,
   195			}
   196			Logger.Printf("roVersionsDir(%q) appending inode %x, %+v", n.fullPath(), dirent.Inode, dirent)
   197			ents = append(ents, dirent)
   198		}
   199		return ents, nil
   200	}
   201	
   202	func (n *roVersionsDir) Lookup(ctx context.Context, name string) (ret fs.Node, err error) {
   203		defer func() {
   204			Logger.Printf("roVersionsDir(%q).Lookup(%q) = %#v, %v", n.fullPath(), name, ret, err)
   205		}()
   206		if err := n.populate(ctx); err != nil {
   207			Logger.Println("populate:", err)
   208			return nil, handleEIOorEINTR(err)
   209		}
   210		n.mu.Lock()
   211		defer n.mu.Unlock()
   212		if n2 := n.children[name]; n2 != nil {
   213			return n2, nil
   214		}
   215		return nil, fuse.ENOENT
   216	}
   217	
   218	// roFileVersionsDir is a fuse directory that represents
   219	// a permandode file. It contains the different versions
   220	// of the file (roFileVersion).
   221	// It is read-only.
   222	type roFileVersionsDir struct {
   223		fs        *CamliFileSystem
   224		permanode blob.Ref
   225		parent    *roVersionsDir
   226		name      string // ent name (base name within parent)
   227	
   228		symLink bool   // if true, is a symlink
   229		target  string // if a symlink
   230	
   231		mu       sync.Mutex
   232		children map[string]roFileOrDir
   233		xattrs   map[string][]byte
   234	}
   235	
   236	// for debugging
   237	func (n *roFileVersionsDir) fullPath() string {
   238		if n == nil {
   239			return ""
   240		}
   241		return filepath.Join(n.parent.fullPath(), n.name)
   242	}
   243	
   244	func (n *roFileVersionsDir) Attr(ctx context.Context, a *fuse.Attr) error {
   245		a.Inode = n.permanode.Sum64()
   246		a.Mode = os.ModeDir | 0500
   247		a.Uid = uint32(os.Getuid())
   248		a.Gid = uint32(os.Getgid())
   249		return nil
   250	}
   251	
   252	// populate hits the blobstore to populate map of child nodes.
   253	func (n *roFileVersionsDir) populate(ctx context.Context) error {
   254		n.mu.Lock()
   255		defer n.mu.Unlock()
   256	
   257		// Things never change here, so if we've ever populated, we're
   258		// populated.
   259		if n.children != nil {
   260			return nil
   261		}
   262	
   263		Logger.Printf("roFileVersionsDir.populate(%q)", n.fullPath())
   264		res, err := n.fs.client.GetClaims(ctx, &search.ClaimsRequest{Permanode: n.permanode, AttrFilter: "camliContent"})
   265		if err != nil {
   266			return fmt.Errorf("error while getting claims: %w", err)
   267		}
   268	
   269		n.children = make(map[string]roFileOrDir)
   270		for _, cl := range res.Claims {
   271			pn, ok := blob.Parse(cl.Value)
   272			if !ok {
   273				return errors.New("invalid blobref")
   274			}
   275			res, err := n.fs.client.Describe(ctx, &search.DescribeRequest{
   276				BlobRef: pn, // this is camliContent
   277				Depth:   1,
   278				At:      cl.Date,
   279			})
   280			if err != nil {
   281				return fmt.Errorf("blobref not described: %w", err)
   282			}
   283			db := res.Meta[cl.Value]
   284			if db == nil {
   285				return errors.New("blobref not described")
   286			}
   287			name := cl.Date.String()
   288			n.children[name] = &roFileVersion{
   289				fs:        n.fs,
   290				permanode: n.permanode,
   291				parent:    n,
   292				name:      name,
   293				content:   db.BlobRef,
   294				size:      db.File.Size,
   295				mtime:     cl.Date.Time(),
   296			}
   297	
   298		}
   299		return nil
   300	}
   301	
   302	func (n *roFileVersionsDir) ReadDir(ctx context.Context) ([]fuse.Dirent, error) {
   303		if err := n.populate(ctx); err != nil {
   304			Logger.Println("populate:", err)
   305			return nil, handleEIOorEINTR(err)
   306		}
   307		n.mu.Lock()
   308		defer n.mu.Unlock()
   309		var ents []fuse.Dirent
   310		for name, childNode := range n.children {
   311			var ino uint64
   312			switch v := childNode.(type) {
   313			case *roDir:
   314				ino = v.permanode.Sum64()
   315			case *roFile:
   316				ino = v.permanode.Sum64()
   317			default:
   318				Logger.Printf("roFileVersionsDir.ReadDir: unknown child type %T", childNode)
   319			}
   320	
   321			// TODO: figure out what Dirent.Type means.
   322			// fuse.go says "Type uint32 // ?"
   323			dirent := fuse.Dirent{
   324				Name:  name,
   325				Inode: ino,
   326			}
   327			Logger.Printf("roFileVersionsDir(%q) appending inode %x, %+v", n.fullPath(), dirent.Inode, dirent)
   328			ents = append(ents, dirent)
   329		}
   330		return ents, nil
   331	}
   332	
   333	func (n *roFileVersionsDir) Lookup(ctx context.Context, name string) (ret fs.Node, err error) {
   334		defer func() {
   335			Logger.Printf("roFileVersionsDir(%q).Lookup(%q) = %#v, %v", n.fullPath(), name, ret, err)
   336		}()
   337		if err := n.populate(ctx); err != nil {
   338			Logger.Println("populate:", err)
   339			return nil, handleEIOorEINTR(err)
   340		}
   341		n.mu.Lock()
   342		defer n.mu.Unlock()
   343		if n2 := n.children[name]; n2 != nil {
   344			return n2, nil
   345		}
   346		return nil, fuse.ENOENT
   347	}
   348	
   349	// roFileVersion is a fuse file that represents
   350	// a permanode file at a specific point in time.
   351	// It is read-only.
   352	type roFileVersion struct {
   353		fs        *CamliFileSystem
   354		permanode blob.Ref
   355		parent    *roFileVersionsDir
   356		name      string // ent name (base name within parent)
   357	
   358		mu           sync.Mutex // protects all following fields
   359		symLink      bool       // if true, is a symlink
   360		content      blob.Ref   // if a regular file
   361		size         int64
   362		mtime, atime time.Time // if zero, use serverStart
   363		xattrs       map[string][]byte
   364	}
   365	
   366	func (n *roFileVersion) Open(ctx context.Context, req *fuse.OpenRequest, res *fuse.OpenResponse) (fs.Handle, error) {
   367		roFileOpen.Incr()
   368	
   369		if isWriteFlags(req.Flags) {
   370			return nil, fuse.EPERM
   371		}
   372	
   373		Logger.Printf("roFile.Open: %v: content: %v dir=%v flags=%v", n.permanode, n.content, req.Dir, req.Flags)
   374		r, err := schema.NewFileReader(ctx, n.fs.fetcher, n.content)
   375		if err != nil {
   376			roFileOpenError.Incr()
   377			Logger.Printf("roFile.Open: %v", err)
   378			return nil, handleEIOorEINTR(err)
   379		}
   380	
   381		// Turn off the OpenDirectIO bit (on by default in rsc fuse server.go),
   382		// else append operations don't work for some reason.
   383		res.Flags &= ^fuse.OpenDirectIO
   384	
   385		// Read-only.
   386		nod := &node{
   387			fs:      n.fs,
   388			blobref: n.content,
   389		}
   390		return &nodeReader{n: nod, fr: r}, nil
   391	}
   392	
   393	func (n *roVersionsDir) Getxattr(ctx context.Context, req *fuse.GetxattrRequest, res *fuse.GetxattrResponse) error {
   394		return n.xattr().get(req, res)
   395	}
   396	
   397	func (n *roVersionsDir) Listxattr(ctx context.Context, req *fuse.ListxattrRequest, res *fuse.ListxattrResponse) error {
   398		return n.xattr().list(req, res)
   399	}
   400	
   401	func (n *roFileVersion) Getxattr(ctx context.Context, req *fuse.GetxattrRequest, res *fuse.GetxattrResponse) error {
   402		return n.xattr().get(req, res)
   403	}
   404	
   405	func (n *roFileVersion) Listxattr(ctx context.Context, req *fuse.ListxattrRequest, res *fuse.ListxattrResponse) error {
   406		return n.xattr().list(req, res)
   407	}
   408	
   409	func (n *roFileVersion) Removexattr(ctx context.Context, req *fuse.RemovexattrRequest) error {
   410		return fuse.EPERM
   411	}
   412	
   413	func (n *roFileVersion) Setxattr(ctx context.Context, req *fuse.SetxattrRequest) error {
   414		return fuse.EPERM
   415	}
   416	
   417	func (n *roFileVersion) Attr(ctx context.Context, a *fuse.Attr) error {
   418		// TODO: don't grab n.mu three+ times in here.
   419		var mode os.FileMode = 0400 // read-only
   420	
   421		n.mu.Lock()
   422		size := n.size
   423		var blocks uint64
   424		if size > 0 {
   425			blocks = uint64(size)/512 + 1
   426		}
   427		inode := n.permanode.Sum64()
   428		if n.symLink {
   429			mode |= os.ModeSymlink
   430		}
   431		n.mu.Unlock()
   432	
   433		*a = fuse.Attr{
   434			Inode:  inode,
   435			Mode:   mode,
   436			Uid:    uint32(os.Getuid()),
   437			Gid:    uint32(os.Getgid()),
   438			Size:   uint64(size),
   439			Blocks: blocks,
   440			Mtime:  n.modTime(),
   441			Atime:  n.accessTime(),
   442			Ctime:  serverStart,
   443		}
   444		return nil
   445	}
   446	
   447	func (n *roFileVersion) accessTime() time.Time {
   448		n.mu.Lock()
   449		if !n.atime.IsZero() {
   450			defer n.mu.Unlock()
   451			return n.atime
   452		}
   453		n.mu.Unlock()
   454		return n.modTime()
   455	}
   456	
   457	func (n *roFileVersion) modTime() time.Time {
   458		n.mu.Lock()
   459		defer n.mu.Unlock()
   460		if !n.mtime.IsZero() {
   461			return n.mtime
   462		}
   463		return serverStart
   464	}
   465	
   466	func (n *roFileVersion) Fsync(ctx context.Context, r *fuse.FsyncRequest) error {
   467		// noop
   468		return nil
   469	}
   470	
   471	func (n *roFileVersion) permanodeString() string {
   472		return n.permanode.String()
   473	}
   474	
   475	func (n *roFileVersionsDir) permanodeString() string {
   476		return n.permanode.String()
   477	}
   478	
   479	func (n *roVersionsDir) permanodeString() string {
   480		return n.permanode.String()
   481	}
   482	
   483	func (n *roFileVersion) xattr() *xattr {
   484		return &xattr{"roFileVersion", n.fs, n.permanode, &n.mu, &n.xattrs}
   485	}
   486	
   487	func (n *roFileVersionsDir) xattr() *xattr {
   488		return &xattr{"roFileVersionsDir", n.fs, n.permanode, &n.mu, &n.xattrs}
   489	}
   490	
   491	func (n *roVersionsDir) xattr() *xattr {
   492		return &xattr{"roVersionsDir", n.fs, n.permanode, &n.mu, &n.xattrs}
   493	}
Website layout inspired by memcached.
Content by the authors.