112 lines
3.2 KiB
Go
112 lines
3.2 KiB
Go
package updates
|
|
|
|
import (
|
|
"sort"
|
|
"strings"
|
|
)
|
|
|
|
// isSecuritySuite reports whether an apt suite carries security fixes. Debian
|
|
// 11+ and every supported Ubuntu name them "<codename>-security".
|
|
func isSecuritySuite(s string) bool { return strings.HasSuffix(s, "-security") }
|
|
|
|
// securitySources reduces a host's apt source files to only the entries that
|
|
// point at a security suite, so an upgrade run against them installs security
|
|
// fixes and nothing else.
|
|
//
|
|
// It is a pure function of file contents so it is tested on any platform. The
|
|
// caller writes list to a *.list file and deb822 to a *.sources file in a
|
|
// temporary SourceParts directory: keeping deb822 paragraphs as deb822 means
|
|
// an inline Signed-By key block survives verbatim, which a conversion to
|
|
// one-line format could not carry.
|
|
//
|
|
// ok is false when no security suite exists at all. The caller must then
|
|
// report unsupported, never fall back to installing everything.
|
|
func securitySources(files map[string]string) (list string, deb822 string, ok bool) {
|
|
paths := make([]string, 0, len(files))
|
|
for p := range files {
|
|
paths = append(paths, p)
|
|
}
|
|
sort.Strings(paths) // deterministic output
|
|
|
|
var lb, db strings.Builder
|
|
for _, p := range paths {
|
|
if strings.HasSuffix(p, ".sources") {
|
|
db.WriteString(filterDeb822(files[p]))
|
|
} else {
|
|
lb.WriteString(filterOneLine(files[p]))
|
|
}
|
|
}
|
|
list, deb822 = lb.String(), db.String()
|
|
return list, deb822, list != "" || deb822 != ""
|
|
}
|
|
|
|
func filterOneLine(content string) string {
|
|
var b strings.Builder
|
|
for _, raw := range strings.Split(content, "\n") {
|
|
line := strings.TrimSpace(raw)
|
|
if line == "" || strings.HasPrefix(line, "#") {
|
|
continue
|
|
}
|
|
fields := strings.Fields(line)
|
|
if len(fields) < 3 || fields[0] != "deb" {
|
|
continue
|
|
}
|
|
i := 1
|
|
if strings.HasPrefix(fields[i], "[") {
|
|
// Options run until the token that closes the bracket.
|
|
for i < len(fields) && !strings.HasSuffix(fields[i], "]") {
|
|
i++
|
|
}
|
|
i++
|
|
}
|
|
// fields[i] is the URI, fields[i+1] the suite.
|
|
if i+1 < len(fields) && isSecuritySuite(fields[i+1]) {
|
|
b.WriteString(line)
|
|
b.WriteString("\n")
|
|
}
|
|
}
|
|
return b.String()
|
|
}
|
|
|
|
func filterDeb822(content string) string {
|
|
var b strings.Builder
|
|
for _, para := range strings.Split(strings.ReplaceAll(content, "\r\n", "\n"), "\n\n") {
|
|
lines := strings.Split(strings.Trim(para, "\n"), "\n")
|
|
var out []string
|
|
isDeb, enabled, kept := false, true, false
|
|
for _, l := range lines {
|
|
key, val, found := strings.Cut(l, ":")
|
|
k := strings.ToLower(strings.TrimSpace(key))
|
|
v := strings.TrimSpace(val)
|
|
switch {
|
|
case found && k == "types":
|
|
for _, t := range strings.Fields(v) {
|
|
if t == "deb" {
|
|
isDeb = true
|
|
}
|
|
}
|
|
case found && k == "enabled":
|
|
enabled = strings.ToLower(v) != "no"
|
|
case found && k == "suites":
|
|
var sec []string
|
|
for _, s := range strings.Fields(v) {
|
|
if isSecuritySuite(s) {
|
|
sec = append(sec, s)
|
|
}
|
|
}
|
|
if len(sec) == 0 {
|
|
continue // drop the line; the paragraph is dropped below
|
|
}
|
|
kept = true
|
|
l = "Suites: " + strings.Join(sec, " ")
|
|
}
|
|
out = append(out, l)
|
|
}
|
|
if isDeb && enabled && kept {
|
|
b.WriteString(strings.Join(out, "\n"))
|
|
b.WriteString("\n\n")
|
|
}
|
|
}
|
|
return b.String()
|
|
}
|