Skip to content

Commit

Permalink
Merge pull request #16 from hellofresh/minor/RDEV-966-copy-stream-ins…
Browse files Browse the repository at this point in the history
…tead

[SelfUpdate] Copy bytes from the stream instead of loading them all into the memory
  • Loading branch information
nhatthm authored Mar 2, 2023
2 parents 3bfd17c + 1fa3f96 commit 594d1ae
Showing 1 changed file with 38 additions and 30 deletions.
68 changes: 38 additions & 30 deletions apply.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,12 +3,12 @@ package updater
import (
"fmt"
"io"
"io/ioutil"
"os"
"path/filepath"
)

// Apply performs an update of the current executable (or opts.TargetFile, if set) with the contents of the given io.Reader.
// Apply performs an update of the current executable (or opts.TargetFile, if set) with the contents of the given
// io.Reader.
//
// Apply performs the following actions to ensure a safe cross-platform update:
// - Creates a new file, /path/to/.target.new with the TargetMode with the contents of the updated file
Expand All @@ -19,10 +19,10 @@ import (
// - If the final rename fails, attempts to roll back by renaming /path/to/.target.old
// back to /path/to/target.
//
// If the roll back operation fails, the file system is left in an inconsistent state where
// there is no new executable file and the old executable file could not be be moved to its original location. In this
// case you should notify the user of the bad news and ask them to recover manually. Applications can determine whether
// the rollback failed by calling RollbackError, see the documentation on that function for additional detail.
// If the rollback operation fails, the file system is left in an inconsistent state where there is no new executable
// file and the old executable file could not be be moved to its original location. In this case you should notify the
// user of the bad news and ask them to recover manually. Applications can determine whether the rollback failed by
// calling RollbackError, see the documentation on that function for additional detail.
func Apply(update io.Reader, targetPath string, targetMode os.FileMode) error {
if targetPath == "" {
executablePath, executableMode, err := executableInfo()
Expand All @@ -42,41 +42,35 @@ func Apply(update io.Reader, targetPath string, targetMode os.FileMode) error {
targetMode = targetStats.Mode()
}

// get the directory the executable exists in
// Get the directory the executable exists in.
updateDir := filepath.Dir(targetPath)
filename := filepath.Base(targetPath)

// define paths to use
// Define paths to use.
newPath := filepath.Join(updateDir, fmt.Sprintf(".%s.new", filename))
oldPath := filepath.Join(updateDir, fmt.Sprintf(".%s.old", filename))

// Read updated binary information
newBytes, err := ioutil.ReadAll(update)
if err != nil {
return err
}

// Copy the contents of newbinary to a new executable file
if err := ioutil.WriteFile(newPath, newBytes, targetMode); err != nil {
// Copy the new executable to a new file.
if err := copyToFile(update, newPath, targetMode); err != nil {
return err
}

// delete any existing old exec file
// Delete any existing old exec file.
os.Remove(oldPath)

// move the existing executable to a new file in the same directory
if err := os.Rename(targetPath, oldPath); err != nil {
return err
}

// move the new executable in to become the new program
// Move the new executable in to become the new program.
if err := os.Rename(newPath, targetPath); err != nil {
// move unsuccessful
// Move unsuccessful.
//
// The filesystem is now in a bad state. We have successfully moved the existing binary to a new location, but
// we couldn't move the new binary to take its place. That means there is no file where the current executable
// binary used to be!
//
// The filesystem is now in a bad state. We have successfully
// moved the existing binary to a new location, but we couldn't move the new
// binary to take its place. That means there is no file where the current executable binary
// used to be!
// Try to rollback by restoring the old binary to its original path.
rollbackErr := os.Rename(oldPath, targetPath)
if rollbackErr != nil {
Expand All @@ -86,20 +80,34 @@ func Apply(update io.Reader, targetPath string, targetMode os.FileMode) error {
return err
}

// remove the old binary
err = os.Remove(oldPath)
if err != nil {
// windows has trouble with removing old binaries, so hide it instead
// Remove the old binary.
if err := os.Remove(oldPath); err != nil {
// Windows has trouble with removing old binaries, so hide it instead.
_ = hideFile(oldPath)
}

return nil
}

// RollbackErr represents an error occurred during rollback operation
// RollbackErr represents an error occurred during rollback operation.
type RollbackErr struct {
// error the original error
// error the original error.
error
// RollbackErr the error encountered while rolling back
// RollbackErr the error encountered while rolling back.
RollbackErr error
}

func copyToFile(src io.Reader, dst string, mode os.FileMode) error {
f, err := os.OpenFile(dst, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, mode)
if err != nil {
return err
}

defer f.Close()

if _, err = io.Copy(f, src); err != nil {
return err
}

return f.Sync()
}

0 comments on commit 594d1ae

Please sign in to comment.