@@ -7,12 +7,15 @@ package linux
77
88import (
99 "bufio"
10+ "os"
11+ "strings"
12+ "time"
13+
1014 "github.com/fsnotify/fsnotify"
15+ "github.com/godbus/dbus/v5"
1116 "github.com/newrelic/infrastructure-agent/internal/agent/types"
1217 "github.com/newrelic/infrastructure-agent/pkg/entity"
1318 "github.com/newrelic/infrastructure-agent/pkg/log"
14- "strings"
15- "time"
1619
1720 "github.com/newrelic/infrastructure-agent/pkg/plugins/ids"
1821
@@ -23,6 +26,8 @@ import (
2326
2427var usrlog = log .WithPlugin ("Users" )
2528
29+ const utmpPath = "/var/run/utmp"
30+
2631type UsersPlugin struct {
2732 agent.PluginCommon
2833 frequency time.Duration
@@ -93,14 +98,29 @@ func (self *UsersPlugin) Run() {
9398 return
9499 }
95100
101+ // Strategy 1: Attempt to use the legacy utmp file watcher.
102+ // We check if the file exists first.
103+ if _ , err := os .Stat (utmpPath ); err == nil {
104+ usrlog .Debug ("utmp file found, using legacy file watcher. path " + utmpPath )
105+ self .runUtmpWatcher ()
106+ } else {
107+ // Strategy 2: Fallback to the modern D-Bus watcher for systemd-logind.
108+ usrlog .Debug ("utmp file not found, falling back to modern D-Bus watcher for systemd-logind." )
109+ self .runDbusWatcher ()
110+ }
111+ }
112+
113+ // This function contains the original logic for watching /var/run/utmp.
114+ func (self * UsersPlugin ) runUtmpWatcher () {
96115 watcher , err := fsnotify .NewWatcher ()
97116 if err != nil {
98- usrlog .WithError (err ).Error ("can't instantiate users watcher" )
117+ usrlog .WithError (err ).Error ("can't instantiate legacy users watcher (fsnotify) " )
99118 self .Unregister ()
100119 return
101120 }
121+ defer watcher .Close ()
102122
103- err = watcher .Add ("/var/run/utmp" )
123+ err = watcher .Add (utmpPath )
104124 if err != nil {
105125 usrlog .WithError (err ).Error ("can't setup trigger file watcher for users" )
106126 self .Unregister ()
@@ -128,3 +148,53 @@ func (self *UsersPlugin) Run() {
128148 }
129149 }
130150}
151+
152+ // This function contains the logic for listening to systemd-logind signals via D-Bus.
153+ func (self * UsersPlugin ) runDbusWatcher () {
154+ conn , err := dbus .SystemBus ()
155+ if err != nil {
156+ usrlog .WithError (err ).Error ("can't connect to system D-Bus, cannot monitor user sessions" )
157+ self .Unregister ()
158+ return
159+ }
160+ defer conn .Close ()
161+
162+ // D-Bus "match rules" for login and logout signals.
163+ rules := []string {
164+ "type='signal',interface='org.freedesktop.login1.Manager',member='SessionNew'" ,
165+ "type='signal',interface='org.freedesktop.login1.Manager',member='SessionRemoved'" ,
166+ }
167+ for _ , rule := range rules {
168+ if err = conn .BusObject ().Call ("org.freedesktop.DBus.AddMatch" , 0 , rule ).Err ; err != nil {
169+ usrlog .WithError (err ).Errorf ("failed to add D-Bus match rule '%s'" , rule )
170+ self .Unregister ()
171+ return
172+ }
173+ }
174+
175+ signals := make (chan * dbus.Signal , 10 )
176+ conn .Signal (signals )
177+
178+ refreshTimer := time .NewTimer (1 )
179+ needsFlush := true
180+
181+ for {
182+ select {
183+ case signal , ok := <- signals :
184+ if ok {
185+ if signal != nil {
186+ // Any signal (SessionNew or SessionRemoved) triggers a refresh.
187+ needsFlush = true
188+ }
189+ }
190+ case <- refreshTimer .C :
191+ {
192+ refreshTimer .Reset (self .frequency )
193+ if needsFlush {
194+ self .EmitInventory (self .getUserDetails (), entity .NewFromNameWithoutID (self .Context .EntityKey ()))
195+ needsFlush = false
196+ }
197+ }
198+ }
199+ }
200+ }
0 commit comments