diff --git a/pkg/storage/fs/ocis/node.go b/pkg/storage/fs/ocis/node.go index 3a07e8b7d31..c96da222f14 100644 --- a/pkg/storage/fs/ocis/node.go +++ b/pkg/storage/fs/ocis/node.go @@ -30,7 +30,6 @@ import ( types "github.com/cs3org/go-cs3apis/cs3/types/v1beta1" "github.com/cs3org/reva/pkg/appctx" "github.com/cs3org/reva/pkg/mime" - "github.com/google/uuid" "github.com/pkg/errors" "github.com/pkg/xattr" ) @@ -46,32 +45,8 @@ type Node struct { Exists bool } -// CreateDir creates a new child directory node with a new id and the given name -// owner is optional -// TODO use in tree CreateDir -// TODO beware the below implementation allows creating two nodes with the same name -// TODO also create link to parent -func (n *Node) CreateDir(pw *Path, name string, owner *userpb.UserId) (c *Node, err error) { - c = &Node{ - pw: pw, - ParentID: n.ID, - ID: uuid.New().String(), - Name: name, - } - - // create a directory node - nodePath := filepath.Join(pw.Root, "nodes", c.ID) - if err = os.MkdirAll(nodePath, 0700); err != nil { - return nil, errors.Wrap(err, "ocisfs: error creating node child dir") - } - - err = c.writeMetadata(nodePath, owner) - - c.Exists = true - return -} - -func (n *Node) writeMetadata(nodePath string, owner *userpb.UserId) (err error) { +func (n *Node) writeMetadata(owner *userpb.UserId) (err error) { + nodePath := filepath.Join(n.pw.Root, "nodes", n.ID) if err = xattr.Set(nodePath, "user.ocis.parentid", []byte(n.ParentID)); err != nil { return errors.Wrap(err, "ocisfs: could not set parentid attribute") } @@ -113,12 +88,7 @@ func ReadNode(ctx context.Context, pw *Path, id string) (n *Node, err error) { } var root *Node - if pw.EnableHome { - root, err = pw.HomeNode(ctx) - } else { - root, err = pw.RootNode(ctx) - } - if err != nil { + if root, err = pw.HomeOrRootNode(ctx); err != nil { return } parentID := n.ParentID diff --git a/pkg/storage/fs/ocis/ocis.go b/pkg/storage/fs/ocis/ocis.go index 8e7c823a1c0..59cac4b0357 100644 --- a/pkg/storage/fs/ocis/ocis.go +++ b/pkg/storage/fs/ocis/ocis.go @@ -23,7 +23,6 @@ import ( "io" "net/url" "os" - "path" "path/filepath" "strings" @@ -152,20 +151,14 @@ func (fs *ocisfs) CreateHome(ctx context.Context) (err error) { if n, err = fs.pw.RootNode(ctx); err != nil { return } - u := user.ContextMustGetUser(ctx) - layout := templates.WithUser(u, fs.pw.UserLayout) - - segments := strings.Split(layout, "/") - for i := range segments { - if n, err = n.Child(segments[i]); err != nil { - return - } + _, err = fs.pw.WalkPath(ctx, n, fs.pw.mustGetUserLayout(ctx), func(ctx context.Context, n *Node) error { if !n.Exists { - if err = fs.tp.CreateDir(ctx, n); err != nil { - return + if err := fs.tp.CreateDir(ctx, n); err != nil { + return err } } - } + return nil + }) return } @@ -195,43 +188,11 @@ func (fs *ocisfs) CreateDir(ctx context.Context, fn string) (err error) { return fs.tp.CreateDir(ctx, node) } -// The share folder, aka "/Shares", is a virtual thing. -func (fs *ocisfs) isShareFolder(ctx context.Context, p string) bool { - // TODO what about a folder '/Sharesabc' ... would also match - return strings.HasPrefix(p, fs.pw.ShareFolder) -} - -func (fs *ocisfs) isShareFolderRoot(ctx context.Context, p string) bool { - return path.Clean(p) == fs.pw.ShareFolder -} - -func (fs *ocisfs) isShareFolderChild(ctx context.Context, p string) bool { - p = path.Clean(p) - vals := strings.Split(p, fs.pw.ShareFolder+"/") - return len(vals) > 1 && vals[1] != "" -} -func (fs *ocisfs) getInternalHome(ctx context.Context) (string, error) { - if !fs.pw.EnableHome { - return "", errtypes.NotSupported("ocisfs: get home not enabled") - } - - u, ok := user.ContextGetUser(ctx) - if !ok { - return "", errtypes.InternalError("ocisfs: wrap: no user in ctx and home is enabled") - } - - relativeHome := templates.WithUser(u, fs.pw.UserLayout) - return relativeHome, nil -} - -// TODO there really is no difference between the /Shares folder and normal nodes -// we can store it is a node -// but when home is enabled the "/Shares" folder should not be listed ... -// so when listing home we need to check that flag and add the shared node - -// /Shares/mounted1 -// /Shares/mounted1/foo/bar -// references should go into a Shares folder +// CreateReference creates a reference as a node folder with the target stored in extended attributes +// There is no difference between the /Shares folder and normal nodes because the storage is not supposed to be accessible without the storage provider. +// In effect everything is a shadow namespace. +// To mimic the eos end owncloud driver we only allow references as children of the "/Shares" folder +// TODO when home support is enabled should the "/Shares" folder still be listed? func (fs *ocisfs) CreateReference(ctx context.Context, p string, targetURI *url.URL) (err error) { p = strings.Trim(p, "/") @@ -260,6 +221,7 @@ func (fs *ocisfs) CreateReference(ctx context.Context, p string, targetURI *url. } if n.Exists { + // TODO append increasing number to mountpoint name return errtypes.AlreadyExists(p) } @@ -269,7 +231,7 @@ func (fs *ocisfs) CreateReference(ctx context.Context, p string, targetURI *url. internal := filepath.Join(fs.pw.Root, "nodes", n.ID) if err = xattr.Set(internal, referenceAttr, []byte(targetURI.String())); err != nil { - return errors.Wrapf(err, "ocfs: error setting the target %s on the reference file %s", targetURI.String(), internal) + return errors.Wrapf(err, "ocisfs: error setting the target %s on the reference file %s", targetURI.String(), internal) } return nil } diff --git a/pkg/storage/fs/ocis/path.go b/pkg/storage/fs/ocis/path.go index 4c13570e493..20a6cbb13d4 100644 --- a/pkg/storage/fs/ocis/path.go +++ b/pkg/storage/fs/ocis/path.go @@ -37,7 +37,7 @@ type Path struct { // ocis fs works on top of a dir of uuid nodes Root string `mapstructure:"root"` - // UserLayout describles the relative path from the storages root node to the users home node. + // UserLayout describes the relative path from the storage's root node to the users home node. UserLayout string `mapstructure:"user_layout"` // TODO NodeLayout option to save nodes as eg. nodes/1d/d8/1dd84abf-9466-4e14-bb86-02fc4ea3abcf @@ -65,25 +65,16 @@ func (pw *Path) NodeFromResource(ctx context.Context, ref *provider.Reference) ( func (pw *Path) NodeFromPath(ctx context.Context, fn string) (node *Node, err error) { log := appctx.GetLogger(ctx) log.Debug().Interface("fn", fn).Msg("NodeFromPath()") - if pw.EnableHome { - node, err = pw.HomeNode(ctx) - } else { - node, err = pw.RootNode(ctx) - } - if err != nil { + + if node, err = pw.HomeOrRootNode(ctx); err != nil { return } if fn != "/" { - // walk the path - segments := strings.Split(strings.TrimLeft(fn, "/"), "/") - for i := range segments { - if node, err = node.Child(segments[i]); err != nil { - log.Error().Err(err).Interface("node", node).Str("segment", segments[i]).Msg("NodeFromPath()") - break - } - log.Debug().Interface("node", node).Str("segment", segments[i]).Msg("NodeFromPath()") - } + node, err = pw.WalkPath(ctx, node, fn, func(ctx context.Context, n *Node) error { + log.Debug().Interface("node", node).Msg("NodeFromPath() walk") + return nil + }) } return @@ -100,12 +91,7 @@ func (pw *Path) NodeFromID(ctx context.Context, id *provider.ResourceId) (n *Nod // Path returns the path for node func (pw *Path) Path(ctx context.Context, n *Node) (p string, err error) { var root *Node - if pw.EnableHome { - root, err = pw.HomeNode(ctx) - } else { - root, err = pw.RootNode(ctx) - } - if err != nil { + if root, err = pw.HomeOrRootNode(ctx); err != nil { return } for n.ID != root.ID { @@ -145,7 +131,7 @@ func (pw *Path) readRootLink(root string) (node *Node, err error) { return } -// RootNode returns the root node of a tree +// RootNode returns the root node of the storage func (pw *Path) RootNode(ctx context.Context) (node *Node, err error) { return &Node{ pw: pw, @@ -165,14 +151,38 @@ func (pw *Path) HomeNode(ctx context.Context) (node *Node, err error) { if node, err = pw.RootNode(ctx); err != nil { return } - u := user.ContextMustGetUser(ctx) - layout := templates.WithUser(u, pw.UserLayout) + node, err = pw.WalkPath(ctx, node, pw.mustGetUserLayout(ctx), nil) + return +} - segments := strings.Split(layout, "/") +// WalkPath calls n.Child(segment) on every path segment in p starting at the node r +// If a function f is given it will be executed for every segment node, but not the root node r +func (pw *Path) WalkPath(ctx context.Context, r *Node, p string, f func(ctx context.Context, n *Node) error) (*Node, error) { + segments := strings.Split(strings.Trim(p, "/"), "/") + var err error for i := range segments { - if node, err = node.Child(segments[i]); err != nil { - return + if r, err = r.Child(segments[i]); err != nil { + return r, err + } + if f != nil { + if err = f(ctx, r); err != nil { + return r, err + } } } - return + return r, nil +} + +// HomeOrRootNode returns the users home node when home support is enabled. +// it returns the storages root node otherwise +func (pw *Path) HomeOrRootNode(ctx context.Context) (node *Node, err error) { + if pw.EnableHome { + return pw.HomeNode(ctx) + } + return pw.RootNode(ctx) +} + +func (pw *Path) mustGetUserLayout(ctx context.Context) string { + u := user.ContextMustGetUser(ctx) + return templates.WithUser(u, pw.UserLayout) } diff --git a/pkg/storage/fs/ocis/persistence.go b/pkg/storage/fs/ocis/persistence.go index 411a96a5fbe..5b9dcc16fc7 100644 --- a/pkg/storage/fs/ocis/persistence.go +++ b/pkg/storage/fs/ocis/persistence.go @@ -20,7 +20,6 @@ package ocis import ( "context" - "net/url" "os" provider "github.com/cs3org/go-cs3apis/cs3/storage/provider/v1beta1" @@ -33,7 +32,7 @@ type TreePersistence interface { ListFolder(ctx context.Context, node *Node) ([]*Node, error) //CreateHome(owner *userpb.UserId) (n *Node, err error) CreateDir(ctx context.Context, node *Node) (err error) - CreateReference(ctx context.Context, node *Node, targetURI *url.URL) error + //CreateReference(ctx context.Context, node *Node, targetURI *url.URL) error Move(ctx context.Context, oldNode *Node, newNode *Node) (err error) Delete(ctx context.Context, node *Node) (err error) @@ -47,7 +46,14 @@ type PathWrapper interface { NodeFromPath(ctx context.Context, fn string) (node *Node, err error) Path(ctx context.Context, node *Node) (path string, err error) + // HomeNode returns the currently logged in users home node + // requires EnableHome to be true + HomeNode(ctx context.Context) (node *Node, err error) + + // RootNode returns the storage root node RootNode(ctx context.Context) (node *Node, err error) - // Root returns the internal root of the storage - Root() string + + // HomeOrRootNode returns the users home node when home support is enabled. + // it returns the storages root node otherwise + HomeOrRootNode(ctx context.Context) (node *Node, err error) } diff --git a/pkg/storage/fs/ocis/tree.go b/pkg/storage/fs/ocis/tree.go index 7134e2d0bcc..518f638117b 100644 --- a/pkg/storage/fs/ocis/tree.go +++ b/pkg/storage/fs/ocis/tree.go @@ -22,7 +22,6 @@ import ( "context" "encoding/hex" "math/rand" - "net/url" "os" "path/filepath" "time" @@ -99,11 +98,10 @@ func createNode(n *Node, owner *userpb.UserId) (err error) { return errors.Wrap(err, "ocisfs: error creating node") } - return n.writeMetadata(nodePath, owner) + return n.writeMetadata(owner) } // CreateDir creates a new directory entry in the tree -// TODO use parentnode and name instead of node? would make the exists stuff clearer? maybe obsolete? func (t *Tree) CreateDir(ctx context.Context, node *Node) (err error) { if node.Exists || node.ID != "" { @@ -115,13 +113,17 @@ func (t *Tree) CreateDir(ctx context.Context, node *Node) (err error) { if t.pw.EnableHome { if u, ok := user.ContextGetUser(ctx); ok { - createNode(node, u.Id) + err = createNode(node, u.Id) } else { log := appctx.GetLogger(ctx) - log.Error().Msg("home enabled but no user in context") + log.Error().Msg("home support enabled but no user in context") + err = errors.Wrap(errtypes.UserRequired("userrequired"), "error getting user from ctx") } } else { - createNode(node, nil) + err = createNode(node, nil) + } + if err != nil { + return nil } // make child appear in listings @@ -132,12 +134,6 @@ func (t *Tree) CreateDir(ctx context.Context, node *Node) (err error) { return t.Propagate(ctx, node) } -// CreateReference creates a new reference entry in the tree -// TODO the -func (t *Tree) CreateReference(ctx context.Context, node *Node, targetURI *url.URL) error { - return errtypes.NotSupported("operation not supported: CreateReference") -} - // Move replaces the target with the source func (t *Tree) Move(ctx context.Context, oldNode *Node, newNode *Node) (err error) { // if target exists delete it without trashing it @@ -283,12 +279,8 @@ func (t *Tree) Propagate(ctx context.Context, node *Node) (err error) { // store in extended attribute etag := hex.EncodeToString(bytes) var root *Node - if t.pw.EnableHome { - root, err = t.pw.HomeNode(ctx) - } else { - root, err = t.pw.RootNode(ctx) - } - if err != nil { + + if root, err = t.pw.HomeOrRootNode(ctx); err != nil { return } for err == nil && node.ID != root.ID { // TODO propagate up to where? diff --git a/pkg/storage/fs/ocis/upload.go b/pkg/storage/fs/ocis/upload.go index cf35fcbe7c3..7899b9af4a4 100644 --- a/pkg/storage/fs/ocis/upload.go +++ b/pkg/storage/fs/ocis/upload.go @@ -30,11 +30,11 @@ import ( userpb "github.com/cs3org/go-cs3apis/cs3/identity/user/v1beta1" provider "github.com/cs3org/go-cs3apis/cs3/storage/provider/v1beta1" "github.com/cs3org/reva/pkg/appctx" + "github.com/cs3org/reva/pkg/errtypes" "github.com/cs3org/reva/pkg/logger" "github.com/cs3org/reva/pkg/user" "github.com/google/uuid" "github.com/pkg/errors" - "github.com/pkg/xattr" "github.com/rs/zerolog/log" tusd "github.com/tus/tusd/pkg/handler" ) @@ -324,7 +324,7 @@ func (upload *fileUpload) writeInfo() error { } // FinishUpload finishes an upload and moves the file to the internal destination -func (upload *fileUpload) FinishUpload(ctx context.Context) error { +func (upload *fileUpload) FinishUpload(ctx context.Context) (err error) { n := &Node{ pw: upload.fs.pw, @@ -339,53 +339,51 @@ func (upload *fileUpload) FinishUpload(ctx context.Context) error { targetPath := filepath.Join(upload.fs.pw.Root, "nodes", n.ID) // if target exists create new version - if fi, err := os.Stat(targetPath); err == nil { + var fi os.FileInfo + if fi, err = os.Stat(targetPath); err == nil { // versions are stored alongside the actual file, so a rename can be efficient and does not cross storage / partition boundaries versionsPath := filepath.Join(upload.fs.pw.Root, "nodes", n.ID+".REV."+fi.ModTime().UTC().Format(time.RFC3339Nano)) - if err := os.Rename(targetPath, versionsPath); err != nil { + if err = os.Rename(targetPath, versionsPath); err != nil { log := appctx.GetLogger(upload.ctx) log.Err(err).Interface("info", upload.info). Str("binPath", upload.binPath). Str("targetPath", targetPath). Msg("ocisfs: could not create version") - return err + return } } // now rename the upload to the target path // TODO put uploads on the same underlying storage as the destination dir? // TODO trigger a workflow as the final rename might eg involve antivirus scanning - if err := os.Rename(upload.binPath, targetPath); err != nil { + if err = os.Rename(upload.binPath, targetPath); err != nil { log := appctx.GetLogger(upload.ctx) log.Err(err).Interface("info", upload.info). Str("binPath", upload.binPath). Str("targetPath", targetPath). Msg("ocisfs: could not rename") - return err - } - - if err := xattr.Set(targetPath, "user.ocis.parentid", []byte(n.ParentID)); err != nil { - return errors.Wrap(err, "ocisfs: could not set parentid attribute") - } - if err := xattr.Set(targetPath, "user.ocis.name", []byte(n.Name)); err != nil { - return errors.Wrap(err, "ocisfs: could not set name attribute") - } - if u, ok := user.ContextGetUser(ctx); ok { - if err := xattr.Set(targetPath, "user.ocis.owner.id", []byte(u.Id.OpaqueId)); err != nil { - return errors.Wrap(err, "ocisfs: could not set owner id attribute") - } - if err := xattr.Set(targetPath, "user.ocis.owner.idp", []byte(u.Id.Idp)); err != nil { - return errors.Wrap(err, "ocisfs: could not set owner idp attribute") + return + } + if n.pw.EnableHome { + if u, ok := user.ContextGetUser(ctx); ok { + err = n.writeMetadata(u.Id) + } else { + log := appctx.GetLogger(ctx) + log.Error().Msg("home support enabled but no user in context") + err = errors.Wrap(errtypes.UserRequired("userrequired"), "error getting user from ctx") } } else { - log := appctx.GetLogger(upload.ctx) - log.Error().Msg("home enabled but no user in context") + err = n.writeMetadata(nil) + } + if err != nil { + return } // link child name to parent if it is new childNameLink := filepath.Join(upload.fs.pw.Root, "nodes", n.ParentID, n.Name) - link, err := os.Readlink(childNameLink) + var link string + link, err = os.Readlink(childNameLink) if err == nil && link != "../"+n.ID { log.Err(err). Interface("info", upload.info). @@ -395,7 +393,7 @@ func (upload *fileUpload) FinishUpload(ctx context.Context) error { Str("link", link). Msg("ocisfs: child name link has wrong target id, repairing") - if err := os.Remove(childNameLink); err != nil { + if err = os.Remove(childNameLink); err != nil { return errors.Wrap(err, "ocisfs: could not remove symlink child entry") } } @@ -406,10 +404,10 @@ func (upload *fileUpload) FinishUpload(ctx context.Context) error { } // only delete the upload if it was successfully written to the storage - if err := os.Remove(upload.infoPath); err != nil { + if err = os.Remove(upload.infoPath); err != nil { if !os.IsNotExist(err) { log.Err(err).Interface("info", upload.info).Msg("ocisfs: could not delete upload info") - return err + return } } // use set arbitrary metadata?