From e840a2cbf78879b335a9ee45e630627b3a2b02d7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=B6rn=20Friedrich=20Dreyer?= Date: Thu, 6 May 2021 21:40:35 +0000 Subject: [PATCH] more spaces work MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Jörn Friedrich Dreyer --- internal/http/services/owncloud/ocdav/dav.go | 3 + .../http/services/owncloud/ocdav/spaces.go | 42 +++++++++-- .../utils/decomposedfs/decomposedfs.go | 71 ++++++++++++------- pkg/storage/utils/decomposedfs/tree/tree.go | 66 +++++++++++++++++ 4 files changed, 150 insertions(+), 32 deletions(-) diff --git a/internal/http/services/owncloud/ocdav/dav.go b/internal/http/services/owncloud/ocdav/dav.go index dee4cc1507f..7fae59a4aa0 100644 --- a/internal/http/services/owncloud/ocdav/dav.go +++ b/internal/http/services/owncloud/ocdav/dav.go @@ -72,6 +72,9 @@ func (h *DavHandler) init(c *Config) error { h.TrashbinHandler = new(TrashbinHandler) h.SpacesHandler = new(SpacesHandler) + if err := h.SpacesHandler.init(c); err != nil { + return err + } h.PublicFolderHandler = new(WebDavHandler) if err := h.PublicFolderHandler.init("public", true); err != nil { // jail public file requests to /public/ prefix diff --git a/internal/http/services/owncloud/ocdav/spaces.go b/internal/http/services/owncloud/ocdav/spaces.go index d009f4f0c66..f9768f673eb 100644 --- a/internal/http/services/owncloud/ocdav/spaces.go +++ b/internal/http/services/owncloud/ocdav/spaces.go @@ -20,6 +20,7 @@ package ocdav import ( "net/http" + "path/filepath" rpc "github.com/cs3org/go-cs3apis/cs3/rpc/v1beta1" storageProvider "github.com/cs3org/go-cs3apis/cs3/storage/provider/v1beta1" @@ -119,8 +120,9 @@ func (s *svc) handleSpacesPropfind(w http.ResponseWriter, r *http.Request, space } } } - req := &storageProvider.ListStorageSpacesRequest{ + // retrieve a specific storage space + lSSReq := &storageProvider.ListStorageSpacesRequest{ Filters: []*storageProvider.ListStorageSpacesRequest_Filter{ { Type: storageProvider.ListStorageSpacesRequest_Filter_TYPE_ID, @@ -133,13 +135,43 @@ func (s *svc) handleSpacesPropfind(w http.ResponseWriter, r *http.Request, space }, } + lSSRes, err := gatewayClient.ListStorageSpaces(ctx, lSSReq) + if err != nil { + sublog.Error().Err(err).Interface("req", lSSReq).Msg("error sending a grpc stat request") + w.WriteHeader(http.StatusInternalServerError) + return + } + + if lSSRes.Status.Code != rpc.Code_CODE_OK { + HandleErrorStatus(&sublog, w, lSSRes.Status) + return + } + + if len(lSSRes.StorageSpaces) != 1 { + sublog.Error().Err(err).Interface("req", lSSReq).Msg("unexpected number of spaces") + return + } + space := lSSRes.StorageSpaces[0] + // TODO: // Use ResourceId to make request to the actual storage provider via the gateway. // - Copy the storageId from the storage space root // - set the opaque Id to /storageSpaceId/relativePath in // Correct fix would be to add a new Reference to the CS3API + ref := &storageProvider.Reference{ + Spec: &storageProvider.Reference_Id{ + Id: &storageProvider.ResourceId{ + StorageId: space.Root.StorageId, + OpaqueId: filepath.Join("/", spaceId, path), // FIXME this is a hack to pass storage space id and a relative path to the storage provider + }, + }, + } - res, err := gatewayClient.ListStorageSpaces(ctx, req) + req := &storageProvider.StatRequest{ + Ref: ref, + ArbitraryMetadataKeys: metadataKeys, + } + res, err := gatewayClient.Stat(ctx, req) if err != nil { sublog.Error().Err(err).Interface("req", req).Msg("error sending a grpc stat request") w.WriteHeader(http.StatusInternalServerError) @@ -158,7 +190,7 @@ func (s *svc) handleSpacesPropfind(w http.ResponseWriter, r *http.Request, space Ref: ref, ArbitraryMetadataKeys: metadataKeys, } - res, err := client.ListContainer(ctx, req) + res, err := gatewayClient.ListContainer(ctx, req) if err != nil { sublog.Error().Err(err).Msg("error sending list container grpc request") w.WriteHeader(http.StatusInternalServerError) @@ -184,7 +216,7 @@ func (s *svc) handleSpacesPropfind(w http.ResponseWriter, r *http.Request, space Ref: ref, ArbitraryMetadataKeys: metadataKeys, } - res, err := client.ListContainer(ctx, req) + res, err := gatewayClient.ListContainer(ctx, req) if err != nil { sublog.Error().Err(err).Str("path", path).Msg("error sending list container grpc request") w.WriteHeader(http.StatusInternalServerError) @@ -216,7 +248,7 @@ func (s *svc) handleSpacesPropfind(w http.ResponseWriter, r *http.Request, space } } - propRes, err := s.formatPropfind(ctx, &pf, infos, ns) + propRes, err := s.formatPropfind(ctx, &pf, infos, spaceId) if err != nil { sublog.Error().Err(err).Msg("error formatting propfind") w.WriteHeader(http.StatusInternalServerError) diff --git a/pkg/storage/utils/decomposedfs/decomposedfs.go b/pkg/storage/utils/decomposedfs/decomposedfs.go index 39f04b64453..329175c55ec 100644 --- a/pkg/storage/utils/decomposedfs/decomposedfs.go +++ b/pkg/storage/utils/decomposedfs/decomposedfs.go @@ -24,6 +24,7 @@ package decomposedfs import ( "context" "io" + "math" "net/url" "os" "path/filepath" @@ -486,30 +487,20 @@ func (fs *Decomposedfs) ListStorageSpaces(ctx context.Context, filter []*provide // /nodes/root/personal/foo and /nodes/root/shares/foo might be two very different spaces, a /nodes/root/foo is not expressive enough // we would not need /nodes/root if access always happened via spaceid+relative path - // 1. how many subdirs are in the user layout? - parts := strings.Split(fs.o.UserLayout, "/") - - var sb strings.Builder - sb.WriteString("*") - for i := 1; i < len(parts); i++ { - sb.WriteString("/*") - } - - // fs.o.Root + layout(eg e/einstein) - // /var/lib/ocis/storage/users/nodes/root/e/einstein - matches, err := filepath.Glob(filepath.Join(fs.o.Root, "nodes", "root", sb.String())) + // /var/lib/ocis/storage/users/spaces/personal/nodeid + // /var/lib/ocis/storage/users/spaces/shared/nodeid + matches, err := filepath.Glob(filepath.Join(fs.o.Root, "spaces/*/*")) if err != nil { return nil, err } var spaces []*provider.StorageSpace for i := range matches { - // use Stat to fetch metadata + // always read link in case storage space id != node id if target, err := os.Readlink(matches[i]); err != nil { // TODO log error continue } else { - // fi.Name() should be the node id n, err := node.ReadNode(ctx, fs.lu, filepath.Base(target)) if err != nil { continue @@ -518,19 +509,20 @@ func (fs *Decomposedfs) ListStorageSpaces(ctx context.Context, filter []*provide if err != nil { continue } + + // TODO continue if not owner or share grantee, depending on filter? + space := &provider.StorageSpace{ Id: &provider.StorageSpaceId{OpaqueId: n.ID}, // FIXME Id should just be a string Owner: &userv1beta1.User{ // FIXME only return a UserID, not a full blown user object Id: owner, }, - Root: &provider.ResourceId{OpaqueId: n.ID}, + Root: &provider.ResourceId{ + StorageId: "1284d238-aa92-42ce-bdc4-0b0000009157", // FIXME storage provider id needs to be returned so the gateway can route + OpaqueId: n.ID, + }, //Name: // TODO read from extended attribute - //Quota: // TODO use decompesodfs to read quota - //Quota: &provider.Quota{ - // QuotaMaxBytes: 0, - // QuotaMaxFiles: 0, - //}, - SpaceType: "personal", + SpaceType: filepath.Base(filepath.Dir(matches[i])), // Mtime is set either as node.tmtime or as fi.mtime below } // override the stat mtime with a tmtime if it is present @@ -540,13 +532,38 @@ func (fs *Decomposedfs) ListStorageSpaces(ctx context.Context, filter []*provide Seconds: uint64(un / 1000000000), Nanos: uint32(un % 1000000000), } - //} else { - //un := TODO stat node - //space.Mtime = &types.Timestamp{ - // Seconds: uint64(un / 1000000000), - // Nanos: uint32(un % 1000000000), - //} + } else { + // fall back to stat mtime + if fi, err := os.Stat(matches[i]); err == nil { + un := fi.ModTime().UnixNano() + space.Mtime = &types.Timestamp{ + Seconds: uint64(un / 1000000000), + Nanos: uint32(un % 1000000000), + } + } } + + // quota + v, err := xattr.Get(matches[i], xattrs.QuotaAttr) + switch { + case err == nil: + // make sure we have a proper signed int + // we use the same magic numbers to indicate: + // -1 = uncalculated + // -2 = unknown + // -3 = unlimited + if quota, err := strconv.ParseInt(string(v), 10, 64); err == nil { + if quota >= 0 { + space.Quota = &provider.Quota{ + QuotaMaxBytes: uint64(quota), + QuotaMaxFiles: math.MaxUint64, // TODO MaxUInt64? = unlimited? why even max files? 0 = unlimited? + } + } + } else { + appctx.GetLogger(ctx).Debug().Err(err).Str("nodepath", matches[i]).Msg("could not read quota") + } + } + spaces = append(spaces, space) } } diff --git a/pkg/storage/utils/decomposedfs/tree/tree.go b/pkg/storage/utils/decomposedfs/tree/tree.go index a6f07008713..e7ea4a15744 100644 --- a/pkg/storage/utils/decomposedfs/tree/tree.go +++ b/pkg/storage/utils/decomposedfs/tree/tree.go @@ -114,9 +114,75 @@ func (t *Tree) Setup(owner string) error { if err != nil { return err } + + // create spaces folder and iterate over existing nodes to populate it + spacesPath := filepath.Join(t.root, "spaces") + fi, err := os.Stat(spacesPath) + if os.IsNotExist(err) { + // create personal spaces dir + if err := os.MkdirAll(filepath.Join(spacesPath, "personal"), 0700); err != nil { + return err + } + // create share spaces dir + if err := os.MkdirAll(filepath.Join(spacesPath, "share"), 0700); err != nil { + return err + } + + f, err := os.Open(filepath.Join(t.root, "nodes")) + if err != nil { + return err + } + nodes, err := f.Readdir(0) + if err != nil { + return err + } + + for i := range nodes { + nodePath := filepath.Join(t.root, "nodes", nodes[i].Name()) + + // is it a user root? -> create personal space + if isRootNode(nodePath) { + // create personal space + // we can reuse the node id as the space id + err = os.Symlink("../../nodes/"+nodes[i].Name(), filepath.Join(t.root, "spaces/personal", nodes[i].Name())) + if err != nil { + fmt.Printf("could not create symlink for personal space %s, %s\n", nodes[i].Name(), err) + } + } + + // is it a shared node? -> create shared space + if isSharedNode(nodePath) { + err = os.Symlink("../../nodes/"+nodes[i].Name(), filepath.Join(t.root, "spaces/share", nodes[i].Name())) + if err != nil { + fmt.Printf("could not create symlink for shared space %s, %s\n", nodes[i].Name(), err) + } + } + } + } else { + // check if it is a directory + if !fi.IsDir() { + return fmt.Errorf("%s is not a directory", spacesPath) + } + } + return nil } +func isRootNode(nodePath string) bool { + attrBytes, err := xattr.Get(nodePath, xattrs.ParentidAttr) + return err == nil && string(attrBytes) == "root" +} +func isSharedNode(nodePath string) bool { + if attrs, err := xattr.List(nodePath); err == nil { + for i := range attrs { + if strings.HasPrefix(attrs[i], xattrs.GrantPrefix) { + return true + } + } + } + return false +} + // GetMD returns the metadata of a node in the tree func (t *Tree) GetMD(ctx context.Context, n *node.Node) (os.FileInfo, error) { md, err := os.Stat(n.InternalPath())