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