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 := ©{ 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 }