fs_test.go (6766B)
1 package client 2 3 import ( 4 "bytes" 5 "fmt" 6 "io" 7 "io/fs" 8 "path" 9 "strings" 10 "time" 11 12 "git.mtkn.jp/lib9p" 13 "git.mtkn.jp/lib9p/testdata" 14 ) 15 16 type testFile struct { 17 // fsys is the testFS this testFile belongs to. 18 fsys *testFS 19 // parent is the parent directory this testFile located at. 20 parent *testFile 21 // children is the child files this testFile has. 22 // It is nil if this testFile is not a directory 23 children []*testFile 24 // content is the content of this testFile. 25 // It is nil if this testFile is a directory 26 content []byte 27 // r is used if this testFile is not a directory and 28 // when this testFile is read. 29 // It contains content. 30 r *bytes.Reader 31 offset int64 32 // stat is the Stat of this testFile. 33 stat lib9p.Stat 34 // closec is used by TestClose to check if *File.Close from the client 35 // invokes *testFile.Close at the server side. 36 closec chan struct{} 37 } 38 39 func (f *testFile) Stat() (*lib9p.FileInfo, error) { 40 return &lib9p.FileInfo{Stat: f.stat}, nil 41 } 42 43 func (f *testFile) Close() error { 44 if f.closec != nil { 45 close(f.closec) 46 } 47 f.closec = nil 48 f.r = nil 49 return nil 50 } 51 52 func (f *testFile) Read(b []byte) (int, error) { 53 if f.fsys.slow { 54 f.fsys.waitc <- struct{}{} 55 <-f.fsys.waitc 56 } 57 if f.r == nil { 58 f.r = bytes.NewReader(f.content) 59 } 60 n, err := f.r.ReadAt(b, f.offset) 61 f.offset += int64(n) 62 return n, err 63 } 64 65 func (f *testFile) ReadAt(b []byte, off int64) (n int, err error) { 66 if f.fsys.slow { 67 f.fsys.waitc <- struct{}{} 68 <-f.fsys.waitc 69 } 70 // ReadAt shoul not affect nor be affected by the underlying seek offset. 71 return f.r.ReadAt(b, off) 72 } 73 74 func (f *testFile) ReadDir(n int) ([]fs.DirEntry, error) { 75 de := make([]fs.DirEntry, len(f.children)) 76 for i, c := range f.children { 77 info, _ := c.Stat() 78 de[i] = info 79 } 80 return de, nil 81 } 82 83 func (f *testFile) WriteAt(p []byte, off int64) (int, error) { 84 if f.fsys.slow { 85 f.fsys.waitc <- struct{}{} 86 } 87 if off < 0 || off > int64(len(f.content)) { 88 return 0, fmt.Errorf("bad offset") 89 } 90 if off+int64(len(p)) > int64(len(f.content)) { 91 newcon := make([]byte, off+int64(len(p))) 92 copy(newcon, f.content) 93 f.content = newcon 94 } 95 copy(f.content[off:], p) 96 if f.r != nil { 97 f.r.Reset(f.content) 98 f.r.Seek(f.offset, io.SeekStart) 99 } 100 if f.fsys.slow { 101 <-f.fsys.waitc 102 } 103 return len(p), nil 104 } 105 106 func (f *testFile) WStat(stat *lib9p.Stat) error { 107 f.stat = *stat 108 return nil 109 } 110 111 type testFS struct { 112 // root is the root testFile of this file system. 113 root *testFile 114 // If slow is true, each function on files of this testFS waits for 115 // something arrives on waitc. 116 slow bool 117 nextQid uint64 118 // waitc is used to wait some timing to emulate slow response. 119 waitc chan struct{} 120 // groups holds the information of groups and it leader and members. 121 groups map[string]group 122 } 123 124 type group struct { 125 leader string 126 members map[string]bool 127 } 128 129 func (fs *testFS) OpenFile(path string, _ int) (lib9p.File, error) { 130 wnames := strings.Split(path, "/") 131 f, err := fs.walk(wnames) 132 if err != nil { 133 return nil, fmt.Errorf("walk: %v", err) 134 } 135 f.r = bytes.NewReader(f.content) 136 return f, nil 137 } 138 139 func (fs *testFS) IsGroupLeader(gid, uid string) bool { 140 g, ok := fs.groups[gid] 141 if !ok { 142 return false 143 } 144 return g.leader == uid 145 } 146 147 func (fs *testFS) IsGroupMember(gid, uid string) bool { 148 g, ok := fs.groups[gid] 149 if !ok { 150 return false 151 } 152 return g.members[uid] 153 } 154 155 // Permission check is intentionally skipped. 156 func (fs *testFS) Create(name string, uid string, mode lib9p.OpenMode, perm lib9p.FileMode) (lib9p.File, error) { 157 dirname, filename := path.Split(name) 158 if dirname == "" && (filename == "." || filename == "") { 159 return nil, fmt.Errorf("cant create root") 160 } 161 dir, err := fs.walkPath(path.Dir(dirname)) 162 if err != nil { 163 return nil, fmt.Errorf("not found") 164 } 165 for _, c := range dir.children { 166 if c.stat.Name == filename { 167 return nil, fmt.Errorf("file already exists") 168 } 169 } 170 f := &testFile{ 171 fsys: fs, 172 parent: dir, 173 stat: lib9p.Stat{ 174 Qid: lib9p.Qid{ 175 Type: lib9p.FSModeToQidType(perm), 176 Path: fs.nextQid, 177 }, 178 Mode: perm, 179 Atime: uint32(time.Now().Unix()), 180 Mtime: uint32(time.Now().Unix()), 181 Name: filename, 182 Uid: uid, 183 Gid: dir.stat.Gid, 184 Muid: uid, 185 }, 186 } 187 fs.nextQid++ 188 dir.children = append(dir.children, f) 189 return f, nil 190 } 191 192 func (fs *testFS) Remove(name string) error { 193 ds, filename := path.Split(name) 194 dirname := path.Dir(ds) 195 dir, err := fs.walkPath(dirname) 196 if err != nil { 197 return fmt.Errorf("not found") 198 } 199 for i, c := range dir.children { 200 if c.stat.Name == filename { 201 if len(c.children) != 0 { 202 return fmt.Errorf("directory not empty") 203 } 204 dir.children = append(dir.children[:i], dir.children[i+1:]...) 205 // TODO: should close the file? 206 return nil 207 } 208 } 209 return fmt.Errorf("not found") 210 } 211 212 func (fs *testFS) walk(wnames []string) (*testFile, error) { 213 if len(wnames) == 1 && (wnames[0] == "." || wnames[0] == "") { 214 return fs.root, nil 215 } 216 cwd := fs.root 217 L: 218 for i, name := range wnames { 219 for _, child := range cwd.children { 220 if child.stat.Name == name { 221 cwd = child 222 continue L 223 } 224 } 225 return nil, fmt.Errorf("%s not found", strings.Join(wnames[:i+1], "/")) 226 } 227 return cwd, nil 228 } 229 230 func (fs *testFS) walkPath(pathname string) (*testFile, error) { 231 wnames := strings.Split(pathname, "/") 232 return fs.walk(wnames) 233 } 234 235 var testfs *testFS 236 237 func init() { 238 fileTree := testdata.FileTree 239 testfs = new(testFS) 240 for i, f := range fileTree { 241 var ff *testFile 242 if i == 0 { // root of the testfs 243 testfs.root = new(testFile) 244 ff = testfs.root 245 ff.parent = testfs.root 246 } else { 247 d, err := testfs.walkPath(path.Dir(f.Path)) 248 if err != nil { 249 panic(err) 250 } 251 ff = new(testFile) 252 d.children = append(d.children, ff) 253 ff.parent = d 254 } 255 ff.content = []byte(f.Content) 256 var qt lib9p.QidType 257 if f.Mode&fs.ModeDir != 0 { 258 qt = lib9p.QTDIR 259 } else { 260 qt = lib9p.QTFILE 261 } 262 ff.stat = lib9p.Stat{ 263 Qid: lib9p.Qid{Path: uint64(i), Type: qt}, 264 Mode: f.Mode, 265 Name: path.Base(f.Path), 266 Uid: f.Uid, 267 Gid: f.Gid, 268 Muid: f.Muid, 269 } 270 ff.fsys = testfs 271 testfs.nextQid++ 272 } 273 testfs.waitc = make(chan struct{}) 274 testfs.groups = make(map[string]group) 275 for _, g := range testdata.Groups { 276 testfs.groups[g.Name] = group{ 277 leader: g.Leader, 278 members: make(map[string]bool), 279 } 280 for _, u := range g.Members { 281 testfs.groups[g.Name].members[u] = true 282 } 283 } 284 } 285 286 // SetfsysAndparent sets file.fsys and file.parent for every file in the fsys. 287 // Pass nil as file to setup entire file system. 288 func SetfsysAndparent(fsys *testFS, file *testFile) { 289 if file == nil { 290 file = fsys.root 291 file.parent = fsys.root 292 file.fsys = fsys 293 } 294 for _, child := range file.children { 295 child.parent = file 296 child.fsys = fsys 297 SetfsysAndparent(fsys, child) 298 } 299 }