1#![cfg(unix)]
7
8use pal::unix::SyscallResult;
9use std::ffi::c_void;
10use std::fs::File;
11use std::io;
12use std::io::Error;
13use std::os::unix::prelude::*;
14use std::ptr::null_mut;
15use std::sync::atomic::AtomicUsize;
16use std::sync::atomic::Ordering;
17
18pub(crate) fn page_size() -> usize {
19 static PAGE_SIZE: AtomicUsize = AtomicUsize::new(0);
20 let s = PAGE_SIZE.load(Ordering::Relaxed);
21 if s != 0 {
22 s
23 } else {
24 let s = unsafe { libc::sysconf(libc::_SC_PAGESIZE) as usize };
25 PAGE_SIZE.store(s, Ordering::Relaxed);
26 s
27 }
28}
29
30#[derive(Debug)]
33pub struct SparseMapping {
34 address: *mut c_void,
35 len: usize,
36}
37
38pub type Mappable = OwnedFd;
42
43pub use std::os::unix::io::AsFd as AsMappableRef;
47
48pub type MappableRef<'a> = BorrowedFd<'a>;
52
53pub fn new_mappable_from_file(
57 file: &File,
58 _writable: bool,
59 _executable: bool,
60) -> io::Result<Mappable> {
61 file.as_fd().try_clone_to_owned()
62}
63
64unsafe impl Send for SparseMapping {}
67unsafe impl Sync for SparseMapping {}
69
70unsafe fn mmap(
71 addr: *mut c_void,
72 len: usize,
73 prot: i32,
74 flags: i32,
75 fd: i32,
76 offset: i64,
77) -> Result<*mut c_void, Error> {
78 let address = unsafe { libc::mmap(addr, len, prot, flags, fd, offset) };
79 if address == libc::MAP_FAILED {
80 return Err(Error::last_os_error());
81 }
82 Ok(address)
83}
84
85unsafe fn munmap(addr: *mut c_void, len: usize) -> Result<(), Error> {
86 if unsafe { libc::munmap(addr, len) } < 0 {
87 return Err(Error::last_os_error());
88 }
89 Ok(())
90}
91
92impl SparseMapping {
93 pub fn new(len: usize) -> Result<Self, Error> {
98 Self::new_with_minimum_alignment(len, 1)
99 }
100
101 pub fn new_with_minimum_alignment(len: usize, minimum_alignment: usize) -> Result<Self, Error> {
103 trycopy::initialize_try_copy();
104
105 if len == 0 {
107 return Err(Error::new(
108 io::ErrorKind::InvalidInput,
109 "length must be greater than 0",
110 ));
111 }
112
113 let page_size = page_size();
114 let alignment = crate::reservation_alignment(len, minimum_alignment)?;
115
116 let len = len
117 .checked_add(alignment - 1)
118 .map(|temp| temp & !(alignment - 1))
119 .ok_or_else(|| {
120 Error::new(
121 io::ErrorKind::InvalidInput,
122 "length and alignment combination causes overflow",
123 )
124 })?;
125
126 let alloc_len = len
127 .checked_add(alignment)
128 .map(|temp| temp - page_size)
129 .ok_or_else(|| {
130 Error::new(
131 io::ErrorKind::InvalidInput,
132 "length and alignment combination causes overflow",
133 )
134 })?;
135
136 let address = unsafe {
138 mmap(
139 null_mut(),
140 alloc_len,
141 libc::PROT_NONE,
142 libc::MAP_PRIVATE | libc::MAP_ANONYMOUS,
143 -1,
144 0,
145 )? as usize
146 };
147 let aligned_address = (address + alignment - 1) & !(alignment - 1);
148 let end = address + alloc_len;
149 let aligned_end = aligned_address + len;
150 assert!(aligned_end <= end);
151
152 if address != aligned_address {
153 unsafe { munmap(address as *mut _, aligned_address - address).unwrap() };
155 }
156 if aligned_end != end {
157 unsafe { munmap(aligned_end as *mut _, end - aligned_end).unwrap() };
159 }
160 Ok(Self {
161 address: aligned_address as *mut _,
162 len,
163 })
164 }
165
166 pub fn is_local(&self) -> bool {
168 true
169 }
170
171 pub fn as_ptr(&self) -> *mut c_void {
173 self.address
174 }
175
176 pub fn len(&self) -> usize {
178 self.len
179 }
180
181 fn validate_offset_len(&self, offset: usize, len: usize) -> io::Result<usize> {
182 let end = offset.checked_add(len).ok_or(io::ErrorKind::InvalidInput)?;
183 let page_size = page_size();
184 if !offset.is_multiple_of(page_size) || !end.is_multiple_of(page_size) || end > self.len {
185 return Err(io::ErrorKind::InvalidInput.into());
186 }
187 Ok(end)
188 }
189
190 pub fn alloc(&self, offset: usize, len: usize) -> Result<(), Error> {
192 unsafe {
194 self.mmap_anonymous(
195 offset,
196 len,
197 libc::PROT_READ | libc::PROT_WRITE,
198 libc::MAP_PRIVATE,
199 )
200 }
201 }
202
203 pub fn map_zero(&self, offset: usize, len: usize) -> Result<(), Error> {
205 unsafe { self.mmap_anonymous(offset, len, libc::PROT_READ, libc::MAP_PRIVATE) }
207 }
208
209 pub fn set_writable(&self, offset: usize, len: usize, allow_writes: bool) -> Result<(), Error> {
212 let prot = if allow_writes {
213 libc::PROT_READ | libc::PROT_WRITE
214 } else {
215 libc::PROT_READ
216 };
217 self.mprotect(offset, len, prot)
218 }
219
220 fn mprotect(&self, offset: usize, len: usize, prot: i32) -> Result<(), Error> {
223 self.validate_offset_len(offset, len)?;
224 if prot & !(libc::PROT_READ | libc::PROT_WRITE) != 0 {
225 return Err(Error::new(
226 io::ErrorKind::InvalidInput,
227 "unsupported protection flags",
228 ));
229 }
230 unsafe {
232 if libc::mprotect(self.address.add(offset), len, prot) < 0 {
233 return Err(Error::last_os_error());
234 }
235 }
236 Ok(())
237 }
238
239 pub fn map_file(
241 &self,
242 offset: usize,
243 len: usize,
244 file_mapping: impl AsFd,
245 file_offset: u64,
246 writable: bool,
247 ) -> Result<(), Error> {
248 let prot = if writable {
249 libc::PROT_READ | libc::PROT_WRITE
250 } else {
251 libc::PROT_READ
252 };
253
254 unsafe {
256 self.mmap(
257 offset,
258 len,
259 prot,
260 libc::MAP_SHARED,
261 file_mapping.as_fd(),
262 file_offset as i64,
263 )
264 }
265 }
266
267 #[cfg(target_os = "linux")]
273 pub fn mbind_at(&self, offset: usize, len: usize, numa_node: u32) -> Result<(), Error> {
274 let _ = self.validate_offset_len(offset, len)?;
275 unsafe { mbind_range(self.address.add(offset), len, numa_node) }
278 }
279
280 pub unsafe fn mmap(
290 &self,
291 offset: usize,
292 len: usize,
293 prot: i32,
294 map_flags: i32,
295 fd: impl AsFd,
296 file_offset: i64,
297 ) -> Result<(), Error> {
298 let _ = self.validate_offset_len(offset, len)?;
299
300 unsafe {
302 let address = self.address.add(offset);
303 let mapped_address = mmap(
304 address,
305 len,
306 prot,
307 map_flags | libc::MAP_FIXED,
308 fd.as_fd().as_raw_fd(),
309 file_offset,
310 )?;
311 assert_eq!(mapped_address, address);
312 }
313 Ok(())
314 }
315
316 pub unsafe fn mmap_anonymous(
325 &self,
326 offset: usize,
327 len: usize,
328 prot: i32,
329 map_flags: i32,
330 ) -> io::Result<()> {
331 let _ = self.validate_offset_len(offset, len)?;
332
333 unsafe {
335 let address = self.address.add(offset);
336 let mapped_address = mmap(
337 address,
338 len,
339 prot,
340 map_flags | libc::MAP_ANONYMOUS | libc::MAP_FIXED,
341 -1,
342 0,
343 )?;
344 assert_eq!(mapped_address, address);
345 }
346 Ok(())
347 }
348
349 pub fn decommit(&self, offset: usize, len: usize) -> Result<(), Error> {
354 let _ = self.validate_offset_len(offset, len)?;
355 if len == 0 {
356 return Ok(());
357 }
358 unsafe {
360 let addr = self.address.add(offset);
361 if libc::madvise(addr, len, libc::MADV_DONTNEED) < 0 {
362 return Err(Error::last_os_error());
363 }
364 }
365 Ok(())
366 }
367
368 #[cfg(target_os = "linux")]
376 pub fn madvise_hugepage(&self, offset: usize, len: usize) -> Result<(), Error> {
377 let _ = self.validate_offset_len(offset, len)?;
378 if len == 0 {
379 return Ok(());
380 }
381 unsafe {
383 let addr = self.address.add(offset);
384 if libc::madvise(addr, len, libc::MADV_HUGEPAGE) < 0 {
385 return Err(Error::last_os_error());
386 }
387 }
388 Ok(())
389 }
390
391 #[cfg(target_os = "linux")]
398 pub fn set_name(&self, offset: usize, len: usize, name: &str) {
399 if len == 0 {
400 return;
401 }
402 if self.validate_offset_len(offset, len).is_err() {
403 return;
404 }
405 let Ok(name) = std::ffi::CString::new(name) else {
406 return;
407 };
408 unsafe {
410 libc::prctl(
411 libc::PR_SET_VMA,
412 libc::PR_SET_VMA_ANON_NAME,
413 self.address.add(offset),
414 len,
415 name.as_ptr(),
416 );
417 }
418 }
419
420 #[cfg(not(target_os = "linux"))]
422 pub fn set_name(&self, _offset: usize, _len: usize, _name: &str) {}
423
424 pub fn commit(&self, offset: usize, len: usize) -> Result<(), Error> {
429 let _ = self.validate_offset_len(offset, len)?;
430 Ok(())
431 }
432
433 pub fn unmap(&self, offset: usize, len: usize) -> io::Result<()> {
435 let _ = self.validate_offset_len(offset, len)?;
436
437 if len == 0 {
439 return Err(io::ErrorKind::InvalidInput.into());
440 }
441
442 unsafe {
445 let address = self.address.add(offset);
446 let mapped_address = mmap(
447 address,
448 len,
449 libc::PROT_NONE,
450 libc::MAP_PRIVATE | libc::MAP_ANONYMOUS | libc::MAP_FIXED,
451 -1,
452 0,
453 )
454 .expect("remap to PROT_NONE should not fail (except for low resources)");
455 assert_eq!(mapped_address, address);
456 }
457 Ok(())
458 }
459}
460
461impl Drop for SparseMapping {
462 fn drop(&mut self) {
463 unsafe {
464 libc::munmap(self.address, self.len)
465 .syscall_result()
466 .expect("unmap should not fail");
467 }
468 }
469}
470#[cfg(target_os = "linux")]
471fn new_memfd(name: &str, flags: libc::c_uint) -> io::Result<File> {
472 let name =
473 std::ffi::CString::new(name).map_err(|e| Error::new(io::ErrorKind::InvalidInput, e))?;
474 unsafe {
477 let fd = libc::memfd_create(name.as_ptr(), flags).syscall_result()?;
478 Ok(File::from_raw_fd(fd))
479 }
480}
481
482#[cfg(not(target_os = "linux"))]
483fn new_memfd(_name: &str) -> io::Result<File> {
484 let mut rand = [0; 16];
488 getrandom::fill(&mut rand).unwrap();
489 let mut name = format!("{:x}", u128::from_ne_bytes(rand));
490 name.truncate(31);
492 let name = std::ffi::CString::new(name).unwrap();
493 unsafe {
494 let fd = libc::shm_open(name.as_ptr(), libc::O_RDWR | libc::O_EXCL | libc::O_CREAT)
496 .syscall_result()?;
497 let _ = libc::shm_unlink(name.as_ptr());
499 Ok(File::from_raw_fd(fd))
500 }
501}
502
503pub fn alloc_shared_memory(size: usize, name: &str) -> io::Result<OwnedFd> {
508 #[cfg(target_os = "linux")]
509 let fd = new_memfd(name, libc::MFD_CLOEXEC)?;
510 #[cfg(not(target_os = "linux"))]
511 let fd = new_memfd(name)?;
512 fd.set_len(size as u64)?;
513 Ok(fd.into())
514}
515
516#[cfg(target_os = "linux")]
521pub fn alloc_shared_memory_hugetlb(
522 size: usize,
523 name: &str,
524 hugepage_size: Option<usize>,
525 _numa_node: Option<u32>,
526) -> io::Result<OwnedFd> {
527 const MFD_HUGE_SHIFT: libc::c_uint = 26;
528
529 let mut flags = libc::MFD_CLOEXEC | libc::MFD_HUGETLB;
530 if let Some(hugepage_size) = hugepage_size {
531 if !hugepage_size.is_power_of_two() {
532 return Err(Error::new(
533 io::ErrorKind::InvalidInput,
534 "hugepage size must be a power of two",
535 ));
536 }
537 flags |= (hugepage_size.trailing_zeros() as libc::c_uint) << MFD_HUGE_SHIFT;
538 }
539
540 let fd = new_memfd(name, flags)?;
541 let size = libc::off_t::try_from(size).map_err(|_| {
542 Error::new(
543 io::ErrorKind::InvalidInput,
544 "hugetlb allocation size is too large",
545 )
546 })?;
547
548 unsafe { libc::fallocate(fd.as_raw_fd(), 0, 0, size).syscall_result()? };
552 Ok(fd.into())
553}
554
555#[cfg(not(target_os = "linux"))]
557pub fn alloc_shared_memory_hugetlb(
558 _size: usize,
559 _name: &str,
560 _hugepage_size: Option<usize>,
561 _numa_node: Option<u32>,
562) -> io::Result<OwnedFd> {
563 Err(Error::new(
564 io::ErrorKind::Unsupported,
565 "hugetlb shared memory is only supported on Linux",
566 ))
567}
568
569#[cfg(target_os = "linux")]
576unsafe fn mbind_range(addr: *mut c_void, len: usize, numa_node: u32) -> io::Result<()> {
577 if numa_node > 0xffff {
580 return Err(Error::new(
581 io::ErrorKind::InvalidInput,
582 "NUMA node exceeds maximum supported value",
583 ));
584 }
585
586 let maxnode = numa_node as usize + 2;
595 let word_bits = libc::c_ulong::BITS as usize;
596 let num_words = maxnode.div_ceil(word_bits);
597 let mut nodemask = vec![0 as libc::c_ulong; num_words];
598 nodemask[numa_node as usize / word_bits] = 1 << (numa_node as usize % word_bits);
599
600 let result = unsafe {
603 libc::syscall(
604 libc::SYS_mbind,
605 addr,
606 len,
607 libc::MPOL_BIND,
608 nodemask.as_ptr(),
609 maxnode,
610 0,
611 )
612 };
613
614 if result == -1 {
615 return Err(Error::last_os_error());
616 }
617
618 Ok(())
619}