22017: Configurable instance type/family mapping.
[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) Open(path string, flags int) (errc int, fh uint64) {
98         defer fs.debugPanics()
99         fs.debugOp("Open", path)
100         if fs.ReadOnly && flags&(os.O_RDWR|os.O_WRONLY|os.O_CREATE) != 0 {
101                 return -fuse.EROFS, invalidFH
102         }
103         f, err := fs.root.OpenFile(path, flags, 0)
104         if err != nil {
105                 return -fuse.ENOENT, invalidFH
106         } else if fi, err := f.Stat(); err != nil {
107                 return -fuse.EIO, invalidFH
108         } else if fi.IsDir() {
109                 f.Close()
110                 return -fuse.EISDIR, invalidFH
111         }
112         return 0, fs.newFH(f)
113 }
114
115 func (fs *keepFS) Utimens(path string, tmsp []fuse.Timespec) int {
116         defer fs.debugPanics()
117         fs.debugOp("Utimens", path)
118         if fs.ReadOnly {
119                 return -fuse.EROFS
120         }
121         f, err := fs.root.OpenFile(path, 0, 0)
122         if err != nil {
123                 return fs.errCode("Utimens", path, err)
124         }
125         f.Close()
126         return 0
127 }
128
129 func (fs *keepFS) errCode(op, path string, err error) (errc int) {
130         if err == nil {
131                 return 0
132         }
133         defer func() {
134                 fs.Logger.WithFields(logrus.Fields{
135                         "op":    op,
136                         "path":  path,
137                         "errno": errc,
138                         "error": err,
139                 }).Debug("fuse call returned error")
140         }()
141         if errors.Is(err, os.ErrNotExist) {
142                 return -fuse.ENOENT
143         }
144         if errors.Is(err, os.ErrExist) {
145                 return -fuse.EEXIST
146         }
147         if errors.Is(err, arvados.ErrInvalidArgument) {
148                 return -fuse.EINVAL
149         }
150         if errors.Is(err, arvados.ErrInvalidOperation) {
151                 return -fuse.ENOSYS
152         }
153         if errors.Is(err, arvados.ErrDirectoryNotEmpty) {
154                 return -fuse.ENOTEMPTY
155         }
156         return -fuse.EIO
157 }
158
159 func (fs *keepFS) Mkdir(path string, mode uint32) int {
160         defer fs.debugPanics()
161         fs.debugOp("Mkdir", path)
162         if fs.ReadOnly {
163                 return -fuse.EROFS
164         }
165         f, err := fs.root.OpenFile(path, os.O_CREATE|os.O_EXCL, os.FileMode(mode)|os.ModeDir)
166         if err != nil {
167                 return fs.errCode("Mkdir", path, err)
168         }
169         f.Close()
170         return 0
171 }
172
173 func (fs *keepFS) Opendir(path string) (errc int, fh uint64) {
174         defer fs.debugPanics()
175         fs.debugOp("Opendir", path)
176         f, err := fs.root.OpenFile(path, 0, 0)
177         if err != nil {
178                 return fs.errCode("Opendir", path, err), invalidFH
179         } else if fi, err := f.Stat(); err != nil {
180                 return fs.errCode("Opendir", path, err), invalidFH
181         } else if !fi.IsDir() {
182                 f.Close()
183                 return -fuse.ENOTDIR, invalidFH
184         }
185         return 0, fs.newFH(f)
186 }
187
188 func (fs *keepFS) Releasedir(path string, fh uint64) (errc int) {
189         defer fs.debugPanics()
190         fs.debugOp("Releasedir", path)
191         return fs.Release(path, fh)
192 }
193
194 func (fs *keepFS) Rmdir(path string) int {
195         defer fs.debugPanics()
196         fs.debugOp("Rmdir", path)
197         return fs.errCode("Rmdir", path, fs.root.Remove(path))
198 }
199
200 func (fs *keepFS) Release(path string, fh uint64) (errc int) {
201         defer fs.debugPanics()
202         fs.debugOp("Release", path)
203         fs.Lock()
204         defer fs.Unlock()
205         defer delete(fs.open, fh)
206         if f := fs.open[fh]; f != nil {
207                 err := f.Close()
208                 if err != nil {
209                         return -fuse.EIO
210                 }
211         }
212         return 0
213 }
214
215 func (fs *keepFS) Rename(oldname, newname string) (errc int) {
216         defer fs.debugPanics()
217         fs.debugOp("Rename", oldname+" -> "+newname)
218         if fs.ReadOnly {
219                 return -fuse.EROFS
220         }
221         return fs.errCode("Rename", oldname+" -> "+newname, fs.root.Rename(oldname, newname))
222 }
223
224 func (fs *keepFS) Unlink(path string) (errc int) {
225         defer fs.debugPanics()
226         fs.debugOp("Unlink", path)
227         if fs.ReadOnly {
228                 return -fuse.EROFS
229         }
230         return fs.errCode("Unlink", path, fs.root.Remove(path))
231 }
232
233 func (fs *keepFS) Truncate(path string, size int64, fh uint64) (errc int) {
234         defer fs.debugPanics()
235         fs.debugOp("Truncate", path)
236         if fs.ReadOnly {
237                 return -fuse.EROFS
238         }
239
240         // Sometimes fh is a valid filehandle and we don't need to
241         // waste a name lookup.
242         if f := fs.lookupFH(fh); f != nil {
243                 return fs.errCode("Truncate", path, f.Truncate(size))
244         }
245
246         // Other times, fh is invalid and we need to lookup path.
247         f, err := fs.root.OpenFile(path, os.O_RDWR, 0)
248         if err != nil {
249                 return fs.errCode("Truncate", path, err)
250         }
251         defer f.Close()
252         return fs.errCode("Truncate", path, f.Truncate(size))
253 }
254
255 func (fs *keepFS) Getattr(path string, stat *fuse.Stat_t, fh uint64) (errc int) {
256         defer fs.debugPanics()
257         fs.debugOp("Getattr", path)
258         var fi os.FileInfo
259         var err error
260         if f := fs.lookupFH(fh); f != nil {
261                 // Valid filehandle -- ignore path.
262                 fi, err = f.Stat()
263         } else {
264                 // Invalid filehandle -- lookup path.
265                 fi, err = fs.root.Stat(path)
266         }
267         if err != nil {
268                 return fs.errCode("Getattr", path, err)
269         }
270         fs.fillStat(stat, fi)
271         return 0
272 }
273
274 func (fs *keepFS) Chmod(path string, mode uint32) (errc int) {
275         defer fs.debugPanics()
276         fs.debugOp("Chmod", path)
277         if fs.ReadOnly {
278                 return -fuse.EROFS
279         }
280         if fi, err := fs.root.Stat(path); err != nil {
281                 return fs.errCode("Chmod", path, err)
282         } else if mode & ^uint32(fuse.S_IFREG|fuse.S_IFDIR|0777) != 0 {
283                 // Refuse to set mode bits other than
284                 // regfile/dir/perms
285                 return -fuse.ENOSYS
286         } else if (fi.Mode()&os.ModeDir != 0) != (mode&fuse.S_IFDIR != 0) {
287                 // Refuse to transform a regular file to a dir, or
288                 // vice versa
289                 return -fuse.ENOSYS
290         }
291         // As long as the change isn't nonsense, chmod is a no-op,
292         // because we don't save permission bits.
293         return 0
294 }
295
296 func (fs *keepFS) fillStat(stat *fuse.Stat_t, fi os.FileInfo) {
297         defer fs.debugPanics()
298         var m uint32
299         if fi.IsDir() {
300                 m = m | fuse.S_IFDIR
301         } else {
302                 m = m | fuse.S_IFREG
303         }
304         m = m | uint32(fi.Mode()&os.ModePerm)
305         stat.Mode = m
306         stat.Nlink = 1
307         stat.Size = fi.Size()
308         t := fuse.NewTimespec(fi.ModTime())
309         stat.Mtim = t
310         stat.Ctim = t
311         stat.Atim = t
312         stat.Birthtim = t
313         stat.Blksize = 1024
314         stat.Blocks = (stat.Size + stat.Blksize - 1) / stat.Blksize
315         if fs.Uid > 0 && int64(fs.Uid) < 1<<31 {
316                 stat.Uid = uint32(fs.Uid)
317         }
318         if fs.Gid > 0 && int64(fs.Gid) < 1<<31 {
319                 stat.Gid = uint32(fs.Gid)
320         }
321 }
322
323 func (fs *keepFS) Write(path string, buf []byte, ofst int64, fh uint64) (n int) {
324         defer fs.debugPanics()
325         fs.debugOp("Write", path)
326         if fs.ReadOnly {
327                 return -fuse.EROFS
328         }
329         f := fs.lookupFH(fh)
330         if f == nil {
331                 return -fuse.EBADF
332         }
333         f.Lock()
334         defer f.Unlock()
335         if _, err := f.Seek(ofst, io.SeekStart); err != nil {
336                 return fs.errCode("Write", path, err)
337         }
338         n, err := f.Write(buf)
339         if err != nil {
340                 return fs.errCode("Write", path, err)
341         }
342         return n
343 }
344
345 func (fs *keepFS) Read(path string, buf []byte, ofst int64, fh uint64) (n int) {
346         defer fs.debugPanics()
347         fs.debugOp("Read", path)
348         f := fs.lookupFH(fh)
349         if f == nil {
350                 return -fuse.EBADF
351         }
352         f.Lock()
353         defer f.Unlock()
354         if _, err := f.Seek(ofst, io.SeekStart); err != nil {
355                 return fs.errCode("Read", path, err)
356         }
357         n, err := f.Read(buf)
358         for err == nil && n < len(buf) {
359                 // f is an io.Reader ("If some data is available but
360                 // not len(p) bytes, Read conventionally returns what
361                 // is available instead of waiting for more") -- but
362                 // our caller requires us to either fill buf or reach
363                 // EOF.
364                 done := n
365                 n, err = f.Read(buf[done:])
366                 n += done
367         }
368         if err != nil && err != io.EOF {
369                 return fs.errCode("Read", path, err)
370         }
371         return n
372 }
373
374 func (fs *keepFS) Readdir(path string,
375         fill func(name string, stat *fuse.Stat_t, ofst int64) bool,
376         ofst int64,
377         fh uint64) (errc int) {
378         defer fs.debugPanics()
379         fs.debugOp("Readdir", path)
380         f := fs.lookupFH(fh)
381         if f == nil {
382                 return -fuse.EBADF
383         }
384         fill(".", nil, 0)
385         fill("..", nil, 0)
386         var stat fuse.Stat_t
387         fis, err := f.Readdir(-1)
388         if err != nil {
389                 return fs.errCode("Readdir", path, err)
390         }
391         for _, fi := range fis {
392                 fs.fillStat(&stat, fi)
393                 fill(fi.Name(), &stat, 0)
394         }
395         return 0
396 }
397
398 func (fs *keepFS) Fsync(path string, datasync bool, fh uint64) int {
399         defer fs.debugPanics()
400         fs.debugOp("Fsync", path)
401         f := fs.lookupFH(fh)
402         if f == nil {
403                 return -fuse.EBADF
404         }
405         return fs.errCode("Fsync", path, f.Sync())
406 }
407
408 func (fs *keepFS) Fsyncdir(path string, datasync bool, fh uint64) int {
409         fs.debugOp("Fsyncdir", path)
410         return fs.Fsync(path, datasync, fh)
411 }
412
413 // debugPanics (when deferred by keepFS handlers) prints an error and
414 // stack trace on stderr when a handler crashes. (Without this,
415 // cgofuse recovers from panics silently and returns EIO.)
416 func (fs *keepFS) debugPanics() {
417         if err := recover(); err != nil {
418                 log.Printf("(%T) %v", err, err)
419                 debug.PrintStack()
420                 panic(err)
421         }
422 }
423
424 func (fs *keepFS) debugOp(op, path string) {
425         fs.Logger.WithFields(nil).Tracef("fuse call %s %s", op, path)
426 }