Skip to content

Commit 635ae60

Browse files
committed
Add a test of sending a memory file descriptor over a socket.
1 parent 23f0db8 commit 635ae60

3 files changed

Lines changed: 188 additions & 0 deletions

File tree

Cargo.lock

Lines changed: 14 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

wgpu-remote/Cargo.toml

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,5 +25,13 @@ optional = true
2525
version = "1.0"
2626
features = ["derive"]
2727

28+
[target.'cfg(unix)'.dev-dependencies]
29+
nix = { version = "0.29", features = ["fs", "mman", "process", "socket", "uio"] }
30+
31+
[[test]]
32+
name = "unix-send-shmem"
33+
path = "tests/unix-send-shmem.rs"
34+
harness = false
35+
2836
[lints]
2937
workspace = true
Lines changed: 166 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,166 @@
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

Comments
 (0)