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
This commit is contained in:
parent
849ec10081
commit
3062de6187
@ -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) {
|
||||
|
@ -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")
|
||||
}
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user