dbus.go 4.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188
  1. // Copyright 2015 CoreOS, Inc.
  2. //
  3. // Licensed under the Apache License, Version 2.0 (the "License");
  4. // you may not use this file except in compliance with the License.
  5. // You may obtain a copy of the License at
  6. //
  7. // http://www.apache.org/licenses/LICENSE-2.0
  8. //
  9. // Unless required by applicable law or agreed to in writing, software
  10. // distributed under the License is distributed on an "AS IS" BASIS,
  11. // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  12. // See the License for the specific language governing permissions and
  13. // limitations under the License.
  14. // Integration with the systemd D-Bus API. See http://www.freedesktop.org/wiki/Software/systemd/dbus/
  15. package dbus
  16. import (
  17. "fmt"
  18. "os"
  19. "strconv"
  20. "strings"
  21. "sync"
  22. "github.com/godbus/dbus"
  23. )
  24. const (
  25. alpha = `abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ`
  26. num = `0123456789`
  27. alphanum = alpha + num
  28. signalBuffer = 100
  29. )
  30. // needsEscape checks whether a byte in a potential dbus ObjectPath needs to be escaped
  31. func needsEscape(i int, b byte) bool {
  32. // Escape everything that is not a-z-A-Z-0-9
  33. // Also escape 0-9 if it's the first character
  34. return strings.IndexByte(alphanum, b) == -1 ||
  35. (i == 0 && strings.IndexByte(num, b) != -1)
  36. }
  37. // PathBusEscape sanitizes a constituent string of a dbus ObjectPath using the
  38. // rules that systemd uses for serializing special characters.
  39. func PathBusEscape(path string) string {
  40. // Special case the empty string
  41. if len(path) == 0 {
  42. return "_"
  43. }
  44. n := []byte{}
  45. for i := 0; i < len(path); i++ {
  46. c := path[i]
  47. if needsEscape(i, c) {
  48. e := fmt.Sprintf("_%x", c)
  49. n = append(n, []byte(e)...)
  50. } else {
  51. n = append(n, c)
  52. }
  53. }
  54. return string(n)
  55. }
  56. // Conn is a connection to systemd's dbus endpoint.
  57. type Conn struct {
  58. // sysconn/sysobj are only used to call dbus methods
  59. sysconn *dbus.Conn
  60. sysobj dbus.BusObject
  61. // sigconn/sigobj are only used to receive dbus signals
  62. sigconn *dbus.Conn
  63. sigobj dbus.BusObject
  64. jobListener struct {
  65. jobs map[dbus.ObjectPath]chan<- string
  66. sync.Mutex
  67. }
  68. subscriber struct {
  69. updateCh chan<- *SubStateUpdate
  70. errCh chan<- error
  71. sync.Mutex
  72. ignore map[dbus.ObjectPath]int64
  73. cleanIgnore int64
  74. }
  75. }
  76. // New establishes a connection to the system bus and authenticates.
  77. // Callers should call Close() when done with the connection.
  78. func New() (*Conn, error) {
  79. return newConnection(func() (*dbus.Conn, error) {
  80. return dbusAuthHelloConnection(dbus.SystemBusPrivate)
  81. })
  82. }
  83. // NewUserConnection establishes a connection to the session bus and
  84. // authenticates. This can be used to connect to systemd user instances.
  85. // Callers should call Close() when done with the connection.
  86. func NewUserConnection() (*Conn, error) {
  87. return newConnection(func() (*dbus.Conn, error) {
  88. return dbusAuthHelloConnection(dbus.SessionBusPrivate)
  89. })
  90. }
  91. // NewSystemdConnection establishes a private, direct connection to systemd.
  92. // This can be used for communicating with systemd without a dbus daemon.
  93. // Callers should call Close() when done with the connection.
  94. func NewSystemdConnection() (*Conn, error) {
  95. return newConnection(func() (*dbus.Conn, error) {
  96. // We skip Hello when talking directly to systemd.
  97. return dbusAuthConnection(func() (*dbus.Conn, error) {
  98. return dbus.Dial("unix:path=/run/systemd/private")
  99. })
  100. })
  101. }
  102. // Close closes an established connection
  103. func (c *Conn) Close() {
  104. c.sysconn.Close()
  105. c.sigconn.Close()
  106. }
  107. func newConnection(createBus func() (*dbus.Conn, error)) (*Conn, error) {
  108. sysconn, err := createBus()
  109. if err != nil {
  110. return nil, err
  111. }
  112. sigconn, err := createBus()
  113. if err != nil {
  114. sysconn.Close()
  115. return nil, err
  116. }
  117. c := &Conn{
  118. sysconn: sysconn,
  119. sysobj: systemdObject(sysconn),
  120. sigconn: sigconn,
  121. sigobj: systemdObject(sigconn),
  122. }
  123. c.subscriber.ignore = make(map[dbus.ObjectPath]int64)
  124. c.jobListener.jobs = make(map[dbus.ObjectPath]chan<- string)
  125. // Setup the listeners on jobs so that we can get completions
  126. c.sigconn.BusObject().Call("org.freedesktop.DBus.AddMatch", 0,
  127. "type='signal', interface='org.freedesktop.systemd1.Manager', member='JobRemoved'")
  128. c.dispatch()
  129. return c, nil
  130. }
  131. func dbusAuthConnection(createBus func() (*dbus.Conn, error)) (*dbus.Conn, error) {
  132. conn, err := createBus()
  133. if err != nil {
  134. return nil, err
  135. }
  136. // Only use EXTERNAL method, and hardcode the uid (not username)
  137. // to avoid a username lookup (which requires a dynamically linked
  138. // libc)
  139. methods := []dbus.Auth{dbus.AuthExternal(strconv.Itoa(os.Getuid()))}
  140. err = conn.Auth(methods)
  141. if err != nil {
  142. conn.Close()
  143. return nil, err
  144. }
  145. return conn, nil
  146. }
  147. func dbusAuthHelloConnection(createBus func() (*dbus.Conn, error)) (*dbus.Conn, error) {
  148. conn, err := dbusAuthConnection(createBus)
  149. if err != nil {
  150. return nil, err
  151. }
  152. if err = conn.Hello(); err != nil {
  153. conn.Close()
  154. return nil, err
  155. }
  156. return conn, nil
  157. }
  158. func systemdObject(conn *dbus.Conn) dbus.BusObject {
  159. return conn.Object("org.freedesktop.systemd1", dbus.ObjectPath("/org/freedesktop/systemd1"))
  160. }