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