github.com/cs3org/reva/v2@v2.27.7/pkg/storage/utils/decomposedfs/tree/tree.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 "bytes" 23 "context" 24 "fmt" 25 "io" 26 "io/fs" 27 "os" 28 "path/filepath" 29 "regexp" 30 "strings" 31 "time" 32 33 provider "github.com/cs3org/go-cs3apis/cs3/storage/provider/v1beta1" 34 "github.com/cs3org/reva/v2/pkg/appctx" 35 "github.com/cs3org/reva/v2/pkg/errtypes" 36 "github.com/cs3org/reva/v2/pkg/storage/utils/decomposedfs/lookup" 37 "github.com/cs3org/reva/v2/pkg/storage/utils/decomposedfs/metadata" 38 "github.com/cs3org/reva/v2/pkg/storage/utils/decomposedfs/metadata/prefixes" 39 "github.com/cs3org/reva/v2/pkg/storage/utils/decomposedfs/node" 40 "github.com/cs3org/reva/v2/pkg/storage/utils/decomposedfs/options" 41 "github.com/cs3org/reva/v2/pkg/storage/utils/decomposedfs/tree/propagator" 42 "github.com/cs3org/reva/v2/pkg/utils" 43 "github.com/google/uuid" 44 "github.com/pkg/errors" 45 "github.com/rs/zerolog" 46 "go-micro.dev/v4/store" 47 "go.opentelemetry.io/otel" 48 "go.opentelemetry.io/otel/trace" 49 "golang.org/x/sync/errgroup" 50 ) 51 52 var tracer trace.Tracer 53 54 func init() { 55 tracer = otel.Tracer("github.com/cs3org/reva/pkg/storage/utils/decomposedfs/tree") 56 } 57 58 // Blobstore defines an interface for storing blobs in a blobstore 59 type Blobstore interface { 60 Upload(node *node.Node, source string) error 61 Download(node *node.Node) (io.ReadCloser, error) 62 Delete(node *node.Node) error 63 } 64 65 // Tree manages a hierarchical tree 66 type Tree struct { 67 lookup node.PathLookup 68 blobstore Blobstore 69 propagator propagator.Propagator 70 71 options *options.Options 72 73 idCache store.Store 74 } 75 76 // PermissionCheckFunc defined a function used to check resource permissions 77 type PermissionCheckFunc func(rp *provider.ResourcePermissions) bool 78 79 // New returns a new instance of Tree 80 func New(lu node.PathLookup, bs Blobstore, o *options.Options, cache store.Store, log *zerolog.Logger) *Tree { 81 return &Tree{ 82 lookup: lu, 83 blobstore: bs, 84 options: o, 85 idCache: cache, 86 propagator: propagator.New(lu, o, log), 87 } 88 } 89 90 // Setup prepares the tree structure 91 func (t *Tree) Setup() error { 92 // create data paths for internal layout 93 dataPaths := []string{ 94 filepath.Join(t.options.Root, "spaces"), 95 // notes contain symlinks from nodes/<u-u-i-d>/uploads/<uploadid> to ../../uploads/<uploadid> 96 // better to keep uploads on a fast / volatile storage before a workflow finally moves them to the nodes dir 97 filepath.Join(t.options.Root, "uploads"), 98 } 99 for _, v := range dataPaths { 100 err := os.MkdirAll(v, 0700) 101 if err != nil { 102 return err 103 } 104 } 105 return nil 106 } 107 108 // GetMD returns the metadata of a node in the tree 109 func (t *Tree) GetMD(ctx context.Context, n *node.Node) (os.FileInfo, error) { 110 _, span := tracer.Start(ctx, "GetMD") 111 defer span.End() 112 md, err := os.Stat(n.InternalPath()) 113 if err != nil { 114 if errors.Is(err, fs.ErrNotExist) { 115 return nil, errtypes.NotFound(n.ID) 116 } 117 return nil, errors.Wrap(err, "tree: error stating "+n.ID) 118 } 119 120 return md, nil 121 } 122 123 // TouchFile creates a new empty file 124 func (t *Tree) TouchFile(ctx context.Context, n *node.Node, markprocessing bool, mtime string) error { 125 _, span := tracer.Start(ctx, "TouchFile") 126 defer span.End() 127 if n.Exists { 128 if markprocessing { 129 return n.SetXattr(ctx, prefixes.StatusPrefix, []byte(node.ProcessingStatus)) 130 } 131 132 return errtypes.AlreadyExists(n.ID) 133 } 134 135 if n.ID == "" { 136 n.ID = uuid.New().String() 137 } 138 n.SetType(provider.ResourceType_RESOURCE_TYPE_FILE) 139 140 nodePath := n.InternalPath() 141 if err := os.MkdirAll(filepath.Dir(nodePath), 0700); err != nil { 142 return errors.Wrap(err, "Decomposedfs: error creating node") 143 } 144 _, err := os.Create(nodePath) 145 if err != nil { 146 return errors.Wrap(err, "Decomposedfs: error creating node") 147 } 148 149 attributes := n.NodeMetadata(ctx) 150 if markprocessing { 151 attributes[prefixes.StatusPrefix] = []byte(node.ProcessingStatus) 152 } 153 if mtime != "" { 154 if err := n.SetMtimeString(ctx, mtime); err != nil { 155 return errors.Wrap(err, "Decomposedfs: could not set mtime") 156 } 157 } else { 158 now := time.Now() 159 if err := n.SetMtime(ctx, &now); err != nil { 160 return errors.Wrap(err, "Decomposedfs: could not set mtime") 161 } 162 } 163 err = n.SetXattrsWithContext(ctx, attributes, true) 164 if err != nil { 165 return err 166 } 167 168 // link child name to parent if it is new 169 childNameLink := filepath.Join(n.ParentPath(), n.Name) 170 var link string 171 link, err = os.Readlink(childNameLink) 172 if err == nil && link != "../"+n.ID { 173 if err = os.Remove(childNameLink); err != nil { 174 return errors.Wrap(err, "Decomposedfs: could not remove symlink child entry") 175 } 176 } 177 if errors.Is(err, fs.ErrNotExist) || link != "../"+n.ID { 178 relativeNodePath := filepath.Join("../../../../../", lookup.Pathify(n.ID, 4, 2)) 179 if err = os.Symlink(relativeNodePath, childNameLink); err != nil { 180 return errors.Wrap(err, "Decomposedfs: could not symlink child entry") 181 } 182 } 183 184 return t.Propagate(ctx, n, 0) 185 } 186 187 // CreateDir creates a new directory entry in the tree 188 func (t *Tree) CreateDir(ctx context.Context, n *node.Node) (err error) { 189 ctx, span := tracer.Start(ctx, "CreateDir") 190 defer span.End() 191 if n.Exists { 192 return errtypes.AlreadyExists(n.ID) // path? 193 } 194 195 // create a directory node 196 n.SetType(provider.ResourceType_RESOURCE_TYPE_CONTAINER) 197 if n.ID == "" { 198 n.ID = uuid.New().String() 199 } 200 201 err = t.createDirNode(ctx, n) 202 if err != nil { 203 return 204 } 205 206 // make child appear in listings 207 relativeNodePath := filepath.Join("../../../../../", lookup.Pathify(n.ID, 4, 2)) 208 ctx, subspan := tracer.Start(ctx, "os.Symlink") 209 err = os.Symlink(relativeNodePath, filepath.Join(n.ParentPath(), n.Name)) 210 subspan.End() 211 if err != nil { 212 // no better way to check unfortunately 213 if !strings.Contains(err.Error(), "file exists") { 214 return 215 } 216 217 // try to remove the node 218 ctx, subspan = tracer.Start(ctx, "os.RemoveAll") 219 e := os.RemoveAll(n.InternalPath()) 220 subspan.End() 221 if e != nil { 222 appctx.GetLogger(ctx).Debug().Err(e).Msg("cannot delete node") 223 } 224 return errtypes.AlreadyExists(err.Error()) 225 } 226 return t.Propagate(ctx, n, 0) 227 } 228 229 // Move replaces the target with the source 230 func (t *Tree) Move(ctx context.Context, oldNode *node.Node, newNode *node.Node) (err error) { 231 _, span := tracer.Start(ctx, "Move") 232 defer span.End() 233 if oldNode.SpaceID != newNode.SpaceID { 234 // WebDAV RFC https://www.rfc-editor.org/rfc/rfc4918#section-9.9.4 says to use 235 // > 502 (Bad Gateway) - This may occur when the destination is on another 236 // > server and the destination server refuses to accept the resource. 237 // > This could also occur when the destination is on another sub-section 238 // > of the same server namespace. 239 // but we only have a not supported error 240 return errtypes.NotSupported("cannot move across spaces") 241 } 242 // if target exists delete it without trashing it 243 if newNode.Exists { 244 // TODO make sure all children are deleted 245 if err := os.RemoveAll(newNode.InternalPath()); err != nil { 246 return errors.Wrap(err, "Decomposedfs: Move: error deleting target node "+newNode.ID) 247 } 248 } 249 250 // remove cache entry in any case to avoid inconsistencies 251 defer func() { _ = t.idCache.Delete(filepath.Join(oldNode.ParentPath(), oldNode.Name)) }() 252 253 // Always target the old node ID for xattr updates. 254 // The new node id is empty if the target does not exist 255 // and we need to overwrite the new one when overwriting an existing path. 256 // are we just renaming (parent stays the same)? 257 if oldNode.ParentID == newNode.ParentID { 258 259 // parentPath := t.lookup.InternalPath(oldNode.SpaceID, oldNode.ParentID) 260 parentPath := oldNode.ParentPath() 261 262 // rename child 263 err = os.Rename( 264 filepath.Join(parentPath, oldNode.Name), 265 filepath.Join(parentPath, newNode.Name), 266 ) 267 if err != nil { 268 return errors.Wrap(err, "Decomposedfs: could not rename child") 269 } 270 271 // update name attribute 272 if err := oldNode.SetXattrString(ctx, prefixes.NameAttr, newNode.Name); err != nil { 273 return errors.Wrap(err, "Decomposedfs: could not set name attribute") 274 } 275 276 return t.Propagate(ctx, newNode, 0) 277 } 278 279 // we are moving the node to a new parent, any target has been removed 280 // bring old node to the new parent 281 282 // rename child 283 err = os.Rename( 284 filepath.Join(oldNode.ParentPath(), oldNode.Name), 285 filepath.Join(newNode.ParentPath(), newNode.Name), 286 ) 287 if err != nil { 288 return errors.Wrap(err, "Decomposedfs: could not move child") 289 } 290 291 // update target parentid and name 292 attribs := node.Attributes{} 293 attribs.SetString(prefixes.ParentidAttr, newNode.ParentID) 294 attribs.SetString(prefixes.NameAttr, newNode.Name) 295 if err := oldNode.SetXattrsWithContext(ctx, attribs, true); err != nil { 296 return errors.Wrap(err, "Decomposedfs: could not update old node attributes") 297 } 298 299 // the size diff is the current treesize or blobsize of the old/source node 300 var sizeDiff int64 301 if oldNode.IsDir(ctx) { 302 treeSize, err := oldNode.GetTreeSize(ctx) 303 if err != nil { 304 return err 305 } 306 sizeDiff = int64(treeSize) 307 } else { 308 sizeDiff = oldNode.Blobsize 309 } 310 311 // TODO inefficient because we might update several nodes twice, only propagate unchanged nodes? 312 // collect in a list, then only stat each node once 313 // also do this in a go routine ... webdav should check the etag async 314 315 err = t.Propagate(ctx, oldNode, -sizeDiff) 316 if err != nil { 317 return errors.Wrap(err, "Decomposedfs: Move: could not propagate old node") 318 } 319 err = t.Propagate(ctx, newNode, sizeDiff) 320 if err != nil { 321 return errors.Wrap(err, "Decomposedfs: Move: could not propagate new node") 322 } 323 return nil 324 } 325 326 func readChildNodeFromLink(ctx context.Context, path string) (string, error) { 327 _, span := tracer.Start(ctx, "readChildNodeFromLink") 328 defer span.End() 329 link, err := os.Readlink(path) 330 if err != nil { 331 return "", err 332 } 333 nodeID := strings.TrimLeft(link, "/.") 334 nodeID = strings.ReplaceAll(nodeID, "/", "") 335 return nodeID, nil 336 } 337 338 // ListFolder lists the content of a folder node 339 func (t *Tree) ListFolder(ctx context.Context, n *node.Node) ([]*node.Node, error) { 340 ctx, span := tracer.Start(ctx, "ListFolder") 341 defer span.End() 342 dir := n.InternalPath() 343 344 _, subspan := tracer.Start(ctx, "os.Open") 345 f, err := os.Open(dir) 346 subspan.End() 347 if err != nil { 348 if errors.Is(err, fs.ErrNotExist) { 349 return nil, errtypes.NotFound(dir) 350 } 351 return nil, errors.Wrap(err, "tree: error listing "+dir) 352 } 353 defer f.Close() 354 355 _, subspan = tracer.Start(ctx, "f.Readdirnames") 356 names, err := f.Readdirnames(0) 357 subspan.End() 358 if err != nil { 359 return nil, err 360 } 361 362 numWorkers := t.options.MaxConcurrency 363 if len(names) < numWorkers { 364 numWorkers = len(names) 365 } 366 work := make(chan string) 367 results := make(chan *node.Node) 368 369 g, ctx := errgroup.WithContext(ctx) 370 371 // Distribute work 372 g.Go(func() error { 373 defer close(work) 374 for _, name := range names { 375 select { 376 case work <- name: 377 case <-ctx.Done(): 378 return ctx.Err() 379 } 380 } 381 return nil 382 }) 383 384 // Spawn workers that'll concurrently work the queue 385 for i := 0; i < numWorkers; i++ { 386 g.Go(func() error { 387 var err error 388 for name := range work { 389 path := filepath.Join(dir, name) 390 nodeID := getNodeIDFromCache(ctx, path, t.idCache) 391 if nodeID == "" { 392 nodeID, err = readChildNodeFromLink(ctx, path) 393 if err != nil { 394 return err 395 } 396 err = storeNodeIDInCache(ctx, path, nodeID, t.idCache) 397 if err != nil { 398 return err 399 } 400 } 401 402 child, err := node.ReadNode(ctx, t.lookup, n.SpaceID, nodeID, false, n.SpaceRoot, true) 403 if err != nil { 404 return err 405 } 406 407 // prevent listing denied resources 408 if !child.IsDenied(ctx) { 409 if child.SpaceRoot == nil { 410 child.SpaceRoot = n.SpaceRoot 411 } 412 select { 413 case results <- child: 414 case <-ctx.Done(): 415 return ctx.Err() 416 } 417 } 418 } 419 return nil 420 }) 421 } 422 // Wait for things to settle down, then close results chan 423 go func() { 424 _ = g.Wait() // error is checked later 425 close(results) 426 }() 427 428 retNodes := []*node.Node{} 429 for n := range results { 430 retNodes = append(retNodes, n) 431 } 432 433 if err := g.Wait(); err != nil { 434 return nil, err 435 } 436 437 return retNodes, nil 438 } 439 440 // Delete deletes a node in the tree by moving it to the trash 441 func (t *Tree) Delete(ctx context.Context, n *node.Node) (err error) { 442 _, span := tracer.Start(ctx, "Delete") 443 defer span.End() 444 path := filepath.Join(n.ParentPath(), n.Name) 445 // remove entry from cache immediately to avoid inconsistencies 446 defer func() { _ = t.idCache.Delete(path) }() 447 448 if appctx.DeletingSharedResourceFromContext(ctx) { 449 src := filepath.Join(n.ParentPath(), n.Name) 450 return os.Remove(src) 451 } 452 453 // get the original path 454 origin, err := t.lookup.Path(ctx, n, node.NoCheck) 455 if err != nil { 456 return 457 } 458 459 // set origin location in metadata 460 nodePath := n.InternalPath() 461 if err := n.SetXattrString(ctx, prefixes.TrashOriginAttr, origin); err != nil { 462 return err 463 } 464 465 var sizeDiff int64 466 if n.IsDir(ctx) { 467 treesize, err := n.GetTreeSize(ctx) 468 if err != nil { 469 return err // TODO calculate treesize if it is not set 470 } 471 sizeDiff = -int64(treesize) 472 } else { 473 sizeDiff = -n.Blobsize 474 } 475 476 deletionTime := time.Now().UTC().Format(time.RFC3339Nano) 477 478 // Prepare the trash 479 trashLink := filepath.Join(t.options.Root, "spaces", lookup.Pathify(n.SpaceRoot.ID, 1, 2), "trash", lookup.Pathify(n.ID, 4, 2)) 480 if err := os.MkdirAll(filepath.Dir(trashLink), 0700); err != nil { 481 // Roll back changes 482 _ = n.RemoveXattr(ctx, prefixes.TrashOriginAttr, true) 483 return err 484 } 485 486 // FIXME can we just move the node into the trash dir? instead of adding another symlink and appending a trash timestamp? 487 // can we just use the mtime as the trash time? 488 // TODO store a trashed by userid 489 490 // first make node appear in the space trash 491 // parent id and name are stored as extended attributes in the node itself 492 err = os.Symlink("../../../../../nodes/"+lookup.Pathify(n.ID, 4, 2)+node.TrashIDDelimiter+deletionTime, trashLink) 493 if err != nil { 494 // Roll back changes 495 _ = n.RemoveXattr(ctx, prefixes.TrashOriginAttr, true) 496 return 497 } 498 499 // at this point we have a symlink pointing to a non existing destination, which is fine 500 501 // rename the trashed node so it is not picked up when traversing up the tree and matches the symlink 502 trashPath := nodePath + node.TrashIDDelimiter + deletionTime 503 err = os.Rename(nodePath, trashPath) 504 if err != nil { 505 // To roll back changes 506 // TODO remove symlink 507 // Roll back changes 508 _ = n.RemoveXattr(ctx, prefixes.TrashOriginAttr, true) 509 return 510 } 511 err = t.lookup.MetadataBackend().Rename(nodePath, trashPath) 512 if err != nil { 513 _ = n.RemoveXattr(ctx, prefixes.TrashOriginAttr, true) 514 _ = os.Rename(trashPath, nodePath) 515 return 516 } 517 518 // Remove lock file if it exists 519 _ = os.Remove(n.LockFilePath()) 520 521 // finally remove the entry from the parent dir 522 if err = os.Remove(path); err != nil { 523 // To roll back changes 524 // TODO revert the rename 525 // TODO remove symlink 526 // Roll back changes 527 _ = n.RemoveXattr(ctx, prefixes.TrashOriginAttr, true) 528 return 529 } 530 531 return t.Propagate(ctx, n, sizeDiff) 532 } 533 534 // RestoreRecycleItemFunc returns a node and a function to restore it from the trash. 535 func (t *Tree) RestoreRecycleItemFunc(ctx context.Context, spaceid, key, trashPath string, targetNode *node.Node) (*node.Node, *node.Node, func() error, error) { 536 _, span := tracer.Start(ctx, "RestoreRecycleItemFunc") 537 defer span.End() 538 logger := appctx.GetLogger(ctx) 539 540 recycleNode, trashItem, deletedNodePath, origin, err := t.readRecycleItem(ctx, spaceid, key, trashPath) 541 if err != nil { 542 return nil, nil, nil, err 543 } 544 545 targetRef := &provider.Reference{ 546 ResourceId: &provider.ResourceId{SpaceId: spaceid, OpaqueId: spaceid}, 547 Path: utils.MakeRelativePath(origin), 548 } 549 550 if targetNode == nil { 551 targetNode, err = t.lookup.NodeFromResource(ctx, targetRef) 552 if err != nil { 553 return nil, nil, nil, err 554 } 555 } 556 557 if err := targetNode.CheckLock(ctx); err != nil { 558 return nil, nil, nil, err 559 } 560 561 parent, err := targetNode.Parent(ctx) 562 if err != nil { 563 return nil, nil, nil, err 564 } 565 566 fn := func() error { 567 if targetNode.Exists { 568 return errtypes.AlreadyExists("origin already exists") 569 } 570 571 // add the entry for the parent dir 572 err = os.Symlink("../../../../../"+lookup.Pathify(recycleNode.ID, 4, 2), filepath.Join(targetNode.ParentPath(), targetNode.Name)) 573 if err != nil { 574 return err 575 } 576 577 // rename to node only name, so it is picked up by id 578 nodePath := recycleNode.InternalPath() 579 580 // attempt to rename only if we're not in a subfolder 581 if deletedNodePath != nodePath { 582 err = os.Rename(deletedNodePath, nodePath) 583 if err != nil { 584 return err 585 } 586 err = t.lookup.MetadataBackend().Rename(deletedNodePath, nodePath) 587 if err != nil { 588 return err 589 } 590 } 591 592 targetNode.Exists = true 593 594 attrs := node.Attributes{} 595 attrs.SetString(prefixes.NameAttr, targetNode.Name) 596 // set ParentidAttr to restorePath's node parent id 597 attrs.SetString(prefixes.ParentidAttr, targetNode.ParentID) 598 599 if err = recycleNode.SetXattrsWithContext(ctx, attrs, true); err != nil { 600 return errors.Wrap(err, "Decomposedfs: could not update recycle node") 601 } 602 603 // delete item link in trash 604 deletePath := trashItem 605 if trashPath != "" && trashPath != "/" { 606 resolvedTrashRoot, err := filepath.EvalSymlinks(trashItem) 607 if err != nil { 608 return errors.Wrap(err, "Decomposedfs: could not resolve trash root") 609 } 610 deletePath = filepath.Join(resolvedTrashRoot, trashPath) 611 if err = os.Remove(deletePath); err != nil { 612 logger.Error().Err(err).Str("trashItem", trashItem).Str("deletePath", deletePath).Str("trashPath", trashPath).Msg("error deleting trash item") 613 } 614 } else { 615 if err = utils.RemoveItem(deletePath); err != nil { 616 logger.Error().Err(err).Str("trashItem", trashItem).Str("deletePath", deletePath).Str("trashPath", trashPath).Msg("error recursively deleting trash item") 617 } 618 } 619 620 var sizeDiff int64 621 if recycleNode.IsDir(ctx) { 622 treeSize, err := recycleNode.GetTreeSize(ctx) 623 if err != nil { 624 return err 625 } 626 sizeDiff = int64(treeSize) 627 } else { 628 sizeDiff = recycleNode.Blobsize 629 } 630 return t.Propagate(ctx, targetNode, sizeDiff) 631 } 632 return recycleNode, parent, fn, nil 633 } 634 635 // PurgeRecycleItemFunc returns a node and a function to purge it from the trash 636 func (t *Tree) PurgeRecycleItemFunc(ctx context.Context, spaceid, key string, path string) (*node.Node, func() error, error) { 637 _, span := tracer.Start(ctx, "PurgeRecycleItemFunc") 638 defer span.End() 639 logger := appctx.GetLogger(ctx) 640 641 rn, trashItem, deletedNodePath, _, err := t.readRecycleItem(ctx, spaceid, key, path) 642 if err != nil { 643 return nil, nil, err 644 } 645 646 ts := "" 647 timeSuffix := strings.SplitN(filepath.Base(deletedNodePath), node.TrashIDDelimiter, 2) 648 if len(timeSuffix) == 2 { 649 ts = timeSuffix[1] 650 } 651 652 fn := func() error { 653 654 if err := t.removeNode(ctx, deletedNodePath, ts, rn); err != nil { 655 return err 656 } 657 658 // delete item link in trash 659 deletePath := trashItem 660 if path != "" && path != "/" { 661 resolvedTrashRoot, err := filepath.EvalSymlinks(trashItem) 662 if err != nil { 663 return errors.Wrap(err, "Decomposedfs: could not resolve trash root") 664 } 665 deletePath = filepath.Join(resolvedTrashRoot, path) 666 } 667 if err = utils.RemoveItem(deletePath); err != nil { 668 logger.Error().Err(err).Str("deletePath", deletePath).Msg("error deleting trash item") 669 return err 670 } 671 672 return nil 673 } 674 675 return rn, fn, nil 676 } 677 678 // InitNewNode initializes a new node 679 func (t *Tree) InitNewNode(ctx context.Context, n *node.Node, fsize uint64) (metadata.UnlockFunc, error) { 680 _, span := tracer.Start(ctx, "InitNewNode") 681 defer span.End() 682 // create folder structure (if needed) 683 684 _, subspan := tracer.Start(ctx, "os.MkdirAll") 685 err := os.MkdirAll(filepath.Dir(n.InternalPath()), 0700) 686 subspan.End() 687 if err != nil { 688 return nil, err 689 } 690 691 // create and write lock new node metadata 692 _, subspan = tracer.Start(ctx, "metadata.Lock") 693 unlock, err := t.lookup.MetadataBackend().Lock(n.InternalPath()) 694 subspan.End() 695 if err != nil { 696 return nil, err 697 } 698 699 // we also need to touch the actual node file here it stores the mtime of the resource 700 _, subspan = tracer.Start(ctx, "os.OpenFile") 701 h, err := os.OpenFile(n.InternalPath(), os.O_CREATE|os.O_EXCL, 0600) 702 subspan.End() 703 if err != nil { 704 return unlock, err 705 } 706 h.Close() 707 708 _, subspan = tracer.Start(ctx, "node.CheckQuota") 709 _, err = node.CheckQuota(ctx, n.SpaceRoot, false, 0, fsize) 710 subspan.End() 711 if err != nil { 712 return unlock, err 713 } 714 715 // link child name to parent if it is new 716 childNameLink := filepath.Join(n.ParentPath(), n.Name) 717 relativeNodePath := filepath.Join("../../../../../", lookup.Pathify(n.ID, 4, 2)) 718 log := appctx.GetLogger(ctx).With().Str("childNameLink", childNameLink).Str("relativeNodePath", relativeNodePath).Logger() 719 log.Info().Msg("initNewNode: creating symlink") 720 721 _, subspan = tracer.Start(ctx, "os.Symlink") 722 err = os.Symlink(relativeNodePath, childNameLink) 723 subspan.End() 724 if err != nil { 725 log.Info().Err(err).Msg("initNewNode: symlink failed") 726 if errors.Is(err, fs.ErrExist) { 727 log.Info().Err(err).Msg("initNewNode: symlink already exists") 728 return unlock, errtypes.AlreadyExists(n.Name) 729 } 730 return unlock, errors.Wrap(err, "Decomposedfs: could not symlink child entry") 731 } 732 log.Info().Msg("initNewNode: symlink created") 733 734 return unlock, nil 735 } 736 737 func (t *Tree) removeNode(ctx context.Context, path, timeSuffix string, n *node.Node) error { 738 logger := appctx.GetLogger(ctx) 739 740 if timeSuffix != "" { 741 n.ID = n.ID + node.TrashIDDelimiter + timeSuffix 742 } 743 744 if n.IsDir(ctx) { 745 item, err := t.ListFolder(ctx, n) 746 if err != nil { 747 logger.Error().Err(err).Str("path", path).Msg("error listing folder") 748 } else { 749 for _, child := range item { 750 if err := t.removeNode(ctx, child.InternalPath(), "", child); err != nil { 751 return err 752 } 753 } 754 } 755 } 756 757 // delete the actual node 758 if err := utils.RemoveItem(path); err != nil { 759 logger.Error().Err(err).Str("path", path).Msg("error purging node") 760 return err 761 } 762 763 if err := t.lookup.MetadataBackend().Purge(ctx, path); err != nil { 764 logger.Error().Err(err).Str("path", t.lookup.MetadataBackend().MetadataPath(path)).Msg("error purging node metadata") 765 return err 766 } 767 768 // delete blob from blobstore 769 if n.BlobID != "" { 770 if err := t.DeleteBlob(n); err != nil { 771 logger.Error().Err(err).Str("blobID", n.BlobID).Msg("error purging nodes blob") 772 return err 773 } 774 } 775 776 // delete revisions 777 revs, err := filepath.Glob(n.InternalPath() + node.RevisionIDDelimiter + "*") 778 if err != nil { 779 logger.Error().Err(err).Str("path", n.InternalPath()+node.RevisionIDDelimiter+"*").Msg("glob failed badly") 780 return err 781 } 782 for _, rev := range revs { 783 if t.lookup.MetadataBackend().IsMetaFile(rev) { 784 continue 785 } 786 787 bID, _, err := t.lookup.ReadBlobIDAndSizeAttr(ctx, rev, nil) 788 if err != nil { 789 logger.Error().Err(err).Str("revision", rev).Msg("error reading blobid attribute") 790 return err 791 } 792 793 if err := utils.RemoveItem(rev); err != nil { 794 logger.Error().Err(err).Str("revision", rev).Msg("error removing revision node") 795 return err 796 } 797 798 if bID != "" { 799 if err := t.DeleteBlob(&node.Node{SpaceID: n.SpaceID, BlobID: bID}); err != nil { 800 logger.Error().Err(err).Str("revision", rev).Str("blobID", bID).Msg("error removing revision node blob") 801 return err 802 } 803 } 804 805 } 806 807 return nil 808 } 809 810 // Propagate propagates changes to the root of the tree 811 func (t *Tree) Propagate(ctx context.Context, n *node.Node, sizeDiff int64) (err error) { 812 return t.propagator.Propagate(ctx, n, sizeDiff) 813 } 814 815 // WriteBlob writes a blob to the blobstore 816 func (t *Tree) WriteBlob(node *node.Node, source string) error { 817 return t.blobstore.Upload(node, source) 818 } 819 820 // ReadBlob reads a blob from the blobstore 821 func (t *Tree) ReadBlob(node *node.Node) (io.ReadCloser, error) { 822 if node.BlobID == "" { 823 // there is no blob yet - we are dealing with a 0 byte file 824 return io.NopCloser(bytes.NewReader([]byte{})), nil 825 } 826 return t.blobstore.Download(node) 827 } 828 829 // DeleteBlob deletes a blob from the blobstore 830 func (t *Tree) DeleteBlob(node *node.Node) error { 831 if node == nil { 832 return fmt.Errorf("could not delete blob, nil node was given") 833 } 834 if node.BlobID == "" { 835 return fmt.Errorf("could not delete blob, node with empty blob id was given") 836 } 837 838 return t.blobstore.Delete(node) 839 } 840 841 // BuildSpaceIDIndexEntry returns the entry for the space id index 842 func (t *Tree) BuildSpaceIDIndexEntry(spaceID, nodeID string) string { 843 return "../../../spaces/" + lookup.Pathify(spaceID, 1, 2) + "/nodes/" + lookup.Pathify(spaceID, 4, 2) 844 } 845 846 // ResolveSpaceIDIndexEntry returns the node id for the space id index entry 847 func (t *Tree) ResolveSpaceIDIndexEntry(_, entry string) (string, string, error) { 848 return ReadSpaceAndNodeFromIndexLink(entry) 849 } 850 851 // ReadSpaceAndNodeFromIndexLink reads a symlink and parses space and node id if the link has the correct format, eg: 852 // ../../spaces/4c/510ada-c86b-4815-8820-42cdf82c3d51/nodes/4c/51/0a/da/-c86b-4815-8820-42cdf82c3d51 853 // ../../spaces/4c/510ada-c86b-4815-8820-42cdf82c3d51/nodes/4c/51/0a/da/-c86b-4815-8820-42cdf82c3d51.T.2022-02-24T12:35:18.196484592Z 854 func ReadSpaceAndNodeFromIndexLink(link string) (string, string, error) { 855 // ../../../spaces/sp/ace-id/nodes/sh/or/tn/od/eid 856 // 0 1 2 3 4 5 6 7 8 9 10 11 857 parts := strings.Split(link, string(filepath.Separator)) 858 if len(parts) != 12 || parts[0] != ".." || parts[1] != ".." || parts[2] != ".." || parts[3] != "spaces" || parts[6] != "nodes" { 859 return "", "", errtypes.InternalError("malformed link") 860 } 861 return strings.Join(parts[4:6], ""), strings.Join(parts[7:12], ""), nil 862 } 863 864 // TODO check if node exists? 865 func (t *Tree) createDirNode(ctx context.Context, n *node.Node) (err error) { 866 ctx, span := tracer.Start(ctx, "createDirNode") 867 defer span.End() 868 // create a directory node 869 nodePath := n.InternalPath() 870 if err := os.MkdirAll(nodePath, 0700); err != nil { 871 return errors.Wrap(err, "Decomposedfs: error creating node") 872 } 873 874 attributes := n.NodeMetadata(ctx) 875 attributes[prefixes.TreesizeAttr] = []byte("0") // initialize as empty, TODO why bother? if it is not set we could treat it as 0? 876 if t.options.TreeTimeAccounting || t.options.TreeSizeAccounting { 877 attributes[prefixes.PropagationAttr] = []byte("1") // mark the node for propagation 878 } 879 return n.SetXattrsWithContext(ctx, attributes, true) 880 } 881 882 var nodeIDRegep = regexp.MustCompile(`.*/nodes/([^.]*).*`) 883 884 // TODO refactor the returned params into Node properties? would make all the path transformations go away... 885 func (t *Tree) readRecycleItem(ctx context.Context, spaceID, key, path string) (recycleNode *node.Node, trashItem string, deletedNodePath string, origin string, err error) { 886 _, span := tracer.Start(ctx, "readRecycleItem") 887 defer span.End() 888 logger := appctx.GetLogger(ctx) 889 890 if key == "" { 891 return nil, "", "", "", errtypes.InternalError("key is empty") 892 } 893 894 backend := t.lookup.MetadataBackend() 895 var nodeID string 896 897 trashItem = filepath.Join(t.lookup.InternalRoot(), "spaces", lookup.Pathify(spaceID, 1, 2), "trash", lookup.Pathify(key, 4, 2)) 898 resolvedTrashItem, err := filepath.EvalSymlinks(trashItem) 899 if err != nil { 900 return 901 } 902 deletedNodePath, err = filepath.EvalSymlinks(filepath.Join(resolvedTrashItem, path)) 903 if err != nil { 904 return 905 } 906 nodeID = nodeIDRegep.ReplaceAllString(deletedNodePath, "$1") 907 nodeID = strings.ReplaceAll(nodeID, "/", "") 908 909 recycleNode = node.New(spaceID, nodeID, "", "", 0, "", provider.ResourceType_RESOURCE_TYPE_INVALID, nil, t.lookup) 910 recycleNode.SpaceRoot, err = node.ReadNode(ctx, t.lookup, spaceID, spaceID, false, nil, false) 911 if err != nil { 912 return 913 } 914 recycleNode.SetType(t.lookup.TypeFromPath(ctx, deletedNodePath)) 915 916 var attrBytes []byte 917 if recycleNode.Type(ctx) == provider.ResourceType_RESOURCE_TYPE_FILE { 918 // lookup blobID in extended attributes 919 if attrBytes, err = backend.Get(ctx, deletedNodePath, prefixes.BlobIDAttr); err == nil { 920 recycleNode.BlobID = string(attrBytes) 921 } else { 922 return 923 } 924 925 // lookup blobSize in extended attributes 926 if recycleNode.Blobsize, err = backend.GetInt64(ctx, deletedNodePath, prefixes.BlobsizeAttr); err != nil { 927 return 928 } 929 } 930 931 // lookup parent id in extended attributes 932 if attrBytes, err = backend.Get(ctx, deletedNodePath, prefixes.ParentidAttr); err == nil { 933 recycleNode.ParentID = string(attrBytes) 934 } else { 935 return 936 } 937 938 // lookup name in extended attributes 939 if attrBytes, err = backend.Get(ctx, deletedNodePath, prefixes.NameAttr); err == nil { 940 recycleNode.Name = string(attrBytes) 941 } else { 942 return 943 } 944 945 // get origin node, is relative to space root 946 origin = "/" 947 948 // lookup origin path in extended attributes 949 if attrBytes, err = backend.Get(ctx, resolvedTrashItem, prefixes.TrashOriginAttr); err == nil { 950 origin = filepath.Join(string(attrBytes), path) 951 } else { 952 logger.Error().Err(err).Str("trashItem", trashItem).Str("deletedNodePath", deletedNodePath).Msg("could not read origin path, restoring to /") 953 } 954 955 return 956 } 957 958 func getNodeIDFromCache(ctx context.Context, path string, cache store.Store) string { 959 _, span := tracer.Start(ctx, "getNodeIDFromCache") 960 defer span.End() 961 recs, err := cache.Read(path) 962 if err == nil && len(recs) > 0 { 963 return string(recs[0].Value) 964 } 965 return "" 966 } 967 968 func storeNodeIDInCache(ctx context.Context, path string, nodeID string, cache store.Store) error { 969 _, span := tracer.Start(ctx, "storeNodeIDInCache") 970 defer span.End() 971 return cache.Write(&store.Record{ 972 Key: path, 973 Value: []byte(nodeID), 974 }) 975 }