1 // Copyright (C) The Arvados Authors. All rights reserved.
3 // SPDX-License-Identifier: Apache-2.0
21 ErrReadOnlyFile = errors.New("read-only file")
22 ErrNegativeOffset = errors.New("cannot seek to negative offset")
23 ErrFileExists = errors.New("file exists")
24 ErrInvalidOperation = errors.New("invalid operation")
25 ErrInvalidArgument = errors.New("invalid argument")
26 ErrDirectoryNotEmpty = errors.New("directory not empty")
27 ErrWriteOnlyMode = errors.New("file is O_WRONLY")
28 ErrSyncNotSupported = errors.New("O_SYNC flag is not supported")
29 ErrIsDirectory = errors.New("cannot rename file to overwrite existing directory")
30 ErrPermission = os.ErrPermission
33 // A File is an *os.File-like interface for reading and writing files
41 Readdir(int) ([]os.FileInfo, error)
42 Stat() (os.FileInfo, error)
47 // A FileSystem is an http.Filesystem plus Stat() and support for
48 // opening writable files. All methods are safe to call from multiple
50 type FileSystem interface {
54 newDirnode(parent inode, name string, perm os.FileMode, modTime time.Time) (node inode, err error)
55 newFilenode(parent inode, name string, perm os.FileMode, modTime time.Time) (node inode, err error)
57 // analogous to os.Stat()
58 Stat(name string) (os.FileInfo, error)
60 // analogous to os.Create(): create/truncate a file and open it O_RDWR.
61 Create(name string) (File, error)
63 // Like os.OpenFile(): create or open a file or directory.
65 // If flag&os.O_EXCL==0, it opens an existing file or
66 // directory if one exists. If flag&os.O_CREATE!=0, it creates
67 // a new empty file or directory if one does not already
70 // When creating a new item, perm&os.ModeDir determines
71 // whether it is a file or a directory.
73 // A file can be opened multiple times and used concurrently
74 // from multiple goroutines. However, each File object should
75 // be used by only one goroutine at a time.
76 OpenFile(name string, flag int, perm os.FileMode) (File, error)
78 Mkdir(name string, perm os.FileMode) error
79 Remove(name string) error
80 RemoveAll(name string) error
81 Rename(oldname, newname string) error
85 type inode interface {
88 Read([]byte, filenodePtr) (int, filenodePtr, error)
89 Write([]byte, filenodePtr) (int, filenodePtr, error)
92 Readdir() []os.FileInfo
94 FileInfo() os.FileInfo
96 // Child() performs lookups and updates of named child nodes.
98 // If replace is non-nil, Child calls replace(x) where x is
99 // the current child inode with the given name. If possible,
100 // the child inode is replaced with the one returned by
103 // Nil represents "no child". replace(nil) signifies that no
104 // child with this name exists yet. If replace() returns nil,
105 // the existing child should be deleted if possible.
107 // An implementation of Child() is permitted to ignore
108 // replace() or its return value. For example, a regular file
109 // inode does not have children, so Child() always returns
112 // Child() returns the child, if any, with the given name: if
113 // a child was added or changed, the new child is returned.
115 // Caller must have lock (or rlock if replace is nil).
116 Child(name string, replace func(inode) inode) inode
123 type fileinfo struct {
130 // Name implements os.FileInfo.
131 func (fi fileinfo) Name() string {
135 // ModTime implements os.FileInfo.
136 func (fi fileinfo) ModTime() time.Time {
140 // Mode implements os.FileInfo.
141 func (fi fileinfo) Mode() os.FileMode {
145 // IsDir implements os.FileInfo.
146 func (fi fileinfo) IsDir() bool {
147 return fi.mode&os.ModeDir != 0
150 // Size implements os.FileInfo.
151 func (fi fileinfo) Size() int64 {
155 // Sys implements os.FileInfo.
156 func (fi fileinfo) Sys() interface{} {
160 type nullnode struct{}
162 func (*nullnode) Mkdir(string, os.FileMode) error {
163 return ErrInvalidOperation
166 func (*nullnode) Read([]byte, filenodePtr) (int, filenodePtr, error) {
167 return 0, filenodePtr{}, ErrInvalidOperation
170 func (*nullnode) Write([]byte, filenodePtr) (int, filenodePtr, error) {
171 return 0, filenodePtr{}, ErrInvalidOperation
174 func (*nullnode) Truncate(int64) error {
175 return ErrInvalidOperation
178 func (*nullnode) FileInfo() os.FileInfo {
182 func (*nullnode) IsDir() bool {
186 func (*nullnode) Readdir() []os.FileInfo {
190 func (*nullnode) Child(name string, replace func(inode) inode) inode {
194 type treenode struct {
197 inodes map[string]inode
203 func (n *treenode) FS() FileSystem {
207 func (n *treenode) Parent() inode {
213 func (n *treenode) IsDir() bool {
217 func (n *treenode) Child(name string, replace func(inode) inode) (child inode) {
218 // TODO: special treatment for "", ".", ".."
219 child = n.inodes[name]
221 child = replace(child)
223 delete(n.inodes, name)
225 n.inodes[name] = child
231 func (n *treenode) Size() int64 {
232 return n.FileInfo().Size()
235 func (n *treenode) FileInfo() os.FileInfo {
238 n.fileinfo.size = int64(len(n.inodes))
242 func (n *treenode) Readdir() (fi []os.FileInfo) {
245 fi = make([]os.FileInfo, 0, len(n.inodes))
246 for _, inode := range n.inodes {
247 fi = append(fi, inode.FileInfo())
252 type fileSystem struct {
257 // OpenFile is analogous to os.OpenFile().
258 func (fs *fileSystem) OpenFile(name string, flag int, perm os.FileMode) (File, error) {
259 return fs.openFile(name, flag, perm)
262 func (fs *fileSystem) openFile(name string, flag int, perm os.FileMode) (*filehandle, error) {
263 if flag&os.O_SYNC != 0 {
264 return nil, ErrSyncNotSupported
266 dirname, name := path.Split(name)
267 parent := rlookup(fs.root, dirname)
269 return nil, os.ErrNotExist
271 var readable, writable bool
272 switch flag & (os.O_RDWR | os.O_RDONLY | os.O_WRONLY) {
281 return nil, fmt.Errorf("invalid flags 0x%x", flag)
283 if !writable && parent.IsDir() {
284 // A directory can be opened via "foo/", "foo/.", or
288 return &filehandle{inode: parent}, nil
290 return &filehandle{inode: parent.Parent()}, nil
293 createMode := flag&os.O_CREATE != 0
296 defer parent.Unlock()
299 defer parent.RUnlock()
301 n := parent.Child(name, nil)
304 return nil, os.ErrNotExist
307 n = parent.Child(name, func(inode) inode {
309 n, err = fs.newDirnode(parent, name, perm|0755, time.Now())
311 n, err = fs.newFilenode(parent, name, perm|0755, time.Now())
318 // parent rejected new child
319 return nil, ErrInvalidOperation
321 } else if flag&os.O_EXCL != 0 {
322 return nil, ErrFileExists
323 } else if flag&os.O_TRUNC != 0 {
325 return nil, fmt.Errorf("invalid flag O_TRUNC in read-only mode")
326 } else if n.IsDir() {
327 return nil, fmt.Errorf("invalid flag O_TRUNC when opening directory")
328 } else if err := n.Truncate(0); err != nil {
334 append: flag&os.O_APPEND != 0,
340 func (fs *fileSystem) Open(name string) (http.File, error) {
341 return fs.OpenFile(name, os.O_RDONLY, 0)
344 func (fs *fileSystem) Create(name string) (File, error) {
345 return fs.OpenFile(name, os.O_CREATE|os.O_RDWR|os.O_TRUNC, 0)
348 func (fs *fileSystem) Mkdir(name string, perm os.FileMode) (err error) {
349 dirname, name := path.Split(name)
350 n := rlookup(fs.root, dirname)
352 return os.ErrNotExist
356 if n.Child(name, nil) != nil {
359 child := n.Child(name, func(inode) (child inode) {
360 child, err = fs.newDirnode(n, name, perm, time.Now())
365 } else if child == nil {
366 return ErrInvalidArgument
371 func (fs *fileSystem) Stat(name string) (fi os.FileInfo, err error) {
372 node := rlookup(fs.root, name)
381 func (fs *fileSystem) Rename(oldname, newname string) error {
382 olddir, oldname := path.Split(oldname)
383 if oldname == "" || oldname == "." || oldname == ".." {
384 return ErrInvalidArgument
386 olddirf, err := fs.openFile(olddir+".", os.O_RDONLY, 0)
388 return fmt.Errorf("%q: %s", olddir, err)
390 defer olddirf.Close()
392 newdir, newname := path.Split(newname)
393 if newname == "." || newname == ".." {
394 return ErrInvalidArgument
395 } else if newname == "" {
396 // Rename("a/b", "c/") means Rename("a/b", "c/b")
399 newdirf, err := fs.openFile(newdir+".", os.O_RDONLY, 0)
401 return fmt.Errorf("%q: %s", newdir, err)
403 defer newdirf.Close()
405 // When acquiring locks on multiple nodes, all common
406 // ancestors must be locked first in order to avoid
407 // deadlock. This is assured by locking the path from root to
408 // newdir, then locking the path from root to olddir, skipping
409 // any already-locked nodes.
410 needLock := []sync.Locker{}
411 for _, f := range []*filehandle{olddirf, newdirf} {
413 needLock = append(needLock, node)
414 for node.Parent() != node && node.Parent().FS() == node.FS() {
416 needLock = append(needLock, node)
419 locked := map[sync.Locker]bool{}
420 for i := len(needLock) - 1; i >= 0; i-- {
421 if n := needLock[i]; !locked[n] {
429 olddirf.inode.Child(oldname, func(oldinode inode) inode {
434 newdirf.inode.Child(newname, func(existing inode) inode {
435 if existing != nil && existing.IsDir() {
445 defer oldinode.Unlock()
446 switch n := oldinode.(type) {
448 n.parent = newdirf.inode
449 n.fileinfo.name = newname
451 n.parent = newdirf.inode
452 n.fileinfo.name = newname
454 panic(fmt.Sprintf("bad inode type %T", n))
456 //TODO: olddirf.setModTime(time.Now())
457 //TODO: newdirf.setModTime(time.Now())
463 func (fs *fileSystem) Remove(name string) error {
464 return fs.remove(strings.TrimRight(name, "/"), false)
467 func (fs *fileSystem) RemoveAll(name string) error {
468 err := fs.remove(strings.TrimRight(name, "/"), true)
469 if os.IsNotExist(err) {
470 // "If the path does not exist, RemoveAll returns
471 // nil." (see "os" pkg)
477 func (fs *fileSystem) remove(name string, recursive bool) (err error) {
478 dirname, name := path.Split(name)
479 if name == "" || name == "." || name == ".." {
480 return ErrInvalidArgument
482 dir := rlookup(fs.root, dirname)
484 return os.ErrNotExist
488 dir.Child(name, func(node inode) inode {
493 if !recursive && node.IsDir() && node.Size() > 0 {
494 err = ErrDirectoryNotEmpty
502 // Caller must have parent lock, and must have already ensured
503 // parent.Child(name,nil) is nil.
504 func (fs *fileSystem) newDirnode(parent inode, name string, perm os.FileMode, modTime time.Time) (node inode, err error) {
505 if name == "" || name == "." || name == ".." {
506 return nil, ErrInvalidArgument
514 mode: perm | os.ModeDir,
517 inodes: make(map[string]inode),
522 func (fs *fileSystem) newFilenode(parent inode, name string, perm os.FileMode, modTime time.Time) (node inode, err error) {
523 if name == "" || name == "." || name == ".." {
524 return nil, ErrInvalidArgument
531 mode: perm & ^os.ModeDir,
537 func (fs *fileSystem) Sync() error {
538 log.Printf("TODO: sync fileSystem")
539 return ErrInvalidOperation
542 // rlookup (recursive lookup) returns the inode for the file/directory
543 // with the given name (which may contain "/" separators). If no such
544 // file/directory exists, the returned node is nil.
545 func rlookup(start inode, path string) (node inode) {
547 for _, name := range strings.Split(path, "/") {
552 if name == "." || name == "" {
560 node = func() inode {
563 return node.Child(name, nil)