ALR

Форк
1
284 строки · 7.3 Кб
1
/*
2
 * ALR - Any Linux Repository
3
 * Copyright (C) 2024 Евгений Храмов
4
 *
5
 * This program is free software: you can redistribute it and/or modify
6
 * it under the terms of the GNU General Public License as published by
7
 * the Free Software Foundation, either version 3 of the License, or
8
 * (at your option) any later version.
9
 *
10
 * This program is distributed in the hope that it will be useful,
11
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
12
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
13
 * GNU General Public License for more details.
14
 *
15
 * You should have received a copy of the GNU General Public License
16
 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
17
 */
18

19
package helpers
20

21
import (
22
	"errors"
23
	"fmt"
24
	"io"
25
	"os"
26
	"path/filepath"
27
	"strconv"
28
	"strings"
29
	"unsafe"
30

31
	"github.com/go-git/go-git/v5"
32
	"github.com/go-git/go-git/v5/plumbing/object"
33
	"golang.org/x/exp/slices"
34
	"plemya-x.ru/alr/internal/shutils/handlers"
35
	"mvdan.cc/sh/v3/interp"
36
)
37

38
var (
39
	ErrNoPipe         = errors.New("command requires data to be piped in")
40
	ErrNoDetectManNum = errors.New("manual number cannot be detected from the filename")
41
)
42

43
// Helpers contains all the helper commands
44
var Helpers = handlers.ExecFuncs{
45
	"install-binary":       installHelperCmd("/usr/bin", 0o755),
46
	"install-systemd-user": installHelperCmd("/usr/lib/systemd/user", 0o644),
47
	"install-systemd":      installHelperCmd("/usr/lib/systemd/system", 0o644),
48
	"install-config":       installHelperCmd("/etc", 0o644),
49
	"install-license":      installHelperCmd("/usr/share/licenses", 0o644),
50
	"install-desktop":      installHelperCmd("/usr/share/applications", 0o644),
51
	"install-icon":         installHelperCmd("/usr/share/pixmaps", 0o644),
52
	"install-manual":       installManualCmd,
53
	"install-completion":   installCompletionCmd,
54
	"install-library":      installLibraryCmd,
55
	"git-version":          gitVersionCmd,
56
}
57

58
// Restricted contains restricted read-only helper commands
59
// that don't modify any state
60
var Restricted = handlers.ExecFuncs{
61
	"git-version": gitVersionCmd,
62
}
63

64
func installHelperCmd(prefix string, perms os.FileMode) handlers.ExecFunc {
65
	return func(hc interp.HandlerContext, cmd string, args []string) error {
66
		if len(args) < 1 {
67
			return handlers.InsufficientArgsError(cmd, 1, len(args))
68
		}
69

70
		from := resolvePath(hc, args[0])
71
		to := ""
72
		if len(args) > 1 {
73
			to = filepath.Join(hc.Env.Get("pkgdir").Str, prefix, args[1])
74
		} else {
75
			to = filepath.Join(hc.Env.Get("pkgdir").Str, prefix, filepath.Base(from))
76
		}
77

78
		err := helperInstall(from, to, perms)
79
		if err != nil {
80
			return fmt.Errorf("%s: %w", cmd, err)
81
		}
82
		return nil
83
	}
84
}
85

86
func installManualCmd(hc interp.HandlerContext, cmd string, args []string) error {
87
	if len(args) < 1 {
88
		return handlers.InsufficientArgsError(cmd, 1, len(args))
89
	}
90

91
	from := resolvePath(hc, args[0])
92
	number := filepath.Base(from)
93
	// The man page may be compressed with gzip.
94
	// If it is, the .gz extension must be removed to properly
95
	// detect the number at the end of the filename.
96
	number = strings.TrimSuffix(number, ".gz")
97
	number = strings.TrimPrefix(filepath.Ext(number), ".")
98

99
	// If number is not actually a number, return an error
100
	if _, err := strconv.Atoi(number); err != nil {
101
		return fmt.Errorf("install-manual: %w", ErrNoDetectManNum)
102
	}
103

104
	prefix := "/usr/share/man/man" + number
105
	to := filepath.Join(hc.Env.Get("pkgdir").Str, prefix, filepath.Base(from))
106

107
	return helperInstall(from, to, 0o644)
108
}
109

110
func installCompletionCmd(hc interp.HandlerContext, cmd string, args []string) error {
111
	// If the command's stdin is the same as the system's,
112
	// that means nothing was piped in. In this case, return an error.
113
	if hc.Stdin == os.Stdin {
114
		return fmt.Errorf("install-completion: %w", ErrNoPipe)
115
	}
116

117
	if len(args) < 2 {
118
		return handlers.InsufficientArgsError(cmd, 2, len(args))
119
	}
120

121
	shell := args[0]
122
	name := args[1]
123

124
	var prefix string
125
	switch shell {
126
	case "bash":
127
		prefix = "/usr/share/bash-completion/completions"
128
	case "zsh":
129
		prefix = "/usr/share/zsh/site-functions"
130
		name = "_" + name
131
	case "fish":
132
		prefix = "/usr/share/fish/vendor_completions.d"
133
		name += ".fish"
134
	}
135

136
	path := filepath.Join(hc.Env.Get("pkgdir").Str, prefix, name)
137

138
	err := os.MkdirAll(filepath.Dir(path), 0o755)
139
	if err != nil {
140
		return err
141
	}
142

143
	dst, err := os.OpenFile(path, os.O_TRUNC|os.O_CREATE|os.O_RDWR, 0o644)
144
	if err != nil {
145
		return err
146
	}
147
	defer dst.Close()
148

149
	_, err = io.Copy(dst, hc.Stdin)
150
	return err
151
}
152

153
func installLibraryCmd(hc interp.HandlerContext, cmd string, args []string) error {
154
	prefix := getLibPrefix(hc)
155
	fn := installHelperCmd(prefix, 0o755)
156
	return fn(hc, cmd, args)
157
}
158

159
// See https://wiki.debian.org/Multiarch/Tuples
160
var multiarchTupleMap = map[string]string{
161
	"386":      "i386-linux-gnu",
162
	"amd64":    "x86_64-linux-gnu",
163
	"arm5":     "arm-linux-gnueabi",
164
	"arm6":     "arm-linux-gnueabihf",
165
	"arm7":     "arm-linux-gnueabihf",
166
	"arm64":    "aarch64-linux-gnu",
167
	"mips":     "mips-linux-gnu",
168
	"mipsle":   "mipsel-linux-gnu",
169
	"mips64":   "mips64-linux-gnuabi64",
170
	"mips64le": "mips64el-linux-gnuabi64",
171
	"ppc64":    "powerpc64-linux-gnu",
172
	"ppc64le":  "powerpc64le-linux-gnu",
173
	"s390x":    "s390x-linux-gnu",
174
	"riscv64":  "riscv64-linux-gnu",
175
	"loong64":  "loongarch64-linux-gnu",
176
}
177

178
// usrLibDistros is a list of distros that don't support
179
// /usr/lib64, and must use /usr/lib
180
var usrLibDistros = []string{
181
	"arch",
182
	"alpine",
183
	"void",
184
	"chimera",
185
}
186

187
// Based on CMake's GNUInstallDirs
188
func getLibPrefix(hc interp.HandlerContext) string {
189
	if dir, ok := os.LookupEnv("ALR_LIB_DIR"); ok {
190
		return dir
191
	}
192

193
	out := "/usr/lib"
194

195
	distroID := hc.Env.Get("DISTRO_ID").Str
196
	distroLike := strings.Split(hc.Env.Get("DISTRO_ID_LIKE").Str, " ")
197

198
	for _, usrLibDistro := range usrLibDistros {
199
		if distroID == usrLibDistro || slices.Contains(distroLike, usrLibDistro) {
200
			return out
201
		}
202
	}
203

204
	wordSize := unsafe.Sizeof(uintptr(0))
205
	if wordSize == 8 {
206
		out = "/usr/lib64"
207
	}
208

209
	architecture := hc.Env.Get("ARCH").Str
210

211
	if distroID == "debian" || slices.Contains(distroLike, "debian") ||
212
		distroID == "ubuntu" || slices.Contains(distroLike, "ubuntu") {
213

214
		tuple, ok := multiarchTupleMap[architecture]
215
		if ok {
216
			out = filepath.Join("/usr/lib", tuple)
217
		}
218
	}
219

220
	return out
221
}
222

223
func gitVersionCmd(hc interp.HandlerContext, cmd string, args []string) error {
224
	path := hc.Dir
225
	if len(args) > 0 {
226
		path = resolvePath(hc, args[0])
227
	}
228

229
	r, err := git.PlainOpen(path)
230
	if err != nil {
231
		return fmt.Errorf("git-version: %w", err)
232
	}
233

234
	revNum := 0
235
	commits, err := r.Log(&git.LogOptions{})
236
	if err != nil {
237
		return fmt.Errorf("git-version: %w", err)
238
	}
239

240
	commits.ForEach(func(*object.Commit) error {
241
		revNum++
242
		return nil
243
	})
244

245
	HEAD, err := r.Head()
246
	if err != nil {
247
		return fmt.Errorf("git-version: %w", err)
248
	}
249

250
	hash := HEAD.Hash().String()
251

252
	fmt.Fprintf(hc.Stdout, "%d.%s\n", revNum, hash[:7])
253

254
	return nil
255
}
256

257
func helperInstall(from, to string, perms os.FileMode) error {
258
	err := os.MkdirAll(filepath.Dir(to), 0o755)
259
	if err != nil {
260
		return err
261
	}
262

263
	src, err := os.Open(from)
264
	if err != nil {
265
		return err
266
	}
267
	defer src.Close()
268

269
	dst, err := os.OpenFile(to, os.O_TRUNC|os.O_CREATE|os.O_RDWR, perms)
270
	if err != nil {
271
		return err
272
	}
273
	defer dst.Close()
274

275
	_, err = io.Copy(dst, src)
276
	return err
277
}
278

279
func resolvePath(hc interp.HandlerContext, path string) string {
280
	if !filepath.IsAbs(path) {
281
		return filepath.Join(hc.Dir, path)
282
	}
283
	return path
284
}
285

Использование cookies

Мы используем файлы cookie в соответствии с Политикой конфиденциальности и Политикой использования cookies.

Нажимая кнопку «Принимаю», Вы даете АО «СберТех» согласие на обработку Ваших персональных данных в целях совершенствования нашего веб-сайта и Сервиса GitVerse, а также повышения удобства их использования.

Запретить использование cookies Вы можете самостоятельно в настройках Вашего браузера.