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