diff options
| author | Jakob Unterwurzacher | 2017-07-11 23:19:58 +0200 | 
|---|---|---|
| committer | Jakob Unterwurzacher | 2017-07-11 23:19:58 +0200 | 
| commit | 3062de6187990f9b4f669ecd9dffdd48ee0d778f (patch) | |
| tree | f62274c8d0497a1fec068f75d8519fbf0263247d /internal/fusefrontend | |
| parent | 849ec10081c0eb04535017f8845501ae799ac477 (diff) | |
fusefronted: enable writing to write-only files
Due to RMW, we always need read permissions on the backing file. This is a
problem if the file permissions do not allow reading (i.e. 0200 permissions).
This patch works around that problem by chmod'ing the file, obtaining a fd,
and chmod'ing it back.
Test included.
Issue reported at: https://github.com/rfjakob/gocryptfs/issues/125
Diffstat (limited to 'internal/fusefrontend')
| -rw-r--r-- | internal/fusefrontend/fs.go | 63 | 
1 files changed, 59 insertions, 4 deletions
| diff --git a/internal/fusefrontend/fs.go b/internal/fusefrontend/fs.go index 16707d6..7a23710 100644 --- a/internal/fusefrontend/fs.go +++ b/internal/fusefrontend/fs.go @@ -34,6 +34,9 @@ type FS struct {  	nameTransform *nametransform.NameTransform  	// Content encryption helper  	contentEnc *contentenc.ContentEnc +	// This lock is used by openWriteOnlyFile() to block concurrent opens while +	// it relaxes the permissions on a file. +	openWriteOnlyLock sync.RWMutex  }  var _ pathfs.FileSystem = &FS{} // Verify that interface is implemented. @@ -102,6 +105,10 @@ func (fs *FS) Open(path string, flags uint32, context *fuse.Context) (fuseFile n  	if fs.isFiltered(path) {  		return nil, fuse.EPERM  	} +	// Taking this lock makes sure we don't race openWriteOnlyFile() +	fs.openWriteOnlyLock.RLock() +	defer fs.openWriteOnlyLock.RUnlock() +  	newFlags := fs.mangleOpenFlags(flags)  	cPath, err := fs.getBackingPath(path)  	if err != nil { @@ -109,20 +116,68 @@ func (fs *FS) Open(path string, flags uint32, context *fuse.Context) (fuseFile n  		return nil, fuse.ToStatus(err)  	}  	tlog.Debug.Printf("Open: %s", cPath) -	f, err := os.OpenFile(cPath, newFlags, 0666) +	f, err := os.OpenFile(cPath, newFlags, 0)  	if err != nil { -		err2 := err.(*os.PathError) -		if err2.Err == syscall.EMFILE { +		sysErr := err.(*os.PathError).Err +		if sysErr == syscall.EMFILE {  			var lim syscall.Rlimit  			syscall.Getrlimit(syscall.RLIMIT_NOFILE, &lim)  			tlog.Warn.Printf("Open %q: too many open files. Current \"ulimit -n\": %d", cPath, lim.Cur)  		} +		if sysErr == syscall.EACCES && (int(flags)&os.O_WRONLY > 0) { +			return fs.openWriteOnlyFile(cPath, newFlags) +		}  		return nil, fuse.ToStatus(err)  	} -  	return NewFile(f, fs)  } +// Due to RMW, we always need read permissions on the backing file. This is a +// problem if the file permissions do not allow reading (i.e. 0200 permissions). +// This function works around that problem by chmod'ing the file, obtaining a fd, +// and chmod'ing it back. +func (fs *FS) openWriteOnlyFile(cPath string, newFlags int) (fuseFile nodefs.File, status fuse.Status) { +	woFd, err := os.OpenFile(cPath, os.O_WRONLY, 0) +	if err != nil { +		return nil, fuse.ToStatus(err) +	} +	defer woFd.Close() +	fi, err := woFd.Stat() +	if err != nil { +		return nil, fuse.ToStatus(err) +	} +	perms := fi.Mode().Perm() +	// Verify that we don't have read permissions +	if perms&0400 != 0 { +		tlog.Warn.Printf("openWriteOnlyFile: unexpected permissions %#o, returning EPERM", perms) +		return nil, fuse.ToStatus(syscall.EPERM) +	} +	// Upgrade the lock to block other Open()s and downgrade again on return +	fs.openWriteOnlyLock.RUnlock() +	fs.openWriteOnlyLock.Lock() +	defer func() { +		fs.openWriteOnlyLock.Unlock() +		fs.openWriteOnlyLock.RLock() +	}() +	// Relax permissions and revert on return +	err = woFd.Chmod(perms | 0400) +	if err != nil { +		tlog.Warn.Printf("openWriteOnlyFile: changing permissions failed: %v", err) +		return nil, fuse.ToStatus(err) +	} +	defer func() { +		err2 := woFd.Chmod(perms) +		if err2 != nil { +			tlog.Warn.Printf("openWriteOnlyFile: reverting permissions failed: %v", err2) +		} +	}() +	rwFd, err := os.OpenFile(cPath, newFlags, 0) +	if err != nil { +		return nil, fuse.ToStatus(err) +	} +	return NewFile(rwFd, fs) +} +  // Create implements pathfs.Filesystem.  func (fs *FS) Create(path string, flags uint32, mode uint32, context *fuse.Context) (fuseFile nodefs.File, code fuse.Status) {  	if fs.isFiltered(path) { | 
