diff options
Diffstat (limited to 'pathfs_frontend')
-rw-r--r-- | pathfs_frontend/args.go | 12 | ||||
-rw-r--r-- | pathfs_frontend/compat_darwin.go | 12 | ||||
-rw-r--r-- | pathfs_frontend/compat_linux.go | 18 | ||||
-rw-r--r-- | pathfs_frontend/file.go | 501 | ||||
-rw-r--r-- | pathfs_frontend/file_holes.go | 29 | ||||
-rw-r--r-- | pathfs_frontend/fs.go | 381 | ||||
-rw-r--r-- | pathfs_frontend/fs_dir.go | 157 | ||||
-rw-r--r-- | pathfs_frontend/names.go | 52 | ||||
-rw-r--r-- | pathfs_frontend/write_lock.go | 66 |
9 files changed, 0 insertions, 1228 deletions
diff --git a/pathfs_frontend/args.go b/pathfs_frontend/args.go deleted file mode 100644 index 91f9ba7..0000000 --- a/pathfs_frontend/args.go +++ /dev/null @@ -1,12 +0,0 @@ -package pathfs_frontend - -// Container for arguments that are passed from main() to pathfs_frontend -type Args struct { - Masterkey []byte - Cipherdir string - OpenSSL bool - PlaintextNames bool - DirIV bool - EMENames bool - GCMIV128 bool -} diff --git a/pathfs_frontend/compat_darwin.go b/pathfs_frontend/compat_darwin.go deleted file mode 100644 index 3ddb50d..0000000 --- a/pathfs_frontend/compat_darwin.go +++ /dev/null @@ -1,12 +0,0 @@ -package pathfs_frontend - -// prealloc - preallocate space without changing the file size. This prevents -// us from running out of space in the middle of an operation. -func prealloc(fd int, off int64, len int64) (err error) { - // - // Sorry, fallocate is not available on OSX at all and - // fcntl F_PREALLOCATE is not accessible from Go. - // - // See https://github.com/rfjakob/gocryptfs/issues/18 if you want to help. - return nil -} diff --git a/pathfs_frontend/compat_linux.go b/pathfs_frontend/compat_linux.go deleted file mode 100644 index 7ed3c74..0000000 --- a/pathfs_frontend/compat_linux.go +++ /dev/null @@ -1,18 +0,0 @@ -package pathfs_frontend - -import "syscall" - -// prealloc - preallocate space without changing the file size. This prevents -// us from running out of space in the middle of an operation. -func prealloc(fd int, off int64, len int64) (err error) { - for { - err = syscall.Fallocate(fd, FALLOC_FL_KEEP_SIZE, off, len) - if err == syscall.EINTR { - // fallocate, like many syscalls, can return EINTR. This is not an - // error and just signifies that the operation was interrupted by a - // signal and we should try again. - continue - } - return err - } -} diff --git a/pathfs_frontend/file.go b/pathfs_frontend/file.go deleted file mode 100644 index 387eb35..0000000 --- a/pathfs_frontend/file.go +++ /dev/null @@ -1,501 +0,0 @@ -package pathfs_frontend - -// FUSE operations on file handles - -import ( - "bytes" - "fmt" - "io" - "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/toggledlog" -) - -// File - based on loopbackFile in go-fuse/fuse/nodefs/files.go -type file struct { - fd *os.File - - // 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, hence has to acquire an exclusive lock. - fdLock sync.RWMutex - - // Was the file opened O_WRONLY? - writeOnly bool - - // Content encryption helper - contentEnc *contentenc.ContentEnc - - // Inode number - ino uint64 - - // File header - header *contentenc.FileHeader - - forgotten bool -} - -func NewFile(fd *os.File, writeOnly bool, contentEnc *contentenc.ContentEnc) nodefs.File { - var st syscall.Stat_t - syscall.Fstat(int(fd.Fd()), &st) - wlock.register(st.Ino) - - return &file{ - fd: fd, - writeOnly: writeOnly, - contentEnc: contentEnc, - ino: st.Ino, - } -} - -// intFd - return the backing file descriptor as an integer. Used for debug -// messages. -func (f *file) intFd() int { - return int(f.fd.Fd()) -} - -func (f *file) InnerFile() nodefs.File { - return nil -} - -func (f *file) SetInode(n *nodefs.Inode) { -} - -// readHeader - load the file header from disk -// -// Returns io.EOF if the file is empty -func (f *file) readHeader() error { - buf := make([]byte, contentenc.HEADER_LEN) - _, err := f.fd.ReadAt(buf, 0) - if err != nil { - return err - } - h, err := contentenc.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 := contentenc.RandomHeader() - buf := h.Pack() - - // Prevent partially written (=corrupt) header by preallocating the space beforehand - err := prealloc(int(f.fd.Fd()), 0, contentenc.HEADER_LEN) - if err != nil { - toggledlog.Warn.Printf("ino%d: createHeader: prealloc failed: %s\n", f.ino, err.Error()) - return err - } - - // Actually write header - _, 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 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() 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 - blocks := f.contentEnc.ExplodePlainRange(off, length) - alignedOffset, alignedLength := blocks[0].JointCiphertextRange(blocks) - skip := blocks[0].Skip - toggledlog.Debug.Printf("JointCiphertextRange(%d, %d) -> %d, %d, %d", off, length, alignedOffset, alignedLength, skip) - ciphertext := make([]byte, int(alignedLength)) - n, err := f.fd.ReadAt(ciphertext, int64(alignedOffset)) - if err != nil && err != io.EOF { - toggledlog.Warn.Printf("read: ReadAt: %s", err.Error()) - return nil, fuse.ToStatus(err) - } - // Truncate ciphertext buffer down to actually read bytes - ciphertext = ciphertext[0:n] - - firstBlockNo := blocks[0].BlockNo - toggledlog.Debug.Printf("ReadAt offset=%d bytes (%d blocks), want=%d, got=%d", alignedOffset, firstBlockNo, alignedLength, n) - - // Decrypt it - plaintext, err := f.contentEnc.DecryptBlocks(ciphertext, firstBlockNo, f.header.Id) - if err != nil { - curruptBlockNo := firstBlockNo + f.contentEnc.PlainOffToBlockNo(uint64(len(plaintext))) - cipherOff := f.contentEnc.BlockNoToCipherOff(curruptBlockNo) - plainOff := f.contentEnc.BlockNoToPlainOff(curruptBlockNo) - toggledlog.Warn.Printf("ino%d: doRead: corrupt block #%d (plainOff=%d, cipherOff=%d)", - f.ino, curruptBlockNo, plainOff, cipherOff) - return nil, fuse.EIO - } - - // Crop down to the relevant part - var out []byte - lenHave := len(plaintext) - lenWant := int(skip + length) - if lenHave > lenWant { - out = plaintext[skip:lenWant] - } else if lenHave > int(skip) { - out = plaintext[skip:lenHave] - } - // 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) { - f.fdLock.RLock() - defer f.fdLock.RUnlock() - - toggledlog.Debug.Printf("ino%d: FUSE Read: offset=%d length=%d", f.ino, len(buf), off) - - if f.writeOnly { - toggledlog.Warn.Printf("ino%d: Tried to read from write-only file", f.ino) - return nil, fuse.EBADF - } - - out, status := f.doRead(uint64(off), uint64(len(buf))) - - if status == fuse.EIO { - toggledlog.Warn.Printf("ino%d: Read failed with EIO, offset=%d, length=%d", f.ino, len(buf), off) - } - if status != fuse.OK { - return nil, status - } - - toggledlog.Debug.Printf("ino%d: Read: status %v, returning %d bytes", f.ino, status, len(out)) - return fuse.ReadResultData(out), status -} - -const FALLOC_FL_KEEP_SIZE = 0x01 - -// 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) - blocks := f.contentEnc.ExplodePlainRange(uint64(off), uint64(len(data))) - for _, b := range blocks { - - blockData := dataBuf.Next(int(b.Length)) - - // Incomplete block -> Read-Modify-Write - if b.IsPartial() { - // Read - o, _ := b.PlaintextRange() - oldData, status := f.doRead(o, f.contentEnc.PlainBS()) - if status != fuse.OK { - toggledlog.Warn.Printf("ino%d fh%d: RMW read failed: %s", f.ino, f.intFd(), status.String()) - return written, status - } - // Modify - blockData = f.contentEnc.MergeBlocks(oldData, blockData, int(b.Skip)) - toggledlog.Debug.Printf("len(oldData)=%d len(blockData)=%d", len(oldData), len(blockData)) - } - - // Encrypt - blockOffset, blockLen := b.CiphertextRange() - blockData = f.contentEnc.EncryptBlock(blockData, b.BlockNo, f.header.Id) - toggledlog.Debug.Printf("ino%d: Writing %d bytes to block #%d", - f.ino, uint64(len(blockData))-f.contentEnc.BlockOverhead(), b.BlockNo) - - // Prevent partially written (=corrupt) blocks by preallocating the space beforehand - err := prealloc(int(f.fd.Fd()), int64(blockOffset), int64(blockLen)) - if err != nil { - toggledlog.Warn.Printf("ino%d fh%d: doWrite: prealloc failed: %s", f.ino, f.intFd(), err.Error()) - status = fuse.ToStatus(err) - break - } - - // Write - _, err = f.fd.WriteAt(blockData, int64(blockOffset)) - - if err != nil { - toggledlog.Warn.Printf("doWrite: Write failed: %s", err.Error()) - status = fuse.ToStatus(err) - break - } - written += uint32(b.Length) - } - return written, status -} - -// Write - FUSE call -func (f *file) Write(data []byte, off int64) (uint32, fuse.Status) { - f.fdLock.RLock() - defer f.fdLock.RUnlock() - wlock.lock(f.ino) - defer wlock.unlock(f.ino) - - toggledlog.Debug.Printf("ino%d: FUSE Write: offset=%d length=%d", f.ino, off, len(data)) - - fi, err := f.fd.Stat() - if err != nil { - toggledlog.Warn.Printf("Write: Fstat failed: %v", err) - return 0, fuse.ToStatus(err) - } - plainSize := f.contentEnc.CipherSizeToPlainSize(uint64(fi.Size())) - if f.createsHole(plainSize, off) { - status := f.zeroPad(plainSize) - if status != fuse.OK { - toggledlog.Warn.Printf("zeroPad returned error %v", status) - return 0, status - } - } - return f.doWrite(data, off) -} - -// Release - FUSE call, close file -func (f *file) Release() { - f.fdLock.Lock() - defer f.fdLock.Unlock() - - f.fd.Close() - f.forgotten = true -} - -// Flush - FUSE call -func (f *file) Flush() fuse.Status { - f.fdLock.RLock() - defer f.fdLock.RUnlock() - - // 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())) - - if err != nil { - return fuse.ToStatus(err) - } - err = syscall.Close(newFd) - return fuse.ToStatus(err) -} - -func (f *file) Fsync(flags int) (code fuse.Status) { - f.fdLock.RLock() - defer f.fdLock.RUnlock() - - 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() - wlock.lock(f.ino) - defer wlock.unlock(f.ino) - - if f.forgotten { - toggledlog.Warn.Printf("ino%d fh%d: Truncate on forgotten file", f.ino, f.intFd()) - } - - // Common case first: Truncate to zero - if newSize == 0 { - err := syscall.Ftruncate(int(f.fd.Fd()), 0) - if err != nil { - toggledlog.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 { - toggledlog.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()) - toggledlog.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) - } - } - - blocks := f.contentEnc.ExplodePlainRange(oldSize, newSize-oldSize) - for _, b := range blocks { - // First and last block may be partial - if b.IsPartial() { - off, _ := b.PlaintextRange() - off += b.Skip - _, status := f.doWrite(make([]byte, b.Length), int64(off)) - if status != fuse.OK { - return status - } - } else { - off, length := b.CiphertextRange() - err := syscall.Ftruncate(int(f.fd.Fd()), int64(off+length)) - if err != nil { - toggledlog.Warn.Printf("grow Ftruncate returned error: %v", err) - return fuse.ToStatus(err) - } - } - } - return fuse.OK - } 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 { - toggledlog.Warn.Printf("shrink doRead returned error: %v", err) - return status - } - } - // Truncate down to last complete block - err = syscall.Ftruncate(int(f.fd.Fd()), int64(cipherOff)) - if err != nil { - toggledlog.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 - } - return fuse.OK - } -} - -func (f *file) Chmod(mode uint32) fuse.Status { - f.fdLock.RLock() - defer f.fdLock.RUnlock() - - return fuse.ToStatus(f.fd.Chmod(os.FileMode(mode))) -} - -func (f *file) Chown(uid uint32, gid uint32) fuse.Status { - f.fdLock.RLock() - defer f.fdLock.RUnlock() - - return fuse.ToStatus(f.fd.Chown(int(uid), int(gid))) -} - -func (f *file) GetAttr(a *fuse.Attr) fuse.Status { - f.fdLock.RLock() - defer f.fdLock.RUnlock() - - toggledlog.Debug.Printf("file.GetAttr()") - st := syscall.Stat_t{} - err := syscall.Fstat(int(f.fd.Fd()), &st) - if err != nil { - return fuse.ToStatus(err) - } - a.FromStat(&st) - a.Size = f.contentEnc.CipherSizeToPlainSize(a.Size) - - return fuse.OK -} - -// Allocate - FUSE call, fallocate(2) -var allocateWarned bool - -func (f *file) Allocate(off uint64, sz uint64, mode uint32) fuse.Status { - // Only warn once - if !allocateWarned { - toggledlog.Warn.Printf("fallocate(2) is not supported, returning ENOSYS - see https://github.com/rfjakob/gocryptfs/issues/1") - allocateWarned = true - } - return fuse.ENOSYS -} - -const _UTIME_OMIT = ((1 << 30) - 2) - -func (f *file) Utimens(a *time.Time, m *time.Time) fuse.Status { - f.fdLock.RLock() - defer f.fdLock.RUnlock() - - ts := make([]syscall.Timespec, 2) - - if a == nil { - ts[0].Nsec = _UTIME_OMIT - } else { - ts[0].Sec = a.Unix() - } - - if m == nil { - ts[1].Nsec = _UTIME_OMIT - } else { - ts[1].Sec = m.Unix() - } - - fn := fmt.Sprintf("/proc/self/fd/%d", f.fd.Fd()) - return fuse.ToStatus(syscall.UtimesNano(fn, ts)) -} diff --git a/pathfs_frontend/file_holes.go b/pathfs_frontend/file_holes.go deleted file mode 100644 index a147deb..0000000 --- a/pathfs_frontend/file_holes.go +++ /dev/null @@ -1,29 +0,0 @@ -package pathfs_frontend - -// Helper functions for sparse files (files with holes) - -import ( - "github.com/hanwen/go-fuse/fuse" - - "github.com/rfjakob/gocryptfs/internal/toggledlog" -) - -// Will a write to offset "off" create a file hole? -func (f *file) createsHole(plainSize uint64, off int64) bool { - nextBlock := f.contentEnc.PlainOffToBlockNo(plainSize) - targetBlock := f.contentEnc.PlainOffToBlockNo(uint64(off)) - if targetBlock > nextBlock { - return true - } - return false -} - -// Zero-pad the file of size plainSize to the next block boundary -func (f *file) zeroPad(plainSize uint64) fuse.Status { - lastBlockLen := plainSize % f.contentEnc.PlainBS() - missing := f.contentEnc.PlainBS() - lastBlockLen - pad := make([]byte, missing) - toggledlog.Debug.Printf("zeroPad: Writing %d bytes\n", missing) - _, status := f.doWrite(pad, int64(plainSize)) - return status -} diff --git a/pathfs_frontend/fs.go b/pathfs_frontend/fs.go deleted file mode 100644 index 212f0a7..0000000 --- a/pathfs_frontend/fs.go +++ /dev/null @@ -1,381 +0,0 @@ -package pathfs_frontend - -// FUSE operations on paths - -import ( - "encoding/base64" - "os" - "path/filepath" - "sync" - "syscall" - "time" - - "github.com/hanwen/go-fuse/fuse" - "github.com/hanwen/go-fuse/fuse/nodefs" - "github.com/hanwen/go-fuse/fuse/pathfs" - - "github.com/rfjakob/gocryptfs/internal/toggledlog" - "github.com/rfjakob/gocryptfs/internal/cryptocore" - "github.com/rfjakob/gocryptfs/internal/nametransform" - "github.com/rfjakob/gocryptfs/internal/contentenc" - "github.com/rfjakob/gocryptfs/internal/configfile" -) - -const plainBS = 4096 - -type FS struct { - pathfs.FileSystem // loopbackFileSystem, see go-fuse/fuse/pathfs/loopback.go - args Args // Stores configuration arguments - // dirIVLock: Lock()ed if any "gocryptfs.diriv" file is modified - // Readers must RLock() it to prevent them from seeing intermediate - // states - dirIVLock sync.RWMutex - // Filename encryption helper - nameTransform *nametransform.NameTransform - // Content encryption helper - contentEnc *contentenc.ContentEnc -} - -// Encrypted FUSE overlay filesystem -func NewFS(args Args) *FS { - - cryptoCore := cryptocore.New(args.Masterkey, args.OpenSSL, args.GCMIV128) - contentEnc := contentenc.New(cryptoCore, plainBS) - nameTransform := nametransform.New(cryptoCore, args.EMENames) - - return &FS{ - FileSystem: pathfs.NewLoopbackFileSystem(args.Cipherdir), - args: args, - nameTransform: nameTransform, - contentEnc: contentEnc, - } -} - -// GetBackingPath - get the absolute encrypted path of the backing file -// from the relative plaintext path "relPath" -func (fs *FS) getBackingPath(relPath string) (string, error) { - cPath, err := fs.encryptPath(relPath) - if err != nil { - return "", err - } - cAbsPath := filepath.Join(fs.args.Cipherdir, cPath) - toggledlog.Debug.Printf("getBackingPath: %s + %s -> %s", fs.args.Cipherdir, relPath, cAbsPath) - return cAbsPath, nil -} - -func (fs *FS) GetAttr(name string, context *fuse.Context) (*fuse.Attr, fuse.Status) { - toggledlog.Debug.Printf("FS.GetAttr('%s')", name) - if fs.isFiltered(name) { - return nil, fuse.EPERM - } - cName, err := fs.encryptPath(name) - if err != nil { - return nil, fuse.ToStatus(err) - } - a, status := fs.FileSystem.GetAttr(cName, context) - if a == nil { - toggledlog.Debug.Printf("FS.GetAttr failed: %s", status.String()) - return a, status - } - if a.IsRegular() { - a.Size = fs.contentEnc.CipherSizeToPlainSize(a.Size) - } else if a.IsSymlink() { - target, _ := fs.Readlink(name, context) - a.Size = uint64(len(target)) - } - return a, status -} - -func (fs *FS) OpenDir(dirName string, context *fuse.Context) ([]fuse.DirEntry, fuse.Status) { - toggledlog.Debug.Printf("OpenDir(%s)", dirName) - cDirName, err := fs.encryptPath(dirName) - if err != nil { - return nil, fuse.ToStatus(err) - } - // Read ciphertext directory - cipherEntries, status := fs.FileSystem.OpenDir(cDirName, context) - if cipherEntries == nil { - return nil, status - } - // Get DirIV (stays nil if DirIV if off) - var cachedIV []byte - if fs.args.DirIV { - // Read the DirIV once and use it for all later name decryptions - cDirAbsPath := filepath.Join(fs.args.Cipherdir, cDirName) - cachedIV, err = fs.nameTransform.ReadDirIV(cDirAbsPath) - if err != nil { - return nil, fuse.ToStatus(err) - } - } - // Filter and decrypt filenames - var plain []fuse.DirEntry - for i := range cipherEntries { - cName := cipherEntries[i].Name - if dirName == "" && cName == configfile.ConfDefaultName { - // silently ignore "gocryptfs.conf" in the top level dir - continue - } - if fs.args.DirIV && cName == nametransform.DirIVFilename { - // silently ignore "gocryptfs.diriv" everywhere if dirIV is enabled - continue - } - var name string = cName - if !fs.args.PlaintextNames { - name, err = fs.nameTransform.DecryptName(cName, cachedIV) - if err != nil { - toggledlog.Warn.Printf("Invalid name \"%s\" in dir \"%s\": %s", cName, cDirName, err) - continue - } - } - cipherEntries[i].Name = name - plain = append(plain, cipherEntries[i]) - } - return plain, status -} - -// We always need read access to do read-modify-write cycles -func (fs *FS) mangleOpenFlags(flags uint32) (newFlags int, writeOnly bool) { - newFlags = int(flags) - if newFlags&os.O_WRONLY > 0 { - writeOnly = true - newFlags = newFlags ^ os.O_WRONLY | os.O_RDWR - } - // We also cannot open the file in append mode, we need to seek back for RMW - newFlags = newFlags &^ os.O_APPEND - - return newFlags, writeOnly -} - -func (fs *FS) Open(path string, flags uint32, context *fuse.Context) (fuseFile nodefs.File, status fuse.Status) { - if fs.isFiltered(path) { - return nil, fuse.EPERM - } - iflags, writeOnly := fs.mangleOpenFlags(flags) - cPath, err := fs.getBackingPath(path) - if err != nil { - toggledlog.Debug.Printf("Open: getBackingPath: %v", err) - return nil, fuse.ToStatus(err) - } - toggledlog.Debug.Printf("Open: %s", cPath) - f, err := os.OpenFile(cPath, iflags, 0666) - if err != nil { - return nil, fuse.ToStatus(err) - } - - return NewFile(f, writeOnly, fs.contentEnc), fuse.OK -} - -func (fs *FS) Create(path string, flags uint32, mode uint32, context *fuse.Context) (fuseFile nodefs.File, code fuse.Status) { - if fs.isFiltered(path) { - return nil, fuse.EPERM - } - iflags, writeOnly := fs.mangleOpenFlags(flags) - cPath, err := fs.getBackingPath(path) - if err != nil { - return nil, fuse.ToStatus(err) - } - f, err := os.OpenFile(cPath, iflags|os.O_CREATE, os.FileMode(mode)) - if err != nil { - return nil, fuse.ToStatus(err) - } - return NewFile(f, writeOnly, fs.contentEnc), fuse.OK -} - -func (fs *FS) Chmod(path string, mode uint32, context *fuse.Context) (code fuse.Status) { - if fs.isFiltered(path) { - return fuse.EPERM - } - cPath, err := fs.encryptPath(path) - if err != nil { - return fuse.ToStatus(err) - } - return fs.FileSystem.Chmod(cPath, mode, context) -} - -func (fs *FS) Chown(path string, uid uint32, gid uint32, context *fuse.Context) (code fuse.Status) { - if fs.isFiltered(path) { - return fuse.EPERM - } - cPath, err := fs.encryptPath(path) - if err != nil { - return fuse.ToStatus(err) - } - return fs.FileSystem.Chown(cPath, uid, gid, context) -} - -func (fs *FS) Mknod(path string, mode uint32, dev uint32, context *fuse.Context) (code fuse.Status) { - if fs.isFiltered(path) { - return fuse.EPERM - } - cPath, err := fs.encryptPath(path) - if err != nil { - return fuse.ToStatus(err) - } - return fs.FileSystem.Mknod(cPath, mode, dev, context) -} - -var truncateWarned bool - -func (fs *FS) Truncate(path string, offset uint64, context *fuse.Context) (code fuse.Status) { - // Only warn once - if !truncateWarned { - toggledlog.Warn.Printf("truncate(2) is not supported, returning ENOSYS - use ftruncate(2)") - truncateWarned = true - } - return fuse.ENOSYS -} - -func (fs *FS) Utimens(path string, Atime *time.Time, Mtime *time.Time, context *fuse.Context) (code fuse.Status) { - if fs.isFiltered(path) { - return fuse.EPERM - } - cPath, err := fs.encryptPath(path) - if err != nil { - return fuse.ToStatus(err) - } - return fs.FileSystem.Utimens(cPath, Atime, Mtime, context) -} - -func (fs *FS) Readlink(path string, context *fuse.Context) (out string, status fuse.Status) { - cPath, err := fs.encryptPath(path) - if err != nil { - return "", fuse.ToStatus(err) - } - cTarget, status := fs.FileSystem.Readlink(cPath, context) - if status != fuse.OK { - return "", status - } - // Old filesystem: symlinks are encrypted like paths (CBC) - if !fs.args.DirIV { - target, err := fs.decryptPath(cTarget) - if err != nil { - toggledlog.Warn.Printf("Readlink: CBC decryption failed: %v", err) - return "", fuse.EIO - } - return target, fuse.OK - } - // Since gocryptfs v0.5 symlinks are encrypted like file contents (GCM) - cBinTarget, err := base64.URLEncoding.DecodeString(cTarget) - if err != nil { - toggledlog.Warn.Printf("Readlink: %v", err) - return "", fuse.EIO - } - target, err := fs.contentEnc.DecryptBlock([]byte(cBinTarget), 0, nil) - if err != nil { - toggledlog.Warn.Printf("Readlink: %v", err) - return "", fuse.EIO - } - return string(target), fuse.OK -} - -func (fs *FS) Unlink(path string, context *fuse.Context) (code fuse.Status) { - if fs.isFiltered(path) { - return fuse.EPERM - } - cPath, err := fs.getBackingPath(path) - if err != nil { - return fuse.ToStatus(err) - } - return fuse.ToStatus(syscall.Unlink(cPath)) -} - -func (fs *FS) Symlink(target string, linkName string, context *fuse.Context) (code fuse.Status) { - toggledlog.Debug.Printf("Symlink(\"%s\", \"%s\")", target, linkName) - if fs.isFiltered(linkName) { - return fuse.EPERM - } - cPath, err := fs.getBackingPath(linkName) - if err != nil { - return fuse.ToStatus(err) - } - // Old filesystem: symlinks are encrypted like paths (CBC) - if !fs.args.DirIV { - cTarget, err := fs.encryptPath(target) - if err != nil { - toggledlog.Warn.Printf("Symlink: BUG: we should not get an error here: %v", err) - return fuse.ToStatus(err) - } - err = os.Symlink(cTarget, cPath) - return fuse.ToStatus(err) - } - // Since gocryptfs v0.5 symlinks are encrypted like file contents (GCM) - cBinTarget := fs.contentEnc.EncryptBlock([]byte(target), 0, nil) - cTarget := base64.URLEncoding.EncodeToString(cBinTarget) - - err = os.Symlink(cTarget, cPath) - toggledlog.Debug.Printf("Symlink: os.Symlink(%s, %s) = %v", cTarget, cPath, err) - return fuse.ToStatus(err) -} - -func (fs *FS) Rename(oldPath string, newPath string, context *fuse.Context) (code fuse.Status) { - if fs.isFiltered(newPath) { - return fuse.EPERM - } - cOldPath, err := fs.getBackingPath(oldPath) - if err != nil { - return fuse.ToStatus(err) - } - cNewPath, err := fs.getBackingPath(newPath) - if err != nil { - return fuse.ToStatus(err) - } - // The Rename may cause a directory to take the place of another directory. - // That directory may still be in the DirIV cache, clear it. - fs.nameTransform.DirIVCache.Clear() - - err = os.Rename(cOldPath, cNewPath) - - if lerr, ok := err.(*os.LinkError); ok && lerr.Err == syscall.ENOTEMPTY { - // If an empty directory is overwritten we will always get - // ENOTEMPTY as the "empty" directory will still contain gocryptfs.diriv. - // Handle that case by removing the target directory and trying again. - toggledlog.Debug.Printf("Rename: Handling ENOTEMPTY") - if fs.Rmdir(newPath, context) == fuse.OK { - err = os.Rename(cOldPath, cNewPath) - } - } - - return fuse.ToStatus(err) -} - -func (fs *FS) Link(oldPath string, newPath string, context *fuse.Context) (code fuse.Status) { - if fs.isFiltered(newPath) { - return fuse.EPERM - } - cOldPath, err := fs.getBackingPath(oldPath) - if err != nil { - return fuse.ToStatus(err) - } - cNewPath, err := fs.getBackingPath(newPath) - if err != nil { - return fuse.ToStatus(err) - } - return fuse.ToStatus(os.Link(cOldPath, cNewPath)) -} - -func (fs *FS) Access(path string, mode uint32, context *fuse.Context) (code fuse.Status) { - if fs.isFiltered(path) { - return fuse.EPERM - } - cPath, err := fs.getBackingPath(path) - if err != nil { - return fuse.ToStatus(err) - } - return fuse.ToStatus(syscall.Access(cPath, mode)) -} - -func (fs *FS) GetXAttr(name string, attr string, context *fuse.Context) ([]byte, fuse.Status) { - return nil, fuse.ENOSYS -} - -func (fs *FS) SetXAttr(name string, attr string, data []byte, flags int, context *fuse.Context) fuse.Status { - return fuse.ENOSYS -} - -func (fs *FS) ListXAttr(name string, context *fuse.Context) ([]string, fuse.Status) { - return nil, fuse.ENOSYS -} - -func (fs *FS) RemoveXAttr(name string, attr string, context *fuse.Context) fuse.Status { - return fuse.ENOSYS -} diff --git a/pathfs_frontend/fs_dir.go b/pathfs_frontend/fs_dir.go deleted file mode 100644 index d378d28..0000000 --- a/pathfs_frontend/fs_dir.go +++ /dev/null @@ -1,157 +0,0 @@ -package pathfs_frontend - -// Mkdir and Rmdir - -import ( - "fmt" - "os" - "path/filepath" - "syscall" - - "github.com/hanwen/go-fuse/fuse" - - "github.com/rfjakob/gocryptfs/internal/toggledlog" - "github.com/rfjakob/gocryptfs/internal/cryptocore" - "github.com/rfjakob/gocryptfs/internal/nametransform" -) - -func (fs *FS) Mkdir(relPath string, mode uint32, context *fuse.Context) (code fuse.Status) { - if fs.isFiltered(relPath) { - return fuse.EPERM - } - encPath, err := fs.getBackingPath(relPath) - if err != nil { - return fuse.ToStatus(err) - } - if !fs.args.DirIV { - return fuse.ToStatus(os.Mkdir(encPath, os.FileMode(mode))) - } - - // We need write and execute permissions to create gocryptfs.diriv - origMode := mode - mode = mode | 0300 - - // The new directory may take the place of an older one that is still in the cache - fs.nameTransform.DirIVCache.Clear() - // Create directory - fs.dirIVLock.Lock() - defer fs.dirIVLock.Unlock() - err = os.Mkdir(encPath, os.FileMode(mode)) - if err != nil { - return fuse.ToStatus(err) - } - // Create gocryptfs.diriv inside - err = nametransform.WriteDirIV(encPath) - if err != nil { - // This should not happen - toggledlog.Warn.Printf("Mkdir: WriteDirIV failed: %v", err) - err2 := syscall.Rmdir(encPath) - if err2 != nil { - toggledlog.Warn.Printf("Mkdir: Rmdir rollback failed: %v", err2) - } - return fuse.ToStatus(err) - } - - // Set permissions back to what the user wanted - if origMode != mode { - err = os.Chmod(encPath, os.FileMode(origMode)) - if err != nil { - toggledlog.Warn.Printf("Mkdir: Chmod failed: %v", err) - } - } - - return fuse.OK -} - -func (fs *FS) Rmdir(name string, context *fuse.Context) (code fuse.Status) { - encPath, err := fs.getBackingPath(name) - if err != nil { - return fuse.ToStatus(err) - } - if !fs.args.DirIV { - return fuse.ToStatus(syscall.Rmdir(encPath)) - } - - // If the directory is not empty besides gocryptfs.diriv, do not even - // attempt the dance around gocryptfs.diriv. - fd, err := os.Open(encPath) - if perr, ok := err.(*os.PathError); ok && perr.Err == syscall.EACCES { - // We need permission to read and modify the directory - toggledlog.Debug.Printf("Rmdir: handling EACCESS") - fi, err2 := os.Stat(encPath) - if err2 != nil { - toggledlog.Debug.Printf("Rmdir: Stat: %v", err2) - return fuse.ToStatus(err2) - } - origMode := fi.Mode() - newMode := origMode | 0700 - err2 = os.Chmod(encPath, newMode) - if err2 != nil { - toggledlog.Debug.Printf("Rmdir: Chmod failed: %v", err2) - return fuse.ToStatus(err) - } - defer func() { - if code != fuse.OK { - // Undo the chmod if removing the directory failed - err3 := os.Chmod(encPath, origMode) - if err3 != nil { - toggledlog.Warn.Printf("Rmdir: Chmod rollback failed: %v", err2) - } - } - }() - // Retry open - fd, err = os.Open(encPath) - } - if err != nil { - toggledlog.Debug.Printf("Rmdir: Open: %v", err) - return fuse.ToStatus(err) - } - list, err := fd.Readdirnames(10) - fd.Close() - if err != nil { - toggledlog.Debug.Printf("Rmdir: Readdirnames: %v", err) - return fuse.ToStatus(err) - } - if len(list) > 1 { - return fuse.ToStatus(syscall.ENOTEMPTY) - } else if len(list) == 0 { - toggledlog.Warn.Printf("Rmdir: gocryptfs.diriv missing, allowing deletion") - return fuse.ToStatus(syscall.Rmdir(encPath)) - } - - // Move "gocryptfs.diriv" to the parent dir as "gocryptfs.diriv.rmdir.XYZ" - dirivPath := filepath.Join(encPath, nametransform.DirIVFilename) - parentDir := filepath.Dir(encPath) - tmpName := fmt.Sprintf("gocryptfs.diriv.rmdir.%d", cryptocore.RandUint64()) - tmpDirivPath := filepath.Join(parentDir, tmpName) - toggledlog.Debug.Printf("Rmdir: Renaming %s to %s", nametransform.DirIVFilename, tmpDirivPath) - // The directory is in an inconsistent state between rename and rmdir. Protect against - // concurrent readers. - fs.dirIVLock.Lock() - defer fs.dirIVLock.Unlock() - err = os.Rename(dirivPath, tmpDirivPath) - if err != nil { - toggledlog.Warn.Printf("Rmdir: Renaming %s to %s failed: %v", - nametransform.DirIVFilename, tmpDirivPath, err) - return fuse.ToStatus(err) - } - // Actual Rmdir - err = syscall.Rmdir(encPath) - if err != nil { - // This can happen if another file in the directory was created in the - // meantime, undo the rename - err2 := os.Rename(tmpDirivPath, dirivPath) - if err2 != nil { - toggledlog.Warn.Printf("Rmdir: Rename rollback failed: %v", err2) - } - return fuse.ToStatus(err) - } - // Delete "gocryptfs.diriv.rmdir.INODENUMBER" - err = syscall.Unlink(tmpDirivPath) - if err != nil { - toggledlog.Warn.Printf("Rmdir: Could not clean up %s: %v", tmpName, err) - } - // The now-deleted directory may have been in the DirIV cache. Clear it. - fs.nameTransform.DirIVCache.Clear() - return fuse.OK -} diff --git a/pathfs_frontend/names.go b/pathfs_frontend/names.go deleted file mode 100644 index 160fa0a..0000000 --- a/pathfs_frontend/names.go +++ /dev/null @@ -1,52 +0,0 @@ -package pathfs_frontend - -// This file forwards file encryption operations to cryptfs - -import ( - "github.com/rfjakob/gocryptfs/internal/configfile" - mylog "github.com/rfjakob/gocryptfs/internal/toggledlog" -) - -// isFiltered - check if plaintext "path" should be forbidden -// -// Prevents name clashes with internal files when file names are not encrypted -func (fs *FS) isFiltered(path string) bool { - if !fs.args.PlaintextNames { - return false - } - // gocryptfs.conf in the root directory is forbidden - if path == configfile.ConfDefaultName { - mylog.Info.Printf("The name /%s is reserved when -plaintextnames is used\n", - configfile.ConfDefaultName) - return true - } - // Note: gocryptfs.diriv is NOT forbidden because diriv and plaintextnames - // are exclusive - return false -} - -// encryptPath - encrypt relative plaintext path -func (fs *FS) encryptPath(plainPath string) (string, error) { - if fs.args.PlaintextNames { - return plainPath, nil - } - if !fs.args.DirIV { - return fs.nameTransform.EncryptPathNoIV(plainPath), nil - } - fs.dirIVLock.RLock() - defer fs.dirIVLock.RUnlock() - return fs.nameTransform.EncryptPathDirIV(plainPath, fs.args.Cipherdir) -} - -// decryptPath - decrypt relative ciphertext path -func (fs *FS) decryptPath(cipherPath string) (string, error) { - if fs.args.PlaintextNames { - return cipherPath, nil - } - if !fs.args.DirIV { - return fs.nameTransform.DecryptPathNoIV(cipherPath) - } - fs.dirIVLock.RLock() - defer fs.dirIVLock.RUnlock() - return fs.nameTransform.DecryptPathDirIV(cipherPath, fs.args.Cipherdir, fs.args.EMENames) -} diff --git a/pathfs_frontend/write_lock.go b/pathfs_frontend/write_lock.go deleted file mode 100644 index 0704eb6..0000000 --- a/pathfs_frontend/write_lock.go +++ /dev/null @@ -1,66 +0,0 @@ -package pathfs_frontend - -import ( - "sync" -) - -func init() { - wlock.m = make(map[uint64]*refCntMutex) -} - -// wlock - serializes write accesses to each file (identified by inode number) -// Writing partial blocks means we have to do read-modify-write cycles. We -// really don't want concurrent writes there. -// Concurrent full-block writes could actually be allowed, but are not to -// keep the locking simple. -var wlock wlockMap - -// wlockMap - usage: -// 1) register -// 2) lock ... unlock ... -// 3) unregister -type wlockMap struct { - mapMutex sync.RWMutex - m map[uint64]*refCntMutex -} - -func (w *wlockMap) register(ino uint64) { - w.mapMutex.Lock() - r := w.m[ino] - if r == nil { - r = &refCntMutex{} - w.m[ino] = r - } - r.refCnt++ // this must happen inside the mapMutex lock - w.mapMutex.Unlock() -} - -func (w *wlockMap) unregister(ino uint64) { - w.mapMutex.Lock() - r := w.m[ino] - r.refCnt-- - if r.refCnt == 0 { - delete(w.m, ino) - } - w.mapMutex.Unlock() -} - -func (w *wlockMap) lock(ino uint64) { - w.mapMutex.RLock() - r := w.m[ino] - w.mapMutex.RUnlock() - r.Lock() // this can take a long time - execute outside the mapMutex lock -} - -func (w *wlockMap) unlock(ino uint64) { - w.mapMutex.RLock() - r := w.m[ino] - w.mapMutex.RUnlock() - r.Unlock() -} - -// refCntMutex - mutex with reference count -type refCntMutex struct { - sync.Mutex - refCnt int -} |