github.com/VertebrateResequencing/muxfys@v3.0.5+incompatible/filesystem.go (about) 1 // Copyright © 2017, 2018 Genome Research Limited 2 // Author: Sendu Bala <sb10@sanger.ac.uk>. 3 // The StatFs() code in this file is based on code in 4 // https://github.com/kahing/goofys Copyright 2015-2017 Ka-Hing Cheung, 5 // licensed under the Apache License, Version 2.0 (the "License"), stating: 6 // "You may not use this file except in compliance with the License. You may 7 // obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0" 8 // 9 // This file is part of muxfys. 10 // 11 // muxfys is free software: you can redistribute it and/or modify 12 // it under the terms of the GNU Lesser General Public License as published by 13 // the Free Software Foundation, either version 3 of the License, or 14 // (at your option) any later version. 15 // 16 // muxfys is distributed in the hope that it will be useful, 17 // but WITHOUT ANY WARRANTY; without even the implied warranty of 18 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 19 // GNU Lesser General Public License for more details. 20 // 21 // You should have received a copy of the GNU Lesser General Public License 22 // along with muxfys. If not, see <http://www.gnu.org/licenses/>. 23 24 package muxfys 25 26 // This file implements pathfs.FileSystem methods. 27 28 import ( 29 "bufio" 30 "io" 31 "os" 32 "path/filepath" 33 "strings" 34 "syscall" 35 "time" 36 37 "github.com/alexflint/go-filemutex" 38 "github.com/hanwen/go-fuse/fuse" 39 "github.com/hanwen/go-fuse/fuse/nodefs" 40 "github.com/hanwen/go-fuse/fuse/pathfs" 41 ) 42 43 const ( 44 blockSize = uint64(4096) 45 totalBlocks = uint64(274877906944) // 1PB / blockSize 46 inodes = uint64(1000000000) 47 ioSize = uint32(1048576) // 1MB 48 ) 49 50 // fileDetails checks the file is known and returns its attributes and the 51 // remote the file came from. If not known, returns ENOENT (which should never 52 // happen). 53 func (fs *MuxFys) fileDetails(name string, shouldBeWritable bool) (*fuse.Attr, *remote, fuse.Status) { 54 fs.mapMutex.RLock() 55 defer fs.mapMutex.RUnlock() 56 attr, exists := fs.files[name] 57 if !exists { 58 return nil, nil, fuse.ENOENT 59 } 60 61 r := fs.fileToRemote[name] 62 status := fuse.OK 63 if shouldBeWritable && !r.write { 64 status = fuse.EPERM 65 } 66 67 return attr, r, status 68 } 69 70 // StatFs returns a constant (faked) set of details describing a very large 71 // file system. 72 func (fs *MuxFys) StatFs(name string) *fuse.StatfsOut { 73 return &fuse.StatfsOut{ 74 Blocks: blockSize, 75 Bfree: totalBlocks, 76 Bavail: totalBlocks, 77 Files: inodes, 78 Ffree: inodes, 79 Bsize: ioSize, 80 // NameLen uint32 81 // Frsize uint32 82 // Padding uint32 83 // Spare [6]uint32 84 } 85 } 86 87 // OnMount prepares MuxFys for use once Mount() has been called. 88 func (fs *MuxFys) OnMount(nodeFs *pathfs.PathNodeFs) { 89 fs.mapMutex.Lock() 90 defer fs.mapMutex.Unlock() 91 // we need to establish that the root directory is a directory; the next 92 // attempt by the user to get it's contents will actually do the remote call 93 // to get the directory entries 94 fs.dirs[""] = fs.remotes 95 } 96 97 // GetAttr finds out about a given object, returning information from a 98 // permanent cache if possible. context is not currently used. 99 func (fs *MuxFys) GetAttr(name string, context *fuse.Context) (*fuse.Attr, fuse.Status) { 100 fs.mapMutex.Lock() 101 defer fs.mapMutex.Unlock() 102 103 if _, isDir := fs.dirs[name]; isDir { 104 return fs.dirAttr, fuse.OK 105 } 106 107 if attr, cached := fs.files[name]; cached { 108 return attr, fuse.OK 109 } 110 111 // rather than call StatObject on name to see if its a file, it's more 112 // efficient to try and open it's parent directory and see if that resulted 113 // in us caching name as one of the parent's contents 114 parent := filepath.Dir(name) 115 if parent == "/" || parent == "." { 116 parent = "" 117 } 118 if _, cached := fs.dirContents[parent]; !cached { 119 // we must populate the contents of parent first, doing the essential 120 // part of OpenDir() 121 if remotes, exists := fs.dirs[parent]; exists { 122 for _, r := range remotes { 123 status := fs.openDir(r, parent) 124 if status != fuse.OK { 125 fs.Warn("GetAttr openDir failed", "path", parent, "status", status) 126 } 127 } 128 } 129 130 if _, isDir := fs.dirs[name]; isDir { 131 return fs.dirAttr, fuse.OK 132 } 133 134 if attr, cached := fs.files[name]; cached { 135 return attr, fuse.OK 136 } 137 } 138 return nil, fuse.ENOENT 139 } 140 141 // OpenDir gets the contents of the given directory for eg. `ls` purposes. It 142 // also caches the attributes of all the files within. context is not currently 143 // used. 144 func (fs *MuxFys) OpenDir(name string, context *fuse.Context) ([]fuse.DirEntry, fuse.Status) { 145 fs.mapMutex.Lock() 146 defer fs.mapMutex.Unlock() 147 148 remotes, exists := fs.dirs[name] 149 if !exists { 150 return nil, fuse.ENOENT 151 } 152 153 entries, cached := fs.dirContents[name] 154 if cached { 155 return entries, fuse.OK 156 } 157 158 // openDir in all remotes that have this dir, then return the combined dir 159 // contents from the cache 160 for _, r := range remotes { 161 status := fs.openDir(r, name) 162 if status != fuse.OK { 163 fs.Warn("GetAttr openDir failed", "path", name, "status", status) 164 } 165 } 166 167 entries, cached = fs.dirContents[name] 168 if cached { 169 return entries, fuse.OK 170 } 171 return nil, fuse.ENOENT 172 } 173 174 // openDir gets the contents of the given name, treating it as a directory, 175 // caching the attributes of its contents. Must be called while you have the 176 // mapMutex Locked. 177 func (fs *MuxFys) openDir(r *remote, name string) fuse.Status { 178 remotePath := r.getRemotePath(name) 179 if remotePath != "" { 180 remotePath += "/" 181 } 182 183 objects, status := r.findObjects(remotePath) 184 185 if status != fuse.OK || len(objects) == 0 { 186 if name == "" { 187 // allow the root to be a non-existent directory 188 fs.dirs[name] = append(fs.dirs[name], r) 189 if _, exists := fs.dirContents[name]; !exists { 190 fs.dirContents[name] = []fuse.DirEntry{} 191 } 192 return fuse.OK 193 } else if status == fuse.OK { 194 return fuse.ENOENT 195 } 196 return status 197 } 198 199 var isDir bool 200 for _, object := range objects { 201 if object.Name == name { 202 continue 203 } 204 isDir = true 205 206 d := fuse.DirEntry{ 207 Name: object.Name[len(remotePath):], 208 } 209 if d.Name == "" { 210 continue 211 } 212 213 if strings.HasSuffix(d.Name, "/") { 214 d.Mode = uint32(fuse.S_IFDIR) 215 d.Name = d.Name[0 : len(d.Name)-1] 216 thisPath := filepath.Join(name, d.Name) 217 fs.dirs[thisPath] = append(fs.dirs[thisPath], r) 218 } else { 219 d.Mode = uint32(fuse.S_IFREG) 220 thisPath := filepath.Join(name, d.Name) 221 mTime := uint64(object.MTime.Unix()) 222 attr := &fuse.Attr{ 223 Mode: fuse.S_IFREG | uint32(fileMode), 224 Size: uint64(object.Size), 225 Mtime: mTime, 226 Atime: mTime, 227 Ctime: mTime, 228 } 229 fs.files[thisPath] = attr 230 fs.fileToRemote[thisPath] = r 231 } 232 fs.dirContents[name] = append(fs.dirContents[name], d) 233 234 // for efficiency, instead of breaking here, we'll keep looping and 235 // cache all the dir contents; this does mean we'll never see externally 236 // added new entries for this dir in the future 237 } 238 239 if !isDir { 240 return fuse.ENOENT 241 } 242 243 fs.dirs[name] = append(fs.dirs[name], r) 244 if _, exists := fs.dirContents[name]; !exists { 245 // empty dir, we must create an entry in this map 246 fs.dirContents[name] = []fuse.DirEntry{} 247 } 248 return fuse.OK 249 } 250 251 // Open is what is called when any request to read a file is made. The file must 252 // already have been stat'ed (eg. with a GetAttr() call), or we report the file 253 // doesn't exist. context is not currently used. If CacheData has been 254 // configured, we defer to openCached(). Otherwise the real implementation is in 255 // remoteFile. 256 func (fs *MuxFys) Open(name string, flags uint32, context *fuse.Context) (nodefs.File, fuse.Status) { 257 checkWritable := false 258 if int(flags)&os.O_WRONLY != 0 || int(flags)&os.O_RDWR != 0 || int(flags)&os.O_APPEND != 0 || int(flags)&os.O_CREATE != 0 || int(flags)&os.O_TRUNC != 0 { 259 checkWritable = true 260 } 261 attr, r, status := fs.fileDetails(name, checkWritable) 262 var file nodefs.File 263 if status != fuse.OK { 264 return file, status 265 } 266 267 if r.cacheData { 268 file, status = fs.openCached(r, name, flags, context, attr, checkWritable) 269 } else { 270 file = newRemoteFile(r, r.getRemotePath(name), attr, false, fs.Logger) 271 } 272 273 if !r.write || (int(flags)&os.O_WRONLY == 0 && int(flags)&os.O_RDWR == 0) { 274 file = nodefs.NewReadOnlyFile(file) 275 } 276 277 return file, status 278 } 279 280 // openCached defers all subsequent read/write operations to a CachedFile for 281 // that local file. 282 func (fs *MuxFys) openCached(r *remote, name string, flags uint32, context *fuse.Context, attr *fuse.Attr, writeMode bool) (nodefs.File, fuse.Status) { 283 remotePath := r.getRemotePath(name) 284 localPath := r.getLocalPath(remotePath) 285 286 fmutex, err := fs.getFileMutex(localPath) 287 if err != nil { 288 return nil, fuse.EIO 289 } 290 err = fmutex.Lock() 291 if err != nil { 292 fs.Error("openCached file mutex lock failed", "err", err) 293 } 294 295 localStats, err := os.Stat(localPath) 296 var create bool 297 if err != nil { 298 err = os.Remove(localPath) 299 if err != nil && !os.IsNotExist(err) { 300 fs.Warn("openCached remove cache file failed", "path", localPath, "err", err) 301 } 302 create = true 303 } else if !writeMode { 304 // check the file is the right size 305 if localStats.Size() != int64(attr.Size) { 306 r.Warn("Cached size differs", "path", name, "localSize", localStats.Size(), "remoteSize", attr.Size) 307 err = os.Remove(localPath) 308 if err != nil { 309 fs.Warn("openCached remove cache file failed", "path", localPath, "err", err) 310 } 311 create = true 312 if int(flags)&os.O_WRONLY != 0 || int(flags)&os.O_RDWR != 0 || int(flags)&os.O_APPEND != 0 || int(flags)&os.O_CREATE != 0 || int(flags)&os.O_TRUNC != 0 { 313 attr.Size = uint64(0) 314 } 315 } else if !r.cacheIsTmp { 316 // if the file already exists at the correct size, but we have no 317 // record of it being cached, assume another process sharing the 318 // same permanent cache folder already cached the whole file 319 iv := NewInterval(0, localStats.Size()) 320 ivs := r.Uncached(localPath, iv) 321 if len(ivs) > 0 { 322 r.Cached(localPath, iv) 323 } 324 325 // *** doesn't this break if two different mount processes are 326 // trying to read the same file at the same time?... Maybe we'll 327 // need to store cached intervals in the lock file after all... 328 } 329 } 330 331 if create { 332 r.CacheDelete(localPath) 333 334 if !r.cacheIsTmp || int(flags)&os.O_APPEND != 0 { 335 // download whole remote object to disk before user appends anything 336 // to it; if we just append to the sparse file then on upload we 337 // lose the contents of the original file. We also do this if we're 338 // not deleting our cache, ie. our cache dir was chosen by the user 339 // and could be in use simultaneously by other muxfys mounts 340 // *** alternatively we could store Invervals in the lock file... 341 if status := r.downloadFile(remotePath, localPath); status != fuse.OK { 342 logClose(fs.Logger, fmutex, "openCached file mutex") 343 return nil, status 344 } 345 346 // check size ok 347 localStats, errs := os.Stat(localPath) 348 if errs != nil { 349 r.Error("Downloaded file could not be accessed", "path", localPath, "err", errs) 350 errr := os.Remove(localPath) 351 if errr != nil { 352 fs.Warn("openCached remove cache file failed", "path", localPath, "err", errr) 353 } 354 logClose(fs.Logger, fmutex, "openCached file mutex") 355 return nil, fuse.ToStatus(errs) 356 } else if localStats.Size() != int64(attr.Size) { 357 r.Error("Downloaded size is wrong", "path", remotePath, "localSize", localStats.Size(), "remoteSize", attr.Size) 358 errr := os.Remove(localPath) 359 if errr != nil { 360 fs.Warn("openCached remove cache file failed", "path", localPath, "err", errr) 361 } 362 logClose(fs.Logger, fmutex, "openCached file mutex") 363 return nil, fuse.EIO 364 } 365 r.CacheOverride(localPath, NewInterval(0, int64(attr.Size))) 366 } else { 367 // this is our first time opening this remote file, create a sparse 368 // file that Read() operations will cache in to 369 f, errc := os.Create(localPath) 370 if errc != nil { 371 fs.Error("openCached create cached file failed", "path", localPath, "err", errc) 372 logClose(fs.Logger, fmutex, "openCached file mutex") 373 return nil, fuse.ToStatus(errc) 374 } 375 if errt := f.Truncate(int64(attr.Size)); errt != nil { 376 fs.Error("openCached truncate failed", "path", localPath, "err", errt) 377 logClose(fs.Logger, fmutex, "openCached file mutex") 378 return nil, fuse.ToStatus(errt) 379 } 380 logClose(fs.Logger, f, "openCached created file", "path", localPath) 381 } 382 } else if r.cacheIsTmp && int(flags)&os.O_APPEND != 0 { 383 // cache everything in the file we haven't already read by reading the 384 // file the way a client would 385 iv := Interval{0, int64(attr.Size)} 386 unread := r.Uncached(localPath, iv) 387 if len(unread) > 0 { 388 err = fmutex.Unlock() 389 if err != nil { 390 fs.Error("openCached file mutex unlock failed", "err", err) 391 } 392 path := filepath.Join(fs.mountPoint, name) 393 reader, err := os.Open(path) 394 if err != nil { 395 r.Error("Could not open cached file", "path", path, "err", err) 396 errl := fmutex.Lock() 397 if errl != nil { 398 fs.Error("openCached file mutex lock failed", "err", errl) 399 } 400 logClose(fs.Logger, fmutex, "openCached file mutex") 401 return nil, fuse.ToStatus(err) 402 } 403 for _, uiv := range unread { 404 _, errs := reader.Seek(uiv.Start, io.SeekStart) 405 if errs != nil { 406 r.Error("openCached reader seek failed", "err", errs) 407 } 408 br := bufio.NewReader(reader) 409 b := make([]byte, 1000) 410 var read int64 411 for read <= uiv.Length() { 412 done, rerr := br.Read(b) 413 if rerr != nil { 414 if rerr != io.EOF { 415 err = rerr 416 } 417 break 418 } 419 read += int64(done) 420 } 421 if err != nil { 422 r.Error("Could not read file", "path", name, "err", err) 423 logClose(fs.Logger, reader, "openCached reader", "path", name) 424 err = fmutex.Lock() 425 if err != nil { 426 fs.Error("openCached file mutex lock failed", "err", err) 427 } 428 logClose(fs.Logger, fmutex, "openCached file mutex") 429 return nil, fuse.EIO 430 } 431 } 432 logClose(fs.Logger, reader, "openCached reader", "path", name) 433 err = fmutex.Lock() 434 if err != nil { 435 fs.Error("openCached file mutex lock failed", "err", err) 436 } 437 } 438 } 439 440 // if the flags suggest any kind of write-ability, treat it like we created 441 // the file 442 if writeMode { 443 return fs.create(name, flags, uint32(fileMode), fmutex) 444 } 445 446 logClose(fs.Logger, fmutex, "openCached file mutex") 447 return newCachedFile(r, remotePath, localPath, attr, flags, fs.Logger), fuse.OK 448 } 449 450 // Chmod is ignored. 451 func (fs *MuxFys) Chmod(name string, mode uint32, context *fuse.Context) fuse.Status { 452 _, _, status := fs.fileDetails(name, true) 453 if status == fuse.ENOENT { 454 fs.mapMutex.RLock() 455 defer fs.mapMutex.RUnlock() 456 if _, exists := fs.dirs[name]; exists { 457 return fuse.OK 458 } 459 } 460 return status 461 } 462 463 // Chown is ignored. 464 func (fs *MuxFys) Chown(name string, uid uint32, gid uint32, context *fuse.Context) fuse.Status { 465 _, _, status := fs.fileDetails(name, true) 466 if status == fuse.ENOENT { 467 fs.mapMutex.RLock() 468 defer fs.mapMutex.RUnlock() 469 if _, exists := fs.dirs[name]; exists { 470 return fuse.OK 471 } 472 } 473 return status 474 } 475 476 // Symlink creates a symbolic link. Only implemented for temporary use when 477 // configured with CacheData: you can create and use symlinks but they don't get 478 // uploaded. context is not currently used. 479 func (fs *MuxFys) Symlink(source string, dest string, context *fuse.Context) (status fuse.Status) { 480 if fs.writeRemote == nil || !fs.writeRemote.cacheData { 481 return fuse.ENOSYS 482 } 483 484 localPathDest := fs.writeRemote.getLocalPath(fs.writeRemote.getRemotePath(dest)) 485 fmutex, err := fs.getFileMutex(localPathDest) 486 if err != nil { 487 return fuse.EIO 488 } 489 err = fmutex.Lock() 490 if err != nil { 491 fs.Error("Symlink file mutex lock failed", "err", err) 492 } 493 defer logClose(fs.Logger, fmutex, "Symlink file mutex") 494 495 // symlink from mount point source to cached dest file 496 err = os.Symlink(source, localPathDest) 497 if err != nil { 498 fs.writeRemote.Error("Could not create symlink", "source", source, "dest", localPathDest, "err", err) 499 return fuse.ToStatus(err) 500 } 501 502 // note the existence of dest without making it uploadable on unmount 503 fs.mapMutex.Lock() 504 fs.addNewEntryToItsDir(dest, fuse.S_IFLNK) 505 mTime := uint64(time.Now().Unix()) 506 attr := &fuse.Attr{ 507 Mode: fuse.S_IFLNK | uint32(fileMode), 508 Size: symlinkSize, // it doesn't matter what the actual size is (which we could get with os.Lstat(localPathDest)), this is just for presentation purposes 509 Mtime: mTime, 510 Atime: mTime, 511 Ctime: mTime, 512 } 513 fs.files[dest] = attr 514 fs.fileToRemote[dest] = fs.writeRemote 515 fs.mapMutex.Unlock() 516 517 return fuse.OK 518 } 519 520 // Readlink returns the destination of a symbolic link that was created with 521 // Symlink(). context is not currently used. 522 func (fs *MuxFys) Readlink(name string, context *fuse.Context) (string, fuse.Status) { 523 _, r, status := fs.fileDetails(name, true) 524 if status != fuse.OK { 525 return "", status 526 } 527 localPath := r.getLocalPath(r.getRemotePath(name)) 528 out, err := os.Readlink(localPath) 529 if err != nil { 530 fs.Warn("Readlink failed", "path", localPath, "err", err) 531 } 532 return out, fuse.ToStatus(err) 533 } 534 535 // SetXAttr is ignored. 536 func (fs *MuxFys) SetXAttr(name string, attr string, data []byte, flags int, context *fuse.Context) fuse.Status { 537 _, _, status := fs.fileDetails(name, true) 538 if status == fuse.ENOENT { 539 fs.mapMutex.RLock() 540 defer fs.mapMutex.RUnlock() 541 if _, exists := fs.dirs[name]; exists { 542 return fuse.OK 543 } 544 } 545 return status 546 } 547 548 // RemoveXAttr is ignored. 549 func (fs *MuxFys) RemoveXAttr(name string, attr string, context *fuse.Context) fuse.Status { 550 _, _, status := fs.fileDetails(name, true) 551 if status == fuse.ENOENT { 552 fs.mapMutex.RLock() 553 defer fs.mapMutex.RUnlock() 554 if _, exists := fs.dirs[name]; exists { 555 return fuse.OK 556 } 557 } 558 return status 559 } 560 561 // Utimens only functions when configured with CacheData and the file is already 562 // in the cache; otherwise ignored. This only gets called by direct operations 563 // like os.Chtimes() (that don't first Open()/Create() the file). context is not 564 // currently used. 565 func (fs *MuxFys) Utimens(name string, Atime *time.Time, Mtime *time.Time, context *fuse.Context) fuse.Status { 566 attr, r, status := fs.fileDetails(name, true) 567 if status == fuse.ENOENT { 568 fs.mapMutex.RLock() 569 defer fs.mapMutex.RUnlock() 570 if _, exists := fs.dirs[name]; exists { 571 return fuse.OK 572 } 573 } 574 if status != fuse.OK || !r.cacheData { 575 return status 576 } 577 578 localPath := r.getLocalPath(r.getRemotePath(name)) 579 if _, err := os.Stat(localPath); err == nil { 580 err = os.Chtimes(localPath, *Atime, *Mtime) 581 if err == nil { 582 attr.Atime = uint64(Atime.Unix()) 583 attr.Mtime = uint64(Mtime.Unix()) 584 } 585 status = fuse.ToStatus(err) 586 } 587 588 return status 589 } 590 591 // Truncate truncates any local cached copy of the file. Only currently 592 // implemented for when configured with CacheData; the results of the Truncate 593 // are only uploaded at Unmount() time. If offset is > size of file, does 594 // nothing and returns OK. context is not currently used. 595 func (fs *MuxFys) Truncate(name string, offset uint64, context *fuse.Context) fuse.Status { 596 attr, r, status := fs.fileDetails(name, true) 597 if status != fuse.OK { 598 return status 599 } 600 601 if offset > attr.Size { 602 return fuse.OK 603 } 604 605 remotePath := r.getRemotePath(name) 606 if r.cacheData { 607 localPath := r.getLocalPath(remotePath) 608 609 fmutex, err := fs.getFileMutex(localPath) 610 if err != nil { 611 return fuse.EIO 612 } 613 err = fmutex.Lock() 614 if err != nil { 615 fs.Error("Truncate file mutex lock failed", "err", err) 616 return fuse.EIO 617 } 618 defer logClose(fs.Logger, fmutex, "Trucate mutex file") 619 620 if _, err := os.Stat(localPath); err == nil { 621 // truncate local cached copy 622 err = os.Truncate(localPath, int64(offset)) 623 if err != nil { 624 fs.Error("Truncate cached file failed", "path", localPath, "err", err) 625 return fuse.ToStatus(err) 626 } 627 r.CacheTruncate(localPath, int64(offset)) 628 } else { 629 // create a new empty file 630 localFile, err := os.Create(localPath) 631 if err != nil { 632 r.Error("Could not create empty file", "path", localPath, "err", err) 633 return fuse.EIO 634 } 635 636 if offset == 0 { 637 logClose(fs.Logger, localFile, "Trucate local file") 638 r.CacheTruncate(localPath, int64(offset)) 639 } else { 640 // download offset bytes of remote file 641 object, status := r.getObject(remotePath, 0) 642 if status != fuse.OK { 643 return status 644 } 645 646 written, err := io.CopyN(localFile, object, int64(offset)) 647 if err != nil || written != int64(offset) { 648 msg := "Could not copy bytes" 649 if err == nil { 650 msg = "Could not copy all bytes" 651 } 652 r.Error(msg, "size", offset, "source", remotePath, "dest", localPath, "err", err) 653 logClose(fs.Logger, localFile, "Trucate local file") 654 erru := syscall.Unlink(localPath) 655 if erru != nil { 656 fs.Error("Truncate cache file unlink failed", "err", erru) 657 } 658 return fuse.EIO 659 } 660 661 logClose(fs.Logger, localFile, "Trucate local file") 662 logClose(fs.Logger, object, "Trucate remote object") 663 664 r.CacheOverride(localPath, NewInterval(0, int64(offset))) 665 } 666 } 667 668 // update attr and claim we created this file 669 attr.Size = offset 670 attr.Mtime = uint64(time.Now().Unix()) 671 fs.mapMutex.Lock() 672 fs.createdFiles[name] = true 673 fs.mapMutex.Unlock() 674 675 return fuse.OK 676 } 677 return fuse.ENOSYS 678 } 679 680 // Mkdir for a directory that doesn't exist yet. neither mode nor context are 681 // currently used. 682 func (fs *MuxFys) Mkdir(name string, mode uint32, context *fuse.Context) fuse.Status { 683 if fs.writeRemote == nil { 684 return fuse.EPERM 685 } 686 687 fs.mapMutex.Lock() 688 defer fs.mapMutex.Unlock() 689 690 if _, isDir := fs.dirs[name]; isDir { 691 return fuse.OK 692 } 693 694 // it's parent directory must already exist 695 parent := filepath.Dir(name) 696 if parent == "." { 697 parent = "" 698 } 699 if _, exists := fs.dirs[parent]; !exists { 700 return fuse.ENOENT 701 } 702 703 remotePath := fs.writeRemote.getRemotePath(name) 704 var err error 705 if fs.writeRemote.cacheData { 706 localPath := fs.writeRemote.getLocalPath(remotePath) 707 708 // make all the parent directories. We use our dirMode constant here 709 // instead of the supplied mode because of strange permission problems 710 // using the latter, and because it doesn't matter what permissions the 711 // user wants for the dir - this is for a user-only cache 712 if err = os.MkdirAll(filepath.Dir(localPath), os.FileMode(dirMode)); err == nil { 713 // make the desired directory 714 err = os.Mkdir(localPath, os.FileMode(dirMode)) 715 } 716 if err != nil { 717 fs.Error("Mkdir failed", "path", localPath, "err", err) 718 fs.mapMutex.Unlock() 719 return fuse.ToStatus(err) 720 } 721 } 722 723 // we mark its existence internally but don't do anything "physical" 724 // to create the dir remotely (applies for cached and uncached modes) 725 fs.dirs[name] = append(fs.dirs[name], fs.writeRemote) 726 if _, exists := fs.dirContents[name]; !exists { 727 fs.dirContents[name] = []fuse.DirEntry{} 728 } 729 if fs.writeRemote.cacheData { 730 fs.createdDirs[name] = true 731 } 732 fs.addNewEntryToItsDir(name, fuse.S_IFDIR) 733 return fuse.OK 734 } 735 736 // Rmdir only works for non-existent or empty dirs. context is not currently 737 // used. 738 func (fs *MuxFys) Rmdir(name string, context *fuse.Context) fuse.Status { 739 if fs.writeRemote == nil { 740 return fuse.EPERM 741 } 742 743 fs.mapMutex.Lock() 744 defer fs.mapMutex.Unlock() 745 746 if _, isDir := fs.dirs[name]; !isDir { 747 return fuse.ENOENT 748 } else if contents, exists := fs.dirContents[name]; exists && len(contents) > 0 { 749 return fuse.ENOSYS 750 } 751 752 remotePath := fs.writeRemote.getRemotePath(name) 753 var err error 754 if fs.writeRemote.cacheData { 755 localPath := fs.writeRemote.getLocalPath(remotePath) 756 err = syscall.Rmdir(localPath) 757 if err != nil { 758 fs.Error("Rmdir failed", "path", localPath, "err", err) 759 return fuse.ToStatus(err) 760 } 761 762 } 763 764 delete(fs.dirs, name) 765 delete(fs.createdDirs, name) 766 delete(fs.dirContents, name) 767 fs.rmEntryFromItsDir(name) 768 769 return fuse.OK 770 } 771 772 // Rename only works where oldPath is found in the writeable remote. For files, 773 // first remotely copies oldPath to newPath (ignoring any local changes to 774 // oldPath), renames any local cached (and possibly modified) copy of oldPath to 775 // newPath, and finally deletes the remote oldPath; if oldPath had been 776 // modified, its changes will only be uploaded to newPath at Unmount() time. For 777 // directories, is only capable of renaming directories you have created whilst 778 // mounted. context is not currently used. 779 func (fs *MuxFys) Rename(oldPath string, newPath string, context *fuse.Context) fuse.Status { 780 if fs.writeRemote == nil { 781 return fuse.EPERM 782 } 783 784 fs.mapMutex.Lock() 785 defer fs.mapMutex.Unlock() 786 787 var isDir bool 788 if _, isDir = fs.dirs[oldPath]; !isDir { 789 if _, isFile := fs.fileToRemote[oldPath]; !isFile { 790 return fuse.ENOENT 791 } 792 } else if _, created := fs.createdDirs[oldPath]; !created { 793 return fuse.ENOSYS 794 } else { 795 // the directory's new parent dir must exist 796 parent := filepath.Dir(newPath) 797 if parent == "." { 798 parent = "" 799 } 800 if _, exists := fs.dirs[parent]; !exists { 801 return fuse.ENOENT 802 } 803 } 804 805 remotePathOld := fs.writeRemote.getRemotePath(oldPath) 806 remotePathNew := fs.writeRemote.getRemotePath(newPath) 807 if isDir { 808 if fs.writeRemote.cacheData { 809 // first create the newPaths's cached parent dir 810 localPathNew := fs.writeRemote.getLocalPath(remotePathNew) 811 812 // *** should we try and lock the old and new directories first? 813 814 var err error 815 if err = os.MkdirAll(filepath.Dir(localPathNew), os.FileMode(dirMode)); err == nil { 816 // now try and rename the cached dir 817 if err = os.Rename(fs.writeRemote.getLocalPath(remotePathOld), localPathNew); err == nil { 818 // update our knowledge of what dirs we have 819 fs.dirs[newPath] = fs.dirs[oldPath] 820 fs.dirContents[newPath] = fs.dirContents[oldPath] 821 fs.createdDirs[newPath] = true 822 delete(fs.dirs, oldPath) 823 delete(fs.createdDirs, oldPath) 824 delete(fs.dirContents, oldPath) 825 fs.rmEntryFromItsDir(oldPath) 826 fs.addNewEntryToItsDir(newPath, fuse.S_IFDIR) 827 } 828 } 829 fs.Error("Rename mkdir failed", "path", localPathNew, "err", err) 830 return fuse.ToStatus(err) 831 } 832 } else { 833 // first trigger a remote copy of oldPath to newPath 834 status := fs.writeRemote.copyFile(remotePathOld, remotePathNew) 835 if status != fuse.OK { 836 return status 837 } 838 839 if fs.writeRemote.cacheData { 840 localPathOld := fs.writeRemote.getLocalPath(remotePathOld) 841 localPathNew := fs.writeRemote.getLocalPath(remotePathNew) 842 843 fmutex, err := fs.getFileMutex(localPathOld) 844 if err != nil { 845 return fuse.EIO 846 } 847 err = fmutex.Lock() 848 if err != nil { 849 fs.Error("Rename file mutex lock failed", "path", localPathOld, "err", err) 850 return fuse.EIO 851 } 852 defer logClose(fs.Logger, fmutex, "Rename file mutex") 853 fmutex2, err := fs.getFileMutex(localPathNew) 854 if err != nil { 855 return fuse.EIO 856 } 857 err = fmutex2.Lock() 858 if err != nil { 859 fs.Error("Rename file mutex lock failed", "path", localPathNew, "err", err) 860 return fuse.EIO 861 } 862 defer logClose(fs.Logger, fmutex2, "Rename file mutex") 863 864 // if we've cached oldPath, move to new cached file 865 err = os.Rename(localPathOld, localPathNew) 866 if err != nil { 867 fs.Error("Rename of cached files failed", "source", localPathOld, "dest", localPathNew, "err", err) 868 } 869 fs.writeRemote.CacheRename(localPathOld, localPathNew) 870 } 871 872 // cache the existence of the new file 873 fs.files[newPath] = fs.files[oldPath] 874 fs.fileToRemote[newPath] = fs.fileToRemote[oldPath] 875 if _, created := fs.createdFiles[oldPath]; created { 876 fs.createdFiles[newPath] = true 877 delete(fs.createdFiles, oldPath) 878 } 879 fs.addNewEntryToItsDir(newPath, fuse.S_IFREG) 880 881 // finally unlink oldPath remotely 882 r := fs.fileToRemote[oldPath] 883 if r != nil { 884 r.deleteFile(remotePathOld) 885 } 886 delete(fs.files, oldPath) 887 delete(fs.fileToRemote, oldPath) 888 delete(fs.createdFiles, oldPath) 889 fs.rmEntryFromItsDir(oldPath) 890 891 return fuse.OK 892 } 893 return fuse.ENOSYS 894 } 895 896 // Unlink deletes a file from the remote system, as well as any locally cached 897 // copy. context is not currently used. 898 func (fs *MuxFys) Unlink(name string, context *fuse.Context) fuse.Status { 899 _, r, status := fs.fileDetails(name, true) 900 if status != fuse.OK { 901 return status 902 } 903 904 remotePath := r.getRemotePath(name) 905 if r.cacheData { 906 localPath := r.getLocalPath(remotePath) 907 // *** we could file lock here, but that is a little wasteful if 908 // localPath doesn't actually exist, and we'd have to file unlock eg. 909 // Rename() and anything else that calls us 910 err := syscall.Unlink(localPath) 911 if err != nil { 912 fs.Warn("Unlink failed", "path", localPath, "err", err) 913 } 914 r.CacheDelete(localPath) 915 } 916 917 fs.mapMutex.Lock() 918 defer fs.mapMutex.Unlock() 919 920 delete(fs.createdFiles, name) 921 922 status = r.deleteFile(remotePath) 923 if status != fuse.OK { 924 return status 925 } 926 927 delete(fs.files, name) 928 delete(fs.fileToRemote, name) 929 fs.rmEntryFromItsDir(name) 930 931 return fuse.OK 932 } 933 934 // Access is ignored. 935 func (fs *MuxFys) Access(name string, mode uint32, context *fuse.Context) fuse.Status { 936 return fuse.OK 937 } 938 939 // Create creates a new file. mode and context are not currently used. When 940 // configured with CacheData the contents of the created file are only uploaded 941 // at Unmount() time. 942 func (fs *MuxFys) Create(name string, flags uint32, mode uint32, context *fuse.Context) (nodefs.File, fuse.Status) { 943 return fs.create(name, flags, mode) 944 } 945 946 // create is the implementation of Create() that also takes an optional 947 // filemutex that should be Lock()ed (it will be Close()d). 948 func (fs *MuxFys) create(name string, flags uint32, mode uint32, fmutex ...*filemutex.FileMutex) (nodefs.File, fuse.Status) { 949 r := fs.writeRemote 950 if r == nil { 951 return nil, fuse.EPERM 952 } 953 954 remotePath := r.getRemotePath(name) 955 var localPath string 956 if r.cacheData { 957 localPath = r.getLocalPath(remotePath) 958 959 if len(fmutex) == 1 { 960 defer logClose(fs.Logger, fmutex[0], "file mutex", "path", localPath) 961 } else { 962 fm, err := fs.getFileMutex(localPath) 963 if err != nil { 964 return nil, fuse.EIO 965 } 966 err = fm.Lock() 967 if err != nil { 968 fs.Error("file mutex lock failed", "path", localPath, "err", err) 969 return nil, fuse.EIO 970 } 971 defer logClose(fs.Logger, fm, "file mutex", "path", localPath) 972 } 973 } 974 975 fs.mapMutex.Lock() 976 defer fs.mapMutex.Unlock() 977 978 attr, existed := fs.files[name] 979 mTime := uint64(time.Now().Unix()) 980 if !existed { 981 // add to our directory entries for this file's dir 982 fs.addNewEntryToItsDir(name, fuse.S_IFREG) 983 984 attr = &fuse.Attr{ 985 Mode: fuse.S_IFREG | uint32(fileMode), 986 Size: uint64(0), 987 Mtime: mTime, 988 Atime: mTime, 989 Ctime: mTime, 990 } 991 fs.files[name] = attr 992 fs.fileToRemote[name] = r 993 } else { 994 attr.Mtime = mTime 995 attr.Atime = mTime 996 997 // *** when not appending, don't we need to reset to 0? It seems to work 998 // without this, and we avoid incorrect reset to 0 when something 999 // opens many simultaneous non-append writes to write at different 1000 // offsets. 1001 // if int(flags)&os.O_APPEND == 0 { 1002 // r.CacheDelete(localPath) 1003 // attr.Size = uint64(0) 1004 // } 1005 } 1006 fs.createdFiles[name] = true 1007 1008 if r.cacheData { 1009 return newCachedFile(r, remotePath, localPath, attr, uint32(int(flags)|os.O_CREATE), fs.Logger), fuse.OK 1010 } 1011 return newRemoteFile(r, remotePath, attr, true, fs.Logger), fuse.OK 1012 } 1013 1014 // addNewEntryToItsDir adds a DirEntry for the file/dir named name to that 1015 // object's containing directory entries. mode should be fuse.S_IFREG or 1016 // fuse.S_IFDIR. Must be called while you have the mapMutex Locked. 1017 func (fs *MuxFys) addNewEntryToItsDir(name string, mode int) { 1018 d := fuse.DirEntry{ 1019 Name: filepath.Base(name), 1020 Mode: uint32(mode), 1021 } 1022 parent := filepath.Dir(name) 1023 if parent == "." { 1024 parent = "" 1025 } 1026 1027 if _, exists := fs.dirContents[parent]; !exists { 1028 // we must populate the contents of parent first, doing the essential 1029 // part of OpenDir() 1030 if remotes, exists := fs.dirs[parent]; exists { 1031 for _, r := range remotes { 1032 status := fs.openDir(r, parent) 1033 if status != fuse.OK { 1034 fs.Warn("addNewEntryToItsDir openDir failed", "path", parent, "status", status) 1035 } 1036 } 1037 } 1038 } 1039 fs.dirContents[parent] = append(fs.dirContents[parent], d) 1040 } 1041 1042 // rmEntryFromItsDir removes a DirEntry for the file/dir named name from that 1043 // object's containing directory entries. Must be called while you have the 1044 // mapMutex Locked. 1045 func (fs *MuxFys) rmEntryFromItsDir(name string) { 1046 parent := filepath.Dir(name) 1047 if parent == "." { 1048 parent = "" 1049 } 1050 baseName := filepath.Base(name) 1051 1052 if dentries, exists := fs.dirContents[parent]; exists { 1053 for i, entry := range dentries { 1054 if entry.Name == baseName { 1055 // delete without preserving order and avoiding memory leak 1056 dentries[i] = dentries[len(dentries)-1] 1057 dentries[len(dentries)-1] = fuse.DirEntry{} 1058 dentries = dentries[:len(dentries)-1] 1059 fs.dirContents[parent] = dentries 1060 break 1061 } 1062 } 1063 } 1064 } 1065 1066 // getFileMutex prepares a lock file for the given local path (in that path's 1067 // directory, creating the directory first if necessary), and returns a mutex 1068 // that you should Lock() and Close(). 1069 func (fs *MuxFys) getFileMutex(localPath string) (*filemutex.FileMutex, error) { 1070 parent := filepath.Dir(localPath) 1071 if _, err := os.Stat(parent); err != nil && os.IsNotExist(err) { 1072 err = os.MkdirAll(parent, dirMode) 1073 if err != nil { 1074 fs.Error("Could not create parent directory", "path", localPath, "err", err) 1075 return nil, err 1076 } 1077 } 1078 mutex, err := filemutex.New(filepath.Join(parent, ".muxfys_lock."+filepath.Base(localPath))) 1079 if err != nil { 1080 fs.Error("Could not create lock file", "path", localPath, "err", err) 1081 } 1082 return mutex, err 1083 }