13111: Port "rename a to a/b/c" fix from 11377b3b.
[arvados.git] / sdk / go / arvados / fs_base.go
1 // Copyright (C) The Arvados Authors. All rights reserved.
2 //
3 // SPDX-License-Identifier: Apache-2.0
4
5 package arvados
6
7 import (
8         "errors"
9         "fmt"
10         "io"
11         "log"
12         "net/http"
13         "os"
14         "path"
15         "strings"
16         "sync"
17         "time"
18 )
19
20 var (
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
31 )
32
33 // A File is an *os.File-like interface for reading and writing files
34 // in a FileSystem.
35 type File interface {
36         io.Reader
37         io.Writer
38         io.Closer
39         io.Seeker
40         Size() int64
41         Readdir(int) ([]os.FileInfo, error)
42         Stat() (os.FileInfo, error)
43         Truncate(int64) error
44         Sync() error
45 }
46
47 // A FileSystem is an http.Filesystem plus Stat() and support for
48 // opening writable files. All methods are safe to call from multiple
49 // goroutines.
50 type FileSystem interface {
51         http.FileSystem
52         fsBackend
53
54         rootnode() inode
55
56         // create a new node with nil parent.
57         newNode(name string, perm os.FileMode, modTime time.Time) (node inode, err error)
58
59         // analogous to os.Stat()
60         Stat(name string) (os.FileInfo, error)
61
62         // analogous to os.Create(): create/truncate a file and open it O_RDWR.
63         Create(name string) (File, error)
64
65         // Like os.OpenFile(): create or open a file or directory.
66         //
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
70         // exist.
71         //
72         // When creating a new item, perm&os.ModeDir determines
73         // whether it is a file or a directory.
74         //
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)
79
80         Mkdir(name string, perm os.FileMode) error
81         Remove(name string) error
82         RemoveAll(name string) error
83         Rename(oldname, newname string) error
84         Sync() error
85 }
86
87 type inode interface {
88         SetParent(parent inode, name string)
89         Parent() inode
90         FS() FileSystem
91         Read([]byte, filenodePtr) (int, filenodePtr, error)
92         Write([]byte, filenodePtr) (int, filenodePtr, error)
93         Truncate(int64) error
94         IsDir() bool
95         Readdir() []os.FileInfo
96         Size() int64
97         FileInfo() os.FileInfo
98
99         // Child() performs lookups and updates of named child nodes.
100         //
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
104         // replace().
105         //
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.
109         //
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
113         // nil.
114         //
115         // Child() returns the child, if any, with the given name: if
116         // a child was added or changed, the new child is returned.
117         //
118         // Caller must have lock (or rlock if replace is nil).
119         Child(name string, replace func(inode) inode) inode
120
121         sync.Locker
122         RLock()
123         RUnlock()
124 }
125
126 type fileinfo struct {
127         name    string
128         mode    os.FileMode
129         size    int64
130         modTime time.Time
131 }
132
133 // Name implements os.FileInfo.
134 func (fi fileinfo) Name() string {
135         return fi.name
136 }
137
138 // ModTime implements os.FileInfo.
139 func (fi fileinfo) ModTime() time.Time {
140         return fi.modTime
141 }
142
143 // Mode implements os.FileInfo.
144 func (fi fileinfo) Mode() os.FileMode {
145         return fi.mode
146 }
147
148 // IsDir implements os.FileInfo.
149 func (fi fileinfo) IsDir() bool {
150         return fi.mode&os.ModeDir != 0
151 }
152
153 // Size implements os.FileInfo.
154 func (fi fileinfo) Size() int64 {
155         return fi.size
156 }
157
158 // Sys implements os.FileInfo.
159 func (fi fileinfo) Sys() interface{} {
160         return nil
161 }
162
163 type nullnode struct{}
164
165 func (*nullnode) Mkdir(string, os.FileMode) error {
166         return ErrInvalidOperation
167 }
168
169 func (*nullnode) Read([]byte, filenodePtr) (int, filenodePtr, error) {
170         return 0, filenodePtr{}, ErrInvalidOperation
171 }
172
173 func (*nullnode) Write([]byte, filenodePtr) (int, filenodePtr, error) {
174         return 0, filenodePtr{}, ErrInvalidOperation
175 }
176
177 func (*nullnode) Truncate(int64) error {
178         return ErrInvalidOperation
179 }
180
181 func (*nullnode) FileInfo() os.FileInfo {
182         return fileinfo{}
183 }
184
185 func (*nullnode) IsDir() bool {
186         return false
187 }
188
189 func (*nullnode) Readdir() []os.FileInfo {
190         return nil
191 }
192
193 func (*nullnode) Child(name string, replace func(inode) inode) inode {
194         return nil
195 }
196
197 type treenode struct {
198         fs       FileSystem
199         parent   inode
200         inodes   map[string]inode
201         fileinfo fileinfo
202         sync.RWMutex
203         nullnode
204 }
205
206 func (n *treenode) FS() FileSystem {
207         return n.fs
208 }
209
210 func (n *treenode) SetParent(p inode, name string) {
211         n.Lock()
212         defer n.Unlock()
213         n.parent = p
214         n.fileinfo.name = name
215 }
216
217 func (n *treenode) Parent() inode {
218         n.RLock()
219         defer n.RUnlock()
220         return n.parent
221 }
222
223 func (n *treenode) IsDir() bool {
224         return true
225 }
226
227 func (n *treenode) Child(name string, replace func(inode) inode) (child inode) {
228         // TODO: special treatment for "", ".", ".."
229         child = n.inodes[name]
230         if replace != nil {
231                 newchild := replace(child)
232                 if newchild == nil {
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()
238                         child = newchild
239                 }
240         }
241         return
242 }
243
244 func (n *treenode) Size() int64 {
245         return n.FileInfo().Size()
246 }
247
248 func (n *treenode) FileInfo() os.FileInfo {
249         n.Lock()
250         defer n.Unlock()
251         n.fileinfo.size = int64(len(n.inodes))
252         return n.fileinfo
253 }
254
255 func (n *treenode) Readdir() (fi []os.FileInfo) {
256         n.RLock()
257         defer n.RUnlock()
258         fi = make([]os.FileInfo, 0, len(n.inodes))
259         for _, inode := range n.inodes {
260                 fi = append(fi, inode.FileInfo())
261         }
262         return
263 }
264
265 type fileSystem struct {
266         root inode
267         fsBackend
268 }
269
270 func (fs *fileSystem) rootnode() inode {
271         return fs.root
272 }
273
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)
277 }
278
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
282         }
283         dirname, name := path.Split(name)
284         parent := rlookup(fs.root, dirname)
285         if parent == nil {
286                 return nil, os.ErrNotExist
287         }
288         var readable, writable bool
289         switch flag & (os.O_RDWR | os.O_RDONLY | os.O_WRONLY) {
290         case os.O_RDWR:
291                 readable = true
292                 writable = true
293         case os.O_RDONLY:
294                 readable = true
295         case os.O_WRONLY:
296                 writable = true
297         default:
298                 return nil, fmt.Errorf("invalid flags 0x%x", flag)
299         }
300         if !writable && parent.IsDir() {
301                 // A directory can be opened via "foo/", "foo/.", or
302                 // "foo/..".
303                 switch name {
304                 case ".", "":
305                         return &filehandle{inode: parent}, nil
306                 case "..":
307                         return &filehandle{inode: parent.Parent()}, nil
308                 }
309         }
310         createMode := flag&os.O_CREATE != 0
311         if createMode {
312                 parent.Lock()
313                 defer parent.Unlock()
314         } else {
315                 parent.RLock()
316                 defer parent.RUnlock()
317         }
318         n := parent.Child(name, nil)
319         if n == nil {
320                 if !createMode {
321                         return nil, os.ErrNotExist
322                 }
323                 var err error
324                 n = parent.Child(name, func(inode) inode {
325                         n, err = parent.FS().newNode(name, perm|0755, time.Now())
326                         return n
327                 })
328                 if err != nil {
329                         return nil, err
330                 } else if n == nil {
331                         // parent rejected new child
332                         return nil, ErrInvalidOperation
333                 }
334         } else if flag&os.O_EXCL != 0 {
335                 return nil, ErrFileExists
336         } else if flag&os.O_TRUNC != 0 {
337                 if !writable {
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 {
342                         return nil, err
343                 }
344         }
345         return &filehandle{
346                 inode:    n,
347                 append:   flag&os.O_APPEND != 0,
348                 readable: readable,
349                 writable: writable,
350         }, nil
351 }
352
353 func (fs *fileSystem) Open(name string) (http.File, error) {
354         return fs.OpenFile(name, os.O_RDONLY, 0)
355 }
356
357 func (fs *fileSystem) Create(name string) (File, error) {
358         return fs.OpenFile(name, os.O_CREATE|os.O_RDWR|os.O_TRUNC, 0)
359 }
360
361 func (fs *fileSystem) Mkdir(name string, perm os.FileMode) (err error) {
362         dirname, name := path.Split(name)
363         n := rlookup(fs.root, dirname)
364         if n == nil {
365                 return os.ErrNotExist
366         }
367         n.Lock()
368         defer n.Unlock()
369         if n.Child(name, nil) != nil {
370                 return os.ErrExist
371         }
372         child := n.Child(name, func(inode) (child inode) {
373                 child, err = n.FS().newNode(name, perm|os.ModeDir, time.Now())
374                 return
375         })
376         if err != nil {
377                 return err
378         } else if child == nil {
379                 return ErrInvalidArgument
380         }
381         return nil
382 }
383
384 func (fs *fileSystem) Stat(name string) (fi os.FileInfo, err error) {
385         node := rlookup(fs.root, name)
386         if node == nil {
387                 err = os.ErrNotExist
388         } else {
389                 fi = node.FileInfo()
390         }
391         return
392 }
393
394 func (fs *fileSystem) Rename(oldname, newname string) error {
395         olddir, oldname := path.Split(oldname)
396         if oldname == "" || oldname == "." || oldname == ".." {
397                 return ErrInvalidArgument
398         }
399         olddirf, err := fs.openFile(olddir+".", os.O_RDONLY, 0)
400         if err != nil {
401                 return fmt.Errorf("%q: %s", olddir, err)
402         }
403         defer olddirf.Close()
404
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")
410                 newname = oldname
411         }
412         newdirf, err := fs.openFile(newdir+".", os.O_RDONLY, 0)
413         if err != nil {
414                 return fmt.Errorf("%q: %s", newdir, err)
415         }
416         defer newdirf.Close()
417
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
423         // nodes.
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() {
428                         node = node.Parent()
429                         needLock = append(needLock, node)
430                 }
431         }
432         locked := map[sync.Locker]bool{}
433         for i := len(needLock) - 1; i >= 0; i-- {
434                 if n := needLock[i]; !locked[n] {
435                         n.Lock()
436                         defer n.Unlock()
437                         locked[n] = true
438                 }
439         }
440
441         err = nil
442         olddirf.inode.Child(oldname, func(oldinode inode) inode {
443                 if oldinode == nil {
444                         err = os.ErrNotExist
445                         return nil
446                 }
447                 if locked[oldinode] {
448                         // oldinode cannot become a descendant of itself.
449                         err = ErrInvalidArgument
450                         return oldinode
451                 }
452                 accepted := newdirf.inode.Child(newname, func(existing inode) inode {
453                         if existing != nil && existing.IsDir() {
454                                 err = ErrIsDirectory
455                                 return existing
456                         }
457                         return oldinode
458                 })
459                 if accepted != oldinode {
460                         if err == nil {
461                                 // newdirf didn't accept oldinode.
462                                 err = ErrInvalidArgument
463                         }
464                         // Leave oldinode in olddir.
465                         return oldinode
466                 }
467                 return nil
468         })
469         return err
470 }
471
472 func (fs *fileSystem) Remove(name string) error {
473         return fs.remove(strings.TrimRight(name, "/"), false)
474 }
475
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)
481                 err = nil
482         }
483         return err
484 }
485
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
490         }
491         dir := rlookup(fs.root, dirname)
492         if dir == nil {
493                 return os.ErrNotExist
494         }
495         dir.Lock()
496         defer dir.Unlock()
497         dir.Child(name, func(node inode) inode {
498                 if node == nil {
499                         err = os.ErrNotExist
500                         return nil
501                 }
502                 if !recursive && node.IsDir() && node.Size() > 0 {
503                         err = ErrDirectoryNotEmpty
504                         return node
505                 }
506                 return nil
507         })
508         return err
509 }
510
511 func (fs *fileSystem) Sync() error {
512         log.Printf("TODO: sync fileSystem")
513         return ErrInvalidOperation
514 }
515
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) {
520         node = start
521         for _, name := range strings.Split(path, "/") {
522                 if node == nil {
523                         break
524                 }
525                 if node.IsDir() {
526                         if name == "." || name == "" {
527                                 continue
528                         }
529                         if name == ".." {
530                                 node = node.Parent()
531                                 continue
532                         }
533                 }
534                 node = func() inode {
535                         node.RLock()
536                         defer node.RUnlock()
537                         return node.Child(name, nil)
538                 }()
539         }
540         return
541 }