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