summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorJakob Unterwurzacher2017-07-11 23:19:58 +0200
committerJakob Unterwurzacher2017-07-11 23:19:58 +0200
commit3062de6187990f9b4f669ecd9dffdd48ee0d778f (patch)
treef62274c8d0497a1fec068f75d8519fbf0263247d
parent849ec10081c0eb04535017f8845501ae799ac477 (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
-rw-r--r--internal/fusefrontend/fs.go63
-rw-r--r--tests/defaults/main_test.go30
2 files changed, 89 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) {
diff --git a/tests/defaults/main_test.go b/tests/defaults/main_test.go
index ca11b43..5c6bb84 100644
--- a/tests/defaults/main_test.go
+++ b/tests/defaults/main_test.go
@@ -4,6 +4,7 @@ package defaults
import (
"bytes"
"io"
+ "io/ioutil"
"os"
"os/exec"
"runtime"
@@ -161,3 +162,32 @@ func TestXfs124(t *testing.T) {
wg.Wait()
}
+
+func TestWrite0200File(t *testing.T) {
+ fn := test_helpers.DefaultPlainDir + "/TestWrite0200File"
+ err := ioutil.WriteFile(fn, nil, 0200)
+ if err != nil {
+ t.Fatalf("creating empty file failed: %v", err)
+ }
+ fd, err := os.OpenFile(fn, os.O_WRONLY, 0)
+ if err != nil {
+ t.Fatal(err)
+ }
+ fi, err := fd.Stat()
+ if err != nil {
+ t.Fatal(err)
+ }
+ perms := fi.Mode().Perm()
+ if perms != 0200 {
+ t.Fatal("wrong initial permissions")
+ }
+ defer fd.Close()
+ _, err = fd.Write(make([]byte, 10))
+ if err != nil {
+ t.Fatal(err)
+ }
+ perms = fi.Mode().Perm()
+ if perms != 0200 {
+ t.Fatal("wrong restored permissions")
+ }
+}