/
main.go
210 lines (178 loc) · 5.26 KB
/
main.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
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
package main
import (
"bytes"
"flag"
"fmt"
"io/ioutil"
"log"
"os"
"os/exec"
"path/filepath"
"strings"
"github.com/Debian/pk4/internal/humanbytes"
"pault.ag/go/debian/control"
)
type verboseLogger bool
func (v verboseLogger) Printf(format string, args ...interface{}) {
if !bool(v) {
return
}
log.Output(2, fmt.Sprintf(format, args...))
}
type invocation struct {
dest string
bin bool
src bool
version string
file bool
arg string
indexDir string
configDir string
verbose bool
diskUsageLimit int64
// TODO(security): ideally, allowUnauthenticated would not be implemented at
// all. However, snapshot.debian.org does not currently provide an
// up-to-date signature for once-verified packages, see
// https://bugs.debian.org/763419. Without such a trust path, we must rely
// on verifying signatures against the the current debian-keyring. That
// should work in most cases, but there are notable exceptions, like paultag
// revoking his key, rendering the fluxbox signatures unverifiable.
allowUnauthenticated bool
snapshotBase string // for testing
mirrorUrl string // for testing
lookPath func(file string) (string, error) // for testing
}
func (i *invocation) V() verboseLogger {
return verboseLogger(i.verbose)
}
func (i *invocation) readConfig(configPath string) error {
b, err := ioutil.ReadFile(configPath)
if err != nil {
if os.IsNotExist(err) {
return nil
}
return err
}
var config struct {
DiskUsageLimit string `control:"Disk-Usage-Limit"`
}
if err := control.Unmarshal(&config, bytes.NewReader(b)); err != nil {
return err
}
i.V().Printf("read config from %s: %+v", configPath, config)
if config.DiskUsageLimit != "" {
if v, err := humanbytes.Parse(config.DiskUsageLimit); err != nil {
log.Printf("invalid Disk-Usage-Limit value %q in config file %s: %v", config.DiskUsageLimit, configPath, err)
} else {
i.diskUsageLimit = v
}
}
return nil
}
func resolveTilde(s string) string {
if !strings.HasPrefix(s, "~") {
return s
}
// We need logic to resolve paths with a tilde prefix: bash passes such
// paths unexpanded.
homedir := os.Getenv("HOME")
if homedir == "" {
log.Fatalf("Cannot resolve path %q: environment variable $HOME empty", s)
}
return filepath.Join(homedir, strings.TrimPrefix(s, "~"))
}
func main() {
i := invocation{
lookPath: exec.LookPath,
indexDir: "/var/cache/pk4",
diskUsageLimit: 1 * 1024 * 1024 * 1024, // 1 GB
// TODO(https://bugs.debian.org/740096): switch to https once available
snapshotBase: "http://snapshot.debian.org/",
mirrorUrl: "https://deb.debian.org/debian",
}
flag.StringVar(&i.dest, "dest",
filepath.Join("~", ".cache", "pk4"),
"Directory in which to store source packages")
flag.BoolVar(&i.src, "src",
false,
"Restrict search to source packages only")
flag.BoolVar(&i.bin, "bin",
false,
"Restrict search to binary packages only")
flag.StringVar(&i.version, "version",
"",
"Use the specified source package version (default: installed package version, or latest known if not installed)")
flag.BoolVar(&i.file, "file",
false,
"Interpret the argument as a file name and operate on the package providing the file")
flag.BoolVar(&i.allowUnauthenticated, "allow_unauthenticated",
false,
"Whether to allow unauthenticated source packages, i.e. disable signature checking")
flag.BoolVar(&i.verbose, "verbose",
false,
"Whether to print messages to stderr")
complete := flag.Bool("complete",
false,
"Whether to return shell completions. Should usually be set by shell completion functions only.")
resolve := flag.Bool("resolve_only",
false,
`Resolve the provided arguments to source package and source package version, then print them to stdout in %s\t%s\n format and exit`)
shell := flag.String("shell",
os.Getenv("SHELL"),
"Which shell to start in the output directory after downloading the source")
flag.Parse()
if i.bin && i.src {
log.Fatalf("At most one of -bin or -src must be specified, not both")
}
i.dest = resolveTilde(i.dest)
i.configDir = resolveTilde("~/.config/pk4")
configPath := filepath.Join(i.configDir, "pk4.deb822")
if err := i.readConfig(configPath); err != nil {
log.Fatal(err)
}
if err := os.MkdirAll(i.dest, 0755); err != nil {
log.Fatal(err)
}
for n := 0; n < flag.NArg(); n++ {
i.arg = flag.Arg(n)
if *complete {
choices, err := i.complete()
if err != nil {
log.Fatal(err)
}
for _, choice := range choices {
fmt.Println(choice)
}
return
}
srcpkg, srcversion, err := i.resolve()
if err != nil {
log.Fatal(err)
}
if *resolve {
fmt.Printf("%s\t%s\n", srcpkg, srcversion)
return
}
outputDir, err := i.download(srcpkg, srcversion)
if err != nil {
log.Fatal(err)
}
if flag.NArg() == 1 {
subshell := exec.Command(*shell)
subshell.Dir = outputDir
subshell.Stdout = os.Stdout
subshell.Stdin = os.Stdin
subshell.Stderr = os.Stderr
subshell.Run()
}
}
if flag.NArg() == 1 {
return // already started a shell in the for loop, done
}
subshell := exec.Command(*shell)
subshell.Dir = i.dest
subshell.Stdout = os.Stdout
subshell.Stdin = os.Stdin
subshell.Stderr = os.Stderr
subshell.Run()
}