1 // Copyright (C) The Arvados Authors. All rights reserved.
3 // SPDX-License-Identifier: Apache-2.0
20 ErrReadOnlyFile = errors.New("read-only file")
21 ErrNegativeOffset = errors.New("cannot seek to negative offset")
22 ErrFileExists = errors.New("file exists")
23 ErrInvalidOperation = errors.New("invalid operation")
24 ErrInvalidArgument = errors.New("invalid argument")
25 ErrDirectoryNotEmpty = errors.New("directory not empty")
26 ErrWriteOnlyMode = errors.New("file is O_WRONLY")
27 ErrSyncNotSupported = errors.New("O_SYNC flag is not supported")
28 ErrIsDirectory = errors.New("cannot rename file to overwrite existing directory")
29 ErrPermission = os.ErrPermission
32 // A File is an *os.File-like interface for reading and writing files
40 Readdir(int) ([]os.FileInfo, error)
41 Stat() (os.FileInfo, error)
45 // A FileSystem is an http.Filesystem plus Stat() and support for
46 // opening writable files. All methods are safe to call from multiple
48 type FileSystem interface {
53 // analogous to os.Stat()
54 Stat(name string) (os.FileInfo, error)
56 // analogous to os.Create(): create/truncate a file and open it O_RDWR.
57 Create(name string) (File, error)
59 // Like os.OpenFile(): create or open a file or directory.
61 // If flag&os.O_EXCL==0, it opens an existing file or
62 // directory if one exists. If flag&os.O_CREATE!=0, it creates
63 // a new empty file or directory if one does not already
66 // When creating a new item, perm&os.ModeDir determines
67 // whether it is a file or a directory.
69 // A file can be opened multiple times and used concurrently
70 // from multiple goroutines. However, each File object should
71 // be used by only one goroutine at a time.
72 OpenFile(name string, flag int, perm os.FileMode) (File, error)
74 Mkdir(name string, perm os.FileMode) error
75 Remove(name string) error
76 RemoveAll(name string) error
77 Rename(oldname, newname string) error
80 type inode interface {
82 Read([]byte, filenodePtr) (int, filenodePtr, error)
83 Write([]byte, filenodePtr) (int, filenodePtr, error)
86 Readdir() []os.FileInfo
88 FileInfo() os.FileInfo
90 // Child() performs lookups and updates of named child nodes.
92 // If replace is non-nil, Child calls replace(x) where x is
93 // the current child inode with the given name. If possible,
94 // the child inode is replaced with the one returned by
97 // Nil represents "no child". replace(nil) signifies that no
98 // child with this name exists yet. If replace() returns nil,
99 // the existing child should be deleted if possible.
101 // An implementation of Child() is permitted to ignore
102 // replace() or its return value. For example, a regular file
103 // inode does not have children, so Child() always returns
106 // Child() returns the child, if any, with the given name: if
107 // a child was added or changed, the new child is returned.
109 // Caller must have lock (or rlock if replace is nil).
110 Child(name string, replace func(inode) inode) inode
117 type fileinfo struct {
124 // Name implements os.FileInfo.
125 func (fi fileinfo) Name() string {
129 // ModTime implements os.FileInfo.
130 func (fi fileinfo) ModTime() time.Time {
134 // Mode implements os.FileInfo.
135 func (fi fileinfo) Mode() os.FileMode {
139 // IsDir implements os.FileInfo.
140 func (fi fileinfo) IsDir() bool {
141 return fi.mode&os.ModeDir != 0
144 // Size implements os.FileInfo.
145 func (fi fileinfo) Size() int64 {
149 // Sys implements os.FileInfo.
150 func (fi fileinfo) Sys() interface{} {
154 type nullnode struct{}
156 func (*nullnode) Mkdir(string, os.FileMode) error {
157 return ErrInvalidOperation
160 func (*nullnode) Read([]byte, filenodePtr) (int, filenodePtr, error) {
161 return 0, filenodePtr{}, ErrInvalidOperation
164 func (*nullnode) Write([]byte, filenodePtr) (int, filenodePtr, error) {
165 return 0, filenodePtr{}, ErrInvalidOperation
168 func (*nullnode) Truncate(int64) error {
169 return ErrInvalidOperation
172 func (*nullnode) FileInfo() os.FileInfo {
176 func (*nullnode) IsDir() bool {
180 func (*nullnode) Readdir() []os.FileInfo {
184 func (*nullnode) Child(name string, replace func(inode) inode) inode {
188 type treenode struct {
190 inodes map[string]inode
196 func (n *treenode) Parent() inode {
202 func (n *treenode) IsDir() bool {
206 func (n *treenode) Child(name string, replace func(inode) inode) (child inode) {
207 // TODO: special treatment for "", ".", ".."
208 child = n.inodes[name]
210 child = replace(child)
212 delete(n.inodes, name)
214 n.inodes[name] = child
220 func (n *treenode) Size() int64 {
221 return n.FileInfo().Size()
224 func (n *treenode) FileInfo() os.FileInfo {
227 n.fileinfo.size = int64(len(n.inodes))
231 func (n *treenode) Readdir() (fi []os.FileInfo) {
234 fi = make([]os.FileInfo, 0, len(n.inodes))
235 for _, inode := range n.inodes {
236 fi = append(fi, inode.FileInfo())
241 type fileSystem struct {
245 // OpenFile is analogous to os.OpenFile().
246 func (fs *fileSystem) OpenFile(name string, flag int, perm os.FileMode) (File, error) {
247 return fs.openFile(name, flag, perm)
250 func (fs *fileSystem) openFile(name string, flag int, perm os.FileMode) (*filehandle, error) {
251 var dn inode = fs.inode
252 if flag&os.O_SYNC != 0 {
253 return nil, ErrSyncNotSupported
255 dirname, name := path.Split(name)
256 parent := rlookup(dn, dirname)
258 return nil, os.ErrNotExist
260 var readable, writable bool
261 switch flag & (os.O_RDWR | os.O_RDONLY | os.O_WRONLY) {
270 return nil, fmt.Errorf("invalid flags 0x%x", flag)
272 if !writable && parent.IsDir() {
273 // A directory can be opened via "foo/", "foo/.", or
277 return &filehandle{inode: parent}, nil
279 return &filehandle{inode: parent.Parent()}, nil
282 createMode := flag&os.O_CREATE != 0
285 defer parent.Unlock()
288 defer parent.RUnlock()
290 n := parent.Child(name, nil)
293 return nil, os.ErrNotExist
296 n = parent.Child(name, func(inode) inode {
298 switch parent := parent.(type) {
301 case *collectionFileSystem:
302 dn = parent.inode.(*dirnode)
304 err = ErrInvalidArgument
308 n, err = dn.newDirnode(dn, name, perm|0755, time.Now())
310 n, err = dn.newFilenode(dn, name, perm|0755, time.Now())
317 // parent rejected new child
318 return nil, ErrInvalidOperation
320 } else if flag&os.O_EXCL != 0 {
321 return nil, ErrFileExists
322 } else if flag&os.O_TRUNC != 0 {
324 return nil, fmt.Errorf("invalid flag O_TRUNC in read-only mode")
325 } else if fn, ok := n.(*filenode); !ok {
326 return nil, fmt.Errorf("invalid flag O_TRUNC when opening directory")
333 append: flag&os.O_APPEND != 0,
339 func (fs *fileSystem) Open(name string) (http.File, error) {
340 return fs.OpenFile(name, os.O_RDONLY, 0)
343 func (fs *fileSystem) Create(name string) (File, error) {
344 return fs.OpenFile(name, os.O_CREATE|os.O_RDWR|os.O_TRUNC, 0)
347 func (fs *fileSystem) Mkdir(name string, perm os.FileMode) (err error) {
348 dirname, name := path.Split(name)
349 n := rlookup(fs.inode, dirname)
351 return os.ErrNotExist
355 if n.Child(name, nil) != nil {
358 dn, ok := n.(*dirnode)
360 return ErrInvalidArgument
362 child := n.Child(name, func(inode) (child inode) {
363 child, err = dn.newDirnode(dn, name, perm, time.Now())
368 } else if child == nil {
369 return ErrInvalidArgument
374 func (fs *fileSystem) Stat(name string) (fi os.FileInfo, err error) {
375 node := rlookup(fs.inode, name)
384 func (fs *fileSystem) Rename(oldname, newname string) error {
385 olddir, oldname := path.Split(oldname)
386 if oldname == "" || oldname == "." || oldname == ".." {
387 return ErrInvalidArgument
389 olddirf, err := fs.openFile(olddir+".", os.O_RDONLY, 0)
391 return fmt.Errorf("%q: %s", olddir, err)
393 defer olddirf.Close()
395 newdir, newname := path.Split(newname)
396 if newname == "." || newname == ".." {
397 return ErrInvalidArgument
398 } else if newname == "" {
399 // Rename("a/b", "c/") means Rename("a/b", "c/b")
402 newdirf, err := fs.openFile(newdir+".", os.O_RDONLY, 0)
404 return fmt.Errorf("%q: %s", newdir, err)
406 defer newdirf.Close()
408 // When acquiring locks on multiple nodes, all common
409 // ancestors must be locked first in order to avoid
410 // deadlock. This is assured by locking the path from root to
411 // newdir, then locking the path from root to olddir, skipping
412 // any already-locked nodes.
413 needLock := []sync.Locker{}
414 for _, f := range []*filehandle{olddirf, newdirf} {
416 needLock = append(needLock, node)
417 for node.Parent() != node {
419 needLock = append(needLock, node)
422 locked := map[sync.Locker]bool{}
423 for i := len(needLock) - 1; i >= 0; i-- {
424 if n := needLock[i]; !locked[n] {
431 if _, ok := newdirf.inode.(*dirnode); !ok {
432 return ErrInvalidOperation
436 olddirf.inode.Child(oldname, func(oldinode inode) inode {
441 newdirf.inode.Child(newname, func(existing inode) inode {
442 if existing != nil && existing.IsDir() {
452 defer oldinode.Unlock()
453 olddn := olddirf.inode.(*dirnode)
454 newdn := newdirf.inode.(*dirnode)
455 switch n := oldinode.(type) {
457 n.parent = newdirf.inode
458 n.treenode.fileinfo.name = newname
461 n.fileinfo.name = newname
463 panic(fmt.Sprintf("bad inode type %T", n))
465 olddn.treenode.fileinfo.modTime = time.Now()
466 newdn.treenode.fileinfo.modTime = time.Now()
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, dirname)
493 return os.ErrNotExist
497 dir.Child(name, func(node inode) inode {
502 if !recursive && node.IsDir() && node.Size() > 0 {
503 err = ErrDirectoryNotEmpty