• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 // Copyright 2017 The ChromiumOS Authors
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
4 
5 //! Used to send and receive messages with file descriptors on sockets that accept control messages
6 //! (e.g. Unix domain sockets).
7 
8 use std::fs::File;
9 use std::io::IoSlice;
10 use std::io::IoSliceMut;
11 use std::mem::size_of;
12 use std::mem::MaybeUninit;
13 use std::os::unix::io::AsRawFd;
14 use std::os::unix::io::FromRawFd;
15 use std::os::unix::io::RawFd;
16 use std::os::unix::net::UnixDatagram;
17 use std::os::unix::net::UnixStream;
18 use std::ptr::copy_nonoverlapping;
19 use std::ptr::null_mut;
20 use std::ptr::write_unaligned;
21 use std::slice;
22 
23 use data_model::IoBufMut;
24 use data_model::VolatileSlice;
25 use libc::c_long;
26 use libc::c_void;
27 use libc::cmsghdr;
28 use libc::iovec;
29 use libc::msghdr;
30 use libc::recvmsg;
31 use libc::sendmsg;
32 use libc::MSG_NOSIGNAL;
33 use libc::SCM_RIGHTS;
34 use libc::SOL_SOCKET;
35 
36 use super::net::UnixSeqpacket;
37 use super::Error;
38 use super::Result;
39 use super::StreamChannel;
40 use crate::AsRawDescriptor;
41 
42 // Each of the following macros performs the same function as their C counterparts. They are each
43 // macros because they are used to size statically allocated arrays.
44 
45 macro_rules! CMSG_ALIGN {
46     ($len:expr) => {
47         ((($len) as usize) + size_of::<c_long>() - 1) & !(size_of::<c_long>() - 1)
48     };
49 }
50 
51 macro_rules! CMSG_SPACE {
52     ($len:expr) => {
53         size_of::<cmsghdr>() + CMSG_ALIGN!($len)
54     };
55 }
56 
57 macro_rules! CMSG_LEN {
58     ($len:expr) => {
59         size_of::<cmsghdr>() + (($len) as usize)
60     };
61 }
62 
63 // This function (macro in the C version) is not used in any compile time constant slots, so is just
64 // an ordinary function. The returned pointer is hard coded to be RawFd because that's all that this
65 // module supports.
66 #[allow(non_snake_case)]
67 #[inline(always)]
CMSG_DATA(cmsg_buffer: *mut cmsghdr) -> *mut RawFd68 fn CMSG_DATA(cmsg_buffer: *mut cmsghdr) -> *mut RawFd {
69     // Essentially returns a pointer to just past the header.
70     cmsg_buffer.wrapping_offset(1) as *mut RawFd
71 }
72 
73 // This function is like CMSG_NEXT, but safer because it reads only from references, although it
74 // does some pointer arithmetic on cmsg_ptr.
75 #[allow(clippy::cast_ptr_alignment)]
get_next_cmsg(msghdr: &msghdr, cmsg: &cmsghdr, cmsg_ptr: *mut cmsghdr) -> *mut cmsghdr76 fn get_next_cmsg(msghdr: &msghdr, cmsg: &cmsghdr, cmsg_ptr: *mut cmsghdr) -> *mut cmsghdr {
77     let next_cmsg = (cmsg_ptr as *mut u8).wrapping_add(CMSG_ALIGN!(cmsg.cmsg_len)) as *mut cmsghdr;
78     if next_cmsg
79         .wrapping_offset(1)
80         .wrapping_sub(msghdr.msg_control as usize) as usize
81         > msghdr.msg_controllen as usize
82     {
83         null_mut()
84     } else {
85         next_cmsg
86     }
87 }
88 
89 const CMSG_BUFFER_INLINE_CAPACITY: usize = CMSG_SPACE!(size_of::<RawFd>() * 32);
90 
91 enum CmsgBuffer {
92     Inline([u64; (CMSG_BUFFER_INLINE_CAPACITY + 7) / 8]),
93     Heap(Box<[cmsghdr]>),
94 }
95 
96 impl CmsgBuffer {
with_capacity(capacity: usize) -> CmsgBuffer97     fn with_capacity(capacity: usize) -> CmsgBuffer {
98         let cap_in_cmsghdr_units =
99             (capacity.checked_add(size_of::<cmsghdr>()).unwrap() - 1) / size_of::<cmsghdr>();
100         if capacity <= CMSG_BUFFER_INLINE_CAPACITY {
101             CmsgBuffer::Inline([0u64; (CMSG_BUFFER_INLINE_CAPACITY + 7) / 8])
102         } else {
103             CmsgBuffer::Heap(
104                 vec![
105                     // Safe because cmsghdr only contains primitive types for
106                     // which zero initialization is valid.
107                     unsafe { MaybeUninit::<cmsghdr>::zeroed().assume_init() };
108                     cap_in_cmsghdr_units
109                 ]
110                 .into_boxed_slice(),
111             )
112         }
113     }
114 
as_mut_ptr(&mut self) -> *mut cmsghdr115     fn as_mut_ptr(&mut self) -> *mut cmsghdr {
116         match self {
117             CmsgBuffer::Inline(a) => a.as_mut_ptr() as *mut cmsghdr,
118             CmsgBuffer::Heap(a) => a.as_mut_ptr(),
119         }
120     }
121 }
122 
123 // Musl requires a try_into when assigning to msg_iovlen and msg_controllen
124 // that is unnecessary when compiling for glibc.
125 #[allow(clippy::useless_conversion)]
raw_sendmsg<D: AsIobuf>(fd: RawFd, out_data: &[D], out_fds: &[RawFd]) -> Result<usize>126 fn raw_sendmsg<D: AsIobuf>(fd: RawFd, out_data: &[D], out_fds: &[RawFd]) -> Result<usize> {
127     let cmsg_capacity = CMSG_SPACE!(size_of::<RawFd>() * out_fds.len());
128     let mut cmsg_buffer = CmsgBuffer::with_capacity(cmsg_capacity);
129 
130     let iovec = AsIobuf::as_iobuf_slice(out_data);
131 
132     // msghdr on musl has private __pad1 and __pad2 fields that cannot be initialized.
133     // Safe because msghdr only contains primitive types for which zero
134     // initialization is valid.
135     let mut msg: msghdr = unsafe { MaybeUninit::zeroed().assume_init() };
136     msg.msg_iov = iovec.as_ptr() as *mut iovec;
137     msg.msg_iovlen = iovec.len().try_into().unwrap();
138 
139     if !out_fds.is_empty() {
140         // msghdr on musl has an extra __pad1 field, initialize the whole struct to zero.
141         // Safe because cmsghdr only contains primitive types for which zero
142         // initialization is valid.
143         let mut cmsg: cmsghdr = unsafe { MaybeUninit::zeroed().assume_init() };
144         cmsg.cmsg_len = CMSG_LEN!(size_of::<RawFd>() * out_fds.len())
145             .try_into()
146             .unwrap();
147         cmsg.cmsg_level = SOL_SOCKET;
148         cmsg.cmsg_type = SCM_RIGHTS;
149         unsafe {
150             // Safe because cmsg_buffer was allocated to be large enough to contain cmsghdr.
151             write_unaligned(cmsg_buffer.as_mut_ptr() as *mut cmsghdr, cmsg);
152             // Safe because the cmsg_buffer was allocated to be large enough to hold out_fds.len()
153             // file descriptors.
154             copy_nonoverlapping(
155                 out_fds.as_ptr(),
156                 CMSG_DATA(cmsg_buffer.as_mut_ptr()),
157                 out_fds.len(),
158             );
159         }
160 
161         msg.msg_control = cmsg_buffer.as_mut_ptr() as *mut c_void;
162         msg.msg_controllen = cmsg_capacity.try_into().unwrap();
163     }
164 
165     // Safe because the msghdr was properly constructed from valid (or null) pointers of the
166     // indicated length and we check the return value.
167     let write_count = unsafe { sendmsg(fd, &msg, MSG_NOSIGNAL) };
168 
169     if write_count == -1 {
170         Err(Error::last())
171     } else {
172         Ok(write_count as usize)
173     }
174 }
175 
176 // Musl requires a try_into when assigning to msg_iovlen, msg_controllen and
177 // cmsg_len that is unnecessary when compiling for glibc.
178 #[allow(clippy::useless_conversion)]
raw_recvmsg(fd: RawFd, iovs: &mut [IoSliceMut], in_fds: &mut [RawFd]) -> Result<(usize, usize)>179 fn raw_recvmsg(fd: RawFd, iovs: &mut [IoSliceMut], in_fds: &mut [RawFd]) -> Result<(usize, usize)> {
180     let cmsg_capacity = CMSG_SPACE!(size_of::<RawFd>() * in_fds.len());
181     let mut cmsg_buffer = CmsgBuffer::with_capacity(cmsg_capacity);
182 
183     // msghdr on musl has private __pad1 and __pad2 fields that cannot be initialized.
184     // Safe because msghdr only contains primitive types for which zero
185     // initialization is valid.
186     let mut msg: msghdr = unsafe { MaybeUninit::zeroed().assume_init() };
187     msg.msg_iov = iovs.as_mut_ptr() as *mut iovec;
188     msg.msg_iovlen = iovs.len().try_into().unwrap();
189 
190     if !in_fds.is_empty() {
191         msg.msg_control = cmsg_buffer.as_mut_ptr() as *mut c_void;
192         msg.msg_controllen = cmsg_capacity.try_into().unwrap();
193     }
194 
195     // Safe because the msghdr was properly constructed from valid (or null) pointers of the
196     // indicated length and we check the return value.
197     let total_read = unsafe { recvmsg(fd, &mut msg, 0) };
198 
199     if total_read == -1 {
200         return Err(Error::last());
201     }
202 
203     if total_read == 0 && (msg.msg_controllen as usize) < size_of::<cmsghdr>() {
204         return Ok((0, 0));
205     }
206 
207     let mut cmsg_ptr = msg.msg_control as *mut cmsghdr;
208     let mut in_fds_count = 0;
209     while !cmsg_ptr.is_null() {
210         // Safe because we checked that cmsg_ptr was non-null, and the loop is constructed such that
211         // that only happens when there is at least sizeof(cmsghdr) space after the pointer to read.
212         let cmsg = unsafe { (cmsg_ptr as *mut cmsghdr).read_unaligned() };
213 
214         if cmsg.cmsg_level == SOL_SOCKET && cmsg.cmsg_type == SCM_RIGHTS {
215             let fd_count = (cmsg.cmsg_len as usize - CMSG_LEN!(0)) / size_of::<RawFd>();
216             unsafe {
217                 copy_nonoverlapping(
218                     CMSG_DATA(cmsg_ptr),
219                     in_fds[in_fds_count..(in_fds_count + fd_count)].as_mut_ptr(),
220                     fd_count,
221                 );
222             }
223             in_fds_count += fd_count;
224         }
225 
226         cmsg_ptr = get_next_cmsg(&msg, &cmsg, cmsg_ptr);
227     }
228 
229     Ok((total_read as usize, in_fds_count))
230 }
231 
232 /// The maximum number of FDs that can be sent in a single send.
233 pub const SCM_SOCKET_MAX_FD_COUNT: usize = 253;
234 
235 /// Trait for file descriptors can send and receive socket control messages via `sendmsg` and
236 /// `recvmsg`.
237 pub trait ScmSocket {
238     /// Gets the file descriptor of this socket.
socket_fd(&self) -> RawFd239     fn socket_fd(&self) -> RawFd;
240 
241     /// Sends the given data and file descriptor over the socket.
242     ///
243     /// On success, returns the number of bytes sent.
244     ///
245     /// # Arguments
246     ///
247     /// * `buf` - A buffer of data to send on the `socket`.
248     /// * `fd` - A file descriptors to be sent.
send_with_fd<D: AsIobuf>(&self, buf: &[D], fd: RawFd) -> Result<usize>249     fn send_with_fd<D: AsIobuf>(&self, buf: &[D], fd: RawFd) -> Result<usize> {
250         self.send_with_fds(buf, &[fd])
251     }
252 
253     /// Sends the given data and file descriptors over the socket.
254     ///
255     /// On success, returns the number of bytes sent.
256     ///
257     /// # Arguments
258     ///
259     /// * `buf` - A buffer of data to send on the `socket`.
260     /// * `fds` - A list of file descriptors to be sent.
send_with_fds<D: AsIobuf>(&self, buf: &[D], fd: &[RawFd]) -> Result<usize>261     fn send_with_fds<D: AsIobuf>(&self, buf: &[D], fd: &[RawFd]) -> Result<usize> {
262         raw_sendmsg(self.socket_fd(), buf, fd)
263     }
264 
265     /// Sends the given data and file descriptor over the socket.
266     ///
267     /// On success, returns the number of bytes sent.
268     ///
269     /// # Arguments
270     ///
271     /// * `bufs` - A slice of slices of data to send on the `socket`.
272     /// * `fd` - A file descriptors to be sent.
send_bufs_with_fd(&self, bufs: &[IoSlice], fd: RawFd) -> Result<usize>273     fn send_bufs_with_fd(&self, bufs: &[IoSlice], fd: RawFd) -> Result<usize> {
274         self.send_bufs_with_fds(bufs, &[fd])
275     }
276 
277     /// Sends the given data and file descriptors over the socket.
278     ///
279     /// On success, returns the number of bytes sent.
280     ///
281     /// # Arguments
282     ///
283     /// * `bufs` - A slice of slices of data to send on the `socket`.
284     /// * `fds` - A list of file descriptors to be sent.
send_bufs_with_fds(&self, bufs: &[IoSlice], fd: &[RawFd]) -> Result<usize>285     fn send_bufs_with_fds(&self, bufs: &[IoSlice], fd: &[RawFd]) -> Result<usize> {
286         raw_sendmsg(self.socket_fd(), bufs, fd)
287     }
288 
289     /// Receives data and potentially a file descriptor from the socket.
290     ///
291     /// On success, returns the number of bytes and an optional file descriptor.
292     ///
293     /// # Arguments
294     ///
295     /// * `buf` - A buffer to receive data from the socket.vm
recv_with_fd(&self, buf: IoSliceMut) -> Result<(usize, Option<File>)>296     fn recv_with_fd(&self, buf: IoSliceMut) -> Result<(usize, Option<File>)> {
297         let mut fd = [0];
298         let (read_count, fd_count) = self.recv_with_fds(buf, &mut fd)?;
299         let file = if fd_count == 0 {
300             None
301         } else {
302             // Safe because the first fd from recv_with_fds is owned by us and valid because this
303             // branch was taken.
304             Some(unsafe { File::from_raw_fd(fd[0]) })
305         };
306         Ok((read_count, file))
307     }
308 
309     /// Receives data and file descriptors from the socket.
310     ///
311     /// On success, returns the number of bytes and file descriptors received as a tuple
312     /// `(bytes count, files count)`.
313     ///
314     /// # Arguments
315     ///
316     /// * `buf` - A buffer to receive data from the socket.
317     /// * `fds` - A slice of `RawFd`s to put the received file descriptors into. On success, the
318     ///           number of valid file descriptors is indicated by the second element of the
319     ///           returned tuple. The caller owns these file descriptors, but they will not be
320     ///           closed on drop like a `File`-like type would be. It is recommended that each valid
321     ///           file descriptor gets wrapped in a drop type that closes it after this returns.
recv_with_fds(&self, buf: IoSliceMut, fds: &mut [RawFd]) -> Result<(usize, usize)>322     fn recv_with_fds(&self, buf: IoSliceMut, fds: &mut [RawFd]) -> Result<(usize, usize)> {
323         raw_recvmsg(self.socket_fd(), &mut [buf], fds)
324     }
325 
326     /// Receives data and file descriptors from the socket.
327     ///
328     /// On success, returns the number of bytes and file descriptors received as a tuple
329     /// `(bytes count, files count)`.
330     ///
331     /// # Arguments
332     ///
333     /// * `iovecs` - A slice of buffers to store received data.
334     /// * `offset` - An offset for `bufs`. The first `offset` bytes in `bufs` won't be touched.
335     ///              Returns an error if `offset` is larger than or equal to the total size of
336     ///              `bufs`.
337     /// * `fds` - A slice of `RawFd`s to put the received file descriptors into. On success, the
338     ///           number of valid file descriptors is indicated by the second element of the
339     ///           returned tuple. The caller owns these file descriptors, but they will not be
340     ///           closed on drop like a `File`-like type would be. It is recommended that each valid
341     ///           file descriptor gets wrapped in a drop type that closes it after this returns.
recv_iovecs_with_fds( &self, iovecs: &mut [IoSliceMut], fds: &mut [RawFd], ) -> Result<(usize, usize)>342     fn recv_iovecs_with_fds(
343         &self,
344         iovecs: &mut [IoSliceMut],
345         fds: &mut [RawFd],
346     ) -> Result<(usize, usize)> {
347         raw_recvmsg(self.socket_fd(), iovecs, fds)
348     }
349 }
350 
351 impl ScmSocket for UnixDatagram {
socket_fd(&self) -> RawFd352     fn socket_fd(&self) -> RawFd {
353         self.as_raw_fd()
354     }
355 }
356 
357 impl ScmSocket for UnixStream {
socket_fd(&self) -> RawFd358     fn socket_fd(&self) -> RawFd {
359         self.as_raw_fd()
360     }
361 }
362 
363 impl ScmSocket for UnixSeqpacket {
socket_fd(&self) -> RawFd364     fn socket_fd(&self) -> RawFd {
365         self.as_raw_descriptor()
366     }
367 }
368 
369 impl ScmSocket for StreamChannel {
socket_fd(&self) -> RawFd370     fn socket_fd(&self) -> RawFd {
371         self.as_raw_fd()
372     }
373 }
374 
375 /// Trait for types that can be converted into an `iovec` that can be referenced by a syscall for
376 /// the lifetime of this object.
377 ///
378 /// # Safety
379 /// This trait is unsafe because interfaces that use this trait depend on the base pointer and size
380 /// being accurate.
381 pub unsafe trait AsIobuf: Sized {
382     /// Returns a `iovec` that describes a contiguous region of memory.
as_iobuf(&self) -> iovec383     fn as_iobuf(&self) -> iovec;
384 
385     /// Returns a slice of `iovec`s that each describe a contiguous region of memory.
386     #[allow(clippy::wrong_self_convention)]
as_iobuf_slice(bufs: &[Self]) -> &[iovec]387     fn as_iobuf_slice(bufs: &[Self]) -> &[iovec];
388 }
389 
390 // Safe because there are no other mutable references to the memory described by `IoSlice` and it is
391 // guaranteed to be ABI-compatible with `iovec`.
392 unsafe impl<'a> AsIobuf for IoSlice<'a> {
as_iobuf(&self) -> iovec393     fn as_iobuf(&self) -> iovec {
394         iovec {
395             iov_base: self.as_ptr() as *mut c_void,
396             iov_len: self.len(),
397         }
398     }
399 
as_iobuf_slice(bufs: &[Self]) -> &[iovec]400     fn as_iobuf_slice(bufs: &[Self]) -> &[iovec] {
401         // Safe because `IoSlice` is guaranteed to be ABI-compatible with `iovec`.
402         unsafe { slice::from_raw_parts(bufs.as_ptr() as *const iovec, bufs.len()) }
403     }
404 }
405 
406 // Safe because there are no other references to the memory described by `IoSliceMut` and it is
407 // guaranteed to be ABI-compatible with `iovec`.
408 unsafe impl<'a> AsIobuf for IoSliceMut<'a> {
as_iobuf(&self) -> iovec409     fn as_iobuf(&self) -> iovec {
410         iovec {
411             iov_base: self.as_ptr() as *mut c_void,
412             iov_len: self.len(),
413         }
414     }
415 
as_iobuf_slice(bufs: &[Self]) -> &[iovec]416     fn as_iobuf_slice(bufs: &[Self]) -> &[iovec] {
417         // Safe because `IoSliceMut` is guaranteed to be ABI-compatible with `iovec`.
418         unsafe { slice::from_raw_parts(bufs.as_ptr() as *const iovec, bufs.len()) }
419     }
420 }
421 
422 // Safe because volatile slices are only ever accessed with other volatile interfaces and the
423 // pointer and size are guaranteed to be accurate.
424 unsafe impl<'a> AsIobuf for VolatileSlice<'a> {
as_iobuf(&self) -> iovec425     fn as_iobuf(&self) -> iovec {
426         *self.as_iobuf().as_ref()
427     }
428 
as_iobuf_slice(bufs: &[Self]) -> &[iovec]429     fn as_iobuf_slice(bufs: &[Self]) -> &[iovec] {
430         IoBufMut::as_iobufs(VolatileSlice::as_iobufs(bufs))
431     }
432 }
433 
434 #[cfg(test)]
435 mod tests {
436     use std::io::Write;
437     use std::mem::size_of;
438     use std::os::raw::c_long;
439     use std::os::unix::net::UnixDatagram;
440     use std::slice::from_raw_parts;
441 
442     use libc::cmsghdr;
443 
444     use super::*;
445     use crate::AsRawDescriptor;
446     use crate::Event;
447     use crate::EventExt;
448 
449     // Doing this as a macro makes it easier to see the line if it fails
450     macro_rules! CMSG_SPACE_TEST {
451         ($len:literal) => {
452             assert_eq!(
453                 CMSG_SPACE!(size_of::<[RawFd; $len]>()) as libc::c_uint,
454                 unsafe { libc::CMSG_SPACE(size_of::<[RawFd; $len]>() as libc::c_uint) }
455             );
456         };
457     }
458 
459     #[test]
460     #[allow(clippy::erasing_op, clippy::identity_op)]
buffer_len()461     fn buffer_len() {
462         CMSG_SPACE_TEST!(0);
463         CMSG_SPACE_TEST!(1);
464         CMSG_SPACE_TEST!(2);
465         CMSG_SPACE_TEST!(3);
466         CMSG_SPACE_TEST!(4);
467     }
468 
469     #[test]
send_recv_no_fd()470     fn send_recv_no_fd() {
471         let (s1, s2) = UnixDatagram::pair().expect("failed to create socket pair");
472 
473         let send_buf = [1u8, 1, 2, 21, 34, 55];
474         let ioslice = IoSlice::new(&send_buf);
475         let write_count = s1
476             .send_with_fds(&[ioslice], &[])
477             .expect("failed to send data");
478 
479         assert_eq!(write_count, 6);
480 
481         let mut buf = [0; 6];
482         let mut files = [0; 1];
483         let (read_count, file_count) = s2
484             .recv_with_fds(IoSliceMut::new(&mut buf), &mut files)
485             .expect("failed to recv data");
486 
487         assert_eq!(read_count, 6);
488         assert_eq!(file_count, 0);
489         assert_eq!(buf, [1, 1, 2, 21, 34, 55]);
490 
491         let write_count = s1
492             .send_bufs_with_fds(&[IoSlice::new(&send_buf[..])], &[])
493             .expect("failed to send data");
494 
495         assert_eq!(write_count, 6);
496         let (read_count, file_count) = s2
497             .recv_with_fds(IoSliceMut::new(&mut buf), &mut files)
498             .expect("failed to recv data");
499 
500         assert_eq!(read_count, 6);
501         assert_eq!(file_count, 0);
502         assert_eq!(buf, [1, 1, 2, 21, 34, 55]);
503     }
504 
505     #[test]
send_recv_only_fd()506     fn send_recv_only_fd() {
507         let (s1, s2) = UnixDatagram::pair().expect("failed to create socket pair");
508 
509         let evt = Event::new().expect("failed to create event");
510         let ioslice = IoSlice::new([].as_ref());
511         let write_count = s1
512             .send_with_fd(&[ioslice], evt.as_raw_descriptor())
513             .expect("failed to send fd");
514 
515         assert_eq!(write_count, 0);
516 
517         let mut buf = [];
518         let (read_count, file_opt) = s2
519             .recv_with_fd(IoSliceMut::new(&mut buf))
520             .expect("failed to recv fd");
521 
522         let mut file = file_opt.unwrap();
523 
524         assert_eq!(read_count, 0);
525         assert!(file.as_raw_fd() >= 0);
526         assert_ne!(file.as_raw_fd(), s1.as_raw_fd());
527         assert_ne!(file.as_raw_fd(), s2.as_raw_fd());
528         assert_ne!(file.as_raw_fd(), evt.as_raw_descriptor());
529 
530         file.write_all(unsafe { from_raw_parts(&1203u64 as *const u64 as *const u8, 8) })
531             .expect("failed to write to sent fd");
532 
533         assert_eq!(evt.read_count().expect("failed to read from event"), 1203);
534     }
535 
536     #[test]
send_recv_with_fd()537     fn send_recv_with_fd() {
538         let (s1, s2) = UnixDatagram::pair().expect("failed to create socket pair");
539 
540         let evt = Event::new().expect("failed to create event");
541         let ioslice = IoSlice::new([237].as_ref());
542         let write_count = s1
543             .send_with_fds(&[ioslice], &[evt.as_raw_descriptor()])
544             .expect("failed to send fd");
545 
546         assert_eq!(write_count, 1);
547 
548         let mut files = [0; 2];
549         let mut buf = [0u8];
550         let (read_count, file_count) = s2
551             .recv_with_fds(IoSliceMut::new(&mut buf), &mut files)
552             .expect("failed to recv fd");
553 
554         assert_eq!(read_count, 1);
555         assert_eq!(buf[0], 237);
556         assert_eq!(file_count, 1);
557         assert!(files[0] >= 0);
558         assert_ne!(files[0], s1.as_raw_fd());
559         assert_ne!(files[0], s2.as_raw_fd());
560         assert_ne!(files[0], evt.as_raw_descriptor());
561 
562         let mut file = unsafe { File::from_raw_fd(files[0]) };
563 
564         file.write_all(unsafe { from_raw_parts(&1203u64 as *const u64 as *const u8, 8) })
565             .expect("failed to write to sent fd");
566 
567         assert_eq!(evt.read_count().expect("failed to read from event"), 1203);
568     }
569 }
570