mirror of
https://github.com/opencloud-eu/opencloud.git
synced 2026-07-19 12:04:09 -04:00
[full-ci] chore: reva bump -2.47.0 (#3127)
This commit is contained in:
51
vendor/github.com/opencloud-eu/reva/v2/internal/http/services/owncloud/ocdav/net/permissions.go
generated
vendored
Normal file
51
vendor/github.com/opencloud-eu/reva/v2/internal/http/services/owncloud/ocdav/net/permissions.go
generated
vendored
Normal file
@@ -0,0 +1,51 @@
|
||||
// Copyright 2018-2026 CERN
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
//
|
||||
// In applying this license, CERN does not waive the privileges and immunities
|
||||
// granted to it by virtue of its status as an Intergovernmental Organization
|
||||
// or submit itself to any jurisdiction.
|
||||
|
||||
package net
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
|
||||
link "github.com/cs3org/go-cs3apis/cs3/sharing/link/v1beta1"
|
||||
provider "github.com/cs3org/go-cs3apis/cs3/storage/provider/v1beta1"
|
||||
|
||||
"github.com/opencloud-eu/reva/v2/pkg/conversions"
|
||||
)
|
||||
|
||||
// WebDAVPermissions derives the WebDAV permissions string (the OC-Perm header value) for a
|
||||
// resource. It is shared by the ocdav gateway's creation-with-upload response and the
|
||||
// dataprovider's chunked TUS finalize response, so the two report the same permissions.
|
||||
func WebDAVPermissions(ctx context.Context, ri *provider.ResourceInfo) string {
|
||||
isPublic := false
|
||||
if o := ri.GetOpaque(); o != nil && o.Map != nil {
|
||||
if e := o.Map["link-share"]; e != nil && e.Decoder == "json" {
|
||||
ls := &link.PublicShare{}
|
||||
_ = json.Unmarshal(e.Value, ls)
|
||||
isPublic = ls != nil
|
||||
}
|
||||
}
|
||||
isShared := !IsCurrentUserOwnerOrManager(ctx, ri.GetOwner(), ri)
|
||||
role := conversions.RoleFromResourcePermissions(ri.GetPermissionSet(), isPublic)
|
||||
return role.WebDAVPermissions(
|
||||
ri.GetType() == provider.ResourceType_RESOURCE_TYPE_CONTAINER,
|
||||
isShared,
|
||||
false,
|
||||
isPublic,
|
||||
)
|
||||
}
|
||||
21
vendor/github.com/opencloud-eu/reva/v2/internal/http/services/owncloud/ocdav/tus.go
generated
vendored
21
vendor/github.com/opencloud-eu/reva/v2/internal/http/services/owncloud/ocdav/tus.go
generated
vendored
@@ -20,7 +20,6 @@ package ocdav
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"io"
|
||||
"net/http"
|
||||
"path"
|
||||
@@ -30,14 +29,12 @@ import (
|
||||
"time"
|
||||
|
||||
rpc "github.com/cs3org/go-cs3apis/cs3/rpc/v1beta1"
|
||||
link "github.com/cs3org/go-cs3apis/cs3/sharing/link/v1beta1"
|
||||
provider "github.com/cs3org/go-cs3apis/cs3/storage/provider/v1beta1"
|
||||
typespb "github.com/cs3org/go-cs3apis/cs3/types/v1beta1"
|
||||
"github.com/opencloud-eu/reva/v2/internal/http/services/owncloud/ocdav/errors"
|
||||
"github.com/opencloud-eu/reva/v2/internal/http/services/owncloud/ocdav/net"
|
||||
"github.com/opencloud-eu/reva/v2/internal/http/services/owncloud/ocdav/spacelookup"
|
||||
"github.com/opencloud-eu/reva/v2/pkg/appctx"
|
||||
"github.com/opencloud-eu/reva/v2/pkg/conversions"
|
||||
"github.com/opencloud-eu/reva/v2/pkg/rhttp"
|
||||
"github.com/opencloud-eu/reva/v2/pkg/storagespace"
|
||||
"github.com/opencloud-eu/reva/v2/pkg/utils"
|
||||
@@ -374,23 +371,7 @@ func (s *svc) handleTusPost(ctx context.Context, w http.ResponseWriter, r *http.
|
||||
return
|
||||
}
|
||||
|
||||
// get WebDav permissions for file
|
||||
isPublic := false
|
||||
if info.Opaque != nil && info.Opaque.Map != nil {
|
||||
if info.Opaque.Map["link-share"] != nil && info.Opaque.Map["link-share"].Decoder == "json" {
|
||||
ls := &link.PublicShare{}
|
||||
_ = json.Unmarshal(info.Opaque.Map["link-share"].Value, ls)
|
||||
isPublic = ls != nil
|
||||
}
|
||||
}
|
||||
isShared := !net.IsCurrentUserOwnerOrManager(ctx, info.Owner, info)
|
||||
role := conversions.RoleFromResourcePermissions(info.PermissionSet, isPublic)
|
||||
permissions := role.WebDAVPermissions(
|
||||
info.Type == provider.ResourceType_RESOURCE_TYPE_CONTAINER,
|
||||
isShared,
|
||||
false,
|
||||
isPublic,
|
||||
)
|
||||
permissions := net.WebDAVPermissions(ctx, info)
|
||||
|
||||
w.Header().Set(net.HeaderContentType, info.MimeType)
|
||||
w.Header().Set(net.HeaderOCFileID, storagespace.FormatResourceID(info.Id))
|
||||
|
||||
39
vendor/github.com/opencloud-eu/reva/v2/pkg/rhttp/datatx/manager/tus/tus.go
generated
vendored
39
vendor/github.com/opencloud-eu/reva/v2/pkg/rhttp/datatx/manager/tus/tus.go
generated
vendored
@@ -108,6 +108,9 @@ func (m *manager) Handler(fs storage.FS) (http.Handler, error) {
|
||||
StoreComposer: composer,
|
||||
NotifyCompleteUploads: true,
|
||||
Logger: slog.New(tusdLogger{log: m.log}),
|
||||
// Add the finalized resource's etag and permissions to the last chunk's response
|
||||
// (see preFinishResponseCallback). https://github.com/opencloud-eu/opencloud/issues/2409
|
||||
PreFinishResponseCallback: m.preFinishResponseCallback(fs),
|
||||
}
|
||||
|
||||
if m.conf.CorsEnabled {
|
||||
@@ -207,6 +210,42 @@ func (m *manager) Handler(fs storage.FS) (http.Handler, error) {
|
||||
return h, nil
|
||||
}
|
||||
|
||||
// preFinishResponseCallback returns the tusd callback that runs when an upload completes. Before
|
||||
// the final response is written, it looks up the finalized resource and attaches its etag and
|
||||
// WebDAV permissions to the response headers, so clients do not need a follow-up PROPFIND after
|
||||
// the last chunk. It mirrors the etag and permissions the ocdav gateway already produces for the
|
||||
// creation-with-upload path. The storage driver resolves the resource as the upload's executant
|
||||
// (the data path's transfer token carries no user identity), so this layer does not reconstruct
|
||||
// the user. It is best effort: it logs any lookup failure and still completes the upload (the
|
||||
// client falls back to a PROPFIND). https://github.com/opencloud-eu/opencloud/issues/2409
|
||||
func (m *manager) preFinishResponseCallback(fs storage.FS) func(tusd.HookEvent) (tusd.HTTPResponse, error) {
|
||||
resolver, ok := fs.(storage.UploadSessionResolver)
|
||||
return func(hook tusd.HookEvent) (tusd.HTTPResponse, error) {
|
||||
resp := tusd.HTTPResponse{Header: tusd.HTTPHeader{}}
|
||||
if !ok {
|
||||
// the storage driver cannot resolve the upload; the client falls back to a PROPFIND
|
||||
return resp, nil
|
||||
}
|
||||
|
||||
ri, ctx, err := resolver.ResolveUpload(hook.Context, hook.Upload)
|
||||
if err != nil || ri == nil {
|
||||
// The stat can fail for a finished upload (for example an expired upload token or a
|
||||
// removed node); the client recovers with a PROPFIND, so this is a warning, not an error.
|
||||
m.log.Warn().Err(err).Str("uploadid", hook.Upload.ID).Msg("could not stat finished upload, not setting etag/permission headers")
|
||||
return resp, nil
|
||||
}
|
||||
|
||||
if etag := ri.GetEtag(); etag != "" {
|
||||
resp.Header[net.HeaderOCETag] = etag
|
||||
resp.Header[net.HeaderETag] = etag
|
||||
}
|
||||
|
||||
resp.Header[net.HeaderOCPermissions] = net.WebDAVPermissions(ctx, ri)
|
||||
|
||||
return resp, nil
|
||||
}
|
||||
}
|
||||
|
||||
func setHeaders(fs storage.FS, w http.ResponseWriter, r *http.Request) {
|
||||
ctx := r.Context()
|
||||
id := path.Base(r.URL.Path)
|
||||
|
||||
89
vendor/github.com/opencloud-eu/reva/v2/pkg/storage/fs/posix/blobstore/blobstore.go
generated
vendored
89
vendor/github.com/opencloud-eu/reva/v2/pkg/storage/fs/posix/blobstore/blobstore.go
generated
vendored
@@ -26,28 +26,34 @@ import (
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
"syscall"
|
||||
"time"
|
||||
|
||||
"github.com/pkg/errors"
|
||||
"github.com/pkg/xattr"
|
||||
|
||||
"github.com/opencloud-eu/reva/v2/pkg/storage/pkg/decomposedfs/disk"
|
||||
"github.com/opencloud-eu/reva/v2/pkg/storage/pkg/decomposedfs/metadata/prefixes"
|
||||
"github.com/opencloud-eu/reva/v2/pkg/storage/pkg/decomposedfs/node"
|
||||
)
|
||||
|
||||
const (
|
||||
TMPDir = ".oc-tmp"
|
||||
TMPDir = ".oc-tmp"
|
||||
fsyncEvery = 16 << 20 // 16 MiB
|
||||
)
|
||||
|
||||
// Blobstore provides an interface to an filesystem based blobstore
|
||||
type Blobstore struct {
|
||||
root string
|
||||
|
||||
canUseRenameForUpload bool
|
||||
}
|
||||
|
||||
// New returns a new Blobstore
|
||||
func New(root string) (*Blobstore, error) {
|
||||
return &Blobstore{
|
||||
root: root,
|
||||
root: root,
|
||||
canUseRenameForUpload: true, // let's assume the upload area is on the same device as the blobstore root by default
|
||||
}, nil
|
||||
}
|
||||
|
||||
@@ -61,29 +67,40 @@ func (bs *Blobstore) Upload(n *node.Node, source, copyTarget string) error {
|
||||
return err
|
||||
}
|
||||
|
||||
sourceFile, err := os.Open(source)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to open source file '%s': %v", source, err)
|
||||
}
|
||||
defer func() {
|
||||
_ = sourceFile.Close()
|
||||
}()
|
||||
|
||||
tempFile, err := os.OpenFile(tempName, os.O_CREATE|os.O_WRONLY|os.O_TRUNC, 0600)
|
||||
if err != nil {
|
||||
return fmt.Errorf("unable to create temp file '%s': %v", tempName, err)
|
||||
if bs.canUseRenameForUpload {
|
||||
err := os.Rename(source, tempName)
|
||||
switch {
|
||||
case err == nil:
|
||||
// continue
|
||||
case errors.Is(err, syscall.EXDEV):
|
||||
// the upload and target file are on different devices, we need to copy the file instead of renaming it
|
||||
bs.canUseRenameForUpload = false
|
||||
default:
|
||||
return fmt.Errorf("failed to move source file '%s' to temp file '%s' - %v", source, tempName, err)
|
||||
}
|
||||
}
|
||||
|
||||
if _, err := tempFile.ReadFrom(sourceFile); err != nil {
|
||||
return fmt.Errorf("failed to write data from source file '%s' to temp file '%s' - %v", source, tempName, err)
|
||||
}
|
||||
if !bs.canUseRenameForUpload {
|
||||
sourceFile, err := os.Open(source)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to open source file '%s': %v", source, err)
|
||||
}
|
||||
defer func() {
|
||||
_ = sourceFile.Close()
|
||||
}()
|
||||
|
||||
if err := tempFile.Sync(); err != nil {
|
||||
return fmt.Errorf("failed to sync temp file '%s' - %v", tempName, err)
|
||||
}
|
||||
tempFile, err := os.OpenFile(tempName, os.O_CREATE|os.O_WRONLY|os.O_TRUNC, 0600)
|
||||
if err != nil {
|
||||
return fmt.Errorf("unable to create temp file '%s': %v", tempName, err)
|
||||
}
|
||||
|
||||
if err := tempFile.Close(); err != nil {
|
||||
return fmt.Errorf("failed to close temp file '%s' - %v", tempName, err)
|
||||
if err := copyWithPeriodicSync(tempFile, sourceFile); err != nil {
|
||||
return fmt.Errorf("failed to copy source file '%s' to temp file '%s' - %v", source, tempName, err)
|
||||
}
|
||||
|
||||
if err := tempFile.Close(); err != nil {
|
||||
return fmt.Errorf("failed to close temp file '%s' - %v", tempName, err)
|
||||
}
|
||||
}
|
||||
|
||||
nodeAttributes, err := n.Xattrs(context.Background())
|
||||
@@ -137,11 +154,15 @@ func (bs *Blobstore) Upload(n *node.Node, source, copyTarget string) error {
|
||||
}
|
||||
|
||||
// also "upload" the file to a local path, e.g., for keeping the "current" version of the file
|
||||
if err := os.MkdirAll(filepath.Dir(copyTarget), 0700); err != nil {
|
||||
return err
|
||||
sourceFile, err := os.Open(source)
|
||||
if err != nil {
|
||||
return errors.Wrapf(err, "could not open source file '%s' for reading", source)
|
||||
}
|
||||
defer func() {
|
||||
_ = sourceFile.Close()
|
||||
}()
|
||||
|
||||
if _, err := sourceFile.Seek(0, 0); err != nil {
|
||||
if err := os.MkdirAll(filepath.Dir(copyTarget), 0700); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
@@ -153,7 +174,7 @@ func (bs *Blobstore) Upload(n *node.Node, source, copyTarget string) error {
|
||||
_ = copyFile.Close()
|
||||
}()
|
||||
|
||||
if _, err := copyFile.ReadFrom(sourceFile); err != nil {
|
||||
if err := copyWithPeriodicSync(copyFile, sourceFile); err != nil {
|
||||
return errors.Wrapf(err, "could not write blob copy of '%s' to '%s'", n.InternalPath(), copyTarget)
|
||||
}
|
||||
|
||||
@@ -173,3 +194,21 @@ func (bs *Blobstore) Download(node *node.Node) (io.ReadCloser, error) {
|
||||
func (bs *Blobstore) Delete(node *node.Node) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func copyWithPeriodicSync(dst, src *os.File) error {
|
||||
for {
|
||||
n, err := io.CopyN(dst, src, fsyncEvery)
|
||||
if n > 0 {
|
||||
if serr := disk.Fdatasync(dst); serr != nil {
|
||||
return serr
|
||||
}
|
||||
}
|
||||
if err == io.EOF {
|
||||
_ = dst.Sync()
|
||||
return nil
|
||||
}
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
51
vendor/github.com/opencloud-eu/reva/v2/pkg/storage/fs/posix/lookup/lookup.go
generated
vendored
51
vendor/github.com/opencloud-eu/reva/v2/pkg/storage/fs/posix/lookup/lookup.go
generated
vendored
@@ -41,7 +41,6 @@ import (
|
||||
"github.com/opencloud-eu/reva/v2/pkg/storage/pkg/decomposedfs/usermapper"
|
||||
"github.com/opencloud-eu/reva/v2/pkg/storage/utils/templates"
|
||||
"github.com/pkg/errors"
|
||||
"github.com/rogpeppe/go-internal/lockedfile"
|
||||
"github.com/rs/zerolog"
|
||||
"go.opentelemetry.io/otel"
|
||||
"go.opentelemetry.io/otel/trace"
|
||||
@@ -398,46 +397,14 @@ func refFromCS3(b []byte) (*provider.Reference, error) {
|
||||
}, nil
|
||||
}
|
||||
|
||||
// CopyMetadata copies all extended attributes from source to target.
|
||||
// The optional filter function can be used to filter by attribute name, e.g. by checking a prefix
|
||||
// For the source file, a shared lock is acquired.
|
||||
// NOTE: target resource will be write locked!
|
||||
func (lu *Lookup) CopyMetadata(ctx context.Context, src, target metadata.MetadataNode, filter func(attributeName string, value []byte) (newValue []byte, copy bool), acquireTargetLock bool) (err error) {
|
||||
// Acquire a read log on the source node
|
||||
// write lock existing node before reading treesize or tree time
|
||||
lock, err := lockedfile.OpenFile(lu.MetadataBackend().LockfilePath(src), os.O_RDONLY|os.O_CREATE, 0600)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "xattrs: Unable to lock source to read")
|
||||
}
|
||||
defer func() {
|
||||
rerr := lock.Close()
|
||||
|
||||
// if err is non nil we do not overwrite that
|
||||
if err == nil {
|
||||
err = rerr
|
||||
}
|
||||
}()
|
||||
|
||||
return lu.CopyMetadataWithSourceLock(ctx, src, target, filter, lock, acquireTargetLock)
|
||||
}
|
||||
|
||||
// CopyMetadataWithSourceLock copies all extended attributes from source to target.
|
||||
// The optional filter function can be used to filter by attribute name, e.g. by checking a prefix
|
||||
// For the source file, a matching lockedfile is required.
|
||||
// NOTE: target resource will be write locked!
|
||||
func (lu *Lookup) CopyMetadataWithSourceLock(ctx context.Context, src, target metadata.MetadataNode, filter func(attributeName string, value []byte) (newValue []byte, copy bool), lockedSource *lockedfile.File, acquireTargetLock bool) (err error) {
|
||||
switch {
|
||||
case lockedSource == nil:
|
||||
return errors.New("no lock provided")
|
||||
case lockedSource.Name() != lu.MetadataBackend().LockfilePath(src):
|
||||
return errors.New("lockpath does not match filepath")
|
||||
}
|
||||
|
||||
attrs, err := lu.metadataBackend.AllWithLockedSource(ctx, src, lockedSource)
|
||||
// CopyMetadata copies all extended attributes from source to target. The optional filter
|
||||
// function can be used to filter by attribute name, e.g. by checking a prefix.
|
||||
//
|
||||
// Locking is derived from the nodes: the source's metadata lock is acquired for the duration
|
||||
// of the read unless the caller already holds it (src.LockHeld()), and the target is write
|
||||
// locked while its attributes are written unless the caller already holds its lock
|
||||
func (lu *Lookup) CopyMetadata(ctx context.Context, src, target metadata.MetadataNode, filter func(attributeName string, value []byte) (newValue []byte, copy bool)) (err error) {
|
||||
attrs, err := lu.metadataBackend.All(ctx, src)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -453,7 +420,7 @@ func (lu *Lookup) CopyMetadataWithSourceLock(ctx context.Context, src, target me
|
||||
newAttrs[attrName] = val
|
||||
}
|
||||
|
||||
return lu.MetadataBackend().SetMultiple(ctx, target, newAttrs, acquireTargetLock)
|
||||
return lu.MetadataBackend().SetMultiple(ctx, target, newAttrs)
|
||||
}
|
||||
|
||||
// GenerateSpaceID generates a space id for the given space type and owner
|
||||
|
||||
13
vendor/github.com/opencloud-eu/reva/v2/pkg/storage/fs/posix/posix.go
generated
vendored
13
vendor/github.com/opencloud-eu/reva/v2/pkg/storage/fs/posix/posix.go
generated
vendored
@@ -26,9 +26,11 @@ import (
|
||||
"syscall"
|
||||
"time"
|
||||
|
||||
provider "github.com/cs3org/go-cs3apis/cs3/storage/provider/v1beta1"
|
||||
"github.com/rs/zerolog"
|
||||
tusd "github.com/tus/tusd/v2/pkg/handler"
|
||||
|
||||
"github.com/opencloud-eu/reva/v2/pkg/errtypes"
|
||||
"github.com/opencloud-eu/reva/v2/pkg/events"
|
||||
"github.com/opencloud-eu/reva/v2/pkg/rgrpc/todo/pool"
|
||||
"github.com/opencloud-eu/reva/v2/pkg/storage"
|
||||
@@ -244,6 +246,17 @@ func (fs *posixFS) ListUploadSessions(ctx context.Context, filter storage.Upload
|
||||
return fs.FS.(storage.UploadSessionLister).ListUploadSessions(ctx, filter)
|
||||
}
|
||||
|
||||
// ResolveUpload resolves a finished upload to the resource it produced, read as the upload's
|
||||
// executant. It is best effort: if the wrapped storage does not resolve uploads it returns
|
||||
// NotSupported rather than panicking, so the tus finalize falls back to no headers.
|
||||
func (fs *posixFS) ResolveUpload(ctx context.Context, info tusd.FileInfo) (*provider.ResourceInfo, context.Context, error) {
|
||||
r, ok := fs.FS.(storage.UploadSessionResolver)
|
||||
if !ok {
|
||||
return nil, ctx, errtypes.NotSupported("storage does not resolve upload sessions")
|
||||
}
|
||||
return r.ResolveUpload(ctx, info)
|
||||
}
|
||||
|
||||
// UseIn tells the tus upload middleware which extensions it supports.
|
||||
func (fs *posixFS) UseIn(composer *tusd.StoreComposer) {
|
||||
fs.FS.(storage.ComposableFS).UseIn(composer)
|
||||
|
||||
@@ -68,7 +68,7 @@ func (m *Manager) TMTime(ctx context.Context, n *node.Node) (time.Time, error) {
|
||||
// If tmtime is nil, the tmtime attribute is removed.
|
||||
func (m *Manager) SetTMTime(ctx context.Context, n *node.Node, tmtime *time.Time) error {
|
||||
if tmtime == nil {
|
||||
return n.RemoveXattr(ctx, prefixes.TreeMTimeAttr, true)
|
||||
return n.RemoveXattr(ctx, prefixes.TreeMTimeAttr)
|
||||
}
|
||||
return n.SetXattrString(ctx, prefixes.TreeMTimeAttr, tmtime.UTC().Format(time.RFC3339Nano))
|
||||
}
|
||||
@@ -113,7 +113,7 @@ func (m *Manager) DTime(ctx context.Context, n *node.Node) (tmTime time.Time, er
|
||||
// If t is nil, the dtime attribute is removed.
|
||||
func (m *Manager) SetDTime(ctx context.Context, n *node.Node, t *time.Time) (err error) {
|
||||
if t == nil {
|
||||
return n.RemoveXattr(ctx, prefixes.DTimeAttr, true)
|
||||
return n.RemoveXattr(ctx, prefixes.DTimeAttr)
|
||||
}
|
||||
return n.SetXattrString(ctx, prefixes.DTimeAttr, t.UTC().Format(time.RFC3339Nano))
|
||||
}
|
||||
|
||||
16
vendor/github.com/opencloud-eu/reva/v2/pkg/storage/fs/posix/trashbin/trashbin.go
generated
vendored
16
vendor/github.com/opencloud-eu/reva/v2/pkg/storage/fs/posix/trashbin/trashbin.go
generated
vendored
@@ -40,6 +40,7 @@ import (
|
||||
"github.com/opencloud-eu/reva/v2/pkg/storage"
|
||||
"github.com/opencloud-eu/reva/v2/pkg/storage/fs/posix/lookup"
|
||||
"github.com/opencloud-eu/reva/v2/pkg/storage/fs/posix/options"
|
||||
"github.com/opencloud-eu/reva/v2/pkg/storage/internal/goroutinelock"
|
||||
"github.com/opencloud-eu/reva/v2/pkg/storage/pkg/decomposedfs/metadata/prefixes"
|
||||
"github.com/opencloud-eu/reva/v2/pkg/storage/pkg/decomposedfs/node"
|
||||
"github.com/opencloud-eu/reva/v2/pkg/utils"
|
||||
@@ -66,6 +67,7 @@ type trashNode struct {
|
||||
spaceID string
|
||||
id string
|
||||
path string
|
||||
lock goroutinelock.Lock
|
||||
}
|
||||
|
||||
func (tn *trashNode) GetSpaceID() string {
|
||||
@@ -80,6 +82,18 @@ func (tn *trashNode) InternalPath() string {
|
||||
return tn.path
|
||||
}
|
||||
|
||||
func (tn *trashNode) LockHeld() bool {
|
||||
return tn.lock.Held()
|
||||
}
|
||||
|
||||
func (tn *trashNode) SetLockHeld(held bool) {
|
||||
if held {
|
||||
tn.lock.Hold()
|
||||
return
|
||||
}
|
||||
tn.lock.Release()
|
||||
}
|
||||
|
||||
const (
|
||||
trashHeader = `[Trash Info]`
|
||||
timeFormat = "2006-01-02T15:04:05"
|
||||
@@ -345,7 +359,7 @@ func (tb *Trashbin) RestoreRecycleItem(ctx context.Context, spaceID string, key,
|
||||
if err = tb.lu.MetadataBackend().SetMultiple(ctx, trashedNode, map[string][]byte{
|
||||
prefixes.NameAttr: []byte(filepath.Base(restorePath)),
|
||||
prefixes.ParentidAttr: []byte(parentID),
|
||||
}, true); err != nil {
|
||||
}); err != nil {
|
||||
return nil, fmt.Errorf("posixfs: failed to update trashed node metadata: %w", err)
|
||||
}
|
||||
|
||||
|
||||
50
vendor/github.com/opencloud-eu/reva/v2/pkg/storage/fs/posix/tree/assimilation.go
generated
vendored
50
vendor/github.com/opencloud-eu/reva/v2/pkg/storage/fs/posix/tree/assimilation.go
generated
vendored
@@ -42,6 +42,7 @@ import (
|
||||
"github.com/opencloud-eu/reva/v2/pkg/errtypes"
|
||||
"github.com/opencloud-eu/reva/v2/pkg/events"
|
||||
"github.com/opencloud-eu/reva/v2/pkg/storage/fs/posix/watcher"
|
||||
"github.com/opencloud-eu/reva/v2/pkg/storage/internal/goroutinelock"
|
||||
"github.com/opencloud-eu/reva/v2/pkg/storage/pkg/decomposedfs/metadata"
|
||||
"github.com/opencloud-eu/reva/v2/pkg/storage/pkg/decomposedfs/metadata/prefixes"
|
||||
"github.com/opencloud-eu/reva/v2/pkg/storage/pkg/decomposedfs/node"
|
||||
@@ -72,20 +73,33 @@ type assimilationNode struct {
|
||||
path string
|
||||
nodeId string
|
||||
spaceID string
|
||||
lock goroutinelock.Lock
|
||||
}
|
||||
|
||||
func (d assimilationNode) GetID() string {
|
||||
func (d *assimilationNode) GetID() string {
|
||||
return d.nodeId
|
||||
}
|
||||
|
||||
func (d assimilationNode) GetSpaceID() string {
|
||||
func (d *assimilationNode) GetSpaceID() string {
|
||||
return d.spaceID
|
||||
}
|
||||
|
||||
func (d assimilationNode) InternalPath() string {
|
||||
func (d *assimilationNode) InternalPath() string {
|
||||
return d.path
|
||||
}
|
||||
|
||||
func (d *assimilationNode) LockHeld() bool {
|
||||
return d.lock.Held()
|
||||
}
|
||||
|
||||
func (d *assimilationNode) SetLockHeld(held bool) {
|
||||
if held {
|
||||
d.lock.Hold()
|
||||
return
|
||||
}
|
||||
d.lock.Release()
|
||||
}
|
||||
|
||||
// NewScanDebouncer returns a new SpaceDebouncer instance
|
||||
func NewScanDebouncer(d time.Duration, f func(item scanItem)) *ScanDebouncer {
|
||||
return &ScanDebouncer{
|
||||
@@ -673,7 +687,8 @@ func (t *Tree) assimilate(item scanItem) error {
|
||||
func (t *Tree) updateFile(path, id, spaceID string, fi fs.FileInfo) (fs.FileInfo, node.Attributes, error) {
|
||||
retries := 1
|
||||
parentID := ""
|
||||
bn := assimilationNode{spaceID: spaceID, nodeId: id, path: path}
|
||||
bn := &assimilationNode{spaceID: spaceID, nodeId: id, path: path}
|
||||
bn.SetLockHeld(true)
|
||||
assimilate:
|
||||
if id != spaceID {
|
||||
// read parent
|
||||
@@ -714,7 +729,7 @@ assimilate:
|
||||
}
|
||||
}
|
||||
|
||||
attrs, err := t.lookup.MetadataBackend().AllWithLockedSource(context.Background(), bn, nil)
|
||||
attrs, err := t.lookup.MetadataBackend().All(context.Background(), bn)
|
||||
if err != nil && !metadata.IsAttrUnset(err) {
|
||||
return nil, nil, errors.Wrap(err, "failed to get item attribs")
|
||||
}
|
||||
@@ -788,6 +803,8 @@ assimilate:
|
||||
go func() {
|
||||
// Copy the previous current version to a revision
|
||||
currentNode := node.NewBaseNode(n.SpaceID, n.ID+node.CurrentIDDelimiter, t.lookup)
|
||||
currentNode.SetLockHeld(true)
|
||||
|
||||
currentPath := currentNode.InternalPath()
|
||||
stat, err := os.Stat(currentPath)
|
||||
if err == nil {
|
||||
@@ -834,7 +851,7 @@ assimilate:
|
||||
attributeName == prefixes.TypeAttr ||
|
||||
attributeName == prefixes.BlobIDAttr ||
|
||||
attributeName == prefixes.BlobsizeAttr
|
||||
}, false)
|
||||
})
|
||||
if err != nil {
|
||||
t.log.Error().Err(err).Str("currentPath", currentPath).Str("path", path).Msg("failed to copy xattrs to 'current' file")
|
||||
return
|
||||
@@ -848,7 +865,7 @@ assimilate:
|
||||
}
|
||||
|
||||
t.log.Debug().Str("path", path).Interface("attributes", attributes).Msg("setting attributes")
|
||||
err = t.lookup.MetadataBackend().SetMultiple(context.Background(), bn, attributes, false)
|
||||
err = t.lookup.MetadataBackend().SetMultiple(context.Background(), bn, attributes)
|
||||
if err != nil {
|
||||
return nil, nil, errors.Wrap(err, "failed to set attributes")
|
||||
}
|
||||
@@ -856,7 +873,7 @@ assimilate:
|
||||
// clear the status attribute if it was set before, if there was any upload to this file in progress
|
||||
// it needs notice that this file was changes meanwhile.
|
||||
if _, ok := previousAttribs[prefixes.StatusPrefix]; ok {
|
||||
err = t.lookup.MetadataBackend().Remove(context.Background(), bn, prefixes.StatusPrefix, false)
|
||||
err = t.lookup.MetadataBackend().Remove(context.Background(), bn, prefixes.StatusPrefix)
|
||||
if err != nil {
|
||||
return nil, nil, errors.Wrap(err, "failed to clear status attribute")
|
||||
}
|
||||
@@ -897,14 +914,19 @@ func (t *Tree) WarmupIDCache(root string, assimilate, onlyDirty bool) error {
|
||||
}
|
||||
|
||||
// skip irrelevant files
|
||||
if !t.Ignorer.IsSpaceRoot(path) && t.Ignorer.IsIgnored(path) {
|
||||
if info.IsDir() {
|
||||
if t.Ignorer.IsIgnored(path) {
|
||||
switch {
|
||||
case t.Ignorer.IsSpaceRoot(path):
|
||||
// ignore the space root itself, but do not skip the whole tree
|
||||
return nil
|
||||
case t.Ignorer.IsRootPath(path):
|
||||
// ignor the root path itself, but do not skip the whole tree
|
||||
return nil
|
||||
case info.IsDir():
|
||||
return filepath.SkipDir
|
||||
default:
|
||||
return nil
|
||||
}
|
||||
return nil
|
||||
}
|
||||
if t.Ignorer.IsRootPath(path) {
|
||||
return nil // ignore the root paths
|
||||
}
|
||||
|
||||
if !info.IsDir() && !info.Mode().IsRegular() {
|
||||
|
||||
14
vendor/github.com/opencloud-eu/reva/v2/pkg/storage/fs/posix/tree/revisions.go
generated
vendored
14
vendor/github.com/opencloud-eu/reva/v2/pkg/storage/fs/posix/tree/revisions.go
generated
vendored
@@ -30,7 +30,6 @@ import (
|
||||
|
||||
provider "github.com/cs3org/go-cs3apis/cs3/storage/provider/v1beta1"
|
||||
"github.com/pkg/errors"
|
||||
"github.com/rogpeppe/go-internal/lockedfile"
|
||||
|
||||
"github.com/opencloud-eu/reva/v2/pkg/appctx"
|
||||
"github.com/opencloud-eu/reva/v2/pkg/errtypes"
|
||||
@@ -46,7 +45,7 @@ import (
|
||||
// can be kept in the same location.
|
||||
|
||||
// CreateRevision creates a new version of the node
|
||||
func (tp *Tree) CreateRevision(ctx context.Context, n *node.Node, version string, f *lockedfile.File) (string, error) {
|
||||
func (tp *Tree) CreateRevision(ctx context.Context, n *node.Node, version string) (string, error) {
|
||||
revNode := node.NewBaseNode(n.SpaceID, n.ID+node.RevisionIDDelimiter+version, tp.lookup)
|
||||
versionPath := revNode.InternalPath()
|
||||
|
||||
@@ -119,13 +118,13 @@ func (tp *Tree) CreateRevision(ctx context.Context, n *node.Node, version string
|
||||
}
|
||||
|
||||
// copy blob metadata to version node
|
||||
if err := tp.lookup.CopyMetadataWithSourceLock(ctx, n, revNode, func(attributeName string, value []byte) (newValue []byte, copy bool) {
|
||||
if err := tp.lookup.CopyMetadata(ctx, n, revNode, func(attributeName string, value []byte) (newValue []byte, copy bool) {
|
||||
return value, strings.HasPrefix(attributeName, prefixes.ChecksumPrefix) ||
|
||||
attributeName == prefixes.TypeAttr ||
|
||||
attributeName == prefixes.BlobIDAttr ||
|
||||
attributeName == prefixes.BlobsizeAttr ||
|
||||
attributeName == prefixes.MTimeAttr
|
||||
}, f, true); err != nil {
|
||||
}); err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
@@ -300,7 +299,7 @@ func (tp *Tree) RestoreRevision(ctx context.Context, srcNode, targetNode metadat
|
||||
attributeName == prefixes.TypeAttr ||
|
||||
attributeName == prefixes.BlobIDAttr ||
|
||||
attributeName == prefixes.BlobsizeAttr
|
||||
}, false)
|
||||
})
|
||||
if err != nil {
|
||||
return errtypes.InternalError("failed to copy blob xattrs to old revision to node: " + err.Error())
|
||||
}
|
||||
@@ -317,8 +316,7 @@ func (tp *Tree) RestoreRevision(ctx context.Context, srcNode, targetNode metadat
|
||||
err = tp.lookup.MetadataBackend().SetMultiple(ctx, targetNode,
|
||||
map[string][]byte{
|
||||
prefixes.MTimeAttr: []byte(mtime.UTC().Format(time.RFC3339Nano)),
|
||||
},
|
||||
false)
|
||||
})
|
||||
if err != nil {
|
||||
return errtypes.InternalError("failed to set mtime attribute on node: " + err.Error())
|
||||
}
|
||||
@@ -350,7 +348,7 @@ func (tp *Tree) RestoreRevision(ctx context.Context, srcNode, targetNode metadat
|
||||
attributeName == prefixes.TypeAttr ||
|
||||
attributeName == prefixes.BlobIDAttr ||
|
||||
attributeName == prefixes.BlobsizeAttr
|
||||
}, false)
|
||||
})
|
||||
if err != nil {
|
||||
return errtypes.InternalError("failed to copy xattrs to 'current' file: " + err.Error())
|
||||
}
|
||||
|
||||
13
vendor/github.com/opencloud-eu/reva/v2/pkg/storage/fs/posix/tree/tree.go
generated
vendored
13
vendor/github.com/opencloud-eu/reva/v2/pkg/storage/fs/posix/tree/tree.go
generated
vendored
@@ -176,6 +176,9 @@ func New(lu node.PathLookup, bs node.Blobstore, um usermapper.Mapper, trashbin *
|
||||
watchPath = o.Root
|
||||
}
|
||||
|
||||
if o.ScanDebounceDelay == 0 {
|
||||
log.Warn().Msg("'scan_debounce_delay' is set to 0. This is not recommended for production setups.")
|
||||
}
|
||||
go t.watcher.Watch(watchPath)
|
||||
go t.workScanQueue()
|
||||
}
|
||||
@@ -394,7 +397,7 @@ func (t *Tree) TouchFile(ctx context.Context, n *node.Node, markprocessing bool,
|
||||
attributes[prefixes.MTimeAttr] = []byte(mtime.UTC().Format(time.RFC3339Nano))
|
||||
}
|
||||
|
||||
err = n.SetXattrsWithContext(ctx, attributes, false)
|
||||
err = n.SetXattrsWithContext(ctx, attributes)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -477,7 +480,7 @@ func (t *Tree) Move(ctx context.Context, oldNode *node.Node, newNode *node.Node)
|
||||
attribs := node.Attributes{}
|
||||
attribs.SetString(prefixes.ParentidAttr, newNode.ParentID)
|
||||
attribs.SetString(prefixes.NameAttr, newNode.Name)
|
||||
if err := newNode.SetXattrsWithContext(ctx, attribs, true); err != nil {
|
||||
if err := newNode.SetXattrsWithContext(ctx, attribs); err != nil {
|
||||
return errors.Wrap(err, "posixfs: could not update node attributes")
|
||||
}
|
||||
|
||||
@@ -726,7 +729,7 @@ func (t *Tree) WriteBlob(n *node.Node, source string) error {
|
||||
attrs.SetString(prefixes.BlobIDAttr, n.BlobID)
|
||||
attrs.SetInt64(prefixes.BlobsizeAttr, n.Blobsize)
|
||||
|
||||
err := t.lookup.MetadataBackend().SetMultiple(context.Background(), node.NewBaseNode(n.SpaceID, n.ID+node.CurrentIDDelimiter, t.lookup), attrs, true)
|
||||
err := t.lookup.MetadataBackend().SetMultiple(context.Background(), node.NewBaseNode(n.SpaceID, n.ID+node.CurrentIDDelimiter, t.lookup), attrs)
|
||||
if err != nil {
|
||||
t.log.Error().Err(err).Str("spaceID", n.SpaceID).Str("id", n.ID).Msg("could not copy metadata to current revision")
|
||||
}
|
||||
@@ -796,7 +799,7 @@ func (t *Tree) InitNewNode(ctx context.Context, n *node.Node, fsize uint64) (met
|
||||
mtime := fi.ModTime()
|
||||
err = n.SetXattrsWithContext(ctx, map[string][]byte{
|
||||
prefixes.MTimeAttr: []byte(mtime.UTC().Format(time.RFC3339Nano)),
|
||||
}, false)
|
||||
})
|
||||
if err != nil {
|
||||
t.log.Error().Err(err).Str("path", n.InternalPath()).Msg("could not set mtime attribute on new node")
|
||||
}
|
||||
@@ -868,5 +871,5 @@ func (t *Tree) createDirNode(ctx context.Context, n *node.Node) (err error) {
|
||||
if t.options.TreeTimeAccounting || t.options.TreeSizeAccounting {
|
||||
attributes[prefixes.PropagationAttr] = []byte("1") // mark the node for propagation
|
||||
}
|
||||
return n.SetXattrsWithContext(ctx, attributes, false)
|
||||
return n.SetXattrsWithContext(ctx, attributes)
|
||||
}
|
||||
|
||||
62
vendor/github.com/opencloud-eu/reva/v2/pkg/storage/internal/goroutinelock/lock.go
generated
vendored
Normal file
62
vendor/github.com/opencloud-eu/reva/v2/pkg/storage/internal/goroutinelock/lock.go
generated
vendored
Normal file
@@ -0,0 +1,62 @@
|
||||
// Copyright 2026 OpenCloud GmbH <mail@opencloud.eu>
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
package goroutinelock
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"runtime"
|
||||
"strconv"
|
||||
"sync"
|
||||
"sync/atomic"
|
||||
|
||||
"github.com/rs/zerolog/log"
|
||||
)
|
||||
|
||||
// Lock tracks which goroutine currently owns a metadata lock.
|
||||
//
|
||||
// Held() only returns true in the owning goroutine, which prevents lock-held
|
||||
// state from leaking across goroutine boundaries when helper nodes are shared.
|
||||
type Lock struct {
|
||||
gid atomic.Uint64
|
||||
}
|
||||
|
||||
var goidLogOnce sync.Once
|
||||
|
||||
func (o *Lock) Hold() {
|
||||
o.gid.Store(goid())
|
||||
}
|
||||
|
||||
func (o *Lock) Release() {
|
||||
o.gid.Store(0)
|
||||
}
|
||||
|
||||
func (o *Lock) Held() bool {
|
||||
return o.gid.Load() == goid()
|
||||
}
|
||||
|
||||
func goid() uint64 {
|
||||
var buf [64]byte
|
||||
n := runtime.Stack(buf[:], false)
|
||||
|
||||
// The header has the form "goroutine 1234 [running]:".
|
||||
fields := bytes.Fields(buf[:n])
|
||||
if len(fields) < 2 {
|
||||
goidLogOnce.Do(func() {
|
||||
log.Error().
|
||||
Str("stack_header", string(buf[:n])).
|
||||
Msg("goroutinelock: could not determine goroutine id from runtime stack header")
|
||||
})
|
||||
return 0
|
||||
}
|
||||
id, err := strconv.ParseUint(string(fields[1]), 10, 64)
|
||||
if err != nil {
|
||||
goidLogOnce.Do(func() {
|
||||
log.Error().Err(err).
|
||||
Str("goroutine_id_field", string(fields[1])).
|
||||
Msg("goroutinelock: could not parse goroutine id")
|
||||
})
|
||||
return 0
|
||||
}
|
||||
return id
|
||||
}
|
||||
1
vendor/github.com/opencloud-eu/reva/v2/pkg/storage/pkg/decomposedfs/decomposedfs.go
generated
vendored
1
vendor/github.com/opencloud-eu/reva/v2/pkg/storage/pkg/decomposedfs/decomposedfs.go
generated
vendored
@@ -108,6 +108,7 @@ type IDCachingTree interface {
|
||||
|
||||
type SessionStore interface {
|
||||
New(ctx context.Context) *upload.DecomposedFsSession
|
||||
SessionFromInfo(info tusd.FileInfo) *upload.DecomposedFsSession
|
||||
List(ctx context.Context) ([]*upload.DecomposedFsSession, error)
|
||||
Get(ctx context.Context, id string) (*upload.DecomposedFsSession, error)
|
||||
Cleanup(ctx context.Context, session upload.Session, revertNodeMetadata, keepUpload, unmarkPostprocessing bool)
|
||||
|
||||
12
vendor/github.com/opencloud-eu/reva/v2/pkg/storage/pkg/decomposedfs/disk/disk_fallback.go
generated
vendored
Normal file
12
vendor/github.com/opencloud-eu/reva/v2/pkg/storage/pkg/decomposedfs/disk/disk_fallback.go
generated
vendored
Normal file
@@ -0,0 +1,12 @@
|
||||
// Copyright 2026 OpenCloud GmbH <mail@opencloud.eu>
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
//go:build !linux && !freebsd && !darwin
|
||||
|
||||
package disk
|
||||
|
||||
import "os"
|
||||
|
||||
func Fdatasync(f *os.File) error {
|
||||
return nil
|
||||
}
|
||||
15
vendor/github.com/opencloud-eu/reva/v2/pkg/storage/pkg/decomposedfs/disk/disk_linux.go
generated
vendored
Normal file
15
vendor/github.com/opencloud-eu/reva/v2/pkg/storage/pkg/decomposedfs/disk/disk_linux.go
generated
vendored
Normal file
@@ -0,0 +1,15 @@
|
||||
// Copyright 2026 OpenCloud GmbH <mail@opencloud.eu>
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
//go:build linux
|
||||
|
||||
package disk
|
||||
|
||||
import (
|
||||
"os"
|
||||
"syscall"
|
||||
)
|
||||
|
||||
func Fdatasync(f *os.File) error {
|
||||
return syscall.Fdatasync(int(f.Fd()))
|
||||
}
|
||||
12
vendor/github.com/opencloud-eu/reva/v2/pkg/storage/pkg/decomposedfs/disk/disk_unix.go
generated
vendored
Normal file
12
vendor/github.com/opencloud-eu/reva/v2/pkg/storage/pkg/decomposedfs/disk/disk_unix.go
generated
vendored
Normal file
@@ -0,0 +1,12 @@
|
||||
// Copyright 2026 OpenCloud GmbH <mail@opencloud.eu>
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
//go:build freebsd || darwin
|
||||
|
||||
package disk
|
||||
|
||||
import "os"
|
||||
|
||||
func Fdatasync(file *os.File) error {
|
||||
return file.Sync()
|
||||
}
|
||||
4
vendor/github.com/opencloud-eu/reva/v2/pkg/storage/pkg/decomposedfs/grants.go
generated
vendored
4
vendor/github.com/opencloud-eu/reva/v2/pkg/storage/pkg/decomposedfs/grants.go
generated
vendored
@@ -223,7 +223,7 @@ func (fs *Decomposedfs) RemoveGrant(ctx context.Context, ref *provider.Reference
|
||||
}
|
||||
}
|
||||
|
||||
if err := grantNode.DeleteGrant(ctx, g, false); err != nil {
|
||||
if err := grantNode.DeleteGrant(ctx, g); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
@@ -349,7 +349,7 @@ func (fs *Decomposedfs) storeGrant(ctx context.Context, n *node.Node, g *provide
|
||||
attribs := node.Attributes{
|
||||
prefixes.GrantPrefix + principal: value,
|
||||
}
|
||||
if err := n.SetXattrsWithContext(ctx, attribs, false); err != nil {
|
||||
if err := n.SetXattrsWithContext(ctx, attribs); err != nil {
|
||||
appctx.GetLogger(ctx).Error().Err(err).
|
||||
Str("principal", principal).Msg("Could not set grant for principal")
|
||||
return err
|
||||
|
||||
49
vendor/github.com/opencloud-eu/reva/v2/pkg/storage/pkg/decomposedfs/lookup/lookup.go
generated
vendored
49
vendor/github.com/opencloud-eu/reva/v2/pkg/storage/pkg/decomposedfs/lookup/lookup.go
generated
vendored
@@ -36,7 +36,6 @@ import (
|
||||
"github.com/opencloud-eu/reva/v2/pkg/storage/pkg/decomposedfs/options"
|
||||
"github.com/opencloud-eu/reva/v2/pkg/utils"
|
||||
"github.com/pkg/errors"
|
||||
"github.com/rogpeppe/go-internal/lockedfile"
|
||||
"go.opentelemetry.io/otel"
|
||||
"go.opentelemetry.io/otel/trace"
|
||||
)
|
||||
@@ -314,45 +313,13 @@ func refFromCS3(b []byte) (*provider.Reference, error) {
|
||||
}, nil
|
||||
}
|
||||
|
||||
// CopyMetadata copies all extended attributes from source to target.
|
||||
// The optional filter function can be used to filter by attribute name, e.g. by checking a prefix
|
||||
// For the source file, a shared lock is acquired.
|
||||
// NOTE: target resource will be write locked!
|
||||
func (lu *Lookup) CopyMetadata(ctx context.Context, sourceNode, targetNode metadata.MetadataNode, filter func(attributeName string, value []byte) (newValue []byte, copy bool), acquireTargetLock bool) (err error) {
|
||||
// Acquire a read log on the source node
|
||||
// write lock existing node before reading treesize or tree time
|
||||
lock, err := lockedfile.OpenFile(lu.MetadataBackend().LockfilePath(sourceNode), os.O_RDONLY|os.O_CREATE, 0600)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "xattrs: Unable to lock source to read")
|
||||
}
|
||||
defer func() {
|
||||
rerr := lock.Close()
|
||||
|
||||
// if err is non nil we do not overwrite that
|
||||
if err == nil {
|
||||
err = rerr
|
||||
}
|
||||
}()
|
||||
|
||||
return lu.CopyMetadataWithSourceLock(ctx, sourceNode, targetNode, filter, lock, acquireTargetLock)
|
||||
}
|
||||
|
||||
// CopyMetadataWithSourceLock copies all extended attributes from source to target.
|
||||
// The optional filter function can be used to filter by attribute name, e.g. by checking a prefix
|
||||
// For the source file, a matching lockedfile is required.
|
||||
// NOTE: target resource will be write locked!
|
||||
func (lu *Lookup) CopyMetadataWithSourceLock(ctx context.Context, sourceNode, targetNode metadata.MetadataNode, filter func(attributeName string, value []byte) (newValue []byte, copy bool), lockedSource *lockedfile.File, acquireTargetLock bool) (err error) {
|
||||
switch {
|
||||
case lockedSource == nil:
|
||||
return errors.New("no lock provided")
|
||||
case lockedSource.Name() != lu.MetadataBackend().LockfilePath(sourceNode):
|
||||
return errors.New("lockpath does not match filepath")
|
||||
}
|
||||
|
||||
// CopyMetadata copies all extended attributes from source to target. The optional filter
|
||||
// function can be used to filter by attribute name, e.g. by checking a prefix.
|
||||
//
|
||||
// Locking is derived from the nodes: the source's metadata lock is acquired for the duration
|
||||
// of the read unless the caller already holds it (sourceNode.LockHeld()), and the target is
|
||||
// write locked while its attributes are written unless the caller already holds its lock
|
||||
func (lu *Lookup) CopyMetadata(ctx context.Context, sourceNode, targetNode metadata.MetadataNode, filter func(attributeName string, value []byte) (newValue []byte, copy bool)) (err error) {
|
||||
attrs, err := lu.metadataBackend.All(ctx, sourceNode)
|
||||
if err != nil {
|
||||
return err
|
||||
@@ -369,7 +336,7 @@ func (lu *Lookup) CopyMetadataWithSourceLock(ctx context.Context, sourceNode, ta
|
||||
newAttrs[attrName] = val
|
||||
}
|
||||
|
||||
return lu.MetadataBackend().SetMultiple(ctx, targetNode, newAttrs, acquireTargetLock)
|
||||
return lu.MetadataBackend().SetMultiple(ctx, targetNode, newAttrs)
|
||||
}
|
||||
|
||||
func (lu *Lookup) PurgeNode(n *node.Node) error {
|
||||
|
||||
2
vendor/github.com/opencloud-eu/reva/v2/pkg/storage/pkg/decomposedfs/metadata.go
generated
vendored
2
vendor/github.com/opencloud-eu/reva/v2/pkg/storage/pkg/decomposedfs/metadata.go
generated
vendored
@@ -142,7 +142,7 @@ func (fs *Decomposedfs) UnsetArbitraryMetadata(ctx context.Context, ref *provide
|
||||
|
||||
errs := []error{}
|
||||
for _, k := range keys {
|
||||
if err = n.RemoveXattr(ctx, prefixes.MetadataPrefix+k, true); err != nil {
|
||||
if err = n.RemoveXattr(ctx, prefixes.MetadataPrefix+k); err != nil {
|
||||
if metadata.IsAttrUnset(err) {
|
||||
continue // already gone, ignore
|
||||
}
|
||||
|
||||
@@ -3,7 +3,6 @@ package metadata
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"io"
|
||||
"io/fs"
|
||||
"os"
|
||||
"path/filepath"
|
||||
@@ -114,11 +113,11 @@ func (b HybridBackend) list(ctx context.Context, n MetadataNode) (attribs []stri
|
||||
return nil, &xattr.Error{Op: "HybridBackend.list", Path: n.InternalPath(), Err: syscall.ENOENT} // attribute not found
|
||||
}
|
||||
|
||||
return xattr.List(filePath)
|
||||
return listXattr(filePath)
|
||||
}
|
||||
|
||||
// All reads all extended attributes for a node, protected by a
|
||||
// shared file lock
|
||||
// All reads all extended attributes for a node. It acquires a shared file lock
|
||||
// unless the caller already holds the node's metadata lock (n.LockHeld()).
|
||||
func (b HybridBackend) All(ctx context.Context, n MetadataNode) (map[string][]byte, error) {
|
||||
attribs := map[string][]byte{}
|
||||
|
||||
@@ -127,29 +126,18 @@ func (b HybridBackend) All(ctx context.Context, n MetadataNode) (map[string][]by
|
||||
return attribs, err
|
||||
}
|
||||
|
||||
unlock, err := b.Lock(n)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer func() { _ = unlock() }()
|
||||
|
||||
return b.getAll(ctx, n, false, false)
|
||||
}
|
||||
|
||||
// AllWithLockedSource reads all extended attributes from the given reader.
|
||||
// The path argument is used for storing the data in the cache
|
||||
func (b HybridBackend) AllWithLockedSource(ctx context.Context, n MetadataNode, _ io.Reader) (map[string][]byte, error) {
|
||||
attribs := map[string][]byte{}
|
||||
|
||||
err := b.metaCache.PullFromCache(b.cacheKey(n), &attribs)
|
||||
if err == nil {
|
||||
return attribs, err
|
||||
if !n.LockHeld() {
|
||||
unlock, err := b.Lock(n)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer func() { _ = unlock() }()
|
||||
}
|
||||
|
||||
return b.getAll(ctx, n, false, false)
|
||||
return b.getAll(ctx, n, false)
|
||||
}
|
||||
|
||||
func (b HybridBackend) getAll(ctx context.Context, n MetadataNode, skipCache, skipOffloaded bool) (map[string][]byte, error) {
|
||||
func (b HybridBackend) getAll(ctx context.Context, n MetadataNode, skipOffloaded bool) (map[string][]byte, error) {
|
||||
attribs := map[string][]byte{}
|
||||
attrNames, err := b.list(ctx, n)
|
||||
if err != nil {
|
||||
@@ -215,13 +203,13 @@ func (b HybridBackend) getAll(ctx context.Context, n MetadataNode, skipCache, sk
|
||||
|
||||
// Set sets one attribute for the given path
|
||||
func (b HybridBackend) Set(ctx context.Context, n MetadataNode, key string, val []byte) (err error) {
|
||||
return b.SetMultiple(ctx, n, map[string][]byte{key: val}, true)
|
||||
return b.SetMultiple(ctx, n, map[string][]byte{key: val})
|
||||
}
|
||||
|
||||
// SetMultiple sets a set of attribute for the given path
|
||||
func (b HybridBackend) SetMultiple(ctx context.Context, n MetadataNode, attribs map[string][]byte, acquireLock bool) (err error) {
|
||||
func (b HybridBackend) SetMultiple(ctx context.Context, n MetadataNode, attribs map[string][]byte) (err error) {
|
||||
path := n.InternalPath()
|
||||
if acquireLock {
|
||||
if !n.LockHeld() {
|
||||
unlock, err := b.Lock(n)
|
||||
if err != nil {
|
||||
return err
|
||||
@@ -247,7 +235,7 @@ func (b HybridBackend) SetMultiple(ctx context.Context, n MetadataNode, attribs
|
||||
mdSize += len(attribs[key]) + len(key)
|
||||
}
|
||||
}
|
||||
existingAttribs, err := b.getAll(ctx, n, true, true)
|
||||
existingAttribs, err := b.getAll(ctx, n, true)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -320,7 +308,7 @@ func (b HybridBackend) SetMultiple(ctx context.Context, n MetadataNode, attribs
|
||||
}
|
||||
|
||||
// Update the cache with the new values
|
||||
_, err = b.getAll(ctx, n, true, false)
|
||||
_, err = b.getAll(ctx, n, false)
|
||||
return err
|
||||
}
|
||||
|
||||
@@ -331,7 +319,7 @@ func (b HybridBackend) offloadMetadata(ctx context.Context, n MetadataNode) erro
|
||||
var xerr error
|
||||
|
||||
// collect attributes to move
|
||||
existingAttribs, err := b.getAll(ctx, n, true, true)
|
||||
existingAttribs, err := b.getAll(ctx, n, true)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -377,9 +365,9 @@ func (b HybridBackend) offloadMetadata(ctx context.Context, n MetadataNode) erro
|
||||
}
|
||||
|
||||
// Remove an extended attribute key
|
||||
func (b HybridBackend) Remove(ctx context.Context, n MetadataNode, key string, acquireLock bool) error {
|
||||
func (b HybridBackend) Remove(ctx context.Context, n MetadataNode, key string) error {
|
||||
path := n.InternalPath()
|
||||
if acquireLock {
|
||||
if !n.LockHeld() {
|
||||
unlock, err := b.Lock(n)
|
||||
if err != nil {
|
||||
return err
|
||||
@@ -444,7 +432,7 @@ func (b HybridBackend) Remove(ctx context.Context, n MetadataNode, key string, a
|
||||
}
|
||||
|
||||
// Update the cache with the new values
|
||||
_, err := b.getAll(ctx, n, true, false)
|
||||
_, err := b.getAll(ctx, n, false)
|
||||
return err
|
||||
}
|
||||
|
||||
@@ -462,7 +450,7 @@ func (b HybridBackend) Purge(ctx context.Context, n MetadataNode) error {
|
||||
}
|
||||
defer func() { _ = unlock() }()
|
||||
|
||||
attribs, err := b.getAll(ctx, n, true, false)
|
||||
attribs, err := b.getAll(ctx, n, false)
|
||||
if err == nil {
|
||||
for attr := range attribs {
|
||||
if strings.HasPrefix(attr, prefixes.OcPrefix) {
|
||||
@@ -504,11 +492,10 @@ func (b HybridBackend) LockfilePath(n MetadataNode) string {
|
||||
|
||||
// Lock locks the metadata for the given path
|
||||
func (b HybridBackend) Lock(n MetadataNode) (UnlockFunc, error) {
|
||||
f, _, err := b.LockAndRead(n)
|
||||
return f, err
|
||||
}
|
||||
if n.LockHeld() {
|
||||
return func() error { return nil }, nil
|
||||
}
|
||||
|
||||
func (b HybridBackend) LockAndRead(n MetadataNode) (UnlockFunc, io.Reader, error) {
|
||||
metaLockPath := b.LockfilePath(n)
|
||||
mlock, err := lockedfile.OpenFile(metaLockPath, os.O_RDWR|os.O_CREATE, 0600)
|
||||
if err != nil {
|
||||
@@ -516,20 +503,22 @@ func (b HybridBackend) LockAndRead(n MetadataNode) (UnlockFunc, io.Reader, error
|
||||
// create the parent directory
|
||||
err = os.MkdirAll(filepath.Dir(metaLockPath), 0700)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
return nil, err
|
||||
}
|
||||
mlock, err = lockedfile.OpenFile(metaLockPath, os.O_RDWR|os.O_CREATE, 0600)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
return nil, err
|
||||
}
|
||||
} else {
|
||||
return nil, nil, err
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
n.SetLockHeld(true)
|
||||
return func() error {
|
||||
n.SetLockHeld(false)
|
||||
// Warning: do not remove the lockfile or we may lock the same file more than once, https://github.com/opencloud-eu/opencloud/issues/1793
|
||||
return mlock.Close()
|
||||
}, mlock, nil
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (b HybridBackend) cacheKey(n MetadataNode) string {
|
||||
|
||||
55
vendor/github.com/opencloud-eu/reva/v2/pkg/storage/pkg/decomposedfs/metadata/list.go
generated
vendored
Normal file
55
vendor/github.com/opencloud-eu/reva/v2/pkg/storage/pkg/decomposedfs/metadata/list.go
generated
vendored
Normal file
@@ -0,0 +1,55 @@
|
||||
// Copyright 2026 OpenCloud GmbH <mail@opencloud.eu>
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
package metadata
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"syscall"
|
||||
|
||||
"github.com/pkg/xattr"
|
||||
)
|
||||
|
||||
// maxListXattrRetries bounds the number of attempts made by listXattr when it
|
||||
// keeps racing with a concurrent writer.
|
||||
const maxListXattrRetries = 10
|
||||
|
||||
// listXattr lists the extended attribute names of the file at path, retrying on
|
||||
// transient ERANGE/E2BIG errors.
|
||||
//
|
||||
// xattr.List is not atomic: internally it issues a listxattr syscall to learn
|
||||
// the size of the extended-attribute name list, allocates a buffer of that
|
||||
// size and issues a second listxattr to read the names into it. If another
|
||||
// goroutine or process adds an extended attribute to the same inode between the
|
||||
// two syscalls, the name list grows beyond the buffer and the kernel returns
|
||||
// ERANGE ("numerical result out of range") (or E2BIG on some filesystems).
|
||||
//
|
||||
// This is a transient condition: a fresh size query on the next attempt
|
||||
// reflects the new size, so we simply retry a bounded number of times. See the
|
||||
// posix fs watcher, whose assimilation can read a node's xattrs while the node
|
||||
// is still being created, for a concrete source of such concurrent writes.
|
||||
func listXattr(path string) ([]string, error) {
|
||||
var (
|
||||
attrs []string
|
||||
err error
|
||||
)
|
||||
for i := 0; i < maxListXattrRetries; i++ {
|
||||
attrs, err = xattr.List(path)
|
||||
if err == nil || !isRangeError(err) {
|
||||
return attrs, err
|
||||
}
|
||||
}
|
||||
return attrs, err
|
||||
}
|
||||
|
||||
// isRangeError reports whether err is a transient listxattr "buffer too small"
|
||||
// error (ERANGE or E2BIG) caused by the name list growing concurrently.
|
||||
func isRangeError(err error) bool {
|
||||
var xerr *xattr.Error
|
||||
if errors.As(err, &xerr) {
|
||||
if serr, ok := xerr.Err.(syscall.Errno); ok {
|
||||
return serr == syscall.ERANGE || serr == syscall.E2BIG
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
@@ -86,12 +86,12 @@ func (b MessagePackBackend) IdentifyPath(_ context.Context, path string) (string
|
||||
|
||||
// All reads all extended attributes for a node
|
||||
func (b MessagePackBackend) All(ctx context.Context, n MetadataNode) (map[string][]byte, error) {
|
||||
return b.loadAttributes(ctx, n, nil)
|
||||
return b.loadAttributes(ctx, n)
|
||||
}
|
||||
|
||||
// Get an extended attribute value for the given key
|
||||
func (b MessagePackBackend) Get(ctx context.Context, n MetadataNode, key string) ([]byte, error) {
|
||||
attribs, err := b.loadAttributes(ctx, n, nil)
|
||||
attribs, err := b.loadAttributes(ctx, n)
|
||||
if err != nil {
|
||||
return []byte{}, err
|
||||
}
|
||||
@@ -104,7 +104,7 @@ func (b MessagePackBackend) Get(ctx context.Context, n MetadataNode, key string)
|
||||
|
||||
// GetInt64 reads a string as int64 from the xattrs
|
||||
func (b MessagePackBackend) GetInt64(ctx context.Context, n MetadataNode, key string) (int64, error) {
|
||||
attribs, err := b.loadAttributes(ctx, n, nil)
|
||||
attribs, err := b.loadAttributes(ctx, n)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
@@ -121,26 +121,20 @@ func (b MessagePackBackend) GetInt64(ctx context.Context, n MetadataNode, key st
|
||||
|
||||
// Set sets one attribute for the given path
|
||||
func (b MessagePackBackend) Set(ctx context.Context, n MetadataNode, key string, val []byte) error {
|
||||
return b.SetMultiple(ctx, n, map[string][]byte{key: val}, true)
|
||||
return b.SetMultiple(ctx, n, map[string][]byte{key: val})
|
||||
}
|
||||
|
||||
// SetMultiple sets a set of attribute for the given path
|
||||
func (b MessagePackBackend) SetMultiple(ctx context.Context, n MetadataNode, attribs map[string][]byte, acquireLock bool) error {
|
||||
return b.saveAttributes(ctx, n, attribs, nil, acquireLock)
|
||||
func (b MessagePackBackend) SetMultiple(ctx context.Context, n MetadataNode, attribs map[string][]byte) error {
|
||||
return b.saveAttributes(ctx, n, attribs, nil)
|
||||
}
|
||||
|
||||
// Remove an extended attribute key
|
||||
func (b MessagePackBackend) Remove(ctx context.Context, n MetadataNode, key string, acquireLock bool) error {
|
||||
return b.saveAttributes(ctx, n, nil, []string{key}, acquireLock)
|
||||
func (b MessagePackBackend) Remove(ctx context.Context, n MetadataNode, key string) error {
|
||||
return b.saveAttributes(ctx, n, nil, []string{key})
|
||||
}
|
||||
|
||||
// AllWithLockedSource reads all extended attributes from the given reader (if possible).
|
||||
// The path argument is used for storing the data in the cache
|
||||
func (b MessagePackBackend) AllWithLockedSource(ctx context.Context, n MetadataNode, source io.Reader) (map[string][]byte, error) {
|
||||
return b.loadAttributes(ctx, n, source)
|
||||
}
|
||||
|
||||
func (b MessagePackBackend) saveAttributes(ctx context.Context, n MetadataNode, setAttribs map[string][]byte, deleteAttribs []string, acquireLock bool) error {
|
||||
func (b MessagePackBackend) saveAttributes(ctx context.Context, n MetadataNode, setAttribs map[string][]byte, deleteAttribs []string) error {
|
||||
var (
|
||||
err error
|
||||
)
|
||||
@@ -155,7 +149,7 @@ func (b MessagePackBackend) saveAttributes(ctx context.Context, n MetadataNode,
|
||||
}()
|
||||
|
||||
metaPath := b.MetadataPath(n)
|
||||
if acquireLock {
|
||||
if !n.LockHeld() {
|
||||
unlock, err := b.Lock(n)
|
||||
if err != nil {
|
||||
return err
|
||||
@@ -211,7 +205,7 @@ func (b MessagePackBackend) saveAttributes(ctx context.Context, n MetadataNode,
|
||||
return err
|
||||
}
|
||||
|
||||
func (b MessagePackBackend) loadAttributes(ctx context.Context, n MetadataNode, source io.Reader) (map[string][]byte, error) {
|
||||
func (b MessagePackBackend) loadAttributes(ctx context.Context, n MetadataNode) (map[string][]byte, error) {
|
||||
ctx, span := tracer.Start(ctx, "loadAttributes")
|
||||
defer span.End()
|
||||
attribs := map[string][]byte{}
|
||||
@@ -221,39 +215,31 @@ func (b MessagePackBackend) loadAttributes(ctx context.Context, n MetadataNode,
|
||||
}
|
||||
|
||||
metaPath := b.MetadataPath(n)
|
||||
var msgBytes []byte
|
||||
|
||||
if source == nil {
|
||||
// // No cached entry found. Read from storage and store in cache
|
||||
_, subspan := tracer.Start(ctx, "os.OpenFile")
|
||||
// source, err = lockedfile.Open(metaPath)
|
||||
source, err = os.Open(metaPath)
|
||||
subspan.End()
|
||||
// // No cached entry found. Read from storage and store in cache
|
||||
if err != nil {
|
||||
if os.IsNotExist(err) {
|
||||
// some of the caller rely on ENOTEXISTS to be returned when the
|
||||
// actual file (not the metafile) does not exist in order to
|
||||
// determine whether a node exists or not -> stat the actual node
|
||||
_, subspan := tracer.Start(ctx, "os.Stat")
|
||||
_, err := os.Stat(n.InternalPath())
|
||||
subspan.End()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return attribs, nil // no attributes set yet
|
||||
// No cached entry found. Read from storage and store in cache
|
||||
_, subspan := tracer.Start(ctx, "os.Open")
|
||||
source, err := os.Open(metaPath)
|
||||
subspan.End()
|
||||
if err != nil {
|
||||
if os.IsNotExist(err) {
|
||||
// some of the callers rely on ENOTEXISTS to be returned when the
|
||||
// actual file (not the metafile) does not exist in order to
|
||||
// determine whether a node exists or not -> stat the actual node
|
||||
_, subspan := tracer.Start(ctx, "os.Stat")
|
||||
_, err := os.Stat(n.InternalPath())
|
||||
subspan.End()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return attribs, nil // no attributes set yet
|
||||
}
|
||||
_, subspan = tracer.Start(ctx, "io.ReadAll")
|
||||
msgBytes, err = io.ReadAll(source)
|
||||
source.(*os.File).Close()
|
||||
subspan.End()
|
||||
} else {
|
||||
_, subspan := tracer.Start(ctx, "io.ReadAll")
|
||||
msgBytes, err = io.ReadAll(source)
|
||||
subspan.End()
|
||||
return nil, err
|
||||
}
|
||||
|
||||
_, subspan = tracer.Start(ctx, "io.ReadAll")
|
||||
msgBytes, err := io.ReadAll(source)
|
||||
_ = source.Close()
|
||||
subspan.End()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -264,7 +250,7 @@ func (b MessagePackBackend) loadAttributes(ctx context.Context, n MetadataNode,
|
||||
}
|
||||
}
|
||||
|
||||
_, subspan := tracer.Start(ctx, "metaCache.PushToCache")
|
||||
_, subspan = tracer.Start(ctx, "metaCache.PushToCache")
|
||||
err = b.metaCache.PushToCache(b.cacheKey(n), attribs)
|
||||
subspan.End()
|
||||
if err != nil {
|
||||
@@ -314,31 +300,23 @@ func (MessagePackBackend) LockfilePath(n MetadataNode) string { return n.Interna
|
||||
|
||||
// Lock locks the metadata for the given path
|
||||
func (b MessagePackBackend) Lock(n MetadataNode) (UnlockFunc, error) {
|
||||
if n.LockHeld() {
|
||||
return func() error { return nil }, nil
|
||||
}
|
||||
|
||||
metaLockPath := b.LockfilePath(n)
|
||||
mlock, err := lockedfile.OpenFile(metaLockPath, os.O_RDWR|os.O_CREATE, 0600)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
n.SetLockHeld(true)
|
||||
return func() error {
|
||||
n.SetLockHeld(false)
|
||||
// Warning: do not remove the lockfile or we may lock the same file more than once, https://github.com/opencloud-eu/opencloud/issues/1793
|
||||
return mlock.Close()
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (b MessagePackBackend) LockAndRead(n MetadataNode) (UnlockFunc, io.Reader, error) {
|
||||
unlock, err := b.Lock(n)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
f, err := os.Open(b.MetadataPath(n))
|
||||
if err != nil {
|
||||
_ = unlock()
|
||||
return nil, nil, err
|
||||
}
|
||||
return unlock, f, nil
|
||||
}
|
||||
|
||||
func (b MessagePackBackend) cacheKey(n MetadataNode) string {
|
||||
return n.GetSpaceID() + "/" + n.GetID()
|
||||
}
|
||||
|
||||
33
vendor/github.com/opencloud-eu/reva/v2/pkg/storage/pkg/decomposedfs/metadata/metadata.go
generated
vendored
33
vendor/github.com/opencloud-eu/reva/v2/pkg/storage/pkg/decomposedfs/metadata/metadata.go
generated
vendored
@@ -21,7 +21,6 @@ package metadata
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"io"
|
||||
"time"
|
||||
|
||||
"go.opentelemetry.io/otel"
|
||||
@@ -42,6 +41,11 @@ type MetadataNode interface {
|
||||
GetSpaceID() string
|
||||
GetID() string
|
||||
InternalPath() string
|
||||
// LockHeld reports whether the caller already holds this node's metadata lock.
|
||||
// Backends use it to decide whether they must (re-)acquire the lock when reading
|
||||
// metadata; re-acquiring a held, non-reentrant advisory file lock self-deadlocks.
|
||||
LockHeld() bool
|
||||
SetLockHeld(held bool)
|
||||
}
|
||||
|
||||
// Backend defines the interface for file attribute backends
|
||||
@@ -49,17 +53,21 @@ type Backend interface {
|
||||
Name() string
|
||||
IdentifyPath(ctx context.Context, path string) (string, string, string, time.Time, error)
|
||||
|
||||
// All reads all extended attributes for a node. It acquires the node's metadata
|
||||
// lock unless n.LockHeld() reports that the caller already holds it.
|
||||
All(ctx context.Context, n MetadataNode) (map[string][]byte, error)
|
||||
AllWithLockedSource(ctx context.Context, n MetadataNode, source io.Reader) (map[string][]byte, error)
|
||||
|
||||
Get(ctx context.Context, n MetadataNode, key string) ([]byte, error)
|
||||
GetInt64(ctx context.Context, n MetadataNode, key string) (int64, error)
|
||||
Set(ctx context.Context, n MetadataNode, key string, val []byte) error
|
||||
SetMultiple(ctx context.Context, n MetadataNode, attribs map[string][]byte, acquireLock bool) error
|
||||
Remove(ctx context.Context, n MetadataNode, key string, acquireLock bool) error
|
||||
// SetMultiple sets multiple extended attributes for a node. It acquires the node's
|
||||
// metadata lock unless n.LockHeld() reports that the caller already holds it.
|
||||
SetMultiple(ctx context.Context, n MetadataNode, attribs map[string][]byte) error
|
||||
// Remove removes an extended attribute key. It acquires the node's metadata lock
|
||||
// unless n.LockHeld() reports that the caller already holds it.
|
||||
Remove(ctx context.Context, n MetadataNode, key string) error
|
||||
|
||||
Lock(n MetadataNode) (UnlockFunc, error)
|
||||
LockAndRead(n MetadataNode) (UnlockFunc, io.Reader, error)
|
||||
Purge(ctx context.Context, n MetadataNode) error
|
||||
Rename(oldNode, newNode MetadataNode) error
|
||||
MetadataPath(n MetadataNode) string
|
||||
@@ -100,12 +108,12 @@ func (NullBackend) Set(ctx context.Context, n MetadataNode, key string, val []by
|
||||
}
|
||||
|
||||
// SetMultiple sets a set of attribute for the given path
|
||||
func (NullBackend) SetMultiple(ctx context.Context, n MetadataNode, attribs map[string][]byte, acquireLock bool) error {
|
||||
func (NullBackend) SetMultiple(ctx context.Context, n MetadataNode, attribs map[string][]byte) error {
|
||||
return errUnconfiguredError
|
||||
}
|
||||
|
||||
// Remove removes an extended attribute key
|
||||
func (NullBackend) Remove(ctx context.Context, n MetadataNode, key string, acquireLock bool) error {
|
||||
func (NullBackend) Remove(ctx context.Context, n MetadataNode, key string) error {
|
||||
return errUnconfiguredError
|
||||
}
|
||||
|
||||
@@ -114,11 +122,6 @@ func (NullBackend) Lock(n MetadataNode) (UnlockFunc, error) {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
// LockAndRead locks the metadata for reading
|
||||
func (NullBackend) LockAndRead(n MetadataNode) (UnlockFunc, io.Reader, error) {
|
||||
return nil, nil, nil
|
||||
}
|
||||
|
||||
// IsMetaFile returns whether the given path represents a meta file
|
||||
func (NullBackend) IsMetaFile(path string) bool { return false }
|
||||
|
||||
@@ -133,9 +136,3 @@ func (NullBackend) MetadataPath(n MetadataNode) string { return "" }
|
||||
|
||||
// LockfilePath returns the path of the lock file
|
||||
func (NullBackend) LockfilePath(n MetadataNode) string { return "" }
|
||||
|
||||
// AllWithLockedSource reads all extended attributes from the given reader
|
||||
// The path argument is used for storing the data in the cache
|
||||
func (NullBackend) AllWithLockedSource(ctx context.Context, n MetadataNode, source io.Reader) (map[string][]byte, error) {
|
||||
return nil, errUnconfiguredError
|
||||
}
|
||||
|
||||
@@ -20,7 +20,6 @@ package metadata
|
||||
|
||||
import (
|
||||
"context"
|
||||
"io"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strconv"
|
||||
@@ -89,7 +88,7 @@ func (b XattrsBackend) GetInt64(ctx context.Context, n MetadataNode, key string)
|
||||
|
||||
func (b XattrsBackend) list(ctx context.Context, n MetadataNode, acquireLock bool) (attribs []string, err error) {
|
||||
filePath := n.InternalPath()
|
||||
attrs, err := xattr.List(filePath)
|
||||
attrs, err := listXattr(filePath)
|
||||
if err == nil {
|
||||
return attrs, nil
|
||||
}
|
||||
@@ -100,17 +99,21 @@ func (b XattrsBackend) list(ctx context.Context, n MetadataNode, acquireLock boo
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
n.SetLockHeld(true)
|
||||
// Warning: do not remove the lockfile or we may lock the same file more than once, https://github.com/opencloud-eu/opencloud/issues/1793
|
||||
defer f.Close()
|
||||
defer func() {
|
||||
n.SetLockHeld(false)
|
||||
_ = f.Close()
|
||||
}()
|
||||
|
||||
}
|
||||
return xattr.List(filePath)
|
||||
return listXattr(filePath)
|
||||
}
|
||||
|
||||
// All reads all extended attributes for a node, protected by a
|
||||
// shared file lock
|
||||
// All reads all extended attributes for a node. It acquires a shared file lock
|
||||
// unless the caller already holds the node's metadata lock (n.LockHeld()).
|
||||
func (b XattrsBackend) All(ctx context.Context, n MetadataNode) (map[string][]byte, error) {
|
||||
return b.getAll(ctx, n, false, true)
|
||||
return b.getAll(ctx, n, false, !n.LockHeld())
|
||||
}
|
||||
|
||||
func (b XattrsBackend) getAll(ctx context.Context, n MetadataNode, skipCache, acquireLock bool) (map[string][]byte, error) {
|
||||
@@ -163,13 +166,13 @@ func (b XattrsBackend) getAll(ctx context.Context, n MetadataNode, skipCache, ac
|
||||
|
||||
// Set sets one attribute for the given path
|
||||
func (b XattrsBackend) Set(ctx context.Context, n MetadataNode, key string, val []byte) (err error) {
|
||||
return b.SetMultiple(ctx, n, map[string][]byte{key: val}, true)
|
||||
return b.SetMultiple(ctx, n, map[string][]byte{key: val})
|
||||
}
|
||||
|
||||
// SetMultiple sets a set of attribute for the given path
|
||||
func (b XattrsBackend) SetMultiple(ctx context.Context, n MetadataNode, attribs map[string][]byte, acquireLock bool) (err error) {
|
||||
func (b XattrsBackend) SetMultiple(ctx context.Context, n MetadataNode, attribs map[string][]byte) (err error) {
|
||||
path := n.InternalPath()
|
||||
if acquireLock {
|
||||
if !n.LockHeld() {
|
||||
err := os.MkdirAll(filepath.Dir(path), 0700)
|
||||
if err != nil {
|
||||
return err
|
||||
@@ -178,8 +181,12 @@ func (b XattrsBackend) SetMultiple(ctx context.Context, n MetadataNode, attribs
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
n.SetLockHeld(true)
|
||||
// Warning: do not remove the lockfile or we may lock the same file more than once, https://github.com/opencloud-eu/opencloud/issues/1793
|
||||
defer lockedFile.Close()
|
||||
defer func() {
|
||||
n.SetLockHeld(false)
|
||||
_ = lockedFile.Close()
|
||||
}()
|
||||
}
|
||||
|
||||
// error handling: Count if there are errors while setting the attribs.
|
||||
@@ -206,15 +213,19 @@ func (b XattrsBackend) SetMultiple(ctx context.Context, n MetadataNode, attribs
|
||||
}
|
||||
|
||||
// Remove an extended attribute key
|
||||
func (b XattrsBackend) Remove(ctx context.Context, n MetadataNode, key string, acquireLock bool) error {
|
||||
func (b XattrsBackend) Remove(ctx context.Context, n MetadataNode, key string) error {
|
||||
path := n.InternalPath()
|
||||
if acquireLock {
|
||||
if !n.LockHeld() {
|
||||
lockedFile, err := lockedfile.OpenFile(path+filelocks.LockFileSuffix, os.O_CREATE|os.O_WRONLY, 0600)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
n.SetLockHeld(true)
|
||||
// Warning: do not remove the lockfile or we may lock the same file more than once, https://github.com/opencloud-eu/opencloud/issues/1793
|
||||
defer lockedFile.Close()
|
||||
defer func() {
|
||||
n.SetLockHeld(false)
|
||||
_ = lockedFile.Close()
|
||||
}()
|
||||
}
|
||||
|
||||
err := xattr.Remove(path, key)
|
||||
@@ -275,38 +286,23 @@ func (XattrsBackend) LockfilePath(n MetadataNode) string { return n.InternalPath
|
||||
|
||||
// Lock locks the metadata for the given path
|
||||
func (b XattrsBackend) Lock(n MetadataNode) (UnlockFunc, error) {
|
||||
if n.LockHeld() {
|
||||
return func() error { return nil }, nil
|
||||
}
|
||||
|
||||
metaLockPath := b.LockfilePath(n)
|
||||
mlock, err := lockedfile.OpenFile(metaLockPath, os.O_RDWR|os.O_CREATE, 0600)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
n.SetLockHeld(true)
|
||||
return func() error {
|
||||
n.SetLockHeld(false)
|
||||
// Warning: do not remove the lockfile or we may lock the same file more than once, https://github.com/opencloud-eu/opencloud/issues/1793
|
||||
return mlock.Close()
|
||||
}, nil
|
||||
}
|
||||
|
||||
// LockAndRead locks the metadata for reading
|
||||
func (b XattrsBackend) LockAndRead(n MetadataNode) (UnlockFunc, io.Reader, error) {
|
||||
unlock, err := b.Lock(n)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
f, err := os.Open(b.MetadataPath(n))
|
||||
if err != nil {
|
||||
_ = unlock()
|
||||
return nil, nil, err
|
||||
}
|
||||
return unlock, f, nil
|
||||
}
|
||||
|
||||
// AllWithLockedSource reads all extended attributes from the given reader.
|
||||
// The path argument is used for storing the data in the cache
|
||||
func (b XattrsBackend) AllWithLockedSource(ctx context.Context, n MetadataNode, _ io.Reader) (map[string][]byte, error) {
|
||||
return b.All(ctx, n)
|
||||
}
|
||||
|
||||
func (b XattrsBackend) cacheKey(n MetadataNode) string {
|
||||
// rootPath is guaranteed to have no trailing slash
|
||||
// the cache key shouldn't begin with a slash as some stores drop it which can cause
|
||||
|
||||
87
vendor/github.com/opencloud-eu/reva/v2/pkg/storage/pkg/decomposedfs/node/node.go
generated
vendored
87
vendor/github.com/opencloud-eu/reva/v2/pkg/storage/pkg/decomposedfs/node/node.go
generated
vendored
@@ -44,13 +44,13 @@ import (
|
||||
"github.com/opencloud-eu/reva/v2/pkg/errtypes"
|
||||
"github.com/opencloud-eu/reva/v2/pkg/mime"
|
||||
"github.com/opencloud-eu/reva/v2/pkg/rhttp/datatx/metrics"
|
||||
"github.com/opencloud-eu/reva/v2/pkg/storage/internal/goroutinelock"
|
||||
"github.com/opencloud-eu/reva/v2/pkg/storage/pkg/decomposedfs/metadata"
|
||||
"github.com/opencloud-eu/reva/v2/pkg/storage/pkg/decomposedfs/metadata/prefixes"
|
||||
"github.com/opencloud-eu/reva/v2/pkg/storage/utils/ace"
|
||||
"github.com/opencloud-eu/reva/v2/pkg/storage/utils/grants"
|
||||
"github.com/opencloud-eu/reva/v2/pkg/utils"
|
||||
"github.com/pkg/errors"
|
||||
"github.com/rogpeppe/go-internal/lockedfile"
|
||||
"go.opentelemetry.io/otel"
|
||||
"go.opentelemetry.io/otel/trace"
|
||||
)
|
||||
@@ -139,7 +139,7 @@ type Tree interface {
|
||||
BuildSpaceIDIndexEntry(spaceID string) string
|
||||
ResolveSpaceIDIndexEntry(spaceID string) (string, error)
|
||||
|
||||
CreateRevision(ctx context.Context, n *Node, version string, f *lockedfile.File) (string, error)
|
||||
CreateRevision(ctx context.Context, n *Node, version string) (string, error)
|
||||
ListRevisions(ctx context.Context, ref *provider.Reference) ([]*provider.FileVersion, error)
|
||||
DownloadRevision(ctx context.Context, ref *provider.Reference, revisionKey string, openReaderFunc func(md *provider.ResourceInfo) bool) (*provider.ResourceInfo, io.ReadCloser, error)
|
||||
|
||||
@@ -166,8 +166,7 @@ type PathLookup interface {
|
||||
MetadataBackend() metadata.Backend
|
||||
TimeManager() TimeManager
|
||||
ReadBlobIDAndSizeAttr(ctx context.Context, n metadata.MetadataNode, attrs Attributes) (string, int64, error)
|
||||
CopyMetadataWithSourceLock(ctx context.Context, sourceNode, targetNode metadata.MetadataNode, filter func(attributeName string, value []byte) (newValue []byte, copy bool), lockedSource *lockedfile.File, acquireTargetLock bool) (err error)
|
||||
CopyMetadata(ctx context.Context, sourceNode, targetNode metadata.MetadataNode, filter func(attributeName string, value []byte) (newValue []byte, copy bool), acquireTargetLock bool) (err error)
|
||||
CopyMetadata(ctx context.Context, sourceNode, targetNode metadata.MetadataNode, filter func(attributeName string, value []byte) (newValue []byte, copy bool)) (err error)
|
||||
|
||||
PurgeNode(n *Node) error
|
||||
}
|
||||
@@ -181,6 +180,12 @@ type BaseNode struct {
|
||||
SpaceID string
|
||||
ID string
|
||||
|
||||
// lock records which goroutine (if any) holds this node's metadata lock, so the
|
||||
// metadata backend must not (re-)acquire it when reading attributes. Ownership is
|
||||
// scoped to the acquiring goroutine: a node leaked into another goroutine reports
|
||||
// LockHeld() == false there and takes its own lock rather than riding a stale flag.
|
||||
lock goroutinelock.Lock
|
||||
|
||||
lu PathLookup
|
||||
internalPathID string
|
||||
internalPath string
|
||||
@@ -197,6 +202,19 @@ func NewBaseNode(spaceID, nodeID string, lu PathLookup) *BaseNode {
|
||||
func (n *BaseNode) GetSpaceID() string { return n.SpaceID }
|
||||
func (n *BaseNode) GetID() string { return n.ID }
|
||||
|
||||
// LockHeld reports whether the calling goroutine holds this node's metadata lock.
|
||||
func (n *BaseNode) LockHeld() bool { return n.lock.Held() }
|
||||
|
||||
// SetLockHeld records whether the calling goroutine holds this node's metadata
|
||||
// lock
|
||||
func (n *BaseNode) SetLockHeld(held bool) {
|
||||
if held {
|
||||
n.lock.Hold()
|
||||
return
|
||||
}
|
||||
n.lock.Release()
|
||||
}
|
||||
|
||||
// InternalPath returns the internal path of the Node
|
||||
func (n *BaseNode) InternalPath() string {
|
||||
if len(n.internalPath) > 0 && n.ID == n.internalPathID {
|
||||
@@ -355,15 +373,15 @@ func LockAndReadNode(ctx context.Context, lu PathLookup, spaceID, nodeID, intern
|
||||
ctx, span := tracer.Start(ctx, "LockAndReadNode")
|
||||
defer span.End()
|
||||
|
||||
_, subspan := tracer.Start(ctx, "lockedfile.OpenFile")
|
||||
_, subspan := tracer.Start(ctx, "MetadataBackend.Lock")
|
||||
bn := NewBaseNode(spaceID, nodeID, lu)
|
||||
unlock, r, err := lu.MetadataBackend().LockAndRead(bn)
|
||||
unlock, err := lu.MetadataBackend().Lock(bn)
|
||||
subspan.End()
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
n, err := readNode(ctx, lu, spaceID, nodeID, internalPath, canListDisabledSpace, spaceRoot, skipParentCheck, r)
|
||||
n, err := readNode(ctx, lu, spaceID, nodeID, internalPath, canListDisabledSpace, spaceRoot, skipParentCheck, true)
|
||||
if err != nil {
|
||||
_ = unlock()
|
||||
return nil, nil, err
|
||||
@@ -373,17 +391,22 @@ func LockAndReadNode(ctx context.Context, lu PathLookup, spaceID, nodeID, intern
|
||||
return n, nil, errtypes.NotFound(filepath.Join(n.ParentID, n.Name))
|
||||
}
|
||||
|
||||
return n, unlock, nil
|
||||
n.SetLockHeld(true)
|
||||
return n, func() error {
|
||||
n.SetLockHeld(false)
|
||||
return unlock()
|
||||
}, nil
|
||||
}
|
||||
|
||||
// ReadNode creates a new instance from an id and checks if it exists
|
||||
func ReadNode(ctx context.Context, lu PathLookup, spaceID, nodeID, internalPath string, canListDisabledSpace bool, spaceRoot *Node, skipParentCheck bool) (*Node, error) {
|
||||
return readNode(ctx, lu, spaceID, nodeID, internalPath, canListDisabledSpace, spaceRoot, skipParentCheck, nil)
|
||||
return readNode(ctx, lu, spaceID, nodeID, internalPath, canListDisabledSpace, spaceRoot, skipParentCheck, false)
|
||||
}
|
||||
|
||||
// readNode reads a node by its id. If a reader is provided, it will be passed to the metadata backend to read the metadata.
|
||||
// This is useful when the caller already holds a lock to prevent deadlocks when reading the metadata.
|
||||
func readNode(ctx context.Context, lu PathLookup, spaceID, nodeID, internalPath string, canListDisabledSpace bool, spaceRoot *Node, skipParentCheck bool, r io.Reader) (*Node, error) {
|
||||
// readNode reads a node by its id. When alreadyLocked is true the caller already
|
||||
// holds the node's metadata lock, so the attributes are read without re-acquiring it
|
||||
// (which would dead-lock e.g. the hybrid backend).
|
||||
func readNode(ctx context.Context, lu PathLookup, spaceID, nodeID, internalPath string, canListDisabledSpace bool, spaceRoot *Node, skipParentCheck bool, alreadyLocked bool) (*Node, error) {
|
||||
ctx, span := tracer.Start(ctx, "ReadNode")
|
||||
defer span.End()
|
||||
var err error
|
||||
@@ -398,12 +421,15 @@ func readNode(ctx context.Context, lu PathLookup, spaceID, nodeID, internalPath
|
||||
},
|
||||
}
|
||||
spaceRoot.SpaceRoot = spaceRoot
|
||||
if alreadyLocked && nodeID == spaceID {
|
||||
spaceRoot.SetLockHeld(true)
|
||||
}
|
||||
|
||||
// If we hold the lock on the space root itself, prime its attribute cache
|
||||
// through the no-lock path so the owner/name/disabled reads below do not try
|
||||
// to re-acquire the already-held lock and self-deadlock.
|
||||
if r != nil && nodeID == spaceID {
|
||||
_, err = spaceRoot.XattrsWithReader(ctx, r)
|
||||
if alreadyLocked && nodeID == spaceID {
|
||||
_, err = spaceRoot.Xattrs(ctx)
|
||||
switch {
|
||||
case metadata.IsNotExist(err):
|
||||
return spaceRoot, nil // swallow not found, the node defaults to exists = false
|
||||
@@ -464,6 +490,9 @@ func readNode(ctx context.Context, lu PathLookup, spaceID, nodeID, internalPath
|
||||
},
|
||||
SpaceRoot: spaceRoot,
|
||||
}
|
||||
if alreadyLocked {
|
||||
n.SetLockHeld(true)
|
||||
}
|
||||
if internalPath != "" {
|
||||
n.internalPath = internalPath
|
||||
}
|
||||
@@ -477,7 +506,7 @@ func readNode(ctx context.Context, lu PathLookup, spaceID, nodeID, internalPath
|
||||
}()
|
||||
|
||||
var attrs Attributes
|
||||
attrs, err = n.XattrsWithReader(ctx, r)
|
||||
attrs, err = n.Xattrs(ctx)
|
||||
switch {
|
||||
case metadata.IsNotExist(err):
|
||||
return n, nil // swallow not found, the node defaults to exists = false
|
||||
@@ -570,9 +599,9 @@ func (n *Node) Child(ctx context.Context, name string) (*Node, error) {
|
||||
return readNode, nil
|
||||
}
|
||||
|
||||
// ParentWithReader returns the parent node
|
||||
func (n *Node) ParentWithReader(ctx context.Context, r io.Reader) (*Node, error) {
|
||||
_, span := tracer.Start(ctx, "ParentWithReader")
|
||||
// Parent returns the parent node
|
||||
func (n *Node) Parent(ctx context.Context) (*Node, error) {
|
||||
_, span := tracer.Start(ctx, "Parent")
|
||||
defer span.End()
|
||||
if n.ParentID == "" {
|
||||
return nil, fmt.Errorf("decomposedfs: root has no parent")
|
||||
@@ -586,8 +615,7 @@ func (n *Node) ParentWithReader(ctx context.Context, r io.Reader) (*Node, error)
|
||||
SpaceRoot: n.SpaceRoot,
|
||||
}
|
||||
|
||||
// fill metadata cache using the reader
|
||||
attrs, err := p.XattrsWithReader(ctx, r)
|
||||
attrs, err := p.Xattrs(ctx)
|
||||
switch {
|
||||
case metadata.IsNotExist(err):
|
||||
return p, nil // swallow not found, the node defaults to exists = false
|
||||
@@ -602,11 +630,6 @@ func (n *Node) ParentWithReader(ctx context.Context, r io.Reader) (*Node, error)
|
||||
return p, err
|
||||
}
|
||||
|
||||
// Parent returns the parent node
|
||||
func (n *Node) Parent(ctx context.Context) (p *Node, err error) {
|
||||
return n.ParentWithReader(ctx, nil)
|
||||
}
|
||||
|
||||
// Owner returns the space owner
|
||||
func (n *Node) Owner() *userpb.UserId {
|
||||
return n.SpaceRoot.owner
|
||||
@@ -740,7 +763,7 @@ func (n *Node) SetMtimeString(ctx context.Context, mtime string) error {
|
||||
// SetMTime writes the UTC mtime to the extended attributes or removes the attribute if nil is passed
|
||||
func (n *Node) SetMtime(ctx context.Context, t *time.Time) (err error) {
|
||||
if t == nil {
|
||||
return n.RemoveXattr(ctx, prefixes.MTimeAttr, true)
|
||||
return n.RemoveXattr(ctx, prefixes.MTimeAttr)
|
||||
}
|
||||
return n.SetXattrString(ctx, prefixes.MTimeAttr, t.UTC().Format(time.RFC3339Nano))
|
||||
}
|
||||
@@ -780,7 +803,7 @@ func (n *Node) SetFavorite(ctx context.Context, uid *userpb.UserId) error {
|
||||
func (n *Node) UnsetFavorite(ctx context.Context, uid *userpb.UserId) error {
|
||||
// the favorite flag is specific to the user, so we need to incorporate the userid
|
||||
fa := prefixes.FavoriteKey(uid)
|
||||
return n.RemoveXattr(ctx, fa, true)
|
||||
return n.RemoveXattr(ctx, fa)
|
||||
}
|
||||
|
||||
// IsDir returns true if the node is a directory
|
||||
@@ -1117,7 +1140,7 @@ func (n *Node) SetChecksum(ctx context.Context, csType string, h hash.Hash) (err
|
||||
|
||||
// UnsetTempEtag removes the temporary etag attribute
|
||||
func (n *Node) UnsetTempEtag(ctx context.Context) (err error) {
|
||||
return n.RemoveXattr(ctx, prefixes.TmpEtagAttr, true)
|
||||
return n.RemoveXattr(ctx, prefixes.TmpEtagAttr)
|
||||
}
|
||||
|
||||
func isGrantExpired(g *provider.Grant) bool {
|
||||
@@ -1278,7 +1301,7 @@ func (n *Node) ReadGrant(ctx context.Context, grantee string) (g *provider.Grant
|
||||
}
|
||||
|
||||
// ReadGrant reads a CS3 grant
|
||||
func (n *Node) DeleteGrant(ctx context.Context, g *provider.Grant, acquireLock bool) (err error) {
|
||||
func (n *Node) DeleteGrant(ctx context.Context, g *provider.Grant) (err error) {
|
||||
|
||||
var attr string
|
||||
if g.Grantee.Type == provider.GranteeType_GRANTEE_TYPE_GROUP {
|
||||
@@ -1287,7 +1310,7 @@ func (n *Node) DeleteGrant(ctx context.Context, g *provider.Grant, acquireLock b
|
||||
attr = prefixes.GrantUserAcePrefix + g.Grantee.GetUserId().OpaqueId
|
||||
}
|
||||
|
||||
if err = n.RemoveXattr(ctx, attr, acquireLock); err != nil {
|
||||
if err = n.RemoveXattr(ctx, attr); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
@@ -1379,7 +1402,7 @@ func (n *Node) UnmarkProcessing(ctx context.Context, uploadID string) error {
|
||||
// file started another postprocessing later - do not remove
|
||||
return nil
|
||||
}
|
||||
return n.RemoveXattr(ctx, prefixes.StatusPrefix, true)
|
||||
return n.RemoveXattr(ctx, prefixes.StatusPrefix)
|
||||
}
|
||||
|
||||
// IsProcessing returns true if the node is currently being processed
|
||||
@@ -1404,7 +1427,7 @@ func (n *Node) SetScanData(ctx context.Context, info string, date time.Time) err
|
||||
attribs := Attributes{}
|
||||
attribs.SetString(prefixes.ScanStatusPrefix, info)
|
||||
attribs.SetString(prefixes.ScanDatePrefix, date.Format(time.RFC3339Nano))
|
||||
return n.SetXattrsWithContext(ctx, attribs, true)
|
||||
return n.SetXattrsWithContext(ctx, attribs)
|
||||
}
|
||||
|
||||
// ScanData returns scanning information of the node
|
||||
|
||||
@@ -134,10 +134,6 @@ func (p *Permissions) assemblePermissions(ctx context.Context, n *Node, failOnTr
|
||||
return NoPermissions(), nil
|
||||
}
|
||||
|
||||
if u.GetId().GetType() == userpb.UserType_USER_TYPE_SERVICE {
|
||||
return ServiceAccountPermissions(), nil
|
||||
}
|
||||
|
||||
// are we reading a revision?
|
||||
if strings.Contains(n.ID, RevisionIDDelimiter) {
|
||||
// verify revision key format
|
||||
@@ -204,6 +200,11 @@ func (p *Permissions) assemblePermissions(ctx context.Context, n *Node, failOnTr
|
||||
return OwnerPermissions(), nil
|
||||
}
|
||||
|
||||
// merge in the general service account permissions; grants can only add to them
|
||||
if u.GetId().GetType() == userpb.UserType_USER_TYPE_SERVICE {
|
||||
AddPermissions(ap, ServiceAccountPermissions())
|
||||
}
|
||||
|
||||
appctx.GetLogger(ctx).Debug().Interface("permissions", ap).Str("spaceid", n.SpaceID).Str("nodeid", n.ID).Interface("user", u).Msg("returning agregated permissions")
|
||||
return ap, nil
|
||||
}
|
||||
|
||||
61
vendor/github.com/opencloud-eu/reva/v2/pkg/storage/pkg/decomposedfs/node/xattrs.go
generated
vendored
61
vendor/github.com/opencloud-eu/reva/v2/pkg/storage/pkg/decomposedfs/node/xattrs.go
generated
vendored
@@ -20,7 +20,6 @@ package node
|
||||
|
||||
import (
|
||||
"context"
|
||||
"io"
|
||||
"io/fs"
|
||||
"strconv"
|
||||
"time"
|
||||
@@ -72,7 +71,7 @@ func (md Attributes) SetTime(key string, t time.Time) {
|
||||
}
|
||||
|
||||
// SetXattrs sets multiple extended attributes on the write-through cache/node
|
||||
func (n *Node) SetXattrsWithContext(ctx context.Context, attribs map[string][]byte, acquireLock bool) (err error) {
|
||||
func (n *Node) SetXattrsWithContext(ctx context.Context, attribs map[string][]byte) (err error) {
|
||||
_, span := tracer.Start(ctx, "SetXattrsWithContext")
|
||||
defer span.End()
|
||||
if n.xattrsCache != nil {
|
||||
@@ -81,12 +80,12 @@ func (n *Node) SetXattrsWithContext(ctx context.Context, attribs map[string][]by
|
||||
}
|
||||
}
|
||||
|
||||
return n.lu.MetadataBackend().SetMultiple(ctx, n, attribs, acquireLock)
|
||||
return n.lu.MetadataBackend().SetMultiple(ctx, n, attribs)
|
||||
}
|
||||
|
||||
// SetXattrs sets multiple extended attributes on the write-through cache/node
|
||||
func (n *Node) SetXattrs(attribs map[string][]byte, acquireLock bool) (err error) {
|
||||
return n.SetXattrsWithContext(context.Background(), attribs, acquireLock)
|
||||
func (n *Node) SetXattrs(attribs map[string][]byte) (err error) {
|
||||
return n.SetXattrsWithContext(context.Background(), attribs)
|
||||
}
|
||||
|
||||
// SetXattr sets an extended attribute on the write-through cache/node
|
||||
@@ -108,33 +107,28 @@ func (n *Node) SetXattrString(ctx context.Context, key, val string) (err error)
|
||||
}
|
||||
|
||||
// RemoveXattr removes an extended attribute from the write-through cache/node
|
||||
func (n *Node) RemoveXattr(ctx context.Context, key string, acquireLock bool) error {
|
||||
func (n *Node) RemoveXattr(ctx context.Context, key string) error {
|
||||
if n.xattrsCache != nil {
|
||||
delete(n.xattrsCache, key)
|
||||
}
|
||||
return n.lu.MetadataBackend().Remove(ctx, n, key, acquireLock)
|
||||
return n.lu.MetadataBackend().Remove(ctx, n, key)
|
||||
}
|
||||
|
||||
// XattrsWithReader returns the extended attributes of the node. If the attributes have already
|
||||
// been cached they are not read from disk again.
|
||||
func (n *Node) XattrsWithReader(ctx context.Context, r io.Reader) (Attributes, error) {
|
||||
// loadXattrs reads and caches the node's extended attributes using the given load
|
||||
// function. It centralizes the empty-node guard and the write-through caching that
|
||||
// is shared by all the Xattrs accessors below.
|
||||
func (n *Node) loadXattrs(load func() (Attributes, error)) (Attributes, error) {
|
||||
if n.ID == "" {
|
||||
// Do not try to read the attribute of an empty node. The InternalPath points to the
|
||||
// base nodes directory in this case.
|
||||
return Attributes{}, &xattr.Error{Op: "node.XattrsWithReader", Path: n.InternalPath(), Err: xattr.ENOATTR}
|
||||
return Attributes{}, &xattr.Error{Op: "node.Xattrs", Path: n.InternalPath(), Err: xattr.ENOATTR}
|
||||
}
|
||||
|
||||
if n.xattrsCache != nil {
|
||||
return n.xattrsCache, nil
|
||||
}
|
||||
|
||||
var attrs Attributes
|
||||
var err error
|
||||
if r != nil {
|
||||
attrs, err = n.lu.MetadataBackend().AllWithLockedSource(ctx, n, r)
|
||||
} else {
|
||||
attrs, err = n.lu.MetadataBackend().All(ctx, n)
|
||||
}
|
||||
attrs, err := load()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -143,37 +137,32 @@ func (n *Node) XattrsWithReader(ctx context.Context, r io.Reader) (Attributes, e
|
||||
return n.xattrsCache, nil
|
||||
}
|
||||
|
||||
// Xattrs returns the extended attributes of the node. If the attributes have already
|
||||
// been cached they are not read from disk again.
|
||||
// Xattrs returns the extended attributes of the node, acquiring the metadata lock as
|
||||
// needed. If the attributes have already been cached they are not read from disk
|
||||
// again.
|
||||
func (n *Node) Xattrs(ctx context.Context) (Attributes, error) {
|
||||
return n.XattrsWithReader(ctx, nil)
|
||||
return n.loadXattrs(func() (Attributes, error) {
|
||||
return n.lu.MetadataBackend().All(ctx, n)
|
||||
})
|
||||
}
|
||||
|
||||
// Xattr returns an extended attribute of the node. If the attributes have already
|
||||
// been cached it is not read from disk again.
|
||||
func (n *Node) Xattr(ctx context.Context, key string) ([]byte, error) {
|
||||
path := n.InternalPath()
|
||||
|
||||
if path == "" {
|
||||
// Do not try to read the attribute of an non-existing node
|
||||
// Do not try to read the attribute of a non-existing node
|
||||
return []byte{}, fs.ErrNotExist
|
||||
}
|
||||
|
||||
if n.ID == "" {
|
||||
// Do not try to read the attribute of an empty node. The InternalPath points to the
|
||||
// base nodes directory in this case.
|
||||
return []byte{}, &xattr.Error{Op: "node.Xattr", Path: path, Name: key, Err: xattr.ENOATTR}
|
||||
attrs, err := n.loadXattrs(func() (Attributes, error) {
|
||||
return n.lu.MetadataBackend().All(ctx, n)
|
||||
})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if n.xattrsCache == nil {
|
||||
attrs, err := n.lu.MetadataBackend().All(ctx, n)
|
||||
if err != nil {
|
||||
return []byte{}, err
|
||||
}
|
||||
n.xattrsCache = attrs
|
||||
}
|
||||
|
||||
if val, ok := n.xattrsCache[key]; ok {
|
||||
if val, ok := attrs[key]; ok {
|
||||
return val, nil
|
||||
}
|
||||
// wrap the error as xattr does
|
||||
|
||||
@@ -127,7 +127,7 @@ func New(m map[string]interface{}) (*Options, error) {
|
||||
o.GatewayAddr = sharedconf.GetGatewaySVC(o.GatewayAddr)
|
||||
|
||||
if o.MetadataBackend == "" {
|
||||
o.MetadataBackend = "xattrs"
|
||||
o.MetadataBackend = "messagepack"
|
||||
}
|
||||
|
||||
// ensure user layout has no starting or trailing /
|
||||
|
||||
7
vendor/github.com/opencloud-eu/reva/v2/pkg/storage/pkg/decomposedfs/revisions.go
generated
vendored
7
vendor/github.com/opencloud-eu/reva/v2/pkg/storage/pkg/decomposedfs/revisions.go
generated
vendored
@@ -27,7 +27,6 @@ import (
|
||||
"time"
|
||||
|
||||
provider "github.com/cs3org/go-cs3apis/cs3/storage/provider/v1beta1"
|
||||
"github.com/rogpeppe/go-internal/lockedfile"
|
||||
|
||||
"github.com/opencloud-eu/reva/v2/pkg/appctx"
|
||||
"github.com/opencloud-eu/reva/v2/pkg/errtypes"
|
||||
@@ -99,12 +98,12 @@ func (fs *Decomposedfs) RestoreRevision(ctx context.Context, ref *provider.Refer
|
||||
}
|
||||
|
||||
// write lock node before copying metadata
|
||||
f, err := lockedfile.OpenFile(fs.lu.MetadataBackend().LockfilePath(n), os.O_RDWR|os.O_CREATE, 0600)
|
||||
unlock, err := fs.lu.MetadataBackend().Lock(n)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer func() {
|
||||
_ = f.Close()
|
||||
_ = unlock()
|
||||
_ = os.Remove(fs.lu.MetadataBackend().LockfilePath(n))
|
||||
}()
|
||||
|
||||
@@ -116,7 +115,7 @@ func (fs *Decomposedfs) RestoreRevision(ctx context.Context, ref *provider.Refer
|
||||
}
|
||||
|
||||
// create a revision of the current node
|
||||
if _, err := fs.tp.CreateRevision(ctx, n, mtime.UTC().Format(time.RFC3339Nano), f); err != nil {
|
||||
if _, err := fs.tp.CreateRevision(ctx, n, mtime.UTC().Format(time.RFC3339Nano)); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
|
||||
40
vendor/github.com/opencloud-eu/reva/v2/pkg/storage/pkg/decomposedfs/spaces.go
generated
vendored
40
vendor/github.com/opencloud-eu/reva/v2/pkg/storage/pkg/decomposedfs/spaces.go
generated
vendored
@@ -196,7 +196,7 @@ func (fs *Decomposedfs) CreateStorageSpace(ctx context.Context, req *provider.Cr
|
||||
metadata.SetString(prefixes.SpaceAliasAttr, alias)
|
||||
}
|
||||
|
||||
if err := root.SetXattrsWithContext(ctx, metadata, true); err != nil {
|
||||
if err := root.SetXattrsWithContext(ctx, metadata); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
@@ -697,7 +697,7 @@ func (fs *Decomposedfs) UpdateStorageSpace(ctx context.Context, req *provider.Up
|
||||
}
|
||||
metadata[prefixes.TreeMTimeAttr] = []byte(time.Now().UTC().Format(time.RFC3339Nano))
|
||||
|
||||
err = spaceNode.SetXattrsWithContext(ctx, metadata, true)
|
||||
err = spaceNode.SetXattrsWithContext(ctx, metadata)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -751,7 +751,6 @@ func (fs *Decomposedfs) DeleteStorageSpace(ctx context.Context, req *provider.De
|
||||
return errtypes.NewErrtypeFromStatus(status.NewInvalid(ctx, "can't purge enabled space"))
|
||||
}
|
||||
|
||||
// TODO invalidate ALL indexes in msgpack, not only by type
|
||||
spaceType, err := n.XattrString(ctx, prefixes.SpaceTypeAttr)
|
||||
if err != nil {
|
||||
return err
|
||||
@@ -760,6 +759,39 @@ func (fs *Decomposedfs) DeleteStorageSpace(ctx context.Context, req *provider.De
|
||||
return err
|
||||
}
|
||||
|
||||
// the by-type index is handled above; also remove the space from the user
|
||||
// and group indexes for the owner and every grantee, or they keep stale
|
||||
// entries pointing at the purged space. best effort, like the expired
|
||||
// grant removal: log and continue.
|
||||
// https://github.com/opencloud-eu/opencloud/issues/2985
|
||||
sublog := appctx.GetLogger(ctx).With().Str("spaceid", spaceID).Logger()
|
||||
if ownerID, err := n.XattrString(ctx, prefixes.OwnerIDAttr); err != nil {
|
||||
sublog.Error().Err(err).Msg("could not read space owner")
|
||||
} else if ownerID != "" {
|
||||
// remove from user index
|
||||
if err := fs.userSpaceIndex.Remove(ownerID, spaceID); err != nil {
|
||||
sublog.Error().Err(err).Msg("could not remove owner from user index")
|
||||
}
|
||||
}
|
||||
grants, err := n.ListGrants(ctx)
|
||||
if err != nil {
|
||||
sublog.Error().Err(err).Msg("could not list grants")
|
||||
}
|
||||
for _, g := range grants {
|
||||
switch g.Grantee.Type {
|
||||
case provider.GranteeType_GRANTEE_TYPE_USER:
|
||||
// remove from user index
|
||||
if err := fs.userSpaceIndex.Remove(g.Grantee.GetUserId().GetOpaqueId(), spaceID); err != nil {
|
||||
sublog.Error().Err(err).Str("grantee", g.Grantee.GetUserId().GetOpaqueId()).Msg("could not remove user from user index")
|
||||
}
|
||||
case provider.GranteeType_GRANTEE_TYPE_GROUP:
|
||||
// remove from group index
|
||||
if err := fs.groupSpaceIndex.Remove(g.Grantee.GetGroupId().GetOpaqueId(), spaceID); err != nil {
|
||||
sublog.Error().Err(err).Str("grantee", g.Grantee.GetGroupId().GetOpaqueId()).Msg("could not remove group from group index")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// invalidate cache
|
||||
if err := fs.lu.MetadataBackend().Purge(ctx, n); err != nil {
|
||||
return err
|
||||
@@ -935,7 +967,7 @@ func (fs *Decomposedfs) StorageSpaceFromNode(ctx context.Context, n *node.Node,
|
||||
// This way we don't have to have a cron job checking the grants in regular intervals.
|
||||
// The tradeof obviously is that this code is here.
|
||||
if isGrantExpired(g) {
|
||||
if err := n.DeleteGrant(ctx, g, true); err != nil {
|
||||
if err := n.DeleteGrant(ctx, g); err != nil {
|
||||
sublog.Error().Err(err).Str("grantee", id).
|
||||
Msg("failed to delete expired space grant")
|
||||
}
|
||||
|
||||
@@ -54,7 +54,7 @@ func (dtm *Manager) MTime(ctx context.Context, n *node.Node) (time.Time, error)
|
||||
// If the time is nil, the attribute is removed.
|
||||
func (dtm *Manager) SetMTime(ctx context.Context, n *node.Node, mtime *time.Time) error {
|
||||
if mtime == nil {
|
||||
return n.RemoveXattr(ctx, prefixes.MTimeAttr, true)
|
||||
return n.RemoveXattr(ctx, prefixes.MTimeAttr)
|
||||
}
|
||||
return n.SetXattrString(ctx, prefixes.MTimeAttr, mtime.UTC().Format(time.RFC3339Nano))
|
||||
}
|
||||
@@ -75,7 +75,7 @@ func (dtm *Manager) TMTime(ctx context.Context, n *node.Node) (time.Time, error)
|
||||
// If the time is nil, the attribute is removed.
|
||||
func (dtm *Manager) SetTMTime(ctx context.Context, n *node.Node, tmtime *time.Time) error {
|
||||
if tmtime == nil {
|
||||
return n.RemoveXattr(ctx, prefixes.TreeMTimeAttr, true)
|
||||
return n.RemoveXattr(ctx, prefixes.TreeMTimeAttr)
|
||||
}
|
||||
return n.SetXattrString(ctx, prefixes.TreeMTimeAttr, tmtime.UTC().Format(time.RFC3339Nano))
|
||||
}
|
||||
@@ -121,7 +121,7 @@ func (dtm *Manager) DTime(ctx context.Context, n *node.Node) (tmTime time.Time,
|
||||
// If the time is nil, the attribute is removed.
|
||||
func (dtm *Manager) SetDTime(ctx context.Context, n *node.Node, t *time.Time) (err error) {
|
||||
if t == nil {
|
||||
return n.RemoveXattr(ctx, prefixes.DTimeAttr, true)
|
||||
return n.RemoveXattr(ctx, prefixes.DTimeAttr)
|
||||
}
|
||||
return n.SetXattrString(ctx, prefixes.DTimeAttr, t.UTC().Format(time.RFC3339Nano))
|
||||
}
|
||||
|
||||
@@ -43,12 +43,6 @@ const (
|
||||
|
||||
var _propagationGracePeriod = 3 * time.Minute
|
||||
|
||||
type PropagationNode interface {
|
||||
GetSpaceID() string
|
||||
GetID() string
|
||||
InternalPath() string
|
||||
}
|
||||
|
||||
// AsyncPropagator implements asynchronous treetime & treesize propagation
|
||||
type AsyncPropagator struct {
|
||||
treeSizeAccounting bool
|
||||
@@ -130,8 +124,8 @@ func NewAsyncPropagator(treeSizeAccounting, treeTimeAccounting bool, o options.A
|
||||
now := time.Now()
|
||||
_ = os.Chtimes(changesDirPath, now, now)
|
||||
|
||||
n := node.NewBaseNode(parts[0], strings.TrimSuffix(parts[1], ".processing"), lookup)
|
||||
p.propagate(context.Background(), n, true, *log)
|
||||
nodeID := strings.TrimSuffix(parts[1], ".processing")
|
||||
p.propagate(context.Background(), parts[0], nodeID, true, *log)
|
||||
}()
|
||||
}
|
||||
}
|
||||
@@ -164,14 +158,17 @@ func (p AsyncPropagator) Propagate(ctx context.Context, n *node.Node, sizeDiff i
|
||||
SyncTime: time.Now().UTC(),
|
||||
SizeDiff: sizeDiff,
|
||||
}
|
||||
go p.queuePropagation(ctx, n, c, log)
|
||||
|
||||
// Hand only the node's logical identity to the goroutine, never the live
|
||||
// *node.Node: sharing the instance would leak and race its metadata-lock state.
|
||||
go p.queuePropagation(ctx, n.SpaceID, n.ID, c, log)
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (p AsyncPropagator) queuePropagation(ctx context.Context, n *node.Node, change Change, log zerolog.Logger) {
|
||||
func (p AsyncPropagator) queuePropagation(ctx context.Context, spaceID, nodeID string, change Change, log zerolog.Logger) {
|
||||
// add a change to the parent node
|
||||
changePath := p.changesPath(n.SpaceID, n.ID, uuid.New().String()+".mpk")
|
||||
changePath := p.changesPath(spaceID, nodeID, uuid.New().String()+".mpk")
|
||||
|
||||
data, err := msgpack.Marshal(change)
|
||||
if err != nil {
|
||||
@@ -212,7 +209,7 @@ func (p AsyncPropagator) queuePropagation(ctx context.Context, n *node.Node, cha
|
||||
|
||||
log.Debug().Msg("propagating")
|
||||
// add a change to the parent node
|
||||
changeDirPath := p.changesPath(n.SpaceID, n.ID, "")
|
||||
changeDirPath := p.changesPath(spaceID, nodeID, "")
|
||||
|
||||
// first rename the existing node dir
|
||||
err = os.Rename(changeDirPath, changeDirPath+".processing")
|
||||
@@ -224,11 +221,11 @@ func (p AsyncPropagator) queuePropagation(ctx context.Context, n *node.Node, cha
|
||||
// -> ignore, the previous propagation will pick the new changes up
|
||||
return
|
||||
}
|
||||
p.propagate(ctx, n, false, log)
|
||||
p.propagate(ctx, spaceID, nodeID, false, log)
|
||||
}
|
||||
|
||||
func (p AsyncPropagator) propagate(ctx context.Context, pn PropagationNode, recalculateTreeSize bool, log zerolog.Logger) {
|
||||
changeDirPath := p.changesPath(pn.GetSpaceID(), pn.GetID(), "")
|
||||
func (p AsyncPropagator) propagate(ctx context.Context, spaceID, nodeID string, recalculateTreeSize bool, log zerolog.Logger) {
|
||||
changeDirPath := p.changesPath(spaceID, nodeID, "")
|
||||
processingPath := changeDirPath + ".processing"
|
||||
|
||||
cleanup := func() {
|
||||
@@ -286,7 +283,7 @@ func (p AsyncPropagator) propagate(ctx context.Context, pn PropagationNode, reca
|
||||
attrs := node.Attributes{}
|
||||
|
||||
_, subspan = tracer.Start(ctx, "node.LockAndReadNode")
|
||||
n, unlock, err := node.LockAndReadNode(ctx, p.lookup, pn.GetSpaceID(), pn.GetID(), "", false, nil, false)
|
||||
n, unlock, err := node.LockAndReadNode(ctx, p.lookup, spaceID, nodeID, "", false, nil, false)
|
||||
subspan.End()
|
||||
if err != nil {
|
||||
if n != nil && !n.Exists {
|
||||
@@ -385,7 +382,7 @@ func (p AsyncPropagator) propagate(ctx context.Context, pn PropagationNode, reca
|
||||
log.Debug().Uint64("newSize", newSize).Msg("updated treesize of node")
|
||||
}
|
||||
|
||||
if err = n.SetXattrsWithContext(ctx, attrs, false); err != nil {
|
||||
if err = n.SetXattrsWithContext(ctx, attrs); err != nil {
|
||||
log.Error().Err(err).Msg("Failed to update extend attributes of node")
|
||||
cleanup()
|
||||
return
|
||||
@@ -400,7 +397,7 @@ func (p AsyncPropagator) propagate(ctx context.Context, pn PropagationNode, reca
|
||||
cleanup()
|
||||
|
||||
if !n.IsSpaceRoot(ctx) {
|
||||
p.queuePropagation(ctx, n, pc, log)
|
||||
p.queuePropagation(ctx, n.SpaceID, n.ID, pc, log)
|
||||
}
|
||||
|
||||
// Check for a changes dir that might have been added meanwhile and pick it up
|
||||
@@ -416,7 +413,7 @@ func (p AsyncPropagator) propagate(ctx context.Context, pn PropagationNode, reca
|
||||
// -> ignore, the previous propagation will pick the new changes up
|
||||
return
|
||||
}
|
||||
p.propagate(ctx, n, false, log)
|
||||
p.propagate(ctx, spaceID, nodeID, false, log)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -190,7 +190,7 @@ func (p SyncPropagator) propagateItem(ctx context.Context, n *node.Node, sTime t
|
||||
log.Debug().Uint64("newSize", newSize).Msg("updated treesize of parent node")
|
||||
}
|
||||
|
||||
if err = n.SetXattrsWithContext(ctx, attrs, false); err != nil {
|
||||
if err = n.SetXattrsWithContext(ctx, attrs); err != nil {
|
||||
log.Error().Err(err).Msg("Failed to update extend attributes of parent node")
|
||||
return n, true, err
|
||||
}
|
||||
|
||||
16
vendor/github.com/opencloud-eu/reva/v2/pkg/storage/pkg/decomposedfs/tree/revisions.go
generated
vendored
16
vendor/github.com/opencloud-eu/reva/v2/pkg/storage/pkg/decomposedfs/tree/revisions.go
generated
vendored
@@ -30,7 +30,6 @@ import (
|
||||
|
||||
provider "github.com/cs3org/go-cs3apis/cs3/storage/provider/v1beta1"
|
||||
"github.com/pkg/errors"
|
||||
"github.com/rogpeppe/go-internal/lockedfile"
|
||||
|
||||
"github.com/opencloud-eu/reva/v2/pkg/appctx"
|
||||
"github.com/opencloud-eu/reva/v2/pkg/errtypes"
|
||||
@@ -49,8 +48,10 @@ import (
|
||||
// We can add a background process to move old revisions to a slower storage
|
||||
// and replace the revision file with a symbolic link in the future, if necessary.
|
||||
|
||||
// CreateVersion creates a new version of the node
|
||||
func (tp *Tree) CreateRevision(ctx context.Context, n *node.Node, version string, f *lockedfile.File) (string, error) {
|
||||
// CreateRevision creates a new version of the node. The caller MUST already hold the
|
||||
// metadata lock of n, as the node's metadata is read (without re-locking) to copy the
|
||||
// blob metadata onto the new revision.
|
||||
func (tp *Tree) CreateRevision(ctx context.Context, n *node.Node, version string) (string, error) {
|
||||
versionNode := node.NewBaseNode(n.SpaceID, n.ID+node.RevisionIDDelimiter+version, tp.lookup)
|
||||
versionPath := versionNode.InternalPath()
|
||||
|
||||
@@ -116,13 +117,13 @@ func (tp *Tree) CreateRevision(ctx context.Context, n *node.Node, version string
|
||||
defer vf.Close()
|
||||
|
||||
// copy blob metadata to version node
|
||||
if err := tp.lookup.CopyMetadataWithSourceLock(ctx, n, versionNode, func(attributeName string, value []byte) (newValue []byte, copy bool) {
|
||||
if err := tp.lookup.CopyMetadata(ctx, n, versionNode, func(attributeName string, value []byte) (newValue []byte, copy bool) {
|
||||
return value, strings.HasPrefix(attributeName, prefixes.ChecksumPrefix) ||
|
||||
attributeName == prefixes.TypeAttr ||
|
||||
attributeName == prefixes.BlobIDAttr ||
|
||||
attributeName == prefixes.BlobsizeAttr ||
|
||||
attributeName == prefixes.MTimeAttr
|
||||
}, f, true); err != nil {
|
||||
}); err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
@@ -336,7 +337,7 @@ func (tp *Tree) RestoreRevision(ctx context.Context, sourceNode, targetNode meta
|
||||
attributeName == prefixes.TypeAttr ||
|
||||
attributeName == prefixes.BlobIDAttr ||
|
||||
attributeName == prefixes.BlobsizeAttr
|
||||
}, false)
|
||||
})
|
||||
if err != nil {
|
||||
return errtypes.InternalError("failed to copy blob xattrs to old revision to node: " + err.Error())
|
||||
}
|
||||
@@ -347,8 +348,7 @@ func (tp *Tree) RestoreRevision(ctx context.Context, sourceNode, targetNode meta
|
||||
err = tp.lookup.MetadataBackend().SetMultiple(ctx, targetNode,
|
||||
map[string][]byte{
|
||||
prefixes.MTimeAttr: []byte(mtime.UTC().Format(time.RFC3339Nano)),
|
||||
},
|
||||
false)
|
||||
})
|
||||
if err != nil {
|
||||
return errtypes.InternalError("failed to set mtime attribute on node: " + err.Error())
|
||||
}
|
||||
|
||||
18
vendor/github.com/opencloud-eu/reva/v2/pkg/storage/pkg/decomposedfs/tree/tree.go
generated
vendored
18
vendor/github.com/opencloud-eu/reva/v2/pkg/storage/pkg/decomposedfs/tree/tree.go
generated
vendored
@@ -156,7 +156,7 @@ func (t *Tree) TouchFile(ctx context.Context, n *node.Node, markprocessing bool,
|
||||
return errors.Wrap(err, "Decomposedfs: could not set mtime")
|
||||
}
|
||||
}
|
||||
err = n.SetXattrsWithContext(ctx, attributes, true)
|
||||
err = n.SetXattrsWithContext(ctx, attributes)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -288,7 +288,7 @@ func (t *Tree) Move(ctx context.Context, oldNode *node.Node, newNode *node.Node)
|
||||
attribs := node.Attributes{}
|
||||
attribs.SetString(prefixes.ParentidAttr, newNode.ParentID)
|
||||
attribs.SetString(prefixes.NameAttr, newNode.Name)
|
||||
if err := oldNode.SetXattrsWithContext(ctx, attribs, true); err != nil {
|
||||
if err := oldNode.SetXattrsWithContext(ctx, attribs); err != nil {
|
||||
return errors.Wrap(err, "Decomposedfs: could not update old node attributes")
|
||||
}
|
||||
|
||||
@@ -458,7 +458,7 @@ func (t *Tree) Delete(ctx context.Context, n *node.Node) (err error) {
|
||||
trashLink := filepath.Join(t.options.Root, "spaces", lookup.Pathify(n.SpaceRoot.ID, 1, 2), "trash", lookup.Pathify(n.ID, 4, 2))
|
||||
if err := os.MkdirAll(filepath.Dir(trashLink), 0700); err != nil {
|
||||
// Roll back changes
|
||||
_ = n.RemoveXattr(ctx, prefixes.TrashOriginAttr, true)
|
||||
_ = n.RemoveXattr(ctx, prefixes.TrashOriginAttr)
|
||||
return err
|
||||
}
|
||||
|
||||
@@ -471,7 +471,7 @@ func (t *Tree) Delete(ctx context.Context, n *node.Node) (err error) {
|
||||
err = os.Symlink("../../../../../nodes/"+lookup.Pathify(n.ID, 4, 2)+node.TrashIDDelimiter+deletionTime, trashLink)
|
||||
if err != nil {
|
||||
// Roll back changes
|
||||
_ = n.RemoveXattr(ctx, prefixes.TrashOriginAttr, true)
|
||||
_ = n.RemoveXattr(ctx, prefixes.TrashOriginAttr)
|
||||
return
|
||||
}
|
||||
|
||||
@@ -485,12 +485,12 @@ func (t *Tree) Delete(ctx context.Context, n *node.Node) (err error) {
|
||||
// To roll back changes
|
||||
// TODO remove symlink
|
||||
// Roll back changes
|
||||
_ = n.RemoveXattr(ctx, prefixes.TrashOriginAttr, true)
|
||||
_ = n.RemoveXattr(ctx, prefixes.TrashOriginAttr)
|
||||
return
|
||||
}
|
||||
err = t.lookup.MetadataBackend().Rename(n, trashNode)
|
||||
if err != nil {
|
||||
_ = n.RemoveXattr(ctx, prefixes.TrashOriginAttr, true)
|
||||
_ = n.RemoveXattr(ctx, prefixes.TrashOriginAttr)
|
||||
_ = os.Rename(trashPath, nodePath)
|
||||
return
|
||||
}
|
||||
@@ -507,7 +507,7 @@ func (t *Tree) Delete(ctx context.Context, n *node.Node) (err error) {
|
||||
// TODO revert the rename
|
||||
// TODO remove symlink
|
||||
// Roll back changes
|
||||
_ = n.RemoveXattr(ctx, prefixes.TrashOriginAttr, true)
|
||||
_ = n.RemoveXattr(ctx, prefixes.TrashOriginAttr)
|
||||
return
|
||||
}
|
||||
|
||||
@@ -580,7 +580,7 @@ func (t *Tree) RestoreRecycleItemFunc(ctx context.Context, spaceid, key, trashPa
|
||||
// set ParentidAttr to restorePath's node parent id
|
||||
attrs.SetString(prefixes.ParentidAttr, targetNode.ParentID)
|
||||
|
||||
if err = t.lookup.MetadataBackend().SetMultiple(ctx, restoreNode, map[string][]byte(attrs), true); err != nil {
|
||||
if err = t.lookup.MetadataBackend().SetMultiple(ctx, restoreNode, map[string][]byte(attrs)); err != nil {
|
||||
return nil, errors.Wrap(err, "Decomposedfs: could not update recycle node")
|
||||
}
|
||||
|
||||
@@ -850,7 +850,7 @@ func (t *Tree) createDirNode(ctx context.Context, n *node.Node) (err error) {
|
||||
if t.options.TreeTimeAccounting || t.options.TreeSizeAccounting {
|
||||
attributes[prefixes.PropagationAttr] = []byte("1") // mark the node for propagation
|
||||
}
|
||||
return n.SetXattrsWithContext(ctx, attributes, true)
|
||||
return n.SetXattrsWithContext(ctx, attributes)
|
||||
}
|
||||
|
||||
var nodeIDRegep = regexp.MustCompile(`.*/nodes/([^.]*).*`)
|
||||
|
||||
13
vendor/github.com/opencloud-eu/reva/v2/pkg/storage/pkg/decomposedfs/upload.go
generated
vendored
13
vendor/github.com/opencloud-eu/reva/v2/pkg/storage/pkg/decomposedfs/upload.go
generated
vendored
@@ -380,6 +380,19 @@ func (fs *Decomposedfs) GetUpload(ctx context.Context, id string) (tusd.Upload,
|
||||
return ul, err
|
||||
}
|
||||
|
||||
// ResolveUpload stats the resource produced by a finished upload, reading it as the upload's
|
||||
// executant. The session is reconstructed from the given tus FileInfo rather than the session
|
||||
// store, so it still resolves after a synchronous upload has cleaned up its session. It returns
|
||||
// the resource info and the executant context, so the caller can derive identity dependent
|
||||
// values such as the WebDAV permissions. https://github.com/opencloud-eu/opencloud/issues/2409
|
||||
func (fs *Decomposedfs) ResolveUpload(ctx context.Context, info tusd.FileInfo) (*provider.ResourceInfo, context.Context, error) {
|
||||
session := fs.sessionStore.SessionFromInfo(info)
|
||||
ctx = session.Context(ctx)
|
||||
ref := session.Reference()
|
||||
ri, err := fs.GetMD(ctx, &ref, nil, nil)
|
||||
return ri, ctx, err
|
||||
}
|
||||
|
||||
// ListUploadSessions returns the upload sessions for the given filter
|
||||
func (fs *Decomposedfs) ListUploadSessions(ctx context.Context, filter storage.UploadSessionFilter) ([]storage.UploadSession, error) {
|
||||
var sessions []*upload.DecomposedFsSession
|
||||
|
||||
25
vendor/github.com/opencloud-eu/reva/v2/pkg/storage/pkg/decomposedfs/upload/store.go
generated
vendored
25
vendor/github.com/opencloud-eu/reva/v2/pkg/storage/pkg/decomposedfs/upload/store.go
generated
vendored
@@ -44,7 +44,6 @@ import (
|
||||
"github.com/opencloud-eu/reva/v2/pkg/storage/pkg/decomposedfs/options"
|
||||
"github.com/opencloud-eu/reva/v2/pkg/storage/pkg/decomposedfs/usermapper"
|
||||
"github.com/pkg/errors"
|
||||
"github.com/rogpeppe/go-internal/lockedfile"
|
||||
"github.com/rs/zerolog"
|
||||
tusd "github.com/tus/tusd/v2/pkg/handler"
|
||||
)
|
||||
@@ -100,6 +99,17 @@ func (store DecomposedFsStore) New(ctx context.Context) *DecomposedFsSession {
|
||||
}
|
||||
}
|
||||
|
||||
// SessionFromInfo wraps an existing tus FileInfo into an upload session without reading the
|
||||
// session store. It lets callers use an upload's session, e.g. its reference and
|
||||
// executant context, after the store entry has already been cleaned up, as happens for a
|
||||
// synchronous upload in its finish response callback.
|
||||
func (store DecomposedFsStore) SessionFromInfo(info tusd.FileInfo) *DecomposedFsSession {
|
||||
return &DecomposedFsSession{
|
||||
store: store,
|
||||
info: info,
|
||||
}
|
||||
}
|
||||
|
||||
// List lists all upload sessions
|
||||
func (store DecomposedFsStore) List(ctx context.Context) ([]*DecomposedFsSession, error) {
|
||||
uploads := []*DecomposedFsSession{}
|
||||
@@ -286,7 +296,7 @@ func (store DecomposedFsStore) CreateNodeForUpload(ctx context.Context, session
|
||||
}
|
||||
|
||||
// update node metadata with new blobid etc
|
||||
err = n.SetXattrsWithContext(ctx, initAttrs, false)
|
||||
err = n.SetXattrsWithContext(ctx, initAttrs)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "Decomposedfs: could not write metadata")
|
||||
}
|
||||
@@ -306,16 +316,11 @@ func (store DecomposedFsStore) updateExistingNode(ctx context.Context, session *
|
||||
defer span.End()
|
||||
|
||||
// write lock existing node before reading any metadata
|
||||
f, err := lockedfile.OpenFile(store.lu.MetadataBackend().LockfilePath(n), os.O_RDWR|os.O_CREATE, 0600)
|
||||
unlock, err := store.lu.MetadataBackend().Lock(n)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
unlock := func() error {
|
||||
// NOTE: to prevent stale NFS file handles do not remove lock file!
|
||||
return f.Close()
|
||||
}
|
||||
|
||||
old, _ := node.ReadNode(ctx, store.lu, spaceID, n.ID, "", false, nil, false)
|
||||
if _, err := node.CheckQuota(ctx, n.SpaceRoot, true, uint64(old.Blobsize), fsize); err != nil {
|
||||
return unlock, err
|
||||
@@ -366,7 +371,7 @@ func (store DecomposedFsStore) updateExistingNode(ctx context.Context, session *
|
||||
span.AddEvent("CreateVersion")
|
||||
timestamp := oldNodeMtime.UTC().Format(time.RFC3339Nano)
|
||||
versionID := n.ID + node.RevisionIDDelimiter + timestamp
|
||||
versionPath, err := session.store.tp.CreateRevision(ctx, n, timestamp, f)
|
||||
versionPath, err := session.store.tp.CreateRevision(ctx, n, timestamp)
|
||||
if err != nil {
|
||||
if !errors.Is(err, os.ErrExist) {
|
||||
return unlock, err
|
||||
@@ -389,7 +394,7 @@ func (store DecomposedFsStore) updateExistingNode(ctx context.Context, session *
|
||||
}
|
||||
|
||||
// clean revision file
|
||||
if versionPath, err = session.store.tp.CreateRevision(ctx, n, timestamp, f); err != nil {
|
||||
if versionPath, err = session.store.tp.CreateRevision(ctx, n, timestamp); err != nil {
|
||||
return unlock, err
|
||||
}
|
||||
}
|
||||
|
||||
56
vendor/github.com/opencloud-eu/reva/v2/pkg/storage/pkg/decomposedfs/upload/upload.go
generated
vendored
56
vendor/github.com/opencloud-eu/reva/v2/pkg/storage/pkg/decomposedfs/upload/upload.go
generated
vendored
@@ -35,7 +35,6 @@ import (
|
||||
provider "github.com/cs3org/go-cs3apis/cs3/storage/provider/v1beta1"
|
||||
"github.com/golang-jwt/jwt/v5"
|
||||
"github.com/pkg/errors"
|
||||
"github.com/rogpeppe/go-internal/lockedfile"
|
||||
tusd "github.com/tus/tusd/v2/pkg/handler"
|
||||
"go.opentelemetry.io/otel"
|
||||
"go.opentelemetry.io/otel/trace"
|
||||
@@ -46,6 +45,7 @@ import (
|
||||
"github.com/opencloud-eu/reva/v2/pkg/events"
|
||||
"github.com/opencloud-eu/reva/v2/pkg/rhttp/datatx/metrics"
|
||||
"github.com/opencloud-eu/reva/v2/pkg/rhttp/datatx/utils/download"
|
||||
"github.com/opencloud-eu/reva/v2/pkg/storage/pkg/decomposedfs/disk"
|
||||
"github.com/opencloud-eu/reva/v2/pkg/storage/pkg/decomposedfs/metadata/prefixes"
|
||||
"github.com/opencloud-eu/reva/v2/pkg/storage/pkg/decomposedfs/node"
|
||||
"github.com/opencloud-eu/reva/v2/pkg/utils"
|
||||
@@ -71,6 +71,10 @@ func (session *DecomposedFsSession) WriteChunk(ctx context.Context, _ int64, src
|
||||
return 0, err
|
||||
}
|
||||
defer func() {
|
||||
// sync the written chunk to disk. This ensures that the upload can be resumed,
|
||||
// and helps to prevent issues with filesystem/journal freezes at the end of the upload
|
||||
// when committing a large fsync operation on slow disks.
|
||||
_ = disk.Fdatasync(file)
|
||||
_ = file.Close()
|
||||
}()
|
||||
|
||||
@@ -291,7 +295,7 @@ func (session *DecomposedFsSession) Finalize(ctx context.Context) (err error) {
|
||||
ctx, span := tracer.Start(session.Context(ctx), "Finalize")
|
||||
defer span.End()
|
||||
|
||||
revisionNode := node.New(session.SpaceID(), session.NodeID(), "", "", session.Size(), session.ID(),
|
||||
n := node.New(session.SpaceID(), session.NodeID(), "", "", session.Size(), session.ID(),
|
||||
provider.ResourceType_RESOURCE_TYPE_FILE, session.SpaceOwner(), session.store.lu)
|
||||
|
||||
var (
|
||||
@@ -305,19 +309,20 @@ func (session *DecomposedFsSession) Finalize(ctx context.Context) (err error) {
|
||||
case spaceRoot.InternalPath() == "":
|
||||
return fmt.Errorf("space root for space id %s has no valid internal path", session.SpaceID())
|
||||
default:
|
||||
revisionNode.SpaceRoot = spaceRoot
|
||||
n.SpaceRoot = spaceRoot
|
||||
}
|
||||
|
||||
// lock the node before writing the blob
|
||||
lockedNode, err := lockedfile.OpenFile(session.store.lu.MetadataBackend().LockfilePath(revisionNode), os.O_RDWR|os.O_CREATE, 0600)
|
||||
// lock the node before reading its metadata and writing the blob
|
||||
unlock, err := session.store.lu.MetadataBackend().Lock(n)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer func() {
|
||||
_ = lockedNode.Close()
|
||||
_ = unlock()
|
||||
}()
|
||||
|
||||
attribs, err := revisionNode.XattrsWithReader(ctx, lockedNode)
|
||||
// Read the node attributes while holding the lock acquired above.
|
||||
attribs, err := n.Xattrs(ctx)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -327,32 +332,27 @@ func (session *DecomposedFsSession) Finalize(ctx context.Context) (err error) {
|
||||
|
||||
// another upload on this node is in progress or has finished since we started
|
||||
if !isProcessing || processingID != session.ID() {
|
||||
versionID := revisionNode.ID + node.RevisionIDDelimiter + session.MTime().UTC().Format(time.RFC3339Nano)
|
||||
versionID := n.ID + node.RevisionIDDelimiter + session.MTime().UTC().Format(time.RFC3339Nano)
|
||||
// There should be a revision node (created by the other upload that finished before us), read it and upload our blob there.
|
||||
existingRevisionNode, err := node.ReadNode(ctx, session.store.lu, session.SpaceID(), versionID, "", false, spaceRoot, false)
|
||||
existingRevisionNode, revisionNodeUnlock, err := node.LockAndReadNode(ctx, session.store.lu, session.SpaceID(), versionID, "", false, spaceRoot, false)
|
||||
if err != nil || !existingRevisionNode.Exists {
|
||||
// The revision node has not been created. Likely because the file on disk was modified externally and re-assilimated (watchfs == true)
|
||||
// Let's create the revision node now and upload the blob to it.
|
||||
revisionNode, err = session.createRevisionNodeForUpload(ctx, revisionNode, session.MTime().UTC().Format(time.RFC3339Nano), lockedNode)
|
||||
n, revisionNodeUnlock, err = session.createRevisionNodeForUpload(ctx, n, session.MTime().UTC().Format(time.RFC3339Nano))
|
||||
if err != nil {
|
||||
appctx.GetLogger(ctx).Debug().Err(err).Str("versionID", session.MTime().UTC().Format(time.RFC3339Nano)).Msg("failed to create revision node for upload finalization")
|
||||
return err
|
||||
}
|
||||
} else {
|
||||
revisionNode = existingRevisionNode
|
||||
n = existingRevisionNode
|
||||
}
|
||||
// lock this node as well, before writing the blob
|
||||
revisionNodeUnlock, err := session.store.lu.MetadataBackend().Lock(revisionNode)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
appctx.GetLogger(ctx).Debug().Str("new nodepath", revisionNode.InternalPath()).Msg("uploading to revision node, that was created for us by another upload")
|
||||
appctx.GetLogger(ctx).Debug().Str("new nodepath", n.InternalPath()).Msg("uploading to revision node, that was created for us by another upload")
|
||||
defer func() { _ = revisionNodeUnlock() }()
|
||||
}
|
||||
|
||||
// upload the data to the blobstore
|
||||
_, subspan := tracer.Start(ctx, "WriteBlob")
|
||||
err = session.store.tp.WriteBlob(revisionNode, session.binPath())
|
||||
err = session.store.tp.WriteBlob(n, session.binPath())
|
||||
subspan.End()
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "failed to upload file to blobstore")
|
||||
@@ -361,19 +361,19 @@ func (session *DecomposedFsSession) Finalize(ctx context.Context) (err error) {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (session *DecomposedFsSession) createRevisionNodeForUpload(ctx context.Context, baseNode *node.Node, rev string, lockedNode *lockedfile.File) (*node.Node, error) {
|
||||
func (session *DecomposedFsSession) createRevisionNodeForUpload(ctx context.Context, baseNode *node.Node, rev string) (*node.Node, func() error, error) {
|
||||
versionID := baseNode.ID + node.RevisionIDDelimiter + rev
|
||||
log := appctx.GetLogger(ctx)
|
||||
_, err := session.store.tp.CreateRevision(ctx, baseNode, rev, lockedNode)
|
||||
_, err := session.store.tp.CreateRevision(ctx, baseNode, rev)
|
||||
if err != nil {
|
||||
log.Error().Err(err).Str("versionID", versionID).Msg("failed to create revision node for upload")
|
||||
return nil, err
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
// FIXME: We already calculated the checksums in FinishUpload, we should maybe pass them via the session instead of recalculating them here
|
||||
sha1h, md5h, adler32h, err := node.CalculateChecksums(ctx, session.binPath())
|
||||
if err != nil {
|
||||
return nil, err
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
// update checksums
|
||||
@@ -382,7 +382,7 @@ func (session *DecomposedFsSession) createRevisionNodeForUpload(ctx context.Cont
|
||||
prefixes.ChecksumPrefix + "md5": md5h.Sum(nil),
|
||||
prefixes.ChecksumPrefix + "adler32": adler32h.Sum(nil),
|
||||
}
|
||||
revisionNode, err := node.ReadNode(ctx, session.store.lu, session.SpaceID(), versionID, "", false, baseNode.SpaceRoot, false)
|
||||
revisionNode, unlock, err := node.LockAndReadNode(ctx, session.store.lu, session.SpaceID(), versionID, "", false, baseNode.SpaceRoot, false)
|
||||
if err == nil {
|
||||
mtime := session.MTime()
|
||||
attrs.SetString(prefixes.BlobIDAttr, session.ID())
|
||||
@@ -392,14 +392,16 @@ func (session *DecomposedFsSession) createRevisionNodeForUpload(ctx context.Cont
|
||||
|
||||
err = session.store.lu.TimeManager().OverrideMtime(ctx, revisionNode, &attrs, mtime)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "Decomposedfs: failed to set the mtime")
|
||||
_ = unlock()
|
||||
return nil, nil, errors.Wrap(err, "Decomposedfs: failed to set the mtime")
|
||||
}
|
||||
err = revisionNode.SetXattrsWithContext(ctx, attrs, false)
|
||||
err = revisionNode.SetXattrsWithContext(ctx, attrs)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "Decomposedfs: failed to set node attributes")
|
||||
_ = unlock()
|
||||
return nil, nil, errors.Wrap(err, "Decomposedfs: failed to set node attributes")
|
||||
}
|
||||
}
|
||||
return revisionNode, err
|
||||
return revisionNode, unlock, err
|
||||
}
|
||||
|
||||
func checkHash(expected string, h hash.Hash) error {
|
||||
|
||||
14
vendor/github.com/opencloud-eu/reva/v2/pkg/storage/uploads.go
generated
vendored
14
vendor/github.com/opencloud-eu/reva/v2/pkg/storage/uploads.go
generated
vendored
@@ -52,6 +52,20 @@ type UploadSessionLister interface {
|
||||
ListUploadSessions(ctx context.Context, filter UploadSessionFilter) ([]UploadSession, error)
|
||||
}
|
||||
|
||||
// UploadSessionResolver defines the interface for FS implementations that can resolve a finished
|
||||
// upload to the resource it produced, read as the upload's executant. The tus data path is
|
||||
// authenticated by a transfer token that carries no user identity, so a caller there cannot stat
|
||||
// the resource itself; this lets the storage driver, which knows the upload's executant, do it.
|
||||
// https://github.com/opencloud-eu/opencloud/issues/2409
|
||||
type UploadSessionResolver interface {
|
||||
// ResolveUpload stats the resource produced by the given finished upload and returns it
|
||||
// together with a context carrying the upload's executant identity, so the caller can derive
|
||||
// identity dependent values such as the WebDAV permissions. The upload is identified by the
|
||||
// given tus FileInfo rather than a session store lookup, so the call still works after a
|
||||
// synchronous upload has cleaned up its session.
|
||||
ResolveUpload(ctx context.Context, info tusd.FileInfo) (*provider.ResourceInfo, context.Context, error)
|
||||
}
|
||||
|
||||
// UploadSession is the interface that storage drivers need to return whan listing upload sessions.
|
||||
type UploadSession interface {
|
||||
// ID returns the upload id
|
||||
|
||||
12
vendor/github.com/opencloud-eu/reva/v2/pkg/storage/utils/middleware/middleware.go
generated
vendored
12
vendor/github.com/opencloud-eu/reva/v2/pkg/storage/utils/middleware/middleware.go
generated
vendored
@@ -27,6 +27,7 @@ import (
|
||||
provider "github.com/cs3org/go-cs3apis/cs3/storage/provider/v1beta1"
|
||||
tusd "github.com/tus/tusd/v2/pkg/handler"
|
||||
|
||||
"github.com/opencloud-eu/reva/v2/pkg/errtypes"
|
||||
"github.com/opencloud-eu/reva/v2/pkg/storage"
|
||||
"github.com/opencloud-eu/reva/v2/pkg/storage/utils/decomposedfs/upload"
|
||||
"github.com/opencloud-eu/reva/v2/pkg/storagespace"
|
||||
@@ -63,6 +64,17 @@ func (f *FS) ListUploadSessions(ctx context.Context, filter storage.UploadSessio
|
||||
return f.next.(storage.UploadSessionLister).ListUploadSessions(ctx, filter)
|
||||
}
|
||||
|
||||
// ResolveUpload resolves a finished upload to the resource it produced, read as the upload's
|
||||
// executant. It is best effort: if the wrapped storage does not resolve uploads it returns
|
||||
// NotSupported rather than panicking, so the tus finalize falls back to no headers.
|
||||
func (f *FS) ResolveUpload(ctx context.Context, info tusd.FileInfo) (*provider.ResourceInfo, context.Context, error) {
|
||||
r, ok := f.next.(storage.UploadSessionResolver)
|
||||
if !ok {
|
||||
return nil, ctx, errtypes.NotSupported("storage does not resolve upload sessions")
|
||||
}
|
||||
return r.ResolveUpload(ctx, info)
|
||||
}
|
||||
|
||||
// UseIn tells the tus upload middleware which extensions it supports.
|
||||
func (f *FS) UseIn(composer *tusd.StoreComposer) {
|
||||
f.next.(storage.ComposableFS).UseIn(composer)
|
||||
|
||||
Reference in New Issue
Block a user