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 {
88 SetParent(parent inode, name string)
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, name string) {
214 n.fileinfo.name = name
217 func (n *treenode) Parent() inode {
223 func (n *treenode) IsDir() bool {
227 func (n *treenode) Child(name string, replace func(inode) inode) (child inode) {
228 // TODO: special treatment for "", ".", ".."
229 child = n.inodes[name]
231 newchild := replace(child)
233 delete(n.inodes, name)
234 } else if newchild != child {
235 n.inodes[name] = newchild
236 newchild.SetParent(n, name)
243 func (n *treenode) Size() int64 {
244 return n.FileInfo().Size()
247 func (n *treenode) FileInfo() os.FileInfo {
250 n.fileinfo.size = int64(len(n.inodes))
254 func (n *treenode) Readdir() (fi []os.FileInfo) {
257 fi = make([]os.FileInfo, 0, len(n.inodes))
258 for _, inode := range n.inodes {
259 fi = append(fi, inode.FileInfo())
264 type fileSystem struct {
269 func (fs *fileSystem) rootnode() inode {
273 // OpenFile is analogous to os.OpenFile().
274 func (fs *fileSystem) OpenFile(name string, flag int, perm os.FileMode) (File, error) {
275 return fs.openFile(name, flag, perm)
278 func (fs *fileSystem) openFile(name string, flag int, perm os.FileMode) (*filehandle, error) {
279 if flag&os.O_SYNC != 0 {
280 return nil, ErrSyncNotSupported
282 dirname, name := path.Split(name)
283 parent := rlookup(fs.root, dirname)
285 return nil, os.ErrNotExist
287 var readable, writable bool
288 switch flag & (os.O_RDWR | os.O_RDONLY | os.O_WRONLY) {
297 return nil, fmt.Errorf("invalid flags 0x%x", flag)
299 if !writable && parent.IsDir() {
300 // A directory can be opened via "foo/", "foo/.", or
304 return &filehandle{inode: parent}, nil
306 return &filehandle{inode: parent.Parent()}, nil
309 createMode := flag&os.O_CREATE != 0
312 defer parent.Unlock()
315 defer parent.RUnlock()
317 n := parent.Child(name, nil)
320 return nil, os.ErrNotExist
323 n = parent.Child(name, func(inode) inode {
324 n, err = parent.FS().newNode(name, perm|0755, time.Now())
330 // parent rejected new child
331 return nil, ErrInvalidOperation
333 } else if flag&os.O_EXCL != 0 {
334 return nil, ErrFileExists
335 } else if flag&os.O_TRUNC != 0 {
337 return nil, fmt.Errorf("invalid flag O_TRUNC in read-only mode")
338 } else if n.IsDir() {
339 return nil, fmt.Errorf("invalid flag O_TRUNC when opening directory")
340 } else if err := n.Truncate(0); err != nil {
346 append: flag&os.O_APPEND != 0,
352 func (fs *fileSystem) Open(name string) (http.File, error) {
353 return fs.OpenFile(name, os.O_RDONLY, 0)
356 func (fs *fileSystem) Create(name string) (File, error) {
357 return fs.OpenFile(name, os.O_CREATE|os.O_RDWR|os.O_TRUNC, 0)
360 func (fs *fileSystem) Mkdir(name string, perm os.FileMode) (err error) {
361 dirname, name := path.Split(name)
362 n := rlookup(fs.root, dirname)
364 return os.ErrNotExist
368 if n.Child(name, nil) != nil {
371 child := n.Child(name, func(inode) (child inode) {
372 child, err = n.FS().newNode(name, perm|os.ModeDir, time.Now())
377 } else if child == nil {
378 return ErrInvalidArgument
383 func (fs *fileSystem) Stat(name string) (fi os.FileInfo, err error) {
384 node := rlookup(fs.root, name)
393 func (fs *fileSystem) Rename(oldname, newname string) error {
394 olddir, oldname := path.Split(oldname)
395 if oldname == "" || oldname == "." || oldname == ".." {
396 return ErrInvalidArgument
398 olddirf, err := fs.openFile(olddir+".", os.O_RDONLY, 0)
400 return fmt.Errorf("%q: %s", olddir, err)
402 defer olddirf.Close()
404 newdir, newname := path.Split(newname)
405 if newname == "." || newname == ".." {
406 return ErrInvalidArgument
407 } else if newname == "" {
408 // Rename("a/b", "c/") means Rename("a/b", "c/b")
411 newdirf, err := fs.openFile(newdir+".", os.O_RDONLY, 0)
413 return fmt.Errorf("%q: %s", newdir, err)
415 defer newdirf.Close()
417 // When acquiring locks on multiple nodes, all common
418 // ancestors must be locked first in order to avoid
419 // deadlock. This is assured by locking the path from
420 // filesystem root to newdir, then locking the path from
421 // filesystem root to olddir, skipping any already-locked
423 needLock := []sync.Locker{}
424 for _, node := range []inode{olddirf.inode, newdirf.inode} {
425 needLock = append(needLock, node)
426 for node.Parent() != node && node.Parent().FS() == node.FS() {
428 needLock = append(needLock, node)
431 locked := map[sync.Locker]bool{}
432 for i := len(needLock) - 1; i >= 0; i-- {
433 if n := needLock[i]; !locked[n] {
441 olddirf.inode.Child(oldname, func(oldinode inode) inode {
446 accepted := newdirf.inode.Child(newname, func(existing inode) inode {
447 if existing != nil && existing.IsDir() {
453 if accepted != oldinode {
455 // newdirf didn't accept oldinode.
456 err = ErrInvalidArgument
458 // Leave oldinode in olddir.
461 //TODO: olddirf.setModTime(time.Now())
462 //TODO: newdirf.setModTime(time.Now())
468 func (fs *fileSystem) Remove(name string) error {
469 return fs.remove(strings.TrimRight(name, "/"), false)
472 func (fs *fileSystem) RemoveAll(name string) error {
473 err := fs.remove(strings.TrimRight(name, "/"), true)
474 if os.IsNotExist(err) {
475 // "If the path does not exist, RemoveAll returns
476 // nil." (see "os" pkg)
482 func (fs *fileSystem) remove(name string, recursive bool) (err error) {
483 dirname, name := path.Split(name)
484 if name == "" || name == "." || name == ".." {
485 return ErrInvalidArgument
487 dir := rlookup(fs.root, dirname)
489 return os.ErrNotExist
493 dir.Child(name, func(node inode) inode {
498 if !recursive && node.IsDir() && node.Size() > 0 {
499 err = ErrDirectoryNotEmpty
507 func (fs *fileSystem) Sync() error {
508 log.Printf("TODO: sync fileSystem")
509 return ErrInvalidOperation
512 // rlookup (recursive lookup) returns the inode for the file/directory
513 // with the given name (which may contain "/" separators). If no such
514 // file/directory exists, the returned node is nil.
515 func rlookup(start inode, path string) (node inode) {
517 for _, name := range strings.Split(path, "/") {
522 if name == "." || name == "" {
530 node = func() inode {
533 return node.Child(name, nil)