Skip to content

Commit

Permalink
tools: add tarinstaller
Browse files Browse the repository at this point in the history
  • Loading branch information
ImSingee committed Jan 4, 2024
1 parent 9234409 commit 2d14cf2
Show file tree
Hide file tree
Showing 6 changed files with 232 additions and 16 deletions.
4 changes: 3 additions & 1 deletion internal/extension-registry/install.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,8 +9,9 @@ import (

"github.com/ImSingee/kitty/internal/extension-registry/installer"
"github.com/ImSingee/kitty/internal/extension-registry/installer/impl/bininstaller"
"github.com/ImSingee/kitty/internal/extension-registry/installer/impl/bininstaller/tarinstaller"
"github.com/ImSingee/kitty/internal/extension-registry/installer/impl/bininstaller/zipinstaller"
"github.com/ImSingee/kitty/internal/extension-registry/installer/impl/goinstaller"
"github.com/ImSingee/kitty/internal/extension-registry/installer/impl/zipinstaller"
eroptions "github.com/ImSingee/kitty/internal/extension-registry/options"
)

Expand Down Expand Up @@ -109,6 +110,7 @@ func factoryIdl(factory installer.Factory) idl {
var installers = []idl{
factoryIdl(&bininstaller.Factory{}),
factoryIdl(&zipinstaller.Factory{}),
factoryIdl(&tarinstaller.Factory{}),
factoryIdl(&goinstaller.Factory{}),
}

Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package zipinstaller
package indirect

import (
"log/slog"
"net/url"
"os"
"path"
Expand All @@ -16,13 +17,7 @@ import (
erutils "github.com/ImSingee/kitty/internal/extension-registry/utils"
)

type Factory struct{}

func (Factory) Key() string {
return "dist-zip"
}

func (Factory) GetInstaller(o eroptions.AnyOptions) (installer.Installer, error) {
func GetInstaller(t string, o eroptions.AnyOptions) (installer.Installer, error) {
object := eroptions.Get(o, string(binkey.GetCurrentBinKey()))
object = eroptions.RenameDollarKey(object, "url", false)

Expand All @@ -40,15 +35,20 @@ func (Factory) GetInstaller(o eroptions.AnyOptions) (installer.Installer, error)
return nil, installer.ErrInstallerNotApplicable
}

return &Installer{url, bin}, nil
return &Installer{t, url, bin}, nil
}

type Installer struct {
t string
url string
bin string
}

func (i *Installer) Install(o *installer.InstallOptions) error {
t := i.t

slog.Debug("tar/zip installer", "t", t, "url", i.url, "bin", i.bin)

url, err := tmpl.Render(i.url, o)
if err != nil {
return ee.Wrap(err, "invalid url")
Expand All @@ -58,43 +58,57 @@ func (i *Installer) Install(o *installer.InstallOptions) error {
return ee.Wrap(err, "invalid bin")
}

slog.Debug("tar/zip installer (rendered)", "t", t, "url", url, "bin", bin)

filenameWithoutExt, ext, err := getFilenamePartsOfUrl(url)
if err != nil {
return err
}
filename := filenameWithoutExt + ext

slog.Debug("tar/zip installer", "filename", filename)

zipDownloadedTo, err := erutils.DownloadFileToTemp(url, "kdl-*-"+filename, o.ShowProgress)
if err != nil {
return ee.Wrap(err, "cannot download zip file")
return ee.Wrapf(err, "cannot download %s file", t)
}

if o.ShowProgress {
pp.Println("zip file downloaded to:", zipDownloadedTo)
pp.Printf("%s file downloaded to: %s\n", t, zipDownloadedTo)
}

unzipToDir, err := os.MkdirTemp("", "kunzip-*")
if err != nil {
return ee.Wrap(err, "cannot create temp dir")
}
slog.Debug("tar/zip installer untar/unzip to", "target", unzipToDir)

if o.ShowProgress {
pp.Println("Extract ...")
}
err = erutils.Unzip(zipDownloadedTo, unzipToDir)

switch t {
case "zip":
err = erutils.Unzip(zipDownloadedTo, unzipToDir)
case "tar":
err = erutils.Untar(zipDownloadedTo, unzipToDir)
default:
panic("zip/tar installer got unknown t " + t)
}

if err != nil {
return ee.Wrap(err, "cannot unzip file")
return ee.Wrapf(err, "cannot un%s file", t)
}

distFrom := path.Join(unzipToDir, bin)
if _, err := os.Lstat(distFrom); err != nil {
return ee.Wrapf(err, "cannot find bin file %s in zip", bin)
return ee.Wrapf(err, "cannot find bin file %s in %s", bin, t)
}

_, _ = erutils.MkdirFor(o.To)
err = erutils.Rename(distFrom, o.To)
if err != nil {
return ee.Wrapf(err, "failed to install bin file %s from zip", bin)
return ee.Wrapf(err, "failed to install bin file %s from %s", bin, t)
}

return nil
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
package tarinstaller

import (
"github.com/ImSingee/kitty/internal/extension-registry/installer"
"github.com/ImSingee/kitty/internal/extension-registry/installer/impl/bininstaller/indirect"
eroptions "github.com/ImSingee/kitty/internal/extension-registry/options"
)

type Factory struct{}

func (Factory) Key() string {
return "dist-tar"
}

func (Factory) GetInstaller(o eroptions.AnyOptions) (installer.Installer, error) {
return indirect.GetInstaller("tar", o)
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
package zipinstaller

import (
"github.com/ImSingee/kitty/internal/extension-registry/installer"
"github.com/ImSingee/kitty/internal/extension-registry/installer/impl/bininstaller/indirect"
eroptions "github.com/ImSingee/kitty/internal/extension-registry/options"
)

type Factory struct{}

func (Factory) Key() string {
return "dist-zip"
}

func (Factory) GetInstaller(o eroptions.AnyOptions) (installer.Installer, error) {
return indirect.GetInstaller("zip", o)
}
2 changes: 2 additions & 0 deletions internal/extension-registry/utils/download.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ package erutils

import (
"io"
"log/slog"
"net/http"
"os"

Expand Down Expand Up @@ -65,6 +66,7 @@ func DownloadFileToTemp(url string, temppattern string, showProgress bool) (stri
}
defer f.Close()

slog.Debug("DownloadFileToTemp", "url", url, "to", f.Name())
err = downloadFileTo(url, f, showProgress)
if err != nil {
return "", ee.Wrap(err, "cannot download file to [tempfile]")
Expand Down
164 changes: 164 additions & 0 deletions internal/extension-registry/utils/tar.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,164 @@
package erutils

import (
"archive/tar"
"compress/gzip"
"errors"
"fmt"
"io"
"io/fs"
"log"
"os"
"path"
"path/filepath"
"runtime"
"strings"
"time"
)

// Untar reads the tar file and writes it into dir.
func Untar(filename string, dir string) error {
f, err := os.Open(filename)
if err != nil {
return err
}
defer f.Close()

return untar(filename, f, dir)
}

func untar(filename string, r io.Reader, dir string) (err error) {
t0 := time.Now()
nFiles := 0
madeDir := map[string]bool{}
defer func() {
td := time.Since(t0)
if err == nil {
log.Printf("extracted tarball into %s: %d files, %d dirs (%v)", dir, nFiles, len(madeDir), td)
} else {
log.Printf("error extracting tarball into %s after %d files, %d dirs, %v: %v", dir, nFiles, len(madeDir), td, err)
}
}()
zr, err := decompress(filename, r)
if err != nil {
return fmt.Errorf("cannot detect compress type: %v", err)
}
tr := tar.NewReader(zr)
loggedChtimesError := false
for {
f, err := tr.Next()
if err == io.EOF {
break
}
if err != nil {
log.Printf("tar reading error: %v", err)
return fmt.Errorf("tar error: %v", err)
}
if !validRelPath(f.Name) {
return fmt.Errorf("tar contained invalid name error %q", f.Name)
}
rel := filepath.FromSlash(f.Name)
abs := filepath.Join(dir, rel)

mode := f.FileInfo().Mode()
switch f.Typeflag {
case tar.TypeReg:
// Make the directory. This is redundant because it should
// already be made by a directory entry in the tar
// beforehand. Thus, don't check for errors; the next
// write will fail with the same error.
dir := filepath.Dir(abs)
if !madeDir[dir] {
if err := os.MkdirAll(filepath.Dir(abs), 0755); err != nil {
return err
}
madeDir[dir] = true
}
if runtime.GOOS == "darwin" && mode&0111 != 0 {
// The darwin kernel caches binary signatures
// and SIGKILLs binaries with mismatched
// signatures. Overwriting a binary with
// O_TRUNC does not clear the cache, rendering
// the new copy unusable. Removing the original
// file first does clear the cache. See #54132.
err := os.Remove(abs)
if err != nil && !errors.Is(err, fs.ErrNotExist) {
return err
}
}
wf, err := os.OpenFile(abs, os.O_RDWR|os.O_CREATE|os.O_TRUNC, mode.Perm())
if err != nil {
return err
}
n, err := io.Copy(wf, tr)
if closeErr := wf.Close(); closeErr != nil && err == nil {
err = closeErr
}
if err != nil {
return fmt.Errorf("error writing to %s: %v", abs, err)
}
if n != f.Size {
return fmt.Errorf("only wrote %d bytes to %s; expected %d", n, abs, f.Size)
}
modTime := f.ModTime
if modTime.After(t0) {
// Clamp modtimes at system time. See
// golang.org/issue/19062 when clock on
// buildlet was behind the gitmirror server
// doing the git-archive.
modTime = t0
}
if !modTime.IsZero() {
if err := os.Chtimes(abs, modTime, modTime); err != nil && !loggedChtimesError {
// benign error. Gerrit doesn't even set the
// modtime in these, and we don't end up relying
// on it anywhere (the gomote push command relies
// on digests only), so this is a little pointless
// for now.
log.Printf("error changing modtime: %v (further Chtimes errors suppressed)", err)
loggedChtimesError = true // once is enough
}
}
nFiles++
case tar.TypeDir:
if err := os.MkdirAll(abs, 0755); err != nil {
return err
}
madeDir[abs] = true
case tar.TypeXGlobalHeader:
// git archive generates these. Ignore them.
default:
return fmt.Errorf("tar file entry %s contained unsupported file type %v", f.Name, mode)
}
}
return nil
}

func decompress(filename string, archive io.Reader) (io.ReadCloser, error) {
switch {
case strings.HasSuffix(filename, ".tar.gz"):
return gzip.NewReader(archive)
case strings.HasSuffix(filename, ".tar"):
return io.NopCloser(archive), nil
default:
return nil, fmt.Errorf("unknown archive format: %s", filename)
}
}

func validRelativeDir(dir string) bool {
if strings.Contains(dir, `\`) || path.IsAbs(dir) {
return false
}
dir = path.Clean(dir)
if strings.HasPrefix(dir, "../") || strings.HasSuffix(dir, "/..") || dir == ".." {
return false
}
return true
}

func validRelPath(p string) bool {
if p == "" || strings.Contains(p, `\`) || strings.HasPrefix(p, "/") || strings.Contains(p, "../") {
return false
}
return true
}

0 comments on commit 2d14cf2

Please sign in to comment.