github.com/cs3org/reva/v2@v2.27.7/pkg/storage/utils/decomposedfs/lookup/lookup.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 lookup
    20  
    21  import (
    22  	"context"
    23  	"fmt"
    24  	"os"
    25  	"path/filepath"
    26  	"strings"
    27  
    28  	user "github.com/cs3org/go-cs3apis/cs3/identity/user/v1beta1"
    29  	provider "github.com/cs3org/go-cs3apis/cs3/storage/provider/v1beta1"
    30  	"github.com/cs3org/reva/v2/pkg/appctx"
    31  	"github.com/cs3org/reva/v2/pkg/errtypes"
    32  	"github.com/cs3org/reva/v2/pkg/storage/utils/decomposedfs/metadata"
    33  	"github.com/cs3org/reva/v2/pkg/storage/utils/decomposedfs/metadata/prefixes"
    34  	"github.com/cs3org/reva/v2/pkg/storage/utils/decomposedfs/node"
    35  	"github.com/cs3org/reva/v2/pkg/storage/utils/decomposedfs/options"
    36  	"github.com/google/uuid"
    37  	"github.com/pkg/errors"
    38  	"github.com/rogpeppe/go-internal/lockedfile"
    39  	"go.opentelemetry.io/otel"
    40  	"go.opentelemetry.io/otel/trace"
    41  )
    42  
    43  var tracer trace.Tracer
    44  
    45  const (
    46  	_spaceTypePersonal = "personal"
    47  )
    48  
    49  func init() {
    50  	tracer = otel.Tracer("github.com/cs3org/reva/pkg/storage/utils/decomposedfs/lookup")
    51  }
    52  
    53  // Lookup implements transformations from filepath to node and back
    54  type Lookup struct {
    55  	Options *options.Options
    56  
    57  	metadataBackend metadata.Backend
    58  	tm              node.TimeManager
    59  }
    60  
    61  // New returns a new Lookup instance
    62  func New(b metadata.Backend, o *options.Options, tm node.TimeManager) *Lookup {
    63  	return &Lookup{
    64  		Options:         o,
    65  		metadataBackend: b,
    66  		tm:              tm,
    67  	}
    68  }
    69  
    70  // MetadataBackend returns the metadata backend
    71  func (lu *Lookup) MetadataBackend() metadata.Backend {
    72  	return lu.metadataBackend
    73  }
    74  
    75  func (lu *Lookup) ReadBlobIDAndSizeAttr(ctx context.Context, path string, attrs node.Attributes) (string, int64, error) {
    76  	blobID := ""
    77  	blobSize := int64(0)
    78  	var err error
    79  
    80  	if attrs != nil {
    81  		blobID = attrs.String(prefixes.BlobIDAttr)
    82  		if blobID != "" {
    83  			blobSize, err = attrs.Int64(prefixes.BlobsizeAttr)
    84  			if err != nil {
    85  				return "", 0, err
    86  			}
    87  		}
    88  	} else {
    89  		attrs, err := lu.metadataBackend.All(ctx, path)
    90  		if err != nil {
    91  			return "", 0, errors.Wrapf(err, "error reading blobid xattr")
    92  		}
    93  		nodeAttrs := node.Attributes(attrs)
    94  		blobID = nodeAttrs.String(prefixes.BlobIDAttr)
    95  		blobSize, err = nodeAttrs.Int64(prefixes.BlobsizeAttr)
    96  		if err != nil {
    97  			return "", 0, errors.Wrapf(err, "error reading blobsize xattr")
    98  		}
    99  	}
   100  	return blobID, blobSize, nil
   101  }
   102  
   103  func readChildNodeFromLink(path string) (string, error) {
   104  	link, err := os.Readlink(path)
   105  	if err != nil {
   106  		return "", err
   107  	}
   108  	nodeID := strings.TrimLeft(link, "/.")
   109  	nodeID = strings.ReplaceAll(nodeID, "/", "")
   110  	return nodeID, nil
   111  }
   112  
   113  func (lu *Lookup) NodeIDFromParentAndName(ctx context.Context, parent *node.Node, name string) (string, error) {
   114  	nodeID, err := readChildNodeFromLink(filepath.Join(parent.InternalPath(), name))
   115  	if err != nil {
   116  		return "", errors.Wrap(err, "decomposedfs: Wrap: readlink error")
   117  	}
   118  	return nodeID, nil
   119  }
   120  
   121  // TypeFromPath returns the type of the node at the given path
   122  func (lu *Lookup) TypeFromPath(ctx context.Context, path string) provider.ResourceType {
   123  	// Try to read from xattrs
   124  	typeAttr, err := lu.metadataBackend.GetInt64(ctx, path, prefixes.TypeAttr)
   125  	if err == nil {
   126  		return provider.ResourceType(int32(typeAttr))
   127  	}
   128  
   129  	t := provider.ResourceType_RESOURCE_TYPE_INVALID
   130  	// Fall back to checking on disk
   131  	fi, err := os.Lstat(path)
   132  	if err != nil {
   133  		return t
   134  	}
   135  
   136  	switch {
   137  	case fi.IsDir():
   138  		if _, err = lu.metadataBackend.Get(ctx, path, prefixes.ReferenceAttr); err == nil {
   139  			t = provider.ResourceType_RESOURCE_TYPE_REFERENCE
   140  		} else {
   141  			t = provider.ResourceType_RESOURCE_TYPE_CONTAINER
   142  		}
   143  	case fi.Mode().IsRegular():
   144  		t = provider.ResourceType_RESOURCE_TYPE_FILE
   145  	case fi.Mode()&os.ModeSymlink != 0:
   146  		t = provider.ResourceType_RESOURCE_TYPE_SYMLINK
   147  		// TODO reference using ext attr on a symlink
   148  		// nodeType = provider.ResourceType_RESOURCE_TYPE_REFERENCE
   149  	}
   150  	return t
   151  }
   152  
   153  // NodeFromResource takes in a request path or request id and converts it to a Node
   154  func (lu *Lookup) NodeFromResource(ctx context.Context, ref *provider.Reference) (*node.Node, error) {
   155  	ctx, span := tracer.Start(ctx, "NodeFromResource")
   156  	defer span.End()
   157  
   158  	if ref.ResourceId != nil {
   159  		// check if a storage space reference is used
   160  		// currently, the decomposed fs uses the root node id as the space id
   161  		n, err := lu.NodeFromID(ctx, ref.ResourceId)
   162  		if err != nil {
   163  			return nil, err
   164  		}
   165  		// is this a relative reference?
   166  		if ref.Path != "" {
   167  			p := filepath.Clean(ref.Path)
   168  			if p != "." && p != "/" {
   169  				// walk the relative path
   170  				n, err = lu.WalkPath(ctx, n, p, false, func(ctx context.Context, n *node.Node) error { return nil })
   171  				if err != nil {
   172  					return nil, err
   173  				}
   174  				n.SpaceID = ref.ResourceId.SpaceId
   175  			}
   176  		}
   177  		return n, nil
   178  	}
   179  
   180  	// reference is invalid
   181  	return nil, fmt.Errorf("invalid reference %+v. resource_id must be set", ref)
   182  }
   183  
   184  // NodeFromID returns the internal path for the id
   185  func (lu *Lookup) NodeFromID(ctx context.Context, id *provider.ResourceId) (n *node.Node, err error) {
   186  	ctx, span := tracer.Start(ctx, "NodeFromID")
   187  	defer span.End()
   188  	if id == nil {
   189  		return nil, fmt.Errorf("invalid resource id %+v", id)
   190  	}
   191  	if id.OpaqueId == "" {
   192  		// The Resource references the root of a space
   193  		return lu.NodeFromSpaceID(ctx, id.SpaceId)
   194  	}
   195  	return node.ReadNode(ctx, lu, id.SpaceId, id.OpaqueId, false, nil, false)
   196  }
   197  
   198  // Pathify segments the beginning of a string into depth segments of width length
   199  // Pathify("aabbccdd", 3, 1) will return "a/a/b/bccdd"
   200  func Pathify(id string, depth, width int) string {
   201  	b := strings.Builder{}
   202  	i := 0
   203  	for ; i < depth; i++ {
   204  		if len(id) <= i*width+width {
   205  			break
   206  		}
   207  		b.WriteString(id[i*width : i*width+width])
   208  		b.WriteRune(filepath.Separator)
   209  	}
   210  	b.WriteString(id[i*width:])
   211  	return b.String()
   212  }
   213  
   214  // NodeFromSpaceID converts a resource id into a Node
   215  func (lu *Lookup) NodeFromSpaceID(ctx context.Context, spaceID string) (n *node.Node, err error) {
   216  	node, err := node.ReadNode(ctx, lu, spaceID, spaceID, false, nil, false)
   217  	if err != nil {
   218  		return nil, err
   219  	}
   220  
   221  	node.SpaceRoot = node
   222  	return node, nil
   223  }
   224  
   225  // GenerateSpaceID generates a new space id and alias
   226  func (lu *Lookup) GenerateSpaceID(spaceType string, owner *user.User) (string, error) {
   227  	switch spaceType {
   228  	case _spaceTypePersonal:
   229  		return owner.Id.OpaqueId, nil
   230  	default:
   231  		return uuid.New().String(), nil
   232  	}
   233  }
   234  
   235  // Path returns the path for node
   236  func (lu *Lookup) Path(ctx context.Context, n *node.Node, hasPermission node.PermissionFunc) (p string, err error) {
   237  	root := n.SpaceRoot
   238  	var child *node.Node
   239  	for n.ID != root.ID {
   240  		p = filepath.Join(n.Name, p)
   241  		child = n
   242  		if n, err = n.Parent(ctx); err != nil {
   243  			appctx.GetLogger(ctx).
   244  				Error().Err(err).
   245  				Str("path", p).
   246  				Str("spaceid", child.SpaceID).
   247  				Str("nodeid", child.ID).
   248  				Str("parentid", child.ParentID).
   249  				Msg("Path()")
   250  			return
   251  		}
   252  
   253  		if !hasPermission(n) {
   254  			break
   255  		}
   256  	}
   257  	p = filepath.Join("/", p)
   258  	return
   259  }
   260  
   261  // WalkPath calls n.Child(segment) on every path segment in p starting at the node r.
   262  // If a function f is given it will be executed for every segment node, but not the root node r.
   263  // If followReferences is given the current visited reference node is replaced by the referenced node.
   264  func (lu *Lookup) WalkPath(ctx context.Context, r *node.Node, p string, followReferences bool, f func(ctx context.Context, n *node.Node) error) (*node.Node, error) {
   265  	segments := strings.Split(strings.Trim(p, "/"), "/")
   266  	var err error
   267  	for i := range segments {
   268  		if r, err = r.Child(ctx, segments[i]); err != nil {
   269  			return r, err
   270  		}
   271  
   272  		if followReferences {
   273  			if attrBytes, err := r.Xattr(ctx, prefixes.ReferenceAttr); err == nil {
   274  				realNodeID := attrBytes
   275  				ref, err := refFromCS3(realNodeID)
   276  				if err != nil {
   277  					return nil, err
   278  				}
   279  
   280  				r, err = lu.NodeFromID(ctx, ref.ResourceId)
   281  				if err != nil {
   282  					return nil, err
   283  				}
   284  			}
   285  		}
   286  		if r.IsSpaceRoot(ctx) {
   287  			r.SpaceRoot = r
   288  		}
   289  
   290  		if !r.Exists && i < len(segments)-1 {
   291  			return r, errtypes.NotFound(segments[i])
   292  		}
   293  		if f != nil {
   294  			if err = f(ctx, r); err != nil {
   295  				return r, err
   296  			}
   297  		}
   298  	}
   299  	return r, nil
   300  }
   301  
   302  // InternalRoot returns the internal storage root directory
   303  func (lu *Lookup) InternalRoot() string {
   304  	return lu.Options.Root
   305  }
   306  
   307  // InternalPath returns the internal path for a given ID
   308  func (lu *Lookup) InternalPath(spaceID, nodeID string) string {
   309  	return filepath.Join(lu.Options.Root, "spaces", Pathify(spaceID, 1, 2), "nodes", Pathify(nodeID, 4, 2))
   310  }
   311  
   312  // // ReferenceFromAttr returns a CS3 reference from xattr of a node.
   313  // // Supported formats are: "cs3:storageid/nodeid"
   314  // func ReferenceFromAttr(b []byte) (*provider.Reference, error) {
   315  // 	return refFromCS3(b)
   316  // }
   317  
   318  // refFromCS3 creates a CS3 reference from a set of bytes. This method should remain private
   319  // and only be called after validation because it can potentially panic.
   320  func refFromCS3(b []byte) (*provider.Reference, error) {
   321  	parts := string(b[4:])
   322  	return &provider.Reference{
   323  		ResourceId: &provider.ResourceId{
   324  			StorageId: strings.Split(parts, "/")[0],
   325  			OpaqueId:  strings.Split(parts, "/")[1],
   326  		},
   327  	}, nil
   328  }
   329  
   330  // CopyMetadata copies all extended attributes from source to target.
   331  // The optional filter function can be used to filter by attribute name, e.g. by checking a prefix
   332  // For the source file, a shared lock is acquired.
   333  // NOTE: target resource will be write locked!
   334  func (lu *Lookup) CopyMetadata(ctx context.Context, src, target string, filter func(attributeName string, value []byte) (newValue []byte, copy bool), acquireTargetLock bool) (err error) {
   335  	// Acquire a read log on the source node
   336  	// write lock existing node before reading treesize or tree time
   337  	lock, err := lockedfile.OpenFile(lu.MetadataBackend().LockfilePath(src), os.O_RDONLY|os.O_CREATE, 0600)
   338  	if err != nil {
   339  		return err
   340  	}
   341  
   342  	if err != nil {
   343  		return errors.Wrap(err, "xattrs: Unable to lock source to read")
   344  	}
   345  	defer func() {
   346  		rerr := lock.Close()
   347  
   348  		// if err is non nil we do not overwrite that
   349  		if err == nil {
   350  			err = rerr
   351  		}
   352  	}()
   353  
   354  	return lu.CopyMetadataWithSourceLock(ctx, src, target, filter, lock, acquireTargetLock)
   355  }
   356  
   357  // CopyMetadataWithSourceLock copies all extended attributes from source to target.
   358  // The optional filter function can be used to filter by attribute name, e.g. by checking a prefix
   359  // For the source file, a matching lockedfile is required.
   360  // NOTE: target resource will be write locked!
   361  func (lu *Lookup) CopyMetadataWithSourceLock(ctx context.Context, sourcePath, targetPath string, filter func(attributeName string, value []byte) (newValue []byte, copy bool), lockedSource *lockedfile.File, acquireTargetLock bool) (err error) {
   362  	switch {
   363  	case lockedSource == nil:
   364  		return errors.New("no lock provided")
   365  	case lockedSource.File.Name() != lu.MetadataBackend().LockfilePath(sourcePath):
   366  		return errors.New("lockpath does not match filepath")
   367  	}
   368  
   369  	attrs, err := lu.metadataBackend.All(ctx, sourcePath)
   370  	if err != nil {
   371  		return err
   372  	}
   373  
   374  	newAttrs := make(map[string][]byte, 0)
   375  	for attrName, val := range attrs {
   376  		if filter != nil {
   377  			var ok bool
   378  			if val, ok = filter(attrName, val); !ok {
   379  				continue
   380  			}
   381  		}
   382  		newAttrs[attrName] = val
   383  	}
   384  
   385  	return lu.MetadataBackend().SetMultiple(ctx, targetPath, newAttrs, acquireTargetLock)
   386  }
   387  
   388  // TimeManager returns the time manager
   389  func (lu *Lookup) TimeManager() node.TimeManager {
   390  	return lu.tm
   391  }
   392  
   393  // DetectBackendOnDisk returns the name of the metadata backend being used on disk
   394  func DetectBackendOnDisk(root string) string {
   395  	matches, _ := filepath.Glob(filepath.Join(root, "spaces", "*", "*"))
   396  	if len(matches) > 0 {
   397  		base := matches[len(matches)-1]
   398  		spaceid := strings.ReplaceAll(
   399  			strings.TrimPrefix(base, filepath.Join(root, "spaces")),
   400  			"/", "")
   401  		spaceRoot := Pathify(spaceid, 4, 2)
   402  		_, err := os.Stat(filepath.Join(base, "nodes", spaceRoot+".mpk"))
   403  		if err == nil {
   404  			return "mpk"
   405  		}
   406  		_, err = os.Stat(filepath.Join(base, "nodes", spaceRoot+".ini"))
   407  		if err == nil {
   408  			return "ini"
   409  		}
   410  	}
   411  	return "xattrs"
   412  }