diff options
Diffstat (limited to 'internal/fusefrontend/file_allocate_truncate.go')
-rw-r--r-- | internal/fusefrontend/file_allocate_truncate.go | 227 |
1 files changed, 0 insertions, 227 deletions
diff --git a/internal/fusefrontend/file_allocate_truncate.go b/internal/fusefrontend/file_allocate_truncate.go deleted file mode 100644 index b6e9150..0000000 --- a/internal/fusefrontend/file_allocate_truncate.go +++ /dev/null @@ -1,227 +0,0 @@ -package fusefrontend - -// FUSE operations Truncate and Allocate on file handles -// i.e. ftruncate and fallocate - -import ( - "log" - "sync" - "syscall" - - "github.com/hanwen/go-fuse/v2/fuse" - - "github.com/rfjakob/gocryptfs/internal/syscallcompat" - "github.com/rfjakob/gocryptfs/internal/tlog" -) - -// FALLOC_DEFAULT is a "normal" fallocate operation -const FALLOC_DEFAULT = 0x00 - -// FALLOC_FL_KEEP_SIZE allocates disk space while not modifying the file size -const FALLOC_FL_KEEP_SIZE = 0x01 - -// Only warn once -var allocateWarnOnce sync.Once - -// Allocate - FUSE call for fallocate(2) -// -// mode=FALLOC_FL_KEEP_SIZE is implemented directly. -// -// mode=FALLOC_DEFAULT is implemented as a two-step process: -// -// (1) Allocate the space using FALLOC_FL_KEEP_SIZE -// (2) Set the file size using ftruncate (via truncateGrowFile) -// -// This allows us to reuse the file grow mechanics from Truncate as they are -// complicated and hard to get right. -// -// Other modes (hole punching, zeroing) are not supported. -func (f *File) Allocate(off uint64, sz uint64, mode uint32) fuse.Status { - if mode != FALLOC_DEFAULT && mode != FALLOC_FL_KEEP_SIZE { - f := func() { - tlog.Info.Printf("fallocate: only mode 0 (default) and 1 (keep size) are supported") - } - allocateWarnOnce.Do(f) - return fuse.Status(syscall.EOPNOTSUPP) - } - - f.fdLock.RLock() - defer f.fdLock.RUnlock() - if f.released { - return fuse.EBADF - } - f.fileTableEntry.ContentLock.Lock() - defer f.fileTableEntry.ContentLock.Unlock() - - blocks := f.contentEnc.ExplodePlainRange(off, sz) - firstBlock := blocks[0] - lastBlock := blocks[len(blocks)-1] - - // Step (1): Allocate the space the user wants using FALLOC_FL_KEEP_SIZE. - // This will fill file holes and/or allocate additional space past the end of - // the file. - cipherOff := firstBlock.BlockCipherOff() - cipherSz := lastBlock.BlockCipherOff() - cipherOff + - f.contentEnc.BlockOverhead() + lastBlock.Skip + lastBlock.Length - err := syscallcompat.Fallocate(f.intFd(), FALLOC_FL_KEEP_SIZE, int64(cipherOff), int64(cipherSz)) - tlog.Debug.Printf("Allocate off=%d sz=%d mode=%x cipherOff=%d cipherSz=%d\n", - off, sz, mode, cipherOff, cipherSz) - if err != nil { - return fuse.ToStatus(err) - } - if mode == FALLOC_FL_KEEP_SIZE { - // The user did not want to change the apparent size. We are done. - return fuse.OK - } - // Step (2): Grow the apparent file size - // We need the old file size to determine if we are growing the file at all. - newPlainSz := off + sz - oldPlainSz, err := f.statPlainSize() - if err != nil { - return fuse.ToStatus(err) - } - if newPlainSz <= oldPlainSz { - // The new size is smaller (or equal). Fallocate with mode = 0 never - // truncates a file, so we are done. - return fuse.OK - } - // The file grows. The space has already been allocated in (1), so what is - // left to do is to pad the first and last block and call truncate. - // truncateGrowFile does just that. - return f.truncateGrowFile(oldPlainSz, newPlainSz) -} - -// 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.qIno.Ino, f.intFd()) - return fuse.EBADF - } - f.fileTableEntry.ContentLock.Lock() - defer f.fileTableEntry.ContentLock.Unlock() - 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.qIno.Ino, f.intFd(), err) - return fuse.ToStatus(err) - } - // Truncate to zero kills the file header - f.fileTableEntry.ID = nil - return fuse.OK - } - // We need the old file size to determine if we are growing or shrinking - // the file - oldSize, err := f.statPlainSize() - if err != nil { - return fuse.ToStatus(err) - } - - 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.qIno.Ino, oldB, newB, oldSize, newSize) - - // File size stays the same - nothing to do - if newSize == oldSize { - return fuse.OK - } - // File grows - if newSize > oldSize { - return f.truncateGrowFile(oldSize, newSize) - } - - // 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(nil, 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 -} - -// statPlainSize stats the file and returns the plaintext size -func (f *File) statPlainSize() (uint64, error) { - fi, err := f.fd.Stat() - if err != nil { - tlog.Warn.Printf("ino%d fh%d: statPlainSize: %v", f.qIno.Ino, f.intFd(), err) - return 0, err - } - cipherSz := uint64(fi.Size()) - plainSz := uint64(f.contentEnc.CipherSizeToPlainSize(cipherSz)) - return plainSz, nil -} - -// truncateGrowFile extends a file using seeking or ftruncate performing RMW on -// the first and last block as necessary. New blocks in the middle become -// file holes unless they have been fallocate()'d beforehand. -func (f *File) truncateGrowFile(oldPlainSz uint64, newPlainSz uint64) fuse.Status { - if newPlainSz <= oldPlainSz { - log.Panicf("BUG: newSize=%d <= oldSize=%d", newPlainSz, oldPlainSz) - } - newEOFOffset := newPlainSz - 1 - if oldPlainSz > 0 { - n1 := f.contentEnc.PlainOffToBlockNo(oldPlainSz - 1) - n2 := f.contentEnc.PlainOffToBlockNo(newEOFOffset) - // The file is grown within one block, no need to pad anything. - // Write a single zero to the last byte and let doWrite figure out the RMW. - if n1 == n2 { - buf := make([]byte, 1) - _, status := f.doWrite(buf, int64(newEOFOffset)) - return status - } - } - // The truncate creates at least one new block. - // - // Make sure the old last block is padded to the block boundary. This call - // is a no-op if it is already block-aligned. - status := f.zeroPad(oldPlainSz) - if !status.Ok() { - return status - } - // The new size is block-aligned. In this case we can do everything ourselves - // and avoid the call to doWrite. - if newPlainSz%f.contentEnc.PlainBS() == 0 { - // The file was empty, so it did not have a header. Create one. - if oldPlainSz == 0 { - id, err := f.createHeader() - if err != nil { - return fuse.ToStatus(err) - } - f.fileTableEntry.ID = id - } - cSz := int64(f.contentEnc.PlainSizeToCipherSize(newPlainSz)) - err := syscall.Ftruncate(f.intFd(), cSz) - if err != nil { - tlog.Warn.Printf("Truncate: grow Ftruncate returned error: %v", err) - } - return fuse.ToStatus(err) - } - // The new size is NOT aligned, so we need to write a partial block. - // Write a single zero to the last byte and let doWrite figure it out. - buf := make([]byte, 1) - _, status = f.doWrite(buf, int64(newEOFOffset)) - return status -} |