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