Browse code

Bump go-systemd dependency to v17

Signed-off-by: Christian Muehlhaeuser <muesli@gmail.com>

Christian Muehlhaeuser authored on 2018/05/24 02:06:13
Showing 13 changed files
... ...
@@ -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
83 83
new file mode 100644
... ...
@@ -0,0 +1,5 @@
0
+CoreOS Project
1
+Copyright 2018 CoreOS, Inc
2
+
3
+This product includes software developed at CoreOS, Inc.
4
+(http://www.coreos.com/).
... ...
@@ -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
+}
... ...
@@ -36,7 +36,7 @@ func (s *set) Length() int {
36 36
 }
37 37
 
38 38
 func (s *set) Values() (values []string) {
39
-	for val, _ := range s.data {
39
+	for val := range s.data {
40 40
 		values = append(values, val)
41 41
 	}
42 42
 	return
... ...
@@ -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
 	}