| /* |
| * Test program for Linux poison memory error recovery. |
| * This program is extended from tinjpage with a multi-process model. |
| * |
| * This injects poison into various mapping cases and triggers the poison |
| * handling. Requires special injection support in the kernel. |
| * |
| * This program 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. |
| * |
| * This program 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, Haicheng Li |
| * |
| */ |
| #define _GNU_SOURCE 1 |
| #include <stdio.h> |
| #include <signal.h> |
| #include <stdlib.h> |
| #include <setjmp.h> |
| #include <errno.h> |
| #include <string.h> |
| #include <unistd.h> |
| #include <stdarg.h> |
| #include <getopt.h> |
| #include <limits.h> |
| |
| #include <sys/mman.h> |
| #include <sys/fcntl.h> |
| #include <sys/types.h> |
| #include <sys/shm.h> |
| #include <sys/sem.h> |
| #include <sys/wait.h> |
| #include <sys/stat.h> |
| |
| #define MADV_POISON 100 |
| |
| #define PAGE_SIZE 4 * 1024 |
| #define SHM_SIZE 1 // in page_size. |
| #define SHM_MODE 0600 |
| #define FILE_SIZE 1 * 1024 * 1024 * 1024 |
| #define LOG_BUFLEN 100 |
| |
| #define INSTANCE_NUM 10000 |
| |
| #define TEST_PASS 1 |
| #define TEST_FAIL 0 |
| |
| static int PS = PAGE_SIZE; |
| static int instance = 0; // index of the child process. |
| static int testid = 0; // test index of the child process. |
| static int test_types = 0; // totoal test types. |
| static int t_shm = -1; // index of shm test case. |
| static int failure = 0; // result of child process. |
| static int unexpected = 0; // result of child process. |
| static int early_kill = 0; |
| struct test { |
| int id; |
| int result; |
| }; |
| struct shm { |
| int id; |
| int ready; |
| int done; |
| }; |
| struct ipc { |
| struct test test[INSTANCE_NUM]; |
| struct shm shm; |
| }; |
| static int ipc_entry; |
| static int *shmptr = NULL; |
| |
| static pid_t g_pid[INSTANCE_NUM] = { 0 }; |
| static int shm_size = SHM_SIZE; |
| static int child_num = INSTANCE_NUM; |
| static int shm_child_num = 0; |
| static char log_file[PATH_MAX]; |
| static FILE *log_fd = NULL; |
| static char result_file[PATH_MAX]; |
| static FILE *result_fd = NULL; |
| static char tmp_dir[PATH_MAX] = { '\0' }; |
| static int clean_env = 0; |
| |
| static int semid_ready = 0; |
| |
| static pid_t mypid; |
| |
| union semun { |
| int val; |
| struct semid_ds *buf; |
| unsigned short int *array; |
| struct semid_info *__buf; |
| }; |
| |
| enum rmode { |
| MREAD = 0, |
| MWRITE = 1, |
| MREAD_OK = 2, |
| MWRITE_OK = 3, |
| MNOTHING = -1, |
| }; |
| |
| static struct option opts[] = { |
| {"clean", 0, 0, 'C'}, |
| {"help", 0, 0, 'h'}, |
| {"instance", 0, 0, 'i'}, |
| {"log", 0, 0, 'l'}, |
| {"result", 0, 0, 'r'}, |
| {"shmsize", 0, 0, 's'}, |
| {"tmpdir", 0, 0, 't'}, |
| {"", 0, 0, '\0'} |
| }; |
| |
| static void help(void) |
| { |
| printf("Usage: page-poisoning [OPTION]...\n" |
| "Stress test for Linux HWPOISON Page Recovery with multiple processes.\n" |
| "\n" |
| "Mandatory arguments to long options are mandatory for short options too.\n" |
| " -C, --clean record log and result in clean files.\n" |
| " -h print this page\n" |
| " -i, --child_num=NUM spawn NUM processes to do test (default NUM = %d)\n" |
| " -l, --log=LOG record logs to file LOG.\n" |
| " -r, --result=RESULT record test result to file RESULT.\n" |
| " -s, --shmsize=SIZE each shared memory segment is SIZE-page based.\n" |
| " -t, --tmpdir=DIR create temporary files in DIR.\n\n", |
| INSTANCE_NUM); |
| } |
| |
| static void err(const char *fmt, ...); |
| static void mylog(const char *fmt, ...) |
| { |
| char buf[LOG_BUFLEN] = { '\0' }; |
| va_list args; |
| if (!log_fd) |
| err("no log file there\n"); |
| |
| va_start(args, fmt); |
| vsprintf(buf, fmt, args); |
| printf("[pid %d] %s", mypid, buf); |
| fprintf(log_fd, "[pid %d] %s", mypid, buf); |
| fflush(log_fd); |
| va_end(args); |
| } |
| |
| static void result(const char *fmt, ...) |
| { |
| char buf[LOG_BUFLEN] = { '\0' }; |
| va_list args; |
| if (!result_fd) |
| err("no result file there\n"); |
| |
| va_start(args, fmt); |
| vsprintf(buf, fmt, args); |
| fprintf(result_fd, "[pid %d] %s", mypid, buf); |
| fflush(result_fd); |
| if (log_fd) |
| mylog("%s", buf); |
| va_end(args); |
| } |
| |
| static void err(const char *fmt, ...) |
| { |
| char buf[LOG_BUFLEN] = { '\0' }; |
| va_list args; |
| va_start(args, fmt); |
| |
| vsprintf(buf, fmt, args); |
| if (result_fd) |
| result("error: %s :%s\n", buf, strerror(errno)); |
| else |
| perror(buf); |
| va_end(args); |
| exit(1); |
| } |
| |
| static 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; |
| } |
| |
| static void munmap_reserve(void *page, int size) |
| { |
| munmap(page, size); |
| mmap(page, size, PROT_NONE, MAP_PRIVATE | MAP_FIXED, 0, 0); |
| } |
| |
| static void *xmalloc(size_t s) |
| { |
| void *p = malloc(s); |
| if (!p) |
| exit(ENOMEM); |
| return p; |
| } |
| |
| static int recovercount; |
| static sigjmp_buf recover_ctx; |
| static sigjmp_buf early_recover_ctx; |
| static void *expected_addr; |
| |
| static void sighandler(int sig, siginfo_t * si, void *arg) |
| { |
| mylog("signal %d code %d addr %p\n", sig, si->si_code, si->si_addr); |
| if (si->si_addr != expected_addr) { |
| result("failed: Unexpected address in signal %p (expected %p)\n", |
| si->si_addr, expected_addr); |
| failure++; |
| } |
| |
| if (--recovercount == 0) { |
| result("failed: 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); |
| } |
| |
| static void poison(char *msg, char *page, enum rmode mode) |
| { |
| expected_addr = page; |
| recovercount = 5; |
| |
| if (sigsetjmp(early_recover_ctx, 1) == 0) { |
| if (madvise(page, PS, MADV_POISON) != 0) { |
| if (errno == EINVAL) { |
| result("failed: Kernel doesn't support poison injection\n"); |
| exit(0); |
| } |
| err("error: madvise: %s", strerror(errno)); |
| return; |
| } |
| |
| if (early_kill && (mode == MWRITE || mode == MREAD)) { |
| result("failed: %s: process is not early killed\n", |
| msg); |
| failure++; |
| } |
| |
| return; |
| } |
| |
| if (early_kill) { |
| if (mode == MREAD_OK || mode == MWRITE_OK) { |
| result("failed: %s: killed\n", msg); |
| failure++; |
| } else |
| mylog("pass: recovered\n"); |
| } |
| } |
| |
| static void recover(char *msg, char *page, enum rmode mode) |
| { |
| expected_addr = page; |
| recovercount = 5; |
| |
| if (sigsetjmp(recover_ctx, 1) == 0) { |
| switch (mode) { |
| case MWRITE: |
| mylog("writing 2\n"); |
| *page = 2; |
| break; |
| case MWRITE_OK: |
| mylog("writing 4\n"); |
| *page = 4; |
| return; |
| case MREAD: |
| mylog("reading %x\n", *(unsigned char *)page); |
| break; |
| case MREAD_OK: |
| mylog("reading %x\n", *(unsigned char *)page); |
| return; |
| case MNOTHING: |
| return; |
| } |
| /* signal or kill should have happened */ |
| result("failed: %s: page is not poisoned after injection\n", msg); |
| failure++; |
| return; |
| } |
| if (mode == MREAD_OK || mode == MWRITE_OK) { |
| result("failed: %s: killed\n", msg); |
| failure++; |
| } else |
| mylog("pass: recovered\n"); |
| } |
| |
| static void testmem(char *msg, char *page, enum rmode mode) |
| { |
| mylog("%s poisoning page %p\n", msg, page); |
| poison(msg, page, mode); |
| recover(msg, page, mode); |
| } |
| |
| static void expecterr(char *msg, int err) |
| { |
| if (err) { |
| mylog("pass: expected error %d on %s\n", errno, msg); |
| } else { |
| result("failed: unexpected no error on %s\n", msg); |
| failure++; |
| } |
| } |
| |
| /* |
| * Any optional error is really a deficiency in the kernel VFS error reporting |
| * and should be eventually fixed and turned into a expecterr |
| */ |
| static void optionalerr(char *msg, int err) |
| { |
| if (err) { |
| mylog("pass: expected error %d on %s\n", errno, msg); |
| } else { |
| mylog("LATER: expected likely incorrect no error on %s\n", msg); |
| unexpected++; |
| } |
| } |
| |
| static int playfile(char *buf) |
| { |
| int fd; |
| if (buf[0] == 0) |
| snprintf(buf, PATH_MAX, "%s/dirty%d", tmp_dir, mypid); |
| fd = open(buf, O_CREAT | O_RDWR | O_TRUNC, 0600); |
| if (fd < 0) |
| err("opening temporary file: %s", buf); |
| |
| 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); |
| free(tmp); |
| return fd; |
| } |
| |
| static void dirty_anonymous(void) |
| { |
| struct ipc *ipc; |
| char *page; |
| |
| if ((ipc = shmat(ipc_entry, 0, 0)) == (void *)-1) |
| err("shmat error\n"); |
| ipc->test[instance].id = testid; |
| page = checked_mmap(NULL, PS, PROT_READ | PROT_WRITE, |
| MAP_PRIVATE | MAP_ANONYMOUS | MAP_POPULATE, 0, 0); |
| testmem("dirty", page, MWRITE); |
| if (!failure) |
| ipc->test[instance].result = TEST_PASS; |
| shmdt(ipc); |
| } |
| |
| static void dirty_anonymous_unmap(void) |
| { |
| struct ipc *ipc; |
| char *page; |
| |
| if ((ipc = shmat(ipc_entry, 0, 0)) == (void *)-1) |
| err("shmat error\n"); |
| ipc->test[instance].id = testid; |
| 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); |
| if (!failure) |
| ipc->test[instance].result = TEST_PASS; |
| shmdt(ipc); |
| } |
| |
| static void mlocked_anonymous(void) |
| { |
| struct ipc *ipc; |
| char *page; |
| |
| if ((ipc = shmat(ipc_entry, 0, 0)) == (void *)-1) |
| err("shmat error\n"); |
| ipc->test[instance].id = testid; |
| page = checked_mmap(NULL, PS, PROT_READ | PROT_WRITE, |
| MAP_PRIVATE | MAP_ANONYMOUS | MAP_LOCKED, 0, 0); |
| testmem("mlocked", page, MWRITE); |
| if (!failure) |
| ipc->test[instance].result = TEST_PASS; |
| shmdt(ipc); |
| } |
| |
| static void do_file_clean(int flags, char *name) |
| { |
| char *page; |
| char fn[PATH_MAX]; |
| snprintf(fn, PATH_MAX, "%s/clean%d", tmp_dir, mypid); |
| int fd = open(fn, O_RDWR | O_TRUNC | O_CREAT, 0600); |
| if (fd < 0) |
| err("opening temporary file: %s", fn); |
| 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 */ |
| mylog("reading %x\n", *(unsigned char *)page); |
| testmem(name, page, MWRITE_OK); |
| } |
| |
| static void file_clean(void) |
| { |
| struct ipc *ipc; |
| |
| if ((ipc = shmat(ipc_entry, 0, 0)) == (void *)-1) |
| err("shmat error\n"); |
| ipc->test[instance].id = testid; |
| do_file_clean(0, "file clean"); |
| if (!failure) |
| ipc->test[instance].result = TEST_PASS; |
| shmdt(ipc); |
| } |
| |
| static void file_clean_mlocked(void) |
| { |
| struct ipc *ipc; |
| |
| if ((ipc = shmat(ipc_entry, 0, 0)) == (void *)-1) |
| err("shmat error\n"); |
| ipc->test[instance].id = testid; |
| do_file_clean(MAP_LOCKED, "file clean mlocked"); |
| if (!failure) |
| ipc->test[instance].result = TEST_PASS; |
| shmdt(ipc); |
| } |
| |
| 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[PATH_MAX]; |
| 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) |
| { |
| struct ipc *ipc; |
| |
| if ((ipc = shmat(ipc_entry, 0, 0)) == (void *)-1) |
| err("shmat error\n"); |
| ipc->test[instance].id = testid; |
| do_file_dirty(0, "file dirty"); |
| if (!failure) |
| ipc->test[instance].result = TEST_PASS; |
| shmdt(ipc); |
| } |
| |
| static void file_dirty_mlocked(void) |
| { |
| struct ipc *ipc; |
| |
| if ((ipc = shmat(ipc_entry, 0, 0)) == (void *)-1) |
| err("shmat error\n"); |
| ipc->test[instance].id = testid; |
| do_file_dirty(MAP_LOCKED, "file dirty mlocked"); |
| if (!failure) |
| ipc->test[instance].result = TEST_PASS; |
| shmdt(ipc); |
| } |
| |
| static void request_sem(int id, int num) |
| { |
| struct sembuf sb; |
| |
| sb.sem_num = num; |
| sb.sem_op = -1; |
| sb.sem_flg = 0; |
| |
| semop(id, &sb, 1); |
| } |
| |
| static void waiton_sem(int id, int num) |
| { |
| struct sembuf sb; |
| |
| sb.sem_num = num; |
| sb.sem_flg = 0; |
| |
| sb.sem_op = -1; |
| semop(id, &sb, 1); |
| sb.sem_op = 0; |
| semop(id, &sb, 1); |
| } |
| |
| static void release_sem(int id, int num) |
| { |
| struct sembuf sb; |
| |
| sb.sem_num = num; |
| sb.sem_op = 1; |
| sb.sem_flg = 0; |
| |
| semop(id, &sb, 1); |
| } |
| |
| static void clean_anonymous(void) |
| { |
| char *page; |
| page = checked_mmap(NULL, PS, PROT_READ | PROT_WRITE, |
| MAP_PRIVATE | MAP_ANONYMOUS, 0, 0); |
| testmem("clean", page, MWRITE_OK); |
| } |
| |
| static void anon_clean(void) |
| { |
| struct ipc *ipc; |
| |
| if ((ipc = shmat(ipc_entry, 0, 0)) == (void *)-1) |
| err("shmat error\n"); |
| ipc->test[instance].id = testid; |
| clean_anonymous(); |
| if (!failure) |
| ipc->test[instance].result = TEST_PASS; |
| shmdt(ipc); |
| } |
| |
| /* TBD |
| static void survival(void) |
| { |
| struct ipc *ipc; |
| char page; |
| |
| if ((ipc = shmat(ipc_entry, 0, 0)) == (void *)-1) |
| err("shmat error\n"); |
| ipc->test[instance].id = testid; |
| testmem("survial", &page, MNOTHING); |
| if (!failure) |
| ipc->test[instance].result = TEST_PASS; |
| shmdt(ipc); |
| } |
| */ |
| |
| static void shm_test(void) |
| { |
| struct ipc *ipc; |
| |
| if ((ipc = shmat(ipc_entry, 0, 0)) == (void *)-1) |
| err("shmat error\n"); |
| ipc->test[instance].id = testid; |
| |
| request_sem(semid_ready, 0); |
| if (!ipc->shm.ready) { |
| if ((ipc->shm.id = shmget(IPC_PRIVATE, shm_size * PS, |
| SHM_MODE)) < 0) |
| err("shmget error\n"); |
| ipc->shm.ready = 1; |
| } |
| if ((shmptr = shmat(ipc->shm.id, 0, 0)) == (void *)-1) { |
| err("shmat error\n"); |
| } else |
| *shmptr = mypid; |
| release_sem(semid_ready, 0); |
| |
| waiton_sem(semid_ready, 1); |
| |
| request_sem(semid_ready, 0); |
| if (!ipc->shm.done) { |
| ipc->shm.done = 1; |
| testmem("shm dirty", (char *)shmptr, MWRITE); |
| } else |
| recover("shm dirty", (char *)shmptr, MREAD); |
| release_sem(semid_ready, 0); |
| |
| if (!failure) |
| ipc->test[instance].result = TEST_PASS; |
| shmdt(shmptr); |
| shmdt(ipc); |
| } |
| |
| static void setup_ipc(void) |
| { |
| int size; |
| union semun sunion; |
| struct ipc *ipc; |
| |
| size = sizeof(struct ipc); |
| |
| if ((ipc_entry = shmget(IPC_PRIVATE, size, SHM_MODE)) < 0) |
| err("shmget error\n"); |
| if ((ipc = shmat(ipc_entry, 0, 0)) == (void *)-1) |
| err("shmat error\n"); |
| memset(ipc, 0, sizeof(struct ipc)); |
| ipc->shm.id = -1; |
| shmdt(ipc); |
| |
| semid_ready = semget(IPC_PRIVATE, 2, SHM_R | SHM_W); |
| sunion.val = 1; |
| semctl(semid_ready, 0, SETVAL, sunion); |
| if (t_shm != -1) { |
| if (((child_num - 1) % test_types) >= t_shm) |
| shm_child_num = (child_num - 1) / test_types + 1; |
| else |
| shm_child_num = (child_num - 1) / test_types; |
| } |
| if (shm_child_num) { |
| sunion.val = shm_child_num; |
| semctl(semid_ready, 1, SETVAL, sunion); |
| mylog("there are %d shm_child\n", shm_child_num); |
| } |
| } |
| |
| static void free_ipc(void) |
| { |
| struct ipc *ipc; |
| |
| semctl(semid_ready, 0, IPC_RMID); |
| if ((ipc = shmat(ipc_entry, 0, 0)) == (void *)-1) |
| err("shmat error\n"); |
| if (ipc->shm.id != -1) |
| shmctl(ipc->shm.id, IPC_RMID, 0); |
| shmdt(ipc); |
| shmctl(ipc_entry, IPC_RMID, 0); |
| } |
| |
| static void cleanup(void) |
| { |
| int i; |
| for (i = 0; i < instance; i++) |
| kill(g_pid[i], 9); //kill the suviving child. |
| free_ipc(); |
| } |
| |
| struct testcase { |
| void (*f) (void); |
| char *name; |
| int survivable; |
| } cases[] = { |
| { |
| shm_test, "shared memory test", 0}, { |
| anon_clean, "anonymous clean", 1}, { |
| dirty_anonymous, "anonymous dirty", 0}, { |
| dirty_anonymous_unmap, "anonymous dirty unmap", 0}, { |
| mlocked_anonymous, "anonymous dirty mlocked", 0}, { |
| file_clean, "file clean", 1}, { |
| file_dirty, "file dirty", 0}, { |
| file_clean_mlocked, "file clean mlocked", 1}, { |
| file_dirty_mlocked, "file dirty mlocked", 0}, |
| // { survival, "survival", 0 }, |
| { |
| NULL, NULL, 0} |
| }; |
| |
| static int run_test(int children) |
| { |
| pid_t pid = -1; |
| int i = 0, rc = 0; |
| siginfo_t sig; |
| struct ipc *ipc; |
| |
| for (i = 0; i < children; i++) { |
| pid = fork(); |
| if (pid < 0) { |
| err("fork %d\n", i); |
| break; |
| } else if (pid == 0) { |
| int j = instance % test_types; |
| mypid = getpid(); |
| testid = j; |
| cases[j].f(); |
| exit(0); |
| } else { |
| g_pid[i] = pid; |
| ++instance; |
| fflush(stdout); |
| } |
| } |
| |
| mylog("have spawned %d processes\n", instance); |
| if (instance) { |
| if ((ipc = shmat(ipc_entry, 0, 0)) == (void *)-1) |
| err("shmat error\n"); |
| |
| for (i = 0; i < instance; i++) { |
| int t = ipc->test[i].id; |
| |
| mylog("wait for Pid %d\n", g_pid[i]); |
| waitid(P_PID, g_pid[i], &sig, WEXITED); |
| if (ipc->test[i].result == TEST_PASS) |
| result("Ins %d: Pid %d: pass - %s\n", i, |
| g_pid[i], cases[t].name); |
| else { |
| result("Ins %d: Pid %d: failed - %s\n", i, |
| g_pid[i], cases[t].name); |
| failure++; |
| } |
| } |
| shmdt(ipc); |
| } |
| |
| if (!failure) |
| result("\t!!! Page Poisoning Test got PASS. !!!\n\n"); |
| else { |
| result("\t!!! Page Poisoning Test is FAILED (%d failures found). !!!\n\n", |
| failure); |
| rc = 1; |
| } |
| |
| return rc; |
| } |
| |
| static void setup_log(void) |
| { |
| int rc = 0; |
| if (clean_env) |
| log_fd = fopen(log_file, "w"); |
| else |
| log_fd = fopen(log_file, "a"); |
| if (!log_fd) |
| err("cannot open log file: %s\n", log_file); |
| |
| if (clean_env) |
| result_fd = fopen(result_file, "w"); |
| else |
| result_fd = fopen(result_file, "a"); |
| if (!result_fd) |
| err("cannot open log file: %s\n", result_file); |
| |
| if (tmp_dir[0] != '\0') { |
| rc = mkdir(tmp_dir, 0777); |
| if (rc && errno != EEXIST) |
| err("cannot create tmp dir: %s: %s\n", tmp_dir, |
| strerror(errno)); |
| } |
| } |
| |
| static void free_log(void) |
| { |
| fclose(log_fd); |
| fclose(result_fd); |
| } |
| |
| static void main_sighandler(int sig, siginfo_t * si, void *arg) |
| { |
| mylog("receive signal to get terminated\n"); |
| cleanup(); |
| exit(1); |
| } |
| |
| static void setup_sig(void) |
| { |
| struct sigaction sa = { |
| .sa_sigaction = main_sighandler, |
| .sa_flags = SA_SIGINFO |
| }; |
| struct sigaction sa_bus = { |
| .sa_sigaction = sighandler, |
| .sa_flags = SA_SIGINFO |
| }; |
| |
| sigaction(SIGINT, &sa, NULL); |
| sigaction(SIGKILL, &sa, NULL); |
| sigaction(SIGTERM, &sa, NULL); |
| sigaction(SIGBUS, &sa_bus, NULL); |
| } |
| |
| static void setup_test(void) |
| { |
| struct testcase *t; |
| /* catch signals */ |
| for (t = cases; t->f; t++) |
| if (t->f == shm_test) |
| t_shm = (t - cases); |
| test_types = t - cases; |
| } |
| |
| int main(int argc, char **argv) |
| { |
| int rc = 0, c, opt_index; |
| |
| snprintf(log_file, sizeof(log_file), "page-poisoning.log"); |
| snprintf(result_file, sizeof(result_file), "page-poisoning.result"); |
| snprintf(tmp_dir, sizeof(tmp_dir), "./tmp"); |
| while (1) { |
| c = getopt_long(argc, argv, "Chi:l:r:s:t:", opts, &opt_index); |
| if (c == -1) |
| break; |
| switch (c) { |
| case 'C': |
| clean_env = 1; |
| break; |
| case 'h': |
| help(); |
| return 0; |
| case 'i': |
| child_num = strtol(optarg, NULL, 0); |
| if (child_num > INSTANCE_NUM) |
| child_num = INSTANCE_NUM; |
| break; |
| case 'l': |
| snprintf(log_file, sizeof(log_file), "%s", optarg); |
| break; |
| case 'r': |
| snprintf(result_file, sizeof(result_file), "%s", |
| optarg); |
| break; |
| case 's': |
| shm_size = strtol(optarg, NULL, 0); |
| if (shm_size < SHM_SIZE) |
| shm_size = SHM_SIZE; |
| break; |
| case 't': |
| snprintf(tmp_dir, sizeof(tmp_dir), "%s", optarg); |
| break; |
| default: |
| help(); |
| return 0; |
| } |
| } |
| |
| if (!early_kill) |
| system("sysctl -w vm.memory_failure_early_kill=0"); |
| mypid = getpid(); |
| setup_log(); |
| setup_test(); |
| if (!child_num) { |
| mylog("end without test executed since child_num = 0\n"); |
| return rc; |
| } |
| |
| mylog("start page-poisoning test\n"); |
| PS = getpagesize(); |
| setup_ipc(); |
| setup_sig(); |
| rc = run_test(child_num); |
| free_ipc(); |
| mylog("page-poisoning test done!\n"); |
| free_log(); |
| |
| return rc; |
| } |