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