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/fs.go | |
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/fs.go')
-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) { |