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 newchild.SetParent(n, name)
236 n.inodes[name] = newchild
237 n.fileinfo.modTime = time.Now()
244 func (n *treenode) Size() int64 {
245 return n.FileInfo().Size()
248 func (n *treenode) FileInfo() os.FileInfo {
251 n.fileinfo.size = int64(len(n.inodes))
255 func (n *treenode) Readdir() (fi []os.FileInfo) {
258 fi = make([]os.FileInfo, 0, len(n.inodes))
259 for _, inode := range n.inodes {
260 fi = append(fi, inode.FileInfo())
265 type fileSystem struct {
270 func (fs *fileSystem) rootnode() inode {
274 // OpenFile is analogous to os.OpenFile().
275 func (fs *fileSystem) OpenFile(name string, flag int, perm os.FileMode) (File, error) {
276 return fs.openFile(name, flag, perm)
279 func (fs *fileSystem) openFile(name string, flag int, perm os.FileMode) (*filehandle, error) {
280 if flag&os.O_SYNC != 0 {
281 return nil, ErrSyncNotSupported
283 dirname, name := path.Split(name)
284 parent := rlookup(fs.root, dirname)
286 return nil, os.ErrNotExist
288 var readable, writable bool
289 switch flag & (os.O_RDWR | os.O_RDONLY | os.O_WRONLY) {
298 return nil, fmt.Errorf("invalid flags 0x%x", flag)
300 if !writable && parent.IsDir() {
301 // A directory can be opened via "foo/", "foo/.", or
305 return &filehandle{inode: parent}, nil
307 return &filehandle{inode: parent.Parent()}, nil
310 createMode := flag&os.O_CREATE != 0
313 defer parent.Unlock()
316 defer parent.RUnlock()
318 n := parent.Child(name, nil)
321 return nil, os.ErrNotExist
324 n = parent.Child(name, func(inode) inode {
325 n, err = parent.FS().newNode(name, perm|0755, time.Now())
331 // parent rejected new child
332 return nil, ErrInvalidOperation
334 } else if flag&os.O_EXCL != 0 {
335 return nil, ErrFileExists
336 } else if flag&os.O_TRUNC != 0 {
338 return nil, fmt.Errorf("invalid flag O_TRUNC in read-only mode")
339 } else if n.IsDir() {
340 return nil, fmt.Errorf("invalid flag O_TRUNC when opening directory")
341 } else if err := n.Truncate(0); err != nil {
347 append: flag&os.O_APPEND != 0,
353 func (fs *fileSystem) Open(name string) (http.File, error) {
354 return fs.OpenFile(name, os.O_RDONLY, 0)
357 func (fs *fileSystem) Create(name string) (File, error) {
358 return fs.OpenFile(name, os.O_CREATE|os.O_RDWR|os.O_TRUNC, 0)
361 func (fs *fileSystem) Mkdir(name string, perm os.FileMode) (err error) {
362 dirname, name := path.Split(name)
363 n := rlookup(fs.root, dirname)
365 return os.ErrNotExist
369 if n.Child(name, nil) != nil {
372 child := n.Child(name, func(inode) (child inode) {
373 child, err = n.FS().newNode(name, perm|os.ModeDir, time.Now())
378 } else if child == nil {
379 return ErrInvalidArgument
384 func (fs *fileSystem) Stat(name string) (fi os.FileInfo, err error) {
385 node := rlookup(fs.root, name)
394 func (fs *fileSystem) Rename(oldname, newname string) error {
395 olddir, oldname := path.Split(oldname)
396 if oldname == "" || oldname == "." || oldname == ".." {
397 return ErrInvalidArgument
399 olddirf, err := fs.openFile(olddir+".", os.O_RDONLY, 0)
401 return fmt.Errorf("%q: %s", olddir, err)
403 defer olddirf.Close()
405 newdir, newname := path.Split(newname)
406 if newname == "." || newname == ".." {
407 return ErrInvalidArgument
408 } else if newname == "" {
409 // Rename("a/b", "c/") means Rename("a/b", "c/b")
412 newdirf, err := fs.openFile(newdir+".", os.O_RDONLY, 0)
414 return fmt.Errorf("%q: %s", newdir, err)
416 defer newdirf.Close()
418 // When acquiring locks on multiple nodes, all common
419 // ancestors must be locked first in order to avoid
420 // deadlock. This is assured by locking the path from
421 // filesystem root to newdir, then locking the path from
422 // filesystem root to olddir, skipping any already-locked
424 needLock := []sync.Locker{}
425 for _, node := range []inode{olddirf.inode, newdirf.inode} {
426 needLock = append(needLock, node)
427 for node.Parent() != node && node.Parent().FS() == node.FS() {
429 needLock = append(needLock, node)
432 locked := map[sync.Locker]bool{}
433 for i := len(needLock) - 1; i >= 0; i-- {
434 if n := needLock[i]; !locked[n] {
442 olddirf.inode.Child(oldname, func(oldinode inode) inode {
447 if locked[oldinode] {
448 // oldinode cannot become a descendant of itself.
449 err = ErrInvalidArgument
452 accepted := newdirf.inode.Child(newname, func(existing inode) inode {
453 if existing != nil && existing.IsDir() {
459 if accepted != oldinode {
461 // newdirf didn't accept oldinode.
462 err = ErrInvalidArgument
464 // Leave oldinode in olddir.
472 func (fs *fileSystem) Remove(name string) error {
473 return fs.remove(strings.TrimRight(name, "/"), false)
476 func (fs *fileSystem) RemoveAll(name string) error {
477 err := fs.remove(strings.TrimRight(name, "/"), true)
478 if os.IsNotExist(err) {
479 // "If the path does not exist, RemoveAll returns
480 // nil." (see "os" pkg)
486 func (fs *fileSystem) remove(name string, recursive bool) (err error) {
487 dirname, name := path.Split(name)
488 if name == "" || name == "." || name == ".." {
489 return ErrInvalidArgument
491 dir := rlookup(fs.root, dirname)
493 return os.ErrNotExist
497 dir.Child(name, func(node inode) inode {
502 if !recursive && node.IsDir() && node.Size() > 0 {
503 err = ErrDirectoryNotEmpty
511 func (fs *fileSystem) Sync() error {
512 log.Printf("TODO: sync fileSystem")
513 return ErrInvalidOperation
516 // rlookup (recursive lookup) returns the inode for the file/directory
517 // with the given name (which may contain "/" separators). If no such
518 // file/directory exists, the returned node is nil.
519 func rlookup(start inode, path string) (node inode) {
521 for _, name := range strings.Split(path, "/") {
526 if name == "." || name == "" {
534 node = func() inode {
537 return node.Child(name, nil)