Skip to content

Commit

Permalink
more spaces work
Browse files Browse the repository at this point in the history
Signed-off-by: Jörn Friedrich Dreyer <jfd@butonic.de>
  • Loading branch information
butonic committed May 6, 2021
1 parent f23fdbc commit e840a2c
Show file tree
Hide file tree
Showing 4 changed files with 150 additions and 32 deletions.
3 changes: 3 additions & 0 deletions internal/http/services/owncloud/ocdav/dav.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
42 changes: 37 additions & 5 deletions internal/http/services/owncloud/ocdav/spaces.go
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down Expand Up @@ -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,
Expand All @@ -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)
Expand All @@ -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)
Expand All @@ -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)
Expand Down Expand Up @@ -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)
Expand Down
71 changes: 44 additions & 27 deletions pkg/storage/utils/decomposedfs/decomposedfs.go
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ package decomposedfs
import (
"context"
"io"
"math"
"net/url"
"os"
"path/filepath"
Expand Down Expand Up @@ -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
Expand All @@ -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
Expand All @@ -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)
}
}
Expand Down
66 changes: 66 additions & 0 deletions pkg/storage/utils/decomposedfs/tree/tree.go
Original file line number Diff line number Diff line change
Expand Up @@ -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())
Expand Down

0 comments on commit e840a2c

Please sign in to comment.