|
|
|
package dbus
|
|
|
|
|
|
|
|
import (
|
|
|
|
"bytes"
|
|
|
|
"errors"
|
|
|
|
"fmt"
|
|
|
|
"reflect"
|
|
|
|
"strings"
|
|
|
|
)
|
|
|
|
|
|
|
|
var (
|
|
|
|
errmsgInvalidArg = Error{
|
|
|
|
"org.freedesktop.DBus.Error.InvalidArgs",
|
|
|
|
[]interface{}{"Invalid type / number of args"},
|
|
|
|
}
|
|
|
|
errmsgNoObject = Error{
|
|
|
|
"org.freedesktop.DBus.Error.NoSuchObject",
|
|
|
|
[]interface{}{"No such object"},
|
|
|
|
}
|
|
|
|
errmsgUnknownMethod = Error{
|
|
|
|
"org.freedesktop.DBus.Error.UnknownMethod",
|
|
|
|
[]interface{}{"Unknown / invalid method"},
|
|
|
|
}
|
|
|
|
)
|
|
|
|
|
|
|
|
// exportedObj represents an exported object. It stores a precomputed
|
|
|
|
// method table that represents the methods exported on the bus.
|
|
|
|
type exportedObj struct {
|
|
|
|
methods map[string]reflect.Value
|
|
|
|
|
|
|
|
// Whether or not this export is for the entire subtree
|
|
|
|
includeSubtree bool
|
|
|
|
}
|
|
|
|
|
|
|
|
func (obj exportedObj) Method(name string) (reflect.Value, bool) {
|
|
|
|
out, exists := obj.methods[name]
|
|
|
|
return out, exists
|
|
|
|
}
|
|
|
|
|
|
|
|
// Sender is a type which can be used in exported methods to receive the message
|
|
|
|
// sender.
|
|
|
|
type Sender string
|
|
|
|
|
|
|
|
func computeMethodName(name string, mapping map[string]string) string {
|
|
|
|
newname, ok := mapping[name]
|
|
|
|
if ok {
|
|
|
|
name = newname
|
|
|
|
}
|
|
|
|
return name
|
|
|
|
}
|
|
|
|
|
|
|
|
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
|
|
|
|
}
|
|
|
|
// map names while building table
|
|
|
|
methods[computeMethodName(methtype.Name, mapping)] = method
|
|
|
|
}
|
|
|
|
return methods
|
|
|
|
}
|
|
|
|
|
|
|
|
// 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]exportedObj, bool) {
|
|
|
|
conn.handlersLck.RLock()
|
|
|
|
defer conn.handlersLck.RUnlock()
|
|
|
|
|
|
|
|
handlers, ok := conn.handlers[path]
|
|
|
|
if ok {
|
|
|
|
return handlers, ok
|
|
|
|
}
|
|
|
|
|
|
|
|
// If handlers weren't found for this exact path, look for a matching subtree
|
|
|
|
// registration
|
|
|
|
handlers = make(map[string]exportedObj)
|
|
|
|
path = path[:strings.LastIndex(string(path), "/")]
|
|
|
|
for len(path) > 0 {
|
|
|
|
var subtreeHandlers map[string]exportedObj
|
|
|
|
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
|
|
|
|
}
|
|
|
|
|
|
|
|
// 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)
|
|
|
|
}
|
|
|
|
return
|
|
|
|
} else if ifaceName == "org.freedesktop.DBus.Introspectable" && name == "Introspect" {
|
|
|
|
if _, ok := conn.handlers[path]; !ok {
|
|
|
|
subpath := make(map[string]struct{})
|
|
|
|
var xml bytes.Buffer
|
|
|
|
xml.WriteString("<node>")
|
|
|
|
for h, _ := range conn.handlers {
|
|
|
|
p := string(path)
|
|
|
|
if p != "/" {
|
|
|
|
p += "/"
|
|
|
|
}
|
|
|
|
if strings.HasPrefix(string(h), p) {
|
|
|
|
node_name := strings.Split(string(h[len(p):]), "/")[0]
|
|
|
|
subpath[node_name] = struct{}{}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
for s, _ := range subpath {
|
|
|
|
xml.WriteString("\n\t<node name=\"" + s + "\"/>")
|
|
|
|
}
|
|
|
|
xml.WriteString("\n</node>")
|
|
|
|
conn.sendReply(sender, serial, xml.String())
|
|
|
|
return
|
|
|
|
}
|
|
|
|
}
|
|
|
|
if len(name) == 0 {
|
|
|
|
conn.sendError(errmsgUnknownMethod, sender, serial)
|
|
|
|
}
|
|
|
|
|
|
|
|
// Find the exported handler (if any) for this path
|
|
|
|
handlers, ok := conn.searchHandlers(path)
|
|
|
|
if !ok {
|
|
|
|
conn.sendError(errmsgNoObject, sender, serial)
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
var m reflect.Value
|
|
|
|
var exists bool
|
|
|
|
if hasIface {
|
|
|
|
iface := handlers[ifaceName]
|
|
|
|
m, exists = iface.Method(name)
|
|
|
|
} else {
|
|
|
|
for _, v := range handlers {
|
|
|
|
m, exists = v.Method(name)
|
|
|
|
if exists {
|
|
|
|
break
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if !exists {
|
|
|
|
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)
|
|
|
|
val := reflect.New(tp)
|
|
|
|
pointers[i] = val.Interface()
|
|
|
|
if tp == reflect.TypeOf((*Sender)(nil)).Elem() {
|
|
|
|
val.Elem().SetString(sender)
|
|
|
|
} else if tp == reflect.TypeOf((*Message)(nil)).Elem() {
|
|
|
|
val.Elem().Set(reflect.ValueOf(*msg))
|
|
|
|
} else {
|
|
|
|
decode = append(decode, pointers[i])
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if len(decode) != len(vs) {
|
|
|
|
conn.sendError(errmsgInvalidArg, sender, serial)
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
if err := Store(vs, decode...); err != nil {
|
|
|
|
conn.sendError(errmsgInvalidArg, 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()
|
|
|
|
}
|
|
|
|
|
|
|
|
// Call method
|
|
|
|
ret := m.Call(params)
|
|
|
|
if em := ret[t.NumOut()-1].Interface().(*Error); em != nil {
|
|
|
|
conn.sendError(*em, sender, serial)
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
if msg.Flags&FlagNoReplyExpected == 0 {
|
|
|
|
reply := new(Message)
|
|
|
|
reply.Type = TypeMethodReply
|
|
|
|
reply.serial = conn.getSerial()
|
|
|
|
reply.Headers = make(map[HeaderField]Variant)
|
|
|
|
if hasSender {
|
|
|
|
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()
|
|
|
|
}
|
|
|
|
if len(ret) != 1 {
|
|
|
|
reply.Headers[FieldSignature] = MakeVariant(SignatureOf(reply.Body...))
|
|
|
|
}
|
|
|
|
conn.outLck.RLock()
|
|
|
|
if !conn.closed {
|
|
|
|
conn.out <- reply
|
|
|
|
}
|
|
|
|
conn.outLck.RUnlock()
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// Emit emits the given signal on the message bus. The name parameter must be
|
|
|
|
// formatted as "interface.member", e.g., "org.freedesktop.DBus.NameLost".
|
|
|
|
func (conn *Conn) Emit(path ObjectPath, name string, values ...interface{}) error {
|
|
|
|
if !path.IsValid() {
|
|
|
|
return errors.New("dbus: invalid object path")
|
|
|
|
}
|
|
|
|
i := strings.LastIndex(name, ".")
|
|
|
|
if i == -1 {
|
|
|
|
return errors.New("dbus: invalid method name")
|
|
|
|
}
|
|
|
|
iface := name[:i]
|
|
|
|
member := name[i+1:]
|
|
|
|
if !isValidMember(member) {
|
|
|
|
return errors.New("dbus: invalid method name")
|
|
|
|
}
|
|
|
|
if !isValidInterface(iface) {
|
|
|
|
return errors.New("dbus: invalid interface name")
|
|
|
|
}
|
|
|
|
msg := new(Message)
|
|
|
|
msg.Type = TypeSignal
|
|
|
|
msg.serial = conn.getSerial()
|
|
|
|
msg.Headers = make(map[HeaderField]Variant)
|
|
|
|
msg.Headers[FieldInterface] = MakeVariant(iface)
|
|
|
|
msg.Headers[FieldMember] = MakeVariant(member)
|
|
|
|
msg.Headers[FieldPath] = MakeVariant(path)
|
|
|
|
msg.Body = values
|
|
|
|
if len(values) > 0 {
|
|
|
|
msg.Headers[FieldSignature] = MakeVariant(SignatureOf(values...))
|
|
|
|
}
|
|
|
|
conn.outLck.RLock()
|
|
|
|
defer conn.outLck.RUnlock()
|
|
|
|
if conn.closed {
|
|
|
|
return ErrClosed
|
|
|
|
}
|
|
|
|
conn.out <- msg
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
|
|
|
// Export registers the given value to be exported as an object on the
|
|
|
|
// message bus.
|
|
|
|
//
|
|
|
|
// If a method call on the given path and interface is received, an exported
|
|
|
|
// method with the same name is called with v as the receiver if the
|
|
|
|
// parameters match and the last return value is of type *Error. If this
|
|
|
|
// *Error is not nil, it is sent back to the caller as an error.
|
|
|
|
// Otherwise, a method reply is sent with the other return values as its body.
|
|
|
|
//
|
|
|
|
// Any parameters with the special type Sender are set to the sender of the
|
|
|
|
// dbus message when the method is called. Parameters of this type do not
|
|
|
|
// contribute to the dbus signature of the method (i.e. the method is exposed
|
|
|
|
// as if the parameters of type Sender were not there).
|
|
|
|
//
|
|
|
|
// Similarly, any parameters with the type Message are set to the raw message
|
|
|
|
// received on the bus. Again, parameters of this type do not contribute to the
|
|
|
|
// dbus signature of the method.
|
|
|
|
//
|
|
|
|
// Every method call is executed in a new goroutine, so the method may be called
|
|
|
|
// in multiple goroutines at once.
|
|
|
|
//
|
|
|
|
// Method calls on the interface org.freedesktop.DBus.Peer will be automatically
|
|
|
|
// handled for every object.
|
|
|
|
//
|
|
|
|
// Passing nil as the first parameter will cause conn to cease handling calls on
|
|
|
|
// the given combination of path and interface.
|
|
|
|
//
|
|
|
|
// Export returns an error if path is not a valid path name.
|
|
|
|
func (conn *Conn) Export(v interface{}, path ObjectPath, iface string) error {
|
|
|
|
return conn.ExportWithMap(v, nil, path, iface)
|
|
|
|
}
|
|
|
|
|
|
|
|
// ExportWithMap works exactly like Export but provides the ability to remap
|
|
|
|
// method names (e.g. export a lower-case method).
|
|
|
|
//
|
|
|
|
// 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.export(getMethods(v, mapping), path, iface, false)
|
|
|
|
}
|
|
|
|
|
|
|
|
// ExportSubtree works exactly like Export but registers the given value for
|
|
|
|
// an entire subtree rather under the root path provided.
|
|
|
|
//
|
|
|
|
// In order to make this useful, one parameter in each of the value's exported
|
|
|
|
// methods should be a Message, in which case it will contain the raw message
|
|
|
|
// (allowing one to get access to the path that caused the method to be called).
|
|
|
|
//
|
|
|
|
// Note that more specific export paths take precedence over less specific. For
|
|
|
|
// example, a method call using the ObjectPath /foo/bar/baz will call a method
|
|
|
|
// exported on /foo/bar before a method exported on /foo.
|
|
|
|
func (conn *Conn) ExportSubtree(v interface{}, path ObjectPath, iface string) error {
|
|
|
|
return conn.ExportSubtreeWithMap(v, nil, path, iface)
|
|
|
|
}
|
|
|
|
|
|
|
|
// ExportSubtreeWithMap works exactly like ExportSubtree but provides the
|
|
|
|
// ability to remap method names (e.g. export a lower-case method).
|
|
|
|
//
|
|
|
|
// 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.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)
|
|
|
|
}
|
|
|
|
|
|
|
|
// exportWithMap is the worker function for all exports/registrations.
|
|
|
|
func (conn *Conn) export(methods map[string]reflect.Value, path ObjectPath, iface string, includeSubtree bool) error {
|
|
|
|
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 methods == 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 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]exportedObj)
|
|
|
|
}
|
|
|
|
|
|
|
|
// Finally, save this handler
|
|
|
|
conn.handlers[path][iface] = exportedObj{
|
|
|
|
methods: methods,
|
|
|
|
includeSubtree: includeSubtree,
|
|
|
|
}
|
|
|
|
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
|
|
|
// ReleaseName calls org.freedesktop.DBus.ReleaseName and awaits a response.
|
|
|
|
func (conn *Conn) ReleaseName(name string) (ReleaseNameReply, error) {
|
|
|
|
var r uint32
|
|
|
|
err := conn.busObj.Call("org.freedesktop.DBus.ReleaseName", 0, name).Store(&r)
|
|
|
|
if err != nil {
|
|
|
|
return 0, err
|
|
|
|
}
|
|
|
|
return ReleaseNameReply(r), nil
|
|
|
|
}
|
|
|
|
|
|
|
|
// RequestName calls org.freedesktop.DBus.RequestName and awaits a response.
|
|
|
|
func (conn *Conn) RequestName(name string, flags RequestNameFlags) (RequestNameReply, error) {
|
|
|
|
var r uint32
|
|
|
|
err := conn.busObj.Call("org.freedesktop.DBus.RequestName", 0, name, flags).Store(&r)
|
|
|
|
if err != nil {
|
|
|
|
return 0, err
|
|
|
|
}
|
|
|
|
return RequestNameReply(r), nil
|
|
|
|
}
|
|
|
|
|
|
|
|
// ReleaseNameReply is the reply to a ReleaseName call.
|
|
|
|
type ReleaseNameReply uint32
|
|
|
|
|
|
|
|
const (
|
|
|
|
ReleaseNameReplyReleased ReleaseNameReply = 1 + iota
|
|
|
|
ReleaseNameReplyNonExistent
|
|
|
|
ReleaseNameReplyNotOwner
|
|
|
|
)
|
|
|
|
|
|
|
|
// RequestNameFlags represents the possible flags for a RequestName call.
|
|
|
|
type RequestNameFlags uint32
|
|
|
|
|
|
|
|
const (
|
|
|
|
NameFlagAllowReplacement RequestNameFlags = 1 << iota
|
|
|
|
NameFlagReplaceExisting
|
|
|
|
NameFlagDoNotQueue
|
|
|
|
)
|
|
|
|
|
|
|
|
// RequestNameReply is the reply to a RequestName call.
|
|
|
|
type RequestNameReply uint32
|
|
|
|
|
|
|
|
const (
|
|
|
|
RequestNameReplyPrimaryOwner RequestNameReply = 1 + iota
|
|
|
|
RequestNameReplyInQueue
|
|
|
|
RequestNameReplyExists
|
|
|
|
RequestNameReplyAlreadyOwner
|
|
|
|
)
|