New Upstream Release - golang-github-coreos-go-systemd

Ready changes

Summary

Merged new upstream version: 22.5.0 (was: 22.3.2).

Resulting package

Built on 2023-01-19T07:06 (took 2m2s)

The resulting binary packages can be installed (if you have the apt repository enabled) by running one of:

apt install -t fresh-releases golang-github-coreos-go-systemd-dev

Diff

diff --git a/.github/workflows/containers.yml b/.github/workflows/containers.yml
index 9858d3e..acb2a8c 100644
--- a/.github/workflows/containers.yml
+++ b/.github/workflows/containers.yml
@@ -3,9 +3,12 @@ name: Containers
 
 on:
   push:
-    branches: [master]
+    branches: [main]
   pull_request:
-    branches: [master]
+    branches: [main]
+
+permissions:
+  contents: read
 
 env:
   GO_TOOLCHAIN: "1.15"
@@ -18,7 +21,7 @@ jobs:
     runs-on: ubuntu-latest
     strategy:
       matrix:
-        baseimage: ['debian:stretch', 'ubuntu:16.04', 'ubuntu:18.04']
+        baseimage: ['debian:stretch', 'ubuntu:18.04', 'ubuntu:20.04']
     steps:
       - run: sudo apt-get -qq update
       - name: Install libsystemd-dev
@@ -35,7 +38,7 @@ jobs:
       - name: Pull base image - ${{ matrix.baseimage }}
         run: docker pull ${{ matrix.baseimage }}
       - name: Install packages for ${{ matrix.baseimage }}
-        run: docker run --privileged -e GOPATH=${GOPATH} --cidfile=/tmp/cidfile ${{ matrix.baseimage }} /bin/bash -c "apt-get update && apt-get install -y sudo build-essential git golang dbus libsystemd-dev libpam-systemd systemd-container"
+        run: docker run --privileged -e GOPATH=${GOPATH} --cidfile=/tmp/cidfile ${{ matrix.baseimage }} /bin/bash -c "export DEBIAN_FRONTEND=noninteractive; apt-get update && apt-get install -y sudo build-essential git golang dbus libsystemd-dev libpam-systemd systemd-container"
       - name: Persist base container
         run: docker commit `cat /tmp/cidfile` go-systemd/container-tests
       - run: rm -f /tmp/cidfile
diff --git a/.github/workflows/go.yml b/.github/workflows/go.yml
index 042da05..22110d9 100644
--- a/.github/workflows/go.yml
+++ b/.github/workflows/go.yml
@@ -1,14 +1,16 @@
----
 name: Go
 on:
   push:
-    branches: [master]
+    branches: [main]
   pull_request:
-    branches: [master]
+    branches: [main]
+
+permissions:
+  contents: read
 
 env:
   # Minimum supported Go toolchain
-  ACTION_MINIMUM_TOOLCHAIN: "1.12"
+  ACTION_MINIMUM_TOOLCHAIN: "1.12.x"
 
 jobs:
   build:
@@ -16,14 +18,14 @@ jobs:
     runs-on: ubuntu-latest
     strategy:
       matrix:
-        go: ['1.14', '1.15']
+        go: ['1.17.x', '1.18.x', '1.19.x']
     steps:
       - run: sudo apt-get -qq update
       - name: Install libsystemd-dev
         run: sudo apt-get install libsystemd-dev
-      - uses: actions/checkout@v2
+      - uses: actions/checkout@v3
       - name: Setup go
-        uses: actions/setup-go@v1
+        uses: actions/setup-go@v3
         with:
           go-version: ${{ matrix.go }}
       - name: Go fmt
@@ -41,9 +43,9 @@ jobs:
       - run: sudo apt-get -qq update
       - name: Install libsystemd-dev
         run: sudo apt-get install libsystemd-dev
-      - uses: actions/checkout@v2
+      - uses: actions/checkout@v3
       - name: Setup go
-        uses: actions/setup-go@v1
+        uses: actions/setup-go@v3
         with:
           go-version: ${{ env['ACTION_MINIMUM_TOOLCHAIN'] }}
       - name: Go fmt
diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md
index 0551ed5..403d9ec 100644
--- a/CONTRIBUTING.md
+++ b/CONTRIBUTING.md
@@ -12,15 +12,6 @@ Origin (DCO). This document was created by the Linux Kernel community and is a
 simple statement that you, as a contributor, have the legal right to make the
 contribution. See the [DCO](DCO) file for details.
 
-# Email and Chat
-
-The project currently uses the general CoreOS email list and IRC channel:
-- Email: [coreos-dev](https://groups.google.com/forum/#!forum/coreos-dev)
-- IRC: #[coreos](irc://irc.freenode.org:6667/#coreos) IRC channel on freenode.org
-
-Please avoid emailing maintainers found in the MAINTAINERS file directly. They
-are very busy and read the mailing lists.
-
 ## Getting Started
 
 - Fork the repository on GitHub
@@ -31,7 +22,7 @@ are very busy and read the mailing lists.
 
 This is a rough outline of what a contributor's workflow looks like:
 
-- Create a topic branch from where you want to base your work (usually master).
+- Create a topic branch from where you want to base your work (usually main).
 - Make commits of logical units.
 - Make sure your commit messages are in the proper format (see below).
 - Push your changes to a topic branch in your fork of the repository.
@@ -40,12 +31,6 @@ This is a rough outline of what a contributor's workflow looks like:
 
 Thanks for your contributions!
 
-### Coding Style
-
-CoreOS projects written in Go follow a set of style guidelines that we've documented 
-[here](https://github.com/coreos/docs/tree/master/golang). Please follow them when 
-working on your contributions.
-
 ### Format of the Commit Message
 
 We follow a rough convention for commit messages that is designed to answer two
diff --git a/Jenkinsfile b/Jenkinsfile
deleted file mode 100644
index 7c89897..0000000
--- a/Jenkinsfile
+++ /dev/null
@@ -1,38 +0,0 @@
-matrixJob('Periodic go-systemd builder') {
-    label('master')
-    displayName('Periodic go-systemd builder (master branch)')
-
-    scm {
-        git {
-            remote {
-                url('https://github.com/coreos/go-systemd.git')
-            }
-            branch('master')
-        }
-    }
-
-    concurrentBuild()
-
-    triggers {
-        cron('@daily')
-    }
-
-    axes {
-        label('os_type', 'debian-testing', 'fedora-24', 'fedora-25')
-    }
-
-    wrappers {
-        buildNameSetter {
-            template('go-systemd master (periodic #${BUILD_NUMBER})')
-            runAtStart(true)
-            runAtEnd(true)
-        }
-        timeout {
-            absolute(25)
-        }
-    }
-
-    steps {
-        shell('./scripts/jenkins/periodic-go-systemd-builder.sh')
-    }
-}
\ No newline at end of file
diff --git a/README.md b/README.md
index 9fac513..9f9f6de 100644
--- a/README.md
+++ b/README.md
@@ -1,6 +1,5 @@
 # go-systemd
 
-[![Build Status](https://travis-ci.org/coreos/go-systemd.png?branch=master)](https://travis-ci.org/coreos/go-systemd)
 [![godoc](https://img.shields.io/badge/godoc-reference-5272B4)](https://pkg.go.dev/mod/github.com/coreos/go-systemd/v22/?tab=packages)
 ![minimum golang 1.12](https://img.shields.io/badge/golang-1.12%2B-orange.svg)
 
@@ -20,7 +19,7 @@ Go bindings to systemd. The project has several packages:
 
 An example HTTP server using socket activation can be quickly set up by following this README on a Linux machine running systemd:
 
-https://github.com/coreos/go-systemd/tree/master/examples/activation/httpserver
+https://github.com/coreos/go-systemd/tree/main/examples/activation/httpserver
 
 ## systemd Service Notification
 
diff --git a/activation/files_unix.go b/activation/files_unix.go
index fc7db98..bf7671d 100644
--- a/activation/files_unix.go
+++ b/activation/files_unix.go
@@ -12,6 +12,7 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
+//go:build !windows
 // +build !windows
 
 // Package activation implements primitives for systemd socket activation.
diff --git a/daemon/watchdog.go b/daemon/watchdog.go
index 7a0e0d3..25d9c1a 100644
--- a/daemon/watchdog.go
+++ b/daemon/watchdog.go
@@ -30,8 +30,8 @@ import (
 // It returns one of the following:
 // (0, nil) - watchdog isn't enabled or we aren't the watched PID.
 // (0, err) - an error happened (e.g. error converting time).
-// (time, nil) - watchdog is enabled and we can send ping.
-//   time is delay before inactive service will be killed.
+// (time, nil) - watchdog is enabled and we can send ping.  time is delay
+// before inactive service will be killed.
 func SdWatchdogEnabled(unsetEnvironment bool) (time.Duration, error) {
 	wusec := os.Getenv("WATCHDOG_USEC")
 	wpid := os.Getenv("WATCHDOG_PID")
diff --git a/dbus/dbus.go b/dbus/dbus.go
index cff5af1..147f756 100644
--- a/dbus/dbus.go
+++ b/dbus/dbus.go
@@ -176,6 +176,11 @@ func (c *Conn) Close() {
 	c.sigconn.Close()
 }
 
+// Connected returns whether conn is connected
+func (c *Conn) Connected() bool {
+	return c.sysconn.Connected() && c.sigconn.Connected()
+}
+
 // NewConnection establishes a connection to a bus using a caller-supplied function.
 // This allows connecting to remote buses through a user-supplied mechanism.
 // The supplied function may be called multiple times, and should return independent connections.
diff --git a/dbus/methods.go b/dbus/methods.go
index fa04afc..074148c 100644
--- a/dbus/methods.go
+++ b/dbus/methods.go
@@ -417,6 +417,29 @@ func (c *Conn) listUnitsInternal(f storeFunc) ([]UnitStatus, error) {
 	return status, nil
 }
 
+// GetUnitByPID returns the unit object path of the unit a process ID
+// belongs to. It takes a UNIX PID and returns the object path. The PID must
+// refer to an existing system process
+func (c *Conn) GetUnitByPID(ctx context.Context, pid uint32) (dbus.ObjectPath, error) {
+	var result dbus.ObjectPath
+
+	err := c.sysobj.CallWithContext(ctx, "org.freedesktop.systemd1.Manager.GetUnitByPID", 0, pid).Store(&result)
+
+	return result, err
+}
+
+// GetUnitNameByPID returns the name of the unit a process ID belongs to. It
+// takes a UNIX PID and returns the object path. The PID must refer to an
+// existing system process
+func (c *Conn) GetUnitNameByPID(ctx context.Context, pid uint32) (string, error) {
+	path, err := c.GetUnitByPID(ctx, pid)
+	if err != nil {
+		return "", err
+	}
+
+	return unitName(path), nil
+}
+
 // Deprecated: use ListUnitsContext instead.
 func (c *Conn) ListUnits() ([]UnitStatus, error) {
 	return c.ListUnitsContext(context.Background())
@@ -828,3 +851,14 @@ func (c *Conn) listJobsInternal(ctx context.Context) ([]JobStatus, error) {
 
 	return status, nil
 }
+
+// Freeze the cgroup associated with the unit.
+// Note that FreezeUnit and ThawUnit are only supported on systems running with cgroup v2.
+func (c *Conn) FreezeUnit(ctx context.Context, unit string) error {
+	return c.sysobj.CallWithContext(ctx, "org.freedesktop.systemd1.Manager.FreezeUnit", 0, unit).Store()
+}
+
+// Unfreeze the cgroup associated with the unit.
+func (c *Conn) ThawUnit(ctx context.Context, unit string) error {
+	return c.sysobj.CallWithContext(ctx, "org.freedesktop.systemd1.Manager.ThawUnit", 0, unit).Store()
+}
diff --git a/dbus/methods_test.go b/dbus/methods_test.go
index aa75117..30cc132 100644
--- a/dbus/methods_test.go
+++ b/dbus/methods_test.go
@@ -15,6 +15,7 @@
 package dbus
 
 import (
+	"context"
 	"fmt"
 	"os"
 	"os/exec"
@@ -449,6 +450,38 @@ func TestReloadOrRestartUnit(t *testing.T) {
 	}
 }
 
+// Ensure that GetUnitByPID works.
+func TestGetUnitByPID(t *testing.T) {
+	conn := setupConn(t)
+	defer conn.Close()
+
+	path, err := conn.GetUnitByPID(context.Background(), 1)
+
+	if err != nil {
+		t.Error(err)
+	}
+
+	if path == "" {
+		t.Fatal("path is empty")
+	}
+}
+
+// Ensure that GetUnitNameByPID works.
+func TestGetUnitNameByPID(t *testing.T) {
+	conn := setupConn(t)
+	defer conn.Close()
+
+	name, err := conn.GetUnitNameByPID(context.Background(), 1)
+
+	if err != nil {
+		t.Error(err)
+	}
+
+	if name == "" {
+		t.Fatal("name is empty")
+	}
+}
+
 // Ensure that ListUnitsByNames works.
 func TestListUnitsByNames(t *testing.T) {
 	target1 := "systemd-journald.service"
@@ -1600,3 +1633,59 @@ func TestUnitName(t *testing.T) {
 		}
 	}
 }
+
+func TestFreezer(t *testing.T) {
+	target := "freeze.service"
+	conn := setupConn(t)
+	defer conn.Close()
+
+	setupUnit(target, conn, t)
+	linkUnit(target, conn, t)
+
+	reschan := make(chan string)
+	_, err := conn.StartUnit(target, "replace", reschan)
+	if err != nil {
+		t.Fatal(err)
+	}
+
+	job := <-reschan
+	if job != "done" {
+		t.Fatal("Job is not done:", job)
+	}
+
+	if err := conn.FreezeUnit(context.Background(), target); err != nil {
+		// Don't fail the test if freezing units is not implemented at all (on older systemd versions) or
+		// not supported (on systems running with cgroup v1).
+		e, ok := err.(dbus.Error)
+		if ok && (e.Name == "org.freedesktop.DBus.Error.UnknownMethod" || e.Name == "org.freedesktop.DBus.Error.NotSupported") {
+			t.SkipNow()
+		}
+		t.Fatalf("failed to freeze unit %s: %s", target, err)
+	}
+
+	p, err := conn.GetUnitProperty(target, "FreezerState")
+	if err != nil {
+		t.Fatal(err)
+	}
+
+	v := p.Value.Value().(string)
+	if v != "frozen" {
+		t.Fatalf("unit is not frozen after calling FreezeUnit(), FreezerState=%s", v)
+	}
+
+	if err := conn.ThawUnit(context.Background(), target); err != nil {
+		t.Fatalf("failed to thaw unit %s: %s", target, err)
+	}
+
+	p, err = conn.GetUnitProperty(target, "FreezerState")
+	if err != nil {
+		t.Fatal(err)
+	}
+
+	v = p.Value.Value().(string)
+	if v != "running" {
+		t.Fatalf("unit is not frozen after calling ThawUnit(), FreezerState=%s", v)
+	}
+
+	runStopUnit(t, conn, TrUnitProp{target, nil})
+}
diff --git a/debian/changelog b/debian/changelog
index dc129ba..c7a14cb 100644
--- a/debian/changelog
+++ b/debian/changelog
@@ -1,9 +1,13 @@
-golang-github-coreos-go-systemd (22.3.2-2) UNRELEASED; urgency=medium
+golang-github-coreos-go-systemd (22.5.0-1) UNRELEASED; urgency=medium
 
+  [ Tianon Gravi ]
   * Team upload.
   * Remove self from Uploaders
 
- -- Tianon Gravi <tianon@debian.org>  Wed, 23 Feb 2022 11:11:30 -0800
+  [ Debian Janitor ]
+  * New upstream release.
+
+ -- Tianon Gravi <tianon@debian.org>  Thu, 19 Jan 2023 07:04:27 -0000
 
 golang-github-coreos-go-systemd (22.3.2-1) unstable; urgency=medium
 
diff --git a/examples/activation/activation.go b/examples/activation/activation.go
index 3a80d3a..815a453 100644
--- a/examples/activation/activation.go
+++ b/examples/activation/activation.go
@@ -12,6 +12,7 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
+//go:build ignore
 // +build ignore
 
 // Activation example used by the activation unit tests.
diff --git a/examples/activation/httpserver/httpserver.go b/examples/activation/httpserver/httpserver.go
index 14e828e..ed848ee 100644
--- a/examples/activation/httpserver/httpserver.go
+++ b/examples/activation/httpserver/httpserver.go
@@ -12,6 +12,7 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
+//go:build ignore
 // +build ignore
 
 package main
diff --git a/examples/activation/listen.go b/examples/activation/listen.go
index dc43811..1946196 100644
--- a/examples/activation/listen.go
+++ b/examples/activation/listen.go
@@ -12,6 +12,7 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
+//go:build ignore
 // +build ignore
 
 // Activation example used by the activation unit tests.
diff --git a/examples/activation/udpconn.go b/examples/activation/udpconn.go
index 7d17a06..6b81615 100644
--- a/examples/activation/udpconn.go
+++ b/examples/activation/udpconn.go
@@ -12,6 +12,7 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
+//go:build ignore
 // +build ignore
 
 // Activation example used by the activation unit tests.
diff --git a/examples/journal/main.go b/examples/journal/main.go
new file mode 100644
index 0000000..86dadc9
--- /dev/null
+++ b/examples/journal/main.go
@@ -0,0 +1,37 @@
+// Copyright 2022 CoreOS, Inc.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License
+
+package main
+
+import (
+	"fmt"
+	"os"
+
+	"github.com/coreos/go-systemd/v22/journal"
+)
+
+func main() {
+	ok, err := journal.StderrIsJournalStream()
+	if err != nil {
+		panic(err)
+	}
+
+	if ok {
+		// use journal native protocol
+		journal.Send("this is a message logged through the native protocol", journal.PriInfo, nil)
+	} else {
+		// use stderr
+		fmt.Fprintln(os.Stderr, "this is a message logged through stderr")
+	}
+}
diff --git a/examples/journal/run.sh b/examples/journal/run.sh
new file mode 100755
index 0000000..2909b8f
--- /dev/null
+++ b/examples/journal/run.sh
@@ -0,0 +1,13 @@
+#!/bin/bash
+
+set -e
+
+go build
+
+echo "Running directly"
+./journal
+
+echo "Running through systemd"
+unit_name="run-$(systemd-id128 new)"
+systemd-run -u "$unit_name" --user --wait --quiet ./journal
+journalctl --user -u "$unit_name"
diff --git a/fixtures/freeze.service b/fixtures/freeze.service
new file mode 100644
index 0000000..8a76bfe
--- /dev/null
+++ b/fixtures/freeze.service
@@ -0,0 +1,5 @@
+[Unit]
+Description=freeze unit test
+
+[Service]
+ExecStart=/bin/sleep 400
diff --git a/import1/dbus.go b/import1/dbus.go
index 8076b21..508c607 100644
--- a/import1/dbus.go
+++ b/import1/dbus.go
@@ -64,6 +64,11 @@ func New() (*Conn, error) {
 	return c, nil
 }
 
+// Connected returns whether conn is connected
+func (c *Conn) Connected() bool {
+	return c.conn.Connected()
+}
+
 func (c *Conn) initConnection() error {
 	var err error
 	c.conn, err = dbus.SystemBusPrivate()
diff --git a/internal/dlopen/dlopen_example.go b/internal/dlopen/dlopen_example.go
new file mode 100644
index 0000000..2065c5e
--- /dev/null
+++ b/internal/dlopen/dlopen_example.go
@@ -0,0 +1,57 @@
+// Copyright 2015 CoreOS, Inc.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+//go:build linux
+// +build linux
+
+package dlopen
+
+// #include <string.h>
+// #include <stdlib.h>
+//
+// int
+// my_strlen(void *f, const char *s)
+// {
+//   size_t (*strlen)(const char *);
+//
+//   strlen = (size_t (*)(const char *))f;
+//   return strlen(s);
+// }
+import "C"
+
+import (
+	"fmt"
+	"unsafe"
+)
+
+func strlen(libs []string, s string) (int, error) {
+	h, err := GetHandle(libs)
+	if err != nil {
+		return -1, fmt.Errorf(`couldn't get a handle to the library: %v`, err)
+	}
+	defer h.Close()
+
+	f := "strlen"
+	cs := C.CString(s)
+	defer C.free(unsafe.Pointer(cs))
+
+	strlen, err := h.GetSymbolPointer(f)
+	if err != nil {
+		return -1, fmt.Errorf(`couldn't get symbol %q: %v`, f, err)
+	}
+
+	len := C.my_strlen(strlen, cs)
+
+	return int(len), nil
+}
diff --git a/internal/dlopen/dlopen_test.go b/internal/dlopen/dlopen_test.go
index 60d88fa..b9017b7 100644
--- a/internal/dlopen/dlopen_test.go
+++ b/internal/dlopen/dlopen_test.go
@@ -11,6 +11,7 @@
 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 // See the License for the specific language governing permissions and
 // limitations under the License.
+
 package dlopen
 
 import (
diff --git a/journal/journal_unix.go b/journal/journal_unix.go
index 8d58ca0..c5b23a8 100644
--- a/journal/journal_unix.go
+++ b/journal/journal_unix.go
@@ -12,6 +12,7 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
+//go:build !windows
 // +build !windows
 
 // Package journal provides write bindings to the local systemd journal.
@@ -53,15 +54,9 @@ var (
 	onceConn sync.Once
 )
 
-func init() {
-	onceConn.Do(initConn)
-}
-
 // Enabled checks whether the local systemd journal is available for logging.
 func Enabled() bool {
-	onceConn.Do(initConn)
-
-	if (*net.UnixConn)(atomic.LoadPointer(&unixConnPtr)) == nil {
+	if c := getOrInitConn(); c == nil {
 		return false
 	}
 
@@ -74,6 +69,58 @@ func Enabled() bool {
 	return true
 }
 
+// StderrIsJournalStream returns whether the process stderr is connected
+// to the Journal's stream transport.
+//
+// This can be used for automatic protocol upgrading described in [Journal Native Protocol].
+//
+// Returns true if JOURNAL_STREAM environment variable is present,
+// and stderr's device and inode numbers match it.
+//
+// Error is returned if unexpected error occurs: e.g. if JOURNAL_STREAM environment variable
+// is present, but malformed, fstat syscall fails, etc.
+//
+// [Journal Native Protocol]: https://systemd.io/JOURNAL_NATIVE_PROTOCOL/#automatic-protocol-upgrading
+func StderrIsJournalStream() (bool, error) {
+	return fdIsJournalStream(syscall.Stderr)
+}
+
+// StdoutIsJournalStream returns whether the process stdout is connected
+// to the Journal's stream transport.
+//
+// Returns true if JOURNAL_STREAM environment variable is present,
+// and stdout's device and inode numbers match it.
+//
+// Error is returned if unexpected error occurs: e.g. if JOURNAL_STREAM environment variable
+// is present, but malformed, fstat syscall fails, etc.
+//
+// Most users should probably use [StderrIsJournalStream].
+func StdoutIsJournalStream() (bool, error) {
+	return fdIsJournalStream(syscall.Stdout)
+}
+
+func fdIsJournalStream(fd int) (bool, error) {
+	journalStream := os.Getenv("JOURNAL_STREAM")
+	if journalStream == "" {
+		return false, nil
+	}
+
+	var expectedStat syscall.Stat_t
+	_, err := fmt.Sscanf(journalStream, "%d:%d", &expectedStat.Dev, &expectedStat.Ino)
+	if err != nil {
+		return false, fmt.Errorf("failed to parse JOURNAL_STREAM=%q: %v", journalStream, err)
+	}
+
+	var stat syscall.Stat_t
+	err = syscall.Fstat(fd, &stat)
+	if err != nil {
+		return false, err
+	}
+
+	match := stat.Dev == expectedStat.Dev && stat.Ino == expectedStat.Ino
+	return match, nil
+}
+
 // Send a message to the local systemd journal. vars is a map of journald
 // fields to values.  Fields must be composed of uppercase letters, numbers,
 // and underscores, but must not start with an underscore. Within these
@@ -82,7 +129,7 @@ func Enabled() bool {
 // (http://www.freedesktop.org/software/systemd/man/systemd.journal-fields.html)
 // for more details.  vars may be nil.
 func Send(message string, priority Priority, vars map[string]string) error {
-	conn := (*net.UnixConn)(atomic.LoadPointer(&unixConnPtr))
+	conn := getOrInitConn()
 	if conn == nil {
 		return errors.New("could not initialize socket to journald")
 	}
@@ -126,6 +173,16 @@ func Send(message string, priority Priority, vars map[string]string) error {
 	return nil
 }
 
+// getOrInitConn attempts to get the global `unixConnPtr` socket, initializing if necessary
+func getOrInitConn() *net.UnixConn {
+	conn := (*net.UnixConn)(atomic.LoadPointer(&unixConnPtr))
+	if conn != nil {
+		return conn
+	}
+	onceConn.Do(initConn)
+	return (*net.UnixConn)(atomic.LoadPointer(&unixConnPtr))
+}
+
 func appendVariable(w io.Writer, name, value string) {
 	if err := validVarName(name); err != nil {
 		fmt.Fprintf(os.Stderr, "variable name %s contains invalid character, ignoring\n", name)
@@ -194,7 +251,7 @@ func tempFd() (*os.File, error) {
 }
 
 // initConn initializes the global `unixConnPtr` socket.
-// It is meant to be called exactly once, at program startup.
+// It is automatically called when needed.
 func initConn() {
 	autobind, err := net.ResolveUnixAddr("unixgram", "")
 	if err != nil {
diff --git a/journal/journal_unix_test.go b/journal/journal_unix_test.go
new file mode 100644
index 0000000..3483d33
--- /dev/null
+++ b/journal/journal_unix_test.go
@@ -0,0 +1,188 @@
+// Copyright 2022 CoreOS, Inc.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+//go:build !windows
+// +build !windows
+
+package journal_test
+
+import (
+	"fmt"
+	"os"
+	"os/exec"
+	"syscall"
+	"testing"
+
+	"github.com/coreos/go-systemd/v22/journal"
+)
+
+func TestJournalStreamParsing(t *testing.T) {
+	if _, ok := os.LookupEnv("JOURNAL_STREAM"); ok {
+		t.Fatal("unset JOURNAL_STREAM before running this test")
+	}
+
+	t.Run("Missing", func(t *testing.T) {
+		ok, err := journal.StderrIsJournalStream()
+		if err != nil {
+			t.Fatal(err)
+		}
+		if ok {
+			t.Error("stderr shouldn't be connected to journal stream")
+		}
+	})
+	t.Run("Present", func(t *testing.T) {
+		f, stat := getUnixStreamSocket(t)
+		defer f.Close()
+		os.Setenv("JOURNAL_STREAM", fmt.Sprintf("%d:%d", stat.Dev, stat.Ino))
+		defer os.Unsetenv("JOURNAL_STREAM")
+		replaceStderr(int(f.Fd()), func() {
+			ok, err := journal.StderrIsJournalStream()
+			if err != nil {
+				t.Fatal(err)
+			}
+			if !ok {
+				t.Error("stderr should've been connected to journal stream")
+			}
+		})
+	})
+	t.Run("NotMatching", func(t *testing.T) {
+		f, stat := getUnixStreamSocket(t)
+		defer f.Close()
+		os.Setenv("JOURNAL_STREAM", fmt.Sprintf("%d:%d", stat.Dev+1, stat.Ino))
+		defer os.Unsetenv("JOURNAL_STREAM")
+		replaceStderr(int(f.Fd()), func() {
+			ok, err := journal.StderrIsJournalStream()
+			if err != nil {
+				t.Fatal(err)
+			}
+			if ok {
+				t.Error("stderr shouldn't be connected to journal stream")
+			}
+		})
+	})
+	t.Run("Malformed", func(t *testing.T) {
+		f, stat := getUnixStreamSocket(t)
+		defer f.Close()
+		os.Setenv("JOURNAL_STREAM", fmt.Sprintf("%d-%d", stat.Dev, stat.Ino))
+		defer os.Unsetenv("JOURNAL_STREAM")
+		replaceStderr(int(f.Fd()), func() {
+			_, err := journal.StderrIsJournalStream()
+			if err == nil {
+				t.Fatal("JOURNAL_STREAM is malformed, but no error returned")
+			}
+		})
+	})
+}
+
+func TestStderrIsJournalStream(t *testing.T) {
+	const (
+		message = "TEST_MESSAGE"
+	)
+
+	userOrSystem := "--user"
+	if os.Getuid() == 0 {
+		userOrSystem = "--system"
+	}
+
+	if _, ok := os.LookupEnv("JOURNAL_STREAM"); !ok {
+		// Re-execute this test under systemd (see the else branch),
+		// and observe its exit code.
+		args := []string{
+			"systemd-run",
+			userOrSystem,
+			"--wait",
+			"--quiet",
+			"--",
+			os.Args[0],
+			"-test.run=TestStderrIsJournalStream",
+			"-test.count=1", // inhibit caching
+		}
+
+		cmd := exec.Command(args[0], args[1:]...)
+		cmd.Stderr = os.Stderr
+		if err := cmd.Run(); err != nil {
+			t.Fatal(err)
+		}
+	} else {
+		ok, err := journal.StderrIsJournalStream()
+		if err != nil {
+			t.Fatal(err)
+		}
+		if !ok {
+			t.Fatal("StderrIsJournalStream should've returned true")
+		}
+
+		err = journal.Send(message, journal.PriInfo, nil)
+		if err != nil {
+			t.Fatal(err)
+		}
+	}
+}
+
+func ExampleStderrIsJournalStream() {
+	// NOTE: this is just an example. Production code
+	// will likely use this to setup a logging library
+	// to write messages to either journal or stderr.
+	ok, err := journal.StderrIsJournalStream()
+	if err != nil {
+		panic(err)
+	}
+
+	if ok {
+		// use journal native protocol
+		journal.Send("this is a message logged through the native protocol", journal.PriInfo, nil)
+	} else {
+		// use stderr
+		fmt.Fprintln(os.Stderr, "this is a message logged through stderr")
+	}
+}
+
+func replaceStderr(fd int, cb func()) {
+	savedStderr, err := syscall.Dup(syscall.Stderr)
+	if err != nil {
+		panic(err)
+	}
+	defer syscall.Close(savedStderr)
+	err = syscall.Dup2(fd, syscall.Stderr)
+	if err != nil {
+		panic(err)
+	}
+	defer func() {
+		err := syscall.Dup2(savedStderr, syscall.Stderr)
+		if err != nil {
+			panic(err)
+		}
+	}()
+	cb()
+}
+
+// getUnixStreamSocket returns a unix stream socket obtained with
+// socketpair(2), and its fstat result. Only one end of the socket pair
+// is returned, and the other end is closed immediately: we don't need
+// it for our purposes.
+func getUnixStreamSocket(t *testing.T) (*os.File, *syscall.Stat_t) {
+	fds, err := syscall.Socketpair(syscall.AF_UNIX, syscall.SOCK_STREAM, 0)
+	if err != nil {
+		t.Fatal(os.NewSyscallError("socketpair", err))
+	}
+	// we don't need the remote end for our tests
+	syscall.Close(fds[1])
+
+	file := os.NewFile(uintptr(fds[0]), "unix-stream")
+	stat, err := file.Stat()
+	if err != nil {
+		t.Fatal(err)
+	}
+	return file, stat.Sys().(*syscall.Stat_t)
+}
diff --git a/journal/journal_windows.go b/journal/journal_windows.go
index 677aca6..322e41e 100644
--- a/journal/journal_windows.go
+++ b/journal/journal_windows.go
@@ -33,3 +33,11 @@ func Enabled() bool {
 func Send(message string, priority Priority, vars map[string]string) error {
 	return errors.New("could not initialize socket to journald")
 }
+
+func StderrIsJournalStream() (bool, error) {
+	return false, nil
+}
+
+func StdoutIsJournalStream() (bool, error) {
+	return false, nil
+}
diff --git a/login1/dbus.go b/login1/dbus.go
index ca71308..613db0d 100644
--- a/login1/dbus.go
+++ b/login1/dbus.go
@@ -16,6 +16,7 @@
 package login1
 
 import (
+	"context"
 	"fmt"
 	"os"
 	"strconv"
@@ -25,9 +26,11 @@ import (
 )
 
 const (
-	dbusDest      = "org.freedesktop.login1"
-	dbusInterface = "org.freedesktop.login1.Manager"
-	dbusPath      = "/org/freedesktop/login1"
+	dbusDest             = "org.freedesktop.login1"
+	dbusManagerInterface = "org.freedesktop.login1.Manager"
+	dbusSessionInterface = "org.freedesktop.login1.Session"
+	dbusUserInterface    = "org.freedesktop.login1.User"
+	dbusPath             = "/org/freedesktop/login1"
 )
 
 // Conn is a connection to systemds dbus endpoint.
@@ -58,6 +61,11 @@ func (c *Conn) Close() {
 	}
 }
 
+// Connected returns whether conn is connected
+func (c *Conn) Connected() bool {
+	return c.conn.Connected()
+}
+
 func (c *Conn) initConnection() error {
 	var err error
 	c.conn, err = dbus.SystemBusPrivate()
@@ -160,7 +168,7 @@ func userFromInterfaces(user []interface{}) (*User, error) {
 // GetActiveSession may be used to get the session object path for the current active session
 func (c *Conn) GetActiveSession() (dbus.ObjectPath, error) {
 	var seat0Path dbus.ObjectPath
-	if err := c.object.Call(dbusInterface+".GetSeat", 0, "seat0").Store(&seat0Path); err != nil {
+	if err := c.object.Call(dbusManagerInterface+".GetSeat", 0, "seat0").Store(&seat0Path); err != nil {
 		return "", err
 	}
 
@@ -236,7 +244,7 @@ func (c *Conn) GetSessionDisplay(sessionPath dbus.ObjectPath) (string, error) {
 // GetSession may be used to get the session object path for the session with the specified ID.
 func (c *Conn) GetSession(id string) (dbus.ObjectPath, error) {
 	var out interface{}
-	if err := c.object.Call(dbusInterface+".GetSession", 0, id).Store(&out); err != nil {
+	if err := c.object.Call(dbusManagerInterface+".GetSession", 0, id).Store(&out); err != nil {
 		return "", err
 	}
 
@@ -248,10 +256,15 @@ func (c *Conn) GetSession(id string) (dbus.ObjectPath, error) {
 	return ret, nil
 }
 
-// ListSessions returns an array with all current sessions.
+// Deprecated: use ListSessionsContext instead.
 func (c *Conn) ListSessions() ([]Session, error) {
+	return c.ListSessionsContext(context.Background())
+}
+
+// ListSessionsContext returns an array with all current sessions.
+func (c *Conn) ListSessionsContext(ctx context.Context) ([]Session, error) {
 	out := [][]interface{}{}
-	if err := c.object.Call(dbusInterface+".ListSessions", 0).Store(&out); err != nil {
+	if err := c.object.CallWithContext(ctx, dbusManagerInterface+".ListSessions", 0).Store(&out); err != nil {
 		return nil, err
 	}
 
@@ -266,10 +279,15 @@ func (c *Conn) ListSessions() ([]Session, error) {
 	return ret, nil
 }
 
-// ListUsers returns an array with all currently logged in users.
+// Deprecated: use ListUsersContext instead.
 func (c *Conn) ListUsers() ([]User, error) {
+	return c.ListUsersContext(context.Background())
+}
+
+// ListUsersContext returns an array with all currently logged-in users.
+func (c *Conn) ListUsersContext(ctx context.Context) ([]User, error) {
 	out := [][]interface{}{}
-	if err := c.object.Call(dbusInterface+".ListUsers", 0).Store(&out); err != nil {
+	if err := c.object.CallWithContext(ctx, dbusManagerInterface+".ListUsers", 0).Store(&out); err != nil {
 		return nil, err
 	}
 
@@ -284,36 +302,56 @@ func (c *Conn) ListUsers() ([]User, error) {
 	return ret, nil
 }
 
+// GetSessionPropertiesContext takes a session path and returns all of its dbus object properties.
+func (c *Conn) GetSessionPropertiesContext(ctx context.Context, sessionPath dbus.ObjectPath) (map[string]dbus.Variant, error) {
+	return c.getProperties(ctx, sessionPath, dbusSessionInterface)
+}
+
+// GetSessionPropertyContext takes a session path and a property name and returns the property value.
+func (c *Conn) GetSessionPropertyContext(ctx context.Context, sessionPath dbus.ObjectPath, property string) (*dbus.Variant, error) {
+	return c.getProperty(ctx, sessionPath, dbusSessionInterface, property)
+}
+
+// GetUserPropertiesContext takes a user path and returns all of its dbus object properties.
+func (c *Conn) GetUserPropertiesContext(ctx context.Context, userPath dbus.ObjectPath) (map[string]dbus.Variant, error) {
+	return c.getProperties(ctx, userPath, dbusUserInterface)
+}
+
+// GetUserPropertyContext takes a user path and a property name and returns the property value.
+func (c *Conn) GetUserPropertyContext(ctx context.Context, userPath dbus.ObjectPath, property string) (*dbus.Variant, error) {
+	return c.getProperty(ctx, userPath, dbusUserInterface, property)
+}
+
 // LockSession asks the session with the specified ID to activate the screen lock.
 func (c *Conn) LockSession(id string) {
-	c.object.Call(dbusInterface+".LockSession", 0, id)
+	c.object.Call(dbusManagerInterface+".LockSession", 0, id)
 }
 
 // LockSessions asks all sessions to activate the screen locks. This may be used to lock any access to the machine in one action.
 func (c *Conn) LockSessions() {
-	c.object.Call(dbusInterface+".LockSessions", 0)
+	c.object.Call(dbusManagerInterface+".LockSessions", 0)
 }
 
 // TerminateSession forcibly terminate one specific session.
 func (c *Conn) TerminateSession(id string) {
-	c.object.Call(dbusInterface+".TerminateSession", 0, id)
+	c.object.Call(dbusManagerInterface+".TerminateSession", 0, id)
 }
 
 // TerminateUser forcibly terminates all processes of a user.
 func (c *Conn) TerminateUser(uid uint32) {
-	c.object.Call(dbusInterface+".TerminateUser", 0, uid)
+	c.object.Call(dbusManagerInterface+".TerminateUser", 0, uid)
 }
 
 // Reboot asks logind for a reboot optionally asking for auth.
 func (c *Conn) Reboot(askForAuth bool) {
-	c.object.Call(dbusInterface+".Reboot", 0, askForAuth)
+	c.object.Call(dbusManagerInterface+".Reboot", 0, askForAuth)
 }
 
 // Inhibit takes inhibition lock in logind.
 func (c *Conn) Inhibit(what, who, why, mode string) (*os.File, error) {
 	var fd dbus.UnixFD
 
-	err := c.object.Call(dbusInterface+".Inhibit", 0, what, who, why, mode).Store(&fd)
+	err := c.object.Call(dbusManagerInterface+".Inhibit", 0, what, who, why, mode).Store(&fd)
 	if err != nil {
 		return nil, err
 	}
@@ -334,5 +372,37 @@ func (c *Conn) Subscribe(members ...string) chan *dbus.Signal {
 
 // PowerOff asks logind for a power off optionally asking for auth.
 func (c *Conn) PowerOff(askForAuth bool) {
-	c.object.Call(dbusInterface+".PowerOff", 0, askForAuth)
+	c.object.Call(dbusManagerInterface+".PowerOff", 0, askForAuth)
+}
+
+func (c *Conn) getProperties(ctx context.Context, path dbus.ObjectPath, dbusInterface string) (map[string]dbus.Variant, error) {
+	if !path.IsValid() {
+		return nil, fmt.Errorf("invalid object path (%s)", path)
+	}
+
+	obj := c.conn.Object(dbusDest, path)
+
+	var props map[string]dbus.Variant
+	err := obj.CallWithContext(ctx, "org.freedesktop.DBus.Properties.GetAll", 0, dbusInterface).Store(&props)
+	if err != nil {
+		return nil, err
+	}
+
+	return props, nil
+}
+
+func (c *Conn) getProperty(ctx context.Context, path dbus.ObjectPath, dbusInterface, property string) (*dbus.Variant, error) {
+	if !path.IsValid() {
+		return nil, fmt.Errorf("invalid object path (%s)", path)
+	}
+
+	obj := c.conn.Object(dbusDest, path)
+
+	var prop dbus.Variant
+	err := obj.CallWithContext(ctx, "org.freedesktop.DBus.Properties.Get", 0, dbusInterface, property).Store(&prop)
+	if err != nil {
+		return nil, err
+	}
+
+	return &prop, nil
 }
diff --git a/login1/dbus_test.go b/login1/dbus_test.go
index b570c92..0c1ffe7 100644
--- a/login1/dbus_test.go
+++ b/login1/dbus_test.go
@@ -15,10 +15,12 @@
 package login1
 
 import (
+	"context"
 	"fmt"
 	"os/user"
 	"regexp"
 	"testing"
+	"time"
 )
 
 // TestNew ensures that New() works without errors.
@@ -87,3 +89,105 @@ func TestListUsers(t *testing.T) {
 		}
 	}
 }
+
+func TestConn_GetSessionPropertiesContext(t *testing.T) {
+	c, err := New()
+	if err != nil {
+		t.Fatal(err)
+	}
+
+	sessions, err := c.ListSessions()
+	if err != nil {
+		t.Fatal(err)
+	}
+
+	for _, s := range sessions {
+		func() {
+			ctx, cancel := context.WithTimeout(context.Background(), time.Second*3)
+			defer cancel()
+
+			props, err := c.GetSessionPropertiesContext(ctx, s.Path)
+			if err != nil {
+				t.Fatal(err)
+			}
+			if len(props) == 0 {
+				t.Fatal("no properties returned")
+			}
+		}()
+	}
+}
+
+func TestConn_GetSessionPropertyContext(t *testing.T) {
+	c, err := New()
+	if err != nil {
+		t.Fatal(err)
+	}
+
+	sessions, err := c.ListSessions()
+	if err != nil {
+		t.Fatal(err)
+	}
+
+	for _, s := range sessions {
+		func() {
+			ctx, cancel := context.WithTimeout(context.Background(), time.Second*3)
+			defer cancel()
+
+			_, err := c.GetSessionPropertyContext(ctx, s.Path, "Remote")
+			if err != nil {
+				t.Fatal(err)
+			}
+		}()
+	}
+}
+
+func TestConn_GetUserPropertiesContext(t *testing.T) {
+	c, err := New()
+	if err != nil {
+		t.Fatal(err)
+	}
+
+	users, err := c.ListUsers()
+	if err != nil {
+		t.Fatal(err)
+	}
+
+	for _, u := range users {
+		func() {
+			ctx, cancel := context.WithTimeout(context.Background(), time.Second*3)
+			defer cancel()
+
+			props, err := c.GetUserPropertiesContext(ctx, u.Path)
+			if err != nil {
+				t.Fatal(err)
+			}
+			if len(props) == 0 {
+				t.Fatal("no properties returned")
+			}
+		}()
+	}
+}
+
+func TestConn_GetUserPropertyContext(t *testing.T) {
+	c, err := New()
+	if err != nil {
+		t.Fatal(err)
+	}
+
+	users, err := c.ListUsers()
+	if err != nil {
+		t.Fatal(err)
+	}
+
+	for _, u := range users {
+		func() {
+			ctx, cancel := context.WithTimeout(context.Background(), time.Second*3)
+			defer cancel()
+
+			_, err := c.GetUserPropertyContext(ctx, u.Path, "State")
+			if err != nil {
+				t.Fatal(err)
+			}
+		}()
+	}
+}
diff --git a/machine1/dbus.go b/machine1/dbus.go
index afcc334..79c30ea 100644
--- a/machine1/dbus.go
+++ b/machine1/dbus.go
@@ -112,6 +112,11 @@ func (c *Conn) getPath(method string, args ...interface{}) (dbus.ObjectPath, err
 	return path, nil
 }
 
+// Connected returns whether conn is connected
+func (c *Conn) Connected() bool {
+	return c.conn.Connected()
+}
+
 // CreateMachine creates a new virtual machine or container with systemd-machined, generating a scope unit for it
 func (c *Conn) CreateMachine(name string, id []byte, service string, class string, pid int, root_directory string, scope_properties []sd_dbus.Property) error {
 	return c.object.Call(dbusInterface+".CreateMachine", 0, name, id, service, class, uint32(pid), root_directory, scope_properties).Err
diff --git a/scripts/ci-runner.sh b/scripts/ci-runner.sh
index 5175204..860dcd8 100755
--- a/scripts/ci-runner.sh
+++ b/scripts/ci-runner.sh
@@ -6,7 +6,7 @@ PROJ="go-systemd"
 ORG_PATH="github.com/coreos"
 REPO_PATH="${ORG_PATH}/${PROJ}"
 
-PACKAGES="activation daemon dbus journal login1 machine1 sdjournal unit util import1"
+PACKAGES="activation daemon dbus internal/dlopen journal login1 machine1 sdjournal unit util import1"
 EXAMPLES="activation listen udpconn"
 
 function build_source {
@@ -23,6 +23,8 @@ function build_tests {
         echo "  - examples/${ex}"
         go build -o ./test_bins/${ex}.example ./examples/activation/${ex}.go
     done
+    # just to make sure it's buildable
+    go build -o ./test_bins/journal ./examples/journal/
 }
 
 function run_tests {
diff --git a/unit/escape.go b/unit/escape.go
index 63b1172..98e2044 100644
--- a/unit/escape.go
+++ b/unit/escape.go
@@ -27,14 +27,15 @@ const (
 )
 
 // If isPath is true:
-//   We remove redundant '/'s, the leading '/', and trailing '/'.
-//   If the result is empty, a '/' is inserted.
+//
+//	We remove redundant '/'s, the leading '/', and trailing '/'.
+//	If the result is empty, a '/' is inserted.
 //
 // We always:
-//  Replace the following characters with `\x%x`:
-//   Leading `.`
-//   `-`, `\`, and anything not in this set: `:-_.\[0-9a-zA-Z]`
-//  Replace '/' with '-'.
+//
+//	Replace the following characters with `\x%x`: Leading `.`,
+//	 `-`, `\`, and anything not in this set: `:-_.\[0-9a-zA-Z]`
+//	Replace '/' with '-'.
 func escape(unescaped string, isPath bool) string {
 	e := []byte{}
 	inSlashes := false
@@ -69,11 +70,13 @@ func escape(unescaped string, isPath bool) string {
 }
 
 // If isPath is true:
-//   We always return a string beginning with '/'.
+//
+//	We always return a string beginning with '/'.
 //
 // We always:
-//  Replace '-' with '/'.
-//  Replace `\x%x` with the value represented in hex.
+//
+//	Replace '-' with '/'.
+//	Replace `\x%x` with the value represented in hex.
 func unescape(escaped string, isPath bool) string {
 	u := []byte{}
 	for i := 0; i < len(escaped); i++ {
diff --git a/util/util_cgo.go b/util/util_cgo.go
index f06fcd7..c572170 100644
--- a/util/util_cgo.go
+++ b/util/util_cgo.go
@@ -12,6 +12,7 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
+//go:build cgo
 // +build cgo
 
 package util
diff --git a/util/util_stub.go b/util/util_stub.go
index 477589e..72efe40 100644
--- a/util/util_stub.go
+++ b/util/util_stub.go
@@ -12,6 +12,7 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
+//go:build !cgo
 // +build !cgo
 
 package util

Debdiff

[The following lists of changes regard files as different if they have different names, permissions or owners.]

Files in second set of .debs but not in first

-rw-r--r--  root/root   /usr/share/doc/golang-github-coreos-go-systemd-dev/examples/journal/main.go
-rw-r--r--  root/root   /usr/share/gocode/src/github.com/coreos/go-systemd/examples/journal/main.go
-rw-r--r--  root/root   /usr/share/gocode/src/github.com/coreos/go-systemd/internal/dlopen/dlopen_example.go
-rw-r--r--  root/root   /usr/share/gocode/src/github.com/coreos/go-systemd/journal/journal_unix_test.go
-rwxr-xr-x  root/root   /usr/bin/journal
-rwxr-xr-x  root/root   /usr/share/doc/golang-github-coreos-go-systemd-dev/examples/journal/run.sh

No differences were encountered in the control files

More details

Full run details