feat(process): fork/exec/id/session + system/popen

This commit is contained in:
2026-09-05 16:55:14 -04:00
parent c9e9676d38
commit 14c6fd6cf6
19 changed files with 2118 additions and 2 deletions
+111
View File
@@ -0,0 +1,111 @@
#ifdef HAVE_CONFIG_H
#include <config.h>
#endif
#include <errno.h>
#include "atfork_impl.h"
#include "../internal/malloc.h"
/*
* The atfork hook table (todo 20).
*
* The list is a doubly linked chain in registration order (head = oldest
* registration). fork() runs the phases through __vlibc_atfork_prepare/
* parent/child around the syscall; pthread_atfork (todo 45) appends
* entries via __vlibc_atfork_register. Nodes live on the malloc heap:
* registration happens long after the allocator is up, and fork() itself
* never allocates — the phases only walk the existing chain.
*
* Phase order (POSIX 1003.1-2008 pthread_atfork):
* - prepare: reverse registration order (tail to head);
* - parent and child: registration order (head to tail).
*/
struct vlibc_atfork_entry *volatile __vlibc_atfork_list = 0;
// NOLINTBEGIN(bugprone-easily-swappable-parameters)
int
__vlibc_atfork_register(void (*prepare)(void), void (*parent)(void), void (*child)(void))
{
struct vlibc_atfork_entry *node;
struct vlibc_atfork_entry *tail;
node = (struct vlibc_atfork_entry *)__libc_malloc(sizeof(*node));
if (node == 0)
{
errno = ENOMEM;
return -1;
}
node->prepare = prepare;
node->parent = parent;
node->child = child;
node->prev = 0;
node->next = 0;
/* Append at the tail so the chain stays in registration order. */
if (__vlibc_atfork_list == 0)
{
__vlibc_atfork_list = node;
return 0;
}
tail = __vlibc_atfork_list;
while (tail->next != 0)
{
tail = tail->next;
}
tail->next = node;
node->prev = tail;
return 0;
}
// NOLINTEND(bugprone-easily-swappable-parameters)
void
__vlibc_atfork_prepare(void)
{
struct vlibc_atfork_entry *node = __vlibc_atfork_list;
/* Reverse registration order: walk to the tail, then back. */
while (node != 0 && node->next != 0)
{
node = node->next;
}
for (; node != 0; node = node->prev)
{
if (node->prepare != 0)
{
node->prepare();
}
}
}
void
__vlibc_atfork_parent(void)
{
struct vlibc_atfork_entry *node;
/* Registration order. */
for (node = __vlibc_atfork_list; node != 0; node = node->next)
{
if (node->parent != 0)
{
node->parent();
}
}
}
void
__vlibc_atfork_child(void)
{
struct vlibc_atfork_entry *node;
/* Registration order. */
for (node = __vlibc_atfork_list; node != 0; node = node->next)
{
if (node->child != 0)
{
node->child();
}
}
}
+67
View File
@@ -0,0 +1,67 @@
#ifndef VLIBC_PROCESS_ATFORK_IMPL_H
#define VLIBC_PROCESS_ATFORK_IMPL_H
/*
* vlibc — internal atfork hook table (todo 20).
*
* fork() runs three hook phases around the clone operation:
*
* 1. prepare — in the parent, before the fork, in REVERSE registration
* order (POSIX 1003.1-2008 pthread_atfork: "the prepare handlers are
* called in the reverse order in which they were registered");
* 2. parent — in the parent, after the fork, in registration order;
* 3. child — in the child, after the fork, in registration order
* (POSIX: "the parent and child handlers are called in the order in
* which they were registered").
*
* The list is a doubly linked chain in registration order (head = oldest
* registration); the phases walk it head-to-tail or tail-to-head as the
* ordering above demands. It starts NULL — with no threads and no
* registrations every phase is a no-op, so fork() keeps working before
* todo 45 (pthread_atfork) lands. Todo 45 appends entries through
* __vlibc_atfork_register(); nothing else needs to change here.
*
* The names sit in the implementation-reserved namespace (this is the
* library's private seam, never public API), so the corresponding
* bugprone checks are waived per the house NOLINT convention.
*/
#include <stddef.h>
#include "../internal/libc.h"
// NOLINTBEGIN(bugprone-reserved-identifier)
struct vlibc_atfork_entry
{
void (*prepare)(void);
void (*parent)(void);
void (*child)(void);
struct vlibc_atfork_entry *prev; /* toward older registrations */
struct vlibc_atfork_entry *next; /* toward newer registrations */
};
/* Head of the registration list; NULL until the first registration. */
hidden extern struct vlibc_atfork_entry *volatile __vlibc_atfork_list;
/*
* Append a handler set to the list. Returns 0, or -1 with errno ENOMEM
* when the node cannot be allocated (pthread_atfork maps that to its
* error return). Only pthread_atfork (todo 45) calls this.
*/
hidden int
__vlibc_atfork_register(void (*prepare)(void), void (*parent)(void), void (*child)(void));
/* The three phases; fork() calls these around the syscall. */
hidden void
__vlibc_atfork_prepare(void);
hidden void
__vlibc_atfork_parent(void);
hidden void
__vlibc_atfork_child(void);
// NOLINTEND(bugprone-reserved-identifier)
#endif /* VLIBC_PROCESS_ATFORK_IMPL_H */
+50
View File
@@ -0,0 +1,50 @@
#ifdef HAVE_CONFIG_H
#include <config.h>
#endif
#include <stdarg.h>
#include <stdlib.h>
#include <unistd.h>
#include "../internal/syscall.h"
/*
* execl: execve with the arguments collected from the varargs list, which
* is terminated by (char *)NULL. POSIX sets no argument-count limit, so
* the list is counted first and staged on a VLA; GCC supports VLA in
* every mode vlibc targets (the C23 auto-VLA optionality does not bite
* here). The POSIX signature is fixed; the adjacent-parameter check is
* waived per the house NOLINT convention.
*/
// NOLINTBEGIN(bugprone-easily-swappable-parameters)
int
execl(const char *path, const char *arg0, ...)
{
va_list ap;
int argc;
int i;
int ret;
va_start(ap, arg0);
argc = 1;
while (va_arg(ap, const char *) != 0)
{
argc++;
}
va_end(ap);
{
char *argv[argc + 1];
argv[0] = (char *)arg0;
va_start(ap, arg0);
for (i = 1; i <= argc; i++)
{
argv[i] = va_arg(ap, char *);
}
va_end(ap);
ret = execve(path, argv, environ);
}
return ret;
}
// NOLINTEND(bugprone-easily-swappable-parameters)
+50
View File
@@ -0,0 +1,50 @@
#ifdef HAVE_CONFIG_H
#include <config.h>
#endif
#include <stdarg.h>
#include <unistd.h>
#include "../internal/syscall.h"
/*
* execle: like execl, but the varargs list is terminated by (char *)NULL
* followed by one final char *const envp[] argument. The argv staging
* uses a VLA (see execl.c); envp is read directly from the varargs. The
* POSIX signature is fixed; the adjacent-parameter check is waived per
* the house NOLINT convention.
*/
// NOLINTBEGIN(bugprone-easily-swappable-parameters)
int
execle(const char *path, const char *arg0, ...)
{
va_list ap;
int argc;
int i;
char **envp;
int ret;
va_start(ap, arg0);
argc = 1;
while (va_arg(ap, const char *) != 0)
{
argc++;
}
envp = va_arg(ap, char **);
va_end(ap);
{
char *argv[argc + 1];
argv[0] = (char *)arg0;
va_start(ap, arg0);
for (i = 1; i <= argc; i++)
{
argv[i] = va_arg(ap, char *);
}
va_end(ap);
ret = execve(path, argv, envp);
}
return ret;
}
// NOLINTEND(bugprone-easily-swappable-parameters)
+47
View File
@@ -0,0 +1,47 @@
#ifdef HAVE_CONFIG_H
#include <config.h>
#endif
#include <stdarg.h>
#include <unistd.h>
#include "../internal/syscall.h"
/*
* execlp: like execl, but the file is located through PATH (see
* execvp.c). The argv staging uses a VLA; the POSIX signature is fixed
* and the adjacent-parameter check is waived per the house NOLINT
* convention.
*/
// NOLINTBEGIN(bugprone-easily-swappable-parameters)
int
execlp(const char *file, const char *arg0, ...)
{
va_list ap;
int argc;
int i;
int ret;
va_start(ap, arg0);
argc = 1;
while (va_arg(ap, const char *) != 0)
{
argc++;
}
va_end(ap);
{
char *argv[argc + 1];
argv[0] = (char *)arg0;
va_start(ap, arg0);
for (i = 1; i <= argc; i++)
{
argv[i] = va_arg(ap, char *);
}
va_end(ap);
ret = execvp(file, argv);
}
return ret;
}
// NOLINTEND(bugprone-easily-swappable-parameters)
+19
View File
@@ -0,0 +1,19 @@
#ifdef HAVE_CONFIG_H
#include <config.h>
#endif
#include <stdlib.h>
#include <unistd.h>
#include "../internal/syscall.h"
/*
* execv: execve with the calling process's environment. environ is the
* global installed by the startup code (src/start/environ.c); the internal
* syscall layer is not needed here beyond execve itself.
*/
int
execv(const char *path, char *const argv[])
{
return execve(path, argv, environ);
}
+33
View File
@@ -0,0 +1,33 @@
#ifdef HAVE_CONFIG_H
#include <config.h>
#endif
#include <unistd.h>
#include "../internal/syscall.h"
/* AT_EMPTY_PATH for execveat (fexecve): resolve the fd itself. */
#define VLIBC_PROC_AT_EMPTY_PATH 0x1000
/*
* execve: replace the process image with the file at path. Success never
* returns; -1 with errno set otherwise.
*/
int
execve(const char *path, char *const argv[], char *const envp[])
{
return syscall_ret(__syscall3(SYS_execve, (long)path, (long)argv, (long)envp));
}
/*
* fexecve: like execve, but the image is the open descriptor fd. Runs the
* kernel's execveat with an empty path plus AT_EMPTY_PATH. A descriptor
* opened with O_PATH cannot be exec'd on kernels before 6.3; a regular
* open works everywhere.
*/
int
fexecve(int fd, char *const argv[], char *const envp[])
{
return syscall_ret(
__syscall5(SYS_execveat, fd, (long)"", (long)argv, (long)envp, VLIBC_PROC_AT_EMPTY_PATH));
}
+104
View File
@@ -0,0 +1,104 @@
#ifdef HAVE_CONFIG_H
#include <config.h>
#endif
#include <errno.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include "../internal/syscall.h"
/*
* execvp: search PATH for file and execve it with the caller's
* environment. If file contains '/', execve is tried directly.
*
* PATH is read via getenv(); a missing PATH defaults to "/bin:/usr/bin"
* (the traditional pathconf default). An empty entry — a leading,
* trailing, or doubled colon — means the current directory, per POSIX.
*
* Error policy (POSIX XBD exec): ENOENT and ENOTDIR from a candidate are
* skipped while the rest of PATH is searched, and EACCES is remembered;
* any other error aborts immediately. When every candidate fails, errno
* is EACCES if any candidate failed with EACCES, else ENOENT.
*/
// NOLINTBEGIN(clang-analyzer-security.insecureAPI.DeprecatedOrUnsafeBufferHandling)
int
execvp(const char *file, char *const argv[])
{
const char *path;
const char *p;
size_t flen;
int saw_eacces;
if (file == 0 || file[0] == '\0')
{
errno = ENOENT;
return -1;
}
if (strchr(file, '/') != 0)
{
return execve(file, argv, environ);
}
path = getenv("PATH");
if (path == 0)
{
path = "/bin:/usr/bin";
}
flen = strlen(file);
saw_eacces = 0;
for (p = path; *p != '\0';)
{
const char *end = p;
size_t dlen;
while (*end != '\0' && *end != ':')
{
end++;
}
dlen = (size_t)(end - p);
{
char candidate[dlen + flen + 2]; /* dir + '/' + file + NUL */
long r;
if (dlen == 0)
{
/* Empty PATH entry: the current directory. */
memcpy(candidate, file, flen + 1);
}
else
{
memcpy(candidate, p, dlen);
candidate[dlen] = '/';
memcpy(candidate + dlen + 1, file, flen + 1);
}
r = __syscall3(SYS_execve, (long)candidate, (long)argv, (long)environ);
if (r < 0)
{
int e = (int)-r;
if (e == EACCES)
{
saw_eacces = 1;
}
else if (e != ENOENT && e != ENOTDIR)
{
/* A real error: stop the search and report it. */
errno = e;
return -1;
}
}
}
if (*end == '\0')
{
break;
}
p = end + 1;
}
errno = saw_eacces ? EACCES : ENOENT;
return -1;
}
// NOLINTEND(clang-analyzer-security.insecureAPI.DeprecatedOrUnsafeBufferHandling)
+77
View File
@@ -0,0 +1,77 @@
#ifdef HAVE_CONFIG_H
#include <config.h>
#endif
#include <unistd.h>
#include "../internal/syscall.h"
#include "atfork_impl.h"
/*
* fork(): create a child that is a copy of the caller.
*
* Hook invocation order (documented for todo 45, pthread_atfork):
*
* 1. __vlibc_atfork_prepare() — reverse registration order (POSIX),
* before the syscall;
* 2. SYS_fork;
* 3. __vlibc_atfork_child() in the child, __vlibc_atfork_parent() in
* the parent — both in registration order (POSIX).
*
* The hook list is NULL by default (no threads, no registrations), so a
* bare fork() costs exactly the three no-op walker calls; with a static
* link the compiler folds them to nothing.
*
* SYS_fork is sufficient here: it is the kernel's plain
* clone(SIGCHLD, 0). There is no thread runtime yet (todo 45); once there
* is, fork must switch to SYS_clone with a clearable child tid so that
* pthread_join can reap without SIGCHLD races.
*/
pid_t
fork(void)
{
pid_t ret;
__vlibc_atfork_prepare();
ret = syscall_ret(__syscall0(SYS_fork));
if (ret == 0)
{
/* Child: run the child phase (registration order). */
__vlibc_atfork_child();
}
else if (ret > 0)
{
/* Parent: run the parent phase (registration order). */
__vlibc_atfork_parent();
}
return ret;
}
#if VLIBC_LEVEL_GE(2)
/*
* vfork(): like fork(), but POSIX lets the child borrow the parent's
* address space until it execs or exits (POSIX [OB], level 2).
*
* Implemented as fork(). Rationale (glibc makes the identical choice on
* Linux): a C vfork cannot honor the "no function call between vfork and
* exec" contract. The child's rsp is the parent's suspended rsp + 8
* (the child has already popped vfork's return address), so the child's
* first call — even into this library's own execve wrapper — writes its
* return address exactly over the parent's suspended return-address
* slot; when the parent resumes it returns through the clobbered slot
* into the child's continuation. Verified empirically (the resumed
* parent landed in the child's exec-failure path). The kernel's COW
* fork already makes fork() as cheap as vfork for all practical
* purposes, and the [OB] semantics differences (no parent suspension,
* no shared stack) are explicitly tolerated by the plan. The atfork
* hooks run, as they do for glibc's fork-based vfork.
*/
pid_t
vfork(void)
{
return fork();
}
#endif /* VLIBC_LEVEL_GE(2) */
+34
View File
@@ -0,0 +1,34 @@
#ifdef HAVE_CONFIG_H
#include <config.h>
#endif
#include <stddef.h>
#include <unistd.h>
#include "../internal/syscall.h"
/*
* Supplementary group access.
*
* getgroups (POSIX base): fill grouplist with up to gidsetsize
* supplementary group ids and return the count; with gidsetsize 0 only
* the count is returned and nothing is written.
*
* setgroups (XSI, level 2): install the given supplementary group list.
* Requires privilege (CAP_SETGID).
*/
int
getgroups(int gidsetsize, gid_t grouplist[])
{
return syscall_ret(__syscall2(SYS_getgroups, gidsetsize, (long)grouplist));
}
#if VLIBC_LEVEL_GE(2)
int
setgroups(size_t gidsetsize, const gid_t *grouplist)
{
return syscall_ret(__syscall2(SYS_setgroups, (long)gidsetsize, (long)grouplist));
}
#endif /* VLIBC_LEVEL_GE(2) */
+27
View File
@@ -0,0 +1,27 @@
#ifdef HAVE_CONFIG_H
#include <config.h>
#endif
#include <unistd.h>
#include "../internal/syscall.h"
/*
* getpid/getppid: plain kernel reads.
*
* No const/pure attribute: the result is process state, not a pure
* function of the arguments. In particular, const would let the compiler
* hoist a getpid call across fork() and observe the parent's pid inside
* the child.
*/
pid_t
getpid(void)
{
return (pid_t)syscall_ret(__syscall0(SYS_getpid));
}
pid_t
getppid(void)
{
return (pid_t)syscall_ret(__syscall0(SYS_getppid));
}
+109
View File
@@ -0,0 +1,109 @@
#ifdef HAVE_CONFIG_H
#include <config.h>
#endif
#include <unistd.h>
#include "../internal/syscall.h"
/* Foreground-process-group ioctls (x86_64 _IO/_IOR derivations). */
#define VLIBC_PROC_TIOCGPGRP 0x540F
#define VLIBC_PROC_TIOCSPGRP 0x5410
/*
* Process group and session control (POSIX base): setpgid, getpgrp,
* setsid. The XSI/obsolescent extras (setpgrp, getpgid, getsid,
* tcgetpgrp, tcsetpgrp) are gated at level 2 below.
*/
/*
* setpgid: move pid (0 = the caller) into process group pgid (0 = pid's
* own value). A process can change groups for itself and its children;
* session leaders cannot change groups.
*/
int
setpgid(pid_t pid, pid_t pgid)
{
return syscall_ret(__syscall2(SYS_setpgid, pid, pgid));
}
/*
* getpgrp: the caller's process group.
*/
pid_t
getpgrp(void)
{
return (pid_t)syscall_ret(__syscall0(SYS_getpgrp));
}
/*
* setsid: make the caller the leader of a new session and process group,
* detaching it from the controlling terminal. Returns the new session id
* (the caller's pid), or -1 with errno EPERM when the caller is already
* a process group leader.
*/
pid_t
setsid(void)
{
return (pid_t)syscall_ret(__syscall0(SYS_setsid));
}
#if VLIBC_LEVEL_GE(2)
/* Level 2 (muslmimic): XSI and obsolescent session extras. */
/*
* setpgrp (obsolescent): setpgid(0, 0) — the caller becomes the leader
* of its own process group.
*/
int
setpgrp(void)
{
return setpgid(0, 0);
}
/*
* getpgid (XSI): the process group of pid (0 = the caller).
*/
pid_t
getpgid(pid_t pid)
{
return (pid_t)syscall_ret(__syscall1(SYS_getpgid, pid));
}
/*
* getsid (XSI): the session id of pid (0 = the caller).
*/
pid_t
getsid(pid_t pid)
{
return (pid_t)syscall_ret(__syscall1(SYS_getsid, pid));
}
/*
* tcgetpgrp (XSI): the foreground process group of the terminal on
* fildes, or -1 with errno set (ENOTTY when fildes is not a terminal).
*/
pid_t
tcgetpgrp(int fildes)
{
int pgrp = 0;
if (syscall_ret(__syscall3(SYS_ioctl, fildes, VLIBC_PROC_TIOCGPGRP, (long)&pgrp)) < 0)
{
return (pid_t)-1;
}
return (pid_t)pgrp;
}
/*
* tcsetpgrp (XSI): make pgid the foreground process group of the
* terminal on fildes.
*/
int
tcsetpgrp(int fildes, pid_t pgid)
{
return syscall_ret(__syscall3(SYS_ioctl, fildes, VLIBC_PROC_TIOCSPGRP, pgid));
}
#endif /* VLIBC_LEVEL_GE(2) */
+145
View File
@@ -0,0 +1,145 @@
#ifdef HAVE_CONFIG_H
#include <config.h>
#endif
#include <errno.h>
#include <stdlib.h>
#include <unistd.h>
#include "../internal/syscall.h"
/*
* system(cmd): run "sh -c cmd" and return the shell's wait status.
*
* - NULL cmd: report whether a command processor exists. /bin/sh is
* assumed here, as everywhere in POSIX, so this always returns 1.
* - otherwise: fork, exec /bin/sh with {"sh", "-c", cmd}, wait for it,
* and return the raw wait4 status (e.g. 768 == "exit 3").
* - -1 with errno set when the child cannot be created or reaped.
*
* POSIX requires the parent to block SIGCHLD and ignore SIGINT/SIGQUIT
* while the shell runs. There is no signal.h yet (todo 28): the mask and
* dispositions are manipulated with raw rt_sigprocmask/rt_sigaction
* syscalls on the kernel's one-word sigset_t, so this file takes no
* dependency on the signal todo. The child restores the saved mask and
* dispositions before exec, matching glibc/musl behavior.
*
* The child runs only async-signal-safe code between fork and exec: the
* raw signal syscalls, execve, and the raw exit syscall. fork()'s atfork
* hooks run (prepare/child), which is what pthread_atfork (todo 45)
* wants. This fork-based implementation matches glibc; POSIX does not
* require system() to be async-signal-safe itself.
*/
/*
* Kernel sigaction layout (x86_64): handler, flags, restorer, and the
* one-word sigset_t mask. This is the syscall ABI, NOT the <signal.h>
* struct — todo 28 owns that one.
*/
struct vlibc_sys_sigaction
{
void (*handler)(int);
unsigned long flags;
void (*restorer)(void);
unsigned long mask;
};
#define VLIBC_PROC_SIG_IGN ((void (*)(int))1)
#define VLIBC_PROC_SIG_BLOCK 0
#define VLIBC_PROC_SIG_SETMASK 2
#define VLIBC_PROC_SIGINT 2
#define VLIBC_PROC_SIGQUIT 3
#define VLIBC_PROC_SIGCHLD 17
int
system(const char *string)
{
struct vlibc_sys_sigaction ignore;
struct vlibc_sys_sigaction old_int;
struct vlibc_sys_sigaction old_quit;
unsigned long block_mask = 1UL << (VLIBC_PROC_SIGCHLD - 1);
unsigned long old_mask = 0;
pid_t pid;
int status = 0;
if (string == 0)
{
/* A command processor is always available. */
return 1;
}
ignore.handler = VLIBC_PROC_SIG_IGN;
ignore.flags = 0;
ignore.restorer = 0;
ignore.mask = 0;
/* 1. Parent: ignore SIGINT/SIGQUIT, block SIGCHLD (all saved). */
if (syscall_ret(
__syscall4(SYS_rt_sigaction, VLIBC_PROC_SIGINT, (long)&ignore, (long)&old_int, 8)) < 0)
{
return -1;
}
if (syscall_ret(__syscall4(SYS_rt_sigaction, VLIBC_PROC_SIGQUIT, (long)&ignore, (long)&old_quit,
8)) < 0)
{
/* Undo the SIGINT change above. */
(void)syscall_ret(__syscall4(SYS_rt_sigaction, VLIBC_PROC_SIGINT, (long)&old_int, 0, 8));
return -1;
}
if (syscall_ret(__syscall4(SYS_rt_sigprocmask, VLIBC_PROC_SIG_BLOCK, (long)&block_mask,
(long)&old_mask, 8)) < 0)
{
(void)syscall_ret(__syscall4(SYS_rt_sigaction, VLIBC_PROC_SIGINT, (long)&old_int, 0, 8));
(void)syscall_ret(__syscall4(SYS_rt_sigaction, VLIBC_PROC_SIGQUIT, (long)&old_quit, 0, 8));
return -1;
}
/* 2. Fork. */
pid = fork();
if (pid == 0)
{
char *sh_argv[4];
/* Child: restore the saved state, then exec the shell. */
(void)__syscall4(SYS_rt_sigaction, VLIBC_PROC_SIGINT, (long)&old_int, 0, 8);
(void)__syscall4(SYS_rt_sigaction, VLIBC_PROC_SIGQUIT, (long)&old_quit, 0, 8);
(void)__syscall4(SYS_rt_sigprocmask, VLIBC_PROC_SIG_SETMASK, (long)&old_mask, 0, 8);
sh_argv[0] = "sh";
sh_argv[1] = "-c";
sh_argv[2] = (char *)string;
sh_argv[3] = 0;
execve("/bin/sh", sh_argv, environ);
__syscall1(SYS_exit_group, 127); /* exec failed */
}
if (pid < 0)
{
/* Restore the parent state; errno was set by fork(). */
(void)__syscall4(SYS_rt_sigprocmask, VLIBC_PROC_SIG_SETMASK, (long)&old_mask, 0, 8);
(void)__syscall4(SYS_rt_sigaction, VLIBC_PROC_SIGINT, (long)&old_int, 0, 8);
(void)__syscall4(SYS_rt_sigaction, VLIBC_PROC_SIGQUIT, (long)&old_quit, 0, 8);
return -1;
}
/* 3. Parent: wait for the shell (raw SYS_wait4; todo 23 owns the
* public wrapper). */
for (;;)
{
long r = __syscall4(SYS_wait4, pid, (long)&status, 0, 0);
if (r < 0 && -r == EINTR)
{
continue;
}
if (r < 0)
{
status = syscall_ret(r); /* -1 + errno */
}
break;
}
/* 4. Parent: restore the saved state. */
(void)__syscall4(SYS_rt_sigprocmask, VLIBC_PROC_SIG_SETMASK, (long)&old_mask, 0, 8);
(void)__syscall4(SYS_rt_sigaction, VLIBC_PROC_SIGINT, (long)&old_int, 0, 8);
(void)__syscall4(SYS_rt_sigaction, VLIBC_PROC_SIGQUIT, (long)&old_quit, 0, 8);
return status;
}
+70
View File
@@ -0,0 +1,70 @@
#ifdef HAVE_CONFIG_H
#include <config.h>
#endif
#include <unistd.h>
#include "../internal/syscall.h"
/*
* Real/effective uid and gid accessors and mutators. None of the getters
* carries a const/pure attribute: the values are process state, not a
* pure function of the arguments, and the set functions change what the
* get functions read.
*/
uid_t
getuid(void)
{
return (uid_t)syscall_ret(__syscall0(SYS_getuid));
}
uid_t
geteuid(void)
{
return (uid_t)syscall_ret(__syscall0(SYS_geteuid));
}
gid_t
getgid(void)
{
return (gid_t)syscall_ret(__syscall0(SYS_getgid));
}
gid_t
getegid(void)
{
return (gid_t)syscall_ret(__syscall0(SYS_getegid));
}
/*
* setuid: set the real, effective, and saved ids (kernel SYS_setuid).
* When unprivileged, uid must match the current real or effective id.
*/
int
setuid(uid_t uid)
{
return syscall_ret(__syscall1(SYS_setuid, uid));
}
/*
* seteuid: set only the effective id. Linux has no seteuid syscall; the
* kernel contract is setresuid(-1, euid, -1), which is what glibc and
* musl issue too.
*/
int
seteuid(uid_t euid)
{
return syscall_ret(__syscall3(SYS_setresuid, -1, euid, -1));
}
int
setgid(gid_t gid)
{
return syscall_ret(__syscall1(SYS_setgid, gid));
}
int
setegid(gid_t egid)
{
return syscall_ret(__syscall3(SYS_setresgid, -1, egid, -1));
}
+201
View File
@@ -0,0 +1,201 @@
#ifdef HAVE_CONFIG_H
#include <config.h>
#endif
#include <errno.h>
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include "stdio_impl.h"
#include "../internal/syscall.h"
/*
* popen/pclose (POSIX base, todo 20): run "sh -c cmd" with a pipe
* attached to its stdin ("w") or stdout ("r").
*
* The stream is a todo-15 FILE built on the FILE core. popen does NOT go
* through the public fdopen(): todo 15's fdopen validates the requested
* mode against the descriptor's F_GETFL access mode by masking with
* O_RDWR, so a plain O_WRONLY descriptor is misread as O_RDONLY and a
* "w" fdopen is rejected — and the pipe write end is exactly that case
* (F_SETFL cannot promote a pipe end to O_RDWR). popen knows the access
* mode of its own freshly created pipe end, so it goes straight to the
* FILE core: stdio_parse_mode + stdio_alloc_file. Only the POSIX modes
* "r" and "w" are accepted; the glibc "re"/"we" close-on-exec extension
* is not. There is no sys/wait.h yet (todo 23): pclose waits via the
* raw SYS_wait4, and the child exits via the raw SYS_exit_group when
* the exec fails.
*
* Registry: a static table mapping FILE* to the shell pid. The todo-15
* FILE struct has no spare field for the pid, so pclose looks the
* stream up here. FOPEN_MAX (16) slots; a full table fails with EMFILE.
* Not thread-safe — fine while there is no thread runtime (todo 45).
*
* pclose closes the stream first, then waits: for "w" streams the close
* flushes the buffered writes and delivers EOF, letting the shell exit;
* waiting first could deadlock on a full pipe. A stream not opened by
* popen is still closed, and pclose returns -1 as POSIX requires. A wait
* failure (ECHILD) returns -1 with errno set.
*/
struct vlibc_popen_entry
{
FILE *stream;
pid_t pid;
};
static struct vlibc_popen_entry popen_table[FOPEN_MAX];
// NOLINTBEGIN(bugprone-easily-swappable-parameters)
FILE *
popen(const char *command, const char *mode)
{
int fds[2] = {0}; /* written by SYS_pipe; the analyzer cannot model it */
int parent_fd;
int child_fd;
int read_mode;
int m;
int oflags;
pid_t pid;
FILE *f;
int i;
if (mode[0] == 'r' && mode[1] == '\0')
{
read_mode = 1;
}
else if (mode[0] == 'w' && mode[1] == '\0')
{
read_mode = 0;
}
else
{
errno = EINVAL;
return 0;
}
(void)stdio_parse_mode(mode, &m, &oflags); /* "r"/"w" always parse */
if (syscall_ret(__syscall1(SYS_pipe, (long)fds)) < 0)
{
return 0;
}
if (read_mode)
{
parent_fd = fds[0]; /* parent reads the shell's stdout */
child_fd = fds[1];
}
else
{
parent_fd = fds[1]; /* parent writes the shell's stdin */
child_fd = fds[0];
}
pid = fork();
if (pid == 0)
{
char *sh_argv[4];
int target = read_mode ? 1 : 0;
/*
* Child: wire the pipe end onto the shell's fd (0 for "w", 1
* for "r"), close everything else. The guards cover the
* (theoretical) case where the pipe handed us fd 0 or 1.
*/
if (parent_fd != target)
{
(void)__syscall1(SYS_close, parent_fd);
}
if (child_fd != target)
{
(void)__syscall2(SYS_dup2, child_fd, target);
(void)__syscall1(SYS_close, child_fd);
}
sh_argv[0] = "sh";
sh_argv[1] = "-c";
sh_argv[2] = (char *)command;
sh_argv[3] = 0;
execve("/bin/sh", sh_argv, environ);
__syscall1(SYS_exit_group, 127); /* exec failed */
}
/* Parent. */
(void)__syscall1(SYS_close, child_fd);
if (pid < 0)
{
(void)__syscall1(SYS_close, parent_fd);
return 0;
}
f = stdio_alloc_file(parent_fd, m);
if (f == 0)
{
/* Drop the descriptor and reap the shell we just spawned. */
(void)__syscall1(SYS_close, parent_fd);
(void)__syscall4(SYS_wait4, pid, 0, 0, 0);
return 0;
}
for (i = 0; i < FOPEN_MAX; i++)
{
if (popen_table[i].stream == 0)
{
popen_table[i].stream = f;
popen_table[i].pid = pid;
return f;
}
}
/* Registry full: drop the stream and reap the shell. */
(void)fclose(f);
(void)__syscall4(SYS_wait4, pid, 0, 0, 0);
errno = EMFILE;
return 0;
}
// NOLINTEND(bugprone-easily-swappable-parameters)
int
pclose(FILE *stream)
{
pid_t pid = 0;
int found = 0;
int status = 0;
int i;
for (i = 0; i < FOPEN_MAX; i++)
{
if (popen_table[i].stream == stream)
{
pid = popen_table[i].pid;
popen_table[i].stream = 0;
popen_table[i].pid = 0;
found = 1;
break;
}
}
(void)fclose(stream);
if (!found)
{
/* Not a popen stream: POSIX wants -1. The stream is still
* closed. */
return -1;
}
for (;;)
{
long r = __syscall4(SYS_wait4, pid, (long)&status, 0, 0);
if (r < 0 && -r == EINTR)
{
continue;
}
if (r < 0)
{
return syscall_ret(r); /* -1 + errno (e.g. ECHILD) */
}
return status;
}
}