github.com/VertebrateResequencing/muxfys@v3.0.5+incompatible/filesystem.go (about)

     1  // Copyright © 2017, 2018 Genome Research Limited
     2  // Author: Sendu Bala <sb10@sanger.ac.uk>.
     3  // The StatFs() code in this file is based on code in
     4  // https://github.com/kahing/goofys Copyright 2015-2017 Ka-Hing Cheung,
     5  // licensed under the Apache License, Version 2.0 (the "License"), stating:
     6  // "You may not use this file except in compliance with the License. You may
     7  // obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0"
     8  //
     9  //  This file is part of muxfys.
    10  //
    11  //  muxfys is free software: you can redistribute it and/or modify
    12  //  it under the terms of the GNU Lesser General Public License as published by
    13  //  the Free Software Foundation, either version 3 of the License, or
    14  //  (at your option) any later version.
    15  //
    16  //  muxfys is distributed in the hope that it will be useful,
    17  //  but WITHOUT ANY WARRANTY; without even the implied warranty of
    18  //  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    19  //  GNU Lesser General Public License for more details.
    20  //
    21  //  You should have received a copy of the GNU Lesser General Public License
    22  //  along with muxfys. If not, see <http://www.gnu.org/licenses/>.
    23  
    24  package muxfys
    25  
    26  // This file implements pathfs.FileSystem methods.
    27  
    28  import (
    29  	"bufio"
    30  	"io"
    31  	"os"
    32  	"path/filepath"
    33  	"strings"
    34  	"syscall"
    35  	"time"
    36  
    37  	"github.com/alexflint/go-filemutex"
    38  	"github.com/hanwen/go-fuse/fuse"
    39  	"github.com/hanwen/go-fuse/fuse/nodefs"
    40  	"github.com/hanwen/go-fuse/fuse/pathfs"
    41  )
    42  
    43  const (
    44  	blockSize   = uint64(4096)
    45  	totalBlocks = uint64(274877906944) // 1PB / blockSize
    46  	inodes      = uint64(1000000000)
    47  	ioSize      = uint32(1048576) // 1MB
    48  )
    49  
    50  // fileDetails checks the file is known and returns its attributes and the
    51  // remote the file came from. If not known, returns ENOENT (which should never
    52  // happen).
    53  func (fs *MuxFys) fileDetails(name string, shouldBeWritable bool) (*fuse.Attr, *remote, fuse.Status) {
    54  	fs.mapMutex.RLock()
    55  	defer fs.mapMutex.RUnlock()
    56  	attr, exists := fs.files[name]
    57  	if !exists {
    58  		return nil, nil, fuse.ENOENT
    59  	}
    60  
    61  	r := fs.fileToRemote[name]
    62  	status := fuse.OK
    63  	if shouldBeWritable && !r.write {
    64  		status = fuse.EPERM
    65  	}
    66  
    67  	return attr, r, status
    68  }
    69  
    70  // StatFs returns a constant (faked) set of details describing a very large
    71  // file system.
    72  func (fs *MuxFys) StatFs(name string) *fuse.StatfsOut {
    73  	return &fuse.StatfsOut{
    74  		Blocks: blockSize,
    75  		Bfree:  totalBlocks,
    76  		Bavail: totalBlocks,
    77  		Files:  inodes,
    78  		Ffree:  inodes,
    79  		Bsize:  ioSize,
    80  		// NameLen uint32
    81  		// Frsize  uint32
    82  		// Padding uint32
    83  		// Spare   [6]uint32
    84  	}
    85  }
    86  
    87  // OnMount prepares MuxFys for use once Mount() has been called.
    88  func (fs *MuxFys) OnMount(nodeFs *pathfs.PathNodeFs) {
    89  	fs.mapMutex.Lock()
    90  	defer fs.mapMutex.Unlock()
    91  	// we need to establish that the root directory is a directory; the next
    92  	// attempt by the user to get it's contents will actually do the remote call
    93  	// to get the directory entries
    94  	fs.dirs[""] = fs.remotes
    95  }
    96  
    97  // GetAttr finds out about a given object, returning information from a
    98  // permanent cache if possible. context is not currently used.
    99  func (fs *MuxFys) GetAttr(name string, context *fuse.Context) (*fuse.Attr, fuse.Status) {
   100  	fs.mapMutex.Lock()
   101  	defer fs.mapMutex.Unlock()
   102  
   103  	if _, isDir := fs.dirs[name]; isDir {
   104  		return fs.dirAttr, fuse.OK
   105  	}
   106  
   107  	if attr, cached := fs.files[name]; cached {
   108  		return attr, fuse.OK
   109  	}
   110  
   111  	// rather than call StatObject on name to see if its a file, it's more
   112  	// efficient to try and open it's parent directory and see if that resulted
   113  	// in us caching name as one of the parent's contents
   114  	parent := filepath.Dir(name)
   115  	if parent == "/" || parent == "." {
   116  		parent = ""
   117  	}
   118  	if _, cached := fs.dirContents[parent]; !cached {
   119  		// we must populate the contents of parent first, doing the essential
   120  		// part of OpenDir()
   121  		if remotes, exists := fs.dirs[parent]; exists {
   122  			for _, r := range remotes {
   123  				status := fs.openDir(r, parent)
   124  				if status != fuse.OK {
   125  					fs.Warn("GetAttr openDir failed", "path", parent, "status", status)
   126  				}
   127  			}
   128  		}
   129  
   130  		if _, isDir := fs.dirs[name]; isDir {
   131  			return fs.dirAttr, fuse.OK
   132  		}
   133  
   134  		if attr, cached := fs.files[name]; cached {
   135  			return attr, fuse.OK
   136  		}
   137  	}
   138  	return nil, fuse.ENOENT
   139  }
   140  
   141  // OpenDir gets the contents of the given directory for eg. `ls` purposes. It
   142  // also caches the attributes of all the files within. context is not currently
   143  // used.
   144  func (fs *MuxFys) OpenDir(name string, context *fuse.Context) ([]fuse.DirEntry, fuse.Status) {
   145  	fs.mapMutex.Lock()
   146  	defer fs.mapMutex.Unlock()
   147  
   148  	remotes, exists := fs.dirs[name]
   149  	if !exists {
   150  		return nil, fuse.ENOENT
   151  	}
   152  
   153  	entries, cached := fs.dirContents[name]
   154  	if cached {
   155  		return entries, fuse.OK
   156  	}
   157  
   158  	// openDir in all remotes that have this dir, then return the combined dir
   159  	// contents from the cache
   160  	for _, r := range remotes {
   161  		status := fs.openDir(r, name)
   162  		if status != fuse.OK {
   163  			fs.Warn("GetAttr openDir failed", "path", name, "status", status)
   164  		}
   165  	}
   166  
   167  	entries, cached = fs.dirContents[name]
   168  	if cached {
   169  		return entries, fuse.OK
   170  	}
   171  	return nil, fuse.ENOENT
   172  }
   173  
   174  // openDir gets the contents of the given name, treating it as a directory,
   175  // caching the attributes of its contents. Must be called while you have the
   176  // mapMutex Locked.
   177  func (fs *MuxFys) openDir(r *remote, name string) fuse.Status {
   178  	remotePath := r.getRemotePath(name)
   179  	if remotePath != "" {
   180  		remotePath += "/"
   181  	}
   182  
   183  	objects, status := r.findObjects(remotePath)
   184  
   185  	if status != fuse.OK || len(objects) == 0 {
   186  		if name == "" {
   187  			// allow the root to be a non-existent directory
   188  			fs.dirs[name] = append(fs.dirs[name], r)
   189  			if _, exists := fs.dirContents[name]; !exists {
   190  				fs.dirContents[name] = []fuse.DirEntry{}
   191  			}
   192  			return fuse.OK
   193  		} else if status == fuse.OK {
   194  			return fuse.ENOENT
   195  		}
   196  		return status
   197  	}
   198  
   199  	var isDir bool
   200  	for _, object := range objects {
   201  		if object.Name == name {
   202  			continue
   203  		}
   204  		isDir = true
   205  
   206  		d := fuse.DirEntry{
   207  			Name: object.Name[len(remotePath):],
   208  		}
   209  		if d.Name == "" {
   210  			continue
   211  		}
   212  
   213  		if strings.HasSuffix(d.Name, "/") {
   214  			d.Mode = uint32(fuse.S_IFDIR)
   215  			d.Name = d.Name[0 : len(d.Name)-1]
   216  			thisPath := filepath.Join(name, d.Name)
   217  			fs.dirs[thisPath] = append(fs.dirs[thisPath], r)
   218  		} else {
   219  			d.Mode = uint32(fuse.S_IFREG)
   220  			thisPath := filepath.Join(name, d.Name)
   221  			mTime := uint64(object.MTime.Unix())
   222  			attr := &fuse.Attr{
   223  				Mode:  fuse.S_IFREG | uint32(fileMode),
   224  				Size:  uint64(object.Size),
   225  				Mtime: mTime,
   226  				Atime: mTime,
   227  				Ctime: mTime,
   228  			}
   229  			fs.files[thisPath] = attr
   230  			fs.fileToRemote[thisPath] = r
   231  		}
   232  		fs.dirContents[name] = append(fs.dirContents[name], d)
   233  
   234  		// for efficiency, instead of breaking here, we'll keep looping and
   235  		// cache all the dir contents; this does mean we'll never see externally
   236  		// added new entries for this dir in the future
   237  	}
   238  
   239  	if !isDir {
   240  		return fuse.ENOENT
   241  	}
   242  
   243  	fs.dirs[name] = append(fs.dirs[name], r)
   244  	if _, exists := fs.dirContents[name]; !exists {
   245  		// empty dir, we must create an entry in this map
   246  		fs.dirContents[name] = []fuse.DirEntry{}
   247  	}
   248  	return fuse.OK
   249  }
   250  
   251  // Open is what is called when any request to read a file is made. The file must
   252  // already have been stat'ed (eg. with a GetAttr() call), or we report the file
   253  // doesn't exist. context is not currently used. If CacheData has been
   254  // configured, we defer to openCached(). Otherwise the real implementation is in
   255  // remoteFile.
   256  func (fs *MuxFys) Open(name string, flags uint32, context *fuse.Context) (nodefs.File, fuse.Status) {
   257  	checkWritable := false
   258  	if int(flags)&os.O_WRONLY != 0 || int(flags)&os.O_RDWR != 0 || int(flags)&os.O_APPEND != 0 || int(flags)&os.O_CREATE != 0 || int(flags)&os.O_TRUNC != 0 {
   259  		checkWritable = true
   260  	}
   261  	attr, r, status := fs.fileDetails(name, checkWritable)
   262  	var file nodefs.File
   263  	if status != fuse.OK {
   264  		return file, status
   265  	}
   266  
   267  	if r.cacheData {
   268  		file, status = fs.openCached(r, name, flags, context, attr, checkWritable)
   269  	} else {
   270  		file = newRemoteFile(r, r.getRemotePath(name), attr, false, fs.Logger)
   271  	}
   272  
   273  	if !r.write || (int(flags)&os.O_WRONLY == 0 && int(flags)&os.O_RDWR == 0) {
   274  		file = nodefs.NewReadOnlyFile(file)
   275  	}
   276  
   277  	return file, status
   278  }
   279  
   280  // openCached defers all subsequent read/write operations to a CachedFile for
   281  // that local file.
   282  func (fs *MuxFys) openCached(r *remote, name string, flags uint32, context *fuse.Context, attr *fuse.Attr, writeMode bool) (nodefs.File, fuse.Status) {
   283  	remotePath := r.getRemotePath(name)
   284  	localPath := r.getLocalPath(remotePath)
   285  
   286  	fmutex, err := fs.getFileMutex(localPath)
   287  	if err != nil {
   288  		return nil, fuse.EIO
   289  	}
   290  	err = fmutex.Lock()
   291  	if err != nil {
   292  		fs.Error("openCached file mutex lock failed", "err", err)
   293  	}
   294  
   295  	localStats, err := os.Stat(localPath)
   296  	var create bool
   297  	if err != nil {
   298  		err = os.Remove(localPath)
   299  		if err != nil && !os.IsNotExist(err) {
   300  			fs.Warn("openCached remove cache file failed", "path", localPath, "err", err)
   301  		}
   302  		create = true
   303  	} else if !writeMode {
   304  		// check the file is the right size
   305  		if localStats.Size() != int64(attr.Size) {
   306  			r.Warn("Cached size differs", "path", name, "localSize", localStats.Size(), "remoteSize", attr.Size)
   307  			err = os.Remove(localPath)
   308  			if err != nil {
   309  				fs.Warn("openCached remove cache file failed", "path", localPath, "err", err)
   310  			}
   311  			create = true
   312  			if int(flags)&os.O_WRONLY != 0 || int(flags)&os.O_RDWR != 0 || int(flags)&os.O_APPEND != 0 || int(flags)&os.O_CREATE != 0 || int(flags)&os.O_TRUNC != 0 {
   313  				attr.Size = uint64(0)
   314  			}
   315  		} else if !r.cacheIsTmp {
   316  			// if the file already exists at the correct size, but we have no
   317  			// record of it being cached, assume another process sharing the
   318  			// same permanent cache folder already cached the whole file
   319  			iv := NewInterval(0, localStats.Size())
   320  			ivs := r.Uncached(localPath, iv)
   321  			if len(ivs) > 0 {
   322  				r.Cached(localPath, iv)
   323  			}
   324  
   325  			// *** doesn't this break if two different mount processes are
   326  			// trying to read the same file at the same time?... Maybe we'll
   327  			// need to store cached intervals in the lock file after all...
   328  		}
   329  	}
   330  
   331  	if create {
   332  		r.CacheDelete(localPath)
   333  
   334  		if !r.cacheIsTmp || int(flags)&os.O_APPEND != 0 {
   335  			// download whole remote object to disk before user appends anything
   336  			// to it; if we just append to the sparse file then on upload we
   337  			// lose the contents of the original file. We also do this if we're
   338  			// not deleting our cache, ie. our cache dir was chosen by the user
   339  			// and could be in use simultaneously by other muxfys mounts
   340  			// *** alternatively we could store Invervals in the lock file...
   341  			if status := r.downloadFile(remotePath, localPath); status != fuse.OK {
   342  				logClose(fs.Logger, fmutex, "openCached file mutex")
   343  				return nil, status
   344  			}
   345  
   346  			// check size ok
   347  			localStats, errs := os.Stat(localPath)
   348  			if errs != nil {
   349  				r.Error("Downloaded file could not be accessed", "path", localPath, "err", errs)
   350  				errr := os.Remove(localPath)
   351  				if errr != nil {
   352  					fs.Warn("openCached remove cache file failed", "path", localPath, "err", errr)
   353  				}
   354  				logClose(fs.Logger, fmutex, "openCached file mutex")
   355  				return nil, fuse.ToStatus(errs)
   356  			} else if localStats.Size() != int64(attr.Size) {
   357  				r.Error("Downloaded size is wrong", "path", remotePath, "localSize", localStats.Size(), "remoteSize", attr.Size)
   358  				errr := os.Remove(localPath)
   359  				if errr != nil {
   360  					fs.Warn("openCached remove cache file failed", "path", localPath, "err", errr)
   361  				}
   362  				logClose(fs.Logger, fmutex, "openCached file mutex")
   363  				return nil, fuse.EIO
   364  			}
   365  			r.CacheOverride(localPath, NewInterval(0, int64(attr.Size)))
   366  		} else {
   367  			// this is our first time opening this remote file, create a sparse
   368  			// file that Read() operations will cache in to
   369  			f, errc := os.Create(localPath)
   370  			if errc != nil {
   371  				fs.Error("openCached create cached file failed", "path", localPath, "err", errc)
   372  				logClose(fs.Logger, fmutex, "openCached file mutex")
   373  				return nil, fuse.ToStatus(errc)
   374  			}
   375  			if errt := f.Truncate(int64(attr.Size)); errt != nil {
   376  				fs.Error("openCached truncate failed", "path", localPath, "err", errt)
   377  				logClose(fs.Logger, fmutex, "openCached file mutex")
   378  				return nil, fuse.ToStatus(errt)
   379  			}
   380  			logClose(fs.Logger, f, "openCached created file", "path", localPath)
   381  		}
   382  	} else if r.cacheIsTmp && int(flags)&os.O_APPEND != 0 {
   383  		// cache everything in the file we haven't already read by reading the
   384  		// file the way a client would
   385  		iv := Interval{0, int64(attr.Size)}
   386  		unread := r.Uncached(localPath, iv)
   387  		if len(unread) > 0 {
   388  			err = fmutex.Unlock()
   389  			if err != nil {
   390  				fs.Error("openCached file mutex unlock failed", "err", err)
   391  			}
   392  			path := filepath.Join(fs.mountPoint, name)
   393  			reader, err := os.Open(path)
   394  			if err != nil {
   395  				r.Error("Could not open cached file", "path", path, "err", err)
   396  				errl := fmutex.Lock()
   397  				if errl != nil {
   398  					fs.Error("openCached file mutex lock failed", "err", errl)
   399  				}
   400  				logClose(fs.Logger, fmutex, "openCached file mutex")
   401  				return nil, fuse.ToStatus(err)
   402  			}
   403  			for _, uiv := range unread {
   404  				_, errs := reader.Seek(uiv.Start, io.SeekStart)
   405  				if errs != nil {
   406  					r.Error("openCached reader seek failed", "err", errs)
   407  				}
   408  				br := bufio.NewReader(reader)
   409  				b := make([]byte, 1000)
   410  				var read int64
   411  				for read <= uiv.Length() {
   412  					done, rerr := br.Read(b)
   413  					if rerr != nil {
   414  						if rerr != io.EOF {
   415  							err = rerr
   416  						}
   417  						break
   418  					}
   419  					read += int64(done)
   420  				}
   421  				if err != nil {
   422  					r.Error("Could not read file", "path", name, "err", err)
   423  					logClose(fs.Logger, reader, "openCached reader", "path", name)
   424  					err = fmutex.Lock()
   425  					if err != nil {
   426  						fs.Error("openCached file mutex lock failed", "err", err)
   427  					}
   428  					logClose(fs.Logger, fmutex, "openCached file mutex")
   429  					return nil, fuse.EIO
   430  				}
   431  			}
   432  			logClose(fs.Logger, reader, "openCached reader", "path", name)
   433  			err = fmutex.Lock()
   434  			if err != nil {
   435  				fs.Error("openCached file mutex lock failed", "err", err)
   436  			}
   437  		}
   438  	}
   439  
   440  	// if the flags suggest any kind of write-ability, treat it like we created
   441  	// the file
   442  	if writeMode {
   443  		return fs.create(name, flags, uint32(fileMode), fmutex)
   444  	}
   445  
   446  	logClose(fs.Logger, fmutex, "openCached file mutex")
   447  	return newCachedFile(r, remotePath, localPath, attr, flags, fs.Logger), fuse.OK
   448  }
   449  
   450  // Chmod is ignored.
   451  func (fs *MuxFys) Chmod(name string, mode uint32, context *fuse.Context) fuse.Status {
   452  	_, _, status := fs.fileDetails(name, true)
   453  	if status == fuse.ENOENT {
   454  		fs.mapMutex.RLock()
   455  		defer fs.mapMutex.RUnlock()
   456  		if _, exists := fs.dirs[name]; exists {
   457  			return fuse.OK
   458  		}
   459  	}
   460  	return status
   461  }
   462  
   463  // Chown is ignored.
   464  func (fs *MuxFys) Chown(name string, uid uint32, gid uint32, context *fuse.Context) fuse.Status {
   465  	_, _, status := fs.fileDetails(name, true)
   466  	if status == fuse.ENOENT {
   467  		fs.mapMutex.RLock()
   468  		defer fs.mapMutex.RUnlock()
   469  		if _, exists := fs.dirs[name]; exists {
   470  			return fuse.OK
   471  		}
   472  	}
   473  	return status
   474  }
   475  
   476  // Symlink creates a symbolic link. Only implemented for temporary use when
   477  // configured with CacheData: you can create and use symlinks but they don't get
   478  // uploaded. context is not currently used.
   479  func (fs *MuxFys) Symlink(source string, dest string, context *fuse.Context) (status fuse.Status) {
   480  	if fs.writeRemote == nil || !fs.writeRemote.cacheData {
   481  		return fuse.ENOSYS
   482  	}
   483  
   484  	localPathDest := fs.writeRemote.getLocalPath(fs.writeRemote.getRemotePath(dest))
   485  	fmutex, err := fs.getFileMutex(localPathDest)
   486  	if err != nil {
   487  		return fuse.EIO
   488  	}
   489  	err = fmutex.Lock()
   490  	if err != nil {
   491  		fs.Error("Symlink file mutex lock failed", "err", err)
   492  	}
   493  	defer logClose(fs.Logger, fmutex, "Symlink file mutex")
   494  
   495  	// symlink from mount point source to cached dest file
   496  	err = os.Symlink(source, localPathDest)
   497  	if err != nil {
   498  		fs.writeRemote.Error("Could not create symlink", "source", source, "dest", localPathDest, "err", err)
   499  		return fuse.ToStatus(err)
   500  	}
   501  
   502  	// note the existence of dest without making it uploadable on unmount
   503  	fs.mapMutex.Lock()
   504  	fs.addNewEntryToItsDir(dest, fuse.S_IFLNK)
   505  	mTime := uint64(time.Now().Unix())
   506  	attr := &fuse.Attr{
   507  		Mode:  fuse.S_IFLNK | uint32(fileMode),
   508  		Size:  symlinkSize, // it doesn't matter what the actual size is (which we could get with os.Lstat(localPathDest)), this is just for presentation purposes
   509  		Mtime: mTime,
   510  		Atime: mTime,
   511  		Ctime: mTime,
   512  	}
   513  	fs.files[dest] = attr
   514  	fs.fileToRemote[dest] = fs.writeRemote
   515  	fs.mapMutex.Unlock()
   516  
   517  	return fuse.OK
   518  }
   519  
   520  // Readlink returns the destination of a symbolic link that was created with
   521  // Symlink(). context is not currently used.
   522  func (fs *MuxFys) Readlink(name string, context *fuse.Context) (string, fuse.Status) {
   523  	_, r, status := fs.fileDetails(name, true)
   524  	if status != fuse.OK {
   525  		return "", status
   526  	}
   527  	localPath := r.getLocalPath(r.getRemotePath(name))
   528  	out, err := os.Readlink(localPath)
   529  	if err != nil {
   530  		fs.Warn("Readlink failed", "path", localPath, "err", err)
   531  	}
   532  	return out, fuse.ToStatus(err)
   533  }
   534  
   535  // SetXAttr is ignored.
   536  func (fs *MuxFys) SetXAttr(name string, attr string, data []byte, flags int, context *fuse.Context) fuse.Status {
   537  	_, _, status := fs.fileDetails(name, true)
   538  	if status == fuse.ENOENT {
   539  		fs.mapMutex.RLock()
   540  		defer fs.mapMutex.RUnlock()
   541  		if _, exists := fs.dirs[name]; exists {
   542  			return fuse.OK
   543  		}
   544  	}
   545  	return status
   546  }
   547  
   548  // RemoveXAttr is ignored.
   549  func (fs *MuxFys) RemoveXAttr(name string, attr string, context *fuse.Context) fuse.Status {
   550  	_, _, status := fs.fileDetails(name, true)
   551  	if status == fuse.ENOENT {
   552  		fs.mapMutex.RLock()
   553  		defer fs.mapMutex.RUnlock()
   554  		if _, exists := fs.dirs[name]; exists {
   555  			return fuse.OK
   556  		}
   557  	}
   558  	return status
   559  }
   560  
   561  // Utimens only functions when configured with CacheData and the file is already
   562  // in the cache; otherwise ignored. This only gets called by direct operations
   563  // like os.Chtimes() (that don't first Open()/Create() the file). context is not
   564  // currently used.
   565  func (fs *MuxFys) Utimens(name string, Atime *time.Time, Mtime *time.Time, context *fuse.Context) fuse.Status {
   566  	attr, r, status := fs.fileDetails(name, true)
   567  	if status == fuse.ENOENT {
   568  		fs.mapMutex.RLock()
   569  		defer fs.mapMutex.RUnlock()
   570  		if _, exists := fs.dirs[name]; exists {
   571  			return fuse.OK
   572  		}
   573  	}
   574  	if status != fuse.OK || !r.cacheData {
   575  		return status
   576  	}
   577  
   578  	localPath := r.getLocalPath(r.getRemotePath(name))
   579  	if _, err := os.Stat(localPath); err == nil {
   580  		err = os.Chtimes(localPath, *Atime, *Mtime)
   581  		if err == nil {
   582  			attr.Atime = uint64(Atime.Unix())
   583  			attr.Mtime = uint64(Mtime.Unix())
   584  		}
   585  		status = fuse.ToStatus(err)
   586  	}
   587  
   588  	return status
   589  }
   590  
   591  // Truncate truncates any local cached copy of the file. Only currently
   592  // implemented for when configured with CacheData; the results of the Truncate
   593  // are only uploaded at Unmount() time. If offset is > size of file, does
   594  // nothing and returns OK. context is not currently used.
   595  func (fs *MuxFys) Truncate(name string, offset uint64, context *fuse.Context) fuse.Status {
   596  	attr, r, status := fs.fileDetails(name, true)
   597  	if status != fuse.OK {
   598  		return status
   599  	}
   600  
   601  	if offset > attr.Size {
   602  		return fuse.OK
   603  	}
   604  
   605  	remotePath := r.getRemotePath(name)
   606  	if r.cacheData {
   607  		localPath := r.getLocalPath(remotePath)
   608  
   609  		fmutex, err := fs.getFileMutex(localPath)
   610  		if err != nil {
   611  			return fuse.EIO
   612  		}
   613  		err = fmutex.Lock()
   614  		if err != nil {
   615  			fs.Error("Truncate file mutex lock failed", "err", err)
   616  			return fuse.EIO
   617  		}
   618  		defer logClose(fs.Logger, fmutex, "Trucate mutex file")
   619  
   620  		if _, err := os.Stat(localPath); err == nil {
   621  			// truncate local cached copy
   622  			err = os.Truncate(localPath, int64(offset))
   623  			if err != nil {
   624  				fs.Error("Truncate cached file failed", "path", localPath, "err", err)
   625  				return fuse.ToStatus(err)
   626  			}
   627  			r.CacheTruncate(localPath, int64(offset))
   628  		} else {
   629  			// create a new empty file
   630  			localFile, err := os.Create(localPath)
   631  			if err != nil {
   632  				r.Error("Could not create empty file", "path", localPath, "err", err)
   633  				return fuse.EIO
   634  			}
   635  
   636  			if offset == 0 {
   637  				logClose(fs.Logger, localFile, "Trucate local file")
   638  				r.CacheTruncate(localPath, int64(offset))
   639  			} else {
   640  				// download offset bytes of remote file
   641  				object, status := r.getObject(remotePath, 0)
   642  				if status != fuse.OK {
   643  					return status
   644  				}
   645  
   646  				written, err := io.CopyN(localFile, object, int64(offset))
   647  				if err != nil || written != int64(offset) {
   648  					msg := "Could not copy bytes"
   649  					if err == nil {
   650  						msg = "Could not copy all bytes"
   651  					}
   652  					r.Error(msg, "size", offset, "source", remotePath, "dest", localPath, "err", err)
   653  					logClose(fs.Logger, localFile, "Trucate local file")
   654  					erru := syscall.Unlink(localPath)
   655  					if erru != nil {
   656  						fs.Error("Truncate cache file unlink failed", "err", erru)
   657  					}
   658  					return fuse.EIO
   659  				}
   660  
   661  				logClose(fs.Logger, localFile, "Trucate local file")
   662  				logClose(fs.Logger, object, "Trucate remote object")
   663  
   664  				r.CacheOverride(localPath, NewInterval(0, int64(offset)))
   665  			}
   666  		}
   667  
   668  		// update attr and claim we created this file
   669  		attr.Size = offset
   670  		attr.Mtime = uint64(time.Now().Unix())
   671  		fs.mapMutex.Lock()
   672  		fs.createdFiles[name] = true
   673  		fs.mapMutex.Unlock()
   674  
   675  		return fuse.OK
   676  	}
   677  	return fuse.ENOSYS
   678  }
   679  
   680  // Mkdir for a directory that doesn't exist yet. neither mode nor context are
   681  // currently used.
   682  func (fs *MuxFys) Mkdir(name string, mode uint32, context *fuse.Context) fuse.Status {
   683  	if fs.writeRemote == nil {
   684  		return fuse.EPERM
   685  	}
   686  
   687  	fs.mapMutex.Lock()
   688  	defer fs.mapMutex.Unlock()
   689  
   690  	if _, isDir := fs.dirs[name]; isDir {
   691  		return fuse.OK
   692  	}
   693  
   694  	// it's parent directory must already exist
   695  	parent := filepath.Dir(name)
   696  	if parent == "." {
   697  		parent = ""
   698  	}
   699  	if _, exists := fs.dirs[parent]; !exists {
   700  		return fuse.ENOENT
   701  	}
   702  
   703  	remotePath := fs.writeRemote.getRemotePath(name)
   704  	var err error
   705  	if fs.writeRemote.cacheData {
   706  		localPath := fs.writeRemote.getLocalPath(remotePath)
   707  
   708  		// make all the parent directories. We use our dirMode constant here
   709  		// instead of the supplied mode because of strange permission problems
   710  		// using the latter, and because it doesn't matter what permissions the
   711  		// user wants for the dir - this is for a user-only cache
   712  		if err = os.MkdirAll(filepath.Dir(localPath), os.FileMode(dirMode)); err == nil {
   713  			// make the desired directory
   714  			err = os.Mkdir(localPath, os.FileMode(dirMode))
   715  		}
   716  		if err != nil {
   717  			fs.Error("Mkdir failed", "path", localPath, "err", err)
   718  			fs.mapMutex.Unlock()
   719  			return fuse.ToStatus(err)
   720  		}
   721  	}
   722  
   723  	// we mark its existence internally but don't do anything "physical"
   724  	// to create the dir remotely (applies for cached and uncached modes)
   725  	fs.dirs[name] = append(fs.dirs[name], fs.writeRemote)
   726  	if _, exists := fs.dirContents[name]; !exists {
   727  		fs.dirContents[name] = []fuse.DirEntry{}
   728  	}
   729  	if fs.writeRemote.cacheData {
   730  		fs.createdDirs[name] = true
   731  	}
   732  	fs.addNewEntryToItsDir(name, fuse.S_IFDIR)
   733  	return fuse.OK
   734  }
   735  
   736  // Rmdir only works for non-existent or empty dirs. context is not currently
   737  // used.
   738  func (fs *MuxFys) Rmdir(name string, context *fuse.Context) fuse.Status {
   739  	if fs.writeRemote == nil {
   740  		return fuse.EPERM
   741  	}
   742  
   743  	fs.mapMutex.Lock()
   744  	defer fs.mapMutex.Unlock()
   745  
   746  	if _, isDir := fs.dirs[name]; !isDir {
   747  		return fuse.ENOENT
   748  	} else if contents, exists := fs.dirContents[name]; exists && len(contents) > 0 {
   749  		return fuse.ENOSYS
   750  	}
   751  
   752  	remotePath := fs.writeRemote.getRemotePath(name)
   753  	var err error
   754  	if fs.writeRemote.cacheData {
   755  		localPath := fs.writeRemote.getLocalPath(remotePath)
   756  		err = syscall.Rmdir(localPath)
   757  		if err != nil {
   758  			fs.Error("Rmdir failed", "path", localPath, "err", err)
   759  			return fuse.ToStatus(err)
   760  		}
   761  
   762  	}
   763  
   764  	delete(fs.dirs, name)
   765  	delete(fs.createdDirs, name)
   766  	delete(fs.dirContents, name)
   767  	fs.rmEntryFromItsDir(name)
   768  
   769  	return fuse.OK
   770  }
   771  
   772  // Rename only works where oldPath is found in the writeable remote. For files,
   773  // first remotely copies oldPath to newPath (ignoring any local changes to
   774  // oldPath), renames any local cached (and possibly modified) copy of oldPath to
   775  // newPath, and finally deletes the remote oldPath; if oldPath had been
   776  // modified, its changes will only be uploaded to newPath at Unmount() time. For
   777  // directories, is only capable of renaming directories you have created whilst
   778  // mounted. context is not currently used.
   779  func (fs *MuxFys) Rename(oldPath string, newPath string, context *fuse.Context) fuse.Status {
   780  	if fs.writeRemote == nil {
   781  		return fuse.EPERM
   782  	}
   783  
   784  	fs.mapMutex.Lock()
   785  	defer fs.mapMutex.Unlock()
   786  
   787  	var isDir bool
   788  	if _, isDir = fs.dirs[oldPath]; !isDir {
   789  		if _, isFile := fs.fileToRemote[oldPath]; !isFile {
   790  			return fuse.ENOENT
   791  		}
   792  	} else if _, created := fs.createdDirs[oldPath]; !created {
   793  		return fuse.ENOSYS
   794  	} else {
   795  		// the directory's new parent dir must exist
   796  		parent := filepath.Dir(newPath)
   797  		if parent == "." {
   798  			parent = ""
   799  		}
   800  		if _, exists := fs.dirs[parent]; !exists {
   801  			return fuse.ENOENT
   802  		}
   803  	}
   804  
   805  	remotePathOld := fs.writeRemote.getRemotePath(oldPath)
   806  	remotePathNew := fs.writeRemote.getRemotePath(newPath)
   807  	if isDir {
   808  		if fs.writeRemote.cacheData {
   809  			// first create the newPaths's cached parent dir
   810  			localPathNew := fs.writeRemote.getLocalPath(remotePathNew)
   811  
   812  			// *** should we try and lock the old and new directories first?
   813  
   814  			var err error
   815  			if err = os.MkdirAll(filepath.Dir(localPathNew), os.FileMode(dirMode)); err == nil {
   816  				// now try and rename the cached dir
   817  				if err = os.Rename(fs.writeRemote.getLocalPath(remotePathOld), localPathNew); err == nil {
   818  					// update our knowledge of what dirs we have
   819  					fs.dirs[newPath] = fs.dirs[oldPath]
   820  					fs.dirContents[newPath] = fs.dirContents[oldPath]
   821  					fs.createdDirs[newPath] = true
   822  					delete(fs.dirs, oldPath)
   823  					delete(fs.createdDirs, oldPath)
   824  					delete(fs.dirContents, oldPath)
   825  					fs.rmEntryFromItsDir(oldPath)
   826  					fs.addNewEntryToItsDir(newPath, fuse.S_IFDIR)
   827  				}
   828  			}
   829  			fs.Error("Rename mkdir failed", "path", localPathNew, "err", err)
   830  			return fuse.ToStatus(err)
   831  		}
   832  	} else {
   833  		// first trigger a remote copy of oldPath to newPath
   834  		status := fs.writeRemote.copyFile(remotePathOld, remotePathNew)
   835  		if status != fuse.OK {
   836  			return status
   837  		}
   838  
   839  		if fs.writeRemote.cacheData {
   840  			localPathOld := fs.writeRemote.getLocalPath(remotePathOld)
   841  			localPathNew := fs.writeRemote.getLocalPath(remotePathNew)
   842  
   843  			fmutex, err := fs.getFileMutex(localPathOld)
   844  			if err != nil {
   845  				return fuse.EIO
   846  			}
   847  			err = fmutex.Lock()
   848  			if err != nil {
   849  				fs.Error("Rename file mutex lock failed", "path", localPathOld, "err", err)
   850  				return fuse.EIO
   851  			}
   852  			defer logClose(fs.Logger, fmutex, "Rename file mutex")
   853  			fmutex2, err := fs.getFileMutex(localPathNew)
   854  			if err != nil {
   855  				return fuse.EIO
   856  			}
   857  			err = fmutex2.Lock()
   858  			if err != nil {
   859  				fs.Error("Rename file mutex lock failed", "path", localPathNew, "err", err)
   860  				return fuse.EIO
   861  			}
   862  			defer logClose(fs.Logger, fmutex2, "Rename file mutex")
   863  
   864  			// if we've cached oldPath, move to new cached file
   865  			err = os.Rename(localPathOld, localPathNew)
   866  			if err != nil {
   867  				fs.Error("Rename of cached files failed", "source", localPathOld, "dest", localPathNew, "err", err)
   868  			}
   869  			fs.writeRemote.CacheRename(localPathOld, localPathNew)
   870  		}
   871  
   872  		// cache the existence of the new file
   873  		fs.files[newPath] = fs.files[oldPath]
   874  		fs.fileToRemote[newPath] = fs.fileToRemote[oldPath]
   875  		if _, created := fs.createdFiles[oldPath]; created {
   876  			fs.createdFiles[newPath] = true
   877  			delete(fs.createdFiles, oldPath)
   878  		}
   879  		fs.addNewEntryToItsDir(newPath, fuse.S_IFREG)
   880  
   881  		// finally unlink oldPath remotely
   882  		r := fs.fileToRemote[oldPath]
   883  		if r != nil {
   884  			r.deleteFile(remotePathOld)
   885  		}
   886  		delete(fs.files, oldPath)
   887  		delete(fs.fileToRemote, oldPath)
   888  		delete(fs.createdFiles, oldPath)
   889  		fs.rmEntryFromItsDir(oldPath)
   890  
   891  		return fuse.OK
   892  	}
   893  	return fuse.ENOSYS
   894  }
   895  
   896  // Unlink deletes a file from the remote system, as well as any locally cached
   897  // copy. context is not currently used.
   898  func (fs *MuxFys) Unlink(name string, context *fuse.Context) fuse.Status {
   899  	_, r, status := fs.fileDetails(name, true)
   900  	if status != fuse.OK {
   901  		return status
   902  	}
   903  
   904  	remotePath := r.getRemotePath(name)
   905  	if r.cacheData {
   906  		localPath := r.getLocalPath(remotePath)
   907  		// *** we could file lock here, but that is a little wasteful if
   908  		// localPath doesn't actually exist, and we'd have to file unlock eg.
   909  		// Rename() and anything else that calls us
   910  		err := syscall.Unlink(localPath)
   911  		if err != nil {
   912  			fs.Warn("Unlink failed", "path", localPath, "err", err)
   913  		}
   914  		r.CacheDelete(localPath)
   915  	}
   916  
   917  	fs.mapMutex.Lock()
   918  	defer fs.mapMutex.Unlock()
   919  
   920  	delete(fs.createdFiles, name)
   921  
   922  	status = r.deleteFile(remotePath)
   923  	if status != fuse.OK {
   924  		return status
   925  	}
   926  
   927  	delete(fs.files, name)
   928  	delete(fs.fileToRemote, name)
   929  	fs.rmEntryFromItsDir(name)
   930  
   931  	return fuse.OK
   932  }
   933  
   934  // Access is ignored.
   935  func (fs *MuxFys) Access(name string, mode uint32, context *fuse.Context) fuse.Status {
   936  	return fuse.OK
   937  }
   938  
   939  // Create creates a new file. mode and context are not currently used. When
   940  // configured with CacheData the contents of the created file are only uploaded
   941  // at Unmount() time.
   942  func (fs *MuxFys) Create(name string, flags uint32, mode uint32, context *fuse.Context) (nodefs.File, fuse.Status) {
   943  	return fs.create(name, flags, mode)
   944  }
   945  
   946  // create is the implementation of Create() that also takes an optional
   947  // filemutex that should be Lock()ed (it will be Close()d).
   948  func (fs *MuxFys) create(name string, flags uint32, mode uint32, fmutex ...*filemutex.FileMutex) (nodefs.File, fuse.Status) {
   949  	r := fs.writeRemote
   950  	if r == nil {
   951  		return nil, fuse.EPERM
   952  	}
   953  
   954  	remotePath := r.getRemotePath(name)
   955  	var localPath string
   956  	if r.cacheData {
   957  		localPath = r.getLocalPath(remotePath)
   958  
   959  		if len(fmutex) == 1 {
   960  			defer logClose(fs.Logger, fmutex[0], "file mutex", "path", localPath)
   961  		} else {
   962  			fm, err := fs.getFileMutex(localPath)
   963  			if err != nil {
   964  				return nil, fuse.EIO
   965  			}
   966  			err = fm.Lock()
   967  			if err != nil {
   968  				fs.Error("file mutex lock failed", "path", localPath, "err", err)
   969  				return nil, fuse.EIO
   970  			}
   971  			defer logClose(fs.Logger, fm, "file mutex", "path", localPath)
   972  		}
   973  	}
   974  
   975  	fs.mapMutex.Lock()
   976  	defer fs.mapMutex.Unlock()
   977  
   978  	attr, existed := fs.files[name]
   979  	mTime := uint64(time.Now().Unix())
   980  	if !existed {
   981  		// add to our directory entries for this file's dir
   982  		fs.addNewEntryToItsDir(name, fuse.S_IFREG)
   983  
   984  		attr = &fuse.Attr{
   985  			Mode:  fuse.S_IFREG | uint32(fileMode),
   986  			Size:  uint64(0),
   987  			Mtime: mTime,
   988  			Atime: mTime,
   989  			Ctime: mTime,
   990  		}
   991  		fs.files[name] = attr
   992  		fs.fileToRemote[name] = r
   993  	} else {
   994  		attr.Mtime = mTime
   995  		attr.Atime = mTime
   996  
   997  		// *** when not appending, don't we need to reset to 0? It seems to work
   998  		// without this, and we avoid incorrect reset to 0 when something
   999  		// opens many simultaneous non-append writes to write at different
  1000  		// offsets.
  1001  		// if int(flags)&os.O_APPEND == 0 {
  1002  		// 	r.CacheDelete(localPath)
  1003  		// 	attr.Size = uint64(0)
  1004  		// }
  1005  	}
  1006  	fs.createdFiles[name] = true
  1007  
  1008  	if r.cacheData {
  1009  		return newCachedFile(r, remotePath, localPath, attr, uint32(int(flags)|os.O_CREATE), fs.Logger), fuse.OK
  1010  	}
  1011  	return newRemoteFile(r, remotePath, attr, true, fs.Logger), fuse.OK
  1012  }
  1013  
  1014  // addNewEntryToItsDir adds a DirEntry for the file/dir named name to that
  1015  // object's containing directory entries. mode should be fuse.S_IFREG or
  1016  // fuse.S_IFDIR. Must be called while you have the mapMutex Locked.
  1017  func (fs *MuxFys) addNewEntryToItsDir(name string, mode int) {
  1018  	d := fuse.DirEntry{
  1019  		Name: filepath.Base(name),
  1020  		Mode: uint32(mode),
  1021  	}
  1022  	parent := filepath.Dir(name)
  1023  	if parent == "." {
  1024  		parent = ""
  1025  	}
  1026  
  1027  	if _, exists := fs.dirContents[parent]; !exists {
  1028  		// we must populate the contents of parent first, doing the essential
  1029  		// part of OpenDir()
  1030  		if remotes, exists := fs.dirs[parent]; exists {
  1031  			for _, r := range remotes {
  1032  				status := fs.openDir(r, parent)
  1033  				if status != fuse.OK {
  1034  					fs.Warn("addNewEntryToItsDir openDir failed", "path", parent, "status", status)
  1035  				}
  1036  			}
  1037  		}
  1038  	}
  1039  	fs.dirContents[parent] = append(fs.dirContents[parent], d)
  1040  }
  1041  
  1042  // rmEntryFromItsDir removes a DirEntry for the file/dir named name from that
  1043  // object's containing directory entries. Must be called while you have the
  1044  // mapMutex Locked.
  1045  func (fs *MuxFys) rmEntryFromItsDir(name string) {
  1046  	parent := filepath.Dir(name)
  1047  	if parent == "." {
  1048  		parent = ""
  1049  	}
  1050  	baseName := filepath.Base(name)
  1051  
  1052  	if dentries, exists := fs.dirContents[parent]; exists {
  1053  		for i, entry := range dentries {
  1054  			if entry.Name == baseName {
  1055  				// delete without preserving order and avoiding memory leak
  1056  				dentries[i] = dentries[len(dentries)-1]
  1057  				dentries[len(dentries)-1] = fuse.DirEntry{}
  1058  				dentries = dentries[:len(dentries)-1]
  1059  				fs.dirContents[parent] = dentries
  1060  				break
  1061  			}
  1062  		}
  1063  	}
  1064  }
  1065  
  1066  // getFileMutex prepares a lock file for the given local path (in that path's
  1067  // directory, creating the directory first if necessary), and returns a mutex
  1068  // that you should Lock() and Close().
  1069  func (fs *MuxFys) getFileMutex(localPath string) (*filemutex.FileMutex, error) {
  1070  	parent := filepath.Dir(localPath)
  1071  	if _, err := os.Stat(parent); err != nil && os.IsNotExist(err) {
  1072  		err = os.MkdirAll(parent, dirMode)
  1073  		if err != nil {
  1074  			fs.Error("Could not create parent directory", "path", localPath, "err", err)
  1075  			return nil, err
  1076  		}
  1077  	}
  1078  	mutex, err := filemutex.New(filepath.Join(parent, ".muxfys_lock."+filepath.Base(localPath)))
  1079  	if err != nil {
  1080  		fs.Error("Could not create lock file", "path", localPath, "err", err)
  1081  	}
  1082  	return mutex, err
  1083  }