Skip to content

Commit

Permalink
Allow updating gopass.exe on Windows (#2345)
Browse files Browse the repository at this point in the history
Windows will lock files being executed but we should be able to rename
them.

Fixes #2011

RELEASE_NOTES=[BUGFIX] Fix updater on Windows.

Signed-off-by: Dominik Schulz <dominik.schulz@gauner.org>

Signed-off-by: Dominik Schulz <dominik.schulz@gauner.org>
  • Loading branch information
dominikschulz authored Sep 20, 2022
1 parent fc91cda commit 7962f08
Show file tree
Hide file tree
Showing 4 changed files with 70 additions and 30 deletions.
7 changes: 0 additions & 7 deletions internal/action/update.go
Original file line number Diff line number Diff line change
@@ -1,9 +1,6 @@
package action

import (
"fmt"
"runtime"

"github.com/gopasspw/gopass/internal/action/exit"
"github.com/gopasspw/gopass/internal/out"
"github.com/gopasspw/gopass/internal/updater"
Expand All @@ -23,10 +20,6 @@ func (s *Action) Update(c *cli.Context) error {
return nil
}

if runtime.GOOS == "windows" {
return fmt.Errorf("gopass update is not supported on windows (#1722)")
}

out.Printf(ctx, "⚒ Checking for available updates ...")
if err := updater.Update(ctx, s.version); err != nil {
return exit.Error(exit.Unknown, err, "Failed to update gopass: %s", err)
Expand Down
5 changes: 5 additions & 0 deletions internal/updater/access_others.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,3 +8,8 @@ import "golang.org/x/sys/unix"
func canWrite(path string) error {
return unix.Access(path, unix.W_OK) //nolint:wrapcheck
}

func removeOldBinary(dir, dest string) error {
// no need, os.Rename will replace the destination
return nil
}
27 changes: 27 additions & 0 deletions internal/updater/access_windows.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,33 @@

package updater

import (
"fmt"
"os"
"path/filepath"
)

func canWrite(path string) error {
return nil
}

// Windows won't allow us to remove the binary that's currently being executed.
// So rename the binary and then the updater should be able to write it's
// update to the correct location.
//
// See https://stackoverflow.com/a/459860
func removeOldBinary(dir, dest string) error {
bakFile := filepath.Join(dir, filepath.Base(dest)+".bak")
// check if the bakup file already exists
if _, err := os.Stat(bakFile); err == nil {
// ... then remove it
_ = os.Remove(bakFile)
}
// we can't remove the currently running binary, but should be able to
// rename it.
if err := os.Rename(dest, bakFile); err != nil {
return fmt.Errorf("unable to rename %s to %s: %w", dest, bakFile, err)
}

return nil
}
61 changes: 38 additions & 23 deletions internal/updater/extract.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import (
"errors"
"fmt"
"io"
"io/ioutil"
"os"
"path/filepath"

Expand All @@ -17,26 +18,40 @@ import (

func extractFile(buf []byte, filename, dest string) error {
mode := os.FileMode(0o755)
dir := filepath.Dir(dest)

// if overwriting an existing binary retain it's mode flags
fi, err := os.Lstat(dest)
if err == nil {
mode = fi.Mode()
}

if err := os.Remove(dest); err != nil {
if !os.IsNotExist(err) {
return fmt.Errorf("unable to remove destination file: %w", err)
}
tfn, err := extractToTempFile(buf, filename, dest)
if err != nil {
return fmt.Errorf("failed to extract update to %s: %w", dest, err)
}

if err := removeOldBinary(dir, dest); err != nil {
return fmt.Errorf("failed to remove old binary %s: %w", dest, err)
}

// open the destination file for writing
dfh, err := os.OpenFile(dest, os.O_WRONLY|os.O_CREATE|os.O_EXCL, mode)
if err := os.Rename(tfn, dest); err != nil {
return fmt.Errorf("failed to rename tempfile %s to %s: %w", tfn, dest, err)
}

return os.Chmod(dest, mode)
}

func extractToTempFile(buf []byte, filename, dest string) (string, error) {
// open a temp file for writing
dir := filepath.Dir(dest)
dfh, err := ioutil.TempFile(dir, "gopass")
if err != nil {
return fmt.Errorf("failed to open file %q: %w", dest, err)
return "", fmt.Errorf("failed to create temp file in %s: %w", dir, err)
}

defer func() {
_ = dfh.Sync()
_ = dfh.Close()
}()

Expand All @@ -46,23 +61,23 @@ func extractFile(buf []byte, filename, dest string) error {
case ".gz":
gzr, err := gzip.NewReader(rd)
if err != nil {
return fmt.Errorf("failed to open gzip file: %w", err)
return "", fmt.Errorf("failed to open gzip file: %w", err)
}

return extractTar(gzr, dfh, dest)
return extractTar(gzr, dfh, dfh.Name())
case ".bz2":
return extractTar(bzip2.NewReader(rd), dfh, dest)
return extractTar(bzip2.NewReader(rd), dfh, dfh.Name())
case ".zip":
return extractZip(buf, dfh, dest)
return extractZip(buf, dfh, dfh.Name())
default:
return fmt.Errorf("unsupported file extension: %q", filepath.Ext(filename))
return "", fmt.Errorf("unsupported file extension: %q", filepath.Ext(filename))
}
}

func extractZip(buf []byte, dfh io.WriteCloser, dest string) error {
func extractZip(buf []byte, dfh io.WriteCloser, dest string) (string, error) {
zrd, err := zip.NewReader(bytes.NewReader(buf), int64(len(buf)))
if err != nil {
return fmt.Errorf("failed to open zip file: %w", err)
return "", fmt.Errorf("failed to open zip file: %w", err)
}

for i := 0; i < len(zrd.File); i++ {
Expand All @@ -72,26 +87,26 @@ func extractZip(buf []byte, dfh io.WriteCloser, dest string) error {

file, err := zrd.File[i].Open()
if err != nil {
return fmt.Errorf("failed to read from zip file: %w", err)
return "", fmt.Errorf("failed to read from zip file: %w", err)
}

n, err := io.Copy(dfh, file)
if err != nil {
_ = dfh.Close()
_ = os.Remove(dest)

return fmt.Errorf("failed to read gopass.exe from zip file: %w", err)
return "", fmt.Errorf("failed to read gopass.exe from zip file: %w", err)
}
// success
debug.Log("wrote %d bytes to %v", n, dest)

return nil
return dest, nil
}

return fmt.Errorf("file not found in archive")
return "", fmt.Errorf("file not found in archive")
}

func extractTar(rd io.Reader, dfh io.WriteCloser, dest string) error {
func extractTar(rd io.Reader, dfh io.WriteCloser, dest string) (string, error) {
tarReader := tar.NewReader(rd)

for {
Expand All @@ -101,7 +116,7 @@ func extractTar(rd io.Reader, dfh io.WriteCloser, dest string) error {
}

if err != nil {
return fmt.Errorf("failed to read from tar file: %w", err)
return "", fmt.Errorf("failed to read from tar file: %w", err)
}

name := filepath.Base(header.Name)
Expand All @@ -119,13 +134,13 @@ func extractTar(rd io.Reader, dfh io.WriteCloser, dest string) error {
_ = dfh.Close()
_ = os.Remove(dest)

return fmt.Errorf("failed to read gopass from tar file: %w", err)
return "", fmt.Errorf("failed to read gopass from tar file: %w", err)
}
// success
debug.Log("wrote %d bytes to %v", n, dest)

return nil
return dest, nil
}

return fmt.Errorf("file not found in archive")
return "", fmt.Errorf("file not found in archive")
}

0 comments on commit 7962f08

Please sign in to comment.