export.go (13933B)
1 package dbus 2 3 import ( 4 "errors" 5 "fmt" 6 "reflect" 7 "strings" 8 ) 9 10 var ( 11 ErrMsgInvalidArg = Error{ 12 "org.freedesktop.DBus.Error.InvalidArgs", 13 []interface{}{"Invalid type / number of args"}, 14 } 15 ErrMsgNoObject = Error{ 16 "org.freedesktop.DBus.Error.NoSuchObject", 17 []interface{}{"No such object"}, 18 } 19 ErrMsgUnknownMethod = Error{ 20 "org.freedesktop.DBus.Error.UnknownMethod", 21 []interface{}{"Unknown / invalid method"}, 22 } 23 ErrMsgUnknownInterface = Error{ 24 "org.freedesktop.DBus.Error.UnknownInterface", 25 []interface{}{"Object does not implement the interface"}, 26 } 27 ) 28 29 func MakeFailedError(err error) *Error { 30 return &Error{ 31 "org.freedesktop.DBus.Error.Failed", 32 []interface{}{err.Error()}, 33 } 34 } 35 36 // Sender is a type which can be used in exported methods to receive the message 37 // sender. 38 type Sender string 39 40 func computeMethodName(name string, mapping map[string]string) string { 41 newname, ok := mapping[name] 42 if ok { 43 name = newname 44 } 45 return name 46 } 47 48 func getMethods(in interface{}, mapping map[string]string) map[string]reflect.Value { 49 if in == nil { 50 return nil 51 } 52 methods := make(map[string]reflect.Value) 53 val := reflect.ValueOf(in) 54 typ := val.Type() 55 for i := 0; i < typ.NumMethod(); i++ { 56 methtype := typ.Method(i) 57 method := val.Method(i) 58 t := method.Type() 59 // only track valid methods must return *Error as last arg 60 // and must be exported 61 if t.NumOut() == 0 || 62 t.Out(t.NumOut()-1) != reflect.TypeOf(&ErrMsgInvalidArg) || 63 methtype.PkgPath != "" { 64 continue 65 } 66 // map names while building table 67 methods[computeMethodName(methtype.Name, mapping)] = method 68 } 69 return methods 70 } 71 72 func getAllMethods(in interface{}, mapping map[string]string) map[string]reflect.Value { 73 if in == nil { 74 return nil 75 } 76 methods := make(map[string]reflect.Value) 77 val := reflect.ValueOf(in) 78 typ := val.Type() 79 for i := 0; i < typ.NumMethod(); i++ { 80 methtype := typ.Method(i) 81 method := val.Method(i) 82 // map names while building table 83 methods[computeMethodName(methtype.Name, mapping)] = method 84 } 85 return methods 86 } 87 88 func standardMethodArgumentDecode(m Method, sender string, msg *Message, body []interface{}) ([]interface{}, error) { 89 pointers := make([]interface{}, m.NumArguments()) 90 decode := make([]interface{}, 0, len(body)) 91 92 for i := 0; i < m.NumArguments(); i++ { 93 tp := reflect.TypeOf(m.ArgumentValue(i)) 94 val := reflect.New(tp) 95 pointers[i] = val.Interface() 96 if tp == reflect.TypeOf((*Sender)(nil)).Elem() { 97 val.Elem().SetString(sender) 98 } else if tp == reflect.TypeOf((*Message)(nil)).Elem() { 99 val.Elem().Set(reflect.ValueOf(*msg)) 100 } else { 101 decode = append(decode, pointers[i]) 102 } 103 } 104 105 if len(decode) != len(body) { 106 return nil, ErrMsgInvalidArg 107 } 108 109 if err := Store(body, decode...); err != nil { 110 return nil, ErrMsgInvalidArg 111 } 112 113 return pointers, nil 114 } 115 116 func (conn *Conn) decodeArguments(m Method, sender string, msg *Message) ([]interface{}, error) { 117 if decoder, ok := m.(ArgumentDecoder); ok { 118 return decoder.DecodeArguments(conn, sender, msg, msg.Body) 119 } 120 return standardMethodArgumentDecode(m, sender, msg, msg.Body) 121 } 122 123 // handleCall handles the given method call (i.e. looks if it's one of the 124 // pre-implemented ones and searches for a corresponding handler if not). 125 func (conn *Conn) handleCall(msg *Message) { 126 name := msg.Headers[FieldMember].value.(string) 127 path := msg.Headers[FieldPath].value.(ObjectPath) 128 ifaceName, _ := msg.Headers[FieldInterface].value.(string) 129 sender, hasSender := msg.Headers[FieldSender].value.(string) 130 serial := msg.serial 131 if ifaceName == "org.freedesktop.DBus.Peer" { 132 switch name { 133 case "Ping": 134 conn.sendReply(sender, serial) 135 case "GetMachineId": 136 conn.sendReply(sender, serial, conn.uuid) 137 default: 138 conn.sendError(ErrMsgUnknownMethod, sender, serial) 139 } 140 return 141 } 142 if len(name) == 0 { 143 conn.sendError(ErrMsgUnknownMethod, sender, serial) 144 } 145 146 object, ok := conn.handler.LookupObject(path) 147 if !ok { 148 conn.sendError(ErrMsgNoObject, sender, serial) 149 return 150 } 151 152 iface, exists := object.LookupInterface(ifaceName) 153 if !exists { 154 conn.sendError(ErrMsgUnknownInterface, sender, serial) 155 return 156 } 157 158 m, exists := iface.LookupMethod(name) 159 if !exists { 160 conn.sendError(ErrMsgUnknownMethod, sender, serial) 161 return 162 } 163 args, err := conn.decodeArguments(m, sender, msg) 164 if err != nil { 165 conn.sendError(err, sender, serial) 166 return 167 } 168 169 ret, err := m.Call(args...) 170 if err != nil { 171 conn.sendError(err, sender, serial) 172 return 173 } 174 175 if msg.Flags&FlagNoReplyExpected == 0 { 176 reply := new(Message) 177 reply.Type = TypeMethodReply 178 reply.Headers = make(map[HeaderField]Variant) 179 if hasSender { 180 reply.Headers[FieldDestination] = msg.Headers[FieldSender] 181 } 182 reply.Headers[FieldReplySerial] = MakeVariant(msg.serial) 183 reply.Body = make([]interface{}, len(ret)) 184 for i := 0; i < len(ret); i++ { 185 reply.Body[i] = ret[i] 186 } 187 reply.Headers[FieldSignature] = MakeVariant(SignatureOf(reply.Body...)) 188 189 conn.sendMessageAndIfClosed(reply, nil) 190 } 191 } 192 193 // Emit emits the given signal on the message bus. The name parameter must be 194 // formatted as "interface.member", e.g., "org.freedesktop.DBus.NameLost". 195 func (conn *Conn) Emit(path ObjectPath, name string, values ...interface{}) error { 196 if !path.IsValid() { 197 return errors.New("dbus: invalid object path") 198 } 199 i := strings.LastIndex(name, ".") 200 if i == -1 { 201 return errors.New("dbus: invalid method name") 202 } 203 iface := name[:i] 204 member := name[i+1:] 205 if !isValidMember(member) { 206 return errors.New("dbus: invalid method name") 207 } 208 if !isValidInterface(iface) { 209 return errors.New("dbus: invalid interface name") 210 } 211 msg := new(Message) 212 msg.Type = TypeSignal 213 msg.Headers = make(map[HeaderField]Variant) 214 msg.Headers[FieldInterface] = MakeVariant(iface) 215 msg.Headers[FieldMember] = MakeVariant(member) 216 msg.Headers[FieldPath] = MakeVariant(path) 217 msg.Body = values 218 if len(values) > 0 { 219 msg.Headers[FieldSignature] = MakeVariant(SignatureOf(values...)) 220 } 221 222 var closed bool 223 conn.sendMessageAndIfClosed(msg, func() { 224 closed = true 225 }) 226 if closed { 227 return ErrClosed 228 } 229 return nil 230 } 231 232 // Export registers the given value to be exported as an object on the 233 // message bus. 234 // 235 // If a method call on the given path and interface is received, an exported 236 // method with the same name is called with v as the receiver if the 237 // parameters match and the last return value is of type *Error. If this 238 // *Error is not nil, it is sent back to the caller as an error. 239 // Otherwise, a method reply is sent with the other return values as its body. 240 // 241 // Any parameters with the special type Sender are set to the sender of the 242 // dbus message when the method is called. Parameters of this type do not 243 // contribute to the dbus signature of the method (i.e. the method is exposed 244 // as if the parameters of type Sender were not there). 245 // 246 // Similarly, any parameters with the type Message are set to the raw message 247 // received on the bus. Again, parameters of this type do not contribute to the 248 // dbus signature of the method. 249 // 250 // Every method call is executed in a new goroutine, so the method may be called 251 // in multiple goroutines at once. 252 // 253 // Method calls on the interface org.freedesktop.DBus.Peer will be automatically 254 // handled for every object. 255 // 256 // Passing nil as the first parameter will cause conn to cease handling calls on 257 // the given combination of path and interface. 258 // 259 // Export returns an error if path is not a valid path name. 260 func (conn *Conn) Export(v interface{}, path ObjectPath, iface string) error { 261 return conn.ExportWithMap(v, nil, path, iface) 262 } 263 264 // ExportAll registers all exported methods defined by the given object on 265 // the message bus. 266 // 267 // Unlike Export there is no requirement to have the last parameter as type 268 // *Error. If you want to be able to return error then you can append an error 269 // type parameter to your method signature. If the error returned is not nil, 270 // it is sent back to the caller as an error. Otherwise, a method reply is 271 // sent with the other return values as its body. 272 func (conn *Conn) ExportAll(v interface{}, path ObjectPath, iface string) error { 273 return conn.export(getAllMethods(v, nil), path, iface, false) 274 } 275 276 // ExportWithMap works exactly like Export but provides the ability to remap 277 // method names (e.g. export a lower-case method). 278 // 279 // The keys in the map are the real method names (exported on the struct), and 280 // the values are the method names to be exported on DBus. 281 func (conn *Conn) ExportWithMap(v interface{}, mapping map[string]string, path ObjectPath, iface string) error { 282 return conn.export(getMethods(v, mapping), path, iface, false) 283 } 284 285 // ExportSubtree works exactly like Export but registers the given value for 286 // an entire subtree rather under the root path provided. 287 // 288 // In order to make this useful, one parameter in each of the value's exported 289 // methods should be a Message, in which case it will contain the raw message 290 // (allowing one to get access to the path that caused the method to be called). 291 // 292 // Note that more specific export paths take precedence over less specific. For 293 // example, a method call using the ObjectPath /foo/bar/baz will call a method 294 // exported on /foo/bar before a method exported on /foo. 295 func (conn *Conn) ExportSubtree(v interface{}, path ObjectPath, iface string) error { 296 return conn.ExportSubtreeWithMap(v, nil, path, iface) 297 } 298 299 // ExportSubtreeWithMap works exactly like ExportSubtree but provides the 300 // ability to remap method names (e.g. export a lower-case method). 301 // 302 // The keys in the map are the real method names (exported on the struct), and 303 // the values are the method names to be exported on DBus. 304 func (conn *Conn) ExportSubtreeWithMap(v interface{}, mapping map[string]string, path ObjectPath, iface string) error { 305 return conn.export(getMethods(v, mapping), path, iface, true) 306 } 307 308 // ExportMethodTable like Export registers the given methods as an object 309 // on the message bus. Unlike Export the it uses a method table to define 310 // the object instead of a native go object. 311 // 312 // The method table is a map from method name to function closure 313 // representing the method. This allows an object exported on the bus to not 314 // necessarily be a native go object. It can be useful for generating exposed 315 // methods on the fly. 316 // 317 // Any non-function objects in the method table are ignored. 318 func (conn *Conn) ExportMethodTable(methods map[string]interface{}, path ObjectPath, iface string) error { 319 return conn.exportMethodTable(methods, path, iface, false) 320 } 321 322 // Like ExportSubtree, but with the same caveats as ExportMethodTable. 323 func (conn *Conn) ExportSubtreeMethodTable(methods map[string]interface{}, path ObjectPath, iface string) error { 324 return conn.exportMethodTable(methods, path, iface, true) 325 } 326 327 func (conn *Conn) exportMethodTable(methods map[string]interface{}, path ObjectPath, iface string, includeSubtree bool) error { 328 var out map[string]reflect.Value 329 if methods != nil { 330 out = make(map[string]reflect.Value) 331 for name, method := range methods { 332 rval := reflect.ValueOf(method) 333 if rval.Kind() != reflect.Func { 334 continue 335 } 336 t := rval.Type() 337 // only track valid methods must return *Error as last arg 338 if t.NumOut() == 0 || 339 t.Out(t.NumOut()-1) != reflect.TypeOf(&ErrMsgInvalidArg) { 340 continue 341 } 342 out[name] = rval 343 } 344 } 345 return conn.export(out, path, iface, includeSubtree) 346 } 347 348 func (conn *Conn) unexport(h *defaultHandler, path ObjectPath, iface string) error { 349 if h.PathExists(path) { 350 obj := h.objects[path] 351 obj.DeleteInterface(iface) 352 if len(obj.interfaces) == 0 { 353 h.DeleteObject(path) 354 } 355 } 356 return nil 357 } 358 359 // export is the worker function for all exports/registrations. 360 func (conn *Conn) export(methods map[string]reflect.Value, path ObjectPath, iface string, includeSubtree bool) error { 361 h, ok := conn.handler.(*defaultHandler) 362 if !ok { 363 return fmt.Errorf( 364 `dbus: export only allowed on the default handler. Received: %T"`, 365 conn.handler) 366 } 367 368 if !path.IsValid() { 369 return fmt.Errorf(`dbus: Invalid path name: "%s"`, path) 370 } 371 372 // Remove a previous export if the interface is nil 373 if methods == nil { 374 return conn.unexport(h, path, iface) 375 } 376 377 // If this is the first handler for this path, make a new map to hold all 378 // handlers for this path. 379 if !h.PathExists(path) { 380 h.AddObject(path, newExportedObject()) 381 } 382 383 exportedMethods := make(map[string]Method) 384 for name, method := range methods { 385 exportedMethods[name] = exportedMethod{method} 386 } 387 388 // Finally, save this handler 389 obj := h.objects[path] 390 obj.AddInterface(iface, newExportedIntf(exportedMethods, includeSubtree)) 391 392 return nil 393 } 394 395 // ReleaseName calls org.freedesktop.DBus.ReleaseName and awaits a response. 396 func (conn *Conn) ReleaseName(name string) (ReleaseNameReply, error) { 397 var r uint32 398 err := conn.busObj.Call("org.freedesktop.DBus.ReleaseName", 0, name).Store(&r) 399 if err != nil { 400 return 0, err 401 } 402 return ReleaseNameReply(r), nil 403 } 404 405 // RequestName calls org.freedesktop.DBus.RequestName and awaits a response. 406 func (conn *Conn) RequestName(name string, flags RequestNameFlags) (RequestNameReply, error) { 407 var r uint32 408 err := conn.busObj.Call("org.freedesktop.DBus.RequestName", 0, name, flags).Store(&r) 409 if err != nil { 410 return 0, err 411 } 412 return RequestNameReply(r), nil 413 } 414 415 // ReleaseNameReply is the reply to a ReleaseName call. 416 type ReleaseNameReply uint32 417 418 const ( 419 ReleaseNameReplyReleased ReleaseNameReply = 1 + iota 420 ReleaseNameReplyNonExistent 421 ReleaseNameReplyNotOwner 422 ) 423 424 // RequestNameFlags represents the possible flags for a RequestName call. 425 type RequestNameFlags uint32 426 427 const ( 428 NameFlagAllowReplacement RequestNameFlags = 1 << iota 429 NameFlagReplaceExisting 430 NameFlagDoNotQueue 431 ) 432 433 // RequestNameReply is the reply to a RequestName call. 434 type RequestNameReply uint32 435 436 const ( 437 RequestNameReplyPrimaryOwner RequestNameReply = 1 + iota 438 RequestNameReplyInQueue 439 RequestNameReplyExists 440 RequestNameReplyAlreadyOwner 441 )