| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102 |
- package systemd
- import (
- "context"
- "errors"
- "fmt"
- "sync"
- systemdDbus "github.com/coreos/go-systemd/v22/dbus"
- dbus "github.com/godbus/dbus/v5"
- )
- var (
- dbusC *systemdDbus.Conn
- dbusMu sync.RWMutex
- dbusInited bool
- dbusRootless bool
- )
- type dbusConnManager struct{}
- // newDbusConnManager initializes systemd dbus connection manager.
- func newDbusConnManager(rootless bool) *dbusConnManager {
- dbusMu.Lock()
- defer dbusMu.Unlock()
- if dbusInited && rootless != dbusRootless {
- panic("can't have both root and rootless dbus")
- }
- dbusInited = true
- dbusRootless = rootless
- return &dbusConnManager{}
- }
- // getConnection lazily initializes and returns systemd dbus connection.
- func (d *dbusConnManager) getConnection() (*systemdDbus.Conn, error) {
- // In the case where dbusC != nil
- // Use the read lock the first time to ensure
- // that Conn can be acquired at the same time.
- dbusMu.RLock()
- if conn := dbusC; conn != nil {
- dbusMu.RUnlock()
- return conn, nil
- }
- dbusMu.RUnlock()
- // In the case where dbusC == nil
- // Use write lock to ensure that only one
- // will be created
- dbusMu.Lock()
- defer dbusMu.Unlock()
- if conn := dbusC; conn != nil {
- return conn, nil
- }
- conn, err := d.newConnection()
- if err != nil {
- // 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.
- // This may fail with a cryptic error "read unix @->/run/systemd/private: read: connection reset by peer: unknown."
- // https://github.com/moby/moby/issues/42793
- 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)
- }
- dbusC = conn
- return conn, nil
- }
- func (d *dbusConnManager) newConnection() (*systemdDbus.Conn, error) {
- if dbusRootless {
- return newUserSystemdDbus()
- }
- return systemdDbus.NewWithContext(context.TODO())
- }
- // resetConnection resets the connection to its initial state
- // (so it can be reconnected if necessary).
- func (d *dbusConnManager) resetConnection(conn *systemdDbus.Conn) {
- dbusMu.Lock()
- defer dbusMu.Unlock()
- if dbusC != nil && dbusC == conn {
- dbusC.Close()
- dbusC = nil
- }
- }
- // retryOnDisconnect calls op, and if the error it returns is about closed dbus
- // connection, the connection is re-established and the op is retried. This helps
- // with the situation when dbus is restarted and we have a stale connection.
- func (d *dbusConnManager) retryOnDisconnect(op func(*systemdDbus.Conn) error) error {
- for {
- conn, err := d.getConnection()
- if err != nil {
- return err
- }
- err = op(conn)
- if err == nil {
- return nil
- }
- if !errors.Is(err, dbus.ErrClosed) {
- return err
- }
- d.resetConnection(conn)
- }
- }
|