github.com/cs3org/reva/v2@v2.27.7/pkg/storage/utils/localfs/localfs.go (about)

     1  // Copyright 2018-2021 CERN
     2  //
     3  // Licensed under the Apache License, Version 2.0 (the "License");
     4  // you may not use this file except in compliance with the License.
     5  // You may obtain a copy of the License at
     6  //
     7  //     http://www.apache.org/licenses/LICENSE-2.0
     8  //
     9  // Unless required by applicable law or agreed to in writing, software
    10  // distributed under the License is distributed on an "AS IS" BASIS,
    11  // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    12  // See the License for the specific language governing permissions and
    13  // limitations under the License.
    14  //
    15  // In applying this license, CERN does not waive the privileges and immunities
    16  // granted to it by virtue of its status as an Intergovernmental Organization
    17  // or submit itself to any jurisdiction.
    18  
    19  package localfs
    20  
    21  import (
    22  	"context"
    23  	"database/sql"
    24  	"fmt"
    25  	"io"
    26  	"net/url"
    27  	"os"
    28  	"path"
    29  	"strconv"
    30  	"strings"
    31  	"time"
    32  
    33  	grouppb "github.com/cs3org/go-cs3apis/cs3/identity/group/v1beta1"
    34  	userpb "github.com/cs3org/go-cs3apis/cs3/identity/user/v1beta1"
    35  	provider "github.com/cs3org/go-cs3apis/cs3/storage/provider/v1beta1"
    36  	types "github.com/cs3org/go-cs3apis/cs3/types/v1beta1"
    37  	ctxpkg "github.com/cs3org/reva/v2/pkg/ctx"
    38  	"github.com/cs3org/reva/v2/pkg/errtypes"
    39  	"github.com/cs3org/reva/v2/pkg/mime"
    40  	"github.com/cs3org/reva/v2/pkg/storage"
    41  	"github.com/cs3org/reva/v2/pkg/storage/utils/acl"
    42  	"github.com/cs3org/reva/v2/pkg/storage/utils/chunking"
    43  	"github.com/cs3org/reva/v2/pkg/storage/utils/grants"
    44  	"github.com/cs3org/reva/v2/pkg/storage/utils/templates"
    45  	"github.com/cs3org/reva/v2/pkg/utils"
    46  	"github.com/pkg/errors"
    47  )
    48  
    49  // Config holds the configuration details for the local fs.
    50  type Config struct {
    51  	Root                string `mapstructure:"root"`
    52  	DisableHome         bool   `mapstructure:"disable_home"`
    53  	UserLayout          string `mapstructure:"user_layout"`
    54  	ShareFolder         string `mapstructure:"share_folder"`
    55  	DataTransfersFolder string `mapstructure:"data_transfers_folder"`
    56  	Uploads             string `mapstructure:"uploads"`
    57  	DataDirectory       string `mapstructure:"data_directory"`
    58  	RecycleBin          string `mapstructure:"recycle_bin"`
    59  	Versions            string `mapstructure:"versions"`
    60  	Shadow              string `mapstructure:"shadow"`
    61  	References          string `mapstructure:"references"`
    62  }
    63  
    64  func (c *Config) init() {
    65  	if c.Root == "" {
    66  		c.Root = "/var/tmp/reva"
    67  	}
    68  
    69  	if c.UserLayout == "" {
    70  		c.UserLayout = "{{.Username}}"
    71  	}
    72  
    73  	if c.ShareFolder == "" {
    74  		c.ShareFolder = "/MyShares"
    75  	}
    76  
    77  	if c.DataTransfersFolder == "" {
    78  		c.DataTransfersFolder = "/DataTransfers"
    79  	}
    80  
    81  	// ensure share folder always starts with slash
    82  	c.ShareFolder = path.Join("/", c.ShareFolder)
    83  
    84  	c.DataDirectory = path.Join(c.Root, "data")
    85  	c.Uploads = path.Join(c.Root, ".uploads")
    86  	c.Shadow = path.Join(c.Root, ".shadow")
    87  
    88  	c.References = path.Join(c.Shadow, "references")
    89  	c.RecycleBin = path.Join(c.Shadow, "recycle_bin")
    90  	c.Versions = path.Join(c.Shadow, "versions")
    91  
    92  }
    93  
    94  type localfs struct {
    95  	conf         *Config
    96  	db           *sql.DB
    97  	chunkHandler *chunking.ChunkHandler
    98  }
    99  
   100  // NewLocalFS returns a storage.FS interface implementation that controls then
   101  // local filesystem.
   102  func NewLocalFS(c *Config) (storage.FS, error) {
   103  	c.init()
   104  
   105  	// create namespaces if they do not exist
   106  	namespaces := []string{c.DataDirectory, c.Uploads, c.Shadow, c.References, c.RecycleBin, c.Versions}
   107  	for _, v := range namespaces {
   108  		if err := os.MkdirAll(v, 0755); err != nil {
   109  			return nil, errors.Wrap(err, "could not create home dir "+v)
   110  		}
   111  	}
   112  
   113  	dbName := "localfs.db"
   114  	if !c.DisableHome {
   115  		dbName = "localhomefs.db"
   116  	}
   117  
   118  	db, err := initializeDB(c.Root, dbName)
   119  	if err != nil {
   120  		return nil, errors.Wrap(err, "localfs: error initializing db")
   121  	}
   122  
   123  	return &localfs{
   124  		conf:         c,
   125  		db:           db,
   126  		chunkHandler: chunking.NewChunkHandler(c.Uploads),
   127  	}, nil
   128  }
   129  
   130  func (fs *localfs) Shutdown(ctx context.Context) error {
   131  	err := fs.db.Close()
   132  	if err != nil {
   133  		return errors.Wrap(err, "localfs: error closing db connection")
   134  	}
   135  	return nil
   136  }
   137  
   138  func (fs *localfs) resolve(ctx context.Context, ref *provider.Reference) (p string, err error) {
   139  	if ref.ResourceId != nil {
   140  		if p, err = fs.GetPathByID(ctx, ref.ResourceId); err != nil {
   141  			return "", err
   142  		}
   143  		return path.Join(p, path.Join("/", ref.Path)), nil
   144  	}
   145  
   146  	if ref.Path != "" {
   147  		return path.Join("/", ref.Path), nil
   148  	}
   149  
   150  	// reference is invalid
   151  	return "", fmt.Errorf("invalid reference %+v. at least resource_id or path must be set", ref)
   152  }
   153  
   154  func getUser(ctx context.Context) (*userpb.User, error) {
   155  	u, ok := ctxpkg.ContextGetUser(ctx)
   156  	if !ok {
   157  		err := errors.Wrap(errtypes.UserRequired(""), "local: error getting user from ctx")
   158  		return nil, err
   159  	}
   160  	return u, nil
   161  }
   162  
   163  func (fs *localfs) wrap(ctx context.Context, p string) string {
   164  	// This is to prevent path traversal.
   165  	// With this p can't break out of its parent folder
   166  	p = path.Join("/", p)
   167  	var internal string
   168  	if !fs.conf.DisableHome {
   169  		layout, err := fs.GetHome(ctx)
   170  		if err != nil {
   171  			panic(err)
   172  		}
   173  		internal = path.Join(fs.conf.DataDirectory, layout, p)
   174  	} else {
   175  		internal = path.Join(fs.conf.DataDirectory, p)
   176  	}
   177  	return internal
   178  }
   179  
   180  func (fs *localfs) wrapReferences(ctx context.Context, p string) string {
   181  	var internal string
   182  	if !fs.conf.DisableHome {
   183  		layout, err := fs.GetHome(ctx)
   184  		if err != nil {
   185  			panic(err)
   186  		}
   187  		internal = path.Join(fs.conf.References, layout, p)
   188  	} else {
   189  		internal = path.Join(fs.conf.References, p)
   190  	}
   191  	return internal
   192  }
   193  
   194  func (fs *localfs) wrapRecycleBin(ctx context.Context, p string) string {
   195  	var internal string
   196  	if !fs.conf.DisableHome {
   197  		layout, err := fs.GetHome(ctx)
   198  		if err != nil {
   199  			panic(err)
   200  		}
   201  		internal = path.Join(fs.conf.RecycleBin, layout, p)
   202  	} else {
   203  		internal = path.Join(fs.conf.RecycleBin, p)
   204  	}
   205  	return internal
   206  }
   207  
   208  func (fs *localfs) wrapVersions(ctx context.Context, p string) string {
   209  	p = path.Join("/", p)
   210  	var internal string
   211  	if !fs.conf.DisableHome {
   212  		layout, err := fs.GetHome(ctx)
   213  		if err != nil {
   214  			panic(err)
   215  		}
   216  		internal = path.Join(fs.conf.Versions, layout, p)
   217  	} else {
   218  		internal = path.Join(fs.conf.Versions, p)
   219  	}
   220  	return internal
   221  }
   222  
   223  func (fs *localfs) unwrap(ctx context.Context, np string) string {
   224  	ns := fs.getNsMatch(np, []string{fs.conf.DataDirectory, fs.conf.References, fs.conf.RecycleBin, fs.conf.Versions})
   225  	var external string
   226  	if !fs.conf.DisableHome {
   227  		layout, err := fs.GetHome(ctx)
   228  		if err != nil {
   229  			panic(err)
   230  		}
   231  		ns = path.Join(ns, layout)
   232  	}
   233  
   234  	external = strings.TrimPrefix(np, ns)
   235  	if external == "" {
   236  		external = "/"
   237  	}
   238  	return external
   239  }
   240  
   241  func (fs *localfs) getNsMatch(internal string, nss []string) string {
   242  	var match string
   243  	for _, ns := range nss {
   244  		if strings.HasPrefix(internal, ns) && len(ns) > len(match) {
   245  			match = ns
   246  		}
   247  	}
   248  	if match == "" {
   249  		panic(fmt.Sprintf("local: path is outside namespaces: path=%s namespaces=%+v", internal, nss))
   250  	}
   251  
   252  	return match
   253  }
   254  
   255  func (fs *localfs) isShareFolder(ctx context.Context, p string) bool {
   256  	return strings.HasPrefix(p, fs.conf.ShareFolder)
   257  }
   258  
   259  func (fs *localfs) isDataTransfersFolder(ctx context.Context, p string) bool {
   260  	return strings.HasPrefix(p, fs.conf.DataTransfersFolder)
   261  }
   262  
   263  func (fs *localfs) isShareFolderRoot(ctx context.Context, p string) bool {
   264  	return path.Clean(p) == fs.conf.ShareFolder
   265  }
   266  
   267  func (fs *localfs) isShareFolderChild(ctx context.Context, p string) bool {
   268  	p = path.Clean(p)
   269  	vals := strings.Split(p, fs.conf.ShareFolder+"/")
   270  	return len(vals) > 1 && vals[1] != ""
   271  }
   272  
   273  // permissionSet returns the permission set for the current user
   274  func (fs *localfs) permissionSet(ctx context.Context, owner *userpb.UserId) *provider.ResourcePermissions {
   275  	u, ok := ctxpkg.ContextGetUser(ctx)
   276  	if !ok {
   277  		return &provider.ResourcePermissions{
   278  			// no permissions
   279  		}
   280  	}
   281  	if u.Id == nil {
   282  		return &provider.ResourcePermissions{
   283  			// no permissions
   284  		}
   285  	}
   286  	if u.Id.OpaqueId == owner.OpaqueId && u.Id.Idp == owner.Idp {
   287  		return &provider.ResourcePermissions{
   288  			// owner has all permissions
   289  			AddGrant:             true,
   290  			CreateContainer:      true,
   291  			Delete:               true,
   292  			GetPath:              true,
   293  			GetQuota:             true,
   294  			InitiateFileDownload: true,
   295  			InitiateFileUpload:   true,
   296  			ListContainer:        true,
   297  			ListFileVersions:     true,
   298  			ListGrants:           true,
   299  			ListRecycle:          true,
   300  			Move:                 true,
   301  			PurgeRecycle:         true,
   302  			RemoveGrant:          true,
   303  			RestoreFileVersion:   true,
   304  			RestoreRecycleItem:   true,
   305  			Stat:                 true,
   306  			UpdateGrant:          true,
   307  		}
   308  	}
   309  	// TODO fix permissions for share recipients by traversing reading acls up to the root? cache acls for the parent node and reuse it
   310  	return &provider.ResourcePermissions{
   311  		AddGrant:             true,
   312  		CreateContainer:      true,
   313  		Delete:               true,
   314  		GetPath:              true,
   315  		GetQuota:             true,
   316  		InitiateFileDownload: true,
   317  		InitiateFileUpload:   true,
   318  		ListContainer:        true,
   319  		ListFileVersions:     true,
   320  		ListGrants:           true,
   321  		ListRecycle:          true,
   322  		Move:                 true,
   323  		PurgeRecycle:         true,
   324  		RemoveGrant:          true,
   325  		RestoreFileVersion:   true,
   326  		RestoreRecycleItem:   true,
   327  		Stat:                 true,
   328  		UpdateGrant:          true,
   329  	}
   330  }
   331  
   332  func (fs *localfs) normalize(ctx context.Context, fi os.FileInfo, fn string, mdKeys []string) (*provider.ResourceInfo, error) {
   333  	fp := fs.unwrap(ctx, path.Join("/", fn))
   334  	owner, err := getUser(ctx)
   335  	if err != nil {
   336  		return nil, err
   337  	}
   338  	metadata, err := fs.retrieveArbitraryMetadata(ctx, fn, mdKeys)
   339  	if err != nil {
   340  		return nil, err
   341  	}
   342  
   343  	var layout string
   344  	if !fs.conf.DisableHome {
   345  		layout, err = fs.GetHome(ctx)
   346  		if err != nil {
   347  			return nil, err
   348  		}
   349  	}
   350  
   351  	// A fileid is constructed like `fileid-url_encoded_path`. See GetPathByID for the inverse conversion
   352  	md := &provider.ResourceInfo{
   353  		Id:            &provider.ResourceId{OpaqueId: "fileid-" + url.QueryEscape(path.Join(layout, fp))},
   354  		Path:          fp,
   355  		Type:          getResourceType(fi.IsDir()),
   356  		Etag:          calcEtag(ctx, fi),
   357  		MimeType:      mime.Detect(fi.IsDir(), fp),
   358  		Size:          uint64(fi.Size()),
   359  		PermissionSet: fs.permissionSet(ctx, owner.Id),
   360  		Mtime: &types.Timestamp{
   361  			Seconds: uint64(fi.ModTime().Unix()),
   362  		},
   363  		Owner:             owner.Id,
   364  		ArbitraryMetadata: metadata,
   365  	}
   366  
   367  	return md, nil
   368  }
   369  
   370  func (fs *localfs) convertToFileReference(ctx context.Context, fi os.FileInfo, fn string, mdKeys []string) (*provider.ResourceInfo, error) {
   371  	info, err := fs.normalize(ctx, fi, fn, mdKeys)
   372  	if err != nil {
   373  		return nil, err
   374  	}
   375  	info.Type = provider.ResourceType_RESOURCE_TYPE_REFERENCE
   376  	target, err := fs.getReferenceEntry(ctx, fn)
   377  	if err != nil {
   378  		return nil, err
   379  	}
   380  	info.Target = target
   381  	return info, nil
   382  }
   383  
   384  func getResourceType(isDir bool) provider.ResourceType {
   385  	if isDir {
   386  		return provider.ResourceType_RESOURCE_TYPE_CONTAINER
   387  	}
   388  	return provider.ResourceType_RESOURCE_TYPE_FILE
   389  }
   390  
   391  func (fs *localfs) retrieveArbitraryMetadata(ctx context.Context, fn string, mdKeys []string) (*provider.ArbitraryMetadata, error) {
   392  	md, err := fs.getMetadata(ctx, fn)
   393  	if err != nil {
   394  		return nil, errors.Wrap(err, "localfs: error listing metadata")
   395  	}
   396  	var mdKey, mdVal string
   397  	metadata := map[string]string{}
   398  
   399  	mdKeysMap := make(map[string]struct{})
   400  	for _, k := range mdKeys {
   401  		mdKeysMap[k] = struct{}{}
   402  	}
   403  
   404  	var returnAllKeys bool
   405  	if _, ok := mdKeysMap["*"]; len(mdKeys) == 0 || ok {
   406  		returnAllKeys = true
   407  	}
   408  
   409  	for md.Next() {
   410  		err = md.Scan(&mdKey, &mdVal)
   411  		if err != nil {
   412  			return nil, errors.Wrap(err, "localfs: error scanning db rows")
   413  		}
   414  		if _, ok := mdKeysMap[mdKey]; returnAllKeys || ok {
   415  			metadata[mdKey] = mdVal
   416  		}
   417  	}
   418  	return &provider.ArbitraryMetadata{
   419  		Metadata: metadata,
   420  	}, nil
   421  }
   422  
   423  // GetPathByID returns the path pointed by the file id
   424  // In this implementation the file id is in the form `fileid-url_encoded_path`
   425  func (fs *localfs) GetPathByID(ctx context.Context, ref *provider.ResourceId) (string, error) {
   426  	var layout string
   427  	if !fs.conf.DisableHome {
   428  		var err error
   429  		layout, err = fs.GetHome(ctx)
   430  		if err != nil {
   431  			return "", err
   432  		}
   433  	}
   434  	unescapedID, err := url.QueryUnescape(ref.OpaqueId)
   435  	if err != nil {
   436  		return "", err
   437  	}
   438  	return strings.TrimPrefix(unescapedID, "fileid-"+layout), nil
   439  }
   440  
   441  func (fs *localfs) DenyGrant(ctx context.Context, ref *provider.Reference, g *provider.Grantee) error {
   442  	return errtypes.NotSupported("localfs: deny grant not supported")
   443  }
   444  
   445  func (fs *localfs) AddGrant(ctx context.Context, ref *provider.Reference, g *provider.Grant) error {
   446  	fn, err := fs.resolve(ctx, ref)
   447  	if err != nil {
   448  		return errors.Wrap(err, "localfs: error resolving ref")
   449  	}
   450  	fn = fs.wrap(ctx, fn)
   451  
   452  	role, err := grants.GetACLPerm(g.Permissions)
   453  	if err != nil {
   454  		return errors.Wrap(err, "localfs: unknown set permissions")
   455  	}
   456  
   457  	granteeType, err := grants.GetACLType(g.Grantee.Type)
   458  	if err != nil {
   459  		return errors.Wrap(err, "localfs: error getting grantee type")
   460  	}
   461  	var grantee string
   462  	if granteeType == acl.TypeUser {
   463  		grantee = fmt.Sprintf("%s:%s:%s@%s", granteeType, g.Grantee.GetUserId().OpaqueId, utils.UserTypeToString(g.Grantee.GetUserId().Type), g.Grantee.GetUserId().Idp)
   464  	} else if granteeType == acl.TypeGroup {
   465  		grantee = fmt.Sprintf("%s::%s@%s", granteeType, g.Grantee.GetGroupId().OpaqueId, g.Grantee.GetGroupId().Idp)
   466  	}
   467  
   468  	err = fs.addToACLDB(ctx, fn, grantee, role)
   469  	if err != nil {
   470  		return errors.Wrap(err, "localfs: error adding entry to DB")
   471  	}
   472  
   473  	return fs.propagate(ctx, fn)
   474  }
   475  
   476  func (fs *localfs) ListGrants(ctx context.Context, ref *provider.Reference) ([]*provider.Grant, error) {
   477  	fn, err := fs.resolve(ctx, ref)
   478  	if err != nil {
   479  		return nil, errors.Wrap(err, "localfs: error resolving ref")
   480  	}
   481  	fn = fs.wrap(ctx, fn)
   482  
   483  	g, err := fs.getACLs(ctx, fn)
   484  	if err != nil {
   485  		return nil, errors.Wrap(err, "localfs: error listing grants")
   486  	}
   487  	var granteeID, role string
   488  	var grantList []*provider.Grant
   489  
   490  	for g.Next() {
   491  		err = g.Scan(&granteeID, &role)
   492  		if err != nil {
   493  			return nil, errors.Wrap(err, "localfs: error scanning db rows")
   494  		}
   495  		grantSplit := strings.Split(granteeID, ":")
   496  		grantee := &provider.Grantee{Type: grants.GetGranteeType(grantSplit[0])}
   497  		parts := strings.Split(grantSplit[2], "@")
   498  		if grantSplit[0] == acl.TypeUser {
   499  			grantee.Id = &provider.Grantee_UserId{UserId: &userpb.UserId{OpaqueId: parts[0], Idp: parts[1], Type: utils.UserTypeMap(grantSplit[1])}}
   500  		} else if grantSplit[0] == acl.TypeGroup {
   501  			grantee.Id = &provider.Grantee_GroupId{GroupId: &grouppb.GroupId{OpaqueId: parts[0], Idp: parts[1]}}
   502  		}
   503  		permissions := grants.GetGrantPermissionSet(role)
   504  
   505  		grantList = append(grantList, &provider.Grant{
   506  			Grantee:     grantee,
   507  			Permissions: permissions,
   508  		})
   509  	}
   510  	return grantList, nil
   511  
   512  }
   513  
   514  func (fs *localfs) RemoveGrant(ctx context.Context, ref *provider.Reference, g *provider.Grant) error {
   515  	fn, err := fs.resolve(ctx, ref)
   516  	if err != nil {
   517  		return errors.Wrap(err, "localfs: error resolving ref")
   518  	}
   519  	fn = fs.wrap(ctx, fn)
   520  
   521  	granteeType, err := grants.GetACLType(g.Grantee.Type)
   522  	if err != nil {
   523  		return errors.Wrap(err, "localfs: error getting grantee type")
   524  	}
   525  	var grantee string
   526  	if granteeType == acl.TypeUser {
   527  		grantee = fmt.Sprintf("%s:%s@%s", granteeType, g.Grantee.GetUserId().OpaqueId, g.Grantee.GetUserId().Idp)
   528  	} else if granteeType == acl.TypeGroup {
   529  		grantee = fmt.Sprintf("%s:%s@%s", granteeType, g.Grantee.GetGroupId().OpaqueId, g.Grantee.GetGroupId().Idp)
   530  	}
   531  
   532  	err = fs.removeFromACLDB(ctx, fn, grantee)
   533  	if err != nil {
   534  		return errors.Wrap(err, "localfs: error removing from DB")
   535  	}
   536  
   537  	return fs.propagate(ctx, fn)
   538  }
   539  
   540  func (fs *localfs) UpdateGrant(ctx context.Context, ref *provider.Reference, g *provider.Grant) error {
   541  	return fs.AddGrant(ctx, ref, g)
   542  }
   543  
   544  func (fs *localfs) CreateReference(ctx context.Context, path string, targetURI *url.URL) error {
   545  	var fn string
   546  	switch {
   547  	case fs.isShareFolder(ctx, path):
   548  		fn = fs.wrapReferences(ctx, path)
   549  	case fs.isDataTransfersFolder(ctx, path):
   550  		fn = fs.wrap(ctx, path)
   551  	default:
   552  		return errtypes.PermissionDenied("localfs: cannot create references outside the share folder and data transfers folder")
   553  	}
   554  
   555  	err := os.MkdirAll(fn, 0700)
   556  	if err != nil {
   557  		if os.IsNotExist(err) {
   558  			return errtypes.NotFound(fn)
   559  		}
   560  		return errors.Wrap(err, "localfs: error creating dir "+fn)
   561  	}
   562  
   563  	if err = fs.addToReferencesDB(ctx, fn, targetURI.String()); err != nil {
   564  		return errors.Wrap(err, "localfs: error adding entry to DB")
   565  	}
   566  
   567  	return fs.propagate(ctx, fn)
   568  }
   569  
   570  // CreateStorageSpace creates a storage space
   571  func (fs *localfs) CreateStorageSpace(ctx context.Context, req *provider.CreateStorageSpaceRequest) (*provider.CreateStorageSpaceResponse, error) {
   572  	return nil, errtypes.NotSupported("unimplemented: CreateStorageSpace")
   573  }
   574  
   575  func (fs *localfs) SetArbitraryMetadata(ctx context.Context, ref *provider.Reference, md *provider.ArbitraryMetadata) error {
   576  
   577  	np, err := fs.resolve(ctx, ref)
   578  	if err != nil {
   579  		return errors.Wrap(err, "localfs: error resolving ref")
   580  	}
   581  
   582  	if fs.isShareFolderRoot(ctx, np) {
   583  		return errtypes.PermissionDenied("localfs: cannot set metadata for the virtual share folder")
   584  	}
   585  
   586  	if fs.isShareFolderChild(ctx, np) {
   587  		np = fs.wrapReferences(ctx, np)
   588  	} else {
   589  		np = fs.wrap(ctx, np)
   590  	}
   591  
   592  	fi, err := os.Stat(np)
   593  	if err != nil {
   594  		if os.IsNotExist(err) {
   595  			return errtypes.NotFound(fs.unwrap(ctx, np))
   596  		}
   597  		return errors.Wrap(err, "localfs: error stating "+np)
   598  	}
   599  
   600  	if md.Metadata != nil {
   601  		if val, ok := md.Metadata["mtime"]; ok {
   602  			if mtime, err := parseMTime(val); err == nil {
   603  				// updating mtime also updates atime
   604  				if err := os.Chtimes(np, mtime, mtime); err != nil {
   605  					return errors.Wrap(err, "could not set mtime")
   606  				}
   607  			} else {
   608  				return errors.Wrap(err, "could not parse mtime")
   609  			}
   610  			delete(md.Metadata, "mtime")
   611  		}
   612  
   613  		if _, ok := md.Metadata["etag"]; ok {
   614  			etag := calcEtag(ctx, fi)
   615  			if etag != md.Metadata["etag"] {
   616  				err = fs.addToMetadataDB(ctx, np, "etag", etag)
   617  				if err != nil {
   618  					return errors.Wrap(err, "localfs: error adding entry to DB")
   619  				}
   620  			}
   621  			delete(md.Metadata, "etag")
   622  		}
   623  
   624  		if _, ok := md.Metadata["favorite"]; ok {
   625  			u, err := getUser(ctx)
   626  			if err != nil {
   627  				return err
   628  			}
   629  			if uid := u.GetId(); uid != nil {
   630  				usr := fmt.Sprintf("u:%s@%s", uid.GetOpaqueId(), uid.GetIdp())
   631  				if err = fs.addToFavoritesDB(ctx, np, usr); err != nil {
   632  					return errors.Wrap(err, "localfs: error adding entry to DB")
   633  				}
   634  			} else {
   635  				return errors.Wrap(errtypes.UserRequired("userrequired"), "user has no id")
   636  			}
   637  			delete(md.Metadata, "favorite")
   638  		}
   639  	}
   640  
   641  	for k, v := range md.Metadata {
   642  		err = fs.addToMetadataDB(ctx, np, k, v)
   643  		if err != nil {
   644  			return errors.Wrap(err, "localfs: error adding entry to DB")
   645  		}
   646  	}
   647  
   648  	return fs.propagate(ctx, np)
   649  }
   650  
   651  func parseMTime(v string) (t time.Time, err error) {
   652  	p := strings.SplitN(v, ".", 2)
   653  	var sec, nsec int64
   654  	if sec, err = strconv.ParseInt(p[0], 10, 64); err == nil {
   655  		if len(p) > 1 {
   656  			nsec, err = strconv.ParseInt(p[1], 10, 64)
   657  		}
   658  	}
   659  	return time.Unix(sec, nsec), err
   660  }
   661  
   662  func (fs *localfs) UnsetArbitraryMetadata(ctx context.Context, ref *provider.Reference, keys []string) error {
   663  
   664  	np, err := fs.resolve(ctx, ref)
   665  	if err != nil {
   666  		return errors.Wrap(err, "localfs: error resolving ref")
   667  	}
   668  
   669  	if fs.isShareFolderRoot(ctx, np) {
   670  		return errtypes.PermissionDenied("localfs: cannot set metadata for the virtual share folder")
   671  	}
   672  
   673  	if fs.isShareFolderChild(ctx, np) {
   674  		np = fs.wrapReferences(ctx, np)
   675  	} else {
   676  		np = fs.wrap(ctx, np)
   677  	}
   678  
   679  	_, err = os.Stat(np)
   680  	if err != nil {
   681  		if os.IsNotExist(err) {
   682  			return errtypes.NotFound(fs.unwrap(ctx, np))
   683  		}
   684  		return errors.Wrap(err, "localfs: error stating "+np)
   685  	}
   686  
   687  	for _, k := range keys {
   688  		switch k {
   689  		case "favorite":
   690  			u, err := getUser(ctx)
   691  			if err != nil {
   692  				return err
   693  			}
   694  			if uid := u.GetId(); uid != nil {
   695  				usr := fmt.Sprintf("u:%s@%s", uid.GetOpaqueId(), uid.GetIdp())
   696  				if err = fs.removeFromFavoritesDB(ctx, np, usr); err != nil {
   697  					return errors.Wrap(err, "localfs: error removing entry from DB")
   698  				}
   699  			} else {
   700  				return errors.Wrap(errtypes.UserRequired("userrequired"), "user has no id")
   701  			}
   702  		case "etag":
   703  			return errors.Wrap(errtypes.NotSupported("unsetting etag not supported"), "could not unset metadata")
   704  		case "mtime":
   705  			return errors.Wrap(errtypes.NotSupported("unsetting mtime not supported"), "could not unset metadata")
   706  		default:
   707  			err = fs.removeFromMetadataDB(ctx, np, k)
   708  			if err != nil {
   709  				return errors.Wrap(err, "localfs: error adding entry to DB")
   710  			}
   711  		}
   712  	}
   713  
   714  	return fs.propagate(ctx, np)
   715  }
   716  
   717  // GetLock returns an existing lock on the given reference
   718  func (fs *localfs) GetLock(ctx context.Context, ref *provider.Reference) (*provider.Lock, error) {
   719  	return nil, errtypes.NotSupported("unimplemented")
   720  }
   721  
   722  // SetLock puts a lock on the given reference
   723  func (fs *localfs) SetLock(ctx context.Context, ref *provider.Reference, lock *provider.Lock) error {
   724  	return errtypes.NotSupported("unimplemented")
   725  }
   726  
   727  // RefreshLock refreshes an existing lock on the given reference
   728  func (fs *localfs) RefreshLock(ctx context.Context, ref *provider.Reference, lock *provider.Lock, existingLockID string) error {
   729  	return errtypes.NotSupported("unimplemented")
   730  }
   731  
   732  // Unlock removes an existing lock from the given reference
   733  func (fs *localfs) Unlock(ctx context.Context, ref *provider.Reference, lock *provider.Lock) error {
   734  	return errtypes.NotSupported("unimplemented")
   735  }
   736  
   737  func (fs *localfs) GetHome(ctx context.Context) (string, error) {
   738  	if fs.conf.DisableHome {
   739  		return "", errtypes.NotSupported("local: get home not supported")
   740  	}
   741  
   742  	u, err := getUser(ctx)
   743  	if err != nil {
   744  		err = errors.Wrap(err, "local: wrap: no user in ctx and home is enabled")
   745  		return "", err
   746  	}
   747  	relativeHome := templates.WithUser(u, fs.conf.UserLayout)
   748  
   749  	return relativeHome, nil
   750  }
   751  
   752  func (fs *localfs) CreateHome(ctx context.Context) error {
   753  	if fs.conf.DisableHome {
   754  		return errtypes.NotSupported("localfs: create home not supported")
   755  	}
   756  
   757  	homePaths := []string{fs.wrap(ctx, "/"), fs.wrapRecycleBin(ctx, "/"), fs.wrapVersions(ctx, "/"), fs.wrapReferences(ctx, fs.conf.ShareFolder)}
   758  
   759  	for _, v := range homePaths {
   760  		if err := fs.createHomeInternal(ctx, v); err != nil {
   761  			return errors.Wrap(err, "local: error creating home dir "+v)
   762  		}
   763  	}
   764  
   765  	return nil
   766  }
   767  
   768  func (fs *localfs) createHomeInternal(ctx context.Context, fn string) error {
   769  	_, err := os.Stat(fn)
   770  	if err != nil {
   771  		if !os.IsNotExist(err) {
   772  			return errors.Wrap(err, "local: error stating:"+fn)
   773  		}
   774  	}
   775  	err = os.MkdirAll(fn, 0700)
   776  	if err != nil {
   777  		return errors.Wrap(err, "local: error creating dir:"+fn)
   778  	}
   779  	return nil
   780  }
   781  
   782  func (fs *localfs) CreateDir(ctx context.Context, ref *provider.Reference) error {
   783  
   784  	fn, err := fs.resolve(ctx, ref)
   785  	if err != nil {
   786  		return nil
   787  	}
   788  
   789  	if fs.isShareFolder(ctx, fn) {
   790  		return errtypes.PermissionDenied("localfs: cannot create folder under the share folder")
   791  	}
   792  
   793  	fn = fs.wrap(ctx, fn)
   794  	if _, err := os.Stat(fn); err == nil {
   795  		return errtypes.AlreadyExists(fn)
   796  	}
   797  	err = os.Mkdir(fn, 0700)
   798  	if err != nil {
   799  		if os.IsNotExist(err) {
   800  			return errtypes.PreconditionFailed(fn)
   801  		}
   802  		return errors.Wrap(err, "localfs: error creating dir "+fn)
   803  	}
   804  
   805  	return fs.propagate(ctx, path.Dir(fn))
   806  }
   807  
   808  // TouchFile as defined in the storage.FS interface
   809  func (fs *localfs) TouchFile(ctx context.Context, ref *provider.Reference, _ bool, _ string) error {
   810  	return fmt.Errorf("unimplemented: TouchFile")
   811  }
   812  
   813  func (fs *localfs) Delete(ctx context.Context, ref *provider.Reference) error {
   814  	fn, err := fs.resolve(ctx, ref)
   815  	if err != nil {
   816  		return errors.Wrap(err, "localfs: error resolving ref")
   817  	}
   818  
   819  	if fs.isShareFolderRoot(ctx, fn) {
   820  		return errtypes.PermissionDenied("localfs: cannot delete the virtual share folder")
   821  	}
   822  
   823  	var fp string
   824  	if fs.isShareFolderChild(ctx, fn) {
   825  		fp = fs.wrapReferences(ctx, fn)
   826  	} else {
   827  		fp = fs.wrap(ctx, fn)
   828  	}
   829  
   830  	_, err = os.Stat(fp)
   831  	if err != nil {
   832  		if os.IsNotExist(err) {
   833  			return errtypes.NotFound(fn)
   834  		}
   835  		return errors.Wrap(err, "localfs: error stating "+fp)
   836  	}
   837  
   838  	key := fmt.Sprintf("%s.d%d", path.Base(fn), time.Now().UnixNano()/int64(time.Millisecond))
   839  	if err := os.Rename(fp, fs.wrapRecycleBin(ctx, key)); err != nil {
   840  		return errors.Wrap(err, "localfs: could not delete item")
   841  	}
   842  
   843  	err = fs.addToRecycledDB(ctx, key, fn)
   844  	if err != nil {
   845  		return errors.Wrap(err, "localfs: error adding entry to DB")
   846  	}
   847  
   848  	return fs.propagate(ctx, path.Dir(fp))
   849  }
   850  
   851  func (fs *localfs) Move(ctx context.Context, oldRef, newRef *provider.Reference) error {
   852  	oldName, err := fs.resolve(ctx, oldRef)
   853  	if err != nil {
   854  		return errors.Wrap(err, "localfs: error resolving ref")
   855  	}
   856  
   857  	newName, err := fs.resolve(ctx, newRef)
   858  	if err != nil {
   859  		return errors.Wrap(err, "localfs: error resolving ref")
   860  	}
   861  
   862  	if fs.isShareFolder(ctx, oldName) || fs.isShareFolder(ctx, newName) {
   863  		return fs.moveReferences(ctx, oldName, newName)
   864  	}
   865  
   866  	oldName = fs.wrap(ctx, oldName)
   867  	newName = fs.wrap(ctx, newName)
   868  
   869  	if err := os.Rename(oldName, newName); err != nil {
   870  		return errors.Wrap(err, "localfs: error moving "+oldName+" to "+newName)
   871  	}
   872  
   873  	if err := fs.copyMD(oldName, newName); err != nil {
   874  		return errors.Wrap(err, "localfs: error copying metadata")
   875  	}
   876  
   877  	if err := fs.propagate(ctx, newName); err != nil {
   878  		return err
   879  	}
   880  	if err := fs.propagate(ctx, path.Dir(oldName)); err != nil {
   881  		return err
   882  	}
   883  
   884  	return nil
   885  }
   886  
   887  func (fs *localfs) moveReferences(ctx context.Context, oldName, newName string) error {
   888  
   889  	if fs.isShareFolderRoot(ctx, oldName) || fs.isShareFolderRoot(ctx, newName) {
   890  		return errtypes.PermissionDenied("localfs: cannot move/rename the virtual share folder")
   891  	}
   892  
   893  	// only rename of the reference is allowed, hence having the same basedir
   894  	bold, _ := path.Split(oldName)
   895  	bnew, _ := path.Split(newName)
   896  
   897  	if bold != bnew {
   898  		return errtypes.PermissionDenied("localfs: cannot move references under the virtual share folder")
   899  	}
   900  
   901  	oldName = fs.wrapReferences(ctx, oldName)
   902  	newName = fs.wrapReferences(ctx, newName)
   903  
   904  	if err := os.Rename(oldName, newName); err != nil {
   905  		return errors.Wrap(err, "localfs: error moving "+oldName+" to "+newName)
   906  	}
   907  
   908  	if err := fs.copyMD(oldName, newName); err != nil {
   909  		return errors.Wrap(err, "localfs: error copying metadata")
   910  	}
   911  
   912  	if err := fs.propagate(ctx, newName); err != nil {
   913  		return err
   914  	}
   915  	if err := fs.propagate(ctx, path.Dir(oldName)); err != nil {
   916  		return err
   917  	}
   918  
   919  	return nil
   920  }
   921  
   922  func (fs *localfs) GetMD(ctx context.Context, ref *provider.Reference, mdKeys []string, fieldMask []string) (*provider.ResourceInfo, error) {
   923  	fn, err := fs.resolve(ctx, ref)
   924  	if err != nil {
   925  		return nil, errors.Wrap(err, "localfs: error resolving ref")
   926  	}
   927  
   928  	if !fs.conf.DisableHome {
   929  		if fs.isShareFolder(ctx, fn) {
   930  			return fs.getMDShareFolder(ctx, fn, mdKeys)
   931  		}
   932  	}
   933  
   934  	fn = fs.wrap(ctx, fn)
   935  	md, err := os.Stat(fn)
   936  	if err != nil {
   937  		if os.IsNotExist(err) {
   938  			return nil, errtypes.NotFound(fn)
   939  		}
   940  		return nil, errors.Wrap(err, "localfs: error stating "+fn)
   941  	}
   942  
   943  	return fs.normalize(ctx, md, fn, mdKeys)
   944  }
   945  
   946  func (fs *localfs) getMDShareFolder(ctx context.Context, p string, mdKeys []string) (*provider.ResourceInfo, error) {
   947  
   948  	fn := fs.wrapReferences(ctx, p)
   949  	md, err := os.Stat(fn)
   950  	if err != nil {
   951  		if os.IsNotExist(err) {
   952  			return nil, errtypes.NotFound(fn)
   953  		}
   954  		return nil, errors.Wrap(err, "localfs: error stating "+fn)
   955  	}
   956  
   957  	if fs.isShareFolderRoot(ctx, p) {
   958  		return fs.normalize(ctx, md, fn, mdKeys)
   959  	}
   960  	return fs.convertToFileReference(ctx, md, fn, mdKeys)
   961  }
   962  
   963  func (fs *localfs) ListFolder(ctx context.Context, ref *provider.Reference, mdKeys, fieldMask []string) ([]*provider.ResourceInfo, error) {
   964  	fn, err := fs.resolve(ctx, ref)
   965  	if err != nil {
   966  		return nil, errors.Wrap(err, "localfs: error resolving ref")
   967  	}
   968  
   969  	if fn == "/" {
   970  		homeFiles, err := fs.listFolder(ctx, fn, mdKeys)
   971  		if err != nil {
   972  			return nil, err
   973  		}
   974  		if !fs.conf.DisableHome {
   975  			sharedReferences, err := fs.listShareFolderRoot(ctx, fn, mdKeys)
   976  			if err != nil {
   977  				return nil, err
   978  			}
   979  			homeFiles = append(homeFiles, sharedReferences...)
   980  		}
   981  		return homeFiles, nil
   982  	}
   983  
   984  	if fs.isShareFolderRoot(ctx, fn) {
   985  		return fs.listShareFolderRoot(ctx, fn, mdKeys)
   986  	}
   987  
   988  	if fs.isShareFolderChild(ctx, fn) {
   989  		return nil, errtypes.PermissionDenied("localfs: error listing folders inside the shared folder, only file references are stored inside")
   990  	}
   991  
   992  	return fs.listFolder(ctx, fn, mdKeys)
   993  }
   994  
   995  func (fs *localfs) listFolder(ctx context.Context, fn string, mdKeys []string) ([]*provider.ResourceInfo, error) {
   996  
   997  	fn = fs.wrap(ctx, fn)
   998  
   999  	mds, err := os.ReadDir(fn)
  1000  	if err != nil {
  1001  		if os.IsNotExist(err) {
  1002  			return nil, errtypes.NotFound(fn)
  1003  		}
  1004  		return nil, errors.Wrap(err, "localfs: error listing "+fn)
  1005  	}
  1006  
  1007  	finfos := []*provider.ResourceInfo{}
  1008  	for _, md := range mds {
  1009  		mdInfo, _ := md.Info()
  1010  		info, err := fs.normalize(ctx, mdInfo, path.Join(fn, md.Name()), mdKeys)
  1011  		if err == nil {
  1012  			finfos = append(finfos, info)
  1013  		}
  1014  	}
  1015  	return finfos, nil
  1016  }
  1017  
  1018  func (fs *localfs) listShareFolderRoot(ctx context.Context, home string, mdKeys []string) ([]*provider.ResourceInfo, error) {
  1019  
  1020  	fn := fs.wrapReferences(ctx, home)
  1021  
  1022  	mds, err := os.ReadDir(fn)
  1023  	if err != nil {
  1024  		if os.IsNotExist(err) {
  1025  			return nil, errtypes.NotFound(fn)
  1026  		}
  1027  		return nil, errors.Wrap(err, "localfs: error listing "+fn)
  1028  	}
  1029  
  1030  	finfos := []*provider.ResourceInfo{}
  1031  	for _, md := range mds {
  1032  		var info *provider.ResourceInfo
  1033  		var err error
  1034  		if fs.isShareFolderRoot(ctx, path.Join("/", md.Name())) {
  1035  			mdInfo, _ := md.Info()
  1036  			info, err = fs.normalize(ctx, mdInfo, path.Join(fn, md.Name()), mdKeys)
  1037  		} else {
  1038  			mdInfo, _ := md.Info()
  1039  			info, err = fs.convertToFileReference(ctx, mdInfo, path.Join(fn, md.Name()), mdKeys)
  1040  		}
  1041  		if err == nil {
  1042  			finfos = append(finfos, info)
  1043  		}
  1044  	}
  1045  	return finfos, nil
  1046  }
  1047  
  1048  func (fs *localfs) Download(ctx context.Context, ref *provider.Reference, openReaderfunc func(*provider.ResourceInfo) bool) (*provider.ResourceInfo, io.ReadCloser, error) {
  1049  	fn, err := fs.resolve(ctx, ref)
  1050  	if err != nil {
  1051  		return nil, nil, errors.Wrap(err, "localfs: error resolving ref")
  1052  	}
  1053  
  1054  	if fs.isShareFolder(ctx, fn) {
  1055  		return nil, nil, errtypes.PermissionDenied("localfs: cannot download under the virtual share folder")
  1056  	}
  1057  
  1058  	fn = fs.wrap(ctx, fn)
  1059  	md, err := os.Stat(fn)
  1060  	if err != nil {
  1061  		if os.IsNotExist(err) {
  1062  			return nil, nil, errtypes.NotFound(fn)
  1063  		}
  1064  		return nil, nil, errors.Wrap(err, "localfs: error stating "+fn)
  1065  	}
  1066  
  1067  	ri, err := fs.normalize(ctx, md, fn, []string{"size", "mimetype", "etag"})
  1068  	if err != nil {
  1069  		return nil, nil, err
  1070  	}
  1071  
  1072  	if !openReaderfunc(ri) {
  1073  		return ri, nil, nil
  1074  	}
  1075  
  1076  	r, err := os.Open(fn)
  1077  	if err != nil {
  1078  		if os.IsNotExist(err) {
  1079  			return nil, nil, errtypes.NotFound(fn)
  1080  		}
  1081  		return nil, nil, errors.Wrap(err, "localfs: error reading "+fn)
  1082  	}
  1083  	return ri, r, nil
  1084  }
  1085  
  1086  func (fs *localfs) archiveRevision(ctx context.Context, np string) error {
  1087  
  1088  	versionsDir := fs.wrapVersions(ctx, fs.unwrap(ctx, np))
  1089  	if err := os.MkdirAll(versionsDir, 0700); err != nil {
  1090  		return errors.Wrap(err, "localfs: error creating file versions dir "+versionsDir)
  1091  	}
  1092  
  1093  	vp := path.Join(versionsDir, fmt.Sprintf("v%d", time.Now().UnixNano()/int64(time.Millisecond)))
  1094  	if err := os.Rename(np, vp); err != nil {
  1095  		return errors.Wrap(err, "localfs: error renaming from "+np+" to "+vp)
  1096  	}
  1097  
  1098  	return nil
  1099  }
  1100  
  1101  func (fs *localfs) ListRevisions(ctx context.Context, ref *provider.Reference) ([]*provider.FileVersion, error) {
  1102  	np, err := fs.resolve(ctx, ref)
  1103  	if err != nil {
  1104  		return nil, errors.Wrap(err, "localfs: error resolving ref")
  1105  	}
  1106  
  1107  	if fs.isShareFolder(ctx, np) {
  1108  		return nil, errtypes.PermissionDenied("localfs: cannot list revisions under the virtual share folder")
  1109  	}
  1110  
  1111  	versionsDir := fs.wrapVersions(ctx, np)
  1112  	revisions := []*provider.FileVersion{}
  1113  	mds, err := os.ReadDir(versionsDir)
  1114  	if err != nil {
  1115  		return nil, errors.Wrap(err, "localfs: error reading"+versionsDir)
  1116  	}
  1117  
  1118  	for i := range mds {
  1119  		// versions resemble v12345678
  1120  		version := mds[i].Name()[1:]
  1121  
  1122  		mtime, err := strconv.Atoi(version)
  1123  		if err != nil {
  1124  			continue
  1125  		}
  1126  		mdsInfo, _ := mds[i].Info()
  1127  		revisions = append(revisions, &provider.FileVersion{
  1128  			Key:   version,
  1129  			Size:  uint64(mdsInfo.Size()),
  1130  			Mtime: uint64(mtime),
  1131  			Etag:  calcEtag(ctx, mdsInfo),
  1132  		})
  1133  	}
  1134  	return revisions, nil
  1135  }
  1136  
  1137  func (fs *localfs) DownloadRevision(ctx context.Context, ref *provider.Reference, revisionKey string, openReaderfunc func(*provider.ResourceInfo) bool) (*provider.ResourceInfo, io.ReadCloser, error) {
  1138  	np, err := fs.resolve(ctx, ref)
  1139  	if err != nil {
  1140  		return nil, nil, errors.Wrap(err, "localfs: error resolving ref")
  1141  	}
  1142  
  1143  	if fs.isShareFolder(ctx, np) {
  1144  		return nil, nil, errtypes.PermissionDenied("localfs: cannot download revisions under the virtual share folder")
  1145  	}
  1146  
  1147  	versionsDir := fs.wrapVersions(ctx, np)
  1148  	vp := path.Join(versionsDir, revisionKey)
  1149  
  1150  	md, err := os.Stat(vp)
  1151  	if err != nil {
  1152  		if os.IsNotExist(err) {
  1153  			return nil, nil, errtypes.NotFound(vp)
  1154  		}
  1155  		return nil, nil, errors.Wrap(err, "localfs: error stating "+vp)
  1156  	}
  1157  
  1158  	ri, err := fs.normalize(ctx, md, vp, []string{"size", "mimetype", "etag"})
  1159  	if err != nil {
  1160  		return nil, nil, err
  1161  	}
  1162  
  1163  	if !openReaderfunc(ri) {
  1164  		return ri, nil, nil
  1165  	}
  1166  
  1167  	r, err := os.Open(vp)
  1168  	if err != nil {
  1169  		return nil, nil, errors.Wrap(err, "localfs: error reading "+vp)
  1170  	}
  1171  
  1172  	return ri, r, nil
  1173  }
  1174  
  1175  func (fs *localfs) RestoreRevision(ctx context.Context, ref *provider.Reference, revisionKey string) error {
  1176  	np, err := fs.resolve(ctx, ref)
  1177  	if err != nil {
  1178  		return errors.Wrap(err, "localfs: error resolving ref")
  1179  	}
  1180  
  1181  	if fs.isShareFolder(ctx, np) {
  1182  		return errtypes.PermissionDenied("localfs: cannot restore revisions under the virtual share folder")
  1183  	}
  1184  
  1185  	versionsDir := fs.wrapVersions(ctx, np)
  1186  	vp := path.Join(versionsDir, revisionKey)
  1187  	np = fs.wrap(ctx, np)
  1188  
  1189  	// check revision exists
  1190  	vs, err := os.Stat(vp)
  1191  	if err != nil {
  1192  		if os.IsNotExist(err) {
  1193  			return errtypes.NotFound(revisionKey)
  1194  		}
  1195  		return errors.Wrap(err, "localfs: error stating "+vp)
  1196  	}
  1197  
  1198  	if !vs.Mode().IsRegular() {
  1199  		return fmt.Errorf("%s is not a regular file", vp)
  1200  	}
  1201  
  1202  	if err := fs.archiveRevision(ctx, np); err != nil {
  1203  		return err
  1204  	}
  1205  
  1206  	if err := os.Rename(vp, np); err != nil {
  1207  		return errors.Wrap(err, "localfs: error renaming from "+vp+" to "+np)
  1208  	}
  1209  
  1210  	return fs.propagate(ctx, np)
  1211  }
  1212  
  1213  func (fs *localfs) PurgeRecycleItem(ctx context.Context, ref *provider.Reference, key, relativePath string) error {
  1214  	rp := fs.wrapRecycleBin(ctx, key)
  1215  
  1216  	if err := os.Remove(rp); err != nil {
  1217  		return errors.Wrap(err, "localfs: error deleting recycle item")
  1218  	}
  1219  	return nil
  1220  }
  1221  
  1222  func (fs *localfs) EmptyRecycle(ctx context.Context, ref *provider.Reference) error {
  1223  	rp := fs.wrapRecycleBin(ctx, "/")
  1224  
  1225  	if err := os.RemoveAll(rp); err != nil {
  1226  		return errors.Wrap(err, "localfs: error deleting recycle files")
  1227  	}
  1228  	if err := fs.createHomeInternal(ctx, rp); err != nil {
  1229  		return errors.Wrap(err, "localfs: error deleting recycle files")
  1230  	}
  1231  	return nil
  1232  }
  1233  
  1234  func (fs *localfs) convertToRecycleItem(ctx context.Context, rp string, md os.FileInfo) *provider.RecycleItem {
  1235  	// trashbin items have filename.txt.d12345678
  1236  	suffix := path.Ext(md.Name())
  1237  	if len(suffix) == 0 || !strings.HasPrefix(suffix, ".d") {
  1238  		return nil
  1239  	}
  1240  
  1241  	trashtime := suffix[2:]
  1242  	ttime, err := strconv.Atoi(trashtime)
  1243  	if err != nil {
  1244  		return nil
  1245  	}
  1246  
  1247  	filePath, err := fs.getRecycledEntry(ctx, md.Name())
  1248  	if err != nil {
  1249  		return nil
  1250  	}
  1251  
  1252  	return &provider.RecycleItem{
  1253  		Type: getResourceType(md.IsDir()),
  1254  		Key:  md.Name(),
  1255  		Ref:  &provider.Reference{Path: filePath},
  1256  		Size: uint64(md.Size()),
  1257  		DeletionTime: &types.Timestamp{
  1258  			Seconds: uint64(ttime),
  1259  		},
  1260  	}
  1261  }
  1262  
  1263  func (fs *localfs) ListRecycle(ctx context.Context, ref *provider.Reference, key, relativePath string) ([]*provider.RecycleItem, error) {
  1264  
  1265  	rp := fs.wrapRecycleBin(ctx, "/")
  1266  
  1267  	mds, err := os.ReadDir(rp)
  1268  	if err != nil {
  1269  		return nil, errors.Wrap(err, "localfs: error listing deleted files")
  1270  	}
  1271  	items := []*provider.RecycleItem{}
  1272  	for i := range mds {
  1273  		mdsInfo, _ := mds[i].Info()
  1274  		ri := fs.convertToRecycleItem(ctx, rp, mdsInfo)
  1275  		if ri != nil {
  1276  			items = append(items, ri)
  1277  		}
  1278  	}
  1279  	return items, nil
  1280  }
  1281  
  1282  func (fs *localfs) RestoreRecycleItem(ctx context.Context, ref *provider.Reference, key, relativePath string, restoreRef *provider.Reference) error {
  1283  
  1284  	suffix := path.Ext(key)
  1285  	if len(suffix) == 0 || !strings.HasPrefix(suffix, ".d") {
  1286  		return errors.New("localfs: invalid trash item suffix")
  1287  	}
  1288  
  1289  	filePath, err := fs.getRecycledEntry(ctx, key)
  1290  	if err != nil {
  1291  		return errors.Wrap(err, "localfs: invalid key")
  1292  	}
  1293  
  1294  	var localRestorePath string
  1295  	switch {
  1296  	case restoreRef != nil && restoreRef.Path != "":
  1297  		localRestorePath = fs.wrap(ctx, restoreRef.Path)
  1298  	case fs.isShareFolder(ctx, filePath):
  1299  		localRestorePath = fs.wrapReferences(ctx, filePath)
  1300  	default:
  1301  		localRestorePath = fs.wrap(ctx, filePath)
  1302  	}
  1303  
  1304  	if _, err = os.Stat(localRestorePath); err == nil {
  1305  		return errors.New("localfs: can't restore - file already exists at original path")
  1306  	}
  1307  
  1308  	rp := fs.wrapRecycleBin(ctx, key)
  1309  	if _, err = os.Stat(rp); err != nil {
  1310  		if os.IsNotExist(err) {
  1311  			return errtypes.NotFound(key)
  1312  		}
  1313  		return errors.Wrap(err, "localfs: error stating "+rp)
  1314  	}
  1315  
  1316  	if err := os.Rename(rp, localRestorePath); err != nil {
  1317  		return errors.Wrap(err, "ocfs: could not restore item")
  1318  	}
  1319  
  1320  	err = fs.removeFromRecycledDB(ctx, key)
  1321  	if err != nil {
  1322  		return errors.Wrap(err, "localfs: error adding entry to DB")
  1323  	}
  1324  
  1325  	return fs.propagate(ctx, localRestorePath)
  1326  }
  1327  
  1328  func (fs *localfs) ListStorageSpaces(ctx context.Context, filter []*provider.ListStorageSpacesRequest_Filter, unrestricted bool) ([]*provider.StorageSpace, error) {
  1329  	return nil, errtypes.NotSupported("list storage spaces")
  1330  }
  1331  
  1332  // UpdateStorageSpace updates a storage space
  1333  func (fs *localfs) UpdateStorageSpace(ctx context.Context, req *provider.UpdateStorageSpaceRequest) (*provider.UpdateStorageSpaceResponse, error) {
  1334  	return nil, errtypes.NotSupported("update storage space")
  1335  }
  1336  
  1337  // DeleteStorageSpace deletes a storage space
  1338  func (fs *localfs) DeleteStorageSpace(ctx context.Context, req *provider.DeleteStorageSpaceRequest) error {
  1339  	return errtypes.NotSupported("delete storage space")
  1340  }
  1341  
  1342  func (fs *localfs) propagate(ctx context.Context, leafPath string) error {
  1343  
  1344  	var root string
  1345  	if fs.isShareFolderChild(ctx, leafPath) || strings.HasSuffix(path.Clean(leafPath), fs.conf.ShareFolder) {
  1346  		root = fs.wrapReferences(ctx, "/")
  1347  	} else {
  1348  		root = fs.wrap(ctx, "/")
  1349  	}
  1350  
  1351  	if !strings.HasPrefix(leafPath, root) {
  1352  		return errors.New("internal path: " + leafPath + " outside root: " + root)
  1353  	}
  1354  
  1355  	fi, err := os.Stat(leafPath)
  1356  	if err != nil {
  1357  		return err
  1358  	}
  1359  
  1360  	parts := strings.Split(strings.TrimPrefix(leafPath, root), "/")
  1361  	// root never ends in / so the split returns an empty first element, which we can skip
  1362  	// we do not need to chmod the last element because it is the leaf path (< and not <= comparison)
  1363  	for i := 1; i < len(parts); i++ {
  1364  		if err := os.Chtimes(root, fi.ModTime(), fi.ModTime()); err != nil {
  1365  			return err
  1366  		}
  1367  		root = path.Join(root, parts[i])
  1368  	}
  1369  	return nil
  1370  }