redox-wayland-compositor/crates/redox-wl-test-fd-passing/src/main.rs
Votre Nom 53e6626231 Initial commit: phases 1-3 du portage Wayland Rust pour Redox OS
Plan directeur 14 phases / 5 ans (REDOX_COSMIC_XWAYLAND_RS_PLAN.md).

Phase 1 — Audit Redox (docs/existing-redox-gui.md, 486 lignes) :
- Orbital, graphics-ipc (API DRM compatible Linux subset KMS), inputd, vesad
- relibc support : AF_UNIX, SCM_RIGHTS, shm_open, mmap, poll
- 3 manques identifiés : memfd_create, keymap XKB, AT-SPI

Phase 2 — Validation primitives sur Redox via redoxer (5 tests + 1 POC) :
- test-unix-socket : SOCK_STREAM Wayland-shaped roundtrip
- test-fd-passing : SCM_RIGHTS mono-process (artefact kernel)
- test-fd-passing-fork : SCM_RIGHTS multi-process (validation Wayland critique)
- test-shm-open : shm_open + mmap + persistance + unlink
- test-poll-multifd : poll() multiplexing + POLLHUP
- poc-pixels : datapath shm + SCM_RIGHTS bout en bout (10000 pixels ARGB)

Phase 3 — wayland-rs sur Redox (compile + runtime) :
- wayland-{scanner,backend,server,client} compilent pour x86_64-unknown-redox
  sans patch upstream (rustix supporte Redox via libc backend)
- test-handshake : server/client wl_registry handshake roundtrip
- test-shm-pipeline : pipeline complet (ListeningSocket Unix réel + fd passing
  via wl_shm.create_pool + wl_shm_pool + wl_buffer + wl_surface + commit +
  serveur lit pixels via fd reçu, validation pixel-perfect)

Verdict phase 3 : wayland-rs upstream est viable sur Redox out-of-the-box,
le port "Wayland sur Redox" est désormais un problème de compositor à écrire,
pas de stack à porter.

Prérequis build : redoxer (pas cargo direct, car CMSG_NXTHDR/CMSG_DATA
ne sont pas linkés autrement vers librelibc.a).

Leyoda 2026 – GPLv3
2026-05-08 17:41:55 +02:00

195 lines
6.8 KiB
Rust

//! Test 2 — fd passing via sendmsg/recvmsg with SCM_RIGHTS.
//!
//! This is THE critical primitive for Wayland: clients pass shm fds
//! (and dmabuf fds) to the compositor through this mechanism.
//!
//! relibc Redox (platform/redox/socket.rs:272,447,835,924) declares
//! support — this test validates the actual behaviour.
//!
//! Flow:
//! 1. socketpair(AF_UNIX, SOCK_STREAM)
//! 2. open a tmp file, write a known marker into it
//! 3. sendmsg the fd via SCM_RIGHTS through socket A
//! 4. recvmsg on socket B, extract the fd
//! 5. read from the received fd, check marker matches
//!
//! Single-process test (no fork needed): we send the fd to ourselves
//! through the socketpair to validate the mechanism works end-to-end.
use std::ffi::CString;
use std::io;
use std::mem::{self, MaybeUninit};
use std::os::fd::{AsRawFd, FromRawFd, OwnedFd, RawFd};
use std::process::ExitCode;
use std::ptr;
const MARKER: &[u8] = b"REDOX-FD-PASSING-OK-2026";
fn errno_str() -> String {
let e = io::Error::last_os_error();
format!("{e} (errno={})", e.raw_os_error().unwrap_or(0))
}
unsafe fn make_socketpair() -> Result<(OwnedFd, OwnedFd), String> {
let mut fds: [RawFd; 2] = [-1, -1];
let r = libc::socketpair(libc::AF_UNIX, libc::SOCK_STREAM, 0, fds.as_mut_ptr());
if r != 0 {
return Err(format!("socketpair: {}", errno_str()));
}
Ok((OwnedFd::from_raw_fd(fds[0]), OwnedFd::from_raw_fd(fds[1])))
}
unsafe fn open_tmp_with_marker() -> Result<OwnedFd, String> {
let path = CString::new("/tmp/test-02-fd-passing.bin").unwrap();
let fd = libc::open(
path.as_ptr(),
libc::O_RDWR | libc::O_CREAT | libc::O_TRUNC,
0o600,
);
if fd < 0 {
return Err(format!("open tmp: {}", errno_str()));
}
let n = libc::write(fd, MARKER.as_ptr() as *const _, MARKER.len());
if n != MARKER.len() as isize {
return Err(format!("write tmp: {}", errno_str()));
}
libc::lseek(fd, 0, libc::SEEK_SET);
Ok(OwnedFd::from_raw_fd(fd))
}
unsafe fn send_fd_over(socket: RawFd, fd_to_send: RawFd) -> Result<(), String> {
let mut iov_buf = [b'X'; 1];
let mut iov = libc::iovec {
iov_base: iov_buf.as_mut_ptr() as *mut _,
iov_len: 1,
};
let cmsg_space = libc::CMSG_SPACE(mem::size_of::<RawFd>() as u32) as usize;
let mut cmsg_buf = vec![0u8; cmsg_space];
let mut msg: libc::msghdr = mem::zeroed();
msg.msg_iov = &mut iov as *mut _;
msg.msg_iovlen = 1;
msg.msg_control = cmsg_buf.as_mut_ptr() as *mut _;
msg.msg_controllen = cmsg_space as _;
let cmsg = libc::CMSG_FIRSTHDR(&msg);
if cmsg.is_null() {
return Err("CMSG_FIRSTHDR null".into());
}
(*cmsg).cmsg_level = libc::SOL_SOCKET;
(*cmsg).cmsg_type = libc::SCM_RIGHTS;
(*cmsg).cmsg_len = libc::CMSG_LEN(mem::size_of::<RawFd>() as u32) as _;
let data_ptr = libc::CMSG_DATA(cmsg) as *mut RawFd;
ptr::write_unaligned(data_ptr, fd_to_send);
let n = libc::sendmsg(socket, &msg, 0);
if n < 0 {
return Err(format!("sendmsg: {}", errno_str()));
}
println!("[test-02] sendmsg sent {n} bytes + 1 fd via SCM_RIGHTS");
Ok(())
}
unsafe fn recv_fd_from(socket: RawFd) -> Result<OwnedFd, String> {
let mut iov_buf = [0u8; 1];
let mut iov = libc::iovec {
iov_base: iov_buf.as_mut_ptr() as *mut _,
iov_len: 1,
};
let cmsg_space = libc::CMSG_SPACE(mem::size_of::<RawFd>() as u32) as usize;
let mut cmsg_buf = vec![MaybeUninit::<u8>::uninit(); cmsg_space];
let mut msg: libc::msghdr = mem::zeroed();
msg.msg_iov = &mut iov as *mut _;
msg.msg_iovlen = 1;
msg.msg_control = cmsg_buf.as_mut_ptr() as *mut _;
msg.msg_controllen = cmsg_space as _;
let n = libc::recvmsg(socket, &mut msg, 0);
if n < 0 {
return Err(format!("recvmsg: {}", errno_str()));
}
println!("[test-02] recvmsg got {n} bytes + control of {} bytes", msg.msg_controllen);
let cmsg = libc::CMSG_FIRSTHDR(&msg);
if cmsg.is_null() {
return Err("no cmsg in received message".into());
}
if (*cmsg).cmsg_level != libc::SOL_SOCKET || (*cmsg).cmsg_type != libc::SCM_RIGHTS {
return Err(format!(
"unexpected cmsg level={} type={}",
(*cmsg).cmsg_level,
(*cmsg).cmsg_type
));
}
let data_ptr = libc::CMSG_DATA(cmsg) as *const RawFd;
let received_fd = ptr::read_unaligned(data_ptr);
if received_fd < 0 {
return Err(format!("received negative fd: {received_fd}"));
}
Ok(OwnedFd::from_raw_fd(received_fd))
}
fn run() -> Result<(), String> {
println!("[test-02] fd passing via SCM_RIGHTS");
unsafe {
let (sock_a, sock_b) = make_socketpair()?;
let tmp_fd = open_tmp_with_marker()?;
println!("[test-02] tmp fd = {}", tmp_fd.as_raw_fd());
// Strategy: send a dup'd fd, then close BOTH the original and the dup.
// If SCM_RIGHTS works per POSIX, the received fd is an independent
// reference to the open file and must remain readable.
let dup_fd = libc::dup(tmp_fd.as_raw_fd());
if dup_fd < 0 {
return Err(format!("dup: {}", errno_str()));
}
println!("[test-02] dup'd tmp_fd ({}) -> {dup_fd}", tmp_fd.as_raw_fd());
send_fd_over(sock_a.as_raw_fd(), dup_fd)?;
let received = recv_fd_from(sock_b.as_raw_fd())?;
let recv_raw = received.as_raw_fd();
println!("[test-02] received fd = {recv_raw}");
// Close BOTH the original and the dup. If SCM_RIGHTS gave us a real
// duplicate, the received fd must survive.
let orig_raw = tmp_fd.as_raw_fd();
drop(tmp_fd);
libc::close(dup_fd);
println!("[test-02] closed original fd ({orig_raw}) and dup ({dup_fd}); reading via received only");
libc::lseek(recv_raw, 0, libc::SEEK_SET);
let mut readback = vec![0u8; MARKER.len()];
let n = libc::read(recv_raw, readback.as_mut_ptr() as *mut _, readback.len());
if n != MARKER.len() as isize {
return Err(format!("read from received fd: only {n} bytes (errno {})",
io::Error::last_os_error().raw_os_error().unwrap_or(0)));
}
if readback != MARKER {
return Err(format!("marker mismatch: got {:?}", String::from_utf8_lossy(&readback)));
}
println!("[test-02] read {} bytes via received fd: marker matches", n);
// Cleanup tmp file
let path = std::ffi::CString::new("/tmp/test-02-fd-passing.bin").unwrap();
libc::unlink(path.as_ptr());
}
Ok(())
}
fn main() -> ExitCode {
match run() {
Ok(()) => {
println!("[test-02] PASS: fd passing works, received fd reads correct marker");
ExitCode::SUCCESS
}
Err(e) => {
eprintln!("[test-02] FAIL: {e}");
ExitCode::FAILURE
}
}
}