Home Download Docs Code Community
     1	//go:build linux
     2	
     3	/*
     4	Copyright 2012 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		"os"
    24		"strings"
    25		"sync"
    26		"time"
    27	
    28		"bazil.org/fuse"
    29		"bazil.org/fuse/fs"
    30		"go4.org/syncutil"
    31		"perkeep.org/pkg/blob"
    32		"perkeep.org/pkg/schema"
    33		"perkeep.org/pkg/search"
    34	)
    35	
    36	const refreshTime = 1 * time.Minute
    37	
    38	type rootsDir struct {
    39		fs *CamliFileSystem
    40		at time.Time
    41	
    42		mu        sync.Mutex // guards following
    43		lastQuery time.Time
    44		m         map[string]blob.Ref // ent name => permanode
    45		children  map[string]fs.Node  // ent name => child node
    46	}
    47	
    48	var (
    49		_ fs.Node               = (*rootsDir)(nil)
    50		_ fs.HandleReadDirAller = (*rootsDir)(nil)
    51		_ fs.NodeRemover        = (*rootsDir)(nil)
    52		_ fs.NodeRenamer        = (*rootsDir)(nil)
    53		_ fs.NodeStringLookuper = (*rootsDir)(nil)
    54		_ fs.NodeMkdirer        = (*rootsDir)(nil)
    55	)
    56	
    57	func (n *rootsDir) isRO() bool {
    58		return !n.at.IsZero()
    59	}
    60	
    61	func (n *rootsDir) dirMode() os.FileMode {
    62		if n.isRO() {
    63			return 0500
    64		}
    65		return 0700
    66	}
    67	
    68	func (n *rootsDir) Attr(ctx context.Context, a *fuse.Attr) error {
    69		a.Mode = os.ModeDir | n.dirMode()
    70		a.Uid = uint32(os.Getuid())
    71		a.Gid = uint32(os.Getgid())
    72		return nil
    73	}
    74	
    75	func (n *rootsDir) ReadDirAll(ctx context.Context) ([]fuse.Dirent, error) {
    76		n.mu.Lock()
    77		defer n.mu.Unlock()
    78		if err := n.condRefresh(ctx); err != nil {
    79			return nil, handleEIOorEINTR(err)
    80		}
    81		var ents []fuse.Dirent
    82		for name := range n.m {
    83			ents = append(ents, fuse.Dirent{Name: name})
    84		}
    85		Logger.Printf("rootsDir.ReadDirAll() -> %v", ents)
    86		return ents, nil
    87	}
    88	
    89	func (n *rootsDir) Remove(ctx context.Context, req *fuse.RemoveRequest) error {
    90		if n.isRO() {
    91			return fuse.EPERM
    92		}
    93		n.mu.Lock()
    94		defer n.mu.Unlock()
    95	
    96		if err := n.condRefresh(ctx); err != nil {
    97			return handleEIOorEINTR(err)
    98		}
    99		br := n.m[req.Name]
   100		if !br.Valid() {
   101			return fuse.ENOENT
   102		}
   103	
   104		claim := schema.NewDelAttributeClaim(br, "camliRoot", "")
   105		_, err := n.fs.client.UploadAndSignBlob(ctx, claim)
   106		if err != nil {
   107			Logger.Println("rootsDir.Remove:", err)
   108			return handleEIOorEINTR(err)
   109		}
   110	
   111		delete(n.m, req.Name)
   112		delete(n.children, req.Name)
   113	
   114		return nil
   115	}
   116	
   117	func (n *rootsDir) Rename(ctx context.Context, req *fuse.RenameRequest, newDir fs.Node) error {
   118		Logger.Printf("rootsDir.Rename %q -> %q", req.OldName, req.NewName)
   119		if n.isRO() {
   120			return fuse.EPERM
   121		}
   122	
   123		n.mu.Lock()
   124		target, exists := n.m[req.OldName]
   125		_, collision := n.m[req.NewName]
   126		n.mu.Unlock()
   127		if !exists {
   128			Logger.Printf("*rootsDir.Rename src name %q isn't known", req.OldName)
   129			return fuse.ENOENT
   130		}
   131		if collision {
   132			Logger.Printf("*rootsDir.Rename dest %q already exists", req.NewName)
   133			return fuse.EIO
   134		}
   135	
   136		// Don't allow renames if the root contains content.  Rename
   137		// is mostly implemented to make GUIs that create directories
   138		// before asking for the directory name.
   139		res, err := n.fs.client.Describe(ctx, &search.DescribeRequest{BlobRef: target})
   140		if err != nil {
   141			Logger.Println("rootsDir.Rename:", err)
   142			return handleEIOorEINTR(err)
   143		}
   144		db := res.Meta[target.String()]
   145		if db == nil {
   146			Logger.Printf("Failed to pull meta for target: %v", target)
   147			return fuse.EIO
   148		}
   149	
   150		for k := range db.Permanode.Attr {
   151			const p = "camliPath:"
   152			if strings.HasPrefix(k, p) {
   153				Logger.Printf("Found file in %q: %q, disallowing rename", req.OldName, k[len(p):])
   154				return fuse.EIO
   155			}
   156		}
   157	
   158		claim := schema.NewSetAttributeClaim(target, "camliRoot", req.NewName)
   159		_, err = n.fs.client.UploadAndSignBlob(ctx, claim)
   160		if err != nil {
   161			Logger.Printf("Upload rename link error: %v", err)
   162			return handleEIOorEINTR(err)
   163		}
   164	
   165		// Comment transplanted from mutDir.Rename
   166		// TODO(bradfitz): this locking would be racy, if the kernel
   167		// doesn't do it properly. (It should) Let's just trust the
   168		// kernel for now. Later we can verify and remove this
   169		// comment.
   170		n.mu.Lock()
   171		if n.m[req.OldName] != target {
   172			panic("Race.")
   173		}
   174		delete(n.m, req.OldName)
   175		delete(n.children, req.OldName)
   176		delete(n.children, req.NewName)
   177		n.m[req.NewName] = target
   178		n.mu.Unlock()
   179	
   180		return nil
   181	}
   182	
   183	func (n *rootsDir) Lookup(ctx context.Context, name string) (fs.Node, error) {
   184		Logger.Printf("fs.roots: Lookup(%q)", name)
   185		n.mu.Lock()
   186		defer n.mu.Unlock()
   187		if err := n.condRefresh(ctx); err != nil {
   188			return nil, handleEIOorEINTR(err)
   189		}
   190		br := n.m[name]
   191		if !br.Valid() {
   192			return nil, fuse.ENOENT
   193		}
   194	
   195		nod, ok := n.children[name]
   196		if ok {
   197			return nod, nil
   198		}
   199	
   200		if n.isRO() {
   201			nod = newRODir(n.fs, br, name, n.at)
   202		} else {
   203			nod = &mutDir{
   204				fs:        n.fs,
   205				permanode: br,
   206				name:      name,
   207				xattrs:    make(map[string][]byte),
   208				children:  make(map[string]mutFileOrDir),
   209			}
   210		}
   211		n.children[name] = nod
   212	
   213		return nod, nil
   214	}
   215	
   216	// requires n.mu is held
   217	func (n *rootsDir) condRefresh(ctx context.Context) error {
   218		if n.lastQuery.After(time.Now().Add(-refreshTime)) {
   219			return nil
   220		}
   221		Logger.Printf("fs.roots: querying")
   222	
   223		var rootRes, impRes *search.WithAttrResponse
   224		var grp syncutil.Group
   225		grp.Go(func() (err error) {
   226			// TODO(mpl): use a search query instead.
   227			rootRes, err = n.fs.client.GetPermanodesWithAttr(ctx, &search.WithAttrRequest{N: 100, Attr: "camliRoot", At: n.at})
   228			return
   229		})
   230		grp.Go(func() (err error) {
   231			impRes, err = n.fs.client.GetPermanodesWithAttr(ctx, &search.WithAttrRequest{N: 100, Attr: "camliImportRoot", At: n.at})
   232			return
   233		})
   234		if err := grp.Err(); err != nil {
   235			Logger.Printf("fs.roots: error refreshing permanodes: %v", err)
   236			return err
   237		}
   238	
   239		n.m = make(map[string]blob.Ref)
   240		if n.children == nil {
   241			n.children = make(map[string]fs.Node)
   242		}
   243	
   244		dr := &search.DescribeRequest{
   245			Depth: 1,
   246		}
   247		for _, wi := range rootRes.WithAttr {
   248			dr.BlobRefs = append(dr.BlobRefs, wi.Permanode)
   249		}
   250		for _, wi := range impRes.WithAttr {
   251			dr.BlobRefs = append(dr.BlobRefs, wi.Permanode)
   252		}
   253		if len(dr.BlobRefs) == 0 {
   254			return nil
   255		}
   256	
   257		dres, err := n.fs.client.Describe(ctx, dr)
   258		if err != nil {
   259			Logger.Printf("Describe failure: %v", err)
   260			return err
   261		}
   262	
   263		// Roots
   264		currentRoots := map[string]bool{}
   265		for _, wi := range rootRes.WithAttr {
   266			pn := wi.Permanode
   267			db := dres.Meta[pn.String()]
   268			if db != nil && db.Permanode != nil {
   269				name := db.Permanode.Attr.Get("camliRoot")
   270				if name != "" {
   271					currentRoots[name] = true
   272					n.m[name] = pn
   273				}
   274			}
   275		}
   276	
   277		// Remove any children objects we have mapped that are no
   278		// longer relevant.
   279		for name := range n.children {
   280			if !currentRoots[name] {
   281				delete(n.children, name)
   282			}
   283		}
   284	
   285		// Importers (mapped as roots for now)
   286		for _, wi := range impRes.WithAttr {
   287			pn := wi.Permanode
   288			db := dres.Meta[pn.String()]
   289			if db != nil && db.Permanode != nil {
   290				name := db.Permanode.Attr.Get("camliImportRoot")
   291				if name != "" {
   292					name = strings.Replace(name, ":", "-", -1)
   293					name = strings.Replace(name, "/", "-", -1)
   294					n.m["importer-"+name] = pn
   295				}
   296			}
   297		}
   298	
   299		n.lastQuery = time.Now()
   300		return nil
   301	}
   302	
   303	func (n *rootsDir) Mkdir(ctx context.Context, req *fuse.MkdirRequest) (fs.Node, error) {
   304		if n.isRO() {
   305			return nil, fuse.EPERM
   306		}
   307	
   308		name := req.Name
   309	
   310		// Create a Permanode for the root.
   311		pr, err := n.fs.client.UploadNewPermanode(ctx)
   312		if err != nil {
   313			Logger.Printf("rootsDir.Create(%q): %v", name, err)
   314			return nil, handleEIOorEINTR(err)
   315		}
   316	
   317		var grp syncutil.Group
   318		// Add a camliRoot attribute to the root permanode.
   319		grp.Go(func() (err error) {
   320			claim := schema.NewSetAttributeClaim(pr.BlobRef, "camliRoot", name)
   321			_, err = n.fs.client.UploadAndSignBlob(ctx, claim)
   322			return
   323		})
   324		// Set the title of the root permanode to the root name.
   325		grp.Go(func() (err error) {
   326			claim := schema.NewSetAttributeClaim(pr.BlobRef, "title", name)
   327			_, err = n.fs.client.UploadAndSignBlob(ctx, claim)
   328			return
   329		})
   330		if err := grp.Err(); err != nil {
   331			Logger.Printf("rootsDir.Create(%q): %v", name, err)
   332			return nil, handleEIOorEINTR(err)
   333		}
   334	
   335		nod := &mutDir{
   336			fs:        n.fs,
   337			permanode: pr.BlobRef,
   338			name:      name,
   339			xattrs:    make(map[string][]byte),
   340			children:  make(map[string]mutFileOrDir),
   341		}
   342		n.mu.Lock()
   343		n.m[name] = pr.BlobRef
   344		n.mu.Unlock()
   345	
   346		return nod, nil
   347	}
Website layout inspired by memcached.
Content by the authors.