Add file header (on-disk-format change)

Format: [ "Version" uint16 big endian ] [ "Id" 16 random bytes ]

Quoting SECURITY.md:

* 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 or copied between different files without
   causing an decryption error.
This commit is contained in:
Jakob Unterwurzacher 2015-11-01 01:32:33 +01:00
parent 73fa8efdb2
commit 76311b60f2
10 changed files with 260 additions and 61 deletions

View File

@ -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.

View File

@ -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

View File

@ -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"

View File

@ -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)
}

View File

@ -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 {

View File

@ -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
}

56
cryptfs/file_header.go Normal file
View File

@ -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
}

View File

@ -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

View File

@ -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)
}

View File

@ -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) {