Go 142 lines
package selfupdate
import (
"archive/tar"
"archive/zip"
"compress/gzip"
"errors"
"fmt"
"io"
"os"
"path"
"path/filepath"
"strings"
)
// extract unpacks archivePath into dstDir and returns the absolute path
// to the eeco binary inside. The archives produced by scripts/build.sh
// place a single directory `${GOOS}_${GOARCH}/` at the root containing
// `eeco{.exe}` + README + LICENSE; extract resolves whichever entry
// matches the binary name and ignores the others.
func extract(archivePath, dstDir, goos string) (string, error) {
binName := "eeco"
if goos == "windows" {
binName = "eeco.exe"
}
switch {
case strings.HasSuffix(archivePath, ".tar.gz"):
return extractTarGz(archivePath, dstDir, binName)
case strings.HasSuffix(archivePath, ".zip"):
return extractZip(archivePath, dstDir, binName)
default:
return "", fmt.Errorf("unknown archive format: %s", archivePath)
}
}
func extractTarGz(archivePath, dstDir, binName string) (string, error) {
f, err := os.Open(archivePath)
if err != nil {
return "", err
}
defer f.Close()
gz, err := gzip.NewReader(f)
if err != nil {
return "", err
}
defer gz.Close()
tr := tar.NewReader(gz)
for {
h, err := tr.Next()
if errors.Is(err, io.EOF) {
break
}
if err != nil {
return "", err
}
if h.Typeflag != tar.TypeReg {
continue
}
if path.Base(h.Name) != binName {
continue
}
dst := filepath.Join(dstDir, binName)
if err := writeBinary(dst, tr, modeFromHeader(h.Mode)); err != nil {
return "", err
}
return dst, nil
}
return "", fmt.Errorf("binary %s not found in archive", binName)
}
func extractZip(archivePath, dstDir, binName string) (string, error) {
zr, err := zip.OpenReader(archivePath)
if err != nil {
return "", err
}
defer zr.Close()
for _, zf := range zr.File {
if path.Base(zf.Name) != binName {
continue
}
rc, err := zf.Open()
if err != nil {
return "", err
}
dst := filepath.Join(dstDir, binName)
err = writeBinary(dst, rc, modeFromHeader(int64(zf.Mode())))
rc.Close()
if err != nil {
return "", err
}
return dst, nil
}
return "", fmt.Errorf("binary %s not found in archive", binName)
}
func writeBinary(dst string, src io.Reader, mode os.FileMode) error {
if err := os.MkdirAll(filepath.Dir(dst), 0o755); err != nil {
return err
}
f, err := os.OpenFile(dst, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, mode)
if err != nil {
return err
}
if _, err := io.Copy(f, src); err != nil {
f.Close()
return err
}
return f.Close()
}
func modeFromHeader(mode int64) os.FileMode {
m := os.FileMode(mode & int64(os.ModePerm))
if m == 0 {
m = 0o755
}
return m | 0o100
}
func copyFile(src, dst string) error {
in, err := os.Open(src)
if err != nil {
return err
}
defer in.Close()
info, err := in.Stat()
if err != nil {
return err
}
if err := os.MkdirAll(filepath.Dir(dst), 0o755); err != nil {
return err
}
out, err := os.OpenFile(dst, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, info.Mode())
if err != nil {
return err
}
if _, err := io.Copy(out, in); err != nil {
out.Close()
return err
}
return out.Close()
}