github.com/cozy/cozy-stack@v0.0.0-20240603063001-31110fa4cae1/model/vfs/vfsswift/impl_v3.go (about) 1 // Package vfsswift is the implementation of the Virtual File System by using 2 // Swift from the OpenStack project. The file contents are saved in the object 3 // storage (Swift), and the metadata are indexed in CouchDB. 4 package vfsswift 5 6 import ( 7 "bytes" 8 "context" 9 "encoding/hex" 10 "errors" 11 "io" 12 "os" 13 "strings" 14 "time" 15 16 "github.com/cozy/cozy-stack/model/vfs" 17 "github.com/cozy/cozy-stack/pkg/config/config" 18 "github.com/cozy/cozy-stack/pkg/couchdb" 19 "github.com/cozy/cozy-stack/pkg/lock" 20 "github.com/cozy/cozy-stack/pkg/logger" 21 "github.com/cozy/cozy-stack/pkg/utils" 22 "github.com/gofrs/uuid/v5" 23 multierror "github.com/hashicorp/go-multierror" 24 "github.com/ncw/swift/v2" 25 ) 26 27 type swiftVFSV3 struct { 28 vfs.Indexer 29 vfs.DiskThresholder 30 c *swift.Connection 31 cluster int 32 domain string 33 prefix string 34 context string 35 container string 36 mu lock.ErrorRWLocker 37 ctx context.Context 38 log *logger.Entry 39 } 40 41 const swiftV3ContainerPrefix = "cozy-v3-" 42 const maxFileSize = 5 << (3 * 10) // 5 GiB 43 44 // NewV3 returns a vfs.VFS instance associated with the specified indexer and 45 // the swift storage url. 46 // 47 // This new V3 version uses only a single swift container per instance. We can 48 // easily put the thumbnails in the same container that the data. And, for the 49 // versioning, Swift Object Versioning is not what we want: it is not as robust 50 // as we expected (we had encoding issue with the V1 layout for file with `? ` 51 // in the name), and it is poor in features (for example, we want to swap an 52 // old version with the current version without having to download/upload 53 // contents, and it is not supported). 54 func NewV3(db vfs.Prefixer, index vfs.Indexer, disk vfs.DiskThresholder, mu lock.ErrorRWLocker) (vfs.VFS, error) { 55 return &swiftVFSV3{ 56 Indexer: index, 57 DiskThresholder: disk, 58 59 c: config.GetSwiftConnection(), 60 cluster: db.DBCluster(), 61 domain: db.DomainName(), 62 prefix: db.DBPrefix(), 63 context: db.GetContextName(), 64 container: swiftV3ContainerPrefix + db.DBPrefix(), 65 mu: mu, 66 ctx: context.Background(), 67 log: logger.WithDomain(db.DomainName()).WithNamespace("vfsswift"), 68 }, nil 69 } 70 71 // NewInternalID returns a random string that can be used as an internal_vfs_id. 72 func NewInternalID() string { 73 return utils.RandomString(16) 74 } 75 76 // MakeObjectNameV3 builds the swift object name for a given file document. It 77 // creates a virtual subfolder by splitting the document ID, which should be 32 78 // bytes long, on the 27nth byte. This avoid having a flat hierarchy in swift 79 // with no bound. And it appends the internalID at the end to regroup all the 80 // versions of a file in the same virtual subfolder. 81 func MakeObjectNameV3(docID, internalID string) string { 82 if len(docID) != 32 || len(internalID) != 16 { 83 return docID + "/" + internalID 84 } 85 return docID[:22] + "/" + docID[22:27] + "/" + docID[27:] + "/" + internalID 86 } 87 88 func makeDocIDV3(objName string) (string, string) { 89 if len(objName) != 51 { 90 parts := strings.SplitN(objName, "/", 2) 91 if len(parts) < 2 { 92 return objName, "" 93 } 94 return parts[0], parts[1] 95 } 96 return objName[:22] + objName[23:28] + objName[29:34], objName[35:] 97 } 98 99 func (sfs *swiftVFSV3) MaxFileSize() int64 { 100 return maxFileSize 101 } 102 103 func (sfs *swiftVFSV3) DBCluster() int { 104 return sfs.cluster 105 } 106 107 func (sfs *swiftVFSV3) DBPrefix() string { 108 return sfs.prefix 109 } 110 111 func (sfs *swiftVFSV3) DomainName() string { 112 return sfs.domain 113 } 114 115 func (sfs *swiftVFSV3) GetContextName() string { 116 return sfs.context 117 } 118 119 func (sfs *swiftVFSV3) GetIndexer() vfs.Indexer { 120 return sfs.Indexer 121 } 122 123 func (sfs *swiftVFSV3) UseSharingIndexer(index vfs.Indexer) vfs.VFS { 124 return &swiftVFSV3{ 125 Indexer: index, 126 DiskThresholder: sfs.DiskThresholder, 127 c: sfs.c, 128 domain: sfs.domain, 129 prefix: sfs.prefix, 130 container: sfs.container, 131 mu: sfs.mu, 132 ctx: context.Background(), 133 log: sfs.log, 134 } 135 } 136 137 func (sfs *swiftVFSV3) ContainerNames() map[string]string { 138 return map[string]string{"container": sfs.container} 139 } 140 141 func (sfs *swiftVFSV3) InitFs() error { 142 if lockerr := sfs.mu.Lock(); lockerr != nil { 143 return lockerr 144 } 145 defer sfs.mu.Unlock() 146 if err := sfs.Indexer.InitIndex(); err != nil { 147 return err 148 } 149 if err := sfs.c.ContainerCreate(sfs.ctx, sfs.container, nil); err != nil { 150 sfs.log.Errorf("Could not create container %q: %s", 151 sfs.container, err.Error()) 152 return err 153 } 154 sfs.log.Infof("Created container %q", sfs.container) 155 return nil 156 } 157 158 func (sfs *swiftVFSV3) Delete() error { 159 containerMeta := swift.Metadata{"to-be-deleted": "1"}.ContainerHeaders() 160 sfs.log.Infof("Marking container %q as to-be-deleted", sfs.container) 161 err := sfs.c.ContainerUpdate(sfs.ctx, sfs.container, containerMeta) 162 if err != nil { 163 sfs.log.Errorf("Could not mark container %q as to-be-deleted: %s", 164 sfs.container, err) 165 } 166 return DeleteContainer(sfs.ctx, sfs.c, sfs.container) 167 } 168 169 func (sfs *swiftVFSV3) CreateDir(doc *vfs.DirDoc) error { 170 if lockerr := sfs.mu.Lock(); lockerr != nil { 171 return lockerr 172 } 173 defer sfs.mu.Unlock() 174 exists, err := sfs.Indexer.DirChildExists(doc.DirID, doc.DocName) 175 if err != nil { 176 return err 177 } 178 if exists { 179 return os.ErrExist 180 } 181 if doc.ID() == "" { 182 return sfs.Indexer.CreateDirDoc(doc) 183 } 184 return sfs.Indexer.CreateNamedDirDoc(doc) 185 } 186 187 func (sfs *swiftVFSV3) CreateFile(newdoc, olddoc *vfs.FileDoc, opts ...vfs.CreateOptions) (vfs.File, error) { 188 if lockerr := sfs.mu.Lock(); lockerr != nil { 189 return nil, lockerr 190 } 191 defer sfs.mu.Unlock() 192 193 newsize, maxsize, capsize, err := vfs.CheckAvailableDiskSpace(sfs, newdoc) 194 if err != nil { 195 return nil, err 196 } 197 if newsize > maxsize { 198 return nil, vfs.ErrFileTooBig 199 } 200 201 if olddoc != nil { 202 newdoc.SetID(olddoc.ID()) 203 newdoc.SetRev(olddoc.Rev()) 204 newdoc.CreatedAt = olddoc.CreatedAt 205 } 206 207 newpath, err := sfs.Indexer.FilePath(newdoc) 208 if err != nil { 209 return nil, err 210 } 211 if strings.HasPrefix(newpath, vfs.TrashDirName+"/") { 212 if !vfs.OptionsAllowCreationInTrash(opts) { 213 return nil, vfs.ErrParentInTrash 214 } 215 } 216 217 if olddoc == nil { 218 var exists bool 219 exists, err = sfs.Indexer.DirChildExists(newdoc.DirID, newdoc.DocName) 220 if err != nil { 221 return nil, err 222 } 223 if exists { 224 return nil, os.ErrExist 225 } 226 } 227 228 if newdoc.DocID == "" { 229 uid, err := uuid.NewV7() 230 if err != nil { 231 return nil, err 232 } 233 newdoc.DocID = uid.String() 234 } 235 236 newdoc.InternalID = NewInternalID() 237 objName := MakeObjectNameV3(newdoc.DocID, newdoc.InternalID) 238 hash := hex.EncodeToString(newdoc.MD5Sum) 239 f, err := sfs.c.ObjectCreate(sfs.ctx, sfs.container, objName, true, hash, newdoc.Mime, nil) 240 if err != nil { 241 return nil, err 242 } 243 extractor := vfs.NewMetaExtractor(newdoc) 244 245 return &swiftFileCreationV3{ 246 fs: sfs, 247 f: f, 248 newdoc: newdoc, 249 olddoc: olddoc, 250 name: objName, 251 w: 0, 252 size: newsize, 253 maxsize: maxsize, 254 capsize: capsize, 255 meta: extractor, 256 }, nil 257 } 258 259 func (sfs *swiftVFSV3) CopyFile(olddoc, newdoc *vfs.FileDoc) error { 260 if lockerr := sfs.mu.Lock(); lockerr != nil { 261 return lockerr 262 } 263 defer sfs.mu.Unlock() 264 265 newsize, _, capsize, err := vfs.CheckAvailableDiskSpace(sfs, olddoc) 266 if err != nil { 267 return err 268 } 269 270 uid, err := uuid.NewV7() 271 if err != nil { 272 return err 273 } 274 newdoc.DocID = uid.String() 275 newdoc.InternalID = NewInternalID() 276 277 // Copy the file 278 srcName := MakeObjectNameV3(olddoc.DocID, olddoc.InternalID) 279 dstName := MakeObjectNameV3(newdoc.DocID, newdoc.InternalID) 280 headers := swift.Metadata{ 281 "creation-name": newdoc.Name(), 282 "created-at": newdoc.CreatedAt.Format(time.RFC3339), 283 "copied-from": olddoc.ID(), 284 }.ObjectHeaders() 285 if _, err := sfs.c.ObjectCopy(sfs.ctx, sfs.container, srcName, sfs.container, dstName, headers); err != nil { 286 return err 287 } 288 if err := sfs.Indexer.CreateNamedFileDoc(newdoc); err != nil { 289 _ = sfs.c.ObjectDelete(sfs.ctx, sfs.container, dstName) 290 return err 291 } 292 293 if capsize > 0 && newsize >= capsize { 294 vfs.PushDiskQuotaAlert(sfs, true) 295 } 296 297 return nil 298 } 299 300 func (sfs *swiftVFSV3) DissociateFile(src, dst *vfs.FileDoc) error { 301 if lockerr := sfs.mu.Lock(); lockerr != nil { 302 return lockerr 303 } 304 defer sfs.mu.Unlock() 305 306 if src.DirID != dst.DirID || src.DocName != dst.DocName { 307 exists, err := sfs.Indexer.DirChildExists(dst.DirID, dst.DocName) 308 if err != nil { 309 return err 310 } 311 if exists { 312 return os.ErrExist 313 } 314 } 315 316 uid, err := uuid.NewV7() 317 if err != nil { 318 return err 319 } 320 dst.DocID = uid.String() 321 322 // Copy the file 323 srcName := MakeObjectNameV3(src.DocID, src.InternalID) 324 dstName := MakeObjectNameV3(dst.DocID, dst.InternalID) 325 headers := swift.Metadata{ 326 "creation-name": src.Name(), 327 "created-at": src.CreatedAt.Format(time.RFC3339), 328 "dissociated-of": src.ID(), 329 }.ObjectHeaders() 330 if _, err := sfs.c.ObjectCopy(sfs.ctx, sfs.container, srcName, sfs.container, dstName, headers); err != nil { 331 return err 332 } 333 if err := sfs.Indexer.CreateNamedFileDoc(dst); err != nil { 334 _ = sfs.c.ObjectDelete(sfs.ctx, sfs.container, dstName) 335 return err 336 } 337 338 // Remove the source 339 thumbsFS := &thumbsV3{ 340 c: sfs.c, 341 container: sfs.container, 342 ctx: context.Background(), 343 } 344 if err := thumbsFS.RemoveThumbs(src, vfs.ThumbnailFormatNames); err != nil { 345 sfs.log.Infof("Cleaning thumbnails in DissociateFile %s has failed: %s", src.ID(), err) 346 } 347 return sfs.destroyFileLocked(src) 348 } 349 350 func (sfs *swiftVFSV3) DissociateDir(src, dst *vfs.DirDoc) error { 351 if lockerr := sfs.mu.Lock(); lockerr != nil { 352 return lockerr 353 } 354 defer sfs.mu.Unlock() 355 356 if dst.DirID != src.DirID || dst.DocName != src.DocName { 357 exists, err := sfs.Indexer.DirChildExists(dst.DirID, dst.DocName) 358 if err != nil { 359 return err 360 } 361 if exists { 362 return os.ErrExist 363 } 364 } 365 366 if err := sfs.Indexer.CreateDirDoc(dst); err != nil { 367 return err 368 } 369 return sfs.Indexer.DeleteDirDoc(src) 370 } 371 372 func (sfs *swiftVFSV3) destroyDir(doc *vfs.DirDoc, push func(vfs.TrashJournal) error, onlyContent bool) error { 373 if lockerr := sfs.mu.Lock(); lockerr != nil { 374 return lockerr 375 } 376 defer sfs.mu.Unlock() 377 diskUsage, _ := sfs.Indexer.DiskUsage() 378 files, destroyed, err := sfs.Indexer.DeleteDirDocAndContent(doc, onlyContent) 379 if err != nil { 380 return err 381 } 382 if len(files) == 0 { 383 return nil 384 } 385 vfs.DiskQuotaAfterDestroy(sfs, diskUsage, destroyed) 386 ids := make([]string, len(files)) 387 objNames := make([]string, len(files)) 388 for i, file := range files { 389 ids[i] = file.DocID 390 objNames[i] = MakeObjectNameV3(file.DocID, file.InternalID) 391 } 392 err = push(vfs.TrashJournal{ 393 FileIDs: ids, 394 ObjectNames: objNames, 395 }) 396 return err 397 } 398 399 func (sfs *swiftVFSV3) DestroyDirContent(doc *vfs.DirDoc, push func(vfs.TrashJournal) error) error { 400 return sfs.destroyDir(doc, push, true) 401 } 402 403 func (sfs *swiftVFSV3) DestroyDirAndContent(doc *vfs.DirDoc, push func(vfs.TrashJournal) error) error { 404 return sfs.destroyDir(doc, push, false) 405 } 406 407 func (sfs *swiftVFSV3) DestroyFile(doc *vfs.FileDoc) error { 408 if lockerr := sfs.mu.Lock(); lockerr != nil { 409 return lockerr 410 } 411 defer sfs.mu.Unlock() 412 return sfs.destroyFileLocked(doc) 413 } 414 415 func (sfs *swiftVFSV3) destroyFileLocked(doc *vfs.FileDoc) error { 416 diskUsage, _ := sfs.Indexer.DiskUsage() 417 objNames := []string{ 418 MakeObjectNameV3(doc.DocID, doc.InternalID), 419 } 420 if err := sfs.Indexer.DeleteFileDoc(doc); err != nil { 421 return err 422 } 423 destroyed := doc.ByteSize 424 if versions, errv := vfs.VersionsFor(sfs, doc.DocID); errv == nil { 425 for _, v := range versions { 426 internalID := v.DocID 427 if parts := strings.SplitN(v.DocID, "/", 2); len(parts) > 1 { 428 internalID = parts[1] 429 } 430 objNames = append(objNames, MakeObjectNameV3(doc.DocID, internalID)) 431 destroyed += v.ByteSize 432 } 433 err := sfs.Indexer.BatchDeleteVersions(versions) 434 if err != nil { 435 sfs.log.Warnf("DestroyFile failed on BatchDeleteVersions: %s", err) 436 } 437 } 438 _, errb := sfs.c.BulkDelete(sfs.ctx, sfs.container, objNames) 439 if errb == swift.Forbidden { 440 for _, objName := range objNames { 441 if err := sfs.c.ObjectDelete(sfs.ctx, sfs.container, objName); err != nil { 442 sfs.log.Infof("DestroyFile failed on ObjectDelete: %s", err) 443 } 444 } 445 } 446 if errb != nil { 447 sfs.log.Warnf("DestroyFile failed on BulkDelete: %s", errb) 448 } 449 vfs.DiskQuotaAfterDestroy(sfs, diskUsage, destroyed) 450 return nil 451 } 452 453 func (sfs *swiftVFSV3) EnsureErased(journal vfs.TrashJournal) error { 454 // No lock needed 455 diskUsage, _ := sfs.Indexer.DiskUsage() 456 objNames := journal.ObjectNames 457 var errm error 458 var destroyed int64 459 var allVersions []*vfs.Version 460 for _, fileID := range journal.FileIDs { 461 versions, err := vfs.VersionsFor(sfs, fileID) 462 if err != nil { 463 if !couchdb.IsNoDatabaseError(err) { 464 sfs.log.Warnf("EnsureErased failed on VersionsFor(%s): %s", fileID, err) 465 errm = multierror.Append(errm, err) 466 } 467 continue 468 } 469 for _, v := range versions { 470 internalID := v.DocID 471 if parts := strings.SplitN(v.DocID, "/", 2); len(parts) > 1 { 472 internalID = parts[1] 473 } 474 objNames = append(objNames, MakeObjectNameV3(fileID, internalID)) 475 destroyed += v.ByteSize 476 } 477 allVersions = append(allVersions, versions...) 478 } 479 if err := sfs.Indexer.BatchDeleteVersions(allVersions); err != nil { 480 sfs.log.Warnf("EnsureErased failed on BatchDeleteVersions: %s", err) 481 errm = multierror.Append(errm, err) 482 } 483 if err := deleteContainerFiles(sfs.ctx, sfs.c, sfs.container, objNames); err != nil { 484 sfs.log.Warnf("EnsureErased failed on deleteContainerFiles: %s", err) 485 errm = multierror.Append(errm, err) 486 } 487 vfs.DiskQuotaAfterDestroy(sfs, diskUsage, destroyed) 488 return errm 489 } 490 491 func (sfs *swiftVFSV3) OpenFile(doc *vfs.FileDoc) (vfs.File, error) { 492 if lockerr := sfs.mu.RLock(); lockerr != nil { 493 return nil, lockerr 494 } 495 defer sfs.mu.RUnlock() 496 objName := MakeObjectNameV3(doc.DocID, doc.InternalID) 497 f, _, err := sfs.c.ObjectOpen(sfs.ctx, sfs.container, objName, false, nil) 498 if errors.Is(err, swift.ObjectNotFound) { 499 return nil, os.ErrNotExist 500 } 501 if err != nil { 502 return nil, err 503 } 504 return &swiftFileOpenV3{f, nil}, nil 505 } 506 507 func (sfs *swiftVFSV3) OpenFileVersion(doc *vfs.FileDoc, version *vfs.Version) (vfs.File, error) { 508 if lockerr := sfs.mu.RLock(); lockerr != nil { 509 return nil, lockerr 510 } 511 defer sfs.mu.RUnlock() 512 internalID := version.DocID 513 if parts := strings.SplitN(version.DocID, "/", 2); len(parts) > 1 { 514 internalID = parts[1] 515 } 516 objName := MakeObjectNameV3(doc.DocID, internalID) 517 f, _, err := sfs.c.ObjectOpen(sfs.ctx, sfs.container, objName, false, nil) 518 if errors.Is(err, swift.ObjectNotFound) { 519 return nil, os.ErrNotExist 520 } 521 if err != nil { 522 return nil, err 523 } 524 return &swiftFileOpenV3{f, nil}, nil 525 } 526 527 func (sfs *swiftVFSV3) ImportFileVersion(version *vfs.Version, content io.ReadCloser) error { 528 if lockerr := sfs.mu.Lock(); lockerr != nil { 529 return lockerr 530 } 531 defer sfs.mu.Unlock() 532 533 diskQuota := sfs.DiskQuota() 534 if diskQuota > 0 { 535 diskUsage, err := sfs.DiskUsage() 536 if err != nil { 537 return err 538 } 539 if diskUsage+version.ByteSize > diskQuota { 540 return vfs.ErrFileTooBig 541 } 542 } 543 544 parts := strings.SplitN(version.DocID, "/", 2) 545 if len(parts) != 2 { 546 return vfs.ErrIllegalFilename 547 } 548 objName := MakeObjectNameV3(parts[0], parts[1]) 549 550 hash := hex.EncodeToString(version.MD5Sum) 551 f, err := sfs.c.ObjectCreate(sfs.ctx, sfs.container, objName, true, hash, "application/octet-stream", nil) 552 if err != nil { 553 return err 554 } 555 556 _, err = io.Copy(f, content) 557 if errc := content.Close(); err == nil { 558 err = errc 559 } 560 if errc := f.Close(); err == nil { 561 err = errc 562 } 563 if err != nil { 564 if errors.Is(err, swift.ObjectCorrupted) { 565 err = vfs.ErrInvalidHash 566 } 567 return err 568 } 569 570 return sfs.Indexer.CreateVersion(version) 571 } 572 573 func (sfs *swiftVFSV3) RevertFileVersion(doc *vfs.FileDoc, version *vfs.Version) error { 574 if lockerr := sfs.mu.Lock(); lockerr != nil { 575 return lockerr 576 } 577 defer sfs.mu.Unlock() 578 579 save := vfs.NewVersion(doc) 580 if err := sfs.Indexer.CreateVersion(save); err != nil { 581 return err 582 } 583 584 newdoc := doc.Clone().(*vfs.FileDoc) 585 if parts := strings.SplitN(version.DocID, "/", 2); len(parts) > 1 { 586 newdoc.InternalID = parts[1] 587 } 588 vfs.SetMetaFromVersion(newdoc, version) 589 if err := sfs.Indexer.UpdateFileDoc(doc, newdoc); err != nil { 590 _ = sfs.Indexer.DeleteVersion(save) 591 return err 592 } 593 594 return sfs.Indexer.DeleteVersion(version) 595 } 596 597 func (sfs *swiftVFSV3) CopyFileFromOtherFS( 598 newdoc, olddoc *vfs.FileDoc, 599 srcFS vfs.Fs, 600 srcDoc *vfs.FileDoc, 601 ) error { 602 if lockerr := sfs.mu.Lock(); lockerr != nil { 603 return lockerr 604 } 605 defer sfs.mu.Unlock() 606 607 newsize, maxsize, capsize, err := vfs.CheckAvailableDiskSpace(sfs, newdoc) 608 if err != nil { 609 return err 610 } 611 if newsize > maxsize { 612 return vfs.ErrFileTooBig 613 } 614 615 newpath, err := sfs.Indexer.FilePath(newdoc) 616 if err != nil { 617 return err 618 } 619 if strings.HasPrefix(newpath, vfs.TrashDirName+"/") { 620 return vfs.ErrParentInTrash 621 } 622 623 if olddoc == nil { 624 var exists bool 625 exists, err = sfs.Indexer.DirChildExists(newdoc.DirID, newdoc.DocName) 626 if err != nil { 627 return err 628 } 629 if exists { 630 return os.ErrExist 631 } 632 } 633 634 if newdoc.DocID == "" { 635 uid, err := uuid.NewV7() 636 if err != nil { 637 return err 638 } 639 newdoc.DocID = uid.String() 640 } 641 642 newdoc.InternalID = NewInternalID() 643 644 srcName := MakeObjectNameV3(srcDoc.DocID, srcDoc.InternalID) 645 dstName := MakeObjectNameV3(newdoc.DocID, newdoc.InternalID) 646 srcContainer := srcFS.(*swiftVFSV3).container 647 if _, err := sfs.c.ObjectCopy(sfs.ctx, srcContainer, srcName, sfs.container, dstName, nil); err != nil { 648 return err 649 } 650 651 var v *vfs.Version 652 if olddoc != nil { 653 v = vfs.NewVersion(olddoc) 654 err = sfs.Indexer.UpdateFileDoc(olddoc, newdoc) 655 } else { 656 err = sfs.Indexer.CreateNamedFileDoc(newdoc) 657 } 658 if err != nil { 659 return err 660 } 661 662 if v != nil { 663 actionV, toClean, _ := vfs.FindVersionsToClean(sfs, newdoc.DocID, v) 664 if bytes.Equal(newdoc.MD5Sum, olddoc.MD5Sum) { 665 actionV = vfs.CleanCandidateVersion 666 } 667 if actionV == vfs.KeepCandidateVersion { 668 if errv := sfs.Indexer.CreateVersion(v); errv != nil { 669 actionV = vfs.CleanCandidateVersion 670 } 671 } 672 if actionV == vfs.CleanCandidateVersion { 673 internalID := v.DocID 674 if parts := strings.SplitN(v.DocID, "/", 2); len(parts) > 1 { 675 internalID = parts[1] 676 } 677 objName := MakeObjectNameV3(newdoc.DocID, internalID) 678 _ = sfs.c.ObjectDelete(sfs.ctx, sfs.container, objName) 679 } 680 for _, old := range toClean { 681 _ = cleanOldVersion(sfs, newdoc.DocID, old) 682 } 683 } 684 685 if capsize > 0 && newsize >= capsize { 686 vfs.PushDiskQuotaAlert(sfs, true) 687 } 688 689 return nil 690 } 691 692 // UpdateFileDoc calls the indexer UpdateFileDoc function and adds a few checks 693 // before actually calling this method: 694 // - locks the filesystem for writing 695 // - checks in case we have a move operation that the new path is available 696 // 697 // @override Indexer.UpdateFileDoc 698 func (sfs *swiftVFSV3) UpdateFileDoc(olddoc, newdoc *vfs.FileDoc) error { 699 if lockerr := sfs.mu.Lock(); lockerr != nil { 700 return lockerr 701 } 702 defer sfs.mu.Unlock() 703 if newdoc.DirID != olddoc.DirID || newdoc.DocName != olddoc.DocName { 704 exists, err := sfs.Indexer.DirChildExists(newdoc.DirID, newdoc.DocName) 705 if err != nil { 706 return err 707 } 708 if exists { 709 return os.ErrExist 710 } 711 } 712 return sfs.Indexer.UpdateFileDoc(olddoc, newdoc) 713 } 714 715 // UdpdateDirDoc calls the indexer UdpdateDirDoc function and adds a few checks 716 // before actually calling this method: 717 // - locks the filesystem for writing 718 // - checks that we don't move a directory to one of its descendant 719 // - checks in case we have a move operation that the new path is available 720 // 721 // @override Indexer.UpdateDirDoc 722 func (sfs *swiftVFSV3) UpdateDirDoc(olddoc, newdoc *vfs.DirDoc) error { 723 if lockerr := sfs.mu.Lock(); lockerr != nil { 724 return lockerr 725 } 726 defer sfs.mu.Unlock() 727 if newdoc.DirID != olddoc.DirID || newdoc.DocName != olddoc.DocName { 728 if strings.HasPrefix(newdoc.Fullpath, olddoc.Fullpath+"/") { 729 return vfs.ErrForbiddenDocMove 730 } 731 exists, err := sfs.Indexer.DirChildExists(newdoc.DirID, newdoc.DocName) 732 if err != nil { 733 return err 734 } 735 if exists { 736 return os.ErrExist 737 } 738 } 739 return sfs.Indexer.UpdateDirDoc(olddoc, newdoc) 740 } 741 742 func (sfs *swiftVFSV3) DirByID(fileID string) (*vfs.DirDoc, error) { 743 if lockerr := sfs.mu.RLock(); lockerr != nil { 744 return nil, lockerr 745 } 746 defer sfs.mu.RUnlock() 747 return sfs.Indexer.DirByID(fileID) 748 } 749 750 func (sfs *swiftVFSV3) DirByPath(name string) (*vfs.DirDoc, error) { 751 if lockerr := sfs.mu.RLock(); lockerr != nil { 752 return nil, lockerr 753 } 754 defer sfs.mu.RUnlock() 755 return sfs.Indexer.DirByPath(name) 756 } 757 758 func (sfs *swiftVFSV3) FileByID(fileID string) (*vfs.FileDoc, error) { 759 if lockerr := sfs.mu.RLock(); lockerr != nil { 760 return nil, lockerr 761 } 762 defer sfs.mu.RUnlock() 763 return sfs.Indexer.FileByID(fileID) 764 } 765 766 func (sfs *swiftVFSV3) FileByPath(name string) (*vfs.FileDoc, error) { 767 if lockerr := sfs.mu.RLock(); lockerr != nil { 768 return nil, lockerr 769 } 770 defer sfs.mu.RUnlock() 771 return sfs.Indexer.FileByPath(name) 772 } 773 774 func (sfs *swiftVFSV3) FilePath(doc *vfs.FileDoc) (string, error) { 775 if lockerr := sfs.mu.RLock(); lockerr != nil { 776 return "", lockerr 777 } 778 defer sfs.mu.RUnlock() 779 return sfs.Indexer.FilePath(doc) 780 } 781 782 func (sfs *swiftVFSV3) DirOrFileByID(fileID string) (*vfs.DirDoc, *vfs.FileDoc, error) { 783 if lockerr := sfs.mu.RLock(); lockerr != nil { 784 return nil, nil, lockerr 785 } 786 defer sfs.mu.RUnlock() 787 return sfs.Indexer.DirOrFileByID(fileID) 788 } 789 790 func (sfs *swiftVFSV3) DirOrFileByPath(name string) (*vfs.DirDoc, *vfs.FileDoc, error) { 791 if lockerr := sfs.mu.RLock(); lockerr != nil { 792 return nil, nil, lockerr 793 } 794 defer sfs.mu.RUnlock() 795 return sfs.Indexer.DirOrFileByPath(name) 796 } 797 798 // swiftFileCreationV3 represents a file open for writing. It is used to create 799 // a file or to modify the content of a file. 800 // 801 // swiftFileCreationV3 implements io.WriteCloser. 802 type swiftFileCreationV3 struct { 803 fs *swiftVFSV3 804 f *swift.ObjectCreateFile 805 newdoc *vfs.FileDoc 806 olddoc *vfs.FileDoc 807 name string 808 w int64 809 size int64 810 maxsize int64 811 capsize int64 812 meta *vfs.MetaExtractor 813 err error 814 } 815 816 func (f *swiftFileCreationV3) Read(p []byte) (int, error) { 817 return 0, os.ErrInvalid 818 } 819 820 func (f *swiftFileCreationV3) ReadAt(p []byte, off int64) (int, error) { 821 return 0, os.ErrInvalid 822 } 823 824 func (f *swiftFileCreationV3) Seek(offset int64, whence int) (int64, error) { 825 return 0, os.ErrInvalid 826 } 827 828 func (f *swiftFileCreationV3) Write(p []byte) (int, error) { 829 if f.meta != nil { 830 if _, err := (*f.meta).Write(p); err != nil && !errors.Is(err, io.ErrClosedPipe) { 831 (*f.meta).Abort(err) 832 f.meta = nil 833 } 834 } 835 836 n, err := f.f.Write(p) 837 if err != nil { 838 f.err = err 839 return n, err 840 } 841 842 f.w += int64(n) 843 if f.maxsize >= 0 && f.w > f.maxsize { 844 f.err = vfs.ErrFileTooBig 845 return n, f.err 846 } 847 848 if f.size >= 0 && f.w > f.size { 849 f.err = vfs.ErrContentLengthMismatch 850 return n, f.err 851 } 852 853 return n, nil 854 } 855 856 func (f *swiftFileCreationV3) Close() (err error) { 857 defer func() { 858 if err != nil { 859 // Remove the temporary file from Swift if an error occurred 860 _ = f.fs.c.ObjectDelete(f.fs.ctx, f.fs.container, f.name) 861 // If an error has occurred when creating a new file, we should 862 // also delete the file from the index. 863 if f.olddoc == nil { 864 _ = f.fs.Indexer.DeleteFileDoc(f.newdoc) 865 } 866 } 867 }() 868 869 if err = f.f.Close(); err != nil { 870 if errors.Is(err, swift.ObjectCorrupted) { 871 err = vfs.ErrInvalidHash 872 } 873 if f.meta != nil { 874 (*f.meta).Abort(err) 875 f.meta = nil 876 } 877 if f.err == nil { 878 f.err = err 879 } 880 } 881 882 newdoc, olddoc, written := f.newdoc, f.olddoc, f.w 883 884 if f.meta != nil { 885 if errc := (*f.meta).Close(); errc == nil { 886 vfs.MergeMetadata(newdoc, (*f.meta).Result()) 887 } 888 } 889 890 if f.err != nil { 891 return f.err 892 } 893 894 // The actual check of the optionally given md5 hash is handled by the swift 895 // library. 896 if newdoc.MD5Sum == nil { 897 var headers swift.Headers 898 var md5sum []byte 899 headers, err = f.f.Headers() 900 if err != nil { 901 return err 902 } 903 // Etags may be double-quoted 904 etag := headers["Etag"] 905 if l := len(etag); l >= 2 { 906 if etag[0] == '"' { 907 etag = etag[1:] 908 } 909 if etag[l-1] == '"' { 910 etag = etag[:l-1] 911 } 912 } 913 md5sum, err = hex.DecodeString(etag) 914 if err != nil { 915 return err 916 } 917 newdoc.MD5Sum = md5sum 918 } 919 920 if f.size < 0 { 921 newdoc.ByteSize = written 922 } 923 924 if newdoc.ByteSize != written { 925 return vfs.ErrContentLengthMismatch 926 } 927 928 lockerr := f.fs.mu.Lock() 929 if lockerr != nil { 930 return lockerr 931 } 932 defer f.fs.mu.Unlock() 933 934 // Check again that a file with the same path does not exist. It can happen 935 // when the same file is uploaded twice in parallel. 936 if olddoc == nil { 937 exists, err := f.fs.Indexer.DirChildExists(newdoc.DirID, newdoc.DocName) 938 if err != nil { 939 return err 940 } 941 if exists { 942 return os.ErrExist 943 } 944 } 945 946 var newpath string 947 newpath, err = f.fs.Indexer.FilePath(newdoc) 948 if err != nil { 949 return err 950 } 951 newdoc.Trashed = strings.HasPrefix(newpath, vfs.TrashDirName+"/") 952 953 var v *vfs.Version 954 if olddoc != nil { 955 v = vfs.NewVersion(olddoc) 956 err = f.fs.Indexer.UpdateFileDoc(olddoc, newdoc) 957 } else if newdoc.ID() == "" { 958 err = f.fs.Indexer.CreateFileDoc(newdoc) 959 } else { 960 err = f.fs.Indexer.CreateNamedFileDoc(newdoc) 961 } 962 if err != nil { 963 return err 964 } 965 966 if v != nil { 967 actionV, toClean, _ := vfs.FindVersionsToClean(f.fs, newdoc.DocID, v) 968 if bytes.Equal(newdoc.MD5Sum, olddoc.MD5Sum) { 969 actionV = vfs.CleanCandidateVersion 970 } 971 if actionV == vfs.KeepCandidateVersion { 972 if errv := f.fs.Indexer.CreateVersion(v); errv != nil { 973 actionV = vfs.CleanCandidateVersion 974 } 975 } 976 if actionV == vfs.CleanCandidateVersion { 977 internalID := v.DocID 978 if parts := strings.SplitN(v.DocID, "/", 2); len(parts) > 1 { 979 internalID = parts[1] 980 } 981 objName := MakeObjectNameV3(newdoc.DocID, internalID) 982 if err := f.fs.c.ObjectDelete(f.fs.ctx, f.fs.container, objName); err != nil { 983 f.fs.log.Warnf("Could not delete previous version %q: %s", objName, err.Error()) 984 } 985 } 986 for _, old := range toClean { 987 if err := cleanOldVersion(f.fs, newdoc.DocID, old); err != nil { 988 f.fs.log.Warnf("Could not delete old versions for %s: %s", newdoc.DocID, err.Error()) 989 } 990 } 991 } 992 993 if f.capsize > 0 && f.size >= f.capsize { 994 vfs.PushDiskQuotaAlert(f.fs, true) 995 } 996 997 return nil 998 } 999 1000 func (sfs *swiftVFSV3) CleanOldVersion(fileID string, v *vfs.Version) error { 1001 if lockerr := sfs.mu.Lock(); lockerr != nil { 1002 return lockerr 1003 } 1004 defer sfs.mu.Unlock() 1005 return cleanOldVersion(sfs, fileID, v) 1006 } 1007 1008 func cleanOldVersion(sfs *swiftVFSV3, fileID string, v *vfs.Version) error { 1009 if err := sfs.Indexer.DeleteVersion(v); err != nil { 1010 return err 1011 } 1012 internalID := v.DocID 1013 if parts := strings.SplitN(v.DocID, "/", 2); len(parts) > 1 { 1014 internalID = parts[1] 1015 } 1016 objName := MakeObjectNameV3(fileID, internalID) 1017 return sfs.c.ObjectDelete(sfs.ctx, sfs.container, objName) 1018 } 1019 1020 func (sfs *swiftVFSV3) ClearOldVersions() error { 1021 if lockerr := sfs.mu.Lock(); lockerr != nil { 1022 return lockerr 1023 } 1024 defer sfs.mu.Unlock() 1025 diskUsage, _ := sfs.Indexer.DiskUsage() 1026 versions, err := sfs.Indexer.AllVersions() 1027 if err != nil { 1028 return err 1029 } 1030 var objNames []string 1031 var destroyed int64 1032 for _, v := range versions { 1033 if parts := strings.SplitN(v.DocID, "/", 2); len(parts) > 1 { 1034 objNames = append(objNames, MakeObjectNameV3(parts[0], parts[1])) 1035 } 1036 destroyed += v.ByteSize 1037 } 1038 if err := sfs.Indexer.BatchDeleteVersions(versions); err != nil { 1039 return err 1040 } 1041 vfs.DiskQuotaAfterDestroy(sfs, diskUsage, destroyed) 1042 return deleteContainerFiles(sfs.ctx, sfs.c, sfs.container, objNames) 1043 } 1044 1045 type swiftFileOpenV3 struct { 1046 f *swift.ObjectOpenFile 1047 br *bytes.Reader 1048 } 1049 1050 func (f *swiftFileOpenV3) Read(p []byte) (int, error) { 1051 return f.f.Read(p) 1052 } 1053 1054 func (f *swiftFileOpenV3) ReadAt(p []byte, off int64) (int, error) { 1055 if f.br == nil { 1056 buf, err := io.ReadAll(f.f) 1057 if err != nil { 1058 return 0, err 1059 } 1060 f.br = bytes.NewReader(buf) 1061 } 1062 return f.br.ReadAt(p, off) 1063 } 1064 1065 func (f *swiftFileOpenV3) Seek(offset int64, whence int) (int64, error) { 1066 n, err := f.f.Seek(context.Background(), offset, whence) 1067 if err != nil { 1068 logger.WithNamespace("vfsswift-v3").Warnf("Can't seek: %s", err) 1069 } 1070 return n, err 1071 } 1072 1073 func (f *swiftFileOpenV3) Write(p []byte) (int, error) { 1074 return 0, os.ErrInvalid 1075 } 1076 1077 func (f *swiftFileOpenV3) Close() error { 1078 return f.f.Close() 1079 } 1080 1081 var ( 1082 _ vfs.VFS = &swiftVFSV3{} 1083 _ vfs.File = &swiftFileCreationV3{} 1084 _ vfs.File = &swiftFileOpenV3{} 1085 )