dbus.go 2.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102
  1. package systemd
  2. import (
  3. "context"
  4. "errors"
  5. "fmt"
  6. "sync"
  7. systemdDbus "github.com/coreos/go-systemd/v22/dbus"
  8. dbus "github.com/godbus/dbus/v5"
  9. )
  10. var (
  11. dbusC *systemdDbus.Conn
  12. dbusMu sync.RWMutex
  13. dbusInited bool
  14. dbusRootless bool
  15. )
  16. type dbusConnManager struct{}
  17. // newDbusConnManager initializes systemd dbus connection manager.
  18. func newDbusConnManager(rootless bool) *dbusConnManager {
  19. dbusMu.Lock()
  20. defer dbusMu.Unlock()
  21. if dbusInited && rootless != dbusRootless {
  22. panic("can't have both root and rootless dbus")
  23. }
  24. dbusInited = true
  25. dbusRootless = rootless
  26. return &dbusConnManager{}
  27. }
  28. // getConnection lazily initializes and returns systemd dbus connection.
  29. func (d *dbusConnManager) getConnection() (*systemdDbus.Conn, error) {
  30. // In the case where dbusC != nil
  31. // Use the read lock the first time to ensure
  32. // that Conn can be acquired at the same time.
  33. dbusMu.RLock()
  34. if conn := dbusC; conn != nil {
  35. dbusMu.RUnlock()
  36. return conn, nil
  37. }
  38. dbusMu.RUnlock()
  39. // In the case where dbusC == nil
  40. // Use write lock to ensure that only one
  41. // will be created
  42. dbusMu.Lock()
  43. defer dbusMu.Unlock()
  44. if conn := dbusC; conn != nil {
  45. return conn, nil
  46. }
  47. conn, err := d.newConnection()
  48. if err != nil {
  49. // When dbus-user-session is not installed, we can't detect whether we should try to connect to user dbus or system dbus, so d.dbusRootless is set to false.
  50. // This may fail with a cryptic error "read unix @->/run/systemd/private: read: connection reset by peer: unknown."
  51. // https://github.com/moby/moby/issues/42793
  52. return nil, fmt.Errorf("failed to connect to dbus (hint: for rootless containers, maybe you need to install dbus-user-session package, see https://github.com/opencontainers/runc/blob/master/docs/cgroup-v2.md): %w", err)
  53. }
  54. dbusC = conn
  55. return conn, nil
  56. }
  57. func (d *dbusConnManager) newConnection() (*systemdDbus.Conn, error) {
  58. if dbusRootless {
  59. return newUserSystemdDbus()
  60. }
  61. return systemdDbus.NewWithContext(context.TODO())
  62. }
  63. // resetConnection resets the connection to its initial state
  64. // (so it can be reconnected if necessary).
  65. func (d *dbusConnManager) resetConnection(conn *systemdDbus.Conn) {
  66. dbusMu.Lock()
  67. defer dbusMu.Unlock()
  68. if dbusC != nil && dbusC == conn {
  69. dbusC.Close()
  70. dbusC = nil
  71. }
  72. }
  73. // retryOnDisconnect calls op, and if the error it returns is about closed dbus
  74. // connection, the connection is re-established and the op is retried. This helps
  75. // with the situation when dbus is restarted and we have a stale connection.
  76. func (d *dbusConnManager) retryOnDisconnect(op func(*systemdDbus.Conn) error) error {
  77. for {
  78. conn, err := d.getConnection()
  79. if err != nil {
  80. return err
  81. }
  82. err = op(conn)
  83. if err == nil {
  84. return nil
  85. }
  86. if !errors.Is(err, dbus.ErrClosed) {
  87. return err
  88. }
  89. d.resetConnection(conn)
  90. }
  91. }