1use std::io;
9use std::ptr;
10use std::sync::atomic::{AtomicPtr, AtomicU32, Ordering};
11
12static CURRENT: AtomicPtr<AtomicU32> = AtomicPtr::new(ptr::null_mut());
13
14#[derive(Clone, Copy)]
19pub struct ProcessOwner {
20 marker: &'static AtomicU32,
21 pid: u32,
22}
23
24#[cfg(all(test, target_os = "linux", target_arch = "x86_64"))]
25#[test]
26fn kernel_marker_invalidates_on_raw_fork_and_stays_invalid_after_rejoin() {
27 let parent = ProcessOwner::current().unwrap();
28 assert_eq!(parent.pid(), std::process::id());
29 let child = unsafe { libc::syscall(libc::SYS_fork) } as libc::pid_t;
31 assert!(child >= 0, "fork failed");
32 if child == 0 {
33 let invalid = !parent.is_current();
34 let joined = ProcessOwner::current().is_ok_and(|owner| {
35 owner.is_current() && owner.pid() == std::process::id()
36 });
37 unsafe {
39 libc::_exit(i32::from(!(invalid && joined && !parent.is_current())))
40 };
41 }
42 let mut status = 0;
43 assert_eq!(unsafe { libc::waitpid(child, &mut status, 0) }, child);
45 assert!(libc::WIFEXITED(status));
46 assert_eq!(libc::WEXITSTATUS(status), 0);
47 assert!(parent.is_current());
48}
49
50impl ProcessOwner {
51 pub fn current() -> io::Result<Self> {
52 loop {
53 let previous = CURRENT.load(Ordering::Acquire);
54 if let Some(marker) = unsafe { previous.as_ref() } {
56 let pid = marker.load(Ordering::Relaxed);
57 if pid != 0 {
58 return Ok(Self { marker, pid });
59 }
60 }
61
62 let candidate = platform::Marker::new()?;
63 let pid = std::process::id();
64 candidate.initialize(pid);
65 if CURRENT
66 .compare_exchange(
67 previous,
68 candidate.as_ptr(),
69 Ordering::AcqRel,
70 Ordering::Acquire,
71 )
72 .is_ok()
73 {
74 return Ok(Self { marker: candidate.publish(), pid });
75 }
76 }
77 }
78
79 pub fn pid(self) -> u32 {
81 self.pid
82 }
83
84 #[inline]
86 pub fn is_current(self) -> bool {
87 self.marker.load(Ordering::Relaxed) != 0
88 }
89}
90
91#[cfg(any(target_os = "linux", target_os = "freebsd"))]
92mod platform {
93 use std::io;
94 use std::mem::size_of;
95 use std::ptr::{self, NonNull};
96 use std::sync::atomic::AtomicU32;
97
98 pub(super) struct Marker {
99 ptr: NonNull<AtomicU32>,
100 len: usize,
101 }
102
103 impl Marker {
104 pub(super) fn new() -> io::Result<Self> {
105 let len = unsafe { libc::sysconf(libc::_SC_PAGESIZE) };
107 if len < size_of::<AtomicU32>() as libc::c_long {
108 return Err(io::Error::other(
109 "invalid process marker page size",
110 ));
111 }
112 let len = len as usize;
113 let page = unsafe {
115 libc::mmap(
116 ptr::null_mut(),
117 len,
118 libc::PROT_READ | libc::PROT_WRITE,
119 libc::MAP_PRIVATE | libc::MAP_ANON,
120 -1,
121 0,
122 )
123 };
124 if page == libc::MAP_FAILED {
125 return Err(io::Error::last_os_error());
126 }
127 let Some(ptr) = NonNull::new(page.cast()) else {
128 unsafe { libc::munmap(page, len) };
130 return Err(io::Error::other("null process marker mapping"));
131 };
132 let marker = Self { ptr, len };
133 #[cfg(target_os = "linux")]
135 let status =
136 unsafe { libc::madvise(page, len, libc::MADV_WIPEONFORK) };
137 #[cfg(target_os = "freebsd")]
138 let status =
139 unsafe { libc::minherit(page, len, libc::INHERIT_ZERO) };
140 if status != 0 {
141 return Err(io::Error::last_os_error());
142 }
143 Ok(marker)
144 }
145
146 pub(super) fn initialize(
147 &self,
148 pid: u32,
149 ) {
150 unsafe { self.ptr.as_ptr().write(AtomicU32::new(pid)) };
152 }
153
154 pub(super) fn as_ptr(&self) -> *mut AtomicU32 {
155 self.ptr.as_ptr()
156 }
157
158 pub(super) fn publish(self) -> &'static AtomicU32 {
159 let ptr = self.ptr;
160 std::mem::forget(self);
163 unsafe { ptr.as_ref() }
165 }
166 }
167
168 impl Drop for Marker {
169 fn drop(&mut self) {
170 unsafe { libc::munmap(self.ptr.as_ptr().cast(), self.len) };
172 }
173 }
174}
175
176#[cfg(not(any(target_os = "linux", target_os = "freebsd")))]
177mod platform {
178 use std::io;
179 use std::sync::atomic::{AtomicU32, Ordering};
180
181 #[cfg(unix)]
182 use super::CURRENT;
183 #[cfg(unix)]
184 use std::sync::atomic::AtomicBool;
185
186 #[cfg(unix)]
187 static HOOK_READY: AtomicBool = AtomicBool::new(false);
188
189 #[cfg(unix)]
190 extern "C" fn after_fork() {
191 if let Some(marker) =
193 unsafe { CURRENT.load(Ordering::Acquire).as_ref() }
194 {
195 marker.store(0, Ordering::Relaxed);
196 }
197 }
198
199 pub(super) struct Marker(Box<AtomicU32>);
200
201 impl Marker {
202 pub(super) fn new() -> io::Result<Self> {
203 #[cfg(unix)]
204 if !HOOK_READY.load(Ordering::Acquire) {
205 let status = unsafe {
208 libc::pthread_atfork(None, None, Some(after_fork))
209 };
210 if status != 0 {
211 return Err(io::Error::from_raw_os_error(status));
212 }
213 HOOK_READY.store(true, Ordering::Release);
214 }
215 Ok(Self(Box::new(AtomicU32::new(0))))
216 }
217
218 pub(super) fn initialize(
219 &self,
220 pid: u32,
221 ) {
222 self.0.store(pid, Ordering::Relaxed);
223 }
224
225 pub(super) fn as_ptr(&self) -> *mut AtomicU32 {
226 ptr::from_ref(&*self.0).cast_mut()
227 }
228
229 pub(super) fn publish(self) -> &'static AtomicU32 {
230 Box::leak(self.0)
231 }
232 }
233
234 use std::ptr;
235}