github.com/cs3org/reva/v2@v2.27.7/pkg/storage/fs/owncloudsql/owncloudsql.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 owncloudsql
    20  
    21  import (
    22  	"context"
    23  	"crypto/md5"
    24  	"crypto/sha1"
    25  	"database/sql"
    26  	"fmt"
    27  	"hash/adler32"
    28  	"io"
    29  	"net/url"
    30  	"os"
    31  	"path/filepath"
    32  	"regexp"
    33  	"strconv"
    34  	"strings"
    35  	"syscall"
    36  	"time"
    37  
    38  	userpb "github.com/cs3org/go-cs3apis/cs3/identity/user/v1beta1"
    39  	rpc "github.com/cs3org/go-cs3apis/cs3/rpc/v1beta1"
    40  	provider "github.com/cs3org/go-cs3apis/cs3/storage/provider/v1beta1"
    41  	types "github.com/cs3org/go-cs3apis/cs3/types/v1beta1"
    42  	"github.com/mitchellh/mapstructure"
    43  	"github.com/pkg/errors"
    44  	"github.com/pkg/xattr"
    45  	"github.com/rs/zerolog"
    46  	"github.com/rs/zerolog/log"
    47  
    48  	"github.com/cs3org/reva/v2/internal/grpc/services/storageprovider"
    49  	"github.com/cs3org/reva/v2/pkg/appctx"
    50  	"github.com/cs3org/reva/v2/pkg/conversions"
    51  	ctxpkg "github.com/cs3org/reva/v2/pkg/ctx"
    52  	"github.com/cs3org/reva/v2/pkg/errtypes"
    53  	"github.com/cs3org/reva/v2/pkg/events"
    54  	"github.com/cs3org/reva/v2/pkg/logger"
    55  	"github.com/cs3org/reva/v2/pkg/mime"
    56  	"github.com/cs3org/reva/v2/pkg/rgrpc/todo/pool"
    57  	"github.com/cs3org/reva/v2/pkg/sharedconf"
    58  	"github.com/cs3org/reva/v2/pkg/storage"
    59  	"github.com/cs3org/reva/v2/pkg/storage/fs/owncloudsql/filecache"
    60  	"github.com/cs3org/reva/v2/pkg/storage/fs/registry"
    61  	"github.com/cs3org/reva/v2/pkg/storage/utils/chunking"
    62  	"github.com/cs3org/reva/v2/pkg/storage/utils/templates"
    63  )
    64  
    65  const (
    66  	// Currently,extended file attributes have four separated
    67  	// namespaces (user, trusted, security and system) followed by a dot.
    68  	// A non root user can only manipulate the user. namespace, which is what
    69  	// we will use to store ownCloud specific metadata. To prevent name
    70  	// collisions with other apps We are going to introduce a sub namespace
    71  	// "user.oc."
    72  	ocPrefix string = "user.oc."
    73  
    74  	mdPrefix     string = ocPrefix + "md."   // arbitrary metadata
    75  	favPrefix    string = ocPrefix + "fav."  // favorite flag, per user
    76  	etagPrefix   string = ocPrefix + "etag." // allow overriding a calculated etag with one from the extended attributes
    77  	checksumsKey string = "http://owncloud.org/ns/checksums"
    78  )
    79  
    80  var defaultPermissions *provider.ResourcePermissions = &provider.ResourcePermissions{
    81  	// no permissions
    82  }
    83  var ownerPermissions *provider.ResourcePermissions = &provider.ResourcePermissions{
    84  	// all permissions
    85  	AddGrant:             true,
    86  	CreateContainer:      true,
    87  	Delete:               true,
    88  	GetPath:              true,
    89  	GetQuota:             true,
    90  	InitiateFileDownload: true,
    91  	InitiateFileUpload:   true,
    92  	ListContainer:        true,
    93  	ListFileVersions:     true,
    94  	ListGrants:           true,
    95  	ListRecycle:          true,
    96  	Move:                 true,
    97  	PurgeRecycle:         true,
    98  	RemoveGrant:          true,
    99  	RestoreFileVersion:   true,
   100  	RestoreRecycleItem:   true,
   101  	Stat:                 true,
   102  	UpdateGrant:          true,
   103  	DenyGrant:            true,
   104  }
   105  
   106  func init() {
   107  	registry.Register("owncloudsql", New)
   108  }
   109  
   110  type config struct {
   111  	DataDirectory            string `mapstructure:"datadirectory"`
   112  	UploadInfoDir            string `mapstructure:"upload_info_dir"`
   113  	DeprecatedShareDirectory string `mapstructure:"sharedirectory"`
   114  	ShareFolder              string `mapstructure:"share_folder"`
   115  	UserLayout               string `mapstructure:"user_layout"`
   116  	EnableHome               bool   `mapstructure:"enable_home"`
   117  	UserProviderEndpoint     string `mapstructure:"userprovidersvc"`
   118  	DbUsername               string `mapstructure:"dbusername"`
   119  	DbPassword               string `mapstructure:"dbpassword"`
   120  	DbHost                   string `mapstructure:"dbhost"`
   121  	DbPort                   int    `mapstructure:"dbport"`
   122  	DbName                   string `mapstructure:"dbname"`
   123  }
   124  
   125  func parseConfig(m map[string]interface{}) (*config, error) {
   126  	c := &config{}
   127  	if err := mapstructure.Decode(m, c); err != nil {
   128  		err = errors.Wrap(err, "error decoding conf")
   129  		return nil, err
   130  	}
   131  	return c, nil
   132  }
   133  
   134  func (c *config) init(m map[string]interface{}) {
   135  	if c.UserLayout == "" {
   136  		c.UserLayout = "{{.Username}}"
   137  	}
   138  	if c.UploadInfoDir == "" {
   139  		c.UploadInfoDir = "/var/tmp/reva/uploadinfo"
   140  	}
   141  	// fallback for old config
   142  	if c.DeprecatedShareDirectory != "" {
   143  		c.ShareFolder = c.DeprecatedShareDirectory
   144  	}
   145  	if c.ShareFolder == "" {
   146  		c.ShareFolder = "/Shares"
   147  	}
   148  	// ensure share folder always starts with slash
   149  	c.ShareFolder = filepath.Join("/", c.ShareFolder)
   150  
   151  	c.UserProviderEndpoint = sharedconf.GetGatewaySVC(c.UserProviderEndpoint)
   152  }
   153  
   154  // New returns an implementation to of the storage.FS interface that talk to
   155  // a local filesystem.
   156  func New(m map[string]interface{}, _ events.Stream, _ *zerolog.Logger) (storage.FS, error) {
   157  	c, err := parseConfig(m)
   158  	if err != nil {
   159  		return nil, err
   160  	}
   161  	c.init(m)
   162  
   163  	// c.DataDirectory should never end in / unless it is the root?
   164  	c.DataDirectory = filepath.Clean(c.DataDirectory)
   165  
   166  	// create datadir if it does not exist
   167  	err = os.MkdirAll(c.DataDirectory, 0700)
   168  	if err != nil {
   169  		logger.New().Error().Err(err).
   170  			Str("path", c.DataDirectory).
   171  			Msg("could not create datadir")
   172  	}
   173  
   174  	err = os.MkdirAll(c.UploadInfoDir, 0700)
   175  	if err != nil {
   176  		logger.New().Error().Err(err).
   177  			Str("path", c.UploadInfoDir).
   178  			Msg("could not create uploadinfo dir")
   179  	}
   180  
   181  	dbSource := fmt.Sprintf("%s:%s@tcp(%s:%d)/%s", c.DbUsername, c.DbPassword, c.DbHost, c.DbPort, c.DbName)
   182  	filecache, err := filecache.NewMysql(dbSource)
   183  	if err != nil {
   184  		return nil, err
   185  	}
   186  
   187  	return &owncloudsqlfs{
   188  		c:            c,
   189  		chunkHandler: chunking.NewChunkHandler(c.UploadInfoDir),
   190  		filecache:    filecache,
   191  	}, nil
   192  }
   193  
   194  type owncloudsqlfs struct {
   195  	c            *config
   196  	chunkHandler *chunking.ChunkHandler
   197  	filecache    *filecache.Cache
   198  }
   199  
   200  func (fs *owncloudsqlfs) Shutdown(ctx context.Context) error {
   201  	return nil
   202  }
   203  
   204  // owncloudsql stores files in the files subfolder
   205  // the incoming path starts with /<username>, so we need to insert the files subfolder into the path
   206  // and prefix the data directory
   207  // TODO the path handed to a storage provider should not contain the username
   208  func (fs *owncloudsqlfs) toInternalPath(ctx context.Context, sp string) (ip string) {
   209  	if fs.c.EnableHome {
   210  		u := ctxpkg.ContextMustGetUser(ctx)
   211  		layout := templates.WithUser(u, fs.c.UserLayout)
   212  		ip = filepath.Join(fs.c.DataDirectory, layout, "files", sp)
   213  	} else {
   214  		// trim all /
   215  		sp = strings.Trim(sp, "/")
   216  		// p = "" or
   217  		// p = <username> or
   218  		// p = <username>/foo/bar.txt
   219  		segments := strings.SplitN(sp, "/", 2)
   220  
   221  		if len(segments) == 1 && segments[0] == "" {
   222  			ip = fs.c.DataDirectory
   223  			return
   224  		}
   225  
   226  		// parts[0] contains the username or userid.
   227  		u, err := fs.getUser(ctx, segments[0])
   228  		if err != nil {
   229  			// TODO return invalid internal path?
   230  			return
   231  		}
   232  		layout := templates.WithUser(u, fs.c.UserLayout)
   233  
   234  		if len(segments) == 1 {
   235  			// parts = "<username>"
   236  			ip = filepath.Join(fs.c.DataDirectory, layout, "files")
   237  		} else {
   238  			// parts = "<username>", "foo/bar.txt"
   239  			ip = filepath.Join(fs.c.DataDirectory, layout, "files", segments[1])
   240  		}
   241  
   242  	}
   243  	return
   244  }
   245  
   246  // owncloudsql stores versions in the files_versions subfolder
   247  // the incoming path starts with /<username>, so we need to insert the files subfolder into the path
   248  // and prefix the data directory
   249  // TODO the path handed to a storage provider should not contain the username
   250  func (fs *owncloudsqlfs) getVersionsPath(ctx context.Context, ip string) string {
   251  	// ip = /path/to/data/<username>/files/foo/bar.txt
   252  	// remove data dir
   253  	if fs.c.DataDirectory != "/" {
   254  		// fs.c.DataDirectory is a clean path, so it never ends in /
   255  		ip = strings.TrimPrefix(ip, fs.c.DataDirectory)
   256  	}
   257  	// ip = /<username>/files/foo/bar.txt
   258  	parts := strings.SplitN(ip, "/", 4)
   259  
   260  	// parts[1] contains the username or userid.
   261  	u, err := fs.getUser(ctx, parts[1])
   262  	if err != nil {
   263  		// TODO return invalid internal path?
   264  		return ""
   265  	}
   266  	layout := templates.WithUser(u, fs.c.UserLayout)
   267  
   268  	switch len(parts) {
   269  	case 3:
   270  		// parts = "", "<username>"
   271  		return filepath.Join(fs.c.DataDirectory, layout, "files_versions")
   272  	case 4:
   273  		// parts = "", "<username>", "foo/bar.txt"
   274  		return filepath.Join(fs.c.DataDirectory, layout, "files_versions", parts[3])
   275  	default:
   276  		return "" // TODO Must not happen?
   277  	}
   278  
   279  }
   280  
   281  // owncloudsql stores trashed items in the files_trashbin subfolder of a users home
   282  func (fs *owncloudsqlfs) getRecyclePath(ctx context.Context) (string, error) {
   283  	u, ok := ctxpkg.ContextGetUser(ctx)
   284  	if !ok {
   285  		err := errors.Wrap(errtypes.UserRequired("userrequired"), "error getting user from ctx")
   286  		return "", err
   287  	}
   288  	layout := templates.WithUser(u, fs.c.UserLayout)
   289  	return fs.getRecyclePathForUser(layout)
   290  }
   291  
   292  func (fs *owncloudsqlfs) getRecyclePathForUser(user string) (string, error) {
   293  	return filepath.Join(fs.c.DataDirectory, user, "files_trashbin/files"), nil
   294  }
   295  
   296  func (fs *owncloudsqlfs) getVersionRecyclePath(ctx context.Context) (string, error) {
   297  	u, ok := ctxpkg.ContextGetUser(ctx)
   298  	if !ok {
   299  		err := errors.Wrap(errtypes.UserRequired("userrequired"), "error getting user from ctx")
   300  		return "", err
   301  	}
   302  	layout := templates.WithUser(u, fs.c.UserLayout)
   303  	return filepath.Join(fs.c.DataDirectory, layout, "files_trashbin/versions"), nil
   304  }
   305  
   306  func (fs *owncloudsqlfs) toDatabasePath(ip string) string {
   307  	owner := fs.getOwner(ip)
   308  	trim := filepath.Join(fs.c.DataDirectory, owner)
   309  	p := strings.TrimPrefix(ip, trim)
   310  	p = strings.TrimPrefix(p, "/")
   311  	return p
   312  }
   313  
   314  func (fs *owncloudsqlfs) toResourcePath(ip, owner string) string {
   315  	trim := filepath.Join(fs.c.DataDirectory, owner, "files")
   316  	p := strings.TrimPrefix(ip, trim)
   317  	p = strings.TrimPrefix(p, "/")
   318  	// root directory
   319  	if p == "" {
   320  		p = "."
   321  	}
   322  	return p
   323  }
   324  
   325  func (fs *owncloudsqlfs) toStoragePath(ctx context.Context, ip string) (sp string) {
   326  	if fs.c.EnableHome {
   327  		u := ctxpkg.ContextMustGetUser(ctx)
   328  		layout := templates.WithUser(u, fs.c.UserLayout)
   329  		trim := filepath.Join(fs.c.DataDirectory, layout, "files")
   330  		sp = strings.TrimPrefix(ip, trim)
   331  		// root directory
   332  		if sp == "" {
   333  			sp = "/"
   334  		}
   335  	} else {
   336  		// ip = /data/<username>/files/foo/bar.txt
   337  		// remove data dir
   338  		if fs.c.DataDirectory != "/" {
   339  			// fs.c.DataDirectory is a clean path, so it never ends in /
   340  			ip = strings.TrimPrefix(ip, fs.c.DataDirectory)
   341  			// ip = /<username>/files/foo/bar.txt
   342  		}
   343  
   344  		segments := strings.SplitN(ip, "/", 4)
   345  		// parts = "", "<username>", "files", "foo/bar.txt"
   346  		switch len(segments) {
   347  		case 1:
   348  			sp = "/"
   349  		case 2:
   350  			sp = filepath.Join("/", segments[1])
   351  		case 3:
   352  			sp = filepath.Join("/", segments[1])
   353  		default:
   354  			sp = filepath.Join(segments[1], segments[3])
   355  		}
   356  	}
   357  	log := appctx.GetLogger(ctx)
   358  	log.Debug().Str("driver", "owncloudsql").Str("ipath", ip).Str("spath", sp).Msg("toStoragePath")
   359  	return
   360  }
   361  
   362  // TODO the owner needs to come from a different place
   363  func (fs *owncloudsqlfs) getOwner(ip string) string {
   364  	ip = strings.TrimPrefix(ip, fs.c.DataDirectory)
   365  	parts := strings.SplitN(ip, "/", 3)
   366  	if len(parts) > 1 {
   367  		return parts[1]
   368  	}
   369  	return ""
   370  }
   371  
   372  // TODO cache user lookup
   373  func (fs *owncloudsqlfs) getUser(ctx context.Context, usernameOrID string) (id *userpb.User, err error) {
   374  	u := ctxpkg.ContextMustGetUser(ctx)
   375  	// check if username matches and id is set
   376  	if u.Username == usernameOrID && u.Id != nil && u.Id.OpaqueId != "" {
   377  		return u, nil
   378  	}
   379  	// check if userid matches and username is set
   380  	if u.Id != nil && u.Id.OpaqueId == usernameOrID && u.Username != "" {
   381  		return u, nil
   382  	}
   383  	// look up at the userprovider
   384  
   385  	// parts[0] contains the username or userid. use  user service to look up id
   386  	c, err := pool.GetUserProviderServiceClient(fs.c.UserProviderEndpoint)
   387  	if err != nil {
   388  		appctx.GetLogger(ctx).
   389  			Error().Err(err).
   390  			Str("userprovidersvc", fs.c.UserProviderEndpoint).
   391  			Str("usernameOrID", usernameOrID).
   392  			Msg("could not get user provider client")
   393  		return nil, err
   394  	}
   395  	res, err := c.GetUser(ctx, &userpb.GetUserRequest{
   396  		UserId: &userpb.UserId{OpaqueId: usernameOrID},
   397  	})
   398  	if err != nil {
   399  		appctx.GetLogger(ctx).
   400  			Error().Err(err).
   401  			Str("userprovidersvc", fs.c.UserProviderEndpoint).
   402  			Str("usernameOrID", usernameOrID).
   403  			Msg("could not get user")
   404  		return nil, err
   405  	}
   406  
   407  	if res.Status.Code == rpc.Code_CODE_NOT_FOUND {
   408  		appctx.GetLogger(ctx).
   409  			Error().
   410  			Str("userprovidersvc", fs.c.UserProviderEndpoint).
   411  			Str("usernameOrID", usernameOrID).
   412  			Interface("status", res.Status).
   413  			Msg("user not found by id. Trying by name")
   414  
   415  		var cres *userpb.GetUserByClaimResponse
   416  		cres, err = c.GetUserByClaim(ctx, &userpb.GetUserByClaimRequest{
   417  			Claim: "username",
   418  			Value: usernameOrID,
   419  		})
   420  		if err != nil {
   421  			appctx.GetLogger(ctx).
   422  				Error().Err(err).
   423  				Str("userprovidersvc", fs.c.UserProviderEndpoint).
   424  				Str("usernameOrID", usernameOrID).
   425  				Msg("could not get user by username")
   426  			return nil, err
   427  		}
   428  		if cres.Status.Code == rpc.Code_CODE_NOT_FOUND {
   429  			appctx.GetLogger(ctx).
   430  				Error().
   431  				Str("userprovidersvc", fs.c.UserProviderEndpoint).
   432  				Str("usernameOrID", usernameOrID).
   433  				Interface("status", cres.Status).
   434  				Msg("user not found by username")
   435  			return nil, fmt.Errorf("user not found")
   436  		}
   437  		res.User = cres.User
   438  		res.Status = cres.Status
   439  	}
   440  
   441  	if res.Status.Code != rpc.Code_CODE_OK {
   442  		appctx.GetLogger(ctx).
   443  			Error().
   444  			Str("userprovidersvc", fs.c.UserProviderEndpoint).
   445  			Str("usernameOrID", usernameOrID).
   446  			Interface("status", res.Status).
   447  			Msg("user lookup failed")
   448  		return nil, fmt.Errorf("user lookup failed")
   449  	}
   450  	return res.User, nil
   451  }
   452  
   453  // permissionSet returns the permission set for the current user
   454  func (fs *owncloudsqlfs) permissionSet(ctx context.Context, owner *userpb.UserId) *provider.ResourcePermissions {
   455  	if owner == nil {
   456  		return &provider.ResourcePermissions{
   457  			Stat: true,
   458  		}
   459  	}
   460  	u, ok := ctxpkg.ContextGetUser(ctx)
   461  	if !ok {
   462  		return &provider.ResourcePermissions{
   463  			// no permissions
   464  		}
   465  	}
   466  	if u.Id == nil {
   467  		return &provider.ResourcePermissions{
   468  			// no permissions
   469  		}
   470  	}
   471  	if u.Id.OpaqueId == owner.OpaqueId && u.Id.Idp == owner.Idp {
   472  		return &provider.ResourcePermissions{
   473  			// owner has all permissions
   474  			AddGrant:             true,
   475  			CreateContainer:      true,
   476  			Delete:               true,
   477  			GetPath:              true,
   478  			GetQuota:             true,
   479  			InitiateFileDownload: true,
   480  			InitiateFileUpload:   true,
   481  			ListContainer:        true,
   482  			ListFileVersions:     true,
   483  			ListGrants:           true,
   484  			ListRecycle:          true,
   485  			Move:                 true,
   486  			PurgeRecycle:         true,
   487  			RemoveGrant:          true,
   488  			RestoreFileVersion:   true,
   489  			RestoreRecycleItem:   true,
   490  			Stat:                 true,
   491  			UpdateGrant:          true,
   492  		}
   493  	}
   494  	// TODO fix permissions for share recipients by traversing reading acls up to the root? cache acls for the parent node and reuse it
   495  	return &provider.ResourcePermissions{
   496  		AddGrant:             true,
   497  		CreateContainer:      true,
   498  		Delete:               true,
   499  		GetPath:              true,
   500  		GetQuota:             true,
   501  		InitiateFileDownload: true,
   502  		InitiateFileUpload:   true,
   503  		ListContainer:        true,
   504  		ListFileVersions:     true,
   505  		ListGrants:           true,
   506  		ListRecycle:          true,
   507  		Move:                 true,
   508  		PurgeRecycle:         true,
   509  		RemoveGrant:          true,
   510  		RestoreFileVersion:   true,
   511  		RestoreRecycleItem:   true,
   512  		Stat:                 true,
   513  		UpdateGrant:          true,
   514  	}
   515  }
   516  
   517  func (fs *owncloudsqlfs) getStorage(ctx context.Context, ip string) (int, error) {
   518  	return fs.filecache.GetNumericStorageID(ctx, "home::"+fs.getOwner(ip))
   519  }
   520  
   521  func (fs *owncloudsqlfs) getUserStorage(ctx context.Context, user string) (int, error) {
   522  	id, err := fs.filecache.GetNumericStorageID(ctx, "home::"+user)
   523  	if err != nil {
   524  		id, err = fs.filecache.CreateStorage(ctx, "home::"+user)
   525  	}
   526  	return id, err
   527  }
   528  
   529  func (fs *owncloudsqlfs) convertToResourceInfo(ctx context.Context, entry *filecache.File, ip string, mdKeys []string) (*provider.ResourceInfo, error) {
   530  	mdKeysMap := make(map[string]struct{})
   531  	for _, k := range mdKeys {
   532  		mdKeysMap[k] = struct{}{}
   533  	}
   534  
   535  	var returnAllKeys bool
   536  	if _, ok := mdKeysMap["*"]; len(mdKeys) == 0 || ok {
   537  		returnAllKeys = true
   538  	}
   539  	owner := fs.getOwner(ip)
   540  	path := fs.toResourcePath(ip, owner)
   541  	isDir := entry.MimeTypeString == "httpd/unix-directory"
   542  	ri := &provider.ResourceInfo{
   543  		Id: &provider.ResourceId{
   544  			// return ownclouds numeric storage id as the space id!
   545  			SpaceId: strconv.Itoa(entry.Storage), OpaqueId: strconv.Itoa(entry.ID),
   546  		},
   547  		Path:     filepath.Base(path), // we currently only return the name, decomposedfs returns the path if the request was path based. is that even still possible?
   548  		Type:     getResourceType(isDir),
   549  		Etag:     entry.Etag,
   550  		MimeType: entry.MimeTypeString,
   551  		Size:     uint64(entry.Size),
   552  		Mtime: &types.Timestamp{
   553  			Seconds: uint64(entry.MTime),
   554  		},
   555  		ArbitraryMetadata: &provider.ArbitraryMetadata{
   556  			Metadata: map[string]string{}, // TODO aduffeck: which metadata needs to go in here?
   557  		},
   558  	}
   559  	// omit parentid for root
   560  	if path != "." {
   561  		ri.Name = entry.Name
   562  		ri.ParentId = &provider.ResourceId{
   563  			// return ownclouds numeric storage id as the space id!
   564  			SpaceId: strconv.Itoa(entry.Storage), OpaqueId: strconv.Itoa(entry.Parent),
   565  		}
   566  	}
   567  
   568  	if owner, err := fs.getUser(ctx, owner); err == nil {
   569  		ri.Owner = owner.Id
   570  	} else {
   571  		appctx.GetLogger(ctx).Error().Err(err).Msg("error getting owner")
   572  	}
   573  
   574  	ri.PermissionSet = fs.permissionSet(ctx, ri.Owner)
   575  
   576  	// checksums
   577  	if !isDir {
   578  		if _, checksumRequested := mdKeysMap[checksumsKey]; returnAllKeys || checksumRequested {
   579  			// TODO which checksum was requested? sha1 adler32 or md5? for now hardcode sha1?
   580  			readChecksumIntoResourceChecksum(ctx, entry.Checksum, storageprovider.XSSHA1, ri)
   581  			readChecksumIntoOpaque(ctx, entry.Checksum, storageprovider.XSMD5, ri)
   582  			readChecksumIntoOpaque(ctx, ip, storageprovider.XSAdler32, ri)
   583  		}
   584  	}
   585  
   586  	return ri, nil
   587  }
   588  
   589  // GetPathByID returns the storage relative path for the file id, without the internal namespace
   590  func (fs *owncloudsqlfs) GetPathByID(ctx context.Context, id *provider.ResourceId) (string, error) {
   591  	ip, err := fs.resolve(ctx, &provider.Reference{ResourceId: id})
   592  	if err != nil {
   593  		return "", err
   594  	}
   595  
   596  	// check permissions
   597  	if perm, err := fs.readPermissions(ctx, ip); err == nil {
   598  		if !perm.GetPath {
   599  			return "", errtypes.PermissionDenied("")
   600  		}
   601  	} else {
   602  		if isNotFound(err) {
   603  			return "", errtypes.NotFound(fs.toStoragePath(ctx, ip))
   604  		}
   605  		return "", errors.Wrap(err, "owncloudsql: error reading permissions")
   606  	}
   607  
   608  	return fs.toStoragePath(ctx, ip), nil
   609  }
   610  
   611  // resolve takes in a request path or request id and converts it to an internal path.
   612  func (fs *owncloudsqlfs) resolve(ctx context.Context, ref *provider.Reference) (string, error) {
   613  
   614  	if ref.GetResourceId() != nil {
   615  		p, err := fs.filecache.Path(ctx, ref.GetResourceId().OpaqueId)
   616  		if err != nil {
   617  			return "", err
   618  		}
   619  		p = strings.TrimPrefix(p, "files/")
   620  		if !fs.c.EnableHome {
   621  			owner, err := fs.filecache.GetStorageOwnerByFileID(ctx, ref.GetResourceId().OpaqueId)
   622  			if err != nil {
   623  				return "", err
   624  			}
   625  			p = filepath.Join(owner, p)
   626  		}
   627  		if ref.GetPath() != "" {
   628  			p = filepath.Join(p, ref.GetPath())
   629  		}
   630  		return fs.toInternalPath(ctx, p), nil
   631  	}
   632  
   633  	if ref.GetPath() != "" {
   634  		return fs.toInternalPath(ctx, ref.GetPath()), nil
   635  	}
   636  
   637  	// reference is invalid
   638  	return "", fmt.Errorf("invalid reference %+v", ref)
   639  }
   640  
   641  func (fs *owncloudsqlfs) DenyGrant(ctx context.Context, ref *provider.Reference, g *provider.Grantee) error {
   642  	return errtypes.NotSupported("owncloudsqlfs: deny grant not supported")
   643  }
   644  
   645  func (fs *owncloudsqlfs) AddGrant(ctx context.Context, ref *provider.Reference, g *provider.Grant) error {
   646  	return errtypes.NotSupported("owncloudsqlfs: add grant not supported")
   647  }
   648  
   649  func (fs *owncloudsqlfs) readPermissions(ctx context.Context, ip string) (p *provider.ResourcePermissions, err error) {
   650  	u, ok := ctxpkg.ContextGetUser(ctx)
   651  	if !ok {
   652  		appctx.GetLogger(ctx).Debug().Str("ipath", ip).Msg("no user in context, returning default permissions")
   653  		return defaultPermissions, nil
   654  	}
   655  	// check if the current user is the owner
   656  	owner := fs.getOwner(ip)
   657  	if owner == u.Username {
   658  		appctx.GetLogger(ctx).Debug().Str("ipath", ip).Msg("user is owner, returning owner permissions")
   659  		return ownerPermissions, nil
   660  	}
   661  
   662  	// otherwise this is a share
   663  	ownerStorageID, err := fs.filecache.GetNumericStorageID(ctx, "home::"+owner)
   664  	if err != nil {
   665  		return nil, err
   666  	}
   667  	entry, err := fs.filecache.Get(ctx, ownerStorageID, fs.toDatabasePath(ip))
   668  	if err != nil {
   669  		return nil, err
   670  	}
   671  	perms, err := conversions.NewPermissions(entry.Permissions)
   672  	if err != nil {
   673  		return nil, err
   674  	}
   675  	return conversions.RoleFromOCSPermissions(perms, nil).CS3ResourcePermissions(), nil
   676  }
   677  
   678  // The os not exists error is buried inside the xattr error,
   679  // so we cannot just use os.IsNotExists().
   680  func isNotFound(err error) bool {
   681  	if xerr, ok := err.(*xattr.Error); ok {
   682  		if serr, ok2 := xerr.Err.(syscall.Errno); ok2 {
   683  			return serr == syscall.ENOENT
   684  		}
   685  	}
   686  	return false
   687  }
   688  
   689  func (fs *owncloudsqlfs) ListGrants(ctx context.Context, ref *provider.Reference) (grants []*provider.Grant, err error) {
   690  	return []*provider.Grant{}, nil // nop
   691  }
   692  
   693  func (fs *owncloudsqlfs) RemoveGrant(ctx context.Context, ref *provider.Reference, g *provider.Grant) (err error) {
   694  	return nil // nop
   695  }
   696  
   697  func (fs *owncloudsqlfs) UpdateGrant(ctx context.Context, ref *provider.Reference, g *provider.Grant) error {
   698  	return nil // nop
   699  }
   700  
   701  func (fs *owncloudsqlfs) CreateHome(ctx context.Context) error {
   702  	u, ok := ctxpkg.ContextGetUser(ctx)
   703  	if !ok {
   704  		err := errors.Wrap(errtypes.UserRequired("userrequired"), "error getting user from ctx")
   705  		return err
   706  	}
   707  	return fs.createHomeForUser(ctx, templates.WithUser(u, fs.c.UserLayout))
   708  }
   709  
   710  func (fs *owncloudsqlfs) createHomeForUser(ctx context.Context, user string) error {
   711  	homePaths := []string{
   712  		filepath.Join(fs.c.DataDirectory, user),
   713  		filepath.Join(fs.c.DataDirectory, user, "files"),
   714  		filepath.Join(fs.c.DataDirectory, user, "files_trashbin"),
   715  		filepath.Join(fs.c.DataDirectory, user, "files_trashbin/files"),
   716  		filepath.Join(fs.c.DataDirectory, user, "files_trashbin/versions"),
   717  		filepath.Join(fs.c.DataDirectory, user, "uploads"),
   718  	}
   719  
   720  	storageID, err := fs.getUserStorage(ctx, user)
   721  	if err != nil {
   722  		return err
   723  	}
   724  	for _, v := range homePaths {
   725  		if err := os.MkdirAll(v, 0755); err != nil {
   726  			return errors.Wrap(err, "owncloudsql: error creating home path: "+v)
   727  		}
   728  
   729  		fi, err := os.Stat(v)
   730  		if err != nil {
   731  			return err
   732  		}
   733  		data := map[string]interface{}{
   734  			"path":        fs.toDatabasePath(v),
   735  			"etag":        calcEtag(ctx, fi),
   736  			"mimetype":    "httpd/unix-directory",
   737  			"permissions": 31, // 1: READ, 2: UPDATE, 4: CREATE, 8: DELETE, 16: SHARE
   738  		}
   739  
   740  		allowEmptyParent := v == filepath.Join(fs.c.DataDirectory, user) // the root doesn't have a parent
   741  		_, err = fs.filecache.InsertOrUpdate(ctx, storageID, data, allowEmptyParent)
   742  		if err != nil {
   743  			return err
   744  		}
   745  	}
   746  	return nil
   747  }
   748  
   749  // If home is enabled, the relative home is always the empty string
   750  func (fs *owncloudsqlfs) GetHome(ctx context.Context) (string, error) {
   751  	if !fs.c.EnableHome {
   752  		return "", errtypes.NotSupported("owncloudsql: get home not supported")
   753  	}
   754  	return "", nil
   755  }
   756  
   757  func (fs *owncloudsqlfs) CreateDir(ctx context.Context, ref *provider.Reference) (err error) {
   758  
   759  	ip, err := fs.resolve(ctx, ref)
   760  	if err != nil {
   761  		return err
   762  	}
   763  
   764  	// check permissions of parent dir
   765  	if perm, err := fs.readPermissions(ctx, filepath.Dir(ip)); err == nil {
   766  		if !perm.CreateContainer {
   767  			return errtypes.PermissionDenied("")
   768  		}
   769  	} else {
   770  		if isNotFound(err) {
   771  			return errtypes.PreconditionFailed(ref.Path)
   772  		}
   773  		return errors.Wrap(err, "owncloudsql: error reading permissions")
   774  	}
   775  
   776  	if err = os.Mkdir(ip, 0700); err != nil {
   777  		if os.IsNotExist(err) {
   778  			return errtypes.PreconditionFailed(ref.Path)
   779  		}
   780  		if os.IsExist(err) {
   781  			return errtypes.AlreadyExists(ref.Path)
   782  		}
   783  		return errors.Wrap(err, "owncloudsql: error creating dir "+fs.toStoragePath(ctx, filepath.Dir(ip)))
   784  	}
   785  
   786  	fi, err := os.Stat(ip)
   787  	if err != nil {
   788  		return err
   789  	}
   790  	mtime := time.Now().Unix()
   791  
   792  	permissions := 31 // 1: READ, 2: UPDATE, 4: CREATE, 8: DELETE, 16: SHARE
   793  	if perm, err := fs.readPermissions(ctx, filepath.Dir(ip)); err == nil {
   794  		permissions = int(conversions.RoleFromResourcePermissions(perm, false).OCSPermissions()) // inherit permissions of parent
   795  	}
   796  	data := map[string]interface{}{
   797  		"path":          fs.toDatabasePath(ip),
   798  		"etag":          calcEtag(ctx, fi),
   799  		"mimetype":      "httpd/unix-directory",
   800  		"permissions":   permissions,
   801  		"mtime":         mtime,
   802  		"storage_mtime": mtime,
   803  	}
   804  	storageID, err := fs.getStorage(ctx, ip)
   805  	if err != nil {
   806  		return err
   807  	}
   808  	_, err = fs.filecache.InsertOrUpdate(ctx, storageID, data, false)
   809  	if err != nil {
   810  		if err != nil {
   811  			return err
   812  		}
   813  	}
   814  
   815  	return fs.propagate(ctx, filepath.Dir(ip))
   816  }
   817  
   818  // TouchFile as defined in the storage.FS interface
   819  func (fs *owncloudsqlfs) TouchFile(ctx context.Context, ref *provider.Reference, markprocessing bool, mtime string) error {
   820  	ip, err := fs.resolve(ctx, ref)
   821  	if err != nil {
   822  		return err
   823  	}
   824  
   825  	// check permissions of parent dir
   826  	parentPerms, err := fs.readPermissions(ctx, filepath.Dir(ip))
   827  	if err == nil {
   828  		if !parentPerms.InitiateFileUpload {
   829  			return errtypes.PermissionDenied("")
   830  		}
   831  	} else {
   832  		if isNotFound(err) {
   833  			return errtypes.NotFound(ref.Path)
   834  		}
   835  		return errors.Wrap(err, "owncloudsql: error reading permissions")
   836  	}
   837  
   838  	_, err = os.Create(ip)
   839  	if err != nil {
   840  		if os.IsNotExist(err) {
   841  			return errtypes.NotFound(ref.Path)
   842  		}
   843  		// FIXME we also need already exists error, webdav expects 405 MethodNotAllowed
   844  		return errors.Wrap(err, "owncloudsql: error creating file "+fs.toStoragePath(ctx, filepath.Dir(ip)))
   845  	}
   846  
   847  	if err = os.Chmod(ip, 0700); err != nil {
   848  		return errors.Wrap(err, "owncloudsql: error setting file permissions on "+fs.toStoragePath(ctx, filepath.Dir(ip)))
   849  	}
   850  
   851  	fi, err := os.Stat(ip)
   852  	if err != nil {
   853  		return err
   854  	}
   855  	storageMtime := time.Now().Unix()
   856  	mt := storageMtime
   857  	if mtime != "" {
   858  		t, err := strconv.Atoi(mtime)
   859  		if err != nil {
   860  			log.Info().
   861  				Str("owncloudsql", ip).
   862  				Msg("error mtime conversion. mtine set to system time")
   863  		}
   864  		mt = time.Unix(int64(t), 0).Unix()
   865  	}
   866  
   867  	data := map[string]interface{}{
   868  		"path":          fs.toDatabasePath(ip),
   869  		"etag":          calcEtag(ctx, fi),
   870  		"mimetype":      mime.Detect(false, ip),
   871  		"permissions":   int(conversions.RoleFromResourcePermissions(parentPerms, false).OCSPermissions()), // inherit permissions of parent
   872  		"mtime":         mt,
   873  		"storage_mtime": storageMtime,
   874  	}
   875  	storageID, err := fs.getStorage(ctx, ip)
   876  	if err != nil {
   877  		return err
   878  	}
   879  	_, err = fs.filecache.InsertOrUpdate(ctx, storageID, data, false)
   880  	if err != nil {
   881  		return err
   882  	}
   883  
   884  	return fs.propagate(ctx, filepath.Dir(ip))
   885  }
   886  
   887  func (fs *owncloudsqlfs) CreateReference(ctx context.Context, sp string, targetURI *url.URL) error {
   888  	return errtypes.NotSupported("owncloudsql: operation not supported")
   889  }
   890  
   891  func (fs *owncloudsqlfs) setMtime(ctx context.Context, ip string, mtime string) error {
   892  	log := appctx.GetLogger(ctx)
   893  	if mt, err := parseMTime(mtime); err == nil {
   894  		// updating mtime also updates atime
   895  		if err := os.Chtimes(ip, mt, mt); err != nil {
   896  			log.Error().Err(err).
   897  				Str("ipath", ip).
   898  				Time("mtime", mt).
   899  				Msg("could not set mtime")
   900  			return errors.Wrap(err, "could not set mtime")
   901  		}
   902  	} else {
   903  		log.Error().Err(err).
   904  			Str("ipath", ip).
   905  			Str("mtime", mtime).
   906  			Msg("could not parse mtime")
   907  		return errors.Wrap(err, "could not parse mtime")
   908  	}
   909  	return nil
   910  }
   911  func (fs *owncloudsqlfs) SetArbitraryMetadata(ctx context.Context, ref *provider.Reference, md *provider.ArbitraryMetadata) (err error) {
   912  	log := appctx.GetLogger(ctx)
   913  
   914  	var ip string
   915  	if ip, err = fs.resolve(ctx, ref); err != nil {
   916  		return errors.Wrap(err, "owncloudsql: error resolving reference")
   917  	}
   918  
   919  	// check permissions
   920  	if perm, err := fs.readPermissions(ctx, ip); err == nil {
   921  		if !perm.InitiateFileUpload { // TODO add dedicated permission?
   922  			return errtypes.PermissionDenied("")
   923  		}
   924  	} else {
   925  		if isNotFound(err) {
   926  			return errtypes.NotFound(fs.toStoragePath(ctx, filepath.Dir(ip)))
   927  		}
   928  		return errors.Wrap(err, "owncloudsql: error reading permissions")
   929  	}
   930  
   931  	var fi os.FileInfo
   932  	fi, err = os.Stat(ip)
   933  	if err != nil {
   934  		if os.IsNotExist(err) {
   935  			return errtypes.NotFound(fs.toStoragePath(ctx, ip))
   936  		}
   937  		return errors.Wrap(err, "owncloudsql: error stating "+ip)
   938  	}
   939  
   940  	errs := []error{}
   941  
   942  	if md.Metadata != nil {
   943  		if val, ok := md.Metadata["mtime"]; ok {
   944  			err := fs.setMtime(ctx, ip, val)
   945  			if err != nil {
   946  				errs = append(errs, errors.Wrap(err, "could not set mtime"))
   947  			}
   948  			// remove from metadata
   949  			delete(md.Metadata, "mtime")
   950  		}
   951  		// TODO(jfd) special handling for atime?
   952  		// TODO(jfd) allow setting birth time (btime)?
   953  		// TODO(jfd) any other metadata that is interesting? fileid?
   954  		if val, ok := md.Metadata["etag"]; ok {
   955  			etag := calcEtag(ctx, fi)
   956  			val = fmt.Sprintf("\"%s\"", strings.Trim(val, "\""))
   957  			if etag == val {
   958  				log.Debug().
   959  					Str("ipath", ip).
   960  					Str("etag", val).
   961  					Msg("ignoring request to update identical etag")
   962  			} else
   963  			// etag is only valid until the calculated etag changes
   964  			// TODO(jfd) cleanup in a batch job
   965  			if err := xattr.Set(ip, etagPrefix+etag, []byte(val)); err != nil {
   966  				log.Error().Err(err).
   967  					Str("ipath", ip).
   968  					Str("calcetag", etag).
   969  					Str("etag", val).
   970  					Msg("could not set etag")
   971  				errs = append(errs, errors.Wrap(err, "could not set etag"))
   972  			}
   973  			delete(md.Metadata, "etag")
   974  		}
   975  		if val, ok := md.Metadata["http://owncloud.org/ns/favorite"]; ok {
   976  			// TODO we should not mess with the user here ... the favorites is now a user specific property for a file
   977  			// that cannot be mapped to extended attributes without leaking who has marked a file as a favorite
   978  			// it is a specific case of a tag, which is user individual as well
   979  			// TODO there are different types of tags
   980  			// 1. public that are managed by everyone
   981  			// 2. private tags that are only visible to the user
   982  			// 3. system tags that are only visible to the system
   983  			// 4. group tags that are only visible to a group ...
   984  			// urgh ... well this can be solved using different namespaces
   985  			// 1. public = p:
   986  			// 2. private = u:<uid>: for user specific
   987  			// 3. system = s: for system
   988  			// 4. group = g:<gid>:
   989  			// 5. app? = a:<aid>: for apps?
   990  			// obviously this only is secure when the u/s/g/a namespaces are not accessible by users in the filesystem
   991  			// public tags can be mapped to extended attributes
   992  			if u, ok := ctxpkg.ContextGetUser(ctx); ok {
   993  				// the favorite flag is specific to the user, so we need to incorporate the userid
   994  				if uid := u.GetId(); uid != nil {
   995  					fa := fmt.Sprintf("%s%s@%s", favPrefix, uid.GetOpaqueId(), uid.GetIdp())
   996  					if err := xattr.Set(ip, fa, []byte(val)); err != nil {
   997  						log.Error().Err(err).
   998  							Str("ipath", ip).
   999  							Interface("user", u).
  1000  							Str("key", fa).
  1001  							Msg("could not set favorite flag")
  1002  						errs = append(errs, errors.Wrap(err, "could not set favorite flag"))
  1003  					}
  1004  				} else {
  1005  					log.Error().
  1006  						Str("ipath", ip).
  1007  						Interface("user", u).
  1008  						Msg("user has no id")
  1009  					errs = append(errs, errors.Wrap(errtypes.UserRequired("userrequired"), "user has no id"))
  1010  				}
  1011  			} else {
  1012  				log.Error().
  1013  					Str("ipath", ip).
  1014  					Interface("user", u).
  1015  					Msg("error getting user from ctx")
  1016  				errs = append(errs, errors.Wrap(errtypes.UserRequired("userrequired"), "error getting user from ctx"))
  1017  			}
  1018  			// remove from metadata
  1019  			delete(md.Metadata, "http://owncloud.org/ns/favorite")
  1020  		}
  1021  	}
  1022  	for k, v := range md.Metadata {
  1023  		if err := xattr.Set(ip, mdPrefix+k, []byte(v)); err != nil {
  1024  			log.Error().Err(err).
  1025  				Str("ipath", ip).
  1026  				Str("key", k).
  1027  				Str("val", v).
  1028  				Msg("could not set metadata")
  1029  			errs = append(errs, errors.Wrap(err, "could not set metadata"))
  1030  		}
  1031  	}
  1032  	switch len(errs) {
  1033  	case 0:
  1034  		return fs.propagate(ctx, ip)
  1035  	case 1:
  1036  		return errs[0]
  1037  	default:
  1038  		// TODO how to return multiple errors?
  1039  		return errors.New("multiple errors occurred, see log for details")
  1040  	}
  1041  }
  1042  
  1043  func parseMTime(v string) (t time.Time, err error) {
  1044  	p := strings.SplitN(v, ".", 2)
  1045  	var sec, nsec int64
  1046  	if sec, err = strconv.ParseInt(p[0], 10, 64); err == nil {
  1047  		if len(p) > 1 {
  1048  			nsec, err = strconv.ParseInt(p[1], 10, 64)
  1049  		}
  1050  	}
  1051  	return time.Unix(sec, nsec), err
  1052  }
  1053  
  1054  func (fs *owncloudsqlfs) UnsetArbitraryMetadata(ctx context.Context, ref *provider.Reference, keys []string) (err error) {
  1055  	log := appctx.GetLogger(ctx)
  1056  
  1057  	var ip string
  1058  	if ip, err = fs.resolve(ctx, ref); err != nil {
  1059  		return errors.Wrap(err, "owncloudsql: error resolving reference")
  1060  	}
  1061  
  1062  	// check permissions
  1063  	if perm, err := fs.readPermissions(ctx, ip); err == nil {
  1064  		if !perm.InitiateFileUpload { // TODO add dedicated permission?
  1065  			return errtypes.PermissionDenied("")
  1066  		}
  1067  	} else {
  1068  		if isNotFound(err) {
  1069  			return errtypes.NotFound(fs.toStoragePath(ctx, ip))
  1070  		}
  1071  		return errors.Wrap(err, "owncloudsql: error reading permissions")
  1072  	}
  1073  
  1074  	_, err = os.Stat(ip)
  1075  	if err != nil {
  1076  		if os.IsNotExist(err) {
  1077  			return errtypes.NotFound(fs.toStoragePath(ctx, ip))
  1078  		}
  1079  		return errors.Wrap(err, "owncloudsql: error stating "+ip)
  1080  	}
  1081  
  1082  	errs := []error{}
  1083  	for _, k := range keys {
  1084  		switch k {
  1085  		case "http://owncloud.org/ns/favorite":
  1086  			if u, ok := ctxpkg.ContextGetUser(ctx); ok {
  1087  				// the favorite flag is specific to the user, so we need to incorporate the userid
  1088  				if uid := u.GetId(); uid != nil {
  1089  					fa := fmt.Sprintf("%s%s@%s", favPrefix, uid.GetOpaqueId(), uid.GetIdp())
  1090  					if err := xattr.Remove(ip, fa); err != nil {
  1091  						log.Error().Err(err).
  1092  							Str("ipath", ip).
  1093  							Interface("user", u).
  1094  							Str("key", fa).
  1095  							Msg("could not unset favorite flag")
  1096  						errs = append(errs, errors.Wrap(err, "could not unset favorite flag"))
  1097  					}
  1098  				} else {
  1099  					log.Error().
  1100  						Str("ipath", ip).
  1101  						Interface("user", u).
  1102  						Msg("user has no id")
  1103  					errs = append(errs, errors.Wrap(errtypes.UserRequired("userrequired"), "user has no id"))
  1104  				}
  1105  			} else {
  1106  				log.Error().
  1107  					Str("ipath", ip).
  1108  					Interface("user", u).
  1109  					Msg("error getting user from ctx")
  1110  				errs = append(errs, errors.Wrap(errtypes.UserRequired("userrequired"), "error getting user from ctx"))
  1111  			}
  1112  		default:
  1113  			if err = xattr.Remove(ip, mdPrefix+k); err != nil {
  1114  				// a non-existing attribute will return an error, which we can ignore
  1115  				// (using string compare because the error type is syscall.Errno and not wrapped/recognizable)
  1116  				if e, ok := err.(*xattr.Error); !ok || !(e.Err.Error() == "no data available" ||
  1117  					// darwin
  1118  					e.Err.Error() == "attribute not found") {
  1119  					log.Error().Err(err).
  1120  						Str("ipath", ip).
  1121  						Str("key", k).
  1122  						Msg("could not unset metadata")
  1123  					errs = append(errs, errors.Wrap(err, "could not unset metadata"))
  1124  				}
  1125  			}
  1126  		}
  1127  	}
  1128  
  1129  	switch len(errs) {
  1130  	case 0:
  1131  		return fs.propagate(ctx, ip)
  1132  	case 1:
  1133  		return errs[0]
  1134  	default:
  1135  		// TODO how to return multiple errors?
  1136  		return errors.New("multiple errors occurred, see log for details")
  1137  	}
  1138  }
  1139  
  1140  // GetLock returns an existing lock on the given reference
  1141  func (fs *owncloudsqlfs) GetLock(ctx context.Context, ref *provider.Reference) (*provider.Lock, error) {
  1142  	return nil, errtypes.NotSupported("unimplemented")
  1143  }
  1144  
  1145  // SetLock puts a lock on the given reference
  1146  func (fs *owncloudsqlfs) SetLock(ctx context.Context, ref *provider.Reference, lock *provider.Lock) error {
  1147  	return errtypes.NotSupported("unimplemented")
  1148  }
  1149  
  1150  // RefreshLock refreshes an existing lock on the given reference
  1151  func (fs *owncloudsqlfs) RefreshLock(ctx context.Context, ref *provider.Reference, lock *provider.Lock, existingLockID string) error {
  1152  	return errtypes.NotSupported("unimplemented")
  1153  }
  1154  
  1155  // Unlock removes an existing lock from the given reference
  1156  func (fs *owncloudsqlfs) Unlock(ctx context.Context, ref *provider.Reference, lock *provider.Lock) error {
  1157  	return errtypes.NotSupported("unimplemented")
  1158  }
  1159  
  1160  // Delete is actually only a move to trash
  1161  //
  1162  // This is a first optimistic approach.
  1163  // When a file has versions and we want to delete the file it could happen that
  1164  // the service crashes before all moves are finished.
  1165  // That would result in invalid state like the main files was moved but the
  1166  // versions were not.
  1167  // We will live with that compromise since this storage driver will be
  1168  // deprecated soon.
  1169  func (fs *owncloudsqlfs) Delete(ctx context.Context, ref *provider.Reference) (err error) {
  1170  	var ip string
  1171  	if ip, err = fs.resolve(ctx, ref); err != nil {
  1172  		return errors.Wrap(err, "owncloudsql: error resolving reference")
  1173  	}
  1174  
  1175  	// check permissions
  1176  	if perm, err := fs.readPermissions(ctx, ip); err == nil {
  1177  		if !perm.Delete {
  1178  			return errtypes.PermissionDenied("")
  1179  		}
  1180  	} else {
  1181  		if isNotFound(err) {
  1182  			return errtypes.NotFound(fs.toStoragePath(ctx, filepath.Dir(ip)))
  1183  		}
  1184  		return errors.Wrap(err, "owncloudsql: error reading permissions")
  1185  	}
  1186  
  1187  	_, err = os.Stat(ip)
  1188  	if err != nil {
  1189  		if os.IsNotExist(err) {
  1190  			return errtypes.NotFound(fs.toStoragePath(ctx, ip))
  1191  		}
  1192  		return errors.Wrap(err, "owncloudsql: error stating "+ip)
  1193  	}
  1194  
  1195  	// Delete file into the owner's trash, not the user's (in case of shares)
  1196  	rp, err := fs.getRecyclePathForUser(fs.getOwner(ip))
  1197  	if err != nil {
  1198  		return errors.Wrap(err, "owncloudsql: error resolving recycle path")
  1199  	}
  1200  
  1201  	if err := os.MkdirAll(rp, 0700); err != nil {
  1202  		return errors.Wrap(err, "owncloudsql: error creating trashbin dir "+rp)
  1203  	}
  1204  
  1205  	// ip is the path on disk ... we need only the path relative to root
  1206  	origin := filepath.Dir(fs.toStoragePath(ctx, ip))
  1207  
  1208  	err = fs.trash(ctx, ip, rp, origin)
  1209  	if err != nil {
  1210  		return errors.Wrapf(err, "owncloudsql: error deleting file %s", ip)
  1211  	}
  1212  	return nil
  1213  }
  1214  
  1215  func (fs *owncloudsqlfs) trash(ctx context.Context, ip string, rp string, origin string) error {
  1216  	// move to trash location
  1217  	dtime := time.Now().Unix()
  1218  	tgt := filepath.Join(rp, fmt.Sprintf("%s.d%d", filepath.Base(ip), dtime))
  1219  	if err := os.Rename(ip, tgt); err != nil {
  1220  		if os.IsExist(err) {
  1221  			// timestamp collision, try again with higher value:
  1222  			dtime++
  1223  			tgt := filepath.Join(rp, fmt.Sprintf("%s.d%d", filepath.Base(ip), dtime))
  1224  			if err := os.Rename(ip, tgt); err != nil {
  1225  				return errors.Wrap(err, "owncloudsql: could not move item to trash")
  1226  			}
  1227  		}
  1228  	}
  1229  
  1230  	storage, err := fs.getStorage(ctx, ip)
  1231  	if err != nil {
  1232  		return err
  1233  	}
  1234  
  1235  	tryDelete := func() error {
  1236  		return fs.filecache.Delete(ctx, storage, fs.getOwner(ip), fs.toDatabasePath(ip), fs.toDatabasePath(tgt))
  1237  	}
  1238  	err = tryDelete()
  1239  	if err != nil {
  1240  		err = fs.createHomeForUser(ctx, fs.getOwner(ip)) // Try setting up the owner's home (incl. trash) to fix the problem
  1241  		if err != nil {
  1242  			return err
  1243  		}
  1244  		err = tryDelete()
  1245  		if err != nil {
  1246  			return err
  1247  		}
  1248  	}
  1249  
  1250  	err = fs.trashVersions(ctx, ip, origin, dtime)
  1251  	if err != nil {
  1252  		return errors.Wrapf(err, "owncloudsql: error deleting versions of file %s", ip)
  1253  	}
  1254  
  1255  	return fs.propagate(ctx, filepath.Dir(ip))
  1256  }
  1257  
  1258  func (fs *owncloudsqlfs) trashVersions(ctx context.Context, ip string, origin string, dtime int64) error {
  1259  	vp := fs.getVersionsPath(ctx, ip)
  1260  	vrp, err := fs.getVersionRecyclePath(ctx)
  1261  	if err != nil {
  1262  		return errors.Wrap(err, "error resolving versions recycle path")
  1263  	}
  1264  
  1265  	if err := os.MkdirAll(vrp, 0700); err != nil {
  1266  		return errors.Wrap(err, "owncloudsql: error creating trashbin dir "+vrp)
  1267  	}
  1268  
  1269  	// Ignore error since the only possible error is malformed pattern.
  1270  	versions, _ := filepath.Glob(vp + ".v*")
  1271  	storage, err := fs.getStorage(ctx, ip)
  1272  	if err != nil {
  1273  		return err
  1274  	}
  1275  	for _, v := range versions {
  1276  		tgt := filepath.Join(vrp, fmt.Sprintf("%s.d%d", filepath.Base(v), dtime))
  1277  		if err := os.Rename(v, tgt); err != nil {
  1278  			if os.IsExist(err) {
  1279  				// timestamp collision, try again with higher value:
  1280  				dtime++
  1281  				tgt := filepath.Join(vrp, fmt.Sprintf("%s.d%d", filepath.Base(ip), dtime))
  1282  				if err := os.Rename(ip, tgt); err != nil {
  1283  					return errors.Wrap(err, "owncloudsql: could not move item to trash")
  1284  				}
  1285  			}
  1286  		}
  1287  		if err != nil {
  1288  			return errors.Wrap(err, "owncloudsql: error deleting file "+v)
  1289  		}
  1290  		err = fs.filecache.Move(ctx, storage, fs.toDatabasePath(v), fs.toDatabasePath(tgt))
  1291  		if err != nil {
  1292  			return errors.Wrap(err, "owncloudsql: error deleting file "+v)
  1293  		}
  1294  	}
  1295  	return nil
  1296  }
  1297  
  1298  func (fs *owncloudsqlfs) Move(ctx context.Context, oldRef, newRef *provider.Reference) (err error) {
  1299  	var oldIP string
  1300  	if oldIP, err = fs.resolve(ctx, oldRef); err != nil {
  1301  		return errors.Wrap(err, "owncloudsql: error resolving reference")
  1302  	}
  1303  
  1304  	// check permissions
  1305  	if perm, err := fs.readPermissions(ctx, oldIP); err == nil {
  1306  		if !perm.Move { // TODO add dedicated permission?
  1307  			return errtypes.PermissionDenied("")
  1308  		}
  1309  	} else {
  1310  		if isNotFound(err) {
  1311  			return errtypes.NotFound(fs.toStoragePath(ctx, filepath.Dir(oldIP)))
  1312  		}
  1313  		return errors.Wrap(err, "owncloudsql: error reading permissions")
  1314  	}
  1315  
  1316  	var newIP string
  1317  	if newIP, err = fs.resolve(ctx, newRef); err != nil {
  1318  		return errors.Wrap(err, "owncloudsql: error resolving reference")
  1319  	}
  1320  
  1321  	// TODO check target permissions ... if it exists
  1322  	storage, err := fs.getStorage(ctx, oldIP)
  1323  	if err != nil {
  1324  		return err
  1325  	}
  1326  	err = fs.filecache.Move(ctx, storage, fs.toDatabasePath(oldIP), fs.toDatabasePath(newIP))
  1327  	if err != nil {
  1328  		return err
  1329  	}
  1330  	if err = os.Rename(oldIP, newIP); err != nil {
  1331  		return errors.Wrap(err, "owncloudsql: error moving "+oldIP+" to "+newIP)
  1332  	}
  1333  
  1334  	if err := fs.propagate(ctx, newIP); err != nil {
  1335  		return err
  1336  	}
  1337  	if filepath.Dir(newIP) != filepath.Dir(oldIP) {
  1338  		if err := fs.propagate(ctx, filepath.Dir(oldIP)); err != nil {
  1339  			return err
  1340  		}
  1341  	}
  1342  	return nil
  1343  }
  1344  
  1345  func (fs *owncloudsqlfs) GetMD(ctx context.Context, ref *provider.Reference, mdKeys []string, fieldMask []string) (*provider.ResourceInfo, error) {
  1346  	ip, err := fs.resolve(ctx, ref)
  1347  	if err != nil {
  1348  		// TODO return correct errtype
  1349  		if _, ok := err.(errtypes.IsNotFound); ok {
  1350  			return nil, err
  1351  		}
  1352  		return nil, errors.Wrap(err, "owncloudsql: error resolving reference")
  1353  	}
  1354  	p := fs.toStoragePath(ctx, ip)
  1355  
  1356  	// If GetMD is called for a path shared with the user then the path is
  1357  	// already wrapped. (fs.resolve wraps the path)
  1358  	if strings.HasPrefix(p, fs.c.DataDirectory) {
  1359  		ip = p
  1360  	}
  1361  
  1362  	// check permissions
  1363  	if perm, err := fs.readPermissions(ctx, ip); err == nil {
  1364  		if !perm.Stat {
  1365  			return nil, errtypes.PermissionDenied("")
  1366  		}
  1367  	} else {
  1368  		if isNotFound(err) {
  1369  			return nil, errtypes.NotFound(fs.toStoragePath(ctx, filepath.Dir(ip)))
  1370  		}
  1371  		return nil, errors.Wrap(err, "owncloudsql: error reading permissions")
  1372  	}
  1373  
  1374  	ownerStorageID, err := fs.filecache.GetNumericStorageID(ctx, "home::"+fs.getOwner(ip))
  1375  	if err != nil {
  1376  		return nil, err
  1377  	}
  1378  	entry, err := fs.filecache.Get(ctx, ownerStorageID, fs.toDatabasePath(ip))
  1379  	switch {
  1380  	case err == sql.ErrNoRows:
  1381  		return nil, errtypes.NotFound(fs.toStoragePath(ctx, filepath.Dir(ip)))
  1382  	case err != nil:
  1383  		return nil, err
  1384  	}
  1385  
  1386  	return fs.convertToResourceInfo(ctx, entry, ip, mdKeys)
  1387  }
  1388  
  1389  func (fs *owncloudsqlfs) ListFolder(ctx context.Context, ref *provider.Reference, mdKeys, fieldMask []string) ([]*provider.ResourceInfo, error) {
  1390  	log := appctx.GetLogger(ctx)
  1391  
  1392  	ip, err := fs.resolve(ctx, ref)
  1393  	if err != nil {
  1394  		return nil, errors.Wrap(err, "owncloudsql: error resolving reference")
  1395  	}
  1396  	sp := fs.toStoragePath(ctx, ip)
  1397  
  1398  	if fs.c.EnableHome {
  1399  		log.Debug().Msg("home enabled")
  1400  		if strings.HasPrefix(sp, "/") {
  1401  			// permissions checked in listWithHome
  1402  			return fs.listWithHome(ctx, "/", sp, mdKeys)
  1403  		}
  1404  	}
  1405  
  1406  	log.Debug().Msg("list with nominal home")
  1407  	// permissions checked in listWithNominalHome
  1408  	return fs.listWithNominalHome(ctx, sp, mdKeys)
  1409  }
  1410  
  1411  func (fs *owncloudsqlfs) listWithNominalHome(ctx context.Context, ip string, mdKeys []string) ([]*provider.ResourceInfo, error) {
  1412  
  1413  	// If a user wants to list a folder shared with him the path will already
  1414  	// be wrapped with the files directory path of the share owner.
  1415  	// In that case we don't want to wrap the path again.
  1416  	if !strings.HasPrefix(ip, fs.c.DataDirectory) {
  1417  		ip = fs.toInternalPath(ctx, ip)
  1418  	}
  1419  
  1420  	// check permissions
  1421  	if perm, err := fs.readPermissions(ctx, ip); err == nil {
  1422  		if !perm.ListContainer {
  1423  			return nil, errtypes.PermissionDenied("")
  1424  		}
  1425  	} else {
  1426  		if isNotFound(err) {
  1427  			return nil, errtypes.NotFound(fs.toStoragePath(ctx, filepath.Dir(ip)))
  1428  		}
  1429  		return nil, errors.Wrap(err, "owncloudsql: error reading permissions")
  1430  	}
  1431  
  1432  	storage, err := fs.getStorage(ctx, ip)
  1433  	if err != nil {
  1434  		return nil, err
  1435  	}
  1436  	entries, err := fs.filecache.List(ctx, storage, fs.toDatabasePath(ip)+"/")
  1437  	if err != nil {
  1438  		return nil, errors.Wrapf(err, "owncloudsql: error listing %s", ip)
  1439  	}
  1440  	owner := fs.getOwner(ip)
  1441  	finfos := []*provider.ResourceInfo{}
  1442  	for _, entry := range entries {
  1443  		cp := filepath.Join(fs.c.DataDirectory, owner, entry.Path)
  1444  		m, err := fs.convertToResourceInfo(ctx, entry, cp, mdKeys)
  1445  		if err != nil {
  1446  			appctx.GetLogger(ctx).Error().Err(err).Str("path", cp).Msg("error converting to a resource info")
  1447  		}
  1448  		finfos = append(finfos, m)
  1449  	}
  1450  	return finfos, nil
  1451  }
  1452  
  1453  func (fs *owncloudsqlfs) listWithHome(ctx context.Context, home, p string, mdKeys []string) ([]*provider.ResourceInfo, error) {
  1454  	log := appctx.GetLogger(ctx)
  1455  	if p == home {
  1456  		log.Debug().Msg("listing home")
  1457  		return fs.listHome(ctx, home, mdKeys)
  1458  	}
  1459  
  1460  	log.Debug().Msg("listing nominal home")
  1461  	return fs.listWithNominalHome(ctx, p, mdKeys)
  1462  }
  1463  
  1464  func (fs *owncloudsqlfs) listHome(ctx context.Context, home string, mdKeys []string) ([]*provider.ResourceInfo, error) {
  1465  	// list files
  1466  	ip := fs.toInternalPath(ctx, home)
  1467  
  1468  	// check permissions
  1469  	if perm, err := fs.readPermissions(ctx, ip); err == nil {
  1470  		if !perm.ListContainer {
  1471  			return nil, errtypes.PermissionDenied("")
  1472  		}
  1473  	} else {
  1474  		if isNotFound(err) {
  1475  			return nil, errtypes.NotFound(fs.toStoragePath(ctx, filepath.Dir(ip)))
  1476  		}
  1477  		return nil, errors.Wrap(err, "owncloudsql: error reading permissions")
  1478  	}
  1479  
  1480  	storage, err := fs.getStorage(ctx, ip)
  1481  	if err != nil {
  1482  		return nil, err
  1483  	}
  1484  	entries, err := fs.filecache.List(ctx, storage, fs.toDatabasePath(ip)+"/")
  1485  	if err != nil {
  1486  		return nil, errors.Wrapf(err, "owncloudsql: error listing %s", ip)
  1487  	}
  1488  	owner := fs.getOwner(ip)
  1489  	finfos := []*provider.ResourceInfo{}
  1490  	for _, entry := range entries {
  1491  		cp := filepath.Join(fs.c.DataDirectory, owner, entry.Path)
  1492  		m, err := fs.convertToResourceInfo(ctx, entry, cp, mdKeys)
  1493  		if err != nil {
  1494  			appctx.GetLogger(ctx).Error().Err(err).Str("path", cp).Msg("error converting to a resource info")
  1495  		}
  1496  		finfos = append(finfos, m)
  1497  	}
  1498  	return finfos, nil
  1499  }
  1500  
  1501  func (fs *owncloudsqlfs) archiveRevision(ctx context.Context, vbp string, ip string) error {
  1502  	// move existing file to versions dir
  1503  	vp := fmt.Sprintf("%s.v%d", vbp, time.Now().Unix())
  1504  	if err := os.MkdirAll(filepath.Dir(vp), 0700); err != nil {
  1505  		return errors.Wrap(err, "owncloudsql: error creating versions dir "+vp)
  1506  	}
  1507  
  1508  	// TODO(jfd): make sure rename is atomic, missing fsync ...
  1509  	if err := os.Rename(ip, vp); err != nil {
  1510  		return errors.Wrap(err, "owncloudsql: error renaming from "+ip+" to "+vp)
  1511  	}
  1512  
  1513  	storage, err := fs.getStorage(ctx, ip)
  1514  	if err != nil {
  1515  		return err
  1516  	}
  1517  
  1518  	vdp := fs.toDatabasePath(vp)
  1519  	basePath := strings.TrimSuffix(vp, vdp)
  1520  	parts := strings.Split(filepath.Dir(vdp), "/")
  1521  	walkPath := ""
  1522  	for i := 0; i < len(parts); i++ {
  1523  		walkPath = filepath.Join(walkPath, parts[i])
  1524  		_, err := fs.filecache.Get(ctx, storage, walkPath)
  1525  		if err == nil {
  1526  			continue
  1527  		}
  1528  
  1529  		fi, err := os.Stat(filepath.Join(basePath, walkPath))
  1530  		if err != nil {
  1531  			return errors.Wrap(err, "could not stat parent version directory")
  1532  		}
  1533  		data := map[string]interface{}{
  1534  			"path":        walkPath,
  1535  			"mimetype":    "httpd/unix-directory",
  1536  			"etag":        calcEtag(ctx, fi),
  1537  			"permissions": 31, // 1: READ, 2: UPDATE, 4: CREATE, 8: DELETE, 16: SHARE
  1538  		}
  1539  
  1540  		_, err = fs.filecache.InsertOrUpdate(ctx, storage, data, false)
  1541  		if err != nil {
  1542  			return errors.Wrap(err, "could not create parent version directory")
  1543  		}
  1544  	}
  1545  	_, err = fs.filecache.Copy(ctx, storage, fs.toDatabasePath(ip), vdp)
  1546  	return err
  1547  }
  1548  
  1549  func (fs *owncloudsqlfs) Download(ctx context.Context, ref *provider.Reference, openReaderfunc func(*provider.ResourceInfo) bool) (*provider.ResourceInfo, io.ReadCloser, error) {
  1550  	ip, err := fs.resolve(ctx, ref)
  1551  	if err != nil {
  1552  		return nil, nil, errors.Wrap(err, "owncloudsql: error resolving reference")
  1553  	}
  1554  	p := fs.toStoragePath(ctx, ip)
  1555  
  1556  	// If Download is called for a path shared with the user then the path is
  1557  	// already wrapped. (fs.resolve wraps the path)
  1558  	if strings.HasPrefix(p, fs.c.DataDirectory) {
  1559  		ip = p
  1560  	}
  1561  
  1562  	// check permissions
  1563  	if perm, err := fs.readPermissions(ctx, ip); err == nil {
  1564  		if !perm.InitiateFileDownload {
  1565  			return nil, nil, errtypes.PermissionDenied("")
  1566  		}
  1567  	} else {
  1568  		if isNotFound(err) {
  1569  			return nil, nil, errtypes.NotFound(fs.toStoragePath(ctx, filepath.Dir(ip)))
  1570  		}
  1571  		return nil, nil, errors.Wrap(err, "owncloudsql: error reading permissions")
  1572  	}
  1573  
  1574  	ownerStorageID, err := fs.filecache.GetNumericStorageID(ctx, "home::"+fs.getOwner(ip))
  1575  	if err != nil {
  1576  		return nil, nil, err
  1577  	}
  1578  	entry, err := fs.filecache.Get(ctx, ownerStorageID, fs.toDatabasePath(ip))
  1579  	switch {
  1580  	case err == sql.ErrNoRows:
  1581  		return nil, nil, errtypes.NotFound(fs.toStoragePath(ctx, filepath.Dir(ip)))
  1582  	case err != nil:
  1583  		return nil, nil, err
  1584  	}
  1585  
  1586  	md, err := fs.convertToResourceInfo(ctx, entry, ip, nil)
  1587  	if err != nil {
  1588  		return nil, nil, err
  1589  	}
  1590  
  1591  	if !openReaderfunc(md) {
  1592  		return md, nil, nil
  1593  	}
  1594  
  1595  	r, err := os.Open(ip)
  1596  	if err != nil {
  1597  		if os.IsNotExist(err) {
  1598  			return nil, nil, errtypes.NotFound(fs.toStoragePath(ctx, ip))
  1599  		}
  1600  		return nil, nil, errors.Wrap(err, "owncloudsql: error reading "+ip)
  1601  	}
  1602  	return md, r, nil
  1603  }
  1604  
  1605  func (fs *owncloudsqlfs) ListRevisions(ctx context.Context, ref *provider.Reference) ([]*provider.FileVersion, error) {
  1606  	ip, err := fs.resolve(ctx, ref)
  1607  	if err != nil {
  1608  		return nil, errors.Wrap(err, "owncloudsql: error resolving reference")
  1609  	}
  1610  
  1611  	// check permissions
  1612  	if perm, err := fs.readPermissions(ctx, ip); err == nil {
  1613  		if !perm.ListFileVersions {
  1614  			return nil, errtypes.PermissionDenied("")
  1615  		}
  1616  	} else {
  1617  		if isNotFound(err) {
  1618  			return nil, errtypes.NotFound(fs.toStoragePath(ctx, filepath.Dir(ip)))
  1619  		}
  1620  		return nil, errors.Wrap(err, "owncloudsql: error reading permissions")
  1621  	}
  1622  
  1623  	vp := fs.getVersionsPath(ctx, ip)
  1624  	bn := filepath.Base(ip)
  1625  	storageID, err := fs.getStorage(ctx, ip)
  1626  	if err != nil {
  1627  		return nil, err
  1628  	}
  1629  	entries, err := fs.filecache.List(ctx, storageID, filepath.Dir(fs.toDatabasePath(vp))+"/")
  1630  	if err != nil {
  1631  		return nil, err
  1632  	}
  1633  	revisions := []*provider.FileVersion{}
  1634  	for _, entry := range entries {
  1635  		if strings.HasPrefix(entry.Name, bn) {
  1636  			// versions have filename.ext.v12345678
  1637  			version := entry.Name[len(bn)+2:] // truncate "<base filename>.v" to get version mtime
  1638  			mtime, err := strconv.Atoi(version)
  1639  			if err != nil {
  1640  				log := appctx.GetLogger(ctx)
  1641  				log.Error().Err(err).Str("path", entry.Name).Msg("invalid version mtime")
  1642  				return nil, err
  1643  			}
  1644  			revisions = append(revisions, &provider.FileVersion{
  1645  				Key:   version,
  1646  				Size:  uint64(entry.Size),
  1647  				Mtime: uint64(mtime),
  1648  				Etag:  entry.Etag,
  1649  			})
  1650  		}
  1651  	}
  1652  
  1653  	return revisions, nil
  1654  }
  1655  
  1656  func (fs *owncloudsqlfs) DownloadRevision(ctx context.Context, ref *provider.Reference, revisionKey string, openReaderfunc func(*provider.ResourceInfo) bool) (*provider.ResourceInfo, io.ReadCloser, error) {
  1657  	return nil, nil, errtypes.NotSupported("download revision")
  1658  }
  1659  
  1660  func (fs *owncloudsqlfs) RestoreRevision(ctx context.Context, ref *provider.Reference, revisionKey string) error {
  1661  	ip, err := fs.resolve(ctx, ref)
  1662  	if err != nil {
  1663  		return errors.Wrap(err, "owncloudsql: error resolving reference")
  1664  	}
  1665  
  1666  	// check permissions
  1667  	if perm, err := fs.readPermissions(ctx, ip); err == nil {
  1668  		if !perm.RestoreFileVersion {
  1669  			return errtypes.PermissionDenied("")
  1670  		}
  1671  	} else {
  1672  		if isNotFound(err) {
  1673  			return errtypes.NotFound(fs.toStoragePath(ctx, filepath.Dir(ip)))
  1674  		}
  1675  		return errors.Wrap(err, "owncloudsql: error reading permissions")
  1676  	}
  1677  
  1678  	vp := fs.getVersionsPath(ctx, ip)
  1679  	rp := vp + ".v" + revisionKey
  1680  
  1681  	// check revision exists
  1682  	rs, err := os.Stat(rp)
  1683  	if err != nil {
  1684  		return err
  1685  	}
  1686  
  1687  	if !rs.Mode().IsRegular() {
  1688  		return fmt.Errorf("%s is not a regular file", rp)
  1689  	}
  1690  
  1691  	source, err := os.Open(rp)
  1692  	if err != nil {
  1693  		return err
  1694  	}
  1695  	defer source.Close()
  1696  
  1697  	// destination should be available, otherwise we could not have navigated to its revisions
  1698  	if err := fs.archiveRevision(ctx, fs.getVersionsPath(ctx, ip), ip); err != nil {
  1699  		return err
  1700  	}
  1701  
  1702  	destination, err := os.Create(ip)
  1703  	if err != nil {
  1704  		// TODO(jfd) bring back revision in case sth goes wrong?
  1705  		return err
  1706  	}
  1707  	defer destination.Close()
  1708  
  1709  	_, err = io.Copy(destination, source)
  1710  	if err != nil {
  1711  		return err
  1712  	}
  1713  
  1714  	sha1h, md5h, adler32h, err := fs.HashFile(ip)
  1715  	if err != nil {
  1716  		log.Err(err).Msg("owncloudsql: could not open file for checksumming")
  1717  	}
  1718  	fi, err := os.Stat(ip)
  1719  	if err != nil {
  1720  		return err
  1721  	}
  1722  	mtime := time.Now().Unix()
  1723  	data := map[string]interface{}{
  1724  		"path":          fs.toDatabasePath(ip),
  1725  		"checksum":      fmt.Sprintf("SHA1:%032x MD5:%032x ADLER32:%032x", sha1h, md5h, adler32h),
  1726  		"etag":          calcEtag(ctx, fi),
  1727  		"size":          fi.Size(),
  1728  		"mimetype":      mime.Detect(false, ip),
  1729  		"mtime":         mtime,
  1730  		"storage_mtime": mtime,
  1731  	}
  1732  	storageID, err := fs.getStorage(ctx, ip)
  1733  	if err != nil {
  1734  		return err
  1735  	}
  1736  	_, err = fs.filecache.InsertOrUpdate(ctx, storageID, data, false)
  1737  	if err != nil {
  1738  		return err
  1739  	}
  1740  
  1741  	// TODO(jfd) bring back revision in case sth goes wrong?
  1742  	return fs.propagate(ctx, ip)
  1743  }
  1744  
  1745  func (fs *owncloudsqlfs) PurgeRecycleItem(ctx context.Context, ref *provider.Reference, key, relativePath string) error {
  1746  	rp, err := fs.getRecyclePath(ctx)
  1747  	if err != nil {
  1748  		return errors.Wrap(err, "owncloudsql: error resolving recycle path")
  1749  	}
  1750  	vp := filepath.Join(filepath.Dir(rp), "versions")
  1751  	ip := filepath.Join(rp, filepath.Clean(key))
  1752  	// TODO check permission?
  1753  
  1754  	// check permissions
  1755  	/* are they stored in the trash?
  1756  	if perm, err := fs.readPermissions(ctx, ip); err == nil {
  1757  		if !perm.ListContainer {
  1758  			return nil, errtypes.PermissionDenied("")
  1759  		}
  1760  	} else {
  1761  		if isNotFound(err) {
  1762  			return nil, errtypes.NotFound(fs.unwrap(ctx, filepath.Dir(ip)))
  1763  		}
  1764  		return nil, errors.Wrap(err, "owncloudsql: error reading permissions")
  1765  	}
  1766  	*/
  1767  
  1768  	err = os.RemoveAll(ip)
  1769  	if err != nil {
  1770  		return errors.Wrap(err, "owncloudsql: error deleting recycle item")
  1771  	}
  1772  	base, ttime, err := splitTrashKey(key)
  1773  	if err != nil {
  1774  		return err
  1775  	}
  1776  	err = fs.filecache.PurgeRecycleItem(ctx, ctxpkg.ContextMustGetUser(ctx).Username, base, ttime, false)
  1777  	if err != nil {
  1778  		return err
  1779  	}
  1780  
  1781  	versionsGlob := filepath.Join(vp, base+".v*.d"+strconv.Itoa(ttime))
  1782  	versionFiles, err := filepath.Glob(versionsGlob)
  1783  	if err != nil {
  1784  		return errors.Wrap(err, "owncloudsql: error listing recycle item versions")
  1785  	}
  1786  	storageID, err := fs.getStorage(ctx, ip)
  1787  	if err != nil {
  1788  		return err
  1789  	}
  1790  	for _, versionFile := range versionFiles {
  1791  		err = os.Remove(versionFile)
  1792  		if err != nil {
  1793  			return errors.Wrap(err, "owncloudsql: error deleting recycle item versions")
  1794  		}
  1795  		err = fs.filecache.Purge(ctx, storageID, fs.toDatabasePath(versionFile))
  1796  		if err != nil {
  1797  			return err
  1798  		}
  1799  	}
  1800  
  1801  	// TODO delete keyfiles, keys, share-keys
  1802  	return nil
  1803  }
  1804  
  1805  func (fs *owncloudsqlfs) EmptyRecycle(ctx context.Context, ref *provider.Reference) error {
  1806  	// TODO check permission? on what? user must be the owner
  1807  	rp, err := fs.getRecyclePath(ctx)
  1808  	if err != nil {
  1809  		return errors.Wrap(err, "owncloudsql: error resolving recycle path")
  1810  	}
  1811  	err = os.RemoveAll(rp)
  1812  	if err != nil {
  1813  		return errors.Wrap(err, "owncloudsql: error deleting recycle files")
  1814  	}
  1815  	err = os.RemoveAll(filepath.Join(filepath.Dir(rp), "versions"))
  1816  	if err != nil {
  1817  		return errors.Wrap(err, "owncloudsql: error deleting recycle files versions")
  1818  	}
  1819  
  1820  	u := ctxpkg.ContextMustGetUser(ctx)
  1821  	err = fs.filecache.EmptyRecycle(ctx, u.Username)
  1822  	if err != nil {
  1823  		return errors.Wrap(err, "owncloudsql: error deleting recycle items from the database")
  1824  	}
  1825  
  1826  	// TODO delete keyfiles, keys, share-keys ... or just everything?
  1827  	return nil
  1828  }
  1829  
  1830  func splitTrashKey(key string) (string, int, error) {
  1831  	// trashbin items have filename.ext.d12345678
  1832  	suffix := filepath.Ext(key)
  1833  	if len(suffix) == 0 || !strings.HasPrefix(suffix, ".d") {
  1834  		return "", -1, fmt.Errorf("invalid suffix")
  1835  	}
  1836  	trashtime := suffix[2:] // truncate "d" to get trashbin time
  1837  	ttime, err := strconv.Atoi(trashtime)
  1838  	if err != nil {
  1839  		return "", -1, fmt.Errorf("invalid suffix")
  1840  	}
  1841  	return strings.TrimSuffix(filepath.Base(key), suffix), ttime, nil
  1842  }
  1843  
  1844  func (fs *owncloudsqlfs) convertToRecycleItem(ctx context.Context, md os.FileInfo) *provider.RecycleItem {
  1845  	base, ttime, err := splitTrashKey(md.Name())
  1846  	if err != nil {
  1847  		log := appctx.GetLogger(ctx)
  1848  		log.Error().Str("path", md.Name()).Msg("invalid trash item key")
  1849  	}
  1850  
  1851  	u := ctxpkg.ContextMustGetUser(ctx)
  1852  	item, err := fs.filecache.GetRecycleItem(ctx, u.Username, base, ttime)
  1853  	if err != nil {
  1854  		log := appctx.GetLogger(ctx)
  1855  		log.Error().Err(err).Str("path", md.Name()).Msg("could not get trash item")
  1856  		return nil
  1857  	}
  1858  
  1859  	// ownCloud 10 stores the parent dir of the deleted item as the location in the oc_files_trashbin table
  1860  	// we use extended attributes for original location, but also only the parent location, which is why
  1861  	// we need to join and trim the path when listing it
  1862  	originalPath := filepath.Join(item.Path, base)
  1863  
  1864  	return &provider.RecycleItem{
  1865  		Type: getResourceType(md.IsDir()),
  1866  		Key:  md.Name(),
  1867  		// TODO do we need to prefix the path? it should be relative to this storage root, right?
  1868  		Ref:  &provider.Reference{Path: originalPath},
  1869  		Size: uint64(md.Size()),
  1870  		DeletionTime: &types.Timestamp{
  1871  			Seconds: uint64(ttime),
  1872  			// no nanos available
  1873  		},
  1874  	}
  1875  }
  1876  
  1877  func (fs *owncloudsqlfs) ListRecycle(ctx context.Context, ref *provider.Reference, key, relativePath string) ([]*provider.RecycleItem, error) {
  1878  	// TODO check permission? on what? user must be the owner?
  1879  	rp, err := fs.getRecyclePath(ctx)
  1880  	if err != nil {
  1881  		return nil, errors.Wrap(err, "owncloudsql: error resolving recycle path")
  1882  	}
  1883  
  1884  	// list files folder
  1885  	mds, err := os.ReadDir(rp)
  1886  	if err != nil {
  1887  		log := appctx.GetLogger(ctx)
  1888  		log.Debug().Err(err).Str("path", rp).Msg("trash not readable")
  1889  		// TODO jfd only ignore not found errors
  1890  		return []*provider.RecycleItem{}, nil
  1891  	}
  1892  	// TODO (jfd) limit and offset
  1893  	items := []*provider.RecycleItem{}
  1894  	for i := range mds {
  1895  		mdsInfo, _ := mds[i].Info()
  1896  		ri := fs.convertToRecycleItem(ctx, mdsInfo)
  1897  		if ri != nil {
  1898  			items = append(items, ri)
  1899  		}
  1900  
  1901  	}
  1902  	return items, nil
  1903  }
  1904  
  1905  func (fs *owncloudsqlfs) RestoreRecycleItem(ctx context.Context, ref *provider.Reference, key, relativePath string, restoreRef *provider.Reference) error {
  1906  	log := appctx.GetLogger(ctx)
  1907  
  1908  	base, ttime, err := splitTrashKey(key)
  1909  	if err != nil {
  1910  		log.Error().Str("path", key).Msg("invalid trash item key")
  1911  		return fmt.Errorf("invalid trash item suffix")
  1912  	}
  1913  
  1914  	recyclePath, err := fs.getRecyclePath(ctx)
  1915  	if err != nil {
  1916  		return errors.Wrap(err, "owncloudsql: error resolving recycle path")
  1917  	}
  1918  	src := filepath.Join(recyclePath, filepath.Clean(key))
  1919  
  1920  	if restoreRef.Path == "" {
  1921  		u := ctxpkg.ContextMustGetUser(ctx)
  1922  		item, err := fs.filecache.GetRecycleItem(ctx, u.Username, base, ttime)
  1923  		if err != nil {
  1924  			log := appctx.GetLogger(ctx)
  1925  			log.Error().Err(err).Str("path", key).Msg("could not get trash item")
  1926  			return nil
  1927  		}
  1928  		restoreRef.Path = filepath.Join(item.Path, item.Name)
  1929  	}
  1930  
  1931  	tgt := fs.toInternalPath(ctx, restoreRef.Path)
  1932  	// move back to original location
  1933  	if err := os.Rename(src, tgt); err != nil {
  1934  		log.Error().Err(err).Str("key", key).Str("restorePath", restoreRef.Path).Str("src", src).Str("tgt", tgt).Msg("could not restore item")
  1935  		return errors.Wrap(err, "owncloudsql: could not restore item")
  1936  	}
  1937  
  1938  	storage, err := fs.getStorage(ctx, src)
  1939  	if err != nil {
  1940  		return err
  1941  	}
  1942  	err = fs.filecache.Move(ctx, storage, fs.toDatabasePath(src), fs.toDatabasePath(tgt))
  1943  	if err != nil {
  1944  		return err
  1945  	}
  1946  	err = fs.filecache.DeleteRecycleItem(ctx, ctxpkg.ContextMustGetUser(ctx).Username, base, ttime)
  1947  	if err != nil {
  1948  		return err
  1949  	}
  1950  	err = fs.RestoreRecycleItemVersions(ctx, key, tgt)
  1951  	if err != nil {
  1952  		return err
  1953  	}
  1954  
  1955  	return fs.propagate(ctx, tgt)
  1956  }
  1957  
  1958  func (fs *owncloudsqlfs) RestoreRecycleItemVersions(ctx context.Context, key, target string) error {
  1959  	base, ttime, err := splitTrashKey(key)
  1960  	if err != nil {
  1961  		return fmt.Errorf("invalid trash item suffix")
  1962  	}
  1963  	storage, err := fs.getStorage(ctx, target)
  1964  	if err != nil {
  1965  		return err
  1966  	}
  1967  
  1968  	recyclePath, err := fs.getRecyclePath(ctx)
  1969  	if err != nil {
  1970  		return errors.Wrap(err, "owncloudsql: error resolving recycle path")
  1971  	}
  1972  	versionsRecyclePath := filepath.Join(filepath.Dir(recyclePath), "versions")
  1973  
  1974  	// Restore versions
  1975  	deleteSuffix := ".d" + strconv.Itoa(ttime)
  1976  	versionsGlob := filepath.Join(versionsRecyclePath, base+".v*"+deleteSuffix)
  1977  	versionFiles, err := filepath.Glob(versionsGlob)
  1978  	versionsRoot := filepath.Dir(fs.getVersionsPath(ctx, target))
  1979  
  1980  	if err != nil {
  1981  		return errors.Wrap(err, "owncloudsql: error listing recycle item versions")
  1982  	}
  1983  	for _, versionFile := range versionFiles {
  1984  		versionBase := strings.TrimSuffix(filepath.Base(versionFile), deleteSuffix)
  1985  		versionsRestorePath := filepath.Join(versionsRoot, versionBase)
  1986  		if err = os.Rename(versionFile, versionsRestorePath); err != nil {
  1987  			return errors.Wrap(err, "owncloudsql: could not restore version file")
  1988  		}
  1989  		err = fs.filecache.Move(ctx, storage, fs.toDatabasePath(versionFile), fs.toDatabasePath(versionsRestorePath))
  1990  		if err != nil {
  1991  			return err
  1992  		}
  1993  	}
  1994  	return nil
  1995  }
  1996  
  1997  func (fs *owncloudsqlfs) propagate(ctx context.Context, leafPath string) error {
  1998  	var root string
  1999  	if fs.c.EnableHome {
  2000  		root = filepath.Clean(fs.toInternalPath(ctx, "/"))
  2001  	} else {
  2002  		owner := fs.getOwner(leafPath)
  2003  		root = filepath.Clean(fs.toInternalPath(ctx, owner))
  2004  	}
  2005  	versionsRoot := filepath.Join(filepath.Dir(root), "files_versions")
  2006  	if !strings.HasPrefix(leafPath, root) {
  2007  		err := errors.New("internal path outside root")
  2008  		appctx.GetLogger(ctx).Error().
  2009  			Err(err).
  2010  			Str("leafPath", leafPath).
  2011  			Str("root", root).
  2012  			Msg("could not propagate change")
  2013  		return err
  2014  	}
  2015  
  2016  	fi, err := os.Stat(leafPath)
  2017  	if err != nil {
  2018  		appctx.GetLogger(ctx).Error().
  2019  			Err(err).
  2020  			Str("leafPath", leafPath).
  2021  			Str("root", root).
  2022  			Msg("could not propagate change")
  2023  		return err
  2024  	}
  2025  
  2026  	storageID, err := fs.getStorage(ctx, leafPath)
  2027  	if err != nil {
  2028  		return err
  2029  	}
  2030  
  2031  	currentPath := filepath.Clean(leafPath)
  2032  	for currentPath != root && currentPath != versionsRoot {
  2033  		appctx.GetLogger(ctx).Debug().
  2034  			Str("leafPath", leafPath).
  2035  			Str("currentPath", currentPath).
  2036  			Msg("propagating change")
  2037  		parentFi, err := os.Stat(currentPath)
  2038  		if err != nil {
  2039  			return err
  2040  		}
  2041  		if fi.ModTime().UnixNano() > parentFi.ModTime().UnixNano() {
  2042  			if err := os.Chtimes(currentPath, fi.ModTime(), fi.ModTime()); err != nil {
  2043  				appctx.GetLogger(ctx).Error().
  2044  					Err(err).
  2045  					Str("leafPath", leafPath).
  2046  					Str("currentPath", currentPath).
  2047  					Msg("could not propagate change")
  2048  				return err
  2049  			}
  2050  		}
  2051  		fi, err = os.Stat(currentPath)
  2052  		if err != nil {
  2053  			return err
  2054  		}
  2055  		etag := calcEtag(ctx, fi)
  2056  		if err := fs.filecache.SetEtag(ctx, storageID, fs.toDatabasePath(currentPath), etag); err != nil {
  2057  			appctx.GetLogger(ctx).Error().
  2058  				Err(err).
  2059  				Str("leafPath", leafPath).
  2060  				Str("currentPath", currentPath).
  2061  				Msg("could not set etag")
  2062  			return err
  2063  		}
  2064  
  2065  		currentPath = filepath.Dir(currentPath)
  2066  	}
  2067  	return nil
  2068  }
  2069  
  2070  func (fs *owncloudsqlfs) HashFile(path string) (string, string, string, error) {
  2071  	sha1h := sha1.New()
  2072  	md5h := md5.New()
  2073  	adler32h := adler32.New()
  2074  	{
  2075  		f, err := os.Open(path)
  2076  		if err != nil {
  2077  			return "", "", "", errors.Wrap(err, "owncloudsql: could not copy bytes for checksumming")
  2078  		}
  2079  		defer f.Close()
  2080  
  2081  		r1 := io.TeeReader(f, sha1h)
  2082  		r2 := io.TeeReader(r1, md5h)
  2083  
  2084  		if _, err := io.Copy(adler32h, r2); err != nil {
  2085  			return "", "", "", errors.Wrap(err, "owncloudsql: could not copy bytes for checksumming")
  2086  		}
  2087  
  2088  		return string(sha1h.Sum(nil)), string(md5h.Sum(nil)), string(adler32h.Sum(nil)), nil
  2089  	}
  2090  }
  2091  
  2092  func readChecksumIntoResourceChecksum(ctx context.Context, checksums, algo string, ri *provider.ResourceInfo) {
  2093  	re := regexp.MustCompile(strings.ToUpper(algo) + `:(.*)`)
  2094  	matches := re.FindStringSubmatch(checksums)
  2095  	if len(matches) < 2 {
  2096  		appctx.GetLogger(ctx).
  2097  			Debug().
  2098  			Str("nodepath", checksums).
  2099  			Str("algorithm", algo).
  2100  			Msg("checksum not set")
  2101  	} else {
  2102  		ri.Checksum = &provider.ResourceChecksum{
  2103  			Type: storageprovider.PKG2GRPCXS(algo),
  2104  			Sum:  matches[1],
  2105  		}
  2106  	}
  2107  }
  2108  
  2109  func readChecksumIntoOpaque(ctx context.Context, checksums, algo string, ri *provider.ResourceInfo) {
  2110  	re := regexp.MustCompile(strings.ToUpper(algo) + `:(.*)`)
  2111  	matches := re.FindStringSubmatch(checksums)
  2112  	if len(matches) < 2 {
  2113  		appctx.GetLogger(ctx).
  2114  			Debug().
  2115  			Str("nodepath", checksums).
  2116  			Str("algorithm", algo).
  2117  			Msg("checksum not set")
  2118  	} else {
  2119  		if ri.Opaque == nil {
  2120  			ri.Opaque = &types.Opaque{
  2121  				Map: map[string]*types.OpaqueEntry{},
  2122  			}
  2123  		}
  2124  		ri.Opaque.Map[algo] = &types.OpaqueEntry{
  2125  			Decoder: "plain",
  2126  			Value:   []byte(matches[1]),
  2127  		}
  2128  	}
  2129  }
  2130  
  2131  func getResourceType(isDir bool) provider.ResourceType {
  2132  	if isDir {
  2133  		return provider.ResourceType_RESOURCE_TYPE_CONTAINER
  2134  	}
  2135  	return provider.ResourceType_RESOURCE_TYPE_FILE
  2136  }
  2137  
  2138  // TODO propagate etag and mtime or append event to history? propagate on disk ...
  2139  // - but propagation is a separate task. only if upload was successful ...