Skip to content

Instantly share code, notes, and snippets.

@jeffguorg
Last active November 28, 2020 10:52
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save jeffguorg/8741334573e5e2aa6087119d75d7c3a4 to your computer and use it in GitHub Desktop.
Save jeffguorg/8741334573e5e2aa6087119d75d7c3a4 to your computer and use it in GitHub Desktop.
dbus counter demo
package main
import (
"fmt"
"log"
"os"
"os/signal"
"strings"
"sync"
"syscall"
"github.com/godbus/dbus/v5"
"github.com/godbus/dbus/v5/introspect"
"github.com/godbus/dbus/v5/prop"
"github.com/google/uuid"
)
type Controller struct {
bus *dbus.Conn
counters map[string]*Counter
locker sync.Locker
privateCounter map[string][]string
privateLocker sync.Locker
}
func NewController(busConn *dbus.Conn) *Controller {
return &Controller{
bus: busConn,
counters: make(map[string]*Counter),
locker: &sync.Mutex{},
privateCounter: make(map[string][]string),
privateLocker: &sync.Mutex{},
}
}
func (controller *Controller) Counter(initValue int64) (string, *dbus.Error) {
id := strings.ReplaceAll(uuid.New().String(), "-", "")
path := fmt.Sprintf("/xyz/jeffthecoder/Counter/%v", id)
counter := &Counter{path: dbus.ObjectPath(path), controller: controller, value: initValue, locker: &sync.Mutex{}}
if err := controller.bus.Export(
counter,
dbus.ObjectPath(path),
"xyz.jeffthecoder.Counter",
); err != nil {
return "", &dbus.Error{}
}
if err := controller.bus.Export(introspect.NewIntrospectable(&introspect.Node{
Name: path,
Interfaces: []introspect.Interface{
{
Name: "xyz.jeffthecoder.Counter",
Methods: introspect.Methods(counter),
Properties: []introspect.Property{
{
Name: "value",
Type: "x",
Access: "read",
},
},
},
},
}), dbus.ObjectPath(path),
"org.freedesktop.DBus.Introspectable"); err != nil {
return "", &dbus.Error{}
}
if _, err := prop.Export(controller.bus, dbus.ObjectPath(path), map[string]map[string]*prop.Prop{
"xyz.jeffthecoder.Counter": {
"value": &prop.Prop{
Value: initValue,
Writable: false,
},
},
}); err != nil {
return "", &dbus.Error{}
}
controller.locker.Lock()
controller.counters[id] = counter
controller.locker.Unlock()
node := introspect.Node{
Name: "/xyz/jeffthecoder/Counter",
Interfaces: []introspect.Interface{
{
Name: "xyz.jeffthecoder.Counter.Manager",
Methods: introspect.Methods(controller),
},
},
}
for k := range controller.counters {
node.Children = append(node.Children, introspect.Node{
Name: fmt.Sprint(k),
})
}
if err := controller.bus.Export(introspect.NewIntrospectable(&node), "/xyz/jeffthecoder/Counter",
"org.freedesktop.DBus.Introspectable"); err != nil {
return "", &dbus.Error{}
}
return path, nil
}
func (controller *Controller) PrivateCounter(sender dbus.Sender, initValue int64) (string, *dbus.Error) {
id := strings.ReplaceAll(uuid.New().String(), "-", "")
path := fmt.Sprintf("/xyz/jeffthecoder/Counter/%v/%v", string(sender), id)
counter := &PrivateCounter{
Counter: Counter{path: dbus.ObjectPath(path), controller: controller, value: initValue, locker: &sync.Mutex{}},
Sender: sender,
}
if err := controller.bus.Export(
counter,
dbus.ObjectPath(path),
"xyz.jeffthecoder.Counter",
); err != nil {
return "", &dbus.Error{}
}
if err := controller.bus.Export(introspect.NewIntrospectable(&introspect.Node{
Name: path,
Interfaces: []introspect.Interface{
{
Name: "xyz.jeffthecoder.Counter",
Methods: introspect.Methods(counter),
Properties: []introspect.Property{
{
Name: "value",
Type: "x",
Access: "read",
},
},
},
},
}), dbus.ObjectPath(path),
"org.freedesktop.DBus.Introspectable"); err != nil {
return "", &dbus.Error{}
}
if _, err := prop.Export(controller.bus, dbus.ObjectPath(path), map[string]map[string]*prop.Prop{
"xyz.jeffthecoder.Counter": {
"value": &prop.Prop{
Value: initValue,
Writable: false,
},
},
}); err != nil {
return "", &dbus.Error{}
}
controller.privateLocker.Lock()
if arr, ok := controller.privateCounter[string(sender)]; ok {
controller.privateCounter[string(sender)] = append(arr, id)
} else {
controller.privateCounter[string(sender)] = []string{id}
}
controller.privateLocker.Unlock()
if err := controller.bus.AddMatchSignal(
dbus.WithMatchObjectPath("/org/freedesktop/DBus"),
dbus.WithMatchInterface("org.freedesktop.DBus"),
dbus.WithMatchSender("org.freedesktop.DBus"),
dbus.WithMatchMember("NameLost"),
dbus.WithMatchOption("arg1", string(sender)),
); err != nil {
log.Printf("warning: failed to add match signal for %v, there will be memory leak in the future: %v", sender, err)
}
return path, nil
}
func (controller *Controller) Destroy(id string) *dbus.Error {
controller.locker.Lock()
defer controller.locker.Unlock()
if _, ok := controller.counters[id]; !ok {
return nil
}
delete(controller.counters, id)
path := fmt.Sprintf("/xyz/jeffthecoder/Counter/%v", id)
// unexport
if err := controller.bus.Export(
nil,
dbus.ObjectPath(path),
"xyz.jeffthecoder.Counter",
); err != nil {
return &dbus.Error{}
}
if err := controller.bus.Export(nil, dbus.ObjectPath(path), "org.freedesktop.DBus.Introspectable"); err != nil {
return &dbus.Error{}
}
// re-export controller's introspectable interface
node := introspect.Node{
Name: "/xyz/jeffthecoder/Counter",
Interfaces: []introspect.Interface{
{
Name: "xyz.jeffthecoder.Counter.Manager",
Methods: introspect.Methods(controller),
},
},
}
for k := range controller.counters {
node.Children = append(node.Children, introspect.Node{
Name: fmt.Sprint(k),
})
}
if err := controller.bus.Export(introspect.NewIntrospectable(&node), "/xyz/jeffthecoder/Counter",
"org.freedesktop.DBus.Introspectable"); err != nil {
return &dbus.Error{}
}
return nil
}
func (controller *Controller) Run() {
busSigChan := make(chan *dbus.Signal)
controller.bus.Signal(busSigChan)
procSigChan := make(chan os.Signal)
signal.Notify(procSigChan, syscall.SIGINT)
MainLoop:
for {
select {
case sig := <-busSigChan:
log.Println("signal from dbus: ", sig)
if sig.Name == "NameOwnerChanged" && len(sig.Body) >= 1 {
if sender, ok := sig.Body[0].(string); ok {
controller.privateLocker.Lock()
if counters, ok := controller.privateCounter[sender]; ok {
for _, counter := range counters {
path := fmt.Sprintf("/xyz/jeffthecoder/Counter/%v/%v", string(sender), counter)
_ = controller.bus.Export(nil, dbus.ObjectPath(path), "xyz.jeffthecoder.Counter")
_ = controller.bus.Export(nil, dbus.ObjectPath(path), "org.freedesktop.DBus.Introspectable")
}
delete(controller.privateCounter, sender)
}
controller.privateLocker.Unlock()
_ = controller.bus.RemoveMatchSignal(
dbus.WithMatchObjectPath("/org/freedesktop/DBus"),
dbus.WithMatchInterface("org.freedesktop.DBus"),
dbus.WithMatchSender("org.freedesktop.DBus"),
dbus.WithMatchMember("NameLost"),
dbus.WithMatchOption("arg1", sender),
)
}
}
case sig := <-procSigChan:
log.Println("signal from system: ", sig)
switch sig {
case syscall.SIGINT:
break MainLoop
}
}
}
}
package main
import (
"github.com/godbus/dbus/v5"
"github.com/godbus/dbus/v5/prop"
"sync"
)
type Counter struct {
path dbus.ObjectPath
controller *Controller
value int64
locker sync.Locker
}
func (counter *Counter) Add(delta int64) (int64, *dbus.Error) {
counter.locker.Lock()
defer counter.locker.Unlock()
counter.value += delta
if _, err := prop.Export(counter.controller.bus, counter.path, map[string]map[string]*prop.Prop{
"xyz.jeffthecoder.Counter": {
"value": &prop.Prop{
Value: counter.value,
Writable: false,
},
},
}); err != nil {
return 0, &dbus.Error{}
}
return counter.value, nil
}
type PrivateCounter struct {
Counter
Sender dbus.Sender
}
func (privateCounter *PrivateCounter) Add(sender dbus.Sender, delta int64) (int64, *dbus.Error) {
if sender == privateCounter.Sender {
return privateCounter.Counter.Add(delta)
}
return 0, &dbus.ErrMsgNoObject
}
package main
import (
"flag"
"github.com/godbus/dbus/v5"
"github.com/godbus/dbus/v5/introspect"
"log"
)
var (
fSystemBus = flag.Bool("system", false, "connect to system bus")
fBusName = flag.String("name", "xyz.jeffthecoder.Counter", "dbus name")
)
func init() {
flag.Parse()
}
func main() {
var (
bus *dbus.Conn
err error
)
if *fSystemBus {
bus, err = dbus.SystemBus()
} else {
bus, err = dbus.SessionBus()
}
if err != nil {
log.Fatal(err)
}
if reply, err := bus.RequestName(*fBusName, dbus.NameFlagAllowReplacement|dbus.NameFlagAllowReplacement); err != nil {
log.Fatal("failure during requesting name: ", err)
} else if reply != dbus.RequestNameReplyPrimaryOwner {
log.Fatal("unexpected result when requesting name: ", reply)
}
defer bus.ReleaseName(*fBusName)
controller := NewController(bus)
if err := bus.Export(controller, "/xyz/jeffthecoder/Counter", "xyz.jeffthecoder.Counter.Manager"); err != nil {
log.Fatal("failed to export object: ", err)
}
if err := bus.Export(introspect.NewIntrospectable(&introspect.Node{
Interfaces: []introspect.Interface{
{
Name: "xyz.jeffthecoder.Counter.Manager",
Methods: introspect.Methods(controller),
},
},
}), "/xyz/jeffthecoder/Counter",
"org.freedesktop.DBus.Introspectable"); err != nil {
}
controller.Run()
}
@jeffguorg
Copy link
Author

jeffguorg commented Nov 16, 2020

this is a demonstration of how to

  • request a name
  • export an object to dbus
  • handle signals
  • export multiple object later

introspection is optional including exporting manager's introspection, counter's introspections and re-exporting manager's introspection after having created new objects. you can still use these objects without any issue but some tools like d-feet may introspect object recursively from / and list all objects found on the path. if you do not update the parent objectpath, your child object will not show up in these tools.

这是一小段在dbus中

  • 请求获得一个名字
  • 发布一个对象
  • 处理信号和事件
  • 之后再发布一些对象
    以提供服务的例子。

内省(introspection),包括发布父路径的内省接口、子路径的内省接口或者重新发布父路径的内省接口,这都是可选不必要的。无论有没有内省,dbus都可以找到你的对象、调用接口,其他的dbus连接也都可以请求到你对象上的函数、监听到事件,不会有任何问题。但是有一些工具,举个例子d-feet,他们会从根目录/一层层检查内省接口,以此枚举对象,不用内省的最主要的问题就是这些工具无法去很方便的去展示有什么东西。类似的,增加对象后,如果不更新父路径,最主要的问题也就是工具上面无论怎么刷新都不会有新的对象,但是实际上还是可以在代码中访问到对象的

@jeffguorg
Copy link
Author

jeffguorg commented Nov 28, 2020

又更新了一下,写了篇博客

https://blog.jeffthecoder.xyz/posts/linux-ipc-dbus-golang/

如果其中有比较大的问题,请各位大神不吝赐教

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment