Signed-off-by: Christian Muehlhaeuser <muesli@gmail.com>
| ... | ... |
@@ -76,7 +76,7 @@ github.com/opencontainers/image-spec v1.0.1 |
| 76 | 76 |
github.com/seccomp/libseccomp-golang 32f571b70023028bd57d9288c20efbcb237f3ce0 |
| 77 | 77 |
|
| 78 | 78 |
# libcontainer deps (see src/github.com/opencontainers/runc/Godeps/Godeps.json) |
| 79 |
-github.com/coreos/go-systemd v15 |
|
| 79 |
+github.com/coreos/go-systemd v17 |
|
| 80 | 80 |
github.com/godbus/dbus v4.0.0 |
| 81 | 81 |
github.com/syndtr/gocapability 2c00daeb6c3b45114c80ac44119e7b8801fdd852 |
| 82 | 82 |
github.com/golang/protobuf 7a211bcf3bce0e3f1d74f9894916e6f116ae83b4 |
| ... | ... |
@@ -6,9 +6,11 @@ |
| 6 | 6 |
Go bindings to systemd. The project has several packages: |
| 7 | 7 |
|
| 8 | 8 |
- `activation` - for writing and using socket activation from Go |
| 9 |
+- `daemon` - for notifying systemd of service status changes |
|
| 9 | 10 |
- `dbus` - for starting/stopping/inspecting running services and units |
| 10 | 11 |
- `journal` - for writing to systemd's logging service, journald |
| 11 | 12 |
- `sdjournal` - for reading from journald by wrapping its C API |
| 13 |
+- `login1` - for integration with the systemd logind API |
|
| 12 | 14 |
- `machine1` - for registering machines/containers with systemd |
| 13 | 15 |
- `unit` - for (de)serialization and comparison of unit files |
| 14 | 16 |
|
| ... | ... |
@@ -18,10 +20,9 @@ An example HTTP server using socket activation can be quickly set up by followin |
| 18 | 18 |
|
| 19 | 19 |
https://github.com/coreos/go-systemd/tree/master/examples/activation/httpserver |
| 20 | 20 |
|
| 21 |
-## Journal |
|
| 21 |
+## systemd Service Notification |
|
| 22 | 22 |
|
| 23 |
-Using the pure-Go `journal` package you can submit journal entries directly to systemd's journal, taking advantage of features like indexed key/value pairs for each log entry. |
|
| 24 |
-The `sdjournal` package provides read access to the journal by wrapping around journald's native C API; consequently it requires cgo and the journal headers to be available. |
|
| 23 |
+The `daemon` package is an implementation of the [sd_notify protocol](https://www.freedesktop.org/software/systemd/man/sd_notify.html#Description). It can be used to inform systemd of service start-up completion, watchdog events, and other status changes. |
|
| 25 | 24 |
|
| 26 | 25 |
## D-Bus |
| 27 | 26 |
|
| ... | ... |
@@ -45,6 +46,20 @@ Create `/etc/dbus-1/system-local.conf` that looks like this: |
| 45 | 45 |
</busconfig> |
| 46 | 46 |
``` |
| 47 | 47 |
|
| 48 |
+## Journal |
|
| 49 |
+ |
|
| 50 |
+### Writing to the Journal |
|
| 51 |
+ |
|
| 52 |
+Using the pure-Go `journal` package you can submit journal entries directly to systemd's journal, taking advantage of features like indexed key/value pairs for each log entry. |
|
| 53 |
+ |
|
| 54 |
+### Reading from the Journal |
|
| 55 |
+ |
|
| 56 |
+The `sdjournal` package provides read access to the journal by wrapping around journald's native C API; consequently it requires cgo and the journal headers to be available. |
|
| 57 |
+ |
|
| 58 |
+## logind |
|
| 59 |
+ |
|
| 60 |
+The `login1` package provides functions to integrate with the [systemd logind API](http://www.freedesktop.org/wiki/Software/systemd/logind/). |
|
| 61 |
+ |
|
| 48 | 62 |
## machined |
| 49 | 63 |
|
| 50 | 64 |
The `machine1` package allows interaction with the [systemd machined D-Bus API](http://www.freedesktop.org/wiki/Software/systemd/machined/). |
| ... | ... |
@@ -18,18 +18,26 @@ package activation |
| 18 | 18 |
import ( |
| 19 | 19 |
"os" |
| 20 | 20 |
"strconv" |
| 21 |
+ "strings" |
|
| 21 | 22 |
"syscall" |
| 22 | 23 |
) |
| 23 | 24 |
|
| 24 |
-// based on: https://gist.github.com/alberts/4640792 |
|
| 25 | 25 |
const ( |
| 26 |
+ // listenFdsStart corresponds to `SD_LISTEN_FDS_START`. |
|
| 26 | 27 |
listenFdsStart = 3 |
| 27 | 28 |
) |
| 28 | 29 |
|
| 30 |
+// Files returns a slice containing a `os.File` object for each |
|
| 31 |
+// file descriptor passed to this process via systemd fd-passing protocol. |
|
| 32 |
+// |
|
| 33 |
+// The order of the file descriptors is preserved in the returned slice. |
|
| 34 |
+// `unsetEnv` is typically set to `true` in order to avoid clashes in |
|
| 35 |
+// fd usage and to avoid leaking environment flags to child processes. |
|
| 29 | 36 |
func Files(unsetEnv bool) []*os.File {
|
| 30 | 37 |
if unsetEnv {
|
| 31 | 38 |
defer os.Unsetenv("LISTEN_PID")
|
| 32 | 39 |
defer os.Unsetenv("LISTEN_FDS")
|
| 40 |
+ defer os.Unsetenv("LISTEN_FDNAMES")
|
|
| 33 | 41 |
} |
| 34 | 42 |
|
| 35 | 43 |
pid, err := strconv.Atoi(os.Getenv("LISTEN_PID"))
|
| ... | ... |
@@ -42,10 +50,17 @@ func Files(unsetEnv bool) []*os.File {
|
| 42 | 42 |
return nil |
| 43 | 43 |
} |
| 44 | 44 |
|
| 45 |
+ names := strings.Split(os.Getenv("LISTEN_FDNAMES"), ":")
|
|
| 46 |
+ |
|
| 45 | 47 |
files := make([]*os.File, 0, nfds) |
| 46 | 48 |
for fd := listenFdsStart; fd < listenFdsStart+nfds; fd++ {
|
| 47 | 49 |
syscall.CloseOnExec(fd) |
| 48 |
- files = append(files, os.NewFile(uintptr(fd), "LISTEN_FD_"+strconv.Itoa(fd))) |
|
| 50 |
+ name := "LISTEN_FD_" + strconv.Itoa(fd) |
|
| 51 |
+ offset := fd - listenFdsStart |
|
| 52 |
+ if offset < len(names) && len(names[offset]) > 0 {
|
|
| 53 |
+ name = names[offset] |
|
| 54 |
+ } |
|
| 55 |
+ files = append(files, os.NewFile(uintptr(fd), name)) |
|
| 49 | 56 |
} |
| 50 | 57 |
|
| 51 | 58 |
return files |
| ... | ... |
@@ -25,13 +25,33 @@ import ( |
| 25 | 25 |
// The order of the file descriptors is preserved in the returned slice. |
| 26 | 26 |
// Nil values are used to fill any gaps. For example if systemd were to return file descriptors |
| 27 | 27 |
// corresponding with "udp, tcp, tcp", then the slice would contain {nil, net.Listener, net.Listener}
|
| 28 |
-func Listeners(unsetEnv bool) ([]net.Listener, error) {
|
|
| 29 |
- files := Files(unsetEnv) |
|
| 28 |
+func Listeners() ([]net.Listener, error) {
|
|
| 29 |
+ files := Files(true) |
|
| 30 | 30 |
listeners := make([]net.Listener, len(files)) |
| 31 | 31 |
|
| 32 | 32 |
for i, f := range files {
|
| 33 | 33 |
if pc, err := net.FileListener(f); err == nil {
|
| 34 | 34 |
listeners[i] = pc |
| 35 |
+ f.Close() |
|
| 36 |
+ } |
|
| 37 |
+ } |
|
| 38 |
+ return listeners, nil |
|
| 39 |
+} |
|
| 40 |
+ |
|
| 41 |
+// ListenersWithNames maps a listener name to a set of net.Listener instances. |
|
| 42 |
+func ListenersWithNames() (map[string][]net.Listener, error) {
|
|
| 43 |
+ files := Files(true) |
|
| 44 |
+ listeners := map[string][]net.Listener{}
|
|
| 45 |
+ |
|
| 46 |
+ for _, f := range files {
|
|
| 47 |
+ if pc, err := net.FileListener(f); err == nil {
|
|
| 48 |
+ current, ok := listeners[f.Name()] |
|
| 49 |
+ if !ok {
|
|
| 50 |
+ listeners[f.Name()] = []net.Listener{pc}
|
|
| 51 |
+ } else {
|
|
| 52 |
+ listeners[f.Name()] = append(current, pc) |
|
| 53 |
+ } |
|
| 54 |
+ f.Close() |
|
| 35 | 55 |
} |
| 36 | 56 |
} |
| 37 | 57 |
return listeners, nil |
| ... | ... |
@@ -40,8 +60,8 @@ func Listeners(unsetEnv bool) ([]net.Listener, error) {
|
| 40 | 40 |
// TLSListeners returns a slice containing a net.listener for each matching TCP socket type |
| 41 | 41 |
// passed to this process. |
| 42 | 42 |
// It uses default Listeners func and forces TCP sockets handlers to use TLS based on tlsConfig. |
| 43 |
-func TLSListeners(unsetEnv bool, tlsConfig *tls.Config) ([]net.Listener, error) {
|
|
| 44 |
- listeners, err := Listeners(unsetEnv) |
|
| 43 |
+func TLSListeners(tlsConfig *tls.Config) ([]net.Listener, error) {
|
|
| 44 |
+ listeners, err := Listeners() |
|
| 45 | 45 |
|
| 46 | 46 |
if listeners == nil || err != nil {
|
| 47 | 47 |
return nil, err |
| ... | ... |
@@ -58,3 +78,26 @@ func TLSListeners(unsetEnv bool, tlsConfig *tls.Config) ([]net.Listener, error) |
| 58 | 58 |
|
| 59 | 59 |
return listeners, err |
| 60 | 60 |
} |
| 61 |
+ |
|
| 62 |
+// TLSListenersWithNames maps a listener name to a net.Listener with |
|
| 63 |
+// the associated TLS configuration. |
|
| 64 |
+func TLSListenersWithNames(tlsConfig *tls.Config) (map[string][]net.Listener, error) {
|
|
| 65 |
+ listeners, err := ListenersWithNames() |
|
| 66 |
+ |
|
| 67 |
+ if listeners == nil || err != nil {
|
|
| 68 |
+ return nil, err |
|
| 69 |
+ } |
|
| 70 |
+ |
|
| 71 |
+ if tlsConfig != nil && err == nil {
|
|
| 72 |
+ for _, ll := range listeners {
|
|
| 73 |
+ // Activate TLS only for TCP sockets |
|
| 74 |
+ for i, l := range ll {
|
|
| 75 |
+ if l.Addr().Network() == "tcp" {
|
|
| 76 |
+ ll[i] = tls.NewListener(l, tlsConfig) |
|
| 77 |
+ } |
|
| 78 |
+ } |
|
| 79 |
+ } |
|
| 80 |
+ } |
|
| 81 |
+ |
|
| 82 |
+ return listeners, err |
|
| 83 |
+} |
| ... | ... |
@@ -24,13 +24,14 @@ import ( |
| 24 | 24 |
// The order of the file descriptors is preserved in the returned slice. |
| 25 | 25 |
// Nil values are used to fill any gaps. For example if systemd were to return file descriptors |
| 26 | 26 |
// corresponding with "udp, tcp, udp", then the slice would contain {net.PacketConn, nil, net.PacketConn}
|
| 27 |
-func PacketConns(unsetEnv bool) ([]net.PacketConn, error) {
|
|
| 28 |
- files := Files(unsetEnv) |
|
| 27 |
+func PacketConns() ([]net.PacketConn, error) {
|
|
| 28 |
+ files := Files(true) |
|
| 29 | 29 |
conns := make([]net.PacketConn, len(files)) |
| 30 | 30 |
|
| 31 | 31 |
for i, f := range files {
|
| 32 | 32 |
if pc, err := net.FilePacketConn(f); err == nil {
|
| 33 | 33 |
conns[i] = pc |
| 34 |
+ f.Close() |
|
| 34 | 35 |
} |
| 35 | 36 |
} |
| 36 | 37 |
return conns, nil |
| ... | ... |
@@ -1,4 +1,5 @@ |
| 1 | 1 |
// Copyright 2014 Docker, Inc. |
| 2 |
+// Copyright 2015-2018 CoreOS, Inc. |
|
| 2 | 3 |
// |
| 3 | 4 |
// Licensed under the Apache License, Version 2.0 (the "License"); |
| 4 | 5 |
// you may not use this file except in compliance with the License. |
| ... | ... |
@@ -13,7 +14,11 @@ |
| 13 | 13 |
// limitations under the License. |
| 14 | 14 |
// |
| 15 | 15 |
|
| 16 |
-// Code forked from Docker project |
|
| 16 |
+// Package daemon provides a Go implementation of the sd_notify protocol. |
|
| 17 |
+// It can be used to inform systemd of service start-up completion, watchdog |
|
| 18 |
+// events, and other status changes. |
|
| 19 |
+// |
|
| 20 |
+// https://www.freedesktop.org/software/systemd/man/sd_notify.html#Description |
|
| 17 | 21 |
package daemon |
| 18 | 22 |
|
| 19 | 23 |
import ( |
| ... | ... |
@@ -21,6 +26,25 @@ import ( |
| 21 | 21 |
"os" |
| 22 | 22 |
) |
| 23 | 23 |
|
| 24 |
+const ( |
|
| 25 |
+ // SdNotifyReady tells the service manager that service startup is finished |
|
| 26 |
+ // or the service finished loading its configuration. |
|
| 27 |
+ SdNotifyReady = "READY=1" |
|
| 28 |
+ |
|
| 29 |
+ // SdNotifyStopping tells the service manager that the service is beginning |
|
| 30 |
+ // its shutdown. |
|
| 31 |
+ SdNotifyStopping = "STOPPING=1" |
|
| 32 |
+ |
|
| 33 |
+ // SdNotifyReloading tells the service manager that this service is |
|
| 34 |
+ // reloading its configuration. Note that you must call SdNotifyReady when |
|
| 35 |
+ // it completed reloading. |
|
| 36 |
+ SdNotifyReloading = "RELOADING=1" |
|
| 37 |
+ |
|
| 38 |
+ // SdNotifyWatchdog tells the service manager to update the watchdog |
|
| 39 |
+ // timestamp for the service. |
|
| 40 |
+ SdNotifyWatchdog = "WATCHDOG=1" |
|
| 41 |
+) |
|
| 42 |
+ |
|
| 24 | 43 |
// SdNotify sends a message to the init daemon. It is common to ignore the error. |
| 25 | 44 |
// If `unsetEnvironment` is true, the environment variable `NOTIFY_SOCKET` |
| 26 | 45 |
// will be unconditionally unset. |
| ... | ... |
@@ -29,7 +53,7 @@ import ( |
| 29 | 29 |
// (false, nil) - notification not supported (i.e. NOTIFY_SOCKET is unset) |
| 30 | 30 |
// (false, err) - notification supported, but failure happened (e.g. error connecting to NOTIFY_SOCKET or while sending data) |
| 31 | 31 |
// (true, nil) - notification supported, data has been sent |
| 32 |
-func SdNotify(unsetEnvironment bool, state string) (sent bool, err error) {
|
|
| 32 |
+func SdNotify(unsetEnvironment bool, state string) (bool, error) {
|
|
| 33 | 33 |
socketAddr := &net.UnixAddr{
|
| 34 | 34 |
Name: os.Getenv("NOTIFY_SOCKET"),
|
| 35 | 35 |
Net: "unixgram", |
| ... | ... |
@@ -41,10 +65,9 @@ func SdNotify(unsetEnvironment bool, state string) (sent bool, err error) {
|
| 41 | 41 |
} |
| 42 | 42 |
|
| 43 | 43 |
if unsetEnvironment {
|
| 44 |
- err = os.Unsetenv("NOTIFY_SOCKET")
|
|
| 45 |
- } |
|
| 46 |
- if err != nil {
|
|
| 47 |
- return false, err |
|
| 44 |
+ if err := os.Unsetenv("NOTIFY_SOCKET"); err != nil {
|
|
| 45 |
+ return false, err |
|
| 46 |
+ } |
|
| 48 | 47 |
} |
| 49 | 48 |
|
| 50 | 49 |
conn, err := net.DialUnix(socketAddr.Net, nil, socketAddr) |
| ... | ... |
@@ -54,9 +77,7 @@ func SdNotify(unsetEnvironment bool, state string) (sent bool, err error) {
|
| 54 | 54 |
} |
| 55 | 55 |
defer conn.Close() |
| 56 | 56 |
|
| 57 |
- _, err = conn.Write([]byte(state)) |
|
| 58 |
- // Error sending the message |
|
| 59 |
- if err != nil {
|
|
| 57 |
+ if _, err = conn.Write([]byte(state)); err != nil {
|
|
| 60 | 58 |
return false, err |
| 61 | 59 |
} |
| 62 | 60 |
return true, nil |
| ... | ... |
@@ -21,10 +21,11 @@ import ( |
| 21 | 21 |
"time" |
| 22 | 22 |
) |
| 23 | 23 |
|
| 24 |
-// SdWatchdogEnabled return watchdog information for a service. |
|
| 25 |
-// Process should send daemon.SdNotify("WATCHDOG=1") every time / 2.
|
|
| 26 |
-// If `unsetEnvironment` is true, the environment variables `WATCHDOG_USEC` |
|
| 27 |
-// and `WATCHDOG_PID` will be unconditionally unset. |
|
| 24 |
+// SdWatchdogEnabled returns watchdog information for a service. |
|
| 25 |
+// Processes should call daemon.SdNotify(false, daemon.SdNotifyWatchdog) every |
|
| 26 |
+// time / 2. |
|
| 27 |
+// If `unsetEnvironment` is true, the environment variables `WATCHDOG_USEC` and |
|
| 28 |
+// `WATCHDOG_PID` will be unconditionally unset. |
|
| 28 | 29 |
// |
| 29 | 30 |
// It returns one of the following: |
| 30 | 31 |
// (0, nil) - watchdog isn't enabled or we aren't the watched PID. |
| ... | ... |
@@ -16,6 +16,7 @@ |
| 16 | 16 |
package dbus |
| 17 | 17 |
|
| 18 | 18 |
import ( |
| 19 |
+ "encoding/hex" |
|
| 19 | 20 |
"fmt" |
| 20 | 21 |
"os" |
| 21 | 22 |
"strconv" |
| ... | ... |
@@ -60,6 +61,27 @@ func PathBusEscape(path string) string {
|
| 60 | 60 |
return string(n) |
| 61 | 61 |
} |
| 62 | 62 |
|
| 63 |
+// pathBusUnescape is the inverse of PathBusEscape. |
|
| 64 |
+func pathBusUnescape(path string) string {
|
|
| 65 |
+ if path == "_" {
|
|
| 66 |
+ return "" |
|
| 67 |
+ } |
|
| 68 |
+ n := []byte{}
|
|
| 69 |
+ for i := 0; i < len(path); i++ {
|
|
| 70 |
+ c := path[i] |
|
| 71 |
+ if c == '_' && i+2 < len(path) {
|
|
| 72 |
+ res, err := hex.DecodeString(path[i+1 : i+3]) |
|
| 73 |
+ if err == nil {
|
|
| 74 |
+ n = append(n, res...) |
|
| 75 |
+ } |
|
| 76 |
+ i += 2 |
|
| 77 |
+ } else {
|
|
| 78 |
+ n = append(n, c) |
|
| 79 |
+ } |
|
| 80 |
+ } |
|
| 81 |
+ return string(n) |
|
| 82 |
+} |
|
| 83 |
+ |
|
| 63 | 84 |
// Conn is a connection to systemd's dbus endpoint. |
| 64 | 85 |
type Conn struct {
|
| 65 | 86 |
// sysconn/sysobj are only used to call dbus methods |
| ... | ... |
@@ -74,13 +96,18 @@ type Conn struct {
|
| 74 | 74 |
jobs map[dbus.ObjectPath]chan<- string |
| 75 | 75 |
sync.Mutex |
| 76 | 76 |
} |
| 77 |
- subscriber struct {
|
|
| 77 |
+ subStateSubscriber struct {
|
|
| 78 | 78 |
updateCh chan<- *SubStateUpdate |
| 79 | 79 |
errCh chan<- error |
| 80 | 80 |
sync.Mutex |
| 81 | 81 |
ignore map[dbus.ObjectPath]int64 |
| 82 | 82 |
cleanIgnore int64 |
| 83 | 83 |
} |
| 84 |
+ propertiesSubscriber struct {
|
|
| 85 |
+ updateCh chan<- *PropertiesUpdate |
|
| 86 |
+ errCh chan<- error |
|
| 87 |
+ sync.Mutex |
|
| 88 |
+ } |
|
| 84 | 89 |
} |
| 85 | 90 |
|
| 86 | 91 |
// New establishes a connection to any available bus and authenticates. |
| ... | ... |
@@ -152,7 +179,7 @@ func NewConnection(dialBus func() (*dbus.Conn, error)) (*Conn, error) {
|
| 152 | 152 |
sigobj: systemdObject(sigconn), |
| 153 | 153 |
} |
| 154 | 154 |
|
| 155 |
- c.subscriber.ignore = make(map[dbus.ObjectPath]int64) |
|
| 155 |
+ c.subStateSubscriber.ignore = make(map[dbus.ObjectPath]int64) |
|
| 156 | 156 |
c.jobListener.jobs = make(map[dbus.ObjectPath]chan<- string) |
| 157 | 157 |
|
| 158 | 158 |
// Setup the listeners on jobs so that we can get completions |
| ... | ... |
@@ -1,4 +1,4 @@ |
| 1 |
-// Copyright 2015 CoreOS, Inc. |
|
| 1 |
+// Copyright 2015, 2018 CoreOS, Inc. |
|
| 2 | 2 |
// |
| 3 | 3 |
// Licensed under the Apache License, Version 2.0 (the "License"); |
| 4 | 4 |
// you may not use this file except in compliance with the License. |
| ... | ... |
@@ -16,6 +16,7 @@ package dbus |
| 16 | 16 |
|
| 17 | 17 |
import ( |
| 18 | 18 |
"errors" |
| 19 |
+ "fmt" |
|
| 19 | 20 |
"path" |
| 20 | 21 |
"strconv" |
| 21 | 22 |
|
| ... | ... |
@@ -148,14 +149,27 @@ func (c *Conn) ResetFailedUnit(name string) error {
|
| 148 | 148 |
return c.sysobj.Call("org.freedesktop.systemd1.Manager.ResetFailedUnit", 0, name).Store()
|
| 149 | 149 |
} |
| 150 | 150 |
|
| 151 |
-// getProperties takes the unit name and returns all of its dbus object properties, for the given dbus interface |
|
| 152 |
-func (c *Conn) getProperties(unit string, dbusInterface string) (map[string]interface{}, error) {
|
|
| 151 |
+// SystemState returns the systemd state. Equivalent to `systemctl is-system-running`. |
|
| 152 |
+func (c *Conn) SystemState() (*Property, error) {
|
|
| 153 |
+ var err error |
|
| 154 |
+ var prop dbus.Variant |
|
| 155 |
+ |
|
| 156 |
+ obj := c.sysconn.Object("org.freedesktop.systemd1", "/org/freedesktop/systemd1")
|
|
| 157 |
+ err = obj.Call("org.freedesktop.DBus.Properties.Get", 0, "org.freedesktop.systemd1.Manager", "SystemState").Store(&prop)
|
|
| 158 |
+ if err != nil {
|
|
| 159 |
+ return nil, err |
|
| 160 |
+ } |
|
| 161 |
+ |
|
| 162 |
+ return &Property{Name: "SystemState", Value: prop}, nil
|
|
| 163 |
+} |
|
| 164 |
+ |
|
| 165 |
+// getProperties takes the unit path and returns all of its dbus object properties, for the given dbus interface |
|
| 166 |
+func (c *Conn) getProperties(path dbus.ObjectPath, dbusInterface string) (map[string]interface{}, error) {
|
|
| 153 | 167 |
var err error |
| 154 | 168 |
var props map[string]dbus.Variant |
| 155 | 169 |
|
| 156 |
- path := unitPath(unit) |
|
| 157 | 170 |
if !path.IsValid() {
|
| 158 |
- return nil, errors.New("invalid unit name: " + unit)
|
|
| 171 |
+ return nil, fmt.Errorf("invalid unit name: %v", path)
|
|
| 159 | 172 |
} |
| 160 | 173 |
|
| 161 | 174 |
obj := c.sysconn.Object("org.freedesktop.systemd1", path)
|
| ... | ... |
@@ -172,9 +186,15 @@ func (c *Conn) getProperties(unit string, dbusInterface string) (map[string]inte |
| 172 | 172 |
return out, nil |
| 173 | 173 |
} |
| 174 | 174 |
|
| 175 |
-// GetUnitProperties takes the unit name and returns all of its dbus object properties. |
|
| 175 |
+// GetUnitProperties takes the (unescaped) unit name and returns all of its dbus object properties. |
|
| 176 | 176 |
func (c *Conn) GetUnitProperties(unit string) (map[string]interface{}, error) {
|
| 177 |
- return c.getProperties(unit, "org.freedesktop.systemd1.Unit") |
|
| 177 |
+ path := unitPath(unit) |
|
| 178 |
+ return c.getProperties(path, "org.freedesktop.systemd1.Unit") |
|
| 179 |
+} |
|
| 180 |
+ |
|
| 181 |
+// GetUnitProperties takes the (escaped) unit path and returns all of its dbus object properties. |
|
| 182 |
+func (c *Conn) GetUnitPathProperties(path dbus.ObjectPath) (map[string]interface{}, error) {
|
|
| 183 |
+ return c.getProperties(path, "org.freedesktop.systemd1.Unit") |
|
| 178 | 184 |
} |
| 179 | 185 |
|
| 180 | 186 |
func (c *Conn) getProperty(unit string, dbusInterface string, propertyName string) (*Property, error) {
|
| ... | ... |
@@ -208,7 +228,8 @@ func (c *Conn) GetServiceProperty(service string, propertyName string) (*Propert |
| 208 | 208 |
// Valid values for unitType: Service, Socket, Target, Device, Mount, Automount, Snapshot, Timer, Swap, Path, Slice, Scope |
| 209 | 209 |
// return "dbus.Error: Unknown interface" if the unitType is not the correct type of the unit |
| 210 | 210 |
func (c *Conn) GetUnitTypeProperties(unit string, unitType string) (map[string]interface{}, error) {
|
| 211 |
- return c.getProperties(unit, "org.freedesktop.systemd1."+unitType) |
|
| 211 |
+ path := unitPath(unit) |
|
| 212 |
+ return c.getProperties(path, "org.freedesktop.systemd1."+unitType) |
|
| 212 | 213 |
} |
| 213 | 214 |
|
| 214 | 215 |
// SetUnitProperties() may be used to modify certain unit properties at runtime. |
| ... | ... |
@@ -292,6 +313,7 @@ func (c *Conn) ListUnitsByPatterns(states []string, patterns []string) ([]UnitSt |
| 292 | 292 |
// names and returns an UnitStatus array. Comparing to ListUnitsByPatterns |
| 293 | 293 |
// method, this method returns statuses even for inactive or non-existing |
| 294 | 294 |
// units. Input array should contain exact unit names, but not patterns. |
| 295 |
+// Note: Requires systemd v230 or higher |
|
| 295 | 296 |
func (c *Conn) ListUnitsByNames(units []string) ([]UnitStatus, error) {
|
| 296 | 297 |
return c.listUnitsInternal(c.sysobj.Call("org.freedesktop.systemd1.Manager.ListUnitsByNames", 0, units).Store)
|
| 297 | 298 |
} |
| ... | ... |
@@ -563,3 +585,8 @@ func (c *Conn) Reload() error {
|
| 563 | 563 |
func unitPath(name string) dbus.ObjectPath {
|
| 564 | 564 |
return dbus.ObjectPath("/org/freedesktop/systemd1/unit/" + PathBusEscape(name))
|
| 565 | 565 |
} |
| 566 |
+ |
|
| 567 |
+// unitName returns the unescaped base element of the supplied escaped path |
|
| 568 |
+func unitName(dpath dbus.ObjectPath) string {
|
|
| 569 |
+ return pathBusUnescape(path.Base(string(dpath))) |
|
| 570 |
+} |
| ... | ... |
@@ -16,6 +16,7 @@ package dbus |
| 16 | 16 |
|
| 17 | 17 |
import ( |
| 18 | 18 |
"errors" |
| 19 |
+ "log" |
|
| 19 | 20 |
"time" |
| 20 | 21 |
|
| 21 | 22 |
"github.com/godbus/dbus" |
| ... | ... |
@@ -36,22 +37,12 @@ func (c *Conn) Subscribe() error {
|
| 36 | 36 |
c.sigconn.BusObject().Call("org.freedesktop.DBus.AddMatch", 0,
|
| 37 | 37 |
"type='signal',interface='org.freedesktop.DBus.Properties',member='PropertiesChanged'") |
| 38 | 38 |
|
| 39 |
- err := c.sigobj.Call("org.freedesktop.systemd1.Manager.Subscribe", 0).Store()
|
|
| 40 |
- if err != nil {
|
|
| 41 |
- return err |
|
| 42 |
- } |
|
| 43 |
- |
|
| 44 |
- return nil |
|
| 39 |
+ return c.sigobj.Call("org.freedesktop.systemd1.Manager.Subscribe", 0).Store()
|
|
| 45 | 40 |
} |
| 46 | 41 |
|
| 47 | 42 |
// Unsubscribe this connection from systemd dbus events. |
| 48 | 43 |
func (c *Conn) Unsubscribe() error {
|
| 49 |
- err := c.sigobj.Call("org.freedesktop.systemd1.Manager.Unsubscribe", 0).Store()
|
|
| 50 |
- if err != nil {
|
|
| 51 |
- return err |
|
| 52 |
- } |
|
| 53 |
- |
|
| 54 |
- return nil |
|
| 44 |
+ return c.sigobj.Call("org.freedesktop.systemd1.Manager.Unsubscribe", 0).Store()
|
|
| 55 | 45 |
} |
| 56 | 46 |
|
| 57 | 47 |
func (c *Conn) dispatch() {
|
| ... | ... |
@@ -70,7 +61,8 @@ func (c *Conn) dispatch() {
|
| 70 | 70 |
c.jobComplete(signal) |
| 71 | 71 |
} |
| 72 | 72 |
|
| 73 |
- if c.subscriber.updateCh == nil {
|
|
| 73 |
+ if c.subStateSubscriber.updateCh == nil && |
|
| 74 |
+ c.propertiesSubscriber.updateCh == nil {
|
|
| 74 | 75 |
continue |
| 75 | 76 |
} |
| 76 | 77 |
|
| ... | ... |
@@ -84,6 +76,12 @@ func (c *Conn) dispatch() {
|
| 84 | 84 |
case "org.freedesktop.DBus.Properties.PropertiesChanged": |
| 85 | 85 |
if signal.Body[0].(string) == "org.freedesktop.systemd1.Unit" {
|
| 86 | 86 |
unitPath = signal.Path |
| 87 |
+ |
|
| 88 |
+ if len(signal.Body) >= 2 {
|
|
| 89 |
+ if changed, ok := signal.Body[1].(map[string]dbus.Variant); ok {
|
|
| 90 |
+ c.sendPropertiesUpdate(unitPath, changed) |
|
| 91 |
+ } |
|
| 92 |
+ } |
|
| 87 | 93 |
} |
| 88 | 94 |
} |
| 89 | 95 |
|
| ... | ... |
@@ -169,42 +167,80 @@ type SubStateUpdate struct {
|
| 169 | 169 |
// is full, it attempts to write an error to errCh; if errCh is full, the error |
| 170 | 170 |
// passes silently. |
| 171 | 171 |
func (c *Conn) SetSubStateSubscriber(updateCh chan<- *SubStateUpdate, errCh chan<- error) {
|
| 172 |
- c.subscriber.Lock() |
|
| 173 |
- defer c.subscriber.Unlock() |
|
| 174 |
- c.subscriber.updateCh = updateCh |
|
| 175 |
- c.subscriber.errCh = errCh |
|
| 172 |
+ if c == nil {
|
|
| 173 |
+ msg := "nil receiver" |
|
| 174 |
+ select {
|
|
| 175 |
+ case errCh <- errors.New(msg): |
|
| 176 |
+ default: |
|
| 177 |
+ log.Printf("full error channel while reporting: %s\n", msg)
|
|
| 178 |
+ } |
|
| 179 |
+ return |
|
| 180 |
+ } |
|
| 181 |
+ |
|
| 182 |
+ c.subStateSubscriber.Lock() |
|
| 183 |
+ defer c.subStateSubscriber.Unlock() |
|
| 184 |
+ c.subStateSubscriber.updateCh = updateCh |
|
| 185 |
+ c.subStateSubscriber.errCh = errCh |
|
| 176 | 186 |
} |
| 177 | 187 |
|
| 178 |
-func (c *Conn) sendSubStateUpdate(path dbus.ObjectPath) {
|
|
| 179 |
- c.subscriber.Lock() |
|
| 180 |
- defer c.subscriber.Unlock() |
|
| 188 |
+func (c *Conn) sendSubStateUpdate(unitPath dbus.ObjectPath) {
|
|
| 189 |
+ c.subStateSubscriber.Lock() |
|
| 190 |
+ defer c.subStateSubscriber.Unlock() |
|
| 191 |
+ |
|
| 192 |
+ if c.subStateSubscriber.updateCh == nil {
|
|
| 193 |
+ return |
|
| 194 |
+ } |
|
| 181 | 195 |
|
| 182 |
- if c.shouldIgnore(path) {
|
|
| 196 |
+ isIgnored := c.shouldIgnore(unitPath) |
|
| 197 |
+ defer c.cleanIgnore() |
|
| 198 |
+ if isIgnored {
|
|
| 183 | 199 |
return |
| 184 | 200 |
} |
| 185 | 201 |
|
| 186 |
- info, err := c.GetUnitProperties(string(path)) |
|
| 202 |
+ info, err := c.GetUnitPathProperties(unitPath) |
|
| 187 | 203 |
if err != nil {
|
| 188 | 204 |
select {
|
| 189 |
- case c.subscriber.errCh <- err: |
|
| 205 |
+ case c.subStateSubscriber.errCh <- err: |
|
| 190 | 206 |
default: |
| 207 |
+ log.Printf("full error channel while reporting: %s\n", err)
|
|
| 191 | 208 |
} |
| 209 |
+ return |
|
| 192 | 210 |
} |
| 211 |
+ defer c.updateIgnore(unitPath, info) |
|
| 193 | 212 |
|
| 194 |
- name := info["Id"].(string) |
|
| 195 |
- substate := info["SubState"].(string) |
|
| 213 |
+ name, ok := info["Id"].(string) |
|
| 214 |
+ if !ok {
|
|
| 215 |
+ msg := "failed to cast info.Id" |
|
| 216 |
+ select {
|
|
| 217 |
+ case c.subStateSubscriber.errCh <- errors.New(msg): |
|
| 218 |
+ default: |
|
| 219 |
+ log.Printf("full error channel while reporting: %s\n", err)
|
|
| 220 |
+ } |
|
| 221 |
+ return |
|
| 222 |
+ } |
|
| 223 |
+ substate, ok := info["SubState"].(string) |
|
| 224 |
+ if !ok {
|
|
| 225 |
+ msg := "failed to cast info.SubState" |
|
| 226 |
+ select {
|
|
| 227 |
+ case c.subStateSubscriber.errCh <- errors.New(msg): |
|
| 228 |
+ default: |
|
| 229 |
+ log.Printf("full error channel while reporting: %s\n", msg)
|
|
| 230 |
+ } |
|
| 231 |
+ return |
|
| 232 |
+ } |
|
| 196 | 233 |
|
| 197 | 234 |
update := &SubStateUpdate{name, substate}
|
| 198 | 235 |
select {
|
| 199 |
- case c.subscriber.updateCh <- update: |
|
| 236 |
+ case c.subStateSubscriber.updateCh <- update: |
|
| 200 | 237 |
default: |
| 238 |
+ msg := "update channel is full" |
|
| 201 | 239 |
select {
|
| 202 |
- case c.subscriber.errCh <- errors.New("update channel full!"):
|
|
| 240 |
+ case c.subStateSubscriber.errCh <- errors.New(msg): |
|
| 203 | 241 |
default: |
| 242 |
+ log.Printf("full error channel while reporting: %s\n", msg)
|
|
| 204 | 243 |
} |
| 244 |
+ return |
|
| 205 | 245 |
} |
| 206 |
- |
|
| 207 |
- c.updateIgnore(path, info) |
|
| 208 | 246 |
} |
| 209 | 247 |
|
| 210 | 248 |
// The ignore functions work around a wart in the systemd dbus interface. |
| ... | ... |
@@ -222,29 +258,76 @@ func (c *Conn) sendSubStateUpdate(path dbus.ObjectPath) {
|
| 222 | 222 |
// the properties). |
| 223 | 223 |
|
| 224 | 224 |
func (c *Conn) shouldIgnore(path dbus.ObjectPath) bool {
|
| 225 |
- t, ok := c.subscriber.ignore[path] |
|
| 225 |
+ t, ok := c.subStateSubscriber.ignore[path] |
|
| 226 | 226 |
return ok && t >= time.Now().UnixNano() |
| 227 | 227 |
} |
| 228 | 228 |
|
| 229 | 229 |
func (c *Conn) updateIgnore(path dbus.ObjectPath, info map[string]interface{}) {
|
| 230 |
- c.cleanIgnore() |
|
| 230 |
+ loadState, ok := info["LoadState"].(string) |
|
| 231 |
+ if !ok {
|
|
| 232 |
+ return |
|
| 233 |
+ } |
|
| 231 | 234 |
|
| 232 | 235 |
// unit is unloaded - it will trigger bad systemd dbus behavior |
| 233 |
- if info["LoadState"].(string) == "not-found" {
|
|
| 234 |
- c.subscriber.ignore[path] = time.Now().UnixNano() + ignoreInterval |
|
| 236 |
+ if loadState == "not-found" {
|
|
| 237 |
+ c.subStateSubscriber.ignore[path] = time.Now().UnixNano() + ignoreInterval |
|
| 235 | 238 |
} |
| 236 | 239 |
} |
| 237 | 240 |
|
| 238 | 241 |
// without this, ignore would grow unboundedly over time |
| 239 | 242 |
func (c *Conn) cleanIgnore() {
|
| 240 | 243 |
now := time.Now().UnixNano() |
| 241 |
- if c.subscriber.cleanIgnore < now {
|
|
| 242 |
- c.subscriber.cleanIgnore = now + cleanIgnoreInterval |
|
| 244 |
+ if c.subStateSubscriber.cleanIgnore < now {
|
|
| 245 |
+ c.subStateSubscriber.cleanIgnore = now + cleanIgnoreInterval |
|
| 243 | 246 |
|
| 244 |
- for p, t := range c.subscriber.ignore {
|
|
| 247 |
+ for p, t := range c.subStateSubscriber.ignore {
|
|
| 245 | 248 |
if t < now {
|
| 246 |
- delete(c.subscriber.ignore, p) |
|
| 249 |
+ delete(c.subStateSubscriber.ignore, p) |
|
| 247 | 250 |
} |
| 248 | 251 |
} |
| 249 | 252 |
} |
| 250 | 253 |
} |
| 254 |
+ |
|
| 255 |
+// PropertiesUpdate holds a map of a unit's changed properties |
|
| 256 |
+type PropertiesUpdate struct {
|
|
| 257 |
+ UnitName string |
|
| 258 |
+ Changed map[string]dbus.Variant |
|
| 259 |
+} |
|
| 260 |
+ |
|
| 261 |
+// SetPropertiesSubscriber writes to updateCh when any unit's properties |
|
| 262 |
+// change. Every property change reported by systemd will be sent; that is, no |
|
| 263 |
+// transitions will be "missed" (as they might be with SetSubStateSubscriber). |
|
| 264 |
+// However, state changes will only be written to the channel with non-blocking |
|
| 265 |
+// writes. If updateCh is full, it attempts to write an error to errCh; if |
|
| 266 |
+// errCh is full, the error passes silently. |
|
| 267 |
+func (c *Conn) SetPropertiesSubscriber(updateCh chan<- *PropertiesUpdate, errCh chan<- error) {
|
|
| 268 |
+ c.propertiesSubscriber.Lock() |
|
| 269 |
+ defer c.propertiesSubscriber.Unlock() |
|
| 270 |
+ c.propertiesSubscriber.updateCh = updateCh |
|
| 271 |
+ c.propertiesSubscriber.errCh = errCh |
|
| 272 |
+} |
|
| 273 |
+ |
|
| 274 |
+// we don't need to worry about shouldIgnore() here because |
|
| 275 |
+// sendPropertiesUpdate doesn't call GetProperties() |
|
| 276 |
+func (c *Conn) sendPropertiesUpdate(unitPath dbus.ObjectPath, changedProps map[string]dbus.Variant) {
|
|
| 277 |
+ c.propertiesSubscriber.Lock() |
|
| 278 |
+ defer c.propertiesSubscriber.Unlock() |
|
| 279 |
+ |
|
| 280 |
+ if c.propertiesSubscriber.updateCh == nil {
|
|
| 281 |
+ return |
|
| 282 |
+ } |
|
| 283 |
+ |
|
| 284 |
+ update := &PropertiesUpdate{unitName(unitPath), changedProps}
|
|
| 285 |
+ |
|
| 286 |
+ select {
|
|
| 287 |
+ case c.propertiesSubscriber.updateCh <- update: |
|
| 288 |
+ default: |
|
| 289 |
+ msg := "update channel is full" |
|
| 290 |
+ select {
|
|
| 291 |
+ case c.propertiesSubscriber.errCh <- errors.New(msg): |
|
| 292 |
+ default: |
|
| 293 |
+ log.Printf("full error channel while reporting: %s\n", msg)
|
|
| 294 |
+ } |
|
| 295 |
+ return |
|
| 296 |
+ } |
|
| 297 |
+} |
| ... | ... |
@@ -103,7 +103,10 @@ func Send(message string, priority Priority, vars map[string]string) error {
|
| 103 | 103 |
if !ok {
|
| 104 | 104 |
return journalError("can't send file through non-Unix connection")
|
| 105 | 105 |
} |
| 106 |
- unixConn.WriteMsgUnix([]byte{}, rights, nil)
|
|
| 106 |
+ _, _, err = unixConn.WriteMsgUnix([]byte{}, rights, nil)
|
|
| 107 |
+ if err != nil {
|
|
| 108 |
+ return journalError(err.Error()) |
|
| 109 |
+ } |
|
| 107 | 110 |
} else if err != nil {
|
| 108 | 111 |
return journalError(err.Error()) |
| 109 | 112 |
} |
| ... | ... |
@@ -165,7 +168,7 @@ func tempFd() (*os.File, error) {
|
| 165 | 165 |
if err != nil {
|
| 166 | 166 |
return nil, err |
| 167 | 167 |
} |
| 168 |
- syscall.Unlink(file.Name()) |
|
| 168 |
+ err = syscall.Unlink(file.Name()) |
|
| 169 | 169 |
if err != nil {
|
| 170 | 170 |
return nil, err |
| 171 | 171 |
} |