github.com/cs3org/reva/v2@v2.27.7/pkg/storage/utils/decomposedfs/spaces.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 decomposedfs
    20  
    21  import (
    22  	"context"
    23  	"encoding/json"
    24  	"fmt"
    25  	"math"
    26  	"os"
    27  	"path/filepath"
    28  	"strconv"
    29  	"strings"
    30  	"sync/atomic"
    31  	"time"
    32  
    33  	userv1beta1 "github.com/cs3org/go-cs3apis/cs3/identity/user/v1beta1"
    34  	v1beta11 "github.com/cs3org/go-cs3apis/cs3/rpc/v1beta1"
    35  	provider "github.com/cs3org/go-cs3apis/cs3/storage/provider/v1beta1"
    36  	types "github.com/cs3org/go-cs3apis/cs3/types/v1beta1"
    37  	"github.com/cs3org/reva/v2/internal/grpc/services/storageprovider"
    38  	"github.com/cs3org/reva/v2/pkg/appctx"
    39  	ocsconv "github.com/cs3org/reva/v2/pkg/conversions"
    40  	ctxpkg "github.com/cs3org/reva/v2/pkg/ctx"
    41  	"github.com/cs3org/reva/v2/pkg/errtypes"
    42  	"github.com/cs3org/reva/v2/pkg/events"
    43  	"github.com/cs3org/reva/v2/pkg/rgrpc/status"
    44  	"github.com/cs3org/reva/v2/pkg/rgrpc/todo/pool"
    45  	sdk "github.com/cs3org/reva/v2/pkg/sdk/common"
    46  	"github.com/cs3org/reva/v2/pkg/storage/utils/decomposedfs/lookup"
    47  	"github.com/cs3org/reva/v2/pkg/storage/utils/decomposedfs/metadata/prefixes"
    48  	"github.com/cs3org/reva/v2/pkg/storage/utils/decomposedfs/node"
    49  	"github.com/cs3org/reva/v2/pkg/storage/utils/decomposedfs/permissions"
    50  	"github.com/cs3org/reva/v2/pkg/storage/utils/templates"
    51  	"github.com/cs3org/reva/v2/pkg/storagespace"
    52  	"github.com/cs3org/reva/v2/pkg/utils"
    53  	"github.com/pkg/errors"
    54  	"github.com/shamaton/msgpack/v2"
    55  	"golang.org/x/sync/errgroup"
    56  )
    57  
    58  const (
    59  	_spaceTypePersonal = "personal"
    60  	_spaceTypeProject  = "project"
    61  	spaceTypeShare     = "share"
    62  	spaceTypeAny       = "*"
    63  	spaceIDAny         = "*"
    64  
    65  	quotaUnrestricted = 0
    66  )
    67  
    68  // CreateStorageSpace creates a storage space
    69  func (fs *Decomposedfs) CreateStorageSpace(ctx context.Context, req *provider.CreateStorageSpaceRequest) (*provider.CreateStorageSpaceResponse, error) {
    70  	ctx = storageprovider.WithSpaceType(ctx, "")
    71  	u := ctxpkg.ContextMustGetUser(ctx)
    72  
    73  	// "everything is a resource" this is the unique ID for the Space resource.
    74  	spaceID, err := fs.lu.GenerateSpaceID(req.Type, req.GetOwner())
    75  	if err != nil {
    76  		return nil, err
    77  	}
    78  	if reqSpaceID := utils.ReadPlainFromOpaque(req.Opaque, "spaceid"); reqSpaceID != "" {
    79  		spaceID = reqSpaceID
    80  	}
    81  
    82  	// Check if space already exists
    83  	rootPath := ""
    84  	switch req.Type {
    85  	case _spaceTypePersonal:
    86  		if fs.o.PersonalSpacePathTemplate != "" {
    87  			rootPath = filepath.Join(fs.o.Root, templates.WithUser(u, fs.o.PersonalSpacePathTemplate))
    88  		}
    89  	default:
    90  		if fs.o.GeneralSpacePathTemplate != "" {
    91  			rootPath = filepath.Join(fs.o.Root, templates.WithSpacePropertiesAndUser(u, req.Type, req.Name, spaceID, fs.o.GeneralSpacePathTemplate))
    92  		}
    93  	}
    94  	if rootPath != "" {
    95  		if _, err := os.Stat(rootPath); err == nil {
    96  			return nil, errtypes.AlreadyExists("decomposedfs: spaces: space already exists")
    97  		}
    98  	}
    99  
   100  	description := utils.ReadPlainFromOpaque(req.Opaque, "description")
   101  	alias := utils.ReadPlainFromOpaque(req.Opaque, "spaceAlias")
   102  	if alias == "" {
   103  		alias = templates.WithSpacePropertiesAndUser(u, req.Type, req.Name, spaceID, fs.o.GeneralSpaceAliasTemplate)
   104  	}
   105  	if req.Type == _spaceTypePersonal {
   106  		alias = templates.WithSpacePropertiesAndUser(u, req.Type, req.Name, spaceID, fs.o.PersonalSpaceAliasTemplate)
   107  	}
   108  
   109  	root, err := node.ReadNode(ctx, fs.lu, spaceID, spaceID, true, nil, false) // will fall into `Exists` case below
   110  	switch {
   111  	case err != nil:
   112  		return nil, err
   113  	case !fs.p.CreateSpace(ctx, spaceID):
   114  		return nil, errtypes.PermissionDenied(spaceID)
   115  	case root.Exists:
   116  		return nil, errtypes.AlreadyExists("decomposedfs: spaces: space already exists")
   117  	}
   118  
   119  	// create a directory node
   120  	root.SetType(provider.ResourceType_RESOURCE_TYPE_CONTAINER)
   121  	if rootPath == "" {
   122  		rootPath = root.InternalPath()
   123  	}
   124  
   125  	// set 755 permissions for the base dir
   126  	if err := os.MkdirAll(filepath.Dir(rootPath), 0755); err != nil {
   127  		return nil, errors.Wrap(err, fmt.Sprintf("Decomposedfs: error creating spaces base dir %s", filepath.Dir(rootPath)))
   128  	}
   129  
   130  	// 770 permissions for the space
   131  	if err := os.MkdirAll(rootPath, 0770); err != nil {
   132  		return nil, errors.Wrap(err, fmt.Sprintf("Decomposedfs: error creating space %s", rootPath))
   133  	}
   134  
   135  	// Store id in cache
   136  	if c, ok := fs.lu.(node.IDCacher); ok {
   137  		if err := c.CacheID(ctx, spaceID, spaceID, rootPath); err != nil {
   138  			return nil, err
   139  		}
   140  	}
   141  
   142  	if req.GetOwner() != nil && req.GetOwner().GetId() != nil {
   143  		root.SetOwner(req.GetOwner().GetId())
   144  	} else {
   145  		root.SetOwner(&userv1beta1.UserId{OpaqueId: spaceID, Type: userv1beta1.UserType_USER_TYPE_SPACE_OWNER})
   146  	}
   147  
   148  	metadata := node.Attributes{}
   149  	metadata.SetString(prefixes.IDAttr, spaceID)
   150  	metadata.SetString(prefixes.SpaceIDAttr, spaceID)
   151  	metadata.SetString(prefixes.OwnerIDAttr, root.Owner().GetOpaqueId())
   152  	metadata.SetString(prefixes.OwnerIDPAttr, root.Owner().GetIdp())
   153  	metadata.SetString(prefixes.OwnerTypeAttr, utils.UserTypeToString(root.Owner().GetType()))
   154  
   155  	// always mark the space root node as the end of propagation
   156  	metadata.SetString(prefixes.PropagationAttr, "1")
   157  	metadata.SetString(prefixes.NameAttr, req.Name)
   158  	metadata.SetString(prefixes.SpaceNameAttr, req.Name)
   159  
   160  	// This space is empty so set initial treesize to 0
   161  	metadata.SetUInt64(prefixes.TreesizeAttr, 0)
   162  
   163  	if req.Type != "" {
   164  		metadata.SetString(prefixes.SpaceTypeAttr, req.Type)
   165  	}
   166  
   167  	if q := req.GetQuota(); q != nil {
   168  		// set default space quota
   169  		if fs.o.MaxQuota != quotaUnrestricted && q.GetQuotaMaxBytes() > fs.o.MaxQuota {
   170  			return nil, errtypes.BadRequest("decompsedFS: requested quota is higher than allowed")
   171  		}
   172  		metadata.SetInt64(prefixes.QuotaAttr, int64(q.QuotaMaxBytes))
   173  	} else if fs.o.MaxQuota != quotaUnrestricted {
   174  		// If no quota was requested but a max quota was set then the the storage space has a quota
   175  		// of max quota.
   176  		metadata.SetInt64(prefixes.QuotaAttr, int64(fs.o.MaxQuota))
   177  	}
   178  
   179  	if description != "" {
   180  		metadata.SetString(prefixes.SpaceDescriptionAttr, description)
   181  	}
   182  
   183  	if alias != "" {
   184  		metadata.SetString(prefixes.SpaceAliasAttr, alias)
   185  	}
   186  
   187  	if err := root.SetXattrsWithContext(ctx, metadata, true); err != nil {
   188  		return nil, err
   189  	}
   190  
   191  	// Write index
   192  	err = fs.updateIndexes(ctx, &provider.Grantee{
   193  		Type: provider.GranteeType_GRANTEE_TYPE_USER,
   194  		Id:   &provider.Grantee_UserId{UserId: req.GetOwner().GetId()},
   195  	}, req.Type, root.ID, root.ID)
   196  	if err != nil {
   197  		return nil, err
   198  	}
   199  
   200  	ctx = storageprovider.WithSpaceType(ctx, req.Type)
   201  
   202  	if req.Type != _spaceTypePersonal {
   203  		if err := fs.AddGrant(ctx, &provider.Reference{
   204  			ResourceId: &provider.ResourceId{
   205  				SpaceId:  spaceID,
   206  				OpaqueId: spaceID,
   207  			},
   208  		}, &provider.Grant{
   209  			Grantee: &provider.Grantee{
   210  				Type: provider.GranteeType_GRANTEE_TYPE_USER,
   211  				Id: &provider.Grantee_UserId{
   212  					UserId: u.Id,
   213  				},
   214  			},
   215  			Permissions: ocsconv.NewManagerRole().CS3ResourcePermissions(),
   216  		}); err != nil {
   217  			return nil, err
   218  		}
   219  	}
   220  
   221  	space, err := fs.StorageSpaceFromNode(ctx, root, true)
   222  	if err != nil {
   223  		return nil, err
   224  	}
   225  
   226  	resp := &provider.CreateStorageSpaceResponse{
   227  		Status: &v1beta11.Status{
   228  			Code: v1beta11.Code_CODE_OK,
   229  		},
   230  		StorageSpace: space,
   231  	}
   232  	return resp, nil
   233  }
   234  
   235  // ListStorageSpaces returns a list of StorageSpaces.
   236  // The list can be filtered by space type or space id.
   237  // Spaces are persisted with symlinks in /spaces/<type>/<spaceid> pointing to ../../nodes/<nodeid>, the root node of the space
   238  // The spaceid is a concatenation of storageid + "!" + nodeid
   239  func (fs *Decomposedfs) ListStorageSpaces(ctx context.Context, filter []*provider.ListStorageSpacesRequest_Filter, unrestricted bool) ([]*provider.StorageSpace, error) {
   240  	// TODO check filters
   241  
   242  	// TODO when a space symlink is broken delete the space for cleanup
   243  	// read permissions are deduced from the node?
   244  
   245  	// TODO for absolute references this actually requires us to move all user homes into a subfolder of /nodes/root,
   246  	// e.g. /nodes/root/<space type> otherwise storage space names might collide even though they are of different types
   247  	// /nodes/root/personal/foo and /nodes/root/shares/foo might be two very different spaces, a /nodes/root/foo is not expressive enough
   248  	// we would not need /nodes/root if access always happened via spaceid+relative path
   249  
   250  	var (
   251  		spaceID         = spaceIDAny
   252  		nodeID          = spaceIDAny
   253  		requestedUserID *userv1beta1.UserId
   254  	)
   255  
   256  	spaceTypes := map[string]struct{}{}
   257  
   258  	for i := range filter {
   259  		switch filter[i].Type {
   260  		case provider.ListStorageSpacesRequest_Filter_TYPE_SPACE_TYPE:
   261  			switch filter[i].GetSpaceType() {
   262  			case "+mountpoint":
   263  				// TODO include mount poits
   264  			case "+grant":
   265  				// TODO include grants
   266  			default:
   267  				spaceTypes[filter[i].GetSpaceType()] = struct{}{}
   268  			}
   269  		case provider.ListStorageSpacesRequest_Filter_TYPE_ID:
   270  			_, spaceID, nodeID, _ = storagespace.SplitID(filter[i].GetId().OpaqueId)
   271  			if strings.Contains(nodeID, "/") {
   272  				return []*provider.StorageSpace{}, nil
   273  			}
   274  		case provider.ListStorageSpacesRequest_Filter_TYPE_USER:
   275  			// TODO: refactor this to GetUserId() in cs3
   276  			requestedUserID = filter[i].GetUser()
   277  		case provider.ListStorageSpacesRequest_Filter_TYPE_OWNER:
   278  			// TODO: improve further by not evaluating shares
   279  			requestedUserID = filter[i].GetOwner()
   280  		}
   281  	}
   282  	if len(spaceTypes) == 0 {
   283  		spaceTypes[spaceTypeAny] = struct{}{}
   284  	}
   285  
   286  	authenticatedUserID := ctxpkg.ContextMustGetUser(ctx).GetId().GetOpaqueId()
   287  
   288  	if !fs.p.ListSpacesOfUser(ctx, requestedUserID) {
   289  		return nil, errtypes.PermissionDenied(fmt.Sprintf("user %s is not allowed to list spaces of other users", authenticatedUserID))
   290  	}
   291  
   292  	checkNodePermissions := fs.MustCheckNodePermissions(ctx, unrestricted)
   293  
   294  	spaces := []*provider.StorageSpace{}
   295  	// build the glob path, eg.
   296  	// /path/to/root/spaces/{spaceType}/{spaceId}
   297  	// /path/to/root/spaces/personal/nodeid
   298  	// /path/to/root/spaces/shared/nodeid
   299  
   300  	if spaceID != spaceIDAny && nodeID != spaceIDAny {
   301  		// try directly reading the node
   302  		n, err := node.ReadNode(ctx, fs.lu, spaceID, nodeID, true, nil, false) // permission to read disabled space is checked later
   303  		if err != nil {
   304  			appctx.GetLogger(ctx).Error().Err(err).Str("id", nodeID).Msg("could not read node")
   305  			return nil, err
   306  		}
   307  		if !n.Exists {
   308  			// return empty list
   309  			return spaces, nil
   310  		}
   311  		space, err := fs.StorageSpaceFromNode(ctx, n, checkNodePermissions)
   312  		if err != nil {
   313  			return nil, err
   314  		}
   315  		// filter space types
   316  		_, ok1 := spaceTypes[spaceTypeAny]
   317  		_, ok2 := spaceTypes[space.SpaceType]
   318  		if ok1 || ok2 {
   319  			spaces = append(spaces, space)
   320  		}
   321  		// TODO: filter user id
   322  		return spaces, nil
   323  	}
   324  
   325  	matches := map[string]string{}
   326  	var allMatches map[string]string
   327  	var err error
   328  
   329  	if requestedUserID != nil {
   330  		allMatches, err = fs.userSpaceIndex.Load(requestedUserID.GetOpaqueId())
   331  		// do not return an error if the user has no spaces
   332  		if err != nil && !os.IsNotExist(err) {
   333  			return nil, errors.Wrap(err, "error reading user index")
   334  		}
   335  
   336  		if nodeID == spaceIDAny {
   337  			for spaceID, nodeID := range allMatches {
   338  				matches[spaceID] = nodeID
   339  			}
   340  		} else {
   341  			matches[allMatches[nodeID]] = allMatches[nodeID]
   342  		}
   343  
   344  		// get Groups for userid
   345  		user := ctxpkg.ContextMustGetUser(ctx)
   346  		// TODO the user from context may not have groups populated
   347  		if !utils.UserIDEqual(user.GetId(), requestedUserID) {
   348  			user, err = fs.UserIDToUserAndGroups(ctx, requestedUserID)
   349  			if err != nil {
   350  				return nil, err // TODO log and continue?
   351  			}
   352  		}
   353  
   354  		for _, group := range user.Groups {
   355  			allMatches, err = fs.groupSpaceIndex.Load(group)
   356  			if err != nil {
   357  				if os.IsNotExist(err) {
   358  					continue // no spaces for this group
   359  				}
   360  				return nil, errors.Wrap(err, "error reading group index")
   361  			}
   362  
   363  			if nodeID == spaceIDAny {
   364  				for spaceID, nodeID := range allMatches {
   365  					matches[spaceID] = nodeID
   366  				}
   367  			} else {
   368  				matches[allMatches[nodeID]] = allMatches[nodeID]
   369  			}
   370  		}
   371  
   372  	}
   373  
   374  	if requestedUserID == nil {
   375  		if _, ok := spaceTypes[spaceTypeAny]; ok {
   376  			// TODO do not hardcode dirs
   377  			spaceTypes = map[string]struct{}{
   378  				"personal": {},
   379  				"project":  {},
   380  				"share":    {},
   381  			}
   382  		}
   383  
   384  		for spaceType := range spaceTypes {
   385  			allMatches, err = fs.spaceTypeIndex.Load(spaceType)
   386  			if err != nil {
   387  				if os.IsNotExist(err) {
   388  					continue // no spaces for this space type
   389  				}
   390  				return nil, errors.Wrap(err, "error reading type index")
   391  			}
   392  
   393  			if nodeID == spaceIDAny {
   394  				for spaceID, nodeID := range allMatches {
   395  					matches[spaceID] = nodeID
   396  				}
   397  			} else {
   398  				matches[allMatches[nodeID]] = allMatches[nodeID]
   399  			}
   400  		}
   401  	}
   402  
   403  	// FIXME if the space does not exist try a node as the space root.
   404  
   405  	// But then the whole /spaces/{spaceType}/{spaceid} becomes obsolete
   406  	// we can alway just look up by nodeid
   407  	// -> no. The /spaces folder is used for efficient lookup by type, otherwise we would have
   408  	//    to iterate over all nodes and read the type from extended attributes
   409  	// -> but for lookup by id we can use the node directly.
   410  	// But what about sharding nodes by space?
   411  	// an efficient lookup would be possible if we received a spaceid&opaqueid in the request
   412  	// the personal spaces must also use the nodeid and not the name
   413  	numShares := atomic.Int64{}
   414  	errg, ctx := errgroup.WithContext(ctx)
   415  	work := make(chan []string, len(matches))
   416  	results := make(chan *provider.StorageSpace, len(matches))
   417  
   418  	// Distribute work
   419  	errg.Go(func() error {
   420  		defer close(work)
   421  		for spaceID, nodeID := range matches {
   422  			select {
   423  			case work <- []string{spaceID, nodeID}:
   424  			case <-ctx.Done():
   425  				return ctx.Err()
   426  			}
   427  		}
   428  		return nil
   429  	})
   430  
   431  	// Spawn workers that'll concurrently work the queue
   432  	numWorkers := 20
   433  	if len(matches) < numWorkers {
   434  		numWorkers = len(matches)
   435  	}
   436  	for i := 0; i < numWorkers; i++ {
   437  		errg.Go(func() error {
   438  			for match := range work {
   439  				spaceID, nodeID, err := fs.tp.ResolveSpaceIDIndexEntry(match[0], match[1])
   440  				if err != nil {
   441  					appctx.GetLogger(ctx).Error().Err(err).Str("id", nodeID).Msg("resolve space id index entry, skipping")
   442  					continue
   443  				}
   444  
   445  				n, err := node.ReadNode(ctx, fs.lu, spaceID, nodeID, true, nil, true)
   446  				if err != nil {
   447  					appctx.GetLogger(ctx).Error().Err(err).Str("id", nodeID).Msg("could not read node, skipping")
   448  					continue
   449  				}
   450  
   451  				if !n.Exists {
   452  					continue
   453  				}
   454  
   455  				space, err := fs.StorageSpaceFromNode(ctx, n, checkNodePermissions)
   456  				if err != nil {
   457  					switch err.(type) {
   458  					case errtypes.IsPermissionDenied:
   459  						// ok
   460  					case errtypes.NotFound:
   461  						// ok
   462  					default:
   463  						appctx.GetLogger(ctx).Error().Err(err).Str("id", nodeID).Msg("could not convert to storage space")
   464  					}
   465  					continue
   466  				}
   467  
   468  				// FIXME type share evolved to grant on the edge branch ... make it configurable if the driver should support them or not for now ... ignore type share
   469  				if space.SpaceType == spaceTypeShare {
   470  					numShares.Add(1)
   471  					// do not list shares as spaces for the owner
   472  					continue
   473  				}
   474  
   475  				// TODO apply more filters
   476  				_, ok1 := spaceTypes[spaceTypeAny]
   477  				_, ok2 := spaceTypes[space.SpaceType]
   478  				if ok1 || ok2 {
   479  					select {
   480  					case results <- space:
   481  					case <-ctx.Done():
   482  						return ctx.Err()
   483  					}
   484  				}
   485  			}
   486  			return nil
   487  		})
   488  	}
   489  
   490  	// Wait for things to settle down, then close results chan
   491  	go func() {
   492  		_ = errg.Wait() // error is checked later
   493  		close(results)
   494  	}()
   495  
   496  	for r := range results {
   497  		spaces = append(spaces, r)
   498  	}
   499  
   500  	// if there are no matches (or they happened to be spaces for the owner) and the node is a child return a space
   501  	if int64(len(matches)) <= numShares.Load() && nodeID != spaceID {
   502  		// try node id
   503  		n, err := node.ReadNode(ctx, fs.lu, spaceID, nodeID, true, nil, false) // permission to read disabled space is checked in storageSpaceFromNode
   504  		if err != nil {
   505  			return nil, err
   506  		}
   507  		if n.Exists {
   508  			space, err := fs.StorageSpaceFromNode(ctx, n, checkNodePermissions)
   509  			if err != nil {
   510  				return nil, err
   511  			}
   512  			spaces = append(spaces, space)
   513  		}
   514  	}
   515  
   516  	return spaces, nil
   517  }
   518  
   519  // UserIDToUserAndGroups converts a user ID to a user with groups
   520  func (fs *Decomposedfs) UserIDToUserAndGroups(ctx context.Context, userid *userv1beta1.UserId) (*userv1beta1.User, error) {
   521  	user, err := fs.UserCache.Get(userid.GetOpaqueId())
   522  	if err == nil {
   523  		return user.(*userv1beta1.User), nil
   524  	}
   525  
   526  	gwConn, err := pool.GetGatewayServiceClient(fs.o.GatewayAddr)
   527  	if err != nil {
   528  		return nil, err
   529  	}
   530  	getUserResponse, err := gwConn.GetUser(ctx, &userv1beta1.GetUserRequest{
   531  		UserId:                 userid,
   532  		SkipFetchingUserGroups: false,
   533  	})
   534  	if err != nil {
   535  		return nil, err
   536  	}
   537  	if getUserResponse.Status.Code != v1beta11.Code_CODE_OK {
   538  		return nil, status.NewErrorFromCode(getUserResponse.Status.Code, "gateway")
   539  	}
   540  	_ = fs.UserCache.Set(userid.GetOpaqueId(), getUserResponse.GetUser())
   541  	return getUserResponse.GetUser(), nil
   542  }
   543  
   544  // MustCheckNodePermissions checks if permission checks are needed to be performed when user requests spaces
   545  func (fs *Decomposedfs) MustCheckNodePermissions(ctx context.Context, unrestricted bool) bool {
   546  	// canListAllSpaces indicates if the user has the permission from the global user role
   547  	canListAllSpaces := fs.p.ListAllSpaces(ctx)
   548  	// unrestricted is the param which indicates if the user wants to list all spaces or only the spaces he is part of
   549  	// if a user lists all spaces unrestricted and doesn't have the permissions from the role, we need to check
   550  	// the nodePermissions and this will return a spaces list where the user has access to
   551  	// we can only skip the NodePermissions check if both values are true
   552  	if canListAllSpaces && unrestricted {
   553  		return false
   554  	}
   555  	return true
   556  }
   557  
   558  // UpdateStorageSpace updates a storage space
   559  func (fs *Decomposedfs) UpdateStorageSpace(ctx context.Context, req *provider.UpdateStorageSpaceRequest) (*provider.UpdateStorageSpaceResponse, error) {
   560  	var restore bool
   561  	if req.Opaque != nil {
   562  		_, restore = req.Opaque.Map["restore"]
   563  	}
   564  
   565  	space := req.StorageSpace
   566  	_, spaceID, _, _ := storagespace.SplitID(space.Id.OpaqueId)
   567  
   568  	metadata := make(node.Attributes, 5)
   569  	if space.Name != "" {
   570  		metadata.SetString(prefixes.NameAttr, space.Name)
   571  		metadata.SetString(prefixes.SpaceNameAttr, space.Name)
   572  	}
   573  
   574  	if space.Quota != nil {
   575  		if fs.o.MaxQuota != quotaUnrestricted && fs.o.MaxQuota < space.Quota.QuotaMaxBytes {
   576  			return &provider.UpdateStorageSpaceResponse{
   577  				Status: &v1beta11.Status{Code: v1beta11.Code_CODE_INVALID_ARGUMENT, Message: "decompsedFS: requested quota is higher than allowed"},
   578  			}, nil
   579  		} else if fs.o.MaxQuota != quotaUnrestricted && space.Quota.QuotaMaxBytes == quotaUnrestricted {
   580  			// If the caller wants to unrestrict the space we give it the maximum allowed quota.
   581  			space.Quota.QuotaMaxBytes = fs.o.MaxQuota
   582  		}
   583  		metadata.SetInt64(prefixes.QuotaAttr, int64(space.Quota.QuotaMaxBytes))
   584  	}
   585  
   586  	// TODO also return values which are not in the request
   587  	if space.Opaque != nil {
   588  		if description, ok := space.Opaque.Map["description"]; ok {
   589  			metadata[prefixes.SpaceDescriptionAttr] = description.Value
   590  		}
   591  		if alias := utils.ReadPlainFromOpaque(space.Opaque, "spaceAlias"); alias != "" {
   592  			metadata.SetString(prefixes.SpaceAliasAttr, alias)
   593  		}
   594  		if image := utils.ReadPlainFromOpaque(space.Opaque, "image"); image != "" {
   595  			imageID, err := storagespace.ParseID(image)
   596  			if err != nil {
   597  				return &provider.UpdateStorageSpaceResponse{
   598  					Status: &v1beta11.Status{Code: v1beta11.Code_CODE_NOT_FOUND, Message: "decomposedFS: space image resource not found"},
   599  				}, nil
   600  			}
   601  			metadata.SetString(prefixes.SpaceImageAttr, imageID.OpaqueId)
   602  		}
   603  		if readme := utils.ReadPlainFromOpaque(space.Opaque, "readme"); readme != "" {
   604  			readmeID, err := storagespace.ParseID(readme)
   605  			if err != nil {
   606  				return &provider.UpdateStorageSpaceResponse{
   607  					Status: &v1beta11.Status{Code: v1beta11.Code_CODE_NOT_FOUND, Message: "decomposedFS: space readme resource not found"},
   608  				}, nil
   609  			}
   610  			metadata.SetString(prefixes.SpaceReadmeAttr, readmeID.OpaqueId)
   611  		}
   612  	}
   613  
   614  	// check which permissions are needed
   615  	spaceNode, err := node.ReadNode(ctx, fs.lu, spaceID, spaceID, true, nil, false)
   616  	if err != nil {
   617  		return nil, err
   618  	}
   619  
   620  	if !spaceNode.Exists {
   621  		return &provider.UpdateStorageSpaceResponse{
   622  			Status: &v1beta11.Status{Code: v1beta11.Code_CODE_NOT_FOUND},
   623  		}, nil
   624  	}
   625  
   626  	sp, err := fs.p.AssemblePermissions(ctx, spaceNode)
   627  	if err != nil {
   628  		return &provider.UpdateStorageSpaceResponse{
   629  			Status: status.NewStatusFromErrType(ctx, "assembling permissions failed", err),
   630  		}, nil
   631  
   632  	}
   633  
   634  	if !restore && len(metadata) == 0 && !permissions.IsViewer(sp) {
   635  		// you may land here when making an update request without changes
   636  		// check if user has access to the drive before continuing
   637  		return &provider.UpdateStorageSpaceResponse{
   638  			Status: &v1beta11.Status{Code: v1beta11.Code_CODE_NOT_FOUND},
   639  		}, nil
   640  	}
   641  
   642  	if !permissions.IsManager(sp) {
   643  		// We are not a space manager. We need to check for additional permissions.
   644  		k := []string{prefixes.NameAttr, prefixes.SpaceDescriptionAttr}
   645  		if !permissions.IsEditor(sp) {
   646  			k = append(k, prefixes.SpaceReadmeAttr, prefixes.SpaceAliasAttr, prefixes.SpaceImageAttr)
   647  		}
   648  
   649  		if mapHasKey(metadata, k...) && !fs.p.ManageSpaceProperties(ctx, spaceID) {
   650  			return &provider.UpdateStorageSpaceResponse{
   651  				Status: &v1beta11.Status{Code: v1beta11.Code_CODE_PERMISSION_DENIED},
   652  			}, nil
   653  		}
   654  
   655  		if restore && !fs.p.SpaceAbility(ctx, spaceID) {
   656  			return &provider.UpdateStorageSpaceResponse{
   657  				Status: &v1beta11.Status{Code: v1beta11.Code_CODE_NOT_FOUND},
   658  			}, nil
   659  		}
   660  	}
   661  
   662  	if mapHasKey(metadata, prefixes.QuotaAttr) {
   663  		typ, err := spaceNode.SpaceRoot.Xattr(ctx, prefixes.SpaceTypeAttr)
   664  		if err != nil {
   665  			return &provider.UpdateStorageSpaceResponse{
   666  				Status: &v1beta11.Status{
   667  					Code:    v1beta11.Code_CODE_INTERNAL,
   668  					Message: "space has no type",
   669  				},
   670  			}, nil
   671  		}
   672  
   673  		if !fs.p.SetSpaceQuota(ctx, spaceID, string(typ)) {
   674  			return &provider.UpdateStorageSpaceResponse{
   675  				Status: &v1beta11.Status{Code: v1beta11.Code_CODE_PERMISSION_DENIED},
   676  			}, nil
   677  		}
   678  	}
   679  	metadata[prefixes.TreeMTimeAttr] = []byte(time.Now().UTC().Format(time.RFC3339Nano))
   680  
   681  	err = spaceNode.SetXattrsWithContext(ctx, metadata, true)
   682  	if err != nil {
   683  		return nil, err
   684  	}
   685  
   686  	if restore {
   687  		if err := spaceNode.SetDTime(ctx, nil); err != nil {
   688  			return nil, err
   689  		}
   690  	}
   691  
   692  	// send back the updated data from the storage
   693  	updatedSpace, err := fs.StorageSpaceFromNode(ctx, spaceNode, false)
   694  	if err != nil {
   695  		return nil, err
   696  	}
   697  
   698  	return &provider.UpdateStorageSpaceResponse{
   699  		Status:       &v1beta11.Status{Code: v1beta11.Code_CODE_OK},
   700  		StorageSpace: updatedSpace,
   701  	}, nil
   702  }
   703  
   704  // DeleteStorageSpace deletes a storage space
   705  func (fs *Decomposedfs) DeleteStorageSpace(ctx context.Context, req *provider.DeleteStorageSpaceRequest) error {
   706  	opaque := req.Opaque
   707  	var purge bool
   708  	if opaque != nil {
   709  		_, purge = opaque.Map["purge"]
   710  	}
   711  
   712  	_, spaceID, _, err := storagespace.SplitID(req.Id.GetOpaqueId())
   713  	if err != nil {
   714  		return err
   715  	}
   716  
   717  	n, err := node.ReadNode(ctx, fs.lu, spaceID, spaceID, true, nil, false) // permission to read disabled space is checked later
   718  	if err != nil {
   719  		return err
   720  	}
   721  
   722  	st, err := n.SpaceRoot.XattrString(ctx, prefixes.SpaceTypeAttr)
   723  	if err != nil {
   724  		return errtypes.InternalError(fmt.Sprintf("space %s does not have a spacetype, possible corrupt decompsedfs", n.ID))
   725  	}
   726  
   727  	if err := canDeleteSpace(ctx, spaceID, st, purge, n, fs.p); err != nil {
   728  		return err
   729  	}
   730  	if purge {
   731  		if !n.IsDisabled(ctx) {
   732  			return errtypes.NewErrtypeFromStatus(status.NewInvalid(ctx, "can't purge enabled space"))
   733  		}
   734  
   735  		// TODO invalidate ALL indexes in msgpack, not only by type
   736  		spaceType, err := n.XattrString(ctx, prefixes.SpaceTypeAttr)
   737  		if err != nil {
   738  			return err
   739  		}
   740  		if err := fs.spaceTypeIndex.Remove(spaceType, spaceID); err != nil {
   741  			return err
   742  		}
   743  
   744  		// invalidate cache
   745  		if err := fs.lu.MetadataBackend().Purge(ctx, n.InternalPath()); err != nil {
   746  			return err
   747  		}
   748  
   749  		root := fs.getSpaceRoot(spaceID)
   750  
   751  		// walkfn will delete the blob if the node has one
   752  		walkfn := func(path string, info os.FileInfo, err error) error {
   753  			if err != nil {
   754  				return err
   755  			}
   756  
   757  			if filepath.Ext(path) != ".mpk" {
   758  				return nil
   759  			}
   760  
   761  			b, err := os.ReadFile(path)
   762  			if err != nil {
   763  				return err
   764  			}
   765  
   766  			m := map[string][]byte{}
   767  			if err := msgpack.Unmarshal(b, &m); err != nil {
   768  				return err
   769  			}
   770  
   771  			bid := m["user.ocis.blobid"]
   772  			if string(bid) == "" {
   773  				return nil
   774  			}
   775  
   776  			if err := fs.tp.DeleteBlob(&node.Node{
   777  				BlobID:  string(bid),
   778  				SpaceID: spaceID,
   779  			}); err != nil {
   780  				return err
   781  			}
   782  
   783  			// remove .mpk file so subsequent attempts will not try to delete the blob again
   784  			return os.Remove(path)
   785  		}
   786  
   787  		// This is deletes all blobs of the space
   788  		// NOTE: This isn't needed when no s3 is used, but we can't differentiate that here...
   789  		if err := filepath.Walk(root, walkfn); err != nil {
   790  			return err
   791  		}
   792  
   793  		// remove space metadata
   794  		if err := os.RemoveAll(root); err != nil {
   795  			return err
   796  		}
   797  
   798  		// try removing the space root node
   799  		// Note that this will fail when there are other spaceids starting with the same two digits.
   800  		_ = os.Remove(filepath.Dir(root))
   801  
   802  		return nil
   803  	}
   804  
   805  	// mark as disabled by writing a dtime attribute
   806  	dtime := time.Now()
   807  	return n.SetDTime(ctx, &dtime)
   808  }
   809  
   810  // the value of `target` depends on the implementation:
   811  // - for ocis/s3ng it is the relative link to the space root
   812  // - for the posixfs it is the node id
   813  func (fs *Decomposedfs) updateIndexes(ctx context.Context, grantee *provider.Grantee, spaceType, spaceID, nodeID string) error {
   814  	target := fs.tp.BuildSpaceIDIndexEntry(spaceID, nodeID)
   815  	err := fs.linkStorageSpaceType(ctx, spaceType, spaceID, target)
   816  	if err != nil {
   817  		return err
   818  	}
   819  	if isShareGrant(ctx) {
   820  		// FIXME we should count the references for the by-type index currently removing the second share from the same
   821  		// space cannot determine if the by-type should be deletet, which is why we never delete them ...
   822  		return nil
   823  	}
   824  
   825  	// create space grant index
   826  	switch {
   827  	case grantee.Type == provider.GranteeType_GRANTEE_TYPE_USER:
   828  		return fs.linkSpaceByUser(ctx, grantee.GetUserId().GetOpaqueId(), spaceID, target)
   829  	case grantee.Type == provider.GranteeType_GRANTEE_TYPE_GROUP:
   830  		return fs.linkSpaceByGroup(ctx, grantee.GetGroupId().GetOpaqueId(), spaceID, target)
   831  	default:
   832  		return errtypes.BadRequest("invalid grantee type: " + grantee.GetType().String())
   833  	}
   834  }
   835  
   836  func (fs *Decomposedfs) linkSpaceByUser(ctx context.Context, userID, spaceID, target string) error {
   837  	return fs.userSpaceIndex.Add(userID, spaceID, target)
   838  }
   839  
   840  func (fs *Decomposedfs) linkSpaceByGroup(ctx context.Context, groupID, spaceID, target string) error {
   841  	return fs.groupSpaceIndex.Add(groupID, spaceID, target)
   842  }
   843  
   844  func (fs *Decomposedfs) linkStorageSpaceType(ctx context.Context, spaceType, spaceID, target string) error {
   845  	return fs.spaceTypeIndex.Add(spaceType, spaceID, target)
   846  }
   847  
   848  func (fs *Decomposedfs) StorageSpaceFromNode(ctx context.Context, n *node.Node, checkPermissions bool) (*provider.StorageSpace, error) {
   849  	user := ctxpkg.ContextMustGetUser(ctx)
   850  	if checkPermissions {
   851  		rp, err := fs.p.AssemblePermissions(ctx, n)
   852  		switch {
   853  		case err != nil:
   854  			return nil, err
   855  		case !rp.Stat:
   856  			return nil, errtypes.NotFound(fmt.Sprintf("space %s not found", n.ID))
   857  		}
   858  
   859  		if n.SpaceRoot.IsDisabled(ctx) {
   860  			rp, err := fs.p.AssemblePermissions(ctx, n)
   861  			if err != nil || !permissions.IsManager(rp) {
   862  				return nil, errtypes.PermissionDenied(fmt.Sprintf("user %s is not allowed to list deleted spaces %s", user.Username, n.ID))
   863  			}
   864  		}
   865  	}
   866  
   867  	sublog := appctx.GetLogger(ctx).With().Str("spaceid", n.SpaceID).Logger()
   868  
   869  	var err error
   870  	// TODO apply more filters
   871  	var sname string
   872  	if sname, err = n.SpaceRoot.XattrString(ctx, prefixes.SpaceNameAttr); err != nil {
   873  		// FIXME: Is that a severe problem?
   874  		sublog.Debug().Err(err).Msg("space does not have a name attribute")
   875  	}
   876  
   877  	/*
   878  		if err := n.FindStorageSpaceRoot(); err != nil {
   879  			return nil, err
   880  		}
   881  	*/
   882  
   883  	// read the grants from the current node, not the root
   884  	grants, err := n.ListGrants(ctx)
   885  	if err != nil {
   886  		return nil, err
   887  	}
   888  
   889  	grantMap := make(map[string]*provider.ResourcePermissions, len(grants))
   890  	grantExpiration := make(map[string]*types.Timestamp)
   891  	groupMap := make(map[string]struct{})
   892  	for _, g := range grants {
   893  		var id string
   894  		switch g.Grantee.Type {
   895  		case provider.GranteeType_GRANTEE_TYPE_GROUP:
   896  			id = g.Grantee.GetGroupId().OpaqueId
   897  			groupMap[id] = struct{}{}
   898  		case provider.GranteeType_GRANTEE_TYPE_USER:
   899  			id = g.Grantee.GetUserId().OpaqueId
   900  		default:
   901  			continue
   902  		}
   903  
   904  		if g.Expiration != nil {
   905  			// We are doing this check here because we want to remove expired grants "on access".
   906  			// This way we don't have to have a cron job checking the grants in regular intervals.
   907  			// The tradeof obviously is that this code is here.
   908  			if isGrantExpired(g) {
   909  				var errDeleteGrant, errIndexRemove error
   910  
   911  				errDeleteGrant = n.DeleteGrant(ctx, g, true)
   912  				if errDeleteGrant != nil {
   913  					sublog.Error().Err(err).Str("grantee", id).
   914  						Msg("failed to delete expired space grant")
   915  				}
   916  				if n.IsSpaceRoot(ctx) {
   917  					// invalidate space grant
   918  					switch {
   919  					case g.Grantee.Type == provider.GranteeType_GRANTEE_TYPE_USER:
   920  						// remove from user index
   921  						errIndexRemove = fs.userSpaceIndex.Remove(g.Grantee.GetUserId().GetOpaqueId(), n.SpaceID)
   922  						if errIndexRemove != nil {
   923  							sublog.Error().Err(err).Str("grantee", id).
   924  								Msg("failed to delete expired user space index")
   925  						}
   926  					case g.Grantee.Type == provider.GranteeType_GRANTEE_TYPE_GROUP:
   927  						// remove from group index
   928  						errIndexRemove = fs.groupSpaceIndex.Remove(g.Grantee.GetGroupId().GetOpaqueId(), n.SpaceID)
   929  						if errIndexRemove != nil {
   930  							sublog.Error().Err(err).Str("grantee", id).
   931  								Msg("failed to delete expired group space index")
   932  						}
   933  					}
   934  				}
   935  
   936  				// publish SpaceMembershipExpired event
   937  				if errDeleteGrant == nil && errIndexRemove == nil {
   938  					ev := events.SpaceMembershipExpired{
   939  						SpaceOwner: n.SpaceOwnerOrManager(ctx),
   940  						SpaceID:    &provider.StorageSpaceId{OpaqueId: n.SpaceID},
   941  						SpaceName:  sname,
   942  						ExpiredAt:  time.Unix(int64(g.Expiration.Seconds), int64(g.Expiration.Nanos)),
   943  						Timestamp:  utils.TSNow(),
   944  					}
   945  					switch {
   946  					case g.Grantee.Type == provider.GranteeType_GRANTEE_TYPE_USER:
   947  						ev.GranteeUserID = g.Grantee.GetUserId()
   948  					case g.Grantee.Type == provider.GranteeType_GRANTEE_TYPE_GROUP:
   949  						ev.GranteeGroupID = g.Grantee.GetGroupId()
   950  					}
   951  					err = events.Publish(ctx, fs.stream, ev)
   952  					if err != nil {
   953  						sublog.Error().Err(err).Msg("error publishing SpaceMembershipExpired event")
   954  					}
   955  				}
   956  
   957  				continue
   958  			}
   959  			grantExpiration[id] = g.Expiration
   960  		}
   961  		grantMap[id] = g.Permissions
   962  	}
   963  
   964  	grantMapJSON, err := json.Marshal(grantMap)
   965  	if err != nil {
   966  		return nil, err
   967  	}
   968  
   969  	grantExpirationMapJSON, err := json.Marshal(grantExpiration)
   970  	if err != nil {
   971  		return nil, err
   972  	}
   973  
   974  	groupMapJSON, err := json.Marshal(groupMap)
   975  	if err != nil {
   976  		return nil, err
   977  	}
   978  
   979  	ssID, err := storagespace.FormatReference(
   980  		&provider.Reference{
   981  			ResourceId: &provider.ResourceId{
   982  				SpaceId:  n.SpaceRoot.SpaceID,
   983  				OpaqueId: n.SpaceRoot.ID},
   984  		},
   985  	)
   986  	if err != nil {
   987  		return nil, err
   988  	}
   989  	space := &provider.StorageSpace{
   990  		Opaque: &types.Opaque{
   991  			Map: map[string]*types.OpaqueEntry{
   992  				"grants": {
   993  					Decoder: "json",
   994  					Value:   grantMapJSON,
   995  				},
   996  				"grants_expirations": {
   997  					Decoder: "json",
   998  					Value:   grantExpirationMapJSON,
   999  				},
  1000  				"groups": {
  1001  					Decoder: "json",
  1002  					Value:   groupMapJSON,
  1003  				},
  1004  			},
  1005  		},
  1006  		Id: &provider.StorageSpaceId{OpaqueId: ssID},
  1007  		Root: &provider.ResourceId{
  1008  			SpaceId:  n.SpaceRoot.SpaceID,
  1009  			OpaqueId: n.SpaceRoot.ID,
  1010  		},
  1011  		Name: sname,
  1012  		// SpaceType is read from xattr below
  1013  		// Mtime is set either as node.tmtime or as fi.mtime below
  1014  	}
  1015  
  1016  	space.SpaceType, err = n.SpaceRoot.XattrString(ctx, prefixes.SpaceTypeAttr)
  1017  	if err != nil {
  1018  		appctx.GetLogger(ctx).Debug().Err(err).Msg("space does not have a type attribute")
  1019  	}
  1020  
  1021  	if n.SpaceRoot.IsDisabled(ctx) {
  1022  		space.Opaque = utils.AppendPlainToOpaque(space.Opaque, "trashed", "trashed")
  1023  	}
  1024  
  1025  	if n.Owner() != nil && n.Owner().OpaqueId != "" {
  1026  		space.Owner = &userv1beta1.User{ // FIXME only return a UserID, not a full blown user object
  1027  			Id: n.Owner(),
  1028  		}
  1029  	}
  1030  
  1031  	// we set the space mtime to the root item mtime
  1032  	// override the stat mtime with a tmtime if it is present
  1033  	var tmtime time.Time
  1034  	if tmt, err := n.GetTMTime(ctx); err == nil {
  1035  		tmtime = tmt
  1036  		un := tmt.UnixNano()
  1037  		space.Mtime = &types.Timestamp{
  1038  			Seconds: uint64(un / 1000000000),
  1039  			Nanos:   uint32(un % 1000000000),
  1040  		}
  1041  	} else if fi, err := os.Stat(n.InternalPath()); err == nil {
  1042  		// fall back to stat mtime
  1043  		tmtime = fi.ModTime()
  1044  		un := fi.ModTime().UnixNano()
  1045  		space.Mtime = &types.Timestamp{
  1046  			Seconds: uint64(un / 1000000000),
  1047  			Nanos:   uint32(un % 1000000000),
  1048  		}
  1049  	}
  1050  
  1051  	etag, err := node.CalculateEtag(n.ID, tmtime)
  1052  	if err != nil {
  1053  		return nil, err
  1054  	}
  1055  	space.Opaque.Map["etag"] = &types.OpaqueEntry{
  1056  		Decoder: "plain",
  1057  		Value:   []byte(etag),
  1058  	}
  1059  
  1060  	spaceAttributes, err := n.SpaceRoot.Xattrs(ctx)
  1061  	if err != nil {
  1062  		return nil, err
  1063  	}
  1064  
  1065  	// if quota is set try parsing it as int64, otherwise don't bother
  1066  	if q, err := spaceAttributes.Int64(prefixes.QuotaAttr); err == nil && q >= 0 {
  1067  		// make sure we have a proper signed int
  1068  		// we use the same magic numbers to indicate:
  1069  		// -1 = uncalculated
  1070  		// -2 = unknown
  1071  		// -3 = unlimited
  1072  		space.Quota = &provider.Quota{
  1073  			QuotaMaxBytes: uint64(q),
  1074  			QuotaMaxFiles: math.MaxUint64, // TODO MaxUInt64? = unlimited? why even max files? 0 = unlimited?
  1075  		}
  1076  
  1077  	}
  1078  	if si := spaceAttributes.String(prefixes.SpaceImageAttr); si != "" {
  1079  		space.Opaque = utils.AppendPlainToOpaque(space.Opaque, "image", storagespace.FormatResourceID(
  1080  			&provider.ResourceId{StorageId: space.Root.StorageId, SpaceId: space.Root.SpaceId, OpaqueId: si},
  1081  		))
  1082  	}
  1083  	if sd := spaceAttributes.String(prefixes.SpaceDescriptionAttr); sd != "" {
  1084  		space.Opaque = utils.AppendPlainToOpaque(space.Opaque, "description", sd)
  1085  	}
  1086  	if sr := spaceAttributes.String(prefixes.SpaceReadmeAttr); sr != "" {
  1087  		space.Opaque = utils.AppendPlainToOpaque(space.Opaque, "readme", storagespace.FormatResourceID(
  1088  			&provider.ResourceId{StorageId: space.Root.StorageId, SpaceId: space.Root.SpaceId, OpaqueId: sr},
  1089  		))
  1090  	}
  1091  	if sa := spaceAttributes.String(prefixes.SpaceAliasAttr); sa != "" {
  1092  		space.Opaque = utils.AppendPlainToOpaque(space.Opaque, "spaceAlias", sa)
  1093  	}
  1094  
  1095  	// add rootinfo
  1096  	ps, _ := n.SpaceRoot.PermissionSet(ctx)
  1097  	space.RootInfo, _ = n.SpaceRoot.AsResourceInfo(ctx, ps, []string{"quota"}, nil, false)
  1098  
  1099  	// we cannot put free, used and remaining into the quota, as quota, when set would always imply a quota limit
  1100  	// for now we use opaque properties with a 'quota.' prefix
  1101  	quotaStr := node.QuotaUnknown
  1102  	if quotaInOpaque := sdk.DecodeOpaqueMap(space.RootInfo.Opaque)["quota"]; quotaInOpaque != "" {
  1103  		quotaStr = quotaInOpaque
  1104  	}
  1105  
  1106  	total, used, remaining, err := fs.calculateTotalUsedRemaining(quotaStr, space.GetRootInfo().GetSize())
  1107  	if err != nil {
  1108  		return nil, err
  1109  	}
  1110  	space.Opaque = utils.AppendPlainToOpaque(space.Opaque, "quota.total", strconv.FormatUint(total, 10))
  1111  	space.Opaque = utils.AppendPlainToOpaque(space.Opaque, "quota.used", strconv.FormatUint(used, 10))
  1112  	space.Opaque = utils.AppendPlainToOpaque(space.Opaque, "quota.remaining", strconv.FormatUint(remaining, 10))
  1113  
  1114  	return space, nil
  1115  }
  1116  
  1117  func mapHasKey(checkMap map[string][]byte, keys ...string) bool {
  1118  	for _, key := range keys {
  1119  		if _, hasKey := checkMap[key]; hasKey {
  1120  			return true
  1121  		}
  1122  	}
  1123  	return false
  1124  }
  1125  
  1126  func isGrantExpired(g *provider.Grant) bool {
  1127  	if g.Expiration == nil {
  1128  		return false
  1129  	}
  1130  	return time.Now().After(time.Unix(int64(g.Expiration.Seconds), int64(g.Expiration.Nanos)))
  1131  }
  1132  
  1133  func (fs *Decomposedfs) getSpaceRoot(spaceID string) string {
  1134  	return filepath.Join(fs.o.Root, "spaces", lookup.Pathify(spaceID, 1, 2))
  1135  }
  1136  
  1137  // Space deletion can be tricky as there are lots of different cases:
  1138  // - spaces of type personal can only be disabled and deleted by users with the "delete-all-home-spaces" permission
  1139  // - a user with the "delete-all-spaces" permission may delete but not enable/disable any project space
  1140  // - a user with the "Drive.ReadWriteEnabled" permission may enable/disable but not delete any project space
  1141  // - a project space can always be enabled/disabled/deleted by its manager (i.e. users have the "remove" grant)
  1142  func canDeleteSpace(ctx context.Context, spaceID string, typ string, purge bool, n *node.Node, p permissions.Permissions) error {
  1143  	// delete-all-home spaces allows to disable and delete a personal space
  1144  	if typ == "personal" {
  1145  		if p.DeleteAllHomeSpaces(ctx) {
  1146  			return nil
  1147  		}
  1148  		return errtypes.PermissionDenied("user is not allowed to delete a personal space")
  1149  	}
  1150  
  1151  	// space managers are allowed to disable and delete their project spaces
  1152  	if rp, err := p.AssemblePermissions(ctx, n); err == nil && permissions.IsManager(rp) {
  1153  		return nil
  1154  	}
  1155  
  1156  	// delete-all-spaces permissions allows to delete (purge, NOT disable) project spaces
  1157  	if purge && p.DeleteAllSpaces(ctx) {
  1158  		return nil
  1159  	}
  1160  
  1161  	// Drive.ReadWriteEnabled allows to disable a space
  1162  	if !purge && p.SpaceAbility(ctx, spaceID) {
  1163  		return nil
  1164  	}
  1165  
  1166  	return errtypes.PermissionDenied(fmt.Sprintf("user is not allowed to delete space %s", n.ID))
  1167  }