//go:build darwin && cgo // Package local provides a filesystem interface package local import ( "context" "fmt" "path/filepath" "runtime" "github.com/go-darwin/apfs" "github.com/rclone/rclone/fs" ) // Copy src to this remote using server-side copy operations. // // # This is stored with the remote path given // // # It returns the destination Object and a possible error // // Will only be called if src.Fs().Name() == f.Name() // // If it isn't possible then return fs.ErrorCantCopy func (f *Fs) Copy(ctx context.Context, src fs.Object, remote string) (fs.Object, error) { if runtime.GOOS != "darwin" || f.opt.NoClone { return nil, fs.ErrorCantCopy } srcObj, ok := src.(*Object) if !ok { fs.Debugf(src, "Can't clone - not same remote type") return nil, fs.ErrorCantCopy } if f.opt.TranslateSymlinks && srcObj.translatedLink { // in --links mode, use cloning only for regular files return nil, fs.ErrorCantCopy } // Fetch metadata if --metadata is in use meta, err := fs.GetMetadataOptions(ctx, f, src, fs.MetadataAsOpenOptions(ctx)) if err != nil { return nil, fmt.Errorf("copy: failed to read metadata: %w", err) } // Create destination dstObj := f.newObject(remote) err = dstObj.mkdirAll() if err != nil { return nil, err } srcPath := srcObj.path if f.opt.FollowSymlinks { // in --copy-links mode, find the real file being pointed to and pass that in instead srcPath, err = filepath.EvalSymlinks(srcPath) if err != nil { return nil, err } } err = Clone(srcPath, f.localPath(remote)) if err != nil { return nil, err } // Set metadata if --metadata is in use if meta != nil { err = dstObj.writeMetadata(meta) if err != nil { return nil, fmt.Errorf("copy: failed to set metadata: %w", err) } } return f.NewObject(ctx, remote) } // Clone uses APFS cloning if possible, otherwise falls back to copying (with full metadata preservation) // note that this is closely related to unix.Clonefile(src, dst, unix.CLONE_NOFOLLOW) but not 100% identical // https://opensource.apple.com/source/copyfile/copyfile-173.40.2/copyfile.c.auto.html func Clone(src, dst string) error { state := apfs.CopyFileStateAlloc() defer func() { if err := apfs.CopyFileStateFree(state); err != nil { fs.Errorf(dst, "free state error: %v", err) } }() cloned, err := apfs.CopyFile(src, dst, state, apfs.COPYFILE_CLONE) fs.Debugf(dst, "isCloned: %v, error: %v", cloned, err) return err } // Check the interfaces are satisfied var ( _ fs.Copier = &Fs{} )