Skip to content

Commit

Permalink
main, fusefrontend: add "-noprealloc" option
Browse files Browse the repository at this point in the history
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: #63
  • Loading branch information
rfjakob committed Nov 24, 2016
1 parent 3412405 commit 2988ccf
Show file tree
Hide file tree
Showing 5 changed files with 28 additions and 15 deletions.
6 changes: 4 additions & 2 deletions cli_args.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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")
Expand All @@ -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)"
Expand Down
2 changes: 2 additions & 0 deletions internal/fusefrontend/args.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
}
30 changes: 19 additions & 11 deletions internal/fusefrontend/file.go
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand All @@ -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
}

Expand Down Expand Up @@ -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)
Expand Down Expand Up @@ -285,19 +290,22 @@ 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 {
writeBuf.Write(w)
}
// 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))
Expand Down
4 changes: 2 additions & 2 deletions internal/fusefrontend/fs.go
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down Expand Up @@ -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.
Expand Down
1 change: 1 addition & 0 deletions mount.go
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down

0 comments on commit 2988ccf

Please sign in to comment.