@@ -52,10 +52,9 @@ func ExtractCpConfig(opts []requestconfig.RequestOption) (CpConfig, error) {
5252
5353// CpCallbacks provides optional progress callbacks for copy operations.
5454type CpCallbacks struct {
55- OnFileStart func (path string , size int64 ) // Called when a file starts copying
56- OnProgress func (bytesCopied int64 ) // Called as bytes are copied
57- OnFileEnd func (path string ) // Called when a file finishes copying
58- OnError func (path string , err error ) // Called on per-file errors (non-fatal)
55+ OnFileStart func (path string , size int64 ) // Called when a file starts copying
56+ OnProgress func (bytesCopied int64 ) // Called as bytes are copied
57+ OnFileEnd func (path string ) // Called when a file finishes copying
5958}
6059
6160// CpToInstanceOptions configures a copy-to-instance operation
@@ -471,15 +470,19 @@ func CpFromInstance(ctx context.Context, cfg CpConfig, opts CpFromInstanceOption
471470 if opts .Archive && (header .Uid != 0 || header .Gid != 0 ) {
472471 os .Chown (targetPath , int (header .Uid ), int (header .Gid ))
473472 }
474- } else if header .IsSymlink {
475- // Validate symlink target to prevent pointing outside destination
476- if filepath .IsAbs (header .LinkTarget ) || strings .HasPrefix (filepath .Clean (header .LinkTarget ), ".." ) {
477- return fmt .Errorf ("invalid symlink target: %s" , header .LinkTarget )
478- }
479- os .Remove (targetPath )
480- if err := os .Symlink (header .LinkTarget , targetPath ); err != nil {
481- return fmt .Errorf ("create symlink %s: %w" , targetPath , err )
482- }
473+ } else if header .IsSymlink {
474+ // Validate symlink target to prevent pointing outside destination
475+ if filepath .IsAbs (header .LinkTarget ) || strings .HasPrefix (filepath .Clean (header .LinkTarget ), ".." ) {
476+ return fmt .Errorf ("invalid symlink target: %s" , header .LinkTarget )
477+ }
478+ // Create parent directory if needed
479+ if err := os .MkdirAll (filepath .Dir (targetPath ), 0755 ); err != nil {
480+ return fmt .Errorf ("create parent dir for symlink: %w" , err )
481+ }
482+ os .Remove (targetPath )
483+ if err := os .Symlink (header .LinkTarget , targetPath ); err != nil {
484+ return fmt .Errorf ("create symlink %s: %w" , targetPath , err )
485+ }
483486 // Apply ownership if archive mode (use Lchown for symlinks)
484487 if opts .Archive && (header .Uid != 0 || header .Gid != 0 ) {
485488 os .Lchown (targetPath , int (header .Uid ), int (header .Gid ))
@@ -501,7 +504,9 @@ func CpFromInstance(ctx context.Context, cfg CpConfig, opts CpFromInstanceOption
501504
502505 case "end" :
503506 var endMarker cpEndMarker
504- json .Unmarshal (message , & endMarker )
507+ if err := json .Unmarshal (message , & endMarker ); err != nil {
508+ return fmt .Errorf ("invalid end marker: %w" , err )
509+ }
505510
506511 if currentFile != nil {
507512 currentFile .Close ()
0 commit comments