github.com/cs3org/reva/v2@v2.27.7/pkg/storage/utils/eosfs/eosfs.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 eosfs 20 21 import ( 22 "context" 23 "database/sql" 24 b64 "encoding/base64" 25 "encoding/json" 26 "fmt" 27 "io" 28 "net/url" 29 "os" 30 "path" 31 "path/filepath" 32 "regexp" 33 "strconv" 34 "strings" 35 "time" 36 37 "github.com/bluele/gcache" 38 grouppb "github.com/cs3org/go-cs3apis/cs3/identity/group/v1beta1" 39 userpb "github.com/cs3org/go-cs3apis/cs3/identity/user/v1beta1" 40 rpc "github.com/cs3org/go-cs3apis/cs3/rpc/v1beta1" 41 provider "github.com/cs3org/go-cs3apis/cs3/storage/provider/v1beta1" 42 types "github.com/cs3org/go-cs3apis/cs3/types/v1beta1" 43 "github.com/cs3org/reva/v2/pkg/appctx" 44 "github.com/cs3org/reva/v2/pkg/conversions" 45 ctxpkg "github.com/cs3org/reva/v2/pkg/ctx" 46 "github.com/cs3org/reva/v2/pkg/eosclient" 47 "github.com/cs3org/reva/v2/pkg/eosclient/eosbinary" 48 "github.com/cs3org/reva/v2/pkg/eosclient/eosgrpc" 49 "github.com/cs3org/reva/v2/pkg/errtypes" 50 "github.com/cs3org/reva/v2/pkg/mime" 51 "github.com/cs3org/reva/v2/pkg/rgrpc/status" 52 "github.com/cs3org/reva/v2/pkg/rgrpc/todo/pool" 53 "github.com/cs3org/reva/v2/pkg/sharedconf" 54 "github.com/cs3org/reva/v2/pkg/storage" 55 "github.com/cs3org/reva/v2/pkg/storage/utils/acl" 56 "github.com/cs3org/reva/v2/pkg/storage/utils/chunking" 57 "github.com/cs3org/reva/v2/pkg/storage/utils/grants" 58 "github.com/cs3org/reva/v2/pkg/storage/utils/templates" 59 "github.com/cs3org/reva/v2/pkg/utils" 60 "github.com/jellydator/ttlcache/v2" 61 "github.com/pkg/errors" 62 ) 63 64 const ( 65 refTargetAttrKey = "reva.target" 66 ) 67 68 const ( 69 // SystemAttr is the system extended attribute. 70 SystemAttr eosclient.AttrType = iota 71 // UserAttr is the user extended attribute. 72 UserAttr 73 ) 74 75 // LockPayloadKey is the key in the xattr for lock payload 76 const LockPayloadKey = "reva.lock.payload" 77 78 // LockExpirationKey is the key in the xattr for lock expiration 79 const LockExpirationKey = "reva.lock.expiration" 80 81 // LockTypeKey is the key in the xattr for lock payload 82 const LockTypeKey = "reva.lock.type" 83 84 var hiddenReg = regexp.MustCompile(`\.sys\..#.`) 85 86 func (c *Config) init() { 87 c.Namespace = path.Clean(c.Namespace) 88 if !strings.HasPrefix(c.Namespace, "/") { 89 c.Namespace = "/" 90 } 91 92 if c.ShadowNamespace == "" { 93 c.ShadowNamespace = path.Join(c.Namespace, ".shadow") 94 } 95 96 // Quota node defaults to namespace if empty 97 if c.QuotaNode == "" { 98 c.QuotaNode = c.Namespace 99 } 100 101 if c.DefaultQuotaBytes == 0 { 102 c.DefaultQuotaBytes = 1000000000000 // 1 TB 103 } 104 if c.DefaultQuotaFiles == 0 { 105 c.DefaultQuotaFiles = 1000000 // 1 Million 106 } 107 108 if c.ShareFolder == "" { 109 c.ShareFolder = "/MyShares" 110 } 111 // ensure share folder always starts with slash 112 c.ShareFolder = path.Join("/", c.ShareFolder) 113 114 if c.EosBinary == "" { 115 c.EosBinary = "/usr/bin/eos" 116 } 117 118 if c.XrdcopyBinary == "" { 119 c.XrdcopyBinary = "/opt/eos/xrootd/bin/xrdcopy" 120 } 121 122 if c.MasterURL == "" { 123 c.MasterURL = "root://eos-example.org" 124 } 125 126 if c.SlaveURL == "" { 127 c.SlaveURL = c.MasterURL 128 } 129 130 if c.CacheDirectory == "" { 131 c.CacheDirectory = os.TempDir() 132 } 133 134 if c.UserLayout == "" { 135 c.UserLayout = "{{.Username}}" // TODO set better layout 136 } 137 138 if c.UserIDCacheSize == 0 { 139 c.UserIDCacheSize = 1000000 140 } 141 142 if c.UserIDCacheWarmupDepth == 0 { 143 c.UserIDCacheWarmupDepth = 2 144 } 145 146 if c.TokenExpiry == 0 { 147 c.TokenExpiry = 3600 148 } 149 150 c.GatewaySvc = sharedconf.GetGatewaySVC(c.GatewaySvc) 151 } 152 153 type eosfs struct { 154 c eosclient.EOSClient 155 conf *Config 156 chunkHandler *chunking.ChunkHandler 157 spacesDB *sql.DB 158 singleUserAuth eosclient.Authorization 159 userIDCache *ttlcache.Cache 160 tokenCache gcache.Cache 161 spacesCache gcache.Cache 162 } 163 164 // NewEOSFS returns a storage.FS interface implementation that connects to an EOS instance 165 func NewEOSFS(c *Config) (storage.FS, error) { 166 c.init() 167 168 // bail out if keytab is not found. 169 if c.UseKeytab { 170 if _, err := os.Stat(c.Keytab); err != nil { 171 err = errors.Wrapf(err, "eosfs: keytab not accessible at location: %s", err) 172 return nil, err 173 } 174 } 175 176 var eosClient eosclient.EOSClient 177 var err error 178 if c.UseGRPC { 179 eosClientOpts := &eosgrpc.Options{ 180 XrdcopyBinary: c.XrdcopyBinary, 181 URL: c.MasterURL, 182 GrpcURI: c.GrpcURI, 183 CacheDirectory: c.CacheDirectory, 184 UseKeytab: c.UseKeytab, 185 Keytab: c.Keytab, 186 Authkey: c.GRPCAuthkey, 187 SecProtocol: c.SecProtocol, 188 VersionInvariant: c.VersionInvariant, 189 ReadUsesLocalTemp: c.ReadUsesLocalTemp, 190 WriteUsesLocalTemp: c.WriteUsesLocalTemp, 191 } 192 eosHTTPOpts := &eosgrpc.HTTPOptions{ 193 BaseURL: c.MasterURL, 194 MaxIdleConns: c.MaxIdleConns, 195 MaxConnsPerHost: c.MaxConnsPerHost, 196 MaxIdleConnsPerHost: c.MaxIdleConnsPerHost, 197 IdleConnTimeout: c.IdleConnTimeout, 198 ClientCertFile: c.ClientCertFile, 199 ClientKeyFile: c.ClientKeyFile, 200 ClientCADirs: c.ClientCADirs, 201 ClientCAFiles: c.ClientCAFiles, 202 } 203 eosClient, err = eosgrpc.New(eosClientOpts, eosHTTPOpts) 204 } else { 205 eosClientOpts := &eosbinary.Options{ 206 XrdcopyBinary: c.XrdcopyBinary, 207 URL: c.MasterURL, 208 EosBinary: c.EosBinary, 209 CacheDirectory: c.CacheDirectory, 210 ForceSingleUserMode: c.ForceSingleUserMode, 211 SingleUsername: c.SingleUsername, 212 UseKeytab: c.UseKeytab, 213 Keytab: c.Keytab, 214 SecProtocol: c.SecProtocol, 215 VersionInvariant: c.VersionInvariant, 216 TokenExpiry: c.TokenExpiry, 217 } 218 eosClient, err = eosbinary.New(eosClientOpts) 219 } 220 221 if err != nil { 222 return nil, errors.Wrap(err, "error initializing eosclient") 223 } 224 225 var db *sql.DB 226 if c.SpacesConfig.Enabled { 227 db, err = sql.Open("mysql", fmt.Sprintf("%s:%s@tcp(%s:%d)/%s", c.SpacesConfig.DbUsername, c.SpacesConfig.DbPassword, c.SpacesConfig.DbHost, c.SpacesConfig.DbPort, c.SpacesConfig.DbName)) 228 if err != nil { 229 return nil, err 230 } 231 } 232 233 eosfs := &eosfs{ 234 c: eosClient, 235 conf: c, 236 spacesDB: db, 237 chunkHandler: chunking.NewChunkHandler(c.CacheDirectory), 238 userIDCache: ttlcache.NewCache(), 239 tokenCache: gcache.New(c.UserIDCacheSize).LFU().Build(), 240 spacesCache: gcache.New(c.UserIDCacheSize).LFU().Build(), 241 } 242 243 eosfs.userIDCache.SetCacheSizeLimit(c.UserIDCacheSize) 244 eosfs.userIDCache.SetExpirationReasonCallback(func(key string, reason ttlcache.EvictionReason, value interface{}) { 245 // We only set those keys with TTL which we weren't able to retrieve the last time 246 // For those keys, try to contact the userprovider service again when they expire 247 if reason == ttlcache.Expired { 248 _, _ = eosfs.getUserIDGateway(context.Background(), key) 249 } 250 }) 251 252 go eosfs.userIDcacheWarmup() 253 254 return eosfs, nil 255 } 256 257 func (fs *eosfs) userIDcacheWarmup() { 258 if !fs.conf.EnableHome { 259 time.Sleep(2 * time.Second) 260 ctx := context.Background() 261 paths := []string{fs.wrap(ctx, "/")} 262 auth, _ := fs.getRootAuth(ctx) 263 264 for i := 0; i < fs.conf.UserIDCacheWarmupDepth; i++ { 265 var newPaths []string 266 for _, fn := range paths { 267 if eosFileInfos, err := fs.c.List(ctx, auth, fn); err == nil { 268 for _, f := range eosFileInfos { 269 _, _ = fs.getUserIDGateway(ctx, strconv.FormatUint(f.UID, 10)) 270 newPaths = append(newPaths, f.File) 271 } 272 } 273 } 274 paths = newPaths 275 } 276 } 277 } 278 279 func (fs *eosfs) Shutdown(ctx context.Context) error { 280 // TODO(labkode): in a grpc implementation we can close connections. 281 return nil 282 } 283 284 func getUser(ctx context.Context) (*userpb.User, error) { 285 u, ok := ctxpkg.ContextGetUser(ctx) 286 if !ok { 287 err := errors.Wrap(errtypes.UserRequired(""), "eosfs: error getting user from ctx") 288 return nil, err 289 } 290 return u, nil 291 } 292 293 func (fs *eosfs) getLayout(ctx context.Context) (layout string) { 294 if fs.conf.EnableHome { 295 u, err := getUser(ctx) 296 if err != nil { 297 panic(err) 298 } 299 layout = templates.WithUser(u, fs.conf.UserLayout) 300 } 301 return 302 } 303 304 func (fs *eosfs) getInternalHome(ctx context.Context) (string, error) { 305 if !fs.conf.EnableHome { 306 return "", errtypes.NotSupported("eos: get home not supported") 307 } 308 309 u, err := getUser(ctx) 310 if err != nil { 311 err = errors.Wrap(err, "eosfs: wrap: no user in ctx and home is enabled") 312 return "", err 313 } 314 315 relativeHome := templates.WithUser(u, fs.conf.UserLayout) 316 return relativeHome, nil 317 } 318 319 func (fs *eosfs) wrapShadow(ctx context.Context, fn string) (internal string) { 320 if fs.conf.EnableHome { 321 layout, err := fs.getInternalHome(ctx) 322 if err != nil { 323 panic(err) 324 } 325 internal = path.Join(fs.conf.ShadowNamespace, layout, fn) 326 } else { 327 internal = path.Join(fs.conf.ShadowNamespace, fn) 328 } 329 return 330 } 331 332 func (fs *eosfs) wrap(ctx context.Context, fn string) (internal string) { 333 fn = strings.TrimPrefix(fn, fs.conf.MountPath) 334 if fs.conf.EnableHome { 335 layout, err := fs.getInternalHome(ctx) 336 if err != nil { 337 panic(err) 338 } 339 internal = path.Join(fs.conf.Namespace, layout, fn) 340 } else { 341 internal = path.Join(fs.conf.Namespace, fn) 342 } 343 log := appctx.GetLogger(ctx) 344 log.Debug().Msg("eosfs: wrap external=" + fn + " internal=" + internal) 345 return 346 } 347 348 func (fs *eosfs) unwrap(ctx context.Context, internal string) (string, error) { 349 log := appctx.GetLogger(ctx) 350 layout := fs.getLayout(ctx) 351 ns, err := fs.getNsMatch(internal, []string{fs.conf.Namespace, fs.conf.ShadowNamespace}) 352 if err != nil { 353 return "", err 354 } 355 external, err := fs.unwrapInternal(ctx, ns, internal, layout) 356 if err != nil { 357 return "", err 358 } 359 log.Debug().Msgf("eosfs: unwrap: internal=%s external=%s", internal, external) 360 return external, nil 361 } 362 363 func (fs *eosfs) getNsMatch(internal string, nss []string) (string, error) { 364 var match string 365 366 for _, ns := range nss { 367 if strings.HasPrefix(internal, ns) && len(ns) > len(match) { 368 match = ns 369 } 370 } 371 372 if match == "" { 373 return "", errtypes.NotFound(fmt.Sprintf("eosfs: path is outside namespaces: path=%s namespaces=%+v", internal, nss)) 374 } 375 376 return match, nil 377 } 378 379 func (fs *eosfs) unwrapInternal(ctx context.Context, ns, np, layout string) (string, error) { 380 trim := path.Join(ns, layout) 381 382 if !strings.HasPrefix(np, trim) { 383 return "", errtypes.NotFound(fmt.Sprintf("eosfs: path is outside the directory of the logged-in user: internal=%s trim=%s namespace=%+v", np, trim, ns)) 384 } 385 386 external := strings.TrimPrefix(np, trim) 387 388 if external == "" { 389 external = "/" 390 } 391 392 return external, nil 393 } 394 395 func (fs *eosfs) resolveRefForbidShareFolder(ctx context.Context, ref *provider.Reference) (string, eosclient.Authorization, error) { 396 p, err := fs.resolve(ctx, ref) 397 if err != nil { 398 return "", eosclient.Authorization{}, errors.Wrap(err, "eosfs: error resolving reference") 399 } 400 if fs.isShareFolder(ctx, p) { 401 return "", eosclient.Authorization{}, errtypes.PermissionDenied("eosfs: cannot perform operation under the virtual share folder") 402 } 403 fn := fs.wrap(ctx, p) 404 405 u, err := getUser(ctx) 406 if err != nil { 407 return "", eosclient.Authorization{}, errors.Wrap(err, "eosfs: no user in ctx") 408 } 409 auth, err := fs.getUserAuth(ctx, u, fn) 410 if err != nil { 411 return "", eosclient.Authorization{}, err 412 } 413 414 return fn, auth, nil 415 } 416 417 func (fs *eosfs) resolveRefAndGetAuth(ctx context.Context, ref *provider.Reference) (string, eosclient.Authorization, error) { 418 p, err := fs.resolve(ctx, ref) 419 if err != nil { 420 return "", eosclient.Authorization{}, errors.Wrap(err, "eosfs: error resolving reference") 421 } 422 fn := fs.wrap(ctx, p) 423 424 u, err := getUser(ctx) 425 if err != nil { 426 return "", eosclient.Authorization{}, errors.Wrap(err, "eosfs: no user in ctx") 427 } 428 auth, err := fs.getUserAuth(ctx, u, fn) 429 if err != nil { 430 return "", eosclient.Authorization{}, err 431 } 432 433 return fn, auth, nil 434 } 435 436 // resolve takes in a request path or request id and returns the unwrapped path. 437 func (fs *eosfs) resolve(ctx context.Context, ref *provider.Reference) (string, error) { 438 if ref.ResourceId != nil { 439 p, err := fs.getPath(ctx, ref.ResourceId) 440 if err != nil { 441 return "", err 442 } 443 p = path.Join(p, ref.Path) 444 return p, nil 445 } 446 if ref.Path != "" { 447 return ref.Path, nil 448 } 449 450 // reference is invalid 451 return "", fmt.Errorf("invalid reference %+v. at least resource_id or path must be set", ref) 452 } 453 454 func (fs *eosfs) getPath(ctx context.Context, id *provider.ResourceId) (string, error) { 455 fid, err := strconv.ParseUint(id.OpaqueId, 10, 64) 456 if err != nil { 457 return "", fmt.Errorf("error converting string to int for eos fileid: %s", id.OpaqueId) 458 } 459 460 auth, err := fs.getRootAuth(ctx) 461 if err != nil { 462 return "", err 463 } 464 465 eosFileInfo, err := fs.c.GetFileInfoByInode(ctx, auth, fid) 466 if err != nil { 467 return "", errors.Wrap(err, "eosfs: error getting file info by inode") 468 } 469 470 return fs.unwrap(ctx, eosFileInfo.File) 471 } 472 473 func (fs *eosfs) isShareFolder(ctx context.Context, p string) bool { 474 return strings.HasPrefix(p, fs.conf.ShareFolder) 475 } 476 477 func (fs *eosfs) isShareFolderRoot(ctx context.Context, p string) bool { 478 return path.Clean(p) == fs.conf.ShareFolder 479 } 480 481 func (fs *eosfs) isShareFolderChild(ctx context.Context, p string) bool { 482 p = path.Clean(p) 483 vals := strings.Split(p, fs.conf.ShareFolder+"/") 484 return len(vals) > 1 && vals[1] != "" 485 } 486 487 func (fs *eosfs) GetPathByID(ctx context.Context, id *provider.ResourceId) (string, error) { 488 fid, err := strconv.ParseUint(id.OpaqueId, 10, 64) 489 if err != nil { 490 return "", errors.Wrap(err, "eosfs: error parsing fileid string") 491 } 492 493 u, err := getUser(ctx) 494 if err != nil { 495 return "", errors.Wrap(err, "eosfs: no user in ctx") 496 } 497 if u.Id.Type == userpb.UserType_USER_TYPE_LIGHTWEIGHT || u.Id.Type == userpb.UserType_USER_TYPE_FEDERATED { 498 auth, err := fs.getRootAuth(ctx) 499 if err != nil { 500 return "", err 501 } 502 eosFileInfo, err := fs.c.GetFileInfoByInode(ctx, auth, fid) 503 if err != nil { 504 return "", errors.Wrap(err, "eosfs: error getting file info by inode") 505 } 506 if perm := fs.permissionSet(ctx, eosFileInfo, nil); perm.GetPath { 507 return fs.unwrap(ctx, eosFileInfo.File) 508 } 509 return "", errtypes.PermissionDenied("eosfs: getting path for id not allowed") 510 } 511 512 auth, err := fs.getUserAuth(ctx, u, "") 513 if err != nil { 514 return "", err 515 } 516 517 eosFileInfo, err := fs.c.GetFileInfoByInode(ctx, auth, fid) 518 if err != nil { 519 return "", errors.Wrap(err, "eosfs: error getting file info by inode") 520 } 521 522 p, err := fs.unwrap(ctx, eosFileInfo.File) 523 if err != nil { 524 return "", err 525 } 526 return path.Join(fs.conf.MountPath, p), nil 527 } 528 529 func (fs *eosfs) SetArbitraryMetadata(ctx context.Context, ref *provider.Reference, md *provider.ArbitraryMetadata) error { 530 if len(md.Metadata) == 0 { 531 return errtypes.BadRequest("eosfs: no metadata set") 532 } 533 534 fn, auth, err := fs.resolveRefAndGetAuth(ctx, ref) 535 if err != nil { 536 return err 537 } 538 539 for k, v := range md.Metadata { 540 if k == "" || v == "" { 541 return errtypes.BadRequest(fmt.Sprintf("eosfs: key or value is empty: key:%s, value:%s", k, v)) 542 } 543 544 // do not allow to set a lock key attr 545 if k == LockPayloadKey || k == LockExpirationKey || k == LockTypeKey { 546 return errtypes.BadRequest(fmt.Sprintf("eosfs: key %s not allowed", k)) 547 } 548 549 attr := &eosclient.Attribute{ 550 Type: UserAttr, 551 Key: k, 552 Val: v, 553 } 554 555 // TODO(labkode): SetArbitraryMetadata does not have semantics for recursivity. 556 // We set it to false 557 err := fs.c.SetAttr(ctx, auth, attr, false, false, fn) 558 if err != nil { 559 return errors.Wrap(err, "eosfs: error setting xattr in eos driver") 560 } 561 562 } 563 return nil 564 } 565 566 func (fs *eosfs) UnsetArbitraryMetadata(ctx context.Context, ref *provider.Reference, keys []string) error { 567 if len(keys) == 0 { 568 return errtypes.BadRequest("eosfs: no keys set") 569 } 570 571 fn, auth, err := fs.resolveRefAndGetAuth(ctx, ref) 572 if err != nil { 573 return err 574 } 575 576 for _, k := range keys { 577 if k == "" { 578 return errtypes.BadRequest("eosfs: key is empty") 579 } 580 581 attr := &eosclient.Attribute{ 582 Type: UserAttr, 583 Key: k, 584 } 585 586 err := fs.c.UnsetAttr(ctx, auth, attr, false, fn) 587 if err != nil { 588 return errors.Wrap(err, "eosfs: error unsetting xattr in eos driver") 589 } 590 591 } 592 return nil 593 } 594 595 func (fs *eosfs) getLockExpiration(ctx context.Context, auth eosclient.Authorization, path string) (*types.Timestamp, bool, error) { 596 expiration, err := fs.c.GetAttr(ctx, auth, "sys."+LockExpirationKey, path) 597 if err != nil { 598 // since the expiration is optional, if we do not find it in the attr 599 // just return a nil value, without reporting the error 600 if _, ok := err.(errtypes.NotFound); ok { 601 return nil, true, nil 602 } 603 return nil, false, err 604 } 605 // the expiration value should be unix time encoded 606 unixTime, err := strconv.ParseInt(expiration.Val, 10, 64) 607 if err != nil { 608 return nil, false, errors.Wrap(err, "eosfs: error converting unix time") 609 } 610 t := time.Unix(unixTime, 0) 611 timestamp := &types.Timestamp{ 612 Seconds: uint64(unixTime), 613 } 614 return timestamp, t.After(time.Now()), nil 615 } 616 617 func (fs *eosfs) getLockContent(ctx context.Context, auth eosclient.Authorization, path string, expiration *types.Timestamp) (*provider.Lock, error) { 618 t, err := fs.c.GetAttr(ctx, auth, "sys."+LockTypeKey, path) 619 if err != nil { 620 return nil, err 621 } 622 lockType, err := strconv.ParseInt(t.Val, 10, 32) 623 if err != nil { 624 return nil, errors.Wrap(err, "eosfs: error decoding lock type") 625 } 626 627 d, err := fs.c.GetAttr(ctx, auth, "sys."+LockPayloadKey, path) 628 if err != nil { 629 return nil, err 630 } 631 632 data, err := b64.StdEncoding.DecodeString(d.Val) 633 if err != nil { 634 return nil, err 635 } 636 l := new(provider.Lock) 637 err = json.Unmarshal(data, l) 638 if err != nil { 639 return nil, err 640 } 641 642 l.Type = provider.LockType(lockType) 643 l.Expiration = expiration 644 645 return l, nil 646 647 } 648 649 func (fs *eosfs) removeLockAttrs(ctx context.Context, auth eosclient.Authorization, path string) error { 650 err := fs.c.UnsetAttr(ctx, auth, &eosclient.Attribute{ 651 Type: SystemAttr, 652 Key: LockExpirationKey, 653 }, false, path) 654 if err != nil { 655 // as the expiration time in the lock is optional 656 // we will discard the error if the attr is not set 657 if !errors.Is(err, eosclient.AttrNotExistsError) { 658 return errors.Wrap(err, "eosfs: error unsetting the lock expiration") 659 } 660 } 661 662 err = fs.c.UnsetAttr(ctx, auth, &eosclient.Attribute{ 663 Type: SystemAttr, 664 Key: LockTypeKey, 665 }, false, path) 666 if err != nil { 667 return errors.Wrap(err, "eosfs: error unsetting the lock type") 668 } 669 670 err = fs.c.UnsetAttr(ctx, auth, &eosclient.Attribute{ 671 Type: SystemAttr, 672 Key: LockPayloadKey, 673 }, false, path) 674 if err != nil { 675 return errors.Wrap(err, "eosfs: error unsetting the lock payload") 676 } 677 678 return nil 679 } 680 681 func (fs *eosfs) getLock(ctx context.Context, auth eosclient.Authorization, user *userpb.User, path string, ref *provider.Reference) (*provider.Lock, error) { 682 // the cs3apis require to have the read permission on the resource 683 // to get the eventual lock. 684 has, err := fs.userHasReadAccess(ctx, user, ref) 685 if err != nil { 686 return nil, errors.Wrap(err, "eosfs: error checking read access to resource") 687 } 688 if !has { 689 return nil, errtypes.BadRequest("user has not read access on resource") 690 } 691 692 expiration, valid, err := fs.getLockExpiration(ctx, auth, path) 693 if err != nil { 694 return nil, err 695 } 696 697 if !valid { 698 // the previous lock expired 699 if err := fs.removeLockAttrs(ctx, auth, path); err != nil { 700 return nil, err 701 } 702 return nil, errtypes.NotFound("lock not found for ref") 703 } 704 705 l, err := fs.getLockContent(ctx, auth, path, expiration) 706 if err != nil { 707 if !errors.Is(err, eosclient.AttrNotExistsError) { 708 return nil, errtypes.NotFound("lock not found for ref") 709 } 710 } 711 return l, nil 712 } 713 714 // GetLock returns an existing lock on the given reference 715 func (fs *eosfs) GetLock(ctx context.Context, ref *provider.Reference) (*provider.Lock, error) { 716 path, err := fs.resolve(ctx, ref) 717 if err != nil { 718 return nil, errors.Wrap(err, "eosfs: error resolving reference") 719 } 720 path = fs.wrap(ctx, path) 721 722 user, err := getUser(ctx) 723 if err != nil { 724 return nil, errors.Wrap(err, "eosfs: no user in ctx") 725 } 726 auth, err := fs.getUserAuth(ctx, user, path) 727 if err != nil { 728 return nil, errors.Wrap(err, "eosfs: error getting uid and gid for user") 729 } 730 731 return fs.getLock(ctx, auth, user, path, ref) 732 } 733 734 func (fs *eosfs) setLock(ctx context.Context, lock *provider.Lock, path string, check bool) error { 735 auth, err := fs.getRootAuth(ctx) 736 if err != nil { 737 return err 738 } 739 740 encodedLock, err := encodeLock(lock) 741 if err != nil { 742 return errors.Wrap(err, "eosfs: error encoding lock") 743 } 744 745 if lock.Expiration != nil { 746 // set expiration 747 err = fs.c.SetAttr(ctx, auth, &eosclient.Attribute{ 748 Type: SystemAttr, 749 Key: LockExpirationKey, 750 Val: strconv.FormatUint(lock.Expiration.Seconds, 10), 751 }, check, false, path) 752 switch { 753 case errors.Is(err, eosclient.AttrAlreadyExistsError): 754 return errtypes.BadRequest("lock already set") 755 case err != nil: 756 return err 757 } 758 } 759 760 // set lock type 761 err = fs.c.SetAttr(ctx, auth, &eosclient.Attribute{ 762 Type: SystemAttr, 763 Key: LockTypeKey, 764 Val: strconv.FormatUint(uint64(lock.Type), 10), 765 }, false, false, path) 766 if err != nil { 767 return errors.Wrap(err, "eosfs: error setting lock type") 768 } 769 770 // set payload 771 err = fs.c.SetAttr(ctx, auth, &eosclient.Attribute{ 772 Type: SystemAttr, 773 Key: LockPayloadKey, 774 Val: encodedLock, 775 }, false, false, path) 776 if err != nil { 777 return errors.Wrap(err, "eosfs: error setting lock payload") 778 } 779 return nil 780 } 781 782 // SetLock puts a lock on the given reference 783 func (fs *eosfs) SetLock(ctx context.Context, ref *provider.Reference, l *provider.Lock) error { 784 if l.Type == provider.LockType_LOCK_TYPE_SHARED { 785 return errtypes.NotSupported("shared lock not yet implemented") 786 } 787 788 path, err := fs.resolve(ctx, ref) 789 if err != nil { 790 return errors.Wrap(err, "eosfs: error resolving reference") 791 } 792 path = fs.wrap(ctx, path) 793 794 user, err := getUser(ctx) 795 if err != nil { 796 return errors.Wrap(err, "eosfs: no user in ctx") 797 } 798 auth, err := fs.getUserAuth(ctx, user, path) 799 if err != nil { 800 return errors.Wrap(err, "eosfs: error getting uid and gid for user") 801 } 802 803 _, err = fs.getLock(ctx, auth, user, path, ref) 804 if err != nil { 805 // if the err is NotFound it is fine, otherwise we have to return 806 if _, ok := err.(errtypes.NotFound); !ok { 807 return err 808 } 809 } 810 if err == nil { 811 // the resource is already locked 812 return errtypes.BadRequest("resource already locked") 813 } 814 815 // the cs3apis require to have the write permission on the resource 816 // to set a lock. because in eos we can set attrs even if the user does 817 // not have the write permission, we need to check if the user that made 818 // the request has it 819 has, err := fs.userHasWriteAccess(ctx, user, ref) 820 if err != nil { 821 return errors.Wrap(err, fmt.Sprintf("eosfs: cannot check if user %s has write access on resource", user.Username)) 822 } 823 if !has { 824 return errtypes.PermissionDenied(fmt.Sprintf("user %s has not write access on resource", user.Username)) 825 } 826 827 // the user in the lock could differ from the user in the context 828 // in that case, also the user in the lock MUST have the write permission 829 if l.User != nil && !utils.UserEqual(user.Id, l.User) { 830 has, err := fs.userIDHasWriteAccess(ctx, l.User, ref) 831 if err != nil { 832 return errors.Wrap(err, "eosfs: cannot check if user has write access on resource") 833 } 834 if !has { 835 return errtypes.PermissionDenied(fmt.Sprintf("user %s has not write access on resource", user.Username)) 836 } 837 } 838 839 return fs.setLock(ctx, l, path, true) 840 } 841 842 func (fs *eosfs) getUserFromID(ctx context.Context, userID *userpb.UserId) (*userpb.User, error) { 843 client, err := pool.GetGatewayServiceClient(fs.conf.GatewaySvc) 844 if err != nil { 845 return nil, err 846 } 847 res, err := client.GetUser(ctx, &userpb.GetUserRequest{ 848 UserId: userID, 849 }) 850 851 if err != nil { 852 return nil, err 853 } 854 if res.Status.Code != rpc.Code_CODE_OK { 855 return nil, errtypes.InternalError(res.Status.Message) 856 } 857 return res.User, nil 858 } 859 860 func (fs *eosfs) userHasWriteAccess(ctx context.Context, user *userpb.User, ref *provider.Reference) (bool, error) { 861 ctx = ctxpkg.ContextSetUser(ctx, user) 862 resInfo, err := fs.GetMD(ctx, ref, nil, nil) 863 if err != nil { 864 return false, err 865 } 866 return resInfo.PermissionSet.InitiateFileUpload, nil 867 } 868 869 func (fs *eosfs) userIDHasWriteAccess(ctx context.Context, userID *userpb.UserId, ref *provider.Reference) (bool, error) { 870 user, err := fs.getUserFromID(ctx, userID) 871 if err != nil { 872 return false, nil 873 } 874 return fs.userHasWriteAccess(ctx, user, ref) 875 } 876 877 func (fs *eosfs) userHasReadAccess(ctx context.Context, user *userpb.User, ref *provider.Reference) (bool, error) { 878 ctx = ctxpkg.ContextSetUser(ctx, user) 879 resInfo, err := fs.GetMD(ctx, ref, nil, nil) 880 if err != nil { 881 return false, err 882 } 883 return resInfo.PermissionSet.InitiateFileDownload, nil 884 } 885 886 func encodeLock(l *provider.Lock) (string, error) { 887 data, err := json.Marshal(l) 888 if err != nil { 889 return "", err 890 } 891 return b64.StdEncoding.EncodeToString(data), nil 892 } 893 894 // RefreshLock refreshes an existing lock on the given reference 895 // TODO: use existingLockId. See https://github.com/cs3org/reva/pull/3286 896 func (fs *eosfs) RefreshLock(ctx context.Context, ref *provider.Reference, newLock *provider.Lock, _ string) error { 897 // TODO (gdelmont): check if the new lock is already expired? 898 899 if newLock.Type == provider.LockType_LOCK_TYPE_SHARED { 900 return errtypes.NotSupported("shared lock not yet implemented") 901 } 902 903 oldLock, err := fs.GetLock(ctx, ref) 904 if err != nil { 905 switch err.(type) { 906 case errtypes.NotFound: 907 // the lock does not exist 908 return errtypes.BadRequest("file was not locked") 909 default: 910 return err 911 } 912 } 913 914 user, err := getUser(ctx) 915 if err != nil { 916 return errors.Wrap(err, "eosfs: error getting user") 917 } 918 919 // check if the holder is the same of the new lock 920 if !sameHolder(oldLock, newLock) { 921 return errtypes.BadRequest("caller does not hold the lock") 922 } 923 924 path, err := fs.resolve(ctx, ref) 925 if err != nil { 926 return errors.Wrap(err, "eosfs: error resolving reference") 927 } 928 path = fs.wrap(ctx, path) 929 930 // the cs3apis require to have the write permission on the resource 931 // to set a lock 932 has, err := fs.userHasWriteAccess(ctx, user, ref) 933 if err != nil { 934 return errors.Wrap(err, "eosfs: cannot check if user has write access on resource") 935 } 936 if !has { 937 return errtypes.PermissionDenied(fmt.Sprintf("user %s has not write access on resource", user.Username)) 938 } 939 940 return fs.setLock(ctx, newLock, path, false) 941 } 942 943 func sameHolder(l1, l2 *provider.Lock) bool { 944 same := true 945 if l1.User != nil || l2.User != nil { 946 same = utils.UserEqual(l1.User, l2.User) 947 } 948 if l1.AppName != "" || l2.AppName != "" { 949 same = l1.AppName == l2.AppName 950 } 951 return same 952 } 953 954 // Unlock removes an existing lock from the given reference 955 func (fs *eosfs) Unlock(ctx context.Context, ref *provider.Reference, lock *provider.Lock) error { 956 if lock.Type == provider.LockType_LOCK_TYPE_SHARED { 957 return errtypes.NotSupported("shared lock not yet implemented") 958 } 959 960 oldLock, err := fs.GetLock(ctx, ref) 961 if err != nil { 962 switch err.(type) { 963 case errtypes.NotFound: 964 // the lock does not exist 965 return errtypes.BadRequest("file was not locked") 966 default: 967 return err 968 } 969 } 970 971 // check if the lock id of the lock corresponds to the stored lock 972 if oldLock.LockId != lock.LockId { 973 return errtypes.BadRequest("lock id does not match") 974 } 975 976 if !sameHolder(oldLock, lock) { 977 return errtypes.BadRequest("caller does not hold the lock") 978 } 979 980 user, err := getUser(ctx) 981 if err != nil { 982 return errors.Wrap(err, "eosfs: error getting user") 983 } 984 985 // the cs3apis require to have the write permission on the resource 986 // to remove the lock 987 has, err := fs.userHasWriteAccess(ctx, user, ref) 988 if err != nil { 989 return errors.Wrap(err, "eosfs: cannot check if user has write access on resource") 990 } 991 if !has { 992 return errtypes.PermissionDenied(fmt.Sprintf("user %s has not write access on resource", user.Username)) 993 } 994 995 path, err := fs.resolve(ctx, ref) 996 if err != nil { 997 return errors.Wrap(err, "eosfs: error resolving reference") 998 } 999 path = fs.wrap(ctx, path) 1000 1001 auth, err := fs.getRootAuth(ctx) 1002 if err != nil { 1003 return errors.Wrap(err, "eosfs: error getting uid and gid for user") 1004 } 1005 return fs.removeLockAttrs(ctx, auth, path) 1006 } 1007 1008 func (fs *eosfs) AddGrant(ctx context.Context, ref *provider.Reference, g *provider.Grant) error { 1009 fn, auth, err := fs.resolveRefAndGetAuth(ctx, ref) 1010 if err != nil { 1011 return err 1012 } 1013 1014 rootAuth, err := fs.getRootAuth(ctx) 1015 if err != nil { 1016 return err 1017 } 1018 1019 // position where put the ACL 1020 position := eosclient.StartPosition 1021 1022 eosACL, err := fs.getEosACL(ctx, g) 1023 if err != nil { 1024 return err 1025 } 1026 1027 err = fs.c.AddACL(ctx, auth, rootAuth, fn, position, eosACL) 1028 if err != nil { 1029 return errors.Wrap(err, "eosfs: error adding acl") 1030 } 1031 return nil 1032 1033 } 1034 1035 func (fs *eosfs) DenyGrant(ctx context.Context, ref *provider.Reference, g *provider.Grantee) error { 1036 fn, auth, err := fs.resolveRefAndGetAuth(ctx, ref) 1037 if err != nil { 1038 return err 1039 } 1040 1041 position := eosclient.EndPosition 1042 1043 rootAuth, err := fs.getRootAuth(ctx) 1044 if err != nil { 1045 return err 1046 } 1047 1048 // empty permissions => deny 1049 grant := &provider.Grant{ 1050 Grantee: g, 1051 Permissions: &provider.ResourcePermissions{}, 1052 } 1053 1054 eosACL, err := fs.getEosACL(ctx, grant) 1055 if err != nil { 1056 return err 1057 } 1058 1059 err = fs.c.AddACL(ctx, auth, rootAuth, fn, position, eosACL) 1060 if err != nil { 1061 return errors.Wrap(err, "eosfs: error adding acl") 1062 } 1063 return nil 1064 } 1065 1066 func (fs *eosfs) getEosACL(ctx context.Context, g *provider.Grant) (*acl.Entry, error) { 1067 permissions, err := grants.GetACLPerm(g.Permissions) 1068 if err != nil { 1069 return nil, err 1070 } 1071 t, err := grants.GetACLType(g.Grantee.Type) 1072 if err != nil { 1073 return nil, err 1074 } 1075 1076 var qualifier string 1077 if t == acl.TypeUser { 1078 // if the grantee is a lightweight account, we need to set it accordingly 1079 if g.Grantee.GetUserId().Type == userpb.UserType_USER_TYPE_LIGHTWEIGHT || 1080 g.Grantee.GetUserId().Type == userpb.UserType_USER_TYPE_FEDERATED { 1081 t = acl.TypeLightweight 1082 qualifier = g.Grantee.GetUserId().OpaqueId 1083 } else { 1084 // since EOS Citrine ACLs are stored with uid, we need to convert username to 1085 // uid only for users. 1086 auth, err := fs.getUIDGateway(ctx, g.Grantee.GetUserId()) 1087 if err != nil { 1088 return nil, err 1089 } 1090 qualifier = auth.Role.UID 1091 } 1092 } else { 1093 qualifier = g.Grantee.GetGroupId().OpaqueId 1094 } 1095 1096 eosACL := &acl.Entry{ 1097 Qualifier: qualifier, 1098 Permissions: permissions, 1099 Type: t, 1100 } 1101 return eosACL, nil 1102 } 1103 1104 func (fs *eosfs) RemoveGrant(ctx context.Context, ref *provider.Reference, g *provider.Grant) error { 1105 eosACLType, err := grants.GetACLType(g.Grantee.Type) 1106 if err != nil { 1107 return err 1108 } 1109 1110 var recipient string 1111 if eosACLType == acl.TypeUser { 1112 // if the grantee is a lightweight account, we need to set it accordingly 1113 if g.Grantee.GetUserId().Type == userpb.UserType_USER_TYPE_LIGHTWEIGHT || 1114 g.Grantee.GetUserId().Type == userpb.UserType_USER_TYPE_FEDERATED { 1115 eosACLType = acl.TypeLightweight 1116 recipient = g.Grantee.GetUserId().OpaqueId 1117 } else { 1118 // since EOS Citrine ACLs are stored with uid, we need to convert username to uid 1119 auth, err := fs.getUIDGateway(ctx, g.Grantee.GetUserId()) 1120 if err != nil { 1121 return err 1122 } 1123 recipient = auth.Role.UID 1124 } 1125 } else { 1126 recipient = g.Grantee.GetGroupId().OpaqueId 1127 } 1128 1129 eosACL := &acl.Entry{ 1130 Qualifier: recipient, 1131 Type: eosACLType, 1132 } 1133 1134 fn, auth, err := fs.resolveRefAndGetAuth(ctx, ref) 1135 if err != nil { 1136 return err 1137 } 1138 1139 rootAuth, err := fs.getRootAuth(ctx) 1140 if err != nil { 1141 return err 1142 } 1143 1144 err = fs.c.RemoveACL(ctx, auth, rootAuth, fn, eosACL) 1145 if err != nil { 1146 return errors.Wrap(err, "eosfs: error removing acl") 1147 } 1148 return nil 1149 } 1150 1151 func (fs *eosfs) UpdateGrant(ctx context.Context, ref *provider.Reference, g *provider.Grant) error { 1152 return fs.AddGrant(ctx, ref, g) 1153 } 1154 1155 func (fs *eosfs) ListGrants(ctx context.Context, ref *provider.Reference) ([]*provider.Grant, error) { 1156 fn, auth, err := fs.resolveRefAndGetAuth(ctx, ref) 1157 if err != nil { 1158 return nil, err 1159 } 1160 1161 acls, err := fs.c.ListACLs(ctx, auth, fn) 1162 if err != nil { 1163 return nil, err 1164 } 1165 1166 grantList := []*provider.Grant{} 1167 for _, a := range acls { 1168 var grantee *provider.Grantee 1169 switch { 1170 case a.Type == acl.TypeUser: 1171 // EOS Citrine ACLs are stored with uid for users. 1172 // This needs to be resolved to the user opaque ID. 1173 qualifier, err := fs.getUserIDGateway(ctx, a.Qualifier) 1174 if err != nil { 1175 return nil, err 1176 } 1177 grantee = &provider.Grantee{ 1178 Id: &provider.Grantee_UserId{UserId: qualifier}, 1179 Type: grants.GetGranteeType(a.Type), 1180 } 1181 case a.Type == acl.TypeLightweight: 1182 a.Type = acl.TypeUser 1183 grantee = &provider.Grantee{ 1184 Id: &provider.Grantee_UserId{UserId: &userpb.UserId{OpaqueId: a.Qualifier}}, 1185 Type: grants.GetGranteeType(a.Type), 1186 } 1187 default: 1188 grantee = &provider.Grantee{ 1189 Id: &provider.Grantee_GroupId{GroupId: &grouppb.GroupId{OpaqueId: a.Qualifier}}, 1190 Type: grants.GetGranteeType(a.Type), 1191 } 1192 } 1193 1194 grantList = append(grantList, &provider.Grant{ 1195 Grantee: grantee, 1196 Permissions: grants.GetGrantPermissionSet(a.Permissions), 1197 }) 1198 } 1199 1200 return grantList, nil 1201 } 1202 1203 func (fs *eosfs) GetMD(ctx context.Context, ref *provider.Reference, mdKeys []string, fieldMask []string) (*provider.ResourceInfo, error) { 1204 log := appctx.GetLogger(ctx) 1205 log.Info().Msg("eosfs: get md for ref:" + ref.String()) 1206 1207 u, err := getUser(ctx) 1208 if err != nil { 1209 return nil, err 1210 } 1211 1212 fn := "" 1213 p := ref.Path 1214 1215 if u.Id.Type == userpb.UserType_USER_TYPE_LIGHTWEIGHT || 1216 u.Id.Type == userpb.UserType_USER_TYPE_FEDERATED { 1217 p, err := fs.resolve(ctx, ref) 1218 if err != nil { 1219 return nil, errors.Wrap(err, "eosfs: error resolving reference") 1220 } 1221 1222 fn = fs.wrap(ctx, p) 1223 } 1224 1225 auth, err := fs.getUserAuth(ctx, u, fn) 1226 if err != nil { 1227 return nil, err 1228 } 1229 1230 // We handle the case when resource ID is set to avoid making duplicate calls to EOS. 1231 // In the previous workflow, we would have called the resolve() method which would return 1232 // the path and then we'll stat the path. 1233 if ref.ResourceId != nil { 1234 fid, err := strconv.ParseUint(ref.ResourceId.OpaqueId, 10, 64) 1235 if err != nil { 1236 return nil, fmt.Errorf("error converting string to int for eos fileid: %s", ref.ResourceId.OpaqueId) 1237 } 1238 1239 eosFileInfo, err := fs.c.GetFileInfoByInode(ctx, auth, fid) 1240 if err != nil { 1241 return nil, err 1242 } 1243 1244 // If it's not a relative reference, return now, else we need to append the path 1245 if !utils.IsRelativeReference(ref) { 1246 return fs.convertToResourceInfo(ctx, eosFileInfo) 1247 } 1248 1249 parent, err := fs.unwrap(ctx, eosFileInfo.File) 1250 if err != nil { 1251 return nil, err 1252 } 1253 1254 p = path.Join(parent, p) 1255 } 1256 1257 // if path is home we need to add in the response any shadow folder in the shadow homedirectory. 1258 if fs.conf.EnableHome { 1259 if fs.isShareFolder(ctx, p) { 1260 return fs.getMDShareFolder(ctx, p, mdKeys) 1261 } 1262 } 1263 1264 fn = fs.wrap(ctx, p) 1265 eosFileInfo, err := fs.c.GetFileInfoByPath(ctx, auth, fn) 1266 if err != nil { 1267 return nil, err 1268 } 1269 1270 return fs.convertToResourceInfo(ctx, eosFileInfo) 1271 } 1272 1273 func (fs *eosfs) getMDShareFolder(ctx context.Context, p string, mdKeys []string) (*provider.ResourceInfo, error) { 1274 fn := fs.wrapShadow(ctx, p) 1275 1276 u, err := getUser(ctx) 1277 if err != nil { 1278 return nil, err 1279 } 1280 1281 // lightweight accounts don't have share folders, so we're passing an empty string as path 1282 auth, err := fs.getUserAuth(ctx, u, "") 1283 if err != nil { 1284 return nil, err 1285 } 1286 1287 eosFileInfo, err := fs.c.GetFileInfoByPath(ctx, auth, fn) 1288 if err != nil { 1289 return nil, err 1290 } 1291 1292 if fs.isShareFolderRoot(ctx, p) { 1293 return fs.convertToResourceInfo(ctx, eosFileInfo) 1294 } 1295 return fs.convertToFileReference(ctx, eosFileInfo) 1296 } 1297 1298 func (fs *eosfs) ListFolder(ctx context.Context, ref *provider.Reference, mdKeys, fieldMask []string) ([]*provider.ResourceInfo, error) { 1299 p, err := fs.resolve(ctx, ref) 1300 if err != nil { 1301 return nil, errors.Wrap(err, "eosfs: error resolving reference") 1302 } 1303 1304 // if path is home we need to add in the response any shadow folder in the shadow homedirectory. 1305 if fs.conf.EnableHome { 1306 return fs.listWithHome(ctx, p) 1307 } 1308 1309 return fs.listWithNominalHome(ctx, p) 1310 } 1311 1312 func (fs *eosfs) listWithNominalHome(ctx context.Context, p string) (finfos []*provider.ResourceInfo, err error) { 1313 log := appctx.GetLogger(ctx) 1314 fn := fs.wrap(ctx, p) 1315 1316 u, err := getUser(ctx) 1317 if err != nil { 1318 return nil, errors.Wrap(err, "eosfs: no user in ctx") 1319 } 1320 auth, err := fs.getUserAuth(ctx, u, fn) 1321 if err != nil { 1322 return nil, err 1323 } 1324 1325 eosFileInfos, err := fs.c.List(ctx, auth, fn) 1326 if err != nil { 1327 return nil, errors.Wrap(err, "eosfs: error listing") 1328 } 1329 1330 for _, eosFileInfo := range eosFileInfos { 1331 // filter out sys files 1332 if !fs.conf.ShowHiddenSysFiles { 1333 base := path.Base(eosFileInfo.File) 1334 if hiddenReg.MatchString(base) { 1335 log.Debug().Msgf("eosfs: path is filtered because is considered hidden: path=%s hiddenReg=%s", base, hiddenReg) 1336 continue 1337 } 1338 } 1339 1340 // Remove the hidden folders in the topmost directory 1341 if finfo, err := fs.convertToResourceInfo(ctx, eosFileInfo); err == nil && finfo.Path != "/" && !strings.HasPrefix(finfo.Path, "/.") { 1342 finfos = append(finfos, finfo) 1343 } 1344 } 1345 1346 return finfos, nil 1347 } 1348 1349 func (fs *eosfs) listWithHome(ctx context.Context, p string) ([]*provider.ResourceInfo, error) { 1350 if p == "/" { 1351 return fs.listHome(ctx) 1352 } 1353 1354 if fs.isShareFolderRoot(ctx, p) { 1355 return fs.listShareFolderRoot(ctx, p) 1356 } 1357 1358 if fs.isShareFolderChild(ctx, p) { 1359 return nil, errtypes.PermissionDenied("eosfs: error listing folders inside the shared folder, only file references are stored inside") 1360 } 1361 1362 // path points to a resource in the nominal home 1363 return fs.listWithNominalHome(ctx, p) 1364 } 1365 1366 func (fs *eosfs) listHome(ctx context.Context) ([]*provider.ResourceInfo, error) { 1367 fns := []string{fs.wrap(ctx, "/"), fs.wrapShadow(ctx, "/")} 1368 1369 u, err := getUser(ctx) 1370 if err != nil { 1371 return nil, errors.Wrap(err, "eosfs: no user in ctx") 1372 } 1373 // lightweight accounts don't have home folders, so we're passing an empty string as path 1374 auth, err := fs.getUserAuth(ctx, u, "") 1375 if err != nil { 1376 return nil, err 1377 } 1378 1379 finfos := []*provider.ResourceInfo{} 1380 for _, fn := range fns { 1381 eosFileInfos, err := fs.c.List(ctx, auth, fn) 1382 if err != nil { 1383 return nil, errors.Wrap(err, "eosfs: error listing") 1384 } 1385 1386 for _, eosFileInfo := range eosFileInfos { 1387 // filter out sys files 1388 if !fs.conf.ShowHiddenSysFiles { 1389 base := path.Base(eosFileInfo.File) 1390 if hiddenReg.MatchString(base) { 1391 continue 1392 } 1393 } 1394 1395 if finfo, err := fs.convertToResourceInfo(ctx, eosFileInfo); err == nil && finfo.Path != "/" && !strings.HasPrefix(finfo.Path, "/.") { 1396 finfos = append(finfos, finfo) 1397 } 1398 } 1399 1400 } 1401 return finfos, nil 1402 } 1403 1404 func (fs *eosfs) listShareFolderRoot(ctx context.Context, p string) (finfos []*provider.ResourceInfo, err error) { 1405 fn := fs.wrapShadow(ctx, p) 1406 1407 u, err := getUser(ctx) 1408 if err != nil { 1409 return nil, errors.Wrap(err, "eosfs: no user in ctx") 1410 } 1411 // lightweight accounts don't have share folders, so we're passing an empty string as path 1412 auth, err := fs.getUserAuth(ctx, u, "") 1413 if err != nil { 1414 return nil, err 1415 } 1416 1417 eosFileInfos, err := fs.c.List(ctx, auth, fn) 1418 if err != nil { 1419 return nil, errors.Wrap(err, "eosfs: error listing") 1420 } 1421 1422 for _, eosFileInfo := range eosFileInfos { 1423 // filter out sys files 1424 if !fs.conf.ShowHiddenSysFiles { 1425 base := path.Base(eosFileInfo.File) 1426 if hiddenReg.MatchString(base) { 1427 continue 1428 } 1429 } 1430 1431 if finfo, err := fs.convertToFileReference(ctx, eosFileInfo); err == nil { 1432 finfos = append(finfos, finfo) 1433 } 1434 } 1435 1436 return finfos, nil 1437 } 1438 1439 func (fs *eosfs) GetQuota(ctx context.Context, ref *provider.Reference) (uint64, uint64, uint64, error) { 1440 // Check if the quota request is for the user's home or a project space 1441 u, err := getUser(ctx) 1442 if err != nil { 1443 return 0, 0, 0, errors.Wrap(err, "eosfs: no user in ctx") 1444 } 1445 1446 // If the quota request is for a resource different than the user home, 1447 // we impersonate the owner in that case 1448 uid := strconv.FormatInt(u.UidNumber, 10) 1449 if ref.ResourceId != nil { 1450 fid, err := strconv.ParseUint(ref.ResourceId.OpaqueId, 10, 64) 1451 if err != nil { 1452 return 0, 0, 0, fmt.Errorf("error converting string to int for eos fileid: %s", ref.ResourceId.OpaqueId) 1453 } 1454 1455 // lightweight accounts don't have quota nodes, so we're passing an empty string as path 1456 auth, err := fs.getUserAuth(ctx, u, "") 1457 if err != nil { 1458 return 0, 0, 0, errors.Wrap(err, "eosfs: error getting uid and gid for user") 1459 } 1460 eosFileInfo, err := fs.c.GetFileInfoByInode(ctx, auth, fid) 1461 if err != nil { 1462 return 0, 0, 0, err 1463 } 1464 uid = strconv.FormatUint(eosFileInfo.UID, 10) 1465 } 1466 1467 rootAuth, err := fs.getRootAuth(ctx) 1468 if err != nil { 1469 return 0, 0, 0, err 1470 } 1471 1472 qi, err := fs.c.GetQuota(ctx, uid, rootAuth, fs.conf.QuotaNode) 1473 if err != nil { 1474 err := errors.Wrap(err, "eosfs: error getting quota") 1475 return 0, 0, 0, err 1476 } 1477 1478 remaining := qi.AvailableBytes - qi.UsedBytes 1479 1480 return qi.AvailableBytes, qi.UsedBytes, remaining, nil 1481 } 1482 1483 func (fs *eosfs) GetHome(ctx context.Context) (string, error) { 1484 if !fs.conf.EnableHome { 1485 return "", errtypes.NotSupported("eosfs: get home not supported") 1486 } 1487 1488 // eos drive for homes assumes root(/) points to the user home. 1489 return "/", nil 1490 } 1491 1492 func (fs *eosfs) createShadowHome(ctx context.Context, home string) error { 1493 u, err := getUser(ctx) 1494 if err != nil { 1495 return errors.Wrap(err, "eosfs: no user in ctx") 1496 } 1497 rootAuth, err := fs.getRootAuth(ctx) 1498 if err != nil { 1499 return nil 1500 } 1501 shadowFolders := []string{fs.conf.ShareFolder} 1502 1503 for _, sf := range shadowFolders { 1504 fn := path.Join(home, sf) 1505 _, err = fs.c.GetFileInfoByPath(ctx, rootAuth, fn) 1506 if err != nil { 1507 if _, ok := err.(errtypes.IsNotFound); !ok { 1508 return errors.Wrap(err, "eosfs: error verifying if shadow directory exists") 1509 } 1510 err = fs.createUserDir(ctx, u, fn, false) 1511 if err != nil { 1512 return err 1513 } 1514 } 1515 } 1516 1517 return nil 1518 } 1519 1520 func (fs *eosfs) createNominalHome(ctx context.Context, home string) error { 1521 u, err := getUser(ctx) 1522 if err != nil { 1523 return errors.Wrap(err, "eosfs: no user in ctx") 1524 } 1525 auth, err := fs.getUserAuth(ctx, u, "") 1526 if err != nil { 1527 return err 1528 } 1529 1530 rootAuth, err := fs.getRootAuth(ctx) 1531 if err != nil { 1532 return nil 1533 } 1534 1535 _, err = fs.c.GetFileInfoByPath(ctx, rootAuth, home) 1536 if err == nil { // home already exists 1537 return nil 1538 } 1539 1540 if _, ok := err.(errtypes.IsNotFound); !ok { 1541 return errors.Wrap(err, "eosfs: error verifying if user home directory exists") 1542 } 1543 1544 err = fs.createUserDir(ctx, u, home, false) 1545 if err != nil { 1546 err := errors.Wrap(err, "eosfs: error creating user dir") 1547 return err 1548 } 1549 1550 // set quota for user 1551 quotaInfo := &eosclient.SetQuotaInfo{ 1552 Username: u.Username, 1553 UID: auth.Role.UID, 1554 GID: auth.Role.GID, 1555 MaxBytes: fs.conf.DefaultQuotaBytes, 1556 MaxFiles: fs.conf.DefaultQuotaFiles, 1557 QuotaNode: fs.conf.QuotaNode, 1558 } 1559 1560 err = fs.c.SetQuota(ctx, rootAuth, quotaInfo) 1561 if err != nil { 1562 err := errors.Wrap(err, "eosfs: error setting quota") 1563 return err 1564 } 1565 1566 return err 1567 } 1568 1569 func (fs *eosfs) CreateHome(ctx context.Context) error { 1570 if !fs.conf.EnableHome { 1571 return errtypes.NotSupported("eosfs: create home not supported") 1572 } 1573 1574 if err := fs.createNominalHome(ctx, fs.wrap(ctx, "/")); err != nil { 1575 return errors.Wrap(err, "eosfs: error creating nominal home") 1576 } 1577 1578 if err := fs.createShadowHome(ctx, fs.wrapShadow(ctx, "/")); err != nil { 1579 return errors.Wrap(err, "eosfs: error creating shadow home") 1580 } 1581 1582 return nil 1583 } 1584 1585 func (fs *eosfs) createUserDir(ctx context.Context, u *userpb.User, path string, recursiveAttr bool) error { 1586 rootAuth, err := fs.getRootAuth(ctx) 1587 if err != nil { 1588 return nil 1589 } 1590 1591 chownAuth, err := fs.getUserAuth(ctx, u, "") 1592 if err != nil { 1593 return err 1594 } 1595 1596 err = fs.c.CreateDir(ctx, rootAuth, path) 1597 if err != nil { 1598 // EOS will return success on mkdir over an existing directory. 1599 return errors.Wrap(err, "eosfs: error creating dir") 1600 } 1601 1602 err = fs.c.Chown(ctx, rootAuth, chownAuth, path) 1603 if err != nil { 1604 return errors.Wrap(err, "eosfs: error chowning directory") 1605 } 1606 1607 err = fs.c.Chmod(ctx, rootAuth, "2770", path) 1608 if err != nil { 1609 return errors.Wrap(err, "eosfs: error chmoding directory") 1610 } 1611 1612 attrs := []*eosclient.Attribute{ 1613 { 1614 Type: SystemAttr, 1615 Key: "mask", 1616 Val: "700", 1617 }, 1618 { 1619 Type: SystemAttr, 1620 Key: "allow.oc.sync", 1621 Val: "1", 1622 }, 1623 { 1624 Type: SystemAttr, 1625 Key: "mtime.propagation", 1626 Val: "1", 1627 }, 1628 { 1629 Type: SystemAttr, 1630 Key: "forced.atomic", 1631 Val: "1", 1632 }, 1633 } 1634 1635 for _, attr := range attrs { 1636 err = fs.c.SetAttr(ctx, rootAuth, attr, false, recursiveAttr, path) 1637 if err != nil { 1638 return errors.Wrap(err, "eosfs: error setting attribute") 1639 } 1640 } 1641 1642 return nil 1643 } 1644 1645 func (fs *eosfs) CreateDir(ctx context.Context, ref *provider.Reference) error { 1646 log := appctx.GetLogger(ctx) 1647 p, err := fs.resolve(ctx, ref) 1648 if err != nil { 1649 return errors.Wrap(err, "eosfs: error resolving reference") 1650 } 1651 if fs.isShareFolder(ctx, p) { 1652 return errtypes.PermissionDenied("eosfs: cannot perform operation under the virtual share folder") 1653 } 1654 fn := fs.wrap(ctx, p) 1655 1656 u, err := getUser(ctx) 1657 if err != nil { 1658 return errors.Wrap(err, "eosfs: no user in ctx") 1659 } 1660 1661 // We need the auth corresponding to the parent directory 1662 // as the file might not exist at the moment 1663 auth, err := fs.getUserAuth(ctx, u, path.Dir(fn)) 1664 if err != nil { 1665 return err 1666 } 1667 1668 log.Info().Msgf("eosfs: createdir: path=%s", fn) 1669 return fs.c.CreateDir(ctx, auth, fn) 1670 } 1671 1672 // TouchFile as defined in the storage.FS interface 1673 func (fs *eosfs) TouchFile(ctx context.Context, ref *provider.Reference, _ bool, _ string) error { 1674 log := appctx.GetLogger(ctx) 1675 1676 fn, auth, err := fs.resolveRefAndGetAuth(ctx, ref) 1677 if err != nil { 1678 return err 1679 } 1680 log.Info().Msgf("eosfs: touch file: path=%s", fn) 1681 1682 return fs.c.Touch(ctx, auth, fn) 1683 } 1684 1685 func (fs *eosfs) CreateReference(ctx context.Context, p string, targetURI *url.URL) error { 1686 // TODO(labkode): for the time being we only allow creating references 1687 // in the virtual share folder to not pollute the nominal user tree. 1688 if !fs.isShareFolder(ctx, p) { 1689 return errtypes.PermissionDenied("eosfs: cannot create references outside the share folder: share_folder=" + fs.conf.ShareFolder + " path=" + p) 1690 } 1691 u, err := getUser(ctx) 1692 if err != nil { 1693 return errors.Wrap(err, "eosfs: no user in ctx") 1694 } 1695 1696 fn := fs.wrapShadow(ctx, p) 1697 1698 // TODO(labkode): with the grpc plugin we can create a file touching with xattrs. 1699 // Current mechanism is: touch to hidden dir, set xattr, rename. 1700 dir, base := path.Split(fn) 1701 tmp := path.Join(dir, fmt.Sprintf(".sys.reva#.%s", base)) 1702 rootAuth, err := fs.getRootAuth(ctx) 1703 if err != nil { 1704 return nil 1705 } 1706 1707 if err := fs.createUserDir(ctx, u, tmp, false); err != nil { 1708 err = errors.Wrapf(err, "eosfs: error creating temporary ref file") 1709 return err 1710 } 1711 1712 // set xattr on ref 1713 attr := &eosclient.Attribute{ 1714 Type: UserAttr, 1715 Key: refTargetAttrKey, 1716 Val: targetURI.String(), 1717 } 1718 1719 if err := fs.c.SetAttr(ctx, rootAuth, attr, false, false, tmp); err != nil { 1720 err = errors.Wrapf(err, "eosfs: error setting reva.ref attr on file: %q", tmp) 1721 return err 1722 } 1723 1724 // rename to have the file visible in user space. 1725 if err := fs.c.Rename(ctx, rootAuth, tmp, fn); err != nil { 1726 err = errors.Wrapf(err, "eosfs: error renaming from: %q to %q", tmp, fn) 1727 return err 1728 } 1729 1730 return nil 1731 } 1732 1733 func (fs *eosfs) Delete(ctx context.Context, ref *provider.Reference) error { 1734 p, err := fs.resolve(ctx, ref) 1735 if err != nil { 1736 return errors.Wrap(err, "eosfs: error resolving reference") 1737 } 1738 1739 if fs.isShareFolder(ctx, p) { 1740 return fs.deleteShadow(ctx, p) 1741 } 1742 1743 fn := fs.wrap(ctx, p) 1744 1745 u, err := getUser(ctx) 1746 if err != nil { 1747 return errors.Wrap(err, "eosfs: no user in ctx") 1748 } 1749 auth, err := fs.getUserAuth(ctx, u, fn) 1750 if err != nil { 1751 return err 1752 } 1753 1754 return fs.c.Remove(ctx, auth, fn, false) 1755 } 1756 1757 func (fs *eosfs) deleteShadow(ctx context.Context, p string) error { 1758 if fs.isShareFolderRoot(ctx, p) { 1759 return errtypes.PermissionDenied("eosfs: cannot delete the virtual share folder") 1760 } 1761 1762 if fs.isShareFolderChild(ctx, p) { 1763 fn := fs.wrapShadow(ctx, p) 1764 1765 // in order to remove the folder or the file without 1766 // moving it to the recycle bin, we should take 1767 // the privileges of the root 1768 auth, err := fs.getRootAuth(ctx) 1769 if err != nil { 1770 return err 1771 } 1772 1773 return fs.c.Remove(ctx, auth, fn, true) 1774 } 1775 1776 return errors.New("eosfs: shadow delete of share folder that is neither root nor child. path=" + p) 1777 } 1778 1779 func (fs *eosfs) Move(ctx context.Context, oldRef, newRef *provider.Reference) error { 1780 oldPath, err := fs.resolve(ctx, oldRef) 1781 if err != nil { 1782 return errors.Wrap(err, "eosfs: error resolving reference") 1783 } 1784 1785 newPath, err := fs.resolve(ctx, newRef) 1786 if err != nil { 1787 return errors.Wrap(err, "eosfs: error resolving reference") 1788 } 1789 1790 if fs.isShareFolder(ctx, oldPath) || fs.isShareFolder(ctx, newPath) { 1791 return fs.moveShadow(ctx, oldPath, newPath) 1792 } 1793 1794 oldFn := fs.wrap(ctx, oldPath) 1795 newFn := fs.wrap(ctx, newPath) 1796 1797 u, err := getUser(ctx) 1798 if err != nil { 1799 return errors.Wrap(err, "eosfs: no user in ctx") 1800 } 1801 auth, err := fs.getUserAuth(ctx, u, oldFn) 1802 if err != nil { 1803 return err 1804 } 1805 1806 return fs.c.Rename(ctx, auth, oldFn, newFn) 1807 } 1808 1809 func (fs *eosfs) moveShadow(ctx context.Context, oldPath, newPath string) error { 1810 if fs.isShareFolderRoot(ctx, oldPath) || fs.isShareFolderRoot(ctx, newPath) { 1811 return errtypes.PermissionDenied("eosfs: cannot move/rename the virtual share folder") 1812 } 1813 1814 // only rename of the reference is allowed, hence having the same basedir 1815 bold, _ := path.Split(oldPath) 1816 bnew, _ := path.Split(newPath) 1817 1818 if bold != bnew { 1819 return errtypes.PermissionDenied("eosfs: cannot move references under the virtual share folder") 1820 } 1821 1822 oldfn := fs.wrapShadow(ctx, oldPath) 1823 newfn := fs.wrapShadow(ctx, newPath) 1824 1825 u, err := getUser(ctx) 1826 if err != nil { 1827 return errors.Wrap(err, "eosfs: no user in ctx") 1828 } 1829 auth, err := fs.getUserAuth(ctx, u, "") 1830 if err != nil { 1831 return err 1832 } 1833 1834 return fs.c.Rename(ctx, auth, oldfn, newfn) 1835 } 1836 1837 func (fs *eosfs) Download(ctx context.Context, ref *provider.Reference, openReaderfunc func(*provider.ResourceInfo) bool) (*provider.ResourceInfo, io.ReadCloser, error) { 1838 fn, auth, err := fs.resolveRefForbidShareFolder(ctx, ref) 1839 if err != nil { 1840 return nil, nil, err 1841 } 1842 1843 md, err := fs.GetMD(ctx, ref, nil, nil) 1844 if err != nil { 1845 return nil, nil, err 1846 } 1847 1848 if !openReaderfunc(md) { 1849 return md, nil, nil 1850 } 1851 1852 reader, err := fs.c.Read(ctx, auth, fn) 1853 return md, reader, err 1854 } 1855 1856 func (fs *eosfs) ListRevisions(ctx context.Context, ref *provider.Reference) ([]*provider.FileVersion, error) { 1857 var auth eosclient.Authorization 1858 var fn string 1859 var err error 1860 1861 if !fs.conf.EnableHome && fs.conf.ImpersonateOwnerforRevisions { 1862 // We need to access the revisions for a non-home reference. 1863 // We'll get the owner of the particular resource and impersonate them 1864 // if we have access to it. 1865 md, err := fs.GetMD(ctx, ref, nil, nil) 1866 if err != nil { 1867 return nil, err 1868 } 1869 fn = fs.wrap(ctx, md.Path) 1870 1871 if md.PermissionSet.ListFileVersions { 1872 auth, err = fs.getUIDGateway(ctx, md.Owner) 1873 if err != nil { 1874 return nil, err 1875 } 1876 } else { 1877 return nil, errtypes.PermissionDenied("eosfs: user doesn't have permissions to list revisions") 1878 } 1879 } else { 1880 fn, auth, err = fs.resolveRefForbidShareFolder(ctx, ref) 1881 if err != nil { 1882 return nil, err 1883 } 1884 } 1885 1886 eosRevisions, err := fs.c.ListVersions(ctx, auth, fn) 1887 if err != nil { 1888 return nil, errors.Wrap(err, "eosfs: error listing versions") 1889 } 1890 revisions := []*provider.FileVersion{} 1891 for _, eosRev := range eosRevisions { 1892 if rev, err := fs.convertToRevision(ctx, eosRev); err == nil { 1893 revisions = append(revisions, rev) 1894 } 1895 } 1896 return revisions, nil 1897 } 1898 1899 func (fs *eosfs) DownloadRevision(ctx context.Context, ref *provider.Reference, revisionKey string, openReaderfunc func(*provider.ResourceInfo) bool) (*provider.ResourceInfo, io.ReadCloser, error) { 1900 var auth eosclient.Authorization 1901 var fn string 1902 var err error 1903 1904 md, err := fs.GetMD(ctx, ref, nil, nil) 1905 if err != nil { 1906 return nil, nil, err 1907 } 1908 1909 if !fs.conf.EnableHome && fs.conf.ImpersonateOwnerforRevisions { 1910 // We need to access the revisions for a non-home reference. 1911 // We'll get the owner of the particular resource and impersonate them 1912 // if we have access to it. 1913 1914 fn = fs.wrap(ctx, md.Path) 1915 if md.PermissionSet.InitiateFileDownload { 1916 auth, err = fs.getUIDGateway(ctx, md.Owner) 1917 if err != nil { 1918 return nil, nil, err 1919 } 1920 } else { 1921 return nil, nil, errtypes.PermissionDenied("eosfs: user doesn't have permissions to download revisions") 1922 } 1923 } else { 1924 fn, auth, err = fs.resolveRefForbidShareFolder(ctx, ref) 1925 if err != nil { 1926 return nil, nil, err 1927 } 1928 } 1929 1930 if !openReaderfunc(md) { 1931 return md, nil, nil 1932 } 1933 1934 reader, err := fs.c.ReadVersion(ctx, auth, fn, revisionKey) 1935 return md, reader, err 1936 } 1937 1938 func (fs *eosfs) RestoreRevision(ctx context.Context, ref *provider.Reference, revisionKey string) error { 1939 var auth eosclient.Authorization 1940 var fn string 1941 var err error 1942 1943 if !fs.conf.EnableHome && fs.conf.ImpersonateOwnerforRevisions { 1944 // We need to access the revisions for a non-home reference. 1945 // We'll get the owner of the particular resource and impersonate them 1946 // if we have access to it. 1947 md, err := fs.GetMD(ctx, ref, nil, nil) 1948 if err != nil { 1949 return err 1950 } 1951 fn = fs.wrap(ctx, md.Path) 1952 1953 if md.PermissionSet.RestoreFileVersion { 1954 auth, err = fs.getUIDGateway(ctx, md.Owner) 1955 if err != nil { 1956 return err 1957 } 1958 } else { 1959 return errtypes.PermissionDenied("eosfs: user doesn't have permissions to restore revisions") 1960 } 1961 } else { 1962 fn, auth, err = fs.resolveRefForbidShareFolder(ctx, ref) 1963 if err != nil { 1964 return err 1965 } 1966 } 1967 1968 return fs.c.RollbackToVersion(ctx, auth, fn, revisionKey) 1969 } 1970 1971 func (fs *eosfs) PurgeRecycleItem(ctx context.Context, ref *provider.Reference, key, relativePath string) error { 1972 return errtypes.NotSupported("eosfs: operation not supported") 1973 } 1974 1975 func (fs *eosfs) EmptyRecycle(ctx context.Context, ref *provider.Reference) error { 1976 u, err := getUser(ctx) 1977 if err != nil { 1978 return errors.Wrap(err, "eosfs: no user in ctx") 1979 } 1980 auth, err := fs.getUserAuth(ctx, u, "") 1981 if err != nil { 1982 return err 1983 } 1984 1985 return fs.c.PurgeDeletedEntries(ctx, auth) 1986 } 1987 1988 func (fs *eosfs) ListRecycle(ctx context.Context, ref *provider.Reference, key, relativePath string) ([]*provider.RecycleItem, error) { 1989 var auth eosclient.Authorization 1990 1991 if !fs.conf.EnableHome && fs.conf.AllowPathRecycleOperations && ref.Path != "/" { 1992 // We need to access the recycle bin for a non-home reference. 1993 // We'll get the owner of the particular resource and impersonate them 1994 // if we have access to it. 1995 md, err := fs.GetMD(ctx, &provider.Reference{Path: ref.Path}, nil, nil) 1996 if err != nil { 1997 return nil, err 1998 } 1999 if md.PermissionSet.ListRecycle { 2000 auth, err = fs.getUIDGateway(ctx, md.Owner) 2001 if err != nil { 2002 return nil, err 2003 } 2004 } else { 2005 return nil, errtypes.PermissionDenied("eosfs: user doesn't have permissions to restore recycled items") 2006 } 2007 } else { 2008 // We just act on the logged-in user's recycle bin 2009 u, err := getUser(ctx) 2010 if err != nil { 2011 return nil, errors.Wrap(err, "eosfs: no user in ctx") 2012 } 2013 auth, err = fs.getUserAuth(ctx, u, "") 2014 if err != nil { 2015 return nil, err 2016 } 2017 } 2018 2019 eosDeletedEntries, err := fs.c.ListDeletedEntries(ctx, auth) 2020 if err != nil { 2021 return nil, errors.Wrap(err, "eosfs: error listing deleted entries") 2022 } 2023 recycleEntries := []*provider.RecycleItem{} 2024 for _, entry := range eosDeletedEntries { 2025 if !fs.conf.ShowHiddenSysFiles { 2026 base := path.Base(entry.RestorePath) 2027 if hiddenReg.MatchString(base) { 2028 continue 2029 } 2030 2031 } 2032 if recycleItem, err := fs.convertToRecycleItem(ctx, entry); err == nil { 2033 recycleEntries = append(recycleEntries, recycleItem) 2034 } 2035 } 2036 return recycleEntries, nil 2037 } 2038 2039 func (fs *eosfs) RestoreRecycleItem(ctx context.Context, ref *provider.Reference, key, relativePath string, restoreRef *provider.Reference) error { 2040 var auth eosclient.Authorization 2041 2042 if !fs.conf.EnableHome && fs.conf.AllowPathRecycleOperations && ref.Path != "/" { 2043 // We need to access the recycle bin for a non-home reference. 2044 // We'll get the owner of the particular resource and impersonate them 2045 // if we have access to it. 2046 md, err := fs.GetMD(ctx, &provider.Reference{Path: ref.Path}, nil, nil) 2047 if err != nil { 2048 return err 2049 } 2050 if md.PermissionSet.RestoreRecycleItem { 2051 auth, err = fs.getUIDGateway(ctx, md.Owner) 2052 if err != nil { 2053 return err 2054 } 2055 } else { 2056 return errtypes.PermissionDenied("eosfs: user doesn't have permissions to restore recycled items") 2057 } 2058 } else { 2059 // We just act on the logged-in user's recycle bin 2060 u, err := getUser(ctx) 2061 if err != nil { 2062 return errors.Wrap(err, "eosfs: no user in ctx") 2063 } 2064 auth, err = fs.getUserAuth(ctx, u, "") 2065 if err != nil { 2066 return err 2067 } 2068 } 2069 2070 return fs.c.RestoreDeletedEntry(ctx, auth, key) 2071 } 2072 2073 func (fs *eosfs) convertToRecycleItem(ctx context.Context, eosDeletedItem *eosclient.DeletedEntry) (*provider.RecycleItem, error) { 2074 path, err := fs.unwrap(ctx, eosDeletedItem.RestorePath) 2075 if err != nil { 2076 return nil, err 2077 } 2078 2079 recycleItem := &provider.RecycleItem{ 2080 Ref: &provider.Reference{Path: path}, 2081 Key: eosDeletedItem.RestoreKey, 2082 Size: eosDeletedItem.Size, 2083 DeletionTime: &types.Timestamp{Seconds: eosDeletedItem.DeletionMTime}, 2084 } 2085 if eosDeletedItem.IsDir { 2086 recycleItem.Type = provider.ResourceType_RESOURCE_TYPE_CONTAINER 2087 } else { 2088 // TODO(labkode): if eos returns more types oin the future we need to map them. 2089 recycleItem.Type = provider.ResourceType_RESOURCE_TYPE_FILE 2090 } 2091 return recycleItem, nil 2092 } 2093 2094 func (fs *eosfs) convertToRevision(ctx context.Context, eosFileInfo *eosclient.FileInfo) (*provider.FileVersion, error) { 2095 md, err := fs.convertToResourceInfo(ctx, eosFileInfo) 2096 if err != nil { 2097 return nil, err 2098 } 2099 revision := &provider.FileVersion{ 2100 Key: path.Base(md.Path), 2101 Size: md.Size, 2102 Mtime: md.Mtime.Seconds, // TODO do we need nanos here? 2103 Etag: md.Etag, 2104 } 2105 return revision, nil 2106 } 2107 2108 func (fs *eosfs) convertToResourceInfo(ctx context.Context, eosFileInfo *eosclient.FileInfo) (*provider.ResourceInfo, error) { 2109 return fs.convert(ctx, eosFileInfo) 2110 } 2111 2112 func (fs *eosfs) convertToFileReference(ctx context.Context, eosFileInfo *eosclient.FileInfo) (*provider.ResourceInfo, error) { 2113 info, err := fs.convert(ctx, eosFileInfo) 2114 if err != nil { 2115 return nil, err 2116 } 2117 info.Type = provider.ResourceType_RESOURCE_TYPE_REFERENCE 2118 val, ok := eosFileInfo.Attrs["reva.target"] 2119 if !ok || val == "" { 2120 return nil, errtypes.InternalError("eosfs: reference does not contain target: target=" + val + " file=" + eosFileInfo.File) 2121 } 2122 info.Target = val 2123 return info, nil 2124 } 2125 2126 // permissionSet returns the permission set for the current user 2127 func (fs *eosfs) permissionSet(ctx context.Context, eosFileInfo *eosclient.FileInfo, owner *userpb.UserId) *provider.ResourcePermissions { 2128 u, ok := ctxpkg.ContextGetUser(ctx) 2129 if !ok || u.Id == nil { 2130 return &provider.ResourcePermissions{ 2131 // no permissions 2132 } 2133 } 2134 2135 if owner != nil && u.Id.OpaqueId == owner.OpaqueId && u.Id.Idp == owner.Idp { 2136 // The logged-in user is the owner but we may be impersonating them 2137 // on behalf of a public share accessor. 2138 2139 // NOTE: This will grant the user full access when the opaque is nil 2140 // it is likely that this can be used for attacks 2141 if u.Opaque != nil { 2142 // FIXME: "editor" and "viewer" are not sufficient anymore, they could have different permissions 2143 // The role names should not be hardcoded any more as they will come from config in the future 2144 if publicShare, ok := u.Opaque.Map["public-share-role"]; ok { 2145 if string(publicShare.Value) == "editor" { 2146 return conversions.NewEditorRole().CS3ResourcePermissions() 2147 } else if string(publicShare.Value) == "uploader" { 2148 return conversions.NewUploaderRole().CS3ResourcePermissions() 2149 } 2150 // Default to viewer role 2151 return conversions.NewViewerRole().CS3ResourcePermissions() 2152 } 2153 } 2154 2155 // owner has all permissions 2156 return conversions.NewManagerRole().CS3ResourcePermissions() 2157 } 2158 2159 auth, err := fs.getUserAuth(ctx, u, eosFileInfo.File) 2160 if err != nil { 2161 return &provider.ResourcePermissions{ 2162 // no permissions 2163 } 2164 } 2165 2166 if eosFileInfo.SysACL == nil { 2167 return &provider.ResourcePermissions{ 2168 // no permissions 2169 } 2170 } 2171 var perm provider.ResourcePermissions 2172 2173 for _, e := range eosFileInfo.SysACL.Entries { 2174 var userInGroup bool 2175 if e.Type == acl.TypeGroup { 2176 for _, g := range u.Groups { 2177 if e.Qualifier == g { 2178 userInGroup = true 2179 break 2180 } 2181 } 2182 } 2183 2184 if (e.Type == acl.TypeUser && e.Qualifier == auth.Role.UID) || (e.Type == acl.TypeLightweight && e.Qualifier == u.Id.OpaqueId) || userInGroup { 2185 mergePermissions(&perm, grants.GetGrantPermissionSet(e.Permissions)) 2186 } 2187 } 2188 2189 return &perm 2190 } 2191 2192 func mergePermissions(l *provider.ResourcePermissions, r *provider.ResourcePermissions) { 2193 l.AddGrant = l.AddGrant || r.AddGrant 2194 l.CreateContainer = l.CreateContainer || r.CreateContainer 2195 l.Delete = l.Delete || r.Delete 2196 l.GetPath = l.GetPath || r.GetPath 2197 l.GetQuota = l.GetQuota || r.GetQuota 2198 l.InitiateFileDownload = l.InitiateFileDownload || r.InitiateFileDownload 2199 l.InitiateFileUpload = l.InitiateFileUpload || r.InitiateFileUpload 2200 l.ListContainer = l.ListContainer || r.ListContainer 2201 l.ListFileVersions = l.ListFileVersions || r.ListFileVersions 2202 l.ListGrants = l.ListGrants || r.ListGrants 2203 l.ListRecycle = l.ListRecycle || r.ListRecycle 2204 l.Move = l.Move || r.Move 2205 l.PurgeRecycle = l.PurgeRecycle || r.PurgeRecycle 2206 l.RemoveGrant = l.RemoveGrant || r.RemoveGrant 2207 l.RestoreFileVersion = l.RestoreFileVersion || r.RestoreFileVersion 2208 l.RestoreRecycleItem = l.RestoreRecycleItem || r.RestoreRecycleItem 2209 l.Stat = l.Stat || r.Stat 2210 l.UpdateGrant = l.UpdateGrant || r.UpdateGrant 2211 l.DenyGrant = l.DenyGrant || r.DenyGrant 2212 } 2213 2214 func (fs *eosfs) convert(ctx context.Context, eosFileInfo *eosclient.FileInfo) (*provider.ResourceInfo, error) { 2215 path, err := fs.unwrap(ctx, eosFileInfo.File) 2216 if err != nil { 2217 return nil, err 2218 } 2219 path = filepath.Join(fs.conf.MountPath, path) 2220 2221 size := eosFileInfo.Size 2222 if eosFileInfo.IsDir { 2223 size = eosFileInfo.TreeSize 2224 } 2225 2226 owner, err := fs.getUserIDGateway(ctx, strconv.FormatUint(eosFileInfo.UID, 10)) 2227 if err != nil { 2228 sublog := appctx.GetLogger(ctx).With().Logger() 2229 sublog.Warn().Uint64("uid", eosFileInfo.UID).Msg("could not lookup userid, leaving empty") 2230 } 2231 2232 var xs provider.ResourceChecksum 2233 if eosFileInfo.XS != nil { 2234 xs.Sum = eosFileInfo.XS.XSSum 2235 switch eosFileInfo.XS.XSType { 2236 case "adler": 2237 xs.Type = provider.ResourceChecksumType_RESOURCE_CHECKSUM_TYPE_ADLER32 2238 default: 2239 xs.Type = provider.ResourceChecksumType_RESOURCE_CHECKSUM_TYPE_INVALID 2240 } 2241 } 2242 2243 // filter 'sys' attrs and the reserved lock 2244 filteredAttrs := make(map[string]string) 2245 for k, v := range eosFileInfo.Attrs { 2246 if !strings.HasPrefix(k, "sys") { 2247 filteredAttrs[k] = v 2248 } 2249 } 2250 2251 info := &provider.ResourceInfo{ 2252 Id: &provider.ResourceId{OpaqueId: fmt.Sprintf("%d", eosFileInfo.Inode)}, 2253 Path: path, 2254 Owner: owner, 2255 Etag: fmt.Sprintf("\"%s\"", strings.Trim(eosFileInfo.ETag, "\"")), 2256 MimeType: mime.Detect(eosFileInfo.IsDir, path), 2257 Size: size, 2258 ParentId: &provider.ResourceId{OpaqueId: fmt.Sprintf("%d", eosFileInfo.FID)}, 2259 PermissionSet: fs.permissionSet(ctx, eosFileInfo, owner), 2260 Checksum: &xs, 2261 Type: getResourceType(eosFileInfo.IsDir), 2262 Mtime: &types.Timestamp{ 2263 Seconds: eosFileInfo.MTimeSec, 2264 Nanos: eosFileInfo.MTimeNanos, 2265 }, 2266 Opaque: &types.Opaque{ 2267 Map: map[string]*types.OpaqueEntry{ 2268 "eos": { 2269 Decoder: "json", 2270 Value: fs.getEosMetadata(eosFileInfo), 2271 }, 2272 }, 2273 }, 2274 ArbitraryMetadata: &provider.ArbitraryMetadata{ 2275 Metadata: filteredAttrs, 2276 }, 2277 } 2278 2279 if eosFileInfo.IsDir { 2280 info.Opaque.Map["disable_tus"] = &types.OpaqueEntry{ 2281 Decoder: "plain", 2282 Value: []byte("true"), 2283 } 2284 } 2285 2286 return info, nil 2287 } 2288 2289 func getResourceType(isDir bool) provider.ResourceType { 2290 if isDir { 2291 return provider.ResourceType_RESOURCE_TYPE_CONTAINER 2292 } 2293 return provider.ResourceType_RESOURCE_TYPE_FILE 2294 } 2295 2296 func (fs *eosfs) extractUIDAndGID(u *userpb.User) (eosclient.Authorization, error) { 2297 if u.UidNumber == 0 { 2298 return eosclient.Authorization{}, errors.New("eosfs: uid missing for user") 2299 } 2300 if u.GidNumber == 0 { 2301 return eosclient.Authorization{}, errors.New("eosfs: gid missing for user") 2302 } 2303 return eosclient.Authorization{Role: eosclient.Role{UID: strconv.FormatInt(u.UidNumber, 10), GID: strconv.FormatInt(u.GidNumber, 10)}}, nil 2304 } 2305 2306 func (fs *eosfs) getUIDGateway(ctx context.Context, u *userpb.UserId) (eosclient.Authorization, error) { 2307 log := appctx.GetLogger(ctx) 2308 if userIDInterface, err := fs.userIDCache.Get(u.OpaqueId); err == nil { 2309 log.Debug().Msg("eosfs: found cached user " + u.OpaqueId) 2310 return fs.extractUIDAndGID(userIDInterface.(*userpb.User)) 2311 } 2312 2313 client, err := pool.GetGatewayServiceClient(fs.conf.GatewaySvc) 2314 if err != nil { 2315 return eosclient.Authorization{}, errors.Wrap(err, "eosfs: error getting gateway grpc client") 2316 } 2317 getUserResp, err := client.GetUser(ctx, &userpb.GetUserRequest{ 2318 UserId: u, 2319 SkipFetchingUserGroups: true, 2320 }) 2321 if err != nil { 2322 _ = fs.userIDCache.SetWithTTL(u.OpaqueId, &userpb.User{}, 12*time.Hour) 2323 return eosclient.Authorization{}, errors.Wrap(err, "eosfs: error getting user") 2324 } 2325 if getUserResp.Status.Code != rpc.Code_CODE_OK { 2326 _ = fs.userIDCache.SetWithTTL(u.OpaqueId, &userpb.User{}, 12*time.Hour) 2327 return eosclient.Authorization{}, status.NewErrorFromCode(getUserResp.Status.Code, "eosfs") 2328 } 2329 2330 _ = fs.userIDCache.Set(u.OpaqueId, getUserResp.User) 2331 return fs.extractUIDAndGID(getUserResp.User) 2332 } 2333 2334 func (fs *eosfs) getUserIDGateway(ctx context.Context, uid string) (*userpb.UserId, error) { 2335 log := appctx.GetLogger(ctx) 2336 // Handle the case of root 2337 if uid == "0" { 2338 return nil, errtypes.BadRequest("eosfs: cannot return root user") 2339 } 2340 2341 if userIDInterface, err := fs.userIDCache.Get(uid); err == nil { 2342 log.Debug().Msg("eosfs: found cached uid " + uid) 2343 return userIDInterface.(*userpb.UserId), nil 2344 } 2345 2346 log.Debug().Msg("eosfs: retrieving user from gateway for uid " + uid) 2347 client, err := pool.GetGatewayServiceClient(fs.conf.GatewaySvc) 2348 if err != nil { 2349 return nil, errors.Wrap(err, "eosfs: error getting gateway grpc client") 2350 } 2351 getUserResp, err := client.GetUserByClaim(ctx, &userpb.GetUserByClaimRequest{ 2352 Claim: "uid", 2353 Value: uid, 2354 SkipFetchingUserGroups: true, 2355 }) 2356 if err != nil { 2357 // Insert an empty object in the cache so that we don't make another call 2358 // for a specific amount of time 2359 _ = fs.userIDCache.SetWithTTL(uid, &userpb.UserId{}, 12*time.Hour) 2360 return nil, errors.Wrap(err, "eosfs: error getting user") 2361 } 2362 if getUserResp.Status.Code != rpc.Code_CODE_OK { 2363 // Insert an empty object in the cache so that we don't make another call 2364 // for a specific amount of time 2365 _ = fs.userIDCache.SetWithTTL(uid, &userpb.UserId{}, 12*time.Hour) 2366 return nil, status.NewErrorFromCode(getUserResp.Status.Code, "eosfs") 2367 } 2368 2369 _ = fs.userIDCache.Set(uid, getUserResp.User.Id) 2370 return getUserResp.User.Id, nil 2371 } 2372 2373 func (fs *eosfs) getUserAuth(ctx context.Context, u *userpb.User, fn string) (eosclient.Authorization, error) { 2374 if fs.conf.ForceSingleUserMode { 2375 if fs.singleUserAuth.Role.UID != "" && fs.singleUserAuth.Role.GID != "" { 2376 return fs.singleUserAuth, nil 2377 } 2378 var err error 2379 fs.singleUserAuth, err = fs.getUIDGateway(ctx, &userpb.UserId{OpaqueId: fs.conf.SingleUsername}) 2380 return fs.singleUserAuth, err 2381 } 2382 2383 if u.Id.Type == userpb.UserType_USER_TYPE_LIGHTWEIGHT || 2384 u.Id.Type == userpb.UserType_USER_TYPE_FEDERATED { 2385 return fs.getEOSToken(ctx, u, fn) 2386 } 2387 2388 return fs.extractUIDAndGID(u) 2389 } 2390 2391 func (fs *eosfs) getEOSToken(ctx context.Context, u *userpb.User, fn string) (eosclient.Authorization, error) { 2392 if fn == "" { 2393 return eosclient.Authorization{}, errtypes.BadRequest("eosfs: path cannot be empty") 2394 } 2395 2396 rootAuth, err := fs.getRootAuth(ctx) 2397 if err != nil { 2398 return eosclient.Authorization{}, err 2399 } 2400 info, err := fs.c.GetFileInfoByPath(ctx, rootAuth, fn) 2401 if err != nil { 2402 return eosclient.Authorization{}, err 2403 } 2404 auth := eosclient.Authorization{ 2405 Role: eosclient.Role{ 2406 UID: strconv.FormatUint(info.UID, 10), 2407 GID: strconv.FormatUint(info.GID, 10), 2408 }, 2409 } 2410 2411 perm := "rwx" 2412 for _, e := range info.SysACL.Entries { 2413 if e.Type == acl.TypeLightweight && e.Qualifier == u.Id.OpaqueId { 2414 perm = e.Permissions 2415 break 2416 } 2417 } 2418 2419 p := path.Clean(fn) 2420 for p != "." && p != fs.conf.Namespace { 2421 key := p + "!" + perm 2422 if tknIf, err := fs.tokenCache.Get(key); err == nil { 2423 return eosclient.Authorization{Token: tknIf.(string)}, nil 2424 } 2425 p = path.Dir(p) 2426 } 2427 2428 if info.IsDir { 2429 // EOS expects directories to have a trailing slash when generating tokens 2430 fn = path.Clean(fn) + "/" 2431 } 2432 tkn, err := fs.c.GenerateToken(ctx, auth, fn, &acl.Entry{Permissions: perm}) 2433 if err != nil { 2434 return eosclient.Authorization{}, err 2435 } 2436 2437 key := path.Clean(fn) + "!" + perm 2438 _ = fs.tokenCache.SetWithExpire(key, tkn, time.Second*time.Duration(fs.conf.TokenExpiry)) 2439 2440 return eosclient.Authorization{Token: tkn}, nil 2441 } 2442 2443 func (fs *eosfs) getRootAuth(ctx context.Context) (eosclient.Authorization, error) { 2444 if fs.conf.ForceSingleUserMode { 2445 if fs.singleUserAuth.Role.UID != "" && fs.singleUserAuth.Role.GID != "" { 2446 return fs.singleUserAuth, nil 2447 } 2448 var err error 2449 fs.singleUserAuth, err = fs.getUIDGateway(ctx, &userpb.UserId{OpaqueId: fs.conf.SingleUsername}) 2450 return fs.singleUserAuth, err 2451 } 2452 return eosclient.Authorization{Role: eosclient.Role{UID: "0", GID: "0"}}, nil 2453 } 2454 2455 type eosSysMetadata struct { 2456 TreeSize uint64 `json:"tree_size"` 2457 TreeCount uint64 `json:"tree_count"` 2458 File string `json:"file"` 2459 Instance string `json:"instance"` 2460 } 2461 2462 func (fs *eosfs) getEosMetadata(finfo *eosclient.FileInfo) []byte { 2463 sys := &eosSysMetadata{ 2464 File: finfo.File, 2465 Instance: finfo.Instance, 2466 } 2467 2468 if finfo.IsDir { 2469 sys.TreeCount = finfo.TreeCount 2470 sys.TreeSize = finfo.TreeSize 2471 } 2472 2473 v, _ := json.Marshal(sys) 2474 return v 2475 } 2476 2477 /* 2478 Merge shadow on requests for /home ? 2479 2480 No - GetHome(ctx context.Context) (string, error) 2481 No -CreateHome(ctx context.Context) error 2482 No - CreateDir(ctx context.Context, fn string) error 2483 No -Delete(ctx context.Context, ref *provider.Reference) error 2484 No -Move(ctx context.Context, oldRef, newRef *provider.Reference) error 2485 No -GetMD(ctx context.Context, ref *provider.Reference) (*provider.ResourceInfo, error) 2486 Yes -ListFolder(ctx context.Context, ref *provider.Reference) ([]*provider.ResourceInfo, error) 2487 No -Upload(ctx context.Context, ref *provider.Reference, r io.ReadCloser) error 2488 No -Download(ctx context.Context, ref *provider.Reference) (io.ReadCloser, error) 2489 No -ListRevisions(ctx context.Context, ref *provider.Reference) ([]*provider.FileVersion, error) 2490 No -DownloadRevision(ctx context.Context, ref *provider.Reference, key string) (io.ReadCloser, error) 2491 No -RestoreRevision(ctx context.Context, ref *provider.Reference, key string) error 2492 No ListRecycle(ctx context.Context) ([]*provider.RecycleItem, error) 2493 No RestoreRecycleItem(ctx context.Context, key string) error 2494 No PurgeRecycleItem(ctx context.Context, key string) error 2495 No EmptyRecycle(ctx context.Context) error 2496 ? GetPathByID(ctx context.Context, id *provider.Reference) (string, error) 2497 No AddGrant(ctx context.Context, ref *provider.Reference, g *provider.Grant) error 2498 No RemoveGrant(ctx context.Context, ref *provider.Reference, g *provider.Grant) error 2499 No UpdateGrant(ctx context.Context, ref *provider.Reference, g *provider.Grant) error 2500 No ListGrants(ctx context.Context, ref *provider.Reference) ([]*provider.Grant, error) 2501 No GetQuota(ctx context.Context) (int, int, error) 2502 No CreateReference(ctx context.Context, path string, targetURI *url.URL) error 2503 No Shutdown(ctx context.Context) error 2504 No SetArbitraryMetadata(ctx context.Context, ref *provider.Reference, md *provider.ArbitraryMetadata) error 2505 No UnsetArbitraryMetadata(ctx context.Context, ref *provider.Reference, keys []string) error 2506 */ 2507 2508 /* 2509 Merge shadow on requests for /home/MyShares ? 2510 2511 No - GetHome(ctx context.Context) (string, error) 2512 No -CreateHome(ctx context.Context) error 2513 No - CreateDir(ctx context.Context, fn string) error 2514 Maybe -Delete(ctx context.Context, ref *provider.Reference) error 2515 No -Move(ctx context.Context, oldRef, newRef *provider.Reference) error 2516 Yes -GetMD(ctx context.Context, ref *provider.Reference) (*provider.ResourceInfo, error) 2517 Yes -ListFolder(ctx context.Context, ref *provider.Reference) ([]*provider.ResourceInfo, error) 2518 No -Upload(ctx context.Context, ref *provider.Reference, r io.ReadCloser) error 2519 No -Download(ctx context.Context, ref *provider.Reference) (io.ReadCloser, error) 2520 No -ListRevisions(ctx context.Context, ref *provider.Reference) ([]*provider.FileVersion, error) 2521 No -DownloadRevision(ctx context.Context, ref *provider.Reference, key string) (io.ReadCloser, error) 2522 No -RestoreRevision(ctx context.Context, ref *provider.Reference, key string) error 2523 No ListRecycle(ctx context.Context) ([]*provider.RecycleItem, error) 2524 No RestoreRecycleItem(ctx context.Context, key string) error 2525 No PurgeRecycleItem(ctx context.Context, key string) error 2526 No EmptyRecycle(ctx context.Context) error 2527 ? GetPathByID(ctx context.Context, id *provider.Reference) (string, error) 2528 No AddGrant(ctx context.Context, ref *provider.Reference, g *provider.Grant) error 2529 No RemoveGrant(ctx context.Context, ref *provider.Reference, g *provider.Grant) error 2530 No UpdateGrant(ctx context.Context, ref *provider.Reference, g *provider.Grant) error 2531 No ListGrants(ctx context.Context, ref *provider.Reference) ([]*provider.Grant, error) 2532 No GetQuota(ctx context.Context) (int, int, error) 2533 No CreateReference(ctx context.Context, path string, targetURI *url.URL) error 2534 No Shutdown(ctx context.Context) error 2535 No SetArbitraryMetadata(ctx context.Context, ref *provider.Reference, md *provider.ArbitraryMetadata) error 2536 No UnsetArbitraryMetadata(ctx context.Context, ref *provider.Reference, keys []string) error 2537 */ 2538 2539 /* 2540 Merge shadow on requests for /home/MyShares/file-reference ? 2541 2542 No - GetHome(ctx context.Context) (string, error) 2543 No -CreateHome(ctx context.Context) error 2544 No - CreateDir(ctx context.Context, fn string) error 2545 Maybe -Delete(ctx context.Context, ref *provider.Reference) error 2546 Yes -Move(ctx context.Context, oldRef, newRef *provider.Reference) error 2547 Yes -GetMD(ctx context.Context, ref *provider.Reference) (*provider.ResourceInfo, error) 2548 No -ListFolder(ctx context.Context, ref *provider.Reference) ([]*provider.ResourceInfo, error) 2549 No -Upload(ctx context.Context, ref *provider.Reference, r io.ReadCloser) error 2550 No -Download(ctx context.Context, ref *provider.Reference) (io.ReadCloser, error) 2551 No -ListRevisions(ctx context.Context, ref *provider.Reference) ([]*provider.FileVersion, error) 2552 No -DownloadRevision(ctx context.Context, ref *provider.Reference, key string) (io.ReadCloser, error) 2553 No -RestoreRevision(ctx context.Context, ref *provider.Reference, key string) error 2554 No ListRecycle(ctx context.Context) ([]*provider.RecycleItem, error) 2555 No RestoreRecycleItem(ctx context.Context, key string) error 2556 No PurgeRecycleItem(ctx context.Context, key string) error 2557 No EmptyRecycle(ctx context.Context) error 2558 ? GetPathByID(ctx context.Context, id *provider.Reference) (string, error) 2559 No AddGrant(ctx context.Context, ref *provider.Reference, g *provider.Grant) error 2560 No RemoveGrant(ctx context.Context, ref *provider.Reference, g *provider.Grant) error 2561 No UpdateGrant(ctx context.Context, ref *provider.Reference, g *provider.Grant) error 2562 No ListGrants(ctx context.Context, ref *provider.Reference) ([]*provider.Grant, error) 2563 No GetQuota(ctx context.Context) (int, int, error) 2564 No CreateReference(ctx context.Context, path string, targetURI *url.URL) error 2565 No Shutdown(ctx context.Context) error 2566 Maybe SetArbitraryMetadata(ctx context.Context, ref *provider.Reference, md *provider.ArbitraryMetadata) error 2567 Maybe UnsetArbitraryMetadata(ctx context.Context, ref *provider.Reference, keys []string) error 2568 */