main, fusefrontend: add "-noprealloc" option
Preallocation is very slow on hdds that run btrfs. Give the user the option to disable it. This greatly speeds up small file operations but reduces the robustness against out-of-space errors. Also add the option to the man page. More info: https://github.com/rfjakob/gocryptfs/issues/63
This commit is contained in:
parent
024511d9c7
commit
0f8d3318a3
@ -106,6 +106,22 @@ Options:
|
||||
: Allow mounting over non-empty directories. FUSE by default disallows
|
||||
this to prevent accidential shadowing of files.
|
||||
|
||||
**-noprealloc**
|
||||
: Disable preallocation before writing. By default, gocryptfs
|
||||
preallocates the space the next write will take using fallocate(2)
|
||||
in mode FALLOC_FL_KEEP_SIZE. The preallocation makes sure it cannot
|
||||
run out of space in the middle of the write, which would cause the
|
||||
last 4kB block to be corrupt and unreadable.
|
||||
|
||||
On ext4, preallocation is fast and does not cause a
|
||||
noticeable performance hit. Unfortunately, on Btrfs, preallocation
|
||||
is very slow, especially on rotational HDDs. The "-noprealloc"
|
||||
option gives users the choice to trade robustness against
|
||||
out-of-space errors for a massive speedup.
|
||||
|
||||
For benchmarks and more details of the issue see
|
||||
https://github.com/rfjakob/gocryptfs/issues/63 .
|
||||
|
||||
**-nosyslog**
|
||||
: Diagnostic messages are normally redirected to syslog once gocryptfs
|
||||
daemonizes. This option disables the redirection and messages will
|
||||
@ -197,4 +213,4 @@ Mount an ecrypted view of joe's home directory using reverse mode:
|
||||
|
||||
SEE ALSO
|
||||
========
|
||||
fuse(8)
|
||||
fuse(8) fallocate(2)
|
||||
|
@ -16,7 +16,8 @@ import (
|
||||
type argContainer struct {
|
||||
debug, init, zerokey, fusedebug, openssl, passwd, fg, version,
|
||||
plaintextnames, quiet, nosyslog, wpanic,
|
||||
longnames, allow_other, ro, reverse, aessiv, nonempty, raw64 bool
|
||||
longnames, allow_other, ro, reverse, aessiv, nonempty, raw64,
|
||||
noprealloc bool
|
||||
masterkey, mountpoint, cipherdir, cpuprofile, extpass,
|
||||
memprofile, ko, passfile, ctlsock string
|
||||
// Configuration file name override
|
||||
@ -105,6 +106,7 @@ func parseCliOpts() (args argContainer) {
|
||||
flagSet.BoolVar(&args.aessiv, "aessiv", false, "AES-SIV encryption")
|
||||
flagSet.BoolVar(&args.nonempty, "nonempty", false, "Allow mounting over non-empty directories")
|
||||
flagSet.BoolVar(&args.raw64, "raw64", false, "Use unpadded base64 for file names")
|
||||
flagSet.BoolVar(&args.noprealloc, "noprealloc", false, "Disable preallocation before writing")
|
||||
flagSet.StringVar(&args.masterkey, "masterkey", "", "Mount with explicit master key")
|
||||
flagSet.StringVar(&args.cpuprofile, "cpuprofile", "", "Write cpu profile to specified file")
|
||||
flagSet.StringVar(&args.memprofile, "memprofile", "", "Write memory profile to specified file")
|
||||
@ -116,7 +118,7 @@ func parseCliOpts() (args argContainer) {
|
||||
flagSet.IntVar(&args.notifypid, "notifypid", 0, "Send USR1 to the specified process after "+
|
||||
"successful mount - used internally for daemonization")
|
||||
flagSet.IntVar(&args.scryptn, "scryptn", configfile.ScryptDefaultLogN, "scrypt cost parameter logN. "+
|
||||
"Setting this to a lower value speeds up mounting but makes the password susceptible to brute-force attacks")
|
||||
"A lower value speeds up mounting but makes the password susceptible to brute-force attacks")
|
||||
// Ignored otions
|
||||
var dummyBool bool
|
||||
ignoreText := "(ignored for compatibility)"
|
||||
|
@ -21,4 +21,6 @@ type Args struct {
|
||||
// Raw64 is true when RawURLEncoding (without padding) should be used for
|
||||
// file names
|
||||
Raw64 bool
|
||||
// NoPrealloc disables automatic preallocation before writing
|
||||
NoPrealloc bool
|
||||
}
|
||||
|
@ -49,10 +49,12 @@ type file struct {
|
||||
// The opCount is used to judge whether "lastWrittenOffset" is still
|
||||
// guaranteed to be correct.
|
||||
lastOpCount uint64
|
||||
// Parent filesystem
|
||||
fs *FS
|
||||
}
|
||||
|
||||
// NewFile returns a new go-fuse File instance.
|
||||
func NewFile(fd *os.File, writeOnly bool, contentEnc *contentenc.ContentEnc) (nodefs.File, fuse.Status) {
|
||||
func NewFile(fd *os.File, writeOnly bool, fs *FS) (nodefs.File, fuse.Status) {
|
||||
var st syscall.Stat_t
|
||||
err := syscall.Fstat(int(fd.Fd()), &st)
|
||||
if err != nil {
|
||||
@ -65,10 +67,11 @@ func NewFile(fd *os.File, writeOnly bool, contentEnc *contentenc.ContentEnc) (no
|
||||
return &file{
|
||||
fd: fd,
|
||||
writeOnly: writeOnly,
|
||||
contentEnc: contentEnc,
|
||||
contentEnc: fs.contentEnc,
|
||||
devIno: di,
|
||||
fileTableEntry: t,
|
||||
loopbackFile: nodefs.NewLoopbackFile(fd),
|
||||
fs: fs,
|
||||
}, fuse.OK
|
||||
}
|
||||
|
||||
@ -107,10 +110,12 @@ func (f *file) createHeader() (fileID []byte, err error) {
|
||||
h := contentenc.RandomHeader()
|
||||
buf := h.Pack()
|
||||
// Prevent partially written (=corrupt) header by preallocating the space beforehand
|
||||
err = syscallcompat.EnospcPrealloc(int(f.fd.Fd()), 0, contentenc.HeaderLen)
|
||||
if err != nil {
|
||||
tlog.Warn.Printf("ino%d: createHeader: prealloc failed: %s\n", f.devIno.ino, err.Error())
|
||||
return nil, err
|
||||
if !f.fs.args.NoPrealloc {
|
||||
err = syscallcompat.EnospcPrealloc(int(f.fd.Fd()), 0, contentenc.HeaderLen)
|
||||
if err != nil {
|
||||
tlog.Warn.Printf("ino%d: createHeader: prealloc failed: %s\n", f.devIno.ino, err.Error())
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
// Actually write header
|
||||
_, err = f.fd.WriteAt(buf, 0)
|
||||
@ -285,7 +290,7 @@ func (f *file) doWrite(data []byte, off int64) (uint32, fuse.Status) {
|
||||
writeChain[i] = blockData
|
||||
numOutBytes += len(blockData)
|
||||
}
|
||||
// Concatenenate all elements in the writeChain into one contigous buffer
|
||||
// Concatenenate all elements in the writeChain into one contiguous buffer
|
||||
tmp := make([]byte, numOutBytes)
|
||||
writeBuf := bytes.NewBuffer(tmp[:0])
|
||||
for _, w := range writeChain {
|
||||
@ -293,11 +298,14 @@ func (f *file) doWrite(data []byte, off int64) (uint32, fuse.Status) {
|
||||
}
|
||||
// Preallocate so we cannot run out of space in the middle of the write.
|
||||
// This prevents partially written (=corrupt) blocks.
|
||||
var err error
|
||||
cOff := blocks[0].BlockCipherOff()
|
||||
err := syscallcompat.EnospcPrealloc(int(f.fd.Fd()), int64(cOff), int64(writeBuf.Len()))
|
||||
if err != nil {
|
||||
tlog.Warn.Printf("ino%d fh%d: doWrite: prealloc failed: %s", f.devIno.ino, f.intFd(), err.Error())
|
||||
return 0, fuse.ToStatus(err)
|
||||
if !f.fs.args.NoPrealloc {
|
||||
err = syscallcompat.EnospcPrealloc(int(f.fd.Fd()), int64(cOff), int64(writeBuf.Len()))
|
||||
if err != nil {
|
||||
tlog.Warn.Printf("ino%d fh%d: doWrite: prealloc failed: %s", f.devIno.ino, f.intFd(), err.Error())
|
||||
return 0, fuse.ToStatus(err)
|
||||
}
|
||||
}
|
||||
// Write
|
||||
_, err = f.fd.WriteAt(writeBuf.Bytes(), int64(cOff))
|
||||
|
@ -106,7 +106,7 @@ func (fs *FS) Open(path string, flags uint32, context *fuse.Context) (fuseFile n
|
||||
return nil, fuse.ToStatus(err)
|
||||
}
|
||||
|
||||
return NewFile(f, writeOnly, fs.contentEnc)
|
||||
return NewFile(f, writeOnly, fs)
|
||||
}
|
||||
|
||||
// Create implements pathfs.Filesystem.
|
||||
@ -160,7 +160,7 @@ func (fs *FS) Create(path string, flags uint32, mode uint32, context *fuse.Conte
|
||||
tlog.Warn.Printf("Create: Chown failed: %v", err)
|
||||
}
|
||||
}
|
||||
return NewFile(fd, writeOnly, fs.contentEnc)
|
||||
return NewFile(fd, writeOnly, fs)
|
||||
}
|
||||
|
||||
// Chmod implements pathfs.Filesystem.
|
||||
|
1
mount.go
1
mount.go
@ -145,6 +145,7 @@ func initFuseFrontend(key []byte, args *argContainer, confFile *configfile.ConfF
|
||||
CryptoBackend: cryptoBackend,
|
||||
ConfigCustom: args._configCustom,
|
||||
Raw64: args.raw64,
|
||||
NoPrealloc: args.noprealloc,
|
||||
}
|
||||
// confFile is nil when "-zerokey" or "-masterkey" was used
|
||||
if confFile != nil {
|
||||
|
Loading…
Reference in New Issue
Block a user