diff options
| -rw-r--r-- | Documentation/MANPAGE.md | 18 | ||||
| -rw-r--r-- | cli_args.go | 6 | ||||
| -rw-r--r-- | internal/fusefrontend/args.go | 2 | ||||
| -rw-r--r-- | internal/fusefrontend/file.go | 30 | ||||
| -rw-r--r-- | internal/fusefrontend/fs.go | 4 | ||||
| -rw-r--r-- | mount.go | 1 | 
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. @@ -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 { | 
