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