1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16 package dbus
17
18 import (
19 "context"
20 "encoding/hex"
21 "fmt"
22 "os"
23 "strconv"
24 "strings"
25 "sync"
26
27 "github.com/godbus/dbus/v5"
28 )
29
30 const (
31 alpha = `abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ`
32 num = `0123456789`
33 alphanum = alpha + num
34 signalBuffer = 100
35 )
36
37
38 func needsEscape(i int, b byte) bool {
39
40
41 return strings.IndexByte(alphanum, b) == -1 ||
42 (i == 0 && strings.IndexByte(num, b) != -1)
43 }
44
45
46
47 func PathBusEscape(path string) string {
48
49 if len(path) == 0 {
50 return "_"
51 }
52 n := []byte{}
53 for i := 0; i < len(path); i++ {
54 c := path[i]
55 if needsEscape(i, c) {
56 e := fmt.Sprintf("_%x", c)
57 n = append(n, []byte(e)...)
58 } else {
59 n = append(n, c)
60 }
61 }
62 return string(n)
63 }
64
65
66 func pathBusUnescape(path string) string {
67 if path == "_" {
68 return ""
69 }
70 n := []byte{}
71 for i := 0; i < len(path); i++ {
72 c := path[i]
73 if c == '_' && i+2 < len(path) {
74 res, err := hex.DecodeString(path[i+1 : i+3])
75 if err == nil {
76 n = append(n, res...)
77 }
78 i += 2
79 } else {
80 n = append(n, c)
81 }
82 }
83 return string(n)
84 }
85
86
87 type Conn struct {
88
89 sysconn *dbus.Conn
90 sysobj dbus.BusObject
91
92
93 sigconn *dbus.Conn
94 sigobj dbus.BusObject
95
96 jobListener struct {
97 jobs map[dbus.ObjectPath]chan<- string
98 sync.Mutex
99 }
100 subStateSubscriber struct {
101 updateCh chan<- *SubStateUpdate
102 errCh chan<- error
103 sync.Mutex
104 ignore map[dbus.ObjectPath]int64
105 cleanIgnore int64
106 }
107 propertiesSubscriber struct {
108 updateCh chan<- *PropertiesUpdate
109 errCh chan<- error
110 sync.Mutex
111 }
112 }
113
114
115 func New() (*Conn, error) {
116 return NewWithContext(context.Background())
117 }
118
119
120
121 func NewWithContext(ctx context.Context) (*Conn, error) {
122 conn, err := NewSystemConnectionContext(ctx)
123 if err != nil && os.Geteuid() == 0 {
124 return NewSystemdConnectionContext(ctx)
125 }
126 return conn, err
127 }
128
129
130 func NewSystemConnection() (*Conn, error) {
131 return NewSystemConnectionContext(context.Background())
132 }
133
134
135
136 func NewSystemConnectionContext(ctx context.Context) (*Conn, error) {
137 return NewConnection(func() (*dbus.Conn, error) {
138 return dbusAuthHelloConnection(ctx, dbus.SystemBusPrivate)
139 })
140 }
141
142
143 func NewUserConnection() (*Conn, error) {
144 return NewUserConnectionContext(context.Background())
145 }
146
147
148
149
150 func NewUserConnectionContext(ctx context.Context) (*Conn, error) {
151 return NewConnection(func() (*dbus.Conn, error) {
152 return dbusAuthHelloConnection(ctx, dbus.SessionBusPrivate)
153 })
154 }
155
156
157 func NewSystemdConnection() (*Conn, error) {
158 return NewSystemdConnectionContext(context.Background())
159 }
160
161
162
163
164 func NewSystemdConnectionContext(ctx context.Context) (*Conn, error) {
165 return NewConnection(func() (*dbus.Conn, error) {
166
167 return dbusAuthConnection(ctx, func(opts ...dbus.ConnOption) (*dbus.Conn, error) {
168 return dbus.Dial("unix:path=/run/systemd/private", opts...)
169 })
170 })
171 }
172
173
174 func (c *Conn) Close() {
175 c.sysconn.Close()
176 c.sigconn.Close()
177 }
178
179
180 func (c *Conn) Connected() bool {
181 return c.sysconn.Connected() && c.sigconn.Connected()
182 }
183
184
185
186
187
188
189 func NewConnection(dialBus func() (*dbus.Conn, error)) (*Conn, error) {
190 sysconn, err := dialBus()
191 if err != nil {
192 return nil, err
193 }
194
195 sigconn, err := dialBus()
196 if err != nil {
197 sysconn.Close()
198 return nil, err
199 }
200
201 c := &Conn{
202 sysconn: sysconn,
203 sysobj: systemdObject(sysconn),
204 sigconn: sigconn,
205 sigobj: systemdObject(sigconn),
206 }
207
208 c.subStateSubscriber.ignore = make(map[dbus.ObjectPath]int64)
209 c.jobListener.jobs = make(map[dbus.ObjectPath]chan<- string)
210
211
212 c.sigconn.BusObject().Call("org.freedesktop.DBus.AddMatch", 0,
213 "type='signal', interface='org.freedesktop.systemd1.Manager', member='JobRemoved'")
214
215 c.dispatch()
216 return c, nil
217 }
218
219
220
221
222 func (c *Conn) GetManagerProperty(prop string) (string, error) {
223 variant, err := c.sysobj.GetProperty("org.freedesktop.systemd1.Manager." + prop)
224 if err != nil {
225 return "", err
226 }
227 return variant.String(), nil
228 }
229
230 func dbusAuthConnection(ctx context.Context, createBus func(opts ...dbus.ConnOption) (*dbus.Conn, error)) (*dbus.Conn, error) {
231 conn, err := createBus(dbus.WithContext(ctx))
232 if err != nil {
233 return nil, err
234 }
235
236
237
238
239 methods := []dbus.Auth{dbus.AuthExternal(strconv.Itoa(os.Getuid()))}
240
241 err = conn.Auth(methods)
242 if err != nil {
243 conn.Close()
244 return nil, err
245 }
246
247 return conn, nil
248 }
249
250 func dbusAuthHelloConnection(ctx context.Context, createBus func(opts ...dbus.ConnOption) (*dbus.Conn, error)) (*dbus.Conn, error) {
251 conn, err := dbusAuthConnection(ctx, createBus)
252 if err != nil {
253 return nil, err
254 }
255
256 if err = conn.Hello(); err != nil {
257 conn.Close()
258 return nil, err
259 }
260
261 return conn, nil
262 }
263
264 func systemdObject(conn *dbus.Conn) dbus.BusObject {
265 return conn.Object("org.freedesktop.systemd1", dbus.ObjectPath("/org/freedesktop/systemd1"))
266 }
267
View as plain text