Commit Diff


commit - a96bd10f7db2c3ccbd86de2ef0a73d8531320d76
commit + 64abb42af527ed0dacbef777a5dbe36e3b1147f0
blob - 44c512ac143eeb14592a939c94a09b1c288a22bf
blob + 34896bc6282b030c3e75848c5a945c2ead6b25be
--- cmd/9ps/main.go
+++ cmd/9ps/main.go
@@ -7,6 +7,7 @@ import (
 	"strings"
 
 	"github.com/docker/go-p9p"
+	"github.com/docker/go-p9p/ufs"
 	"golang.org/x/net/context"
 )
 
@@ -49,7 +50,7 @@ func main() {
 
 			ctx := context.WithValue(ctx, "conn", conn)
 			log.Println("connected", conn.RemoteAddr())
-			session, err := newLocalSession(ctx, root)
+			session, err := ufs.NewSession(ctx, root)
 			if err != nil {
 				log.Println("error creating session")
 				return
@@ -61,21 +62,3 @@ func main() {
 		}(c)
 	}
 }
-
-// newLocalSession returns a session to serve the local filesystem, restricted
-// to the provided root.
-func newLocalSession(ctx context.Context, root string) (p9p.Session, error) {
-	// silly, just connect to ufs for now! replace this with real code later!
-	log.Println("dialing", ":5640", "for", ctx.Value("conn"))
-	conn, err := net.Dial("tcp", ":5640")
-	if err != nil {
-		return nil, err
-	}
-
-	session, err := p9p.NewSession(ctx, conn)
-	if err != nil {
-		return nil, err
-	}
-
-	return session, nil
-}
blob - /dev/null
blob + 143cad902fed1b41ba8d96d840ddde3d2092887d (mode 644)
--- /dev/null
+++ ufs/fileref.go
@@ -0,0 +1,36 @@
+package ufs
+
+import (
+	"os"
+	"sync"
+
+	p9p "github.com/docker/go-p9p"
+)
+
+type FileRef struct {
+	sync.Mutex
+	Path    string
+	Info    p9p.Dir
+	File    *os.File
+	Readdir *p9p.Readdir
+}
+
+func (f *FileRef) Stat() error {
+	f.Lock()
+	defer f.Unlock()
+	return f.statLocked()
+}
+
+func (f *FileRef) statLocked() error {
+	info, err := os.Lstat(f.Path)
+	if err != nil {
+		return err
+	}
+
+	f.Info = dirFromInfo(info)
+	return nil
+}
+
+func (f *FileRef) IsDir() bool {
+	return f.Info.Mode&p9p.DMDIR > 0
+}
blob - /dev/null
blob + 5fd27ee2d96390c6e5a693acc20dd40c19ed4f63 (mode 644)
--- /dev/null
+++ ufs/session.go
@@ -0,0 +1,357 @@
+package ufs
+
+import (
+	"context"
+	"io"
+	"io/ioutil"
+	"os"
+	"os/user"
+	"path/filepath"
+	"strconv"
+	"sync"
+	"syscall"
+
+	"github.com/docker/go-p9p"
+)
+
+type session struct {
+	sync.Mutex
+	rootRef *FileRef
+	refs    map[p9p.Fid]*FileRef
+}
+
+func NewSession(ctx context.Context, root string) (p9p.Session, error) {
+	return &session{
+		rootRef: &FileRef{Path: root},
+		refs:    make(map[p9p.Fid]*FileRef),
+	}, nil
+}
+
+func (sess *session) getRef(fid p9p.Fid) (*FileRef, error) {
+	sess.Lock()
+	defer sess.Unlock()
+
+	if fid == p9p.NOFID {
+		return nil, p9p.ErrUnknownfid
+	}
+
+	ref, found := sess.refs[fid]
+	if !found {
+		return nil, p9p.ErrUnknownfid
+	}
+
+	if err := ref.Stat(); err != nil {
+		return nil, err
+	}
+
+	return ref, nil
+}
+
+func (sess *session) newRef(fid p9p.Fid, path string) (*FileRef, error) {
+	sess.Lock()
+	defer sess.Unlock()
+
+	if fid == p9p.NOFID {
+		return nil, p9p.ErrUnknownfid
+	}
+
+	_, found := sess.refs[fid]
+	if found {
+		return nil, p9p.ErrDupfid
+	}
+
+	ref := &FileRef{Path: path}
+	if err := ref.Stat(); err != nil {
+		return nil, err
+	}
+
+	sess.refs[fid] = ref
+	return ref, nil
+}
+
+func (sess *session) Auth(ctx context.Context, afid p9p.Fid, uname, aname string) (p9p.Qid, error) {
+	// TODO: AuthInit?
+	return p9p.Qid{}, nil //p9p.MessageRerror{Ename: "no auth"}
+}
+
+func (sess *session) Attach(ctx context.Context, fid, afid p9p.Fid, uname, aname string) (p9p.Qid, error) {
+	if uname == "" {
+		return p9p.Qid{}, p9p.MessageRerror{Ename: "no user"}
+	}
+
+	// TODO: AuthCheck?
+
+	// if afid > 0 {
+	// 	return p9p.Qid{}, p9p.MessageRerror{Ename: "attach: no auth"}
+	// }
+
+	if aname == "" {
+		aname = sess.rootRef.Path
+	}
+
+	ref, err := sess.newRef(fid, aname)
+	if err != nil {
+		return p9p.Qid{}, err
+	}
+
+	return ref.Info.Qid, nil
+}
+
+func (sess *session) Clunk(ctx context.Context, fid p9p.Fid) error {
+	ref, err := sess.getRef(fid)
+	if err != nil {
+		return err
+	}
+
+	ref.Lock()
+	defer ref.Unlock()
+	if ref.File != nil {
+		ref.File.Close()
+	}
+
+	sess.Lock()
+	defer sess.Unlock()
+	delete(sess.refs, fid)
+
+	return nil
+}
+
+func (sess *session) Remove(ctx context.Context, fid p9p.Fid) error {
+	defer sess.Clunk(ctx, fid)
+
+	ref, err := sess.getRef(fid)
+	if err != nil {
+		return err
+	}
+
+	// TODO: check write perms on parent
+
+	return os.Remove(ref.Path)
+}
+
+func (sess *session) Walk(ctx context.Context, fid p9p.Fid, newfid p9p.Fid, names ...string) ([]p9p.Qid, error) {
+	var qids []p9p.Qid
+
+	ref, err := sess.getRef(fid)
+	if err != nil {
+		return qids, err
+	}
+
+	newref, err := sess.newRef(newfid, ref.Path)
+	if err != nil {
+		return qids, err
+	}
+
+	path := newref.Path
+	for _, name := range names {
+		newpath := filepath.Join(path, name)
+		r := &FileRef{Path: newpath}
+		if err := r.Stat(); err != nil {
+			break
+		}
+		qids = append(qids, r.Info.Qid)
+		path = newpath
+	}
+
+	newref.Path = path
+	return qids, nil
+}
+
+func (sess *session) Read(ctx context.Context, fid p9p.Fid, p []byte, offset int64) (n int, err error) {
+	ref, err := sess.getRef(fid)
+	if err != nil {
+		return 0, err
+	}
+
+	ref.Lock()
+	defer ref.Unlock()
+
+	if ref.IsDir() {
+		if offset == 0 && ref.Readdir == nil {
+			files, err := ioutil.ReadDir(ref.Path)
+			if err != nil {
+				return 0, err
+			}
+			var dirs []p9p.Dir
+			for _, info := range files {
+				dirs = append(dirs, dirFromInfo(info))
+			}
+			ref.Readdir = p9p.NewFixedReaddir(p9p.NewCodec(), dirs)
+		}
+		if ref.Readdir == nil {
+			return 0, p9p.ErrBadoffset
+		}
+		return ref.Readdir.Read(ctx, p, offset)
+	}
+
+	if ref.File == nil {
+		return 0, p9p.MessageRerror{Ename: "no file open"} //p9p.ErrClosed
+	}
+
+	n, err = ref.File.ReadAt(p, offset)
+	if err != nil && err != io.EOF {
+		return n, err
+	}
+	return n, nil
+}
+
+func (sess *session) Write(ctx context.Context, fid p9p.Fid, p []byte, offset int64) (n int, err error) {
+	ref, err := sess.getRef(fid)
+	if err != nil {
+		return 0, err
+	}
+
+	ref.Lock()
+	defer ref.Unlock()
+	if ref.File == nil {
+		return 0, p9p.ErrClosed
+	}
+
+	return ref.File.WriteAt(p, offset)
+}
+
+func (sess *session) Open(ctx context.Context, fid p9p.Fid, mode p9p.Flag) (p9p.Qid, uint32, error) {
+	ref, err := sess.getRef(fid)
+	if err != nil {
+		return p9p.Qid{}, 0, err
+	}
+
+	ref.Lock()
+	defer ref.Unlock()
+	f, err := os.OpenFile(ref.Path, oflags(mode), 0)
+	if err != nil {
+		return p9p.Qid{}, 0, err
+	}
+	ref.File = f
+	return ref.Info.Qid, 0, nil
+}
+
+func (sess *session) Create(ctx context.Context, parent p9p.Fid, name string, perm uint32, mode p9p.Flag) (p9p.Qid, uint32, error) {
+	ref, err := sess.getRef(parent)
+	if err != nil {
+		return p9p.Qid{}, 0, err
+	}
+
+	newpath := filepath.Join(ref.Path, name)
+
+	var file *os.File
+	switch {
+	case perm&p9p.DMDIR != 0:
+		err = os.Mkdir(newpath, os.FileMode(perm&0777))
+
+	case perm&p9p.DMSYMLINK != 0:
+	case perm&p9p.DMNAMEDPIPE != 0:
+	case perm&p9p.DMDEVICE != 0:
+		err = p9p.MessageRerror{Ename: "not implemented"}
+
+	default:
+		file, err = os.OpenFile(newpath, oflags(mode)|os.O_CREATE, os.FileMode(perm&0777))
+	}
+
+	if file == nil && err == nil {
+		file, err = os.OpenFile(newpath, oflags(mode), 0)
+	}
+
+	if err != nil {
+		return p9p.Qid{}, 0, err
+	}
+
+	ref.Lock()
+	defer ref.Unlock()
+	ref.Path = newpath
+	ref.File = file
+	if err := ref.statLocked(); err != nil {
+		return p9p.Qid{}, 0, err
+	}
+	return ref.Info.Qid, 0, err
+}
+
+func (sess *session) Stat(ctx context.Context, fid p9p.Fid) (p9p.Dir, error) {
+	ref, err := sess.getRef(fid)
+	if err != nil {
+		return p9p.Dir{}, err
+	}
+	return ref.Info, nil
+}
+
+func (sess *session) WStat(ctx context.Context, fid p9p.Fid, dir p9p.Dir) error {
+	ref, err := sess.getRef(fid)
+	if err != nil {
+		return err
+	}
+
+	if dir.Mode != ^uint32(0) {
+		// TODO: 9P2000.u: DMSETUID DMSETGID
+		err := os.Chmod(ref.Path, os.FileMode(dir.Mode&0777))
+		if err != nil {
+			return err
+		}
+	}
+
+	if dir.UID != "" || dir.GID != "" {
+		usr, err := user.Lookup(dir.UID)
+		if err != nil {
+			return err
+		}
+		uid, err := strconv.Atoi(usr.Uid)
+		if err != nil {
+			return err
+		}
+		grp, err := user.LookupGroup(dir.GID)
+		if err != nil {
+			return err
+		}
+		gid, err := strconv.Atoi(grp.Gid)
+		if err != nil {
+			return err
+		}
+		if err := os.Chown(ref.Path, uid, gid); err != nil {
+			return err
+		}
+	}
+
+	if dir.Name != "" {
+		newpath := filepath.Join(filepath.Dir(ref.Path), dir.Name)
+		if err := syscall.Rename(ref.Path, newpath); err != nil {
+			return nil
+		}
+		ref.Lock()
+		defer ref.Unlock()
+		ref.Path = newpath
+	}
+
+	if dir.Length != ^uint64(0) {
+		if err := os.Truncate(ref.Path, int64(dir.Length)); err != nil {
+			return err
+		}
+	}
+
+	// If either mtime or atime need to be changed, then
+	// we must change both.
+	//if dir.ModTime != time.Time{} || dir.AccessTime != ^uint32(0) {
+	// mt, at := time.Unix(int64(dir.Mtime), 0), time.Unix(int64(dir.Atime), 0)
+	// if cmt, cat := (dir.Mtime == ^uint32(0)), (dir.Atime == ^uint32(0)); cmt || cat {
+	// 	st, e := os.Stat(fid.path)
+	// 	if e != nil {
+	// 		req.RespondError(toError(e))
+	// 		return
+	// 	}
+	// 	switch cmt {
+	// 	case true:
+	// 		mt = st.ModTime()
+	// 	default:
+	// 		at = atime(st.Sys().(*syscall.Stat_t))
+	// 	}
+	// }
+	// e := os.Chtimes(fid.path, at, mt)
+	// if e != nil {
+	// 	req.RespondError(toError(e))
+	// 	return
+	// }
+	//}
+	return nil
+}
+
+func (sess *session) Version() (msize int, version string) {
+	return p9p.DefaultMSize, p9p.DefaultVersion
+}
blob - /dev/null
blob + e20fd5bedcc5dc73923443f22ee84a73eb6e8758 (mode 644)
--- /dev/null
+++ ufs/util.go
@@ -0,0 +1,57 @@
+package ufs
+
+import (
+	"os"
+	"syscall"
+
+	p9p "github.com/docker/go-p9p"
+)
+
+func dirFromInfo(info os.FileInfo) p9p.Dir {
+	dir := p9p.Dir{}
+
+	dir.Qid.Path = info.Sys().(*syscall.Stat_t).Ino
+	dir.Qid.Version = uint32(info.ModTime().UnixNano() / 1000000)
+
+	dir.Name = info.Name()
+	dir.Mode = uint32(info.Mode() & 0777)
+	dir.Length = uint64(info.Size())
+	dir.AccessTime = atime(info.Sys().(*syscall.Stat_t))
+	dir.ModTime = info.ModTime()
+	dir.MUID = "none"
+
+	if info.IsDir() {
+		dir.Qid.Type |= p9p.QTDIR
+		dir.Mode |= p9p.DMDIR
+	}
+
+	return dir
+}
+
+func oflags(mode p9p.Flag) int {
+	flags := 0
+
+	switch mode & 3 {
+	case p9p.OREAD:
+		flags = os.O_RDONLY
+		break
+
+	case p9p.ORDWR:
+		flags = os.O_RDWR
+		break
+
+	case p9p.OWRITE:
+		flags = os.O_WRONLY
+		break
+
+	case p9p.OEXEC:
+		flags = os.O_RDONLY
+		break
+	}
+
+	if mode&p9p.OTRUNC != 0 {
+		flags |= os.O_TRUNC
+	}
+
+	return flags
+}
blob - /dev/null
blob + 1dc7d8d0307dc888c3a0fafe687520e60d0f1036 (mode 644)
--- /dev/null
+++ ufs/util_darwin.go
@@ -0,0 +1,11 @@
+// +build darwin
+package ufs
+
+import (
+	"syscall"
+	"time"
+)
+
+func atime(stat *syscall.Stat_t) time.Time {
+	return time.Unix(stat.Atimespec.Unix())
+}
blob - /dev/null
blob + fa10e156e8e9fe523e8b3ae2cbc51e9eacc47dfa (mode 644)
--- /dev/null
+++ ufs/util_linux.go
@@ -0,0 +1,11 @@
+// +build linux
+package ufs
+
+import (
+	"syscall"
+	"time"
+)
+
+func atime(stat *syscall.Stat_t) time.Time {
+	return time.Unix(stat.Atim.Unix())
+}