systemd: serve kernel-log, the kernel's messages of one boot (issue 1975)
journalctl --dmesg --boot=N under the same escalation, window, priority, fixed-string match, 2000-line cap and redaction as the journal verb (shapeLines is the one filter both use); a boot is 0 or a negative whole number, refused otherwise before anything runs. No scope: the kernel belongs to no manager. Builds: issue 1975, the operator's request of 2026-11-12 (the lead's handover; no design line names the verb) Claude-Session: https://claude.ai/code/session_01NcbgfGKaxw984XyAPfLm4h
This commit is contained in:
@@ -332,28 +332,7 @@ func (m *Manager) Journal(scope Scope, unit string, q JournalQuery) (map[string]
|
||||
return nil, err
|
||||
}
|
||||
known, envErr := m.unitSecrets(scope, unit)
|
||||
all := []string{}
|
||||
for _, l := range strings.Split(out, "\n") {
|
||||
if l != "" {
|
||||
all = append(all, l)
|
||||
}
|
||||
}
|
||||
scanned := len(all)
|
||||
kept, redacted := []string{}, 0
|
||||
for _, l := range all {
|
||||
l, n := redact(l, known)
|
||||
redacted += n
|
||||
if q.Match != "" && !strings.Contains(l, q.Match) {
|
||||
continue
|
||||
}
|
||||
if len(l) > LongestLine {
|
||||
l = l[:LongestLine] + "…"
|
||||
}
|
||||
kept = append(kept, l)
|
||||
}
|
||||
if len(kept) > q.Lines {
|
||||
kept = kept[len(kept)-q.Lines:]
|
||||
}
|
||||
kept, scanned, redacted := shapeLines(out, known, q)
|
||||
answer := map[string]any{"unit": unit, "scope": string(scope), "lines": kept, "count": len(kept)}
|
||||
for k, v := range map[string]string{"since": q.Since, "until": q.Until, "match": q.Match, "priority": q.Priority} {
|
||||
if v != "" {
|
||||
@@ -376,6 +355,74 @@ func (m *Manager) Journal(scope Scope, unit string, q JournalQuery) (map[string]
|
||||
return answer, nil
|
||||
}
|
||||
|
||||
// shapeLines is the lines of journalctl's output that a query keeps, each redacted and cut, the last q.Lines of
|
||||
// those holding the match; and how many lines were read and how many secrets replaced.
|
||||
func shapeLines(out string, known []knownSecret, q JournalQuery) (kept []string, scanned, redacted int) {
|
||||
all := []string{}
|
||||
for _, l := range strings.Split(out, "\n") {
|
||||
if l != "" {
|
||||
all = append(all, l)
|
||||
}
|
||||
}
|
||||
scanned = len(all)
|
||||
kept = []string{}
|
||||
for _, l := range all {
|
||||
l, n := redact(l, known)
|
||||
redacted += n
|
||||
if q.Match != "" && !strings.Contains(l, q.Match) {
|
||||
continue
|
||||
}
|
||||
if len(l) > LongestLine {
|
||||
l = l[:LongestLine] + "…"
|
||||
}
|
||||
kept = append(kept, l)
|
||||
}
|
||||
if len(kept) > q.Lines {
|
||||
kept = kept[len(kept)-q.Lines:]
|
||||
}
|
||||
return kept, scanned, redacted
|
||||
}
|
||||
|
||||
// KernelLog is the kernel's messages of one boot (journalctl -k, dmesg's, of this boot or an earlier one) that a
|
||||
// query keeps: the same window, priority, match, cap and redaction as Journal. The kernel's messages belong to
|
||||
// no unit and no manager, so it has no scope; the read is the system journal's, escalated as Journal's is.
|
||||
func (m *Manager) KernelLog(q JournalQuery) (map[string]any, error) {
|
||||
q, err := q.valid()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
read := q.Lines
|
||||
if q.Match != "" {
|
||||
read = MatchScan
|
||||
}
|
||||
out, err := m.call(System, "journalctl", q.kernelArgv(read)...)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
kept, scanned, redacted := shapeLines(out, nil, q)
|
||||
boot := q.Boot
|
||||
if boot == "" {
|
||||
boot = "0"
|
||||
}
|
||||
answer := map[string]any{"boot": boot, "lines": kept, "count": len(kept)}
|
||||
for k, v := range map[string]string{"since": q.Since, "until": q.Until, "match": q.Match, "priority": q.Priority} {
|
||||
if v != "" {
|
||||
answer[k] = v
|
||||
}
|
||||
}
|
||||
if q.Match != "" {
|
||||
answer["scanned"] = scanned
|
||||
if scanned >= MatchScan {
|
||||
answer["note"] = fmt.Sprintf("the match was looked for in the window's last %d lines only: narrow the window to reach earlier ones", MatchScan)
|
||||
}
|
||||
}
|
||||
if redacted > 0 {
|
||||
answer["redacted"] = redacted
|
||||
answer["leak"] = "the kernel's messages hold secrets, shown as [redacted: <what it was>]"
|
||||
}
|
||||
return answer, nil
|
||||
}
|
||||
|
||||
// unitSecrets are the values of the unit's own Environment= that must not be answered. Read with
|
||||
// systemctl show, which needs no escalation; a unit that does not exist has none.
|
||||
func (m *Manager) unitSecrets(scope Scope, unit string) ([]knownSecret, error) {
|
||||
|
||||
Reference in New Issue
Block a user