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 {
56 // create a new node with nil parent.
57 newNode(name string, perm os.FileMode, modTime time.Time) (node inode, err error)
59 // analogous to os.Stat()
60 Stat(name string) (os.FileInfo, error)
62 // analogous to os.Create(): create/truncate a file and open it O_RDWR.
63 Create(name string) (File, error)
65 // Like os.OpenFile(): create or open a file or directory.
67 // If flag&os.O_EXCL==0, it opens an existing file or
68 // directory if one exists. If flag&os.O_CREATE!=0, it creates
69 // a new empty file or directory if one does not already
72 // When creating a new item, perm&os.ModeDir determines
73 // whether it is a file or a directory.
75 // A file can be opened multiple times and used concurrently
76 // from multiple goroutines. However, each File object should
77 // be used by only one goroutine at a time.
78 OpenFile(name string, flag int, perm os.FileMode) (File, error)
80 Mkdir(name string, perm os.FileMode) error
81 Remove(name string) error
82 RemoveAll(name string) error
83 Rename(oldname, newname string) error
87 type inode interface {
91 Read([]byte, filenodePtr) (int, filenodePtr, error)
92 Write([]byte, filenodePtr) (int, filenodePtr, error)
95 Readdir() []os.FileInfo
97 FileInfo() os.FileInfo
99 // Child() performs lookups and updates of named child nodes.
101 // If replace is non-nil, Child calls replace(x) where x is
102 // the current child inode with the given name. If possible,
103 // the child inode is replaced with the one returned by
106 // Nil represents "no child". replace(nil) signifies that no
107 // child with this name exists yet. If replace() returns nil,
108 // the existing child should be deleted if possible.
110 // An implementation of Child() is permitted to ignore
111 // replace() or its return value. For example, a regular file
112 // inode does not have children, so Child() always returns
115 // Child() returns the child, if any, with the given name: if
116 // a child was added or changed, the new child is returned.
118 // Caller must have lock (or rlock if replace is nil).
119 Child(name string, replace func(inode) inode) inode
126 type fileinfo struct {
133 // Name implements os.FileInfo.
134 func (fi fileinfo) Name() string {
138 // ModTime implements os.FileInfo.
139 func (fi fileinfo) ModTime() time.Time {
143 // Mode implements os.FileInfo.
144 func (fi fileinfo) Mode() os.FileMode {
148 // IsDir implements os.FileInfo.
149 func (fi fileinfo) IsDir() bool {
150 return fi.mode&os.ModeDir != 0
153 // Size implements os.FileInfo.
154 func (fi fileinfo) Size() int64 {
158 // Sys implements os.FileInfo.
159 func (fi fileinfo) Sys() interface{} {
163 type nullnode struct{}
165 func (*nullnode) Mkdir(string, os.FileMode) error {
166 return ErrInvalidOperation
169 func (*nullnode) Read([]byte, filenodePtr) (int, filenodePtr, error) {
170 return 0, filenodePtr{}, ErrInvalidOperation
173 func (*nullnode) Write([]byte, filenodePtr) (int, filenodePtr, error) {
174 return 0, filenodePtr{}, ErrInvalidOperation
177 func (*nullnode) Truncate(int64) error {
178 return ErrInvalidOperation
181 func (*nullnode) FileInfo() os.FileInfo {
185 func (*nullnode) IsDir() bool {
189 func (*nullnode) Readdir() []os.FileInfo {
193 func (*nullnode) Child(name string, replace func(inode) inode) inode {
197 type treenode struct {
200 inodes map[string]inode
206 func (n *treenode) FS() FileSystem {
210 func (n *treenode) SetParent(p inode) {
216 func (n *treenode) Parent() inode {
222 func (n *treenode) IsDir() bool {
226 func (n *treenode) Child(name string, replace func(inode) inode) (child inode) {
227 // TODO: special treatment for "", ".", ".."
228 child = n.inodes[name]
230 newchild := replace(child)
232 delete(n.inodes, name)
233 } else if newchild != child {
234 n.inodes[name] = newchild
235 newchild.SetParent(n)
242 func (n *treenode) Size() int64 {
243 return n.FileInfo().Size()
246 func (n *treenode) FileInfo() os.FileInfo {
249 n.fileinfo.size = int64(len(n.inodes))
253 func (n *treenode) Readdir() (fi []os.FileInfo) {
256 fi = make([]os.FileInfo, 0, len(n.inodes))
257 for _, inode := range n.inodes {
258 fi = append(fi, inode.FileInfo())
263 type fileSystem struct {
268 func (fs *fileSystem) rootnode() inode {
272 // OpenFile is analogous to os.OpenFile().
273 func (fs *fileSystem) OpenFile(name string, flag int, perm os.FileMode) (File, error) {
274 return fs.openFile(name, flag, perm)
277 func (fs *fileSystem) openFile(name string, flag int, perm os.FileMode) (*filehandle, error) {
278 if flag&os.O_SYNC != 0 {
279 return nil, ErrSyncNotSupported
281 dirname, name := path.Split(name)
282 parent := rlookup(fs.root, dirname)
284 return nil, os.ErrNotExist
286 var readable, writable bool
287 switch flag & (os.O_RDWR | os.O_RDONLY | os.O_WRONLY) {
296 return nil, fmt.Errorf("invalid flags 0x%x", flag)
298 if !writable && parent.IsDir() {
299 // A directory can be opened via "foo/", "foo/.", or
303 return &filehandle{inode: parent}, nil
305 return &filehandle{inode: parent.Parent()}, nil
308 createMode := flag&os.O_CREATE != 0
311 defer parent.Unlock()
314 defer parent.RUnlock()
316 n := parent.Child(name, nil)
319 return nil, os.ErrNotExist
322 n = parent.Child(name, func(inode) inode {
323 n, err = parent.FS().newNode(name, perm|0755, time.Now())
329 // parent rejected new child
330 return nil, ErrInvalidOperation
332 } else if flag&os.O_EXCL != 0 {
333 return nil, ErrFileExists
334 } else if flag&os.O_TRUNC != 0 {
336 return nil, fmt.Errorf("invalid flag O_TRUNC in read-only mode")
337 } else if n.IsDir() {
338 return nil, fmt.Errorf("invalid flag O_TRUNC when opening directory")
339 } else if err := n.Truncate(0); err != nil {
345 append: flag&os.O_APPEND != 0,
351 func (fs *fileSystem) Open(name string) (http.File, error) {
352 return fs.OpenFile(name, os.O_RDONLY, 0)
355 func (fs *fileSystem) Create(name string) (File, error) {
356 return fs.OpenFile(name, os.O_CREATE|os.O_RDWR|os.O_TRUNC, 0)
359 func (fs *fileSystem) Mkdir(name string, perm os.FileMode) (err error) {
360 dirname, name := path.Split(name)
361 n := rlookup(fs.root, dirname)
363 return os.ErrNotExist
367 if n.Child(name, nil) != nil {
370 child := n.Child(name, func(inode) (child inode) {
371 child, err = n.FS().newNode(name, perm|os.ModeDir, time.Now())
376 } else if child == nil {
377 return ErrInvalidArgument
382 func (fs *fileSystem) Stat(name string) (fi os.FileInfo, err error) {
383 node := rlookup(fs.root, name)
392 func (fs *fileSystem) Rename(oldname, newname string) error {
393 olddir, oldname := path.Split(oldname)
394 if oldname == "" || oldname == "." || oldname == ".." {
395 return ErrInvalidArgument
397 olddirf, err := fs.openFile(olddir+".", os.O_RDONLY, 0)
399 return fmt.Errorf("%q: %s", olddir, err)
401 defer olddirf.Close()
403 newdir, newname := path.Split(newname)
404 if newname == "." || newname == ".." {
405 return ErrInvalidArgument
406 } else if newname == "" {
407 // Rename("a/b", "c/") means Rename("a/b", "c/b")
410 newdirf, err := fs.openFile(newdir+".", os.O_RDONLY, 0)
412 return fmt.Errorf("%q: %s", newdir, err)
414 defer newdirf.Close()
416 // When acquiring locks on multiple nodes, all common
417 // ancestors must be locked first in order to avoid
418 // deadlock. This is assured by locking the path from
419 // filesystem root to newdir, then locking the path from
420 // filesystem root to olddir, skipping any already-locked
422 needLock := []sync.Locker{}
423 for _, node := range []inode{olddirf.inode, newdirf.inode} {
424 needLock = append(needLock, node)
425 for node.Parent() != node && node.Parent().FS() == node.FS() {
427 needLock = append(needLock, node)
430 locked := map[sync.Locker]bool{}
431 for i := len(needLock) - 1; i >= 0; i-- {
432 if n := needLock[i]; !locked[n] {
440 olddirf.inode.Child(oldname, func(oldinode inode) inode {
445 newdirf.inode.Child(newname, func(existing inode) inode {
446 if existing != nil && existing.IsDir() {
456 defer oldinode.Unlock()
457 switch n := oldinode.(type) {
459 n.parent = newdirf.inode
460 n.fileinfo.name = newname
462 n.parent = newdirf.inode
463 n.fileinfo.name = newname
465 panic(fmt.Sprintf("bad inode type %T", n))
467 //TODO: olddirf.setModTime(time.Now())
468 //TODO: newdirf.setModTime(time.Now())
474 func (fs *fileSystem) Remove(name string) error {
475 return fs.remove(strings.TrimRight(name, "/"), false)
478 func (fs *fileSystem) RemoveAll(name string) error {
479 err := fs.remove(strings.TrimRight(name, "/"), true)
480 if os.IsNotExist(err) {
481 // "If the path does not exist, RemoveAll returns
482 // nil." (see "os" pkg)
488 func (fs *fileSystem) remove(name string, recursive bool) (err error) {
489 dirname, name := path.Split(name)
490 if name == "" || name == "." || name == ".." {
491 return ErrInvalidArgument
493 dir := rlookup(fs.root, dirname)
495 return os.ErrNotExist
499 dir.Child(name, func(node inode) inode {
504 if !recursive && node.IsDir() && node.Size() > 0 {
505 err = ErrDirectoryNotEmpty
513 func (fs *fileSystem) Sync() error {
514 log.Printf("TODO: sync fileSystem")
515 return ErrInvalidOperation
518 // rlookup (recursive lookup) returns the inode for the file/directory
519 // with the given name (which may contain "/" separators). If no such
520 // file/directory exists, the returned node is nil.
521 func rlookup(start inode, path string) (node inode) {
523 for _, name := range strings.Split(path, "/") {
528 if name == "." || name == "" {
536 node = func() inode {
539 return node.Child(name, nil)