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
package install
import (
"fmt"
"io/fs"
"os"
"path/filepath"
"regexp"
"sort"
"strings"
)
type GCReport struct {
Kept map[string]string `json:"kept"`
Removed []string `json:"removed"`
}
func (o UpgradeOptions) binRef() *regexp.Regexp {
return regexp.MustCompile(regexp.QuoteMeta(o.bin()) + `/([0-9a-f]{64})/`)
}
func (o UpgradeOptions) pinnedByProcesses(ref *regexp.Regexp, kept map[string]string) {
entries, err := os.ReadDir(o.procRoot())
if err != nil {
return
}
for _, e := range entries {
if strings.Trim(e.Name(), "0123456789") != "" {
continue
}
exe, err := os.Readlink(filepath.Join(o.procRoot(), e.Name(), "exe"))
if err != nil {
continue
}
if m := ref.FindStringSubmatch(exe); m != nil {
if _, ok := kept[m[1]]; !ok {
kept[m[1]] = "the running process " + e.Name() + " executes it"
}
}
}
}
func pinnedByUnitFiles(dir string, ref *regexp.Regexp, kept map[string]string) {
filepath.WalkDir(dir, func(p string, d fs.DirEntry, err error) error {
if err != nil || d.IsDir() {
return nil
}
b, err := os.ReadFile(p)
if err != nil {
return nil
}
for _, m := range ref.FindAllStringSubmatch(string(b), -1) {
if _, ok := kept[m[1]]; !ok {
kept[m[1]] = "the unit file " + p + " names it"
}
}
return nil
})
}
func (o UpgradeOptions) procRoot() string {
if o.Proc != "" {
return o.Proc
}
return "/proc"
}
func GC(o UpgradeOptions) (GCReport, error) {
rep := GCReport{Kept: map[string]string{}}
for _, link := range []string{"current", "previous"} {
if t := readTarget(filepath.Join(o.bin(), link)); t != "" {
rep.Kept[t] = "bin/" + link
}
}
ref := o.binRef()
o.pinnedByProcesses(ref, rep.Kept)
dirs := []string{filepath.Join(o.Home, UserUnitDir)}
if o.RuntimeDir != "" {
dirs = append(dirs, filepath.Join(o.RuntimeDir, "systemd"))
}
for _, d := range dirs {
pinnedByUnitFiles(d, ref, rep.Kept)
}
entries, err := os.ReadDir(o.bin())
if err != nil {
return rep, err
}
for _, e := range entries {
if !e.IsDir() || !digestName.MatchString(e.Name()) {
continue
}
if _, ok := rep.Kept[e.Name()]; ok {
continue
}
if err := os.RemoveAll(filepath.Join(o.bin(), e.Name())); err != nil {
return rep, err
}
rep.Removed = append(rep.Removed, e.Name())
}
sort.Strings(rep.Removed)
return rep, nil
}
func (o UpgradeOptions) collect() {
rep, err := GC(o)
if err != nil {
fmt.Fprintf(o.out(), "gc: %v\n", err)
return
}
keys := make([]string, 0, len(rep.Kept))
for k := range rep.Kept {
keys = append(keys, k)
}
sort.Strings(keys)
for _, k := range keys {
fmt.Fprintf(o.out(), "gc: keep bin/%s (%s)\n", k, rep.Kept[k])
}
for _, r := range rep.Removed {
fmt.Fprintf(o.out(), "gc: removed bin/%s\n", r)
}
}