fusefrontend_reverse: switch to stable inode numbers
The volatile inode numbers that we used before cause "find" to complain and error out. Virtual inode numbers are derived from their parent file inode number by adding 10^19, which is hopefully large enough no never cause problems in practice. If the backing directory contains inode numbers higher than that, stat() on these files will return EOVERFLOW. Example directory lising after this change: $ ls -i 926473 gocryptfs.conf 1000000000000926466 gocryptfs.diriv 944878 gocryptfs.longname.hmZojMqC6ns47eyVxLlH2ailKjN9bxfosi3C-FR8mjA 1000000000000944878 gocryptfs.longname.hmZojMqC6ns47eyVxLlH2ailKjN9bxfosi3C-FR8mjA.name 934408 Tdfbf02CKsTaGVYnAsSypA
This commit is contained in:
parent
e87aebb835
commit
778c955eea
@ -7,6 +7,8 @@ import (
|
||||
// Args is a container for arguments that are passed from main() to fusefrontend
|
||||
type Args struct {
|
||||
Masterkey []byte
|
||||
// Cipherdir is the backing storage directory (absolute path).
|
||||
// For reverse mode, Cipherdir actually contains *plaintext* files.
|
||||
Cipherdir string
|
||||
CryptoBackend cryptocore.AEADTypeEnum
|
||||
PlaintextNames bool
|
||||
|
@ -1,19 +0,0 @@
|
||||
package fusefrontend_reverse
|
||||
|
||||
import (
|
||||
"sync/atomic"
|
||||
)
|
||||
|
||||
func newInoGen() *inoGenT {
|
||||
var ino uint64 = 1
|
||||
return &inoGenT{&ino}
|
||||
}
|
||||
|
||||
type inoGenT struct {
|
||||
ino *uint64
|
||||
}
|
||||
|
||||
// Get the next inode counter value
|
||||
func (i *inoGenT) next() uint64 {
|
||||
return atomic.AddUint64(i.ino, 1)
|
||||
}
|
@ -5,7 +5,6 @@ import (
|
||||
"log"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"sync"
|
||||
"syscall"
|
||||
|
||||
"github.com/hanwen/go-fuse/fuse"
|
||||
@ -20,12 +19,6 @@ import (
|
||||
"github.com/rfjakob/gocryptfs/internal/tlog"
|
||||
)
|
||||
|
||||
const (
|
||||
// virtualFileMode is the mode to use for virtual files (gocryptfs.diriv and gocryptfs.longname.*.name)
|
||||
// they are always readable, as stated in func Access
|
||||
virtualFileMode = syscall.S_IFREG | 0444
|
||||
)
|
||||
|
||||
// ReverseFS implements the pathfs.FileSystem interface and provides an
|
||||
// encrypted view of a plaintext directory.
|
||||
type ReverseFS struct {
|
||||
@ -39,12 +32,6 @@ type ReverseFS struct {
|
||||
nameTransform *nametransform.NameTransform
|
||||
// Content encryption helper
|
||||
contentEnc *contentenc.ContentEnc
|
||||
// Inode number generator
|
||||
inoGen *inoGenT
|
||||
// Maps backing files device+inode pairs to user-facing unique inode numbers
|
||||
inoMap map[fusefrontend.DevInoStruct]uint64
|
||||
// Protects map access
|
||||
inoMapLock sync.Mutex
|
||||
}
|
||||
|
||||
var _ pathfs.FileSystem = &ReverseFS{}
|
||||
@ -68,8 +55,6 @@ func NewFS(args fusefrontend.Args) *ReverseFS {
|
||||
args: args,
|
||||
nameTransform: nameTransform,
|
||||
contentEnc: contentEnc,
|
||||
inoGen: newInoGen(),
|
||||
inoMap: map[fusefrontend.DevInoStruct]uint64{},
|
||||
}
|
||||
}
|
||||
|
||||
@ -102,7 +87,7 @@ func (rfs *ReverseFS) isNameFile(relPath string) bool {
|
||||
return fileType == nametransform.LongNameFilename
|
||||
}
|
||||
|
||||
// isTranslatedConfig returns true if the default config file name is in
|
||||
// isTranslatedConfig returns true if the default config file name is in use
|
||||
// and the ciphertext path is "gocryptfs.conf".
|
||||
// "gocryptfs.conf" then maps to ".gocryptfs.reverse.conf" in the plaintext
|
||||
// directory.
|
||||
@ -116,47 +101,22 @@ func (rfs *ReverseFS) isTranslatedConfig(relPath string) bool {
|
||||
return false
|
||||
}
|
||||
|
||||
func (rfs *ReverseFS) inoAwareStat(relPlainPath string) (*fuse.Attr, fuse.Status) {
|
||||
absPath, err := rfs.abs(relPlainPath, nil)
|
||||
if err != nil {
|
||||
return nil, fuse.ToStatus(err)
|
||||
}
|
||||
var fi os.FileInfo
|
||||
if relPlainPath == "" {
|
||||
// Look through symlinks for the root dir
|
||||
fi, err = os.Stat(absPath)
|
||||
} else {
|
||||
fi, err = os.Lstat(absPath)
|
||||
}
|
||||
if err != nil {
|
||||
return nil, fuse.ToStatus(err)
|
||||
}
|
||||
st := fi.Sys().(*syscall.Stat_t)
|
||||
// The file has hard links. We have to give it a stable inode number so
|
||||
// tar or rsync can find them.
|
||||
if fi.Mode().IsRegular() && st.Nlink > 1 {
|
||||
di := fusefrontend.DevInoFromStat(st)
|
||||
rfs.inoMapLock.Lock()
|
||||
stableIno := rfs.inoMap[di]
|
||||
if stableIno == 0 {
|
||||
rfs.inoMap[di] = rfs.inoGen.next()
|
||||
}
|
||||
rfs.inoMapLock.Unlock()
|
||||
st.Ino = stableIno
|
||||
} else {
|
||||
st.Ino = rfs.inoGen.next()
|
||||
}
|
||||
a := &fuse.Attr{}
|
||||
a.FromStat(st)
|
||||
return a, fuse.OK
|
||||
}
|
||||
|
||||
// GetAttr - FUSE call
|
||||
// "relPath" is the relative ciphertext path
|
||||
func (rfs *ReverseFS) GetAttr(relPath string, context *fuse.Context) (*fuse.Attr, fuse.Status) {
|
||||
// Handle "gocryptfs.conf"
|
||||
if rfs.isTranslatedConfig(relPath) {
|
||||
return rfs.inoAwareStat(configfile.ConfReverseName)
|
||||
absConfPath, _ := rfs.abs(configfile.ConfReverseName, nil)
|
||||
var st syscall.Stat_t
|
||||
err := syscall.Lstat(absConfPath, &st)
|
||||
if err != nil {
|
||||
return nil, fuse.ToStatus(err)
|
||||
}
|
||||
// Handle virtual files
|
||||
var a fuse.Attr
|
||||
a.FromStat(&st)
|
||||
return &a, fuse.OK
|
||||
}
|
||||
// Handle virtual files (gocryptfs.diriv, *.name)
|
||||
var f nodefs.File
|
||||
var status fuse.Status
|
||||
virtual := false
|
||||
@ -177,15 +137,31 @@ func (rfs *ReverseFS) GetAttr(relPath string, context *fuse.Context) (*fuse.Attr
|
||||
status = f.GetAttr(&a)
|
||||
return &a, status
|
||||
}
|
||||
|
||||
cPath, err := rfs.decryptPath(relPath)
|
||||
// Decrypt path to "plaintext relative path"
|
||||
pRelPath, err := rfs.decryptPath(relPath)
|
||||
if err != nil {
|
||||
return nil, fuse.ToStatus(err)
|
||||
}
|
||||
a, status := rfs.inoAwareStat(cPath)
|
||||
if !status.Ok() {
|
||||
return nil, status
|
||||
absPath, _ := rfs.abs(pRelPath, nil)
|
||||
// Stat the backing file
|
||||
var st syscall.Stat_t
|
||||
if relPath == "" {
|
||||
// Look through symlinks for the root dir
|
||||
err = syscall.Stat(absPath, &st)
|
||||
} else {
|
||||
err = syscall.Lstat(absPath, &st)
|
||||
}
|
||||
if err != nil {
|
||||
return nil, fuse.ToStatus(err)
|
||||
}
|
||||
// Instead of risking an inode number collision, we return an error.
|
||||
if st.Ino > virtualInoBase {
|
||||
tlog.Warn.Printf("GetAttr %q: backing file inode number %d crosses reserved space, max=%d. Returning EOVERFLOW.",
|
||||
relPath, st.Ino, virtualInoBase)
|
||||
return nil, fuse.ToStatus(syscall.EOVERFLOW)
|
||||
}
|
||||
var a fuse.Attr
|
||||
a.FromStat(&st)
|
||||
// Calculate encrypted file size
|
||||
if a.IsRegular() {
|
||||
a.Size = rfs.contentEnc.PlainSizeToCipherSize(a.Size)
|
||||
@ -200,7 +176,7 @@ func (rfs *ReverseFS) GetAttr(relPath string, context *fuse.Context) (*fuse.Attr
|
||||
|
||||
a.Size = uint64(len(linkTarget))
|
||||
}
|
||||
return a, fuse.OK
|
||||
return &a, fuse.OK
|
||||
}
|
||||
|
||||
// Access - FUSE call
|
||||
|
@ -37,6 +37,11 @@ func derivePathIV(path string, purpose ivPurposeType) []byte {
|
||||
return hash[:nametransform.DirIVLen]
|
||||
}
|
||||
|
||||
// abs basically returns storage dir + "/" + relPath.
|
||||
// It takes an error parameter so it can directly wrap decryptPath like this:
|
||||
// a, err := rfs.abs(rfs.decryptPath(relPath))
|
||||
// abs never generates an error on its own. In other words, abs(p, nil) never
|
||||
// fails.
|
||||
func (rfs *ReverseFS) abs(relPath string, err error) (string, error) {
|
||||
if err != nil {
|
||||
return "", err
|
||||
|
@ -9,6 +9,18 @@ import (
|
||||
"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
|
||||
// virtualInoBase is the start of the inode number range that is used
|
||||
// for virtual 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 UINT64_MAX/2.
|
||||
virtualInoBase = uint64(1000000000000000000)
|
||||
)
|
||||
|
||||
func (rfs *ReverseFS) newDirIVFile(cRelPath string) (nodefs.File, fuse.Status) {
|
||||
cDir := saneDir(cRelPath)
|
||||
absDir, err := rfs.abs(rfs.decryptPath(cDir))
|
||||
@ -25,8 +37,6 @@ type virtualFile struct {
|
||||
content []byte
|
||||
// absolute path to a parent file
|
||||
parentFile string
|
||||
// inode number
|
||||
ino uint64
|
||||
}
|
||||
|
||||
// newVirtualFile creates a new in-memory file that does not have a representation
|
||||
@ -38,7 +48,6 @@ func (rfs *ReverseFS) newVirtualFile(content []byte, parentFile string) (nodefs.
|
||||
File: nodefs.NewDefaultFile(),
|
||||
content: content,
|
||||
parentFile: parentFile,
|
||||
ino: rfs.inoGen.next(),
|
||||
}, fuse.OK
|
||||
}
|
||||
|
||||
@ -62,7 +71,12 @@ func (f *virtualFile) GetAttr(a *fuse.Attr) fuse.Status {
|
||||
tlog.Debug.Printf("GetAttr: Lstat %q: %v\n", f.parentFile, err)
|
||||
return fuse.ToStatus(err)
|
||||
}
|
||||
st.Ino = f.ino
|
||||
if st.Ino > virtualInoBase {
|
||||
tlog.Warn.Printf("virtualFile.GetAttr: parent file inode number %d crosses reserved space, max=%d. Returning EOVERFLOW.",
|
||||
st.Ino, virtualInoBase)
|
||||
return fuse.ToStatus(syscall.EOVERFLOW)
|
||||
}
|
||||
st.Ino = st.Ino + virtualInoBase
|
||||
st.Size = int64(len(f.content))
|
||||
st.Mode = virtualFileMode
|
||||
st.Nlink = 1
|
||||
|
Loading…
Reference in New Issue
Block a user