github.com/jstaf/onedriver@v0.14.2-0.20240420231225-f07678f9e6ef/fs/fs.go (about) 1 package fs 2 3 import ( 4 "io" 5 "math" 6 "os" 7 "path/filepath" 8 "regexp" 9 "strings" 10 "syscall" 11 "time" 12 13 "github.com/hanwen/go-fuse/v2/fuse" 14 "github.com/jstaf/onedriver/fs/graph" 15 "github.com/rs/zerolog/log" 16 ) 17 18 const timeout = time.Second 19 20 func (f *Filesystem) getInodeContent(i *Inode) *[]byte { 21 i.RLock() 22 defer i.RUnlock() 23 data := f.content.Get(i.DriveItem.ID) 24 return &data 25 } 26 27 // remoteID uploads a file to obtain a Onedrive ID if it doesn't already 28 // have one. This is necessary to avoid race conditions against uploads if the 29 // file has not already been uploaded. 30 func (f *Filesystem) remoteID(i *Inode) (string, error) { 31 if i.IsDir() { 32 // Directories are always created with an ID. (And this method is only 33 // really used for files anyways...) 34 return i.ID(), nil 35 } 36 37 originalID := i.ID() 38 if isLocalID(originalID) && f.auth.AccessToken != "" { 39 // perform a blocking upload of the item 40 data := f.getInodeContent(i) 41 session, err := NewUploadSession(i, data) 42 if err != nil { 43 return originalID, err 44 } 45 46 i.Lock() 47 name := i.DriveItem.Name 48 err = session.Upload(f.auth) 49 if err != nil { 50 i.Unlock() 51 52 if strings.Contains(err.Error(), "nameAlreadyExists") { 53 // A file with this name already exists on the server, get its ID and 54 // use that. This is probably the same file, but just got uploaded 55 // earlier. 56 children, err := graph.GetItemChildren(i.ParentID(), f.auth) 57 if err != nil { 58 return originalID, err 59 } 60 for _, child := range children { 61 if child.Name == name { 62 log.Info(). 63 Str("name", name). 64 Str("originalID", originalID). 65 Str("newID", child.ID). 66 Msg("Exchanged ID.") 67 return child.ID, f.MoveID(originalID, child.ID) 68 } 69 } 70 } 71 // failed to obtain an ID, return whatever it was beforehand 72 return originalID, err 73 } 74 75 // we just successfully uploaded a copy, no need to do it again 76 i.hasChanges = false 77 i.DriveItem.ETag = session.ETag 78 i.Unlock() 79 80 // this is all we really wanted from this transaction 81 err = f.MoveID(originalID, session.ID) 82 log.Info(). 83 Str("name", name). 84 Str("originalID", originalID). 85 Str("newID", session.ID). 86 Msg("Exchanged ID.") 87 return session.ID, err 88 } 89 return originalID, nil 90 } 91 92 var disallowedRexp = regexp.MustCompile(`(?i)LPT[0-9]|COM[0-9]|_vti_|["*:<>?\/\\\|]`) 93 94 // isNameRestricted returns true if the name is disallowed according to the doc here: 95 // https://support.microsoft.com/en-us/office/restrictions-and-limitations-in-onedrive-and-sharepoint-64883a5d-228e-48f5-b3d2-eb39e07630fa 96 func isNameRestricted(name string) bool { 97 if strings.EqualFold(name, "CON") { 98 return true 99 } 100 if strings.EqualFold(name, "AUX") { 101 return true 102 } 103 if strings.EqualFold(name, "PRN") { 104 return true 105 } 106 if strings.EqualFold(name, "NUL") { 107 return true 108 } 109 if strings.EqualFold(name, ".lock") { 110 return true 111 } 112 if strings.EqualFold(name, "desktop.ini") { 113 return true 114 } 115 return disallowedRexp.FindStringIndex(name) != nil 116 } 117 118 // Statfs returns information about the filesystem. Mainly useful for checking 119 // quotas and storage limits. 120 func (f *Filesystem) StatFs(cancel <-chan struct{}, in *fuse.InHeader, out *fuse.StatfsOut) fuse.Status { 121 ctx := log.With().Str("op", "StatFs").Logger() 122 ctx.Debug().Msg("") 123 drive, err := graph.GetDrive(f.auth) 124 if err != nil { 125 return fuse.EREMOTEIO 126 } 127 128 if drive.DriveType == graph.DriveTypePersonal { 129 ctx.Warn().Msg("Personal OneDrive accounts do not show number of files, " + 130 "inode counts reported by onedriver will be bogus.") 131 } else if drive.Quota.Total == 0 { // <-- check for if microsoft ever fixes their API 132 ctx.Warn().Msg("OneDrive for Business accounts do not report quotas, " + 133 "pretending the quota is 5TB and it's all unused.") 134 drive.Quota.Total = 5 * uint64(math.Pow(1024, 4)) 135 drive.Quota.Remaining = 5 * uint64(math.Pow(1024, 4)) 136 drive.Quota.FileCount = 0 137 } 138 139 // limits are pasted from https://support.microsoft.com/en-us/help/3125202 140 const blkSize uint64 = 4096 // default ext4 block size 141 out.Bsize = uint32(blkSize) 142 out.Blocks = drive.Quota.Total / blkSize 143 out.Bfree = drive.Quota.Remaining / blkSize 144 out.Bavail = drive.Quota.Remaining / blkSize 145 out.Files = 100000 146 out.Ffree = 100000 - drive.Quota.FileCount 147 out.NameLen = 260 148 return fuse.OK 149 } 150 151 // Mkdir creates a directory. 152 func (f *Filesystem) Mkdir(cancel <-chan struct{}, in *fuse.MkdirIn, name string, out *fuse.EntryOut) fuse.Status { 153 if isNameRestricted(name) { 154 return fuse.EINVAL 155 } 156 157 inode := f.GetNodeID(in.NodeId) 158 if inode == nil { 159 return fuse.ENOENT 160 } 161 id := inode.ID() 162 path := filepath.Join(inode.Path(), name) 163 ctx := log.With(). 164 Str("op", "Mkdir"). 165 Uint64("nodeID", in.NodeId). 166 Str("id", id). 167 Str("path", path). 168 Str("mode", Octal(in.Mode)). 169 Logger() 170 ctx.Debug().Msg("") 171 172 // create the new directory on the server 173 item, err := graph.Mkdir(name, id, f.auth) 174 if err != nil { 175 ctx.Error().Err(err).Msg("Could not create remote directory!") 176 return fuse.EREMOTEIO 177 } 178 179 newInode := NewInodeDriveItem(item) 180 newInode.mode = in.Mode | fuse.S_IFDIR 181 182 out.NodeId = f.InsertChild(id, newInode) 183 out.Attr = newInode.makeAttr() 184 out.SetAttrTimeout(timeout) 185 out.SetEntryTimeout(timeout) 186 return fuse.OK 187 } 188 189 // Rmdir removes a directory if it's empty. 190 func (f *Filesystem) Rmdir(cancel <-chan struct{}, in *fuse.InHeader, name string) fuse.Status { 191 parentID := f.TranslateID(in.NodeId) 192 if parentID == "" { 193 return fuse.ENOENT 194 } 195 child, _ := f.GetChild(parentID, name, f.auth) 196 if child == nil { 197 return fuse.ENOENT 198 } 199 if child.HasChildren() { 200 return fuse.Status(syscall.ENOTEMPTY) 201 } 202 return f.Unlink(cancel, in, name) 203 } 204 205 // ReadDir provides a list of all the entries in the directory 206 func (f *Filesystem) OpenDir(cancel <-chan struct{}, in *fuse.OpenIn, out *fuse.OpenOut) fuse.Status { 207 id := f.TranslateID(in.NodeId) 208 dir := f.GetID(id) 209 if dir == nil { 210 return fuse.ENOENT 211 } 212 if !dir.IsDir() { 213 return fuse.ENOTDIR 214 } 215 path := dir.Path() 216 ctx := log.With(). 217 Str("op", "OpenDir"). 218 Uint64("nodeID", in.NodeId). 219 Str("id", id). 220 Str("path", path).Logger() 221 ctx.Debug().Msg("") 222 223 children, err := f.GetChildrenID(id, f.auth) 224 if err != nil { 225 // not an item not found error (Lookup/Getattr will always be called 226 // before Readdir()), something has happened to our connection 227 ctx.Error().Err(err).Msg("Could not fetch children") 228 return fuse.EREMOTEIO 229 } 230 231 parent := f.GetID(dir.ParentID()) 232 if parent == nil { 233 // This is the parent of the mountpoint. The FUSE kernel module discards 234 // this info, so what we put here doesn't actually matter. 235 parent = NewInode("..", 0755|fuse.S_IFDIR, nil) 236 parent.nodeID = math.MaxUint64 237 } 238 239 entries := make([]*Inode, 2) 240 entries[0] = dir 241 entries[1] = parent 242 243 for _, child := range children { 244 entries = append(entries, child) 245 } 246 f.opendirsM.Lock() 247 f.opendirs[in.NodeId] = entries 248 f.opendirsM.Unlock() 249 250 return fuse.OK 251 } 252 253 // ReleaseDir closes a directory and purges it from memory 254 func (f *Filesystem) ReleaseDir(in *fuse.ReleaseIn) { 255 f.opendirsM.Lock() 256 delete(f.opendirs, in.NodeId) 257 f.opendirsM.Unlock() 258 } 259 260 // ReadDirPlus reads an individual directory entry AND does a lookup. 261 func (f *Filesystem) ReadDirPlus(cancel <-chan struct{}, in *fuse.ReadIn, out *fuse.DirEntryList) fuse.Status { 262 f.opendirsM.RLock() 263 entries, ok := f.opendirs[in.NodeId] 264 f.opendirsM.RUnlock() 265 if !ok { 266 // readdir can sometimes arrive before the corresponding opendir, so we force it 267 f.OpenDir(cancel, &fuse.OpenIn{InHeader: in.InHeader}, nil) 268 f.opendirsM.RLock() 269 entries, ok = f.opendirs[in.NodeId] 270 f.opendirsM.RUnlock() 271 if !ok { 272 return fuse.EBADF 273 } 274 } 275 276 if in.Offset >= uint64(len(entries)) { 277 // just tried to seek past end of directory, we're all done! 278 return fuse.OK 279 } 280 281 inode := entries[in.Offset] 282 entry := fuse.DirEntry{ 283 Ino: inode.NodeID(), 284 Mode: inode.Mode(), 285 } 286 // first two entries will always be "." and ".." 287 switch in.Offset { 288 case 0: 289 entry.Name = "." 290 case 1: 291 entry.Name = ".." 292 default: 293 entry.Name = inode.Name() 294 } 295 entryOut := out.AddDirLookupEntry(entry) 296 if entryOut == nil { 297 //FIXME probably need to handle this better using the "overflow stuff" 298 log.Error(). 299 Str("op", "ReadDirPlus"). 300 Uint64("nodeID", in.NodeId). 301 Uint64("offset", in.Offset). 302 Str("entryName", entry.Name). 303 Uint64("entryNodeID", entry.Ino). 304 Msg("Exceeded DirLookupEntry bounds!") 305 return fuse.EIO 306 } 307 entryOut.NodeId = entry.Ino 308 entryOut.Attr = inode.makeAttr() 309 entryOut.SetAttrTimeout(timeout) 310 entryOut.SetEntryTimeout(timeout) 311 return fuse.OK 312 } 313 314 // ReadDir reads a directory entry. Usually doesn't get called (ReadDirPlus is 315 // typically used). 316 func (f *Filesystem) ReadDir(cancel <-chan struct{}, in *fuse.ReadIn, out *fuse.DirEntryList) fuse.Status { 317 f.opendirsM.RLock() 318 entries, ok := f.opendirs[in.NodeId] 319 f.opendirsM.RUnlock() 320 if !ok { 321 // readdir can sometimes arrive before the corresponding opendir, so we force it 322 f.OpenDir(cancel, &fuse.OpenIn{InHeader: in.InHeader}, nil) 323 f.opendirsM.RLock() 324 entries, ok = f.opendirs[in.NodeId] 325 f.opendirsM.RUnlock() 326 if !ok { 327 return fuse.EBADF 328 } 329 } 330 331 if in.Offset >= uint64(len(entries)) { 332 // just tried to seek past end of directory, we're all done! 333 return fuse.OK 334 } 335 336 inode := entries[in.Offset] 337 entry := fuse.DirEntry{ 338 Ino: inode.NodeID(), 339 Mode: inode.Mode(), 340 } 341 // first two entries will always be "." and ".." 342 switch in.Offset { 343 case 0: 344 entry.Name = "." 345 case 1: 346 entry.Name = ".." 347 default: 348 entry.Name = inode.Name() 349 } 350 351 out.AddDirEntry(entry) 352 return fuse.OK 353 } 354 355 // Lookup is called by the kernel when the VFS wants to know about a file inside 356 // a directory. 357 func (f *Filesystem) Lookup(cancel <-chan struct{}, in *fuse.InHeader, name string, out *fuse.EntryOut) fuse.Status { 358 id := f.TranslateID(in.NodeId) 359 log.Trace(). 360 Str("op", "Lookup"). 361 Uint64("nodeID", in.NodeId). 362 Str("id", id). 363 Str("name", name). 364 Msg("") 365 366 child, _ := f.GetChild(id, strings.ToLower(name), f.auth) 367 if child == nil { 368 return fuse.ENOENT 369 } 370 371 out.NodeId = child.NodeID() 372 out.Attr = child.makeAttr() 373 out.SetAttrTimeout(timeout) 374 out.SetEntryTimeout(timeout) 375 return fuse.OK 376 } 377 378 // Mknod creates a regular file. The server doesn't have this yet. 379 func (f *Filesystem) Mknod(cancel <-chan struct{}, in *fuse.MknodIn, name string, out *fuse.EntryOut) fuse.Status { 380 if isNameRestricted(name) { 381 return fuse.EINVAL 382 } 383 384 parentID := f.TranslateID(in.NodeId) 385 if parentID == "" { 386 return fuse.EBADF 387 } 388 389 parent := f.GetID(parentID) 390 if parent == nil { 391 return fuse.ENOENT 392 } 393 394 path := filepath.Join(parent.Path(), name) 395 ctx := log.With(). 396 Str("op", "Mknod"). 397 Uint64("nodeID", in.NodeId). 398 Str("path", path). 399 Logger() 400 if f.IsOffline() { 401 ctx.Warn().Msg("We are offline. Refusing Mknod() to avoid data loss later.") 402 return fuse.EROFS 403 } 404 405 if child, _ := f.GetChild(parentID, name, f.auth); child != nil { 406 return fuse.Status(syscall.EEXIST) 407 } 408 409 inode := NewInode(name, in.Mode, parent) 410 ctx.Debug(). 411 Str("childID", inode.ID()). 412 Str("mode", Octal(in.Mode)). 413 Msg("Creating inode.") 414 out.NodeId = f.InsertChild(parentID, inode) 415 out.Attr = inode.makeAttr() 416 out.SetAttrTimeout(timeout) 417 out.SetEntryTimeout(timeout) 418 return fuse.OK 419 } 420 421 // Create creates a regular file and opens it. The server doesn't have this yet. 422 func (f *Filesystem) Create(cancel <-chan struct{}, in *fuse.CreateIn, name string, out *fuse.CreateOut) fuse.Status { 423 // we reuse mknod here 424 result := f.Mknod( 425 cancel, 426 // we don't actually use the umask or padding here, so they don't get passed 427 &fuse.MknodIn{ 428 InHeader: in.InHeader, 429 Mode: in.Mode, 430 }, 431 name, 432 &out.EntryOut, 433 ) 434 if result == fuse.Status(syscall.EEXIST) { 435 // if the inode already exists, we should truncate the existing file and 436 // return the existing file inode as per "man creat" 437 parentID := f.TranslateID(in.NodeId) 438 child, _ := f.GetChild(parentID, name, f.auth) 439 log.Debug(). 440 Str("op", "Create"). 441 Uint64("nodeID", in.NodeId). 442 Str("id", parentID). 443 Str("childID", child.ID()). 444 Str("path", child.Path()). 445 Str("mode", Octal(in.Mode)). 446 Msg("Child inode already exists, truncating.") 447 f.content.Delete(child.ID()) 448 f.content.Open(child.ID()) 449 child.DriveItem.Size = 0 450 child.hasChanges = true 451 return fuse.OK 452 } 453 // no further initialized required to open the file, it's empty 454 return result 455 } 456 457 // Open fetches a Inodes's content and initializes the .Data field with actual 458 // data from the server. 459 func (f *Filesystem) Open(cancel <-chan struct{}, in *fuse.OpenIn, out *fuse.OpenOut) fuse.Status { 460 id := f.TranslateID(in.NodeId) 461 inode := f.GetID(id) 462 if inode == nil { 463 return fuse.ENOENT 464 } 465 466 path := inode.Path() 467 ctx := log.With(). 468 Str("op", "Open"). 469 Uint64("nodeID", in.NodeId). 470 Str("id", id). 471 Str("path", path). 472 Logger() 473 474 flags := int(in.Flags) 475 if flags&os.O_RDWR+flags&os.O_WRONLY > 0 && f.IsOffline() { 476 ctx.Warn(). 477 Bool("readWrite", flags&os.O_RDWR > 0). 478 Bool("writeOnly", flags&os.O_WRONLY > 0). 479 Msg("Refusing Open() with write flag, FS is offline.") 480 return fuse.EROFS 481 } 482 483 ctx.Debug().Msg("") 484 485 // try grabbing from disk 486 fd, err := f.content.Open(id) 487 if err != nil { 488 ctx.Error().Err(err).Msg("Could not create cache file.") 489 return fuse.EIO 490 } 491 492 if isLocalID(id) { 493 // just use whatever's present if we're the only ones who have it 494 return fuse.OK 495 } 496 497 // we have something on disk- 498 // verify content against what we're supposed to have 499 inode.Lock() 500 defer inode.Unlock() 501 // stay locked until end to prevent multiple Opens() from competing for 502 // downloads of the same file. 503 504 if inode.VerifyChecksum(graph.QuickXORHashStream(fd)) { 505 // disk content is only used if the checksums match 506 ctx.Info().Msg("Found content in cache.") 507 508 // we check size ourselves in case the API file sizes are WRONG (it happens) 509 st, _ := fd.Stat() 510 inode.DriveItem.Size = uint64(st.Size()) 511 return fuse.OK 512 } 513 514 ctx.Info().Msg( 515 "Not using cached item due to file hash mismatch, fetching content from API.", 516 ) 517 518 // write to tempfile first to ensure our download is good 519 tempID := "temp-" + id 520 temp, err := f.content.Open(tempID) 521 if err != nil { 522 ctx.Error().Err(err).Msg("Failed to create tempfile for download.") 523 return fuse.EIO 524 } 525 defer f.content.Delete(tempID) 526 527 // replace content only on a match 528 size, err := graph.GetItemContentStream(id, f.auth, temp) 529 if err != nil || !inode.VerifyChecksum(graph.QuickXORHashStream(temp)) { 530 ctx.Error().Err(err).Msg("Failed to fetch remote content.") 531 return fuse.EREMOTEIO 532 } 533 temp.Seek(0, 0) // being explicit, even though already done in hashstream func 534 fd.Seek(0, 0) 535 fd.Truncate(0) 536 io.Copy(fd, temp) 537 inode.DriveItem.Size = size 538 return fuse.OK 539 } 540 541 // Unlink deletes a child file. 542 func (f *Filesystem) Unlink(cancel <-chan struct{}, in *fuse.InHeader, name string) fuse.Status { 543 parentID := f.TranslateID(in.NodeId) 544 child, _ := f.GetChild(parentID, name, nil) 545 if child == nil { 546 // the file we are unlinking never existed 547 return fuse.ENOENT 548 } 549 if f.IsOffline() { 550 return fuse.EROFS 551 } 552 553 id := child.ID() 554 path := child.Path() 555 ctx := log.With(). 556 Str("op", "Unlink"). 557 Uint64("nodeID", in.NodeId). 558 Str("id", parentID). 559 Str("childID", id). 560 Str("path", path). 561 Logger() 562 ctx.Debug().Msg("Unlinking inode.") 563 564 // if no ID, the item is local-only, and does not need to be deleted on the 565 // server 566 if !isLocalID(id) { 567 if err := graph.Remove(id, f.auth); err != nil { 568 ctx.Err(err).Msg("Failed to delete item on server. Aborting op.") 569 return fuse.EREMOTEIO 570 } 571 } 572 573 f.DeleteID(id) 574 f.content.Delete(id) 575 return fuse.OK 576 } 577 578 // Read an inode's data like a file. 579 func (f *Filesystem) Read(cancel <-chan struct{}, in *fuse.ReadIn, buf []byte) (fuse.ReadResult, fuse.Status) { 580 inode := f.GetNodeID(in.NodeId) 581 if inode == nil { 582 return fuse.ReadResultData(make([]byte, 0)), fuse.EBADF 583 } 584 585 id := inode.ID() 586 path := inode.Path() 587 ctx := log.With(). 588 Str("op", "Read"). 589 Uint64("nodeID", in.NodeId). 590 Str("id", id). 591 Str("path", path). 592 Int("bufsize", len(buf)). 593 Logger() 594 ctx.Trace().Msg("") 595 596 fd, err := f.content.Open(id) 597 if err != nil { 598 ctx.Error().Err(err).Msg("Cache Open() failed.") 599 return fuse.ReadResultData(make([]byte, 0)), fuse.EIO 600 } 601 602 // we are locked for the remainder of this op 603 inode.RLock() 604 defer inode.RUnlock() 605 return fuse.ReadResultFd(fd.Fd(), int64(in.Offset), int(in.Size)), fuse.OK 606 } 607 608 // Write to an Inode like a file. Note that changes are 100% local until 609 // Flush() is called. Returns the number of bytes written and the status of the 610 // op. 611 func (f *Filesystem) Write(cancel <-chan struct{}, in *fuse.WriteIn, data []byte) (uint32, fuse.Status) { 612 id := f.TranslateID(in.NodeId) 613 inode := f.GetID(id) 614 if inode == nil { 615 return 0, fuse.EBADF 616 } 617 618 nWrite := len(data) 619 offset := int(in.Offset) 620 ctx := log.With(). 621 Str("op", "Write"). 622 Str("id", id). 623 Uint64("nodeID", in.NodeId). 624 Str("path", inode.Path()). 625 Int("bufsize", nWrite). 626 Int("offset", offset). 627 Logger() 628 ctx.Trace().Msg("") 629 630 fd, err := f.content.Open(id) 631 if err != nil { 632 ctx.Error().Msg("Cache Open() failed.") 633 return 0, fuse.EIO 634 } 635 636 inode.Lock() 637 defer inode.Unlock() 638 n, err := fd.WriteAt(data, int64(offset)) 639 if err != nil { 640 ctx.Error().Err(err).Msg("Error during write") 641 return uint32(n), fuse.EIO 642 } 643 644 st, _ := fd.Stat() 645 inode.DriveItem.Size = uint64(st.Size()) 646 inode.hasChanges = true 647 return uint32(n), fuse.OK 648 } 649 650 // Fsync is a signal to ensure writes to the Inode are flushed to stable 651 // storage. This method is used to trigger uploads of file content. 652 func (f *Filesystem) Fsync(cancel <-chan struct{}, in *fuse.FsyncIn) fuse.Status { 653 id := f.TranslateID(in.NodeId) 654 inode := f.GetID(id) 655 if inode == nil { 656 return fuse.EBADF 657 } 658 659 ctx := log.With(). 660 Str("op", "Fsync"). 661 Str("id", id). 662 Uint64("nodeID", in.NodeId). 663 Str("path", inode.Path()). 664 Logger() 665 ctx.Debug().Msg("") 666 if inode.HasChanges() { 667 inode.Lock() 668 inode.hasChanges = false 669 670 // recompute hashes when saving new content 671 inode.DriveItem.File = &graph.File{} 672 fd, err := f.content.Open(id) 673 if err != nil { 674 ctx.Error().Err(err).Msg("Could not get fd.") 675 } 676 fd.Sync() 677 inode.DriveItem.File.Hashes.QuickXorHash = graph.QuickXORHashStream(fd) 678 inode.Unlock() 679 680 if err := f.uploads.QueueUpload(inode); err != nil { 681 ctx.Error().Err(err).Msg("Error creating upload session.") 682 return fuse.EREMOTEIO 683 } 684 return fuse.OK 685 } 686 return fuse.OK 687 } 688 689 // Flush is called when a file descriptor is closed. Uses Fsync() to perform file 690 // uploads. (Release not implemented because all cleanup is already done here). 691 func (f *Filesystem) Flush(cancel <-chan struct{}, in *fuse.FlushIn) fuse.Status { 692 inode := f.GetNodeID(in.NodeId) 693 if inode == nil { 694 return fuse.EBADF 695 } 696 697 id := inode.ID() 698 log.Trace(). 699 Str("op", "Flush"). 700 Str("id", id). 701 Str("path", inode.Path()). 702 Uint64("nodeID", in.NodeId). 703 Msg("") 704 f.Fsync(cancel, &fuse.FsyncIn{InHeader: in.InHeader}) 705 f.content.Close(id) 706 return 0 707 } 708 709 // Getattr returns a the Inode as a UNIX stat. Holds the read mutex for all of 710 // the "metadata fetch" operations. 711 func (f *Filesystem) GetAttr(cancel <-chan struct{}, in *fuse.GetAttrIn, out *fuse.AttrOut) fuse.Status { 712 id := f.TranslateID(in.NodeId) 713 inode := f.GetID(id) 714 if inode == nil { 715 return fuse.ENOENT 716 } 717 log.Trace(). 718 Str("op", "GetAttr"). 719 Uint64("nodeID", in.NodeId). 720 Str("id", id). 721 Str("path", inode.Path()). 722 Msg("") 723 724 out.Attr = inode.makeAttr() 725 out.SetTimeout(timeout) 726 return fuse.OK 727 } 728 729 // Setattr is the workhorse for setting filesystem attributes. Does the work of 730 // operations like utimens, chmod, chown (not implemented, FUSE is single-user), 731 // and truncate. 732 func (f *Filesystem) SetAttr(cancel <-chan struct{}, in *fuse.SetAttrIn, out *fuse.AttrOut) fuse.Status { 733 i := f.GetNodeID(in.NodeId) 734 if i == nil { 735 return fuse.ENOENT 736 } 737 path := i.Path() 738 isDir := i.IsDir() // holds an rlock 739 i.Lock() 740 741 ctx := log.With(). 742 Str("op", "SetAttr"). 743 Uint64("nodeID", in.NodeId). 744 Str("id", i.DriveItem.ID). 745 Str("path", path). 746 Logger() 747 748 // utimens 749 if mtime, valid := in.GetMTime(); valid { 750 ctx.Info(). 751 Str("subop", "utimens"). 752 Time("oldMtime", *i.DriveItem.ModTime). 753 Time("newMtime", *i.DriveItem.ModTime). 754 Msg("") 755 i.DriveItem.ModTime = &mtime 756 } 757 758 // chmod 759 if mode, valid := in.GetMode(); valid { 760 ctx.Info(). 761 Str("subop", "chmod"). 762 Str("oldMode", Octal(i.mode)). 763 Str("newMode", Octal(mode)). 764 Msg("") 765 if isDir { 766 i.mode = fuse.S_IFDIR | mode 767 } else { 768 i.mode = fuse.S_IFREG | mode 769 } 770 } 771 772 // truncate 773 if size, valid := in.GetSize(); valid { 774 ctx.Info(). 775 Str("subop", "truncate"). 776 Uint64("oldSize", i.DriveItem.Size). 777 Uint64("newSize", size). 778 Msg("") 779 fd, _ := f.content.Open(i.DriveItem.ID) 780 // the unix syscall does not update the seek position, so neither should we 781 fd.Truncate(int64(size)) 782 i.DriveItem.Size = size 783 i.hasChanges = true 784 } 785 786 i.Unlock() 787 out.Attr = i.makeAttr() 788 out.SetTimeout(timeout) 789 return fuse.OK 790 } 791 792 // Rename renames and/or moves an inode. 793 func (f *Filesystem) Rename(cancel <-chan struct{}, in *fuse.RenameIn, name string, newName string) fuse.Status { 794 if isNameRestricted(newName) { 795 return fuse.EINVAL 796 } 797 798 oldParentID := f.TranslateID(in.NodeId) 799 oldParentItem := f.GetNodeID(in.NodeId) 800 if oldParentID == "" || oldParentItem == nil { 801 return fuse.EBADF 802 } 803 path := filepath.Join(oldParentItem.Path(), name) 804 805 // we'll have the metadata for the dest inode already so it is not necessary 806 // to use GetPath() to prefetch it. In order for the fs to know about this 807 // inode, it has already fetched all of the inodes up to the new destination. 808 newParentItem := f.GetNodeID(in.Newdir) 809 if newParentItem == nil { 810 return fuse.ENOENT 811 } 812 dest := filepath.Join(newParentItem.Path(), newName) 813 814 inode, _ := f.GetChild(oldParentID, name, f.auth) 815 id, err := f.remoteID(inode) 816 newParentID := newParentItem.ID() 817 818 ctx := log.With(). 819 Str("op", "Rename"). 820 Str("id", id). 821 Str("parentID", newParentID). 822 Str("path", path). 823 Str("dest", dest). 824 Logger() 825 ctx.Info(). 826 Uint64("srcNodeID", in.NodeId). 827 Uint64("dstNodeID", in.Newdir). 828 Msg("") 829 830 if isLocalID(id) || err != nil { 831 // uploads will fail without an id 832 ctx.Error().Err(err). 833 Msg("ID of item to move cannot be local and we failed to obtain an ID.") 834 return fuse.EREMOTEIO 835 } 836 837 // perform remote rename 838 if err = graph.Rename(id, newName, newParentID, f.auth); err != nil { 839 ctx.Error().Err(err).Msg("Failed to rename remote item.") 840 return fuse.EREMOTEIO 841 } 842 843 // now rename local copy 844 if err = f.MovePath(oldParentID, newParentID, name, newName, f.auth); err != nil { 845 ctx.Error().Err(err).Msg("Failed to rename local item.") 846 return fuse.EIO 847 } 848 849 // whew! item renamed 850 return fuse.OK 851 }