github.com/rclone/rclone@v1.66.1-0.20240517100346-7b89735ae726/fs/operations/copy.go (about)

     1  // This file implements operations.Copy
     2  //
     3  // This is probably the most important operation in rclone.
     4  
     5  package operations
     6  
     7  import (
     8  	"context"
     9  	"errors"
    10  	"fmt"
    11  	"io"
    12  	"path"
    13  	"strings"
    14  	"time"
    15  	"unicode/utf8"
    16  
    17  	"github.com/rclone/rclone/fs"
    18  	"github.com/rclone/rclone/fs/accounting"
    19  	"github.com/rclone/rclone/fs/fserrors"
    20  	"github.com/rclone/rclone/fs/hash"
    21  	"github.com/rclone/rclone/lib/atexit"
    22  	"github.com/rclone/rclone/lib/pacer"
    23  	"github.com/rclone/rclone/lib/random"
    24  )
    25  
    26  // State of the copy
    27  type copy struct {
    28  	f             fs.Fs                // destination fs.Fs
    29  	dstFeatures   *fs.Features         // Features() for fs.Fs
    30  	dst           fs.Object            // destination object to update, may be nil
    31  	remote        string               // destination path, used if dst is nil
    32  	src           fs.Object            // source object
    33  	ci            *fs.ConfigInfo       // current config
    34  	maxTries      int                  // max number of tries to do the copy
    35  	doUpdate      bool                 // whether we are updating an existing file or not
    36  	hashType      hash.Type            // common hash to use
    37  	hashOption    *fs.HashesOption     // open option for the common hash
    38  	tr            *accounting.Transfer // accounting for the transfer
    39  	inplace       bool                 // set if we are updating inplace and not using a partial name
    40  	remoteForCopy string               // the name used for the transfer, either remote or remote+".partial"
    41  }
    42  
    43  // Used to remove a failed copy
    44  func (c *copy) removeFailedCopy(ctx context.Context, o fs.Object) {
    45  	if o == nil {
    46  		return
    47  	}
    48  	fs.Infof(o, "Removing failed copy")
    49  	err := o.Remove(ctx)
    50  	if err != nil {
    51  		fs.Infof(o, "Failed to remove failed copy: %s", err)
    52  	}
    53  }
    54  
    55  // Used to remove a failed partial copy
    56  func (c *copy) removeFailedPartialCopy(ctx context.Context, f fs.Fs, remote string) {
    57  	o, err := f.NewObject(ctx, remote)
    58  	if errors.Is(err, fs.ErrorObjectNotFound) {
    59  		// Assume object has been deleted
    60  		return
    61  	}
    62  	if err != nil {
    63  		fs.Infof(remote, "Failed to remove failed partial copy: %s", err)
    64  		return
    65  	}
    66  	c.removeFailedCopy(ctx, o)
    67  }
    68  
    69  // TruncateString s to n bytes.
    70  //
    71  // If s is valid UTF-8 then this may truncate to fewer than n bytes to
    72  // make the returned string also valid UTF-8.
    73  func TruncateString(s string, n int) string {
    74  	truncated := s[:n]
    75  	if !utf8.ValidString(s) {
    76  		// If input string wasn't valid UTF-8 then just return the truncation
    77  		return truncated
    78  	}
    79  	for len(truncated) > 0 {
    80  		if utf8.ValidString(truncated) {
    81  			return truncated
    82  		}
    83  		// Remove 1 byte until valid
    84  		truncated = truncated[:len(truncated)-1]
    85  	}
    86  	return truncated
    87  }
    88  
    89  // Check to see if we should be using a partial name and return the name for the copy and the inplace flag
    90  func (c *copy) checkPartial() (remoteForCopy string, inplace bool, err error) {
    91  	remoteForCopy = c.remote
    92  	if c.ci.Inplace || c.dstFeatures.Move == nil || !c.dstFeatures.PartialUploads || strings.HasSuffix(c.remote, ".rclonelink") {
    93  		return remoteForCopy, true, nil
    94  	}
    95  	if len(c.ci.PartialSuffix) > 16 {
    96  		return remoteForCopy, true, fmt.Errorf("expecting length of --partial-suffix to be not greater than %d but got %d", 16, len(c.ci.PartialSuffix))
    97  	}
    98  	// Avoid making the leaf name longer if it's already lengthy to avoid
    99  	// trouble with file name length limits.
   100  	suffix := "." + random.String(8) + c.ci.PartialSuffix
   101  	base := path.Base(remoteForCopy)
   102  	if len(base) > 100 {
   103  		remoteForCopy = TruncateString(remoteForCopy, len(remoteForCopy)-len(suffix)) + suffix
   104  	} else {
   105  		remoteForCopy += suffix
   106  	}
   107  	return remoteForCopy, false, nil
   108  }
   109  
   110  // Check to see if we have hit max transfer limits
   111  func (c *copy) checkLimits(ctx context.Context) (err error) {
   112  	if c.ci.MaxTransfer < 0 {
   113  		return nil
   114  	}
   115  	var bytesSoFar int64
   116  	if c.ci.CutoffMode == fs.CutoffModeCautious {
   117  		bytesSoFar = accounting.Stats(ctx).GetBytesWithPending() + c.src.Size()
   118  	} else {
   119  		bytesSoFar = accounting.Stats(ctx).GetBytes()
   120  	}
   121  	if bytesSoFar >= int64(c.ci.MaxTransfer) {
   122  		if c.ci.CutoffMode == fs.CutoffModeHard {
   123  			return accounting.ErrorMaxTransferLimitReachedFatal
   124  		}
   125  		return accounting.ErrorMaxTransferLimitReachedGraceful
   126  	}
   127  	return nil
   128  }
   129  
   130  // Server side copy c.src to (c.f, c.remoteForCopy) if possible or return fs.ErrorCantCopy if not
   131  func (c *copy) serverSideCopy(ctx context.Context) (actionTaken string, newDst fs.Object, err error) {
   132  	doCopy := c.dstFeatures.Copy
   133  	serverSideCopyOK := false
   134  	if doCopy == nil {
   135  		serverSideCopyOK = false
   136  	} else if SameConfig(c.src.Fs(), c.f) {
   137  		serverSideCopyOK = true
   138  	} else if SameRemoteType(c.src.Fs(), c.f) {
   139  		serverSideCopyOK = c.dstFeatures.ServerSideAcrossConfigs || c.ci.ServerSideAcrossConfigs
   140  	}
   141  	if !serverSideCopyOK {
   142  		return actionTaken, nil, fs.ErrorCantCopy
   143  	}
   144  	in := c.tr.Account(ctx, nil) // account the transfer
   145  	in.ServerSideTransferStart()
   146  	newDst, err = doCopy(ctx, c.src, c.remoteForCopy)
   147  	if err == nil {
   148  		in.ServerSideCopyEnd(newDst.Size()) // account the bytes for the server-side transfer
   149  	}
   150  	_ = in.Close()
   151  	if errors.Is(err, fs.ErrorCantCopy) {
   152  		c.tr.Reset(ctx) // skip incomplete accounting - will be overwritten by the manual copy
   153  	}
   154  	actionTaken = "Copied (server-side copy)"
   155  	return actionTaken, newDst, err
   156  }
   157  
   158  // Copy c.src to (c.f, c.remoteForCopy) using multiThreadCopy
   159  func (c *copy) multiThreadCopy(ctx context.Context, uploadOptions []fs.OpenOption) (actionTaken string, newDst fs.Object, err error) {
   160  	newDst, err = multiThreadCopy(ctx, c.f, c.remoteForCopy, c.src, c.ci.MultiThreadStreams, c.tr, uploadOptions...)
   161  	if c.doUpdate {
   162  		actionTaken = "Multi-thread Copied (replaced existing)"
   163  	} else {
   164  		actionTaken = "Multi-thread Copied (new)"
   165  	}
   166  	return actionTaken, newDst, err
   167  }
   168  
   169  // Copy the stream from in to (c.f, c.remoteForCopy) and close it
   170  //
   171  // Use Rcat to handle both remotes supporting and not supporting PutStream.
   172  func (c *copy) rcat(ctx context.Context, in io.ReadCloser) (actionTaken string, newDst fs.Object, err error) {
   173  	// Make any metadata to pass to rcat
   174  	var meta fs.Metadata
   175  	if c.ci.Metadata {
   176  		meta, err = fs.GetMetadata(ctx, c.src)
   177  		if err != nil {
   178  			fs.Errorf(c.src, "Failed to read metadata: %v", err)
   179  		}
   180  	}
   181  
   182  	// NB Rcat closes in0
   183  	newDst, err = Rcat(ctx, c.f, c.remoteForCopy, in, c.src.ModTime(ctx), meta)
   184  	if c.doUpdate {
   185  		actionTaken = "Copied (Rcat, replaced existing)"
   186  	} else {
   187  		actionTaken = "Copied (Rcat, new)"
   188  	}
   189  	return actionTaken, newDst, err
   190  }
   191  
   192  // Copy the stream from in to (c.f, c.remoteForCopy) and close it
   193  func (c *copy) updateOrPut(ctx context.Context, in io.ReadCloser, uploadOptions []fs.OpenOption) (actionTaken string, newDst fs.Object, err error) {
   194  	// account and buffer the transfer
   195  	inAcc := c.tr.Account(ctx, in).WithBuffer()
   196  	var wrappedSrc fs.ObjectInfo = c.src
   197  
   198  	// We try to pass the original object if possible
   199  	if c.src.Remote() != c.remoteForCopy {
   200  		wrappedSrc = fs.NewOverrideRemote(c.src, c.remoteForCopy)
   201  	}
   202  	if c.doUpdate && c.inplace {
   203  		err = c.dst.Update(ctx, inAcc, wrappedSrc, uploadOptions...)
   204  		// Make sure newDst is c.dst since we updated it
   205  		if err == nil {
   206  			newDst = c.dst
   207  		}
   208  	} else {
   209  		newDst, err = c.f.Put(ctx, inAcc, wrappedSrc, uploadOptions...)
   210  	}
   211  	closeErr := inAcc.Close()
   212  	if err == nil {
   213  		err = closeErr
   214  	}
   215  	if c.doUpdate {
   216  		actionTaken = "Copied (replaced existing)"
   217  	} else {
   218  		actionTaken = "Copied (new)"
   219  	}
   220  	return actionTaken, newDst, err
   221  }
   222  
   223  // Do a manual copy by reading the bytes and writing them
   224  func (c *copy) manualCopy(ctx context.Context) (actionTaken string, newDst fs.Object, err error) {
   225  	// Remove partial files on premature exit
   226  	if !c.inplace {
   227  		defer atexit.Unregister(atexit.Register(func() {
   228  			ctx := context.Background()
   229  			c.removeFailedPartialCopy(ctx, c.f, c.remoteForCopy)
   230  		}))
   231  	}
   232  
   233  	// Options for the upload
   234  	uploadOptions := []fs.OpenOption{c.hashOption}
   235  	for _, option := range c.ci.UploadHeaders {
   236  		uploadOptions = append(uploadOptions, option)
   237  	}
   238  	if c.ci.MetadataSet != nil {
   239  		uploadOptions = append(uploadOptions, fs.MetadataOption(c.ci.MetadataSet))
   240  	}
   241  
   242  	// Options for the download
   243  	downloadOptions := []fs.OpenOption{c.hashOption}
   244  	for _, option := range c.ci.DownloadHeaders {
   245  		downloadOptions = append(downloadOptions, option)
   246  	}
   247  
   248  	if doMultiThreadCopy(ctx, c.f, c.src) {
   249  		return c.multiThreadCopy(ctx, uploadOptions)
   250  	}
   251  
   252  	var in io.ReadCloser
   253  	in, err = Open(ctx, c.src, downloadOptions...)
   254  	if err != nil {
   255  		return actionTaken, nil, fmt.Errorf("failed to open source object: %w", err)
   256  	}
   257  
   258  	// Note that c.rcat and c.updateOrPut close in
   259  	if c.src.Size() == -1 {
   260  		return c.rcat(ctx, in)
   261  	}
   262  	return c.updateOrPut(ctx, in, uploadOptions)
   263  }
   264  
   265  // Verify the copy
   266  func (c *copy) verify(ctx context.Context, newDst fs.Object) (err error) {
   267  	// Verify sizes are the same after transfer
   268  	if sizeDiffers(ctx, c.src, newDst) {
   269  		return fmt.Errorf("corrupted on transfer: sizes differ src(%s) %d vs dst(%s) %d", c.src.Fs(), c.src.Size(), newDst.Fs(), newDst.Size())
   270  	}
   271  	// Verify hashes are the same after transfer - ignoring blank hashes
   272  	if c.hashType != hash.None {
   273  		// checkHashes has logs and counts errors
   274  		equal, _, srcSum, dstSum, _ := checkHashes(ctx, c.src, newDst, c.hashType)
   275  		if !equal {
   276  			return fmt.Errorf("corrupted on transfer: %v hashes differ src(%s) %q vs dst(%s) %q", c.hashType, c.src.Fs(), srcSum, newDst.Fs(), dstSum)
   277  		}
   278  	}
   279  	return nil
   280  }
   281  
   282  // copy src object to dst or f if nil.  If dst is nil then it uses
   283  // remote as the name of the new object.
   284  //
   285  // It returns the destination object if possible.  Note that this may
   286  // be nil.
   287  func (c *copy) copy(ctx context.Context) (newDst fs.Object, err error) {
   288  	var actionTaken string
   289  	retry := true
   290  	for tries := 0; retry && tries < c.maxTries; tries++ {
   291  		// Check we haven't hit any accounting limits
   292  		err = c.checkLimits(ctx)
   293  		if err != nil {
   294  			return nil, err
   295  		}
   296  
   297  		// Try server side copy
   298  		actionTaken, newDst, err = c.serverSideCopy(ctx)
   299  
   300  		// If can't server-side copy, do it manually
   301  		if errors.Is(err, fs.ErrorCantCopy) {
   302  			actionTaken, newDst, err = c.manualCopy(ctx)
   303  		}
   304  
   305  		// End if ctx is in error
   306  		if fserrors.ContextError(ctx, &err) {
   307  			break
   308  		}
   309  
   310  		// Retry if err returned a retry error
   311  		retry = false
   312  		if fserrors.IsRetryError(err) || fserrors.ShouldRetry(err) {
   313  			retry = true
   314  		} else if t, ok := pacer.IsRetryAfter(err); ok {
   315  			fs.Debugf(c.src, "Sleeping for %v (as indicated by the server) to obey Retry-After error: %v", t, err)
   316  			time.Sleep(t)
   317  			retry = true
   318  		}
   319  		if retry {
   320  			fs.Debugf(c.src, "Received error: %v - low level retry %d/%d", err, tries, c.maxTries)
   321  			c.tr.Reset(ctx) // skip incomplete accounting - will be overwritten by retry
   322  			continue
   323  		}
   324  	}
   325  	if err != nil {
   326  		err = fs.CountError(err)
   327  		fs.Errorf(c.src, "Failed to copy: %v", err)
   328  		if !c.inplace {
   329  			c.removeFailedPartialCopy(ctx, c.f, c.remoteForCopy)
   330  		}
   331  		return newDst, err
   332  	}
   333  
   334  	// Verify the copy
   335  	err = c.verify(ctx, newDst)
   336  	if err != nil {
   337  		fs.Errorf(newDst, "%v", err)
   338  		err = fs.CountError(err)
   339  		c.removeFailedCopy(ctx, newDst)
   340  		return nil, err
   341  	}
   342  
   343  	// Move the copied file to its real destination.
   344  	if !c.inplace && c.remoteForCopy != c.remote {
   345  		movedNewDst, err := c.dstFeatures.Move(ctx, newDst, c.remote)
   346  		if err != nil {
   347  			fs.Errorf(newDst, "partial file rename failed: %v", err)
   348  			err = fs.CountError(err)
   349  			c.removeFailedCopy(ctx, newDst)
   350  			return nil, err
   351  		}
   352  		fs.Debugf(newDst, "renamed to: %s", c.remote)
   353  		newDst = movedNewDst
   354  	}
   355  
   356  	// Log what we have done
   357  	if newDst != nil && c.src.String() != newDst.String() {
   358  		actionTaken = fmt.Sprintf("%s to: %s", actionTaken, newDst.String())
   359  	}
   360  	fs.Infof(c.src, "%s%s", actionTaken, fs.LogValueHide("size", fs.SizeSuffix(c.src.Size())))
   361  
   362  	return newDst, nil
   363  }
   364  
   365  // Copy src object to dst or f if nil.  If dst is nil then it uses
   366  // remote as the name of the new object.
   367  //
   368  // It returns the destination object if possible.  Note that this may
   369  // be nil.
   370  func Copy(ctx context.Context, f fs.Fs, dst fs.Object, remote string, src fs.Object) (newDst fs.Object, err error) {
   371  	ci := fs.GetConfig(ctx)
   372  	tr := accounting.Stats(ctx).NewTransfer(src, f)
   373  	defer func() {
   374  		tr.Done(ctx, err)
   375  	}()
   376  	if SkipDestructive(ctx, src, "copy") {
   377  		in := tr.Account(ctx, nil)
   378  		in.DryRun(src.Size())
   379  		return newDst, nil
   380  	}
   381  	c := &copy{
   382  		f:           f,
   383  		dstFeatures: f.Features(),
   384  		dst:         dst,
   385  		remote:      remote,
   386  		src:         src,
   387  		ci:          ci,
   388  		tr:          tr,
   389  		maxTries:    ci.LowLevelRetries,
   390  		doUpdate:    dst != nil,
   391  	}
   392  	c.hashType, c.hashOption = CommonHash(ctx, f, src.Fs())
   393  	if c.dst != nil {
   394  		c.remote = c.dst.Remote()
   395  	}
   396  	// Are we using partials?
   397  	//
   398  	// If so set the flag and update the name we use for the copy
   399  	c.remoteForCopy, c.inplace, err = c.checkPartial()
   400  	if err != nil {
   401  		return nil, err
   402  	}
   403  	// Do the copy now everything is set up
   404  	return c.copy(ctx)
   405  }
   406  
   407  // CopyFile moves a single file possibly to a new name
   408  func CopyFile(ctx context.Context, fdst fs.Fs, fsrc fs.Fs, dstFileName string, srcFileName string) (err error) {
   409  	return moveOrCopyFile(ctx, fdst, fsrc, dstFileName, srcFileName, true)
   410  }