blob: bee38af08a5f87c98babde06953bf449aa7d6842 [file] [log] [blame]
/*
* Test program for Linux poison memory error recovery.
* This injects poison into various mapping cases and triggers the poison
* handling. Requires special injection support in the kernel.
*
* Copyright 2009, 2010 Intel Corporation
*
* tinjpage is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public
* License as published by the Free Software Foundation; version
* 2.
*
* tinjpage is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* General Public License for more details.
*
* You should find a copy of v2 of the GNU General Public License somewhere
* on your Linux system; if not, write to the Free Software Foundation,
* Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
*
* Authors: Andi Kleen, Fengguang Wu
*/
#define _GNU_SOURCE 1
#include <stdio.h>
#include <signal.h>
#include <unistd.h>
#include <sys/fcntl.h>
#include <sys/wait.h>
#include <sys/mman.h>
#include <stdlib.h>
#include <setjmp.h>
#include <errno.h>
#include <string.h>
#include <time.h>
#include <pthread.h>
#include <sys/ipc.h>
#include <sys/shm.h>
#include <sys/sem.h>
#include "utils.h"
#include "hugepage.h"
#define MADV_POISON 100
#define TMPDIR "./"
#define PATHBUFLEN 100
#define Perror(x) failure++, perror(x)
#define PAIR(x) x, sizeof(x)-1
#define mb() asm volatile("" ::: "memory")
#if defined(__i386__) || defined(__x86_64__)
#define cpu_relax() asm volatile("rep ; nop" ::: "memory")
#else
#define cpu_relax() mb()
#endif
typedef unsigned long long u64;
int PS;
int failure;
int unexpected;
int early_kill;
int test_hugepage;
void *checked_mmap(void *start, size_t length, int prot, int flags,
int fd, off_t offset)
{
void *map = mmap(start, length, prot, flags, fd, offset);
if (map == (void*)-1L)
err("mmap");
return map;
}
void munmap_reserve(void *page, int size)
{
if (munmap(page, size) < 0)
err("munmap");
if (mmap(page, size, PROT_NONE, MAP_PRIVATE|MAP_FIXED, 0, 0) < 0)
err("mmap2");
}
void *xmalloc(size_t s)
{
void *p = malloc(s);
if (!p)
exit(ENOMEM);
return p;
}
static int ilog2(int n)
{
int r = 0;
n--;
while (n) {
n >>= 1;
r++;
}
return r;
}
int recovercount;
sigjmp_buf recover_ctx;
sigjmp_buf early_recover_ctx;
void *expected_addr;
/* Work around glibc not defining this yet */
struct my_siginfo {
int si_signo;
int si_errno;
int si_code;
union {
struct {
void *_addr; /* faulting insn/memory ref. */
#ifdef __ARCH_SI_TRAPNO
int _trapno; /* TRAP # which caused the signal */
#endif
short _addr_lsb; /* LSB of the reported address */
} _sigfault;
} _sifields;
};
#undef si_addr_lsb
#define si_addr_lsb _sifields._sigfault._addr_lsb
void sighandler(int sig, siginfo_t *si, void *arg)
{
if (si->si_addr != expected_addr) {
printf("XXX: Unexpected address in signal %p (expected %p)\n", si->si_addr,
expected_addr);
failure++;
}
int lsb = ((struct my_siginfo *)si)->si_addr_lsb;
if (test_hugepage) {
if (lsb != ilog2(HPS)) {
printf("LATER: Unexpected addr lsb in siginfo %d\n", lsb);
}
} else {
if (lsb != ilog2(sysconf(_SC_PAGE_SIZE))) {
printf("LATER: Unexpected addr lsb in siginfo %d\n", lsb);
}
}
printf("\tsignal %d code %d addr %p\n", sig, si->si_code, si->si_addr);
if (--recovercount == 0) {
write(1, PAIR("I seem to be in a signal loop. bailing out.\n"));
exit(1);
}
if (si->si_code == 4)
siglongjmp(recover_ctx, 1);
else
siglongjmp(early_recover_ctx, 1);
}
enum rmode {
MREAD = 0,
MWRITE = 1,
MREAD_OK = 2,
MWRITE_OK = 3,
MNOTHING = -1,
};
void inject_madvise(char *page)
{
if (madvise(page, PS, MADV_POISON) != 0) {
if (errno == EINVAL) {
printf("Kernel doesn't support poison injection\n");
exit(0);
}
Perror("madvise");
}
}
u64 page_to_pfn(char *page)
{
static int pagemap_fd = -1;
u64 pfn;
if (pagemap_fd < 0) {
pagemap_fd = open("/proc/self/pagemap", O_RDONLY);
if (pagemap_fd < 0)
err("/proc/self/pagemap not supported");
}
if (pread(pagemap_fd, &pfn, sizeof(u64),
((u64)page / PS)*sizeof(u64)) != sizeof(u64))
err("Cannot read from pagemap");
pfn &= (1ULL<<56)-1;
return pfn;
}
/*
* Inject Action Optional #MC
* with mce-inject using the software injector.
*
* This tests the low level machine check handler too.
*
* Slightly racy with page migration because we don't mlock the page.
*/
void inject_mce_inject(char *page)
{
u64 pfn = page_to_pfn(page);
FILE *mce_inject;
mce_inject = popen("mce-inject", "w");
if (!mce_inject) {
fprintf(stderr, "Cannot open pipe to mce-inject: %s\n",
strerror(errno));
exit(1);
}
fprintf(mce_inject,
"CPU 0 BANK 3 STATUS UNCORRECTED SRAO 0xc0\n"
"MCGSTATUS RIPV MCIP\n"
"ADDR %#llx\n"
"MISC 0x8c\n"
"RIP 0x73:0x1eadbabe\n", pfn);
if (ferror(mce_inject) || fclose(mce_inject) < 0) {
fprintf(stderr, "mce-inject failed: %s\n", strerror(errno));
exit(1);
}
}
void (*inject)(char *page) = inject_madvise;
void poison(char *msg, char *page, enum rmode mode)
{
expected_addr = page;
recovercount = 5;
if (sigsetjmp(early_recover_ctx, 1) == 0) {
inject(page);
if (early_kill && (mode == MWRITE || mode == MREAD)) {
printf("XXX: %s: process is not early killed\n", msg);
failure++;
}
return;
}
if (early_kill) {
if (mode == MREAD_OK || mode == MWRITE_OK) {
printf("XXX: %s: killed\n", msg);
failure++;
} else
printf("\trecovered\n");
}
}
void recover(char *msg, char *page, enum rmode mode)
{
expected_addr = page;
recovercount = 5;
if (sigsetjmp(recover_ctx, 1) == 0) {
switch (mode) {
case MWRITE:
printf("\twriting 2\n");
*page = 2;
break;
case MWRITE_OK:
printf("\twriting 4\n");
*page = 4;
return;
case MREAD:
printf("\treading %x\n", *(unsigned char *)page);
break;
case MREAD_OK:
printf("\treading %x\n", *(unsigned char *)page);
return;
case MNOTHING:
return;
}
/* signal or kill should have happened */
printf("XXX: %s: page not poisoned after injection\n", msg);
failure++;
return;
}
if (mode == MREAD_OK || mode == MWRITE_OK) {
printf("XXX: %s: killed\n", msg);
failure++;
} else
printf("\trecovered\n");
}
void testmem(char *msg, char *page, enum rmode mode)
{
printf("\t%s poisoning page %p\n", msg, page);
poison(msg, page, mode);
recover(msg, page, mode);
}
void expecterr(char *msg, int err)
{
if (err) {
printf("\texpected error %d on %s\n", errno, msg);
} else {
failure++;
printf("XXX: unexpected no error on %s\n", msg);
}
}
/*
* Any optional error is really a deficiency in the kernel VFS error reporting
* and should be eventually fixed and turned into a expecterr
*/
void optionalerr(char *msg, int err)
{
if (err) {
printf("\texpected optional error %d on %s\n", errno, msg);
} else {
unexpected++;
printf("LATER: expected likely incorrect no error on %s\n", msg);
}
}
static int tmpcount;
int tempfd(void)
{
int fd;
char buf[PATHBUFLEN];
snprintf(buf, sizeof buf, TMPDIR "~poison%d",tmpcount++);
fd = open(buf, O_CREAT|O_RDWR, 0600);
if (fd >= 0)
unlink(buf);
if (fd < 0)
err("opening temporary file in " TMPDIR);
return fd;
}
int playfile(char *buf)
{
int fd;
if (buf[0] == 0)
snprintf(buf, PATHBUFLEN, TMPDIR "~poison%d", tmpcount++);
fd = open(buf, O_CREAT|O_RDWR|O_TRUNC, 0600);
if (fd < 0)
err("opening temporary file in " TMPDIR);
const int NPAGES = 5;
char *tmp = xmalloc(PS * NPAGES);
int i;
for (i = 0; i < PS*NPAGES; i++)
tmp[i] = i;
write(fd, tmp, PS*NPAGES);
lseek(fd, 0, SEEK_SET);
return fd;
}
static void dirty_anonymous(void)
{
char *page;
page = checked_mmap(NULL, PS, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS|MAP_POPULATE, 0, 0);
testmem("dirty", page, MWRITE);
}
static void dirty_anonymous_unmap(void)
{
char *page;
page = checked_mmap(NULL, PS, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS|MAP_POPULATE, 0, 0);
testmem("dirty", page, MWRITE);
munmap_reserve(page, PS);
}
static void mlocked_anonymous(void)
{
char *page;
page = checked_mmap(NULL, PS, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS|MAP_LOCKED, 0, 0);
testmem("mlocked", page, MWRITE);
}
static void do_file_clean(int flags, char *name)
{
char *page;
char fn[30];
snprintf(fn, 30, TMPDIR "~test%d", tmpcount++);
int fd = open(fn, O_RDWR|O_TRUNC|O_CREAT);
if (fd < 0)
err("open temp file");
write(fd, fn, 4);
page = checked_mmap(NULL, PS, PROT_READ|PROT_WRITE, MAP_SHARED|flags,
fd, 0);
fsync(fd);
close(fd);
testmem(name, page, MREAD_OK);
/* reread page from disk */
printf("\t reading %x\n", *(unsigned char *)page);
testmem(name, page, MWRITE_OK);
}
static void file_clean(void)
{
do_file_clean(0, "file clean");
}
static void file_clean_mlocked(void)
{
do_file_clean(MAP_LOCKED, "file clean mlocked");
}
static char *ndesc(char *buf, char *name, char *add)
{
snprintf(buf, 100, "%s %s", name, add);
return buf;
}
static void do_file_dirty(int flags, char *name)
{
char nbuf[100];
char *page;
char fn[PATHBUFLEN];
fn[0] = 0;
int fd = playfile(fn);
page = checked_mmap(NULL, PS, PROT_READ,
MAP_SHARED|MAP_POPULATE|flags, fd, 0);
testmem(ndesc(nbuf, name, "initial"), page, MREAD);
expecterr("msync expect error", msync(page, PS, MS_SYNC) < 0);
close(fd);
munmap_reserve(page, PS);
fd = open(fn, O_RDONLY);
if (fd < 0) err("reopening temp file");
page = checked_mmap(NULL, PS, PROT_READ, MAP_SHARED|MAP_POPULATE|flags,
fd, 0);
recover(ndesc(nbuf, name, "populated"), page, MREAD_OK);
close(fd);
munmap_reserve(page, PS);
fd = open(fn, O_RDONLY);
if (fd < 0) err("reopening temp file");
page = checked_mmap(NULL, PS, PROT_READ, MAP_SHARED|flags, fd, 0);
recover(ndesc(nbuf, name, "fault"), page, MREAD_OK);
close(fd);
munmap_reserve(page, PS);
fd = open(fn, O_RDWR);
char buf[128];
/* the earlier close has eaten the error */
optionalerr("explicit read after poison", read(fd, buf, sizeof buf) < 0);
optionalerr("explicit write after poison", write(fd, "foobar", 6) < 0);
optionalerr("fsync expect error", fsync(fd) < 0);
close(fd);
/* should unlink return an error here? */
if (unlink(fn) < 0)
perror("unlink");
}
static void file_dirty(void)
{
do_file_dirty(0, "file dirty");
}
static void file_dirty_mlocked(void)
{
do_file_dirty(MAP_LOCKED, "file dirty mlocked");
}
/* TBD */
static void file_hole(void)
{
int fd = tempfd();
char *page;
ftruncate(fd, PS);
page = checked_mmap(NULL, PS, PROT_READ|PROT_WRITE, MAP_SHARED, fd, 0);
*page = 1;
testmem("hole file dirty", page, MREAD);
/* hole error reporting doesn't work in kernel currently, so optional */
optionalerr("hole fsync expect error", fsync(fd) < 0);
optionalerr("hole msync expect error", msync(page, PS, MS_SYNC) < 0);
close(fd);
}
static void nonlinear(void)
{
int fd;
const int NPAGES = 10;
int i;
char *page;
char *tmp;
fd = tempfd();
tmp = xmalloc(PS);
for (i = 0; i < NPAGES; i++) {
memset(tmp, i, PS);
write(fd, tmp, PS);
}
free(tmp);
page = checked_mmap(NULL, PS*NPAGES, PROT_READ|PROT_WRITE, MAP_SHARED, fd, 0);
int k = NPAGES - 1;
for (i = 0; i < NPAGES; i++, k--) {
if (remap_file_pages(page + i*PS, PS, 0, k, 0))
perror("remap_file_pages");
}
*page = 1;
testmem("rfp file dirty", page, MREAD);
expecterr("rfp fsync expect error", fsync(fd) < 0);
optionalerr("rfp msync expect error", msync(page, PS, MS_SYNC) < 0);
close(fd);
}
/*
* These tests are currently too racy to be enabled.
*/
/*
* This is quite timing dependent. The sniper might hit the page
* before it is dirtied. If that happens tweak the delay
* (should auto tune)
*/
enum {
DELAY_NS = 30,
};
volatile enum sstate { START, WAITING, SNIPE } sstate;
void waitfor(enum sstate w, enum sstate s)
{
sstate = w;
mb();
while (sstate != s)
cpu_relax();
}
struct poison_arg {
char *msg;
char *page;
enum rmode mode;
};
void *sniper(void *p)
{
struct poison_arg *arg = p;
waitfor(START, WAITING);
nanosleep(&((struct timespec) { .tv_nsec = DELAY_NS }), NULL);
poison(arg->msg, arg->page, arg->mode);
return NULL;
}
int setup_sniper(struct poison_arg *arg)
{
if (sysconf(_SC_NPROCESSORS_ONLN) < 2) {
printf("%s: Need at least two CPUs. Not tested\n", arg->msg);
return -1;
}
sstate = START;
mb();
pthread_t thr;
if (pthread_create(&thr, NULL, sniper, arg) < 0)
err("pthread_create");
pthread_detach(thr);
return 0;
}
static void under_io_dirty(void)
{
struct poison_arg arg;
int fd = tempfd();
char *page;
page = checked_mmap(NULL, PS, PROT_READ|PROT_WRITE, MAP_SHARED|MAP_POPULATE, fd, 0);
arg.page = page;
arg.msg = "under io dirty";
arg.mode = MWRITE;
if (setup_sniper(&arg) < 0)
return;
write(fd, "xyz", 3);
waitfor(WAITING, WAITING);
expecterr("write under io", fsync(fd) < 0);
close(fd);
}
static void under_io_clean(void)
{
struct poison_arg arg;
char fn[PATHBUFLEN];
int fd;
char *page;
char buf[10];
fd = playfile(fn);
page = checked_mmap(NULL, PS, PROT_READ|PROT_WRITE, MAP_SHARED|MAP_POPULATE, fd, 0);
madvise(page, PS, MADV_DONTNEED);
arg.page = page;
arg.msg = "under io clean";
arg.mode = MREAD_OK;
if (setup_sniper(&arg) < 0)
return;
waitfor(WAITING, WAITING);
// what is correct here?
if (pread(fd, buf, 10, 0) != 0)
perror("pread under io clean");
close(fd);
}
/*
* semaphore get/put wrapper
*/
int get_semaphore(int sem_id, struct sembuf *sembuffer)
{
sembuffer->sem_num = 0;
sembuffer->sem_op = -1;
sembuffer->sem_flg = SEM_UNDO;
return semop(sem_id, sembuffer, 1);
}
int put_semaphore(int sem_id, struct sembuf *sembuffer)
{
sembuffer->sem_num = 0;
sembuffer->sem_op = 1;
sembuffer->sem_flg = SEM_UNDO;
return semop(sem_id, sembuffer, 1);
}
/* memory sharing mode */
enum shared_mode {
MMAP_SHARED = 0,
IPV_SHARED = 1,
};
/*
* testcase for shared pages, where
* if early_kill == 0, parent access the shared page hwpoisoned by child, and
* if early_kill == 1, parent will be killed by SIGBUS from child.
* This testcase checks whether if a shared page is hwpoisoned by one process,
* another process sharing the page will be killed expectedly.
*/
static void do_shared(int shared_mode)
{
int shm_id = -1, sem_id = -1, semaphore;
pid_t pid;
char *shared_page = NULL;
struct sembuf sembuffer;
if (shared_mode == MMAP_SHARED) {
shared_page = checked_mmap(NULL, PS, PROT_READ|PROT_WRITE,
MAP_SHARED|MAP_ANONYMOUS|MAP_POPULATE, 0, 0);
} else if (shared_mode == IPV_SHARED) {
shm_id = shmget(IPC_PRIVATE, PS, 0666|IPC_CREAT);
if (shm_id == -1)
err("shmget");
} else {
printf("XXX: invalid shared_mode\n");
return;
}
if (early_kill) {
sem_id = semget(IPC_PRIVATE, 1, 0666|IPC_CREAT);
if (sem_id == -1) {
perror("semget");
goto cleanup;
}
semaphore = semctl(sem_id, 0, SETVAL, 1);
if (semaphore == -1) {
perror("semctl");
goto cleanup;
}
if (get_semaphore(sem_id, &sembuffer)) {
perror("get_semaphore");
goto cleanup;
}
}
pid = fork();
if (pid < 0) {
perror("fork");
goto cleanup;
}
if (shared_mode == IPV_SHARED) {
shared_page = shmat(shm_id, NULL, 0);
if (shared_page == (char *)-1) {
perror("shmat");
goto cleanup;
}
}
memset(shared_page, 'a', 3);
if (early_kill) {
struct sigaction sa = {
.sa_sigaction = sighandler,
.sa_flags = SA_SIGINFO
};
sigaction(SIGBUS, &sa, NULL);
expected_addr = shared_page;
}
if (pid) {
siginfo_t sig;
if (early_kill && sigsetjmp(early_recover_ctx, 1) == 0) {
if (put_semaphore(sem_id, &sembuffer))
err("get_semaphore");
/* waiting for SIGBUS from child */
sleep(10);
printf("XXX timeout: child process does not send signal\n");
failure++;
goto cleanup;
}
waitid(P_PID, pid, &sig, WEXITED);
/*
* check child termination status
* late kill : child should exit
* suicide version : child should be killed by signal
* early kill : child should be killed by signal
*/
if (!early_kill) {
struct sigaction sigact;
sigaction(SIGBUS, NULL, &sigact);
if (sigact.sa_handler == SIG_DFL) {/* suicide version */
if (sig.si_code != CLD_KILLED)
goto child_error;
} else { /* late kill */
if (sig.si_code != CLD_EXITED)
goto child_error;
}
} else { /* early kill */
if (sig.si_code != CLD_EXITED)
goto child_error;
}
if (!early_kill)
recover("ipv shared page (parent)",
shared_page, MWRITE);
if (shared_mode == IPV_SHARED && shmdt(shared_page) == -1) {
perror("shmdt");
goto cleanup;
}
}
if (!pid) {
failure = 0;
if (early_kill)
if (get_semaphore(sem_id, &sembuffer))
err("get_semaphore");
testmem("ipv shared page", shared_page, MWRITE);
if (shared_mode == IPV_SHARED && shmdt(shared_page) == -1)
err("shmdt");
fflush(stdout);
_exit(failure);
}
cleanup:
if (shared_page) {
if (shared_mode == IPV_SHARED)
shmdt(shared_page);
else
munmap_reserve(shared_page, PS);
}
if (shm_id >= 0 && shmctl(shm_id, IPC_RMID, NULL) < 0)
err("shmctl IPC_RMID");
if (sem_id >= 0 && semctl(sem_id, 0, IPC_RMID) < 0)
err("semctl IPC_RMID");
return;
child_error:
printf("XXX child process was terminated unexpectedly\n");
failure++;
goto cleanup;
}
static void mmap_shared(void)
{
do_shared(MMAP_SHARED);
}
static void ipv_shared(void)
{
do_shared(IPV_SHARED);
}
static void anonymous_hugepage(void)
{
char *page;
/* Hugepage isn't supported. */
if (!HPS)
return;
test_hugepage = 1;
page = alloc_anonymous_hugepage(HPS, 1);
/* prefault */
page[0] = 'a';
testmem("anonymous hugepage", page, MWRITE);
free_anonymous_hugepage(page, HPS);
test_hugepage = 0;
}
static void file_backed_hugepage(void)
{
char *page;
char buf[PATHBUFLEN];
int fd;
/* Hugepage isn't supported. */
if (!HPS)
return;
test_hugepage = 1;
snprintf(buf, PATHBUFLEN, "%s/test%d", hugetlbfsdir, tmpcount++);
page = alloc_filebacked_hugepage(buf, HPS, 0, &fd);
/* prefault */
page[0] = 'a';
testmem("file backed hugepage", page, MWRITE);
free_filebacked_hugepage(page, HPS, fd, buf);
test_hugepage = 0;
}
static void shm_hugepage(void)
{
char *page;
/* Hugepage isn't supported. */
if (!HPS)
return;
test_hugepage = 1;
page = alloc_shm_hugepage(&tmpcount, HPS);
/* prefault */
page[0] = 'a';
testmem("shared memory hugepage", page, MWRITE);
free_shm_hugepage(tmpcount, page);
tmpcount++;
test_hugepage = 0;
}
struct testcase {
void (*f)(void);
char *name;
int survivable;
} cases[] = {
{ dirty_anonymous, "dirty anonymous" },
{ dirty_anonymous_unmap, "dirty anonymous unmap" },
{ mlocked_anonymous, "mlocked anonymous" },
{ file_clean, "file clean", 1 },
{ file_dirty, "file dirty" },
{ file_hole, "file hole" },
{ file_clean_mlocked, "file clean mlocked", 1 },
{ file_dirty_mlocked, "file dirty mlocked"},
{ nonlinear, "nonlinear" },
{ mmap_shared, "mmap shared" },
{ ipv_shared, "ipv shared" },
{ anonymous_hugepage, "anonymous hugepage" },
{ file_backed_hugepage, "file backed hugepage" },
{ shm_hugepage, "shared memory hugepage" },
{}, /* dummy 1 for sniper */
{}, /* dummy 2 for sniper */
{}
};
struct testcase snipercases[] = {
{ under_io_dirty, "under io dirty" },
{ under_io_clean, "under io clean" },
};
void usage(void)
{
fprintf(stderr, "Usage: tinjpage [--sniper]\n"
"Test hwpoison injection on pages in various states\n"
"--mce-inject Use mce-inject for injection\n"
"--sniper Enable racy sniper tests (likely broken)\n");
exit(1);
}
void handle_opts(char **av)
{
while (*++av) {
if (!strcmp(*av, "--sniper")) {
struct testcase *t;
for (t = cases; t->f; t++)
;
*t++ = snipercases[0];
*t++ = snipercases[1];
}
else if (!strcmp(*av, "--mce-inject")) {
inject = inject_mce_inject;
} else
usage();
}
}
int main(int ac, char **av)
{
if (av[1])
handle_opts(av);
PS = getpagesize();
if (hugetlbfs_root(hugetlbfsdir))
HPS = gethugepagesize();
/* don't kill me at poison time, but possibly at page fault time */
early_kill = 0;
system("sysctl -w vm.memory_failure_early_kill=0");
struct sigaction sa = {
.sa_sigaction = sighandler,
.sa_flags = SA_SIGINFO
};
struct testcase *t;
/* catch signals */
sigaction(SIGBUS, &sa, NULL);
for (t = cases; t->f; t++) {
printf("---- testing %s\n", t->name);
t->f();
}
/* suicide version */
for (t = cases; t->f; t++) {
printf("---- testing %s in child\n", t->name);
pid_t child = fork();
if (child == 0) {
signal(SIGBUS, SIG_DFL);
t->f();
if (t->survivable)
_exit(2);
write(1, t->name, strlen(t->name));
write(1, PAIR(" didn't kill itself?\n"));
_exit(1);
} else {
siginfo_t sig;
if (waitid(P_PID, child, &sig, WEXITED) < 0)
perror("waitid");
else {
if (t->survivable) {
if (sig.si_code != CLD_EXITED) {
printf("XXX: %s: child not survived\n", t->name);
failure++;
}
} else {
if (sig.si_code != CLD_KILLED || sig.si_status != SIGBUS) {
printf("XXX: %s: child not killed by SIGBUS\n", t->name);
failure++;
}
}
}
}
}
/* early kill version */
early_kill = 1;
system("sysctl -w vm.memory_failure_early_kill=1");
sigaction(SIGBUS, &sa, NULL);
for (t = cases; t->f; t++) {
printf("---- testing %s (early kill)\n", t->name);
t->f();
}
if (failure > 0) {
printf("FAILURE -- %d cases broken!\n", failure);
return 1;
}
printf("SUCCESS\n");
return 0;
}