From 27b309125cf832aa5364d6ebfc6c11a37b9c3e7b Mon Sep 17 00:00:00 2001 From: kobergj Date: Fri, 4 Feb 2022 10:49:29 +0100 Subject: [PATCH] Restoring Spaces (#2458) * add restore functionality to decomposedfs Signed-off-by: jkoberg * add changelog item Signed-off-by: jkoberg * find trashed spaces Signed-off-by: jkoberg * add includeTrashed paramter to ListSpaces call Signed-off-by: jkoberg * only add * if neccessary Signed-off-by: jkoberg * move instead delete to make restore possible Signed-off-by: jkoberg * pass trashed information via Opaque Signed-off-by: jkoberg * don't return trashed space for non owners Signed-off-by: jkoberg * revert includeTrashed hack Signed-off-by: jkoberg * no need to symlink trashed spaces any more Signed-off-by: jkoberg * bad solution - just wanna paint it green Signed-off-by: jkoberg * allow listing of deleted spaces Signed-off-by: jkoberg --- changelog/unreleased/restore-spaces.md | 7 + .../grpc/services/gateway/storageprovider.go | 11 +- pkg/storage/utils/decomposedfs/spaces.go | 139 +++++++++++++----- 3 files changed, 120 insertions(+), 37 deletions(-) create mode 100644 changelog/unreleased/restore-spaces.md diff --git a/changelog/unreleased/restore-spaces.md b/changelog/unreleased/restore-spaces.md new file mode 100644 index 00000000000..93d288b02d0 --- /dev/null +++ b/changelog/unreleased/restore-spaces.md @@ -0,0 +1,7 @@ +Enhancement: Restore spaces that were previously deleted + +After the first step of the two step delete process an admin can decide to restore +the space instead of deleting it. This will undo the deletion and +all files and shares are accessible again + +https://github.com/cs3org/reva/pull/2457 diff --git a/internal/grpc/services/gateway/storageprovider.go b/internal/grpc/services/gateway/storageprovider.go index 6aebda5f5a2..b2337d64f1b 100644 --- a/internal/grpc/services/gateway/storageprovider.go +++ b/internal/grpc/services/gateway/storageprovider.go @@ -286,9 +286,11 @@ func (s *svc) UpdateStorageSpace(ctx context.Context, req *provider.UpdateStorag }, nil } - id := res.StorageSpace.Root - s.cache.RemoveStat(ctxpkg.ContextMustGetUser(ctx), id) - s.cache.RemoveListStorageProviders(id) + if res.Status.Code == rpc.Code_CODE_OK { + id := res.StorageSpace.Root + s.cache.RemoveStat(ctxpkg.ContextMustGetUser(ctx), id) + s.cache.RemoveListStorageProviders(id) + } return res, nil } @@ -1089,8 +1091,9 @@ func (s *svc) findSpaces(ctx context.Context, ref *provider.Reference) ([]*regis } listReq := ®istry.ListStorageProvidersRequest{ - Opaque: &typesv1beta1.Opaque{}, + Opaque: &typesv1beta1.Opaque{Map: make(map[string]*typesv1beta1.OpaqueEntry)}, } + sdk.EncodeOpaqueMap(listReq.Opaque, filters) return s.findProvider(ctx, listReq) diff --git a/pkg/storage/utils/decomposedfs/spaces.go b/pkg/storage/utils/decomposedfs/spaces.go index e4049658f32..717c3825c01 100644 --- a/pkg/storage/utils/decomposedfs/spaces.go +++ b/pkg/storage/utils/decomposedfs/spaces.go @@ -27,6 +27,7 @@ import ( "path/filepath" "strconv" "strings" + "time" userv1beta1 "github.com/cs3org/go-cs3apis/cs3/identity/user/v1beta1" permissionsv1beta1 "github.com/cs3org/go-cs3apis/cs3/permissions/v1beta1" @@ -217,7 +218,8 @@ func (fs *Decomposedfs) ListStorageSpaces(ctx context.Context, filter []*provide matches := []string{} for _, spaceType := range spaceTypes { - m, err := filepath.Glob(filepath.Join(fs.o.Root, "spaces", spaceType, nodeID)) + path := filepath.Join(fs.o.Root, "spaces", spaceType, nodeID) + m, err := filepath.Glob(path) if err != nil { return nil, err } @@ -317,10 +319,50 @@ func (fs *Decomposedfs) ListStorageSpaces(ctx context.Context, filter []*provide // UpdateStorageSpace updates a storage space func (fs *Decomposedfs) UpdateStorageSpace(ctx context.Context, req *provider.UpdateStorageSpaceRequest) (*provider.UpdateStorageSpaceResponse, error) { - space := req.StorageSpace + var restore bool + if req.Opaque != nil { + _, restore = req.Opaque.Map["restore"] + } + space := req.StorageSpace _, spaceID, _ := utils.SplitStorageSpaceID(space.Id.OpaqueId) + if restore { + matches, err := filepath.Glob(filepath.Join(fs.o.Root, "spaces", spaceTypeAny, spaceID)) + if err != nil { + return nil, err + } + + if len(matches) != 1 { + return &provider.UpdateStorageSpaceResponse{ + Status: &v1beta11.Status{ + Code: v1beta11.Code_CODE_NOT_FOUND, + Message: fmt.Sprintf("restoring space failed: found %d matching spaces", len(matches)), + }, + }, nil + + } + + target, err := os.Readlink(matches[0]) + if err != nil { + appctx.GetLogger(ctx).Error().Err(err).Str("match", matches[0]).Msg("could not read link, skipping") + } + + n, err := node.ReadNode(ctx, fs.lu, filepath.Base(target)) + if err != nil { + return nil, err + } + + newnode := *n + newnode.Name = strings.Split(n.Name, node.TrashIDDelimiter)[0] + newnode.Exists = false + + err = fs.tp.Move(ctx, n, &newnode) + if err != nil { + return nil, err + } + } + matches, err := filepath.Glob(filepath.Join(fs.o.Root, "spaces", spaceTypeAny, spaceID)) if err != nil { return nil, err @@ -377,8 +419,29 @@ func (fs *Decomposedfs) DeleteStorageSpace(ctx context.Context, req *provider.De _, purge = opaque.Map["purge"] } + spaceID := req.Id.OpaqueId + + matches, err := filepath.Glob(filepath.Join(fs.o.Root, "spaces", spaceTypeAny, spaceID)) + if err != nil { + return err + } + + if len(matches) != 1 { + return fmt.Errorf("delete space failed: found %d matching spaces", len(matches)) + } + + target, err := os.Readlink(matches[0]) + if err != nil { + appctx.GetLogger(ctx).Error().Err(err).Str("match", matches[0]).Msg("could not read link, skipping") + } + + n, err := node.ReadNode(ctx, fs.lu, filepath.Base(target)) + if err != nil { + return err + } + if purge { - if !strings.Contains(req.Id.OpaqueId, node.TrashIDDelimiter) { + if !strings.Contains(n.Name, node.TrashIDDelimiter) { return errtypes.NewErrtypeFromStatus(status.NewInvalidArg(ctx, "can't purge enabled space")) } ip := fs.lu.InternalPath(req.Id.OpaqueId) @@ -400,31 +463,33 @@ func (fs *Decomposedfs) DeleteStorageSpace(ctx context.Context, req *provider.De return fmt.Errorf("delete space failed: found %d matching spaces", len(matches)) } - return os.RemoveAll(matches[0]) - } - - spaceID := req.Id.OpaqueId - - matches, err := filepath.Glob(filepath.Join(fs.o.Root, "spaces", spaceTypeAny, spaceID)) - if err != nil { - return err - } + if err := os.RemoveAll(matches[0]); err != nil { + return err + } - if len(matches) != 1 { - return fmt.Errorf("delete space failed: found %d matching spaces", len(matches)) - } + matches, err = filepath.Glob(filepath.Join(fs.o.Root, "nodes", "root", req.Id.OpaqueId+node.TrashIDDelimiter+"*")) + if err != nil { + return err + } - target, err := os.Readlink(matches[0]) - if err != nil { - appctx.GetLogger(ctx).Error().Err(err).Str("match", matches[0]).Msg("could not read link, skipping") - } + if len(matches) != 1 { + return fmt.Errorf("delete root node failed: found %d matching root nodes", len(matches)) + } +<<<<<<< HEAD n, err := node.ReadNode(ctx, fs.lu, spaceID, filepath.Base(target)) if err != nil { return err - } - - err = fs.tp.Delete(ctx, n) +======= + return os.RemoveAll(matches[0]) +>>>>>>> fa24ac39 (Restoring Spaces (#2458)) + } + // don't delete - just rename + dn := *n + deletionTime := time.Now().UTC().Format(time.RFC3339Nano) + dn.Name = n.Name + node.TrashIDDelimiter + deletionTime + dn.Exists = false + err = fs.tp.Move(ctx, n, &dn) if err != nil { return err } @@ -434,15 +499,9 @@ func (fs *Decomposedfs) DeleteStorageSpace(ctx context.Context, req *provider.De return err } - trashPathMatches, err := filepath.Glob(n.InternalPath() + node.TrashIDDelimiter + "*") - if err != nil { - return err - } - if len(trashPathMatches) != 1 { - return fmt.Errorf("delete space failed: found %d matching trashed spaces", len(trashPathMatches)) - } - trashPath := trashPathMatches[0] - return os.Symlink(trashPath, filepath.Join(filepath.Dir(matches[0]), filepath.Base(trashPath))) + trashPath := dn.InternalPath() + np := filepath.Join(filepath.Dir(matches[0]), filepath.Base(trashPath)) + return os.Symlink(trashPath, np) } // createHiddenSpaceFolder bootstraps a storage space root with a hidden ".space" folder used to store space related @@ -487,8 +546,15 @@ func (fs *Decomposedfs) storageSpaceFromNode(ctx context.Context, n *node.Node, if err != nil || !ok { return nil, errtypes.PermissionDenied(fmt.Sprintf("user %s is not allowed to Stat the space %s", user.Username, n.SpaceRoot.ID)) } - if n.SpaceRoot != nil && strings.Contains(n.SpaceRoot.ID, node.TrashIDDelimiter) { - return nil, errtypes.PermissionDenied(fmt.Sprintf("user %s is not allowed to list deleted spaces %s", user.Username, n.SpaceRoot.ID)) + + if strings.Contains(n.Name, node.TrashIDDelimiter) { + ok, err := node.NewPermissions(fs.lu).HasPermission(ctx, n, func(p *provider.ResourcePermissions) bool { + // TODO: Which permission do I need to see the space? + return p.AddGrant + }) + if err != nil || !ok { + return nil, errtypes.PermissionDenied(fmt.Sprintf("user %s is not allowed to list deleted spaces %s", user.Username, n.SpaceRoot.ID)) + } } } @@ -563,6 +629,13 @@ func (fs *Decomposedfs) storageSpaceFromNode(ctx context.Context, n *node.Node, // Mtime is set either as node.tmtime or as fi.mtime below } + if strings.Contains(n.Name, node.TrashIDDelimiter) { + space.Opaque.Map["trashed"] = &types.OpaqueEntry{ + Decoder: "plain", + Value: []byte("trashed"), + } + } + space.Owner = &userv1beta1.User{ // FIXME only return a UserID, not a full blown user object Id: owner, }