@@ -27,6 +27,8 @@ use std::env;
27
27
use std:: fmt:: { self , Display , Formatter , Write } ;
28
28
use std:: fs;
29
29
use std:: io:: { self , ErrorKind } ;
30
+ #[ cfg( feature = "fdstore" ) ]
31
+ use std:: os:: fd:: BorrowedFd ;
30
32
use std:: os:: unix:: io:: RawFd ;
31
33
use std:: os:: unix:: net:: UnixDatagram ;
32
34
use std:: process;
@@ -59,6 +61,15 @@ pub enum NotifyState<'a> {
59
61
WatchdogUsec ( u32 ) ,
60
62
/// Tells the service manager to extend the service timeout.
61
63
ExtendTimeoutUsec ( u32 ) ,
64
+ /// Tells the service manager to store attached file descriptors.
65
+ #[ cfg( feature = "fdstore" ) ]
66
+ FdStore ,
67
+ /// Tells the service manager to remove stored file descriptors.
68
+ #[ cfg( feature = "fdstore" ) ]
69
+ FdStoreRemove ,
70
+ /// Tells the service manager to use this name for the attached file descriptor.
71
+ #[ cfg( feature = "fdstore" ) ]
72
+ FdName ( & ' a str ) ,
62
73
/// Custom state.
63
74
Custom ( & ' a str ) ,
64
75
}
@@ -77,6 +88,12 @@ impl Display for NotifyState<'_> {
77
88
NotifyState :: WatchdogTrigger => write ! ( f, "WATCHDOG=trigger" ) ,
78
89
NotifyState :: WatchdogUsec ( usec) => write ! ( f, "WATCHDOG_USEC={}" , usec) ,
79
90
NotifyState :: ExtendTimeoutUsec ( usec) => write ! ( f, "EXTEND_TIMEOUT_USEC={}" , usec) ,
91
+ #[ cfg( feature = "fdstore" ) ]
92
+ NotifyState :: FdStore => write ! ( f, "FDSTORE=1" ) ,
93
+ #[ cfg( feature = "fdstore" ) ]
94
+ NotifyState :: FdStoreRemove => write ! ( f, "FDSTOREREMOVE=1" ) ,
95
+ #[ cfg( feature = "fdstore" ) ]
96
+ NotifyState :: FdName ( name) => write ! ( f, "FDNAME={}" , name) ,
80
97
NotifyState :: Custom ( state) => write ! ( f, "{}" , state) ,
81
98
}
82
99
}
@@ -110,9 +127,11 @@ pub fn booted() -> io::Result<bool> {
110
127
/// # Limitations
111
128
///
112
129
/// The implementation of this function is somewhat naive: it doesn't support
113
- /// sending notifications on behalf of other processes, doesn't pass file
114
- /// descriptors, doesn't send credentials, and does not increase the send
115
- /// buffer size. It's still useful, though, in usual situations.
130
+ /// sending notifications on behalf of other processes, doesn't send credentials,
131
+ /// and does not increase the send buffer size. It's still useful, though, in
132
+ /// usual situations.
133
+ ///
134
+ /// If you wish to send file descriptors, use the `notify_with_fds` function.
116
135
///
117
136
/// # Example
118
137
///
@@ -122,27 +141,94 @@ pub fn booted() -> io::Result<bool> {
122
141
/// let _ = sd_notify::notify(true, &[NotifyState::Ready]);
123
142
/// ```
124
143
pub fn notify ( unset_env : bool , state : & [ NotifyState ] ) -> io:: Result < ( ) > {
125
- let socket_path = match env :: var_os ( "NOTIFY_SOCKET" ) {
126
- Some ( path ) => path ,
127
- None => return Ok ( ( ) ) ,
144
+ let mut msg = String :: new ( ) ;
145
+ let Some ( sock ) = connect_notify_socket ( unset_env ) ? else {
146
+ return Ok ( ( ) ) ;
128
147
} ;
129
- if unset_env {
130
- env :: remove_var ( "NOTIFY_SOCKET" ) ;
148
+ for s in state {
149
+ let _ = writeln ! ( msg , "{}" , s ) ;
131
150
}
151
+ let len = sock. send ( msg. as_bytes ( ) ) ?;
152
+ if len != msg. len ( ) {
153
+ Err ( io:: Error :: new ( ErrorKind :: WriteZero , "incomplete write" ) )
154
+ } else {
155
+ Ok ( ( ) )
156
+ }
157
+ }
158
+
159
+ /// Sends the service manager a list of state changes with file descriptors.
160
+ ///
161
+ /// If the `unset_env` parameter is set, the `NOTIFY_SOCKET` environment variable
162
+ /// will be unset before returning. Further calls to `sd_notify` will fail, but
163
+ /// child processes will no longer inherit the variable.
164
+ ///
165
+ /// The notification mechanism involves sending a datagram to a Unix domain socket.
166
+ /// See [`sd_pid_notify_with_fds(3)`][sd_pid_notify_with_fds] for details.
167
+ ///
168
+ /// [sd_pid_notify_with_fds]: https://www.freedesktop.org/software/systemd/man/sd_notify.html
169
+ ///
170
+ /// # Limitations
171
+ ///
172
+ /// The implementation of this function is somewhat naive: it doesn't support
173
+ /// sending notifications on behalf of other processes, doesn't send credentials,
174
+ /// and does not increase the send buffer size. It's still useful, though, in
175
+ /// usual situations.
176
+ ///
177
+ /// # Example
178
+ ///
179
+ /// ```no_run
180
+ /// # use sd_notify::NotifyState;
181
+ /// # use std::os::fd::BorrowedFd;
182
+ /// #
183
+ /// # let fd = unsafe { BorrowedFd::borrow_raw(0) };
184
+ /// #
185
+ /// let _ = sd_notify::notify_with_fds(false, &[NotifyState::FdStore], &[fd]);
186
+ /// ```
187
+ #[ cfg( feature = "fdstore" ) ]
188
+ pub fn notify_with_fds (
189
+ unset_env : bool ,
190
+ state : & [ NotifyState ] ,
191
+ fds : & [ BorrowedFd < ' _ > ] ,
192
+ ) -> io:: Result < ( ) > {
193
+ use sendfd:: SendWithFd ;
132
194
133
195
let mut msg = String :: new ( ) ;
134
- let sock = UnixDatagram :: unbound ( ) ?;
196
+ let Some ( sock) = connect_notify_socket ( unset_env) ? else {
197
+ return Ok ( ( ) ) ;
198
+ } ;
135
199
for s in state {
136
200
let _ = writeln ! ( msg, "{}" , s) ;
137
201
}
138
- let len = sock. send_to ( msg. as_bytes ( ) , socket_path ) ?;
202
+ let len = sock. send_with_fd ( msg. as_bytes ( ) , borrowed_fd_slice ( fds ) ) ?;
139
203
if len != msg. len ( ) {
140
204
Err ( io:: Error :: new ( ErrorKind :: WriteZero , "incomplete write" ) )
141
205
} else {
142
206
Ok ( ( ) )
143
207
}
144
208
}
145
209
210
+ #[ cfg( feature = "fdstore" ) ]
211
+ fn borrowed_fd_slice < ' a > ( s : & ' a [ BorrowedFd < ' _ > ] ) -> & ' a [ RawFd ] {
212
+ // SAFETY: BorrowedFd is #[repr(transparent)] over RawFd (memory safety)
213
+ // and implements AsRawFd (lifetime safety).
214
+ // Required only because sendfd does not have i/o safety traits.
215
+ unsafe { std:: mem:: transmute ( s) }
216
+ }
217
+
218
+ fn connect_notify_socket ( unset_env : bool ) -> io:: Result < Option < UnixDatagram > > {
219
+ let Some ( socket_path) = env:: var_os ( "NOTIFY_SOCKET" ) else {
220
+ return Ok ( None ) ;
221
+ } ;
222
+
223
+ if unset_env {
224
+ env:: remove_var ( "NOTIFY_SOCKET" ) ;
225
+ }
226
+
227
+ let sock = UnixDatagram :: unbound ( ) ?;
228
+ sock. connect ( socket_path) ?;
229
+ Ok ( Some ( sock) )
230
+ }
231
+
146
232
/// Checks for file descriptors passed by the service manager for socket
147
233
/// activation.
148
234
///
@@ -162,16 +248,18 @@ pub fn notify(unset_env: bool, state: &[NotifyState]) -> io::Result<()> {
162
248
/// let socket = sd_notify::listen_fds().map(|mut fds| fds.next().expect("missing fd"));
163
249
/// ```
164
250
pub fn listen_fds ( ) -> io:: Result < impl Iterator < Item = RawFd > > {
165
- struct Guard ;
166
-
167
- impl Drop for Guard {
168
- fn drop ( & mut self ) {
169
- env:: remove_var ( "LISTEN_PID" ) ;
170
- env:: remove_var ( "LISTEN_FDS" ) ;
171
- }
172
- }
251
+ listen_fds_internal ( true )
252
+ }
173
253
174
- let _guard = Guard ;
254
+ fn listen_fds_internal ( unset_env : bool ) -> io:: Result < impl ExactSizeIterator < Item = RawFd > > {
255
+ let _guard1 = UnsetEnvGuard {
256
+ name : "LISTEN_PID" ,
257
+ unset_env,
258
+ } ;
259
+ let _guard2 = UnsetEnvGuard {
260
+ name : "LISTEN_FDS" ,
261
+ unset_env,
262
+ } ;
175
263
176
264
let listen_pid = if let Ok ( pid) = env:: var ( "LISTEN_PID" ) {
177
265
pid
@@ -209,6 +297,87 @@ pub fn listen_fds() -> io::Result<impl Iterator<Item = RawFd>> {
209
297
Ok ( listen_fds)
210
298
}
211
299
300
+ /// Checks for file descriptors passed by the service manager for socket
301
+ /// activation.
302
+ ///
303
+ /// The function returns an iterator over file descriptors, starting from
304
+ /// `SD_LISTEN_FDS_START`. The number of descriptors is obtained from the
305
+ /// `LISTEN_FDS` environment variable.
306
+ ///
307
+ /// If the `unset_env` parameter is set, the `LISTEN_PID`, `LISTEN_FDS` and
308
+ /// `LISTEN_FDNAMES` environment variable will be unset before returning.
309
+ /// Child processes will not see the fdnames passed to this process. This is
310
+ /// usually not necessary, as a process should only use the `LISTEN_FDS`
311
+ /// variable if it is the PID given in `LISTEN_PID`.
312
+ ///
313
+ /// Before returning, the file descriptors are set as `O_CLOEXEC`.
314
+ ///
315
+ /// See [`sd_listen_fds_with_names(3)`][sd_listen_fds_with_names] for details.
316
+ ///
317
+ /// [sd_listen_fds_with_names]: https://www.freedesktop.org/software/systemd/man/sd_listen_fds.html
318
+ ///
319
+ /// # Example
320
+ ///
321
+ /// ```no_run
322
+ /// let socket = sd_notify::listen_fds().map(|mut fds| fds.next().expect("missing fd"));
323
+ /// ```
324
+ pub fn listen_fds_with_names (
325
+ unset_env : bool ,
326
+ ) -> io:: Result < impl ExactSizeIterator < Item = ( RawFd , String ) > > {
327
+ let listen_fds = listen_fds_internal ( unset_env) ?;
328
+ let _guard = UnsetEnvGuard {
329
+ name : "LISTEN_FDNAMES" ,
330
+ unset_env,
331
+ } ;
332
+ zip_fds_with_names ( listen_fds, env:: var ( "LISTEN_FDNAMES" ) . ok ( ) )
333
+ }
334
+
335
+ /// Internal helper that is independent of listen_fds function, for testing purposes.
336
+ fn zip_fds_with_names (
337
+ listen_fds : impl ExactSizeIterator < Item = RawFd > ,
338
+ listen_fdnames : Option < String > ,
339
+ ) -> io:: Result < impl ExactSizeIterator < Item = ( RawFd , String ) > > {
340
+ let listen_fdnames = if let Some ( names) = listen_fdnames {
341
+ // systemd shouldn't provide an empty fdname element. However if it does, the
342
+ // sd_listen_fds_with_names function will return an empty string for that fd,
343
+ // as in the following C example:
344
+ //
345
+ // void main() {
346
+ // char **names;
347
+ // setenv("LISTEN_FDNAMES", "x::z", 1);
348
+ // int n = sd_listen_fds_with_names(0, &names);
349
+ // assert(*names[1] == 0);
350
+ // }
351
+ names. split ( ':' ) . map ( |x| x. to_owned ( ) ) . collect :: < Vec < _ > > ( )
352
+ } else {
353
+ let mut names = vec ! [ ] ;
354
+ names. resize ( listen_fds. len ( ) , "unknown" . to_string ( ) ) ;
355
+ names
356
+ } ;
357
+
358
+ if listen_fdnames. len ( ) == listen_fds. len ( ) {
359
+ Ok ( listen_fds. zip ( listen_fdnames) )
360
+ } else {
361
+ Err ( io:: Error :: new (
362
+ ErrorKind :: InvalidInput ,
363
+ "invalid LISTEN_FDNAMES" ,
364
+ ) )
365
+ }
366
+ }
367
+
368
+ struct UnsetEnvGuard {
369
+ name : & ' static str ,
370
+ unset_env : bool ,
371
+ }
372
+
373
+ impl Drop for UnsetEnvGuard {
374
+ fn drop ( & mut self ) {
375
+ if self . unset_env {
376
+ env:: remove_var ( self . name ) ;
377
+ }
378
+ }
379
+ }
380
+
212
381
fn fd_cloexec ( fd : u32 ) -> io:: Result < ( ) > {
213
382
let fd = RawFd :: try_from ( fd) . map_err ( |_| io:: Error :: from_raw_os_error ( ffi:: EBADF ) ) ?;
214
383
let flags = unsafe { ffi:: fcntl ( fd, ffi:: F_GETFD , 0 ) } ;
@@ -281,6 +450,7 @@ mod tests {
281
450
use super :: NotifyState ;
282
451
use std:: env;
283
452
use std:: fs;
453
+ use std:: os:: fd:: RawFd ;
284
454
use std:: os:: unix:: net:: UnixDatagram ;
285
455
use std:: path:: PathBuf ;
286
456
use std:: process;
@@ -353,6 +523,55 @@ mod tests {
353
523
assert ! ( env:: var_os( "LISTEN_FDS" ) . is_none( ) ) ;
354
524
}
355
525
526
+ #[ test]
527
+ fn listen_fds_with_names ( ) {
528
+ assert_eq ! (
529
+ super :: zip_fds_with_names( 3 as RawFd ..4 as RawFd , Some ( "omelette" . to_string( ) ) )
530
+ . unwrap( )
531
+ . collect:: <Vec <_>>( ) ,
532
+ vec![ ( 3 as RawFd , "omelette" . to_string( ) ) ]
533
+ ) ;
534
+
535
+ assert_eq ! (
536
+ super :: zip_fds_with_names(
537
+ 3 as RawFd ..5 as RawFd ,
538
+ Some ( "omelette:baguette" . to_string( ) )
539
+ )
540
+ . unwrap( )
541
+ . collect:: <Vec <_>>( ) ,
542
+ vec![
543
+ ( 3 as RawFd , "omelette" . to_string( ) ) ,
544
+ ( 4 as RawFd , "baguette" . to_string( ) )
545
+ ]
546
+ ) ;
547
+
548
+ // LISTEN_FDNAMES is cleared
549
+ assert_eq ! (
550
+ super :: zip_fds_with_names( 3 as RawFd ..4 as RawFd , None )
551
+ . unwrap( )
552
+ . next( ) ,
553
+ Some ( ( 3 as RawFd , "unknown" . to_string( ) ) )
554
+ ) ;
555
+
556
+ // LISTEN_FDNAMES is cleared, every fd should have the name "unknown"
557
+ assert_eq ! (
558
+ super :: zip_fds_with_names( 3 as RawFd ..5 as RawFd , None )
559
+ . unwrap( )
560
+ . collect:: <Vec <_>>( ) ,
561
+ vec![
562
+ ( 3 as RawFd , "unknown" . to_string( ) ) ,
563
+ ( 4 as RawFd , "unknown" . to_string( ) )
564
+ ] ,
565
+ ) ;
566
+
567
+ // Raise an error if LISTEN_FDNAMES has a different number of entries as fds
568
+ assert ! ( super :: zip_fds_with_names(
569
+ 3 as RawFd ..6 as RawFd ,
570
+ Some ( "omelette:baguette" . to_string( ) )
571
+ )
572
+ . is_err( ) ) ;
573
+ }
574
+
356
575
#[ test]
357
576
fn watchdog_enabled ( ) {
358
577
// test original logic: https://github.com/systemd/systemd/blob/f3376ee8fa28aab3f7edfad1ddfbcceca5bc841c/src/libsystemd/sd-daemon/sd-daemon.c#L632
0 commit comments