From 2988ccfc7785b404256da0428a594bfdc3df737c Mon Sep 17 00:00:00 2001 From: Jakob Unterwurzacher Date: Thu, 24 Nov 2016 22:36:04 +0100 Subject: [PATCH] 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. More info: https://github.com/rfjakob/gocryptfs/issues/63 --- cli_args.go | 6 ++++-- internal/fusefrontend/args.go | 2 ++ internal/fusefrontend/file.go | 30 +++++++++++++++++++----------- internal/fusefrontend/fs.go | 4 ++-- mount.go | 1 + 5 files changed, 28 insertions(+), 15 deletions(-) diff --git a/cli_args.go b/cli_args.go index 5751f81d..7301d9ac 100644 --- a/cli_args.go +++ b/cli_args.go @@ -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)" diff --git a/internal/fusefrontend/args.go b/internal/fusefrontend/args.go index d8b03048..eb796cdc 100644 --- a/internal/fusefrontend/args.go +++ b/internal/fusefrontend/args.go @@ -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 } diff --git a/internal/fusefrontend/file.go b/internal/fusefrontend/file.go index d637528c..5fe866b9 100644 --- a/internal/fusefrontend/file.go +++ b/internal/fusefrontend/file.go @@ -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)) diff --git a/internal/fusefrontend/fs.go b/internal/fusefrontend/fs.go index e9e61139..f41e9a7c 100644 --- a/internal/fusefrontend/fs.go +++ b/internal/fusefrontend/fs.go @@ -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. diff --git a/mount.go b/mount.go index 3bafd8c3..29d34b80 100644 --- a/mount.go +++ b/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 {