@@ -10,6 +10,7 @@ use crate::ffi::{
10
10
} ;
11
11
use anyhow:: { anyhow, format_err, Context , Result } ;
12
12
use cap_std:: fs:: Dir ;
13
+ use cap_std:: io_lifetimes:: { IntoSocketlike , OwnedFd , OwnedSocketlike } ;
13
14
use cap_std_ext:: dirext:: CapStdExtDirExt ;
14
15
use cap_std_ext:: { cap_std, rustix} ;
15
16
use fn_error_context:: context;
@@ -18,16 +19,29 @@ use once_cell::sync::Lazy;
18
19
use ostree_ext:: { gio, glib, ostree} ;
19
20
use rustix:: fd:: { BorrowedFd , FromRawFd } ;
20
21
use rustix:: fs:: MetadataExt ;
22
+ use serde:: { Deserialize , Serialize } ;
21
23
use std:: collections:: { BTreeMap , BTreeSet } ;
22
- use std:: io:: { Read , Write } ;
24
+ use std:: io:: Read ;
23
25
use std:: os:: unix:: fs:: PermissionsExt ;
24
26
use std:: path:: Path ;
25
27
use std:: sync:: Mutex ;
26
- use tokio:: net:: { UnixListener , UnixStream } ;
27
- use tokio:: sync:: oneshot:: { Receiver , Sender } ;
28
+ use tokio:: sync:: oneshot:: Sender ;
28
29
29
30
const RPM_OSTREED_COMMIT_VERIFICATION_CACHE : & str = "rpm-ostree/gpgcheck-cache" ;
30
31
32
+ // Messages sent across the socket
33
+ #[ derive( Debug , Serialize , Deserialize ) ]
34
+ pub ( crate ) enum SocketMessage {
35
+ ClientHello { selfid : String } ,
36
+ ServerOk ,
37
+ ServerError { msg : String } ,
38
+ }
39
+
40
+ impl SocketMessage {
41
+ // Maximum size of a message.
42
+ pub ( crate ) const BUFSIZE : usize = 8192 ;
43
+ }
44
+
31
45
/// Validate basic assumptions on daemon startup.
32
46
pub ( crate ) fn daemon_sanitycheck_environment ( sysroot : & crate :: FFIOstreeSysroot ) -> CxxResult < ( ) > {
33
47
let sysroot = & sysroot. glib_reborrow ( ) ;
@@ -135,57 +149,83 @@ fn deployment_populate_variant_origin(
135
149
Ok ( ( ) )
136
150
}
137
151
138
- async fn send_ok_result_to_client ( _client : UnixStream ) {
139
- // On success we close the stream without writing anything,
140
- // which acknowledges successful startup to the client.
141
- // In the future we may actually implement a protocol here, so this
142
- // is stubbed out as a full async fn in preparation for that.
152
+ pub ( crate ) fn write_message ( conn : & OwnedFd , message : SocketMessage ) -> Result < ( ) > {
153
+ let sendbuf = serde_json:: to_vec ( & message) ?;
154
+ rustix:: net:: send ( conn, & sendbuf, rustix:: net:: SendFlags :: empty ( ) ) ?;
155
+ Ok ( ( ) )
156
+ }
157
+
158
+ pub ( crate ) fn recv_message ( conn : & OwnedFd ) -> Result < SocketMessage > {
159
+ let mut buf = [ 0u8 ; SocketMessage :: BUFSIZE ] ;
160
+ let n = rustix:: net:: recv ( & conn, & mut buf, rustix:: net:: RecvFlags :: empty ( ) ) ?;
161
+ if n == SocketMessage :: BUFSIZE {
162
+ anyhow:: bail!( "Buffer filled to {n} bytes when reading" ) ;
163
+ }
164
+ assert ! ( n < SocketMessage :: BUFSIZE ) ;
165
+ let buf = & buf[ 0 ..n] ;
166
+ let msg: SocketMessage =
167
+ serde_json:: from_slice ( buf) . context ( "Parsing client message" ) ?;
168
+ Ok ( msg)
169
+ }
170
+
171
+ fn client_hello ( client : OwnedSocketlike , e : anyhow:: Result < ( ) > ) -> Result < ( ) > {
172
+ let msg = recv_message ( & client) ?;
173
+ let reply = match msg {
174
+ SocketMessage :: ClientHello { selfid } => {
175
+ let myid = crate :: core:: self_id ( ) ?;
176
+ if selfid != myid {
177
+ // For now, make this not an error
178
+ tracing:: warn!( "Client reported id: {selfid} different from mine: {myid}" ) ;
179
+ }
180
+ match e {
181
+ Ok ( ( ) ) => SocketMessage :: ServerOk ,
182
+ Err ( e) => SocketMessage :: ServerError {
183
+ msg : format ! ( "{e}" ) ,
184
+ } ,
185
+ }
186
+ }
187
+ o => SocketMessage :: ServerError {
188
+ msg : format ! ( "Unexpected message: {o:?}" ) ,
189
+ } ,
190
+ } ;
191
+ write_message ( & client, reply) . context ( "Writing client reply" ) ?;
143
192
tracing:: debug!( "Acknowleged client" ) ;
193
+ Ok ( ( ) )
144
194
}
145
195
146
196
static SHUTDOWN_SIGNAL : Lazy < Mutex < Option < Sender < ( ) > > > > = Lazy :: new ( || Mutex :: new ( None ) ) ;
147
197
148
- async fn process_clients_with_ok ( listener : UnixListener , mut cancel : Receiver < ( ) > ) {
198
+ fn run_acknowledgement_worker ( listener : OwnedSocketlike ) {
149
199
tracing:: debug!( "Processing clients..." ) ;
150
200
loop {
151
- tokio:: select! {
152
- _ = & mut cancel => {
153
- tracing:: debug!( "Got cancellation event" ) ;
154
- return
201
+ let sock = match rustix:: net:: accept ( & listener) {
202
+ Ok ( s) => s,
203
+ Err ( e) => {
204
+ tracing:: warn!( "Failed to accept client: {e}" ) ;
205
+ continue ;
155
206
}
156
- r = listener. accept( ) => {
157
- match r {
158
- Ok ( ( stream, _addr) ) => {
159
- send_ok_result_to_client( stream) . await ;
160
- } ,
161
- Err ( e) => {
162
- tracing:: debug!( "failed to accept client: {e}" )
163
- }
164
- }
207
+ } ;
208
+ std:: thread:: spawn ( move || {
209
+ if let Err ( e) = client_hello ( sock. into_socketlike ( ) , Ok ( ( ) ) ) {
210
+ tracing:: warn!( "error acknowledging client: {e}" ) ;
165
211
}
166
- }
212
+ } ) ;
167
213
}
168
214
}
169
215
170
216
/// Ensure all asynchronous tasks in this Rust half of the daemon code are stopped.
171
217
/// Called from C++.
172
218
pub ( crate ) fn daemon_terminate ( ) {
173
- let chan = ( * SHUTDOWN_SIGNAL ) . lock ( ) . unwrap ( ) . take ( ) . unwrap ( ) ;
174
- let _ = chan. send ( ( ) ) ;
219
+ if let Some ( chan) = ( * SHUTDOWN_SIGNAL ) . lock ( ) . unwrap ( ) . take ( ) {
220
+ let _ = chan. send ( ( ) ) ;
221
+ }
175
222
}
176
223
177
- fn process_one_client ( listener : std:: os:: unix:: net:: UnixListener , e : anyhow:: Error ) -> Result < ( ) > {
178
- let mut incoming = match listener. incoming ( ) . next ( ) {
179
- Some ( r) => r?,
180
- None => {
181
- anyhow:: bail!( "Expected to find client socket from activation" ) ;
182
- }
183
- } ;
184
-
185
- let buf = format ! ( "{e}" ) ;
186
- incoming. write_all ( buf. as_bytes ( ) ) ?;
187
-
188
- todo ! ( )
224
+ fn process_one_client ( listener : OwnedSocketlike , e : anyhow:: Error ) -> Result < ( ) > {
225
+ let incoming = rustix:: net:: accept ( & listener) ?;
226
+ client_hello ( incoming. into_socketlike ( ) , Err ( e) ) ?;
227
+ // Now that we've acknowledged one client, exit the process with an error
228
+ Ok ( ( ) )
189
229
}
190
230
191
231
/// Perform initialization steps required by systemd service activation.
@@ -195,7 +235,6 @@ fn process_one_client(listener: std::os::unix::net::UnixListener, e: anyhow::Err
195
235
pub ( crate ) fn daemon_main ( debug : bool ) -> Result < ( ) > {
196
236
let handle = tokio:: runtime:: Handle :: current ( ) ;
197
237
let _tokio_guard = handle. enter ( ) ;
198
- use std:: os:: unix:: net:: UnixListener as StdUnixListener ;
199
238
if !systemd:: daemon:: booted ( ) ? {
200
239
return Err ( anyhow ! ( "not running as a systemd service" ) ) ;
201
240
}
@@ -204,54 +243,65 @@ pub(crate) fn daemon_main(debug: bool) -> Result<()> {
204
243
tracing:: debug!( "Initialization result: {init_res:?}" ) ;
205
244
206
245
let mut fds = systemd:: daemon:: listen_fds ( false ) ?. iter ( ) ;
207
- let listener = match fds. next ( ) {
246
+ let ( listener, init_res ) = match fds. next ( ) {
208
247
None => {
209
248
// If started directly via `systemctl start` or DBus activation, we
210
- // directly propagate the error back to our exit code.
249
+ // directly propagate the error back to our exit code without even bothering
250
+ // with a socket.
211
251
init_res?;
212
252
tracing:: debug!( "Initializing directly (not socket activated)" ) ;
213
- cfg ! ( feature = "client-socket" )
214
- . then ( || StdUnixListener :: bind ( "/run/rpm-ostree/client.sock" ) )
253
+ let listener = cfg ! ( feature = "client-socket" )
254
+ . then ( || {
255
+ let socket = rustix:: net:: socket (
256
+ rustix:: net:: AddressFamily :: UNIX ,
257
+ rustix:: net:: SocketType :: SEQPACKET ,
258
+ rustix:: net:: Protocol :: from_raw ( 0 ) ,
259
+ ) ?;
260
+ let addr = crate :: client:: sockaddr ( ) ?;
261
+ rustix:: net:: bind_unix ( & socket, & addr) ?;
262
+ Ok :: < _ , anyhow:: Error > ( socket. into_socketlike ( ) )
263
+ } )
215
264
. transpose ( )
216
- . context ( "Binding to socket" ) ?
265
+ . context ( "Binding to socket" ) ?;
266
+ ( listener, Ok ( ( ) ) )
217
267
}
218
268
Some ( fd) => {
219
269
if fds. next ( ) . is_some ( ) {
220
270
return Err ( anyhow ! ( "Expected exactly 1 fd from systemd activation" ) ) ;
221
271
}
222
272
tracing:: debug!( "Initializing from socket activation; fd={fd}" ) ;
223
- let listener = unsafe { StdUnixListener :: from_raw_fd ( fd) } ;
224
-
225
- match init_res {
226
- Ok ( _) => Some ( listener) ,
227
- Err ( e) => {
228
- let err_copy = anyhow ! ( "{e}" ) ;
229
- tracing:: debug!( "Reporting initialization error: {e}" ) ;
230
- match process_one_client ( listener, err_copy) {
231
- Ok ( ( ) ) => {
232
- tracing:: debug!( "Acknowleged initial client" ) ;
233
- }
234
- Err ( e) => {
235
- tracing:: debug!( "Caught error while processing client {e}" ) ;
236
- }
237
- }
238
- return Err ( e) ;
239
- }
240
- }
273
+ let listener = unsafe { OwnedFd :: from_raw_fd ( fd) } . into_socketlike ( ) ;
274
+ // In the socket case, we will process the initialization error later.
275
+ ( Some ( listener) , init_res)
241
276
}
242
277
} ;
243
278
244
279
if let Some ( listener) = listener {
245
- let ( shutdown_send, shutdown_recv) = tokio:: sync:: oneshot:: channel ( ) ;
246
- ( * SHUTDOWN_SIGNAL ) . lock ( ) . unwrap ( ) . replace ( shutdown_send) ;
247
-
248
- let listener = UnixListener :: from_std ( listener) ?;
249
-
250
280
// On success, we spawn a helper task that just responds with
251
281
// sucess to clients that connect via the socket. In the future,
252
282
// perhaps we'll expose an API here.
253
283
tracing:: debug!( "Spawning acknowledgement task" ) ;
254
- tokio:: task:: spawn ( async { process_clients_with_ok ( listener, shutdown_recv) . await } ) ;
284
+ match init_res {
285
+ Ok ( ( ) ) => {
286
+ std:: thread:: spawn ( move || run_acknowledgement_worker ( listener) ) ;
287
+ }
288
+ Err ( e) => {
289
+ let err_copy = anyhow:: format_err!( "{e}" ) ;
290
+ let r = std:: thread:: spawn ( move || {
291
+ if let Err ( suberr) = process_one_client ( listener, err_copy) {
292
+ tracing:: warn!( "Failed to respond to client: {suberr}" )
293
+ }
294
+ } ) ;
295
+ // Block until we've written the reply to the client;
296
+ if let Err ( e) = r. join ( ) {
297
+ tracing:: warn!( "Failed to join response thread: {e:?}" ) ;
298
+ }
299
+ // And finally propagate out the error
300
+ return Err ( e) ;
301
+ }
302
+ } ;
303
+ } else {
304
+ init_res?;
255
305
}
256
306
257
307
tracing:: debug!( "Entering daemon mainloop" ) ;
0 commit comments