164 lines
4.2 KiB
Go
164 lines
4.2 KiB
Go
// 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.Object
|
|
|
|
// sigconn/sigobj are only used to receive dbus signals
|
|
sigconn *dbus.Conn
|
|
sigobj *dbus.Object
|
|
|
|
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(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(dbus.SessionBusPrivate)
|
|
}
|
|
|
|
// 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 := dbusConnection(createBus)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
sigconn, err := dbusConnection(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 dbusConnection(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
|
|
}
|
|
|
|
err = conn.Hello()
|
|
if err != nil {
|
|
conn.Close()
|
|
return nil, err
|
|
}
|
|
|
|
return conn, nil
|
|
}
|
|
|
|
func systemdObject(conn *dbus.Conn) *dbus.Object {
|
|
return conn.Object("org.freedesktop.systemd1", dbus.ObjectPath("/org/freedesktop/systemd1"))
|
|
}
|