Allocate the new handler node independently of the initial chain
conversion so handlers beyond the second are appended instead of silently
dropped. Delay vector conversion until both required nodes are available
to avoid leaving a partially constructed chain on allocation failure.
Assisted-by: OpenAI Codex
Signed-off-by: yushuailong <yyyusl@qq.com>
Store the jail as an absolute path on the task group, copy it to
children, and free it when the last member leaves.
Signed-off-by: Abhishek Mishra <mishra.abhishek2808@gmail.com>
1. nxsched_process_timer: call clock_update_wall_time() under
CONFIG_CLOCK_TIMEKEEPING so that wall time is updated on timer
events during tickless operation.
2. clock_timekeeping_get_wall_time: call clock_update_wall_time()
before sampling the base and counter.
3. clock_timekeeping: allow overriding NTP_MAX_ADJUST with
CONFIG_CLOCK_ADJTIME_SLEWLIMIT_PPM if configured.
4. Use clock_t consistently for counter values in clock_timekeeping.c.
Signed-off-by: Daniel P. Carvalho <danieloak@gmail.com>
Convert failures from CLOCK_FD lookup and PTP_CLOCK_GETRES into the public
clock_getres() convention of returning ERROR and setting errno. This keeps
dynamic PTP clocks consistent with the other clock_getres() error paths.
Assisted-by: OpenAI Codex
Signed-off-by: yushuailong <yyyusl@qq.com>
Move the critical-monitor update after PID hash entry validation and keep
the scheduler critical section held so the TCB remains stable. Invalid or
stale PIDs now return -ESRCH instead of passing a NULL TCB to
nxsched_update_critmon().
Also add the declaration spacing required by nxstyle in the modified file.
Assisted-by: OpenAI Codex
Signed-off-by: yushuailong <yyyusl@qq.com>
Move RTC synchronization outside the non-timekeeping branch so setting
CLOCK_REALTIME also updates the RTC when CONFIG_CLOCK_TIMEKEEPING is
enabled. This prevents corrected wall time from reverting to an older RTC
value after restart.
Preserve the existing low-priority work queue path for RTC drivers that may
block while updating hardware.
Assisted-by: OpenAI Codex
Signed-off-by: yushuailong <yyyusl@qq.com>
Remove pending IRQ work before clearing its callback state, and guard the
worker callback against a concurrent detach. This prevents detached worked
IRQs from invoking a NULL function pointer.
Assisted-by: OpenAI Codex
Signed-off-by: yushuailong <yyyusl@qq.com>
Only call kthread_delete for a valid positive PID and always clear the IRQ
thread slot afterward. This makes detach on an unused IRQ, including a
repeated detach, a safe no-op instead of deleting the calling task.
Assisted-by: OpenAI Codex
Signed-off-by: yushuailong <yyyusl@qq.com>
Return ENOMEM and leave the IRQ detached when no custom work queue can be
created or all queue slots are occupied. Also release the queue mutex on
the full-table path and avoid caching a failed queue creation.
Assisted-by: OpenAI Codex
Signed-off-by: yushuailong <yyyusl@qq.com>
Store the thread PID only after kthread_create succeeds. This prevents a
negative error value from making subsequent attachment attempts fail with
EINVAL after a transient thread creation failure.
Assisted-by: OpenAI Codex
Signed-off-by: yushuailong <yyyusl@qq.com>
strlcpy() was given sizeof(tcb->name), i.e. CONFIG_TASK_NAME_SIZE + 1,
but the documented caller contract is a buffer of CONFIG_TASK_NAME_SIZE
bytes (include/sys/prctl.h). When a task name is exactly
CONFIG_TASK_NAME_SIZE chars (the normal result of nxtask_setup_name()
truncation), the terminating NUL lands one byte past the caller buffer.
Pass CONFIG_TASK_NAME_SIZE to strlcpy() so the copy is truncated
in-bounds, and drop the stale forced-NUL line left over from the strncpy
era (it ran after the overflow had already happened).
Before:
```
guard byte placed right after a CONFIG_TASK_NAME_SIZE caller buffer
reads 0x00 (expected 0xAA) after the call: strlcpy writes its
terminating NUL one byte past the buffer when the task name is exactly
CONFIG_TASK_NAME_SIZE chars.
```
After:
```
strlcpy(name, tcb->name, CONFIG_TASK_NAME_SIZE) writes at most
CONFIG_TASK_NAME_SIZE bytes; the caller buffer stays intact.
```
Testing:
Simulated (sim:nsh, CONFIG_TASK_NAME_SIZE=31).
Build and run:
```
cmake -B build -DBOARD_CONFIG=sim:nsh -GNinja
cmake --build build -j$(nproc)
echo hello | ./build/nuttx
```
then run "hello" at the NSH prompt.
The test was carried by apps/examples/hello/hello_main.c (scratch only,
not part of this commit); its diff:
```
--- a/examples/hello/hello_main.c
+++ b/examples/hello/hello_main.c
@@ -24,6 +24,8 @@
#include <nuttx/config.h>
#include <stdio.h>
+#include <string.h>
+#include <sys/prctl.h>
/****************************************************************************
* Public Functions
@@ -35,6 +37,55 @@
int main(int argc, FAR char *argv[])
{
+ /* Longest-legal task name: exactly CONFIG_TASK_NAME_SIZE chars, the
+ * normal result of nxtask_setup_name() truncation.
+ */
+
+ static const char longname[] =
+ "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789";
+
+ /* Caller buffer per the documented prctl(PR_GET_NAME) contract, with a
+ * guard byte immediately after it to detect the 1-byte overflow.
+ */
+
+ struct
+ {
+ char buf[CONFIG_TASK_NAME_SIZE];
+ volatile unsigned char guard;
+ } s;
+
+ _Static_assert(sizeof(longname) - 1 > CONFIG_TASK_NAME_SIZE,
+ "test name must exceed CONFIG_TASK_NAME_SIZE");
+
printf("Hello, World!!\n");
+ printf("prctl test: CONFIG_TASK_NAME_SIZE=%d\n", CONFIG_TASK_NAME_SIZE);
+
+ s.guard = 0xaa;
+ s.buf[0] = '\0';
+
+ if (prctl(PR_SET_NAME, (unsigned long)longname) != 0)
+ {
+ printf("prctl test: PR_SET_NAME failed\n");
+ return 1;
+ }
+
+ if (prctl(PR_GET_NAME, (unsigned long)s.buf) != 0)
+ {
+ printf("prctl test: PR_GET_NAME failed\n");
+ return 1;
+ }
+
+ printf("prctl test: guard=0x%02x (expected 0xaa), name len=%zu, "
+ "last char=0x%02x\n",
s.guard, strlen(s.buf), (unsigned char)s.buf[strlen(s.buf)]);
+
+ if (s.guard != 0xaa)
+ {
+ printf("prctl test: FAIL - terminating NUL written 1 byte past "
+ "the caller buffer\n");
+ return 1;
+ }
+
+ printf("prctl test: PASS - caller buffer intact\n");
return 0;
}
```
Before the fix:
```
prctl test: guard=0x00 (expected 0xaa), name len=30, last char=0x00
prctl test: FAIL - terminating NUL written 1 byte past the caller buffer
```
After the fix:
```
prctl test: guard=0xaa (expected 0xaa), name len=30, last char=0x00
prctl test: PASS - caller buffer intact
```
Assisted-by: Claude Code (GLM-5.3) <claude@anthropic.com>
Signed-off-by: Junbo Zheng <zhengjunbo1@xiaomi.com>
work_cancel() used to return -ENOENT when the work structure was not
in the queue, and callers depend on that: aio_cancel() tears down the
AIO container (file_put() + aioc_free()) only when work_cancel()
reports success, because a work item that is not queued may already be
executing on a worker thread (see the comment in fs/aio/aio_cancel.c).
Since commit 6f72f5481d ("sched/wqueue: Refactor delayed and periodical
workqueue") work_cancel() returns OK unconditionally, and commit
d2e01b9055 ("sched/wqueue: harden custom queue lifecycle") kept that
behaviour and dropped -ENOENT from the function documentation. Under
SMP the LTP aio_cancel tests then free the aio container and its file
while the lpwork thread is still executing aio_write_worker() on it,
which ends in a page fault in file_write() (f_inode == NULL) and a
panic.
Return -ENOENT again when the work is not queued, and document it.
For the synchronous variant "not queued" alone does not tell whether
the callback is running: the worker scan does, so report OK when a
running callback was found and waited for, and -ENOENT only when the
work was neither queued nor running.
Assisted-by: Claude Code
Signed-off-by: raiden00pl <raiden00@railab.me>
Clamp each wall-clock adjustment in both positive and negative directions, then subtract the applied amount from the remaining adjustment. This makes adjtime converge to zero and prevents large negative adjustments from slewing the clock in the wrong direction.
Assisted-by: OpenAI Codex
Signed-off-by: yushuailong <yyyusl@qq.com>
CLOCK_TIMEKEEPING already provides a software-based adjtime implementation. Exclude the generic adjtime state and entry point when timekeeping is enabled, while retaining clock_adjtime support for PTP clocks.
Assisted-by: OpenAI Codex
Signed-off-by: yushuailong <yyyusl@qq.com>
spawn_execattrs() discards the return value from nxsched_set_scheduler(). As a result, an invalid scheduling policy or parameter is silently ignored and the spawn operation continues with the original scheduling configuration.
Store and return the result from nxsched_set_scheduler() so that the caller can tear down the child task when applying the requested scheduler attributes fails. Update the function comments to describe the existing error return behavior.
Assisted-by: OpenAI Codex
Signed-off-by: yushuailong <yyyusl@qq.com>
Sporadic scheduling parameters are processed independently by sched_setparam(), sched_setscheduler(), and pthread_create(). The three paths currently validate different subsets of the parameters.
In particular, pthread_create() does not validate sched_ss_max_repl and only requires the replenishment period to be greater than the budget, while the scheduler interfaces enforce the implementation's 50 percent duty-cycle limit.
Add nxsched_validate_sporadic() to validate the common parameters and convert the replenishment period and budget to ticks. Use it from all paths that directly initialize or update sporadic scheduler state.
Express the duty-cycle check using division to avoid overflow when doubling a clock_t value.
Assisted-by: OpenAI Codex
Signed-off-by: yushuailong <yyyusl@qq.com>
nxsched_set_param() applied the sporadic parameters and restarted the
replenishment timer in set_sporadic_param() before nxsched_reprioritize()
rejected an out-of-range priority with EINVAL, leaving stale sporadic
state (e.g. a truncated hi_priority) behind on failure.
Validate sched_priority up front so the error path is atomic.
Assisted-by: OpenAI Codex
Signed-off-by: yushuailong <yyyusl@qq.com>
Commit 2ec7d90eba refactored sched_setparam() into set_sporadic_param()
but moved the apply logic into the former reject branch without
inverting the condition. As a result sched_setparam() rejects valid
sporadic parameters (repl >= 2 * budget) with EINVAL and accepts
invalid ones, tripping the DEBUGASSERT in sched_sporadic.c or wrapping
the replenishment calculation.
Invert the condition to match process_sporadic() in sched_setscheduler.c.
Fixes: 2ec7d90eba ("sched_setparam.c: coverity HIS_metric_violation: RETURN")
Assisted-by: OpenAI Codex
Signed-off-by: yushuailong <yyyusl@qq.com>
waitid() documents WNOWAIT support but exited_child() unconditionally
removed and freed the child status entry, so a second wait on the same
child failed with ECHILD. Debug builds were also inconsistent: the
options mask rejected WNOWAIT entirely with ENOSYS.
Pass options down to exited_child() and skip the discard when WNOWAIT
is set, matching waitpid(), and add WNOWAIT to the debug options mask.
Assisted-by: OpenAI Codex
Signed-off-by: yushuailong <yyyusl@qq.com>
Replace a literal 100000 with the USEC_PER_SEC macro in the oneshot
timer restart calculation. This bug causes incorrect timeout values
when CONFIG_SCHED_CPULOAD_TICKSPERSEC=1, though defaults are not
affected. Present since 2016 (commit 300361539a).
Assisted-by: OpenAI Codex
Signed-off-by: yushuailong <yyyusl@qq.com>
Add a NULL check for the param argument before dereferencing
param->sched_priority. The sibling functions sched_setparam and
sched_getparam already have this check; sched_setscheduler was
the only one missing it, allowing a NULL pointer dereference
via sched_setscheduler(0, SCHED_FIFO, NULL).
Assisted-by: OpenAI Codex
Signed-off-by: yushuailong <yyyusl@qq.com>
Compare ctcb->group->tg_ppid against rtcb->group->tg_pid (the group
leader PID) instead of rtcb->pid in waitid() and in the
!CONFIG_SCHED_CHILD_STATUS path of waitpid(). Commit ece224a7e3
("handle waitpid waitting tcb->group is NULL") rewrote the
comparisons this way when adding the ctcb->group NULL guard,
regressing what 90be95bb89 had correct: a non-group-leader thread
calling waitid(P_PID) or waitpid() on a child always gets ECHILD.
Assisted-by: OpenAI Codex
Signed-off-by: yushuailong <yyyusl@qq.com>
Protect the environment release in clearenv() with the task group mutex.
This prevents concurrent environment operations from racing with cleanup.
Assisted-by: OpenAI Codex
Signed-off-by: yushuailong <yyyusl@qq.com>
Remove the per-arch testset implementation from the spinlock layer.
The testset abstraction predates the unified spinlock.h API and is no
longer used now that all arches provide spin_lock_irqsave()/
spin_unlock_irqrestore() directly. Drop the per-arch *_testset.{c,S}
implementations and spinlock.h files for arm, sim, sparc, tricore,
x86_64, and xtensa, along with the CXD56_TESTSET,
CXD56_TESTSET_WITH_HWSEM, and CXD56_ATOMIC_WITH_HWSEM Kconfig options
in arch/arm/src/cxd56xx, and simplify the CXD56 semaphore pool loop
in cxd56_sph.c to a single unconditional range.
Signed-off-by: zhangyu117 <zhangyu117@xiaomi.com>
Fix checkpatch "Missing blank line after declarations" errors in
drivers/timers/arch_timer.c and sched/sched/sched_processtickless.c.
These are pre-existing issues, not introduced by the recent tickless
RR series.
Assisted-by: Zhipu GLM-5.3
Signed-off-by: ouyangxiangzhen <ouyangxiangzhen@xiaomi.com>
In tickless mode, the scheduler timer is stopped whenever the currently
running task requires no time slicing (CLOCK_MAX). When a SCHED_RR task
was later switched in, nothing re-armed the timer, so the task could run
indefinitely without round-robin rotation.
Also, when a SCHED_RR task was preempted, its timeslice counter was not
decremented for the time already consumed, effectively giving the task
"bonus" CPU time when resumed.
Solve both by performing RR accounting on context switches:
- nxsched_suspend_roundrobin() charges the elapsed execution time
against the timeslice of the RR task being switched out
- nxsched_resume_roundrobin() restarts the scheduler timer for the
remaining timeslice of the RR task being switched in, so the timer
is always armed while an RR task is running
This also removes the previous workaround in nxsched_process_timer
that triggered the scheduler on every timer tick.
Assisted-by: Zhipu GLM-5.3
Signed-off-by: ouyangxiangzhen <ouyangxiangzhen@xiaomi.com>
In tickless mode, the scheduler timer is stopped whenever the currently
running task requires no time slicing (CLOCK_MAX). When a SCHED_RR task
was later switched in, nothing re-armed the timer, so the task could run
indefinitely without round-robin rotation.
Reassess the scheduler timer in nxsched_switch_context() before the
context switch when the task being switched in uses round-robin
scheduling, so that the timer is always armed while an RR task is
running. Hooking into nxsched_switch_context() covers all context
switch paths (task context switch, interrupt exit, syscall and task
exit) since every architecture calls it on every switch.
Signed-off-by: ouyangxiangzhen <ouyangxiangzhen@xiaomi.com>
In hrtimer_start_absolute, when a pending hrtimer is removed (was the
head) and reinserted with a later expiration time, the reprogram flag
remains true but the hrtimer is no longer the earliest timer in the
queue. The old code passed hrtimer->expired to hrtimer_reprogram, which
was incorrect. Use hrtimer_get_first()->expired to ensure the hardware
timer is reprogrammed with the actual earliest timer's expiration time.
Signed-off-by: ouyangxiangzhen <ouyangxiangzhen@xiaomi.com>
The context-switch merge assigned the current timestamp to run_time instead of run_start. This overwrote the accumulated runtime and left the next elapsed-time calculation with a stale start value when critical-monitor CPU load accounting was disabled.
Store the timestamp in run_start under the thread runtime monitor configuration, matching the former resume path.
Fixes: b2a69ba781 ("sched: merge nxsched_suspend/resume_critmon")
Assisted-by: OpenAI Codex
Signed-off-by: yushuailong <yyyusl@qq.com>
The premp_start member was renamed to preemp_start, but the old name was reintroduced when the critical monitor switch paths were merged.
Use the current struct tcb_s field name so configurations with preemption monitoring enabled build successfully.
Assisted-by: OpenAI Codex
Signed-off-by: yushuailong <yyyusl@qq.com>
Add the blank lines required between local declarations and statements so sched_critmonitor.c passes nxstyle.
Assisted-by: OpenAI Codex
Signed-off-by: yushuailong <yyyusl@qq.com>
Replace the local EINTR retry loop with
nxsem_wait_uninterruptible(). This keeps the master implementation
aligned with the semaphore API without changing cancellation behavior.
Keep the cleanup separate so release branches where the helper is
unavailable can use the lifecycle commit without a downstream
compatibility patch.
Assisted-by: Codex:GPT-5
Signed-off-by: DuoYuWang <thirteenking.wang@gmail.com>
Factor the common queueing logic used by work_queue_wq() and
work_queue_next_wq() into a private helper.
Preserve existing timing semantics: regular work calculates its absolute
expiration before taking the queue lock, while periodic work advances the
previous expiration under the lock.
This is a code deduplication change with no public API or behavior changes.
Assisted-by: Codex:GPT-5
Signed-off-by: DuoYuWang <thirteenking.wang@gmail.com>
Prevent work_queue_free() from destroying predefined queues or freeing a
custom queue from one of its own callbacks. Mark teardown under the queue
lock, reject new submissions, return pending work to its owner, and wait for
every worker before releasing queue resources.
Clean up partially created worker pools, reject invalid delays, safely
replace pending periodic work, and make synchronous cancellation wait for
every concurrent callback using the same work structure.
Tested on an STM32H7 PX4 FMUv6C with the matching ostest suite in Flat and
Protected kernel builds.
Assisted-by: Codex:GPT-5
Signed-off-by: DuoYuWang <thirteenking.wang@gmail.com>
set_sporadic_param() tested rtcb (the calling task) instead of tcb (the
task being modified). A cross-task sched_setparam() therefore either
skipped the sporadic parameter update entirely or, when the calling
task was itself sporadic, reset a task that had no sporadic state.
Use tcb consistently and drop the now-unused rtcb argument.
Signed-off-by: yushuailong <yyyusl@qq.com>
The policy flag bits were cleared before the switch statement, so the
checks testing whether the task was previously SCHED_SPORADIC could
never be true. As a result nxsched_stop_sporadic() was never called
when a sporadic task switched to SCHED_FIFO/SCHED_RR, leaking the
sporadic state, and a sporadic-to-sporadic reconfiguration ran
initialize instead of reset.
Clear the policy flag bits only after the previous policy has been
evaluated, right before the new policy bits are set.
Signed-off-by: yushuailong <yyyusl@qq.com>
nxsched_stop_sporadic() freed tcb->sporadic but left
TCB_FLAG_SCHED_SPORADIC set in tcb->flags. On thread exit,
nxtask_recover() calls nxsched_stop_sporadic() and the final context
switch in up_exit() then sees the stale SPORADIC policy flag and calls
nxsched_suspend_sporadic() on a TCB whose sporadic state is already
freed, tripping DEBUGASSERT(tcb->sporadic) and hanging the system
(reproduced by ostest sporadic_test on sim, present on master).
Clear the policy bits inside nxsched_stop_sporadic() so every caller
leaves the TCB in a consistent state.
Signed-off-by: yushuailong <yyyusl@qq.com>
Rename atomic_fetch_add/sub/or/and/xor to atomic_add/sub/or/and/xor
to avoid conflicts with the C/C++ standard library naming. The
atomic_fetch_xxx naming is reserved by the standard; keeping it causes
function name conflicts when source files indirectly include both
<nuttx/atomic.h> and <atomic>/<stdatomic.h>.
Signed-off-by: zhangyu117 <zhangyu117@xiaomi.com>
clock_gettime(CLOCK_MONOTONIC) reads g_system_ticks, which is only
refreshed when a timer expiration is processed. On SCHED_TICKLESS an
idle system has no timeout armed, so the clock returns 0 before the
first expiration and a frozen value afterwards.
This regressed in commit c7b6442974, which switched CLOCK_MONOTONIC to
the sched tick counter to exclude suspended time. Excluding suspend time
needs explicit accounting maintained by PM code, the tick counter cannot
provide it on tickless.
Restore the live read. On non-tickless builds clock_systime_timespec()
falls back to the same tick counter, so behavior there is unchanged.
Verified on qemu-intel64, nrf52840-dk and rv-virt.
Signed-off-by: raiden00pl <raiden00@railab.me>
Assisted-by: Claude Code
When stdio buffering is disabled, fgetc/getchar on stdin always returned EOF
because fs_cookie and fs_oflags were left uninitialized and lib_fread_unlocked
bails out on (fs_oflags & O_RDOK) == 0.
Fix this by moving the initialization of the fs_cookie and fs_oflags outside the
CONFIG check; these fields need to be initialized regardless of
CONFIG_STDIO_DISABLE_BUFFERING.
In addition, initializing stream[i].fs_iofunc pointers to NULL is redundant
since the task group is allocated with kmm_zalloc/group_zalloc. Zero
allocation was already assumed on fs_flags, so remove the unnecessary code.
Signed-off-by: Jukka Laitinen <jukka.laitinen@tii.ae>
Supports the UNIX setuid-on-exec sudo helper. Documents the model,
generates an extra ROMFS user and /etc/sudoers for a non-root test,
reports BINFS modes from the builtin table so ls -l matches execute
bits, and skips NULL environment entries when sanitizing a setuid exec.
Signed-off-by: Abhishek Mishra <mishra.abhishek2808@gmail.com>
Complete the POSIX credential setters for real/effective/saved UID and
GID so login and privilege-drop paths can clear saved-root without
relying on setreuid patterns alone.
Signed-off-by: Abhishek Mishra <mishra.abhishek2808@gmail.com>
Track supplementary GIDs per task group, wire setgroups/getgroups
syscalls when CONFIG_SCHED_NGROUPS > 0, and honor them in DAC checks
via nxsched_has_gid(). When NGROUPS is 0, libc provides getgroups/
setgroups stubs. initgroups() fails instead of silently truncating
when membership exceeds CONFIG_SCHED_NGROUPS.
Signed-off-by: Abhishek Mishra <mishra.abhishek2808@gmail.com>
NuttX implemented fork() and vfork() as the same function. Both were libc
wrappers around a single up_fork() syscall; vfork() differed only by a
trailing waitpid(). Underneath, the child joined the parent's address
environment -- the same addrenv_join() that pthread_create() uses -- and got
a private copy of the stack. So the child shared .data, .bss and the heap
with its parent and ran concurrently with it.
That is not fork(). It is vfork()-with-a-private-stack under fork()'s name,
and the history says so: today's fork() is NuttX's old vfork(), renamed in
c33d1c9c97 (2023) without any change of behaviour. The failure was silent --
a program written against POSIX fork() compiled, ran, and had its child's
writes land in the parent's variables.
Separate them into two primitives, chosen by which function the caller
called rather than by what the hardware happens to be:
fork() child gets its own copy of the parent's memory at the same
virtual addresses; runs concurrently. Only where an address
environment can be duplicated -- elsewhere it is not declared at
all, so calling it is a build error naming the function.
vfork() child shares the parent's memory; parent suspended until the
child _exit()s or exec()s. Implementable everywhere.
Below libc there is still one syscall. up_fork() gains a bool saying which
primitive the caller used, since the per-architecture register snapshot is
the same for both, and passes it to nxtask_setup_fork(), which is the single
place the memory semantics are decided. The argument arrives in the first
argument register and is never touched: each architecture's snapshot takes
some other call-clobbered register for its scratch, so the flag is simply
still there when the C worker is called.
The vfork() parent suspension moves out of libc into nxtask_start_fork(),
released from nxsched_release_tcb() by nxtask_resume_vfork(). Two things
follow: the parent is resumed at exec(), since exec_swap() has already handed
the child's pid to the loaded program by the time the vfork stub exits, and
vfork() no longer depends on CONFIG_SCHED_WAITPID.
Releasing there requires one fix in nxtask_exit(). It raises rtcb->lockcount
directly rather than through sched_lock() while it tears the TCB down, so the
nxsem_post() that wakes the vfork() parent leaves it queued where a blocked
task collects while pre-emption is off -- g_pendingtasks, or g_readytorun on
SMP -- and the matching raw lockcount-- does not publish it the way
sched_unlock() would, leaving the parent stranded with nothing to move it on.
The fix mirrors sched_unlock() for each case: nxsched_merge_pending(), or
nxsched_deliver_task() under CONFIG_SMP. Both are no-ops while pre-emption is
still disabled, and up_exit() re-reads this_task() afterwards, so a change of
the ready-to-run head is honoured. Without it vfork() deadlocks wherever no
other task happens to call sched_unlock() afterwards -- rv-virt:nsh64 and
rv-virt:pnsh64, where NSH is blocked in waitpid() holding the lock, and
qemu-armv8a:citest_smp, which hangs the moment the vfork() test runs.
fork() is built on a new addrenv_fork(), backed by an up_addrenv_fork() hook
that duplicates an address environment into freshly allocated pages mapped at
the same virtual addresses -- unlike up_addrenv_clone(), which copies only
the representation and leaves both pointing at the same page tables. The
child then adopts the parent's stack geometry rather than being given a
relocated copy: a pointer to a stack local taken before fork() must name the
same object in the child that it named in the parent, and the parent's stack
is already in the duplicate, with its contents, at the parent's address.
No architecture implements up_addrenv_fork() yet, so this commit leaves
fork() unavailable everywhere. That is the intended state. It withdraws
fork() from ARCH_ARM, flat ARCH_ARM64, ARCH_RISCV, ARCH_SIM and ARCH_X86_64,
where until now it named the sharing primitive; per-architecture patches
restore it, with POSIX semantics, as up_addrenv_fork() lands. In the
meantime the sharing primitive is still there under the name that describes
it: vfork() for a child that runs a program, pthread_create() for a second
flow of control that shares memory, posix_spawn() for both at once.
Kconfig: ARCH_HAVE_VFORK inherits ARCH_HAVE_FORK's select lines, conditions
included, so no configuration gains machinery; ARCH_HAVE_FORK is redefined to
mean "can provide POSIX fork() semantics" and now depends on ARCH_ADDRENV.
There is one deliberate departure from "verbatim". ARCH_ARM selected the
fork family unconditionally, BUILD_KERNEL included, and that has never
worked: on a kernel build the architecture's fork entry point sees the
kernel's return address and stack pointer rather than the caller's, so the
child resumes at a kernel address. On qemu-armv7a:knsh master faults in
ostest's fork case with "Child did not run" and then a data abort; without
the condition this change faults the same way through vfork(). ARCH_ARM64
and ARCH_X86_64 already carried "if !BUILD_KERNEL" for exactly this reason --
ARM was the outlier. Conditioning it turns a runtime fault into an honest
absence, which is the whole point of the change; arch/arm takes the condition
off again in the patch that adds its saved-syscall-frame path. Only the
MMU-capable ARM ports are affected, since Cortex-M cannot build BUILD_KERNEL
at all.
Also fixes two latent syntax errors found on the way: a missing comma in
riscv_fork.c and mips_fork.c, both in *_FRAMEPOINTER && !SAVE_GP branches
that are never compiled today.
BREAKING CHANGE: fork() is withdrawn from every architecture. It is no
longer declared in unistd.h, so code that calls it fails to build with an error
naming the function, and the sharing behaviour it used to have is gone rather
than renamed. CONFIG_ARCH_HAVE_FORK no longer means "fork() exists"; it means
"this configuration can provide POSIX fork() semantics", and no architecture
selects it yet.
Quick fix, chosen by why the call was made:
to run a program vfork() + exec*(), or better posix_spawn()
a second flow of control that pthread_create()
shares the caller's memory
a genuinely independent copy keep fork(), and wait for the per-arch patch
of the process that implements up_addrenv_fork() and selects
CONFIG_ARCH_HAVE_FORK
Out-of-tree code that tests CONFIG_ARCH_HAVE_FORK to decide whether a
fork-then-exec path is available wants CONFIG_ARCH_HAVE_VFORK instead, which is
selected in exactly the places CONFIG_ARCH_HAVE_FORK used to be. The full
migration guide is Documentation/guides/fork_vfork_migration.rst.
Assisted-by: Claude Code:claude-opus-5
Signed-off-by: Marco Casaroli <marco.casaroli@gmail.com>
A mutex records its holder as a task id in the low 31 bits of a word
whose top bit means "someone is blocked on this". The id was stored
without masking, so an id with its top bit set became a holder with the
blocking bit raised.
Task ids are normally small and positive, but not always.
nxsched_gettid() reports -ESRCH for a context that no longer maps to a
running task, and there is a window where that is exactly what the
running context is: nxtask_exit() marks the next task ready to run
while the dying task is still executing on its own stack, and only then
releases the TCB. Freeing the group inside that release takes and
drops the group's mutexes, so the lock stores 0xfffffffd and the unlock
compares 0x7ffffffd, which are not equal.
With assertions enabled the unlock trips its holder check, and every
exit of a process that frees memory panics. In a kernel build that is
every exit, so no program could be run twice, and running one at all
took the shell down with it. Without assertions the failure is silent:
the accidental blocking bit sends the unlock looking for a waiter that
never existed.
Encode the id the same way everywhere it is stored or compared, so that
a lock and an unlock from one context agree whatever the id's sign.
The masked forms of -1 and -2 would alias the "no holder" and "reset"
values, but nxsched_gettid() yields only valid ids and -ESRCH.
mm_lock() already sidesteps this window with a note that gettid() may
return -ESRCH during a context switch; this gives the generic mutex the
same footing rather than a second special case.
Test case, on the EIC7700 EVB, which is a kernel build with assertions:
nsh> hello
Hello, World!!
Before, that printed and then panicked in sem_post, taking the shell
with it, every time. After, five runs in a row complete and the shell
survives. ps over telnet still completes.
Assisted-by: Claude:claude-opus-5
Signed-off-by: Justin Hammond <justin@dynam.ac>
nxsig_timedwait parked a pointer to the caller's siginfo buffer in the
TCB, for whoever eventually posts the signal to fill in. But the
poster fills it in from its own context (another task, a kernel
thread, an interrupt), and in a kernel build the caller's buffer is
an address in the caller's private address space, which the poster
does not share. The write lands wherever the currently active
mappings put it: the waiter wakes to find garbage where the signal
number should be, and some other process is left with a corrupted
page. Flat builds share one address space, which is why this never
showed there.
Park the stack local in the TCB instead. That is kernel memory,
mapped in every context, and it is copied out to the caller's buffer
after waking, in the caller's own context, exactly where the
pending-signal path already does the same thing.
Found on the EIC7700X port by an RTC alarm: the alarm signal, posted
from the low-priority work queue, woke a sigwaitinfo caller into an
assertion on the unblocking signal number while the init process,
whose address space had received the stray write, died of a jump to
address zero. With this change the same test arms, waits and wakes
cleanly, repeatedly.
Assisted-by: Claude:claude-opus-5
Signed-off-by: Justin Hammond <justin@dynam.ac>
When a running task changes its affinity on SMP and is no longer eligible to run on the current CPU, merely delivering an equal-priority scheduling request can leave the task in g_readytorun while the target CPU remains idle. The task then never runs again.
Remove the task from its current CPU, add it back to the ready-to-run list so a suitable CPU is selected, and perform the context switch unconditionally. This ensures the task is migrated according to its updated affinity.
Fixes: https://github.com/apache/nuttx/issues/19680
Assisted-by: GitHub Copilot:ppio/pa/gpt-5.6-sol
Signed-off-by: hujun5 <hujun5@xiaomi.com>
Clear NXSEM_MBLOCKING_BIT in nxsem_wait_irq() when a mutex waiter is
removed and the wait queue becomes empty.
Signed-off-by: Martin Krasula <mkrasula@elektroline.cz>
dlopen() of a library that is already loaded fails. libelf_insert()
rejects a name that is already in the module registry with EEXIST, and
dlinsert() passes that straight out, so the second caller gets NULL.
POSIX says dlopen() shall return a handle to the object, and there is no
way today for two modules to hold the same library at once -- which is
what a shared library is for.
So dlopen() now takes another reference on a library that is already
there, and dlclose() only tears it down when the last handle goes. The
count lives in the dlfcn layer rather than in libelf_insert() so that
insmod keeps its own behaviour: a second insmod of the same name still
fails with EEXIST, which is right for a kernel module.
The module name is what makes any of this possible, and a PROTECTED build
did not have one. Names were defined for CONFIG_BUILD_FLAT or the kernel
side of a split build, on the reasoning that only the kernel needed them,
which predates dlopen() being usable from user space. Without a name the
user-space copy of libelf cannot recognise a second open of a library,
cannot count opens, and cannot make dlclose() mean anything -- two
dlopen()s there produce two independent copies of the library and lose
track of the first. Names are therefore defined wherever CONFIG_LIBC_DLFCN
is, which costs NAME_MAX per loaded module in that configuration.
The path no longer has to be copied either. The module name is the
basename of the file and libelf_insert() takes it as a const string, so
dlinsert() finds it with strrchr() instead of handing a writable
duplicate of the whole path to basename().
BUILD_KERNEL is deliberately untouched. dlopen() returns NULL there
unconditionally: dlinsert() is a stub, because sharing a library between
processes with separate address spaces needs the text in a shared region
and the data per process at a matching virtual address, which is a
different problem from this one.
Built for mps3-an547:picostest with and without CONFIG_LIBC_DLFCN, and
for stm32f4discovery:kostest, a PROTECTED configuration, with it enabled.
Assisted-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
Signed-off-by: Marco Casaroli <marco.casaroli@gmail.com>
Perform pseudo-filesystem permission checks inside inode_reserve() and
inode_remove() while the inode tree lock is held, and hold that lock across
pseudorename mutations so symlink swaps cannot bypass directory checks.
Hold a read lock around pseudo-fs open permission checks.
On setuid/setgid exec, update saved set-IDs, mark the task group secure,
sanitize dangerous environment variables, clear debug/dumpable flags, and
add issetugid(), secure_getenv(), and PR_SET/GET_DUMPABLE support.
Signed-off-by: Abhishek Mishra <mishra.abhishek2808@gmail.com>