// FromStream reads the contents of the supplied io.Reader newBinary // and uses them to update the current program's executable file. // // FromStream performs the following actions to ensure a cross-platform safe // update: // // - Creates a new file, /path/to/.program-name.new with mode 0755 and copies // the contents of newBinary into the file // // - Renames the current program's executable file from /path/to/program-name // to /path/to/.program-name.old // // - Renames /path/to/.program-name.new to /path/to/program-name // // - If the rename is successful, it erases /path/to/.program.old. If this operation // fails, no error is reported. // // - If the rename is unsuccessful, it attempts to rename /path/to/.program-name.old // back to /path/to/program-name. If this operation fails, the error is not reported // in order to not mask the error that caused the rename recovery attempt. func FromStream(newBinary io.Reader) (err error, errRecover error) { // get the path to the executable thisExecPath, err := osext.Executable() if err != nil { return } // get the directory the executable exists in execDir := filepath.Dir(thisExecPath) execName := filepath.Base(thisExecPath) // Copy the contents of of newbinary to a the new executable file newExecPath := filepath.Join(execDir, fmt.Sprintf(".%s.new", execName)) fp, err := os.OpenFile(newExecPath, os.O_CREATE|os.O_WRONLY|os.O_TRUNC, 0755) if err != nil { return } defer fp.Close() _, err = io.Copy(fp, newBinary) // if we don't call fp.Close(), windows won't let us move the new executable // because the file will still be "in use" fp.Close() // this is where we'll move the executable to so that we can swap in the updated replacement oldExecPath := filepath.Join(execDir, fmt.Sprintf(".%s.old", execName)) // delete any existing old exec file - this is necessary on Windows for two reasons: // 1. after a successful update, windows can't remove the .old file because the process is still running // 2. windows rename operations fail if the destination file already exists _ = os.Remove(oldExecPath) // move the existing executable to a new file in the same directory err = os.Rename(thisExecPath, oldExecPath) if err != nil { return } // move the new exectuable in to become the new program err = os.Rename(newExecPath, thisExecPath) if err != nil { // copy unsuccessful errRecover = os.Rename(oldExecPath, thisExecPath) } else { // copy successful, remove the old binary _ = os.Remove(oldExecPath) } return }
// SanityCheck() attempts to determine whether an in-place executable update could // succeed by performing preliminary checks (to establish valid permissions, etc). // This helps avoid downloading updates when we know the update can't be successfully // applied later. func SanityCheck() (err error) { // get the path to the executable thisExecPath, err := osext.Executable() if err != nil { return } // get the directory the executable exists in execDir := filepath.Dir(thisExecPath) execName := filepath.Base(thisExecPath) // attempt to open a file in the executable's directory newExecPath := filepath.Join(execDir, fmt.Sprintf(".%s.new", execName)) fp, err := os.OpenFile(newExecPath, os.O_CREATE|os.O_WRONLY|os.O_TRUNC, 0755) if err != nil { return } fp.Close() _ = os.Remove(newExecPath) return }
// Update the currently running binary to a specific version func (d *Dist) UpdateTo(to string) (err error) { if d.Version == to { return errors.New("nothing to update") } binary, _ := osext.Executable() reader, err := os.Open(binary) if err != nil { return err } defer reader.Close() url := fmt.Sprintf("%s/projects/%s/diff/%s/%s/%s-%s", d.Host, d.Project, d.Version, to, runtime.GOOS, runtime.GOARCH) patch, err := d.httpGet(url) if err != nil { return err } writer := new(bytes.Buffer) err = binarydist.Patch(reader, writer, bytes.NewReader(patch)) if err != nil { return err } reader.Close() err, _ = update.FromStream(writer) return }