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
|
nameTransform *nametransform.NameTransform
|
||||||
// Content encryption helper
|
// Content encryption helper
|
||||||
contentEnc *contentenc.ContentEnc
|
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.
|
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) {
|
if fs.isFiltered(path) {
|
||||||
return nil, fuse.EPERM
|
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)
|
newFlags := fs.mangleOpenFlags(flags)
|
||||||
cPath, err := fs.getBackingPath(path)
|
cPath, err := fs.getBackingPath(path)
|
||||||
if err != nil {
|
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)
|
return nil, fuse.ToStatus(err)
|
||||||
}
|
}
|
||||||
tlog.Debug.Printf("Open: %s", cPath)
|
tlog.Debug.Printf("Open: %s", cPath)
|
||||||
f, err := os.OpenFile(cPath, newFlags, 0666)
|
f, err := os.OpenFile(cPath, newFlags, 0)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
err2 := err.(*os.PathError)
|
sysErr := err.(*os.PathError).Err
|
||||||
if err2.Err == syscall.EMFILE {
|
if sysErr == syscall.EMFILE {
|
||||||
var lim syscall.Rlimit
|
var lim syscall.Rlimit
|
||||||
syscall.Getrlimit(syscall.RLIMIT_NOFILE, &lim)
|
syscall.Getrlimit(syscall.RLIMIT_NOFILE, &lim)
|
||||||
tlog.Warn.Printf("Open %q: too many open files. Current \"ulimit -n\": %d", cPath, lim.Cur)
|
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 nil, fuse.ToStatus(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
return NewFile(f, fs)
|
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.
|
// Create implements pathfs.Filesystem.
|
||||||
func (fs *FS) Create(path string, flags uint32, mode uint32, context *fuse.Context) (fuseFile nodefs.File, code fuse.Status) {
|
func (fs *FS) Create(path string, flags uint32, mode uint32, context *fuse.Context) (fuseFile nodefs.File, code fuse.Status) {
|
||||||
if fs.isFiltered(path) {
|
if fs.isFiltered(path) {
|
||||||
|
@ -4,6 +4,7 @@ package defaults
|
|||||||
import (
|
import (
|
||||||
"bytes"
|
"bytes"
|
||||||
"io"
|
"io"
|
||||||
|
"io/ioutil"
|
||||||
"os"
|
"os"
|
||||||
"os/exec"
|
"os/exec"
|
||||||
"runtime"
|
"runtime"
|
||||||
@ -161,3 +162,32 @@ func TestXfs124(t *testing.T) {
|
|||||||
|
|
||||||
wg.Wait()
|
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