summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--Documentation/MANPAGE.md18
-rw-r--r--cli_args.go6
-rw-r--r--internal/fusefrontend/args.go2
-rw-r--r--internal/fusefrontend/file.go30
-rw-r--r--internal/fusefrontend/fs.go4
-rw-r--r--mount.go1
6 files changed, 45 insertions, 16 deletions
diff --git a/Documentation/MANPAGE.md b/Documentation/MANPAGE.md
index 9446f0b..aa40922 100644
--- a/Documentation/MANPAGE.md
+++ b/Documentation/MANPAGE.md
@@ -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)
diff --git a/cli_args.go b/cli_args.go
index 5751f81..7301d9a 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 d8b0304..eb796cd 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 d637528..5fe866b 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 e9e6113..f41e9a7 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 3bafd8c..29d34b8 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 {