diff --git a/internal/fusefrontend/file2.go b/internal/fusefrontend/file2.go index 7fe3d3a..0de325c 100644 --- a/internal/fusefrontend/file2.go +++ b/internal/fusefrontend/file2.go @@ -25,6 +25,7 @@ import ( "github.com/rfjakob/gocryptfs/internal/tlog" ) +// File2 implements the go-fuse v2 API (github.com/hanwen/go-fuse/v2/fs) type File2 struct { fd *os.File // Has Release() already been called on this file? This also means that the @@ -129,7 +130,7 @@ func (f *File2) createHeader() (fileID []byte, err error) { // // Called by Read() for normal reading, // by Write() and Truncate() via doWrite() for Read-Modify-Write. -func (f *File2) doRead(dst []byte, off uint64, length uint64) ([]byte, fuse.Status) { +func (f *File2) doRead(dst []byte, off uint64, length uint64) ([]byte, syscall.Errno) { // Get the file ID, either from the open file table, or from disk. var fileID []byte f.fileTableEntry.IDLock.Lock() @@ -144,7 +145,7 @@ func (f *File2) doRead(dst []byte, off uint64, length uint64) ([]byte, fuse.Stat f.fileTableEntry.IDLock.Unlock() if err == io.EOF { // Empty file - return nil, fuse.OK + return nil, 0 } buf := make([]byte, 100) n, _ := f.fd.ReadAt(buf, 0) @@ -152,7 +153,7 @@ func (f *File2) doRead(dst []byte, off uint64, length uint64) ([]byte, fuse.Stat hexdump := hex.EncodeToString(buf) tlog.Warn.Printf("doRead %d: corrupt header: %v\nFile hexdump (%d bytes): %s", f.qIno.Ino, err, n, hexdump) - return nil, fuse.EIO + return nil, syscall.EIO } // Save into the file table f.fileTableEntry.ID = fileID @@ -173,12 +174,12 @@ func (f *File2) doRead(dst []byte, off uint64, length uint64) ([]byte, fuse.Stat n, err := f.fd.ReadAt(ciphertext, int64(alignedOffset)) if err != nil && err != io.EOF { tlog.Warn.Printf("read: ReadAt: %s", err.Error()) - return nil, fuse.ToStatus(err) + return nil, fs.ToErrno(err) } // The ReadAt came back empty. We can skip all the decryption and return early. if n == 0 { f.rootNode.contentEnc.CReqPool.Put(ciphertext) - return dst, fuse.OK + return dst, 0 } // Truncate ciphertext buffer down to actually read bytes ciphertext = ciphertext[0:n] @@ -198,7 +199,7 @@ func (f *File2) doRead(dst []byte, off uint64, length uint64) ([]byte, fuse.Stat } else { curruptBlockNo := firstBlockNo + f.contentEnc.PlainOffToBlockNo(uint64(len(plaintext))) tlog.Warn.Printf("doRead %d: corrupt block #%d: %v", f.qIno.Ino, curruptBlockNo, err) - return nil, fuse.EIO + return nil, syscall.EIO } } @@ -216,15 +217,15 @@ func (f *File2) doRead(dst []byte, off uint64, length uint64) ([]byte, fuse.Stat out = append(dst, out...) f.rootNode.contentEnc.PReqPool.Put(plaintext) - return out, fuse.OK + return out, 0 } // Read - FUSE call -func (f *File2) Read(buf []byte, off int64) (resultData fuse.ReadResult, code fuse.Status) { +func (f *File2) Read(ctx context.Context, buf []byte, off int64) (resultData fuse.ReadResult, errno syscall.Errno) { if len(buf) > fuse.MAX_KERNEL_WRITE { // This would crash us due to our fixed-size buffer pool tlog.Warn.Printf("Read: rejecting oversized request with EMSGSIZE, len=%d", len(buf)) - return nil, fuse.Status(syscall.EMSGSIZE) + return nil, syscall.EMSGSIZE } f.fdLock.RLock() defer f.fdLock.RUnlock() @@ -236,15 +237,15 @@ func (f *File2) Read(buf []byte, off int64) (resultData fuse.ReadResult, code fu if f.rootNode.args.SerializeReads { serialize_reads.Wait(off, len(buf)) } - out, status := f.doRead(buf[:0], uint64(off), uint64(len(buf))) + out, errno := f.doRead(buf[:0], uint64(off), uint64(len(buf))) if f.rootNode.args.SerializeReads { serialize_reads.Done() } - if status != fuse.OK { - return nil, status + if errno != 0 { + return nil, errno } - tlog.Debug.Printf("ino%d: Read: status %v, returning %d bytes", f.qIno.Ino, status, len(out)) - return fuse.ReadResultData(out), status + tlog.Debug.Printf("ino%d: Read: errno=%d, returning %d bytes", f.qIno.Ino, errno, len(out)) + return fuse.ReadResultData(out), errno } // doWrite - encrypt "data" and write it to plaintext offset "off" @@ -256,7 +257,7 @@ func (f *File2) Read(buf []byte, off int64) (resultData fuse.ReadResult, code fu // and by Truncate() to rewrite the last file block. // // Empty writes do nothing and are allowed. -func (f *File2) doWrite(data []byte, off int64) (uint32, fuse.Status) { +func (f *File2) doWrite(data []byte, off int64) (uint32, syscall.Errno) { fileWasEmpty := false // Get the file ID, create a new one if it does not exist yet. var fileID []byte @@ -274,7 +275,7 @@ func (f *File2) doWrite(data []byte, off int64) (uint32, fuse.Status) { fileWasEmpty = true } if err != nil { - return 0, fuse.ToStatus(err) + return 0, fs.ToErrno(err) } f.fileTableEntry.ID = fileID } @@ -287,10 +288,10 @@ func (f *File2) doWrite(data []byte, off int64) (uint32, fuse.Status) { // Incomplete block -> Read-Modify-Write if b.IsPartial() { // Read - oldData, status := f.doRead(nil, b.BlockPlainOff(), f.contentEnc.PlainBS()) - if status != fuse.OK { - tlog.Warn.Printf("ino%d fh%d: RMW read failed: %s", f.qIno.Ino, f.intFd(), status.String()) - return 0, status + oldData, errno := f.doRead(nil, b.BlockPlainOff(), f.contentEnc.PlainBS()) + if errno != 0 { + tlog.Warn.Printf("ino%d fh%d: RMW read failed: errno=%d", f.qIno.Ino, f.intFd(), errno) + return 0, errno } // Modify blockData = f.contentEnc.MergeBlocks(oldData, blockData, int(b.Skip)) @@ -321,7 +322,7 @@ func (f *File2) doWrite(data []byte, off int64) (uint32, fuse.Status) { tlog.Warn.Printf("ino%d fh%d: doWrite: rollback failed: %v", f.qIno.Ino, f.intFd(), err2) } } - return 0, fuse.ToStatus(err) + return 0, fs.ToErrno(err) } } // Write @@ -331,9 +332,9 @@ func (f *File2) doWrite(data []byte, off int64) (uint32, fuse.Status) { if err != nil { tlog.Warn.Printf("ino%d fh%d: doWrite: WriteAt off=%d len=%d failed: %v", f.qIno.Ino, f.intFd(), cOff, len(ciphertext), err) - return 0, fuse.ToStatus(err) + return 0, fs.ToErrno(err) } - return uint32(len(data)), fuse.OK + return uint32(len(data)), 0 } // isConsecutiveWrite returns true if the current write @@ -349,18 +350,18 @@ func (f *File2) isConsecutiveWrite(off int64) bool { // Write - FUSE call // // If the write creates a hole, pads the file to the next block boundary. -func (f *File2) Write(data []byte, off int64) (uint32, fuse.Status) { +func (f *File2) Write(ctx context.Context, data []byte, off int64) (uint32, syscall.Errno) { if len(data) > fuse.MAX_KERNEL_WRITE { // This would crash us due to our fixed-size buffer pool tlog.Warn.Printf("Write: rejecting oversized request with EMSGSIZE, len=%d", len(data)) - return 0, fuse.Status(syscall.EMSGSIZE) + return 0, syscall.EMSGSIZE } f.fdLock.RLock() defer f.fdLock.RUnlock() if f.released { // The file descriptor has been closed concurrently tlog.Warn.Printf("ino%d fh%d: Write on released file", f.qIno.Ino, f.intFd()) - return 0, fuse.EBADF + return 0, syscall.EBADF } f.fileTableEntry.ContentLock.Lock() defer f.fileTableEntry.ContentLock.Unlock() @@ -369,21 +370,21 @@ func (f *File2) Write(data []byte, off int64) (uint32, fuse.Status) { // But if the write directly follows an earlier write, it cannot create a // hole, and we can save one Stat() call. if !f.isConsecutiveWrite(off) { - status := f.writePadHole(off) - if !status.Ok() { - return 0, status + errno := f.writePadHole(off) + if errno != 0 { + return 0, errno } } - n, status := f.doWrite(data, off) - if status.Ok() { + n, errno := f.doWrite(data, off) + if errno != 0 { f.lastOpCount = openfiletable.WriteOpCount() f.lastWrittenOffset = off + int64(len(data)) - 1 } - return n, status + return n, errno } // Release - FUSE call, close file -func (f *File2) Release() { +func (f *File2) Release(ctx context.Context) syscall.Errno { f.fdLock.Lock() if f.released { log.Panicf("ino%d fh%d: double release", f.qIno.Ino, f.intFd()) @@ -392,10 +393,11 @@ func (f *File2) Release() { openfiletable.Unregister(f.qIno) f.fd.Close() f.fdLock.Unlock() + return 0 } // Flush - FUSE call -func (f *File2) Flush() fuse.Status { +func (f *File2) Flush(ctx context.Context) syscall.Errno { f.fdLock.RLock() defer f.fdLock.RUnlock() @@ -405,18 +407,18 @@ func (f *File2) Flush() fuse.Status { newFd, err := syscall.Dup(f.intFd()) if err != nil { - return fuse.ToStatus(err) + return fs.ToErrno(err) } err = syscall.Close(newFd) - return fuse.ToStatus(err) + return fs.ToErrno(err) } // Fsync FUSE call -func (f *File2) Fsync(flags int) (code fuse.Status) { +func (f *File2) Fsync(ctx context.Context, flags uint32) (errno syscall.Errno) { f.fdLock.RLock() defer f.fdLock.RUnlock() - return fuse.ToStatus(syscall.Fsync(f.intFd())) + return fs.ToErrno(syscall.Fsync(f.intFd())) } // Getattr FUSE call (like stat) diff --git a/internal/fusefrontend/file2_allocate_truncate.go b/internal/fusefrontend/file2_allocate_truncate.go index 9a3d7d1..9413b90 100644 --- a/internal/fusefrontend/file2_allocate_truncate.go +++ b/internal/fusefrontend/file2_allocate_truncate.go @@ -4,10 +4,11 @@ package fusefrontend // i.e. ftruncate and fallocate import ( + "context" "log" "syscall" - "github.com/hanwen/go-fuse/v2/fuse" + "github.com/hanwen/go-fuse/v2/fs" "github.com/rfjakob/gocryptfs/internal/syscallcompat" "github.com/rfjakob/gocryptfs/internal/tlog" @@ -26,19 +27,19 @@ import ( // complicated and hard to get right. // // Other modes (hole punching, zeroing) are not supported. -func (f *File2) Allocate(off uint64, sz uint64, mode uint32) fuse.Status { +func (f *File2) Allocate(ctx context.Context, off uint64, sz uint64, mode uint32) syscall.Errno { 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) + return syscall.EOPNOTSUPP } f.fdLock.RLock() defer f.fdLock.RUnlock() if f.released { - return fuse.EBADF + return syscall.EBADF } f.fileTableEntry.ContentLock.Lock() defer f.fileTableEntry.ContentLock.Unlock() @@ -57,23 +58,23 @@ func (f *File2) Allocate(off uint64, sz uint64, mode uint32) fuse.Status { 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) + return fs.ToErrno(err) } if mode == FALLOC_FL_KEEP_SIZE { // The user did not want to change the apparent size. We are done. - return fuse.OK + return 0 } // 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) + return fs.ToErrno(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 + return 0 } // 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. @@ -82,13 +83,13 @@ func (f *File2) Allocate(off uint64, sz uint64, mode uint32) fuse.Status { } // truncate - called from Setattr. -func (f *File2) truncate(newSize uint64) fuse.Status { +func (f *File2) truncate(newSize uint64) (errno syscall.Errno) { 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 + return syscall.EBADF } f.fileTableEntry.ContentLock.Lock() defer f.fileTableEntry.ContentLock.Unlock() @@ -98,17 +99,17 @@ func (f *File2) truncate(newSize uint64) fuse.Status { 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) + return fs.ToErrno(err) } // Truncate to zero kills the file header f.fileTableEntry.ID = nil - return fuse.OK + return 0 } // 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) + return fs.ToErrno(err) } oldB := float32(oldSize) / float32(f.contentEnc.PlainBS()) @@ -117,7 +118,7 @@ func (f *File2) truncate(newSize uint64) fuse.Status { // File size stays the same - nothing to do if newSize == oldSize { - return fuse.OK + return 0 } // File grows if newSize > oldSize { @@ -131,25 +132,24 @@ func (f *File2) truncate(newSize uint64) fuse.Status { lastBlockLen := newSize - plainOff var data []byte if lastBlockLen > 0 { - var status fuse.Status - data, status = f.doRead(nil, plainOff, lastBlockLen) - if status != fuse.OK { + data, errno = f.doRead(nil, plainOff, lastBlockLen) + if errno != 0 { tlog.Warn.Printf("Truncate: shrink doRead returned error: %v", err) - return status + return errno } } // 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) + return fs.ToErrno(err) } // Append partial block if lastBlockLen > 0 { _, status := f.doWrite(data, int64(plainOff)) return status } - return fuse.OK + return 0 } // statPlainSize stats the file and returns the plaintext size @@ -167,7 +167,7 @@ func (f *File2) statPlainSize() (uint64, error) { // 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 *File2) truncateGrowFile(oldPlainSz uint64, newPlainSz uint64) fuse.Status { +func (f *File2) truncateGrowFile(oldPlainSz uint64, newPlainSz uint64) syscall.Errno { if newPlainSz <= oldPlainSz { log.Panicf("BUG: newSize=%d <= oldSize=%d", newPlainSz, oldPlainSz) } @@ -179,17 +179,17 @@ func (f *File2) truncateGrowFile(oldPlainSz uint64, newPlainSz uint64) fuse.Stat // 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 + _, errno := f.doWrite(buf, int64(newEOFOffset)) + return errno } } // 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 + errno := f.zeroPad(oldPlainSz) + if errno != 0 { + return errno } // The new size is block-aligned. In this case we can do everything ourselves // and avoid the call to doWrite. @@ -198,7 +198,7 @@ func (f *File2) truncateGrowFile(oldPlainSz uint64, newPlainSz uint64) fuse.Stat if oldPlainSz == 0 { id, err := f.createHeader() if err != nil { - return fuse.ToStatus(err) + return fs.ToErrno(err) } f.fileTableEntry.ID = id } @@ -207,11 +207,11 @@ func (f *File2) truncateGrowFile(oldPlainSz uint64, newPlainSz uint64) fuse.Stat if err != nil { tlog.Warn.Printf("Truncate: grow Ftruncate returned error: %v", err) } - return fuse.ToStatus(err) + return fs.ToErrno(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 + _, errno = f.doWrite(buf, int64(newEOFOffset)) + return errno } diff --git a/internal/fusefrontend/file2_api_check.go b/internal/fusefrontend/file2_api_check.go index 3f2c595..4a6d6a1 100644 --- a/internal/fusefrontend/file2_api_check.go +++ b/internal/fusefrontend/file2_api_check.go @@ -7,17 +7,17 @@ import ( // Check that we have implemented the fs.File* interfaces var _ = (fs.FileGetattrer)((*File2)(nil)) var _ = (fs.FileSetattrer)((*File2)(nil)) - -/* TODO -var _ = (fs.FileHandle)((*File2)(nil)) var _ = (fs.FileReleaser)((*File2)(nil)) var _ = (fs.FileReader)((*File2)(nil)) var _ = (fs.FileWriter)((*File2)(nil)) +var _ = (fs.FileFsyncer)((*File2)(nil)) +var _ = (fs.FileFlusher)((*File2)(nil)) +var _ = (fs.FileAllocater)((*File2)(nil)) + +/* TODO +var _ = (fs.FileHandle)((*File2)(nil)) var _ = (fs.FileGetlker)((*File2)(nil)) var _ = (fs.FileSetlker)((*File2)(nil)) var _ = (fs.FileSetlkwer)((*File2)(nil)) var _ = (fs.FileLseeker)((*File2)(nil)) -var _ = (fs.FileFlusher)((*File2)(nil)) -var _ = (fs.FileFsyncer)((*File2)(nil)) -var _ = (fs.FileAllocater)((*File2)(nil)) */ diff --git a/internal/fusefrontend/file2_holes.go b/internal/fusefrontend/file2_holes.go index 5e06981..83918d2 100644 --- a/internal/fusefrontend/file2_holes.go +++ b/internal/fusefrontend/file2_holes.go @@ -6,19 +6,19 @@ import ( "runtime" "syscall" - "github.com/hanwen/go-fuse/v2/fuse" + "github.com/hanwen/go-fuse/v2/fs" "github.com/rfjakob/gocryptfs/internal/tlog" ) // Will a write to plaintext offset "targetOff" create a file hole in the // ciphertext? If yes, zero-pad the last ciphertext block. -func (f *File2) writePadHole(targetOff int64) fuse.Status { +func (f *File2) writePadHole(targetOff int64) syscall.Errno { // Get the current file size. fi, err := f.fd.Stat() if err != nil { tlog.Warn.Printf("checkAndPadHole: Fstat failed: %v", err) - return fuse.ToStatus(err) + return fs.ToErrno(err) } plainSize := f.contentEnc.CipherSizeToPlainSize(uint64(fi.Size())) // Appending a single byte to the file (equivalent to writing to @@ -29,31 +29,31 @@ func (f *File2) writePadHole(targetOff int64) fuse.Status { // The write goes into an existing block or (if the last block was full) // starts a new one directly after the last block. Nothing to do. if targetBlock <= nextBlock { - return fuse.OK + return 0 } // The write goes past the next block. nextBlock has // to be zero-padded to the block boundary and (at least) nextBlock+1 // will contain a file hole in the ciphertext. - status := f.zeroPad(plainSize) - if status != fuse.OK { - return status + errno := f.zeroPad(plainSize) + if errno != 0 { + return errno } - return fuse.OK + return 0 } // Zero-pad the file of size plainSize to the next block boundary. This is a no-op // if the file is already block-aligned. -func (f *File2) zeroPad(plainSize uint64) fuse.Status { +func (f *File2) zeroPad(plainSize uint64) syscall.Errno { lastBlockLen := plainSize % f.contentEnc.PlainBS() if lastBlockLen == 0 { // Already block-aligned - return fuse.OK + return 0 } missing := f.contentEnc.PlainBS() - lastBlockLen pad := make([]byte, missing) tlog.Debug.Printf("zeroPad: Writing %d bytes\n", missing) - _, status := f.doWrite(pad, int64(plainSize)) - return status + _, errno := f.doWrite(pad, int64(plainSize)) + return errno } // SeekData calls the lseek syscall with SEEK_DATA. It returns the offset of the diff --git a/internal/fusefrontend/node.go b/internal/fusefrontend/node.go index ee2d8d9..6cdc552 100644 --- a/internal/fusefrontend/node.go +++ b/internal/fusefrontend/node.go @@ -286,7 +286,7 @@ func (n *Node) Setattr(ctx context.Context, f fs.FileHandle, in *fuse.SetAttrIn, return errno } f2 = f.(*File2) - defer f2.Release() + defer f2.Release(ctx) } return f2.Setattr(ctx, in, out) }