bump coreos/go-systemd v19, godbus/dbus v5.0.1
- https://github.com/coreos/go-systemd/compare/v14..v19 - coreos/go-systemd#248 dbus: add SetPropertiesSubscriber method - coreos/go-systemd#251 activation: add support for listeners with names - coreos/go-systemd#296 dbus: Fix API break from godbus - https://github.com/godbus/dbus/compare/v3..v5.0.1 - godbus/dbus#89 introduce MakeVariantWithSignature Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
This commit is contained in:
parent
21498b8e54
commit
85c02f3f30
|
@ -13,8 +13,8 @@ github.com/syndtr/gocapability d98352740cb2c55f81556b63d4a1ec64c5a319c2
|
|||
github.com/vishvananda/netlink 1e2e08e8a2dcdacaae3f14ac44c5cfa31361f270
|
||||
|
||||
# systemd integration.
|
||||
github.com/coreos/go-systemd 48702e0da86bd25e76cfef347e2adeb434a0d0a6 # v14
|
||||
github.com/godbus/dbus c7fdd8b5cd55e87b4e1f4e372cdb1db61dd6c66f # v3
|
||||
github.com/coreos/go-systemd 95778dfbb74eb7e4dbaf43bf7d71809650ef8076 # v19
|
||||
github.com/godbus/dbus 2ff6f7ffd60f0f2410b3105864bdd12c7894f844 # v5.0.1
|
||||
github.com/golang/protobuf 18c9bb3261723cd5401db4d0c9fbc5c3b6c70fe8
|
||||
|
||||
# Command-line interface.
|
||||
|
|
|
@ -0,0 +1,5 @@
|
|||
CoreOS Project
|
||||
Copyright 2018 CoreOS, Inc
|
||||
|
||||
This product includes software developed at CoreOS, Inc.
|
||||
(http://www.coreos.com/).
|
|
@ -2,13 +2,17 @@
|
|||
|
||||
[![Build Status](https://travis-ci.org/coreos/go-systemd.png?branch=master)](https://travis-ci.org/coreos/go-systemd)
|
||||
[![godoc](https://godoc.org/github.com/coreos/go-systemd?status.svg)](http://godoc.org/github.com/coreos/go-systemd)
|
||||
![minimum golang 1.10](https://img.shields.io/badge/golang-1.10%2B-orange.svg)
|
||||
|
||||
|
||||
Go bindings to systemd. The project has several packages:
|
||||
|
||||
- `activation` - for writing and using socket activation from Go
|
||||
- `daemon` - for notifying systemd of service status changes
|
||||
- `dbus` - for starting/stopping/inspecting running services and units
|
||||
- `journal` - for writing to systemd's logging service, journald
|
||||
- `sdjournal` - for reading from journald by wrapping its C API
|
||||
- `login1` - for integration with the systemd logind API
|
||||
- `machine1` - for registering machines/containers with systemd
|
||||
- `unit` - for (de)serialization and comparison of unit files
|
||||
|
||||
|
@ -18,10 +22,9 @@ An example HTTP server using socket activation can be quickly set up by followin
|
|||
|
||||
https://github.com/coreos/go-systemd/tree/master/examples/activation/httpserver
|
||||
|
||||
## Journal
|
||||
## systemd Service Notification
|
||||
|
||||
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.
|
||||
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.
|
||||
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.
|
||||
|
||||
## D-Bus
|
||||
|
||||
|
@ -45,6 +48,20 @@ Create `/etc/dbus-1/system-local.conf` that looks like this:
|
|||
</busconfig>
|
||||
```
|
||||
|
||||
## Journal
|
||||
|
||||
### Writing to the Journal
|
||||
|
||||
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.
|
||||
|
||||
### Reading from the Journal
|
||||
|
||||
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.
|
||||
|
||||
## logind
|
||||
|
||||
The `login1` package provides functions to integrate with the [systemd logind API](http://www.freedesktop.org/wiki/Software/systemd/logind/).
|
||||
|
||||
## machined
|
||||
|
||||
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
|
|||
import (
|
||||
"os"
|
||||
"strconv"
|
||||
"strings"
|
||||
"syscall"
|
||||
)
|
||||
|
||||
// based on: https://gist.github.com/alberts/4640792
|
||||
const (
|
||||
// listenFdsStart corresponds to `SD_LISTEN_FDS_START`.
|
||||
listenFdsStart = 3
|
||||
)
|
||||
|
||||
// Files returns a slice containing a `os.File` object for each
|
||||
// file descriptor passed to this process via systemd fd-passing protocol.
|
||||
//
|
||||
// The order of the file descriptors is preserved in the returned slice.
|
||||
// `unsetEnv` is typically set to `true` in order to avoid clashes in
|
||||
// fd usage and to avoid leaking environment flags to child processes.
|
||||
func Files(unsetEnv bool) []*os.File {
|
||||
if unsetEnv {
|
||||
defer os.Unsetenv("LISTEN_PID")
|
||||
defer os.Unsetenv("LISTEN_FDS")
|
||||
defer os.Unsetenv("LISTEN_FDNAMES")
|
||||
}
|
||||
|
||||
pid, err := strconv.Atoi(os.Getenv("LISTEN_PID"))
|
||||
|
@ -42,10 +50,17 @@ func Files(unsetEnv bool) []*os.File {
|
|||
return nil
|
||||
}
|
||||
|
||||
names := strings.Split(os.Getenv("LISTEN_FDNAMES"), ":")
|
||||
|
||||
files := make([]*os.File, 0, nfds)
|
||||
for fd := listenFdsStart; fd < listenFdsStart+nfds; fd++ {
|
||||
syscall.CloseOnExec(fd)
|
||||
files = append(files, os.NewFile(uintptr(fd), "LISTEN_FD_"+strconv.Itoa(fd)))
|
||||
name := "LISTEN_FD_" + strconv.Itoa(fd)
|
||||
offset := fd - listenFdsStart
|
||||
if offset < len(names) && len(names[offset]) > 0 {
|
||||
name = names[offset]
|
||||
}
|
||||
files = append(files, os.NewFile(uintptr(fd), name))
|
||||
}
|
||||
|
||||
return files
|
||||
|
|
|
@ -25,13 +25,33 @@ import (
|
|||
// The order of the file descriptors is preserved in the returned slice.
|
||||
// Nil values are used to fill any gaps. For example if systemd were to return file descriptors
|
||||
// corresponding with "udp, tcp, tcp", then the slice would contain {nil, net.Listener, net.Listener}
|
||||
func Listeners(unsetEnv bool) ([]net.Listener, error) {
|
||||
files := Files(unsetEnv)
|
||||
func Listeners() ([]net.Listener, error) {
|
||||
files := Files(true)
|
||||
listeners := make([]net.Listener, len(files))
|
||||
|
||||
for i, f := range files {
|
||||
if pc, err := net.FileListener(f); err == nil {
|
||||
listeners[i] = pc
|
||||
f.Close()
|
||||
}
|
||||
}
|
||||
return listeners, nil
|
||||
}
|
||||
|
||||
// ListenersWithNames maps a listener name to a set of net.Listener instances.
|
||||
func ListenersWithNames() (map[string][]net.Listener, error) {
|
||||
files := Files(true)
|
||||
listeners := map[string][]net.Listener{}
|
||||
|
||||
for _, f := range files {
|
||||
if pc, err := net.FileListener(f); err == nil {
|
||||
current, ok := listeners[f.Name()]
|
||||
if !ok {
|
||||
listeners[f.Name()] = []net.Listener{pc}
|
||||
} else {
|
||||
listeners[f.Name()] = append(current, pc)
|
||||
}
|
||||
f.Close()
|
||||
}
|
||||
}
|
||||
return listeners, nil
|
||||
|
@ -40,8 +60,8 @@ func Listeners(unsetEnv bool) ([]net.Listener, error) {
|
|||
// TLSListeners returns a slice containing a net.listener for each matching TCP socket type
|
||||
// passed to this process.
|
||||
// It uses default Listeners func and forces TCP sockets handlers to use TLS based on tlsConfig.
|
||||
func TLSListeners(unsetEnv bool, tlsConfig *tls.Config) ([]net.Listener, error) {
|
||||
listeners, err := Listeners(unsetEnv)
|
||||
func TLSListeners(tlsConfig *tls.Config) ([]net.Listener, error) {
|
||||
listeners, err := Listeners()
|
||||
|
||||
if listeners == nil || err != nil {
|
||||
return nil, err
|
||||
|
@ -58,3 +78,26 @@ func TLSListeners(unsetEnv bool, tlsConfig *tls.Config) ([]net.Listener, error)
|
|||
|
||||
return listeners, err
|
||||
}
|
||||
|
||||
// TLSListenersWithNames maps a listener name to a net.Listener with
|
||||
// the associated TLS configuration.
|
||||
func TLSListenersWithNames(tlsConfig *tls.Config) (map[string][]net.Listener, error) {
|
||||
listeners, err := ListenersWithNames()
|
||||
|
||||
if listeners == nil || err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if tlsConfig != nil && err == nil {
|
||||
for _, ll := range listeners {
|
||||
// Activate TLS only for TCP sockets
|
||||
for i, l := range ll {
|
||||
if l.Addr().Network() == "tcp" {
|
||||
ll[i] = tls.NewListener(l, tlsConfig)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return listeners, err
|
||||
}
|
||||
|
|
|
@ -24,13 +24,14 @@ import (
|
|||
// The order of the file descriptors is preserved in the returned slice.
|
||||
// Nil values are used to fill any gaps. For example if systemd were to return file descriptors
|
||||
// corresponding with "udp, tcp, udp", then the slice would contain {net.PacketConn, nil, net.PacketConn}
|
||||
func PacketConns(unsetEnv bool) ([]net.PacketConn, error) {
|
||||
files := Files(unsetEnv)
|
||||
func PacketConns() ([]net.PacketConn, error) {
|
||||
files := Files(true)
|
||||
conns := make([]net.PacketConn, len(files))
|
||||
|
||||
for i, f := range files {
|
||||
if pc, err := net.FilePacketConn(f); err == nil {
|
||||
conns[i] = pc
|
||||
f.Close()
|
||||
}
|
||||
}
|
||||
return conns, nil
|
||||
|
|
|
@ -16,6 +16,7 @@
|
|||
package dbus
|
||||
|
||||
import (
|
||||
"encoding/hex"
|
||||
"fmt"
|
||||
"os"
|
||||
"strconv"
|
||||
|
@ -60,6 +61,27 @@ func PathBusEscape(path string) string {
|
|||
return string(n)
|
||||
}
|
||||
|
||||
// pathBusUnescape is the inverse of PathBusEscape.
|
||||
func pathBusUnescape(path string) string {
|
||||
if path == "_" {
|
||||
return ""
|
||||
}
|
||||
n := []byte{}
|
||||
for i := 0; i < len(path); i++ {
|
||||
c := path[i]
|
||||
if c == '_' && i+2 < len(path) {
|
||||
res, err := hex.DecodeString(path[i+1 : i+3])
|
||||
if err == nil {
|
||||
n = append(n, res...)
|
||||
}
|
||||
i += 2
|
||||
} 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
|
||||
|
@ -74,13 +96,18 @@ type Conn struct {
|
|||
jobs map[dbus.ObjectPath]chan<- string
|
||||
sync.Mutex
|
||||
}
|
||||
subscriber struct {
|
||||
subStateSubscriber struct {
|
||||
updateCh chan<- *SubStateUpdate
|
||||
errCh chan<- error
|
||||
sync.Mutex
|
||||
ignore map[dbus.ObjectPath]int64
|
||||
cleanIgnore int64
|
||||
}
|
||||
propertiesSubscriber struct {
|
||||
updateCh chan<- *PropertiesUpdate
|
||||
errCh chan<- error
|
||||
sync.Mutex
|
||||
}
|
||||
}
|
||||
|
||||
// New establishes a connection to any available bus and authenticates.
|
||||
|
@ -116,7 +143,7 @@ func NewUserConnection() (*Conn, error) {
|
|||
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 dbusAuthConnection(func(opts ...dbus.ConnOption) (*dbus.Conn, error) {
|
||||
return dbus.Dial("unix:path=/run/systemd/private")
|
||||
})
|
||||
})
|
||||
|
@ -152,7 +179,7 @@ func NewConnection(dialBus func() (*dbus.Conn, error)) (*Conn, error) {
|
|||
sigobj: systemdObject(sigconn),
|
||||
}
|
||||
|
||||
c.subscriber.ignore = make(map[dbus.ObjectPath]int64)
|
||||
c.subStateSubscriber.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
|
||||
|
@ -174,7 +201,7 @@ func (c *Conn) GetManagerProperty(prop string) (string, error) {
|
|||
return variant.String(), nil
|
||||
}
|
||||
|
||||
func dbusAuthConnection(createBus func() (*dbus.Conn, error)) (*dbus.Conn, error) {
|
||||
func dbusAuthConnection(createBus func(opts ...dbus.ConnOption) (*dbus.Conn, error)) (*dbus.Conn, error) {
|
||||
conn, err := createBus()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
|
@ -194,7 +221,7 @@ func dbusAuthConnection(createBus func() (*dbus.Conn, error)) (*dbus.Conn, error
|
|||
return conn, nil
|
||||
}
|
||||
|
||||
func dbusAuthHelloConnection(createBus func() (*dbus.Conn, error)) (*dbus.Conn, error) {
|
||||
func dbusAuthHelloConnection(createBus func(opts ...dbus.ConnOption) (*dbus.Conn, error)) (*dbus.Conn, error) {
|
||||
conn, err := dbusAuthConnection(createBus)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
// Copyright 2015 CoreOS, Inc.
|
||||
// Copyright 2015, 2018 CoreOS, Inc.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
|
@ -16,6 +16,7 @@ package dbus
|
|||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"path"
|
||||
"strconv"
|
||||
|
||||
|
@ -116,13 +117,13 @@ func (c *Conn) TryRestartUnit(name string, mode string, ch chan<- string) (int,
|
|||
return c.startJob(ch, "org.freedesktop.systemd1.Manager.TryRestartUnit", name, mode)
|
||||
}
|
||||
|
||||
// ReloadOrRestart attempts a reload if the unit supports it and use a restart
|
||||
// ReloadOrRestartUnit attempts a reload if the unit supports it and use a restart
|
||||
// otherwise.
|
||||
func (c *Conn) ReloadOrRestartUnit(name string, mode string, ch chan<- string) (int, error) {
|
||||
return c.startJob(ch, "org.freedesktop.systemd1.Manager.ReloadOrRestartUnit", name, mode)
|
||||
}
|
||||
|
||||
// ReloadOrTryRestart attempts a reload if the unit supports it and use a "Try"
|
||||
// ReloadOrTryRestartUnit attempts a reload if the unit supports it and use a "Try"
|
||||
// flavored restart otherwise.
|
||||
func (c *Conn) ReloadOrTryRestartUnit(name string, mode string, ch chan<- string) (int, error) {
|
||||
return c.startJob(ch, "org.freedesktop.systemd1.Manager.ReloadOrTryRestartUnit", name, mode)
|
||||
|
@ -148,14 +149,27 @@ func (c *Conn) ResetFailedUnit(name string) error {
|
|||
return c.sysobj.Call("org.freedesktop.systemd1.Manager.ResetFailedUnit", 0, name).Store()
|
||||
}
|
||||
|
||||
// getProperties takes the unit name and returns all of its dbus object properties, for the given dbus interface
|
||||
func (c *Conn) getProperties(unit string, dbusInterface string) (map[string]interface{}, error) {
|
||||
// SystemState returns the systemd state. Equivalent to `systemctl is-system-running`.
|
||||
func (c *Conn) SystemState() (*Property, error) {
|
||||
var err error
|
||||
var prop dbus.Variant
|
||||
|
||||
obj := c.sysconn.Object("org.freedesktop.systemd1", "/org/freedesktop/systemd1")
|
||||
err = obj.Call("org.freedesktop.DBus.Properties.Get", 0, "org.freedesktop.systemd1.Manager", "SystemState").Store(&prop)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &Property{Name: "SystemState", Value: prop}, nil
|
||||
}
|
||||
|
||||
// getProperties takes the unit path and returns all of its dbus object properties, for the given dbus interface
|
||||
func (c *Conn) getProperties(path dbus.ObjectPath, dbusInterface string) (map[string]interface{}, error) {
|
||||
var err error
|
||||
var props map[string]dbus.Variant
|
||||
|
||||
path := unitPath(unit)
|
||||
if !path.IsValid() {
|
||||
return nil, errors.New("invalid unit name: " + unit)
|
||||
return nil, fmt.Errorf("invalid unit name: %v", path)
|
||||
}
|
||||
|
||||
obj := c.sysconn.Object("org.freedesktop.systemd1", path)
|
||||
|
@ -172,9 +186,15 @@ func (c *Conn) getProperties(unit string, dbusInterface string) (map[string]inte
|
|||
return out, nil
|
||||
}
|
||||
|
||||
// GetUnitProperties takes the unit name and returns all of its dbus object properties.
|
||||
// GetUnitProperties takes the (unescaped) unit name and returns all of its dbus object properties.
|
||||
func (c *Conn) GetUnitProperties(unit string) (map[string]interface{}, error) {
|
||||
return c.getProperties(unit, "org.freedesktop.systemd1.Unit")
|
||||
path := unitPath(unit)
|
||||
return c.getProperties(path, "org.freedesktop.systemd1.Unit")
|
||||
}
|
||||
|
||||
// GetUnitPathProperties takes the (escaped) unit path and returns all of its dbus object properties.
|
||||
func (c *Conn) GetUnitPathProperties(path dbus.ObjectPath) (map[string]interface{}, error) {
|
||||
return c.getProperties(path, "org.freedesktop.systemd1.Unit")
|
||||
}
|
||||
|
||||
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
|
|||
// Valid values for unitType: Service, Socket, Target, Device, Mount, Automount, Snapshot, Timer, Swap, Path, Slice, Scope
|
||||
// return "dbus.Error: Unknown interface" if the unitType is not the correct type of the unit
|
||||
func (c *Conn) GetUnitTypeProperties(unit string, unitType string) (map[string]interface{}, error) {
|
||||
return c.getProperties(unit, "org.freedesktop.systemd1."+unitType)
|
||||
path := unitPath(unit)
|
||||
return c.getProperties(path, "org.freedesktop.systemd1."+unitType)
|
||||
}
|
||||
|
||||
// SetUnitProperties() may be used to modify certain unit properties at runtime.
|
||||
|
@ -270,6 +291,8 @@ func (c *Conn) listUnitsInternal(f storeFunc) ([]UnitStatus, error) {
|
|||
// ListUnits returns an array with all currently loaded units. Note that
|
||||
// units may be known by multiple names at the same time, and hence there might
|
||||
// be more unit names loaded than actual units behind them.
|
||||
// Also note that a unit is only loaded if it is active and/or enabled.
|
||||
// Units that are both disabled and inactive will thus not be returned.
|
||||
func (c *Conn) ListUnits() ([]UnitStatus, error) {
|
||||
return c.listUnitsInternal(c.sysobj.Call("org.freedesktop.systemd1.Manager.ListUnits", 0).Store)
|
||||
}
|
||||
|
@ -292,6 +315,7 @@ func (c *Conn) ListUnitsByPatterns(states []string, patterns []string) ([]UnitSt
|
|||
// names and returns an UnitStatus array. Comparing to ListUnitsByPatterns
|
||||
// method, this method returns statuses even for inactive or non-existing
|
||||
// units. Input array should contain exact unit names, but not patterns.
|
||||
// Note: Requires systemd v230 or higher
|
||||
func (c *Conn) ListUnitsByNames(units []string) ([]UnitStatus, error) {
|
||||
return c.listUnitsInternal(c.sysobj.Call("org.freedesktop.systemd1.Manager.ListUnitsByNames", 0, units).Store)
|
||||
}
|
||||
|
@ -563,3 +587,8 @@ func (c *Conn) Reload() error {
|
|||
func unitPath(name string) dbus.ObjectPath {
|
||||
return dbus.ObjectPath("/org/freedesktop/systemd1/unit/" + PathBusEscape(name))
|
||||
}
|
||||
|
||||
// unitName returns the unescaped base element of the supplied escaped path
|
||||
func unitName(dpath dbus.ObjectPath) string {
|
||||
return pathBusUnescape(path.Base(string(dpath)))
|
||||
}
|
||||
|
|
|
@ -36,7 +36,7 @@ func (s *set) Length() int {
|
|||
}
|
||||
|
||||
func (s *set) Values() (values []string) {
|
||||
for val, _ := range s.data {
|
||||
for val := range s.data {
|
||||
values = append(values, val)
|
||||
}
|
||||
return
|
||||
|
|
|
@ -16,6 +16,7 @@ package dbus
|
|||
|
||||
import (
|
||||
"errors"
|
||||
"log"
|
||||
"time"
|
||||
|
||||
"github.com/godbus/dbus"
|
||||
|
@ -36,22 +37,12 @@ func (c *Conn) Subscribe() error {
|
|||
c.sigconn.BusObject().Call("org.freedesktop.DBus.AddMatch", 0,
|
||||
"type='signal',interface='org.freedesktop.DBus.Properties',member='PropertiesChanged'")
|
||||
|
||||
err := c.sigobj.Call("org.freedesktop.systemd1.Manager.Subscribe", 0).Store()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
return c.sigobj.Call("org.freedesktop.systemd1.Manager.Subscribe", 0).Store()
|
||||
}
|
||||
|
||||
// Unsubscribe this connection from systemd dbus events.
|
||||
func (c *Conn) Unsubscribe() error {
|
||||
err := c.sigobj.Call("org.freedesktop.systemd1.Manager.Unsubscribe", 0).Store()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
return c.sigobj.Call("org.freedesktop.systemd1.Manager.Unsubscribe", 0).Store()
|
||||
}
|
||||
|
||||
func (c *Conn) dispatch() {
|
||||
|
@ -70,7 +61,8 @@ func (c *Conn) dispatch() {
|
|||
c.jobComplete(signal)
|
||||
}
|
||||
|
||||
if c.subscriber.updateCh == nil {
|
||||
if c.subStateSubscriber.updateCh == nil &&
|
||||
c.propertiesSubscriber.updateCh == nil {
|
||||
continue
|
||||
}
|
||||
|
||||
|
@ -84,6 +76,12 @@ func (c *Conn) dispatch() {
|
|||
case "org.freedesktop.DBus.Properties.PropertiesChanged":
|
||||
if signal.Body[0].(string) == "org.freedesktop.systemd1.Unit" {
|
||||
unitPath = signal.Path
|
||||
|
||||
if len(signal.Body) >= 2 {
|
||||
if changed, ok := signal.Body[1].(map[string]dbus.Variant); ok {
|
||||
c.sendPropertiesUpdate(unitPath, changed)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -96,7 +94,7 @@ func (c *Conn) dispatch() {
|
|||
}()
|
||||
}
|
||||
|
||||
// Returns two unbuffered channels which will receive all changed units every
|
||||
// SubscribeUnits returns two unbuffered channels which will receive all changed units every
|
||||
// interval. Deleted units are sent as nil.
|
||||
func (c *Conn) SubscribeUnits(interval time.Duration) (<-chan map[string]*UnitStatus, <-chan error) {
|
||||
return c.SubscribeUnitsCustom(interval, 0, func(u1, u2 *UnitStatus) bool { return *u1 != *u2 }, nil)
|
||||
|
@ -169,42 +167,80 @@ type SubStateUpdate struct {
|
|||
// is full, it attempts to write an error to errCh; if errCh is full, the error
|
||||
// passes silently.
|
||||
func (c *Conn) SetSubStateSubscriber(updateCh chan<- *SubStateUpdate, errCh chan<- error) {
|
||||
c.subscriber.Lock()
|
||||
defer c.subscriber.Unlock()
|
||||
c.subscriber.updateCh = updateCh
|
||||
c.subscriber.errCh = errCh
|
||||
}
|
||||
|
||||
func (c *Conn) sendSubStateUpdate(path dbus.ObjectPath) {
|
||||
c.subscriber.Lock()
|
||||
defer c.subscriber.Unlock()
|
||||
|
||||
if c.shouldIgnore(path) {
|
||||
if c == nil {
|
||||
msg := "nil receiver"
|
||||
select {
|
||||
case errCh <- errors.New(msg):
|
||||
default:
|
||||
log.Printf("full error channel while reporting: %s\n", msg)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
info, err := c.GetUnitProperties(string(path))
|
||||
if err != nil {
|
||||
select {
|
||||
case c.subscriber.errCh <- err:
|
||||
default:
|
||||
}
|
||||
c.subStateSubscriber.Lock()
|
||||
defer c.subStateSubscriber.Unlock()
|
||||
c.subStateSubscriber.updateCh = updateCh
|
||||
c.subStateSubscriber.errCh = errCh
|
||||
}
|
||||
|
||||
func (c *Conn) sendSubStateUpdate(unitPath dbus.ObjectPath) {
|
||||
c.subStateSubscriber.Lock()
|
||||
defer c.subStateSubscriber.Unlock()
|
||||
|
||||
if c.subStateSubscriber.updateCh == nil {
|
||||
return
|
||||
}
|
||||
|
||||
name := info["Id"].(string)
|
||||
substate := info["SubState"].(string)
|
||||
isIgnored := c.shouldIgnore(unitPath)
|
||||
defer c.cleanIgnore()
|
||||
if isIgnored {
|
||||
return
|
||||
}
|
||||
|
||||
info, err := c.GetUnitPathProperties(unitPath)
|
||||
if err != nil {
|
||||
select {
|
||||
case c.subStateSubscriber.errCh <- err:
|
||||
default:
|
||||
log.Printf("full error channel while reporting: %s\n", err)
|
||||
}
|
||||
return
|
||||
}
|
||||
defer c.updateIgnore(unitPath, info)
|
||||
|
||||
name, ok := info["Id"].(string)
|
||||
if !ok {
|
||||
msg := "failed to cast info.Id"
|
||||
select {
|
||||
case c.subStateSubscriber.errCh <- errors.New(msg):
|
||||
default:
|
||||
log.Printf("full error channel while reporting: %s\n", err)
|
||||
}
|
||||
return
|
||||
}
|
||||
substate, ok := info["SubState"].(string)
|
||||
if !ok {
|
||||
msg := "failed to cast info.SubState"
|
||||
select {
|
||||
case c.subStateSubscriber.errCh <- errors.New(msg):
|
||||
default:
|
||||
log.Printf("full error channel while reporting: %s\n", msg)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
update := &SubStateUpdate{name, substate}
|
||||
select {
|
||||
case c.subscriber.updateCh <- update:
|
||||
case c.subStateSubscriber.updateCh <- update:
|
||||
default:
|
||||
msg := "update channel is full"
|
||||
select {
|
||||
case c.subscriber.errCh <- errors.New("update channel full!"):
|
||||
case c.subStateSubscriber.errCh <- errors.New(msg):
|
||||
default:
|
||||
log.Printf("full error channel while reporting: %s\n", msg)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
c.updateIgnore(path, info)
|
||||
}
|
||||
|
||||
// The ignore functions work around a wart in the systemd dbus interface.
|
||||
|
@ -222,29 +258,76 @@ func (c *Conn) sendSubStateUpdate(path dbus.ObjectPath) {
|
|||
// the properties).
|
||||
|
||||
func (c *Conn) shouldIgnore(path dbus.ObjectPath) bool {
|
||||
t, ok := c.subscriber.ignore[path]
|
||||
t, ok := c.subStateSubscriber.ignore[path]
|
||||
return ok && t >= time.Now().UnixNano()
|
||||
}
|
||||
|
||||
func (c *Conn) updateIgnore(path dbus.ObjectPath, info map[string]interface{}) {
|
||||
c.cleanIgnore()
|
||||
loadState, ok := info["LoadState"].(string)
|
||||
if !ok {
|
||||
return
|
||||
}
|
||||
|
||||
// unit is unloaded - it will trigger bad systemd dbus behavior
|
||||
if info["LoadState"].(string) == "not-found" {
|
||||
c.subscriber.ignore[path] = time.Now().UnixNano() + ignoreInterval
|
||||
if loadState == "not-found" {
|
||||
c.subStateSubscriber.ignore[path] = time.Now().UnixNano() + ignoreInterval
|
||||
}
|
||||
}
|
||||
|
||||
// without this, ignore would grow unboundedly over time
|
||||
func (c *Conn) cleanIgnore() {
|
||||
now := time.Now().UnixNano()
|
||||
if c.subscriber.cleanIgnore < now {
|
||||
c.subscriber.cleanIgnore = now + cleanIgnoreInterval
|
||||
if c.subStateSubscriber.cleanIgnore < now {
|
||||
c.subStateSubscriber.cleanIgnore = now + cleanIgnoreInterval
|
||||
|
||||
for p, t := range c.subscriber.ignore {
|
||||
for p, t := range c.subStateSubscriber.ignore {
|
||||
if t < now {
|
||||
delete(c.subscriber.ignore, p)
|
||||
delete(c.subStateSubscriber.ignore, p)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// PropertiesUpdate holds a map of a unit's changed properties
|
||||
type PropertiesUpdate struct {
|
||||
UnitName string
|
||||
Changed map[string]dbus.Variant
|
||||
}
|
||||
|
||||
// SetPropertiesSubscriber writes to updateCh when any unit's properties
|
||||
// change. Every property change reported by systemd will be sent; that is, no
|
||||
// transitions will be "missed" (as they might be with SetSubStateSubscriber).
|
||||
// However, state changes will only be written to the channel with non-blocking
|
||||
// writes. If updateCh is full, it attempts to write an error to errCh; if
|
||||
// errCh is full, the error passes silently.
|
||||
func (c *Conn) SetPropertiesSubscriber(updateCh chan<- *PropertiesUpdate, errCh chan<- error) {
|
||||
c.propertiesSubscriber.Lock()
|
||||
defer c.propertiesSubscriber.Unlock()
|
||||
c.propertiesSubscriber.updateCh = updateCh
|
||||
c.propertiesSubscriber.errCh = errCh
|
||||
}
|
||||
|
||||
// we don't need to worry about shouldIgnore() here because
|
||||
// sendPropertiesUpdate doesn't call GetProperties()
|
||||
func (c *Conn) sendPropertiesUpdate(unitPath dbus.ObjectPath, changedProps map[string]dbus.Variant) {
|
||||
c.propertiesSubscriber.Lock()
|
||||
defer c.propertiesSubscriber.Unlock()
|
||||
|
||||
if c.propertiesSubscriber.updateCh == nil {
|
||||
return
|
||||
}
|
||||
|
||||
update := &PropertiesUpdate{unitName(unitPath), changedProps}
|
||||
|
||||
select {
|
||||
case c.propertiesSubscriber.updateCh <- update:
|
||||
default:
|
||||
msg := "update channel is full"
|
||||
select {
|
||||
case c.propertiesSubscriber.errCh <- errors.New(msg):
|
||||
default:
|
||||
log.Printf("full error channel while reporting: %s\n", msg)
|
||||
}
|
||||
return
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,3 +1,5 @@
|
|||
[![Build Status](https://travis-ci.org/godbus/dbus.svg?branch=master)](https://travis-ci.org/godbus/dbus)
|
||||
|
||||
dbus
|
||||
----
|
||||
|
||||
|
@ -12,7 +14,7 @@ D-Bus message bus system.
|
|||
|
||||
### Installation
|
||||
|
||||
This packages requires Go 1.1. If you installed it and set up your GOPATH, just run:
|
||||
This packages requires Go 1.7. If you installed it and set up your GOPATH, just run:
|
||||
|
||||
```
|
||||
go get github.com/godbus/dbus
|
||||
|
@ -29,6 +31,7 @@ gives a short overview over the basic usage.
|
|||
|
||||
#### Projects using godbus
|
||||
- [notify](https://github.com/esiqveland/notify) provides desktop notifications over dbus into a library.
|
||||
- [go-bluetooth](https://github.com/muka/go-bluetooth) provides a bluetooth client over bluez dbus API.
|
||||
|
||||
Please note that the API is considered unstable for now and may change without
|
||||
further notice.
|
||||
|
|
|
@ -116,7 +116,6 @@ func (conn *Conn) Auth(methods []Auth) error {
|
|||
return err
|
||||
}
|
||||
go conn.inWorker()
|
||||
go conn.outWorker()
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,16 @@
|
|||
package dbus
|
||||
|
||||
// AuthAnonymous returns an Auth that uses the ANONYMOUS mechanism.
|
||||
func AuthAnonymous() Auth {
|
||||
return &authAnonymous{}
|
||||
}
|
||||
|
||||
type authAnonymous struct{}
|
||||
|
||||
func (a *authAnonymous) FirstData() (name, resp []byte, status AuthStatus) {
|
||||
return []byte("ANONYMOUS"), nil, AuthOk
|
||||
}
|
||||
|
||||
func (a *authAnonymous) HandleData(data []byte) (resp []byte, status AuthStatus) {
|
||||
return nil, AuthError
|
||||
}
|
|
@ -1,9 +1,12 @@
|
|||
package dbus
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
)
|
||||
|
||||
var errSignature = errors.New("dbus: mismatched signature")
|
||||
|
||||
// Call represents a pending or completed method call.
|
||||
type Call struct {
|
||||
Destination string
|
||||
|
@ -20,9 +23,25 @@ type Call struct {
|
|||
|
||||
// Holds the response once the call is done.
|
||||
Body []interface{}
|
||||
|
||||
// tracks context and canceler
|
||||
ctx context.Context
|
||||
ctxCanceler context.CancelFunc
|
||||
}
|
||||
|
||||
var errSignature = errors.New("dbus: mismatched signature")
|
||||
func (c *Call) Context() context.Context {
|
||||
if c.ctx == nil {
|
||||
return context.Background()
|
||||
}
|
||||
|
||||
return c.ctx
|
||||
}
|
||||
|
||||
func (c *Call) ContextCancel() {
|
||||
if c.ctxCanceler != nil {
|
||||
c.ctxCanceler()
|
||||
}
|
||||
}
|
||||
|
||||
// Store stores the body of the reply into the provided pointers. It returns
|
||||
// an error if the signatures of the body and retvalues don't match, or if
|
||||
|
@ -34,3 +53,8 @@ func (c *Call) Store(retvalues ...interface{}) error {
|
|||
|
||||
return Store(c.Body, retvalues...)
|
||||
}
|
||||
|
||||
func (c *Call) done() {
|
||||
c.Done <- c
|
||||
c.ContextCancel()
|
||||
}
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
package dbus
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"io"
|
||||
"os"
|
||||
|
@ -9,8 +10,6 @@ import (
|
|||
"sync"
|
||||
)
|
||||
|
||||
const defaultSystemBusAddress = "unix:path=/var/run/dbus/system_bus_socket"
|
||||
|
||||
var (
|
||||
systemBus *Conn
|
||||
systemBusLck sync.Mutex
|
||||
|
@ -36,25 +35,13 @@ type Conn struct {
|
|||
unixFD bool
|
||||
uuid string
|
||||
|
||||
names []string
|
||||
namesLck sync.RWMutex
|
||||
handler Handler
|
||||
signalHandler SignalHandler
|
||||
serialGen SerialGenerator
|
||||
|
||||
serialLck sync.Mutex
|
||||
nextSerial uint32
|
||||
serialUsed map[uint32]bool
|
||||
|
||||
calls map[uint32]*Call
|
||||
callsLck sync.RWMutex
|
||||
|
||||
handlers map[ObjectPath]map[string]exportWithMapping
|
||||
handlersLck sync.RWMutex
|
||||
|
||||
out chan *Message
|
||||
closed bool
|
||||
outLck sync.RWMutex
|
||||
|
||||
signals []chan<- *Signal
|
||||
signalsLck sync.Mutex
|
||||
names *nameTracker
|
||||
calls *callTracker
|
||||
outHandler *outputHandler
|
||||
|
||||
eavesdropped chan<- *Message
|
||||
eavesdroppedLck sync.Mutex
|
||||
|
@ -89,14 +76,32 @@ func SessionBus() (conn *Conn, err error) {
|
|||
return
|
||||
}
|
||||
|
||||
func getSessionBusAddress() (string, error) {
|
||||
if address := os.Getenv("DBUS_SESSION_BUS_ADDRESS"); address != "" && address != "autolaunch:" {
|
||||
return address, nil
|
||||
|
||||
} else if address := tryDiscoverDbusSessionBusAddress(); address != "" {
|
||||
os.Setenv("DBUS_SESSION_BUS_ADDRESS", address)
|
||||
return address, nil
|
||||
}
|
||||
return getSessionBusPlatformAddress()
|
||||
}
|
||||
|
||||
// SessionBusPrivate returns a new private connection to the session bus.
|
||||
func SessionBusPrivate() (*Conn, error) {
|
||||
address := os.Getenv("DBUS_SESSION_BUS_ADDRESS")
|
||||
if address != "" && address != "autolaunch:" {
|
||||
return Dial(address)
|
||||
func SessionBusPrivate(opts ...ConnOption) (*Conn, error) {
|
||||
address, err := getSessionBusAddress()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return sessionBusPlatform()
|
||||
return Dial(address, opts...)
|
||||
}
|
||||
|
||||
// SessionBusPrivate returns a new private connection to the session bus.
|
||||
//
|
||||
// Deprecated: use SessionBusPrivate with options instead.
|
||||
func SessionBusPrivateHandler(handler Handler, signalHandler SignalHandler) (*Conn, error) {
|
||||
return SessionBusPrivate(WithHandler(handler), WithSignalHandler(signalHandler))
|
||||
}
|
||||
|
||||
// SystemBus returns a shared connection to the system bus, connecting to it if
|
||||
|
@ -129,37 +134,93 @@ func SystemBus() (conn *Conn, err error) {
|
|||
}
|
||||
|
||||
// SystemBusPrivate returns a new private connection to the system bus.
|
||||
func SystemBusPrivate() (*Conn, error) {
|
||||
address := os.Getenv("DBUS_SYSTEM_BUS_ADDRESS")
|
||||
if address != "" {
|
||||
return Dial(address)
|
||||
}
|
||||
return Dial(defaultSystemBusAddress)
|
||||
func SystemBusPrivate(opts ...ConnOption) (*Conn, error) {
|
||||
return Dial(getSystemBusPlatformAddress(), opts...)
|
||||
}
|
||||
|
||||
// SystemBusPrivateHandler returns a new private connection to the system bus, using the provided handlers.
|
||||
//
|
||||
// Deprecated: use SystemBusPrivate with options instead.
|
||||
func SystemBusPrivateHandler(handler Handler, signalHandler SignalHandler) (*Conn, error) {
|
||||
return SystemBusPrivate(WithHandler(handler), WithSignalHandler(signalHandler))
|
||||
}
|
||||
|
||||
// Dial establishes a new private connection to the message bus specified by address.
|
||||
func Dial(address string) (*Conn, error) {
|
||||
func Dial(address string, opts ...ConnOption) (*Conn, error) {
|
||||
tr, err := getTransport(address)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return newConn(tr)
|
||||
return newConn(tr, opts...)
|
||||
}
|
||||
|
||||
// DialHandler establishes a new private connection to the message bus specified by address, using the supplied handlers.
|
||||
//
|
||||
// Deprecated: use Dial with options instead.
|
||||
func DialHandler(address string, handler Handler, signalHandler SignalHandler) (*Conn, error) {
|
||||
return Dial(address, WithSignalHandler(signalHandler))
|
||||
}
|
||||
|
||||
// ConnOption is a connection option.
|
||||
type ConnOption func(conn *Conn) error
|
||||
|
||||
// WithHandler overrides the default handler.
|
||||
func WithHandler(handler Handler) ConnOption {
|
||||
return func(conn *Conn) error {
|
||||
conn.handler = handler
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
// WithSignalHandler overrides the default signal handler.
|
||||
func WithSignalHandler(handler SignalHandler) ConnOption {
|
||||
return func(conn *Conn) error {
|
||||
conn.signalHandler = handler
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
// WithSerialGenerator overrides the default signals generator.
|
||||
func WithSerialGenerator(gen SerialGenerator) ConnOption {
|
||||
return func(conn *Conn) error {
|
||||
conn.serialGen = gen
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
// NewConn creates a new private *Conn from an already established connection.
|
||||
func NewConn(conn io.ReadWriteCloser) (*Conn, error) {
|
||||
return newConn(genericTransport{conn})
|
||||
func NewConn(conn io.ReadWriteCloser, opts ...ConnOption) (*Conn, error) {
|
||||
return newConn(genericTransport{conn}, opts...)
|
||||
}
|
||||
|
||||
// NewConnHandler creates a new private *Conn from an already established connection, using the supplied handlers.
|
||||
//
|
||||
// Deprecated: use NewConn with options instead.
|
||||
func NewConnHandler(conn io.ReadWriteCloser, handler Handler, signalHandler SignalHandler) (*Conn, error) {
|
||||
return NewConn(genericTransport{conn}, WithHandler(handler), WithSignalHandler(signalHandler))
|
||||
}
|
||||
|
||||
// newConn creates a new *Conn from a transport.
|
||||
func newConn(tr transport) (*Conn, error) {
|
||||
func newConn(tr transport, opts ...ConnOption) (*Conn, error) {
|
||||
conn := new(Conn)
|
||||
conn.transport = tr
|
||||
conn.calls = make(map[uint32]*Call)
|
||||
conn.out = make(chan *Message, 10)
|
||||
conn.handlers = make(map[ObjectPath]map[string]exportWithMapping)
|
||||
conn.nextSerial = 1
|
||||
conn.serialUsed = map[uint32]bool{0: true}
|
||||
for _, opt := range opts {
|
||||
if err := opt(conn); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
conn.calls = newCallTracker()
|
||||
if conn.handler == nil {
|
||||
conn.handler = NewDefaultHandler()
|
||||
}
|
||||
if conn.signalHandler == nil {
|
||||
conn.signalHandler = NewDefaultSignalHandler()
|
||||
}
|
||||
if conn.serialGen == nil {
|
||||
conn.serialGen = newSerialGenerator()
|
||||
}
|
||||
conn.outHandler = &outputHandler{conn: conn}
|
||||
conn.names = newNameTracker()
|
||||
conn.busObj = conn.Object("org.freedesktop.DBus", "/org/freedesktop/DBus")
|
||||
return conn, nil
|
||||
}
|
||||
|
@ -174,27 +235,21 @@ func (conn *Conn) BusObject() BusObject {
|
|||
// and the channels passed to Eavesdrop and Signal are closed. This method must
|
||||
// not be called on shared connections.
|
||||
func (conn *Conn) Close() error {
|
||||
conn.outLck.Lock()
|
||||
if conn.closed {
|
||||
// inWorker calls Close on read error, the read error may
|
||||
// be caused by another caller calling Close to shutdown the
|
||||
// dbus connection, a double-close scenario we prevent here.
|
||||
conn.outLck.Unlock()
|
||||
return nil
|
||||
conn.outHandler.close()
|
||||
if term, ok := conn.signalHandler.(Terminator); ok {
|
||||
term.Terminate()
|
||||
}
|
||||
close(conn.out)
|
||||
conn.closed = true
|
||||
conn.outLck.Unlock()
|
||||
conn.signalsLck.Lock()
|
||||
for _, ch := range conn.signals {
|
||||
close(ch)
|
||||
|
||||
if term, ok := conn.handler.(Terminator); ok {
|
||||
term.Terminate()
|
||||
}
|
||||
conn.signalsLck.Unlock()
|
||||
|
||||
conn.eavesdroppedLck.Lock()
|
||||
if conn.eavesdropped != nil {
|
||||
close(conn.eavesdropped)
|
||||
}
|
||||
conn.eavesdroppedLck.Unlock()
|
||||
|
||||
return conn.transport.Close()
|
||||
}
|
||||
|
||||
|
@ -212,17 +267,9 @@ func (conn *Conn) Eavesdrop(ch chan<- *Message) {
|
|||
conn.eavesdroppedLck.Unlock()
|
||||
}
|
||||
|
||||
// getSerial returns an unused serial.
|
||||
// GetSerial returns an unused serial.
|
||||
func (conn *Conn) getSerial() uint32 {
|
||||
conn.serialLck.Lock()
|
||||
defer conn.serialLck.Unlock()
|
||||
n := conn.nextSerial
|
||||
for conn.serialUsed[n] {
|
||||
n++
|
||||
}
|
||||
conn.serialUsed[n] = true
|
||||
conn.nextSerial = n + 1
|
||||
return n
|
||||
return conn.serialGen.GetSerial()
|
||||
}
|
||||
|
||||
// Hello sends the initial org.freedesktop.DBus.Hello call. This method must be
|
||||
|
@ -234,10 +281,7 @@ func (conn *Conn) Hello() error {
|
|||
if err != nil {
|
||||
return err
|
||||
}
|
||||
conn.namesLck.Lock()
|
||||
conn.names = make([]string, 1)
|
||||
conn.names[0] = s
|
||||
conn.namesLck.Unlock()
|
||||
conn.names.acquireUniqueConnectionName(s)
|
||||
return nil
|
||||
}
|
||||
|
||||
|
@ -246,132 +290,90 @@ func (conn *Conn) Hello() error {
|
|||
func (conn *Conn) inWorker() {
|
||||
for {
|
||||
msg, err := conn.ReadMessage()
|
||||
if err == nil {
|
||||
conn.eavesdroppedLck.Lock()
|
||||
if conn.eavesdropped != nil {
|
||||
select {
|
||||
case conn.eavesdropped <- msg:
|
||||
default:
|
||||
}
|
||||
conn.eavesdroppedLck.Unlock()
|
||||
continue
|
||||
if err != nil {
|
||||
if _, ok := err.(InvalidMessageError); !ok {
|
||||
// Some read error occured (usually EOF); we can't really do
|
||||
// anything but to shut down all stuff and returns errors to all
|
||||
// pending replies.
|
||||
conn.Close()
|
||||
conn.calls.finalizeAllWithError(err)
|
||||
return
|
||||
}
|
||||
// invalid messages are ignored
|
||||
continue
|
||||
}
|
||||
conn.eavesdroppedLck.Lock()
|
||||
if conn.eavesdropped != nil {
|
||||
select {
|
||||
case conn.eavesdropped <- msg:
|
||||
default:
|
||||
}
|
||||
conn.eavesdroppedLck.Unlock()
|
||||
dest, _ := msg.Headers[FieldDestination].value.(string)
|
||||
found := false
|
||||
if dest == "" {
|
||||
found = true
|
||||
} else {
|
||||
conn.namesLck.RLock()
|
||||
if len(conn.names) == 0 {
|
||||
found = true
|
||||
}
|
||||
for _, v := range conn.names {
|
||||
if dest == v {
|
||||
found = true
|
||||
break
|
||||
}
|
||||
}
|
||||
conn.namesLck.RUnlock()
|
||||
}
|
||||
if !found {
|
||||
// Eavesdropped a message, but no channel for it is registered.
|
||||
// Ignore it.
|
||||
continue
|
||||
}
|
||||
switch msg.Type {
|
||||
case TypeMethodReply, TypeError:
|
||||
serial := msg.Headers[FieldReplySerial].value.(uint32)
|
||||
conn.callsLck.Lock()
|
||||
if c, ok := conn.calls[serial]; ok {
|
||||
if msg.Type == TypeError {
|
||||
name, _ := msg.Headers[FieldErrorName].value.(string)
|
||||
c.Err = Error{name, msg.Body}
|
||||
} else {
|
||||
c.Body = msg.Body
|
||||
}
|
||||
c.Done <- c
|
||||
conn.serialLck.Lock()
|
||||
delete(conn.serialUsed, serial)
|
||||
conn.serialLck.Unlock()
|
||||
delete(conn.calls, serial)
|
||||
}
|
||||
conn.callsLck.Unlock()
|
||||
case TypeSignal:
|
||||
iface := msg.Headers[FieldInterface].value.(string)
|
||||
member := msg.Headers[FieldMember].value.(string)
|
||||
// as per http://dbus.freedesktop.org/doc/dbus-specification.html ,
|
||||
// sender is optional for signals.
|
||||
sender, _ := msg.Headers[FieldSender].value.(string)
|
||||
if iface == "org.freedesktop.DBus" && sender == "org.freedesktop.DBus" {
|
||||
if member == "NameLost" {
|
||||
// If we lost the name on the bus, remove it from our
|
||||
// tracking list.
|
||||
name, ok := msg.Body[0].(string)
|
||||
if !ok {
|
||||
panic("Unable to read the lost name")
|
||||
}
|
||||
conn.namesLck.Lock()
|
||||
for i, v := range conn.names {
|
||||
if v == name {
|
||||
conn.names = append(conn.names[:i],
|
||||
conn.names[i+1:]...)
|
||||
}
|
||||
}
|
||||
conn.namesLck.Unlock()
|
||||
} else if member == "NameAcquired" {
|
||||
// If we acquired the name on the bus, add it to our
|
||||
// tracking list.
|
||||
name, ok := msg.Body[0].(string)
|
||||
if !ok {
|
||||
panic("Unable to read the acquired name")
|
||||
}
|
||||
conn.namesLck.Lock()
|
||||
conn.names = append(conn.names, name)
|
||||
conn.namesLck.Unlock()
|
||||
}
|
||||
}
|
||||
signal := &Signal{
|
||||
Sender: sender,
|
||||
Path: msg.Headers[FieldPath].value.(ObjectPath),
|
||||
Name: iface + "." + member,
|
||||
Body: msg.Body,
|
||||
}
|
||||
conn.signalsLck.Lock()
|
||||
for _, ch := range conn.signals {
|
||||
ch <- signal
|
||||
}
|
||||
conn.signalsLck.Unlock()
|
||||
case TypeMethodCall:
|
||||
go conn.handleCall(msg)
|
||||
}
|
||||
} else if _, ok := err.(InvalidMessageError); !ok {
|
||||
// Some read error occured (usually EOF); we can't really do
|
||||
// anything but to shut down all stuff and returns errors to all
|
||||
// pending replies.
|
||||
conn.Close()
|
||||
conn.callsLck.RLock()
|
||||
for _, v := range conn.calls {
|
||||
v.Err = err
|
||||
v.Done <- v
|
||||
}
|
||||
conn.callsLck.RUnlock()
|
||||
return
|
||||
continue
|
||||
}
|
||||
// invalid messages are ignored
|
||||
conn.eavesdroppedLck.Unlock()
|
||||
dest, _ := msg.Headers[FieldDestination].value.(string)
|
||||
found := dest == "" ||
|
||||
!conn.names.uniqueNameIsKnown() ||
|
||||
conn.names.isKnownName(dest)
|
||||
if !found {
|
||||
// Eavesdropped a message, but no channel for it is registered.
|
||||
// Ignore it.
|
||||
continue
|
||||
}
|
||||
switch msg.Type {
|
||||
case TypeError:
|
||||
conn.serialGen.RetireSerial(conn.calls.handleDBusError(msg))
|
||||
case TypeMethodReply:
|
||||
conn.serialGen.RetireSerial(conn.calls.handleReply(msg))
|
||||
case TypeSignal:
|
||||
conn.handleSignal(msg)
|
||||
case TypeMethodCall:
|
||||
go conn.handleCall(msg)
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
func (conn *Conn) handleSignal(msg *Message) {
|
||||
iface := msg.Headers[FieldInterface].value.(string)
|
||||
member := msg.Headers[FieldMember].value.(string)
|
||||
// as per http://dbus.freedesktop.org/doc/dbus-specification.html ,
|
||||
// sender is optional for signals.
|
||||
sender, _ := msg.Headers[FieldSender].value.(string)
|
||||
if iface == "org.freedesktop.DBus" && sender == "org.freedesktop.DBus" {
|
||||
if member == "NameLost" {
|
||||
// If we lost the name on the bus, remove it from our
|
||||
// tracking list.
|
||||
name, ok := msg.Body[0].(string)
|
||||
if !ok {
|
||||
panic("Unable to read the lost name")
|
||||
}
|
||||
conn.names.loseName(name)
|
||||
} else if member == "NameAcquired" {
|
||||
// If we acquired the name on the bus, add it to our
|
||||
// tracking list.
|
||||
name, ok := msg.Body[0].(string)
|
||||
if !ok {
|
||||
panic("Unable to read the acquired name")
|
||||
}
|
||||
conn.names.acquireName(name)
|
||||
}
|
||||
}
|
||||
signal := &Signal{
|
||||
Sender: sender,
|
||||
Path: msg.Headers[FieldPath].value.(ObjectPath),
|
||||
Name: iface + "." + member,
|
||||
Body: msg.Body,
|
||||
}
|
||||
conn.signalHandler.DeliverSignal(iface, member, signal)
|
||||
}
|
||||
|
||||
// Names returns the list of all names that are currently owned by this
|
||||
// connection. The slice is always at least one element long, the first element
|
||||
// being the unique name of the connection.
|
||||
func (conn *Conn) Names() []string {
|
||||
conn.namesLck.RLock()
|
||||
// copy the slice so it can't be modified
|
||||
s := make([]string, len(conn.names))
|
||||
copy(s, conn.names)
|
||||
conn.namesLck.RUnlock()
|
||||
return s
|
||||
return conn.names.listKnownNames()
|
||||
}
|
||||
|
||||
// Object returns the object identified by the given destination name and path.
|
||||
|
@ -381,24 +383,17 @@ func (conn *Conn) Object(dest string, path ObjectPath) BusObject {
|
|||
|
||||
// outWorker runs in an own goroutine, encoding and sending messages that are
|
||||
// sent to conn.out.
|
||||
func (conn *Conn) outWorker() {
|
||||
for msg := range conn.out {
|
||||
err := conn.SendMessage(msg)
|
||||
conn.callsLck.RLock()
|
||||
if err != nil {
|
||||
if c := conn.calls[msg.serial]; c != nil {
|
||||
c.Err = err
|
||||
c.Done <- c
|
||||
}
|
||||
conn.serialLck.Lock()
|
||||
delete(conn.serialUsed, msg.serial)
|
||||
conn.serialLck.Unlock()
|
||||
} else if msg.Type != TypeMethodCall {
|
||||
conn.serialLck.Lock()
|
||||
delete(conn.serialUsed, msg.serial)
|
||||
conn.serialLck.Unlock()
|
||||
}
|
||||
conn.callsLck.RUnlock()
|
||||
func (conn *Conn) sendMessage(msg *Message) {
|
||||
conn.sendMessageAndIfClosed(msg, func() {})
|
||||
}
|
||||
|
||||
func (conn *Conn) sendMessageAndIfClosed(msg *Message, ifClosed func()) {
|
||||
err := conn.outHandler.sendAndIfClosed(msg, ifClosed)
|
||||
conn.calls.handleSendError(msg, err)
|
||||
if err != nil {
|
||||
conn.serialGen.RetireSerial(msg.serial)
|
||||
} else if msg.Type != TypeMethodCall {
|
||||
conn.serialGen.RetireSerial(msg.serial)
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -409,8 +404,21 @@ func (conn *Conn) outWorker() {
|
|||
// once the call is complete. Otherwise, ch is ignored and a Call structure is
|
||||
// returned of which only the Err member is valid.
|
||||
func (conn *Conn) Send(msg *Message, ch chan *Call) *Call {
|
||||
var call *Call
|
||||
return conn.send(context.Background(), msg, ch)
|
||||
}
|
||||
|
||||
// SendWithContext acts like Send but takes a context
|
||||
func (conn *Conn) SendWithContext(ctx context.Context, msg *Message, ch chan *Call) *Call {
|
||||
return conn.send(ctx, msg, ch)
|
||||
}
|
||||
|
||||
func (conn *Conn) send(ctx context.Context, msg *Message, ch chan *Call) *Call {
|
||||
if ctx == nil {
|
||||
panic("nil context")
|
||||
}
|
||||
|
||||
var call *Call
|
||||
ctx, canceler := context.WithCancel(ctx)
|
||||
msg.serial = conn.getSerial()
|
||||
if msg.Type == TypeMethodCall && msg.Flags&FlagNoReplyExpected == 0 {
|
||||
if ch == nil {
|
||||
|
@ -426,33 +434,42 @@ func (conn *Conn) Send(msg *Message, ch chan *Call) *Call {
|
|||
call.Method = iface + "." + member
|
||||
call.Args = msg.Body
|
||||
call.Done = ch
|
||||
conn.callsLck.Lock()
|
||||
conn.calls[msg.serial] = call
|
||||
conn.callsLck.Unlock()
|
||||
conn.outLck.RLock()
|
||||
if conn.closed {
|
||||
call.Err = ErrClosed
|
||||
call.Done <- call
|
||||
} else {
|
||||
conn.out <- msg
|
||||
}
|
||||
conn.outLck.RUnlock()
|
||||
call.ctx = ctx
|
||||
call.ctxCanceler = canceler
|
||||
conn.calls.track(msg.serial, call)
|
||||
go func() {
|
||||
<-ctx.Done()
|
||||
conn.calls.handleSendError(msg, ctx.Err())
|
||||
}()
|
||||
conn.sendMessageAndIfClosed(msg, func() {
|
||||
conn.calls.handleSendError(msg, ErrClosed)
|
||||
canceler()
|
||||
})
|
||||
} else {
|
||||
conn.outLck.RLock()
|
||||
if conn.closed {
|
||||
canceler()
|
||||
call = &Call{Err: nil}
|
||||
conn.sendMessageAndIfClosed(msg, func() {
|
||||
call = &Call{Err: ErrClosed}
|
||||
} else {
|
||||
conn.out <- msg
|
||||
call = &Call{Err: nil}
|
||||
}
|
||||
conn.outLck.RUnlock()
|
||||
})
|
||||
}
|
||||
return call
|
||||
}
|
||||
|
||||
// sendError creates an error message corresponding to the parameters and sends
|
||||
// it to conn.out.
|
||||
func (conn *Conn) sendError(e Error, dest string, serial uint32) {
|
||||
func (conn *Conn) sendError(err error, dest string, serial uint32) {
|
||||
var e *Error
|
||||
switch em := err.(type) {
|
||||
case Error:
|
||||
e = &em
|
||||
case *Error:
|
||||
e = em
|
||||
case DBusError:
|
||||
name, body := em.DBusError()
|
||||
e = NewError(name, body)
|
||||
default:
|
||||
e = MakeFailedError(err)
|
||||
}
|
||||
msg := new(Message)
|
||||
msg.Type = TypeError
|
||||
msg.serial = conn.getSerial()
|
||||
|
@ -466,11 +483,7 @@ func (conn *Conn) sendError(e Error, dest string, serial uint32) {
|
|||
if len(e.Body) > 0 {
|
||||
msg.Headers[FieldSignature] = MakeVariant(SignatureOf(e.Body...))
|
||||
}
|
||||
conn.outLck.RLock()
|
||||
if !conn.closed {
|
||||
conn.out <- msg
|
||||
}
|
||||
conn.outLck.RUnlock()
|
||||
conn.sendMessage(msg)
|
||||
}
|
||||
|
||||
// sendReply creates a method reply message corresponding to the parameters and
|
||||
|
@ -488,28 +501,33 @@ func (conn *Conn) sendReply(dest string, serial uint32, values ...interface{}) {
|
|||
if len(values) > 0 {
|
||||
msg.Headers[FieldSignature] = MakeVariant(SignatureOf(values...))
|
||||
}
|
||||
conn.outLck.RLock()
|
||||
if !conn.closed {
|
||||
conn.out <- msg
|
||||
conn.sendMessage(msg)
|
||||
}
|
||||
|
||||
func (conn *Conn) defaultSignalAction(fn func(h *defaultSignalHandler, ch chan<- *Signal), ch chan<- *Signal) {
|
||||
if !isDefaultSignalHandler(conn.signalHandler) {
|
||||
return
|
||||
}
|
||||
conn.outLck.RUnlock()
|
||||
handler := conn.signalHandler.(*defaultSignalHandler)
|
||||
fn(handler, ch)
|
||||
}
|
||||
|
||||
// Signal registers the given channel to be passed all received signal messages.
|
||||
// The caller has to make sure that ch is sufficiently buffered; if a message
|
||||
// arrives when a write to c is not possible, it is discarded.
|
||||
//
|
||||
// Multiple of these channels can be registered at the same time. Passing a
|
||||
// channel that already is registered will remove it from the list of the
|
||||
// registered channels.
|
||||
// Multiple of these channels can be registered at the same time.
|
||||
//
|
||||
// These channels are "overwritten" by Eavesdrop; i.e., if there currently is a
|
||||
// channel for eavesdropped messages, this channel receives all signals, and
|
||||
// none of the channels passed to Signal will receive any signals.
|
||||
func (conn *Conn) Signal(ch chan<- *Signal) {
|
||||
conn.signalsLck.Lock()
|
||||
conn.signals = append(conn.signals, ch)
|
||||
conn.signalsLck.Unlock()
|
||||
conn.defaultSignalAction((*defaultSignalHandler).addSignal, ch)
|
||||
}
|
||||
|
||||
// RemoveSignal removes the given channel from the list of the registered channels.
|
||||
func (conn *Conn) RemoveSignal(ch chan<- *Signal) {
|
||||
conn.defaultSignalAction((*defaultSignalHandler).removeSignal, ch)
|
||||
}
|
||||
|
||||
// SupportsUnixFDs returns whether the underlying transport supports passing of
|
||||
|
@ -610,16 +628,220 @@ func dereferenceAll(vs []interface{}) []interface{} {
|
|||
|
||||
// getKey gets a key from a the list of keys. Returns "" on error / not found...
|
||||
func getKey(s, key string) string {
|
||||
i := strings.Index(s, key)
|
||||
if i == -1 {
|
||||
return ""
|
||||
for _, keyEqualsValue := range strings.Split(s, ",") {
|
||||
keyValue := strings.SplitN(keyEqualsValue, "=", 2)
|
||||
if len(keyValue) == 2 && keyValue[0] == key {
|
||||
return keyValue[1]
|
||||
}
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
type outputHandler struct {
|
||||
conn *Conn
|
||||
sendLck sync.Mutex
|
||||
closed struct {
|
||||
isClosed bool
|
||||
lck sync.RWMutex
|
||||
}
|
||||
}
|
||||
|
||||
func (h *outputHandler) sendAndIfClosed(msg *Message, ifClosed func()) error {
|
||||
h.closed.lck.RLock()
|
||||
defer h.closed.lck.RUnlock()
|
||||
if h.closed.isClosed {
|
||||
ifClosed()
|
||||
return nil
|
||||
}
|
||||
h.sendLck.Lock()
|
||||
defer h.sendLck.Unlock()
|
||||
return h.conn.SendMessage(msg)
|
||||
}
|
||||
|
||||
func (h *outputHandler) close() {
|
||||
h.closed.lck.Lock()
|
||||
defer h.closed.lck.Unlock()
|
||||
h.closed.isClosed = true
|
||||
}
|
||||
|
||||
type serialGenerator struct {
|
||||
lck sync.Mutex
|
||||
nextSerial uint32
|
||||
serialUsed map[uint32]bool
|
||||
}
|
||||
|
||||
func newSerialGenerator() *serialGenerator {
|
||||
return &serialGenerator{
|
||||
serialUsed: map[uint32]bool{0: true},
|
||||
nextSerial: 1,
|
||||
}
|
||||
}
|
||||
|
||||
func (gen *serialGenerator) GetSerial() uint32 {
|
||||
gen.lck.Lock()
|
||||
defer gen.lck.Unlock()
|
||||
n := gen.nextSerial
|
||||
for gen.serialUsed[n] {
|
||||
n++
|
||||
}
|
||||
gen.serialUsed[n] = true
|
||||
gen.nextSerial = n + 1
|
||||
return n
|
||||
}
|
||||
|
||||
func (gen *serialGenerator) RetireSerial(serial uint32) {
|
||||
gen.lck.Lock()
|
||||
defer gen.lck.Unlock()
|
||||
delete(gen.serialUsed, serial)
|
||||
}
|
||||
|
||||
type nameTracker struct {
|
||||
lck sync.RWMutex
|
||||
unique string
|
||||
names map[string]struct{}
|
||||
}
|
||||
|
||||
func newNameTracker() *nameTracker {
|
||||
return &nameTracker{names: map[string]struct{}{}}
|
||||
}
|
||||
func (tracker *nameTracker) acquireUniqueConnectionName(name string) {
|
||||
tracker.lck.Lock()
|
||||
defer tracker.lck.Unlock()
|
||||
tracker.unique = name
|
||||
}
|
||||
func (tracker *nameTracker) acquireName(name string) {
|
||||
tracker.lck.Lock()
|
||||
defer tracker.lck.Unlock()
|
||||
tracker.names[name] = struct{}{}
|
||||
}
|
||||
func (tracker *nameTracker) loseName(name string) {
|
||||
tracker.lck.Lock()
|
||||
defer tracker.lck.Unlock()
|
||||
delete(tracker.names, name)
|
||||
}
|
||||
|
||||
func (tracker *nameTracker) uniqueNameIsKnown() bool {
|
||||
tracker.lck.RLock()
|
||||
defer tracker.lck.RUnlock()
|
||||
return tracker.unique != ""
|
||||
}
|
||||
func (tracker *nameTracker) isKnownName(name string) bool {
|
||||
tracker.lck.RLock()
|
||||
defer tracker.lck.RUnlock()
|
||||
_, ok := tracker.names[name]
|
||||
return ok || name == tracker.unique
|
||||
}
|
||||
func (tracker *nameTracker) listKnownNames() []string {
|
||||
tracker.lck.RLock()
|
||||
defer tracker.lck.RUnlock()
|
||||
out := make([]string, 0, len(tracker.names)+1)
|
||||
out = append(out, tracker.unique)
|
||||
for k := range tracker.names {
|
||||
out = append(out, k)
|
||||
}
|
||||
return out
|
||||
}
|
||||
|
||||
type callTracker struct {
|
||||
calls map[uint32]*Call
|
||||
lck sync.RWMutex
|
||||
}
|
||||
|
||||
func newCallTracker() *callTracker {
|
||||
return &callTracker{calls: map[uint32]*Call{}}
|
||||
}
|
||||
|
||||
func (tracker *callTracker) track(sn uint32, call *Call) {
|
||||
tracker.lck.Lock()
|
||||
tracker.calls[sn] = call
|
||||
tracker.lck.Unlock()
|
||||
}
|
||||
|
||||
func (tracker *callTracker) handleReply(msg *Message) uint32 {
|
||||
serial := msg.Headers[FieldReplySerial].value.(uint32)
|
||||
tracker.lck.RLock()
|
||||
_, ok := tracker.calls[serial]
|
||||
tracker.lck.RUnlock()
|
||||
if ok {
|
||||
tracker.finalizeWithBody(serial, msg.Body)
|
||||
}
|
||||
return serial
|
||||
}
|
||||
|
||||
func (tracker *callTracker) handleDBusError(msg *Message) uint32 {
|
||||
serial := msg.Headers[FieldReplySerial].value.(uint32)
|
||||
tracker.lck.RLock()
|
||||
_, ok := tracker.calls[serial]
|
||||
tracker.lck.RUnlock()
|
||||
if ok {
|
||||
name, _ := msg.Headers[FieldErrorName].value.(string)
|
||||
tracker.finalizeWithError(serial, Error{name, msg.Body})
|
||||
}
|
||||
return serial
|
||||
}
|
||||
|
||||
func (tracker *callTracker) handleSendError(msg *Message, err error) {
|
||||
if err == nil {
|
||||
return
|
||||
}
|
||||
tracker.lck.RLock()
|
||||
_, ok := tracker.calls[msg.serial]
|
||||
tracker.lck.RUnlock()
|
||||
if ok {
|
||||
tracker.finalizeWithError(msg.serial, err)
|
||||
}
|
||||
}
|
||||
|
||||
// finalize was the only func that did not strobe Done
|
||||
func (tracker *callTracker) finalize(sn uint32) {
|
||||
tracker.lck.Lock()
|
||||
defer tracker.lck.Unlock()
|
||||
c, ok := tracker.calls[sn]
|
||||
if ok {
|
||||
delete(tracker.calls, sn)
|
||||
c.ContextCancel()
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func (tracker *callTracker) finalizeWithBody(sn uint32, body []interface{}) {
|
||||
tracker.lck.Lock()
|
||||
c, ok := tracker.calls[sn]
|
||||
if ok {
|
||||
delete(tracker.calls, sn)
|
||||
}
|
||||
tracker.lck.Unlock()
|
||||
if ok {
|
||||
c.Body = body
|
||||
c.done()
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func (tracker *callTracker) finalizeWithError(sn uint32, err error) {
|
||||
tracker.lck.Lock()
|
||||
c, ok := tracker.calls[sn]
|
||||
if ok {
|
||||
delete(tracker.calls, sn)
|
||||
}
|
||||
tracker.lck.Unlock()
|
||||
if ok {
|
||||
c.Err = err
|
||||
c.done()
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func (tracker *callTracker) finalizeAllWithError(err error) {
|
||||
tracker.lck.Lock()
|
||||
closedCalls := make([]*Call, 0, len(tracker.calls))
|
||||
for sn := range tracker.calls {
|
||||
closedCalls = append(closedCalls, tracker.calls[sn])
|
||||
}
|
||||
tracker.calls = map[uint32]*Call{}
|
||||
tracker.lck.Unlock()
|
||||
for _, call := range closedCalls {
|
||||
call.Err = err
|
||||
call.done()
|
||||
}
|
||||
if i+len(key)+1 >= len(s) || s[i+len(key)] != '=' {
|
||||
return ""
|
||||
}
|
||||
j := strings.Index(s, ",")
|
||||
if j == -1 {
|
||||
j = len(s)
|
||||
}
|
||||
return s[i+len(key)+1 : j]
|
||||
}
|
||||
|
|
|
@ -2,20 +2,36 @@ package dbus
|
|||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"os"
|
||||
"os/exec"
|
||||
)
|
||||
|
||||
func sessionBusPlatform() (*Conn, error) {
|
||||
const defaultSystemBusAddress = "unix:path=/opt/local/var/run/dbus/system_bus_socket"
|
||||
|
||||
func getSessionBusPlatformAddress() (string, error) {
|
||||
cmd := exec.Command("launchctl", "getenv", "DBUS_LAUNCHD_SESSION_BUS_SOCKET")
|
||||
b, err := cmd.CombinedOutput()
|
||||
|
||||
if err != nil {
|
||||
return nil, err
|
||||
return "", err
|
||||
}
|
||||
|
||||
if len(b) == 0 {
|
||||
return nil, errors.New("dbus: couldn't determine address of session bus")
|
||||
return "", errors.New("dbus: couldn't determine address of session bus")
|
||||
}
|
||||
|
||||
return Dial("unix:path=" + string(b[:len(b)-1]))
|
||||
return "unix:path=" + string(b[:len(b)-1]), nil
|
||||
}
|
||||
|
||||
func getSystemBusPlatformAddress() string {
|
||||
address := os.Getenv("DBUS_LAUNCHD_SESSION_BUS_SOCKET")
|
||||
if address != "" {
|
||||
return fmt.Sprintf("unix:path=%s", address)
|
||||
}
|
||||
return defaultSystemBusAddress
|
||||
}
|
||||
|
||||
func tryDiscoverDbusSessionBusAddress() string {
|
||||
return ""
|
||||
}
|
||||
|
|
|
@ -5,23 +5,87 @@ package dbus
|
|||
import (
|
||||
"bytes"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"os/exec"
|
||||
"os/user"
|
||||
"path"
|
||||
"strings"
|
||||
)
|
||||
|
||||
func sessionBusPlatform() (*Conn, error) {
|
||||
func getSessionBusPlatformAddress() (string, error) {
|
||||
cmd := exec.Command("dbus-launch")
|
||||
b, err := cmd.CombinedOutput()
|
||||
|
||||
if err != nil {
|
||||
return nil, err
|
||||
return "", err
|
||||
}
|
||||
|
||||
i := bytes.IndexByte(b, '=')
|
||||
j := bytes.IndexByte(b, '\n')
|
||||
|
||||
if i == -1 || j == -1 {
|
||||
return nil, errors.New("dbus: couldn't determine address of session bus")
|
||||
return "", errors.New("dbus: couldn't determine address of session bus")
|
||||
}
|
||||
|
||||
return Dial(string(b[i+1 : j]))
|
||||
env, addr := string(b[0:i]), string(b[i+1:j])
|
||||
os.Setenv(env, addr)
|
||||
|
||||
return addr, nil
|
||||
}
|
||||
|
||||
// tryDiscoverDbusSessionBusAddress tries to discover an existing dbus session
|
||||
// and return the value of its DBUS_SESSION_BUS_ADDRESS.
|
||||
// It tries different techniques employed by different operating systems,
|
||||
// returning the first valid address it finds, or an empty string.
|
||||
//
|
||||
// * /run/user/<uid>/bus if this exists, it *is* the bus socket. present on
|
||||
// Ubuntu 18.04
|
||||
// * /run/user/<uid>/dbus-session: if this exists, it can be parsed for the bus
|
||||
// address. present on Ubuntu 16.04
|
||||
//
|
||||
// See https://dbus.freedesktop.org/doc/dbus-launch.1.html
|
||||
func tryDiscoverDbusSessionBusAddress() string {
|
||||
if runtimeDirectory, err := getRuntimeDirectory(); err == nil {
|
||||
|
||||
if runUserBusFile := path.Join(runtimeDirectory, "bus"); fileExists(runUserBusFile) {
|
||||
// if /run/user/<uid>/bus exists, that file itself
|
||||
// *is* the unix socket, so return its path
|
||||
return fmt.Sprintf("unix:path=%s", runUserBusFile)
|
||||
}
|
||||
if runUserSessionDbusFile := path.Join(runtimeDirectory, "dbus-session"); fileExists(runUserSessionDbusFile) {
|
||||
// if /run/user/<uid>/dbus-session exists, it's a
|
||||
// text file // containing the address of the socket, e.g.:
|
||||
// DBUS_SESSION_BUS_ADDRESS=unix:abstract=/tmp/dbus-E1c73yNqrG
|
||||
|
||||
if f, err := ioutil.ReadFile(runUserSessionDbusFile); err == nil {
|
||||
fileContent := string(f)
|
||||
|
||||
prefix := "DBUS_SESSION_BUS_ADDRESS="
|
||||
|
||||
if strings.HasPrefix(fileContent, prefix) {
|
||||
address := strings.TrimRight(strings.TrimPrefix(fileContent, prefix), "\n\r")
|
||||
return address
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
func getRuntimeDirectory() (string, error) {
|
||||
if currentUser, err := user.Current(); err != nil {
|
||||
return "", err
|
||||
} else {
|
||||
return fmt.Sprintf("/run/user/%s", currentUser.Uid), nil
|
||||
}
|
||||
}
|
||||
|
||||
func fileExists(filename string) bool {
|
||||
if _, err := os.Stat(filename); !os.IsNotExist(err) {
|
||||
return true
|
||||
} else {
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,18 @@
|
|||
//+build !windows,!solaris,!darwin
|
||||
|
||||
package dbus
|
||||
|
||||
import (
|
||||
"os"
|
||||
"fmt"
|
||||
)
|
||||
|
||||
const defaultSystemBusAddress = "unix:path=/var/run/dbus/system_bus_socket"
|
||||
|
||||
func getSystemBusPlatformAddress() string {
|
||||
address := os.Getenv("DBUS_SYSTEM_BUS_ADDRESS")
|
||||
if address != "" {
|
||||
return fmt.Sprintf("unix:path=%s", address)
|
||||
}
|
||||
return defaultSystemBusAddress
|
||||
}
|
|
@ -0,0 +1,15 @@
|
|||
//+build windows
|
||||
|
||||
package dbus
|
||||
|
||||
import "os"
|
||||
|
||||
const defaultSystemBusAddress = "tcp:host=127.0.0.1,port=12434"
|
||||
|
||||
func getSystemBusPlatformAddress() string {
|
||||
address := os.Getenv("DBUS_SYSTEM_BUS_ADDRESS")
|
||||
if address != "" {
|
||||
return address
|
||||
}
|
||||
return defaultSystemBusAddress
|
||||
}
|
|
@ -2,6 +2,7 @@ package dbus
|
|||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"reflect"
|
||||
"strings"
|
||||
)
|
||||
|
@ -12,6 +13,8 @@ var (
|
|||
uint8Type = reflect.TypeOf(uint8(0))
|
||||
int16Type = reflect.TypeOf(int16(0))
|
||||
uint16Type = reflect.TypeOf(uint16(0))
|
||||
intType = reflect.TypeOf(int(0))
|
||||
uintType = reflect.TypeOf(uint(0))
|
||||
int32Type = reflect.TypeOf(int32(0))
|
||||
uint32Type = reflect.TypeOf(uint32(0))
|
||||
int64Type = reflect.TypeOf(int64(0))
|
||||
|
@ -22,6 +25,7 @@ var (
|
|||
objectPathType = reflect.TypeOf(ObjectPath(""))
|
||||
variantType = reflect.TypeOf(Variant{Signature{""}, nil})
|
||||
interfacesType = reflect.TypeOf([]interface{}{})
|
||||
interfaceType = reflect.TypeOf((*interface{})(nil)).Elem()
|
||||
unixFDType = reflect.TypeOf(UnixFD(0))
|
||||
unixFDIndexType = reflect.TypeOf(UnixFDIndex(0))
|
||||
)
|
||||
|
@ -46,86 +50,251 @@ func Store(src []interface{}, dest ...interface{}) error {
|
|||
}
|
||||
|
||||
for i := range src {
|
||||
if err := store(src[i], dest[i]); err != nil {
|
||||
if err := storeInterfaces(src[i], dest[i]); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func store(src, dest interface{}) error {
|
||||
if reflect.TypeOf(dest).Elem() == reflect.TypeOf(src) {
|
||||
reflect.ValueOf(dest).Elem().Set(reflect.ValueOf(src))
|
||||
return nil
|
||||
} else if hasStruct(dest) {
|
||||
rv := reflect.ValueOf(dest).Elem()
|
||||
switch rv.Kind() {
|
||||
case reflect.Struct:
|
||||
vs, ok := src.([]interface{})
|
||||
if !ok {
|
||||
return errors.New("dbus.Store: type mismatch")
|
||||
}
|
||||
t := rv.Type()
|
||||
ndest := make([]interface{}, 0, rv.NumField())
|
||||
for i := 0; i < rv.NumField(); i++ {
|
||||
field := t.Field(i)
|
||||
if field.PkgPath == "" && field.Tag.Get("dbus") != "-" {
|
||||
ndest = append(ndest, rv.Field(i).Addr().Interface())
|
||||
}
|
||||
}
|
||||
if len(vs) != len(ndest) {
|
||||
return errors.New("dbus.Store: type mismatch")
|
||||
}
|
||||
err := Store(vs, ndest...)
|
||||
if err != nil {
|
||||
return errors.New("dbus.Store: type mismatch")
|
||||
}
|
||||
case reflect.Slice:
|
||||
sv := reflect.ValueOf(src)
|
||||
if sv.Kind() != reflect.Slice {
|
||||
return errors.New("dbus.Store: type mismatch")
|
||||
}
|
||||
rv.Set(reflect.MakeSlice(rv.Type(), sv.Len(), sv.Len()))
|
||||
for i := 0; i < sv.Len(); i++ {
|
||||
if err := store(sv.Index(i).Interface(), rv.Index(i).Addr().Interface()); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
case reflect.Map:
|
||||
sv := reflect.ValueOf(src)
|
||||
if sv.Kind() != reflect.Map {
|
||||
return errors.New("dbus.Store: type mismatch")
|
||||
}
|
||||
keys := sv.MapKeys()
|
||||
rv.Set(reflect.MakeMap(sv.Type()))
|
||||
for _, key := range keys {
|
||||
v := reflect.New(sv.Type().Elem())
|
||||
if err := store(v, sv.MapIndex(key).Interface()); err != nil {
|
||||
return err
|
||||
}
|
||||
rv.SetMapIndex(key, v.Elem())
|
||||
}
|
||||
default:
|
||||
return errors.New("dbus.Store: type mismatch")
|
||||
}
|
||||
return nil
|
||||
} else {
|
||||
return errors.New("dbus.Store: type mismatch")
|
||||
func storeInterfaces(src, dest interface{}) error {
|
||||
return store(reflect.ValueOf(dest), reflect.ValueOf(src))
|
||||
}
|
||||
|
||||
func store(dest, src reflect.Value) error {
|
||||
if dest.Kind() == reflect.Ptr {
|
||||
return store(dest.Elem(), src)
|
||||
}
|
||||
switch src.Kind() {
|
||||
case reflect.Slice:
|
||||
return storeSlice(dest, src)
|
||||
case reflect.Map:
|
||||
return storeMap(dest, src)
|
||||
default:
|
||||
return storeBase(dest, src)
|
||||
}
|
||||
}
|
||||
|
||||
func hasStruct(v interface{}) bool {
|
||||
t := reflect.TypeOf(v)
|
||||
for {
|
||||
switch t.Kind() {
|
||||
case reflect.Struct:
|
||||
return true
|
||||
case reflect.Slice, reflect.Ptr, reflect.Map:
|
||||
t = t.Elem()
|
||||
default:
|
||||
return false
|
||||
func storeBase(dest, src reflect.Value) error {
|
||||
return setDest(dest, src)
|
||||
}
|
||||
|
||||
func setDest(dest, src reflect.Value) error {
|
||||
if !isVariant(src.Type()) && isVariant(dest.Type()) {
|
||||
//special conversion for dbus.Variant
|
||||
dest.Set(reflect.ValueOf(MakeVariant(src.Interface())))
|
||||
return nil
|
||||
}
|
||||
if isVariant(src.Type()) && !isVariant(dest.Type()) {
|
||||
src = getVariantValue(src)
|
||||
}
|
||||
if !src.Type().ConvertibleTo(dest.Type()) {
|
||||
return fmt.Errorf(
|
||||
"dbus.Store: type mismatch: cannot convert %s to %s",
|
||||
src.Type(), dest.Type())
|
||||
}
|
||||
dest.Set(src.Convert(dest.Type()))
|
||||
return nil
|
||||
}
|
||||
|
||||
func kindsAreCompatible(dest, src reflect.Type) bool {
|
||||
switch {
|
||||
case isVariant(dest):
|
||||
return true
|
||||
case dest.Kind() == reflect.Interface:
|
||||
return true
|
||||
default:
|
||||
return dest.Kind() == src.Kind()
|
||||
}
|
||||
}
|
||||
|
||||
func isConvertibleTo(dest, src reflect.Type) bool {
|
||||
switch {
|
||||
case isVariant(dest):
|
||||
return true
|
||||
case dest.Kind() == reflect.Interface:
|
||||
return true
|
||||
case dest.Kind() == reflect.Slice:
|
||||
return src.Kind() == reflect.Slice &&
|
||||
isConvertibleTo(dest.Elem(), src.Elem())
|
||||
case dest.Kind() == reflect.Struct:
|
||||
return src == interfacesType
|
||||
default:
|
||||
return src.ConvertibleTo(dest)
|
||||
}
|
||||
}
|
||||
|
||||
func storeMap(dest, src reflect.Value) error {
|
||||
switch {
|
||||
case !kindsAreCompatible(dest.Type(), src.Type()):
|
||||
return fmt.Errorf(
|
||||
"dbus.Store: type mismatch: "+
|
||||
"map: cannot store a value of %s into %s",
|
||||
src.Type(), dest.Type())
|
||||
case isVariant(dest.Type()):
|
||||
return storeMapIntoVariant(dest, src)
|
||||
case dest.Kind() == reflect.Interface:
|
||||
return storeMapIntoInterface(dest, src)
|
||||
case isConvertibleTo(dest.Type().Key(), src.Type().Key()) &&
|
||||
isConvertibleTo(dest.Type().Elem(), src.Type().Elem()):
|
||||
return storeMapIntoMap(dest, src)
|
||||
default:
|
||||
return fmt.Errorf(
|
||||
"dbus.Store: type mismatch: "+
|
||||
"map: cannot convert a value of %s into %s",
|
||||
src.Type(), dest.Type())
|
||||
}
|
||||
}
|
||||
|
||||
func storeMapIntoVariant(dest, src reflect.Value) error {
|
||||
dv := reflect.MakeMap(src.Type())
|
||||
err := store(dv, src)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return storeBase(dest, dv)
|
||||
}
|
||||
|
||||
func storeMapIntoInterface(dest, src reflect.Value) error {
|
||||
var dv reflect.Value
|
||||
if isVariant(src.Type().Elem()) {
|
||||
//Convert variants to interface{} recursively when converting
|
||||
//to interface{}
|
||||
dv = reflect.MakeMap(
|
||||
reflect.MapOf(src.Type().Key(), interfaceType))
|
||||
} else {
|
||||
dv = reflect.MakeMap(src.Type())
|
||||
}
|
||||
err := store(dv, src)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return storeBase(dest, dv)
|
||||
}
|
||||
|
||||
func storeMapIntoMap(dest, src reflect.Value) error {
|
||||
if dest.IsNil() {
|
||||
dest.Set(reflect.MakeMap(dest.Type()))
|
||||
}
|
||||
keys := src.MapKeys()
|
||||
for _, key := range keys {
|
||||
dkey := key.Convert(dest.Type().Key())
|
||||
dval := reflect.New(dest.Type().Elem()).Elem()
|
||||
err := store(dval, getVariantValue(src.MapIndex(key)))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
dest.SetMapIndex(dkey, dval)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func storeSlice(dest, src reflect.Value) error {
|
||||
switch {
|
||||
case src.Type() == interfacesType && dest.Kind() == reflect.Struct:
|
||||
//The decoder always decodes structs as slices of interface{}
|
||||
return storeStruct(dest, src)
|
||||
case !kindsAreCompatible(dest.Type(), src.Type()):
|
||||
return fmt.Errorf(
|
||||
"dbus.Store: type mismatch: "+
|
||||
"slice: cannot store a value of %s into %s",
|
||||
src.Type(), dest.Type())
|
||||
case isVariant(dest.Type()):
|
||||
return storeSliceIntoVariant(dest, src)
|
||||
case dest.Kind() == reflect.Interface:
|
||||
return storeSliceIntoInterface(dest, src)
|
||||
case isConvertibleTo(dest.Type().Elem(), src.Type().Elem()):
|
||||
return storeSliceIntoSlice(dest, src)
|
||||
default:
|
||||
return fmt.Errorf(
|
||||
"dbus.Store: type mismatch: "+
|
||||
"slice: cannot convert a value of %s into %s",
|
||||
src.Type(), dest.Type())
|
||||
}
|
||||
}
|
||||
|
||||
func storeStruct(dest, src reflect.Value) error {
|
||||
if isVariant(dest.Type()) {
|
||||
return storeBase(dest, src)
|
||||
}
|
||||
dval := make([]interface{}, 0, dest.NumField())
|
||||
dtype := dest.Type()
|
||||
for i := 0; i < dest.NumField(); i++ {
|
||||
field := dest.Field(i)
|
||||
ftype := dtype.Field(i)
|
||||
if ftype.PkgPath != "" {
|
||||
continue
|
||||
}
|
||||
if ftype.Tag.Get("dbus") == "-" {
|
||||
continue
|
||||
}
|
||||
dval = append(dval, field.Addr().Interface())
|
||||
}
|
||||
if src.Len() != len(dval) {
|
||||
return fmt.Errorf(
|
||||
"dbus.Store: type mismatch: "+
|
||||
"destination struct does not have "+
|
||||
"enough fields need: %d have: %d",
|
||||
src.Len(), len(dval))
|
||||
}
|
||||
return Store(src.Interface().([]interface{}), dval...)
|
||||
}
|
||||
|
||||
func storeSliceIntoVariant(dest, src reflect.Value) error {
|
||||
dv := reflect.MakeSlice(src.Type(), src.Len(), src.Cap())
|
||||
err := store(dv, src)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return storeBase(dest, dv)
|
||||
}
|
||||
|
||||
func storeSliceIntoInterface(dest, src reflect.Value) error {
|
||||
var dv reflect.Value
|
||||
if isVariant(src.Type().Elem()) {
|
||||
//Convert variants to interface{} recursively when converting
|
||||
//to interface{}
|
||||
dv = reflect.MakeSlice(reflect.SliceOf(interfaceType),
|
||||
src.Len(), src.Cap())
|
||||
} else {
|
||||
dv = reflect.MakeSlice(src.Type(), src.Len(), src.Cap())
|
||||
}
|
||||
err := store(dv, src)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return storeBase(dest, dv)
|
||||
}
|
||||
|
||||
func storeSliceIntoSlice(dest, src reflect.Value) error {
|
||||
if dest.IsNil() || dest.Len() < src.Len() {
|
||||
dest.Set(reflect.MakeSlice(dest.Type(), src.Len(), src.Cap()))
|
||||
}
|
||||
if dest.Len() != src.Len() {
|
||||
return fmt.Errorf(
|
||||
"dbus.Store: type mismatch: "+
|
||||
"slices are different lengths "+
|
||||
"need: %d have: %d",
|
||||
src.Len(), dest.Len())
|
||||
}
|
||||
for i := 0; i < src.Len(); i++ {
|
||||
err := store(dest.Index(i), getVariantValue(src.Index(i)))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func getVariantValue(in reflect.Value) reflect.Value {
|
||||
if isVariant(in.Type()) {
|
||||
return reflect.ValueOf(in.Interface().(Variant).Value())
|
||||
}
|
||||
return in
|
||||
}
|
||||
|
||||
func isVariant(t reflect.Type) bool {
|
||||
return t == variantType
|
||||
}
|
||||
|
||||
// An ObjectPath is an object path as defined by the D-Bus spec.
|
||||
|
@ -177,15 +346,15 @@ func alignment(t reflect.Type) int {
|
|||
return 4
|
||||
case signatureType:
|
||||
return 1
|
||||
case interfacesType: // sometimes used for structs
|
||||
return 8
|
||||
case interfacesType:
|
||||
return 4
|
||||
}
|
||||
switch t.Kind() {
|
||||
case reflect.Uint8:
|
||||
return 1
|
||||
case reflect.Uint16, reflect.Int16:
|
||||
return 2
|
||||
case reflect.Uint32, reflect.Int32, reflect.String, reflect.Array, reflect.Slice, reflect.Map:
|
||||
case reflect.Uint, reflect.Int, reflect.Uint32, reflect.Int32, reflect.String, reflect.Array, reflect.Slice, reflect.Map:
|
||||
return 4
|
||||
case reflect.Uint64, reflect.Int64, reflect.Float64, reflect.Struct:
|
||||
return 8
|
||||
|
@ -200,7 +369,7 @@ func isKeyType(t reflect.Type) bool {
|
|||
switch t.Kind() {
|
||||
case reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64,
|
||||
reflect.Int16, reflect.Int32, reflect.Int64, reflect.Float64,
|
||||
reflect.String:
|
||||
reflect.String, reflect.Uint, reflect.Int:
|
||||
|
||||
return true
|
||||
}
|
||||
|
|
|
@ -191,7 +191,14 @@ func (dec *decoder) decode(s string, depth int) interface{} {
|
|||
length := dec.decode("u", depth).(uint32)
|
||||
v := reflect.MakeSlice(reflect.SliceOf(typeFor(s[1:])), 0, int(length))
|
||||
// Even for empty arrays, the correct padding must be included
|
||||
dec.align(alignment(typeFor(s[1:])))
|
||||
align := alignment(typeFor(s[1:]))
|
||||
if len(s) > 1 && s[1] == '(' {
|
||||
//Special case for arrays of structs
|
||||
//structs decode as a slice of interface{} values
|
||||
//but the dbus alignment does not match this
|
||||
align = 8
|
||||
}
|
||||
dec.align(align)
|
||||
spos := dec.pos
|
||||
for dec.pos < spos+int(length) {
|
||||
ev := dec.decode(s[1:], depth+1)
|
||||
|
|
|
@ -0,0 +1,321 @@
|
|||
package dbus
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"reflect"
|
||||
"strings"
|
||||
"sync"
|
||||
)
|
||||
|
||||
func newIntrospectIntf(h *defaultHandler) *exportedIntf {
|
||||
methods := make(map[string]Method)
|
||||
methods["Introspect"] = exportedMethod{
|
||||
reflect.ValueOf(func(msg Message) (string, *Error) {
|
||||
path := msg.Headers[FieldPath].value.(ObjectPath)
|
||||
return h.introspectPath(path), nil
|
||||
}),
|
||||
}
|
||||
return newExportedIntf(methods, true)
|
||||
}
|
||||
|
||||
//NewDefaultHandler returns an instance of the default
|
||||
//call handler. This is useful if you want to implement only
|
||||
//one of the two handlers but not both.
|
||||
//
|
||||
// Deprecated: this is the default value, don't use it, it will be unexported.
|
||||
func NewDefaultHandler() *defaultHandler {
|
||||
h := &defaultHandler{
|
||||
objects: make(map[ObjectPath]*exportedObj),
|
||||
defaultIntf: make(map[string]*exportedIntf),
|
||||
}
|
||||
h.defaultIntf["org.freedesktop.DBus.Introspectable"] = newIntrospectIntf(h)
|
||||
return h
|
||||
}
|
||||
|
||||
type defaultHandler struct {
|
||||
sync.RWMutex
|
||||
objects map[ObjectPath]*exportedObj
|
||||
defaultIntf map[string]*exportedIntf
|
||||
}
|
||||
|
||||
func (h *defaultHandler) PathExists(path ObjectPath) bool {
|
||||
_, ok := h.objects[path]
|
||||
return ok
|
||||
}
|
||||
|
||||
func (h *defaultHandler) introspectPath(path ObjectPath) string {
|
||||
subpath := make(map[string]struct{})
|
||||
var xml bytes.Buffer
|
||||
xml.WriteString("<node>")
|
||||
for obj, _ := range h.objects {
|
||||
p := string(path)
|
||||
if p != "/" {
|
||||
p += "/"
|
||||
}
|
||||
if strings.HasPrefix(string(obj), p) {
|
||||
node_name := strings.Split(string(obj[len(p):]), "/")[0]
|
||||
subpath[node_name] = struct{}{}
|
||||
}
|
||||
}
|
||||
for s, _ := range subpath {
|
||||
xml.WriteString("\n\t<node name=\"" + s + "\"/>")
|
||||
}
|
||||
xml.WriteString("\n</node>")
|
||||
return xml.String()
|
||||
}
|
||||
|
||||
func (h *defaultHandler) LookupObject(path ObjectPath) (ServerObject, bool) {
|
||||
h.RLock()
|
||||
defer h.RUnlock()
|
||||
object, ok := h.objects[path]
|
||||
if ok {
|
||||
return object, ok
|
||||
}
|
||||
|
||||
// If an object wasn't found for this exact path,
|
||||
// look for a matching subtree registration
|
||||
subtreeObject := newExportedObject()
|
||||
path = path[:strings.LastIndex(string(path), "/")]
|
||||
for len(path) > 0 {
|
||||
object, ok = h.objects[path]
|
||||
if ok {
|
||||
for name, iface := range object.interfaces {
|
||||
// Only include this handler if it registered for the subtree
|
||||
if iface.isFallbackInterface() {
|
||||
subtreeObject.interfaces[name] = iface
|
||||
}
|
||||
}
|
||||
break
|
||||
}
|
||||
|
||||
path = path[:strings.LastIndex(string(path), "/")]
|
||||
}
|
||||
|
||||
for name, intf := range h.defaultIntf {
|
||||
if _, exists := subtreeObject.interfaces[name]; exists {
|
||||
continue
|
||||
}
|
||||
subtreeObject.interfaces[name] = intf
|
||||
}
|
||||
|
||||
return subtreeObject, true
|
||||
}
|
||||
|
||||
func (h *defaultHandler) AddObject(path ObjectPath, object *exportedObj) {
|
||||
h.Lock()
|
||||
h.objects[path] = object
|
||||
h.Unlock()
|
||||
}
|
||||
|
||||
func (h *defaultHandler) DeleteObject(path ObjectPath) {
|
||||
h.Lock()
|
||||
delete(h.objects, path)
|
||||
h.Unlock()
|
||||
}
|
||||
|
||||
type exportedMethod struct {
|
||||
reflect.Value
|
||||
}
|
||||
|
||||
func (m exportedMethod) Call(args ...interface{}) ([]interface{}, error) {
|
||||
t := m.Type()
|
||||
|
||||
params := make([]reflect.Value, len(args))
|
||||
for i := 0; i < len(args); i++ {
|
||||
params[i] = reflect.ValueOf(args[i]).Elem()
|
||||
}
|
||||
|
||||
ret := m.Value.Call(params)
|
||||
|
||||
err := ret[t.NumOut()-1].Interface().(*Error)
|
||||
ret = ret[:t.NumOut()-1]
|
||||
out := make([]interface{}, len(ret))
|
||||
for i, val := range ret {
|
||||
out[i] = val.Interface()
|
||||
}
|
||||
if err == nil {
|
||||
//concrete type to interface nil is a special case
|
||||
return out, nil
|
||||
}
|
||||
return out, err
|
||||
}
|
||||
|
||||
func (m exportedMethod) NumArguments() int {
|
||||
return m.Value.Type().NumIn()
|
||||
}
|
||||
|
||||
func (m exportedMethod) ArgumentValue(i int) interface{} {
|
||||
return reflect.Zero(m.Type().In(i)).Interface()
|
||||
}
|
||||
|
||||
func (m exportedMethod) NumReturns() int {
|
||||
return m.Value.Type().NumOut()
|
||||
}
|
||||
|
||||
func (m exportedMethod) ReturnValue(i int) interface{} {
|
||||
return reflect.Zero(m.Type().Out(i)).Interface()
|
||||
}
|
||||
|
||||
func newExportedObject() *exportedObj {
|
||||
return &exportedObj{
|
||||
interfaces: make(map[string]*exportedIntf),
|
||||
}
|
||||
}
|
||||
|
||||
type exportedObj struct {
|
||||
mu sync.RWMutex
|
||||
interfaces map[string]*exportedIntf
|
||||
}
|
||||
|
||||
func (obj *exportedObj) LookupInterface(name string) (Interface, bool) {
|
||||
if name == "" {
|
||||
return obj, true
|
||||
}
|
||||
obj.mu.RLock()
|
||||
defer obj.mu.RUnlock()
|
||||
intf, exists := obj.interfaces[name]
|
||||
return intf, exists
|
||||
}
|
||||
|
||||
func (obj *exportedObj) AddInterface(name string, iface *exportedIntf) {
|
||||
obj.mu.Lock()
|
||||
defer obj.mu.Unlock()
|
||||
obj.interfaces[name] = iface
|
||||
}
|
||||
|
||||
func (obj *exportedObj) DeleteInterface(name string) {
|
||||
obj.mu.Lock()
|
||||
defer obj.mu.Unlock()
|
||||
delete(obj.interfaces, name)
|
||||
}
|
||||
|
||||
func (obj *exportedObj) LookupMethod(name string) (Method, bool) {
|
||||
obj.mu.RLock()
|
||||
defer obj.mu.RUnlock()
|
||||
for _, intf := range obj.interfaces {
|
||||
method, exists := intf.LookupMethod(name)
|
||||
if exists {
|
||||
return method, exists
|
||||
}
|
||||
}
|
||||
return nil, false
|
||||
}
|
||||
|
||||
func (obj *exportedObj) isFallbackInterface() bool {
|
||||
return false
|
||||
}
|
||||
|
||||
func newExportedIntf(methods map[string]Method, includeSubtree bool) *exportedIntf {
|
||||
return &exportedIntf{
|
||||
methods: methods,
|
||||
includeSubtree: includeSubtree,
|
||||
}
|
||||
}
|
||||
|
||||
type exportedIntf struct {
|
||||
methods map[string]Method
|
||||
|
||||
// Whether or not this export is for the entire subtree
|
||||
includeSubtree bool
|
||||
}
|
||||
|
||||
func (obj *exportedIntf) LookupMethod(name string) (Method, bool) {
|
||||
out, exists := obj.methods[name]
|
||||
return out, exists
|
||||
}
|
||||
|
||||
func (obj *exportedIntf) isFallbackInterface() bool {
|
||||
return obj.includeSubtree
|
||||
}
|
||||
|
||||
//NewDefaultSignalHandler returns an instance of the default
|
||||
//signal handler. This is useful if you want to implement only
|
||||
//one of the two handlers but not both.
|
||||
//
|
||||
// Deprecated: this is the default value, don't use it, it will be unexported.
|
||||
func NewDefaultSignalHandler() *defaultSignalHandler {
|
||||
return &defaultSignalHandler{
|
||||
closeChan: make(chan struct{}),
|
||||
}
|
||||
}
|
||||
|
||||
func isDefaultSignalHandler(handler SignalHandler) bool {
|
||||
_, ok := handler.(*defaultSignalHandler)
|
||||
return ok
|
||||
}
|
||||
|
||||
type defaultSignalHandler struct {
|
||||
sync.RWMutex
|
||||
closed bool
|
||||
signals []chan<- *Signal
|
||||
closeChan chan struct{}
|
||||
}
|
||||
|
||||
func (sh *defaultSignalHandler) DeliverSignal(intf, name string, signal *Signal) {
|
||||
sh.RLock()
|
||||
defer sh.RUnlock()
|
||||
if sh.closed {
|
||||
return
|
||||
}
|
||||
for _, ch := range sh.signals {
|
||||
select {
|
||||
case ch <- signal:
|
||||
case <-sh.closeChan:
|
||||
return
|
||||
default:
|
||||
go func() {
|
||||
select {
|
||||
case ch <- signal:
|
||||
case <-sh.closeChan:
|
||||
return
|
||||
}
|
||||
}()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (sh *defaultSignalHandler) Init() error {
|
||||
sh.Lock()
|
||||
sh.signals = make([]chan<- *Signal, 0)
|
||||
sh.closeChan = make(chan struct{})
|
||||
sh.Unlock()
|
||||
return nil
|
||||
}
|
||||
|
||||
func (sh *defaultSignalHandler) Terminate() {
|
||||
sh.Lock()
|
||||
if !sh.closed {
|
||||
close(sh.closeChan)
|
||||
}
|
||||
sh.closed = true
|
||||
for _, ch := range sh.signals {
|
||||
close(ch)
|
||||
}
|
||||
sh.signals = nil
|
||||
sh.Unlock()
|
||||
}
|
||||
|
||||
func (sh *defaultSignalHandler) addSignal(ch chan<- *Signal) {
|
||||
sh.Lock()
|
||||
defer sh.Unlock()
|
||||
if sh.closed {
|
||||
return
|
||||
}
|
||||
sh.signals = append(sh.signals, ch)
|
||||
|
||||
}
|
||||
|
||||
func (sh *defaultSignalHandler) removeSignal(ch chan<- *Signal) {
|
||||
sh.Lock()
|
||||
defer sh.Unlock()
|
||||
if sh.closed {
|
||||
return
|
||||
}
|
||||
for i := len(sh.signals) - 1; i >= 0; i-- {
|
||||
if ch == sh.signals[i] {
|
||||
copy(sh.signals[i:], sh.signals[i+1:])
|
||||
sh.signals[len(sh.signals)-1] = nil
|
||||
sh.signals = sh.signals[:len(sh.signals)-1]
|
||||
}
|
||||
}
|
||||
}
|
|
@ -19,6 +19,8 @@ respective D-Bus equivalents:
|
|||
bool | BOOLEAN
|
||||
int16 | INT16
|
||||
uint16 | UINT16
|
||||
int | INT32
|
||||
uint | UINT32
|
||||
int32 | INT32
|
||||
uint32 | UINT32
|
||||
int64 | INT64
|
||||
|
@ -28,6 +30,7 @@ respective D-Bus equivalents:
|
|||
ObjectPath | OBJECT_PATH
|
||||
Signature | SIGNATURE
|
||||
Variant | VARIANT
|
||||
interface{} | VARIANT
|
||||
UnixFDIndex | UNIX_FD
|
||||
|
||||
Slices and arrays encode as ARRAYs of their element type.
|
||||
|
@ -41,6 +44,9 @@ be skipped.
|
|||
|
||||
Pointers encode as the value they're pointed to.
|
||||
|
||||
Types convertible to one of the base types above will be mapped as the
|
||||
base type.
|
||||
|
||||
Trying to encode any other type or a slice, map or struct containing an
|
||||
unsupported type will result in an InvalidTypeError.
|
||||
|
||||
|
|
|
@ -96,10 +96,10 @@ func (enc *encoder) encode(v reflect.Value, depth int) {
|
|||
case reflect.Uint16:
|
||||
enc.binwrite(uint16(v.Uint()))
|
||||
enc.pos += 2
|
||||
case reflect.Int32:
|
||||
case reflect.Int, reflect.Int32:
|
||||
enc.binwrite(int32(v.Int()))
|
||||
enc.pos += 4
|
||||
case reflect.Uint32:
|
||||
case reflect.Uint, reflect.Uint32:
|
||||
enc.binwrite(uint32(v.Uint()))
|
||||
enc.pos += 4
|
||||
case reflect.Int64:
|
||||
|
@ -202,6 +202,8 @@ func (enc *encoder) encode(v reflect.Value, depth int) {
|
|||
panic(err)
|
||||
}
|
||||
enc.pos += length
|
||||
case reflect.Interface:
|
||||
enc.encode(reflect.ValueOf(MakeVariant(v.Interface())), depth)
|
||||
default:
|
||||
panic(InvalidTypeError{v.Type()})
|
||||
}
|
||||
|
|
|
@ -8,167 +8,73 @@ import (
|
|||
)
|
||||
|
||||
var (
|
||||
errmsgInvalidArg = Error{
|
||||
ErrMsgInvalidArg = Error{
|
||||
"org.freedesktop.DBus.Error.InvalidArgs",
|
||||
[]interface{}{"Invalid type / number of args"},
|
||||
}
|
||||
errmsgNoObject = Error{
|
||||
ErrMsgNoObject = Error{
|
||||
"org.freedesktop.DBus.Error.NoSuchObject",
|
||||
[]interface{}{"No such object"},
|
||||
}
|
||||
errmsgUnknownMethod = Error{
|
||||
ErrMsgUnknownMethod = Error{
|
||||
"org.freedesktop.DBus.Error.UnknownMethod",
|
||||
[]interface{}{"Unknown / invalid method"},
|
||||
}
|
||||
ErrMsgUnknownInterface = Error{
|
||||
"org.freedesktop.DBus.Error.UnknownInterface",
|
||||
[]interface{}{"Object does not implement the interface"},
|
||||
}
|
||||
)
|
||||
|
||||
// exportWithMapping represents an exported struct along with a method name
|
||||
// mapping to allow for exporting lower-case methods, etc.
|
||||
type exportWithMapping struct {
|
||||
export interface{}
|
||||
|
||||
// Method name mapping; key -> struct method, value -> dbus method.
|
||||
mapping map[string]string
|
||||
|
||||
// Whether or not this export is for the entire subtree
|
||||
includeSubtree bool
|
||||
func MakeFailedError(err error) *Error {
|
||||
return &Error{
|
||||
"org.freedesktop.DBus.Error.Failed",
|
||||
[]interface{}{err.Error()},
|
||||
}
|
||||
}
|
||||
|
||||
// Sender is a type which can be used in exported methods to receive the message
|
||||
// sender.
|
||||
type Sender string
|
||||
|
||||
func exportedMethod(export exportWithMapping, name string) reflect.Value {
|
||||
if export.export == nil {
|
||||
return reflect.Value{}
|
||||
}
|
||||
|
||||
// If a mapping was included in the export, check the map to see if we
|
||||
// should be looking for a different method in the export.
|
||||
if export.mapping != nil {
|
||||
for key, value := range export.mapping {
|
||||
if value == name {
|
||||
name = key
|
||||
break
|
||||
}
|
||||
|
||||
// Catch the case where a method is aliased but the client is calling
|
||||
// the original, e.g. the "Foo" method was exported mapped to
|
||||
// "foo," and dbus client called the original "Foo."
|
||||
if key == name {
|
||||
return reflect.Value{}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
value := reflect.ValueOf(export.export)
|
||||
m := value.MethodByName(name)
|
||||
|
||||
// Catch the case of attempting to call an unexported method
|
||||
method, ok := value.Type().MethodByName(name)
|
||||
|
||||
if !m.IsValid() || !ok || method.PkgPath != "" {
|
||||
return reflect.Value{}
|
||||
}
|
||||
t := m.Type()
|
||||
if t.NumOut() == 0 ||
|
||||
t.Out(t.NumOut()-1) != reflect.TypeOf(&errmsgInvalidArg) {
|
||||
|
||||
return reflect.Value{}
|
||||
}
|
||||
return m
|
||||
}
|
||||
|
||||
// searchHandlers will look through all registered handlers looking for one
|
||||
// to handle the given path. If a verbatim one isn't found, it will check for
|
||||
// a subtree registration for the path as well.
|
||||
func (conn *Conn) searchHandlers(path ObjectPath) (map[string]exportWithMapping, bool) {
|
||||
conn.handlersLck.RLock()
|
||||
defer conn.handlersLck.RUnlock()
|
||||
|
||||
handlers, ok := conn.handlers[path]
|
||||
func computeMethodName(name string, mapping map[string]string) string {
|
||||
newname, ok := mapping[name]
|
||||
if ok {
|
||||
return handlers, ok
|
||||
name = newname
|
||||
}
|
||||
|
||||
// If handlers weren't found for this exact path, look for a matching subtree
|
||||
// registration
|
||||
handlers = make(map[string]exportWithMapping)
|
||||
path = path[:strings.LastIndex(string(path), "/")]
|
||||
for len(path) > 0 {
|
||||
var subtreeHandlers map[string]exportWithMapping
|
||||
subtreeHandlers, ok = conn.handlers[path]
|
||||
if ok {
|
||||
for iface, handler := range subtreeHandlers {
|
||||
// Only include this handler if it registered for the subtree
|
||||
if handler.includeSubtree {
|
||||
handlers[iface] = handler
|
||||
}
|
||||
}
|
||||
|
||||
break
|
||||
}
|
||||
|
||||
path = path[:strings.LastIndex(string(path), "/")]
|
||||
}
|
||||
|
||||
return handlers, ok
|
||||
return name
|
||||
}
|
||||
|
||||
// handleCall handles the given method call (i.e. looks if it's one of the
|
||||
// pre-implemented ones and searches for a corresponding handler if not).
|
||||
func (conn *Conn) handleCall(msg *Message) {
|
||||
name := msg.Headers[FieldMember].value.(string)
|
||||
path := msg.Headers[FieldPath].value.(ObjectPath)
|
||||
ifaceName, hasIface := msg.Headers[FieldInterface].value.(string)
|
||||
sender, hasSender := msg.Headers[FieldSender].value.(string)
|
||||
serial := msg.serial
|
||||
if ifaceName == "org.freedesktop.DBus.Peer" {
|
||||
switch name {
|
||||
case "Ping":
|
||||
conn.sendReply(sender, serial)
|
||||
case "GetMachineId":
|
||||
conn.sendReply(sender, serial, conn.uuid)
|
||||
default:
|
||||
conn.sendError(errmsgUnknownMethod, sender, serial)
|
||||
func getMethods(in interface{}, mapping map[string]string) map[string]reflect.Value {
|
||||
if in == nil {
|
||||
return nil
|
||||
}
|
||||
methods := make(map[string]reflect.Value)
|
||||
val := reflect.ValueOf(in)
|
||||
typ := val.Type()
|
||||
for i := 0; i < typ.NumMethod(); i++ {
|
||||
methtype := typ.Method(i)
|
||||
method := val.Method(i)
|
||||
t := method.Type()
|
||||
// only track valid methods must return *Error as last arg
|
||||
// and must be exported
|
||||
if t.NumOut() == 0 ||
|
||||
t.Out(t.NumOut()-1) != reflect.TypeOf(&ErrMsgInvalidArg) ||
|
||||
methtype.PkgPath != "" {
|
||||
continue
|
||||
}
|
||||
return
|
||||
}
|
||||
if len(name) == 0 {
|
||||
conn.sendError(errmsgUnknownMethod, sender, serial)
|
||||
// map names while building table
|
||||
methods[computeMethodName(methtype.Name, mapping)] = method
|
||||
}
|
||||
return methods
|
||||
}
|
||||
|
||||
// Find the exported handler (if any) for this path
|
||||
handlers, ok := conn.searchHandlers(path)
|
||||
if !ok {
|
||||
conn.sendError(errmsgNoObject, sender, serial)
|
||||
return
|
||||
}
|
||||
func standardMethodArgumentDecode(m Method, sender string, msg *Message, body []interface{}) ([]interface{}, error) {
|
||||
pointers := make([]interface{}, m.NumArguments())
|
||||
decode := make([]interface{}, 0, len(body))
|
||||
|
||||
var m reflect.Value
|
||||
if hasIface {
|
||||
iface := handlers[ifaceName]
|
||||
m = exportedMethod(iface, name)
|
||||
} else {
|
||||
for _, v := range handlers {
|
||||
m = exportedMethod(v, name)
|
||||
if m.IsValid() {
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if !m.IsValid() {
|
||||
conn.sendError(errmsgUnknownMethod, sender, serial)
|
||||
return
|
||||
}
|
||||
|
||||
t := m.Type()
|
||||
vs := msg.Body
|
||||
pointers := make([]interface{}, t.NumIn())
|
||||
decode := make([]interface{}, 0, len(vs))
|
||||
for i := 0; i < t.NumIn(); i++ {
|
||||
tp := t.In(i)
|
||||
for i := 0; i < m.NumArguments(); i++ {
|
||||
tp := reflect.TypeOf(m.ArgumentValue(i))
|
||||
val := reflect.New(tp)
|
||||
pointers[i] = val.Interface()
|
||||
if tp == reflect.TypeOf((*Sender)(nil)).Elem() {
|
||||
|
@ -180,26 +86,73 @@ func (conn *Conn) handleCall(msg *Message) {
|
|||
}
|
||||
}
|
||||
|
||||
if len(decode) != len(vs) {
|
||||
conn.sendError(errmsgInvalidArg, sender, serial)
|
||||
if len(decode) != len(body) {
|
||||
return nil, ErrMsgInvalidArg
|
||||
}
|
||||
|
||||
if err := Store(body, decode...); err != nil {
|
||||
return nil, ErrMsgInvalidArg
|
||||
}
|
||||
|
||||
return pointers, nil
|
||||
}
|
||||
|
||||
func (conn *Conn) decodeArguments(m Method, sender string, msg *Message) ([]interface{}, error) {
|
||||
if decoder, ok := m.(ArgumentDecoder); ok {
|
||||
return decoder.DecodeArguments(conn, sender, msg, msg.Body)
|
||||
}
|
||||
return standardMethodArgumentDecode(m, sender, msg, msg.Body)
|
||||
}
|
||||
|
||||
// handleCall handles the given method call (i.e. looks if it's one of the
|
||||
// pre-implemented ones and searches for a corresponding handler if not).
|
||||
func (conn *Conn) handleCall(msg *Message) {
|
||||
name := msg.Headers[FieldMember].value.(string)
|
||||
path := msg.Headers[FieldPath].value.(ObjectPath)
|
||||
ifaceName, _ := msg.Headers[FieldInterface].value.(string)
|
||||
sender, hasSender := msg.Headers[FieldSender].value.(string)
|
||||
serial := msg.serial
|
||||
if ifaceName == "org.freedesktop.DBus.Peer" {
|
||||
switch name {
|
||||
case "Ping":
|
||||
conn.sendReply(sender, serial)
|
||||
case "GetMachineId":
|
||||
conn.sendReply(sender, serial, conn.uuid)
|
||||
default:
|
||||
conn.sendError(ErrMsgUnknownMethod, sender, serial)
|
||||
}
|
||||
return
|
||||
}
|
||||
if len(name) == 0 {
|
||||
conn.sendError(ErrMsgUnknownMethod, sender, serial)
|
||||
}
|
||||
|
||||
object, ok := conn.handler.LookupObject(path)
|
||||
if !ok {
|
||||
conn.sendError(ErrMsgNoObject, sender, serial)
|
||||
return
|
||||
}
|
||||
|
||||
if err := Store(vs, decode...); err != nil {
|
||||
conn.sendError(errmsgInvalidArg, sender, serial)
|
||||
iface, exists := object.LookupInterface(ifaceName)
|
||||
if !exists {
|
||||
conn.sendError(ErrMsgUnknownInterface, sender, serial)
|
||||
return
|
||||
}
|
||||
|
||||
// Extract parameters
|
||||
params := make([]reflect.Value, len(pointers))
|
||||
for i := 0; i < len(pointers); i++ {
|
||||
params[i] = reflect.ValueOf(pointers[i]).Elem()
|
||||
m, exists := iface.LookupMethod(name)
|
||||
if !exists {
|
||||
conn.sendError(ErrMsgUnknownMethod, sender, serial)
|
||||
return
|
||||
}
|
||||
args, err := conn.decodeArguments(m, sender, msg)
|
||||
if err != nil {
|
||||
conn.sendError(err, sender, serial)
|
||||
return
|
||||
}
|
||||
|
||||
// Call method
|
||||
ret := m.Call(params)
|
||||
if em := ret[t.NumOut()-1].Interface().(*Error); em != nil {
|
||||
conn.sendError(*em, sender, serial)
|
||||
ret, err := m.Call(args...)
|
||||
if err != nil {
|
||||
conn.sendError(err, sender, serial)
|
||||
return
|
||||
}
|
||||
|
||||
|
@ -212,18 +165,13 @@ func (conn *Conn) handleCall(msg *Message) {
|
|||
reply.Headers[FieldDestination] = msg.Headers[FieldSender]
|
||||
}
|
||||
reply.Headers[FieldReplySerial] = MakeVariant(msg.serial)
|
||||
reply.Body = make([]interface{}, len(ret)-1)
|
||||
for i := 0; i < len(ret)-1; i++ {
|
||||
reply.Body[i] = ret[i].Interface()
|
||||
reply.Body = make([]interface{}, len(ret))
|
||||
for i := 0; i < len(ret); i++ {
|
||||
reply.Body[i] = ret[i]
|
||||
}
|
||||
if len(ret) != 1 {
|
||||
reply.Headers[FieldSignature] = MakeVariant(SignatureOf(reply.Body...))
|
||||
}
|
||||
conn.outLck.RLock()
|
||||
if !conn.closed {
|
||||
conn.out <- reply
|
||||
}
|
||||
conn.outLck.RUnlock()
|
||||
reply.Headers[FieldSignature] = MakeVariant(SignatureOf(reply.Body...))
|
||||
|
||||
conn.sendMessage(reply)
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -256,12 +204,14 @@ func (conn *Conn) Emit(path ObjectPath, name string, values ...interface{}) erro
|
|||
if len(values) > 0 {
|
||||
msg.Headers[FieldSignature] = MakeVariant(SignatureOf(values...))
|
||||
}
|
||||
conn.outLck.RLock()
|
||||
defer conn.outLck.RUnlock()
|
||||
if conn.closed {
|
||||
|
||||
var closed bool
|
||||
conn.sendMessageAndIfClosed(msg, func() {
|
||||
closed = true
|
||||
})
|
||||
if closed {
|
||||
return ErrClosed
|
||||
}
|
||||
conn.out <- msg
|
||||
return nil
|
||||
}
|
||||
|
||||
|
@ -303,7 +253,7 @@ func (conn *Conn) Export(v interface{}, path ObjectPath, iface string) error {
|
|||
// The keys in the map are the real method names (exported on the struct), and
|
||||
// the values are the method names to be exported on DBus.
|
||||
func (conn *Conn) ExportWithMap(v interface{}, mapping map[string]string, path ObjectPath, iface string) error {
|
||||
return conn.exportWithMap(v, mapping, path, iface, false)
|
||||
return conn.export(getMethods(v, mapping), path, iface, false)
|
||||
}
|
||||
|
||||
// ExportSubtree works exactly like Export but registers the given value for
|
||||
|
@ -326,38 +276,89 @@ func (conn *Conn) ExportSubtree(v interface{}, path ObjectPath, iface string) er
|
|||
// The keys in the map are the real method names (exported on the struct), and
|
||||
// the values are the method names to be exported on DBus.
|
||||
func (conn *Conn) ExportSubtreeWithMap(v interface{}, mapping map[string]string, path ObjectPath, iface string) error {
|
||||
return conn.exportWithMap(v, mapping, path, iface, true)
|
||||
return conn.export(getMethods(v, mapping), path, iface, true)
|
||||
}
|
||||
|
||||
// ExportMethodTable like Export registers the given methods as an object
|
||||
// on the message bus. Unlike Export the it uses a method table to define
|
||||
// the object instead of a native go object.
|
||||
//
|
||||
// The method table is a map from method name to function closure
|
||||
// representing the method. This allows an object exported on the bus to not
|
||||
// necessarily be a native go object. It can be useful for generating exposed
|
||||
// methods on the fly.
|
||||
//
|
||||
// Any non-function objects in the method table are ignored.
|
||||
func (conn *Conn) ExportMethodTable(methods map[string]interface{}, path ObjectPath, iface string) error {
|
||||
return conn.exportMethodTable(methods, path, iface, false)
|
||||
}
|
||||
|
||||
// Like ExportSubtree, but with the same caveats as ExportMethodTable.
|
||||
func (conn *Conn) ExportSubtreeMethodTable(methods map[string]interface{}, path ObjectPath, iface string) error {
|
||||
return conn.exportMethodTable(methods, path, iface, true)
|
||||
}
|
||||
|
||||
func (conn *Conn) exportMethodTable(methods map[string]interface{}, path ObjectPath, iface string, includeSubtree bool) error {
|
||||
out := make(map[string]reflect.Value)
|
||||
for name, method := range methods {
|
||||
rval := reflect.ValueOf(method)
|
||||
if rval.Kind() != reflect.Func {
|
||||
continue
|
||||
}
|
||||
t := rval.Type()
|
||||
// only track valid methods must return *Error as last arg
|
||||
if t.NumOut() == 0 ||
|
||||
t.Out(t.NumOut()-1) != reflect.TypeOf(&ErrMsgInvalidArg) {
|
||||
continue
|
||||
}
|
||||
out[name] = rval
|
||||
}
|
||||
return conn.export(out, path, iface, includeSubtree)
|
||||
}
|
||||
|
||||
func (conn *Conn) unexport(h *defaultHandler, path ObjectPath, iface string) error {
|
||||
if h.PathExists(path) {
|
||||
obj := h.objects[path]
|
||||
obj.DeleteInterface(iface)
|
||||
if len(obj.interfaces) == 0 {
|
||||
h.DeleteObject(path)
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// exportWithMap is the worker function for all exports/registrations.
|
||||
func (conn *Conn) exportWithMap(v interface{}, mapping map[string]string, path ObjectPath, iface string, includeSubtree bool) error {
|
||||
func (conn *Conn) export(methods map[string]reflect.Value, path ObjectPath, iface string, includeSubtree bool) error {
|
||||
h, ok := conn.handler.(*defaultHandler)
|
||||
if !ok {
|
||||
return fmt.Errorf(
|
||||
`dbus: export only allowed on the default hander handler have %T"`,
|
||||
conn.handler)
|
||||
}
|
||||
|
||||
if !path.IsValid() {
|
||||
return fmt.Errorf(`dbus: Invalid path name: "%s"`, path)
|
||||
}
|
||||
|
||||
conn.handlersLck.Lock()
|
||||
defer conn.handlersLck.Unlock()
|
||||
|
||||
// Remove a previous export if the interface is nil
|
||||
if v == nil {
|
||||
if _, ok := conn.handlers[path]; ok {
|
||||
delete(conn.handlers[path], iface)
|
||||
if len(conn.handlers[path]) == 0 {
|
||||
delete(conn.handlers, path)
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
if methods == nil {
|
||||
return conn.unexport(h, path, iface)
|
||||
}
|
||||
|
||||
// If this is the first handler for this path, make a new map to hold all
|
||||
// handlers for this path.
|
||||
if _, ok := conn.handlers[path]; !ok {
|
||||
conn.handlers[path] = make(map[string]exportWithMapping)
|
||||
if !h.PathExists(path) {
|
||||
h.AddObject(path, newExportedObject())
|
||||
}
|
||||
|
||||
exportedMethods := make(map[string]Method)
|
||||
for name, method := range methods {
|
||||
exportedMethods[name] = exportedMethod{method}
|
||||
}
|
||||
|
||||
// Finally, save this handler
|
||||
conn.handlers[path][iface] = exportWithMapping{export: v, mapping: mapping, includeSubtree: includeSubtree}
|
||||
obj := h.objects[path]
|
||||
obj.AddInterface(iface, newExportedIntf(exportedMethods, includeSubtree))
|
||||
|
||||
return nil
|
||||
}
|
||||
|
|
|
@ -0,0 +1 @@
|
|||
module github.com/godbus/dbus
|
|
@ -22,6 +22,13 @@ const (
|
|||
// FlagNoAutoStart signals that the message bus should not automatically
|
||||
// start an application when handling this message.
|
||||
FlagNoAutoStart
|
||||
// FlagAllowInteractiveAuthorization may be set on a method call
|
||||
// message to inform the receiving side that the caller is prepared
|
||||
// to wait for interactive authorization, which might take a
|
||||
// considerable time to complete. For instance, if this flag is set,
|
||||
// it would be appropriate to query the user for passwords or
|
||||
// confirmation via Polkit or a similar framework.
|
||||
FlagAllowInteractiveAuthorization
|
||||
)
|
||||
|
||||
// Type represents the possible types of a D-Bus message.
|
||||
|
@ -248,7 +255,7 @@ func (msg *Message) EncodeTo(out io.Writer, order binary.ByteOrder) error {
|
|||
// IsValid checks whether msg is a valid message and returns an
|
||||
// InvalidMessageError if it is not.
|
||||
func (msg *Message) IsValid() error {
|
||||
if msg.Flags & ^(FlagNoAutoStart|FlagNoReplyExpected) != 0 {
|
||||
if msg.Flags & ^(FlagNoAutoStart|FlagNoReplyExpected|FlagAllowInteractiveAuthorization) != 0 {
|
||||
return InvalidMessageError("invalid flags")
|
||||
}
|
||||
if msg.Type == 0 || msg.Type >= typeMax {
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
package dbus
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"strings"
|
||||
)
|
||||
|
@ -9,7 +10,11 @@ import (
|
|||
// invoked.
|
||||
type BusObject interface {
|
||||
Call(method string, flags Flags, args ...interface{}) *Call
|
||||
CallWithContext(ctx context.Context, method string, flags Flags, args ...interface{}) *Call
|
||||
Go(method string, flags Flags, ch chan *Call, args ...interface{}) *Call
|
||||
GoWithContext(ctx context.Context, method string, flags Flags, ch chan *Call, args ...interface{}) *Call
|
||||
AddMatchSignal(iface, member string, options ...MatchOption) *Call
|
||||
RemoveMatchSignal(iface, member string, options ...MatchOption) *Call
|
||||
GetProperty(p string) (Variant, error)
|
||||
Destination() string
|
||||
Path() ObjectPath
|
||||
|
@ -24,7 +29,74 @@ type Object struct {
|
|||
|
||||
// Call calls a method with (*Object).Go and waits for its reply.
|
||||
func (o *Object) Call(method string, flags Flags, args ...interface{}) *Call {
|
||||
return <-o.Go(method, flags, make(chan *Call, 1), args...).Done
|
||||
return <-o.createCall(context.Background(), method, flags, make(chan *Call, 1), args...).Done
|
||||
}
|
||||
|
||||
// CallWithContext acts like Call but takes a context
|
||||
func (o *Object) CallWithContext(ctx context.Context, method string, flags Flags, args ...interface{}) *Call {
|
||||
return <-o.createCall(ctx, method, flags, make(chan *Call, 1), args...).Done
|
||||
}
|
||||
|
||||
// MatchOption specifies option for dbus routing match rule. Options can be constructed with WithMatch* helpers.
|
||||
// For full list of available options consult
|
||||
// https://dbus.freedesktop.org/doc/dbus-specification.html#message-bus-routing-match-rules
|
||||
type MatchOption struct {
|
||||
key string
|
||||
value string
|
||||
}
|
||||
|
||||
// WithMatchOption creates match option with given key and value
|
||||
func WithMatchOption(key, value string) MatchOption {
|
||||
return MatchOption{key, value}
|
||||
}
|
||||
|
||||
// WithMatchObjectPath creates match option that filters events based on given path
|
||||
func WithMatchObjectPath(path ObjectPath) MatchOption {
|
||||
return MatchOption{"path", string(path)}
|
||||
}
|
||||
|
||||
func formatMatchOptions(options []MatchOption) string {
|
||||
items := make([]string, 0, len(options))
|
||||
for _, option := range options {
|
||||
items = append(items, option.key+"='"+option.value+"'")
|
||||
}
|
||||
|
||||
return strings.Join(items, ",")
|
||||
}
|
||||
|
||||
// AddMatchSignal subscribes BusObject to signals from specified interface,
|
||||
// method (member). Additional filter rules can be added via WithMatch* option constructors.
|
||||
// Note: To filter events by object path you have to specify this path via an option.
|
||||
func (o *Object) AddMatchSignal(iface, member string, options ...MatchOption) *Call {
|
||||
base := []MatchOption{
|
||||
{"type", "signal"},
|
||||
{"interface", iface},
|
||||
{"member", member},
|
||||
}
|
||||
|
||||
options = append(base, options...)
|
||||
return o.conn.BusObject().Call(
|
||||
"org.freedesktop.DBus.AddMatch",
|
||||
0,
|
||||
formatMatchOptions(options),
|
||||
)
|
||||
}
|
||||
|
||||
// RemoveMatchSignal unsubscribes BusObject from signals from specified interface,
|
||||
// method (member). Additional filter rules can be added via WithMatch* option constructors
|
||||
func (o *Object) RemoveMatchSignal(iface, member string, options ...MatchOption) *Call {
|
||||
base := []MatchOption{
|
||||
{"type", "signal"},
|
||||
{"interface", iface},
|
||||
{"member", member},
|
||||
}
|
||||
|
||||
options = append(base, options...)
|
||||
return o.conn.BusObject().Call(
|
||||
"org.freedesktop.DBus.RemoveMatch",
|
||||
0,
|
||||
formatMatchOptions(options),
|
||||
)
|
||||
}
|
||||
|
||||
// Go calls a method with the given arguments asynchronously. It returns a
|
||||
|
@ -33,11 +105,24 @@ func (o *Object) Call(method string, flags Flags, args ...interface{}) *Call {
|
|||
// will be allocated. Otherwise, ch has to be buffered or Go will panic.
|
||||
//
|
||||
// If the flags include FlagNoReplyExpected, ch is ignored and a Call structure
|
||||
// is returned of which only the Err member is valid.
|
||||
// is returned with any error in Err and a closed channel in Done containing
|
||||
// the returned Call as it's one entry.
|
||||
//
|
||||
// If the method parameter contains a dot ('.'), the part before the last dot
|
||||
// specifies the interface on which the method is called.
|
||||
func (o *Object) Go(method string, flags Flags, ch chan *Call, args ...interface{}) *Call {
|
||||
return o.createCall(context.Background(), method, flags, ch, args...)
|
||||
}
|
||||
|
||||
// GoWithContext acts like Go but takes a context
|
||||
func (o *Object) GoWithContext(ctx context.Context, method string, flags Flags, ch chan *Call, args ...interface{}) *Call {
|
||||
return o.createCall(ctx, method, flags, ch, args...)
|
||||
}
|
||||
|
||||
func (o *Object) createCall(ctx context.Context, method string, flags Flags, ch chan *Call, args ...interface{}) *Call {
|
||||
if ctx == nil {
|
||||
panic("nil context")
|
||||
}
|
||||
iface := ""
|
||||
i := strings.LastIndex(method, ".")
|
||||
if i != -1 {
|
||||
|
@ -65,33 +150,41 @@ func (o *Object) Go(method string, flags Flags, ch chan *Call, args ...interface
|
|||
} else if cap(ch) == 0 {
|
||||
panic("dbus: unbuffered channel passed to (*Object).Go")
|
||||
}
|
||||
ctx, cancel := context.WithCancel(ctx)
|
||||
call := &Call{
|
||||
Destination: o.dest,
|
||||
Path: o.path,
|
||||
Method: method,
|
||||
Args: args,
|
||||
Done: ch,
|
||||
ctxCanceler: cancel,
|
||||
ctx: ctx,
|
||||
}
|
||||
o.conn.callsLck.Lock()
|
||||
o.conn.calls[msg.serial] = call
|
||||
o.conn.callsLck.Unlock()
|
||||
o.conn.outLck.RLock()
|
||||
if o.conn.closed {
|
||||
call.Err = ErrClosed
|
||||
call.Done <- call
|
||||
} else {
|
||||
o.conn.out <- msg
|
||||
}
|
||||
o.conn.outLck.RUnlock()
|
||||
o.conn.calls.track(msg.serial, call)
|
||||
o.conn.sendMessageAndIfClosed(msg, func() {
|
||||
o.conn.calls.handleSendError(msg, ErrClosed)
|
||||
cancel()
|
||||
})
|
||||
go func() {
|
||||
<-ctx.Done()
|
||||
o.conn.calls.handleSendError(msg, ctx.Err())
|
||||
}()
|
||||
|
||||
return call
|
||||
}
|
||||
o.conn.outLck.RLock()
|
||||
defer o.conn.outLck.RUnlock()
|
||||
if o.conn.closed {
|
||||
return &Call{Err: ErrClosed}
|
||||
done := make(chan *Call, 1)
|
||||
call := &Call{
|
||||
Err: nil,
|
||||
Done: done,
|
||||
}
|
||||
o.conn.out <- msg
|
||||
return &Call{Err: nil}
|
||||
defer func() {
|
||||
call.Done <- call
|
||||
close(done)
|
||||
}()
|
||||
o.conn.sendMessageAndIfClosed(msg, func() {
|
||||
call.Err = ErrClosed
|
||||
})
|
||||
return call
|
||||
}
|
||||
|
||||
// GetProperty calls org.freedesktop.DBus.Properties.GetProperty on the given
|
||||
|
@ -115,12 +208,12 @@ func (o *Object) GetProperty(p string) (Variant, error) {
|
|||
return result, nil
|
||||
}
|
||||
|
||||
// Destination returns the destination that calls on o are sent to.
|
||||
// Destination returns the destination that calls on (o *Object) are sent to.
|
||||
func (o *Object) Destination() string {
|
||||
return o.dest
|
||||
}
|
||||
|
||||
// Path returns the path that calls on o are sent to.
|
||||
// Path returns the path that calls on (o *Object") are sent to.
|
||||
func (o *Object) Path() ObjectPath {
|
||||
return o.path
|
||||
}
|
||||
|
|
|
@ -0,0 +1,99 @@
|
|||
package dbus
|
||||
|
||||
// Terminator allows a handler to implement a shutdown mechanism that
|
||||
// is called when the connection terminates.
|
||||
type Terminator interface {
|
||||
Terminate()
|
||||
}
|
||||
|
||||
// Handler is the representation of a D-Bus Application.
|
||||
//
|
||||
// The Handler must have a way to lookup objects given
|
||||
// an ObjectPath. The returned object must implement the
|
||||
// ServerObject interface.
|
||||
type Handler interface {
|
||||
LookupObject(path ObjectPath) (ServerObject, bool)
|
||||
}
|
||||
|
||||
// ServerObject is the representation of an D-Bus Object.
|
||||
//
|
||||
// Objects are registered at a path for a given Handler.
|
||||
// The Objects implement D-Bus interfaces. The semantics
|
||||
// of Interface lookup is up to the implementation of
|
||||
// the ServerObject. The ServerObject implementation may
|
||||
// choose to implement empty string as a valid interface
|
||||
// represeting all methods or not per the D-Bus specification.
|
||||
type ServerObject interface {
|
||||
LookupInterface(name string) (Interface, bool)
|
||||
}
|
||||
|
||||
// An Interface is the representation of a D-Bus Interface.
|
||||
//
|
||||
// Interfaces are a grouping of methods implemented by the Objects.
|
||||
// Interfaces are responsible for routing method calls.
|
||||
type Interface interface {
|
||||
LookupMethod(name string) (Method, bool)
|
||||
}
|
||||
|
||||
// A Method represents the exposed methods on D-Bus.
|
||||
type Method interface {
|
||||
// Call requires that all arguments are decoded before being passed to it.
|
||||
Call(args ...interface{}) ([]interface{}, error)
|
||||
NumArguments() int
|
||||
NumReturns() int
|
||||
// ArgumentValue returns a representative value for the argument at position
|
||||
// it should be of the proper type. reflect.Zero would be a good mechanism
|
||||
// to use for this Value.
|
||||
ArgumentValue(position int) interface{}
|
||||
// ReturnValue returns a representative value for the return at position
|
||||
// it should be of the proper type. reflect.Zero would be a good mechanism
|
||||
// to use for this Value.
|
||||
ReturnValue(position int) interface{}
|
||||
}
|
||||
|
||||
// An Argument Decoder can decode arguments using the non-standard mechanism
|
||||
//
|
||||
// If a method implements this interface then the non-standard
|
||||
// decoder will be used.
|
||||
//
|
||||
// Method arguments must be decoded from the message.
|
||||
// The mechanism for doing this will vary based on the
|
||||
// implementation of the method. A normal approach is provided
|
||||
// as part of this library, but may be replaced with
|
||||
// any other decoding scheme.
|
||||
type ArgumentDecoder interface {
|
||||
// To decode the arguments of a method the sender and message are
|
||||
// provided incase the semantics of the implementer provides access
|
||||
// to these as part of the method invocation.
|
||||
DecodeArguments(conn *Conn, sender string, msg *Message, args []interface{}) ([]interface{}, error)
|
||||
}
|
||||
|
||||
// A SignalHandler is responsible for delivering a signal.
|
||||
//
|
||||
// Signal delivery may be changed from the default channel
|
||||
// based approach by Handlers implementing the SignalHandler
|
||||
// interface.
|
||||
type SignalHandler interface {
|
||||
DeliverSignal(iface, name string, signal *Signal)
|
||||
}
|
||||
|
||||
// A DBusError is used to convert a generic object to a D-Bus error.
|
||||
//
|
||||
// Any custom error mechanism may implement this interface to provide
|
||||
// a custom encoding of the error on D-Bus. By default if a normal
|
||||
// error is returned, it will be encoded as the generic
|
||||
// "org.freedesktop.DBus.Error.Failed" error. By implementing this
|
||||
// interface as well a custom encoding may be provided.
|
||||
type DBusError interface {
|
||||
DBusError() (string, []interface{})
|
||||
}
|
||||
|
||||
// SerialGenerator is responsible for serials generation.
|
||||
//
|
||||
// Different approaches for the serial generation can be used,
|
||||
// maintaining a map guarded with a mutex (the standard way) or
|
||||
// simply increment an atomic counter.
|
||||
type SerialGenerator interface {
|
||||
GetSerial() uint32
|
||||
RetireSerial(serial uint32)
|
||||
}
|
|
@ -57,12 +57,12 @@ func getSignature(t reflect.Type) string {
|
|||
return "n"
|
||||
case reflect.Uint16:
|
||||
return "q"
|
||||
case reflect.Int32:
|
||||
case reflect.Int, reflect.Int32:
|
||||
if t == unixFDType {
|
||||
return "h"
|
||||
}
|
||||
return "i"
|
||||
case reflect.Uint32:
|
||||
case reflect.Uint, reflect.Uint32:
|
||||
if t == unixFDIndexType {
|
||||
return "h"
|
||||
}
|
||||
|
@ -101,6 +101,8 @@ func getSignature(t reflect.Type) string {
|
|||
panic(InvalidTypeError{t})
|
||||
}
|
||||
return "a{" + getSignature(t.Key()) + getSignature(t.Elem()) + "}"
|
||||
case reflect.Interface:
|
||||
return "v"
|
||||
}
|
||||
panic(InvalidTypeError{t})
|
||||
}
|
||||
|
@ -162,7 +164,7 @@ func (e SignatureError) Error() string {
|
|||
return fmt.Sprintf("dbus: invalid signature: %q (%s)", e.Sig, e.Reason)
|
||||
}
|
||||
|
||||
// Try to read a single type from this string. If it was successfull, err is nil
|
||||
// Try to read a single type from this string. If it was successful, err is nil
|
||||
// and rem is the remaining unparsed part. Otherwise, err is a non-nil
|
||||
// SignatureError and rem is "". depth is the current recursion depth which may
|
||||
// not be greater than 64 and should be given as 0 on the first call.
|
||||
|
|
|
@ -4,8 +4,23 @@ import (
|
|||
"encoding/binary"
|
||||
"errors"
|
||||
"io"
|
||||
"unsafe"
|
||||
)
|
||||
|
||||
var nativeEndian binary.ByteOrder
|
||||
|
||||
func detectEndianness() binary.ByteOrder {
|
||||
var x uint32 = 0x01020304
|
||||
if *(*byte)(unsafe.Pointer(&x)) == 0x01 {
|
||||
return binary.BigEndian
|
||||
}
|
||||
return binary.LittleEndian
|
||||
}
|
||||
|
||||
func init() {
|
||||
nativeEndian = detectEndianness()
|
||||
}
|
||||
|
||||
type genericTransport struct {
|
||||
io.ReadWriteCloser
|
||||
}
|
||||
|
@ -31,5 +46,5 @@ func (t genericTransport) SendMessage(msg *Message) error {
|
|||
return errors.New("dbus: unix fd passing not enabled")
|
||||
}
|
||||
}
|
||||
return msg.EncodeTo(t, binary.LittleEndian)
|
||||
return msg.EncodeTo(t, nativeEndian)
|
||||
}
|
||||
|
|
|
@ -0,0 +1,39 @@
|
|||
//+build !windows
|
||||
|
||||
package dbus
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"io/ioutil"
|
||||
"net"
|
||||
)
|
||||
|
||||
func init() {
|
||||
transports["nonce-tcp"] = newNonceTcpTransport
|
||||
}
|
||||
|
||||
func newNonceTcpTransport(keys string) (transport, error) {
|
||||
host := getKey(keys, "host")
|
||||
port := getKey(keys, "port")
|
||||
noncefile := getKey(keys, "noncefile")
|
||||
if host == "" || port == "" || noncefile == "" {
|
||||
return nil, errors.New("dbus: unsupported address (must set host, port and noncefile)")
|
||||
}
|
||||
protocol, err := tcpFamily(keys)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
socket, err := net.Dial(protocol, net.JoinHostPort(host, port))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
b, err := ioutil.ReadFile(noncefile)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
_, err = socket.Write(b)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return NewConn(socket)
|
||||
}
|
|
@ -0,0 +1,43 @@
|
|||
//+build !windows
|
||||
|
||||
package dbus
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"net"
|
||||
)
|
||||
|
||||
func init() {
|
||||
transports["tcp"] = newTcpTransport
|
||||
}
|
||||
|
||||
func tcpFamily(keys string) (string, error) {
|
||||
switch getKey(keys, "family") {
|
||||
case "":
|
||||
return "tcp", nil
|
||||
case "ipv4":
|
||||
return "tcp4", nil
|
||||
case "ipv6":
|
||||
return "tcp6", nil
|
||||
default:
|
||||
return "", errors.New("dbus: invalid tcp family (must be ipv4 or ipv6)")
|
||||
}
|
||||
}
|
||||
|
||||
func newTcpTransport(keys string) (transport, error) {
|
||||
host := getKey(keys, "host")
|
||||
port := getKey(keys, "port")
|
||||
if host == "" || port == "" {
|
||||
return nil, errors.New("dbus: unsupported address (must set host and port)")
|
||||
}
|
||||
|
||||
protocol, err := tcpFamily(keys)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
socket, err := net.Dial(protocol, net.JoinHostPort(host, port))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return NewConn(socket)
|
||||
}
|
|
@ -1,4 +1,4 @@
|
|||
//+build !windows
|
||||
//+build !windows,!solaris
|
||||
|
||||
package dbus
|
||||
|
||||
|
@ -31,6 +31,7 @@ func (o *oobReader) Read(b []byte) (n int, err error) {
|
|||
|
||||
type unixTransport struct {
|
||||
*net.UnixConn
|
||||
rdr *oobReader
|
||||
hasUnixFDs bool
|
||||
}
|
||||
|
||||
|
@ -79,10 +80,15 @@ func (t *unixTransport) ReadMessage() (*Message, error) {
|
|||
// To be sure that all bytes of out-of-band data are read, we use a special
|
||||
// reader that uses ReadUnix on the underlying connection instead of Read
|
||||
// and gathers the out-of-band data in a buffer.
|
||||
rd := &oobReader{conn: t.UnixConn}
|
||||
if t.rdr == nil {
|
||||
t.rdr = &oobReader{conn: t.UnixConn}
|
||||
} else {
|
||||
t.rdr.oob = nil
|
||||
}
|
||||
|
||||
// read the first 16 bytes (the part of the header that has a constant size),
|
||||
// from which we can figure out the length of the rest of the message
|
||||
if _, err := io.ReadFull(rd, csheader[:]); err != nil {
|
||||
if _, err := io.ReadFull(t.rdr, csheader[:]); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
switch csheader[0] {
|
||||
|
@ -104,7 +110,7 @@ func (t *unixTransport) ReadMessage() (*Message, error) {
|
|||
// decode headers and look for unix fds
|
||||
headerdata := make([]byte, hlen+4)
|
||||
copy(headerdata, csheader[12:])
|
||||
if _, err := io.ReadFull(t, headerdata[4:]); err != nil {
|
||||
if _, err := io.ReadFull(t.rdr, headerdata[4:]); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
dec := newDecoder(bytes.NewBuffer(headerdata), order)
|
||||
|
@ -122,7 +128,7 @@ func (t *unixTransport) ReadMessage() (*Message, error) {
|
|||
all := make([]byte, 16+hlen+blen)
|
||||
copy(all, csheader[:])
|
||||
copy(all[16:], headerdata[4:])
|
||||
if _, err := io.ReadFull(rd, all[16+hlen:]); err != nil {
|
||||
if _, err := io.ReadFull(t.rdr, all[16+hlen:]); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if unixfds != 0 {
|
||||
|
@ -130,7 +136,7 @@ func (t *unixTransport) ReadMessage() (*Message, error) {
|
|||
return nil, errors.New("dbus: got unix fds on unsupported transport")
|
||||
}
|
||||
// read the fds from the OOB data
|
||||
scms, err := syscall.ParseSocketControlMessage(rd.oob)
|
||||
scms, err := syscall.ParseSocketControlMessage(t.rdr.oob)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
@ -148,11 +154,23 @@ func (t *unixTransport) ReadMessage() (*Message, error) {
|
|||
// substitute the values in the message body (which are indices for the
|
||||
// array receiver via OOB) with the actual values
|
||||
for i, v := range msg.Body {
|
||||
if j, ok := v.(UnixFDIndex); ok {
|
||||
switch v.(type) {
|
||||
case UnixFDIndex:
|
||||
j := v.(UnixFDIndex)
|
||||
if uint32(j) >= unixfds {
|
||||
return nil, InvalidMessageError("invalid index for unix fd")
|
||||
}
|
||||
msg.Body[i] = UnixFD(fds[j])
|
||||
case []UnixFDIndex:
|
||||
idxArray := v.([]UnixFDIndex)
|
||||
fdArray := make([]UnixFD, len(idxArray))
|
||||
for k, j := range idxArray {
|
||||
if uint32(j) >= unixfds {
|
||||
return nil, InvalidMessageError("invalid index for unix fd")
|
||||
}
|
||||
fdArray[k] = UnixFD(fds[j])
|
||||
}
|
||||
msg.Body[i] = fdArray
|
||||
}
|
||||
}
|
||||
return msg, nil
|
||||
|
@ -175,7 +193,7 @@ func (t *unixTransport) SendMessage(msg *Message) error {
|
|||
msg.Headers[FieldUnixFDs] = MakeVariant(uint32(len(fds)))
|
||||
oob := syscall.UnixRights(fds...)
|
||||
buf := new(bytes.Buffer)
|
||||
msg.EncodeTo(buf, binary.LittleEndian)
|
||||
msg.EncodeTo(buf, nativeEndian)
|
||||
n, oobn, err := t.UnixConn.WriteMsgUnix(buf.Bytes(), oob, nil)
|
||||
if err != nil {
|
||||
return err
|
||||
|
@ -184,7 +202,7 @@ func (t *unixTransport) SendMessage(msg *Message) error {
|
|||
return io.ErrShortWrite
|
||||
}
|
||||
} else {
|
||||
if err := msg.EncodeTo(t, binary.LittleEndian); err != nil {
|
||||
if err := msg.EncodeTo(t, nativeEndian); err != nil {
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,91 @@
|
|||
// The UnixCredentials system call is currently only implemented on Linux
|
||||
// http://golang.org/src/pkg/syscall/sockcmsg_linux.go
|
||||
// https://golang.org/s/go1.4-syscall
|
||||
// http://code.google.com/p/go/source/browse/unix/sockcmsg_linux.go?repo=sys
|
||||
|
||||
// Local implementation of the UnixCredentials system call for FreeBSD
|
||||
|
||||
package dbus
|
||||
|
||||
/*
|
||||
const int sizeofPtr = sizeof(void*);
|
||||
#define _WANT_UCRED
|
||||
#include <sys/ucred.h>
|
||||
*/
|
||||
import "C"
|
||||
|
||||
import (
|
||||
"io"
|
||||
"os"
|
||||
"syscall"
|
||||
"unsafe"
|
||||
)
|
||||
|
||||
// http://golang.org/src/pkg/syscall/ztypes_linux_amd64.go
|
||||
// https://golang.org/src/syscall/ztypes_freebsd_amd64.go
|
||||
type Ucred struct {
|
||||
Pid int32
|
||||
Uid uint32
|
||||
Gid uint32
|
||||
}
|
||||
|
||||
// http://golang.org/src/pkg/syscall/types_linux.go
|
||||
// https://golang.org/src/syscall/types_freebsd.go
|
||||
// https://github.com/freebsd/freebsd/blob/master/sys/sys/ucred.h
|
||||
const (
|
||||
SizeofUcred = C.sizeof_struct_ucred
|
||||
)
|
||||
|
||||
// http://golang.org/src/pkg/syscall/sockcmsg_unix.go
|
||||
func cmsgAlignOf(salen int) int {
|
||||
salign := C.sizeofPtr
|
||||
|
||||
return (salen + salign - 1) & ^(salign - 1)
|
||||
}
|
||||
|
||||
// http://golang.org/src/pkg/syscall/sockcmsg_unix.go
|
||||
func cmsgData(h *syscall.Cmsghdr) unsafe.Pointer {
|
||||
return unsafe.Pointer(uintptr(unsafe.Pointer(h)) + uintptr(cmsgAlignOf(syscall.SizeofCmsghdr)))
|
||||
}
|
||||
|
||||
// http://golang.org/src/pkg/syscall/sockcmsg_linux.go
|
||||
// UnixCredentials encodes credentials into a socket control message
|
||||
// for sending to another process. This can be used for
|
||||
// authentication.
|
||||
func UnixCredentials(ucred *Ucred) []byte {
|
||||
b := make([]byte, syscall.CmsgSpace(SizeofUcred))
|
||||
h := (*syscall.Cmsghdr)(unsafe.Pointer(&b[0]))
|
||||
h.Level = syscall.SOL_SOCKET
|
||||
h.Type = syscall.SCM_CREDS
|
||||
h.SetLen(syscall.CmsgLen(SizeofUcred))
|
||||
*((*Ucred)(cmsgData(h))) = *ucred
|
||||
return b
|
||||
}
|
||||
|
||||
// http://golang.org/src/pkg/syscall/sockcmsg_linux.go
|
||||
// ParseUnixCredentials decodes a socket control message that contains
|
||||
// credentials in a Ucred structure. To receive such a message, the
|
||||
// SO_PASSCRED option must be enabled on the socket.
|
||||
func ParseUnixCredentials(m *syscall.SocketControlMessage) (*Ucred, error) {
|
||||
if m.Header.Level != syscall.SOL_SOCKET {
|
||||
return nil, syscall.EINVAL
|
||||
}
|
||||
if m.Header.Type != syscall.SCM_CREDS {
|
||||
return nil, syscall.EINVAL
|
||||
}
|
||||
ucred := *(*Ucred)(unsafe.Pointer(&m.Data[0]))
|
||||
return &ucred, nil
|
||||
}
|
||||
|
||||
func (t *unixTransport) SendNullByte() error {
|
||||
ucred := &Ucred{Pid: int32(os.Getpid()), Uid: uint32(os.Getuid()), Gid: uint32(os.Getgid())}
|
||||
b := UnixCredentials(ucred)
|
||||
_, oobn, err := t.UnixConn.WriteMsgUnix([]byte{0}, b, nil)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if oobn != len(b) {
|
||||
return io.ErrShortWrite
|
||||
}
|
||||
return nil
|
||||
}
|
|
@ -0,0 +1,14 @@
|
|||
package dbus
|
||||
|
||||
import "io"
|
||||
|
||||
func (t *unixTransport) SendNullByte() error {
|
||||
n, _, err := t.UnixConn.WriteMsgUnix([]byte{0}, nil, nil)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if n != 1 {
|
||||
return io.ErrShortWrite
|
||||
}
|
||||
return nil
|
||||
}
|
|
@ -17,7 +17,12 @@ type Variant struct {
|
|||
// MakeVariant converts the given value to a Variant. It panics if v cannot be
|
||||
// represented as a D-Bus type.
|
||||
func MakeVariant(v interface{}) Variant {
|
||||
return Variant{SignatureOf(v), v}
|
||||
return MakeVariantWithSignature(v, SignatureOf(v))
|
||||
}
|
||||
|
||||
// MakeVariantWithSignature converts the given value to a Variant.
|
||||
func MakeVariantWithSignature(v interface{}, s Signature) Variant {
|
||||
return Variant{s, v}
|
||||
}
|
||||
|
||||
// ParseVariant parses the given string as a variant as described at
|
||||
|
|
Loading…
Reference in New Issue