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 ErrNotADirectory = errors.New("not a directory")
31 ErrPermission = os.ErrPermission
34 // A File is an *os.File-like interface for reading and writing files
42 Readdir(int) ([]os.FileInfo, error)
43 Stat() (os.FileInfo, error)
48 // A FileSystem is an http.Filesystem plus Stat() and support for
49 // opening writable files. All methods are safe to call from multiple
51 type FileSystem interface {
57 // filesystem-wide lock: used by Rename() to prevent deadlock
58 // while locking multiple inodes.
61 // create a new node with nil parent.
62 newNode(name string, perm os.FileMode, modTime time.Time) (node inode, err error)
64 // analogous to os.Stat()
65 Stat(name string) (os.FileInfo, error)
67 // analogous to os.Create(): create/truncate a file and open it O_RDWR.
68 Create(name string) (File, error)
70 // Like os.OpenFile(): create or open a file or directory.
72 // If flag&os.O_EXCL==0, it opens an existing file or
73 // directory if one exists. If flag&os.O_CREATE!=0, it creates
74 // a new empty file or directory if one does not already
77 // When creating a new item, perm&os.ModeDir determines
78 // whether it is a file or a directory.
80 // A file can be opened multiple times and used concurrently
81 // from multiple goroutines. However, each File object should
82 // be used by only one goroutine at a time.
83 OpenFile(name string, flag int, perm os.FileMode) (File, error)
85 Mkdir(name string, perm os.FileMode) error
86 Remove(name string) error
87 RemoveAll(name string) error
88 Rename(oldname, newname string) error
92 type inode interface {
93 SetParent(parent inode, name string)
96 Read([]byte, filenodePtr) (int, filenodePtr, error)
97 Write([]byte, filenodePtr) (int, filenodePtr, error)
100 Readdir() ([]os.FileInfo, error)
102 FileInfo() os.FileInfo
104 // Child() performs lookups and updates of named child nodes.
106 // If replace is non-nil, Child calls replace(x) where x is
107 // the current child inode with the given name. If possible,
108 // the child inode is replaced with the one returned by
111 // If replace(x) returns an inode (besides x or nil) that is
112 // subsequently returned by Child(), then Child()'s caller
113 // must ensure the new child's name and parent are set/updated
114 // to Child()'s name argument and its receiver respectively.
115 // This is not necessarily done before replace(x) returns, but
116 // it must be done before Child()'s caller releases the
119 // Nil represents "no child". replace(nil) signifies that no
120 // child with this name exists yet. If replace() returns nil,
121 // the existing child should be deleted if possible.
123 // An implementation of Child() is permitted to ignore
124 // replace() or its return value. For example, a regular file
125 // inode does not have children, so Child() always returns
128 // Child() returns the child, if any, with the given name: if
129 // a child was added or changed, the new child is returned.
131 // Caller must have lock (or rlock if replace is nil).
132 Child(name string, replace func(inode) (inode, error)) (inode, error)
139 type fileinfo struct {
146 // Name implements os.FileInfo.
147 func (fi fileinfo) Name() string {
151 // ModTime implements os.FileInfo.
152 func (fi fileinfo) ModTime() time.Time {
156 // Mode implements os.FileInfo.
157 func (fi fileinfo) Mode() os.FileMode {
161 // IsDir implements os.FileInfo.
162 func (fi fileinfo) IsDir() bool {
163 return fi.mode&os.ModeDir != 0
166 // Size implements os.FileInfo.
167 func (fi fileinfo) Size() int64 {
171 // Sys implements os.FileInfo.
172 func (fi fileinfo) Sys() interface{} {
176 type nullnode struct{}
178 func (*nullnode) Mkdir(string, os.FileMode) error {
179 return ErrInvalidOperation
182 func (*nullnode) Read([]byte, filenodePtr) (int, filenodePtr, error) {
183 return 0, filenodePtr{}, ErrInvalidOperation
186 func (*nullnode) Write([]byte, filenodePtr) (int, filenodePtr, error) {
187 return 0, filenodePtr{}, ErrInvalidOperation
190 func (*nullnode) Truncate(int64) error {
191 return ErrInvalidOperation
194 func (*nullnode) FileInfo() os.FileInfo {
198 func (*nullnode) IsDir() bool {
202 func (*nullnode) Readdir() ([]os.FileInfo, error) {
203 return nil, ErrInvalidOperation
206 func (*nullnode) Child(name string, replace func(inode) (inode, error)) (inode, error) {
207 return nil, ErrNotADirectory
210 type treenode struct {
213 inodes map[string]inode
219 func (n *treenode) FS() FileSystem {
223 func (n *treenode) SetParent(p inode, name string) {
227 n.fileinfo.name = name
230 func (n *treenode) Parent() inode {
236 func (n *treenode) IsDir() bool {
240 func (n *treenode) Child(name string, replace func(inode) (inode, error)) (child inode, err error) {
241 child = n.inodes[name]
242 if name == "" || name == "." || name == ".." {
243 err = ErrInvalidArgument
249 newchild, err := replace(child)
254 delete(n.inodes, name)
255 } else if newchild != child {
256 n.inodes[name] = newchild
257 n.fileinfo.modTime = time.Now()
263 func (n *treenode) Size() int64 {
264 return n.FileInfo().Size()
267 func (n *treenode) FileInfo() os.FileInfo {
270 n.fileinfo.size = int64(len(n.inodes))
274 func (n *treenode) Readdir() (fi []os.FileInfo, err error) {
277 fi = make([]os.FileInfo, 0, len(n.inodes))
278 for _, inode := range n.inodes {
279 fi = append(fi, inode.FileInfo())
284 type fileSystem struct {
290 func (fs *fileSystem) rootnode() inode {
294 func (fs *fileSystem) locker() sync.Locker {
298 // OpenFile is analogous to os.OpenFile().
299 func (fs *fileSystem) OpenFile(name string, flag int, perm os.FileMode) (File, error) {
300 return fs.openFile(name, flag, perm)
303 func (fs *fileSystem) openFile(name string, flag int, perm os.FileMode) (*filehandle, error) {
304 if flag&os.O_SYNC != 0 {
305 return nil, ErrSyncNotSupported
307 dirname, name := path.Split(name)
308 parent, err := rlookup(fs.root, dirname)
312 var readable, writable bool
313 switch flag & (os.O_RDWR | os.O_RDONLY | os.O_WRONLY) {
322 return nil, fmt.Errorf("invalid flags 0x%x", flag)
324 if !writable && parent.IsDir() {
325 // A directory can be opened via "foo/", "foo/.", or
329 return &filehandle{inode: parent}, nil
331 return &filehandle{inode: parent.Parent()}, nil
334 createMode := flag&os.O_CREATE != 0
337 defer parent.Unlock()
340 defer parent.RUnlock()
342 n, err := parent.Child(name, nil)
347 return nil, os.ErrNotExist
349 n, err = parent.Child(name, func(inode) (repl inode, err error) {
350 repl, err = parent.FS().newNode(name, perm|0755, time.Now())
354 repl.SetParent(parent, name)
360 // Parent rejected new child, but returned no error
361 return nil, ErrInvalidArgument
363 } else if flag&os.O_EXCL != 0 {
364 return nil, ErrFileExists
365 } else if flag&os.O_TRUNC != 0 {
367 return nil, fmt.Errorf("invalid flag O_TRUNC in read-only mode")
368 } else if n.IsDir() {
369 return nil, fmt.Errorf("invalid flag O_TRUNC when opening directory")
370 } else if err := n.Truncate(0); err != nil {
376 append: flag&os.O_APPEND != 0,
382 func (fs *fileSystem) Open(name string) (http.File, error) {
383 return fs.OpenFile(name, os.O_RDONLY, 0)
386 func (fs *fileSystem) Create(name string) (File, error) {
387 return fs.OpenFile(name, os.O_CREATE|os.O_RDWR|os.O_TRUNC, 0)
390 func (fs *fileSystem) Mkdir(name string, perm os.FileMode) error {
391 dirname, name := path.Split(name)
392 n, err := rlookup(fs.root, dirname)
398 if child, err := n.Child(name, nil); err != nil {
400 } else if child != nil {
404 _, err = n.Child(name, func(inode) (repl inode, err error) {
405 repl, err = n.FS().newNode(name, perm|os.ModeDir, time.Now())
409 repl.SetParent(n, name)
415 func (fs *fileSystem) Stat(name string) (os.FileInfo, error) {
416 node, err := rlookup(fs.root, name)
420 return node.FileInfo(), nil
423 func (fs *fileSystem) Rename(oldname, newname string) error {
424 olddir, oldname := path.Split(oldname)
425 if oldname == "" || oldname == "." || oldname == ".." {
426 return ErrInvalidArgument
428 olddirf, err := fs.openFile(olddir+".", os.O_RDONLY, 0)
430 return fmt.Errorf("%q: %s", olddir, err)
432 defer olddirf.Close()
434 newdir, newname := path.Split(newname)
435 if newname == "." || newname == ".." {
436 return ErrInvalidArgument
437 } else if newname == "" {
438 // Rename("a/b", "c/") means Rename("a/b", "c/b")
441 newdirf, err := fs.openFile(newdir+".", os.O_RDONLY, 0)
443 return fmt.Errorf("%q: %s", newdir, err)
445 defer newdirf.Close()
447 // TODO: If the nearest common ancestor ("nca") of olddirf and
448 // newdirf is on a different filesystem than fs, we should
449 // call nca.FS().Rename() instead of proceeding. Until then
450 // it's awkward for filesystems to implement their own Rename
451 // methods effectively: the only one that runs is the one on
452 // the root filesystem exposed to the caller (webdav, fuse,
455 // When acquiring locks on multiple inodes, avoid deadlock by
456 // locking the entire containing filesystem first.
457 cfs := olddirf.inode.FS()
459 defer cfs.locker().Unlock()
461 if cfs != newdirf.inode.FS() {
462 // Moving inodes across filesystems is not (yet)
463 // supported. Locking inodes from different
464 // filesystems could deadlock, so we must error out
466 return ErrInvalidArgument
469 // To ensure we can test reliably whether we're about to move
470 // a directory into itself, lock all potential common
471 // ancestors of olddir and newdir.
472 needLock := []sync.Locker{}
473 for _, node := range []inode{olddirf.inode, newdirf.inode} {
474 needLock = append(needLock, node)
475 for node.Parent() != node && node.Parent().FS() == node.FS() {
477 needLock = append(needLock, node)
480 locked := map[sync.Locker]bool{}
481 for i := len(needLock) - 1; i >= 0; i-- {
482 if n := needLock[i]; !locked[n] {
489 _, err = olddirf.inode.Child(oldname, func(oldinode inode) (inode, error) {
491 return oldinode, os.ErrNotExist
493 if locked[oldinode] {
494 // oldinode cannot become a descendant of itself.
495 return oldinode, ErrInvalidArgument
497 if oldinode.FS() != cfs && newdirf.inode != olddirf.inode {
498 // moving a mount point to a different parent
499 // is not (yet) supported.
500 return oldinode, ErrInvalidArgument
502 accepted, err := newdirf.inode.Child(newname, func(existing inode) (inode, error) {
503 if existing != nil && existing.IsDir() {
504 return existing, ErrIsDirectory
509 // Leave oldinode in olddir.
512 accepted.SetParent(newdirf.inode, newname)
518 func (fs *fileSystem) Remove(name string) error {
519 return fs.remove(strings.TrimRight(name, "/"), false)
522 func (fs *fileSystem) RemoveAll(name string) error {
523 err := fs.remove(strings.TrimRight(name, "/"), true)
524 if os.IsNotExist(err) {
525 // "If the path does not exist, RemoveAll returns
526 // nil." (see "os" pkg)
532 func (fs *fileSystem) remove(name string, recursive bool) error {
533 dirname, name := path.Split(name)
534 if name == "" || name == "." || name == ".." {
535 return ErrInvalidArgument
537 dir, err := rlookup(fs.root, dirname)
543 _, err = dir.Child(name, func(node inode) (inode, error) {
545 return nil, os.ErrNotExist
547 if !recursive && node.IsDir() && node.Size() > 0 {
548 return node, ErrDirectoryNotEmpty
555 func (fs *fileSystem) Sync() error {
556 log.Printf("TODO: sync fileSystem")
557 return ErrInvalidOperation
560 // rlookup (recursive lookup) returns the inode for the file/directory
561 // with the given name (which may contain "/" separators). If no such
562 // file/directory exists, the returned node is nil.
563 func rlookup(start inode, path string) (node inode, err error) {
565 for _, name := range strings.Split(path, "/") {
567 if name == "." || name == "" {
575 node, err = func() (inode, error) {
578 return node.Child(name, nil)
580 if node == nil || err != nil {
584 if node == nil && err == nil {