Skip to content

Commit 40c729e

Browse files
committed
fix(cp): ensure parent dirs for symlinks, check end marker unmarshal, remove unused OnError callback
- Add os.MkdirAll before creating symlinks in CpFromInstance to prevent ENOENT - Check json.Unmarshal error for end marker to avoid indefinite hangs on malformed messages - Remove unused OnError callback from CpCallbacks (never invoked)
1 parent c8e386b commit 40c729e

File tree

1 file changed

+19
-14
lines changed

1 file changed

+19
-14
lines changed

lib/cp.go

Lines changed: 19 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -52,10 +52,9 @@ func ExtractCpConfig(opts []requestconfig.RequestOption) (CpConfig, error) {
5252

5353
// CpCallbacks provides optional progress callbacks for copy operations.
5454
type 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

Comments
 (0)