#include <sys/cdefs.h>
__COPYRIGHT("@(#) Copyright (c) 2018\
The NetBSD Foundation, inc. All rights reserved.");
__RCSID("$NetBSD: t_zombie.c,v 1.3 2022/05/24 20:08:38 andvar Exp $");
#include <sys/types.h>
#include <sys/sysctl.h>
#include <sys/wait.h>
#include <errno.h>
#include <stdbool.h>
#include <stddef.h>
#include <stdio.h>
#include <stdlib.h>
#include <signal.h>
#include <time.h>
#include <unistd.h>
#include <err.h>
#include <atf-c.h>
static int debug = 0;
#define DPRINTF(a, ...) \
do { \
if (debug) printf(a, ##__VA_ARGS__); \
} while (0)
#define ASSERT_EQ(x, y) \
do { \
uintmax_t vx = (x); \
uintmax_t vy = (y); \
int ret = vx == vy; \
if (!ret) \
errx(EXIT_FAILURE, "%s:%d %s(): Assertion failed for: " \
"%s(%ju) == %s(%ju)", __FILE__, __LINE__, __func__, \
#x, vx, #y, vy); \
} while (0)
#define ASSERT_NEQ(x, y) \
do { \
uintmax_t vx = (x); \
uintmax_t vy = (y); \
int ret = vx != vy; \
if (!ret) \
errx(EXIT_FAILURE, "%s:%d %s(): Assertion failed for: " \
"%s(%ju) != %s(%ju)", __FILE__, __LINE__, __func__, \
#x, vx, #y, vy); \
} while (0)
#define ASSERT(x) \
do { \
int ret = (x); \
if (!ret) \
errx(EXIT_FAILURE, "%s:%d %s(): Assertion failed for: %s", \
__FILE__, __LINE__, __func__, #x); \
} while (0)
static bool
check_zombie(pid_t process)
{
struct kinfo_proc2 p;
size_t len = sizeof(p);
const int name[] = {
[0] = CTL_KERN,
[1] = KERN_PROC2,
[2] = KERN_PROC_PID,
[3] = process,
[4] = sizeof(p),
[5] = 1
};
const size_t namelen = __arraycount(name);
ASSERT_EQ(sysctl(name, namelen, &p, &len, NULL, 0), 0);
return (p.p_stat == LSZOMB);
}
static void __used
await_zombie(pid_t process)
{
while (!check_zombie(process)) {
ASSERT_EQ(usleep(100), 0);
}
}
static void
signal_raw(int sig)
{
int status;
pid_t child1, child2, pid;
child1 = atf_utils_fork();
ATF_REQUIRE(child1 != -1);
if (child1 == 0) {
_exit(0);
}
child2 = atf_utils_fork();
ATF_REQUIRE(child2 != -1);
if (child2 == 0) {
await_zombie(child1);
errno = 0;
kill(child1, sig);
ASSERT_NEQ(errno, ESRCH);
ASSERT(check_zombie(child1));
_exit(0);
}
pid = waitpid(child2, &status, WEXITED);
ATF_REQUIRE_EQ(pid, child2);
ATF_REQUIRE(WIFEXITED(status));
ATF_REQUIRE(!WIFCONTINUED(status));
ATF_REQUIRE(!WIFSIGNALED(status));
ATF_REQUIRE(!WIFSTOPPED(status));
ATF_REQUIRE_EQ(WEXITSTATUS(status), 0);
ATF_REQUIRE(check_zombie(child1));
errno = 0;
kill(child1, sig);
ASSERT_NEQ(errno, ESRCH);
ATF_REQUIRE(check_zombie(child1));
pid = waitpid(child1, &status, WEXITED);
ATF_REQUIRE_EQ(pid, child1);
ATF_REQUIRE(WIFEXITED(status));
ATF_REQUIRE(!WIFCONTINUED(status));
ATF_REQUIRE(!WIFSIGNALED(status));
ATF_REQUIRE(!WIFSTOPPED(status));
ATF_REQUIRE_EQ(WEXITSTATUS(status), 0);
}
#define KILLABLE(test, sig) \
ATF_TC(test); \
ATF_TC_HEAD(test, tc) \
{ \
\
atf_tc_set_md_var(tc, "descr", \
"process is not killable with " #sig); \
} \
\
ATF_TC_BODY(test, tc) \
{ \
\
signal_raw(sig); \
}
KILLABLE(signal1, SIGKILL)
KILLABLE(signal2, SIGSTOP)
KILLABLE(signal3, SIGABRT)
KILLABLE(signal4, SIGHUP)
KILLABLE(signal5, SIGCONT)
ATF_TC(race1);
ATF_TC_HEAD(race1, tc)
{
atf_tc_set_md_var(tc, "descr",
"check if there are any races with sending signals, killing and "
"lookup of a zombie");
}
ATF_TC_BODY(race1, tc)
{
time_t start, end;
double diff;
unsigned long N = 0;
int sig;
start = time(NULL);
while (true) {
sig = 1 + arc4random_uniform(NSIG - 2);
DPRINTF("Step: %lu (signal: %s)\n", N, signalname(sig));
signal_raw(sig);
end = time(NULL);
diff = difftime(end, start);
if (diff >= 5.0)
break;
++N;
}
DPRINTF("Iterations: %lu\n", N);
}
ATF_TP_ADD_TCS(tp)
{
ATF_TP_ADD_TC(tp, signal1);
ATF_TP_ADD_TC(tp, signal2);
ATF_TP_ADD_TC(tp, signal3);
ATF_TP_ADD_TC(tp, signal4);
ATF_TP_ADD_TC(tp, signal5);
ATF_TP_ADD_TC(tp, race1);
return atf_no_error();
}