Skip to content
Snippets Groups Projects
Code owners
Assign users and groups as approvers for specific file changes. Learn more.
dfsSolution.go 11.59 KiB
// memfs implements a simple in-memory file system.
// Copyright Pete Keleher

package main

/*
 Two main files are ../fuse.go and ../fs/serve.go
*/

import (
	"fmt"
	. "github.com/mattn/go-getopt"
	"log"
	"os"
	"os/signal"
	"time"

	"bazil.org/fuse"
	"bazil.org/fuse/fs"

	"golang.org/x/net/context"
)

/*
    Need to implement these types from bazil/fuse/fs!

    type FS interface {
	  // Root is called to obtain the Node for the file system root.
	  Root() (Node, error)
    }

    type Node interface {
	  // Attr fills attr with the standard metadata for the node.
	  Attr(ctx context.Context, attr *fuse.Attr) error
    }
*/

//=============================================================================

type Dfs struct{}

type DNode struct {
	nid   uint64
	name  string
	attr  fuse.Attr
	dirty bool
	kids  map[string]*DNode
	data  []uint8
}

var root *DNode
var nextInd uint64
var nodeMap = make(map[uint64]*DNode) // not currently queried
var debug = false
var mountPoint = "dss"
var conn *fuse.Conn
var uid = os.Geteuid()
var gid = os.Getegid()

var _ fs.Node = (*DNode)(nil)
var _ fs.FS = (*Dfs)(nil)

//=============================================================================

func p_out(s string, args ...interface{}) {
	if !debug {
		return
	}
	fmt.Printf("\t"+s, args...)
}

func p_err(s string, args ...interface{}) {
	fmt.Printf(s, args...)
}

func p_call(s string, args ...interface{}) {
	if !debug {
		return
	}
	fmt.Printf(s, args...)
}

func p_exit(s string, args ...interface{}) {
	fmt.Printf(s, args...)
	os.Exit(1)
}

//=============================================================================

func (Dfs) Root() (n fs.Node, err error) {
	p_call("func (Dfs) Root() (n fs.Node, err error) \n")
	p_out("root returns as %d\n", int(root.attr.Inode))
	return root, nil
}

//=============================================================================

func (n *DNode) isDir() bool {
	return (n.attr.Mode & os.ModeDir) != 0
}

func (n *DNode) fuseType() fuse.DirentType {
	if n.isDir() {
		return fuse.DT_Dir
	} else {
		return fuse.DT_File
	}
}

func (n *DNode) init(name string, mode os.FileMode) {
	nextInd++
	n.attr.Inode = nextInd
	n.attr.Nlink = 1
	n.name = name

	tm := time.Now()
	n.attr.Atime = tm
	n.attr.Mtime = tm
	n.attr.Ctime = tm
	n.attr.Crtime = tm
	n.attr.Mode = mode

	n.attr.Gid = uint32(gid)
	n.attr.Uid = uint32(uid)

	n.kids = make(map[string]*DNode)

	p_out("inited node inode %d, %q\n", nextInd, name)
}

func (n *DNode) Attr(ctx context.Context, attr *fuse.Attr) error {
	p_call("func (n *DNode) Attr(ctx context.Context, attr *fuse.Attr) error \n")
	p_out("Attr() on %q (%d)\n", n.name, n.attr.Inode)
	*attr = n.attr
	return nil
}

func (n *DNode) Lookup(ctx context.Context, name string) (fs.Node, error) {
	p_call("func (n *DNode) Lookup(ctx context.Context, name string) (fs.Node, error) \n")
	p_out("Lookup on %q from %q\n", name, n.name)

	if k, ok := n.kids[name]; ok {
		return k, nil
	}
	return nil, fuse.ENOENT
}

func (n *DNode) ReadDirAll(ctx context.Context) ([]fuse.Dirent, error) {
	p_call("func (n *DNode) ReadDirAll(ctx context.Context) ([]fuse.Dirent, error) \n")
	p_out("readdirall %q\n", n.name)
	dirs := make([]fuse.Dirent, 0, 10)
	for k, v := range n.kids {
		dirs = append(dirs, fuse.Dirent{Inode: v.attr.Inode, Name: k, Type: v.fuseType()})
	}
	return dirs, nil
}

func (n *DNode) Getattrff(ctx context.Context, req *fuse.GetattrRequest, resp *fuse.GetattrResponse) error {
	p_call("func (n *DNode) Getattr(ctx context.Context, req *fuse.GetattrRequest, resp *fuse.GetattrResponse) error \n")
	p_out("getattr '%s': %v\n", n.name, n.attr)
	resp.Attr = n.attr
	return nil
}

// must be defined or editing w/ vi or emacs fails. Doesn't have to do anything
func (n *DNode) Fsync(ctx context.Context, req *fuse.FsyncRequest) error {
	p_call("func (n *DNode) Fsync(ctx context.Context, req *fuse.FsyncRequest) error \n")
	p_out("FSYNC\n")
	return nil
}

func (n *DNode) Setattr(ctx context.Context, req *fuse.SetattrRequest, resp *fuse.SetattrResponse) error {
	p_call("func (n *DNode) Setattr(ctx context.Context, req *fuse.SetattrRequest, resp *fuse.SetattrResponse) error \n")
	p_out("SETATTR: %v\n", req)
	if fuse.SetattrValid.Mode(req.Valid) {
		p_out("SETTING Mode %v\n", req.Mode)
		n.attr.Mode = req.Mode
	}
	if fuse.SetattrValid.Uid(req.Valid) {
		p_out("SETTING Uid %v\n", req.Uid)
		n.attr.Uid = req.Uid
	}
	if fuse.SetattrValid.Gid(req.Valid) {
		p_out("SETTING Gid %v\n", req.Gid)
		n.attr.Gid = req.Gid
	}
	if fuse.SetattrValid.Size(req.Valid) {
		p_out("SETTING Size %v (TRUNCATE)\n", req.Size)
		n.attr.Size = req.Size
		n.data = n.data[:req.Size]
		//		n.attr.Mtime = time.Now()
		//		n.attr.Atime = n.attr.Mtime
	}
	if fuse.SetattrValid.Atime(req.Valid) {
		p_out("SETTING Atime %v\n", req.Atime)
		n.attr.Atime = req.Atime
	}
	if fuse.SetattrValid.Mtime(req.Valid) {
		p_out("SETTING Mtime %v\n", req.Mtime)
		n.attr.Mtime = req.Mtime
	}
	if fuse.SetattrValid.Handle(req.Valid) {
		p_out("SETTING handle, but don't have one: %v\n", req.Handle)
	}
	// if fuse.SetattrValid.AtimeNow(req.Valid) {
	// 	p_err("ERROR: n.attr.AtimeNow = req.AtimeNow\n")
	// }
	// if fuse.SetattrValid.MtimeNow(req.Valid) {
	// 	p_err("ERROR: n.attr.MtimeNow = req.MtimeNow\n")
	// }
	// if fuse.SetattrValid.LockOwner(req.Valid) {
	// 	p_err("ERROR: n.attr.LockOwner = req.LockOwner\n")
	// }
	if fuse.SetattrValid.Crtime(req.Valid) {
		p_out("SETTING Crtime %v\n", req.Crtime)
		n.attr.Crtime = req.Crtime
	}
	if fuse.SetattrValid.Chgtime(req.Valid) {
		p_out("SETTING n.attr.Chgtime = req.Chgtime: %v\n", req.Chgtime)
		n.attr.Ctime = req.Chgtime
	}
	if fuse.SetattrValid.Bkuptime(req.Valid) {
		p_exit("ERROR: n.attr.Bkuptime = req.Bkuptime: %v\n", req.Bkuptime)
	}
	if fuse.SetattrValid.Flags(req.Valid) {
		p_out("SETTING Flags %v\n", req.Flags)
		n.attr.Flags = req.Flags
	}

	resp.Attr = n.attr
	return nil
}

// func (n fs.Node) Open(ctx context.Context, req *fuse.OpenRequest, resp *fuse.OpenResponse) (fs.Handle, error){}
// func (n fs.Node) Release(ctx context.Context, req *fuse.ReleaseRequest) error {}
// func (n fs.Node) Removexattr(ctx context.Context, req *fuse.RemovexattrRequest) error {}
// func (n fs.Node) Setxattr(ctx context.Context, req *fuse.SetxattrRequest) error {}
// func (n fs.Node) Listxattr(ctx context.Context, req *fuse.ListxattrRequest, resp *fuse.ListxattrResponse) error {}
// func (n fs.Node) Getxattr(ctx context.Context, req *fuse.GetxattrRequest, resp *fuse.GetxattrResponse) error {}

func (p *DNode) Mkdir(ctx context.Context, req *fuse.MkdirRequest) (fs.Node, error) {
	p_call("func (p *DNode) Mkdir(ctx context.Context, req *fuse.MkdirRequest) (fs.Node, error) \n")
	p_out("mkdir %q in %q\n", req.Name, p.name)
	d := new(DNode)
	d.init(req.Name, os.ModeDir|0755)
	p.kids[req.Name] = d
	return d, nil
}

func (p *DNode) Create(ctx context.Context, req *fuse.CreateRequest, resp *fuse.CreateResponse) (fs.Node, fs.Handle, error) {
	p_call("func (p *DNode) Create(ctx context.Context, req *fuse.CreateRequest, resp *fuse.CreateResponse) (fs.Node, fs.Handle, error) \n")
	p_out("Create [%s]: (from %q) %q, mode %o\n", req, p.name, req.Name, req.Mode)
	n := new(DNode)
	n.init(req.Name, req.Mode)
	p.kids[req.Name] = n
	return n, n, nil
}

func (n *DNode) ReadAll(ctx context.Context) ([]byte, error) {
	p_call("func (n *DNode) ReadAll(ctx context.Context) ([]byte, error) \n")
	return n.data, nil
}

func (n *DNode) Write(ctx context.Context, req *fuse.WriteRequest, resp *fuse.WriteResponse) error {
	p_call("func (n *DNode) Write(ctx context.Context, req *fuse.WriteRequest, resp *fuse.WriteResponse) error \n")
	olen := uint64(len(n.data))
	wlen := uint64(len(req.Data))
	off := uint64(req.Offset)
	limit := off + wlen
	if olen != n.attr.Size {
		p_out("BAD SIZE MATCH %d %d\n", olen, n.attr.Size)
	}
	p_out("WRITING [%v] to %q, %d bytes, offset %d, oldlen %d\n",
		req, n.name, wlen, off, olen)

	if limit > olen {
		b := make([]byte, limit)
		var tocopy uint64
		if off < olen {
			tocopy = off
		} else {
			tocopy = olen
		}
		copy(b[0:tocopy], n.data[0:tocopy])
		n.data = b
		n.attr.Size = limit
	}
	copy(n.data[off:limit], req.Data[:])
	resp.Size = int(wlen)
	n.dirty = true
	return nil
}

func (n *DNode) Flush(ctx context.Context, req *fuse.FlushRequest) error {
	p_call("func (n *DNode) Flush(ctx context.Context, req *fuse.FlushRequest) error \n")
	p_out("flush [%v] %q (dirty: %t, now %d bytes)\n", req, n.name, n.dirty, len(n.data))
	if !n.dirty {
		return nil
	}
	n.attr.Atime = time.Now()
	n.attr.Mtime = n.attr.Atime
	n.dirty = false
	return nil
}

// hard links. Note that 'name' is not modified, so potential debugging problem.
func (p *DNode) Link(ctx context.Context, req *fuse.LinkRequest, oldNode fs.Node) (fs.Node, error) {
	p_call("func (p *DNode) Link(ctx context.Context, req *fuse.LinkRequest, oldNode fs.Node) (fs.Node, error) \n")
	p_out("Link Call   -- receiver id %d", p.nid)
	targetNode := oldNode.(*DNode)
	targetNode.attr.Nlink++
	p.kids[req.NewName] = targetNode
	return targetNode, nil
}

func (n *DNode) Remove(ctx context.Context, req *fuse.RemoveRequest) error {
	p_call("func (n *DNode) Remove(ctx context.Context, req *fuse.RemoveRequest) error \n")
	p_out("remove %q [name: %s, isdir %t]\n", n.name, req.Name, req.Dir)
	kid := n.kids[req.Name]
	if (kid != nil) && (len(kid.kids) != 0) {
		return fuse.EPERM
	} else {
		kid.attr.Nlink--
		delete(n.kids, req.Name)
	}
	return nil
}

func (p *DNode) Symlink(ctx context.Context, req *fuse.SymlinkRequest) (fs.Node, error) {
	p_call("func (p *DNode) Symlink(ctx context.Context, req *fuse.SymlinkRequest) (fs.Node, error) \n")
	p_out("Sym Link Call   -- receiver id %d, new name: %q, target name: %q\n", p.nid, req.NewName, req.Target)
	d := new(DNode)
	d.init(req.NewName, os.ModeSymlink|0777)
	p.kids[req.NewName] = d
	d.data = []byte(req.Target)
	d.dirty = true
	d.attr.Size = uint64(len(d.data))
	return d, nil
}

func (n *DNode) Readlink(ctx context.Context, req *fuse.ReadlinkRequest) (string, error) {
	p_call("func (n *DNode) Readlink(ctx context.Context, req *fuse.ReadlinkRequest) (string, error) \n")
	p_out("Read Link Call   -- receiver id %d\n", n.nid)
	s := string(n.data)
	p_out("Returned String  %q\n", s)
	return s, nil
}

func (n *DNode) Rename(ctx context.Context, req *fuse.RenameRequest, newDir fs.Node) error {
	p_call("func (n *DNode) Rename(ctx context.Context, req *fuse.RenameRequest, newDir fs.Node) error \n")
	p_out("RENAME (%s)%d, %q - %q, %d\n", req, int(n.attr.Inode), req.OldName, req.NewName, int(req.NewDir))
	if outDir, ok := newDir.(*DNode); ok {
		outDir.kids[req.NewName] = n.kids[req.OldName]
		outDir.kids[req.NewName].name = req.NewName
		delete(n.kids, req.OldName)
	}
	return nil
}

//=============================================================================

func main() {
	var c int

	for {
		if c = Getopt("dm:"); c == EOF {
			break
		}

		switch c {
		case 'd':
			debug = !debug
		case 'm':
			mountPoint = OptArg
		default:
			println("usage: main.go [-d | -m <mountpt>]", c)
			os.Exit(1)
		}
	}

	p_out("main\n")

	root = new(DNode)
	root.init("", os.ModeDir|0755)

	nodeMap[uint64(root.attr.Inode)] = root
	p_out("root inode %d\n", int(root.attr.Inode))

	if _, err := os.Stat(mountPoint); err != nil {
		os.Mkdir(mountPoint, 0755)
	}
	fuse.Unmount(mountPoint)
	conn, err := fuse.Mount(mountPoint, fuse.FSName("dssFS"), fuse.Subtype("project P1"),
		fuse.LocalVolume(), fuse.VolumeName("dssFS"))
	if err != nil {
		log.Fatal(err)
	}

	ch := make(chan os.Signal, 1)
	signal.Notify(ch, os.Interrupt, os.Kill)
	go func() {
		<-ch
		defer conn.Close()
		fuse.Unmount(mountPoint)
		os.Exit(1)
	}()

	err = fs.Serve(conn, Dfs{})
	p_out("AFTER\n")
	if err != nil {
		log.Fatal(err)
	}

	// check if the mount process has an error to report
	<-conn.Ready
	if err := conn.MountError; err != nil {
		log.Fatal(err)
	}
}