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
}