|
| 1 | +/*! Exercise sending shared memory via Unix domain sockets. |
| 2 | +
|
| 3 | +This isn't really a test of the `wgpu-remote` crate's |
| 4 | +functionality; it's a verification that the system actually |
| 5 | +behaves as `wgpu-remote` expects. |
| 6 | +
|
| 7 | +If it turns out that all Unix systems behave identically, then |
| 8 | +this is a waste of time. But if it turns out that there are bugs, |
| 9 | +quirks, or limits, then this serves to detect and document them. |
| 10 | +
|
| 11 | +*/ |
| 12 | + |
| 13 | +use std::os::fd::{self, AsRawFd, FromRawFd}; |
| 14 | + |
| 15 | +use nix::sys::{memfd, mman, socket, wait}; |
| 16 | +use nix::unistd; |
| 17 | + |
| 18 | +fn main() { |
| 19 | + let (sender_socket, receiver_socket) = socket::socketpair( |
| 20 | + socket::AddressFamily::Unix, |
| 21 | + socket::SockType::Stream, |
| 22 | + None, |
| 23 | + socket::SockFlag::empty(), |
| 24 | + ).unwrap(); |
| 25 | + |
| 26 | + // Start the sender. |
| 27 | + // Safety: we are single-threaded. |
| 28 | + let fork = unsafe { unistd::fork() }.unwrap(); |
| 29 | + let unistd::ForkResult::Parent { child: sender_pid } = fork else { |
| 30 | + drop(receiver_socket); |
| 31 | + sender(sender_socket); |
| 32 | + // never returns |
| 33 | + }; |
| 34 | + |
| 35 | + // Start the receiver. |
| 36 | + // Safety: we are single-threaded. |
| 37 | + let fork = unsafe { unistd::fork() }.unwrap(); |
| 38 | + let unistd::ForkResult::Parent { child: receiver_pid } = fork else { |
| 39 | + drop(sender_socket); |
| 40 | + receiver(receiver_socket); |
| 41 | + // never returns |
| 42 | + }; |
| 43 | + |
| 44 | + drop(sender_socket); |
| 45 | + drop(receiver_socket); |
| 46 | + |
| 47 | + let status = wait::waitpid(sender_pid, None).unwrap(); |
| 48 | + assert_eq!(status, wait::WaitStatus::Exited(sender_pid, 42)); |
| 49 | + |
| 50 | + let status = wait::waitpid(receiver_pid, None).unwrap(); |
| 51 | + assert_eq!(status, wait::WaitStatus::Exited(receiver_pid, 43)); |
| 52 | +} |
| 53 | + |
| 54 | +const LEN: usize = 10; |
| 55 | + |
| 56 | +fn sender(socket: fd::OwnedFd) -> ! { |
| 57 | + // Create a file descriptor referring to zeroed memory. |
| 58 | + // |
| 59 | + // The `nix` source code suggests `memfd_create` is available on |
| 60 | + // Linux, Android, and FreeBSD. |
| 61 | + let mem_fd = memfd::memfd_create( |
| 62 | + c"unix-send-shmem", |
| 63 | + memfd::MemFdCreateFlag::empty(), |
| 64 | + ).unwrap(); |
| 65 | + |
| 66 | + // Set the file's size. |
| 67 | + unistd::ftruncate(&mem_fd, LEN as _).unwrap(); |
| 68 | + |
| 69 | + // Map the memory file descriptor into our address space. |
| 70 | + // |
| 71 | + // Safety: We're not requesting an address, our offset is aligned, |
| 72 | + // and our flags are reasonable. |
| 73 | + let mapped = unsafe { |
| 74 | + mman::mmap( |
| 75 | + None, |
| 76 | + std::num::NonZeroUsize::new(LEN).unwrap(), |
| 77 | + mman::ProtFlags::PROT_READ | mman::ProtFlags::PROT_WRITE, |
| 78 | + mman::MapFlags::MAP_SHARED, |
| 79 | + &mem_fd, |
| 80 | + 0, |
| 81 | + ).unwrap() |
| 82 | + }; |
| 83 | + |
| 84 | + // Write a message to the memory. |
| 85 | + // |
| 86 | + // Safety: the memory is there and zeroed, and `u8` has no |
| 87 | + // requirement alignments. |
| 88 | + let mapped = unsafe { mapped.cast::<[u8; LEN]>().as_mut() }; |
| 89 | + mapped.copy_from_slice(b"Greetings!"); |
| 90 | + |
| 91 | + // Send the file descriptor to the other process. |
| 92 | + // |
| 93 | + // The unix(7) man page for Linux says: |
| 94 | + // |
| 95 | + // At least one byte of real data should be sent when sending ancillary |
| 96 | + // data. On Linux, this is required to successfully send ancillary data |
| 97 | + // over a UNIX domain stream socket. When sending ancillary data over a |
| 98 | + // UNIX domain datagram socket, it is not necessary on Linux to send any |
| 99 | + // accompanying real data. However, portable applications should also in‐ |
| 100 | + // clude at least one byte of real data when sending ancillary data over a |
| 101 | + // datagram socket. |
| 102 | + socket::sendmsg::<()>( |
| 103 | + socket.as_raw_fd(), |
| 104 | + &[std::io::IoSlice::new(b"X")], // one byte of "real" data |
| 105 | + &[socket::ControlMessage::ScmRights(&[mem_fd.as_raw_fd()])], |
| 106 | + socket::MsgFlags::empty(), |
| 107 | + None, // address |
| 108 | + ).unwrap(); |
| 109 | + |
| 110 | + std::process::exit(42); |
| 111 | +} |
| 112 | + |
| 113 | +fn receiver(socket: fd::OwnedFd) -> ! { |
| 114 | + // Now do a recvmsg that accepts one byte of real data. |
| 115 | + let mut data_buf = [0_u8; 1]; |
| 116 | + let mut slices = [std::io::IoSliceMut::new(&mut data_buf)]; |
| 117 | + let mut fd_buf = nix::cmsg_space!(fd::RawFd); |
| 118 | + let msg = socket::recvmsg::<()>( |
| 119 | + socket.as_raw_fd(), |
| 120 | + &mut slices, // iov (ordinary bytes) |
| 121 | + Some(&mut fd_buf), // cmsg_buffer |
| 122 | + socket::MsgFlags::empty(), |
| 123 | + ).unwrap(); |
| 124 | + |
| 125 | + // Get the memory file descriptor out of the message. |
| 126 | + assert_eq!(msg.bytes, 1); |
| 127 | + let mut cmsgs = msg.cmsgs().unwrap(); |
| 128 | + let mut fds; |
| 129 | + match cmsgs.next() { |
| 130 | + Some(socket::ControlMessageOwned::ScmRights(f)) => { fds = f; } |
| 131 | + Some(other) => panic!("Got unexpected control message: {other:#?}"), |
| 132 | + None => panic!("didn't get any control messages"), |
| 133 | + } |
| 134 | + assert_eq!(cmsgs.next(), None); |
| 135 | + let raw_mem_fd = fds.pop().unwrap(); |
| 136 | + // Safety: we just got this fd from a control message, so it should be open. |
| 137 | + let mem_fd = unsafe { fd::OwnedFd::from_raw_fd(raw_mem_fd) }; |
| 138 | + assert!(fds.is_empty()); |
| 139 | + |
| 140 | + // Check the byte that was transmitted. |
| 141 | + assert_eq!(&slices[0][..1], b"X"); |
| 142 | + |
| 143 | + // Map the memory file descriptor into our address space. |
| 144 | + // |
| 145 | + // Safety: We're not requesting an address, our offset is aligned, |
| 146 | + // and our flags are reasonable. |
| 147 | + let mapped = unsafe { |
| 148 | + mman::mmap( |
| 149 | + None, |
| 150 | + std::num::NonZeroUsize::new(LEN).unwrap(), |
| 151 | + mman::ProtFlags::PROT_READ, |
| 152 | + mman::MapFlags::MAP_SHARED, |
| 153 | + &mem_fd, |
| 154 | + 0, |
| 155 | + ).unwrap() |
| 156 | + }; |
| 157 | + |
| 158 | + // Check the message in the memory. |
| 159 | + // |
| 160 | + // Safety: the memory is there and initialized, and `u8` has no |
| 161 | + // requirement alignments. |
| 162 | + let mapped = unsafe { mapped.cast::<[u8; LEN]>().as_ref() }; |
| 163 | + assert_eq!(mapped, b"Greetings!"); |
| 164 | + |
| 165 | + std::process::exit(43); |
| 166 | +} |
0 commit comments