github.com/cs3org/reva/v2@v2.27.7/pkg/storage/fs/posix/tree/assimilation.go (about) 1 // Copyright 2018-2021 CERN 2 // 3 // Licensed under the Apache License, Version 2.0 (the "License"); 4 // you may not use this file except in compliance with the License. 5 // You may obtain a copy of the License at 6 // 7 // http://www.apache.org/licenses/LICENSE-2.0 8 // 9 // Unless required by applicable law or agreed to in writing, software 10 // distributed under the License is distributed on an "AS IS" BASIS, 11 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 // See the License for the specific language governing permissions and 13 // limitations under the License. 14 // 15 // In applying this license, CERN does not waive the privileges and immunities 16 // granted to it by virtue of its status as an Intergovernmental Organization 17 // or submit itself to any jurisdiction. 18 19 package tree 20 21 import ( 22 "context" 23 "fmt" 24 "io/fs" 25 "os" 26 "path/filepath" 27 "strings" 28 "sync" 29 "syscall" 30 "time" 31 32 "github.com/google/uuid" 33 "github.com/pkg/errors" 34 "github.com/rs/zerolog/log" 35 36 userv1beta1 "github.com/cs3org/go-cs3apis/cs3/identity/user/v1beta1" 37 provider "github.com/cs3org/go-cs3apis/cs3/storage/provider/v1beta1" 38 "github.com/cs3org/reva/v2/pkg/events" 39 "github.com/cs3org/reva/v2/pkg/storage/fs/posix/lookup" 40 "github.com/cs3org/reva/v2/pkg/storage/utils/decomposedfs/metadata" 41 "github.com/cs3org/reva/v2/pkg/storage/utils/decomposedfs/metadata/prefixes" 42 "github.com/cs3org/reva/v2/pkg/storage/utils/decomposedfs/node" 43 "github.com/cs3org/reva/v2/pkg/utils" 44 ) 45 46 type ScanDebouncer struct { 47 after time.Duration 48 f func(item scanItem) 49 pending sync.Map 50 inProgress sync.Map 51 52 mutex sync.Mutex 53 } 54 55 type EventAction int 56 57 const ( 58 ActionCreate EventAction = iota 59 ActionUpdate 60 ActionMove 61 ActionDelete 62 ActionMoveFrom 63 ) 64 65 type queueItem struct { 66 item scanItem 67 timer *time.Timer 68 } 69 70 const dirtyFlag = "user.ocis.dirty" 71 72 // NewScanDebouncer returns a new SpaceDebouncer instance 73 func NewScanDebouncer(d time.Duration, f func(item scanItem)) *ScanDebouncer { 74 return &ScanDebouncer{ 75 after: d, 76 f: f, 77 pending: sync.Map{}, 78 inProgress: sync.Map{}, 79 } 80 } 81 82 // Debounce restarts the debounce timer for the given space 83 func (d *ScanDebouncer) Debounce(item scanItem) { 84 if d.after == 0 { 85 d.f(item) 86 return 87 } 88 89 d.mutex.Lock() 90 defer d.mutex.Unlock() 91 92 path := item.Path 93 force := item.ForceRescan 94 recurse := item.Recurse 95 if i, ok := d.pending.Load(item.Path); ok { 96 queueItem := i.(*queueItem) 97 force = force || queueItem.item.ForceRescan 98 recurse = recurse || queueItem.item.Recurse 99 queueItem.timer.Stop() 100 } 101 102 d.pending.Store(item.Path, &queueItem{ 103 item: item, 104 timer: time.AfterFunc(d.after, func() { 105 if _, ok := d.inProgress.Load(path); ok { 106 // Reschedule this run for when the previous run has finished 107 d.mutex.Lock() 108 if i, ok := d.pending.Load(path); ok { 109 i.(*queueItem).timer.Reset(d.after) 110 } 111 112 d.mutex.Unlock() 113 return 114 } 115 116 d.pending.Delete(path) 117 d.inProgress.Store(path, true) 118 defer d.inProgress.Delete(path) 119 d.f(scanItem{ 120 Path: path, 121 ForceRescan: force, 122 Recurse: recurse, 123 }) 124 }), 125 }) 126 } 127 128 // InProgress returns true if the given path is currently being processed 129 func (d *ScanDebouncer) InProgress(path string) bool { 130 d.mutex.Lock() 131 defer d.mutex.Unlock() 132 if _, ok := d.pending.Load(path); ok { 133 return true 134 } 135 136 _, ok := d.inProgress.Load(path) 137 return ok 138 } 139 140 func (t *Tree) workScanQueue() { 141 for i := 0; i < t.options.MaxConcurrency; i++ { 142 go func() { 143 for { 144 item := <-t.scanQueue 145 146 err := t.assimilate(item) 147 if err != nil { 148 log.Error().Err(err).Str("path", item.Path).Msg("failed to assimilate item") 149 continue 150 } 151 152 if item.Recurse { 153 err = t.WarmupIDCache(item.Path, true, false) 154 if err != nil { 155 log.Error().Err(err).Str("path", item.Path).Msg("failed to warmup id cache") 156 } 157 } 158 } 159 }() 160 } 161 } 162 163 // Scan scans the given path and updates the id chache 164 func (t *Tree) Scan(path string, action EventAction, isDir bool) error { 165 // cases: 166 switch action { 167 case ActionCreate: 168 t.log.Debug().Str("path", path).Bool("isDir", isDir).Msg("scanning path (ActionCreate)") 169 if !isDir { 170 // 1. New file (could be emitted as part of a new directory) 171 // -> assimilate file 172 // -> scan parent directory recursively to update tree size and catch nodes that weren't covered by an event 173 if !t.scanDebouncer.InProgress(filepath.Dir(path)) { 174 t.scanDebouncer.Debounce(scanItem{ 175 Path: path, 176 ForceRescan: false, 177 }) 178 } 179 if err := t.setDirty(filepath.Dir(path), true); err != nil { 180 return err 181 } 182 t.scanDebouncer.Debounce(scanItem{ 183 Path: filepath.Dir(path), 184 ForceRescan: true, 185 Recurse: true, 186 }) 187 } else { 188 // 2. New directory 189 // -> scan directory 190 if err := t.setDirty(path, true); err != nil { 191 return err 192 } 193 t.scanDebouncer.Debounce(scanItem{ 194 Path: path, 195 ForceRescan: true, 196 Recurse: true, 197 }) 198 } 199 200 case ActionUpdate: 201 t.log.Debug().Str("path", path).Bool("isDir", isDir).Msg("scanning path (ActionUpdate)") 202 // 3. Updated file 203 // -> update file unless parent directory is being rescanned 204 if !t.scanDebouncer.InProgress(filepath.Dir(path)) { 205 t.scanDebouncer.Debounce(scanItem{ 206 Path: path, 207 ForceRescan: true, 208 }) 209 } 210 211 case ActionMove: 212 t.log.Debug().Str("path", path).Bool("isDir", isDir).Msg("scanning path (ActionMove)") 213 // 4. Moved file 214 // -> update file 215 // 5. Moved directory 216 // -> update directory and all children 217 t.scanDebouncer.Debounce(scanItem{ 218 Path: path, 219 ForceRescan: isDir, 220 Recurse: isDir, 221 }) 222 223 case ActionMoveFrom: 224 t.log.Debug().Str("path", path).Bool("isDir", isDir).Msg("scanning path (ActionMoveFrom)") 225 // 6. file/directory moved out of the watched directory 226 // -> update directory 227 if err := t.setDirty(filepath.Dir(path), true); err != nil { 228 return err 229 } 230 231 go func() { _ = t.WarmupIDCache(filepath.Dir(path), false, true) }() 232 233 case ActionDelete: 234 t.log.Debug().Str("path", path).Bool("isDir", isDir).Msg("handling deleted item") 235 236 // 7. Deleted file or directory 237 // -> update parent and all children 238 239 err := t.HandleFileDelete(path) 240 if err != nil { 241 return err 242 } 243 244 t.scanDebouncer.Debounce(scanItem{ 245 Path: filepath.Dir(path), 246 ForceRescan: true, 247 Recurse: true, 248 }) 249 } 250 251 return nil 252 } 253 254 func (t *Tree) HandleFileDelete(path string) error { 255 // purge metadata 256 if err := t.lookup.(*lookup.Lookup).IDCache.DeleteByPath(context.Background(), path); err != nil { 257 t.log.Error().Err(err).Str("path", path).Msg("could not delete id cache entry by path") 258 } 259 if err := t.lookup.MetadataBackend().Purge(context.Background(), path); err != nil { 260 t.log.Error().Err(err).Str("path", path).Msg("could not purge metadata") 261 } 262 263 // send event 264 owner, spaceID, nodeID, parentID, err := t.getOwnerAndIDs(filepath.Dir(path)) 265 if err != nil { 266 return err 267 } 268 269 t.PublishEvent(events.ItemTrashed{ 270 Owner: owner, 271 Executant: owner, 272 Ref: &provider.Reference{ 273 ResourceId: &provider.ResourceId{ 274 StorageId: t.options.MountID, 275 SpaceId: spaceID, 276 OpaqueId: parentID, 277 }, 278 Path: filepath.Base(path), 279 }, 280 ID: &provider.ResourceId{ 281 StorageId: t.options.MountID, 282 SpaceId: spaceID, 283 OpaqueId: nodeID, 284 }, 285 Timestamp: utils.TSNow(), 286 }) 287 288 return nil 289 } 290 291 func (t *Tree) getOwnerAndIDs(path string) (*userv1beta1.UserId, string, string, string, error) { 292 lu := t.lookup.(*lookup.Lookup) 293 294 spaceID, nodeID, err := lu.IDsForPath(context.Background(), path) 295 if err != nil { 296 return nil, "", "", "", err 297 } 298 299 attrs, err := t.lookup.MetadataBackend().All(context.Background(), path) 300 if err != nil { 301 return nil, "", "", "", err 302 } 303 304 parentID := string(attrs[prefixes.ParentidAttr]) 305 306 spacePath, ok := lu.GetCachedID(context.Background(), spaceID, spaceID) 307 if !ok { 308 return nil, "", "", "", fmt.Errorf("could not find space root for path %s", path) 309 } 310 311 spaceAttrs, err := t.lookup.MetadataBackend().All(context.Background(), spacePath) 312 if err != nil { 313 return nil, "", "", "", err 314 } 315 316 owner := &userv1beta1.UserId{ 317 Idp: string(spaceAttrs[prefixes.OwnerIDPAttr]), 318 OpaqueId: string(spaceAttrs[prefixes.OwnerIDAttr]), 319 } 320 321 return owner, nodeID, spaceID, parentID, nil 322 } 323 324 func (t *Tree) findSpaceId(path string) (string, node.Attributes, error) { 325 // find the space id, scope by the according user 326 spaceCandidate := path 327 spaceAttrs := node.Attributes{} 328 for strings.HasPrefix(spaceCandidate, t.options.Root) { 329 spaceAttrs, err := t.lookup.MetadataBackend().All(context.Background(), spaceCandidate) 330 spaceID := spaceAttrs[prefixes.SpaceIDAttr] 331 if err == nil && len(spaceID) > 0 { 332 if t.options.UseSpaceGroups { 333 // set the uid and gid for the space 334 fi, err := os.Stat(spaceCandidate) 335 if err != nil { 336 return "", spaceAttrs, err 337 } 338 sys := fi.Sys().(*syscall.Stat_t) 339 gid := int(sys.Gid) 340 _, err = t.userMapper.ScopeUserByIds(-1, gid) 341 if err != nil { 342 return "", spaceAttrs, err 343 } 344 } 345 346 return string(spaceID), spaceAttrs, nil 347 } 348 spaceCandidate = filepath.Dir(spaceCandidate) 349 } 350 return "", spaceAttrs, fmt.Errorf("could not find space for path %s", path) 351 } 352 353 func (t *Tree) assimilate(item scanItem) error { 354 var id []byte 355 var err error 356 357 // First find the space id 358 spaceID, spaceAttrs, err := t.findSpaceId(item.Path) 359 if err != nil { 360 return err 361 } 362 363 // lock the file for assimilation 364 unlock, err := t.lookup.MetadataBackend().Lock(item.Path) 365 if err != nil { 366 return errors.Wrap(err, "failed to lock item for assimilation") 367 } 368 defer func() { 369 _ = unlock() 370 }() 371 372 user := &userv1beta1.UserId{ 373 Idp: string(spaceAttrs[prefixes.OwnerIDPAttr]), 374 OpaqueId: string(spaceAttrs[prefixes.OwnerIDAttr]), 375 } 376 377 // check for the id attribute again after grabbing the lock, maybe the file was assimilated/created by us in the meantime 378 id, err = t.lookup.MetadataBackend().Get(context.Background(), item.Path, prefixes.IDAttr) 379 if err == nil { 380 previousPath, ok := t.lookup.(*lookup.Lookup).GetCachedID(context.Background(), spaceID, string(id)) 381 previousParentID, _ := t.lookup.MetadataBackend().Get(context.Background(), item.Path, prefixes.ParentidAttr) 382 383 // was it moved or copied/restored with a clashing id? 384 if ok && len(previousParentID) > 0 && previousPath != item.Path { 385 _, err := os.Stat(previousPath) 386 if err == nil { 387 // this id clashes with an existing item -> clear metadata and re-assimilate 388 t.log.Debug().Str("path", item.Path).Msg("ID clash detected, purging metadata and re-assimilating") 389 390 if err := t.lookup.MetadataBackend().Purge(context.Background(), item.Path); err != nil { 391 t.log.Error().Err(err).Str("path", item.Path).Msg("could not purge metadata") 392 } 393 go func() { 394 if err := t.assimilate(scanItem{Path: item.Path, ForceRescan: true}); err != nil { 395 t.log.Error().Err(err).Str("path", item.Path).Msg("could not re-assimilate") 396 } 397 }() 398 } else { 399 // this is a move 400 t.log.Debug().Str("path", item.Path).Msg("move detected") 401 402 if err := t.lookup.(*lookup.Lookup).CacheID(context.Background(), spaceID, string(id), item.Path); err != nil { 403 t.log.Error().Err(err).Str("spaceID", spaceID).Str("id", string(id)).Str("path", item.Path).Msg("could not cache id") 404 } 405 _, err := t.updateFile(item.Path, string(id), spaceID) 406 if err != nil { 407 return err 408 } 409 410 // purge original metadata. Only delete the path entry using DeletePath(reverse lookup), not the whole entry pair. 411 if err := t.lookup.(*lookup.Lookup).IDCache.DeletePath(context.Background(), previousPath); err != nil { 412 t.log.Error().Err(err).Str("path", previousPath).Msg("could not delete id cache entry by path") 413 } 414 if err := t.lookup.MetadataBackend().Purge(context.Background(), previousPath); err != nil { 415 t.log.Error().Err(err).Str("path", previousPath).Msg("could not purge metadata") 416 } 417 418 fi, err := os.Stat(item.Path) 419 if err != nil { 420 return err 421 } 422 if fi.IsDir() { 423 // if it was moved and it is a directory we need to propagate the move 424 go func() { 425 if err := t.WarmupIDCache(item.Path, false, true); err != nil { 426 t.log.Error().Err(err).Str("path", item.Path).Msg("could not warmup id cache") 427 } 428 }() 429 } 430 431 parentID, err := t.lookup.MetadataBackend().Get(context.Background(), item.Path, prefixes.ParentidAttr) 432 if err == nil && len(parentID) > 0 { 433 ref := &provider.Reference{ 434 ResourceId: &provider.ResourceId{ 435 StorageId: t.options.MountID, 436 SpaceId: spaceID, 437 OpaqueId: string(parentID), 438 }, 439 Path: filepath.Base(item.Path), 440 } 441 oldRef := &provider.Reference{ 442 ResourceId: &provider.ResourceId{ 443 StorageId: t.options.MountID, 444 SpaceId: spaceID, 445 OpaqueId: string(previousParentID), 446 }, 447 Path: filepath.Base(previousPath), 448 } 449 t.PublishEvent(events.ItemMoved{ 450 SpaceOwner: user, 451 Executant: user, 452 Owner: user, 453 Ref: ref, 454 OldReference: oldRef, 455 Timestamp: utils.TSNow(), 456 }) 457 } 458 } 459 } else { 460 // This item had already been assimilated in the past. Update the path 461 t.log.Debug().Str("path", item.Path).Msg("updating cached path") 462 if err := t.lookup.(*lookup.Lookup).CacheID(context.Background(), spaceID, string(id), item.Path); err != nil { 463 t.log.Error().Err(err).Str("spaceID", spaceID).Str("id", string(id)).Str("path", item.Path).Msg("could not cache id") 464 } 465 466 _, err := t.updateFile(item.Path, string(id), spaceID) 467 if err != nil { 468 return err 469 } 470 } 471 } else { 472 t.log.Debug().Str("path", item.Path).Msg("new item detected") 473 // assimilate new file 474 newId := uuid.New().String() 475 fi, err := t.updateFile(item.Path, newId, spaceID) 476 if err != nil { 477 return err 478 } 479 480 ref := &provider.Reference{ 481 ResourceId: &provider.ResourceId{ 482 StorageId: t.options.MountID, 483 SpaceId: spaceID, 484 OpaqueId: newId, 485 }, 486 } 487 if fi.IsDir() { 488 t.PublishEvent(events.ContainerCreated{ 489 SpaceOwner: user, 490 Executant: user, 491 Owner: user, 492 Ref: ref, 493 Timestamp: utils.TSNow(), 494 }) 495 } else { 496 if fi.Size() == 0 { 497 t.PublishEvent(events.FileTouched{ 498 SpaceOwner: user, 499 Executant: user, 500 Ref: ref, 501 Timestamp: utils.TSNow(), 502 }) 503 } else { 504 t.PublishEvent(events.UploadReady{ 505 SpaceOwner: user, 506 FileRef: ref, 507 Timestamp: utils.TSNow(), 508 }) 509 } 510 } 511 } 512 return nil 513 } 514 515 func (t *Tree) updateFile(path, id, spaceID string) (fs.FileInfo, error) { 516 retries := 1 517 parentID := "" 518 assimilate: 519 if id != spaceID { 520 // read parent 521 parentAttribs, err := t.lookup.MetadataBackend().All(context.Background(), filepath.Dir(path)) 522 if err != nil { 523 return nil, fmt.Errorf("failed to read parent item attributes") 524 } 525 526 if len(parentAttribs) == 0 || len(parentAttribs[prefixes.IDAttr]) == 0 { 527 if retries == 0 { 528 return nil, fmt.Errorf("got empty parent attribs even after assimilating") 529 } 530 531 // assimilate parent first 532 err = t.assimilate(scanItem{Path: filepath.Dir(path), ForceRescan: false}) 533 if err != nil { 534 return nil, err 535 } 536 537 // retry 538 retries-- 539 goto assimilate 540 } 541 parentID = string(parentAttribs[prefixes.IDAttr]) 542 } 543 544 // assimilate file 545 fi, err := os.Stat(path) 546 if err != nil { 547 return nil, errors.Wrap(err, "failed to stat item") 548 } 549 550 attrs, err := t.lookup.MetadataBackend().All(context.Background(), path) 551 if err != nil && !metadata.IsAttrUnset(err) { 552 return nil, errors.Wrap(err, "failed to get item attribs") 553 } 554 previousAttribs := node.Attributes(attrs) 555 556 attributes := node.Attributes{ 557 prefixes.IDAttr: []byte(id), 558 prefixes.NameAttr: []byte(filepath.Base(path)), 559 } 560 if len(parentID) > 0 { 561 attributes[prefixes.ParentidAttr] = []byte(parentID) 562 } 563 564 sha1h, md5h, adler32h, err := node.CalculateChecksums(context.Background(), path) 565 if err == nil { 566 attributes[prefixes.ChecksumPrefix+"sha1"] = sha1h.Sum(nil) 567 attributes[prefixes.ChecksumPrefix+"md5"] = md5h.Sum(nil) 568 attributes[prefixes.ChecksumPrefix+"adler32"] = adler32h.Sum(nil) 569 } 570 571 if fi.IsDir() { 572 attributes.SetInt64(prefixes.TypeAttr, int64(provider.ResourceType_RESOURCE_TYPE_CONTAINER)) 573 attributes.SetInt64(prefixes.TreesizeAttr, 0) 574 if previousAttribs != nil && previousAttribs[prefixes.TreesizeAttr] != nil { 575 attributes[prefixes.TreesizeAttr] = previousAttribs[prefixes.TreesizeAttr] 576 } 577 attributes[prefixes.PropagationAttr] = []byte("1") 578 } else { 579 attributes.SetInt64(prefixes.TypeAttr, int64(provider.ResourceType_RESOURCE_TYPE_FILE)) 580 } 581 582 n := node.New(spaceID, id, parentID, filepath.Base(path), fi.Size(), "", provider.ResourceType_RESOURCE_TYPE_FILE, nil, t.lookup) 583 n.SpaceRoot = &node.Node{SpaceID: spaceID, ID: spaceID} 584 err = t.Propagate(context.Background(), n, 0) 585 if err != nil { 586 return nil, errors.Wrap(err, "failed to propagate") 587 } 588 589 t.log.Debug().Str("path", path).Interface("attributes", attributes).Msg("setting attributes") 590 err = t.lookup.MetadataBackend().SetMultiple(context.Background(), path, attributes, false) 591 if err != nil { 592 return nil, errors.Wrap(err, "failed to set attributes") 593 } 594 595 if err := t.lookup.(*lookup.Lookup).CacheID(context.Background(), spaceID, id, path); err != nil { 596 t.log.Error().Err(err).Str("spaceID", spaceID).Str("id", id).Str("path", path).Msg("could not cache id") 597 } 598 599 return fi, nil 600 } 601 602 // WarmupIDCache warms up the id cache 603 func (t *Tree) WarmupIDCache(root string, assimilate, onlyDirty bool) error { 604 root = filepath.Clean(root) 605 spaceID := []byte("") 606 607 scopeSpace := func(spaceCandidate string) error { 608 if !t.options.UseSpaceGroups { 609 return nil 610 } 611 612 // set the uid and gid for the space 613 fi, err := os.Stat(spaceCandidate) 614 if err != nil { 615 return err 616 } 617 sys := fi.Sys().(*syscall.Stat_t) 618 gid := int(sys.Gid) 619 _, err = t.userMapper.ScopeUserByIds(-1, gid) 620 return err 621 } 622 623 sizes := make(map[string]int64) 624 err := filepath.Walk(root, func(path string, info os.FileInfo, err error) error { 625 // skip lock and upload files 626 if isLockFile(path) { 627 return nil 628 } 629 if isTrash(path) || t.isUpload(path) { 630 return filepath.SkipDir 631 } 632 633 if err != nil { 634 return err 635 } 636 637 // calculate tree sizes 638 if !info.IsDir() { 639 dir := path 640 for dir != root { 641 dir = filepath.Clean(filepath.Dir(dir)) 642 sizes[dir] += info.Size() 643 } 644 } else if onlyDirty { 645 dirty, err := t.isDirty(path) 646 if err != nil { 647 return err 648 } 649 if !dirty { 650 return filepath.SkipDir 651 } 652 sizes[path] += 0 // Make sure to set the size to 0 for empty directories 653 } 654 655 attribs, err := t.lookup.MetadataBackend().All(context.Background(), path) 656 if err == nil && len(attribs[prefixes.IDAttr]) > 0 { 657 nodeSpaceID := attribs[prefixes.SpaceIDAttr] 658 if len(nodeSpaceID) > 0 { 659 spaceID = nodeSpaceID 660 661 err = scopeSpace(path) 662 if err != nil { 663 return err 664 } 665 } else { 666 // try to find space 667 spaceCandidate := path 668 for strings.HasPrefix(spaceCandidate, t.options.Root) { 669 spaceID, err = t.lookup.MetadataBackend().Get(context.Background(), spaceCandidate, prefixes.SpaceIDAttr) 670 if err == nil { 671 err = scopeSpace(path) 672 if err != nil { 673 return err 674 } 675 break 676 } 677 spaceCandidate = filepath.Dir(spaceCandidate) 678 } 679 } 680 if len(spaceID) == 0 { 681 return nil // no space found 682 } 683 684 id, ok := attribs[prefixes.IDAttr] 685 if ok { 686 // Check if the item on the previous still exists. In this case it might have been a copy with extended attributes -> set new ID 687 previousPath, ok := t.lookup.(*lookup.Lookup).GetCachedID(context.Background(), string(spaceID), string(id)) 688 if ok && previousPath != path { 689 // this id clashes with an existing id -> clear metadata and re-assimilate 690 _, err := os.Stat(previousPath) 691 if err == nil { 692 _ = t.lookup.MetadataBackend().Purge(context.Background(), path) 693 _ = t.assimilate(scanItem{Path: path, ForceRescan: true}) 694 } 695 } 696 if err := t.lookup.(*lookup.Lookup).CacheID(context.Background(), string(spaceID), string(id), path); err != nil { 697 t.log.Error().Err(err).Str("spaceID", string(spaceID)).Str("id", string(id)).Str("path", path).Msg("could not cache id") 698 } 699 } 700 } else if assimilate { 701 if err := t.assimilate(scanItem{Path: path, ForceRescan: true}); err != nil { 702 t.log.Error().Err(err).Str("path", path).Msg("could not assimilate item") 703 } 704 } 705 return t.setDirty(path, false) 706 }) 707 708 for dir, size := range sizes { 709 if dir == root { 710 // Propagate the size diff further up the tree 711 if err := t.propagateSizeDiff(dir, size); err != nil { 712 t.log.Error().Err(err).Str("path", dir).Msg("could not propagate size diff") 713 } 714 } 715 if err := t.lookup.MetadataBackend().Set(context.Background(), dir, prefixes.TreesizeAttr, []byte(fmt.Sprintf("%d", size))); err != nil { 716 t.log.Error().Err(err).Str("path", dir).Int64("size", size).Msg("could not set tree size") 717 } 718 } 719 720 if err != nil { 721 return err 722 } 723 724 return nil 725 } 726 727 func (t *Tree) propagateSizeDiff(dir string, size int64) error { 728 // First find the space id 729 spaceID, _, err := t.findSpaceId(dir) 730 if err != nil { 731 return err 732 } 733 attrs, err := t.lookup.MetadataBackend().All(context.Background(), dir) 734 if err != nil { 735 return err 736 } 737 n, err := t.lookup.NodeFromID(context.Background(), &provider.ResourceId{ 738 StorageId: t.options.MountID, 739 SpaceId: spaceID, 740 OpaqueId: string(attrs[prefixes.IDAttr]), 741 }) 742 if err != nil { 743 return err 744 } 745 oldSize, err := node.Attributes(attrs).Int64(prefixes.TreesizeAttr) 746 if err != nil { 747 return err 748 } 749 return t.Propagate(context.Background(), n, size-oldSize) 750 } 751 752 func (t *Tree) setDirty(path string, dirty bool) error { 753 return t.lookup.MetadataBackend().Set(context.Background(), path, dirtyFlag, []byte(fmt.Sprintf("%t", dirty))) 754 } 755 756 func (t *Tree) isDirty(path string) (bool, error) { 757 dirtyAttr, err := t.lookup.MetadataBackend().Get(context.Background(), path, dirtyFlag) 758 if err != nil { 759 return false, err 760 } 761 return string(dirtyAttr) == "true", nil 762 }