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 // (The term "child" here is used strictly. This means name is
107 // not "." or "..", and name does not contain "/".)
109 // If replace is non-nil, Child calls replace(x) where x is
110 // the current child inode with the given name. If possible,
111 // the child inode is replaced with the one returned by
114 // If replace(x) returns an inode (besides x or nil) that is
115 // subsequently returned by Child(), then Child()'s caller
116 // must ensure the new child's name and parent are set/updated
117 // to Child()'s name argument and its receiver respectively.
118 // This is not necessarily done before replace(x) returns, but
119 // it must be done before Child()'s caller releases the
122 // Nil represents "no child". replace(nil) signifies that no
123 // child with this name exists yet. If replace() returns nil,
124 // the existing child should be deleted if possible.
126 // An implementation of Child() is permitted to ignore
127 // replace() or its return value. For example, a regular file
128 // inode does not have children, so Child() always returns
131 // Child() returns the child, if any, with the given name: if
132 // a child was added or changed, the new child is returned.
134 // Caller must have lock (or rlock if replace is nil).
135 Child(name string, replace func(inode) (inode, error)) (inode, error)
142 type fileinfo struct {
149 // Name implements os.FileInfo.
150 func (fi fileinfo) Name() string {
154 // ModTime implements os.FileInfo.
155 func (fi fileinfo) ModTime() time.Time {
159 // Mode implements os.FileInfo.
160 func (fi fileinfo) Mode() os.FileMode {
164 // IsDir implements os.FileInfo.
165 func (fi fileinfo) IsDir() bool {
166 return fi.mode&os.ModeDir != 0
169 // Size implements os.FileInfo.
170 func (fi fileinfo) Size() int64 {
174 // Sys implements os.FileInfo.
175 func (fi fileinfo) Sys() interface{} {
179 type nullnode struct{}
181 func (*nullnode) Mkdir(string, os.FileMode) error {
182 return ErrInvalidOperation
185 func (*nullnode) Read([]byte, filenodePtr) (int, filenodePtr, error) {
186 return 0, filenodePtr{}, ErrInvalidOperation
189 func (*nullnode) Write([]byte, filenodePtr) (int, filenodePtr, error) {
190 return 0, filenodePtr{}, ErrInvalidOperation
193 func (*nullnode) Truncate(int64) error {
194 return ErrInvalidOperation
197 func (*nullnode) FileInfo() os.FileInfo {
201 func (*nullnode) IsDir() bool {
205 func (*nullnode) Readdir() ([]os.FileInfo, error) {
206 return nil, ErrInvalidOperation
209 func (*nullnode) Child(name string, replace func(inode) (inode, error)) (inode, error) {
210 return nil, ErrNotADirectory
213 type treenode struct {
216 inodes map[string]inode
222 func (n *treenode) FS() FileSystem {
226 func (n *treenode) SetParent(p inode, name string) {
230 n.fileinfo.name = name
233 func (n *treenode) Parent() inode {
239 func (n *treenode) IsDir() bool {
243 func (n *treenode) Child(name string, replace func(inode) (inode, error)) (child inode, err error) {
244 child = n.inodes[name]
245 if name == "" || name == "." || name == ".." {
246 err = ErrInvalidArgument
252 newchild, err := replace(child)
257 delete(n.inodes, name)
258 } else if newchild != child {
259 n.inodes[name] = newchild
260 n.fileinfo.modTime = time.Now()
266 func (n *treenode) Size() int64 {
267 return n.FileInfo().Size()
270 func (n *treenode) FileInfo() os.FileInfo {
273 n.fileinfo.size = int64(len(n.inodes))
277 func (n *treenode) Readdir() (fi []os.FileInfo, err error) {
280 fi = make([]os.FileInfo, 0, len(n.inodes))
281 for _, inode := range n.inodes {
282 fi = append(fi, inode.FileInfo())
287 type fileSystem struct {
293 func (fs *fileSystem) rootnode() inode {
297 func (fs *fileSystem) locker() sync.Locker {
301 // OpenFile is analogous to os.OpenFile().
302 func (fs *fileSystem) OpenFile(name string, flag int, perm os.FileMode) (File, error) {
303 return fs.openFile(name, flag, perm)
306 func (fs *fileSystem) openFile(name string, flag int, perm os.FileMode) (*filehandle, error) {
307 if flag&os.O_SYNC != 0 {
308 return nil, ErrSyncNotSupported
310 dirname, name := path.Split(name)
311 parent, err := rlookup(fs.root, dirname)
315 var readable, writable bool
316 switch flag & (os.O_RDWR | os.O_RDONLY | os.O_WRONLY) {
325 return nil, fmt.Errorf("invalid flags 0x%x", flag)
327 if !writable && parent.IsDir() {
328 // A directory can be opened via "foo/", "foo/.", or
332 return &filehandle{inode: parent}, nil
334 return &filehandle{inode: parent.Parent()}, nil
337 createMode := flag&os.O_CREATE != 0
340 defer parent.Unlock()
343 defer parent.RUnlock()
345 n, err := parent.Child(name, nil)
350 return nil, os.ErrNotExist
352 n, err = parent.Child(name, func(inode) (repl inode, err error) {
353 repl, err = parent.FS().newNode(name, perm|0755, time.Now())
357 repl.SetParent(parent, name)
363 // Parent rejected new child, but returned no error
364 return nil, ErrInvalidArgument
366 } else if flag&os.O_EXCL != 0 {
367 return nil, ErrFileExists
368 } else if flag&os.O_TRUNC != 0 {
370 return nil, fmt.Errorf("invalid flag O_TRUNC in read-only mode")
371 } else if n.IsDir() {
372 return nil, fmt.Errorf("invalid flag O_TRUNC when opening directory")
373 } else if err := n.Truncate(0); err != nil {
379 append: flag&os.O_APPEND != 0,
385 func (fs *fileSystem) Open(name string) (http.File, error) {
386 return fs.OpenFile(name, os.O_RDONLY, 0)
389 func (fs *fileSystem) Create(name string) (File, error) {
390 return fs.OpenFile(name, os.O_CREATE|os.O_RDWR|os.O_TRUNC, 0)
393 func (fs *fileSystem) Mkdir(name string, perm os.FileMode) error {
394 dirname, name := path.Split(name)
395 n, err := rlookup(fs.root, dirname)
401 if child, err := n.Child(name, nil); err != nil {
403 } else if child != nil {
407 _, err = n.Child(name, func(inode) (repl inode, err error) {
408 repl, err = n.FS().newNode(name, perm|os.ModeDir, time.Now())
412 repl.SetParent(n, name)
418 func (fs *fileSystem) Stat(name string) (os.FileInfo, error) {
419 node, err := rlookup(fs.root, name)
423 return node.FileInfo(), nil
426 func (fs *fileSystem) Rename(oldname, newname string) error {
427 olddir, oldname := path.Split(oldname)
428 if oldname == "" || oldname == "." || oldname == ".." {
429 return ErrInvalidArgument
431 olddirf, err := fs.openFile(olddir+".", os.O_RDONLY, 0)
433 return fmt.Errorf("%q: %s", olddir, err)
435 defer olddirf.Close()
437 newdir, newname := path.Split(newname)
438 if newname == "." || newname == ".." {
439 return ErrInvalidArgument
440 } else if newname == "" {
441 // Rename("a/b", "c/") means Rename("a/b", "c/b")
444 newdirf, err := fs.openFile(newdir+".", os.O_RDONLY, 0)
446 return fmt.Errorf("%q: %s", newdir, err)
448 defer newdirf.Close()
450 // TODO: If the nearest common ancestor ("nca") of olddirf and
451 // newdirf is on a different filesystem than fs, we should
452 // call nca.FS().Rename() instead of proceeding. Until then
453 // it's awkward for filesystems to implement their own Rename
454 // methods effectively: the only one that runs is the one on
455 // the root FileSystem exposed to the caller (webdav, fuse,
458 // When acquiring locks on multiple inodes, avoid deadlock by
459 // locking the entire containing filesystem first.
460 cfs := olddirf.inode.FS()
462 defer cfs.locker().Unlock()
464 if cfs != newdirf.inode.FS() {
465 // Moving inodes across filesystems is not (yet)
466 // supported. Locking inodes from different
467 // filesystems could deadlock, so we must error out
469 return ErrInvalidArgument
472 // To ensure we can test reliably whether we're about to move
473 // a directory into itself, lock all potential common
474 // ancestors of olddir and newdir.
475 needLock := []sync.Locker{}
476 for _, node := range []inode{olddirf.inode, newdirf.inode} {
477 needLock = append(needLock, node)
478 for node.Parent() != node && node.Parent().FS() == node.FS() {
480 needLock = append(needLock, node)
483 locked := map[sync.Locker]bool{}
484 for i := len(needLock) - 1; i >= 0; i-- {
485 if n := needLock[i]; !locked[n] {
492 _, err = olddirf.inode.Child(oldname, func(oldinode inode) (inode, error) {
494 return oldinode, os.ErrNotExist
496 if locked[oldinode] {
497 // oldinode cannot become a descendant of itself.
498 return oldinode, ErrInvalidArgument
500 if oldinode.FS() != cfs && newdirf.inode != olddirf.inode {
501 // moving a mount point to a different parent
502 // is not (yet) supported.
503 return oldinode, ErrInvalidArgument
505 accepted, err := newdirf.inode.Child(newname, func(existing inode) (inode, error) {
506 if existing != nil && existing.IsDir() {
507 return existing, ErrIsDirectory
512 // Leave oldinode in olddir.
515 accepted.SetParent(newdirf.inode, newname)
521 func (fs *fileSystem) Remove(name string) error {
522 return fs.remove(strings.TrimRight(name, "/"), false)
525 func (fs *fileSystem) RemoveAll(name string) error {
526 err := fs.remove(strings.TrimRight(name, "/"), true)
527 if os.IsNotExist(err) {
528 // "If the path does not exist, RemoveAll returns
529 // nil." (see "os" pkg)
535 func (fs *fileSystem) remove(name string, recursive bool) error {
536 dirname, name := path.Split(name)
537 if name == "" || name == "." || name == ".." {
538 return ErrInvalidArgument
540 dir, err := rlookup(fs.root, dirname)
546 _, err = dir.Child(name, func(node inode) (inode, error) {
548 return nil, os.ErrNotExist
550 if !recursive && node.IsDir() && node.Size() > 0 {
551 return node, ErrDirectoryNotEmpty
558 func (fs *fileSystem) Sync() error {
559 log.Printf("TODO: sync fileSystem")
560 return ErrInvalidOperation
563 // rlookup (recursive lookup) returns the inode for the file/directory
564 // with the given name (which may contain "/" separators). If no such
565 // file/directory exists, the returned node is nil.
566 func rlookup(start inode, path string) (node inode, err error) {
568 for _, name := range strings.Split(path, "/") {
570 if name == "." || name == "" {
578 node, err = func() (inode, error) {
581 return node.Child(name, nil)
583 if node == nil || err != nil {
587 if node == nil && err == nil {
593 func permittedName(name string) bool {
594 return name != "" && name != "." && name != ".." && !strings.Contains(name, "/")