<commit_before>package fusefrontend

// FUSE operations on file handles

import (
	"bytes"
	"fmt"
	"io"
	"log"
	"os"
	"sync"
	"syscall"
	"time"

	"github.com/hanwen/go-fuse/fuse"
	"github.com/hanwen/go-fuse/fuse/nodefs"

	"github.com/rfjakob/gocryptfs/internal/contentenc"
	"github.com/rfjakob/gocryptfs/internal/openfiletable"
	"github.com/rfjakob/gocryptfs/internal/serialize_reads"
	"github.com/rfjakob/gocryptfs/internal/stupidgcm"
	"github.com/rfjakob/gocryptfs/internal/syscallcompat"
	"github.com/rfjakob/gocryptfs/internal/tlog"
)

var _ nodefs.File = &File{} // Verify that interface is implemented.

// File - based on loopbackFile in go-fuse/fuse/nodefs/files.go
type File struct {
	fd *os.File
	// Has Release() already been called on this file? This also means that the
	// wlock entry has been freed, so let's not crash trying to access it.
	// Due to concurrency, Release can overtake other operations. These will
	// return EBADF in that case.
	released bool
	// fdLock prevents the fd to be closed while we are in the middle of
	// an operation.
	// Every FUSE entrypoint should RLock(). The only user of Lock() is
	// Release(), which closes the fd and sets "released" to true.
	fdLock sync.RWMutex
	// Content encryption helper
	contentEnc *contentenc.ContentEnc
	// Device and inode number uniquely identify the backing file
	qIno openfiletable.QIno
	// Entry in the open file table
	fileTableEntry *openfiletable.Entry
	// go-fuse nodefs.loopbackFile
	loopbackFile nodefs.File
	// Store where the last byte was written
	lastWrittenOffset int64
	// The opCount is used to judge whether "lastWrittenOffset" is still
	// guaranteed to be correct.
	lastOpCount uint64
	// Parent filesystem
	fs *FS
	// We embed a nodefs.NewDefaultFile() that returns ENOSYS for every operation we
	// have not implemented. This prevents build breakage when the go-fuse library
	// adds new methods to the nodefs.File interface.
	nodefs.File
}

// NewFile returns a new go-fuse File instance.
func NewFile(fd *os.File, fs *FS) (*File, fuse.Status) {
	var st syscall.Stat_t
	err := syscall.Fstat(int(fd.Fd()), &st)
	if err != nil {
		tlog.Warn.Printf("NewFile: Fstat on fd %d failed: %v\n", fd.Fd(), err)
		return nil, fuse.ToStatus(err)
	}
	qi := openfiletable.QInoFromStat(&st)
	e := openfiletable.Register(qi)

	return &File{
		fd:             fd,
		contentEnc:     fs.contentEnc,
		qIno:           qi,
		fileTableEntry: e,
		loopbackFile:   nodefs.NewLoopbackFile(fd),
		fs:             fs,
		File:           nodefs.NewDefaultFile(),
	}, fuse.OK
}

// intFd - return the backing file descriptor as an integer. Used for debug
// messages.
func (f *File) intFd() int {
	return int(f.fd.Fd())
}

// readFileID loads the file header from disk and extracts the file ID.
// Returns io.EOF if the file is empty.
func (f *File) readFileID() ([]byte, error) {
	// We read +1 byte to determine if the file has actual content
	// and not only the header. A header-only file will be considered empty.
	// This makes File ID poisoning more difficult.
	readLen := contentenc.HeaderLen + 1
	buf := make([]byte, readLen)
	n, err := f.fd.ReadAt(buf, 0)
	if err != nil {
		if err == io.EOF && n != 0 {
			tlog.Warn.Printf("readFileID %d: incomplete file, got %d instead of %d bytes",
				f.qIno.Ino, n, readLen)
			f.fs.reportMitigatedCorruption(fmt.Sprint(f.qIno.Ino))
		}
		return nil, err
	}
	buf = buf[:contentenc.HeaderLen]
	h, err := contentenc.ParseHeader(buf)
	if err != nil {
		return nil, err
	}
	return h.ID, nil
}

// createHeader creates a new random header and writes it to disk.
// Returns the new file ID.
// The caller must hold fileIDLock.Lock().
func (f *File) createHeader() (fileID []byte, err error) {
	h := contentenc.RandomHeader()
	buf := h.Pack()
	// Prevent partially written (=corrupt) header by preallocating the space beforehand
	if !f.fs.args.NoPrealloc {
		err = syscallcompat.EnospcPrealloc(int(f.fd.Fd()), 0, contentenc.HeaderLen)
		if err != nil {
			if !syscallcompat.IsENOSPC(err) {
				tlog.Warn.Printf("ino%d: createHeader: prealloc failed: %s\n", f.qIno.Ino, err.Error())
			}
			return nil, err
		}
	}
	// Actually write header
	_, err = f.fd.WriteAt(buf, 0)
	if err != nil {
		return nil, err
	}
	return h.ID, err
}

// doRead - read "length" plaintext bytes from plaintext offset "off" and append
// to "dst".
// Arguments "length" and "off" do not have to be block-aligned.
//
// doRead reads the corresponding ciphertext blocks from disk, decrypts them and
// returns the requested part of the plaintext.
//
// Called by Read() for normal reading,
// by Write() and Truncate() via doWrite() for Read-Modify-Write.
func (f *File) doRead(dst []byte, off uint64, length uint64) ([]byte, fuse.Status) {
	// Get the file ID, either from the open file table, or from disk.
	var fileID []byte
	f.fileTableEntry.IDLock.Lock()
	if f.fileTableEntry.ID != nil {
		// Use the cached value in the file table
		fileID = f.fileTableEntry.ID
	} else {
		// Not cached, we have to read it from disk.
		var err error
		fileID, err = f.readFileID()
		if err != nil {
			f.fileTableEntry.IDLock.Unlock()
			if err == io.EOF {
				// Empty file
				return nil, fuse.OK
			}
			if err != nil {
				tlog.Warn.Printf("doRead %d: corrupt header: %v", f.qIno.Ino, err)
				return nil, fuse.EIO
			}
		}
		// Save into the file table
		f.fileTableEntry.ID = fileID
	}
	f.fileTableEntry.IDLock.Unlock()
	if fileID == nil {
		log.Panicf("fileID=%v", fileID)
	}
	// Read the backing ciphertext in one go
	blocks := f.contentEnc.ExplodePlainRange(off, length)
	alignedOffset, alignedLength := blocks[0].JointCiphertextRange(blocks)
	skip := blocks[0].Skip
	tlog.Debug.Printf("doRead: off=%d len=%d -> off=%d len=%d skip=%d\n",
		off, length, alignedOffset, alignedLength, skip)

	ciphertext := f.fs.contentEnc.CReqPool.Get()
	ciphertext = ciphertext[:int(alignedLength)]
	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)
	}
	// The ReadAt came back empty. We can skip all the decryption and return early.
	if n == 0 {
		f.fs.contentEnc.CReqPool.Put(ciphertext)
		return dst, fuse.OK
	}
	// Truncate ciphertext buffer down to actually read bytes<commit_msg>fusefrontend: Remove unnecessary check in doRead function.<commit_after>package fusefrontend

// FUSE operations on file handles

import (
	"bytes"
	"fmt"
	"io"
	"log"
	"os"
	"sync"
	"syscall"
	"time"

	"github.com/hanwen/go-fuse/fuse"
	"github.com/hanwen/go-fuse/fuse/nodefs"

	"github.com/rfjakob/gocryptfs/internal/contentenc"
	"github.com/rfjakob/gocryptfs/internal/openfiletable"
	"github.com/rfjakob/gocryptfs/internal/serialize_reads"
	"github.com/rfjakob/gocryptfs/internal/stupidgcm"
	"github.com/rfjakob/gocryptfs/internal/syscallcompat"
	"github.com/rfjakob/gocryptfs/internal/tlog"
)

var _ nodefs.File = &File{} // Verify that interface is implemented.

// File - based on loopbackFile in go-fuse/fuse/nodefs/files.go
type File struct {
	fd *os.File
	// Has Release() already been called on this file? This also means that the
	// wlock entry has been freed, so let's not crash trying to access it.
	// Due to concurrency, Release can overtake other operations. These will
	// return EBADF in that case.
	released bool
	// fdLock prevents the fd to be closed while we are in the middle of
	// an operation.
	// Every FUSE entrypoint should RLock(). The only user of Lock() is
	// Release(), which closes the fd and sets "released" to true.
	fdLock sync.RWMutex
	// Content encryption helper
	contentEnc *contentenc.ContentEnc
	// Device and inode number uniquely identify the backing file
	qIno openfiletable.QIno
	// Entry in the open file table
	fileTableEntry *openfiletable.Entry
	// go-fuse nodefs.loopbackFile
	loopbackFile nodefs.File
	// Store where the last byte was written
	lastWrittenOffset int64
	// The opCount is used to judge whether "lastWrittenOffset" is still
	// guaranteed to be correct.
	lastOpCount uint64
	// Parent filesystem
	fs *FS
	// We embed a nodefs.NewDefaultFile() that returns ENOSYS for every operation we
	// have not implemented. This prevents build breakage when the go-fuse library
	// adds new methods to the nodefs.File interface.
	nodefs.File
}

// NewFile returns a new go-fuse File instance.
func NewFile(fd *os.File, fs *FS) (*File, fuse.Status) {
	var st syscall.Stat_t
	err := syscall.Fstat(int(fd.Fd()), &st)
	if err != nil {
		tlog.Warn.Printf("NewFile: Fstat on fd %d failed: %v\n", fd.Fd(), err)
		return nil, fuse.ToStatus(err)
	}
	qi := openfiletable.QInoFromStat(&st)
	e := openfiletable.Register(qi)

	return &File{
		fd:             fd,
		contentEnc:     fs.contentEnc,
		qIno:           qi,
		fileTableEntry: e,
		loopbackFile:   nodefs.NewLoopbackFile(fd),
		fs:             fs,
		File:           nodefs.NewDefaultFile(),
	}, fuse.OK
}

// intFd - return the backing file descriptor as an integer. Used for debug
// messages.
func (f *File) intFd() int {
	return int(f.fd.Fd())
}

// readFileID loads the file header from disk and extracts the file ID.
// Returns io.EOF if the file is empty.
func (f *File) readFileID() ([]byte, error) {
	// We read +1 byte to determine if the file has actual content
	// and not only the header. A header-only file will be considered empty.
	// This makes File ID poisoning more difficult.
	readLen := contentenc.HeaderLen + 1
	buf := make([]byte, readLen)
	n, err := f.fd.ReadAt(buf, 0)
	if err != nil {
		if err == io.EOF && n != 0 {
			tlog.Warn.Printf("readFileID %d: incomplete file, got %d instead of %d bytes",
				f.qIno.Ino, n, readLen)
			f.fs.reportMitigatedCorruption(fmt.Sprint(f.qIno.Ino))
		}
		return nil, err
	}
	buf = buf[:contentenc.HeaderLen]
	h, err := contentenc.ParseHeader(buf)
	if err != nil {
		return nil, err
	}
	return h.ID, nil
}

// createHeader creates a new random header and writes it to disk.
// Returns the new file ID.
// The caller must hold fileIDLock.Lock().
func (f *File) createHeader() (fileID []byte, err error) {
	h := contentenc.RandomHeader()
	buf := h.Pack()
	// Prevent partially written (=corrupt) header by preallocating the space beforehand
	if !f.fs.args.NoPrealloc {
		err = syscallcompat.EnospcPrealloc(int(f.fd.Fd()), 0, contentenc.HeaderLen)
		if err != nil {
			if !syscallcompat.IsENOSPC(err) {
				tlog.Warn.Printf("ino%d: createHeader: prealloc failed: %s\n", f.qIno.Ino, err.Error())
			}
			return nil, err
		}
	}
	// Actually write header
	_, err = f.fd.WriteAt(buf, 0)
	if err != nil {
		return nil, err
	}
	return h.ID, err
}

// doRead - read "length" plaintext bytes from plaintext offset "off" and append
// to "dst".
// Arguments "length" and "off" do not have to be block-aligned.
//
// doRead reads the corresponding ciphertext blocks from disk, decrypts them and
// returns the requested part of the plaintext.
//
// Called by Read() for normal reading,
// by Write() and Truncate() via doWrite() for Read-Modify-Write.
func (f *File) doRead(dst []byte, off uint64, length uint64) ([]byte, fuse.Status) {
	// Get the file ID, either from the open file table, or from disk.
	var fileID []byte
	f.fileTableEntry.IDLock.Lock()
	if f.fileTableEntry.ID != nil {
		// Use the cached value in the file table
		fileID = f.fileTableEntry.ID
	} else {
		// Not cached, we have to read it from disk.
		var err error
		fileID, err = f.readFileID()
		if err != nil {
			f.fileTableEntry.IDLock.Unlock()
			if err == io.EOF {
				// Empty file
				return nil, fuse.OK
			}
			tlog.Warn.Printf("doRead %d: corrupt header: %v", f.qIno.Ino, err)
			return nil, fuse.EIO
		}
		// Save into the file table
		f.fileTableEntry.ID = fileID
	}
	f.fileTableEntry.IDLock.Unlock()
	if fileID == nil {
		log.Panicf("fileID=%v", fileID)
	}
	// Read the backing ciphertext in one go
	blocks := f.contentEnc.ExplodePlainRange(off, length)
	alignedOffset, alignedLength := blocks[0].JointCiphertextRange(blocks)
	skip := blocks[0].Skip
	tlog.Debug.Printf("doRead: off=%d len=%d -> off=%d len=%d skip=%d\n",
		off, length, alignedOffset, alignedLength, skip)

	ciphertext := f.fs.contentEnc.CReqPool.Get()
	ciphertext = ciphertext[:int(alignedLength)]
	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)
	}
	// The ReadAt came back empty. We can skip all the decryption and return early.
	if n == 0 {
		f.fs.contentEnc.CReqPool.Put(ciphertext)
		return dst, fuse.OK
	}
	// Truncate ciphertext buffer down to actually read bytes
{'fraction_non_alphanumeric': 0.12258526114953494, 'fraction_numerical': 0.0030209078623101996, 'mean_word_length': 3.4202389318341533, 'pattern_counts': {'":': 0, '<': 3, '<?xml version=': 0, '>': 5, 'https://': 0, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '944032', 'n_tokens_mistral': 4602, 'n_tokens_neox': 4352, 'n_words': 1396}