github.com/cs3org/reva/v2@v2.27.7/pkg/storage/utils/decomposedfs/node/node.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 node 20 21 import ( 22 "context" 23 "crypto/md5" 24 "crypto/sha1" 25 "encoding/hex" 26 "encoding/json" 27 "fmt" 28 "hash" 29 "hash/adler32" 30 "io" 31 "os" 32 "path/filepath" 33 "strconv" 34 "strings" 35 "time" 36 37 userpb "github.com/cs3org/go-cs3apis/cs3/identity/user/v1beta1" 38 provider "github.com/cs3org/go-cs3apis/cs3/storage/provider/v1beta1" 39 types "github.com/cs3org/go-cs3apis/cs3/types/v1beta1" 40 "github.com/cs3org/reva/v2/internal/grpc/services/storageprovider" 41 "github.com/cs3org/reva/v2/pkg/appctx" 42 ctxpkg "github.com/cs3org/reva/v2/pkg/ctx" 43 "github.com/cs3org/reva/v2/pkg/errtypes" 44 "github.com/cs3org/reva/v2/pkg/mime" 45 "github.com/cs3org/reva/v2/pkg/rhttp/datatx/metrics" 46 "github.com/cs3org/reva/v2/pkg/storage/utils/ace" 47 "github.com/cs3org/reva/v2/pkg/storage/utils/decomposedfs/metadata" 48 "github.com/cs3org/reva/v2/pkg/storage/utils/decomposedfs/metadata/prefixes" 49 "github.com/cs3org/reva/v2/pkg/storage/utils/grants" 50 "github.com/cs3org/reva/v2/pkg/utils" 51 "github.com/google/uuid" 52 "github.com/pkg/errors" 53 "github.com/rogpeppe/go-internal/lockedfile" 54 "go.opentelemetry.io/otel" 55 "go.opentelemetry.io/otel/trace" 56 ) 57 58 var tracer trace.Tracer 59 60 func init() { 61 tracer = otel.Tracer("github.com/cs3org/reva/pkg/storage/utils/decomposedfs/node") 62 } 63 64 // Define keys and values used in the node metadata 65 const ( 66 LockdiscoveryKey = "lockdiscovery" 67 FavoriteKey = "http://owncloud.org/ns/favorite" 68 ShareTypesKey = "http://owncloud.org/ns/share-types" 69 ChecksumsKey = "http://owncloud.org/ns/checksums" 70 UserShareType = "0" 71 QuotaKey = "quota" 72 73 QuotaUnlimited = "0" 74 QuotaUncalculated = "-1" 75 QuotaUnknown = "-2" 76 77 // TrashIDDelimiter represents the characters used to separate the nodeid and the deletion time. 78 TrashIDDelimiter = ".T." 79 RevisionIDDelimiter = ".REV." 80 81 // RootID defines the root node's ID 82 RootID = "root" 83 84 // ProcessingStatus is the name of the status when processing a file 85 ProcessingStatus = "processing:" 86 ) 87 88 type TimeManager interface { 89 // OverrideMTime overrides the mtime of the node, either on the node itself or in the given attributes, depending on the implementation 90 OverrideMtime(ctx context.Context, n *Node, attrs *Attributes, mtime time.Time) error 91 92 // MTime returns the mtime of the node 93 MTime(ctx context.Context, n *Node) (time.Time, error) 94 // SetMTime sets the mtime of the node 95 SetMTime(ctx context.Context, n *Node, mtime *time.Time) error 96 97 // TMTime returns the tmtime of the node 98 TMTime(ctx context.Context, n *Node) (time.Time, error) 99 // SetTMTime sets the tmtime of the node 100 SetTMTime(ctx context.Context, n *Node, tmtime *time.Time) error 101 102 // CTime returns the ctime of the node 103 CTime(ctx context.Context, n *Node) (time.Time, error) 104 105 // DTime returns the deletion time of the node 106 DTime(ctx context.Context, n *Node) (time.Time, error) 107 // SetDTime sets the deletion time of the node 108 SetDTime(ctx context.Context, n *Node, mtime *time.Time) error 109 } 110 111 // Tree is used to manage a tree hierarchy 112 type Tree interface { 113 Setup() error 114 115 GetMD(ctx context.Context, node *Node) (os.FileInfo, error) 116 ListFolder(ctx context.Context, node *Node) ([]*Node, error) 117 // CreateHome(owner *userpb.UserId) (n *Node, err error) 118 CreateDir(ctx context.Context, node *Node) (err error) 119 TouchFile(ctx context.Context, node *Node, markprocessing bool, mtime string) error 120 // CreateReference(ctx context.Context, node *Node, targetURI *url.URL) error 121 Move(ctx context.Context, oldNode *Node, newNode *Node) (err error) 122 Delete(ctx context.Context, node *Node) (err error) 123 RestoreRecycleItemFunc(ctx context.Context, spaceid, key, trashPath string, target *Node) (*Node, *Node, func() error, error) 124 PurgeRecycleItemFunc(ctx context.Context, spaceid, key, purgePath string) (*Node, func() error, error) 125 126 InitNewNode(ctx context.Context, n *Node, fsize uint64) (metadata.UnlockFunc, error) 127 128 WriteBlob(node *Node, source string) error 129 ReadBlob(node *Node) (io.ReadCloser, error) 130 DeleteBlob(node *Node) error 131 132 BuildSpaceIDIndexEntry(spaceID, nodeID string) string 133 ResolveSpaceIDIndexEntry(spaceID, entry string) (string, string, error) 134 135 Propagate(ctx context.Context, node *Node, sizeDiff int64) (err error) 136 } 137 138 // PathLookup defines the interface for the lookup component 139 type PathLookup interface { 140 NodeFromSpaceID(ctx context.Context, spaceID string) (n *Node, err error) 141 NodeFromResource(ctx context.Context, ref *provider.Reference) (*Node, error) 142 NodeFromID(ctx context.Context, id *provider.ResourceId) (n *Node, err error) 143 144 NodeIDFromParentAndName(ctx context.Context, n *Node, name string) (string, error) 145 146 GenerateSpaceID(spaceType string, owner *userpb.User) (string, error) 147 148 InternalRoot() string 149 InternalPath(spaceID, nodeID string) string 150 Path(ctx context.Context, n *Node, hasPermission PermissionFunc) (path string, err error) 151 MetadataBackend() metadata.Backend 152 TimeManager() TimeManager 153 ReadBlobIDAndSizeAttr(ctx context.Context, path string, attrs Attributes) (string, int64, error) 154 TypeFromPath(ctx context.Context, path string) provider.ResourceType 155 CopyMetadataWithSourceLock(ctx context.Context, sourcePath, targetPath string, filter func(attributeName string, value []byte) (newValue []byte, copy bool), lockedSource *lockedfile.File, acquireTargetLock bool) (err error) 156 CopyMetadata(ctx context.Context, src, target string, filter func(attributeName string, value []byte) (newValue []byte, copy bool), acquireTargetLock bool) (err error) 157 } 158 159 type IDCacher interface { 160 CacheID(ctx context.Context, spaceID, nodeID, val string) error 161 GetCachedID(ctx context.Context, spaceID, nodeID string) (string, bool) 162 } 163 164 // Node represents a node in the tree and provides methods to get a Parent or Child instance 165 type Node struct { 166 SpaceID string 167 ParentID string 168 ID string 169 Name string 170 Blobsize int64 171 BlobID string 172 owner *userpb.UserId 173 Exists bool 174 SpaceRoot *Node 175 176 lu PathLookup 177 xattrsCache map[string][]byte 178 nodeType *provider.ResourceType 179 } 180 181 // New returns a new instance of Node 182 func New(spaceID, id, parentID, name string, blobsize int64, blobID string, t provider.ResourceType, owner *userpb.UserId, lu PathLookup) *Node { 183 if blobID == "" { 184 blobID = uuid.New().String() 185 } 186 return &Node{ 187 SpaceID: spaceID, 188 ID: id, 189 ParentID: parentID, 190 Name: name, 191 Blobsize: blobsize, 192 owner: owner, 193 lu: lu, 194 BlobID: blobID, 195 nodeType: &t, 196 } 197 } 198 199 func (n *Node) MarshalJSON() ([]byte, error) { 200 return json.Marshal(&struct { 201 Name string `json:"name"` 202 ID string `json:"id"` 203 SpaceID string `json:"spaceID"` 204 ParentID string `json:"parentID"` 205 BlobID string `json:"blobID"` 206 BlobSize int64 `json:"blobSize"` 207 Exists bool `json:"exists"` 208 }{ 209 Name: n.Name, 210 ID: n.ID, 211 SpaceID: n.SpaceID, 212 ParentID: n.ParentID, 213 BlobID: n.BlobID, 214 BlobSize: n.Blobsize, 215 Exists: n.Exists, 216 }) 217 } 218 219 // Type returns the node's resource type 220 func (n *Node) Type(ctx context.Context) provider.ResourceType { 221 _, span := tracer.Start(ctx, "Type") 222 defer span.End() 223 if n.nodeType != nil { 224 return *n.nodeType 225 } 226 227 t := provider.ResourceType_RESOURCE_TYPE_INVALID 228 229 // Try to read from xattrs 230 typeAttr, err := n.XattrInt32(ctx, prefixes.TypeAttr) 231 if err == nil { 232 t = provider.ResourceType(typeAttr) 233 n.nodeType = &t 234 return t 235 } 236 237 // Fall back to checking on disk 238 fi, err := os.Lstat(n.InternalPath()) 239 if err != nil { 240 return t 241 } 242 243 switch { 244 case fi.IsDir(): 245 if _, err = n.Xattr(ctx, prefixes.ReferenceAttr); err == nil { 246 t = provider.ResourceType_RESOURCE_TYPE_REFERENCE 247 } else { 248 t = provider.ResourceType_RESOURCE_TYPE_CONTAINER 249 } 250 case fi.Mode().IsRegular(): 251 t = provider.ResourceType_RESOURCE_TYPE_FILE 252 case fi.Mode()&os.ModeSymlink != 0: 253 t = provider.ResourceType_RESOURCE_TYPE_SYMLINK 254 // TODO reference using ext attr on a symlink 255 // nodeType = provider.ResourceType_RESOURCE_TYPE_REFERENCE 256 } 257 n.nodeType = &t 258 return t 259 } 260 261 // SetType sets the type of the node. 262 func (n *Node) SetType(t provider.ResourceType) { 263 n.nodeType = &t 264 } 265 266 // NodeMetadata writes the Node metadata to disk and allows passing additional attributes 267 func (n *Node) NodeMetadata(ctx context.Context) Attributes { 268 attribs := Attributes{} 269 attribs.SetInt64(prefixes.TypeAttr, int64(n.Type(ctx))) 270 attribs.SetString(prefixes.ParentidAttr, n.ParentID) 271 attribs.SetString(prefixes.NameAttr, n.Name) 272 if n.Type(ctx) == provider.ResourceType_RESOURCE_TYPE_FILE { 273 attribs.SetString(prefixes.BlobIDAttr, n.BlobID) 274 attribs.SetInt64(prefixes.BlobsizeAttr, n.Blobsize) 275 } 276 return attribs 277 } 278 279 // SetOwner sets the space owner on the node 280 func (n *Node) SetOwner(owner *userpb.UserId) { 281 n.SpaceRoot.owner = owner 282 } 283 284 // SpaceOwnerOrManager returns the space owner of the space. If no owner is set 285 // one of the space managers is returned instead. 286 func (n *Node) SpaceOwnerOrManager(ctx context.Context) *userpb.UserId { 287 owner := n.Owner() 288 if owner != nil && owner.Type != userpb.UserType_USER_TYPE_SPACE_OWNER { 289 return owner 290 } 291 292 // We don't have an owner set. Find a manager instead. 293 grants, err := n.SpaceRoot.ListGrants(ctx) 294 if err != nil { 295 return nil 296 } 297 for _, grant := range grants { 298 if grant.Permissions.Stat && grant.Permissions.ListContainer && grant.Permissions.InitiateFileDownload { 299 return grant.GetGrantee().GetUserId() 300 } 301 } 302 303 return nil 304 } 305 306 // ReadNode creates a new instance from an id and checks if it exists 307 func ReadNode(ctx context.Context, lu PathLookup, spaceID, nodeID string, canListDisabledSpace bool, spaceRoot *Node, skipParentCheck bool) (*Node, error) { 308 ctx, span := tracer.Start(ctx, "ReadNode") 309 defer span.End() 310 var err error 311 312 if spaceRoot == nil { 313 // read space root 314 spaceRoot = &Node{ 315 SpaceID: spaceID, 316 lu: lu, 317 ID: spaceID, 318 } 319 spaceRoot.SpaceRoot = spaceRoot 320 spaceRoot.owner, err = spaceRoot.readOwner(ctx) 321 switch { 322 case metadata.IsNotExist(err): 323 return spaceRoot, nil // swallow not found, the node defaults to exists = false 324 case err != nil: 325 return nil, err 326 } 327 spaceRoot.Exists = true 328 329 // lookup name in extended attributes 330 spaceRoot.Name, err = spaceRoot.XattrString(ctx, prefixes.NameAttr) 331 if err != nil { 332 return nil, err 333 } 334 } 335 336 // TODO ReadNode should not check permissions 337 if !canListDisabledSpace && spaceRoot.IsDisabled(ctx) { 338 // no permission = not found 339 return nil, errtypes.NotFound(spaceID) 340 } 341 342 // if current user cannot stat the root return not found? 343 // no for shares the root might be a different resource 344 345 // check if this is a space root 346 if spaceID == nodeID { 347 return spaceRoot, nil 348 } 349 350 // are we reading a revision? 351 revisionSuffix := "" 352 if strings.Contains(nodeID, RevisionIDDelimiter) { 353 // verify revision key format 354 kp := strings.SplitN(nodeID, RevisionIDDelimiter, 2) 355 if len(kp) == 2 { 356 // use the actual node for the metadata lookup 357 nodeID = kp[0] 358 // remember revision for blob metadata 359 revisionSuffix = RevisionIDDelimiter + kp[1] 360 } 361 } 362 363 // read node 364 n := &Node{ 365 SpaceID: spaceID, 366 lu: lu, 367 ID: nodeID, 368 SpaceRoot: spaceRoot, 369 } 370 nodePath := n.InternalPath() 371 372 // append back revision to nodeid, even when returning a not existing node 373 defer func() { 374 // when returning errors n is nil 375 if n != nil { 376 n.ID += revisionSuffix 377 } 378 }() 379 380 attrs, err := n.Xattrs(ctx) 381 switch { 382 case metadata.IsNotExist(err): 383 return n, nil // swallow not found, the node defaults to exists = false 384 case err != nil: 385 return nil, err 386 } 387 n.Exists = true 388 389 n.Name = attrs.String(prefixes.NameAttr) 390 n.ParentID = attrs.String(prefixes.ParentidAttr) 391 if n.ParentID == "" { 392 d, _ := os.ReadFile(lu.MetadataBackend().MetadataPath(n.InternalPath())) 393 if _, ok := lu.MetadataBackend().(metadata.MessagePackBackend); ok { 394 appctx.GetLogger(ctx).Error().Str("path", n.InternalPath()).Str("nodeid", n.ID).Interface("attrs", attrs).Bytes("messagepack", d).Msg("missing parent id") 395 } 396 return nil, errtypes.InternalError("Missing parent ID on node") 397 } 398 399 if revisionSuffix == "" { 400 n.BlobID, n.Blobsize, err = lu.ReadBlobIDAndSizeAttr(ctx, nodePath, attrs) 401 if err != nil { 402 return nil, err 403 } 404 } else { 405 n.BlobID, n.Blobsize, err = lu.ReadBlobIDAndSizeAttr(ctx, nodePath+revisionSuffix, nil) 406 if err != nil { 407 return nil, err 408 } 409 } 410 411 return n, nil 412 } 413 414 // Child returns the child node with the given name 415 func (n *Node) Child(ctx context.Context, name string) (*Node, error) { 416 ctx, span := tracer.Start(ctx, "Child") 417 defer span.End() 418 419 spaceID := n.SpaceID 420 if spaceID == "" && n.ParentID == "root" { 421 spaceID = n.ID 422 } else if n.SpaceRoot != nil { 423 spaceID = n.SpaceRoot.ID 424 } 425 c := &Node{ 426 SpaceID: spaceID, 427 lu: n.lu, 428 ParentID: n.ID, 429 Name: name, 430 SpaceRoot: n.SpaceRoot, 431 } 432 433 nodeID, err := n.lu.NodeIDFromParentAndName(ctx, n, name) 434 switch { 435 case metadata.IsNotExist(err) || metadata.IsNotDir(err): 436 return c, nil // if the file does not exist we return a node that has Exists = false 437 case err != nil: 438 return nil, err 439 } 440 441 c, err = ReadNode(ctx, n.lu, spaceID, nodeID, false, n.SpaceRoot, true) 442 if err != nil { 443 return nil, errors.Wrap(err, "could not read child node") 444 } 445 446 return c, nil 447 } 448 449 // ParentWithReader returns the parent node 450 func (n *Node) ParentWithReader(ctx context.Context, r io.Reader) (*Node, error) { 451 _, span := tracer.Start(ctx, "ParentWithReader") 452 defer span.End() 453 if n.ParentID == "" { 454 return nil, fmt.Errorf("decomposedfs: root has no parent") 455 } 456 p := &Node{ 457 SpaceID: n.SpaceID, 458 lu: n.lu, 459 ID: n.ParentID, 460 SpaceRoot: n.SpaceRoot, 461 } 462 463 // fill metadata cache using the reader 464 attrs, err := p.XattrsWithReader(ctx, r) 465 switch { 466 case metadata.IsNotExist(err): 467 return p, nil // swallow not found, the node defaults to exists = false 468 case err != nil: 469 return nil, err 470 } 471 p.Exists = true 472 473 p.Name = attrs.String(prefixes.NameAttr) 474 p.ParentID = attrs.String(prefixes.ParentidAttr) 475 476 return p, err 477 } 478 479 // Parent returns the parent node 480 func (n *Node) Parent(ctx context.Context) (p *Node, err error) { 481 return n.ParentWithReader(ctx, nil) 482 } 483 484 // Owner returns the space owner 485 func (n *Node) Owner() *userpb.UserId { 486 return n.SpaceRoot.owner 487 } 488 489 // readOwner reads the owner from the extended attributes of the space root 490 // in case either owner id or owner idp are unset we return an error and an empty owner object 491 func (n *Node) readOwner(ctx context.Context) (*userpb.UserId, error) { 492 owner := &userpb.UserId{} 493 494 // lookup parent id in extended attributes 495 var attr string 496 var err error 497 // lookup ID in extended attributes 498 attr, err = n.SpaceRoot.XattrString(ctx, prefixes.OwnerIDAttr) 499 switch { 500 case err == nil: 501 owner.OpaqueId = attr 502 case metadata.IsAttrUnset(err): 503 // ignore 504 default: 505 return nil, err 506 } 507 508 // lookup IDP in extended attributes 509 attr, err = n.SpaceRoot.XattrString(ctx, prefixes.OwnerIDPAttr) 510 switch { 511 case err == nil: 512 owner.Idp = attr 513 case metadata.IsAttrUnset(err): 514 // ignore 515 default: 516 return nil, err 517 } 518 519 // lookup type in extended attributes 520 attr, err = n.SpaceRoot.XattrString(ctx, prefixes.OwnerTypeAttr) 521 switch { 522 case err == nil: 523 owner.Type = utils.UserTypeMap(attr) 524 case metadata.IsAttrUnset(err): 525 // ignore 526 default: 527 return nil, err 528 } 529 530 // owner is an optional property 531 if owner.Idp == "" && owner.OpaqueId == "" { 532 return nil, nil 533 } 534 return owner, nil 535 } 536 537 // PermissionSet returns the permission set and an accessDenied flag 538 // for the current user 539 // the parent nodes are not taken into account 540 // accessDenied is separate from the resource permissions 541 // because we only support full denials 542 func (n *Node) PermissionSet(ctx context.Context) (*provider.ResourcePermissions, bool) { 543 u, ok := ctxpkg.ContextGetUser(ctx) 544 if !ok { 545 appctx.GetLogger(ctx).Debug().Str("spaceid", n.SpaceID).Str("nodeid", n.ID).Msg("no user in context, returning default permissions") 546 return NoPermissions(), false 547 } 548 if utils.UserEqual(u.Id, n.SpaceRoot.Owner()) { 549 return OwnerPermissions(), false 550 } 551 // read the permissions for the current user from the acls of the current node 552 if np, accessDenied, err := n.ReadUserPermissions(ctx, u); err == nil { 553 return np, accessDenied 554 } 555 // be defensive, we could have access via another grant 556 return NoPermissions(), true 557 } 558 559 // InternalPath returns the internal path of the Node 560 func (n *Node) InternalPath() string { 561 return n.lu.InternalPath(n.SpaceID, n.ID) 562 } 563 564 // ParentPath returns the internal path of the parent of the current node 565 func (n *Node) ParentPath() string { 566 return n.lu.InternalPath(n.SpaceID, n.ParentID) 567 } 568 569 // LockFilePath returns the internal path of the lock file of the node 570 func (n *Node) LockFilePath() string { 571 return n.InternalPath() + ".lock" 572 } 573 574 // CalculateEtag returns a hash of fileid + tmtime (or mtime) 575 func CalculateEtag(id string, tmTime time.Time) (string, error) { 576 h := md5.New() 577 if _, err := io.WriteString(h, id); err != nil { 578 return "", err 579 } 580 /* TODO we could strengthen the etag by adding the blobid, but then all etags would change. we would need a legacy etag check as well 581 if _, err := io.WriteString(h, n.BlobID); err != nil { 582 return "", err 583 } 584 */ 585 if tb, err := tmTime.UTC().MarshalBinary(); err == nil { 586 if _, err := h.Write(tb); err != nil { 587 return "", err 588 } 589 } else { 590 return "", err 591 } 592 return fmt.Sprintf(`"%x"`, h.Sum(nil)), nil 593 } 594 595 // SetMtimeString sets the mtime and atime of a node to the unixtime parsed from the given string 596 func (n *Node) SetMtimeString(ctx context.Context, mtime string) error { 597 mt, err := utils.MTimeToTime(mtime) 598 if err != nil { 599 return err 600 } 601 return n.SetMtime(ctx, &mt) 602 } 603 604 // SetMTime writes the UTC mtime to the extended attributes or removes the attribute if nil is passed 605 func (n *Node) SetMtime(ctx context.Context, t *time.Time) (err error) { 606 if t == nil { 607 return n.RemoveXattr(ctx, prefixes.MTimeAttr, true) 608 } 609 return n.SetXattrString(ctx, prefixes.MTimeAttr, t.UTC().Format(time.RFC3339Nano)) 610 } 611 612 // SetEtag sets the temporary etag of a node if it differs from the current etag 613 func (n *Node) SetEtag(ctx context.Context, val string) (err error) { 614 sublog := appctx.GetLogger(ctx).With().Str("spaceid", n.SpaceID).Str("nodeid", n.ID).Logger() 615 var tmTime time.Time 616 if tmTime, err = n.GetTMTime(ctx); err != nil { 617 return 618 } 619 var etag string 620 if etag, err = CalculateEtag(n.ID, tmTime); err != nil { 621 return 622 } 623 624 // sanitize etag 625 val = fmt.Sprintf("\"%s\"", strings.Trim(val, "\"")) 626 if etag == val { 627 sublog.Debug(). 628 Str("etag", val). 629 Msg("ignoring request to update identical etag") 630 return nil 631 } 632 // etag is only valid until the calculated etag changes, is part of propagation 633 return n.SetXattrString(ctx, prefixes.TmpEtagAttr, val) 634 } 635 636 // SetFavorite sets the favorite for the current user 637 // TODO we should not mess with the user here ... the favorites is now a user specific property for a file 638 // that cannot be mapped to extended attributes without leaking who has marked a file as a favorite 639 // it is a specific case of a tag, which is user individual as well 640 // TODO there are different types of tags 641 // 1. public that are managed by everyone 642 // 2. private tags that are only visible to the user 643 // 3. system tags that are only visible to the system 644 // 4. group tags that are only visible to a group ... 645 // urgh ... well this can be solved using different namespaces 646 // 1. public = p: 647 // 2. private = u:<uid>: for user specific 648 // 3. system = s: for system 649 // 4. group = g:<gid>: 650 // 5. app? = a:<aid>: for apps? 651 // obviously this only is secure when the u/s/g/a namespaces are not accessible by users in the filesystem 652 // public tags can be mapped to extended attributes 653 func (n *Node) SetFavorite(ctx context.Context, uid *userpb.UserId, val string) error { 654 // the favorite flag is specific to the user, so we need to incorporate the userid 655 fa := fmt.Sprintf("%s:%s:%s@%s", prefixes.FavPrefix, utils.UserTypeToString(uid.GetType()), uid.GetOpaqueId(), uid.GetIdp()) 656 return n.SetXattrString(ctx, fa, val) 657 } 658 659 // IsDir returns true if the node is a directory 660 func (n *Node) IsDir(ctx context.Context) bool { 661 attr, _ := n.XattrInt32(ctx, prefixes.TypeAttr) 662 return attr == int32(provider.ResourceType_RESOURCE_TYPE_CONTAINER) 663 } 664 665 // AsResourceInfo return the node as CS3 ResourceInfo 666 func (n *Node) AsResourceInfo(ctx context.Context, rp *provider.ResourcePermissions, mdKeys, fieldMask []string, returnBasename bool) (ri *provider.ResourceInfo, err error) { 667 sublog := appctx.GetLogger(ctx).With().Str("spaceid", n.SpaceID).Str("nodeid", n.ID).Logger() 668 669 var fn string 670 nodeType := n.Type(ctx) 671 672 var target string 673 if nodeType == provider.ResourceType_RESOURCE_TYPE_REFERENCE { 674 target, _ = n.XattrString(ctx, prefixes.ReferenceAttr) 675 } 676 677 id := &provider.ResourceId{SpaceId: n.SpaceID, OpaqueId: n.ID} 678 679 switch { 680 case n.IsSpaceRoot(ctx): 681 fn = "." // space roots do not have a path as they are referencing themselves 682 case returnBasename: 683 fn = n.Name 684 default: 685 fn, err = n.lu.Path(ctx, n, NoCheck) 686 if err != nil { 687 return nil, err 688 } 689 } 690 691 ri = &provider.ResourceInfo{ 692 Id: id, 693 Path: fn, 694 Type: nodeType, 695 MimeType: mime.Detect(nodeType == provider.ResourceType_RESOURCE_TYPE_CONTAINER, fn), 696 Size: uint64(n.Blobsize), 697 Target: target, 698 PermissionSet: rp, 699 Owner: n.Owner(), 700 ParentId: &provider.ResourceId{ 701 SpaceId: n.SpaceID, 702 OpaqueId: n.ParentID, 703 }, 704 Name: n.Name, 705 } 706 707 if n.IsProcessing(ctx) { 708 ri.Opaque = utils.AppendPlainToOpaque(ri.Opaque, "status", "processing") 709 } 710 711 if nodeType == provider.ResourceType_RESOURCE_TYPE_CONTAINER { 712 ts, err := n.GetTreeSize(ctx) 713 if err == nil { 714 ri.Size = ts 715 } else { 716 ri.Size = 0 // make dirs always return 0 if it is unknown 717 sublog.Debug().Err(err).Msg("could not read treesize") 718 } 719 } 720 721 // TODO make etag of files use fileid and checksum 722 723 var tmTime time.Time 724 if tmTime, err = n.GetTMTime(ctx); err != nil { 725 sublog.Debug().Err(err).Msg("could not get tmtime") 726 } 727 728 // use temporary etag if it is set 729 if b, err := n.XattrString(ctx, prefixes.TmpEtagAttr); err == nil && b != "" { 730 ri.Etag = fmt.Sprintf(`"%x"`, b) 731 } else if ri.Etag, err = CalculateEtag(n.ID, tmTime); err != nil { 732 sublog.Debug().Err(err).Msg("could not calculate etag") 733 } 734 735 // mtime uses tmtime if present 736 // TODO expose mtime and tmtime separately? 737 un := tmTime.UnixNano() 738 ri.Mtime = &types.Timestamp{ 739 Seconds: uint64(un / 1000000000), 740 Nanos: uint32(un % 1000000000), 741 } 742 743 mdKeysMap := make(map[string]struct{}) 744 for _, k := range mdKeys { 745 mdKeysMap[k] = struct{}{} 746 } 747 748 var returnAllMetadata bool 749 if _, ok := mdKeysMap["*"]; len(mdKeys) == 0 || ok { 750 returnAllMetadata = true 751 } 752 753 metadata := map[string]string{} 754 755 fieldMaskKeysMap := make(map[string]struct{}) 756 for _, k := range fieldMask { 757 fieldMaskKeysMap[k] = struct{}{} 758 } 759 760 var returnAllFields bool 761 if _, ok := fieldMaskKeysMap["*"]; len(fieldMask) == 0 || ok { 762 returnAllFields = true 763 } 764 765 // read favorite flag for the current user 766 if _, ok := mdKeysMap[FavoriteKey]; returnAllMetadata || ok { 767 favorite := "" 768 if u, ok := ctxpkg.ContextGetUser(ctx); ok { 769 // the favorite flag is specific to the user, so we need to incorporate the userid 770 if uid := u.GetId(); uid != nil { 771 fa := fmt.Sprintf("%s:%s:%s@%s", prefixes.FavPrefix, utils.UserTypeToString(uid.GetType()), uid.GetOpaqueId(), uid.GetIdp()) 772 if val, err := n.XattrString(ctx, fa); err == nil { 773 sublog.Debug(). 774 Str("favorite", fa). 775 Msg("found favorite flag") 776 favorite = val 777 } 778 } else { 779 sublog.Error().Err(errtypes.UserRequired("userrequired")).Msg("user has no id") 780 } 781 } else { 782 sublog.Error().Err(errtypes.UserRequired("userrequired")).Msg("error getting user from ctx") 783 } 784 metadata[FavoriteKey] = favorite 785 } 786 // read locks 787 // FIXME move to fieldmask 788 if _, ok := mdKeysMap[LockdiscoveryKey]; returnAllMetadata || ok { 789 if n.hasLocks(ctx) { 790 err = readLocksIntoOpaque(ctx, n, ri) 791 if err != nil { 792 sublog.Debug().Err(errtypes.InternalError("lockfail")) 793 } 794 } 795 } 796 797 // share indicator 798 if _, ok := fieldMaskKeysMap["share-types"]; returnAllFields || ok { 799 granteeTypes := n.getGranteeTypes(ctx) 800 if len(granteeTypes) > 0 { 801 // TODO add optional property to CS3 ResourceInfo to transport grants? 802 var s strings.Builder 803 first := true 804 for _, t := range granteeTypes { 805 if !first { 806 s.WriteString(",") 807 } else { 808 first = false 809 } 810 s.WriteString(strconv.Itoa(int(t))) 811 } 812 ri.Opaque = utils.AppendPlainToOpaque(ri.Opaque, "share-types", s.String()) 813 } 814 } 815 816 // checksums 817 // FIXME move to fieldmask 818 if _, ok := mdKeysMap[ChecksumsKey]; (nodeType == provider.ResourceType_RESOURCE_TYPE_FILE) && (returnAllMetadata || ok) { 819 // TODO which checksum was requested? sha1 adler32 or md5? for now hardcode sha1? 820 // TODO make ResourceInfo carry multiple checksums 821 n.readChecksumIntoResourceChecksum(ctx, storageprovider.XSSHA1, ri) 822 n.readChecksumIntoOpaque(ctx, storageprovider.XSMD5, ri) 823 n.readChecksumIntoOpaque(ctx, storageprovider.XSAdler32, ri) 824 } 825 // quota 826 // FIXME move to fieldmask 827 if _, ok := mdKeysMap[QuotaKey]; (nodeType == provider.ResourceType_RESOURCE_TYPE_CONTAINER) && returnAllMetadata || ok { 828 if n.SpaceRoot != nil && n.SpaceRoot.InternalPath() != "" { 829 n.SpaceRoot.readQuotaIntoOpaque(ctx, ri) 830 } 831 } 832 833 // only read the requested metadata attributes 834 attrs, err := n.Xattrs(ctx) 835 if err != nil { 836 sublog.Error().Err(err).Msg("error getting list of extended attributes") 837 } else { 838 for key, value := range attrs { 839 // filter out non-custom properties 840 if !strings.HasPrefix(key, prefixes.MetadataPrefix) { 841 continue 842 } 843 // only read when key was requested 844 k := key[len(prefixes.MetadataPrefix):] 845 if _, ok := mdKeysMap[k]; returnAllMetadata || ok { 846 metadata[k] = string(value) 847 } 848 849 } 850 } 851 ri.ArbitraryMetadata = &provider.ArbitraryMetadata{ 852 Metadata: metadata, 853 } 854 855 // add virusscan information 856 if scanned, _, date := n.ScanData(ctx); scanned { 857 ri.Opaque = utils.AppendPlainToOpaque(ri.Opaque, "scantime", date.Format(time.RFC3339Nano)) 858 } 859 860 sublog.Debug(). 861 Interface("ri", ri). 862 Msg("AsResourceInfo") 863 864 return ri, nil 865 } 866 867 func (n *Node) readChecksumIntoResourceChecksum(ctx context.Context, algo string, ri *provider.ResourceInfo) { 868 v, err := n.Xattr(ctx, prefixes.ChecksumPrefix+algo) 869 switch { 870 case err == nil: 871 ri.Checksum = &provider.ResourceChecksum{ 872 Type: storageprovider.PKG2GRPCXS(algo), 873 Sum: hex.EncodeToString(v), 874 } 875 case metadata.IsAttrUnset(err): 876 appctx.GetLogger(ctx).Debug().Str("spaceid", n.SpaceID).Str("nodeid", n.ID).Str("nodepath", n.InternalPath()).Str("algorithm", algo).Msg("checksum not set") 877 default: 878 appctx.GetLogger(ctx).Error().Err(err).Str("spaceid", n.SpaceID).Str("nodeid", n.ID).Str("nodepath", n.InternalPath()).Str("algorithm", algo).Msg("could not read checksum") 879 } 880 } 881 882 func (n *Node) readChecksumIntoOpaque(ctx context.Context, algo string, ri *provider.ResourceInfo) { 883 v, err := n.Xattr(ctx, prefixes.ChecksumPrefix+algo) 884 switch { 885 case err == nil: 886 if ri.Opaque == nil { 887 ri.Opaque = &types.Opaque{ 888 Map: map[string]*types.OpaqueEntry{}, 889 } 890 } 891 ri.Opaque.Map[algo] = &types.OpaqueEntry{ 892 Decoder: "plain", 893 Value: []byte(hex.EncodeToString(v)), 894 } 895 case metadata.IsAttrUnset(err): 896 appctx.GetLogger(ctx).Debug().Str("spaceid", n.SpaceID).Str("nodeid", n.ID).Str("nodepath", n.InternalPath()).Str("algorithm", algo).Msg("checksum not set") 897 default: 898 appctx.GetLogger(ctx).Error().Err(err).Str("spaceid", n.SpaceID).Str("nodeid", n.ID).Str("nodepath", n.InternalPath()).Str("algorithm", algo).Msg("could not read checksum") 899 } 900 } 901 902 // quota is always stored on the root node 903 func (n *Node) readQuotaIntoOpaque(ctx context.Context, ri *provider.ResourceInfo) { 904 v, err := n.XattrString(ctx, prefixes.QuotaAttr) 905 switch { 906 case err == nil: 907 // make sure we have a proper signed int 908 // we use the same magic numbers to indicate: 909 // -1 = uncalculated 910 // -2 = unknown 911 // -3 = unlimited 912 if _, err := strconv.ParseInt(v, 10, 64); err == nil { 913 if ri.Opaque == nil { 914 ri.Opaque = &types.Opaque{ 915 Map: map[string]*types.OpaqueEntry{}, 916 } 917 } 918 ri.Opaque.Map[QuotaKey] = &types.OpaqueEntry{ 919 Decoder: "plain", 920 Value: []byte(v), 921 } 922 } else { 923 appctx.GetLogger(ctx).Error().Err(err).Str("spaceid", n.SpaceID).Str("nodeid", n.ID).Str("nodepath", n.InternalPath()).Str("quota", v).Msg("malformed quota") 924 } 925 case metadata.IsAttrUnset(err): 926 appctx.GetLogger(ctx).Debug().Str("spaceid", n.SpaceID).Str("nodeid", n.ID).Str("nodepath", n.InternalPath()).Msg("quota not set") 927 default: 928 appctx.GetLogger(ctx).Error().Err(err).Str("spaceid", n.SpaceID).Str("nodeid", n.ID).Str("nodepath", n.InternalPath()).Msg("could not read quota") 929 } 930 } 931 932 // HasPropagation checks if the propagation attribute exists and is set to "1" 933 func (n *Node) HasPropagation(ctx context.Context) (propagation bool) { 934 if b, err := n.XattrString(ctx, prefixes.PropagationAttr); err == nil { 935 return b == "1" 936 } 937 return false 938 } 939 940 // IsDisabled returns true when the node has a dmtime attribute set 941 // only used to check if a space is disabled 942 // FIXME confusing with the trash logic 943 func (n *Node) IsDisabled(ctx context.Context) bool { 944 if _, err := n.GetDTime(ctx); err == nil { 945 return true 946 } 947 return false 948 } 949 950 // GetTreeSize reads the treesize from the extended attributes 951 func (n *Node) GetTreeSize(ctx context.Context) (treesize uint64, err error) { 952 ctx, span := tracer.Start(ctx, "GetTreeSize") 953 defer span.End() 954 s, err := n.XattrUint64(ctx, prefixes.TreesizeAttr) 955 if err != nil { 956 return 0, err 957 } 958 return s, nil 959 } 960 961 // SetTreeSize writes the treesize to the extended attributes 962 func (n *Node) SetTreeSize(ctx context.Context, ts uint64) (err error) { 963 return n.SetXattrString(ctx, prefixes.TreesizeAttr, strconv.FormatUint(ts, 10)) 964 } 965 966 // GetBlobSize reads the blobsize from the extended attributes 967 func (n *Node) GetBlobSize(ctx context.Context) (treesize uint64, err error) { 968 s, err := n.XattrInt64(ctx, prefixes.BlobsizeAttr) 969 if err != nil { 970 return 0, err 971 } 972 return uint64(s), nil 973 } 974 975 // SetChecksum writes the checksum with the given checksum type to the extended attributes 976 func (n *Node) SetChecksum(ctx context.Context, csType string, h hash.Hash) (err error) { 977 return n.SetXattr(ctx, prefixes.ChecksumPrefix+csType, h.Sum(nil)) 978 } 979 980 // UnsetTempEtag removes the temporary etag attribute 981 func (n *Node) UnsetTempEtag(ctx context.Context) (err error) { 982 return n.RemoveXattr(ctx, prefixes.TmpEtagAttr, true) 983 } 984 985 func isGrantExpired(g *provider.Grant) bool { 986 if g.Expiration == nil { 987 return false 988 } 989 return time.Now().After(time.Unix(int64(g.Expiration.Seconds), int64(g.Expiration.Nanos))) 990 } 991 992 // ReadUserPermissions will assemble the permissions for the current user on the given node without parent nodes 993 // we indicate if the access was denied by setting a grant with no permissions 994 func (n *Node) ReadUserPermissions(ctx context.Context, u *userpb.User) (ap *provider.ResourcePermissions, accessDenied bool, err error) { 995 // check if the current user is the owner 996 if utils.UserEqual(u.Id, n.Owner()) { 997 appctx.GetLogger(ctx).Debug().Str("spaceid", n.SpaceID).Str("nodeid", n.ID).Msg("user is owner, returning owner permissions") 998 return OwnerPermissions(), false, nil 999 } 1000 1001 ap = &provider.ResourcePermissions{} 1002 1003 // for an efficient group lookup convert the list of groups to a map 1004 // groups are just strings ... groupnames ... or group ids ??? AAARGH !!! 1005 groupsMap := make(map[string]bool, len(u.Groups)) 1006 for i := range u.Groups { 1007 groupsMap[u.Groups[i]] = true 1008 } 1009 1010 var g *provider.Grant 1011 1012 // we read all grantees from the node 1013 var grantees []string 1014 if grantees, err = n.ListGrantees(ctx); err != nil { 1015 appctx.GetLogger(ctx).Error().Err(err).Str("spaceid", n.SpaceID).Str("nodeid", n.ID).Msg("error listing grantees") 1016 return NoPermissions(), true, err 1017 } 1018 1019 // instead of making n getxattr syscalls we are going to list the acls and filter them here 1020 // we have two options here: 1021 // 1. we can start iterating over the acls / grants on the node or 1022 // 2. we can iterate over the number of groups 1023 // The current implementation tries to be defensive for cases where users have hundreds or thousands of groups, so we iterate over the existing acls. 1024 userace := prefixes.GrantPrefix + ace.UserAce(u.Id) 1025 userFound := false 1026 for i := range grantees { 1027 switch { 1028 // we only need to find the user once 1029 case !userFound && grantees[i] == userace: 1030 g, err = n.ReadGrant(ctx, grantees[i]) 1031 case strings.HasPrefix(grantees[i], prefixes.GrantGroupAcePrefix): // only check group grantees 1032 gr := strings.TrimPrefix(grantees[i], prefixes.GrantGroupAcePrefix) 1033 if groupsMap[gr] { 1034 g, err = n.ReadGrant(ctx, grantees[i]) 1035 } else { 1036 // no need to check attribute 1037 continue 1038 } 1039 default: 1040 // no need to check attribute 1041 continue 1042 } 1043 1044 if isGrantExpired(g) { 1045 continue 1046 } 1047 1048 switch { 1049 case err == nil: 1050 // If all permissions are set to false we have a deny grant 1051 if grants.PermissionsEqual(g.Permissions, &provider.ResourcePermissions{}) { 1052 return NoPermissions(), true, nil 1053 } 1054 AddPermissions(ap, g.GetPermissions()) 1055 case metadata.IsAttrUnset(err): 1056 appctx.GetLogger(ctx).Error().Str("spaceid", n.SpaceID).Str("nodeid", n.ID).Str("grant", grantees[i]).Interface("grantees", grantees).Msg("grant vanished from node after listing") 1057 // continue with next segment 1058 default: 1059 appctx.GetLogger(ctx).Error().Err(err).Str("spaceid", n.SpaceID).Str("nodeid", n.ID).Str("grant", grantees[i]).Msg("error reading permissions") 1060 // continue with next segment 1061 } 1062 } 1063 1064 appctx.GetLogger(ctx).Debug().Interface("permissions", ap).Str("spaceid", n.SpaceID).Str("nodeid", n.ID).Interface("user", u).Msg("returning aggregated permissions") 1065 return ap, false, nil 1066 } 1067 1068 // IsDenied checks if the node was denied to that user 1069 func (n *Node) IsDenied(ctx context.Context) bool { 1070 gs, err := n.ListGrants(ctx) 1071 if err != nil { 1072 // be paranoid, resource is denied 1073 return true 1074 } 1075 1076 u := ctxpkg.ContextMustGetUser(ctx) 1077 isExecutant := func(g *provider.Grantee) bool { 1078 switch g.GetType() { 1079 case provider.GranteeType_GRANTEE_TYPE_USER: 1080 return g.GetUserId().GetOpaqueId() == u.GetId().GetOpaqueId() 1081 case provider.GranteeType_GRANTEE_TYPE_GROUP: 1082 // check gid 1083 gid := g.GetGroupId().GetOpaqueId() 1084 for _, group := range u.Groups { 1085 if gid == group { 1086 return true 1087 } 1088 1089 } 1090 return false 1091 default: 1092 return false 1093 } 1094 1095 } 1096 1097 for _, g := range gs { 1098 if !isExecutant(g.Grantee) { 1099 continue 1100 } 1101 1102 if grants.PermissionsEqual(g.Permissions, &provider.ResourcePermissions{}) { 1103 // resource is denied 1104 return true 1105 } 1106 } 1107 1108 // no deny grants 1109 return false 1110 } 1111 1112 // ListGrantees lists the grantees of the current node 1113 // We don't want to wast time and memory by creating grantee objects. 1114 // The function will return a list of opaque strings that can be used to make a ReadGrant call 1115 func (n *Node) ListGrantees(ctx context.Context) (grantees []string, err error) { 1116 attrs, err := n.Xattrs(ctx) 1117 if err != nil { 1118 appctx.GetLogger(ctx).Error().Err(err).Str("spaceid", n.SpaceID).Str("nodeid", n.ID).Msg("error listing attributes") 1119 return nil, err 1120 } 1121 for name := range attrs { 1122 if strings.HasPrefix(name, prefixes.GrantPrefix) { 1123 grantees = append(grantees, name) 1124 } 1125 } 1126 return 1127 } 1128 1129 // ReadGrant reads a CS3 grant 1130 func (n *Node) ReadGrant(ctx context.Context, grantee string) (g *provider.Grant, err error) { 1131 xattr, err := n.Xattr(ctx, grantee) 1132 if err != nil { 1133 return nil, err 1134 } 1135 var e *ace.ACE 1136 if e, err = ace.Unmarshal(strings.TrimPrefix(grantee, prefixes.GrantPrefix), xattr); err != nil { 1137 return nil, err 1138 } 1139 return e.Grant(), nil 1140 } 1141 1142 // ReadGrant reads a CS3 grant 1143 func (n *Node) DeleteGrant(ctx context.Context, g *provider.Grant, acquireLock bool) (err error) { 1144 1145 var attr string 1146 if g.Grantee.Type == provider.GranteeType_GRANTEE_TYPE_GROUP { 1147 attr = prefixes.GrantGroupAcePrefix + g.Grantee.GetGroupId().OpaqueId 1148 } else { 1149 attr = prefixes.GrantUserAcePrefix + g.Grantee.GetUserId().OpaqueId 1150 } 1151 1152 if err = n.RemoveXattr(ctx, attr, acquireLock); err != nil { 1153 return err 1154 } 1155 1156 return nil 1157 } 1158 1159 // Purge removes a node from disk. It does not move it to the trash 1160 func (n *Node) Purge(ctx context.Context) error { 1161 // remove node 1162 if err := utils.RemoveItem(n.InternalPath()); err != nil { 1163 return err 1164 } 1165 1166 // remove child entry in parent 1167 src := filepath.Join(n.ParentPath(), n.Name) 1168 return os.Remove(src) 1169 } 1170 1171 // ListGrants lists all grants of the current node. 1172 func (n *Node) ListGrants(ctx context.Context) ([]*provider.Grant, error) { 1173 grantees, err := n.ListGrantees(ctx) 1174 if err != nil { 1175 return nil, err 1176 } 1177 1178 grants := make([]*provider.Grant, 0, len(grantees)) 1179 for _, g := range grantees { 1180 grant, err := n.ReadGrant(ctx, g) 1181 if err != nil { 1182 appctx.GetLogger(ctx). 1183 Error(). 1184 Err(err). 1185 Str("spaceid", n.SpaceID). 1186 Str("nodeid", n.ID). 1187 Str("grantee", g). 1188 Msg("error reading grant") 1189 continue 1190 } 1191 grants = append(grants, grant) 1192 } 1193 return grants, nil 1194 } 1195 1196 func (n *Node) getGranteeTypes(ctx context.Context) []provider.GranteeType { 1197 types := []provider.GranteeType{} 1198 if g, err := n.ListGrantees(ctx); err == nil { 1199 hasUserShares, hasGroupShares := false, false 1200 for i := range g { 1201 switch { 1202 case !hasUserShares && strings.HasPrefix(g[i], prefixes.GrantUserAcePrefix): 1203 hasUserShares = true 1204 case !hasGroupShares && strings.HasPrefix(g[i], prefixes.GrantGroupAcePrefix): 1205 hasGroupShares = true 1206 case hasUserShares && hasGroupShares: 1207 break 1208 } 1209 } 1210 if hasUserShares { 1211 types = append(types, provider.GranteeType_GRANTEE_TYPE_USER) 1212 } 1213 if hasGroupShares { 1214 types = append(types, provider.GranteeType_GRANTEE_TYPE_GROUP) 1215 } 1216 } 1217 return types 1218 } 1219 1220 // FindStorageSpaceRoot calls n.Parent() and climbs the tree 1221 // until it finds the space root node and adds it to the node 1222 func (n *Node) FindStorageSpaceRoot(ctx context.Context) error { 1223 if n.SpaceRoot != nil { 1224 return nil 1225 } 1226 var err error 1227 // remember the node we ask for and use parent to climb the tree 1228 parent := n 1229 for { 1230 if parent.IsSpaceRoot(ctx) { 1231 n.SpaceRoot = parent 1232 break 1233 } 1234 if parent, err = parent.Parent(ctx); err != nil { 1235 return err 1236 } 1237 } 1238 return nil 1239 } 1240 1241 // UnmarkProcessing removes the processing flag from the node 1242 func (n *Node) UnmarkProcessing(ctx context.Context, uploadID string) error { 1243 // we currently have to decrease the counter for every processing run to match the incrases 1244 metrics.UploadProcessing.Sub(1) 1245 1246 v, _ := n.XattrString(ctx, prefixes.StatusPrefix) 1247 if v != ProcessingStatus+uploadID { 1248 // file started another postprocessing later - do not remove 1249 return nil 1250 } 1251 return n.RemoveXattr(ctx, prefixes.StatusPrefix, true) 1252 } 1253 1254 // IsProcessing returns true if the node is currently being processed 1255 func (n *Node) IsProcessing(ctx context.Context) bool { 1256 v, err := n.XattrString(ctx, prefixes.StatusPrefix) 1257 return err == nil && strings.HasPrefix(v, ProcessingStatus) 1258 } 1259 1260 // ProcessingID returns the latest upload session id 1261 func (n *Node) ProcessingID(ctx context.Context) (string, error) { 1262 v, err := n.XattrString(ctx, prefixes.StatusPrefix) 1263 return strings.TrimPrefix(v, ProcessingStatus), err 1264 } 1265 1266 // IsSpaceRoot checks if the node is a space root 1267 func (n *Node) IsSpaceRoot(ctx context.Context) bool { 1268 return n.ID == n.SpaceID 1269 } 1270 1271 // SetScanData sets the virus scan info to the node 1272 func (n *Node) SetScanData(ctx context.Context, info string, date time.Time) error { 1273 attribs := Attributes{} 1274 attribs.SetString(prefixes.ScanStatusPrefix, info) 1275 attribs.SetString(prefixes.ScanDatePrefix, date.Format(time.RFC3339Nano)) 1276 return n.SetXattrsWithContext(ctx, attribs, true) 1277 } 1278 1279 // ScanData returns scanning information of the node 1280 func (n *Node) ScanData(ctx context.Context) (scanned bool, virus string, scantime time.Time) { 1281 ti, _ := n.XattrString(ctx, prefixes.ScanDatePrefix) 1282 if ti == "" { 1283 return // not scanned yet 1284 } 1285 1286 t, err := time.Parse(time.RFC3339Nano, ti) 1287 if err != nil { 1288 return 1289 } 1290 1291 i, err := n.XattrString(ctx, prefixes.ScanStatusPrefix) 1292 if err != nil { 1293 return 1294 } 1295 1296 return true, i, t 1297 } 1298 1299 // CheckQuota checks if both disk space and available quota are sufficient 1300 // Overwrite must be set to true if the new file replaces the old file e.g. 1301 // when creating a new file version. In such a case the function will 1302 // reduce the used bytes by the old file size and then add the new size. 1303 // If overwrite is false oldSize will be ignored. 1304 var CheckQuota = func(ctx context.Context, spaceRoot *Node, overwrite bool, oldSize, newSize uint64) (quotaSufficient bool, err error) { 1305 used, _ := spaceRoot.GetTreeSize(ctx) 1306 if !enoughDiskSpace(spaceRoot.InternalPath(), newSize) { 1307 return false, errtypes.InsufficientStorage("disk full") 1308 } 1309 quotaByteStr, _ := spaceRoot.XattrString(ctx, prefixes.QuotaAttr) 1310 switch quotaByteStr { 1311 case "": 1312 // if quota is not set, it means unlimited 1313 return true, nil 1314 case QuotaUnlimited: 1315 return true, nil 1316 case QuotaUncalculated: 1317 // treat it as unlimited 1318 return true, nil 1319 case QuotaUnknown: 1320 // treat it as unlimited 1321 return true, nil 1322 } 1323 quotaByte, _ := strconv.ParseUint(quotaByteStr, 10, 64) 1324 if overwrite { 1325 if quotaByte < used-oldSize+newSize { 1326 return false, errtypes.InsufficientStorage("quota exceeded") 1327 } 1328 // if total is smaller than used, total-used could overflow and be bigger than fileSize 1329 } else if newSize > quotaByte-used || quotaByte < used { 1330 return false, errtypes.InsufficientStorage("quota exceeded") 1331 } 1332 return true, nil 1333 } 1334 1335 func enoughDiskSpace(path string, fileSize uint64) bool { 1336 avalB, err := GetAvailableSize(path) 1337 if err != nil { 1338 return false 1339 } 1340 return avalB > fileSize 1341 } 1342 1343 // CalculateChecksums calculates the sha1, md5 and adler32 checksums of a file 1344 func CalculateChecksums(ctx context.Context, path string) (hash.Hash, hash.Hash, hash.Hash32, error) { 1345 sha1h := sha1.New() 1346 md5h := md5.New() 1347 adler32h := adler32.New() 1348 1349 _, subspan := tracer.Start(ctx, "os.Open") 1350 f, err := os.Open(path) 1351 subspan.End() 1352 if err != nil { 1353 return nil, nil, nil, err 1354 } 1355 defer f.Close() 1356 1357 r1 := io.TeeReader(f, sha1h) 1358 r2 := io.TeeReader(r1, md5h) 1359 1360 _, subspan = tracer.Start(ctx, "io.Copy") 1361 _, err = io.Copy(adler32h, r2) 1362 subspan.End() 1363 if err != nil { 1364 return nil, nil, nil, err 1365 } 1366 1367 return sha1h, md5h, adler32h, nil 1368 } 1369 1370 // GetMTime reads the mtime from the extended attributes 1371 func (n *Node) GetMTime(ctx context.Context) (time.Time, error) { 1372 return n.lu.TimeManager().MTime(ctx, n) 1373 } 1374 1375 // GetTMTime reads the tmtime from the extended attributes 1376 func (n *Node) GetTMTime(ctx context.Context) (time.Time, error) { 1377 return n.lu.TimeManager().TMTime(ctx, n) 1378 } 1379 1380 // SetTMTime writes the UTC tmtime to the extended attributes or removes the attribute if nil is passed 1381 func (n *Node) SetTMTime(ctx context.Context, t *time.Time) (err error) { 1382 return n.lu.TimeManager().SetTMTime(ctx, n, t) 1383 } 1384 1385 // GetDTime reads the dmtime from the extended attributes 1386 func (n *Node) GetDTime(ctx context.Context) (time.Time, error) { 1387 return n.lu.TimeManager().DTime(ctx, n) 1388 } 1389 1390 // SetDTime writes the UTC dmtime to the extended attributes or removes the attribute if nil is passed 1391 func (n *Node) SetDTime(ctx context.Context, t *time.Time) (err error) { 1392 return n.lu.TimeManager().SetDTime(ctx, n, t) 1393 }