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) 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
103 f, err := fs.root.OpenFile(path, flags, 0)
105 return -fuse.ENOENT, invalidFH
106 } else if fi, err := f.Stat(); err != nil {
107 return -fuse.EIO, invalidFH
108 } else if fi.IsDir() {
110 return -fuse.EISDIR, invalidFH
112 return 0, fs.newFH(f)
115 func (fs *keepFS) Utimens(path string, tmsp []fuse.Timespec) int {
116 defer fs.debugPanics()
117 fs.debugOp("Utimens", path)
121 f, err := fs.root.OpenFile(path, 0, 0)
123 return fs.errCode("Utimens", path, err)
129 func (fs *keepFS) errCode(op, path string, err error) (errc int) {
134 fs.Logger.WithFields(logrus.Fields{
139 }).Debug("fuse call returned error")
141 if errors.Is(err, os.ErrNotExist) {
144 if errors.Is(err, os.ErrExist) {
147 if errors.Is(err, arvados.ErrInvalidArgument) {
150 if errors.Is(err, arvados.ErrInvalidOperation) {
153 if errors.Is(err, arvados.ErrDirectoryNotEmpty) {
154 return -fuse.ENOTEMPTY
159 func (fs *keepFS) Mkdir(path string, mode uint32) int {
160 defer fs.debugPanics()
161 fs.debugOp("Mkdir", path)
165 f, err := fs.root.OpenFile(path, os.O_CREATE|os.O_EXCL, os.FileMode(mode)|os.ModeDir)
167 return fs.errCode("Mkdir", path, err)
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)
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() {
183 return -fuse.ENOTDIR, invalidFH
185 return 0, fs.newFH(f)
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)
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))
200 func (fs *keepFS) Release(path string, fh uint64) (errc int) {
201 defer fs.debugPanics()
202 fs.debugOp("Release", path)
205 defer delete(fs.open, fh)
206 if f := fs.open[fh]; f != nil {
215 func (fs *keepFS) Rename(oldname, newname string) (errc int) {
216 defer fs.debugPanics()
217 fs.debugOp("Rename", oldname+" -> "+newname)
221 return fs.errCode("Rename", oldname+" -> "+newname, fs.root.Rename(oldname, newname))
224 func (fs *keepFS) Unlink(path string) (errc int) {
225 defer fs.debugPanics()
226 fs.debugOp("Unlink", path)
230 return fs.errCode("Unlink", path, fs.root.Remove(path))
233 func (fs *keepFS) Truncate(path string, size int64, fh uint64) (errc int) {
234 defer fs.debugPanics()
235 fs.debugOp("Truncate", path)
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))
246 // Other times, fh is invalid and we need to lookup path.
247 f, err := fs.root.OpenFile(path, os.O_RDWR, 0)
249 return fs.errCode("Truncate", path, err)
252 return fs.errCode("Truncate", path, f.Truncate(size))
255 func (fs *keepFS) Getattr(path string, stat *fuse.Stat_t, fh uint64) (errc int) {
256 defer fs.debugPanics()
257 fs.debugOp("Getattr", path)
260 if f := fs.lookupFH(fh); f != nil {
261 // Valid filehandle -- ignore path.
264 // Invalid filehandle -- lookup path.
265 fi, err = fs.root.Stat(path)
268 return fs.errCode("Getattr", path, err)
270 fs.fillStat(stat, fi)
274 func (fs *keepFS) Chmod(path string, mode uint32) (errc int) {
275 defer fs.debugPanics()
276 fs.debugOp("Chmod", path)
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
286 } else if (fi.Mode()&os.ModeDir != 0) != (mode&fuse.S_IFDIR != 0) {
287 // Refuse to transform a regular file to a dir, or
291 // As long as the change isn't nonsense, chmod is a no-op,
292 // because we don't save permission bits.
296 func (fs *keepFS) fillStat(stat *fuse.Stat_t, fi os.FileInfo) {
297 defer fs.debugPanics()
304 m = m | uint32(fi.Mode()&os.ModePerm)
307 stat.Size = fi.Size()
308 t := fuse.NewTimespec(fi.ModTime())
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)
318 if fs.Gid > 0 && int64(fs.Gid) < 1<<31 {
319 stat.Gid = uint32(fs.Gid)
323 func (fs *keepFS) Write(path string, buf []byte, ofst int64, fh uint64) (n int) {
324 defer fs.debugPanics()
325 fs.debugOp("Write", path)
335 if _, err := f.Seek(ofst, io.SeekStart); err != nil {
336 return fs.errCode("Write", path, err)
338 n, err := f.Write(buf)
340 return fs.errCode("Write", path, err)
345 func (fs *keepFS) Read(path string, buf []byte, ofst int64, fh uint64) (n int) {
346 defer fs.debugPanics()
347 fs.debugOp("Read", path)
354 if _, err := f.Seek(ofst, io.SeekStart); err != nil {
355 return fs.errCode("Read", path, err)
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
365 n, err = f.Read(buf[done:])
368 if err != nil && err != io.EOF {
369 return fs.errCode("Read", path, err)
374 func (fs *keepFS) Readdir(path string,
375 fill func(name string, stat *fuse.Stat_t, ofst int64) bool,
377 fh uint64) (errc int) {
378 defer fs.debugPanics()
379 fs.debugOp("Readdir", path)
387 fis, err := f.Readdir(-1)
389 return fs.errCode("Readdir", path, err)
391 for _, fi := range fis {
392 fs.fillStat(&stat, fi)
393 fill(fi.Name(), &stat, 0)
398 func (fs *keepFS) Fsync(path string, datasync bool, fh uint64) int {
399 defer fs.debugPanics()
400 fs.debugOp("Fsync", path)
405 return fs.errCode("Fsync", path, f.Sync())
408 func (fs *keepFS) Fsyncdir(path string, datasync bool, fh uint64) int {
409 fs.debugOp("Fsyncdir", path)
410 return fs.Fsync(path, datasync, fh)
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)
424 func (fs *keepFS) debugOp(op, path string) {
425 fs.Logger.WithFields(nil).Tracef("fuse call %s %s", op, path)