diff --git a/internal/contentenc/intrablock.go b/internal/contentenc/intrablock.go index ec9562d..9a22ea2 100644 --- a/internal/contentenc/intrablock.go +++ b/internal/contentenc/intrablock.go @@ -4,7 +4,7 @@ package contentenc type intraBlock struct { BlockNo uint64 // Block number in file Skip uint64 // Offset into block plaintext - Length uint64 // Length of data from this block + Length uint64 // Length of plaintext data in this block fs *ContentEnc } diff --git a/internal/contentenc/offsets.go b/internal/contentenc/offsets.go index 256ea26..ebafe9b 100644 --- a/internal/contentenc/offsets.go +++ b/internal/contentenc/offsets.go @@ -76,7 +76,7 @@ func (be *ContentEnc) ExplodePlainRange(offset uint64, length uint64) []intraBlo nextBlock.BlockNo = be.PlainOffToBlockNo(offset) nextBlock.Skip = offset - be.BlockNoToPlainOff(nextBlock.BlockNo) - // Minimum of remaining data and remaining space in the block + // Minimum of remaining plaintext data and remaining space in the block nextBlock.Length = MinUint64(length, be.plainBS-nextBlock.Skip) blocks = append(blocks, nextBlock) diff --git a/internal/fusefrontend/file.go b/internal/fusefrontend/file.go index b5775c2..1835b53 100644 --- a/internal/fusefrontend/file.go +++ b/internal/fusefrontend/file.go @@ -354,103 +354,6 @@ func (f *file) Fsync(flags int) (code fuse.Status) { return fuse.ToStatus(syscall.Fsync(int(f.fd.Fd()))) } -// Truncate - FUSE call -func (f *file) Truncate(newSize uint64) fuse.Status { - f.fdLock.RLock() - defer f.fdLock.RUnlock() - if f.released { - // The file descriptor has been closed concurrently. - tlog.Warn.Printf("ino%d fh%d: Truncate on released file", f.ino, f.intFd()) - return fuse.EBADF - } - wlock.lock(f.ino) - defer wlock.unlock(f.ino) - var err error - // Common case first: Truncate to zero - if newSize == 0 { - err = syscall.Ftruncate(int(f.fd.Fd()), 0) - if err != nil { - tlog.Warn.Printf("ino%d fh%d: Ftruncate(fd, 0) returned error: %v", f.ino, f.intFd(), err) - return fuse.ToStatus(err) - } - // 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 - fi, err := f.fd.Stat() - if err != nil { - tlog.Warn.Printf("ino%d fh%d: Truncate: Fstat failed: %v", f.ino, f.intFd(), err) - return fuse.ToStatus(err) - } - oldSize := f.contentEnc.CipherSizeToPlainSize(uint64(fi.Size())) - { - oldB := float32(oldSize) / float32(f.contentEnc.PlainBS()) - newB := float32(newSize) / float32(f.contentEnc.PlainBS()) - tlog.Debug.Printf("ino%d: FUSE Truncate from %.2f to %.2f blocks (%d to %d bytes)", f.ino, oldB, newB, oldSize, newSize) - } - // File size stays the same - nothing to do - if newSize == oldSize { - return fuse.OK - } - // File grows - if newSize > oldSize { - // File was empty, create new header - if oldSize == 0 { - err = f.createHeader() - if err != nil { - return fuse.ToStatus(err) - } - } - // New blocks to add - addBlocks := f.contentEnc.ExplodePlainRange(oldSize, newSize-oldSize) - if len(addBlocks) >= 2 { - f.zeroPad(oldSize) - } - lastBlock := addBlocks[len(addBlocks)-1] - if lastBlock.IsPartial() { - off := lastBlock.BlockPlainOff() - _, status := f.doWrite(make([]byte, lastBlock.Length), int64(off+lastBlock.Skip)) - return status - } else { - off := lastBlock.BlockCipherOff() - err = syscall.Ftruncate(f.intFd(), int64(off+f.contentEnc.CipherBS())) - if err != nil { - tlog.Warn.Printf("Truncate: grow Ftruncate returned error: %v", err) - } - return fuse.ToStatus(err) - } - } else { - // File shrinks - blockNo := f.contentEnc.PlainOffToBlockNo(newSize) - cipherOff := f.contentEnc.BlockNoToCipherOff(blockNo) - plainOff := f.contentEnc.BlockNoToPlainOff(blockNo) - lastBlockLen := newSize - plainOff - var data []byte - if lastBlockLen > 0 { - var status fuse.Status - data, status = f.doRead(plainOff, lastBlockLen) - if status != fuse.OK { - tlog.Warn.Printf("Truncate: shrink doRead returned error: %v", err) - return status - } - } - // Truncate down to the last complete block - err = syscall.Ftruncate(int(f.fd.Fd()), int64(cipherOff)) - if err != nil { - tlog.Warn.Printf("Truncate: shrink Ftruncate returned error: %v", err) - return fuse.ToStatus(err) - } - // Append partial block - if lastBlockLen > 0 { - _, status := f.doWrite(data, int64(plainOff)) - return status - } - return fuse.OK - } -} - func (f *file) Chmod(mode uint32) fuse.Status { f.fdLock.RLock() defer f.fdLock.RUnlock() @@ -484,19 +387,6 @@ func (f *file) GetAttr(a *fuse.Attr) fuse.Status { return fuse.OK } -// Only warn once -var allocateWarnOnce sync.Once - -// Allocate - FUSE call, fallocate(2) -// This is not implemented yet in gocryptfs, but it is neither in EncFS. This -// suggests that the user demand is low. -func (f *file) Allocate(off uint64, sz uint64, mode uint32) fuse.Status { - allocateWarnOnce.Do(func() { - tlog.Warn.Printf("fallocate(2) is not supported, returning ENOSYS - see https://github.com/rfjakob/gocryptfs/issues/1") - }) - return fuse.ENOSYS -} - const _UTIME_OMIT = ((1 << 30) - 2) func (f *file) Utimens(a *time.Time, m *time.Time) fuse.Status { diff --git a/internal/fusefrontend/file_allocate_truncate.go b/internal/fusefrontend/file_allocate_truncate.go new file mode 100644 index 0000000..5be7df4 --- /dev/null +++ b/internal/fusefrontend/file_allocate_truncate.go @@ -0,0 +1,123 @@ +package fusefrontend + +// FUSE operations Truncate and Allocate on file handles +// i.e. ftruncate and fallocate + +import ( + "sync" + "syscall" + + "github.com/hanwen/go-fuse/fuse" + + "github.com/rfjakob/gocryptfs/internal/tlog" +) + +// Only warn once +var allocateWarnOnce sync.Once + +// Allocate - FUSE call, fallocate(2) +// This is not implemented yet in gocryptfs, but it is neither in EncFS. This +// suggests that the user demand is low. +func (f *file) Allocate(off uint64, sz uint64, mode uint32) fuse.Status { + allocateWarnOnce.Do(func() { + tlog.Warn.Printf("fallocate(2) is not supported, returning ENOSYS - see https://github.com/rfjakob/gocryptfs/issues/1") + }) + return fuse.ENOSYS +} + +// Truncate - FUSE call +func (f *file) Truncate(newSize uint64) fuse.Status { + f.fdLock.RLock() + defer f.fdLock.RUnlock() + if f.released { + // The file descriptor has been closed concurrently. + tlog.Warn.Printf("ino%d fh%d: Truncate on released file", f.ino, f.intFd()) + return fuse.EBADF + } + wlock.lock(f.ino) + defer wlock.unlock(f.ino) + var err error + // Common case first: Truncate to zero + if newSize == 0 { + err = syscall.Ftruncate(int(f.fd.Fd()), 0) + if err != nil { + tlog.Warn.Printf("ino%d fh%d: Ftruncate(fd, 0) returned error: %v", f.ino, f.intFd(), err) + return fuse.ToStatus(err) + } + // 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 + fi, err := f.fd.Stat() + if err != nil { + tlog.Warn.Printf("ino%d fh%d: Truncate: Fstat failed: %v", f.ino, f.intFd(), err) + return fuse.ToStatus(err) + } + oldSize := f.contentEnc.CipherSizeToPlainSize(uint64(fi.Size())) + { + oldB := float32(oldSize) / float32(f.contentEnc.PlainBS()) + newB := float32(newSize) / float32(f.contentEnc.PlainBS()) + tlog.Debug.Printf("ino%d: FUSE Truncate from %.2f to %.2f blocks (%d to %d bytes)", f.ino, oldB, newB, oldSize, newSize) + } + // File size stays the same - nothing to do + if newSize == oldSize { + return fuse.OK + } + // File grows + if newSize > oldSize { + // File was empty, create new header + if oldSize == 0 { + err = f.createHeader() + if err != nil { + return fuse.ToStatus(err) + } + } + // New blocks to add + addBlocks := f.contentEnc.ExplodePlainRange(oldSize, newSize-oldSize) + if len(addBlocks) >= 2 { + f.zeroPad(oldSize) + } + lastBlock := addBlocks[len(addBlocks)-1] + if lastBlock.IsPartial() { + off := lastBlock.BlockPlainOff() + _, status := f.doWrite(make([]byte, lastBlock.Length), int64(off+lastBlock.Skip)) + return status + } else { + off := lastBlock.BlockCipherOff() + err = syscall.Ftruncate(f.intFd(), int64(off+f.contentEnc.CipherBS())) + if err != nil { + tlog.Warn.Printf("Truncate: grow Ftruncate returned error: %v", err) + } + return fuse.ToStatus(err) + } + } else { + // File shrinks + blockNo := f.contentEnc.PlainOffToBlockNo(newSize) + cipherOff := f.contentEnc.BlockNoToCipherOff(blockNo) + plainOff := f.contentEnc.BlockNoToPlainOff(blockNo) + lastBlockLen := newSize - plainOff + var data []byte + if lastBlockLen > 0 { + var status fuse.Status + data, status = f.doRead(plainOff, lastBlockLen) + if status != fuse.OK { + tlog.Warn.Printf("Truncate: shrink doRead returned error: %v", err) + return status + } + } + // Truncate down to the last complete block + err = syscall.Ftruncate(int(f.fd.Fd()), int64(cipherOff)) + if err != nil { + tlog.Warn.Printf("Truncate: shrink Ftruncate returned error: %v", err) + return fuse.ToStatus(err) + } + // Append partial block + if lastBlockLen > 0 { + _, status := f.doWrite(data, int64(plainOff)) + return status + } + return fuse.OK + } +}