13111: Combine newDirnode and newFIlenode to just newNode.
[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(inode)
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) {
211         n.RLock()
212         defer n.RUnlock()
213         n.parent = p
214 }
215
216 func (n *treenode) Parent() inode {
217         n.RLock()
218         defer n.RUnlock()
219         return n.parent
220 }
221
222 func (n *treenode) IsDir() bool {
223         return true
224 }
225
226 func (n *treenode) Child(name string, replace func(inode) inode) (child inode) {
227         // TODO: special treatment for "", ".", ".."
228         child = n.inodes[name]
229         if replace != nil {
230                 newchild := replace(child)
231                 if newchild == nil {
232                         delete(n.inodes, name)
233                 } else if newchild != child {
234                         n.inodes[name] = newchild
235                         newchild.SetParent(n)
236                         child = newchild
237                 }
238         }
239         return
240 }
241
242 func (n *treenode) Size() int64 {
243         return n.FileInfo().Size()
244 }
245
246 func (n *treenode) FileInfo() os.FileInfo {
247         n.Lock()
248         defer n.Unlock()
249         n.fileinfo.size = int64(len(n.inodes))
250         return n.fileinfo
251 }
252
253 func (n *treenode) Readdir() (fi []os.FileInfo) {
254         n.RLock()
255         defer n.RUnlock()
256         fi = make([]os.FileInfo, 0, len(n.inodes))
257         for _, inode := range n.inodes {
258                 fi = append(fi, inode.FileInfo())
259         }
260         return
261 }
262
263 type fileSystem struct {
264         root inode
265         fsBackend
266 }
267
268 func (fs *fileSystem) rootnode() inode {
269         return fs.root
270 }
271
272 // OpenFile is analogous to os.OpenFile().
273 func (fs *fileSystem) OpenFile(name string, flag int, perm os.FileMode) (File, error) {
274         return fs.openFile(name, flag, perm)
275 }
276
277 func (fs *fileSystem) openFile(name string, flag int, perm os.FileMode) (*filehandle, error) {
278         if flag&os.O_SYNC != 0 {
279                 return nil, ErrSyncNotSupported
280         }
281         dirname, name := path.Split(name)
282         parent := rlookup(fs.root, dirname)
283         if parent == nil {
284                 return nil, os.ErrNotExist
285         }
286         var readable, writable bool
287         switch flag & (os.O_RDWR | os.O_RDONLY | os.O_WRONLY) {
288         case os.O_RDWR:
289                 readable = true
290                 writable = true
291         case os.O_RDONLY:
292                 readable = true
293         case os.O_WRONLY:
294                 writable = true
295         default:
296                 return nil, fmt.Errorf("invalid flags 0x%x", flag)
297         }
298         if !writable && parent.IsDir() {
299                 // A directory can be opened via "foo/", "foo/.", or
300                 // "foo/..".
301                 switch name {
302                 case ".", "":
303                         return &filehandle{inode: parent}, nil
304                 case "..":
305                         return &filehandle{inode: parent.Parent()}, nil
306                 }
307         }
308         createMode := flag&os.O_CREATE != 0
309         if createMode {
310                 parent.Lock()
311                 defer parent.Unlock()
312         } else {
313                 parent.RLock()
314                 defer parent.RUnlock()
315         }
316         n := parent.Child(name, nil)
317         if n == nil {
318                 if !createMode {
319                         return nil, os.ErrNotExist
320                 }
321                 var err error
322                 n = parent.Child(name, func(inode) inode {
323                         n, err = parent.FS().newNode(name, perm|0755, time.Now())
324                         return n
325                 })
326                 if err != nil {
327                         return nil, err
328                 } else if n == nil {
329                         // parent rejected new child
330                         return nil, ErrInvalidOperation
331                 }
332         } else if flag&os.O_EXCL != 0 {
333                 return nil, ErrFileExists
334         } else if flag&os.O_TRUNC != 0 {
335                 if !writable {
336                         return nil, fmt.Errorf("invalid flag O_TRUNC in read-only mode")
337                 } else if n.IsDir() {
338                         return nil, fmt.Errorf("invalid flag O_TRUNC when opening directory")
339                 } else if err := n.Truncate(0); err != nil {
340                         return nil, err
341                 }
342         }
343         return &filehandle{
344                 inode:    n,
345                 append:   flag&os.O_APPEND != 0,
346                 readable: readable,
347                 writable: writable,
348         }, nil
349 }
350
351 func (fs *fileSystem) Open(name string) (http.File, error) {
352         return fs.OpenFile(name, os.O_RDONLY, 0)
353 }
354
355 func (fs *fileSystem) Create(name string) (File, error) {
356         return fs.OpenFile(name, os.O_CREATE|os.O_RDWR|os.O_TRUNC, 0)
357 }
358
359 func (fs *fileSystem) Mkdir(name string, perm os.FileMode) (err error) {
360         dirname, name := path.Split(name)
361         n := rlookup(fs.root, dirname)
362         if n == nil {
363                 return os.ErrNotExist
364         }
365         n.Lock()
366         defer n.Unlock()
367         if n.Child(name, nil) != nil {
368                 return os.ErrExist
369         }
370         child := n.Child(name, func(inode) (child inode) {
371                 child, err = n.FS().newNode(name, perm|os.ModeDir, time.Now())
372                 return
373         })
374         if err != nil {
375                 return err
376         } else if child == nil {
377                 return ErrInvalidArgument
378         }
379         return nil
380 }
381
382 func (fs *fileSystem) Stat(name string) (fi os.FileInfo, err error) {
383         node := rlookup(fs.root, name)
384         if node == nil {
385                 err = os.ErrNotExist
386         } else {
387                 fi = node.FileInfo()
388         }
389         return
390 }
391
392 func (fs *fileSystem) Rename(oldname, newname string) error {
393         olddir, oldname := path.Split(oldname)
394         if oldname == "" || oldname == "." || oldname == ".." {
395                 return ErrInvalidArgument
396         }
397         olddirf, err := fs.openFile(olddir+".", os.O_RDONLY, 0)
398         if err != nil {
399                 return fmt.Errorf("%q: %s", olddir, err)
400         }
401         defer olddirf.Close()
402
403         newdir, newname := path.Split(newname)
404         if newname == "." || newname == ".." {
405                 return ErrInvalidArgument
406         } else if newname == "" {
407                 // Rename("a/b", "c/") means Rename("a/b", "c/b")
408                 newname = oldname
409         }
410         newdirf, err := fs.openFile(newdir+".", os.O_RDONLY, 0)
411         if err != nil {
412                 return fmt.Errorf("%q: %s", newdir, err)
413         }
414         defer newdirf.Close()
415
416         // When acquiring locks on multiple nodes, all common
417         // ancestors must be locked first in order to avoid
418         // deadlock. This is assured by locking the path from
419         // filesystem root to newdir, then locking the path from
420         // filesystem root to olddir, skipping any already-locked
421         // nodes.
422         needLock := []sync.Locker{}
423         for _, node := range []inode{olddirf.inode, newdirf.inode} {
424                 needLock = append(needLock, node)
425                 for node.Parent() != node && node.Parent().FS() == node.FS() {
426                         node = node.Parent()
427                         needLock = append(needLock, node)
428                 }
429         }
430         locked := map[sync.Locker]bool{}
431         for i := len(needLock) - 1; i >= 0; i-- {
432                 if n := needLock[i]; !locked[n] {
433                         n.Lock()
434                         defer n.Unlock()
435                         locked[n] = true
436                 }
437         }
438
439         err = nil
440         olddirf.inode.Child(oldname, func(oldinode inode) inode {
441                 if oldinode == nil {
442                         err = os.ErrNotExist
443                         return nil
444                 }
445                 newdirf.inode.Child(newname, func(existing inode) inode {
446                         if existing != nil && existing.IsDir() {
447                                 err = ErrIsDirectory
448                                 return existing
449                         }
450                         return oldinode
451                 })
452                 if err != nil {
453                         return oldinode
454                 }
455                 oldinode.Lock()
456                 defer oldinode.Unlock()
457                 switch n := oldinode.(type) {
458                 case *dirnode:
459                         n.parent = newdirf.inode
460                         n.fileinfo.name = newname
461                 case *filenode:
462                         n.parent = newdirf.inode
463                         n.fileinfo.name = newname
464                 default:
465                         panic(fmt.Sprintf("bad inode type %T", n))
466                 }
467                 //TODO: olddirf.setModTime(time.Now())
468                 //TODO: newdirf.setModTime(time.Now())
469                 return nil
470         })
471         return err
472 }
473
474 func (fs *fileSystem) Remove(name string) error {
475         return fs.remove(strings.TrimRight(name, "/"), false)
476 }
477
478 func (fs *fileSystem) RemoveAll(name string) error {
479         err := fs.remove(strings.TrimRight(name, "/"), true)
480         if os.IsNotExist(err) {
481                 // "If the path does not exist, RemoveAll returns
482                 // nil." (see "os" pkg)
483                 err = nil
484         }
485         return err
486 }
487
488 func (fs *fileSystem) remove(name string, recursive bool) (err error) {
489         dirname, name := path.Split(name)
490         if name == "" || name == "." || name == ".." {
491                 return ErrInvalidArgument
492         }
493         dir := rlookup(fs.root, dirname)
494         if dir == nil {
495                 return os.ErrNotExist
496         }
497         dir.Lock()
498         defer dir.Unlock()
499         dir.Child(name, func(node inode) inode {
500                 if node == nil {
501                         err = os.ErrNotExist
502                         return nil
503                 }
504                 if !recursive && node.IsDir() && node.Size() > 0 {
505                         err = ErrDirectoryNotEmpty
506                         return node
507                 }
508                 return nil
509         })
510         return err
511 }
512
513 func (fs *fileSystem) Sync() error {
514         log.Printf("TODO: sync fileSystem")
515         return ErrInvalidOperation
516 }
517
518 // rlookup (recursive lookup) returns the inode for the file/directory
519 // with the given name (which may contain "/" separators). If no such
520 // file/directory exists, the returned node is nil.
521 func rlookup(start inode, path string) (node inode) {
522         node = start
523         for _, name := range strings.Split(path, "/") {
524                 if node == nil {
525                         break
526                 }
527                 if node.IsDir() {
528                         if name == "." || name == "" {
529                                 continue
530                         }
531                         if name == ".." {
532                                 node = node.Parent()
533                                 continue
534                         }
535                 }
536                 node = func() inode {
537                         node.RLock()
538                         defer node.RUnlock()
539                         return node.Child(name, nil)
540                 }()
541         }
542         return
543 }