16778: Simplify logic
[arvados.git] / lib / mount / fs.go
1 // Copyright (C) The Arvados Authors. All rights reserved.
2 //
3 // SPDX-License-Identifier: Apache-2.0
4
5 package mount
6
7 import (
8         "io"
9         "log"
10         "os"
11         "runtime/debug"
12         "sync"
13
14         "git.arvados.org/arvados.git/sdk/go/arvados"
15         "git.arvados.org/arvados.git/sdk/go/keepclient"
16         "github.com/arvados/cgofuse/fuse"
17 )
18
19 // sharedFile wraps arvados.File with a sync.Mutex, so fuse can safely
20 // use a single filehandle concurrently on behalf of multiple
21 // threads/processes.
22 type sharedFile struct {
23         arvados.File
24         sync.Mutex
25 }
26
27 // keepFS implements cgofuse's FileSystemInterface.
28 type keepFS struct {
29         fuse.FileSystemBase
30         Client     *arvados.Client
31         KeepClient *keepclient.KeepClient
32         ReadOnly   bool
33         Uid        int
34         Gid        int
35
36         root   arvados.CustomFileSystem
37         open   map[uint64]*sharedFile
38         lastFH uint64
39         sync.RWMutex
40
41         // If non-nil, this channel will be closed by Init() to notify
42         // other goroutines that the mount is ready.
43         ready chan struct{}
44 }
45
46 var (
47         invalidFH = ^uint64(0)
48 )
49
50 // newFH wraps f in a sharedFile, adds it to fs's lookup table using a
51 // new handle number, and returns the handle number.
52 func (fs *keepFS) newFH(f arvados.File) uint64 {
53         fs.Lock()
54         defer fs.Unlock()
55         if fs.open == nil {
56                 fs.open = make(map[uint64]*sharedFile)
57         }
58         fs.lastFH++
59         fh := fs.lastFH
60         fs.open[fh] = &sharedFile{File: f}
61         return fh
62 }
63
64 func (fs *keepFS) lookupFH(fh uint64) *sharedFile {
65         fs.RLock()
66         defer fs.RUnlock()
67         return fs.open[fh]
68 }
69
70 func (fs *keepFS) Init() {
71         defer fs.debugPanics()
72         fs.root = fs.Client.SiteFileSystem(fs.KeepClient)
73         fs.root.MountProject("home", "")
74         if fs.ready != nil {
75                 close(fs.ready)
76         }
77 }
78
79 func (fs *keepFS) Create(path string, flags int, mode uint32) (errc int, fh uint64) {
80         defer fs.debugPanics()
81         if fs.ReadOnly {
82                 return -fuse.EROFS, invalidFH
83         }
84         f, err := fs.root.OpenFile(path, flags|os.O_CREATE, os.FileMode(mode))
85         if err == os.ErrExist {
86                 return -fuse.EEXIST, invalidFH
87         } else if err != nil {
88                 return -fuse.EINVAL, invalidFH
89         }
90         return 0, fs.newFH(f)
91 }
92
93 func (fs *keepFS) Open(path string, flags int) (errc int, fh uint64) {
94         defer fs.debugPanics()
95         if fs.ReadOnly && flags&(os.O_RDWR|os.O_WRONLY|os.O_CREATE) != 0 {
96                 return -fuse.EROFS, invalidFH
97         }
98         f, err := fs.root.OpenFile(path, flags, 0)
99         if err != nil {
100                 return -fuse.ENOENT, invalidFH
101         } else if fi, err := f.Stat(); err != nil {
102                 return -fuse.EIO, invalidFH
103         } else if fi.IsDir() {
104                 f.Close()
105                 return -fuse.EISDIR, invalidFH
106         }
107         return 0, fs.newFH(f)
108 }
109
110 func (fs *keepFS) Utimens(path string, tmsp []fuse.Timespec) int {
111         defer fs.debugPanics()
112         if fs.ReadOnly {
113                 return -fuse.EROFS
114         }
115         f, err := fs.root.OpenFile(path, 0, 0)
116         if err != nil {
117                 return fs.errCode(err)
118         }
119         f.Close()
120         return 0
121 }
122
123 func (fs *keepFS) errCode(err error) int {
124         if os.IsNotExist(err) {
125                 return -fuse.ENOENT
126         }
127         switch err {
128         case os.ErrExist:
129                 return -fuse.EEXIST
130         case arvados.ErrInvalidArgument:
131                 return -fuse.EINVAL
132         case arvados.ErrInvalidOperation:
133                 return -fuse.ENOSYS
134         case arvados.ErrDirectoryNotEmpty:
135                 return -fuse.ENOTEMPTY
136         case nil:
137                 return 0
138         default:
139                 return -fuse.EIO
140         }
141 }
142
143 func (fs *keepFS) Mkdir(path string, mode uint32) int {
144         defer fs.debugPanics()
145         if fs.ReadOnly {
146                 return -fuse.EROFS
147         }
148         f, err := fs.root.OpenFile(path, os.O_CREATE|os.O_EXCL, os.FileMode(mode)|os.ModeDir)
149         if err != nil {
150                 return fs.errCode(err)
151         }
152         f.Close()
153         return 0
154 }
155
156 func (fs *keepFS) Opendir(path string) (errc int, fh uint64) {
157         defer fs.debugPanics()
158         f, err := fs.root.OpenFile(path, 0, 0)
159         if err != nil {
160                 return fs.errCode(err), invalidFH
161         } else if fi, err := f.Stat(); err != nil {
162                 return fs.errCode(err), invalidFH
163         } else if !fi.IsDir() {
164                 f.Close()
165                 return -fuse.ENOTDIR, invalidFH
166         }
167         return 0, fs.newFH(f)
168 }
169
170 func (fs *keepFS) Releasedir(path string, fh uint64) (errc int) {
171         defer fs.debugPanics()
172         return fs.Release(path, fh)
173 }
174
175 func (fs *keepFS) Rmdir(path string) int {
176         defer fs.debugPanics()
177         return fs.errCode(fs.root.Remove(path))
178 }
179
180 func (fs *keepFS) Release(path string, fh uint64) (errc int) {
181         defer fs.debugPanics()
182         fs.Lock()
183         defer fs.Unlock()
184         defer delete(fs.open, fh)
185         if f := fs.open[fh]; f != nil {
186                 err := f.Close()
187                 if err != nil {
188                         return -fuse.EIO
189                 }
190         }
191         return 0
192 }
193
194 func (fs *keepFS) Rename(oldname, newname string) (errc int) {
195         defer fs.debugPanics()
196         if fs.ReadOnly {
197                 return -fuse.EROFS
198         }
199         return fs.errCode(fs.root.Rename(oldname, newname))
200 }
201
202 func (fs *keepFS) Unlink(path string) (errc int) {
203         defer fs.debugPanics()
204         if fs.ReadOnly {
205                 return -fuse.EROFS
206         }
207         return fs.errCode(fs.root.Remove(path))
208 }
209
210 func (fs *keepFS) Truncate(path string, size int64, fh uint64) (errc int) {
211         defer fs.debugPanics()
212         if fs.ReadOnly {
213                 return -fuse.EROFS
214         }
215
216         // Sometimes fh is a valid filehandle and we don't need to
217         // waste a name lookup.
218         if f := fs.lookupFH(fh); f != nil {
219                 return fs.errCode(f.Truncate(size))
220         }
221
222         // Other times, fh is invalid and we need to lookup path.
223         f, err := fs.root.OpenFile(path, os.O_RDWR, 0)
224         if err != nil {
225                 return fs.errCode(err)
226         }
227         defer f.Close()
228         return fs.errCode(f.Truncate(size))
229 }
230
231 func (fs *keepFS) Getattr(path string, stat *fuse.Stat_t, fh uint64) (errc int) {
232         defer fs.debugPanics()
233         var fi os.FileInfo
234         var err error
235         if f := fs.lookupFH(fh); f != nil {
236                 // Valid filehandle -- ignore path.
237                 fi, err = f.Stat()
238         } else {
239                 // Invalid filehandle -- lookup path.
240                 fi, err = fs.root.Stat(path)
241         }
242         if err != nil {
243                 return fs.errCode(err)
244         }
245         fs.fillStat(stat, fi)
246         return 0
247 }
248
249 func (fs *keepFS) Chmod(path string, mode uint32) (errc int) {
250         if fs.ReadOnly {
251                 return -fuse.EROFS
252         }
253         if fi, err := fs.root.Stat(path); err != nil {
254                 return fs.errCode(err)
255         } else if mode & ^uint32(fuse.S_IFREG|fuse.S_IFDIR|0777) != 0 {
256                 // Refuse to set mode bits other than
257                 // regfile/dir/perms
258                 return -fuse.ENOSYS
259         } else if (fi.Mode()&os.ModeDir != 0) != (mode&fuse.S_IFDIR != 0) {
260                 // Refuse to transform a regular file to a dir, or
261                 // vice versa
262                 return -fuse.ENOSYS
263         }
264         // As long as the change isn't nonsense, chmod is a no-op,
265         // because we don't save permission bits.
266         return 0
267 }
268
269 func (fs *keepFS) fillStat(stat *fuse.Stat_t, fi os.FileInfo) {
270         defer fs.debugPanics()
271         var m uint32
272         if fi.IsDir() {
273                 m = m | fuse.S_IFDIR
274         } else {
275                 m = m | fuse.S_IFREG
276         }
277         m = m | uint32(fi.Mode()&os.ModePerm)
278         stat.Mode = m
279         stat.Nlink = 1
280         stat.Size = fi.Size()
281         t := fuse.NewTimespec(fi.ModTime())
282         stat.Mtim = t
283         stat.Ctim = t
284         stat.Atim = t
285         stat.Birthtim = t
286         stat.Blksize = 1024
287         stat.Blocks = (stat.Size + stat.Blksize - 1) / stat.Blksize
288         if fs.Uid > 0 && int64(fs.Uid) < 1<<31 {
289                 stat.Uid = uint32(fs.Uid)
290         }
291         if fs.Gid > 0 && int64(fs.Gid) < 1<<31 {
292                 stat.Gid = uint32(fs.Gid)
293         }
294 }
295
296 func (fs *keepFS) Write(path string, buf []byte, ofst int64, fh uint64) (n int) {
297         defer fs.debugPanics()
298         if fs.ReadOnly {
299                 return -fuse.EROFS
300         }
301         f := fs.lookupFH(fh)
302         if f == nil {
303                 return -fuse.EBADF
304         }
305         f.Lock()
306         defer f.Unlock()
307         if _, err := f.Seek(ofst, io.SeekStart); err != nil {
308                 return fs.errCode(err)
309         }
310         n, err := f.Write(buf)
311         if err != nil {
312                 log.Printf("error writing %q: %s", path, err)
313                 return fs.errCode(err)
314         }
315         return n
316 }
317
318 func (fs *keepFS) Read(path string, buf []byte, ofst int64, fh uint64) (n int) {
319         defer fs.debugPanics()
320         f := fs.lookupFH(fh)
321         if f == nil {
322                 return -fuse.EBADF
323         }
324         f.Lock()
325         defer f.Unlock()
326         if _, err := f.Seek(ofst, io.SeekStart); err != nil {
327                 return fs.errCode(err)
328         }
329         n, err := f.Read(buf)
330         for err == nil && n < len(buf) {
331                 // f is an io.Reader ("If some data is available but
332                 // not len(p) bytes, Read conventionally returns what
333                 // is available instead of waiting for more") -- but
334                 // our caller requires us to either fill buf or reach
335                 // EOF.
336                 done := n
337                 n, err = f.Read(buf[done:])
338                 n += done
339         }
340         if err != nil && err != io.EOF {
341                 log.Printf("error reading %q: %s", path, err)
342                 return fs.errCode(err)
343         }
344         return n
345 }
346
347 func (fs *keepFS) Readdir(path string,
348         fill func(name string, stat *fuse.Stat_t, ofst int64) bool,
349         ofst int64,
350         fh uint64) (errc int) {
351         defer fs.debugPanics()
352         f := fs.lookupFH(fh)
353         if f == nil {
354                 return -fuse.EBADF
355         }
356         fill(".", nil, 0)
357         fill("..", nil, 0)
358         var stat fuse.Stat_t
359         fis, err := f.Readdir(-1)
360         if err != nil {
361                 return fs.errCode(err)
362         }
363         for _, fi := range fis {
364                 fs.fillStat(&stat, fi)
365                 fill(fi.Name(), &stat, 0)
366         }
367         return 0
368 }
369
370 func (fs *keepFS) Fsync(path string, datasync bool, fh uint64) int {
371         defer fs.debugPanics()
372         f := fs.lookupFH(fh)
373         if f == nil {
374                 return -fuse.EBADF
375         }
376         return fs.errCode(f.Sync())
377 }
378
379 func (fs *keepFS) Fsyncdir(path string, datasync bool, fh uint64) int {
380         return fs.Fsync(path, datasync, fh)
381 }
382
383 // debugPanics (when deferred by keepFS handlers) prints an error and
384 // stack trace on stderr when a handler crashes. (Without this,
385 // cgofuse recovers from panics silently and returns EIO.)
386 func (fs *keepFS) debugPanics() {
387         if err := recover(); err != nil {
388                 log.Printf("(%T) %v", err, err)
389                 debug.PrintStack()
390                 panic(err)
391         }
392 }