github.com/pachyderm/pachyderm@v1.13.4/src/server/pfs/fuse/loopback.go (about)

     1  // Copyright 2019 the Go-FUSE Authors. All rights reserved.
     2  // Use of this source code is governed by a BSD-style
     3  // license that can be found in the LICENSE file.
     4  
     5  package fuse
     6  
     7  import (
     8  	"context"
     9  	"os"
    10  	pathpkg "path"
    11  	"path/filepath"
    12  	"strings"
    13  	"sync"
    14  	"syscall"
    15  
    16  	"github.com/hanwen/go-fuse/v2/fs"
    17  	"github.com/hanwen/go-fuse/v2/fuse"
    18  
    19  	"github.com/pachyderm/pachyderm/src/client"
    20  	"github.com/pachyderm/pachyderm/src/client/pfs"
    21  	"github.com/pachyderm/pachyderm/src/client/pkg/errors"
    22  	pfsserver "github.com/pachyderm/pachyderm/src/server/pfs"
    23  	"github.com/pachyderm/pachyderm/src/server/pkg/errutil"
    24  )
    25  
    26  type fileState int32
    27  
    28  const (
    29  	none  fileState = iota // we don't know about this file
    30  	meta                   // we have meta information (but not content for this file)
    31  	full                   // we have full content for this file
    32  	dirty                  // we have full content for this file and the user has written to it
    33  )
    34  
    35  type loopbackRoot struct {
    36  	loopbackNode
    37  
    38  	rootPath string
    39  	rootDev  uint64
    40  
    41  	targetPath string
    42  
    43  	write bool
    44  
    45  	c *client.APIClient
    46  
    47  	repoOpts map[string]*RepoOptions
    48  	branches map[string]string
    49  	commits  map[string]string
    50  	files    map[string]fileState
    51  	mu       sync.Mutex
    52  }
    53  
    54  type loopbackNode struct {
    55  	fs.Inode
    56  }
    57  
    58  var _ = (fs.NodeStatfser)((*loopbackNode)(nil))
    59  var _ = (fs.NodeStatfser)((*loopbackNode)(nil))
    60  var _ = (fs.NodeGetattrer)((*loopbackNode)(nil))
    61  var _ = (fs.NodeGetxattrer)((*loopbackNode)(nil))
    62  var _ = (fs.NodeSetxattrer)((*loopbackNode)(nil))
    63  var _ = (fs.NodeRemovexattrer)((*loopbackNode)(nil))
    64  var _ = (fs.NodeListxattrer)((*loopbackNode)(nil))
    65  var _ = (fs.NodeReadlinker)((*loopbackNode)(nil))
    66  var _ = (fs.NodeOpener)((*loopbackNode)(nil))
    67  var _ = (fs.NodeCopyFileRanger)((*loopbackNode)(nil))
    68  var _ = (fs.NodeLookuper)((*loopbackNode)(nil))
    69  var _ = (fs.NodeOpendirer)((*loopbackNode)(nil))
    70  var _ = (fs.NodeReaddirer)((*loopbackNode)(nil))
    71  var _ = (fs.NodeMkdirer)((*loopbackNode)(nil))
    72  var _ = (fs.NodeMknoder)((*loopbackNode)(nil))
    73  var _ = (fs.NodeLinker)((*loopbackNode)(nil))
    74  var _ = (fs.NodeSymlinker)((*loopbackNode)(nil))
    75  var _ = (fs.NodeUnlinker)((*loopbackNode)(nil))
    76  var _ = (fs.NodeRmdirer)((*loopbackNode)(nil))
    77  var _ = (fs.NodeRenamer)((*loopbackNode)(nil))
    78  
    79  func (n *loopbackNode) Statfs(ctx context.Context, out *fuse.StatfsOut) syscall.Errno {
    80  	s := syscall.Statfs_t{}
    81  	err := syscall.Statfs(n.path(), &s)
    82  	if err != nil {
    83  		return fs.ToErrno(err)
    84  	}
    85  	out.FromStatfsT(&s)
    86  	return fs.OK
    87  }
    88  
    89  func (r *loopbackRoot) Getattr(ctx context.Context, f fs.FileHandle, out *fuse.AttrOut) syscall.Errno {
    90  
    91  	st := syscall.Stat_t{}
    92  	err := syscall.Stat(r.rootPath, &st)
    93  	if err != nil {
    94  		return fs.ToErrno(err)
    95  	}
    96  	out.FromStat(&st)
    97  	return fs.OK
    98  }
    99  
   100  func (n *loopbackNode) root() *loopbackRoot {
   101  	return n.Root().Operations().(*loopbackRoot)
   102  }
   103  
   104  func (n *loopbackNode) c() *client.APIClient {
   105  	return n.root().c
   106  }
   107  
   108  func (n *loopbackNode) path() string {
   109  	path := n.Path(nil)
   110  	return filepath.Join(n.root().rootPath, path)
   111  }
   112  
   113  func (n *loopbackNode) Lookup(ctx context.Context, name string, out *fuse.EntryOut) (*fs.Inode, syscall.Errno) {
   114  	p := filepath.Join(n.path(), name)
   115  	if err := n.download(p, meta); err != nil {
   116  		return nil, fs.ToErrno(err)
   117  	}
   118  
   119  	st := syscall.Stat_t{}
   120  	err := syscall.Lstat(p, &st)
   121  	if err != nil {
   122  		return nil, fs.ToErrno(err)
   123  	}
   124  
   125  	out.Attr.FromStat(&st)
   126  	node := &loopbackNode{}
   127  	ch := n.NewInode(ctx, node, n.root().idFromStat(&st))
   128  	return ch, 0
   129  }
   130  
   131  func (n *loopbackNode) Mknod(ctx context.Context, name string, mode, rdev uint32, out *fuse.EntryOut) (*fs.Inode, syscall.Errno) {
   132  	p := filepath.Join(n.path(), name)
   133  	if errno := n.checkWrite(p); errno != 0 {
   134  		return nil, errno
   135  	}
   136  	err := syscall.Mknod(p, mode, int(rdev))
   137  	if err != nil {
   138  		return nil, fs.ToErrno(err)
   139  	}
   140  	st := syscall.Stat_t{}
   141  	if err := syscall.Lstat(p, &st); err != nil {
   142  		syscall.Rmdir(p)
   143  		return nil, fs.ToErrno(err)
   144  	}
   145  
   146  	out.Attr.FromStat(&st)
   147  
   148  	node := &loopbackNode{}
   149  	ch := n.NewInode(ctx, node, n.root().idFromStat(&st))
   150  
   151  	return ch, 0
   152  }
   153  
   154  func (n *loopbackNode) Mkdir(ctx context.Context, name string, mode uint32, out *fuse.EntryOut) (*fs.Inode, syscall.Errno) {
   155  	p := filepath.Join(n.path(), name)
   156  	if errno := n.checkWrite(p); errno != 0 {
   157  		return nil, errno
   158  	}
   159  	if err := n.download(p, meta); err != nil {
   160  		return nil, fs.ToErrno(err)
   161  	}
   162  	err := os.Mkdir(p, os.FileMode(mode))
   163  	if err != nil {
   164  		return nil, fs.ToErrno(err)
   165  	}
   166  	st := syscall.Stat_t{}
   167  	if err := syscall.Lstat(p, &st); err != nil {
   168  		syscall.Rmdir(p)
   169  		return nil, fs.ToErrno(err)
   170  	}
   171  
   172  	out.Attr.FromStat(&st)
   173  
   174  	node := &loopbackNode{}
   175  	ch := n.NewInode(ctx, node, n.root().idFromStat(&st))
   176  
   177  	return ch, 0
   178  }
   179  
   180  func (n *loopbackNode) Rmdir(ctx context.Context, name string) syscall.Errno {
   181  	p := filepath.Join(n.path(), name)
   182  	if errno := n.checkWrite(p); errno != 0 {
   183  		return errno
   184  	}
   185  	if err := n.download(p, meta); err != nil {
   186  		return fs.ToErrno(err)
   187  	}
   188  	err := syscall.Rmdir(p)
   189  	return fs.ToErrno(err)
   190  }
   191  
   192  func (n *loopbackNode) Unlink(ctx context.Context, name string) (errno syscall.Errno) {
   193  	p := filepath.Join(n.path(), name)
   194  	if errno := n.checkWrite(p); errno != 0 {
   195  		return errno
   196  	}
   197  	if err := n.download(p, meta); err != nil {
   198  		return fs.ToErrno(err)
   199  	}
   200  	defer func() {
   201  		if errno == 0 {
   202  			n.setFileState(p, dirty)
   203  		}
   204  	}()
   205  	err := syscall.Unlink(p)
   206  	return fs.ToErrno(err)
   207  }
   208  
   209  func toLoopbackNode(op fs.InodeEmbedder) *loopbackNode {
   210  	if r, ok := op.(*loopbackRoot); ok {
   211  		return &r.loopbackNode
   212  	}
   213  	return op.(*loopbackNode)
   214  }
   215  
   216  func (n *loopbackNode) Rename(ctx context.Context, name string, newParent fs.InodeEmbedder, newName string, flags uint32) syscall.Errno {
   217  	newParentLoopback := toLoopbackNode(newParent)
   218  	if flags&fs.RENAME_EXCHANGE != 0 {
   219  		return n.renameExchange(name, newParentLoopback, newName)
   220  	}
   221  
   222  	p1 := filepath.Join(n.path(), name)
   223  
   224  	p2 := filepath.Join(newParentLoopback.path(), newName)
   225  	if errno := n.checkWrite(p1); errno != 0 {
   226  		return errno
   227  	}
   228  	if errno := n.checkWrite(p2); errno != 0 {
   229  		return errno
   230  	}
   231  	err := os.Rename(p1, p2)
   232  	return fs.ToErrno(err)
   233  }
   234  
   235  func (r *loopbackRoot) idFromStat(st *syscall.Stat_t) fs.StableAttr {
   236  	return fs.StableAttr{
   237  		Mode: uint32(st.Mode),
   238  		Gen:  1,
   239  		Ino:  0, // let fuse generate this automatically
   240  	}
   241  }
   242  
   243  var _ = (fs.NodeCreater)((*loopbackNode)(nil))
   244  
   245  func (n *loopbackNode) Create(ctx context.Context, name string, flags uint32, mode uint32, out *fuse.EntryOut) (inode *fs.Inode, fh fs.FileHandle, fuseFlags uint32, errno syscall.Errno) {
   246  	p := filepath.Join(n.path(), name)
   247  	if errno := n.checkWrite(p); errno != 0 {
   248  		return nil, nil, 0, errno
   249  	}
   250  	if err := n.download(p, full); err != nil {
   251  		return nil, nil, 0, fs.ToErrno(err)
   252  	}
   253  	defer func() {
   254  		if errno == 0 {
   255  			n.setFileState(p, dirty)
   256  		}
   257  	}()
   258  
   259  	fd, err := syscall.Open(p, int(flags)|os.O_CREATE, mode)
   260  	if err != nil {
   261  		return nil, nil, 0, fs.ToErrno(err)
   262  	}
   263  
   264  	st := syscall.Stat_t{}
   265  	if err := syscall.Fstat(fd, &st); err != nil {
   266  		syscall.Close(fd)
   267  		return nil, nil, 0, fs.ToErrno(err)
   268  	}
   269  
   270  	node := &loopbackNode{}
   271  	ch := n.NewInode(ctx, node, n.root().idFromStat(&st))
   272  	lf := NewLoopbackFile(fd)
   273  
   274  	out.FromStat(&st)
   275  	return ch, lf, 0, 0
   276  }
   277  
   278  func (n *loopbackNode) Symlink(ctx context.Context, target, name string, out *fuse.EntryOut) (_ *fs.Inode, errno syscall.Errno) {
   279  	p := filepath.Join(n.path(), name)
   280  	if errno := n.checkWrite(p); errno != 0 {
   281  		return nil, errno
   282  	}
   283  	if err := n.download(p, full); err != nil {
   284  		return nil, fs.ToErrno(err)
   285  	}
   286  	target = filepath.Join(n.root().rootPath, n.trimTargetPath(target))
   287  	if err := n.download(target, full); err != nil {
   288  		return nil, fs.ToErrno(err)
   289  	}
   290  	defer func() {
   291  		if errno == 0 {
   292  			n.setFileState(p, dirty)
   293  		}
   294  	}()
   295  	err := syscall.Symlink(target, p)
   296  	if err != nil {
   297  		return nil, fs.ToErrno(err)
   298  	}
   299  	st := syscall.Stat_t{}
   300  	if syscall.Lstat(p, &st); err != nil {
   301  		syscall.Unlink(p)
   302  		return nil, fs.ToErrno(err)
   303  	}
   304  	node := &loopbackNode{}
   305  	ch := n.NewInode(ctx, node, n.root().idFromStat(&st))
   306  
   307  	out.Attr.FromStat(&st)
   308  	return ch, 0
   309  }
   310  
   311  func (n *loopbackNode) Link(ctx context.Context, target fs.InodeEmbedder, name string, out *fuse.EntryOut) (_ *fs.Inode, errno syscall.Errno) {
   312  	p := filepath.Join(n.path(), name)
   313  	if errno := n.checkWrite(p); errno != 0 {
   314  		return nil, errno
   315  	}
   316  	if err := n.download(p, full); err != nil {
   317  		return nil, fs.ToErrno(err)
   318  	}
   319  	targetNode := toLoopbackNode(target)
   320  	if err := n.download(targetNode.path(), full); err != nil {
   321  		return nil, fs.ToErrno(err)
   322  	}
   323  	err := syscall.Link(targetNode.path(), p)
   324  	if err != nil {
   325  		return nil, fs.ToErrno(err)
   326  	}
   327  	defer func() {
   328  		if errno == 0 {
   329  			n.setFileState(p, dirty)
   330  		}
   331  	}()
   332  	st := syscall.Stat_t{}
   333  	if syscall.Lstat(p, &st); err != nil {
   334  		syscall.Unlink(p)
   335  		return nil, fs.ToErrno(err)
   336  	}
   337  	node := &loopbackNode{}
   338  	ch := n.NewInode(ctx, node, n.root().idFromStat(&st))
   339  
   340  	out.Attr.FromStat(&st)
   341  	return ch, 0
   342  }
   343  
   344  func (n *loopbackNode) Readlink(ctx context.Context) ([]byte, syscall.Errno) {
   345  	p := n.path()
   346  
   347  	for l := 256; ; l *= 2 {
   348  		buf := make([]byte, l)
   349  		sz, err := syscall.Readlink(p, buf)
   350  		if err != nil {
   351  			return nil, fs.ToErrno(err)
   352  		}
   353  
   354  		if sz < len(buf) {
   355  			return buf[:sz], 0
   356  		}
   357  	}
   358  }
   359  
   360  func (n *loopbackNode) Open(ctx context.Context, flags uint32) (fh fs.FileHandle, fuseFlags uint32, errno syscall.Errno) {
   361  	p := n.path()
   362  	state := full
   363  	if isWrite(flags) {
   364  		if errno := n.checkWrite(p); errno != 0 {
   365  			return nil, 0, errno
   366  		}
   367  		state = dirty
   368  	}
   369  	if err := n.download(p, state); err != nil {
   370  		return nil, 0, fs.ToErrno(err)
   371  	}
   372  	if isCreate(flags) {
   373  		defer func() {
   374  			if errno == 0 {
   375  				n.setFileState(p, dirty)
   376  			}
   377  		}()
   378  	}
   379  	f, err := syscall.Open(p, int(flags), 0)
   380  	if err != nil {
   381  		return nil, 0, fs.ToErrno(err)
   382  	}
   383  	lf := NewLoopbackFile(f)
   384  	return lf, 0, 0
   385  }
   386  
   387  func (n *loopbackNode) Opendir(ctx context.Context) syscall.Errno {
   388  	if err := n.download(n.path(), meta); err != nil {
   389  		return fs.ToErrno(err)
   390  	}
   391  	fd, err := syscall.Open(n.path(), syscall.O_DIRECTORY, 0755)
   392  	if err != nil {
   393  		return fs.ToErrno(err)
   394  	}
   395  	syscall.Close(fd)
   396  	return fs.OK
   397  }
   398  
   399  func (n *loopbackNode) Readdir(ctx context.Context) (fs.DirStream, syscall.Errno) {
   400  	if err := n.download(n.path(), meta); err != nil {
   401  		return nil, fs.ToErrno(err)
   402  	}
   403  	return fs.NewLoopbackDirStream(n.path())
   404  }
   405  
   406  func (n *loopbackNode) Getattr(ctx context.Context, f fs.FileHandle, out *fuse.AttrOut) syscall.Errno {
   407  	if f != nil {
   408  		return f.(fs.FileGetattrer).Getattr(ctx, out)
   409  	}
   410  	p := n.path()
   411  	if err := n.download(p, meta); err != nil {
   412  		return fs.ToErrno(err)
   413  	}
   414  
   415  	var err error = nil
   416  	st := syscall.Stat_t{}
   417  	err = syscall.Lstat(p, &st)
   418  	if err != nil {
   419  		return fs.ToErrno(err)
   420  	}
   421  	out.FromStat(&st)
   422  	return fs.OK
   423  }
   424  
   425  var _ = (fs.NodeSetattrer)((*loopbackNode)(nil))
   426  
   427  func (n *loopbackNode) Setattr(ctx context.Context, f fs.FileHandle, in *fuse.SetAttrIn, out *fuse.AttrOut) syscall.Errno {
   428  	p := n.path()
   429  	fsa, ok := f.(fs.FileSetattrer)
   430  	if ok && fsa != nil {
   431  		fsa.Setattr(ctx, in, out)
   432  	} else {
   433  		if m, ok := in.GetMode(); ok {
   434  			if err := syscall.Chmod(p, m); err != nil {
   435  				return fs.ToErrno(err)
   436  			}
   437  		}
   438  
   439  		uid, uok := in.GetUID()
   440  		gid, gok := in.GetGID()
   441  		if uok || gok {
   442  			suid := -1
   443  			sgid := -1
   444  			if uok {
   445  				suid = int(uid)
   446  			}
   447  			if gok {
   448  				sgid = int(gid)
   449  			}
   450  			if err := syscall.Chown(p, suid, sgid); err != nil {
   451  				return fs.ToErrno(err)
   452  			}
   453  		}
   454  
   455  		mtime, mok := in.GetMTime()
   456  		atime, aok := in.GetATime()
   457  
   458  		if mok || aok {
   459  
   460  			ap := &atime
   461  			mp := &mtime
   462  			if !aok {
   463  				ap = nil
   464  			}
   465  			if !mok {
   466  				mp = nil
   467  			}
   468  			var ts [2]syscall.Timespec
   469  			ts[0] = fuse.UtimeToTimespec(ap)
   470  			ts[1] = fuse.UtimeToTimespec(mp)
   471  
   472  			if err := syscall.UtimesNano(p, ts[:]); err != nil {
   473  				return fs.ToErrno(err)
   474  			}
   475  		}
   476  
   477  		if sz, ok := in.GetSize(); ok {
   478  			if err := syscall.Truncate(p, int64(sz)); err != nil {
   479  				return fs.ToErrno(err)
   480  			}
   481  		}
   482  	}
   483  
   484  	fga, ok := f.(fs.FileGetattrer)
   485  	if ok && fga != nil {
   486  		fga.Getattr(ctx, out)
   487  	} else {
   488  		st := syscall.Stat_t{}
   489  		err := syscall.Lstat(p, &st)
   490  		if err != nil {
   491  			return fs.ToErrno(err)
   492  		}
   493  		out.FromStat(&st)
   494  	}
   495  	return fs.OK
   496  }
   497  
   498  // newLoopbackRoot returns a root node for a loopback file system whose
   499  // root is at the given root. This node implements all NodeXxxxer
   500  // operations available.
   501  func newLoopbackRoot(root, target string, c *client.APIClient, opts *Options) (*loopbackRoot, error) {
   502  	var st syscall.Stat_t
   503  	err := syscall.Stat(root, &st)
   504  	if err != nil {
   505  		return nil, errors.WithStack(err)
   506  	}
   507  
   508  	n := &loopbackRoot{
   509  		rootPath:   root,
   510  		rootDev:    uint64(st.Dev),
   511  		targetPath: target,
   512  		write:      opts.getWrite(),
   513  		c:          c,
   514  		repoOpts:   opts.getRepoOpts(),
   515  		branches:   opts.getBranches(),
   516  		commits:    make(map[string]string),
   517  		files:      make(map[string]fileState),
   518  	}
   519  	return n, nil
   520  }
   521  
   522  func (n *loopbackNode) downloadRepos() (retErr error) {
   523  	if n.getFileState("") != none {
   524  		return nil
   525  	}
   526  	defer func() {
   527  		if retErr == nil {
   528  			n.setFileState("", meta)
   529  		}
   530  	}()
   531  	ris, err := n.c().ListRepo()
   532  	if err != nil {
   533  		return err
   534  	}
   535  	ro := n.root().repoOpts
   536  	for _, ri := range ris {
   537  		if len(ro) > 0 && ro[ri.Repo.Name] == nil {
   538  			continue
   539  		}
   540  		p := n.repoPath(ri)
   541  		if err := os.MkdirAll(p, 0777); err != nil {
   542  			return errors.WithStack(err)
   543  		}
   544  	}
   545  	return nil
   546  }
   547  
   548  // download files into the loopback filesystem, if meta is true then only the
   549  // directory structure will be created, no actual data will be downloaded,
   550  // files will be truncated to their actual sizes (but will be all zeros).
   551  func (n *loopbackNode) download(path string, state fileState) (retErr error) {
   552  	if n.getFileState(path) >= state {
   553  		// Already got this file, so we can just return
   554  		return nil
   555  	}
   556  	if err := n.downloadRepos(); err != nil {
   557  		return err
   558  	}
   559  	path = n.trimPath(path)
   560  	parts := strings.Split(path, "/")
   561  	defer func() {
   562  		if retErr == nil {
   563  			n.setFileState(path, state)
   564  		}
   565  	}()
   566  	// Note, len(parts) < 1 should not actually be possible, but just in case
   567  	// no need to panic.
   568  	if len(parts) < 1 || parts[0] == "" {
   569  		return nil //already downloaded in downloadRepos
   570  	}
   571  	commit, err := n.commit(parts[0])
   572  	if err != nil {
   573  		return err
   574  	}
   575  	if commit == "" {
   576  		return nil
   577  	}
   578  	if err := n.c().ListFileF(parts[0], commit, pathpkg.Join(parts[1:]...), 0,
   579  		func(fi *pfs.FileInfo) (retErr error) {
   580  			if fi.FileType == pfs.FileType_DIR {
   581  				return os.MkdirAll(n.filePath(fi), 0777)
   582  			}
   583  			p := n.filePath(fi)
   584  			// Make sure the directory exists
   585  			// I think this may be unnecessary based on the constraints the
   586  			// OS imposes, but don't want to rely on that, especially
   587  			// because Mkdir should be pretty cheap.
   588  			if err := os.MkdirAll(filepath.Dir(p), 0777); err != nil {
   589  				return errors.WithStack(err)
   590  			}
   591  			f, err := os.Create(p)
   592  			if err != nil {
   593  				return errors.WithStack(err)
   594  			}
   595  			defer func() {
   596  				if err := f.Close(); err != nil && retErr == nil {
   597  					retErr = errors.WithStack(err)
   598  				}
   599  			}()
   600  			if state < full {
   601  				return f.Truncate(int64(fi.SizeBytes))
   602  			}
   603  			if err := n.c().GetFile(fi.File.Commit.Repo.Name, fi.File.Commit.ID, fi.File.Path, 0, 0, f); err != nil {
   604  				return err
   605  			}
   606  			return nil
   607  		}); err != nil && !errutil.IsNotFoundError(err) &&
   608  		!pfsserver.IsOutputCommitNotFinishedErr(err) {
   609  		return err
   610  	}
   611  	return nil
   612  }
   613  
   614  func (n *loopbackNode) trimPath(path string) string {
   615  	path = strings.TrimPrefix(path, n.root().rootPath)
   616  	return strings.TrimPrefix(path, "/")
   617  }
   618  
   619  func (n *loopbackNode) trimTargetPath(path string) string {
   620  	path = strings.TrimPrefix(path, n.root().targetPath)
   621  	return strings.TrimPrefix(path, "/")
   622  }
   623  
   624  func (n *loopbackNode) branch(repo string) string {
   625  	// no need to lock mu for branches since we only ever read from it.
   626  	if branch, ok := n.root().branches[repo]; ok {
   627  		return branch
   628  	}
   629  	return "master"
   630  }
   631  
   632  func (n *loopbackNode) commit(repo string) (string, error) {
   633  	if commit, ok := func() (string, bool) {
   634  		n.root().mu.Lock()
   635  		defer n.root().mu.Unlock()
   636  		commit, ok := n.root().commits[repo]
   637  		return commit, ok
   638  	}(); ok {
   639  		return commit, nil
   640  	}
   641  	branch := n.root().branch(repo)
   642  	bi, err := n.root().c.InspectBranch(repo, branch)
   643  	if err != nil && !errutil.IsNotFoundError(err) {
   644  		return "", err
   645  	}
   646  	// Lock mu to assign commits
   647  	n.root().mu.Lock()
   648  	defer n.root().mu.Unlock()
   649  	// You can access branches that don't exist, which allows you to create
   650  	// branches through the fuse mount.
   651  	if errutil.IsNotFoundError(err) || bi.Head == nil {
   652  		n.root().commits[repo] = ""
   653  		return "", nil
   654  	}
   655  	n.root().commits[repo] = bi.Head.ID
   656  	return bi.Head.ID, nil
   657  }
   658  
   659  func (n *loopbackNode) repoPath(ri *pfs.RepoInfo) string {
   660  	return filepath.Join(n.root().rootPath, ri.Repo.Name)
   661  }
   662  
   663  func (n *loopbackNode) filePath(fi *pfs.FileInfo) string {
   664  	return filepath.Join(n.root().rootPath, fi.File.Commit.Repo.Name, fi.File.Path)
   665  }
   666  
   667  func (n *loopbackNode) getFileState(path string) fileState {
   668  	n.root().mu.Lock()
   669  	defer n.root().mu.Unlock()
   670  	return n.root().files[n.trimPath(path)]
   671  }
   672  
   673  func (n *loopbackNode) setFileState(path string, state fileState) {
   674  	n.root().mu.Lock()
   675  	defer n.root().mu.Unlock()
   676  	n.root().files[n.trimPath(path)] = state
   677  }
   678  
   679  func (n *loopbackNode) checkWrite(path string) syscall.Errno {
   680  	repo := strings.Split(n.trimPath(path), "/")[0]
   681  	ros := n.root().repoOpts
   682  	if len(ros) > 0 {
   683  		ro, ok := ros[repo]
   684  		if !ok || !ro.Write {
   685  			return syscall.EROFS
   686  		}
   687  		return 0
   688  	}
   689  	if !n.root().write {
   690  		return syscall.EROFS
   691  	}
   692  	return 0
   693  }
   694  
   695  func isWrite(flags uint32) bool {
   696  	return (int(flags) & (os.O_WRONLY | os.O_RDWR)) != 0
   697  }
   698  
   699  func isCreate(flags uint32) bool {
   700  	return int(flags)&os.O_CREATE != 0
   701  }