Skip to content

Commit

Permalink
Use D-Bus to get Systemd version
Browse files Browse the repository at this point in the history
  • Loading branch information
belimawr committed May 23, 2024
1 parent c997503 commit 32d12e3
Show file tree
Hide file tree
Showing 2 changed files with 73 additions and 30 deletions.
81 changes: 65 additions & 16 deletions filebeat/input/journald/input.go
Original file line number Diff line number Diff line change
Expand Up @@ -20,14 +20,14 @@
package journald

import (
"context"
"errors"
"fmt"
"os/exec"
"strconv"
"strings"
"time"

"github.com/coreos/go-systemd/v22/sdjournal"
"github.com/godbus/dbus/v5"
"github.com/urso/sderr"

"github.com/elastic/beats/v7/filebeat/input/journald/pkg/journalfield"
Expand Down Expand Up @@ -63,6 +63,10 @@ type checkpoint struct {
MonotonicTimestamp uint64
}

// errCannotConnectToDBus is returned when the connection to D-Bus
// cannot be established.
var errCannotConnectToDBus = errors.New("cannot connect to D-Bus")

// LocalSystemJournalID is the ID of the local system journal.
const localSystemJournalID = "LOCAL_SYSTEM_JOURNAL"

Expand All @@ -88,7 +92,7 @@ func Plugin(log *logp.Logger, store cursor.StateStore) input.Plugin {
Manager: m,
}

version, err := getJournaldVersion()
version, err := systemdVersion()
if err != nil {
configErr := fmt.Errorf("%w: %s", ErrCannotGetSystemdVersion, err)
m.Configure = func(_ *conf.C) ([]cursor.Source, cursor.Input, error) {
Expand Down Expand Up @@ -331,25 +335,70 @@ func (r *readerAdapter) Next() (reader.Message, error) {

return m, nil
}
func getJournaldVersion() (int, error) {
cmd := exec.CommandContext(context.Background(), "journalctl", "--version")
outputBytes, err := cmd.CombinedOutput()

// parseSystemdVersion parses the string version from Systemd fetched via D-Bus.
//
// The expected format is:
//
// - 255.6-1-arch
// - 252.16-1.amzn2023.0.2
//
// The function will parse and return the integer before the full stop.
func parseSystemdVersion(output string) (int, error) {
parts := strings.Split(output, ".")
if len(parts) < 2 {
return 0, errors.New("unexpected format for version.")
}

version, err := strconv.Atoi(parts[0])
if err != nil {
return 0, fmt.Errorf("cannot parse Systemd version: %s", err)
}

return version, err
}

// getSystemdVersionViaDBus foo
//
// Version string should not be parsed:
//
// Version encodes the version string of the running systemd
// instance. Note that the version string is purely informational,
// it should not be parsed, one may not assume the version to be
// formatted in any particular way. We take the liberty to change
// the versioning scheme at any time and it is not part of the API.
// Source: https://www.freedesktop.org/wiki/Software/systemd/dbus/
func getSystemdVersionViaDBus() (string, error) {
conn, err := dbus.ConnectSessionBus()
if err != nil {
return "", fmt.Errorf("%w: %w", errCannotConnectToDBus, err)
}
defer conn.Close()

obj := conn.Object("org.freedesktop.systemd1", "/org/freedesktop/systemd1")
resp, err := obj.GetProperty("org.freedesktop.systemd1.Manager.Version")
if err != nil {
return 0, fmt.Errorf("cannot call journald. Err: '%w'. Output: '%s'", err, string(outputBytes))
return "", fmt.Errorf("cannot get version property from D-Bus %w", err)
}

return parseJournaldVersion(string(outputBytes))
version := ""
if err := resp.Store(&version); err != nil {
return "", fmt.Errorf("cannot store Systemd version into Go string: %s", err)
}

return version, nil
}

func parseJournaldVersion(output string) (int, error) {
lines := strings.Split(output, "\n")
if len(lines) < 2 {
return 0, fmt.Errorf("unexpected format for version command. Returned value: '%s'", output)
func systemdVersion() (int, error) {
versionStr, err := getSystemdVersionViaDBus()
if err != nil {
return 0, fmt.Errorf("caanot get Systemd version: %w", err)
}

versionLine := lines[0]
version := 0
_, err := fmt.Sscanf(versionLine, "systemd %d", &version)
version, err := parseSystemdVersion(versionStr)
if err != nil {
return 0, fmt.Errorf("cannot parse Systemd version: %w", err)
}

return version, err
return version, nil
}
22 changes: 8 additions & 14 deletions filebeat/input/journald/input_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -92,24 +92,21 @@ func TestParseJournaldVersion(t *testing.T) {
}{
"Archlinux": {
expected: 255,
data: `systemd 255 (255.6-1-arch)
+PAM +AUDIT -SELINUX -APPARMOR -IMA +SMACK +SECCOMP +GCRYPT +GNUTLS +OPENSSL +ACL +BLKID +CURL +ELFUTILS +FIDO2 +IDN2 -IDN +IPTC +KMOD +LIBCRYPTSETUP +LIBFDISK +PCRE2 +PWQUALITY +P11KIT +QRENCODE +TPM2 +BZIP2 +LZ4 +XZ +ZLIB +ZSTD +BPF_FRAMEWORK +XKBCOMMON +UTMP -SYSVINIT default-hierarchy=unified`,
data: `255.6-1-arch`,
},
"AmazonLinux2": {
expected: 219,
data: `systemd 219
+PAM +AUDIT +SELINUX +IMA -APPARMOR +SMACK +SYSVINIT +UTMP +LIBCRYPTSETUP +GCRYPT +GNUTLS +ACL +XZ +LZ4 -SECCOMP +BLKID +ELFUTILS +KMOD +IDN`,
expected: 252,
data: `252.16-1.amzn2023.0.2`,
},
"Ubuntu 2204": {
expected: 249,
data: `systemd 249 (249.11-0ubuntu3.12)
+PAM +AUDIT +SELINUX +APPARMOR +IMA +SMACK +SECCOMP +GCRYPT +GNUTLS +OPENSSL +ACL +BLKID +CURL +ELFUTILS +FIDO2 +IDN2 -IDN +IPTC +KMOD +LIBCRYPTSETUP +LIBFDISK +PCRE2 -PWQUALITY -P11KIT -QRENCODE +BZIP2 +LZ4 +XZ +ZLIB +ZSTD -XKBCOMMON +UTMP +SYSVINIT default-hierarchy=unified`,
data: `249.11-0ubuntu3.12`,
},
}

for name, tc := range foo {
t.Run(name, func(t *testing.T) {
version, err := parseJournaldVersion(tc.data)
version, err := parseSystemdVersion(tc.data)
if err != nil {
t.Errorf("did not expect an error: %s", err)
}
Expand All @@ -122,15 +119,12 @@ func TestParseJournaldVersion(t *testing.T) {
}

func TestGetJounraldVersion(t *testing.T) {
// This test already has build tags to only run on systems
// with systemd. So there should be no problem calling
// journalctl directly.
version, err := getJournaldVersion()
version, err := getSystemdVersionViaDBus()
if err != nil {
t.Fatalf("did not expect an error: %s", err)
}

if version == 0 {
t.Fatal("version must be grater than 0")
if version == "" {
t.Fatal("version must not be an empty string")
}
}

0 comments on commit 32d12e3

Please sign in to comment.