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