parent
779ad6dda3
commit
6f90ec716a
|
@ -0,0 +1,2 @@
|
||||||
|
# Binary
|
||||||
|
/gocryptfs
|
|
@ -8,7 +8,7 @@ import (
|
||||||
"crypto/cipher"
|
"crypto/cipher"
|
||||||
)
|
)
|
||||||
|
|
||||||
type File struct {
|
type CryptFile struct {
|
||||||
file *os.File
|
file *os.File
|
||||||
gcm cipher.AEAD
|
gcm cipher.AEAD
|
||||||
plainBS int64
|
plainBS int64
|
||||||
|
@ -17,7 +17,7 @@ type File struct {
|
||||||
|
|
||||||
// readCipherBlock - Read ciphertext block number "blockNo", decrypt,
|
// readCipherBlock - Read ciphertext block number "blockNo", decrypt,
|
||||||
// return plaintext
|
// return plaintext
|
||||||
func (be *File) readCipherBlock(blockNo int64) ([]byte, error) {
|
func (be *CryptFile) readCipherBlock(blockNo int64) ([]byte, error) {
|
||||||
off := blockNo * int64(be.cipherBS)
|
off := blockNo * int64(be.cipherBS)
|
||||||
buf := make([]byte, be.cipherBS)
|
buf := make([]byte, be.cipherBS)
|
||||||
|
|
||||||
|
@ -64,7 +64,7 @@ type intraBlock struct {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Split a plaintext byte range into (possible partial) blocks
|
// Split a plaintext byte range into (possible partial) blocks
|
||||||
func (be *File) splitRange(offset int64, length int64) []intraBlock {
|
func (be *CryptFile) splitRange(offset int64, length int64) []intraBlock {
|
||||||
var b intraBlock
|
var b intraBlock
|
||||||
var parts []intraBlock
|
var parts []intraBlock
|
||||||
|
|
||||||
|
@ -79,7 +79,7 @@ func (be *File) splitRange(offset int64, length int64) []intraBlock {
|
||||||
return parts
|
return parts
|
||||||
}
|
}
|
||||||
|
|
||||||
func (be *File) min64(x int64, y int64) int64 {
|
func (be *CryptFile) min64(x int64, y int64) int64 {
|
||||||
if x < y {
|
if x < y {
|
||||||
return x
|
return x
|
||||||
}
|
}
|
||||||
|
@ -87,7 +87,7 @@ func (be *File) min64(x int64, y int64) int64 {
|
||||||
}
|
}
|
||||||
|
|
||||||
// writeCipherBlock - Encrypt plaintext and write it to file block "blockNo"
|
// writeCipherBlock - Encrypt plaintext and write it to file block "blockNo"
|
||||||
func (be *File) writeCipherBlock(blockNo int64, plain []byte) error {
|
func (be *CryptFile) writeCipherBlock(blockNo int64, plain []byte) error {
|
||||||
|
|
||||||
if int64(len(plain)) > be.plainBS {
|
if int64(len(plain)) > be.plainBS {
|
||||||
panic("writeCipherBlock: Cannot write block that is larger than plainBS")
|
panic("writeCipherBlock: Cannot write block that is larger than plainBS")
|
||||||
|
@ -109,7 +109,7 @@ func (be *File) writeCipherBlock(blockNo int64, plain []byte) error {
|
||||||
|
|
||||||
// Perform RMW cycle on block
|
// Perform RMW cycle on block
|
||||||
// Write "data" into file location specified in "b"
|
// Write "data" into file location specified in "b"
|
||||||
func (be *File) rmwWrite(b intraBlock, data []byte, f *os.File) error {
|
func (be *CryptFile) rmwWrite(b intraBlock, data []byte, f *os.File) error {
|
||||||
if b.length != int64(len(data)) {
|
if b.length != int64(len(data)) {
|
||||||
panic("Length mismatch")
|
panic("Length mismatch")
|
||||||
}
|
}
|
||||||
|
|
|
@ -19,14 +19,14 @@ const (
|
||||||
DECRYPT = false
|
DECRYPT = false
|
||||||
)
|
)
|
||||||
|
|
||||||
type FS struct {
|
type CryptFS struct {
|
||||||
blockCipher cipher.Block
|
blockCipher cipher.Block
|
||||||
gcm cipher.AEAD
|
gcm cipher.AEAD
|
||||||
plainBS int64
|
plainBS int64
|
||||||
cipherBS int64
|
cipherBS int64
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewFS(key [16]byte) *FS {
|
func NewCryptFS(key [16]byte) *CryptFS {
|
||||||
|
|
||||||
b, err := aes.NewCipher(key[:])
|
b, err := aes.NewCipher(key[:])
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
@ -38,7 +38,7 @@ func NewFS(key [16]byte) *FS {
|
||||||
panic(err)
|
panic(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
return &FS{
|
return &CryptFS{
|
||||||
blockCipher: b,
|
blockCipher: b,
|
||||||
gcm: g,
|
gcm: g,
|
||||||
plainBS: DEFAULT_PLAINBS,
|
plainBS: DEFAULT_PLAINBS,
|
||||||
|
@ -46,8 +46,8 @@ func NewFS(key [16]byte) *FS {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (fs *FS) NewFile(f *os.File) *File {
|
func (fs *CryptFS) NewFile(f *os.File) *CryptFile {
|
||||||
return &File {
|
return &CryptFile {
|
||||||
file: f,
|
file: f,
|
||||||
gcm: fs.gcm,
|
gcm: fs.gcm,
|
||||||
plainBS: fs.plainBS,
|
plainBS: fs.plainBS,
|
||||||
|
@ -56,7 +56,7 @@ func (fs *FS) NewFile(f *os.File) *File {
|
||||||
}
|
}
|
||||||
|
|
||||||
// DecryptName - decrypt filename
|
// DecryptName - decrypt filename
|
||||||
func (be *FS) decryptName(cipherName string) (string, error) {
|
func (be *CryptFS) decryptName(cipherName string) (string, error) {
|
||||||
|
|
||||||
bin, err := base64.URLEncoding.DecodeString(cipherName)
|
bin, err := base64.URLEncoding.DecodeString(cipherName)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
@ -81,7 +81,7 @@ func (be *FS) decryptName(cipherName string) (string, error) {
|
||||||
}
|
}
|
||||||
|
|
||||||
// EncryptName - encrypt filename
|
// EncryptName - encrypt filename
|
||||||
func (be *FS) encryptName(plainName string) string {
|
func (be *CryptFS) encryptName(plainName string) string {
|
||||||
|
|
||||||
bin := []byte(plainName)
|
bin := []byte(plainName)
|
||||||
bin = be.pad16(bin)
|
bin = be.pad16(bin)
|
||||||
|
@ -97,7 +97,7 @@ func (be *FS) encryptName(plainName string) string {
|
||||||
|
|
||||||
// TranslatePath - encrypt or decrypt path. Just splits the string on "/"
|
// TranslatePath - encrypt or decrypt path. Just splits the string on "/"
|
||||||
// and hands the parts to EncryptName() / DecryptName()
|
// and hands the parts to EncryptName() / DecryptName()
|
||||||
func (be *FS) translatePath(path string, op bool) (string, error) {
|
func (be *CryptFS) translatePath(path string, op bool) (string, error) {
|
||||||
var err error
|
var err error
|
||||||
|
|
||||||
// Empty string means root directory
|
// Empty string means root directory
|
||||||
|
@ -125,18 +125,18 @@ func (be *FS) translatePath(path string, op bool) (string, error) {
|
||||||
}
|
}
|
||||||
|
|
||||||
// EncryptPath - encrypt filename or path. Just hands it to TranslatePath().
|
// EncryptPath - encrypt filename or path. Just hands it to TranslatePath().
|
||||||
func (be *FS) EncryptPath(path string) string {
|
func (be *CryptFS) EncryptPath(path string) string {
|
||||||
newPath, _ := be.translatePath(path, ENCRYPT)
|
newPath, _ := be.translatePath(path, ENCRYPT)
|
||||||
return newPath
|
return newPath
|
||||||
}
|
}
|
||||||
|
|
||||||
// DecryptPath - decrypt filename or path. Just hands it to TranslatePath().
|
// DecryptPath - decrypt filename or path. Just hands it to TranslatePath().
|
||||||
func (be *FS) DecryptPath(path string) (string, error) {
|
func (be *CryptFS) DecryptPath(path string) (string, error) {
|
||||||
return be.translatePath(path, DECRYPT)
|
return be.translatePath(path, DECRYPT)
|
||||||
}
|
}
|
||||||
|
|
||||||
// plainSize - calculate plaintext size from ciphertext size
|
// plainSize - calculate plaintext size from ciphertext size
|
||||||
func (be *FS) PlainSize(s int64) int64 {
|
func (be *CryptFS) PlainSize(s int64) int64 {
|
||||||
// Zero sized files stay zero-sized
|
// Zero sized files stay zero-sized
|
||||||
if s > 0 {
|
if s > 0 {
|
||||||
// Number of blocks
|
// Number of blocks
|
||||||
|
@ -149,7 +149,7 @@ func (be *FS) PlainSize(s int64) int64 {
|
||||||
|
|
||||||
// pad16 - pad filename to 16 byte blocks using standard PKCS#7 padding
|
// pad16 - pad filename to 16 byte blocks using standard PKCS#7 padding
|
||||||
// https://tools.ietf.org/html/rfc5652#section-6.3
|
// https://tools.ietf.org/html/rfc5652#section-6.3
|
||||||
func (be *FS) pad16(orig []byte) (padded []byte) {
|
func (be *CryptFS) pad16(orig []byte) (padded []byte) {
|
||||||
oldLen := len(orig)
|
oldLen := len(orig)
|
||||||
if oldLen == 0 {
|
if oldLen == 0 {
|
||||||
panic("Padding zero-length string makes no sense")
|
panic("Padding zero-length string makes no sense")
|
||||||
|
@ -169,7 +169,7 @@ func (be *FS) pad16(orig []byte) (padded []byte) {
|
||||||
}
|
}
|
||||||
|
|
||||||
// unPad16 - remove padding
|
// unPad16 - remove padding
|
||||||
func (be *FS) unPad16(orig []byte) ([]byte, error) {
|
func (be *CryptFS) unPad16(orig []byte) ([]byte, error) {
|
||||||
oldLen := len(orig)
|
oldLen := len(orig)
|
||||||
if oldLen % aes.BlockSize != 0 {
|
if oldLen % aes.BlockSize != 0 {
|
||||||
return nil, errors.New("Unaligned size")
|
return nil, errors.New("Unaligned size")
|
||||||
|
|
|
@ -0,0 +1,10 @@
|
||||||
|
package frontend
|
||||||
|
|
||||||
|
import (
|
||||||
|
//"github.com/rfjakob/gocryptfs/cryptfs"
|
||||||
|
"github.com/rfjakob/cluefs/lib/cluefs"
|
||||||
|
)
|
||||||
|
|
||||||
|
type Dir struct {
|
||||||
|
*cluefs.Dir
|
||||||
|
}
|
|
@ -2,8 +2,10 @@ package frontend
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"github.com/rfjakob/gocryptfs/cryptfs"
|
"github.com/rfjakob/gocryptfs/cryptfs"
|
||||||
|
"github.com/rfjakob/cluefs/lib/cluefs"
|
||||||
)
|
)
|
||||||
|
|
||||||
type File struct {
|
type File struct {
|
||||||
cryptfs.File
|
*cryptfs.CryptFile
|
||||||
|
*cluefs.File
|
||||||
}
|
}
|
||||||
|
|
|
@ -2,25 +2,26 @@ package frontend
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"github.com/rfjakob/gocryptfs/cryptfs"
|
"github.com/rfjakob/gocryptfs/cryptfs"
|
||||||
"bazil.org/fuse/fs"
|
"github.com/rfjakob/cluefs/lib/cluefs"
|
||||||
)
|
)
|
||||||
|
|
||||||
type FS struct {
|
type FS struct {
|
||||||
*cryptfs.FS
|
*cryptfs.CryptFS
|
||||||
backing string
|
*cluefs.ClueFS
|
||||||
}
|
}
|
||||||
|
|
||||||
func New(key [16]byte, b string) *FS {
|
type nullTracer struct {}
|
||||||
|
|
||||||
|
func (nullTracer) Trace(op cluefs.FsOperTracer) {}
|
||||||
|
|
||||||
|
func NewFS(key [16]byte, backing string) *FS {
|
||||||
|
var nt nullTracer
|
||||||
|
clfs, err := cluefs.NewClueFS(backing, nt)
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
return &FS {
|
return &FS {
|
||||||
FS: cryptfs.NewFS(key),
|
CryptFS: cryptfs.NewCryptFS(key),
|
||||||
backing: b,
|
ClueFS: clfs,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (fs *FS) Root() (fs.Node, error) {
|
|
||||||
n := Node{
|
|
||||||
backing: "",
|
|
||||||
parentFS: fs,
|
|
||||||
}
|
|
||||||
return n, nil
|
|
||||||
}
|
|
||||||
|
|
108
frontend/node.go
108
frontend/node.go
|
@ -1,113 +1,9 @@
|
||||||
package frontend
|
package frontend
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"github.com/rfjakob/cluefs/lib/cluefs"
|
||||||
"os"
|
|
||||||
"time"
|
|
||||||
"syscall"
|
|
||||||
"io/ioutil"
|
|
||||||
"path"
|
|
||||||
|
|
||||||
"golang.org/x/net/context"
|
|
||||||
|
|
||||||
//"github.com/rfjakob/gocryptfs/cryptfs"
|
|
||||||
"bazil.org/fuse"
|
|
||||||
"bazil.org/fuse/fs"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
type Node struct {
|
type Node struct {
|
||||||
fs.NodeRef
|
*cluefs.Node
|
||||||
backing string
|
|
||||||
parentFS *FS
|
|
||||||
}
|
|
||||||
|
|
||||||
// FileModeFromStat - create os.FileMode from stat value
|
|
||||||
// For some reason, they use different constants.
|
|
||||||
// Adapted from https://golang.org/src/os/stat_linux.go
|
|
||||||
func FileModeFromStat(st *syscall.Stat_t) os.FileMode {
|
|
||||||
fileMode := os.FileMode(st.Mode & 0777)
|
|
||||||
switch st.Mode & syscall.S_IFMT {
|
|
||||||
case syscall.S_IFBLK:
|
|
||||||
fileMode |= os.ModeDevice
|
|
||||||
case syscall.S_IFCHR:
|
|
||||||
fileMode |= os.ModeDevice | os.ModeCharDevice
|
|
||||||
case syscall.S_IFDIR:
|
|
||||||
fileMode |= os.ModeDir
|
|
||||||
case syscall.S_IFIFO:
|
|
||||||
fileMode |= os.ModeNamedPipe
|
|
||||||
case syscall.S_IFLNK:
|
|
||||||
fileMode |= os.ModeSymlink
|
|
||||||
case syscall.S_IFREG:
|
|
||||||
// nothing to do
|
|
||||||
case syscall.S_IFSOCK:
|
|
||||||
fileMode |= os.ModeSocket
|
|
||||||
}
|
|
||||||
if st.Mode & syscall.S_ISGID != 0 {
|
|
||||||
fileMode |= os.ModeSetgid
|
|
||||||
}
|
|
||||||
if st.Mode & syscall.S_ISUID != 0 {
|
|
||||||
fileMode |= os.ModeSetuid
|
|
||||||
}
|
|
||||||
if st.Mode & syscall.S_ISVTX != 0 {
|
|
||||||
fileMode |= os.ModeSticky
|
|
||||||
}
|
|
||||||
return fileMode
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
func StatToAttr(s *syscall.Stat_t, a *fuse.Attr) {
|
|
||||||
a.Inode = s.Ino
|
|
||||||
a.Size = uint64(s.Size)
|
|
||||||
a.Blocks = uint64(s.Blocks)
|
|
||||||
a.Atime = time.Unix(s.Atim.Sec, s.Atim.Nsec)
|
|
||||||
a.Mtime = time.Unix(s.Mtim.Sec, s.Mtim.Nsec)
|
|
||||||
a.Ctime = time.Unix(s.Ctim.Sec, s.Ctim.Nsec)
|
|
||||||
a.Mode = FileModeFromStat(s)
|
|
||||||
a.Nlink = uint32(s.Nlink)
|
|
||||||
a.Uid = uint32(s.Uid)
|
|
||||||
a.Gid = uint32(s.Gid)
|
|
||||||
a.Rdev = uint32(s.Rdev)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (n Node) Attr(ctx context.Context, attr *fuse.Attr) error {
|
|
||||||
var err error
|
|
||||||
var st syscall.Stat_t
|
|
||||||
if n.backing == "" {
|
|
||||||
// When GetAttr is called for the toplevel directory, we always want
|
|
||||||
// to look through symlinks.
|
|
||||||
fmt.Printf("Attr %s\n", n.parentFS.backing)
|
|
||||||
//err = syscall.Stat(n.parentFS.backing, &st)
|
|
||||||
err = syscall.Stat("/", &st)
|
|
||||||
} else {
|
|
||||||
fmt.Printf("Attr %s\n", path.Join(n.parentFS.backing, n.backing))
|
|
||||||
p := path.Join(n.parentFS.backing, n.backing)
|
|
||||||
err = syscall.Lstat(p, &st)
|
|
||||||
}
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
StatToAttr(&st, attr)
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (n *Node) ReadDirAll(ctx context.Context) ([]fuse.Dirent, error) {
|
|
||||||
entries, err := ioutil.ReadDir(n.backing)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
var fuseEntries []fuse.Dirent
|
|
||||||
for _, e := range entries {
|
|
||||||
var d fuse.Dirent
|
|
||||||
d.Name = e.Name()
|
|
||||||
fuseEntries = append(fuseEntries, d)
|
|
||||||
}
|
|
||||||
return fuseEntries, err
|
|
||||||
}
|
|
||||||
|
|
||||||
func (n *Node) Lookup(ctx context.Context, name string) (fs.Node, error) {
|
|
||||||
if name == "hello" {
|
|
||||||
return Node{}, nil
|
|
||||||
}
|
|
||||||
return nil, fuse.ENOENT
|
|
||||||
}
|
}
|
||||||
|
|
394
main.go
394
main.go
|
@ -1,396 +1,28 @@
|
||||||
// Memfs implements an in-memory file system.
|
|
||||||
package main
|
package main
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"flag"
|
"github.com/rfjakob/cluefs/lib/cluefs"
|
||||||
"fmt"
|
|
||||||
"log"
|
|
||||||
"os"
|
|
||||||
"sync"
|
|
||||||
"sync/atomic"
|
|
||||||
"time"
|
|
||||||
|
|
||||||
"bazil.org/fuse"
|
|
||||||
"bazil.org/fuse/fs"
|
|
||||||
"golang.org/x/net/context"
|
|
||||||
|
|
||||||
"github.com/rfjakob/gocryptfs/frontend"
|
"github.com/rfjakob/gocryptfs/frontend"
|
||||||
|
"os"
|
||||||
)
|
)
|
||||||
|
|
||||||
// debug flag enables logging of debug messages to stderr.
|
|
||||||
var debug = flag.Bool("debug", true, "enable debug log messages to stderr")
|
|
||||||
|
|
||||||
func usage() {
|
|
||||||
fmt.Fprintf(os.Stderr, "Usage of %s:\n", os.Args[0])
|
|
||||||
fmt.Fprintf(os.Stderr, " %s BACKING MOUNTPOINT\n", os.Args[0])
|
|
||||||
flag.PrintDefaults()
|
|
||||||
}
|
|
||||||
|
|
||||||
func debugLog(msg interface{}) {
|
|
||||||
fmt.Fprintf(os.Stderr, "%v\n", msg)
|
|
||||||
}
|
|
||||||
|
|
||||||
func main() {
|
func main() {
|
||||||
flag.Usage = usage
|
// Parse command line arguments
|
||||||
flag.Parse()
|
conf, err := cluefs.ParseArguments()
|
||||||
|
|
||||||
if flag.NArg() != 2 {
|
|
||||||
usage()
|
|
||||||
os.Exit(2)
|
|
||||||
}
|
|
||||||
|
|
||||||
backing := flag.Arg(0)
|
|
||||||
mountpoint := flag.Arg(1)
|
|
||||||
c, err := fuse.Mount(
|
|
||||||
mountpoint,
|
|
||||||
fuse.FSName("gocryptfs"),
|
|
||||||
fuse.Subtype("gocryptfs"),
|
|
||||||
fuse.LocalVolume(),
|
|
||||||
fuse.VolumeName("gocryptfs"),
|
|
||||||
)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Fatal(err)
|
os.Exit(1)
|
||||||
}
|
|
||||||
defer c.Close()
|
|
||||||
|
|
||||||
cfg := &fs.Config{}
|
|
||||||
if *debug {
|
|
||||||
cfg.Debug = debugLog
|
|
||||||
}
|
}
|
||||||
|
|
||||||
srv := fs.New(c, cfg)
|
// Create the file system object
|
||||||
var key [16]byte
|
var key [16]byte
|
||||||
filesys := frontend.New(key, backing)
|
cfs := frontend.NewFS(key, conf.GetShadowDir())
|
||||||
|
|
||||||
if err := srv.Serve(filesys); err != nil {
|
// Mount and serve file system requests
|
||||||
log.Fatal(err)
|
if err = cfs.MountAndServe(conf.GetMountPoint(), conf.GetReadOnly()); err != nil {
|
||||||
|
cluefs.ErrlogMain.Printf("could not mount file system [%s]", err)
|
||||||
|
os.Exit(3)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Check if the mount process has an error to report.
|
// We are done
|
||||||
<-c.Ready
|
os.Exit(0)
|
||||||
if err := c.MountError; err != nil {
|
|
||||||
log.Fatal(err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
type MemFS struct {
|
|
||||||
root *Dir
|
|
||||||
nodeId uint64
|
|
||||||
|
|
||||||
nodeCount uint64
|
|
||||||
size int64
|
|
||||||
}
|
|
||||||
|
|
||||||
// Compile-time interface checks.
|
|
||||||
var _ fs.FS = (*MemFS)(nil)
|
|
||||||
var _ fs.FSStatfser = (*MemFS)(nil)
|
|
||||||
|
|
||||||
var _ fs.Node = (*Dir)(nil)
|
|
||||||
var _ fs.NodeCreater = (*Dir)(nil)
|
|
||||||
var _ fs.NodeMkdirer = (*Dir)(nil)
|
|
||||||
var _ fs.NodeRemover = (*Dir)(nil)
|
|
||||||
var _ fs.NodeRenamer = (*Dir)(nil)
|
|
||||||
var _ fs.NodeStringLookuper = (*Dir)(nil)
|
|
||||||
|
|
||||||
var _ fs.HandleReadAller = (*File)(nil)
|
|
||||||
var _ fs.HandleWriter = (*File)(nil)
|
|
||||||
var _ fs.Node = (*File)(nil)
|
|
||||||
var _ fs.NodeOpener = (*File)(nil)
|
|
||||||
var _ fs.NodeSetattrer = (*File)(nil)
|
|
||||||
|
|
||||||
func NewMemFS() *MemFS {
|
|
||||||
fs := &MemFS{
|
|
||||||
nodeCount: 1,
|
|
||||||
}
|
|
||||||
fs.root = fs.newDir(os.ModeDir | 0777)
|
|
||||||
if fs.root.attr.Inode != 1 {
|
|
||||||
panic("Root node should have been assigned id 1")
|
|
||||||
}
|
|
||||||
return fs
|
|
||||||
}
|
|
||||||
|
|
||||||
func (m *MemFS) nextId() uint64 {
|
|
||||||
return atomic.AddUint64(&m.nodeId, 1)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (m *MemFS) newDir(mode os.FileMode) *Dir {
|
|
||||||
n := time.Now()
|
|
||||||
return &Dir{
|
|
||||||
attr: fuse.Attr{
|
|
||||||
Inode: m.nextId(),
|
|
||||||
Atime: n,
|
|
||||||
Mtime: n,
|
|
||||||
Ctime: n,
|
|
||||||
Crtime: n,
|
|
||||||
Mode: os.ModeDir | mode,
|
|
||||||
},
|
|
||||||
fs: m,
|
|
||||||
nodes: make(map[string]fs.Node),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (m *MemFS) newFile(mode os.FileMode) *File {
|
|
||||||
n := time.Now()
|
|
||||||
return &File{
|
|
||||||
attr: fuse.Attr{
|
|
||||||
Inode: m.nextId(),
|
|
||||||
Atime: n,
|
|
||||||
Mtime: n,
|
|
||||||
Ctime: n,
|
|
||||||
Crtime: n,
|
|
||||||
Mode: mode,
|
|
||||||
},
|
|
||||||
data: make([]byte, 0),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
type Dir struct {
|
|
||||||
sync.RWMutex
|
|
||||||
attr fuse.Attr
|
|
||||||
|
|
||||||
fs *MemFS
|
|
||||||
parent *Dir
|
|
||||||
nodes map[string]fs.Node
|
|
||||||
}
|
|
||||||
|
|
||||||
type File struct {
|
|
||||||
sync.RWMutex
|
|
||||||
attr fuse.Attr
|
|
||||||
|
|
||||||
fs *MemFS
|
|
||||||
data []byte
|
|
||||||
}
|
|
||||||
|
|
||||||
func (f *MemFS) Root() (fs.Node, error) {
|
|
||||||
return f.root, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (f *MemFS) Statfs(ctx context.Context, req *fuse.StatfsRequest,
|
|
||||||
resp *fuse.StatfsResponse) error {
|
|
||||||
resp.Blocks = uint64((atomic.LoadInt64(&f.size) + 511) / 512)
|
|
||||||
resp.Bsize = 512
|
|
||||||
resp.Files = atomic.LoadUint64(&f.nodeCount)
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (f *File) Attr(ctx context.Context, o *fuse.Attr) error {
|
|
||||||
f.RLock()
|
|
||||||
*o = f.attr
|
|
||||||
f.RUnlock()
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (d *Dir) Attr(ctx context.Context, o *fuse.Attr) error {
|
|
||||||
d.RLock()
|
|
||||||
*o = d.attr
|
|
||||||
d.RUnlock()
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (d *Dir) Lookup(ctx context.Context, name string) (fs.Node, error) {
|
|
||||||
d.RLock()
|
|
||||||
n, exist := d.nodes[name]
|
|
||||||
d.RUnlock()
|
|
||||||
|
|
||||||
if !exist {
|
|
||||||
return nil, fuse.ENOENT
|
|
||||||
}
|
|
||||||
return n, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (d *Dir) ReadDirAll(ctx context.Context) ([]fuse.Dirent, error) {
|
|
||||||
d.RLock()
|
|
||||||
dirs := make([]fuse.Dirent, len(d.nodes)+2)
|
|
||||||
|
|
||||||
// Add special references.
|
|
||||||
dirs[0] = fuse.Dirent{
|
|
||||||
Name: ".",
|
|
||||||
Inode: d.attr.Inode,
|
|
||||||
Type: fuse.DT_Dir,
|
|
||||||
}
|
|
||||||
dirs[1] = fuse.Dirent{
|
|
||||||
Name: "..",
|
|
||||||
Type: fuse.DT_Dir,
|
|
||||||
}
|
|
||||||
if d.parent != nil {
|
|
||||||
dirs[1].Inode = d.parent.attr.Inode
|
|
||||||
} else {
|
|
||||||
dirs[1].Inode = d.attr.Inode
|
|
||||||
}
|
|
||||||
|
|
||||||
// Add remaining files.
|
|
||||||
idx := 2
|
|
||||||
for name, node := range d.nodes {
|
|
||||||
ent := fuse.Dirent{
|
|
||||||
Name: name,
|
|
||||||
}
|
|
||||||
switch n := node.(type) {
|
|
||||||
case *File:
|
|
||||||
ent.Inode = n.attr.Inode
|
|
||||||
ent.Type = fuse.DT_File
|
|
||||||
case *Dir:
|
|
||||||
ent.Inode = n.attr.Inode
|
|
||||||
ent.Type = fuse.DT_Dir
|
|
||||||
}
|
|
||||||
dirs[idx] = ent
|
|
||||||
idx++
|
|
||||||
}
|
|
||||||
d.RUnlock()
|
|
||||||
return dirs, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (d *Dir) Mkdir(ctx context.Context, req *fuse.MkdirRequest) (fs.Node, error) {
|
|
||||||
d.Lock()
|
|
||||||
defer d.Unlock()
|
|
||||||
|
|
||||||
if _, exists := d.nodes[req.Name]; exists {
|
|
||||||
return nil, fuse.EEXIST
|
|
||||||
}
|
|
||||||
|
|
||||||
n := d.fs.newDir(req.Mode)
|
|
||||||
d.nodes[req.Name] = n
|
|
||||||
atomic.AddUint64(&d.fs.nodeCount, 1)
|
|
||||||
|
|
||||||
return n, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (d *Dir) Create(ctx context.Context, req *fuse.CreateRequest,
|
|
||||||
resp *fuse.CreateResponse) (fs.Node, fs.Handle, error) {
|
|
||||||
d.Lock()
|
|
||||||
defer d.Unlock()
|
|
||||||
|
|
||||||
if _, exists := d.nodes[req.Name]; exists {
|
|
||||||
return nil, nil, fuse.EEXIST
|
|
||||||
}
|
|
||||||
|
|
||||||
n := d.fs.newFile(req.Mode)
|
|
||||||
n.fs = d.fs
|
|
||||||
d.nodes[req.Name] = n
|
|
||||||
atomic.AddUint64(&d.fs.nodeCount, 1)
|
|
||||||
|
|
||||||
resp.Attr = n.attr
|
|
||||||
|
|
||||||
return n, n, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (d *Dir) Rename(ctx context.Context, req *fuse.RenameRequest, newDir fs.Node) error {
|
|
||||||
nd := newDir.(*Dir)
|
|
||||||
if d.attr.Inode == nd.attr.Inode {
|
|
||||||
d.Lock()
|
|
||||||
defer d.Unlock()
|
|
||||||
} else if d.attr.Inode < nd.attr.Inode {
|
|
||||||
d.Lock()
|
|
||||||
defer d.Unlock()
|
|
||||||
nd.Lock()
|
|
||||||
defer nd.Unlock()
|
|
||||||
} else {
|
|
||||||
nd.Lock()
|
|
||||||
defer nd.Unlock()
|
|
||||||
d.Lock()
|
|
||||||
defer d.Unlock()
|
|
||||||
}
|
|
||||||
|
|
||||||
if _, exists := d.nodes[req.OldName]; !exists {
|
|
||||||
return fuse.ENOENT
|
|
||||||
}
|
|
||||||
|
|
||||||
// Rename can be used as an atomic replace, override an existing file.
|
|
||||||
if old, exists := nd.nodes[req.NewName]; exists {
|
|
||||||
atomic.AddUint64(&d.fs.nodeCount, ^uint64(0)) // decrement by one
|
|
||||||
if oldFile, ok := old.(*File); !ok {
|
|
||||||
atomic.AddInt64(&d.fs.size, -int64(oldFile.attr.Size))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
nd.nodes[req.NewName] = d.nodes[req.OldName]
|
|
||||||
delete(d.nodes, req.OldName)
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (d *Dir) Remove(ctx context.Context, req *fuse.RemoveRequest) error {
|
|
||||||
d.Lock()
|
|
||||||
defer d.Unlock()
|
|
||||||
|
|
||||||
if n, exists := d.nodes[req.Name]; !exists {
|
|
||||||
return fuse.ENOENT
|
|
||||||
} else if req.Dir && len(n.(*Dir).nodes) > 0 {
|
|
||||||
return fuse.ENOTEMPTY
|
|
||||||
}
|
|
||||||
|
|
||||||
delete(d.nodes, req.Name)
|
|
||||||
atomic.AddUint64(&d.fs.nodeCount, ^uint64(0)) // decrement by one
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (f *File) Open(ctx context.Context, req *fuse.OpenRequest, resp *fuse.OpenResponse) (fs.Handle,
|
|
||||||
error) {
|
|
||||||
return f, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (f *File) ReadAll(ctx context.Context) ([]byte, error) {
|
|
||||||
f.RLock()
|
|
||||||
out := make([]byte, len(f.data))
|
|
||||||
copy(out, f.data)
|
|
||||||
f.RUnlock()
|
|
||||||
|
|
||||||
return out, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (f *File) Write(ctx context.Context, req *fuse.WriteRequest, resp *fuse.WriteResponse) error {
|
|
||||||
f.Lock()
|
|
||||||
|
|
||||||
l := len(req.Data)
|
|
||||||
end := int(req.Offset) + l
|
|
||||||
if end > len(f.data) {
|
|
||||||
delta := end - len(f.data)
|
|
||||||
f.data = append(f.data, make([]byte, delta)...)
|
|
||||||
f.attr.Size = uint64(len(f.data))
|
|
||||||
atomic.AddInt64(&f.fs.size, int64(delta))
|
|
||||||
}
|
|
||||||
copy(f.data[req.Offset:end], req.Data)
|
|
||||||
resp.Size = l
|
|
||||||
|
|
||||||
f.Unlock()
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (f *File) Setattr(ctx context.Context, req *fuse.SetattrRequest,
|
|
||||||
resp *fuse.SetattrResponse) error {
|
|
||||||
f.Lock()
|
|
||||||
|
|
||||||
if req.Valid.Size() {
|
|
||||||
delta := int(req.Size) - len(f.data)
|
|
||||||
if delta > 0 {
|
|
||||||
f.data = append(f.data, make([]byte, delta)...)
|
|
||||||
} else {
|
|
||||||
f.data = f.data[0:req.Size]
|
|
||||||
}
|
|
||||||
f.attr.Size = req.Size
|
|
||||||
atomic.AddInt64(&f.fs.size, int64(delta))
|
|
||||||
}
|
|
||||||
|
|
||||||
if req.Valid.Mode() {
|
|
||||||
f.attr.Mode = req.Mode
|
|
||||||
}
|
|
||||||
|
|
||||||
if req.Valid.Atime() {
|
|
||||||
f.attr.Atime = req.Atime
|
|
||||||
}
|
|
||||||
|
|
||||||
if req.Valid.AtimeNow() {
|
|
||||||
f.attr.Atime = time.Now()
|
|
||||||
}
|
|
||||||
|
|
||||||
if req.Valid.Mtime() {
|
|
||||||
f.attr.Mtime = req.Mtime
|
|
||||||
}
|
|
||||||
|
|
||||||
if req.Valid.MtimeNow() {
|
|
||||||
f.attr.Mtime = time.Now()
|
|
||||||
}
|
|
||||||
|
|
||||||
resp.Attr = f.attr
|
|
||||||
|
|
||||||
f.Unlock()
|
|
||||||
return nil
|
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue