9ec9d0c49c
The function used to do two things: 1) Walk the directory tree in a manner safe from symlink attacks 2) Open the final component in the mode requested by the caller This change drops (2), which was only used once, and lets the caller handle it. This simplifies the function and makes it fit for reuse in forward mode in openBackingPath(), and for using O_PATH on Linux.
119 lines
3.8 KiB
Go
119 lines
3.8 KiB
Go
package fusefrontend_reverse
|
|
|
|
import (
|
|
"log"
|
|
"path/filepath"
|
|
"syscall"
|
|
|
|
"golang.org/x/sys/unix"
|
|
|
|
"github.com/hanwen/go-fuse/fuse"
|
|
"github.com/hanwen/go-fuse/fuse/nodefs"
|
|
|
|
"github.com/rfjakob/gocryptfs/internal/nametransform"
|
|
"github.com/rfjakob/gocryptfs/internal/pathiv"
|
|
"github.com/rfjakob/gocryptfs/internal/syscallcompat"
|
|
"github.com/rfjakob/gocryptfs/internal/tlog"
|
|
)
|
|
|
|
const (
|
|
// virtualFileMode is the mode to use for virtual files (gocryptfs.diriv and
|
|
// *.name). They are always readable, as stated in func Access
|
|
virtualFileMode = syscall.S_IFREG | 0444
|
|
// inoBaseDirIV is the start of the inode number range that is used
|
|
// for virtual gocryptfs.diriv files. inoBaseNameFile is the thing for
|
|
// *.name files.
|
|
// The value 10^19 is just below 2^60. A power of 10 has been chosen so the
|
|
// "ls -li" output (which is base-10) is easy to read.
|
|
// 10^19 is the largest power of 10 that is smaller than
|
|
// INT64_MAX (=UINT64_MAX/2). This avoids signedness issues.
|
|
inoBaseDirIV = uint64(1000000000000000000)
|
|
inoBaseNameFile = uint64(2000000000000000000)
|
|
// inoBaseMin marks the start of the inode number space that is
|
|
// reserved for virtual files. It is the lowest of the inoBaseXXX values
|
|
// above.
|
|
inoBaseMin = inoBaseDirIV
|
|
)
|
|
|
|
func (rfs *ReverseFS) newDirIVFile(cRelPath string) (nodefs.File, fuse.Status) {
|
|
cDir := nametransform.Dir(cRelPath)
|
|
dir, err := rfs.decryptPath(cDir)
|
|
if err != nil {
|
|
return nil, fuse.ToStatus(err)
|
|
}
|
|
iv := pathiv.Derive(cDir, pathiv.PurposeDirIV)
|
|
return rfs.newVirtualFile(iv, rfs.args.Cipherdir, dir, inoBaseDirIV)
|
|
}
|
|
|
|
type virtualFile struct {
|
|
// Embed nodefs.defaultFile for a ENOSYS implementation of all methods
|
|
nodefs.File
|
|
// file content
|
|
content []byte
|
|
// backing directory
|
|
cipherdir string
|
|
// path to a parent file (relative to cipherdir)
|
|
parentFile string
|
|
// inode number of a virtual file is inode of parent file plus inoBase
|
|
inoBase uint64
|
|
}
|
|
|
|
// newVirtualFile creates a new in-memory file that does not have a representation
|
|
// on disk. "content" is the file content. Timestamps and file owner are copied
|
|
// from "parentFile" (plaintext path relative to "cipherdir").
|
|
// For a "gocryptfs.diriv" file, you would use the parent directory as
|
|
// "parentFile".
|
|
func (rfs *ReverseFS) newVirtualFile(content []byte, cipherdir string, parentFile string, inoBase uint64) (nodefs.File, fuse.Status) {
|
|
if inoBase < inoBaseMin {
|
|
log.Panicf("BUG: virtual inode number base %d is below reserved space", inoBase)
|
|
}
|
|
return &virtualFile{
|
|
File: nodefs.NewDefaultFile(),
|
|
content: content,
|
|
cipherdir: cipherdir,
|
|
parentFile: parentFile,
|
|
inoBase: inoBase,
|
|
}, fuse.OK
|
|
}
|
|
|
|
// Read - FUSE call
|
|
func (f *virtualFile) Read(buf []byte, off int64) (resultData fuse.ReadResult, status fuse.Status) {
|
|
if off >= int64(len(f.content)) {
|
|
return nil, fuse.OK
|
|
}
|
|
end := int(off) + len(buf)
|
|
if end > len(f.content) {
|
|
end = len(f.content)
|
|
}
|
|
return fuse.ReadResultData(f.content[off:end]), fuse.OK
|
|
}
|
|
|
|
// GetAttr - FUSE call
|
|
func (f *virtualFile) GetAttr(a *fuse.Attr) fuse.Status {
|
|
dir := filepath.Dir(f.parentFile)
|
|
dirfd, err := syscallcompat.OpenDirNofollow(f.cipherdir, dir)
|
|
if err != nil {
|
|
return fuse.ToStatus(err)
|
|
}
|
|
defer syscall.Close(dirfd)
|
|
name := filepath.Base(f.parentFile)
|
|
var st unix.Stat_t
|
|
err = syscallcompat.Fstatat(dirfd, name, &st, unix.AT_SYMLINK_NOFOLLOW)
|
|
if err != nil {
|
|
tlog.Debug.Printf("GetAttr: Fstatat %q: %v\n", f.parentFile, err)
|
|
return fuse.ToStatus(err)
|
|
}
|
|
if st.Ino > inoBaseMin {
|
|
tlog.Warn.Printf("virtualFile.GetAttr: parent file inode number %d crosses reserved space, max=%d. Returning EOVERFLOW.",
|
|
st.Ino, inoBaseMin)
|
|
return fuse.ToStatus(syscall.EOVERFLOW)
|
|
}
|
|
st.Ino = st.Ino + f.inoBase
|
|
st.Size = int64(len(f.content))
|
|
st.Mode = virtualFileMode
|
|
st.Nlink = 1
|
|
st2 := syscallcompat.Unix2syscall(st)
|
|
a.FromStat(&st2)
|
|
return fuse.OK
|
|
}
|