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 // If replace(x) returns an inode (besides x or nil) that is
107 // subsequently returned by Child(), then Child()'s caller
108 // must ensure the new child's name and parent are set/updated
109 // to Child()'s name argument and its receiver respectively.
110 // This is not necessarily done before replace(x) returns, but
111 // it must be done before Child()'s caller releases the
114 // Nil represents "no child". replace(nil) signifies that no
115 // child with this name exists yet. If replace() returns nil,
116 // the existing child should be deleted if possible.
118 // An implementation of Child() is permitted to ignore
119 // replace() or its return value. For example, a regular file
120 // inode does not have children, so Child() always returns
123 // Child() returns the child, if any, with the given name: if
124 // a child was added or changed, the new child is returned.
126 // Caller must have lock (or rlock if replace is nil).
127 Child(name string, replace func(inode) inode) inode
134 type fileinfo struct {
141 // Name implements os.FileInfo.
142 func (fi fileinfo) Name() string {
146 // ModTime implements os.FileInfo.
147 func (fi fileinfo) ModTime() time.Time {
151 // Mode implements os.FileInfo.
152 func (fi fileinfo) Mode() os.FileMode {
156 // IsDir implements os.FileInfo.
157 func (fi fileinfo) IsDir() bool {
158 return fi.mode&os.ModeDir != 0
161 // Size implements os.FileInfo.
162 func (fi fileinfo) Size() int64 {
166 // Sys implements os.FileInfo.
167 func (fi fileinfo) Sys() interface{} {
171 type nullnode struct{}
173 func (*nullnode) Mkdir(string, os.FileMode) error {
174 return ErrInvalidOperation
177 func (*nullnode) Read([]byte, filenodePtr) (int, filenodePtr, error) {
178 return 0, filenodePtr{}, ErrInvalidOperation
181 func (*nullnode) Write([]byte, filenodePtr) (int, filenodePtr, error) {
182 return 0, filenodePtr{}, ErrInvalidOperation
185 func (*nullnode) Truncate(int64) error {
186 return ErrInvalidOperation
189 func (*nullnode) FileInfo() os.FileInfo {
193 func (*nullnode) IsDir() bool {
197 func (*nullnode) Readdir() []os.FileInfo {
201 func (*nullnode) Child(name string, replace func(inode) inode) inode {
205 type treenode struct {
208 inodes map[string]inode
214 func (n *treenode) FS() FileSystem {
218 func (n *treenode) SetParent(p inode, name string) {
222 n.fileinfo.name = name
225 func (n *treenode) Parent() inode {
231 func (n *treenode) IsDir() bool {
235 func (n *treenode) Child(name string, replace func(inode) inode) (child inode) {
236 // TODO: special treatment for "", ".", ".."
237 child = n.inodes[name]
239 newchild := replace(child)
241 delete(n.inodes, name)
242 } else if newchild != child {
243 n.inodes[name] = newchild
244 n.fileinfo.modTime = time.Now()
251 func (n *treenode) Size() int64 {
252 return n.FileInfo().Size()
255 func (n *treenode) FileInfo() os.FileInfo {
258 n.fileinfo.size = int64(len(n.inodes))
262 func (n *treenode) Readdir() (fi []os.FileInfo) {
265 fi = make([]os.FileInfo, 0, len(n.inodes))
266 for _, inode := range n.inodes {
267 fi = append(fi, inode.FileInfo())
272 type fileSystem struct {
277 func (fs *fileSystem) rootnode() inode {
281 // OpenFile is analogous to os.OpenFile().
282 func (fs *fileSystem) OpenFile(name string, flag int, perm os.FileMode) (File, error) {
283 return fs.openFile(name, flag, perm)
286 func (fs *fileSystem) openFile(name string, flag int, perm os.FileMode) (*filehandle, error) {
287 if flag&os.O_SYNC != 0 {
288 return nil, ErrSyncNotSupported
290 dirname, name := path.Split(name)
291 parent := rlookup(fs.root, dirname)
293 return nil, os.ErrNotExist
295 var readable, writable bool
296 switch flag & (os.O_RDWR | os.O_RDONLY | os.O_WRONLY) {
305 return nil, fmt.Errorf("invalid flags 0x%x", flag)
307 if !writable && parent.IsDir() {
308 // A directory can be opened via "foo/", "foo/.", or
312 return &filehandle{inode: parent}, nil
314 return &filehandle{inode: parent.Parent()}, nil
317 createMode := flag&os.O_CREATE != 0
320 defer parent.Unlock()
323 defer parent.RUnlock()
325 n := parent.Child(name, nil)
328 return nil, os.ErrNotExist
331 n = parent.Child(name, func(inode) inode {
332 n, err = parent.FS().newNode(name, perm|0755, time.Now())
333 n.SetParent(parent, name)
339 // parent rejected new child
340 return nil, ErrInvalidOperation
342 } else if flag&os.O_EXCL != 0 {
343 return nil, ErrFileExists
344 } else if flag&os.O_TRUNC != 0 {
346 return nil, fmt.Errorf("invalid flag O_TRUNC in read-only mode")
347 } else if n.IsDir() {
348 return nil, fmt.Errorf("invalid flag O_TRUNC when opening directory")
349 } else if err := n.Truncate(0); err != nil {
355 append: flag&os.O_APPEND != 0,
361 func (fs *fileSystem) Open(name string) (http.File, error) {
362 return fs.OpenFile(name, os.O_RDONLY, 0)
365 func (fs *fileSystem) Create(name string) (File, error) {
366 return fs.OpenFile(name, os.O_CREATE|os.O_RDWR|os.O_TRUNC, 0)
369 func (fs *fileSystem) Mkdir(name string, perm os.FileMode) (err error) {
370 dirname, name := path.Split(name)
371 n := rlookup(fs.root, dirname)
373 return os.ErrNotExist
377 if n.Child(name, nil) != nil {
380 child := n.Child(name, func(inode) (child inode) {
381 child, err = n.FS().newNode(name, perm|os.ModeDir, time.Now())
382 child.SetParent(n, name)
387 } else if child == nil {
388 return ErrInvalidArgument
393 func (fs *fileSystem) Stat(name string) (fi os.FileInfo, err error) {
394 node := rlookup(fs.root, name)
403 func (fs *fileSystem) Rename(oldname, newname string) error {
404 olddir, oldname := path.Split(oldname)
405 if oldname == "" || oldname == "." || oldname == ".." {
406 return ErrInvalidArgument
408 olddirf, err := fs.openFile(olddir+".", os.O_RDONLY, 0)
410 return fmt.Errorf("%q: %s", olddir, err)
412 defer olddirf.Close()
414 newdir, newname := path.Split(newname)
415 if newname == "." || newname == ".." {
416 return ErrInvalidArgument
417 } else if newname == "" {
418 // Rename("a/b", "c/") means Rename("a/b", "c/b")
421 newdirf, err := fs.openFile(newdir+".", os.O_RDONLY, 0)
423 return fmt.Errorf("%q: %s", newdir, err)
425 defer newdirf.Close()
427 // When acquiring locks on multiple nodes, all common
428 // ancestors must be locked first in order to avoid
429 // deadlock. This is assured by locking the path from
430 // filesystem root to newdir, then locking the path from
431 // filesystem root to olddir, skipping any already-locked
433 needLock := []sync.Locker{}
434 for _, node := range []inode{olddirf.inode, newdirf.inode} {
435 needLock = append(needLock, node)
436 for node.Parent() != node && node.Parent().FS() == node.FS() {
438 needLock = append(needLock, node)
441 locked := map[sync.Locker]bool{}
442 for i := len(needLock) - 1; i >= 0; i-- {
443 if n := needLock[i]; !locked[n] {
451 olddirf.inode.Child(oldname, func(oldinode inode) inode {
456 if locked[oldinode] {
457 // oldinode cannot become a descendant of itself.
458 err = ErrInvalidArgument
461 accepted := newdirf.inode.Child(newname, func(existing inode) inode {
462 if existing != nil && existing.IsDir() {
468 if accepted != oldinode {
470 // newdirf didn't accept oldinode.
471 err = ErrInvalidArgument
473 // Leave oldinode in olddir.
476 accepted.SetParent(newdirf.inode, newname)
482 func (fs *fileSystem) Remove(name string) error {
483 return fs.remove(strings.TrimRight(name, "/"), false)
486 func (fs *fileSystem) RemoveAll(name string) error {
487 err := fs.remove(strings.TrimRight(name, "/"), true)
488 if os.IsNotExist(err) {
489 // "If the path does not exist, RemoveAll returns
490 // nil." (see "os" pkg)
496 func (fs *fileSystem) remove(name string, recursive bool) (err error) {
497 dirname, name := path.Split(name)
498 if name == "" || name == "." || name == ".." {
499 return ErrInvalidArgument
501 dir := rlookup(fs.root, dirname)
503 return os.ErrNotExist
507 dir.Child(name, func(node inode) inode {
512 if !recursive && node.IsDir() && node.Size() > 0 {
513 err = ErrDirectoryNotEmpty
521 func (fs *fileSystem) Sync() error {
522 log.Printf("TODO: sync fileSystem")
523 return ErrInvalidOperation
526 // rlookup (recursive lookup) returns the inode for the file/directory
527 // with the given name (which may contain "/" separators). If no such
528 // file/directory exists, the returned node is nil.
529 func rlookup(start inode, path string) (node inode) {
531 for _, name := range strings.Split(path, "/") {
536 if name == "." || name == "" {
544 node = func() inode {
547 return node.Child(name, nil)