diff --git a/pkg/storage/fs/owncloud/owncloud.go b/pkg/storage/fs/owncloud/owncloud.go index 5d79662aac..0daf1996f3 100644 --- a/pkg/storage/fs/owncloud/owncloud.go +++ b/pkg/storage/fs/owncloud/owncloud.go @@ -152,12 +152,13 @@ func init() { } type config struct { - DataDirectory string `mapstructure:"datadirectory"` - UploadInfoDir string `mapstructure:"upload_info_dir"` - UserLayout string `mapstructure:"user_layout"` - Redis string `mapstructure:"redis"` - EnableHome bool `mapstructure:"enable_home"` - Scan bool `mapstructure:"scan"` + DataDirectory string `mapstructure:"datadirectory"` + UploadInfoDir string `mapstructure:"upload_info_dir"` + ShareDirectory string `mapstructure:"sharedirectory"` + UserLayout string `mapstructure:"user_layout"` + Redis string `mapstructure:"redis"` + EnableHome bool `mapstructure:"enable_home"` + Scan bool `mapstructure:"scan"` } func parseConfig(m map[string]interface{}) (*config, error) { @@ -179,6 +180,9 @@ func (c *config) init(m map[string]interface{}) { if c.UploadInfoDir == "" { c.UploadInfoDir = "/var/tmp/reva/uploadinfo" } + if c.ShareDirectory == "" { + c.ShareDirectory = "/Shares" + } // default to scanning if not configured if _, ok := m["scan"]; !ok { c.Scan = true @@ -313,7 +317,37 @@ func (fs *ocfs) wrap(ctx context.Context, fn string) (internal string) { return } -// owncloud stores versions in the files_versions subfolder +func (fs *ocfs) wrapShadow(ctx context.Context, fn string) (internal string) { + if fs.c.EnableHome { + u := user.ContextMustGetUser(ctx) + layout := templates.WithUser(u, fs.c.UserLayout) + internal = path.Join(fs.c.DataDirectory, layout, "shadow_files", fn) + } else { + // trim all / + fn = strings.Trim(fn, "/") + // p = "" or + // p = or + // p = /foo/bar.txt + parts := strings.SplitN(fn, "/", 2) + + switch len(parts) { + case 1: + // parts = "" or "" + if parts[0] == "" { + internal = fs.c.DataDirectory + return + } + // parts = "" + internal = path.Join(fs.c.DataDirectory, parts[0], "shadow_files") + default: + // parts = "", "foo/bar.txt" + internal = path.Join(fs.c.DataDirectory, parts[0], "shadow_files", parts[1]) + } + } + return +} + +// ownloud stores versions in the files_versions subfolder // the incoming path starts with /, so we need to insert the files subfolder into the path // and prefix the data directory // TODO the path handed to a storage provider should not contain the username @@ -365,6 +399,10 @@ func (fs *ocfs) unwrap(ctx context.Context, internal string) (external string) { layout := templates.WithUser(u, fs.c.UserLayout) trim := path.Join(fs.c.DataDirectory, layout, "files") external = strings.TrimPrefix(internal, trim) + // root directory + if external == "" { + external = "/" + } } else { // np = /data//files/foo/bar.txt // remove data dir @@ -392,6 +430,39 @@ func (fs *ocfs) unwrap(ctx context.Context, internal string) (external string) { return } +func (fs *ocfs) unwrapShadow(ctx context.Context, internal string) (external string) { + if fs.c.EnableHome { + u := user.ContextMustGetUser(ctx) + layout := templates.WithUser(u, fs.c.UserLayout) + trim := path.Join(fs.c.DataDirectory, layout, "shadow_files") + external = strings.TrimPrefix(internal, trim) + } else { + // np = /data//shadow_files/foo/bar.txt + // remove data dir + if fs.c.DataDirectory != "/" { + // fs.c.DataDirectory is a clean path, so it never ends in / + internal = strings.TrimPrefix(internal, fs.c.DataDirectory) + // np = //shadow_files/foo/bar.txt + } + + parts := strings.SplitN(internal, "/", 4) + // parts = "", "", "shadow_files", "foo/bar.txt" + switch len(parts) { + case 1: + external = "/" + case 2: + external = path.Join("/", parts[1]) + case 3: + external = path.Join("/", parts[1]) + default: + external = path.Join("/", parts[1], parts[3]) + } + } + log := appctx.GetLogger(ctx) + log.Debug().Msgf("ocfs: unwrapShadow: internal=%s external=%s", internal, external) + return +} + // TODO the owner needs to come from a different place func (fs *ocfs) getOwner(internal string) string { internal = strings.TrimPrefix(internal, fs.c.DataDirectory) @@ -402,9 +473,8 @@ func (fs *ocfs) getOwner(internal string) string { return "" } -func (fs *ocfs) convertToResourceInfo(ctx context.Context, fi os.FileInfo, np string, c redis.Conn, mdKeys []string) *provider.ResourceInfo { +func (fs *ocfs) convertToResourceInfo(ctx context.Context, fi os.FileInfo, np string, fn string, c redis.Conn, mdKeys []string) *provider.ResourceInfo { id := readOrCreateID(ctx, np, c) - fn := fs.unwrap(ctx, path.Join("/", np)) etag := calcEtag(ctx, fi) @@ -937,6 +1007,7 @@ func (fs *ocfs) CreateHome(ctx context.Context) error { path.Join(fs.c.DataDirectory, layout, "files_trashbin"), path.Join(fs.c.DataDirectory, layout, "files_versions"), path.Join(fs.c.DataDirectory, layout, "uploads"), + path.Join(fs.c.DataDirectory, layout, "shadow_files"), } for _, v := range homePaths { @@ -968,9 +1039,36 @@ func (fs *ocfs) CreateDir(ctx context.Context, fn string) (err error) { return fs.propagate(ctx, np) } -func (fs *ocfs) CreateReference(ctx context.Context, path string, targetURI *url.URL) error { - // TODO(jfd): implement - return errtypes.NotSupported("ocfs: operation not supported") +func (fs *ocfs) isShareFolderChild(p string) bool { + return strings.HasPrefix(p, fs.c.ShareDirectory) +} + +func (fs *ocfs) isShareFolderRoot(p string) bool { + return p == fs.c.ShareDirectory +} + +func (fs *ocfs) CreateReference(ctx context.Context, p string, targetURI *url.URL) error { + if !fs.isShareFolderChild(p) { + return errtypes.PermissionDenied("ocfs: cannot create references outside the share folder: share_folder=" + "/Shares" + " path=" + p) + } + + fn := fs.wrapShadow(ctx, p) + + dir, _ := path.Split(fn) + if err := os.MkdirAll(dir, 0700); err != nil { + return errors.Wrapf(err, "ocfs: error creating shadow path %s", dir) + } + + f, err := os.Create(fn) + if err != nil { + return errors.Wrapf(err, "ocfs: error creating shadow file %s", fn) + } + + err = xattr.FSet(f, mdPrefix+"target", []byte(targetURI.String())) + if err != nil { + return errors.Wrapf(err, "ocfs: error setting the target %s on the shadow file %s", targetURI.String(), fn) + } + return nil } func (fs *ocfs) setMtime(ctx context.Context, np string, mtimeString string) error { @@ -1314,7 +1412,19 @@ func (fs *ocfs) GetMD(ctx context.Context, ref *provider.Reference, mdKeys []str if err != nil { return nil, errors.Wrap(err, "ocfs: error resolving reference") } + p := fs.unwrap(ctx, np) + + if fs.c.EnableHome { + if fs.isShareFolderChild(p) { + return fs.getMDShareFolder(ctx, p, mdKeys) + } + } + // If GetMD is called for a path shared with the user then the path is + // already wrapped. (fs.resolve wraps the path) + if strings.HasPrefix(p, fs.c.DataDirectory) { + np = p + } md, err := os.Stat(np) if err != nil { if os.IsNotExist(err) { @@ -1324,34 +1434,153 @@ func (fs *ocfs) GetMD(ctx context.Context, ref *provider.Reference, mdKeys []str } c := fs.pool.Get() defer c.Close() - m := fs.convertToResourceInfo(ctx, md, np, c, mdKeys) + m := fs.convertToResourceInfo(ctx, md, np, fs.unwrap(ctx, np), c, mdKeys) + + return m, nil +} + +func (fs *ocfs) getMDShareFolder(ctx context.Context, p string, mdKeys []string) (*provider.ResourceInfo, error) { + fn := fs.wrapShadow(ctx, p) + md, err := os.Stat(fn) + if err != nil { + if os.IsNotExist(err) { + return nil, errtypes.NotFound(fs.unwrapShadow(ctx, fn)) + } + return nil, errors.Wrapf(err, "ocfs: error stating %s", fn) + } + c := fs.pool.Get() + defer c.Close() + m := fs.convertToResourceInfo(ctx, md, fn, fs.unwrapShadow(ctx, fn), c, mdKeys) + if !fs.isShareFolderRoot(p) { + m.Type = provider.ResourceType_RESOURCE_TYPE_REFERENCE + ref, err := xattr.Get(fn, mdPrefix+"target") + if err != nil { + return nil, err + } + m.Target = string(ref) + } return m, nil } func (fs *ocfs) ListFolder(ctx context.Context, ref *provider.Reference, mdKeys []string) ([]*provider.ResourceInfo, error) { + log := appctx.GetLogger(ctx) + np, err := fs.resolve(ctx, ref) if err != nil { return nil, errors.Wrap(err, "ocfs: error resolving reference") } + p := fs.unwrap(ctx, np) - mds, err := ioutil.ReadDir(np) - if err != nil { - if os.IsNotExist(err) { - return nil, errtypes.NotFound(fs.unwrap(ctx, np)) + if fs.c.EnableHome { + log.Debug().Msg("home enabled") + if strings.HasPrefix(p, "/") { + return fs.listWithHome(ctx, "/", p, mdKeys) } - return nil, errors.Wrap(err, "ocfs: error listing "+np) } - finfos := make([]*provider.ResourceInfo, 0, len(mds)) - // TODO we should only open a connection if we need to set / store the fileid. no need to always open a connection when listing files + log.Debug().Msg("list with nominal home") + return fs.listWithNominalHome(ctx, p, mdKeys) +} + +func (fs *ocfs) listWithNominalHome(ctx context.Context, p string, mdKeys []string) ([]*provider.ResourceInfo, error) { + fn := p + // If a user wants to list a folder shared with him the path will already + // be wrapped with the files directory path of the share owner. + // In that case we don't want to wrap the path again. + if !strings.HasPrefix(p, fs.c.DataDirectory) { + fn = fs.wrap(ctx, p) + } + mds, err := ioutil.ReadDir(fn) + if err != nil { + return nil, errors.Wrapf(err, "ocfs: error listing %s", fn) + } c := fs.pool.Get() defer c.Close() - for i := range mds { - p := path.Join(np, mds[i].Name()) - m := fs.convertToResourceInfo(ctx, mds[i], p, c, mdKeys) + finfos := []*provider.ResourceInfo{} + for _, md := range mds { + p := path.Join(fn, md.Name()) + m := fs.convertToResourceInfo(ctx, md, p, fs.unwrap(ctx, p), c, mdKeys) + finfos = append(finfos, m) + } + return finfos, nil +} + +func (fs *ocfs) listWithHome(ctx context.Context, home, p string, mdKeys []string) ([]*provider.ResourceInfo, error) { + log := appctx.GetLogger(ctx) + if p == home { + log.Debug().Msg("listing home") + return fs.listHome(ctx, home, mdKeys) + } + + if fs.isShareFolderRoot(p) { + log.Debug().Msg("listing share folder root") + return fs.listShareFolderRoot(ctx, p, mdKeys) + } + + if fs.isShareFolderChild(p) { + return nil, errtypes.PermissionDenied("ocfs: error listing folders inside the shared folder, only file references are stored inside") + } + + log.Debug().Msg("listing nominal home") + return fs.listWithNominalHome(ctx, p, mdKeys) +} + +func (fs *ocfs) listHome(ctx context.Context, home string, mdKeys []string) ([]*provider.ResourceInfo, error) { + // list files + fn := fs.wrap(ctx, home) + mds, err := ioutil.ReadDir(fn) + if err != nil { + return nil, errors.Wrap(err, "ocfs: error listing files") + } + + c := fs.pool.Get() + defer c.Close() + + finfos := []*provider.ResourceInfo{} + for _, md := range mds { + p := path.Join(fn, md.Name()) + m := fs.convertToResourceInfo(ctx, md, p, fs.unwrap(ctx, p), c, mdKeys) finfos = append(finfos, m) } + + // list shadow_files + fn = fs.wrapShadow(ctx, home) + mds, err = ioutil.ReadDir(fn) + if err != nil { + return nil, errors.Wrap(err, "ocfs: error listing shadow_files") + } + for _, md := range mds { + p := path.Join(fn, md.Name()) + m := fs.convertToResourceInfo(ctx, md, p, fs.unwrapShadow(ctx, p), c, mdKeys) + finfos = append(finfos, m) + } + return finfos, nil +} + +func (fs *ocfs) listShareFolderRoot(ctx context.Context, p string, mdKeys []string) ([]*provider.ResourceInfo, error) { + fn := fs.wrapShadow(ctx, p) + mds, err := ioutil.ReadDir(fn) + if err != nil { + return nil, errors.Wrap(err, "ocfs: error listing shadow_files") + } + + c := fs.pool.Get() + defer c.Close() + + finfos := []*provider.ResourceInfo{} + for _, md := range mds { + p := path.Join(fn, md.Name()) + m := fs.convertToResourceInfo(ctx, md, p, fs.unwrapShadow(ctx, p), c, mdKeys) + m.Type = provider.ResourceType_RESOURCE_TYPE_REFERENCE + ref, err := xattr.Get(p, mdPrefix+"target") + if err != nil { + return nil, err + } + m.Target = string(ref) + finfos = append(finfos, m) + } + return finfos, nil }