aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--README.md16
-rw-r--r--SECURITY.md9
-rw-r--r--cryptfs/config_file.go14
-rw-r--r--cryptfs/content_test.go21
-rw-r--r--cryptfs/cryptfs.go1
-rw-r--r--cryptfs/cryptfs_content.go20
-rw-r--r--cryptfs/file_header.go56
-rw-r--r--cryptfs/intrablock.go2
-rw-r--r--pathfs_frontend/file.go179
-rw-r--r--pathfs_frontend/fs.go3
10 files changed, 260 insertions, 61 deletions
diff --git a/README.md b/README.md
index e8d69cf..79ff3c8 100644
--- a/README.md
+++ b/README.md
@@ -62,3 +62,19 @@ The output should look like this:
BenchmarkStreamRead 200 7848155 ns/op 133.61 MB/s
ok github.com/rfjakob/gocryptfs 9.407s
+Changelog
+---------
+
+v0.3 (in progress)
+* Add file header that contains a random id to authenticate blocks
+ * This is an on-disk-format change
+
+v0.2
+* Replace bash daemonization wrapper with native Go implementation
+* Better user feedback on mount failures
+
+v0.1
+* First release
+
+See https://github.com/rfjakob/gocryptfs/releases for the release dates
+and associated tags.
diff --git a/SECURITY.md b/SECURITY.md
index dcd16c5..2e6f3f4 100644
--- a/SECURITY.md
+++ b/SECURITY.md
@@ -52,12 +52,11 @@ unless you have the key. The opposite of integrity is *malleability*.
* This means that any modification inside a block will be detected when reading
the block and decryption will be aborted. The failure is logged and an
I/O error is returned to the user.
-* Each block uses its block number as GCM *authentication data*
+* Every file has a header that contains a 16-byte random *file id*
+* Each block uses the file id and its block number as GCM *authentication data*
* This means the position of the blocks is protected as well. The blocks
- can not be reordered without causing an decryption error.
-* However, proper affiliation of a block to the file is can not be verified.
- * This means that blocks can be copied between different files provided
- that they stay at the same position.
+ can not be reordered or copied between different files without
+ causing an decryption error.
* For technical reasons (sparse files), the special "all-zero" block is
always seen as a valid block that decrypts to all-zero plaintext.
* This means that whole blocks can be zeroed out
diff --git a/cryptfs/config_file.go b/cryptfs/config_file.go
index ef426ff..7e762ad 100644
--- a/cryptfs/config_file.go
+++ b/cryptfs/config_file.go
@@ -1,6 +1,7 @@
package cryptfs
import (
+ "fmt"
"encoding/json"
"io/ioutil"
)
@@ -19,6 +20,8 @@ type ConfFile struct {
EncryptedKey []byte
// Stores parameters for scrypt hashing (key derivation)
ScryptObject scryptKdf
+ // The On-Disk-Format version this filesystem uses
+ Version uint16
}
// CreateConfFile - create a new config with a random key encrypted with
@@ -31,8 +34,11 @@ func CreateConfFile(filename string, password string) error {
key := RandBytes(KEY_LEN)
// Encrypt it using the password
+ // This sets ScryptObject and EncryptedKey
cf.EncryptKey(key, password)
+ cf.Version = HEADER_CURRENT_VERSION
+
// Write file to disk
err := cf.WriteFile()
@@ -58,6 +64,10 @@ func LoadConfFile(filename string, password string) ([]byte, *ConfFile, error) {
return nil, nil, err
}
+ if cf.Version != HEADER_CURRENT_VERSION {
+ return nil, nil, fmt.Errorf("Unsupported version %d", cf.Version)
+ }
+
// Generate derived key from password
scryptHash := cf.ScryptObject.DeriveKey(password)
@@ -65,7 +75,7 @@ func LoadConfFile(filename string, password string) ([]byte, *ConfFile, error) {
// We use stock go GCM instead of OpenSSL here as speed is not important
// and we get better error messages
cfs := NewCryptFS(scryptHash, false)
- key, err := cfs.DecryptBlock(cf.EncryptedKey, 0)
+ key, err := cfs.DecryptBlock(cf.EncryptedKey, 0, nil)
if err != nil {
Warn.Printf("failed to unlock master key: %s\n", err.Error())
Warn.Printf("Password incorrect.\n")
@@ -84,7 +94,7 @@ func (cf *ConfFile) EncryptKey(key []byte, password string) {
// Lock master key using password-based key
cfs := NewCryptFS(scryptHash, false)
- cf.EncryptedKey = cfs.EncryptBlock(key, 0)
+ cf.EncryptedKey = cfs.EncryptBlock(key, 0, nil)
}
// WriteFile - write out config in JSON format to file "filename.tmp"
diff --git a/cryptfs/content_test.go b/cryptfs/content_test.go
index aa6c9f3..4e1b447 100644
--- a/cryptfs/content_test.go
+++ b/cryptfs/content_test.go
@@ -1,7 +1,6 @@
package cryptfs
import (
- "fmt"
"testing"
)
@@ -17,6 +16,8 @@ func TestSplitRange(t *testing.T) {
testRange{0, 10},
testRange{234, 6511},
testRange{65444, 54},
+ testRange{0, 1024*1024},
+ testRange{0, 65536},
testRange{6654, 8945})
key := make([]byte, KEY_LEN)
@@ -24,10 +25,14 @@ func TestSplitRange(t *testing.T) {
for _, r := range ranges {
parts := f.SplitRange(r.offset, r.length)
+ var lastBlockNo uint64 = 1<<63
for _, p := range parts {
+ if p.BlockNo == lastBlockNo {
+ t.Errorf("Duplicate block number %d", p.BlockNo)
+ }
+ lastBlockNo = p.BlockNo
if p.Length > DEFAULT_PLAINBS || p.Skip >= DEFAULT_PLAINBS {
- fmt.Printf("Test fail: n=%d, length=%d, offset=%d\n", p.BlockNo, p.Length, p.Skip)
- t.Fail()
+ t.Errorf("Test fail: n=%d, length=%d, offset=%d\n", p.BlockNo, p.Length, p.Skip)
}
}
}
@@ -48,13 +53,13 @@ func TestCiphertextRange(t *testing.T) {
for _, r := range ranges {
alignedOffset, alignedLength, skipBytes := f.CiphertextRange(r.offset, r.length)
if alignedLength < r.length {
- t.Fail()
+ t.Errorf("alignedLength=%s is smaller than length=%d", alignedLength, r.length)
}
- if alignedOffset%f.cipherBS != 0 {
- t.Fail()
+ if (alignedOffset - HEADER_LEN)%f.cipherBS != 0 {
+ t.Errorf("alignedOffset=%d is not aligned", alignedOffset)
}
if r.offset%f.plainBS != 0 && skipBytes == 0 {
- t.Fail()
+ t.Errorf("skipBytes=0")
}
}
}
@@ -67,7 +72,7 @@ func TestBlockNo(t *testing.T) {
if b != 0 {
t.Errorf("actual: %d", b)
}
- b = f.BlockNoCipherOff(f.CipherBS())
+ b = f.BlockNoCipherOff(HEADER_LEN + f.CipherBS())
if b != 1 {
t.Errorf("actual: %d", b)
}
diff --git a/cryptfs/cryptfs.go b/cryptfs/cryptfs.go
index 1556757..0593214 100644
--- a/cryptfs/cryptfs.go
+++ b/cryptfs/cryptfs.go
@@ -13,6 +13,7 @@ const (
KEY_LEN = 32 // AES-256
NONCE_LEN = 12
AUTH_TAG_LEN = 16
+ BLOCK_OVERHEAD = NONCE_LEN + AUTH_TAG_LEN
)
type CryptFS struct {
diff --git a/cryptfs/cryptfs_content.go b/cryptfs/cryptfs_content.go
index 7a1859a..03253d3 100644
--- a/cryptfs/cryptfs_content.go
+++ b/cryptfs/cryptfs_content.go
@@ -30,14 +30,14 @@ type CryptFile struct {
}
// DecryptBlocks - Decrypt a number of blocks
-func (be *CryptFS) DecryptBlocks(ciphertext []byte, firstBlockNo uint64) ([]byte, error) {
+func (be *CryptFS) DecryptBlocks(ciphertext []byte, firstBlockNo uint64, fileId []byte) ([]byte, error) {
cBuf := bytes.NewBuffer(ciphertext)
var err error
var pBuf bytes.Buffer
for cBuf.Len() > 0 {
cBlock := cBuf.Next(int(be.cipherBS))
var pBlock []byte
- pBlock, err = be.DecryptBlock(cBlock, firstBlockNo)
+ pBlock, err = be.DecryptBlock(cBlock, firstBlockNo, fileId)
if err != nil {
break
}
@@ -51,7 +51,7 @@ func (be *CryptFS) DecryptBlocks(ciphertext []byte, firstBlockNo uint64) ([]byte
//
// Corner case: A full-sized block of all-zero ciphertext bytes is translated
// to an all-zero plaintext block, i.e. file hole passtrough.
-func (be *CryptFS) DecryptBlock(ciphertext []byte, blockNo uint64) ([]byte, error) {
+func (be *CryptFS) DecryptBlock(ciphertext []byte, blockNo uint64, fileId []byte) ([]byte, error) {
// Empty block?
if len(ciphertext) == 0 {
@@ -77,6 +77,7 @@ func (be *CryptFS) DecryptBlock(ciphertext []byte, blockNo uint64) ([]byte, erro
// Decrypt
var plaintext []byte
aData := make([]byte, 8)
+ aData = append(aData, fileId...)
binary.BigEndian.PutUint64(aData, blockNo)
plaintext, err := be.gcm.Open(plaintext, nonce, ciphertext, aData)
@@ -89,8 +90,8 @@ func (be *CryptFS) DecryptBlock(ciphertext []byte, blockNo uint64) ([]byte, erro
return plaintext, nil
}
-// encryptBlock - Encrypt and add MAC using GCM
-func (be *CryptFS) EncryptBlock(plaintext []byte, blockNo uint64) []byte {
+// encryptBlock - Encrypt and add IV and MAC
+func (be *CryptFS) EncryptBlock(plaintext []byte, blockNo uint64, fileId []byte) []byte {
// Empty block?
if len(plaintext) == 0 {
@@ -103,6 +104,7 @@ func (be *CryptFS) EncryptBlock(plaintext []byte, blockNo uint64) []byte {
// Encrypt plaintext and append to nonce
aData := make([]byte, 8)
binary.BigEndian.PutUint64(aData, blockNo)
+ aData = append(aData, fileId...)
ciphertext := be.gcm.Seal(nonce, nonce, plaintext, aData)
return ciphertext
@@ -118,6 +120,7 @@ func (be *CryptFS) SplitRange(offset uint64, length uint64) []intraBlock {
for length > 0 {
b.BlockNo = offset / be.plainBS
b.Skip = offset % be.plainBS
+ // Minimum of remaining data and remaining space in the block
b.Length = be.minu64(length, be.plainBS-b.Skip)
parts = append(parts, b)
offset += b.Length
@@ -134,6 +137,9 @@ func (be *CryptFS) PlainSize(size uint64) uint64 {
return 0
}
+ // Account for header
+ size -= HEADER_LEN
+
overhead := be.cipherBS - be.plainBS
nBlocks := (size + be.cipherBS - 1) / be.cipherBS
if nBlocks*overhead > size {
@@ -171,7 +177,7 @@ func (be *CryptFS) CiphertextRange(offset uint64, length uint64) (alignedOffset
firstBlockNo := offset / be.plainBS
lastBlockNo := (offset + length - 1) / be.plainBS
- alignedOffset = firstBlockNo * be.cipherBS
+ alignedOffset = HEADER_LEN + firstBlockNo * be.cipherBS
alignedLength = (lastBlockNo - firstBlockNo + 1) * be.cipherBS
skipBytes = int(skip)
@@ -232,5 +238,5 @@ func (be *CryptFS) BlockNoPlainOff(plainOffset uint64) uint64 {
// Get the block number at ciphter-text offset
func (be *CryptFS) BlockNoCipherOff(cipherOffset uint64) uint64 {
- return cipherOffset / be.cipherBS
+ return (cipherOffset - HEADER_LEN) / be.cipherBS
}
diff --git a/cryptfs/file_header.go b/cryptfs/file_header.go
new file mode 100644
index 0000000..3fd7266
--- /dev/null
+++ b/cryptfs/file_header.go
@@ -0,0 +1,56 @@
+package cryptfs
+
+// Per-file header
+//
+// Format: [ "Version" uint16 big endian ] [ "Id" 16 random bytes ]
+
+import (
+ "encoding/binary"
+ "fmt"
+)
+
+const (
+ HEADER_CURRENT_VERSION = 1 // Current on-disk-format version
+ HEADER_VERSION_LEN = 2 // uint16
+ HEADER_ID_LEN = 16 // 128 bit random file id
+ HEADER_LEN = HEADER_VERSION_LEN + HEADER_ID_LEN // Total header length
+)
+
+type FileHeader struct {
+ Version uint16
+ Id []byte
+}
+
+// Pack - serialize fileHeader object
+func (h *FileHeader) Pack() []byte {
+ if len(h.Id) != HEADER_ID_LEN || h.Version != HEADER_CURRENT_VERSION {
+ panic("FileHeader object not properly initialized")
+ }
+ buf := make([]byte, HEADER_LEN)
+ binary.BigEndian.PutUint16(buf[0:HEADER_VERSION_LEN], h.Version)
+ copy(buf[HEADER_VERSION_LEN:], h.Id)
+ return buf
+
+}
+
+// ParseHeader - parse "buf" into fileHeader object
+func ParseHeader(buf []byte) (*FileHeader, error) {
+ if len(buf) != HEADER_LEN {
+ return nil, fmt.Errorf("ParseHeader: invalid length: got %d, want %d", len(buf), HEADER_LEN)
+ }
+ var h FileHeader
+ h.Version = binary.BigEndian.Uint16(buf[0:HEADER_VERSION_LEN])
+ if h.Version != HEADER_CURRENT_VERSION {
+ return nil, fmt.Errorf("ParseHeader: invalid version: got %d, want %d", h.Version, HEADER_CURRENT_VERSION)
+ }
+ h.Id = buf[HEADER_VERSION_LEN:]
+ return &h, nil
+}
+
+// RandomHeader - create new fileHeader object with random Id
+func RandomHeader() *FileHeader {
+ var h FileHeader
+ h.Version = HEADER_CURRENT_VERSION
+ h.Id = RandBytes(HEADER_ID_LEN)
+ return &h
+}
diff --git a/cryptfs/intrablock.go b/cryptfs/intrablock.go
index 7f3a1eb..c83976c 100644
--- a/cryptfs/intrablock.go
+++ b/cryptfs/intrablock.go
@@ -19,7 +19,7 @@ func (ib *intraBlock) IsPartial() bool {
// CiphertextRange - get byte range in ciphertext file corresponding to BlockNo
// (complete block)
func (ib *intraBlock) CiphertextRange() (offset uint64, length uint64) {
- return ib.BlockNo * ib.fs.cipherBS, ib.fs.cipherBS
+ return HEADER_LEN + ib.BlockNo * ib.fs.cipherBS, ib.fs.cipherBS
}
// PlaintextRange - get byte range in plaintext corresponding to BlockNo
diff --git a/pathfs_frontend/file.go b/pathfs_frontend/file.go
index 3304267..438fe77 100644
--- a/pathfs_frontend/file.go
+++ b/pathfs_frontend/file.go
@@ -23,7 +23,7 @@ type file struct {
// reuse the fd number after it is closed. When open races
// with another close, they may lead to confusion as which
// file gets written in the end.
- lock sync.Mutex
+ fdLock sync.Mutex
// Was the file opened O_WRONLY?
writeOnly bool
@@ -33,6 +33,9 @@ type file struct {
// Inode number
ino uint64
+
+ // File header
+ header *cryptfs.FileHeader
}
func NewFile(fd *os.File, writeOnly bool, cfs *cryptfs.CryptFS) nodefs.File {
@@ -54,26 +57,83 @@ func (f *file) InnerFile() nodefs.File {
func (f *file) SetInode(n *nodefs.Inode) {
}
+// Ensure that all modifications to the file contents are serialized and no
+// reads happen concurrently.
+//
+// This prevents several races:
+// * getFileId vs Truncate
+// * zeroPad vs Read
+// * RMW vs Write
+func (f *file) wlock() {
+}
+func (f *file) rlock() {
+}
+func (f *file) unlock() {
+}
+
+// readHeader - load the file header from disk
+//
+// Returns io.EOF if the file is empty
+func (f *file) readHeader() error {
+ buf := make([]byte, cryptfs.HEADER_LEN)
+ _, err := f.fd.ReadAt(buf, 0)
+ if err != nil {
+ return err
+ }
+ h, err := cryptfs.ParseHeader(buf)
+ if err != nil {
+ return err
+ }
+ f.header = h
+
+ return nil
+}
+
+// createHeader - create a new random header and write it to disk
+func (f *file) createHeader() error {
+ h := cryptfs.RandomHeader()
+ buf := h.Pack()
+ _, err := f.fd.WriteAt(buf, 0)
+ if err != nil {
+ return err
+ }
+ f.header = h
+
+ return nil
+}
+
func (f *file) String() string {
return fmt.Sprintf("cryptFile(%s)", f.fd.Name())
}
// doRead - returns "length" plaintext bytes from plaintext offset "off".
-// Arguments "length" and "off" do not have to be aligned.
+// Arguments "length" and "off" do not have to be block-aligned.
//
-// doRead reads the corresponding ciphertext blocks from disk, decryptfs them and
+// doRead reads the corresponding ciphertext blocks from disk, decrypts them and
// returns the requested part of the plaintext.
//
-// Called by Read() and by Write() and Truncate() for RMW
+// Called by Read() for normal reading,
+// by Write() and Truncate() for Read-Modify-Write
func (f *file) doRead(off uint64, length uint64) ([]byte, fuse.Status) {
+ // Read file header
+ if f.header == nil {
+ err := f.readHeader()
+ if err == io.EOF {
+ return nil, fuse.OK
+ }
+ if err != nil {
+ return nil, fuse.ToStatus(err)
+ }
+ }
+
// Read the backing ciphertext in one go
alignedOffset, alignedLength, skip := f.cfs.CiphertextRange(off, length)
cryptfs.Debug.Printf("CiphertextRange(%d, %d) -> %d, %d, %d\n", off, length, alignedOffset, alignedLength, skip)
ciphertext := make([]byte, int(alignedLength))
- f.lock.Lock()
+ f.fdLock.Lock()
n, err := f.fd.ReadAt(ciphertext, int64(alignedOffset))
- f.lock.Unlock()
+ f.fdLock.Unlock()
if err != nil && err != io.EOF {
cryptfs.Warn.Printf("read: ReadAt: %s\n", err.Error())
return nil, fuse.ToStatus(err)
@@ -81,14 +141,14 @@ func (f *file) doRead(off uint64, length uint64) ([]byte, fuse.Status) {
// Truncate ciphertext buffer down to actually read bytes
ciphertext = ciphertext[0:n]
- blockNo := alignedOffset / f.cfs.CipherBS()
+ blockNo := (alignedOffset - cryptfs.HEADER_LEN) / f.cfs.CipherBS()
cryptfs.Debug.Printf("ReadAt offset=%d bytes (%d blocks), want=%d, got=%d\n", alignedOffset, blockNo, alignedLength, n)
// Decrypt it
- plaintext, err := f.cfs.DecryptBlocks(ciphertext, blockNo)
+ plaintext, err := f.cfs.DecryptBlocks(ciphertext, blockNo, f.header.Id)
if err != nil {
blockNo := (alignedOffset + uint64(len(plaintext))) / f.cfs.PlainBS()
- cipherOff := blockNo * f.cfs.CipherBS()
+ cipherOff := cryptfs.HEADER_LEN + blockNo * f.cfs.CipherBS()
plainOff := blockNo * f.cfs.PlainBS()
cryptfs.Warn.Printf("ino%d: doRead: corrupt block #%d (plainOff=%d/%d, cipherOff=%d/%d)\n",
f.ino, blockNo, plainOff, f.cfs.PlainBS(), cipherOff, f.cfs.CipherBS())
@@ -103,15 +163,15 @@ func (f *file) doRead(off uint64, length uint64) ([]byte, fuse.Status) {
out = plaintext[skip : skip+int(length)]
} else if lenHave > skip {
out = plaintext[skip:lenHave]
- } else {
- // Out stays empty, file was smaller than the requested offset
}
+ // else: out stays empty, file was smaller than the requested offset
return out, fuse.OK
}
// Read - FUSE call
func (f *file) Read(buf []byte, off int64) (resultData fuse.ReadResult, code fuse.Status) {
+
cryptfs.Debug.Printf("ino%d: FUSE Read: offset=%d length=%d\n", f.ino, len(buf), off)
if f.writeOnly {
@@ -132,8 +192,27 @@ func (f *file) Read(buf []byte, off int64) (resultData fuse.ReadResult, code fus
return fuse.ReadResultData(out), status
}
-// Do the actual write
+// doWrite - encrypt "data" and write it to plaintext offset "off"
+//
+// Arguments do not have to be block-aligned, read-modify-write is
+// performed internally as neccessary
+//
+// Called by Write() for normal writing,
+// and by Truncate() to rewrite the last file block.
func (f *file) doWrite(data []byte, off int64) (uint32, fuse.Status) {
+
+ // Read header from disk, create a new one if the file is empty
+ if f.header == nil {
+ err := f.readHeader()
+ if err == io.EOF {
+ err = f.createHeader()
+
+ }
+ if err != nil {
+ return 0, fuse.ToStatus(err)
+ }
+ }
+
var written uint32
status := fuse.OK
dataBuf := bytes.NewBuffer(data)
@@ -158,14 +237,16 @@ func (f *file) doWrite(data []byte, off int64) (uint32, fuse.Status) {
// Write
blockOffset, _ := b.CiphertextRange()
- blockData = f.cfs.EncryptBlock(blockData, b.BlockNo)
- cryptfs.Debug.Printf("ino%d: Writing %d bytes to block #%d, md5=%s\n", f.ino, len(blockData), b.BlockNo, cryptfs.Debug.Md5sum(blockData))
+ blockData = f.cfs.EncryptBlock(blockData, b.BlockNo, f.header.Id)
+ cryptfs.Debug.Printf("ino%d: Writing %d bytes to block #%d, md5=%s\n",
+ f.ino, len(blockData) - cryptfs.BLOCK_OVERHEAD, b.BlockNo, cryptfs.Debug.Md5sum(blockData))
if len(blockData) != int(f.cfs.CipherBS()) {
- cryptfs.Debug.Printf("ino%d: Writing partial block #%d (%d bytes)\n", f.ino, b.BlockNo, len(blockData))
+ cryptfs.Debug.Printf("ino%d: Writing partial block #%d (%d bytes)\n",
+ f.ino, b.BlockNo, len(blockData) - cryptfs.BLOCK_OVERHEAD)
}
- f.lock.Lock()
+ f.fdLock.Lock()
_, err := f.fd.WriteAt(blockData, int64(blockOffset))
- f.lock.Unlock()
+ f.fdLock.Unlock()
if err != nil {
cryptfs.Warn.Printf("Write failed: %s\n", err.Error())
@@ -199,20 +280,20 @@ func (f *file) Write(data []byte, off int64) (uint32, fuse.Status) {
// Release - FUSE call, forget file
func (f *file) Release() {
- f.lock.Lock()
+ f.fdLock.Lock()
f.fd.Close()
- f.lock.Unlock()
+ f.fdLock.Unlock()
}
// Flush - FUSE call
func (f *file) Flush() fuse.Status {
- f.lock.Lock()
+ f.fdLock.Lock()
// Since Flush() may be called for each dup'd fd, we don't
// want to really close the file, we just want to flush. This
// is achieved by closing a dup'd fd.
newFd, err := syscall.Dup(int(f.fd.Fd()))
- f.lock.Unlock()
+ f.fdLock.Unlock()
if err != nil {
return fuse.ToStatus(err)
@@ -222,14 +303,27 @@ func (f *file) Flush() fuse.Status {
}
func (f *file) Fsync(flags int) (code fuse.Status) {
- f.lock.Lock()
+ f.fdLock.Lock()
r := fuse.ToStatus(syscall.Fsync(int(f.fd.Fd())))
- f.lock.Unlock()
+ f.fdLock.Unlock()
return r
}
func (f *file) Truncate(newSize uint64) fuse.Status {
+ // Common case first: Truncate to zero
+ if newSize == 0 {
+ f.fdLock.Lock()
+ err := syscall.Ftruncate(int(f.fd.Fd()), 0)
+ f.fdLock.Unlock()
+ if err != nil {
+ cryptfs.Warn.Printf("Ftruncate(fd, 0) returned error: %v", err)
+ return fuse.ToStatus(err)
+ }
+ // A truncate to zero kills the file header
+ f.header = nil
+ return fuse.OK
+ }
// We need the old file size to determine if we are growing or shrinking
// the file
@@ -247,6 +341,15 @@ func (f *file) Truncate(newSize uint64) fuse.Status {
// File grows
if newSize > oldSize {
+
+ // File was empty, create new header
+ if oldSize == 0 {
+ err := f.createHeader()
+ if err != nil {
+ return fuse.ToStatus(err)
+ }
+ }
+
blocks := f.cfs.SplitRange(oldSize, newSize-oldSize)
for _, b := range blocks {
// First and last block may be partial
@@ -259,9 +362,9 @@ func (f *file) Truncate(newSize uint64) fuse.Status {
}
} else {
off, length := b.CiphertextRange()
- f.lock.Lock()
+ f.fdLock.Lock()
err := syscall.Ftruncate(int(f.fd.Fd()), int64(off+length))
- f.lock.Unlock()
+ f.fdLock.Unlock()
if err != nil {
cryptfs.Warn.Printf("grow Ftruncate returned error: %v", err)
return fuse.ToStatus(err)
@@ -272,7 +375,7 @@ func (f *file) Truncate(newSize uint64) fuse.Status {
} else {
// File shrinks
blockNo := f.cfs.BlockNoPlainOff(newSize)
- cipherOff := blockNo * f.cfs.CipherBS()
+ cipherOff := cryptfs.HEADER_LEN + blockNo * f.cfs.CipherBS()
plainOff := blockNo * f.cfs.PlainBS()
lastBlockLen := newSize - plainOff
var data []byte
@@ -284,13 +387,15 @@ func (f *file) Truncate(newSize uint64) fuse.Status {
return status
}
}
- f.lock.Lock()
+ // Truncate down to last complete block
+ f.fdLock.Lock()
err = syscall.Ftruncate(int(f.fd.Fd()), int64(cipherOff))
- f.lock.Unlock()
+ f.fdLock.Unlock()
if err != nil {
cryptfs.Warn.Printf("shrink Ftruncate returned error: %v", err)
return fuse.ToStatus(err)
}
+ // Append partial block
if lastBlockLen > 0 {
_, status := f.doWrite(data, int64(plainOff))
return status
@@ -300,17 +405,17 @@ func (f *file) Truncate(newSize uint64) fuse.Status {
}
func (f *file) Chmod(mode uint32) fuse.Status {
- f.lock.Lock()
+ f.fdLock.Lock()
r := fuse.ToStatus(f.fd.Chmod(os.FileMode(mode)))
- f.lock.Unlock()
+ f.fdLock.Unlock()
return r
}
func (f *file) Chown(uid uint32, gid uint32) fuse.Status {
- f.lock.Lock()
+ f.fdLock.Lock()
r := fuse.ToStatus(f.fd.Chown(int(uid), int(gid)))
- f.lock.Unlock()
+ f.fdLock.Unlock()
return r
}
@@ -318,9 +423,9 @@ func (f *file) Chown(uid uint32, gid uint32) fuse.Status {
func (f *file) GetAttr(a *fuse.Attr) fuse.Status {
cryptfs.Debug.Printf("file.GetAttr()\n")
st := syscall.Stat_t{}
- f.lock.Lock()
+ f.fdLock.Lock()
err := syscall.Fstat(int(f.fd.Fd()), &st)
- f.lock.Unlock()
+ f.fdLock.Unlock()
if err != nil {
return fuse.ToStatus(err)
}
@@ -330,7 +435,7 @@ func (f *file) GetAttr(a *fuse.Attr) fuse.Status {
return fuse.OK
}
-// Allocate FUSE call, fallocate(2)
+// Allocate - FUSE call, fallocate(2)
func (f *file) Allocate(off uint64, sz uint64, mode uint32) fuse.Status {
cryptfs.Warn.Printf("Fallocate is not supported, returning ENOSYS - see https://github.com/rfjakob/gocryptfs/issues/1\n")
return fuse.ENOSYS
@@ -354,9 +459,9 @@ func (f *file) Utimens(a *time.Time, m *time.Time) fuse.Status {
ts[1].Sec = m.Unix()
}
- f.lock.Lock()
+ f.fdLock.Lock()
fn := fmt.Sprintf("/proc/self/fd/%d", f.fd.Fd())
err := syscall.UtimesNano(fn, ts)
- f.lock.Unlock()
+ f.fdLock.Unlock()
return fuse.ToStatus(err)
}
diff --git a/pathfs_frontend/fs.go b/pathfs_frontend/fs.go
index ba46309..eebc87b 100644
--- a/pathfs_frontend/fs.go
+++ b/pathfs_frontend/fs.go
@@ -119,7 +119,8 @@ func (fs *FS) Mknod(name string, mode uint32, dev uint32, context *fuse.Context)
}
func (fs *FS) Truncate(path string, offset uint64, context *fuse.Context) (code fuse.Status) {
- return fs.FileSystem.Truncate(fs.EncryptPath(path), offset, context)
+ cryptfs.Warn.Printf("Truncate of a closed file is not supported, returning ENOSYS\n")
+ return fuse.ENOSYS
}
func (fs *FS) Utimens(path string, Atime *time.Time, Mtime *time.Time, context *fuse.Context) (code fuse.Status) {