Blob


1 package ufs
3 import (
4 "context"
5 "io"
6 "io/ioutil"
7 "os"
8 "os/user"
9 "path/filepath"
10 "strconv"
11 "sync"
12 "syscall"
14 "git.omarpolo.com/go-p9p"
15 )
17 type session struct {
18 sync.Mutex
19 rootRef *FileRef
20 refs map[p9p.Fid]*FileRef
21 }
23 func NewSession(ctx context.Context, root string) (p9p.Session, error) {
24 return &session{
25 rootRef: &FileRef{Path: root},
26 refs: make(map[p9p.Fid]*FileRef),
27 }, nil
28 }
30 func (sess *session) getRef(fid p9p.Fid) (*FileRef, error) {
31 sess.Lock()
32 defer sess.Unlock()
34 if fid == p9p.NOFID {
35 return nil, p9p.ErrUnknownfid
36 }
38 ref, found := sess.refs[fid]
39 if !found {
40 return nil, p9p.ErrUnknownfid
41 }
43 if err := ref.Stat(); err != nil {
44 return nil, err
45 }
47 return ref, nil
48 }
50 func (sess *session) newRef(fid p9p.Fid, path string) (*FileRef, error) {
51 sess.Lock()
52 defer sess.Unlock()
54 if fid == p9p.NOFID {
55 return nil, p9p.ErrUnknownfid
56 }
58 _, found := sess.refs[fid]
59 if found {
60 return nil, p9p.ErrDupfid
61 }
63 ref := &FileRef{Path: path}
64 if err := ref.Stat(); err != nil {
65 return nil, err
66 }
68 sess.refs[fid] = ref
69 return ref, nil
70 }
72 func (sess *session) Auth(ctx context.Context, afid p9p.Fid, uname, aname string) (p9p.Qid, error) {
73 // TODO: AuthInit?
74 return p9p.Qid{}, nil //p9p.MessageRerror{Ename: "no auth"}
75 }
77 func (sess *session) Attach(ctx context.Context, fid, afid p9p.Fid, uname, aname string) (p9p.Qid, error) {
78 if uname == "" {
79 return p9p.Qid{}, p9p.MessageRerror{Ename: "no user"}
80 }
82 // TODO: AuthCheck?
84 // if afid > 0 {
85 // return p9p.Qid{}, p9p.MessageRerror{Ename: "attach: no auth"}
86 // }
88 if aname == "" {
89 aname = sess.rootRef.Path
90 }
92 ref, err := sess.newRef(fid, aname)
93 if err != nil {
94 return p9p.Qid{}, err
95 }
97 return ref.Info.Qid, nil
98 }
100 func (sess *session) Clunk(ctx context.Context, fid p9p.Fid) error {
101 ref, err := sess.getRef(fid)
102 if err != nil {
103 return err
106 ref.Lock()
107 defer ref.Unlock()
108 if ref.File != nil {
109 ref.File.Close()
112 sess.Lock()
113 defer sess.Unlock()
114 delete(sess.refs, fid)
116 return nil
119 func (sess *session) Remove(ctx context.Context, fid p9p.Fid) error {
120 defer sess.Clunk(ctx, fid)
122 ref, err := sess.getRef(fid)
123 if err != nil {
124 return err
127 // TODO: check write perms on parent
129 return os.Remove(ref.Path)
132 func (sess *session) Walk(ctx context.Context, fid p9p.Fid, newfid p9p.Fid, names ...string) ([]p9p.Qid, error) {
133 var qids []p9p.Qid
135 ref, err := sess.getRef(fid)
136 if err != nil {
137 return qids, err
140 newref, err := sess.newRef(newfid, ref.Path)
141 if err != nil {
142 return qids, err
145 path := newref.Path
146 for _, name := range names {
147 newpath := filepath.Join(path, name)
148 r := &FileRef{Path: newpath}
149 if err := r.Stat(); err != nil {
150 break
152 qids = append(qids, r.Info.Qid)
153 path = newpath
156 newref.Path = path
157 return qids, nil
160 func (sess *session) Read(ctx context.Context, fid p9p.Fid, p []byte, offset int64) (n int, err error) {
161 ref, err := sess.getRef(fid)
162 if err != nil {
163 return 0, err
166 ref.Lock()
167 defer ref.Unlock()
169 if ref.IsDir() {
170 if offset == 0 && ref.Readdir == nil {
171 files, err := ioutil.ReadDir(ref.Path)
172 if err != nil {
173 return 0, err
175 var dirs []p9p.Dir
176 for _, info := range files {
177 dirs = append(dirs, dirFromInfo(info))
179 ref.Readdir = p9p.NewFixedReaddir(p9p.NewCodec(), dirs)
181 if ref.Readdir == nil {
182 return 0, p9p.ErrBadoffset
184 return ref.Readdir.Read(ctx, p, offset)
187 if ref.File == nil {
188 return 0, p9p.MessageRerror{Ename: "no file open"} //p9p.ErrClosed
191 n, err = ref.File.ReadAt(p, offset)
192 if err != nil && err != io.EOF {
193 return n, err
195 return n, nil
198 func (sess *session) Write(ctx context.Context, fid p9p.Fid, p []byte, offset int64) (n int, err error) {
199 ref, err := sess.getRef(fid)
200 if err != nil {
201 return 0, err
204 ref.Lock()
205 defer ref.Unlock()
206 if ref.File == nil {
207 return 0, p9p.ErrClosed
210 return ref.File.WriteAt(p, offset)
213 func (sess *session) Open(ctx context.Context, fid p9p.Fid, mode p9p.Flag) (p9p.Qid, uint32, error) {
214 ref, err := sess.getRef(fid)
215 if err != nil {
216 return p9p.Qid{}, 0, err
219 ref.Lock()
220 defer ref.Unlock()
221 f, err := os.OpenFile(ref.Path, oflags(mode), 0)
222 if err != nil {
223 return p9p.Qid{}, 0, err
225 ref.File = f
226 return ref.Info.Qid, 0, nil
229 func (sess *session) Create(ctx context.Context, parent p9p.Fid, name string, perm uint32, mode p9p.Flag) (p9p.Qid, uint32, error) {
230 ref, err := sess.getRef(parent)
231 if err != nil {
232 return p9p.Qid{}, 0, err
235 newpath := filepath.Join(ref.Path, name)
237 var file *os.File
238 switch {
239 case perm&p9p.DMDIR != 0:
240 err = os.Mkdir(newpath, os.FileMode(perm&0777))
242 case perm&p9p.DMSYMLINK != 0:
243 case perm&p9p.DMNAMEDPIPE != 0:
244 case perm&p9p.DMDEVICE != 0:
245 err = p9p.MessageRerror{Ename: "not implemented"}
247 default:
248 file, err = os.OpenFile(newpath, oflags(mode)|os.O_CREATE, os.FileMode(perm&0777))
251 if file == nil && err == nil {
252 file, err = os.OpenFile(newpath, oflags(mode), 0)
255 if err != nil {
256 return p9p.Qid{}, 0, err
259 ref.Lock()
260 defer ref.Unlock()
261 ref.Path = newpath
262 ref.File = file
263 if err := ref.statLocked(); err != nil {
264 return p9p.Qid{}, 0, err
266 return ref.Info.Qid, 0, err
269 func (sess *session) Stat(ctx context.Context, fid p9p.Fid) (p9p.Dir, error) {
270 ref, err := sess.getRef(fid)
271 if err != nil {
272 return p9p.Dir{}, err
274 return ref.Info, nil
277 func (sess *session) WStat(ctx context.Context, fid p9p.Fid, dir p9p.Dir) error {
278 ref, err := sess.getRef(fid)
279 if err != nil {
280 return err
283 if dir.Mode != ^uint32(0) {
284 // TODO: 9P2000.u: DMSETUID DMSETGID
285 err := os.Chmod(ref.Path, os.FileMode(dir.Mode&0777))
286 if err != nil {
287 return err
291 if dir.UID != "" || dir.GID != "" {
292 usr, err := user.Lookup(dir.UID)
293 if err != nil {
294 return err
296 uid, err := strconv.Atoi(usr.Uid)
297 if err != nil {
298 return err
300 grp, err := user.LookupGroup(dir.GID)
301 if err != nil {
302 return err
304 gid, err := strconv.Atoi(grp.Gid)
305 if err != nil {
306 return err
308 if err := os.Chown(ref.Path, uid, gid); err != nil {
309 return err
313 if dir.Name != "" {
314 newpath := filepath.Join(filepath.Dir(ref.Path), dir.Name)
315 if err := syscall.Rename(ref.Path, newpath); err != nil {
316 return nil
318 ref.Lock()
319 defer ref.Unlock()
320 ref.Path = newpath
323 if dir.Length != ^uint64(0) {
324 if err := os.Truncate(ref.Path, int64(dir.Length)); err != nil {
325 return err
329 // If either mtime or atime need to be changed, then
330 // we must change both.
331 //if dir.ModTime != time.Time{} || dir.AccessTime != ^uint32(0) {
332 // mt, at := time.Unix(int64(dir.Mtime), 0), time.Unix(int64(dir.Atime), 0)
333 // if cmt, cat := (dir.Mtime == ^uint32(0)), (dir.Atime == ^uint32(0)); cmt || cat {
334 // st, e := os.Stat(fid.path)
335 // if e != nil {
336 // req.RespondError(toError(e))
337 // return
338 // }
339 // switch cmt {
340 // case true:
341 // mt = st.ModTime()
342 // default:
343 // at = atime(st.Sys().(*syscall.Stat_t))
344 // }
345 // }
346 // e := os.Chtimes(fid.path, at, mt)
347 // if e != nil {
348 // req.RespondError(toError(e))
349 // return
350 // }
351 //}
352 return nil
355 func (sess *session) Version() (msize int, version string) {
356 return p9p.DefaultMSize, p9p.DefaultVersion