-
Notifications
You must be signed in to change notification settings - Fork 0
/
path.go
131 lines (109 loc) · 3.79 KB
/
path.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
package integrate
// handle integrations in PATH
//
// according to the XDG basedir spec we're supposed to just dump things into ~/.local/bin no XDG env to respect here
//
// User-specific executable files may be stored in $HOME/.local/bin.
// Distributions should ensure this directory shows up in the UNIX $PATH environment variable,
// at an appropriate place.
import (
"errors"
"fmt"
"io"
"io/fs"
"io/ioutil"
"os"
"path/filepath"
"strings"
)
const wrapperHeader = "#!/bin/sh\n" +
"# THIS FILE WAS AUTOMATICALLY GENERATED BY `ayy` DO NOT EDIT. NO REALLY YOU WONT JUST LOSE CHANGES, THINGS WILL BREAK.\n"
// creates a shell script that wraps the AppImage in path
func CreatePathWrapper(name, appimgpath string) error {
localbin := filepath.Join(os.Getenv("HOME"), ".local/bin")
wrapperpath := filepath.Join(localbin, name)
if exists(wrapperpath) {
return errors.New("a file with the specified name already exists in ~/.local/bin/. not creating " + wrapperpath)
}
// there is an argument that we should just symlink here
// however a symlink has the issue, that we later won't be able
// to tell if it has been created by us or the user.
// if it has been created by the user, we should probably not touch it
//
// e.g. the user uninstalls "foo" via ayy, and we find a symlink pointing to "foo.AppImage"
// i think we should only automatically delete that if it has also been created by ayy.
// this is why we don't create a simple symlink, but a shellscript instead
// so we can stuff some magic string in there to know this is something we created.
// that way we don't need to try to keep a database of stuff we should manage in sync
// we can just rescan ~/.local/bin and check the first few bytes for our header
wrapper := fmt.Sprintf(wrapperHeader+`%s "$@"`+"\n", appimgpath)
err := os.WriteFile(wrapperpath, []byte(wrapper), 0755)
return err
}
// checks if file is a path wrapper and deletes it if so
func RemovePathWrapper(name string) error {
localbin := filepath.Join(os.Getenv("HOME"), ".local/bin")
wrapperpath := filepath.Join(localbin, name)
if !IsPathWrapper(wrapperpath) {
return errors.New("file is not a wrapper managed by ayy: " + wrapperpath)
}
return os.Remove(wrapperpath)
}
func IsPathWrapper(path string) bool {
if !exists(path) {
return false
}
f, err := os.Open(path)
if err != nil {
return false
}
defer f.Close()
buf := make([]byte, len(wrapperHeader))
_, err = io.ReadFull(f, buf)
if err != nil {
// well, what we do now? only safe option is to claim it's not a path wrapper
// so that ayy leaves it alone
return false
}
bstr := string(buf)
return wrapperHeader == bstr
}
type PathWrapperEntry struct {
WrapperPath string
WrapperName string
AppImagePath string
}
func ListPathWrappers() (paths []PathWrapperEntry) {
localbin := filepath.Join(os.Getenv("HOME"), ".local/bin")
var wrapperList []PathWrapperEntry
filepath.Walk(localbin, func(path string, info fs.FileInfo, err error) error {
if IsPathWrapper(path) {
var pwe PathWrapperEntry
pwe.WrapperPath = path
pwe.WrapperName = filepath.Base(pwe.WrapperPath)
buf, err := ioutil.ReadFile(path)
if err != nil {
return nil // yes we want to swallow errors here
}
wrapper := string(buf)
// short of (basically deliberate) TOCTOU, [2] should be safe,
//since IsPathWrapper checks the header and guarantees 2 new lines
line := strings.TrimSpace(strings.Split(wrapper, "\n")[2])
line = strings.TrimSuffix(line, ` "$@"`)
pwe.AppImagePath = line
wrapperList = append(wrapperList, pwe)
}
return nil
})
return wrapperList
}
func PathWrappersForAppImage(path string) []PathWrapperEntry {
ret := make([]PathWrapperEntry, 0)
list := ListPathWrappers()
for _, pwe := range list {
if pwe.AppImagePath == path {
ret = append(ret, pwe)
}
}
return ret
}