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  }