feat(process): fork/exec/id/session + system/popen
This commit is contained in:
@@ -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();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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 */
|
||||
@@ -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)
|
||||
@@ -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)
|
||||
@@ -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)
|
||||
@@ -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);
|
||||
}
|
||||
@@ -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));
|
||||
}
|
||||
@@ -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)
|
||||
@@ -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) */
|
||||
@@ -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) */
|
||||
@@ -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));
|
||||
}
|
||||
@@ -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) */
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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));
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user