Initial commit
This commit is contained in:
		
							
								
								
									
										284
									
								
								internal/shutils/helpers/helpers.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										284
									
								
								internal/shutils/helpers/helpers.go
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,284 @@ | ||||
| /* | ||||
|  * LURE - Linux User REpository | ||||
|  * Copyright (C) 2023 Elara Musayelyan | ||||
|  * | ||||
|  * This program is free software: you can redistribute it and/or modify | ||||
|  * it under the terms of the GNU General Public License as published by | ||||
|  * the Free Software Foundation, either version 3 of the License, or | ||||
|  * (at your option) any later version. | ||||
|  * | ||||
|  * This program is distributed in the hope that it will be useful, | ||||
|  * but WITHOUT ANY WARRANTY; without even the implied warranty of | ||||
|  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the | ||||
|  * GNU General Public License for more details. | ||||
|  * | ||||
|  * You should have received a copy of the GNU General Public License | ||||
|  * along with this program.  If not, see <http://www.gnu.org/licenses/>. | ||||
|  */ | ||||
|  | ||||
| package helpers | ||||
|  | ||||
| import ( | ||||
| 	"errors" | ||||
| 	"fmt" | ||||
| 	"io" | ||||
| 	"os" | ||||
| 	"path/filepath" | ||||
| 	"strconv" | ||||
| 	"strings" | ||||
| 	"unsafe" | ||||
|  | ||||
| 	"github.com/go-git/go-git/v5" | ||||
| 	"github.com/go-git/go-git/v5/plumbing/object" | ||||
| 	"golang.org/x/exp/slices" | ||||
| 	"lure.sh/lure/internal/shutils/handlers" | ||||
| 	"mvdan.cc/sh/v3/interp" | ||||
| ) | ||||
|  | ||||
| var ( | ||||
| 	ErrNoPipe         = errors.New("command requires data to be piped in") | ||||
| 	ErrNoDetectManNum = errors.New("manual number cannot be detected from the filename") | ||||
| ) | ||||
|  | ||||
| // Helpers contains all the helper commands | ||||
| var Helpers = handlers.ExecFuncs{ | ||||
| 	"install-binary":       installHelperCmd("/usr/bin", 0o755), | ||||
| 	"install-systemd-user": installHelperCmd("/usr/lib/systemd/user", 0o644), | ||||
| 	"install-systemd":      installHelperCmd("/usr/lib/systemd/system", 0o644), | ||||
| 	"install-config":       installHelperCmd("/etc", 0o644), | ||||
| 	"install-license":      installHelperCmd("/usr/share/licenses", 0o644), | ||||
| 	"install-desktop":      installHelperCmd("/usr/share/applications", 0o644), | ||||
| 	"install-icon":         installHelperCmd("/usr/share/pixmaps", 0o644), | ||||
| 	"install-manual":       installManualCmd, | ||||
| 	"install-completion":   installCompletionCmd, | ||||
| 	"install-library":      installLibraryCmd, | ||||
| 	"git-version":          gitVersionCmd, | ||||
| } | ||||
|  | ||||
| // Restricted contains restricted read-only helper commands | ||||
| // that don't modify any state | ||||
| var Restricted = handlers.ExecFuncs{ | ||||
| 	"git-version": gitVersionCmd, | ||||
| } | ||||
|  | ||||
| func installHelperCmd(prefix string, perms os.FileMode) handlers.ExecFunc { | ||||
| 	return func(hc interp.HandlerContext, cmd string, args []string) error { | ||||
| 		if len(args) < 1 { | ||||
| 			return handlers.InsufficientArgsError(cmd, 1, len(args)) | ||||
| 		} | ||||
|  | ||||
| 		from := resolvePath(hc, args[0]) | ||||
| 		to := "" | ||||
| 		if len(args) > 1 { | ||||
| 			to = filepath.Join(hc.Env.Get("pkgdir").Str, prefix, args[1]) | ||||
| 		} else { | ||||
| 			to = filepath.Join(hc.Env.Get("pkgdir").Str, prefix, filepath.Base(from)) | ||||
| 		} | ||||
|  | ||||
| 		err := helperInstall(from, to, perms) | ||||
| 		if err != nil { | ||||
| 			return fmt.Errorf("%s: %w", cmd, err) | ||||
| 		} | ||||
| 		return nil | ||||
| 	} | ||||
| } | ||||
|  | ||||
| func installManualCmd(hc interp.HandlerContext, cmd string, args []string) error { | ||||
| 	if len(args) < 1 { | ||||
| 		return handlers.InsufficientArgsError(cmd, 1, len(args)) | ||||
| 	} | ||||
|  | ||||
| 	from := resolvePath(hc, args[0]) | ||||
| 	number := filepath.Base(from) | ||||
| 	// The man page may be compressed with gzip. | ||||
| 	// If it is, the .gz extension must be removed to properly | ||||
| 	// detect the number at the end of the filename. | ||||
| 	number = strings.TrimSuffix(number, ".gz") | ||||
| 	number = strings.TrimPrefix(filepath.Ext(number), ".") | ||||
|  | ||||
| 	// If number is not actually a number, return an error | ||||
| 	if _, err := strconv.Atoi(number); err != nil { | ||||
| 		return fmt.Errorf("install-manual: %w", ErrNoDetectManNum) | ||||
| 	} | ||||
|  | ||||
| 	prefix := "/usr/share/man/man" + number | ||||
| 	to := filepath.Join(hc.Env.Get("pkgdir").Str, prefix, filepath.Base(from)) | ||||
|  | ||||
| 	return helperInstall(from, to, 0o644) | ||||
| } | ||||
|  | ||||
| func installCompletionCmd(hc interp.HandlerContext, cmd string, args []string) error { | ||||
| 	// If the command's stdin is the same as the system's, | ||||
| 	// that means nothing was piped in. In this case, return an error. | ||||
| 	if hc.Stdin == os.Stdin { | ||||
| 		return fmt.Errorf("install-completion: %w", ErrNoPipe) | ||||
| 	} | ||||
|  | ||||
| 	if len(args) < 2 { | ||||
| 		return handlers.InsufficientArgsError(cmd, 2, len(args)) | ||||
| 	} | ||||
|  | ||||
| 	shell := args[0] | ||||
| 	name := args[1] | ||||
|  | ||||
| 	var prefix string | ||||
| 	switch shell { | ||||
| 	case "bash": | ||||
| 		prefix = "/usr/share/bash-completion/completions" | ||||
| 	case "zsh": | ||||
| 		prefix = "/usr/share/zsh/site-functions" | ||||
| 		name = "_" + name | ||||
| 	case "fish": | ||||
| 		prefix = "/usr/share/fish/vendor_completions.d" | ||||
| 		name += ".fish" | ||||
| 	} | ||||
|  | ||||
| 	path := filepath.Join(hc.Env.Get("pkgdir").Str, prefix, name) | ||||
|  | ||||
| 	err := os.MkdirAll(filepath.Dir(path), 0o755) | ||||
| 	if err != nil { | ||||
| 		return err | ||||
| 	} | ||||
|  | ||||
| 	dst, err := os.OpenFile(path, os.O_TRUNC|os.O_CREATE|os.O_RDWR, 0o644) | ||||
| 	if err != nil { | ||||
| 		return err | ||||
| 	} | ||||
| 	defer dst.Close() | ||||
|  | ||||
| 	_, err = io.Copy(dst, hc.Stdin) | ||||
| 	return err | ||||
| } | ||||
|  | ||||
| func installLibraryCmd(hc interp.HandlerContext, cmd string, args []string) error { | ||||
| 	prefix := getLibPrefix(hc) | ||||
| 	fn := installHelperCmd(prefix, 0o755) | ||||
| 	return fn(hc, cmd, args) | ||||
| } | ||||
|  | ||||
| // See https://wiki.debian.org/Multiarch/Tuples | ||||
| var multiarchTupleMap = map[string]string{ | ||||
| 	"386":      "i386-linux-gnu", | ||||
| 	"amd64":    "x86_64-linux-gnu", | ||||
| 	"arm5":     "arm-linux-gnueabi", | ||||
| 	"arm6":     "arm-linux-gnueabihf", | ||||
| 	"arm7":     "arm-linux-gnueabihf", | ||||
| 	"arm64":    "aarch64-linux-gnu", | ||||
| 	"mips":     "mips-linux-gnu", | ||||
| 	"mipsle":   "mipsel-linux-gnu", | ||||
| 	"mips64":   "mips64-linux-gnuabi64", | ||||
| 	"mips64le": "mips64el-linux-gnuabi64", | ||||
| 	"ppc64":    "powerpc64-linux-gnu", | ||||
| 	"ppc64le":  "powerpc64le-linux-gnu", | ||||
| 	"s390x":    "s390x-linux-gnu", | ||||
| 	"riscv64":  "riscv64-linux-gnu", | ||||
| 	"loong64":  "loongarch64-linux-gnu", | ||||
| } | ||||
|  | ||||
| // usrLibDistros is a list of distros that don't support | ||||
| // /usr/lib64, and must use /usr/lib | ||||
| var usrLibDistros = []string{ | ||||
| 	"arch", | ||||
| 	"alpine", | ||||
| 	"void", | ||||
| 	"chimera", | ||||
| } | ||||
|  | ||||
| // Based on CMake's GNUInstallDirs | ||||
| func getLibPrefix(hc interp.HandlerContext) string { | ||||
| 	if dir, ok := os.LookupEnv("LURE_LIB_DIR"); ok { | ||||
| 		return dir | ||||
| 	} | ||||
|  | ||||
| 	out := "/usr/lib" | ||||
|  | ||||
| 	distroID := hc.Env.Get("DISTRO_ID").Str | ||||
| 	distroLike := strings.Split(hc.Env.Get("DISTRO_ID_LIKE").Str, " ") | ||||
|  | ||||
| 	for _, usrLibDistro := range usrLibDistros { | ||||
| 		if distroID == usrLibDistro || slices.Contains(distroLike, usrLibDistro) { | ||||
| 			return out | ||||
| 		} | ||||
| 	} | ||||
|  | ||||
| 	wordSize := unsafe.Sizeof(uintptr(0)) | ||||
| 	if wordSize == 8 { | ||||
| 		out = "/usr/lib64" | ||||
| 	} | ||||
|  | ||||
| 	architecture := hc.Env.Get("ARCH").Str | ||||
|  | ||||
| 	if distroID == "debian" || slices.Contains(distroLike, "debian") || | ||||
| 		distroID == "ubuntu" || slices.Contains(distroLike, "ubuntu") { | ||||
|  | ||||
| 		tuple, ok := multiarchTupleMap[architecture] | ||||
| 		if ok { | ||||
| 			out = filepath.Join("/usr/lib", tuple) | ||||
| 		} | ||||
| 	} | ||||
|  | ||||
| 	return out | ||||
| } | ||||
|  | ||||
| func gitVersionCmd(hc interp.HandlerContext, cmd string, args []string) error { | ||||
| 	path := hc.Dir | ||||
| 	if len(args) > 0 { | ||||
| 		path = resolvePath(hc, args[0]) | ||||
| 	} | ||||
|  | ||||
| 	r, err := git.PlainOpen(path) | ||||
| 	if err != nil { | ||||
| 		return fmt.Errorf("git-version: %w", err) | ||||
| 	} | ||||
|  | ||||
| 	revNum := 0 | ||||
| 	commits, err := r.Log(&git.LogOptions{}) | ||||
| 	if err != nil { | ||||
| 		return fmt.Errorf("git-version: %w", err) | ||||
| 	} | ||||
|  | ||||
| 	commits.ForEach(func(*object.Commit) error { | ||||
| 		revNum++ | ||||
| 		return nil | ||||
| 	}) | ||||
|  | ||||
| 	HEAD, err := r.Head() | ||||
| 	if err != nil { | ||||
| 		return fmt.Errorf("git-version: %w", err) | ||||
| 	} | ||||
|  | ||||
| 	hash := HEAD.Hash().String() | ||||
|  | ||||
| 	fmt.Fprintf(hc.Stdout, "%d.%s\n", revNum, hash[:7]) | ||||
|  | ||||
| 	return nil | ||||
| } | ||||
|  | ||||
| func helperInstall(from, to string, perms os.FileMode) error { | ||||
| 	err := os.MkdirAll(filepath.Dir(to), 0o755) | ||||
| 	if err != nil { | ||||
| 		return err | ||||
| 	} | ||||
|  | ||||
| 	src, err := os.Open(from) | ||||
| 	if err != nil { | ||||
| 		return err | ||||
| 	} | ||||
| 	defer src.Close() | ||||
|  | ||||
| 	dst, err := os.OpenFile(to, os.O_TRUNC|os.O_CREATE|os.O_RDWR, perms) | ||||
| 	if err != nil { | ||||
| 		return err | ||||
| 	} | ||||
| 	defer dst.Close() | ||||
|  | ||||
| 	_, err = io.Copy(dst, src) | ||||
| 	return err | ||||
| } | ||||
|  | ||||
| func resolvePath(hc interp.HandlerContext, path string) string { | ||||
| 	if !filepath.IsAbs(path) { | ||||
| 		return filepath.Join(hc.Dir, path) | ||||
| 	} | ||||
| 	return path | ||||
| } | ||||
		Reference in New Issue
	
	Block a user