fs.go (3700B)
1 package client 2 3 import ( 4 "context" 5 "fmt" 6 "io" 7 "path" 8 "strings" 9 10 "git.mtkn.jp/lib9p" 11 ) 12 13 // FS represents the file system the client imports. 14 type FS struct { 15 c *Client 16 root *File 17 tPool *tagPool 18 fPool *filePool 19 } 20 21 // OpenFile opens the file named name in fsys with omode. 22 // If the file does not exist, it create it with perm. 23 // The flag bits which are not implemeted by the library are just ignored. 24 func (fsys *FS) OpenFile(name string, flag int) (lib9p.File, error) { 25 var ( 26 qid lib9p.Qid 27 iounit uint32 28 ) 29 f, err := fsys.walkFile(name) 30 if err != nil { 31 return nil, err 32 } else { 33 // File exists. Open it. 34 tag, err := fsys.tPool.add() 35 if err != nil { 36 return nil, err 37 } 38 qid, iounit, err = fsys.c.Open(tag, f.fid, lib9p.FlagToMode(flag)) 39 fsys.tPool.delete(tag) 40 if err != nil { 41 f.Close() 42 return nil, fmt.Errorf("open: %v", err) 43 } 44 } 45 f.omode = lib9p.FlagToMode(flag) 46 f.qid = qid 47 f.iounit = iounit 48 return f, nil 49 } 50 51 // CleanPath cleans name. 52 // It first call path.Clean(name) and then 53 // delete leading ".." elements. 54 func CleanPath(name string) string { 55 name = path.Clean(name) 56 for strings.HasPrefix(name, "../") { 57 name = name[3:] 58 } 59 if len(name) == 0 || name == "/" || name == ".." { 60 name = "." 61 } else if name[0] == '/' { 62 name = name[1:] 63 } 64 return name 65 } 66 67 // walkFile walks the file system to the file named name and 68 // returns the corresponding file. 69 // returned file is not open. 70 func (fsys *FS) walkFile(name string) (*File, error) { 71 f, err := fsys.fPool.add() 72 if err != nil { 73 return nil, fmt.Errorf("add file: %v", err) 74 } 75 var wname []string 76 if name != "." { 77 wname = strings.Split(CleanPath(name), "/") 78 } 79 tag, err := fsys.tPool.add() 80 if err != nil { 81 return nil, err 82 } 83 wqid, err := fsys.c.Walk(tag, fsys.root.fid, f.fid, wname) 84 fsys.tPool.delete(tag) 85 if err != nil { 86 return nil, fmt.Errorf("walk: %v", err) 87 } 88 if len(wqid) < len(wname) { 89 fsys.fPool.delete(f.fid) 90 return nil, fmt.Errorf("not found") 91 } 92 var qid lib9p.Qid 93 if name == "." { 94 qid = lib9p.Qid{} // TODO: ?? why it is not fsys.c.root.qid? 95 } else if len(wqid) > 0 { 96 qid = wqid[len(wqid)-1] 97 } else { 98 return nil, fmt.Errorf("invalid wqid: %v", wqid) 99 } 100 f.name = path.Base(name) 101 f.path = name 102 f.qid = qid 103 f.fs = fsys 104 return f, nil 105 } 106 107 // Mount initiates a 9P session and returns the resulting file system. 108 // The 9P session is established by writing to w and reading from r. 109 // When fs is not needed anymore, ctx should be canceled to stop the 110 // underlying client's goroutines. 111 // If non-nil error is returned, underlying client is stopped by this function, 112 // and there is no need to cancel ctx. 113 func Mount(ctx context.Context, r io.Reader, w io.Writer, uname, aname string) (fs *FS, err error) { 114 var ( 115 mSize uint32 = 8192 116 version = "9P2000" 117 ) 118 ctx0, cancel0 := context.WithCancel(ctx) 119 cfs := &FS{ 120 c: NewClient(ctx0, mSize, uname, r, w), 121 tPool: newTagPool(), 122 fPool: newFilePool(), 123 } 124 defer func() { 125 if err != nil { 126 cancel0() 127 } 128 }() 129 rmSize, rver, err := cfs.c.Version(lib9p.NOTAG, mSize, version) 130 if err != nil { 131 return nil, fmt.Errorf("version: %v", err) 132 } 133 if rver != version { 134 return nil, fmt.Errorf("incompatible version %s", rver) 135 } 136 if rmSize < mSize { 137 cfs.c.setMSize(rmSize) 138 } 139 // TODO: auth 140 f, err := cfs.fPool.add() 141 if err != nil { 142 return nil, fmt.Errorf("add file: %v", err) 143 } 144 tag, err := cfs.tPool.add() 145 if err != nil { 146 return nil, err 147 } 148 qid, err := cfs.c.Attach(tag, f.fid, lib9p.NOFID, uname, aname) 149 cfs.tPool.delete(tag) 150 if err != nil { 151 return nil, fmt.Errorf("attach: %v", err) 152 } 153 f.name = "." 154 f.path = "." 155 f.qid = qid 156 f.fs = cfs 157 cfs.root = f 158 return cfs, nil 159 }