123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188 |
- // 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.
- // Integration with the systemd D-Bus API. See http://www.freedesktop.org/wiki/Software/systemd/dbus/
- package dbus
- import (
- "fmt"
- "os"
- "strconv"
- "strings"
- "sync"
- "github.com/godbus/dbus"
- )
- const (
- alpha = `abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ`
- num = `0123456789`
- alphanum = alpha + num
- signalBuffer = 100
- )
- // needsEscape checks whether a byte in a potential dbus ObjectPath needs to be escaped
- func needsEscape(i int, b byte) bool {
- // Escape everything that is not a-z-A-Z-0-9
- // Also escape 0-9 if it's the first character
- return strings.IndexByte(alphanum, b) == -1 ||
- (i == 0 && strings.IndexByte(num, b) != -1)
- }
- // PathBusEscape sanitizes a constituent string of a dbus ObjectPath using the
- // rules that systemd uses for serializing special characters.
- func PathBusEscape(path string) string {
- // Special case the empty string
- if len(path) == 0 {
- return "_"
- }
- n := []byte{}
- for i := 0; i < len(path); i++ {
- c := path[i]
- if needsEscape(i, c) {
- e := fmt.Sprintf("_%x", c)
- n = append(n, []byte(e)...)
- } else {
- n = append(n, c)
- }
- }
- return string(n)
- }
- // Conn is a connection to systemd's dbus endpoint.
- type Conn struct {
- // sysconn/sysobj are only used to call dbus methods
- sysconn *dbus.Conn
- sysobj dbus.BusObject
- // sigconn/sigobj are only used to receive dbus signals
- sigconn *dbus.Conn
- sigobj dbus.BusObject
- jobListener struct {
- jobs map[dbus.ObjectPath]chan<- string
- sync.Mutex
- }
- subscriber struct {
- updateCh chan<- *SubStateUpdate
- errCh chan<- error
- sync.Mutex
- ignore map[dbus.ObjectPath]int64
- cleanIgnore int64
- }
- }
- // New establishes a connection to the system bus and authenticates.
- // Callers should call Close() when done with the connection.
- func New() (*Conn, error) {
- return newConnection(func() (*dbus.Conn, error) {
- return dbusAuthHelloConnection(dbus.SystemBusPrivate)
- })
- }
- // NewUserConnection establishes a connection to the session bus and
- // authenticates. This can be used to connect to systemd user instances.
- // Callers should call Close() when done with the connection.
- func NewUserConnection() (*Conn, error) {
- return newConnection(func() (*dbus.Conn, error) {
- return dbusAuthHelloConnection(dbus.SessionBusPrivate)
- })
- }
- // NewSystemdConnection establishes a private, direct connection to systemd.
- // This can be used for communicating with systemd without a dbus daemon.
- // Callers should call Close() when done with the connection.
- func NewSystemdConnection() (*Conn, error) {
- return newConnection(func() (*dbus.Conn, error) {
- // We skip Hello when talking directly to systemd.
- return dbusAuthConnection(func() (*dbus.Conn, error) {
- return dbus.Dial("unix:path=/run/systemd/private")
- })
- })
- }
- // Close closes an established connection
- func (c *Conn) Close() {
- c.sysconn.Close()
- c.sigconn.Close()
- }
- func newConnection(createBus func() (*dbus.Conn, error)) (*Conn, error) {
- sysconn, err := createBus()
- if err != nil {
- return nil, err
- }
- sigconn, err := createBus()
- if err != nil {
- sysconn.Close()
- return nil, err
- }
- c := &Conn{
- sysconn: sysconn,
- sysobj: systemdObject(sysconn),
- sigconn: sigconn,
- sigobj: systemdObject(sigconn),
- }
- c.subscriber.ignore = make(map[dbus.ObjectPath]int64)
- c.jobListener.jobs = make(map[dbus.ObjectPath]chan<- string)
- // Setup the listeners on jobs so that we can get completions
- c.sigconn.BusObject().Call("org.freedesktop.DBus.AddMatch", 0,
- "type='signal', interface='org.freedesktop.systemd1.Manager', member='JobRemoved'")
- c.dispatch()
- return c, nil
- }
- func dbusAuthConnection(createBus func() (*dbus.Conn, error)) (*dbus.Conn, error) {
- conn, err := createBus()
- if err != nil {
- return nil, err
- }
- // Only use EXTERNAL method, and hardcode the uid (not username)
- // to avoid a username lookup (which requires a dynamically linked
- // libc)
- methods := []dbus.Auth{dbus.AuthExternal(strconv.Itoa(os.Getuid()))}
- err = conn.Auth(methods)
- if err != nil {
- conn.Close()
- return nil, err
- }
- return conn, nil
- }
- func dbusAuthHelloConnection(createBus func() (*dbus.Conn, error)) (*dbus.Conn, error) {
- conn, err := dbusAuthConnection(createBus)
- if err != nil {
- return nil, err
- }
- if err = conn.Hello(); err != nil {
- conn.Close()
- return nil, err
- }
- return conn, nil
- }
- func systemdObject(conn *dbus.Conn) dbus.BusObject {
- return conn.Object("org.freedesktop.systemd1", dbus.ObjectPath("/org/freedesktop/systemd1"))
- }
|