lib9p

Go 9P library.
Log | Files | Refs | LICENSE

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 }