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 ...