Skip to main content

c/
compat_extra.rs

1// Copyright (c) 2026 Meng Zhuo
2// SPDX-License-Identifier: BSD-3-Clause
3
4// Real implementations for the remaining POSIX/GNU compatibility surface:
5// AIO, mqueue attributes, pthread introspection, spawn attributes, stdio
6// internals, and assorted legacy calls.
7
8// ============================================================
9// AIO (synchronous completion)
10// ============================================================
11
12#[repr(C)]
13pub struct Aiocb {
14    pub aio_fildes: c_int,
15    pub aio_lio_opcode: c_int,
16    pub aio_reqprio: c_int,
17    __pad: c_int,
18    pub aio_buf: *mut c_void,
19    pub aio_nbytes: usize,
20    pub aio_sigevent: [u8; 64],
21    pub aio_offset: i64,
22}
23
24const _: [(); 104] = [(); core::mem::size_of::<Aiocb>()];
25
26#[derive(Copy, Clone)]
27struct AioSlot {
28    cb: *mut Aiocb,
29    ret: isize,
30    err: c_int,
31    active: bool,
32    next: *mut AioSlot,
33    prev: *mut AioSlot,
34}
35
36// Completion records are allocated on demand and recycled through a free
37// list, mirroring musl's dynamic AIO bookkeeping.
38static mut AIO_HEAD: *mut AioSlot = core::ptr::null_mut();
39static mut AIO_FREE: *mut AioSlot = core::ptr::null_mut();
40static AIO_LOCK: AtomicI32 = AtomicI32::new(0);
41
42unsafe fn aio_lock() {
43    while AIO_LOCK
44        .compare_exchange_weak(0, 1, Ordering::Acquire, Ordering::Relaxed)
45        .is_err()
46    {
47        core::hint::spin_loop();
48    }
49}
50
51unsafe fn aio_unlock() {
52    AIO_LOCK.store(0, Ordering::Release);
53}
54
55unsafe fn aio_alloc() -> *mut AioSlot {
56    let slot = if AIO_FREE.is_null() {
57        crate::malloc(core::mem::size_of::<AioSlot>()) as *mut AioSlot
58    } else {
59        let slot = AIO_FREE;
60        AIO_FREE = (*slot).next;
61        slot
62    };
63    if !slot.is_null() {
64        (*slot).cb = core::ptr::null_mut();
65        (*slot).ret = 0;
66        (*slot).err = 0;
67        (*slot).active = false;
68        (*slot).next = core::ptr::null_mut();
69        (*slot).prev = core::ptr::null_mut();
70    }
71    slot
72}
73
74unsafe fn aio_get(cb: *mut Aiocb) -> *mut AioSlot {
75    let mut slot = AIO_HEAD;
76    while !slot.is_null() {
77        if (*slot).cb == cb {
78            return slot;
79        }
80        slot = (*slot).next;
81    }
82    let slot = aio_alloc();
83    if slot.is_null() {
84        return core::ptr::null_mut();
85    }
86    (*slot).cb = cb;
87    (*slot).next = AIO_HEAD;
88    if !AIO_HEAD.is_null() {
89        (*AIO_HEAD).prev = slot;
90    }
91    AIO_HEAD = slot;
92    slot
93}
94
95unsafe fn aio_find(cb: *const Aiocb) -> *mut AioSlot {
96    let mut slot = AIO_HEAD;
97    while !slot.is_null() {
98        if (*slot).cb == cb as *mut Aiocb && (*slot).active {
99            return slot;
100        }
101        slot = (*slot).next;
102    }
103    core::ptr::null_mut()
104}
105
106unsafe fn aio_unlink(slot: *mut AioSlot) {
107    let prev = (*slot).prev;
108    let next = (*slot).next;
109    if prev.is_null() {
110        AIO_HEAD = next;
111    } else {
112        (*prev).next = next;
113    }
114    if !next.is_null() {
115        (*next).prev = prev;
116    }
117    (*slot).prev = core::ptr::null_mut();
118    (*slot).next = core::ptr::null_mut();
119}
120
121unsafe fn aio_finish(cb: *mut Aiocb, ret: isize, err: c_int) -> bool {
122    aio_lock();
123    let slot = aio_get(cb);
124    if slot.is_null() {
125        aio_unlock();
126        return false;
127    }
128    (*slot).ret = ret;
129    (*slot).err = err;
130    (*slot).active = true;
131    aio_unlock();
132    true
133}
134
135#[no_mangle]
136pub unsafe extern "C" fn aio_read(aiocbp: *mut Aiocb) -> c_int {
137    if aiocbp.is_null() { ERRNO!() = EINVAL; return -1; }
138    let r = crate::pread(
139        (*aiocbp).aio_fildes,
140        (*aiocbp).aio_buf,
141        (*aiocbp).aio_nbytes,
142        (*aiocbp).aio_offset,
143    );
144    let ok = if r < 0 {
145        aio_finish(aiocbp, -1, ERRNO!())
146    } else {
147        aio_finish(aiocbp, r, 0)
148    };
149    if !ok { ERRNO!() = EAGAIN; return -1; }
150    0
151}
152
153#[no_mangle]
154pub unsafe extern "C" fn aio_write(aiocbp: *mut Aiocb) -> c_int {
155    if aiocbp.is_null() { ERRNO!() = EINVAL; return -1; }
156    let r = crate::pwrite(
157        (*aiocbp).aio_fildes,
158        (*aiocbp).aio_buf as *const u8,
159        (*aiocbp).aio_nbytes,
160        (*aiocbp).aio_offset,
161    );
162    let ok = if r < 0 {
163        aio_finish(aiocbp, -1, ERRNO!())
164    } else {
165        aio_finish(aiocbp, r, 0)
166    };
167    if !ok { ERRNO!() = EAGAIN; return -1; }
168    0
169}
170
171#[no_mangle]
172pub unsafe extern "C" fn aio_fsync(op: c_int, aiocbp: *mut Aiocb) -> c_int {
173    if aiocbp.is_null() { ERRNO!() = EINVAL; return -1; }
174    let r = if op == 1 { crate::fdatasync((*aiocbp).aio_fildes) } else { crate::fsync((*aiocbp).aio_fildes) };
175    let ok = if r < 0 {
176        aio_finish(aiocbp, -1, ERRNO!())
177    } else {
178        aio_finish(aiocbp, 0, 0)
179    };
180    if !ok { ERRNO!() = EAGAIN; return -1; }
181    0
182}
183
184#[no_mangle]
185pub unsafe extern "C" fn aio_error(aiocbp: *const Aiocb) -> c_int {
186    if aiocbp.is_null() { return EINVAL; }
187    aio_lock();
188    let slot = aio_find(aiocbp);
189    let err = if slot.is_null() { EINVAL } else { (*slot).err };
190    aio_unlock();
191    err
192}
193
194#[no_mangle]
195pub unsafe extern "C" fn aio_return(aiocbp: *mut Aiocb) -> isize {
196    if aiocbp.is_null() { ERRNO!() = EINVAL; return -1; }
197    aio_lock();
198    let slot = aio_find(aiocbp);
199    if slot.is_null() {
200        aio_unlock();
201        ERRNO!() = EINVAL;
202        return -1;
203    }
204    let ret = (*slot).ret;
205    (*slot).active = false;
206    (*slot).cb = core::ptr::null_mut();
207    (*slot).ret = 0;
208    (*slot).err = 0;
209    aio_unlink(slot);
210    (*slot).next = AIO_FREE;
211    AIO_FREE = slot;
212    aio_unlock();
213    ret
214}
215
216#[no_mangle]
217pub unsafe extern "C" fn aio_cancel(fd: c_int, aiocbp: *mut Aiocb) -> c_int {
218    aio_lock();
219    if !aiocbp.is_null() {
220        let slot = aio_find(aiocbp);
221        let canceled = !slot.is_null() && (*slot).active && (*aiocbp).aio_fildes == fd;
222        if canceled {
223            (*slot).ret = -1;
224            (*slot).err = ECANCELED;
225        }
226        aio_unlock();
227        return if canceled { 1 } else { 2 };
228    }
229    let mut canceled = false;
230    let mut slot = AIO_HEAD;
231    while !slot.is_null() {
232        if (*slot).active && !(*slot).cb.is_null() && (*(*slot).cb).aio_fildes == fd {
233            (*slot).ret = -1;
234            (*slot).err = ECANCELED;
235            canceled = true;
236        }
237        slot = (*slot).next;
238    }
239    aio_unlock();
240    if canceled { 1 } else { 2 }
241}
242
243#[no_mangle]
244pub unsafe extern "C" fn aio_suspend(
245    aiocb_list: *const *const Aiocb,
246    nent: c_int,
247    _timeout: *const timespec,
248) -> c_int {
249    if nent <= 0 || aiocb_list.is_null() { ERRNO!() = EINVAL; return -1; }
250    // Every request completes synchronously, so all of them are already done.
251    aio_lock();
252    for i in 0..nent as usize {
253        let cb = *aiocb_list.add(i);
254        if cb.is_null() { continue; }
255        if aio_find(cb).is_null() {
256            aio_unlock();
257            ERRNO!() = EINVAL;
258            return -1;
259        }
260    }
261    aio_unlock();
262    0
263}
264
265#[repr(C)]
266#[derive(Copy, Clone)]
267pub struct SigEvent {
268    pub sigev_value: usize,
269    pub sigev_signo: c_int,
270    pub sigev_notify: c_int,
271    pub sigev_un: [u8; 48],
272}
273
274const SIGEV_SIGNAL_V: c_int = 0;
275const SIGEV_NONE_V: c_int = 1;
276const SIGEV_THREAD_V: c_int = 2;
277
278unsafe fn aio_notify(sev: *const SigEvent) {
279    if sev.is_null() { return; }
280    match (*sev).sigev_notify {
281        SIGEV_SIGNAL_V => {
282            crate::sigqueue(crate::getpid(), (*sev).sigev_signo, (*sev).sigev_value as *mut c_void);
283        }
284        SIGEV_THREAD_V => {
285            let fnptr = core::ptr::read_unaligned((*sev).sigev_un.as_ptr().add(0) as *const usize);
286            if fnptr != 0 {
287                let f: unsafe extern "C" fn(usize) = core::mem::transmute(fnptr);
288                f((*sev).sigev_value);
289            }
290        }
291        _ => {}
292    }
293}
294
295#[no_mangle]
296pub unsafe extern "C" fn lio_listio(
297    mode: c_int,
298    aiocb_list: *const *mut Aiocb,
299    nent: c_int,
300    sevp: *mut SigEvent,
301) -> c_int {
302    if nent < 0 || aiocb_list.is_null() { ERRNO!() = EINVAL; return -1; }
303    let mut ok = 0;
304    for i in 0..nent as usize {
305        let cb = *aiocb_list.add(i);
306        if cb.is_null() { continue; }
307        let r = match (*cb).aio_lio_opcode {
308            0 => aio_read(cb),
309            1 => aio_write(cb),
310            _ => 0,
311        };
312        if r == 0 { ok += 1; }
313    }
314    if mode == 1 && !sevp.is_null() {
315        aio_notify(sevp);
316    }
317    if ok == 0 && nent > 0 { -1 } else { 0 }
318}
319
320// ============================================================
321// pthread introspection
322// ============================================================
323
324static mut PTHREAD_DEFAULT_STACK: usize = STACK_SIZE;
325static mut PTHREAD_DEFAULT_GUARD: usize = 4096;
326static mut PTHREAD_CONCURRENCY: c_int = 0;
327
328/// Find the mapping that contains `addr` by reading the tail of
329/// /proc/self/maps. Used for the main thread, whose stack was set up by the
330/// loader and is not tracked in a `Thread` slot.
331unsafe fn mapping_containing(addr: usize) -> Option<(usize, usize)> {
332    let fd = crate::open(b"/proc/self/maps\0".as_ptr() as *const core::ffi::c_char, 0, 0);
333    if fd < 0 {
334        return None;
335    }
336    let size: i64 = crate::lseek(fd, 0, 2);
337    let want: i64 = if size > 16384 { 16384 } else { size };
338    let start = if size > want { size - want } else { 0 };
339    if crate::lseek(fd, start, 0) < 0 {
340        crate::close(fd);
341        return None;
342    }
343    let mut buf = [0u8; 16384];
344    let mut len = 0usize;
345    while len < buf.len() {
346        let n = crate::read(
347            fd,
348            buf.as_mut_ptr().add(len) as *mut core::ffi::c_void,
349            buf.len() - len,
350        );
351        if n <= 0 {
352            break;
353        }
354        len += n as usize;
355    }
356    crate::close(fd);
357
358    let mut result = None;
359    let mut line_start = 0usize;
360    while line_start < len {
361        let mut line_end = line_start;
362        while line_end < len && buf[line_end] != b'\n' {
363            line_end += 1;
364        }
365        let line = &buf[line_start..line_end];
366        if let Some(dash) = line.iter().position(|&c| c == b'-') {
367            let mut lo = 0usize;
368            let mut ok = true;
369            for &c in &line[..dash] {
370                if c.is_ascii_digit() {
371                    lo = lo * 16 + (c - b'0') as usize;
372                } else if (b'a'..=b'f').contains(&c) {
373                    lo = lo * 16 + (c - b'a' + 10) as usize;
374                } else {
375                    ok = false;
376                    break;
377                }
378            }
379            if ok && !line[..dash].is_empty() {
380                let mut hi = 0usize;
381                let mut i = dash + 1;
382                while i < line.len() {
383                    let c = line[i];
384                    if c.is_ascii_digit() {
385                        hi = hi * 16 + (c - b'0') as usize;
386                    } else if (b'a'..=b'f').contains(&c) {
387                        hi = hi * 16 + (c - b'a' + 10) as usize;
388                    } else {
389                        break;
390                    }
391                    i += 1;
392                }
393                if i > dash + 1 && lo <= addr && addr < hi {
394                    result = Some((lo, hi - lo));
395                }
396            }
397        }
398        line_start = line_end + 1;
399    }
400    result
401}
402
403#[no_mangle]
404pub unsafe extern "C" fn pthread_getattr_np(thread: PthreadT, attr: *mut pthread_attr_t) -> c_int {
405    if attr.is_null() || thread == 0 { return EINVAL; }
406    let slot = thread as *mut Thread;
407    core::ptr::write_bytes(attr, 0, 1);
408    let mut stack_base = (*slot).stack as usize;
409    let mut stack_size = if (*slot).stack_size > 0 { (*slot).stack_size } else { PTHREAD_DEFAULT_STACK };
410    if stack_base == 0 {
411        // Main thread: the loader allocated its stack, so ask the kernel which
412        // mapping holds the current stack pointer. Returning 0 here makes
413        // runtimes such as V8 conclude they are running on a foreign stack.
414        let sp: usize;
415        #[cfg(target_arch = "x86_64")]
416        core::arch::asm!("mov {}, rsp", out(reg) sp, options(nomem, nostack));
417        #[cfg(target_arch = "aarch64")]
418        core::arch::asm!("mov {}, sp", out(reg) sp, options(nomem, nostack));
419        #[cfg(target_arch = "riscv64")]
420        core::arch::asm!("mv {}, sp", out(reg) sp, options(nomem, nostack));
421        if let Some((base, size)) = mapping_containing(sp) {
422            stack_base = base;
423            stack_size = size;
424        } else {
425            stack_base = sp & !0xfff;
426            stack_size = 4096;
427        }
428    }
429    *((*attr).__i.as_mut_ptr() as *mut usize) = stack_size;
430    *((*attr).__i.as_mut_ptr().add(2) as *mut usize) = PTHREAD_DEFAULT_GUARD;
431    *((*attr).__i.as_mut_ptr().add(4) as *mut *mut c_void) =
432        (stack_base + stack_size) as *mut c_void;
433    (*attr).__i[6] = if (*slot).detach_state == DT_DETACHED { 1 } else { 0 };
434    (*attr).__i[7] = 0;
435    0
436}
437
438#[no_mangle]
439pub unsafe extern "C" fn pthread_getattr_default_np(attr: *mut pthread_attr_t) -> c_int {
440    if attr.is_null() { return EINVAL; }
441    core::ptr::write_bytes(attr, 0, 1);
442    *((*attr).__i.as_mut_ptr() as *mut usize) = PTHREAD_DEFAULT_STACK;
443    *((*attr).__i.as_mut_ptr().add(2) as *mut usize) = PTHREAD_DEFAULT_GUARD;
444    (*attr).__i[6] = 0;
445    (*attr).__i[7] = 1;
446    0
447}
448
449#[no_mangle]
450pub unsafe extern "C" fn pthread_setattr_default_np(attr: *const pthread_attr_t) -> c_int {
451    if attr.is_null() { return EINVAL; }
452    let stack = *((*attr).__i.as_ptr() as *const usize);
453    let guard = *((*attr).__i.as_ptr().add(2) as *const usize);
454    // Reject attributes that cannot be made defaults.
455    if (*attr).__i[6] != 0 { return EINVAL; }
456    if stack > 0 {
457        if stack < 16384 { return EINVAL; }
458        PTHREAD_DEFAULT_STACK = stack;
459    }
460    if guard > 0 { PTHREAD_DEFAULT_GUARD = guard; }
461    0
462}
463
464#[no_mangle]
465pub unsafe extern "C" fn pthread_getconcurrency() -> c_int {
466    PTHREAD_CONCURRENCY
467}
468
469#[no_mangle]
470pub unsafe extern "C" fn pthread_setconcurrency(level: c_int) -> c_int {
471    if level < 0 { return EINVAL; }
472    PTHREAD_CONCURRENCY = level;
473    0
474}
475
476#[no_mangle]
477pub unsafe extern "C" fn pthread_getcpuclockid(thread: PthreadT, clockid: *mut i32) -> c_int {
478    if thread == 0 || clockid.is_null() { return EINVAL; }
479    let slot = thread as *mut Thread;
480    let tid = (*slot).tid as i64;
481    *clockid = (((-tid - 1) * 8 + 6) & 0xffffffff) as i32;
482    0
483}
484
485#[no_mangle]
486pub unsafe extern "C" fn pthread_setname_np(thread: PthreadT, name: *const c_char) -> c_int {
487    if thread == 0 || name.is_null() { return EINVAL; }
488    if crate::strnlen(name as *const u8, 16) > 15 { return 34; }
489    let slot = thread as *mut Thread;
490    if slot == pthread_self() as *mut Thread {
491        return if crate::prctl(15, name as c_long) < 0 { ERRNO!() } else { 0 };
492    }
493    let mut path = [0 as c_char; 48];
494    let mut o = 0usize;
495    for &c in b"/proc/self/task/" { path[o] = c as c_char; o += 1; }
496    let mut tid = (*slot).tid as u32;
497    let mut digits = [0u8; 10];
498    let mut d = 0usize;
499    if tid == 0 { digits[0] = b'0'; d = 1; }
500    while tid > 0 { digits[d] = b'0' + (tid % 10) as u8; tid /= 10; d += 1; }
501    while d > 0 { d -= 1; path[o] = digits[d] as c_char; o += 1; }
502    for &c in b"/comm\0" { path[o] = c as c_char; o += 1; }
503    let fd = crate::open(path.as_ptr(), 1, 0);
504    if fd < 0 { return ERRNO!(); }
505    let n = crate::strlen(name);
506    let w = crate::write(fd, name as *const c_void, n);
507    crate::close(fd);
508    if w < 0 { ERRNO!() } else { 0 }
509}
510
511#[no_mangle]
512pub unsafe extern "C" fn pthread_getname_np(thread: PthreadT, name: *mut c_char, len: usize) -> c_int {
513    if thread == 0 || name.is_null() { return EINVAL; }
514    if len < 16 { return 34; }
515    let slot = thread as *mut Thread;
516    if slot == pthread_self() as *mut Thread {
517        return if crate::prctl(16, name as c_long) < 0 { ERRNO!() } else { 0 };
518    }
519    let mut path = [0 as c_char; 48];
520    let mut o = 0usize;
521    for &c in b"/proc/self/task/" { path[o] = c as c_char; o += 1; }
522    let mut tid = (*slot).tid as u32;
523    let mut digits = [0u8; 10];
524    let mut d = 0usize;
525    if tid == 0 { digits[0] = b'0'; d = 1; }
526    while tid > 0 { digits[d] = b'0' + (tid % 10) as u8; tid /= 10; d += 1; }
527    while d > 0 { d -= 1; path[o] = digits[d] as c_char; o += 1; }
528    for &c in b"/comm\0" { path[o] = c as c_char; o += 1; }
529    let fd = crate::open(path.as_ptr(), 0, 0);
530    if fd < 0 { return ERRNO!(); }
531    let n = crate::read(fd, name as *mut c_void, len);
532    crate::close(fd);
533    if n < 0 { return ERRNO!(); }
534    let mut l = n as usize;
535    if l > 0 && *name.add(l - 1) == b'\n' as c_char { l -= 1; }
536    *name.add(l) = 0;
537    0
538}
539
540#[no_mangle]
541pub unsafe extern "C" fn pthread_getschedparam(thread: PthreadT, policy: *mut c_int, param: *mut sched_param) -> c_int {
542    if thread == 0 || policy.is_null() || param.is_null() { return EINVAL; }
543    let tid = (*(thread as *mut Thread)).tid;
544    *policy = crate::sched_getscheduler(tid);
545    crate::sched_getparam(tid, param as *mut c_void)
546}
547
548#[no_mangle]
549pub unsafe extern "C" fn pthread_setschedparam(thread: PthreadT, policy: c_int, param: *const sched_param) -> c_int {
550    if thread == 0 || param.is_null() { return EINVAL; }
551    let tid = (*(thread as *mut Thread)).tid;
552    crate::sched_setscheduler(tid, policy, param as *const c_void)
553}
554
555#[no_mangle]
556pub unsafe extern "C" fn pthread_setschedprio(thread: PthreadT, prio: c_int) -> c_int {
557    if thread == 0 { return EINVAL; }
558    let tid = (*(thread as *mut Thread)).tid;
559    let p = sched_param { sched_priority: prio };
560    crate::sched_setparam(tid, &p as *const sched_param as *const c_void)
561}
562
563#[no_mangle]
564pub unsafe extern "C" fn pthread_mutex_getprioceiling(mutex: *const pthread_mutex_t, ceiling: *mut c_int) -> c_int {
565    if mutex.is_null() || ceiling.is_null() { return EINVAL; }
566    *ceiling = 0;
567    0
568}
569
570#[no_mangle]
571pub unsafe extern "C" fn pthread_mutex_setprioceiling(mutex: *mut pthread_mutex_t, ceiling: c_int, old: *mut c_int) -> c_int {
572    if mutex.is_null() { return EINVAL; }
573    if !old.is_null() { *old = 0; }
574    let _ = ceiling;
575    0
576}
577
578// ============================================================
579// posix_spawn attributes and file actions
580// ============================================================
581
582
583#[repr(C)]
584pub struct posix_spawn_file_actions_t {
585    __pad0: [c_int; 2],
586    __actions: *mut c_void,
587    __pad: [c_int; 16],
588}
589
590#[repr(C)]
591#[derive(Copy, Clone)]
592pub struct posix_spawnattr_t {
593    __flags: c_int,
594    __pgrp: c_int,
595    __def: [c_ulong; 16],
596    __mask: [c_ulong; 16],
597    __prio: c_int,
598    __pol: c_int,
599    __fn: *mut c_void,
600    __pad: [u8; 64 - core::mem::size_of::<*mut c_void>()],
601}
602
603#[repr(C)]
604struct SpawnAction {
605    next: *mut SpawnAction,
606    kind: c_int,
607    fd: c_int,
608    newfd: c_int,
609    oflag: c_int,
610    mode: u32,
611    path: *mut c_char,
612    path_owned: bool,
613}
614
615static mut SPAWN_DEFAULT_ATTR: posix_spawnattr_t = posix_spawnattr_t {
616    __flags: 0,
617    __pgrp: 0,
618    __def: [0; 16],
619    __mask: [0; 16],
620    __prio: 0,
621    __pol: 0,
622    __fn: core::ptr::null_mut(),
623    __pad: [0; 64 - core::mem::size_of::<*mut c_void>()],
624};
625
626unsafe fn spawn_actions(fa: *const posix_spawn_file_actions_t) -> *mut SpawnAction {
627    (*fa).__actions as *mut SpawnAction
628}
629
630unsafe fn spawn_action_add(
631    fa: *mut posix_spawn_file_actions_t,
632    kind: c_int,
633    fd: c_int,
634    newfd: c_int,
635    oflag: c_int,
636    mode: u32,
637    path: *const c_char,
638) -> c_int {
639    let node = crate::calloc(1, core::mem::size_of::<SpawnAction>()) as *mut SpawnAction;
640    if node.is_null() { return ENOMEM; }
641    (*node).kind = kind;
642    (*node).fd = fd;
643    (*node).newfd = newfd;
644    (*node).oflag = oflag;
645    (*node).mode = mode;
646    (*node).next = core::ptr::null_mut();
647    if !path.is_null() {
648        let n = crate::strlen(path);
649        let p = crate::malloc(n + 1) as *mut c_char;
650        if p.is_null() { crate::free(node as *mut c_void); return ENOMEM; }
651        core::ptr::copy_nonoverlapping(path, p, n + 1);
652        (*node).path = p;
653        (*node).path_owned = true;
654    }
655    if (*fa).__actions.is_null() {
656        (*fa).__actions = node as *mut c_void;
657    } else {
658        let mut tail = (*fa).__actions as *mut SpawnAction;
659        while !(*tail).next.is_null() { tail = (*tail).next; }
660        (*tail).next = node;
661    }
662    0
663}
664
665#[no_mangle]
666pub unsafe extern "C" fn posix_spawn_file_actions_init(fa: *mut posix_spawn_file_actions_t) -> c_int {
667    if fa.is_null() { return EINVAL; }
668    core::ptr::write_bytes(fa, 0, 1);
669    0
670}
671
672#[no_mangle]
673pub unsafe extern "C" fn posix_spawn_file_actions_destroy(fa: *mut posix_spawn_file_actions_t) -> c_int {
674    if fa.is_null() { return EINVAL; }
675    let mut p = spawn_actions(fa);
676    while !p.is_null() {
677        let next = (*p).next;
678        if (*p).path_owned && !(*p).path.is_null() { crate::free((*p).path as *mut c_void); }
679        crate::free(p as *mut c_void);
680        p = next;
681    }
682    (*fa).__actions = core::ptr::null_mut();
683    0
684}
685
686#[no_mangle]
687pub unsafe extern "C" fn posix_spawn_file_actions_addclose(fa: *mut posix_spawn_file_actions_t, fd: c_int) -> c_int {
688    if fa.is_null() || fd < 0 { return EINVAL; }
689    spawn_action_add(fa, 0, fd, 0, 0, 0, core::ptr::null())
690}
691
692#[no_mangle]
693pub unsafe extern "C" fn posix_spawn_file_actions_adddup2(fa: *mut posix_spawn_file_actions_t, fd: c_int, newfd: c_int) -> c_int {
694    if fa.is_null() || fd < 0 || newfd < 0 { return EINVAL; }
695    spawn_action_add(fa, 1, fd, newfd, 0, 0, core::ptr::null())
696}
697
698#[no_mangle]
699pub unsafe extern "C" fn posix_spawn_file_actions_addopen(
700    fa: *mut posix_spawn_file_actions_t,
701    fd: c_int,
702    path: *const c_char,
703    oflag: c_int,
704    mode: u32,
705) -> c_int {
706    if fa.is_null() || fd < 0 || path.is_null() { return EINVAL; }
707    spawn_action_add(fa, 2, fd, 0, oflag, mode, path)
708}
709
710#[no_mangle]
711pub unsafe extern "C" fn posix_spawn_file_actions_addchdir_np(fa: *mut posix_spawn_file_actions_t, path: *const c_char) -> c_int {
712    if fa.is_null() || path.is_null() { return EINVAL; }
713    spawn_action_add(fa, 3, 0, 0, 0, 0, path)
714}
715
716#[no_mangle]
717pub unsafe extern "C" fn posix_spawn_file_actions_addfchdir_np(fa: *mut posix_spawn_file_actions_t, fd: c_int) -> c_int {
718    if fa.is_null() || fd < 0 { return EINVAL; }
719    spawn_action_add(fa, 4, fd, 0, 0, 0, core::ptr::null())
720}
721
722unsafe fn spawn_apply_actions(fa: *const posix_spawn_file_actions_t) {
723    if fa.is_null() { return; }
724    let mut p = spawn_actions(fa);
725    while !p.is_null() {
726        match (*p).kind {
727            0 => { crate::close((*p).fd); }
728            1 => { crate::dup2((*p).fd, (*p).newfd); }
729            2 => {
730                let fd = crate::open((*p).path, (*p).oflag, (*p).mode as c_int);
731                if fd >= 0 && fd != (*p).fd {
732                    crate::dup2(fd, (*p).fd);
733                    crate::close(fd);
734                }
735            }
736            3 => { crate::chdir((*p).path); }
737            4 => { crate::fchdir((*p).fd); }
738            _ => {}
739        }
740        p = (*p).next;
741    }
742}
743
744unsafe fn spawn_apply_attr(attr: *const posix_spawnattr_t) {
745    if attr.is_null() { return; }
746    let flags = (*attr).__flags;
747    if flags & 1 != 0 {
748        if crate::geteuid() == 0 {
749            crate::setgid(crate::getgid());
750            crate::setuid(crate::getuid());
751        }
752    }
753    if flags & 2 != 0 { crate::setpgid(0, (*attr).__pgrp); }
754    if flags & 4 != 0 {
755        for sig in 1..65 {
756            let idx = (sig - 1) / 64;
757            let bit = (sig - 1) % 64;
758            if idx < 16 && ((*attr).__def[idx] >> bit) & 1 != 0 {
759                crate::signal(sig as c_int, 0);
760            }
761        }
762    }
763    if flags & 8 != 0 {
764        crate::sigprocmask(2, (*attr).__mask.as_ptr() as *const SigSetT, core::ptr::null_mut());
765    }
766    if flags & 16 != 0 {
767        let p = sched_param { sched_priority: (*attr).__prio };
768        crate::sched_setparam(0, &p as *const sched_param as *const c_void);
769    }
770    if flags & 32 != 0 {
771        let p = sched_param { sched_priority: (*attr).__prio };
772        crate::sched_setscheduler(0, (*attr).__pol, &p as *const sched_param as *const c_void);
773    }
774}
775
776#[no_mangle]
777pub unsafe extern "C" fn posix_spawnattr_init(attr: *mut posix_spawnattr_t) -> c_int {
778    if attr.is_null() { return EINVAL; }
779    *attr = SPAWN_DEFAULT_ATTR;
780    0
781}
782
783#[no_mangle]
784pub unsafe extern "C" fn posix_spawnattr_destroy(attr: *mut posix_spawnattr_t) -> c_int {
785    if attr.is_null() { return EINVAL; }
786    0
787}
788
789#[no_mangle]
790pub unsafe extern "C" fn posix_spawnattr_getflags(attr: *const posix_spawnattr_t, flags: *mut i16) -> c_int {
791    if attr.is_null() || flags.is_null() { return EINVAL; }
792    *flags = (*attr).__flags as i16;
793    0
794}
795
796#[no_mangle]
797pub unsafe extern "C" fn posix_spawnattr_setflags(attr: *mut posix_spawnattr_t, flags: i16) -> c_int {
798    if attr.is_null() { return EINVAL; }
799    if (flags as c_int) & !0x7f != 0 { return EINVAL; }
800    (*attr).__flags = flags as c_int;
801    0
802}
803
804#[no_mangle]
805pub unsafe extern "C" fn posix_spawnattr_getpgroup(attr: *const posix_spawnattr_t, pgroup: *mut c_int) -> c_int {
806    if attr.is_null() || pgroup.is_null() { return EINVAL; }
807    *pgroup = (*attr).__pgrp;
808    0
809}
810
811#[no_mangle]
812pub unsafe extern "C" fn posix_spawnattr_setpgroup(attr: *mut posix_spawnattr_t, pgroup: c_int) -> c_int {
813    if attr.is_null() { return EINVAL; }
814    (*attr).__pgrp = pgroup;
815    0
816}
817
818#[no_mangle]
819pub unsafe extern "C" fn posix_spawnattr_getschedparam(attr: *const posix_spawnattr_t, param: *mut sched_param) -> c_int {
820    if attr.is_null() || param.is_null() { return EINVAL; }
821    (*param).sched_priority = (*attr).__prio;
822    0
823}
824
825#[no_mangle]
826pub unsafe extern "C" fn posix_spawnattr_setschedparam(attr: *mut posix_spawnattr_t, param: *const sched_param) -> c_int {
827    if attr.is_null() || param.is_null() { return EINVAL; }
828    (*attr).__prio = (*param).sched_priority;
829    0
830}
831
832#[no_mangle]
833pub unsafe extern "C" fn posix_spawnattr_getschedpolicy(attr: *const posix_spawnattr_t, policy: *mut c_int) -> c_int {
834    if attr.is_null() || policy.is_null() { return EINVAL; }
835    *policy = (*attr).__pol;
836    0
837}
838
839#[no_mangle]
840pub unsafe extern "C" fn posix_spawnattr_setschedpolicy(attr: *mut posix_spawnattr_t, policy: c_int) -> c_int {
841    if attr.is_null() { return EINVAL; }
842    (*attr).__pol = policy;
843    0
844}
845
846#[no_mangle]
847pub unsafe extern "C" fn posix_spawnattr_getsigdefault(attr: *const posix_spawnattr_t, set: *mut SigSetT) -> c_int {
848    if attr.is_null() || set.is_null() { return EINVAL; }
849    *set = (*attr).__def[0];
850    0
851}
852
853#[no_mangle]
854pub unsafe extern "C" fn posix_spawnattr_setsigdefault(attr: *mut posix_spawnattr_t, set: *const SigSetT) -> c_int {
855    if attr.is_null() || set.is_null() { return EINVAL; }
856    (*attr).__def = [0; 16];
857    (*attr).__def[0] = *set;
858    0
859}
860
861#[no_mangle]
862pub unsafe extern "C" fn posix_spawnattr_getsigmask(attr: *const posix_spawnattr_t, set: *mut SigSetT) -> c_int {
863    if attr.is_null() || set.is_null() { return EINVAL; }
864    *set = (*attr).__mask[0];
865    0
866}
867
868#[no_mangle]
869pub unsafe extern "C" fn posix_spawnattr_setsigmask(attr: *mut posix_spawnattr_t, set: *const SigSetT) -> c_int {
870    if attr.is_null() || set.is_null() { return EINVAL; }
871    (*attr).__mask = [0; 16];
872    (*attr).__mask[0] = *set;
873    0
874}
875
876// ============================================================
877// stdio internals
878// ============================================================
879
880#[no_mangle]
881pub unsafe extern "C" fn __fbufsize(f: *mut FILE) -> usize {
882    if f.is_null() { return 0; }
883    (*f).buf_size
884}
885
886#[no_mangle]
887pub unsafe extern "C" fn __flbf(f: *mut FILE) -> c_int {
888    if f.is_null() { return 0; }
889    if (*f).lbf >= 0 { 1 } else { 0 }
890}
891
892#[no_mangle]
893pub unsafe extern "C" fn __fpending(f: *mut FILE) -> usize {
894    if f.is_null() || (*f).wend.is_null() { return 0; }
895    (*f).wpos as usize - (*f).wbase as usize
896}
897
898#[no_mangle]
899pub unsafe extern "C" fn __fpurge(f: *mut FILE) {
900    if f.is_null() { return; }
901    (*f).wpos = core::ptr::null_mut();
902    (*f).wbase = core::ptr::null_mut();
903    (*f).wend = core::ptr::null_mut();
904    (*f).rpos = core::ptr::null_mut();
905    (*f).rend = core::ptr::null_mut();
906}
907
908#[no_mangle]
909pub unsafe extern "C" fn __freadable(f: *mut FILE) -> c_int {
910    if f.is_null() { return 0; }
911    if (*f).flags & F_NORD == 0 { 1 } else { 0 }
912}
913
914#[no_mangle]
915pub unsafe extern "C" fn __freadahead(f: *mut FILE) -> usize {
916    if f.is_null() || (*f).rend.is_null() { return 0; }
917    (*f).rend as usize - (*f).rpos as usize
918}
919
920#[no_mangle]
921pub unsafe extern "C" fn __freading(f: *mut FILE) -> c_int {
922    if f.is_null() { return 0; }
923    if (*f).flags & F_NOWR != 0 || !(*f).rend.is_null() { 1 } else { 0 }
924}
925
926#[no_mangle]
927pub unsafe extern "C" fn __freadptr(f: *mut FILE, sizep: *mut usize) -> *const c_char {
928    if f.is_null() || sizep.is_null() { return core::ptr::null(); }
929    if (*f).rpos == (*f).rend { *sizep = 0; return core::ptr::null(); }
930    *sizep = (*f).rend as usize - (*f).rpos as usize;
931    (*f).rpos as *const c_char
932}
933
934#[no_mangle]
935pub unsafe extern "C" fn __freadptrinc(f: *mut FILE, inc: usize) {
936    if f.is_null() || (*f).rpos.is_null() { return; }
937    let left = (*f).rend as usize - (*f).rpos as usize;
938    let step = if inc > left { left } else { inc };
939    (*f).rpos = (*f).rpos.add(step);
940}
941
942#[no_mangle]
943pub unsafe extern "C" fn __fsetlocking(_f: *mut FILE, _type: c_int) -> c_int {
944    1
945}
946
947#[no_mangle]
948pub unsafe extern "C" fn __fwritable(f: *mut FILE) -> c_int {
949    if f.is_null() { return 0; }
950    if (*f).flags & F_NOWR == 0 { 1 } else { 0 }
951}
952
953#[no_mangle]
954pub unsafe extern "C" fn __fwriting(f: *mut FILE) -> c_int {
955    if f.is_null() { return 0; }
956    if (*f).flags & F_NORD != 0 || !(*f).wend.is_null() { 1 } else { 0 }
957}
958
959#[no_mangle]
960pub unsafe extern "C" fn __fseterr(f: *mut FILE) {
961    if !f.is_null() { (*f).flags |= F_ERR; }
962}
963
964#[no_mangle]
965pub unsafe extern "C" fn __fputwc_unlocked(c: wchar_t, f: *mut FILE) -> wint_t {
966    crate::fputwc(c, f)
967}
968
969// ============================================================
970// Miscellaneous legacy calls
971// ============================================================
972
973#[no_mangle]
974pub unsafe extern "C" fn sigqueue(pid: c_int, sig: c_int, value: *mut c_void) -> c_int {
975    let mut si = [0u8; 128];
976    *(si.as_mut_ptr() as *mut c_int) = sig;
977    *(si.as_mut_ptr().add(8) as *mut c_int) = -1; // SI_QUEUE
978    *(si.as_mut_ptr().add(16) as *mut c_int) = crate::getpid();
979    *(si.as_mut_ptr().add(20) as *mut u32) = crate::getuid();
980    *(si.as_mut_ptr().add(24) as *mut *mut c_void) = value;
981    let r = <Arch as Syscalls>::syscall3(sysno(129, 138), pid as i64, sig as i64, si.as_ptr() as i64);
982    if r < 0 { ERRNO!() = (-r) as c_int; -1 } else { 0 }
983}
984
985#[no_mangle]
986pub unsafe extern "C" fn timespec_get(ts: *mut timespec, base: c_int) -> c_int {
987    if base != 1 || ts.is_null() { return 0; }
988    if crate::clock_gettime(0, ts) != 0 { return 0; }
989    base
990}
991
992#[no_mangle]
993pub unsafe extern "C" fn ualarm(usecs: u32, interval: u32) -> u32 {
994    #[repr(C)]
995    struct ITV {
996        sec: i64,
997        usec: i64,
998    }
999    let mut new: [ITV; 2] = [
1000        ITV { sec: (interval / 1_000_000) as i64, usec: (interval % 1_000_000) as i64 },
1001        ITV { sec: (usecs / 1_000_000) as i64, usec: (usecs % 1_000_000) as i64 },
1002    ];
1003    let mut old: [ITV; 2] = [ITV { sec: 0, usec: 0 }, ITV { sec: 0, usec: 0 }];
1004    if crate::setitimer(0, new.as_mut_ptr() as *const c_void, old.as_mut_ptr() as *mut c_void) != 0 {
1005        return u32::MAX;
1006    }
1007    (old[0].sec as u32) * 1_000_000 + old[0].usec as u32
1008}
1009
1010static mut LCKPWDF_FD: c_int = -1;
1011
1012#[repr(C)]
1013#[derive(Copy, Clone)]
1014struct FlockT {
1015    l_type: i16,
1016    l_whence: i16,
1017    l_start: i64,
1018    l_len: i64,
1019    l_pid: i32,
1020    __pad: i32,
1021}
1022
1023#[no_mangle]
1024pub unsafe extern "C" fn lckpwdf() -> c_int {
1025    if LCKPWDF_FD >= 0 { return 0; }
1026    let fd = crate::open(
1027        b"/etc/.pwd.lock\0".as_ptr() as *const c_char,
1028        1 | 0x40 | 0x80000,
1029        0o600,
1030    );
1031    if fd < 0 { return -1; }
1032    let fl = FlockT { l_type: 1, l_whence: 0, l_start: 0, l_len: 0, l_pid: 0, __pad: 0 };
1033    if crate::fcntl(fd, 7, &fl as *const FlockT as *mut c_void) != 0 {
1034        crate::close(fd);
1035        return -1;
1036    }
1037    LCKPWDF_FD = fd;
1038    0
1039}
1040
1041#[no_mangle]
1042pub unsafe extern "C" fn ulckpwdf() -> c_int {
1043    if LCKPWDF_FD < 0 { return -1; }
1044    let fd = LCKPWDF_FD;
1045    LCKPWDF_FD = -1;
1046    crate::close(fd);
1047    0
1048}
1049
1050#[no_mangle]
1051pub unsafe extern "C" fn malloc_usable_size(ptr: *mut u8) -> usize {
1052    crate::alloc_usable_size(ptr)
1053}
1054
1055#[no_mangle]
1056pub unsafe extern "C" fn __wcsftime_l(
1057    s: *mut wchar_t,
1058    max: usize,
1059    fmt: *const wchar_t,
1060    tm: *const c_void,
1061    _loc: locale_t,
1062) -> usize {
1063    crate::wcsftime(s as *mut u32, max, fmt as *const u32, tm)
1064}
1065
1066#[no_mangle]
1067pub unsafe extern "C" fn __wctype_l(name: *const c_char, _loc: locale_t) -> wctype_t {
1068    crate::wctype(name)
1069}
1070
1071#[no_mangle]
1072pub unsafe extern "C" fn __wctrans_l(name: *const c_char, _loc: locale_t) -> wctrans_t {
1073    crate::wctrans(name)
1074}
1075
1076#[no_mangle]
1077pub unsafe extern "C" fn __fgetwc_unlocked(f: *mut FILE) -> wint_t {
1078    crate::fgetwc(f)
1079}
1080
1081#[no_mangle]
1082pub unsafe extern "C" fn _flushlbf() {
1083    crate::fflush(core::ptr::null_mut());
1084}