]> git.lizzy.rs Git - rust.git/blob - library/std/src/sys/unix/thread.rs
Rollup merge of #105955 - Nilstrieb:no-trivial-opt-wrappers-we-have-field-accesses...
[rust.git] / library / std / src / sys / unix / thread.rs
1 use crate::cmp;
2 use crate::ffi::CStr;
3 use crate::io;
4 use crate::mem;
5 use crate::num::NonZeroUsize;
6 use crate::ptr;
7 use crate::sys::{os, stack_overflow};
8 use crate::time::Duration;
9
10 #[cfg(all(target_os = "linux", target_env = "gnu"))]
11 use crate::sys::weak::dlsym;
12 #[cfg(any(target_os = "solaris", target_os = "illumos"))]
13 use crate::sys::weak::weak;
14 #[cfg(not(any(target_os = "l4re", target_os = "vxworks", target_os = "espidf")))]
15 pub const DEFAULT_MIN_STACK_SIZE: usize = 2 * 1024 * 1024;
16 #[cfg(target_os = "l4re")]
17 pub const DEFAULT_MIN_STACK_SIZE: usize = 1024 * 1024;
18 #[cfg(target_os = "vxworks")]
19 pub const DEFAULT_MIN_STACK_SIZE: usize = 256 * 1024;
20 #[cfg(target_os = "espidf")]
21 pub const DEFAULT_MIN_STACK_SIZE: usize = 0; // 0 indicates that the stack size configured in the ESP-IDF menuconfig system should be used
22
23 #[cfg(target_os = "fuchsia")]
24 mod zircon {
25     type zx_handle_t = u32;
26     type zx_status_t = i32;
27     pub const ZX_PROP_NAME: u32 = 3;
28
29     extern "C" {
30         pub fn zx_object_set_property(
31             handle: zx_handle_t,
32             property: u32,
33             value: *const libc::c_void,
34             value_size: libc::size_t,
35         ) -> zx_status_t;
36         pub fn zx_thread_self() -> zx_handle_t;
37     }
38 }
39
40 pub struct Thread {
41     id: libc::pthread_t,
42 }
43
44 // Some platforms may have pthread_t as a pointer in which case we still want
45 // a thread to be Send/Sync
46 unsafe impl Send for Thread {}
47 unsafe impl Sync for Thread {}
48
49 impl Thread {
50     // unsafe: see thread::Builder::spawn_unchecked for safety requirements
51     pub unsafe fn new(stack: usize, p: Box<dyn FnOnce()>) -> io::Result<Thread> {
52         let p = Box::into_raw(box p);
53         let mut native: libc::pthread_t = mem::zeroed();
54         let mut attr: libc::pthread_attr_t = mem::zeroed();
55         assert_eq!(libc::pthread_attr_init(&mut attr), 0);
56
57         #[cfg(target_os = "espidf")]
58         if stack > 0 {
59             // Only set the stack if a non-zero value is passed
60             // 0 is used as an indication that the default stack size configured in the ESP-IDF menuconfig system should be used
61             assert_eq!(
62                 libc::pthread_attr_setstacksize(&mut attr, cmp::max(stack, min_stack_size(&attr))),
63                 0
64             );
65         }
66
67         #[cfg(not(target_os = "espidf"))]
68         {
69             let stack_size = cmp::max(stack, min_stack_size(&attr));
70
71             match libc::pthread_attr_setstacksize(&mut attr, stack_size) {
72                 0 => {}
73                 n => {
74                     assert_eq!(n, libc::EINVAL);
75                     // EINVAL means |stack_size| is either too small or not a
76                     // multiple of the system page size.  Because it's definitely
77                     // >= PTHREAD_STACK_MIN, it must be an alignment issue.
78                     // Round up to the nearest page and try again.
79                     let page_size = os::page_size();
80                     let stack_size =
81                         (stack_size + page_size - 1) & (-(page_size as isize - 1) as usize - 1);
82                     assert_eq!(libc::pthread_attr_setstacksize(&mut attr, stack_size), 0);
83                 }
84             };
85         }
86
87         let ret = libc::pthread_create(&mut native, &attr, thread_start, p as *mut _);
88         // Note: if the thread creation fails and this assert fails, then p will
89         // be leaked. However, an alternative design could cause double-free
90         // which is clearly worse.
91         assert_eq!(libc::pthread_attr_destroy(&mut attr), 0);
92
93         return if ret != 0 {
94             // The thread failed to start and as a result p was not consumed. Therefore, it is
95             // safe to reconstruct the box so that it gets deallocated.
96             drop(Box::from_raw(p));
97             Err(io::Error::from_raw_os_error(ret))
98         } else {
99             Ok(Thread { id: native })
100         };
101
102         extern "C" fn thread_start(main: *mut libc::c_void) -> *mut libc::c_void {
103             unsafe {
104                 // Next, set up our stack overflow handler which may get triggered if we run
105                 // out of stack.
106                 let _handler = stack_overflow::Handler::new();
107                 // Finally, let's run some code.
108                 Box::from_raw(main as *mut Box<dyn FnOnce()>)();
109             }
110             ptr::null_mut()
111         }
112     }
113
114     pub fn yield_now() {
115         let ret = unsafe { libc::sched_yield() };
116         debug_assert_eq!(ret, 0);
117     }
118
119     #[cfg(target_os = "android")]
120     pub fn set_name(name: &CStr) {
121         const PR_SET_NAME: libc::c_int = 15;
122         unsafe {
123             libc::prctl(
124                 PR_SET_NAME,
125                 name.as_ptr(),
126                 0 as libc::c_ulong,
127                 0 as libc::c_ulong,
128                 0 as libc::c_ulong,
129             );
130         }
131     }
132
133     #[cfg(target_os = "linux")]
134     pub fn set_name(name: &CStr) {
135         const TASK_COMM_LEN: usize = 16;
136
137         unsafe {
138             // Available since glibc 2.12, musl 1.1.16, and uClibc 1.0.20.
139             let name = truncate_cstr::<{ TASK_COMM_LEN }>(name);
140             let res = libc::pthread_setname_np(libc::pthread_self(), name.as_ptr());
141             // We have no good way of propagating errors here, but in debug-builds let's check that this actually worked.
142             debug_assert_eq!(res, 0);
143         }
144     }
145
146     #[cfg(any(target_os = "freebsd", target_os = "dragonfly", target_os = "openbsd"))]
147     pub fn set_name(name: &CStr) {
148         unsafe {
149             libc::pthread_set_name_np(libc::pthread_self(), name.as_ptr());
150         }
151     }
152
153     #[cfg(any(target_os = "macos", target_os = "ios", target_os = "watchos"))]
154     pub fn set_name(name: &CStr) {
155         unsafe {
156             let name = truncate_cstr::<{ libc::MAXTHREADNAMESIZE }>(name);
157             let res = libc::pthread_setname_np(name.as_ptr());
158             // We have no good way of propagating errors here, but in debug-builds let's check that this actually worked.
159             debug_assert_eq!(res, 0);
160         }
161     }
162
163     #[cfg(target_os = "netbsd")]
164     pub fn set_name(name: &CStr) {
165         unsafe {
166             let cname = CStr::from_bytes_with_nul_unchecked(b"%s\0".as_slice());
167             let res = libc::pthread_setname_np(
168                 libc::pthread_self(),
169                 cname.as_ptr(),
170                 name.as_ptr() as *mut libc::c_void,
171             );
172             debug_assert_eq!(res, 0);
173         }
174     }
175
176     #[cfg(any(target_os = "solaris", target_os = "illumos"))]
177     pub fn set_name(name: &CStr) {
178         weak! {
179             fn pthread_setname_np(
180                 libc::pthread_t, *const libc::c_char
181             ) -> libc::c_int
182         }
183
184         if let Some(f) = pthread_setname_np.get() {
185             let res = unsafe { f(libc::pthread_self(), name.as_ptr()) };
186             debug_assert_eq!(res, 0);
187         }
188     }
189
190     #[cfg(target_os = "fuchsia")]
191     pub fn set_name(name: &CStr) {
192         use self::zircon::*;
193         unsafe {
194             zx_object_set_property(
195                 zx_thread_self(),
196                 ZX_PROP_NAME,
197                 name.as_ptr() as *const libc::c_void,
198                 name.to_bytes().len(),
199             );
200         }
201     }
202
203     #[cfg(target_os = "haiku")]
204     pub fn set_name(name: &CStr) {
205         unsafe {
206             let thread_self = libc::find_thread(ptr::null_mut());
207             libc::rename_thread(thread_self, name.as_ptr());
208         }
209     }
210
211     #[cfg(any(
212         target_env = "newlib",
213         target_os = "l4re",
214         target_os = "emscripten",
215         target_os = "redox",
216         target_os = "vxworks"
217     ))]
218     pub fn set_name(_name: &CStr) {
219         // Newlib, Emscripten, and VxWorks have no way to set a thread name.
220     }
221
222     #[cfg(not(target_os = "espidf"))]
223     pub fn sleep(dur: Duration) {
224         let mut secs = dur.as_secs();
225         let mut nsecs = dur.subsec_nanos() as _;
226
227         // If we're awoken with a signal then the return value will be -1 and
228         // nanosleep will fill in `ts` with the remaining time.
229         unsafe {
230             while secs > 0 || nsecs > 0 {
231                 let mut ts = libc::timespec {
232                     tv_sec: cmp::min(libc::time_t::MAX as u64, secs) as libc::time_t,
233                     tv_nsec: nsecs,
234                 };
235                 secs -= ts.tv_sec as u64;
236                 let ts_ptr = &mut ts as *mut _;
237                 if libc::nanosleep(ts_ptr, ts_ptr) == -1 {
238                     assert_eq!(os::errno(), libc::EINTR);
239                     secs += ts.tv_sec as u64;
240                     nsecs = ts.tv_nsec;
241                 } else {
242                     nsecs = 0;
243                 }
244             }
245         }
246     }
247
248     #[cfg(target_os = "espidf")]
249     pub fn sleep(dur: Duration) {
250         let mut micros = dur.as_micros();
251         unsafe {
252             while micros > 0 {
253                 let st = if micros > u32::MAX as u128 { u32::MAX } else { micros as u32 };
254                 libc::usleep(st);
255
256                 micros -= st as u128;
257             }
258         }
259     }
260
261     pub fn join(self) {
262         unsafe {
263             let ret = libc::pthread_join(self.id, ptr::null_mut());
264             mem::forget(self);
265             assert!(ret == 0, "failed to join thread: {}", io::Error::from_raw_os_error(ret));
266         }
267     }
268
269     pub fn id(&self) -> libc::pthread_t {
270         self.id
271     }
272
273     pub fn into_id(self) -> libc::pthread_t {
274         let id = self.id;
275         mem::forget(self);
276         id
277     }
278 }
279
280 impl Drop for Thread {
281     fn drop(&mut self) {
282         let ret = unsafe { libc::pthread_detach(self.id) };
283         debug_assert_eq!(ret, 0);
284     }
285 }
286
287 #[cfg(any(target_os = "linux", target_os = "macos", target_os = "ios", target_os = "watchos"))]
288 fn truncate_cstr<const MAX_WITH_NUL: usize>(cstr: &CStr) -> [libc::c_char; MAX_WITH_NUL] {
289     let mut result = [0; MAX_WITH_NUL];
290     for (src, dst) in cstr.to_bytes().iter().zip(&mut result[..MAX_WITH_NUL - 1]) {
291         *dst = *src as libc::c_char;
292     }
293     result
294 }
295
296 pub fn available_parallelism() -> io::Result<NonZeroUsize> {
297     cfg_if::cfg_if! {
298         if #[cfg(any(
299             target_os = "android",
300             target_os = "emscripten",
301             target_os = "fuchsia",
302             target_os = "ios",
303             target_os = "linux",
304             target_os = "macos",
305             target_os = "solaris",
306             target_os = "illumos",
307         ))] {
308             #[cfg(any(target_os = "android", target_os = "linux"))]
309             {
310                 let quota = cgroups::quota().max(1);
311                 let mut set: libc::cpu_set_t = unsafe { mem::zeroed() };
312                 unsafe {
313                     if libc::sched_getaffinity(0, mem::size_of::<libc::cpu_set_t>(), &mut set) == 0 {
314                         let count = libc::CPU_COUNT(&set) as usize;
315                         let count = count.min(quota);
316                         // SAFETY: affinity mask can't be empty and the quota gets clamped to a minimum of 1
317                         return Ok(NonZeroUsize::new_unchecked(count));
318                     }
319                 }
320             }
321             match unsafe { libc::sysconf(libc::_SC_NPROCESSORS_ONLN) } {
322                 -1 => Err(io::Error::last_os_error()),
323                 0 => Err(io::const_io_error!(io::ErrorKind::NotFound, "The number of hardware threads is not known for the target platform")),
324                 cpus => Ok(unsafe { NonZeroUsize::new_unchecked(cpus as usize) }),
325             }
326         } else if #[cfg(any(target_os = "freebsd", target_os = "dragonfly", target_os = "netbsd"))] {
327             use crate::ptr;
328
329             let mut cpus: libc::c_uint = 0;
330             let mut cpus_size = crate::mem::size_of_val(&cpus);
331
332             unsafe {
333                 cpus = libc::sysconf(libc::_SC_NPROCESSORS_ONLN) as libc::c_uint;
334             }
335
336             // Fallback approach in case of errors or no hardware threads.
337             if cpus < 1 {
338                 let mut mib = [libc::CTL_HW, libc::HW_NCPU, 0, 0];
339                 let res = unsafe {
340                     libc::sysctl(
341                         mib.as_mut_ptr(),
342                         2,
343                         &mut cpus as *mut _ as *mut _,
344                         &mut cpus_size as *mut _ as *mut _,
345                         ptr::null_mut(),
346                         0,
347                     )
348                 };
349
350                 // Handle errors if any.
351                 if res == -1 {
352                     return Err(io::Error::last_os_error());
353                 } else if cpus == 0 {
354                     return Err(io::const_io_error!(io::ErrorKind::NotFound, "The number of hardware threads is not known for the target platform"));
355                 }
356             }
357             Ok(unsafe { NonZeroUsize::new_unchecked(cpus as usize) })
358         } else if #[cfg(target_os = "openbsd")] {
359             use crate::ptr;
360
361             let mut cpus: libc::c_uint = 0;
362             let mut cpus_size = crate::mem::size_of_val(&cpus);
363             let mut mib = [libc::CTL_HW, libc::HW_NCPU, 0, 0];
364
365             let res = unsafe {
366                 libc::sysctl(
367                     mib.as_mut_ptr(),
368                     2,
369                     &mut cpus as *mut _ as *mut _,
370                     &mut cpus_size as *mut _ as *mut _,
371                     ptr::null_mut(),
372                     0,
373                 )
374             };
375
376             // Handle errors if any.
377             if res == -1 {
378                 return Err(io::Error::last_os_error());
379             } else if cpus == 0 {
380                 return Err(io::const_io_error!(io::ErrorKind::NotFound, "The number of hardware threads is not known for the target platform"));
381             }
382
383             Ok(unsafe { NonZeroUsize::new_unchecked(cpus as usize) })
384         } else if #[cfg(target_os = "haiku")] {
385             // system_info cpu_count field gets the static data set at boot time with `smp_set_num_cpus`
386             // `get_system_info` calls then `smp_get_num_cpus`
387             unsafe {
388                 let mut sinfo: libc::system_info = crate::mem::zeroed();
389                 let res = libc::get_system_info(&mut sinfo);
390
391                 if res != libc::B_OK {
392                     return Err(io::const_io_error!(io::ErrorKind::NotFound, "The number of hardware threads is not known for the target platform"));
393                 }
394
395                 Ok(NonZeroUsize::new_unchecked(sinfo.cpu_count as usize))
396             }
397         } else {
398             // FIXME: implement on vxWorks, Redox, l4re
399             Err(io::const_io_error!(io::ErrorKind::Unsupported, "Getting the number of hardware threads is not supported on the target platform"))
400         }
401     }
402 }
403
404 #[cfg(any(target_os = "android", target_os = "linux"))]
405 mod cgroups {
406     //! Currently not covered
407     //! * cgroup v2 in non-standard mountpoints
408     //! * paths containing control characters or spaces, since those would be escaped in procfs
409     //!   output and we don't unescape
410     use crate::borrow::Cow;
411     use crate::ffi::OsString;
412     use crate::fs::{try_exists, File};
413     use crate::io::Read;
414     use crate::io::{BufRead, BufReader};
415     use crate::os::unix::ffi::OsStringExt;
416     use crate::path::Path;
417     use crate::path::PathBuf;
418     use crate::str::from_utf8;
419
420     #[derive(PartialEq)]
421     enum Cgroup {
422         V1,
423         V2,
424     }
425
426     /// Returns cgroup CPU quota in core-equivalents, rounded down or usize::MAX if the quota cannot
427     /// be determined or is not set.
428     pub(super) fn quota() -> usize {
429         let mut quota = usize::MAX;
430         if cfg!(miri) {
431             // Attempting to open a file fails under default flags due to isolation.
432             // And Miri does not have parallelism anyway.
433             return quota;
434         }
435
436         let _: Option<()> = try {
437             let mut buf = Vec::with_capacity(128);
438             // find our place in the cgroup hierarchy
439             File::open("/proc/self/cgroup").ok()?.read_to_end(&mut buf).ok()?;
440             let (cgroup_path, version) =
441                 buf.split(|&c| c == b'\n').fold(None, |previous, line| {
442                     let mut fields = line.splitn(3, |&c| c == b':');
443                     // 2nd field is a list of controllers for v1 or empty for v2
444                     let version = match fields.nth(1) {
445                         Some(b"") => Cgroup::V2,
446                         Some(controllers)
447                             if from_utf8(controllers)
448                                 .is_ok_and(|c| c.split(',').any(|c| c == "cpu")) =>
449                         {
450                             Cgroup::V1
451                         }
452                         _ => return previous,
453                     };
454
455                     // already-found v1 trumps v2 since it explicitly specifies its controllers
456                     if previous.is_some() && version == Cgroup::V2 {
457                         return previous;
458                     }
459
460                     let path = fields.last()?;
461                     // skip leading slash
462                     Some((path[1..].to_owned(), version))
463                 })?;
464             let cgroup_path = PathBuf::from(OsString::from_vec(cgroup_path));
465
466             quota = match version {
467                 Cgroup::V1 => quota_v1(cgroup_path),
468                 Cgroup::V2 => quota_v2(cgroup_path),
469             };
470         };
471
472         quota
473     }
474
475     fn quota_v2(group_path: PathBuf) -> usize {
476         let mut quota = usize::MAX;
477
478         let mut path = PathBuf::with_capacity(128);
479         let mut read_buf = String::with_capacity(20);
480
481         // standard mount location defined in file-hierarchy(7) manpage
482         let cgroup_mount = "/sys/fs/cgroup";
483
484         path.push(cgroup_mount);
485         path.push(&group_path);
486
487         path.push("cgroup.controllers");
488
489         // skip if we're not looking at cgroup2
490         if matches!(try_exists(&path), Err(_) | Ok(false)) {
491             return usize::MAX;
492         };
493
494         path.pop();
495
496         let _: Option<()> = try {
497             while path.starts_with(cgroup_mount) {
498                 path.push("cpu.max");
499
500                 read_buf.clear();
501
502                 if File::open(&path).and_then(|mut f| f.read_to_string(&mut read_buf)).is_ok() {
503                     let raw_quota = read_buf.lines().next()?;
504                     let mut raw_quota = raw_quota.split(' ');
505                     let limit = raw_quota.next()?;
506                     let period = raw_quota.next()?;
507                     match (limit.parse::<usize>(), period.parse::<usize>()) {
508                         (Ok(limit), Ok(period)) => {
509                             quota = quota.min(limit / period);
510                         }
511                         _ => {}
512                     }
513                 }
514
515                 path.pop(); // pop filename
516                 path.pop(); // pop dir
517             }
518         };
519
520         quota
521     }
522
523     fn quota_v1(group_path: PathBuf) -> usize {
524         let mut quota = usize::MAX;
525         let mut path = PathBuf::with_capacity(128);
526         let mut read_buf = String::with_capacity(20);
527
528         // Hardcode commonly used locations mentioned in the cgroups(7) manpage
529         // if that doesn't work scan mountinfo and adjust `group_path` for bind-mounts
530         let mounts: &[fn(&Path) -> Option<(_, &Path)>] = &[
531             |p| Some((Cow::Borrowed("/sys/fs/cgroup/cpu"), p)),
532             |p| Some((Cow::Borrowed("/sys/fs/cgroup/cpu,cpuacct"), p)),
533             // this can be expensive on systems with tons of mountpoints
534             // but we only get to this point when /proc/self/cgroups explicitly indicated
535             // this process belongs to a cpu-controller cgroup v1 and the defaults didn't work
536             find_mountpoint,
537         ];
538
539         for mount in mounts {
540             let Some((mount, group_path)) = mount(&group_path) else { continue };
541
542             path.clear();
543             path.push(mount.as_ref());
544             path.push(&group_path);
545
546             // skip if we guessed the mount incorrectly
547             if matches!(try_exists(&path), Err(_) | Ok(false)) {
548                 continue;
549             }
550
551             while path.starts_with(mount.as_ref()) {
552                 let mut parse_file = |name| {
553                     path.push(name);
554                     read_buf.clear();
555
556                     let f = File::open(&path);
557                     path.pop(); // restore buffer before any early returns
558                     f.ok()?.read_to_string(&mut read_buf).ok()?;
559                     let parsed = read_buf.trim().parse::<usize>().ok()?;
560
561                     Some(parsed)
562                 };
563
564                 let limit = parse_file("cpu.cfs_quota_us");
565                 let period = parse_file("cpu.cfs_period_us");
566
567                 match (limit, period) {
568                     (Some(limit), Some(period)) => quota = quota.min(limit / period),
569                     _ => {}
570                 }
571
572                 path.pop();
573             }
574
575             // we passed the try_exists above so we should have traversed the correct hierarchy
576             // when reaching this line
577             break;
578         }
579
580         quota
581     }
582
583     /// Scan mountinfo for cgroup v1 mountpoint with a cpu controller
584     ///
585     /// If the cgroupfs is a bind mount then `group_path` is adjusted to skip
586     /// over the already-included prefix
587     fn find_mountpoint(group_path: &Path) -> Option<(Cow<'static, str>, &Path)> {
588         let mut reader = BufReader::new(File::open("/proc/self/mountinfo").ok()?);
589         let mut line = String::with_capacity(256);
590         loop {
591             line.clear();
592             if reader.read_line(&mut line).ok()? == 0 {
593                 break;
594             }
595
596             let line = line.trim();
597             let mut items = line.split(' ');
598
599             let sub_path = items.nth(3)?;
600             let mount_point = items.next()?;
601             let mount_opts = items.next_back()?;
602             let filesystem_type = items.nth_back(1)?;
603
604             if filesystem_type != "cgroup" || !mount_opts.split(',').any(|opt| opt == "cpu") {
605                 // not a cgroup / not a cpu-controller
606                 continue;
607             }
608
609             let sub_path = Path::new(sub_path).strip_prefix("/").ok()?;
610
611             if !group_path.starts_with(sub_path) {
612                 // this is a bind-mount and the bound subdirectory
613                 // does not contain the cgroup this process belongs to
614                 continue;
615             }
616
617             let trimmed_group_path = group_path.strip_prefix(sub_path).ok()?;
618
619             return Some((Cow::Owned(mount_point.to_owned()), trimmed_group_path));
620         }
621
622         None
623     }
624 }
625
626 #[cfg(all(
627     not(target_os = "linux"),
628     not(target_os = "freebsd"),
629     not(target_os = "macos"),
630     not(target_os = "netbsd"),
631     not(target_os = "openbsd"),
632     not(target_os = "solaris")
633 ))]
634 #[cfg_attr(test, allow(dead_code))]
635 pub mod guard {
636     use crate::ops::Range;
637     pub type Guard = Range<usize>;
638     pub unsafe fn current() -> Option<Guard> {
639         None
640     }
641     pub unsafe fn init() -> Option<Guard> {
642         None
643     }
644 }
645
646 #[cfg(any(
647     target_os = "linux",
648     target_os = "freebsd",
649     target_os = "macos",
650     target_os = "netbsd",
651     target_os = "openbsd",
652     target_os = "solaris"
653 ))]
654 #[cfg_attr(test, allow(dead_code))]
655 pub mod guard {
656     #[cfg(not(all(target_os = "linux", target_env = "gnu")))]
657     use libc::{mmap as mmap64, mprotect};
658     #[cfg(all(target_os = "linux", target_env = "gnu"))]
659     use libc::{mmap64, mprotect};
660     use libc::{MAP_ANON, MAP_FAILED, MAP_FIXED, MAP_PRIVATE, PROT_NONE, PROT_READ, PROT_WRITE};
661
662     use crate::io;
663     use crate::ops::Range;
664     use crate::sync::atomic::{AtomicUsize, Ordering};
665     use crate::sys::os;
666
667     // This is initialized in init() and only read from after
668     static PAGE_SIZE: AtomicUsize = AtomicUsize::new(0);
669
670     pub type Guard = Range<usize>;
671
672     #[cfg(target_os = "solaris")]
673     unsafe fn get_stack_start() -> Option<*mut libc::c_void> {
674         let mut current_stack: libc::stack_t = crate::mem::zeroed();
675         assert_eq!(libc::stack_getbounds(&mut current_stack), 0);
676         Some(current_stack.ss_sp)
677     }
678
679     #[cfg(target_os = "macos")]
680     unsafe fn get_stack_start() -> Option<*mut libc::c_void> {
681         let th = libc::pthread_self();
682         let stackptr = libc::pthread_get_stackaddr_np(th);
683         Some(stackptr.map_addr(|addr| addr - libc::pthread_get_stacksize_np(th)))
684     }
685
686     #[cfg(target_os = "openbsd")]
687     unsafe fn get_stack_start() -> Option<*mut libc::c_void> {
688         let mut current_stack: libc::stack_t = crate::mem::zeroed();
689         assert_eq!(libc::pthread_stackseg_np(libc::pthread_self(), &mut current_stack), 0);
690
691         let stack_ptr = current_stack.ss_sp;
692         let stackaddr = if libc::pthread_main_np() == 1 {
693             // main thread
694             stack_ptr.addr() - current_stack.ss_size + PAGE_SIZE.load(Ordering::Relaxed)
695         } else {
696             // new thread
697             stack_ptr.addr() - current_stack.ss_size
698         };
699         Some(stack_ptr.with_addr(stackaddr))
700     }
701
702     #[cfg(any(
703         target_os = "android",
704         target_os = "freebsd",
705         target_os = "linux",
706         target_os = "netbsd",
707         target_os = "l4re"
708     ))]
709     unsafe fn get_stack_start() -> Option<*mut libc::c_void> {
710         let mut ret = None;
711         let mut attr: libc::pthread_attr_t = crate::mem::zeroed();
712         #[cfg(target_os = "freebsd")]
713         assert_eq!(libc::pthread_attr_init(&mut attr), 0);
714         #[cfg(target_os = "freebsd")]
715         let e = libc::pthread_attr_get_np(libc::pthread_self(), &mut attr);
716         #[cfg(not(target_os = "freebsd"))]
717         let e = libc::pthread_getattr_np(libc::pthread_self(), &mut attr);
718         if e == 0 {
719             let mut stackaddr = crate::ptr::null_mut();
720             let mut stacksize = 0;
721             assert_eq!(libc::pthread_attr_getstack(&attr, &mut stackaddr, &mut stacksize), 0);
722             ret = Some(stackaddr);
723         }
724         if e == 0 || cfg!(target_os = "freebsd") {
725             assert_eq!(libc::pthread_attr_destroy(&mut attr), 0);
726         }
727         ret
728     }
729
730     // Precondition: PAGE_SIZE is initialized.
731     unsafe fn get_stack_start_aligned() -> Option<*mut libc::c_void> {
732         let page_size = PAGE_SIZE.load(Ordering::Relaxed);
733         assert!(page_size != 0);
734         let stackptr = get_stack_start()?;
735         let stackaddr = stackptr.addr();
736
737         // Ensure stackaddr is page aligned! A parent process might
738         // have reset RLIMIT_STACK to be non-page aligned. The
739         // pthread_attr_getstack() reports the usable stack area
740         // stackaddr < stackaddr + stacksize, so if stackaddr is not
741         // page-aligned, calculate the fix such that stackaddr <
742         // new_page_aligned_stackaddr < stackaddr + stacksize
743         let remainder = stackaddr % page_size;
744         Some(if remainder == 0 {
745             stackptr
746         } else {
747             stackptr.with_addr(stackaddr + page_size - remainder)
748         })
749     }
750
751     pub unsafe fn init() -> Option<Guard> {
752         let page_size = os::page_size();
753         PAGE_SIZE.store(page_size, Ordering::Relaxed);
754
755         if cfg!(all(target_os = "linux", not(target_env = "musl"))) {
756             // Linux doesn't allocate the whole stack right away, and
757             // the kernel has its own stack-guard mechanism to fault
758             // when growing too close to an existing mapping.  If we map
759             // our own guard, then the kernel starts enforcing a rather
760             // large gap above that, rendering much of the possible
761             // stack space useless.  See #43052.
762             //
763             // Instead, we'll just note where we expect rlimit to start
764             // faulting, so our handler can report "stack overflow", and
765             // trust that the kernel's own stack guard will work.
766             let stackptr = get_stack_start_aligned()?;
767             let stackaddr = stackptr.addr();
768             Some(stackaddr - page_size..stackaddr)
769         } else if cfg!(all(target_os = "linux", target_env = "musl")) {
770             // For the main thread, the musl's pthread_attr_getstack
771             // returns the current stack size, rather than maximum size
772             // it can eventually grow to. It cannot be used to determine
773             // the position of kernel's stack guard.
774             None
775         } else if cfg!(target_os = "freebsd") {
776             // FreeBSD's stack autogrows, and optionally includes a guard page
777             // at the bottom.  If we try to remap the bottom of the stack
778             // ourselves, FreeBSD's guard page moves upwards.  So we'll just use
779             // the builtin guard page.
780             let stackptr = get_stack_start_aligned()?;
781             let guardaddr = stackptr.addr();
782             // Technically the number of guard pages is tunable and controlled
783             // by the security.bsd.stack_guard_page sysctl, but there are
784             // few reasons to change it from the default.  The default value has
785             // been 1 ever since FreeBSD 11.1 and 10.4.
786             const GUARD_PAGES: usize = 1;
787             let guard = guardaddr..guardaddr + GUARD_PAGES * page_size;
788             Some(guard)
789         } else if cfg!(target_os = "openbsd") {
790             // OpenBSD stack already includes a guard page, and stack is
791             // immutable.
792             //
793             // We'll just note where we expect rlimit to start
794             // faulting, so our handler can report "stack overflow", and
795             // trust that the kernel's own stack guard will work.
796             let stackptr = get_stack_start_aligned()?;
797             let stackaddr = stackptr.addr();
798             Some(stackaddr - page_size..stackaddr)
799         } else {
800             // Reallocate the last page of the stack.
801             // This ensures SIGBUS will be raised on
802             // stack overflow.
803             // Systems which enforce strict PAX MPROTECT do not allow
804             // to mprotect() a mapping with less restrictive permissions
805             // than the initial mmap() used, so we mmap() here with
806             // read/write permissions and only then mprotect() it to
807             // no permissions at all. See issue #50313.
808             let stackptr = get_stack_start_aligned()?;
809             let result = mmap64(
810                 stackptr,
811                 page_size,
812                 PROT_READ | PROT_WRITE,
813                 MAP_PRIVATE | MAP_ANON | MAP_FIXED,
814                 -1,
815                 0,
816             );
817             if result != stackptr || result == MAP_FAILED {
818                 panic!("failed to allocate a guard page: {}", io::Error::last_os_error());
819             }
820
821             let result = mprotect(stackptr, page_size, PROT_NONE);
822             if result != 0 {
823                 panic!("failed to protect the guard page: {}", io::Error::last_os_error());
824             }
825
826             let guardaddr = stackptr.addr();
827
828             Some(guardaddr..guardaddr + page_size)
829         }
830     }
831
832     #[cfg(any(target_os = "macos", target_os = "openbsd", target_os = "solaris"))]
833     pub unsafe fn current() -> Option<Guard> {
834         let stackptr = get_stack_start()?;
835         let stackaddr = stackptr.addr();
836         Some(stackaddr - PAGE_SIZE.load(Ordering::Relaxed)..stackaddr)
837     }
838
839     #[cfg(any(
840         target_os = "android",
841         target_os = "freebsd",
842         target_os = "linux",
843         target_os = "netbsd",
844         target_os = "l4re"
845     ))]
846     pub unsafe fn current() -> Option<Guard> {
847         let mut ret = None;
848         let mut attr: libc::pthread_attr_t = crate::mem::zeroed();
849         #[cfg(target_os = "freebsd")]
850         assert_eq!(libc::pthread_attr_init(&mut attr), 0);
851         #[cfg(target_os = "freebsd")]
852         let e = libc::pthread_attr_get_np(libc::pthread_self(), &mut attr);
853         #[cfg(not(target_os = "freebsd"))]
854         let e = libc::pthread_getattr_np(libc::pthread_self(), &mut attr);
855         if e == 0 {
856             let mut guardsize = 0;
857             assert_eq!(libc::pthread_attr_getguardsize(&attr, &mut guardsize), 0);
858             if guardsize == 0 {
859                 if cfg!(all(target_os = "linux", target_env = "musl")) {
860                     // musl versions before 1.1.19 always reported guard
861                     // size obtained from pthread_attr_get_np as zero.
862                     // Use page size as a fallback.
863                     guardsize = PAGE_SIZE.load(Ordering::Relaxed);
864                 } else {
865                     panic!("there is no guard page");
866                 }
867             }
868             let mut stackptr = crate::ptr::null_mut::<libc::c_void>();
869             let mut size = 0;
870             assert_eq!(libc::pthread_attr_getstack(&attr, &mut stackptr, &mut size), 0);
871
872             let stackaddr = stackptr.addr();
873             ret = if cfg!(any(target_os = "freebsd", target_os = "netbsd")) {
874                 Some(stackaddr - guardsize..stackaddr)
875             } else if cfg!(all(target_os = "linux", target_env = "musl")) {
876                 Some(stackaddr - guardsize..stackaddr)
877             } else if cfg!(all(target_os = "linux", any(target_env = "gnu", target_env = "uclibc")))
878             {
879                 // glibc used to include the guard area within the stack, as noted in the BUGS
880                 // section of `man pthread_attr_getguardsize`.  This has been corrected starting
881                 // with glibc 2.27, and in some distro backports, so the guard is now placed at the
882                 // end (below) the stack.  There's no easy way for us to know which we have at
883                 // runtime, so we'll just match any fault in the range right above or below the
884                 // stack base to call that fault a stack overflow.
885                 Some(stackaddr - guardsize..stackaddr + guardsize)
886             } else {
887                 Some(stackaddr..stackaddr + guardsize)
888             };
889         }
890         if e == 0 || cfg!(target_os = "freebsd") {
891             assert_eq!(libc::pthread_attr_destroy(&mut attr), 0);
892         }
893         ret
894     }
895 }
896
897 // glibc >= 2.15 has a __pthread_get_minstack() function that returns
898 // PTHREAD_STACK_MIN plus bytes needed for thread-local storage.
899 // We need that information to avoid blowing up when a small stack
900 // is created in an application with big thread-local storage requirements.
901 // See #6233 for rationale and details.
902 #[cfg(all(target_os = "linux", target_env = "gnu"))]
903 fn min_stack_size(attr: *const libc::pthread_attr_t) -> usize {
904     // We use dlsym to avoid an ELF version dependency on GLIBC_PRIVATE. (#23628)
905     // We shouldn't really be using such an internal symbol, but there's currently
906     // no other way to account for the TLS size.
907     dlsym!(fn __pthread_get_minstack(*const libc::pthread_attr_t) -> libc::size_t);
908
909     match __pthread_get_minstack.get() {
910         None => libc::PTHREAD_STACK_MIN,
911         Some(f) => unsafe { f(attr) },
912     }
913 }
914
915 // No point in looking up __pthread_get_minstack() on non-glibc platforms.
916 #[cfg(all(not(all(target_os = "linux", target_env = "gnu")), not(target_os = "netbsd")))]
917 fn min_stack_size(_: *const libc::pthread_attr_t) -> usize {
918     libc::PTHREAD_STACK_MIN
919 }
920
921 #[cfg(target_os = "netbsd")]
922 fn min_stack_size(_: *const libc::pthread_attr_t) -> usize {
923     2048 // just a guess
924 }