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  )