commit b1b8898e7cb788d8274eb8d4c9cca1fe380f505a from: Jeff Lindsay date: Wed Jun 21 00:56:40 2017 UTC ufs: added mostly working unix filesystem server session implementation Signed-off-by: Jeff Lindsay commit - 4a062b38f25cc43661f462ffffeb99456b98a64e commit + b1b8898e7cb788d8274eb8d4c9cca1fe380f505a 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 + ee80835ab698df40062bc48c3def67269779c739 (mode 644) --- /dev/null +++ ufs/fileref.go @@ -0,0 +1,33 @@ +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() + + 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 + ecab9cec2a80493fdd9c4cb4ce0c25c008f2eda2 (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 == 0 { + 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 == 0 { + 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.Stat(); 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()) +}