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