1 // Copyright (C) The Arvados Authors. All rights reserved.
3 // SPDX-License-Identifier: Apache-2.0
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"
21 // sharedFile wraps arvados.File with a sync.Mutex, so fuse can safely
22 // use a single filehandle concurrently on behalf of multiple
24 type sharedFile struct {
29 // keepFS implements cgofuse's FileSystemInterface.
32 Client *arvados.Client
33 KeepClient *keepclient.KeepClient
37 Logger logrus.FieldLogger
39 root arvados.CustomFileSystem
40 open map[uint64]*sharedFile
44 // If non-nil, this channel will be closed by Init() to notify
45 // other goroutines that the mount is ready.
50 invalidFH = ^uint64(0)
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 {
59 fs.open = make(map[uint64]*sharedFile)
63 fs.open[fh] = &sharedFile{File: f}
67 func (fs *keepFS) lookupFH(fh uint64) *sharedFile {
73 func (fs *keepFS) Init() {
74 defer fs.debugPanics()
75 fs.root = fs.Client.SiteFileSystem(fs.KeepClient)
76 fs.root.MountProject("home", "")
82 func (fs *keepFS) Create(path string, flags int, mode uint32) (errc int, fh uint64) {
83 defer fs.debugPanics()
84 fs.debugOp("Create", path)
86 return -fuse.EROFS, invalidFH
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
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) {
104 _, err := fs.root.Stat(path)
111 f, err := fs.root.OpenFile(path, os.O_CREATE|os.O_EXCL, os.FileMode(mode)&os.ModePerm)
113 return fs.errCode("Mknod", path, err)
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
125 f, err := fs.root.OpenFile(path, flags, 0)
127 return -fuse.ENOENT, invalidFH
128 } else if fi, err := f.Stat(); err != nil {
129 return -fuse.EIO, invalidFH
130 } else if fi.IsDir() {
132 return -fuse.EISDIR, invalidFH
134 return 0, fs.newFH(f)
137 func (fs *keepFS) Utimens(path string, tmsp []fuse.Timespec) int {
138 defer fs.debugPanics()
139 fs.debugOp("Utimens", path)
143 f, err := fs.root.OpenFile(path, 0, 0)
145 return fs.errCode("Utimens", path, err)
151 func (fs *keepFS) errCode(op, path string, err error) (errc int) {
156 fs.Logger.WithFields(logrus.Fields{
161 }).Debug("fuse call returned error")
163 if errors.Is(err, os.ErrNotExist) {
166 if errors.Is(err, os.ErrExist) {
169 if errors.Is(err, arvados.ErrInvalidArgument) {
172 if errors.Is(err, arvados.ErrInvalidOperation) {
175 if errors.Is(err, arvados.ErrDirectoryNotEmpty) {
176 return -fuse.ENOTEMPTY
181 func (fs *keepFS) Mkdir(path string, mode uint32) int {
182 defer fs.debugPanics()
183 fs.debugOp("Mkdir", path)
187 f, err := fs.root.OpenFile(path, os.O_CREATE|os.O_EXCL, os.FileMode(mode)|os.ModeDir)
189 return fs.errCode("Mkdir", path, err)
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)
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() {
205 return -fuse.ENOTDIR, invalidFH
207 return 0, fs.newFH(f)
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)
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))
222 func (fs *keepFS) Release(path string, fh uint64) (errc int) {
223 defer fs.debugPanics()
224 fs.debugOp("Release", path)
227 defer delete(fs.open, fh)
228 if f := fs.open[fh]; f != nil {
237 func (fs *keepFS) Rename(oldname, newname string) (errc int) {
238 defer fs.debugPanics()
239 fs.debugOp("Rename", oldname+" -> "+newname)
243 return fs.errCode("Rename", oldname+" -> "+newname, fs.root.Rename(oldname, newname))
246 func (fs *keepFS) Unlink(path string) (errc int) {
247 defer fs.debugPanics()
248 fs.debugOp("Unlink", path)
252 return fs.errCode("Unlink", path, fs.root.Remove(path))
255 func (fs *keepFS) Truncate(path string, size int64, fh uint64) (errc int) {
256 defer fs.debugPanics()
257 fs.debugOp("Truncate", path)
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))
268 // Other times, fh is invalid and we need to lookup path.
269 f, err := fs.root.OpenFile(path, os.O_RDWR, 0)
271 return fs.errCode("Truncate", path, err)
274 return fs.errCode("Truncate", path, f.Truncate(size))
277 func (fs *keepFS) Getattr(path string, stat *fuse.Stat_t, fh uint64) (errc int) {
278 defer fs.debugPanics()
279 fs.debugOp("Getattr", path)
282 if f := fs.lookupFH(fh); f != nil {
283 // Valid filehandle -- ignore path.
286 // Invalid filehandle -- lookup path.
287 fi, err = fs.root.Stat(path)
290 return fs.errCode("Getattr", path, err)
292 fs.fillStat(stat, fi)
296 func (fs *keepFS) Chmod(path string, mode uint32) (errc int) {
297 defer fs.debugPanics()
298 fs.debugOp("Chmod", path)
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
308 } else if (fi.Mode()&os.ModeDir != 0) != (mode&fuse.S_IFDIR != 0) {
309 // Refuse to transform a regular file to a dir, or
313 // As long as the change isn't nonsense, chmod is a no-op,
314 // because we don't save permission bits.
318 func (fs *keepFS) fillStat(stat *fuse.Stat_t, fi os.FileInfo) {
319 defer fs.debugPanics()
326 m = m | uint32(fi.Mode()&os.ModePerm)
329 stat.Size = fi.Size()
330 t := fuse.NewTimespec(fi.ModTime())
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)
340 if fs.Gid > 0 && int64(fs.Gid) < 1<<31 {
341 stat.Gid = uint32(fs.Gid)
345 func (fs *keepFS) Write(path string, buf []byte, ofst int64, fh uint64) (n int) {
346 defer fs.debugPanics()
347 fs.debugOp("Write", path)
357 if _, err := f.Seek(ofst, io.SeekStart); err != nil {
358 return fs.errCode("Write", path, err)
360 n, err := f.Write(buf)
362 return fs.errCode("Write", path, err)
367 func (fs *keepFS) Read(path string, buf []byte, ofst int64, fh uint64) (n int) {
368 defer fs.debugPanics()
369 fs.debugOp("Read", path)
376 if _, err := f.Seek(ofst, io.SeekStart); err != nil {
377 return fs.errCode("Read", path, err)
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
387 n, err = f.Read(buf[done:])
390 if err != nil && err != io.EOF {
391 return fs.errCode("Read", path, err)
396 func (fs *keepFS) Readdir(path string,
397 fill func(name string, stat *fuse.Stat_t, ofst int64) bool,
399 fh uint64) (errc int) {
400 defer fs.debugPanics()
401 fs.debugOp("Readdir", path)
409 fis, err := f.Readdir(-1)
411 return fs.errCode("Readdir", path, err)
413 for _, fi := range fis {
414 fs.fillStat(&stat, fi)
415 fill(fi.Name(), &stat, 0)
420 func (fs *keepFS) Fsync(path string, datasync bool, fh uint64) int {
421 defer fs.debugPanics()
422 fs.debugOp("Fsync", path)
427 return fs.errCode("Fsync", path, f.Sync())
430 func (fs *keepFS) Fsyncdir(path string, datasync bool, fh uint64) int {
431 fs.debugOp("Fsyncdir", path)
432 return fs.Fsync(path, datasync, fh)
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)
446 func (fs *keepFS) debugOp(op, path string) {
447 fs.Logger.WithFields(nil).Tracef("fuse call %s %s", op, path)