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
This commit is contained in:
commit
53e6626231
21 changed files with 3676 additions and 0 deletions
215
crates/redox-wl-test-fd-passing-fork/src/main.rs
Normal file
215
crates/redox-wl-test-fd-passing-fork/src/main.rs
Normal file
|
|
@ -0,0 +1,215 @@
|
|||
//! Test 2b — fd passing via SCM_RIGHTS, multi-process via fork().
|
||||
//!
|
||||
//! Mono-process test (test-02) revealed the kernel reuses fd numbers and
|
||||
//! does not duplicate the file table entry. This test validates whether
|
||||
//! SCM_RIGHTS works correctly *across* a process boundary, which is the
|
||||
//! actual Wayland use case.
|
||||
//!
|
||||
//! Flow:
|
||||
//! 1. Parent opens a tmp file with a marker
|
||||
//! 2. socketpair(AF_UNIX, SOCK_STREAM)
|
||||
//! 3. fork()
|
||||
//! 4. Parent: closes child end, sendmsg with the tmp_fd
|
||||
//! 5. Child: closes parent end, recvmsg, reads via received fd, exits with status 0 if marker matches
|
||||
//! 6. Parent: waitpid, returns child's exit status
|
||||
|
||||
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-FORK-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];
|
||||
if libc::socketpair(libc::AF_UNIX, libc::SOCK_STREAM, 0, fds.as_mut_ptr()) != 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-02b-fd-passing-fork.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);
|
||||
(*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()));
|
||||
}
|
||||
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()));
|
||||
}
|
||||
|
||||
let cmsg = libc::CMSG_FIRSTHDR(&msg);
|
||||
if cmsg.is_null() {
|
||||
return Err("no cmsg".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);
|
||||
Ok(OwnedFd::from_raw_fd(received_fd))
|
||||
}
|
||||
|
||||
unsafe fn child_main(child_sock: OwnedFd) -> ! {
|
||||
let raw_sock = child_sock.as_raw_fd();
|
||||
eprintln!("[test-02b CHILD pid={}] recvmsg on sock {}", libc::getpid(), raw_sock);
|
||||
let received = match recv_fd_from(raw_sock) {
|
||||
Ok(fd) => fd,
|
||||
Err(e) => {
|
||||
eprintln!("[test-02b CHILD] recvmsg fail: {e}");
|
||||
libc::_exit(2);
|
||||
}
|
||||
};
|
||||
let recv_raw = received.as_raw_fd();
|
||||
eprintln!("[test-02b CHILD] received fd={recv_raw}");
|
||||
|
||||
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 {
|
||||
eprintln!(
|
||||
"[test-02b CHILD] read returned {n}, errno={}",
|
||||
io::Error::last_os_error().raw_os_error().unwrap_or(0)
|
||||
);
|
||||
libc::_exit(3);
|
||||
}
|
||||
if readback != MARKER {
|
||||
eprintln!(
|
||||
"[test-02b CHILD] marker mismatch: {:?}",
|
||||
String::from_utf8_lossy(&readback)
|
||||
);
|
||||
libc::_exit(4);
|
||||
}
|
||||
eprintln!("[test-02b CHILD] marker matches, exit 0");
|
||||
libc::_exit(0);
|
||||
}
|
||||
|
||||
fn run() -> Result<(), String> {
|
||||
println!("[test-02b] fd passing via SCM_RIGHTS across fork()");
|
||||
|
||||
unsafe {
|
||||
let (sock_parent, sock_child) = make_socketpair()?;
|
||||
let tmp_fd = open_tmp_with_marker()?;
|
||||
println!("[test-02b PARENT] tmp fd={}, sockets {}/{}",
|
||||
tmp_fd.as_raw_fd(), sock_parent.as_raw_fd(), sock_child.as_raw_fd());
|
||||
|
||||
let pid = libc::fork();
|
||||
if pid < 0 {
|
||||
return Err(format!("fork: {}", errno_str()));
|
||||
}
|
||||
if pid == 0 {
|
||||
// Child: drop tmp_fd, drop parent socket, run child_main on its own socket
|
||||
drop(tmp_fd);
|
||||
drop(sock_parent);
|
||||
child_main(sock_child);
|
||||
}
|
||||
|
||||
// Parent path
|
||||
drop(sock_child);
|
||||
println!("[test-02b PARENT] forked child pid={pid}, sending fd");
|
||||
send_fd_over(sock_parent.as_raw_fd(), tmp_fd.as_raw_fd())?;
|
||||
drop(tmp_fd);
|
||||
drop(sock_parent);
|
||||
|
||||
let mut status: libc::c_int = 0;
|
||||
let r = libc::waitpid(pid, &mut status, 0);
|
||||
if r < 0 {
|
||||
return Err(format!("waitpid: {}", errno_str()));
|
||||
}
|
||||
let exited = libc::WIFEXITED(status);
|
||||
let code = libc::WEXITSTATUS(status);
|
||||
println!("[test-02b PARENT] child reaped: exited={exited} code={code}");
|
||||
|
||||
if !exited {
|
||||
return Err("child did not exit normally".into());
|
||||
}
|
||||
if code != 0 {
|
||||
return Err(format!("child exited with status {code}"));
|
||||
}
|
||||
|
||||
// Cleanup tmp file
|
||||
let path = std::ffi::CString::new("/tmp/test-02b-fd-passing-fork.bin").unwrap();
|
||||
libc::unlink(path.as_ptr());
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn main() -> ExitCode {
|
||||
match run() {
|
||||
Ok(()) => {
|
||||
println!("[test-02b] PASS: fd passed across fork() correctly");
|
||||
ExitCode::SUCCESS
|
||||
}
|
||||
Err(e) => {
|
||||
eprintln!("[test-02b] FAIL: {e}");
|
||||
ExitCode::FAILURE
|
||||
}
|
||||
}
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue