Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion op-deployer/pkg/deployer/forge/binary.go
Original file line number Diff line number Diff line change
Expand Up @@ -254,7 +254,7 @@ func (b *StandardBin) downloadBinary(ctx context.Context, dest string) error {
if err := ioutil.Untar(tmpDir, tr); err != nil {
return fmt.Errorf("failed to untar: %w", err)
}
if err := os.Rename(path.Join(tmpDir, "forge"), path.Join(dest, "forge")); err != nil {
if err := ioutil.SafeRename(path.Join(tmpDir, "forge"), path.Join(dest, "forge")); err != nil {
return fmt.Errorf("failed to move binary: %w", err)
}
if err := os.Chmod(path.Join(dest, "forge"), 0o755); err != nil {
Expand Down
2 changes: 1 addition & 1 deletion op-service/github/release/provider.go
Original file line number Diff line number Diff line change
Expand Up @@ -437,7 +437,7 @@ func (d *GithubReleaseDownloader) download(ctx context.Context, version string,

// Move the extracted name to the destination path and ensure it is
// executable by clearing/setting appropriate file mode bits.
if err := os.Rename(sourcePath, destinationPath); err != nil {
if err := ioutil.SafeRename(sourcePath, destinationPath); err != nil {
return "", fmt.Errorf("failed to move name from %s to %s: %w", sourcePath, destinationPath, err)
}
if err := os.Chmod(destinationPath, 0o755); err != nil {
Expand Down
75 changes: 75 additions & 0 deletions op-service/ioutil/rename.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
package ioutil

import (
"fmt"
"io"
"os"
"syscall"

"errors"
)

// SafeRename attempts to rename a file from source to destination.
// If the rename fails due to cross-device link error, it falls back to copying the file and deleting the source.
func SafeRename(source, destination string) error {
// First see if we can just rename the file normally
err := os.Rename(source, destination)

// If we get an "invalid cross-device link" error, we need to do a copy and delete
if err != nil && errors.Is(err, syscall.EXDEV) {
return renameCrossDevice(source, destination)
}

return err
}

func renameCrossDevice(source, destination string) error {
// Open the source file
src, err := os.Open(source)
if err != nil {
return fmt.Errorf("rename: failed to open source file %s: %w", source, err)
}

// Create the destination file
dst, err := os.Create(destination)
if err != nil {
// Make sure to close the source file before returning
src.Close()

return fmt.Errorf("rename: failed to create destination file %s: %w", destination, err)
}

// Copy the contents over
_, err = io.Copy(dst, src)

// Close both files
src.Close()
dst.Close()

if err != nil {
return fmt.Errorf("rename: failed to copy source %s to destination %s: %w", source, destination, err)
}

// Get source file permissions
fileInfo, err := os.Stat(source)
if err != nil {
// Remove the destination file if we fail to stat the source
os.Remove(destination)

return fmt.Errorf("rename: failed to stat source %s: %w", source, err)
}

// Apply source file permissions to destination
err = os.Chmod(destination, fileInfo.Mode())
if err != nil {
// Remove the destination file if we fail to apply the permissions
os.Remove(destination)

return fmt.Errorf("rename: failed to apply file permissions to destination %s: %w", destination, err)
}

// Delete the source file
os.Remove(source)

return nil
}