@@ -5,7 +5,10 @@ package guestagent
55
66import  (
77	"context" 
8+ 	"encoding/json" 
9+ 	"errors" 
810	"os" 
11+ 	"path/filepath" 
912	"reflect" 
1013	"time" 
1114
@@ -19,7 +22,7 @@ import (
1922	"github.com/lima-vm/lima/v2/pkg/guestagent/timesync" 
2023)
2124
22- func  New (ctx  context.Context , ticker  ticker.Ticker ) (Agent , error ) {
25+ func  New (ctx  context.Context , ticker  ticker.Ticker ,  runtimeDir   string ) (Agent , error ) {
2326	socketsLister , err  :=  sockets .NewLister ()
2427	if  err  !=  nil  {
2528		return  nil , err 
@@ -28,25 +31,37 @@ func New(ctx context.Context, ticker ticker.Ticker) (Agent, error) {
2831		ticker :                   ticker ,
2932		socketLister :             socketsLister ,
3033		kubernetesServiceWatcher : kubernetesservice .NewServiceWatcher (),
34+ 		runtimeDir :               runtimeDir ,
3135	}
3236
3337	go  a .kubernetesServiceWatcher .Start (ctx )
34- 	go  a .fixSystemTimeSkew ()
38+ 	go  a .fixSystemTimeSkew (ctx )
39+ 
40+ 	go  func () {
41+ 		<- ctx .Done ()
42+ 		logrus .Debug ("Closing the agent" )
43+ 		if  err  :=  a .Close (); err  !=  nil  {
44+ 			logrus .Errorf ("error on agent.Close(): %v" , err )
45+ 		}
46+ 	}()
3547
3648	return  a , nil 
3749}
3850
51+ var  _  Agent  =  (* agent )(nil )
52+ 
3953type  agent  struct  {
4054	// Ticker is like time.Ticker. 
4155	// We can't use inotify for /proc/net/tcp, so we need this ticker to 
4256	// reload /proc/net/tcp. 
4357	ticker                    ticker.Ticker 
4458	socketLister              * sockets.Lister 
4559	kubernetesServiceWatcher  * kubernetesservice.ServiceWatcher 
60+ 	runtimeDir                string 
4661}
4762
4863type  eventState  struct  {
49- 	ports  []* api.IPPort 
64+ 	Ports  []* api.IPPort   `json:"ports,omitempty"` 
5065}
5166
5267func  comparePorts (old , neww  []* api.IPPort ) (added , removed  []* api.IPPort ) {
@@ -82,13 +97,13 @@ func (a *agent) collectEvent(ctx context.Context, st eventState) (*api.Event, ev
8297		err  error 
8398	)
8499	newSt  :=  st 
85- 	newSt .ports , err  =  a .LocalPorts (ctx )
100+ 	newSt .Ports , err  =  a .LocalPorts (ctx )
86101	if  err  !=  nil  {
87102		ev .Errors  =  append (ev .Errors , err .Error ())
88103		ev .Time  =  timestamppb .Now ()
89104		return  ev , newSt 
90105	}
91- 	ev .AddedLocalPorts , ev .RemovedLocalPorts  =  comparePorts (st .ports , newSt .ports )
106+ 	ev .AddedLocalPorts , ev .RemovedLocalPorts  =  comparePorts (st .Ports , newSt .Ports )
92107	ev .Time  =  timestamppb .Now ()
93108	return  ev , newSt 
94109}
@@ -102,8 +117,16 @@ func isEventEmpty(ev *api.Event) bool {
102117func  (a  * agent ) Events (ctx  context.Context , ch  chan  * api.Event ) {
103118	defer  close (ch )
104119	tickerCh  :=  a .ticker .Chan ()
105- 	defer  a .ticker .Stop ()
106- 	var  st  eventState 
120+ 
121+ 	st , err  :=  a .LoadEventState ()
122+ 	if  err  !=  nil  {
123+ 		logrus .Errorf ("failed to load state: %v" , err )
124+ 	}
125+ 	defer  func () {
126+ 		if  err  :=  a .SaveEventState (st ); err  !=  nil  {
127+ 			logrus .Errorf ("failed to save state: %v" , err )
128+ 		}
129+ 	}()
107130	for  {
108131		var  ev  * api.Event 
109132		ev , st  =  a .collectEvent (ctx , st )
@@ -115,6 +138,7 @@ func (a *agent) Events(ctx context.Context, ch chan *api.Event) {
115138			return 
116139		case  _ , ok  :=  <- tickerCh :
117140			if  ! ok  {
141+ 				logrus .Debug ("ticker channel closed" )
118142				return 
119143			}
120144			logrus .Debug ("tick!" )
@@ -190,7 +214,7 @@ func (a *agent) Info(ctx context.Context) (*api.Info, error) {
190214
191215const  deltaLimit  =  2  *  time .Second 
192216
193- func  (a  * agent ) fixSystemTimeSkew () {
217+ func  (a  * agent ) fixSystemTimeSkew (ctx  context. Context ) {
194218	logrus .Info ("fixSystemTimeSkew(): monitoring system time skew" )
195219	for  {
196220		ok , err  :=  timesync .HasRTC ()
@@ -217,6 +241,13 @@ func (a *agent) fixSystemTimeSkew() {
217241				logrus .Infof ("fixSystemTimeSkew: system time synchronized with rtc" )
218242				break 
219243			}
244+ 			select  {
245+ 			case  <- ctx .Done ():
246+ 				logrus .Debug ("fixSystemTimeSkew: context done, exiting" )
247+ 				ticker .Stop ()
248+ 				return 
249+ 			default :
250+ 			}
220251		}
221252		ticker .Stop ()
222253	}
@@ -239,5 +270,42 @@ func (a *agent) Close() error {
239270			return  err 
240271		}
241272	}
273+ 	a .ticker .Stop ()
242274	return  nil 
243275}
276+ 
277+ const  eventStateFileName  =  "event-state.json" 
278+ 
279+ // LoadEventState loads the event state from a file in JSON format. 
280+ // If the file does not exist, it returns an empty eventState with no error. 
281+ // The saved eventState is expected to be removed on OS restart. 
282+ func  (a  * agent ) LoadEventState () (eventState , error ) {
283+ 	logrus .Debug ("Loading event state" )
284+ 	path  :=  filepath .Join (a .runtimeDir , eventStateFileName )
285+ 	data , err  :=  os .ReadFile (path )
286+ 	if  err  !=  nil  {
287+ 		if  errors .Is (err , os .ErrNotExist ) {
288+ 			return  eventState {}, nil 
289+ 		}
290+ 		return  eventState {}, err 
291+ 	}
292+ 	var  st  eventState 
293+ 	if  err  :=  json .Unmarshal (data , & st ); err  !=  nil  {
294+ 		return  eventState {}, err 
295+ 	}
296+ 	// We don't remove the file after loading for debugging purposes. 
297+ 	return  st , nil 
298+ }
299+ 
300+ // SaveEventState saves the event state to a file in JSON format. 
301+ // It overwrites the file if it already exists. 
302+ // The saved eventState is expected to be removed on OS restart. 
303+ func  (a  * agent ) SaveEventState (st  eventState ) error  {
304+ 	logrus .Debug ("Saving event state" )
305+ 	data , err  :=  json .Marshal (st )
306+ 	if  err  !=  nil  {
307+ 		return  err 
308+ 	}
309+ 	path  :=  filepath .Join (a .runtimeDir , eventStateFileName )
310+ 	return  os .WriteFile (path , data , 0o644 )
311+ }
0 commit comments