← luna

Commit 6ea82da

0
files
+0
added
-0
deleted
@@ -2,7 +2,7 @@
2 2
3 3 use std::ffi::c_void;
4 4 use std::os::fd::OwnedFd;
5 -use std::sync::Arc;
5 +use std::sync::{Arc, Mutex};
6 6
7 7 use wayland_server::{
8 8 protocol::{wl_buffer, wl_shm, wl_shm_pool},
@@ -10,80 +10,116 @@ use wayland_server::{
10 10 };
11 11
12 12 use crate::state::State;
13 +use crate::sync::LockExt;
13 14
14 15 /// Górny limit pojedynczego obrazu (RGBA) — chroni przed OOM wymuszonym przez klienta.
15 16 /// 256 MiB pokrywa 8K na kilku monitorach, a odcina absurdalne żądania.
16 17 const MAX_IMAGE_BYTES: usize = 256 * 1024 * 1024;
17 18
18 -/// Dane puli pamięci (`wl_shm_pool`): deskryptor, rozmiar i **zbuforowana mapa**.
19 +/// Zmapowany region puli. Odświeżany przy `wl_shm_pool.resize`.
20 +struct Mapping {
21 + ptr: *mut c_void,
22 + size: usize,
23 +}
24 +
25 +// SAFETY: mapa jest tylko do odczytu; dostęp do `ptr` serializuje `Mutex`
26 +// (i tak dzieje się to w wątku protokołu).
27 +unsafe impl Send for Mapping {}
28 +
29 +/// Dane puli pamięci (`wl_shm_pool`): deskryptor + zmapowany region.
19 30 ///
20 -/// Mapę tworzymy raz (przy `create_pool`) i zwalniamy przy zniszczeniu puli — zamiast
21 -/// mapować/odmapowywać przy każdym commicie.
31 +/// Klient może tworzyć pulę małą i **rozszerzać** ją (`wl_shm_pool.resize`) —
32 +/// dlatego mapa i rozmiar żyją za `Mutex` i są powiększane na żądanie.
22 33 pub struct ShmPoolData {
23 - #[allow(dead_code)]
24 34 pub fd: OwnedFd,
25 - pub size: usize,
26 - map: *mut c_void,
35 + map: Mutex<Mapping>,
27 36 }
28 37
29 -// Mapa jest tylko do odczytu i współdzielona — bezpieczna do wysyłania między wątkami.
30 -unsafe impl Send for ShmPoolData {}
31 -unsafe impl Sync for ShmPoolData {}
32 -
33 38 impl ShmPoolData {
34 - /// Mapuje pulę (tylko odczyt). Przy niepowodzeniu zostaje pusta (bez paniki).
39 + /// Mapuje początkowy zakres puli (tylko odczyt). Błąd mapowania = pusta pula.
35 40 pub fn new(fd: OwnedFd, declared_size: usize) -> Self {
36 41 // Faktyczny rozmiar obiektu shm: klient może zadeklarować więcej, niż istnieje,
37 42 // a odczyt poza obiekt kończy się SIGBUS (nie do złapania). Przycinamy rozmiar.
38 - let real = rustix::fs::fstat(&fd)
39 - .map(|st| st.st_size.max(0) as usize)
40 - .unwrap_or(0);
43 + let real = real_size(&fd);
41 44 let size = declared_size.min(real);
42 - let map = if size == 0 {
43 - std::ptr::null_mut()
44 - } else {
45 - // SAFETY: `fd` żyje w strukturze, mapujemy tylko do odczytu.
46 - unsafe {
47 - rustix::mm::mmap(
48 - std::ptr::null_mut::<c_void>(),
49 - size,
50 - rustix::mm::ProtFlags::READ,
51 - rustix::mm::MapFlags::PRIVATE,
52 - &fd,
53 - 0,
54 - )
55 - }
56 - .unwrap_or(std::ptr::null_mut())
57 - };
58 - Self { fd, size, map }
45 + let ptr = map_region(&fd, size);
46 + if debug() {
47 + eprintln!("selene/shm: create_pool declared={declared_size} real={real} used={size}");
48 + }
49 + Self {
50 + fd,
51 + map: Mutex::new(Mapping { ptr, size }),
52 + }
59 53 }
60 54
61 - /// Odczyt jednego bajtu przez `read_volatile`.
55 + /// Powiększa mapę po `wl_shm_pool.resize` (klient wcześniej powiększył `fd`).
62 56 ///
63 - /// Klient (osobny proces) może pisać do tej pamięci w dowolnym momencie, więc nie
64 - /// tworzymy `&[u8]` na współdzielonym mapowaniu (to formalny wyścig). `None`, gdy
65 - /// indeks poza mapą albo mapowanie się nie powiodło.
66 - pub fn read_u8(&self, index: usize) -> Option<u8> {
67 - if self.map.is_null() || index >= self.size {
68 - return None;
57 + /// Rozmiar bywa deklarowany większy niż realny obiekt — przycinamy do realnego,
58 + /// żeby nie czytać poza obiekt (SIGBUS).
59 + pub fn resize(&self, declared_size: usize) {
60 + let real = real_size(&self.fd);
61 + let size = declared_size.min(real);
62 + let mut map = self.map.lock_or_recover();
63 + if size <= map.size {
64 + return; // tylko rośnie
65 + }
66 + if !map.ptr.is_null() {
67 + // SAFETY: odmapowujemy poprzedni zakres.
68 + unsafe {
69 + let _ = rustix::mm::munmap(map.ptr, map.size);
70 + }
71 + }
72 + map.ptr = map_region(&self.fd, size);
73 + map.size = size;
74 + if debug() {
75 + eprintln!("selene/shm: pool resize -> used={size}");
69 76 }
70 - // SAFETY: `index < self.size`, a mapa obejmuje `self.size` bajtów.
71 - Some(unsafe { std::ptr::read_volatile((self.map as *const u8).add(index)) })
72 77 }
73 78 }
74 79
75 80 impl Drop for ShmPoolData {
76 81 fn drop(&mut self) {
77 - if self.map.is_null() {
82 + let map = self.map.get_mut().unwrap_or_else(|e| e.into_inner());
83 + if map.ptr.is_null() {
78 84 return;
79 85 }
80 86 // SAFETY: odmapowujemy ten sam zakres, który zmapowaliśmy.
81 87 unsafe {
82 - let _ = rustix::mm::munmap(self.map, self.size);
88 + let _ = rustix::mm::munmap(map.ptr, map.size);
83 89 }
84 90 }
85 91 }
86 92
93 +/// Realny rozmiar obiektu shm (bez deklaracji klienta).
94 +fn real_size(fd: &OwnedFd) -> usize {
95 + rustix::fs::fstat(fd)
96 + .map(|st| st.st_size.max(0) as usize)
97 + .unwrap_or(0)
98 +}
99 +
100 +/// Mapuje `size` bajtów pliku `fd` (tylko odczyt). `null` przy niepowodzeniu.
101 +fn map_region(fd: &OwnedFd, size: usize) -> *mut c_void {
102 + if size == 0 {
103 + return std::ptr::null_mut();
104 + }
105 + // SAFETY: `fd` żyje w strukturze, mapujemy tylko do odczytu.
106 + unsafe {
107 + rustix::mm::mmap(
108 + std::ptr::null_mut::<c_void>(),
109 + size,
110 + rustix::mm::ProtFlags::READ,
111 + rustix::mm::MapFlags::PRIVATE,
112 + fd,
113 + 0,
114 + )
115 + }
116 + .unwrap_or(std::ptr::null_mut())
117 +}
118 +
119 +fn debug() -> bool {
120 + std::env::var("SELENE_STATS").is_ok()
121 +}
122 +
87 123 /// Dane bufora (`wl_buffer`): odniesienie do puli + geometria.
88 124 pub struct BufferData {
89 125 pub pool: Arc<ShmPoolData>,
@@ -111,29 +147,37 @@ impl BufferData {
111 147 }
112 148 // Cały zakres danych musi mieścić się w puli — na sprawdzonej arytmetyce.
113 149 let end = self.offset.checked_add(stride.checked_mul(h)?)?;
114 - if end > self.pool.size {
115 - return None;
116 - }
117 150 // Limit alokacji chroniący przed OOM wymuszonym przez klienta.
118 151 let out_len = w.checked_mul(h)?.checked_mul(4)?;
119 152 if out_len > MAX_IMAGE_BYTES {
120 153 return None;
121 154 }
155 +
156 + // Trzymamy blokadę mapy przez cały odczyt (mapa może rosnąć w `resize`).
157 + let map = self.pool.map.lock_or_recover();
158 + if map.ptr.is_null() || end > map.size {
159 + return None;
160 + }
161 + let base = map.ptr as *const u8;
162 + let xrgb = self.format == wl_shm::Format::Xrgb8888;
122 163 let mut out = vec![0u8; out_len];
123 164 for y in 0..h {
124 165 let row = self.offset + y * stride;
125 166 for x in 0..w {
126 167 let i = row + x * 4;
127 168 let o = (y * w + x) * 4;
128 - // ARGB8888 (pamięć: B,G,R,A) → RGBA8; XRGB8888 → alfa wymuszona na 1.
129 - out[o] = self.pool.read_u8(i + 2)?;
130 - out[o + 1] = self.pool.read_u8(i + 1)?;
131 - out[o + 2] = self.pool.read_u8(i)?;
132 - out[o + 3] = if self.format == wl_shm::Format::Xrgb8888 {
133 - 255
134 - } else {
135 - self.pool.read_u8(i + 3)?
136 - };
169 + // SAFETY: `i + 3 < end <= map.size`, a mapa obejmuje `map.size` bajtów.
170 + // Odczyt przez `read_volatile` — klient może pisać równolegle.
171 + unsafe {
172 + out[o] = std::ptr::read_volatile(base.add(i + 2));
173 + out[o + 1] = std::ptr::read_volatile(base.add(i + 1));
174 + out[o + 2] = std::ptr::read_volatile(base.add(i));
175 + out[o + 3] = if xrgb {
176 + 255
177 + } else {
178 + std::ptr::read_volatile(base.add(i + 3))
179 + };
180 + }
137 181 }
138 182 }
139 183 Some(out)
@@ -183,26 +227,31 @@ impl Dispatch<wl_shm_pool::WlShmPool, Arc<ShmPoolData>> for State {
183 227 _handle: &DisplayHandle,
184 228 data_init: &mut DataInit<'_, Self>,
185 229 ) {
186 - if let wl_shm_pool::Request::CreateBuffer {
187 - id,
188 - offset,
189 - width,
190 - height,
191 - stride,
192 - format,
193 - } = request
194 - {
195 - data_init.init(
230 + match request {
231 + wl_shm_pool::Request::CreateBuffer {
196 232 id,
197 - BufferData {
198 - pool: Arc::clone(data),
199 - offset: offset.max(0) as usize,
200 - width,
201 - height,
202 - stride,
203 - format: format.into_result().unwrap_or(wl_shm::Format::Argb8888),
204 - },
205 - );
233 + offset,
234 + width,
235 + height,
236 + stride,
237 + format,
238 + } => {
239 + data_init.init(
240 + id,
241 + BufferData {
242 + pool: Arc::clone(data),
243 + offset: offset.max(0) as usize,
244 + width,
245 + height,
246 + stride,
247 + format: format.into_result().unwrap_or(wl_shm::Format::Argb8888),
248 + },
249 + );
250 + }
251 + wl_shm_pool::Request::Resize { size } => {
252 + data.resize(size.max(0) as usize);
253 + }
254 + _ => {}
206 255 }
207 256 }
208 257 }
@@ -225,12 +274,16 @@ mod tests {
225 274 use super::*;
226 275 use std::io::Write;
227 276
228 - fn pool(bytes: &[u8]) -> Arc<ShmPoolData> {
277 + fn memfd(bytes: &[u8]) -> OwnedFd {
229 278 let fd =
230 279 rustix::fs::memfd_create("selene-shm-test", rustix::fs::MemfdFlags::CLOEXEC).unwrap();
231 280 let mut file = std::fs::File::from(fd);
232 281 file.write_all(bytes).unwrap();
233 - Arc::new(ShmPoolData::new(file.into(), bytes.len()))
282 + file.into()
283 + }
284 +
285 + fn pool(bytes: &[u8]) -> Arc<ShmPoolData> {
286 + Arc::new(ShmPoolData::new(memfd(bytes), bytes.len()))
234 287 }
235 288
236 289 fn buffer(pool: Arc<ShmPoolData>, w: i32, h: i32, stride: i32) -> BufferData {
@@ -269,4 +322,16 @@ mod tests {
269 322 // 2x2 ARGB = 16 bajtów > pula 8 → odrzucone.
270 323 assert!(buffer(pool(&[0u8; 8]), 2, 2, 8).to_rgba().is_none());
271 324 }
325 +
326 + #[test]
327 + fn resize_extends_mapping() {
328 + // W pliku jest 8 B, ale deklarujemy na starcie 4 B (jak sctk: mała pula + resize).
329 + let pool = Arc::new(ShmPoolData::new(memfd(&[1, 2, 3, 4, 5, 6, 7, 8]), 4));
330 + // Bufor 2x1 (8 B) nie mieści się w zmapowanych 4 B.
331 + assert!(buffer(Arc::clone(&pool), 2, 1, 8).to_rgba().is_none());
332 + // Po `resize` mapa rośnie i bufor się mieści.
333 + pool.resize(8);
334 + let rgba = buffer(Arc::clone(&pool), 2, 1, 8).to_rgba().unwrap();
335 + assert_eq!(rgba, vec![3, 2, 1, 4, 7, 6, 5, 8]);
336 + }
272 337 }