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