initial commit [141.14.140.180,mike]

This commit is contained in:
2026-08-14 09:09:59 +02:00
commit 6a991a25a6
19 changed files with 6691 additions and 0 deletions
+355
View File
@@ -0,0 +1,355 @@
package main
import (
"archive/tar"
"archive/zip"
"bytes"
"compress/bzip2"
"compress/gzip"
"fmt"
"io"
"os"
"os/exec"
"path/filepath"
"strings"
)
// Single-file compression that Go does not carry in its standard library. Each
// entry lists the tools that can expand it, in order of preference; the first
// one found wins. All of them write to stdout with the flags given.
var external = map[string]struct {
tools []string
args []string
}{
"xz": {[]string{"xz", "unxz"}, []string{"-dc"}},
"lzma": {[]string{"xz", "unxz", "lzma"}, []string{"-dc"}},
"zst": {[]string{"zstd", "unzstd"}, []string{"-dcq"}},
"lz4": {[]string{"lz4", "unlz4"}, []string{"-dcq"}},
}
type decomp struct {
prog string
args []string
}
// decompressor reports how to expand ext, or nil if this machine cannot.
// gz and bz2 are handled in-process and need no helper at all.
func decompressor(ext string) *decomp {
switch ext {
case "gz", "bz2":
return &decomp{} // stdlib, no external program
}
e, ok := external[ext]
if !ok {
return nil
}
for _, t := range e.tools {
if p, err := exec.LookPath(t); err == nil {
return &decomp{prog: p, args: e.args}
}
}
return nil
}
func sevenZip() string {
for _, t := range []string{"7zz", "7z", "7za"} {
if p, err := exec.LookPath(t); err == nil {
return p
}
}
return ""
}
// reader wraps f in whatever ext needs, shelling out only when the standard
// library has no codec for it.
func decompressReader(ext, path string, f *os.File) (io.Reader, func() error, error) {
nop := func() error { return nil }
switch ext {
case "gz":
zr, err := gzip.NewReader(f)
if err != nil {
return nil, nil, err
}
return zr, zr.Close, nil
case "bz2":
return bzip2.NewReader(f), nop, nil
}
d := decompressor(ext)
if d == nil {
return nil, nil, fmt.Errorf("cannot unpack .%s - install %s or pick another asset with --asset/--pattern",
ext, strings.Join(external[ext].tools, "/"))
}
cmd := exec.Command(d.prog, append(append([]string{}, d.args...), path)...)
cmd.Stdin = nil
cmd.Stderr = os.Stderr
out, err := cmd.StdoutPipe()
if err != nil {
return nil, nil, err
}
if err := cmd.Start(); err != nil {
return nil, nil, err
}
return out, func() error {
io.Copy(io.Discard, out) // drain, or the child gets EPIPE
if err := cmd.Wait(); err != nil {
return fmt.Errorf("failed to decompress %s (%s: %w)", filepath.Base(path), filepath.Base(d.prog), err)
}
return nil
}, nil
}
// extractIfArchive unpacks into a fresh directory below tmpdir and returns its
// path, or "" if the asset is a bare binary (then the download is the binary).
func extractIfArchive(file, tmpdir string) (string, error) {
lc := strings.ToLower(filepath.Base(file))
out := filepath.Join(tmpdir, "x")
switch {
case tarballRe.MatchString(lc):
if err := os.Mkdir(out, 0o755); err != nil {
return "", err
}
return out, untar(file, out, tarCodec(lc))
case strings.HasSuffix(lc, ".zip"):
if err := os.Mkdir(out, 0o755); err != nil {
return "", err
}
return out, unzip(file, out)
case strings.HasSuffix(lc, ".7z"):
z := sevenZip()
if z == "" {
return "", fmt.Errorf("7z not found, but needed for %s", lc)
}
if err := os.Mkdir(out, 0o755); err != nil {
return "", err
}
cmd := exec.Command(z, "x", "-y", "-o"+out, file)
cmd.Stdout, cmd.Stderr = io.Discard, os.Stderr
if err := cmd.Run(); err != nil {
return "", fmt.Errorf("failed to extract %s: %w", lc, err)
}
return out, nil
}
// A single compressed file, e.g. restic_0.19.1_darwin_arm64.bz2 - the
// binary itself, just squeezed.
if m := singleRe.FindStringSubmatch(lc); m != nil {
ext := m[1]
if err := os.Mkdir(out, 0o755); err != nil {
return "", err
}
stem := strings.TrimSuffix(filepath.Base(file), filepath.Ext(file))
plain := filepath.Join(out, stem)
if err := decompressFile(ext, file, plain); err != nil {
return "", err
}
return out, nil
}
return "", nil // bare binary
}
// tarCodec maps a tarball suffix to the compression wrapped around the tar.
func tarCodec(lc string) string {
switch {
case strings.HasSuffix(lc, ".tar.gz"), strings.HasSuffix(lc, ".tgz"):
return "gz"
case strings.HasSuffix(lc, ".tar.bz2"), strings.HasSuffix(lc, ".tbz"):
return "bz2"
case strings.HasSuffix(lc, ".tar.xz"), strings.HasSuffix(lc, ".txz"):
return "xz"
case strings.HasSuffix(lc, ".tar.zst"), strings.HasSuffix(lc, ".tzst"):
return "zst"
}
return "" // plain .tar
}
func decompressFile(ext, src, dst string) error {
f, err := os.Open(src)
if err != nil {
return err
}
defer f.Close()
r, closer, err := decompressReader(ext, src, f)
if err != nil {
return err
}
out, err := os.Create(dst)
if err != nil {
return err
}
defer out.Close()
if _, err := io.Copy(out, r); err != nil {
return err
}
return closer()
}
func untar(file, dest, codec string) error {
f, err := os.Open(file)
if err != nil {
return err
}
defer f.Close()
var r io.Reader = f
closer := func() error { return nil }
if codec != "" {
r, closer, err = decompressReader(codec, file, f)
if err != nil {
return err
}
}
tr := tar.NewReader(r)
for {
h, err := tr.Next()
if err == io.EOF {
break
}
if err != nil {
return fmt.Errorf("failed to extract %s: %w", filepath.Base(file), err)
}
path, ok := safeJoin(dest, h.Name)
if !ok {
verbose("skipping %s (path escapes the archive)", h.Name)
continue
}
switch h.Typeflag {
case tar.TypeDir:
if err := os.MkdirAll(path, 0o755); err != nil {
return err
}
case tar.TypeReg:
if err := writeFileFrom(path, tr, os.FileMode(h.Mode).Perm()); err != nil {
return err
}
default:
// Symlinks and devices are never the binary we are after.
verbose("skipping %s (not a regular file)", h.Name)
}
}
return closer()
}
func unzip(file, dest string) error {
zr, err := zip.OpenReader(file)
if err != nil {
return fmt.Errorf("failed to extract %s: %w", filepath.Base(file), err)
}
defer zr.Close()
for _, f := range zr.File {
path, ok := safeJoin(dest, f.Name)
if !ok {
verbose("skipping %s (path escapes the archive)", f.Name)
continue
}
if f.FileInfo().IsDir() {
if err := os.MkdirAll(path, 0o755); err != nil {
return err
}
continue
}
if !f.Mode().IsRegular() {
continue
}
rc, err := f.Open()
if err != nil {
return err
}
err = writeFileFrom(path, rc, f.Mode().Perm())
rc.Close()
if err != nil {
return err
}
}
return nil
}
// safeJoin keeps an archive from writing outside its own directory.
func safeJoin(dest, name string) (string, bool) {
clean := filepath.Clean(filepath.FromSlash(name))
if filepath.IsAbs(clean) || clean == ".." || strings.HasPrefix(clean, ".."+string(os.PathSeparator)) {
return "", false
}
return filepath.Join(dest, clean), true
}
func writeFileFrom(path string, r io.Reader, mode os.FileMode) error {
if err := os.MkdirAll(filepath.Dir(path), 0o755); err != nil {
return err
}
if mode == 0 {
mode = 0o644
}
f, err := os.OpenFile(path, os.O_CREATE|os.O_TRUNC|os.O_WRONLY, mode)
if err != nil {
return err
}
defer f.Close()
_, err = io.Copy(f, r)
return err
}
// findInTree looks for a file called name; failing that, for the single
// executable in the tree.
func findInTree(root, name string) string {
var files []string
filepath.WalkDir(root, func(p string, d os.DirEntry, err error) error {
if err == nil && d.Type().IsRegular() {
files = append(files, p)
}
return nil
})
for _, p := range files {
if b := filepath.Base(p); b == name || b == name+".exe" {
return p
}
}
var execs []string
for _, p := range files {
if looksExecutable(p) {
execs = append(execs, p)
}
}
if len(execs) == 1 {
return execs[0]
}
verbose("archive contains %d files, %d of them executable", len(files), len(execs))
return ""
}
// Magic bytes beat guessing by file size: ELF, Mach-O (incl. fat), PE, script.
func looksExecutable(path string) bool {
f, err := os.Open(path)
if err != nil {
return false
}
defer f.Close()
m := make([]byte, 4)
n, _ := io.ReadFull(f, m)
if n < 2 {
return false
}
m = m[:n]
switch {
case bytes.HasPrefix(m, []byte("\x7fELF")):
return true
case bytes.HasPrefix(m, []byte{0xcf, 0xfa, 0xed, 0xfe}), // Mach-O 64
bytes.HasPrefix(m, []byte{0xce, 0xfa, 0xed, 0xfe}), // Mach-O 32
bytes.HasPrefix(m, []byte{0xca, 0xfe, 0xba, 0xbe}), // fat
bytes.HasPrefix(m, []byte{0xbe, 0xba, 0xfe, 0xca}):
return true
case bytes.HasPrefix(m, []byte("MZ")):
return true
case bytes.HasPrefix(m, []byte("#!")):
return true
}
return false
}