# -*- Autotest -*- AT_BANNER([/proc helpers]) ## -------------------- ## ## get_env_variable_ext ## ## -------------------- ## AT_TESTFUN([get_env_variable_ext], [[ #include "testsuite.h" void test_delim(char delim) { const char *test_data[][2] = { { "EMPTY", "" }, { "SATYR", "awesome" }, { "STUFF", "" }, { "OPENSOURCE", "brilliant" }, { "LIBREPORT", "great" }, { "TRICK", "" }, { "ABRT", "fabulous" }, { "SENTINEL", NULL} }; char fdname[] = "/tmp/libreprt-testsuite.XXXXXX"; int fd = mkstemp(fdname); assert(fd >= 0); printf("Temporary file: %s\n", fdname); int fddup = dup(fd); assert(fddup >= 0); FILE *f = fdopen(fddup, "w"); assert(f); for (size_t i = 0; i < ARRAY_SIZE(test_data); ++i) { if (test_data[i][1] == NULL) continue; fprintf(f, "%s=%s", test_data[i][0], test_data[i][1]); /* Do not add delimiter after the last entry */ if (i < ARRAY_SIZE(test_data) - 1) fputc(delim, f); } fclose(f); for (size_t i = ARRAY_SIZE(test_data); i != 0; --i) { lseek(fd, 0, SEEK_SET); char *value = NULL; printf("Looking for '%s'\n", test_data[i-1][0]); TS_ASSERT_FUNCTION(get_env_variable_ext(fd, delim, test_data[i-1][0], &value)); TS_ASSERT_STRING_EQ(value, test_data[i-1][1], "Environment value at 'i'"); free(value); } close(fd); } TS_MAIN { test_delim('\n'); test_delim('\0'); } TS_RETURN_MAIN ]]) ## ---------------- ## ## get_env_variable ## ## ---------------- ## AT_TESTFUN([get_env_variable], [[ #include "testsuite.h" TS_MAIN { char cwd[257]; getcwd(cwd, sizeof(cwd)); char *value = NULL; TS_ASSERT_FUNCTION(get_env_variable(getpid(), "PWD", &value)); TS_ASSERT_STRING_EQ(value, cwd, "Test environment variable - PWD"); free(value); } TS_RETURN_MAIN ]]) ## ----------- ## ## get_cmdline ## ## ----------- ## AT_TESTFUN([get_cmdline], [[ #include "testsuite.h" #include void test(const char *program, const char *args[], const char *expected) { int inout[2]; xpipe(inout); pid_t pid = fork(); if (pid < 0) { err(EXIT_FAILURE, "fork"); } if (pid == 0) { close(STDOUT_FILENO); xdup2(inout[1], STDOUT_FILENO); close(inout[0]); execv(program, (char **)args); err(EXIT_FAILURE, "exec(%s)", program); } close(inout[1]); int status = 0; if (safe_waitpid(pid, &status, 0) < 0) { err(EXIT_FAILURE, "waitpid"); } if (WEXITSTATUS(status) != 0) { errx(EXIT_FAILURE, "Child not exited with 0"); } const size_t buffer_size = strlen(expected) * 2; char cmdline[buffer_size]; const ssize_t total = full_read(inout[0], cmdline, buffer_size); close(inout[0]); if (total < 0) { err(EXIT_FAILURE, "full_read"); } cmdline[total] = '\0'; TS_ASSERT_STRING_EQ(cmdline, expected, "/proc/[pid]/cmd"); } TS_MAIN { if (argc > 1) { char *cmdline = NULL; if (strcmp(argv[0], "get_cmdline") == 0) { cmdline = get_cmdline(getpid()); } else if (strcmp(argv[0], "get_cmdline_at") == 0) { int pid_proc_fd = open("/proc/self", O_DIRECTORY); if (pid_proc_fd < 0) { err(EXIT_FAILURE, "open(/proc/self, O_DIRECTORY)"); } cmdline = get_cmdline_at(pid_proc_fd); close(pid_proc_fd); } else { errx(EXIT_FAILURE, "Unsupported function type '%s'", argv[0]); } fprintf(stdout, "%s", cmdline); fflush(stdout); exit(EXIT_SUCCESS); } char *binary = malloc_readlink("/proc/self/exe"); const char *args[] = { NULL, "!fo\" \"o", "@blah", "b\na'r", "g\rea\t", "regular", NULL }; #define EXPECTED " '!fo\\\" \\\"o' @blah 'b\\na\\'r' 'g\\rea\\t' regular" args[0] = "get_cmdline"; test(binary, args, "get_cmdline" EXPECTED); args[0] = "get_cmdline_at"; test(binary, args, "get_cmdline_at" EXPECTED); free(binary); } TS_RETURN_MAIN ]]) ## -------------- ## ## get_executable ## ## -------------- ## AT_TESTFUN([get_executable], [[ #include "testsuite.h" #include #include #define PRELINK_BASENAME "/tmp/libreport.testsuite.get_executable" # void test(const char *program, const char *expected, const char *function, const char *argv1) { int inout[2]; xpipe(inout); pid_t pid = fork(); if (pid < 0) { err(EXIT_FAILURE, "fork"); } if (pid == 0) { close(STDOUT_FILENO); xdup2(inout[1], STDOUT_FILENO); close(inout[0]); const char *args[3] = { function, argv1, NULL }; execv(program, (char **)args); err(EXIT_FAILURE, "execv(%s) : %d", program, errno); } close(inout[1]); int status = 0; if (safe_waitpid(pid, &status, 0) < 0) { err(EXIT_FAILURE, "waitpid"); } if (WEXITSTATUS(status) != 0) { errx(EXIT_FAILURE, "Child not exited with 0"); } const size_t buffer_size = strlen(expected) * 2; char executable[buffer_size]; const ssize_t total = full_read(inout[0], executable, buffer_size); close(inout[0]); if (total < 0) { err(EXIT_FAILURE, "full_read"); } executable[total] = '\0'; TS_ASSERT_STRING_EQ(executable, expected, "/proc/[pid]/exe"); } int copy_to_temporary(const char *source, char *dest) { int dest_fd = mkstemp(dest); if (dest_fd < 0) { err(EXIT_FAILURE, "mkstemp(%s)", dest); } int src_fd = open(source, O_RDONLY); if (src_fd < 0) { err(EXIT_FAILURE, "open(%s, O_RDONLY)", source); } struct stat src_stat; if (fstat(src_fd, &src_stat) < 0) { err(EXIT_FAILURE, "fstat(%s)", source); } if (sendfile(dest_fd, src_fd, NULL, src_stat.st_size) < 0) { err(EXIT_FAILURE, "splice(%s, %s, %zu)", source, dest, src_stat.st_size); } close(src_fd); fchmod(dest_fd, src_stat.st_mode); return dest_fd; } TS_MAIN { if (argc > 1) { if (strcmp(argv[1], "delete") == 0) { char *binary = malloc_readlink("/proc/self/exe"); unlink(binary); if (access(binary, R_OK) != -1 && errno != !ENOENT) { err(EXIT_FAILURE, "failed to remove %s", binary); } free(binary); } char *executable = NULL; if (strcmp(argv[0], "get_executable") == 0) { executable = get_executable(getpid()); } else if (strcmp(argv[0], "get_executable_at") == 0) { int pid_proc_fd = open("/proc/self", O_DIRECTORY); if (pid_proc_fd < 0) { err(EXIT_FAILURE, "open(/proc/self, O_DIRECTORY)"); } executable = get_executable_at(pid_proc_fd); close(pid_proc_fd); } else { errx(EXIT_FAILURE, "Unsupported function type '%s'", argv[0]); } fprintf(stdout, "%s", executable); fflush(stdout); exit(EXIT_SUCCESS); } { char *binary = malloc_readlink("/proc/self/exe"); test(binary, binary, "get_executable", "keep"); test(binary, binary, "get_executable_at", "keep"); free(binary); } { char binary[] = PRELINK_BASENAME ".#prelink#.XXXXXX"; int binary_fd = copy_to_temporary("/proc/self/exe", binary); close(binary_fd); test(binary, PRELINK_BASENAME, "get_executable", "keep"); unlink(binary); } { char binary[] = PRELINK_BASENAME ".#prelink#.XXXXXX"; int binary_fd = copy_to_temporary("/proc/self/exe", binary); close(binary_fd); test(binary, PRELINK_BASENAME, "get_executable_at", "keep"); unlink(binary); } { char binary[] = PRELINK_BASENAME ".#prelink#.XXXXXX"; int binary_fd = copy_to_temporary("/proc/self/exe", binary); close(binary_fd); test(binary, PRELINK_BASENAME, "get_executable", "delete"); if (unlink(binary) == 0) { errx(EXIT_FAILURE, "should be already removed %s", binary); } } { char binary[] = PRELINK_BASENAME ".#prelink#.XXXXXX"; int binary_fd = copy_to_temporary("/proc/self/exe", binary); close(binary_fd); test(binary, PRELINK_BASENAME, "get_executable_at", "delete"); if (unlink(binary) == 0) { errx(EXIT_FAILURE, "should be already removed %s", binary); } } { char binary[] = "/tmp/libreport.testsuite.get_executable.XXXXXX"; int binary_fd = copy_to_temporary("/proc/self/exe", binary); close(binary_fd); test(binary, binary, "get_executable", "delete"); if (unlink(binary) == 0) { errx(EXIT_FAILURE, "should be already removed %s", binary); } } { char binary[] = "/tmp/libreport.testsuite.get_executable.XXXXXX"; int binary_fd = copy_to_temporary("/proc/self/exe", binary); close(binary_fd); test(binary, binary, "get_executable_at", "delete"); if (unlink(binary) == 0) { errx(EXIT_FAILURE, "should be already removed %s", binary); } } } TS_RETURN_MAIN ]]) ## ------- ## ## get_cwd ## ## ------- ## AT_TESTFUN([get_cwd], [[ #include "testsuite.h" #include #include TS_MAIN { char wd[PATH_MAX]; getcwd(wd, sizeof(wd)); char *cwd = get_cwd(getpid()); TS_ASSERT_STRING_EQ(cwd, wd, "get_cwd(getpid())"); free(cwd); int pid_proc_fd = open("/proc/self", O_DIRECTORY | O_PATH); if (pid_proc_fd < 0) { err(EXIT_FAILURE, "open(/proc/self, O_DIRECTORY | O_PATH)"); } char *cwd_at = get_cwd_at(pid_proc_fd); TS_ASSERT_STRING_EQ(cwd_at, wd, "get_cwd_at(open(/proc/self))"); close(pid_proc_fd); free(cwd_at); } TS_RETURN_MAIN ]]) ## ----------- ## ## get_rootdir ## ## ----------- ## AT_TESTFUN([get_rootdir], [[ #include "testsuite.h" #include #include TS_MAIN { char *proc_self_root = malloc_readlink("/proc/self/root"); char *root_dir = get_rootdir(getpid()); TS_ASSERT_STRING_EQ(root_dir, proc_self_root, "get_rootdir(getpid())"); free(root_dir); int pid_proc_fd = open("/proc/self", O_DIRECTORY | O_PATH); if (pid_proc_fd < 0) { err(EXIT_FAILURE, "open(/proc/self, O_DIRECTORY | O_PATH)"); } char *root_dir_at = get_rootdir_at(pid_proc_fd); TS_ASSERT_STRING_EQ(root_dir_at, proc_self_root, "get_rootdir_at(open(/proc/self))"); close(pid_proc_fd); free(root_dir_at); } TS_RETURN_MAIN ]]) ## ------------ ## ## dump_fd_info ## ## ------------ ## AT_TESTFUN([dump_fd_info], [[ #include "testsuite.h" #include #include #define FILENAME_FORMAT "/tmp/libreport.testsuite.fdinfo.%d.%s" pid_t prepare_process(void) { int toparent[2]; xpipe(toparent); char *binary = malloc_readlink("/proc/self/exe"); pid_t pid = fork(); if (pid < 0) { err(EXIT_FAILURE, "fork"); } if (pid == 0) { close(STDOUT_FILENO); xdup2(toparent[1], STDOUT_FILENO); DIR *fddir = opendir("/proc/self/fd"); struct dirent *dent; while ((dent = readdir(fddir))) { const int fd = atoi(dent->d_name); if (fd != STDOUT_FILENO) { close(fd); } } execl(binary, "wait", NULL); exit(EXIT_FAILURE); } close(toparent[1]); free(binary); /* Wait for child */ char buf[8]; if (full_read(toparent[0], buf, 8) < 0) { fprintf(stderr, "Failed to read from child: %s\n", strerror(errno)); fflush(stderr); } close(toparent[0]); return pid; } void kill_process(pid_t pid) { /* Notify child */ kill(pid, SIGTERM); int status = 0; if (safe_waitpid(pid, &status, 0) < 0) { fprintf(stderr, "Couldn't wait for child\n"); } else if (!WIFSIGNALED(status) || WTERMSIG(status) != SIGTERM) { fprintf(stderr, "Child was not TERMinated - %d\n", status); } } void check_file_contents(const char *fdinfo_filename) { struct fd { int fd; const char *file; } fds[] = { { .fd = 0, .file = "/etc/services", }, { .fd = 2, .file = "/etc/passwd", }, { .fd = 3, .file = "/etc/group", }, }; char *file = xmalloc_xopen_read_close(fdinfo_filename, NULL); int fdno = 0; char *cursor = file; char *line = file; char *end = file + strlen(file); while (cursor < end) { cursor = strchrnul(line, '\n'); if (*cursor != '\0') { *cursor = '\0'; } ++cursor; if (fdno < (sizeof(fds)/sizeof(fds[0]))) { int fd = 0; char *file = NULL; const int res = sscanf(line, "%d:%ms", &fd, &file); TS_ASSERT_SIGNED_EQ(res, 2); TS_ASSERT_SIGNED_EQ(fd, fds[fdno].fd); TS_ASSERT_STRING_EQ(file, fds[fdno].file, "FD file name"); free(file); } line = cursor; int fieldscnt = 0; while (line < end) { cursor = strchrnul(line, '\n'); if (*cursor != '\0') { *cursor = '\0'; } ++cursor; if (strcmp(line, "") == 0) { break; } int col = 0; for (; col < strlen(line); ++col) { if (line[col] == ':') { break; } TS_ASSERT_TRUE(line[col] != ' ' && line[col] != '\t'); if (!g_testsuite_last_ok) { break; } } TS_ASSERT_SIGNED_NEQ(col, 0); TS_ASSERT_SIGNED_LT(col, strlen(line)); if (g_testsuite_last_ok) { TS_ASSERT_CHAR_EQ(line[col], ':'); } fieldscnt += g_testsuite_last_ok; line = cursor; } TS_ASSERT_SIGNED_GT(fieldscnt, 2); ++fdno; line = cursor; } TS_ASSERT_SIGNED_EQ(fdno, sizeof(fds)/sizeof(fds[0])); free(file); } TS_MAIN { if (strcmp(argv[0], "wait") == 0) { FILE *services = fopen("/etc/services", "r"); FILE *passwd = fopen("/etc/passwd", "r"); FILE *group = fopen("/etc/group", "r"); /* Notify parent */ close(STDOUT_FILENO); /* Wait for parent */ while (1) { sleep(1); } fclose(group); fclose(passwd); fclose(services); exit(EXIT_SUCCESS); } pid_t pid = prepare_process(); char proc_dir_path[strlen("/proc/%d/fd") + sizeof(pid_t) * 3]; if (sizeof(proc_dir_path) <= snprintf(proc_dir_path, sizeof(proc_dir_path), "/proc/%d/fd", pid)) { errx(EXIT_FAILURE, "too small buffer for proc dir path"); } { TS_PRINTF("%s\n", "dump_fd_info"); char fdinfo_filename[strlen(FILENAME_FORMAT) + sizeof(pid_t) * 3 + strlen("dump_fd_info")]; if (sizeof(fdinfo_filename) <= snprintf(fdinfo_filename, sizeof(fdinfo_filename), FILENAME_FORMAT, pid, "dump_fd_info")) { errx(EXIT_FAILURE, "too small buffer for file name"); } TS_ASSERT_FUNCTION(dump_fd_info(fdinfo_filename, proc_dir_path)); struct stat st; TS_ASSERT_FUNCTION(stat(fdinfo_filename, &st)); if (g_testsuite_last_ok) { TS_ASSERT_SIGNED_EQ(st.st_mode & 0777, 0600); } check_file_contents(fdinfo_filename); unlink(fdinfo_filename); } { TS_PRINTF("%s\n", "dump_fd_info_ext"); char fdinfo_filename[strlen(FILENAME_FORMAT) + sizeof(pid_t) * 3 + strlen("dump_fd_info_ext")]; if (sizeof(fdinfo_filename) <= snprintf(fdinfo_filename, sizeof(fdinfo_filename), FILENAME_FORMAT, pid, "dump_fd_info_ext")) { errx(EXIT_FAILURE, "too small buffer for file name"); } const uid_t uid = getuid(); const gid_t gid = getgid(); TS_ASSERT_FUNCTION(dump_fd_info_ext(fdinfo_filename, proc_dir_path, uid, gid)); struct stat st; TS_ASSERT_FUNCTION(stat(fdinfo_filename, &st)); if (g_testsuite_last_ok) { TS_ASSERT_SIGNED_EQ(st.st_mode & 0777, 0600); } check_file_contents(fdinfo_filename); unlink(fdinfo_filename); } { TS_PRINTF("%s\n", "dump_fd_info_at"); char fdinfo_filename[strlen(FILENAME_FORMAT) + sizeof(pid_t) * 3 + strlen("dump_fd_info_at")]; if (sizeof(fdinfo_filename) <= snprintf(fdinfo_filename, sizeof(fdinfo_filename), FILENAME_FORMAT, pid, "dump_fd_info_at")) { errx(EXIT_FAILURE, "too small buffer for file name"); } FILE *dest = fopen(fdinfo_filename, "w"); const int pid_proc_fd = open_proc_pid_dir(pid); TS_ASSERT_FUNCTION(dump_fd_info_at(pid_proc_fd, dest)); close(pid_proc_fd); fclose(dest); check_file_contents(fdinfo_filename); unlink(fdinfo_filename); } kill_process(pid); } TS_RETURN_MAIN ]]) ## ------------- ## ## get_fs-u_g-id ## ## ------------- ## AT_TESTFUN([get_fs-u_g-id], [[ #include "testsuite.h" #include #include TS_MAIN { char *proc_pid_status = xmalloc_xopen_read_close("/proc/self/status", NULL); TS_ASSERT_SIGNED_EQ(get_fsuid(proc_pid_status), getuid()); TS_ASSERT_SIGNED_EQ(get_fsgid(proc_pid_status), getgid()); free(proc_pid_status); } TS_RETURN_MAIN ]]) ## ---------- ## ## get_ns_ids ## ## ---------- ## AT_TESTFUN([get_ns_ids], [[ #include "testsuite.h" #include #include void check(struct ns_ids *ids) { const int nsfd = open("/proc/self/ns", O_DIRECTORY); assert(nsfd >= 0); for (size_t i = 0; i < ARRAY_SIZE(libreport_proc_namespaces); ++i) { struct stat st; if (fstatat(nsfd, libreport_proc_namespaces[i], &st, 0) < 0) { TS_ASSERT_SIGNED_OP_MESSAGE(ids->nsi_ids[i], ==, PROC_NS_UNSUPPORTED, libreport_proc_namespaces[i]); } else { TS_ASSERT_SIGNED_OP_MESSAGE(ids->nsi_ids[i], ==, st.st_ino, libreport_proc_namespaces[i]); } } DIR *nsdir = fdopendir(nsfd); assert(nsdir != NULL); struct dirent *dent = NULL; while ((dent = readdir(nsdir))) { if (dot_or_dotdot(dent->d_name)) continue; size_t i = 0; for (; i < ARRAY_SIZE(libreport_proc_namespaces); ++i) { if (strcmp(libreport_proc_namespaces[i], dent->d_name) == 0) { break; } } TS_ASSERT_SIGNED_OP_MESSAGE(i, <, ARRAY_SIZE(libreport_proc_namespaces), dent->d_name); if (g_testsuite_last_ok) { struct stat st; TS_ASSERT_FUNCTION(fstatat(nsfd, dent->d_name, &st, 0)); TS_ASSERT_SIGNED_OP_MESSAGE(ids->nsi_ids[i], ==, st.st_ino, dent->d_name); } } closedir(nsdir); } TS_MAIN { TS_PRINTF("%s\n", "get_ns_ids"); struct ns_ids pid_ids; TS_ASSERT_FUNCTION(get_ns_ids(getpid(), &pid_ids)); check(&pid_ids); TS_PRINTF("%s\n", "get_ns_ids_at"); const int pid_proc_dir = open_proc_pid_dir(getpid()); struct ns_ids proc_ids; TS_ASSERT_FUNCTION(get_ns_ids_at(pid_proc_dir, &proc_ids)); check(&proc_ids); close(pid_proc_dir); } TS_RETURN_MAIN ]]) ## ------------------------------ ## ## get_mountinfo_for_mount_point ## ## ------------------------------ ## AT_TESTFUN([get_mountinfo_for_mount_point], [[ #include "testsuite.h" #include void test_get_mountinfo_for_mount_point( const char *mount_point, const char *description, const char *input, const char *error, int exp_r, const struct mountinfo *exp_mntnf) { TS_PRINTF("++++ %s\n", description); char *buf = NULL; FILE *mntnf_file = fmemopen((void *)input, strlen(input), "r"); if (mntnf_file == NULL) { err(EXIT_FAILURE, "open_memstream"); } struct mountinfo mntnf; int r; TS_ASSERT_STREAM_FD_CONTENTS_EQ_BEGIN(STDERR_FILENO); r = get_mountinfo_for_mount_point(mntnf_file, &mntnf, mount_point); TS_ASSERT_STREAM_FD_CONTENTS_EQ_END(error, description); TS_ASSERT_SIGNED_OP_MESSAGE(r, ==, exp_r, description); if (r == 0 && exp_mntnf != NULL) { TS_ASSERT_STRING_EQ(mntnf.mntnf_items[0], exp_mntnf->mntnf_items[0], "Item 0"); TS_ASSERT_STRING_EQ(mntnf.mntnf_items[1], exp_mntnf->mntnf_items[1], "Item 1"); TS_ASSERT_STRING_EQ(mntnf.mntnf_items[2], exp_mntnf->mntnf_items[2], "Item 2"); TS_ASSERT_STRING_EQ(mntnf.mntnf_items[3], exp_mntnf->mntnf_items[3], "Item 3"); TS_ASSERT_STRING_EQ(mntnf.mntnf_items[4], exp_mntnf->mntnf_items[4], "Item 4"); TS_ASSERT_STRING_EQ(mntnf.mntnf_items[5], exp_mntnf->mntnf_items[5], "Item 5"); TS_ASSERT_STRING_EQ(mntnf.mntnf_items[6], exp_mntnf->mntnf_items[6], "Item 6"); TS_ASSERT_STRING_EQ(mntnf.mntnf_items[7], exp_mntnf->mntnf_items[7], "Item 7"); TS_ASSERT_STRING_EQ(mntnf.mntnf_items[8], exp_mntnf->mntnf_items[8], "Item 8"); TS_ASSERT_STRING_EQ(mntnf.mntnf_items[9], exp_mntnf->mntnf_items[9], "Item 9"); } fclose(mntnf_file); free(buf); TS_PRINTF("---- %s\n", description); } #define TS_MOUNT_INFO(f0, f1, f2, f3, f4, f5, f6, f7, f8, f9) \ ((struct mountinfo){ .mntnf_items = { (char *)f0, (char *)f1, (char *)f2, \ (char *)f3, (char *)f4, (char *)f5, \ (char *)f6, (char *)f7, (char *)f8, \ (char *)f9 } }) TS_MAIN { test_get_mountinfo_for_mount_point( /* mount point */ "/", /* description */ "Empty file", /* input */ "", /* error */ "Mountinfo line does not have enough fields 0\n", /* exp_r */ 1, /* exp_mntnf */ NULL ); test_get_mountinfo_for_mount_point( /* mount point */ "/", /* description */ "Not-enough fields", /* input */ "12 34 567:10 ", /* error */ "Mountinfo line does not have enough fields 3\n", /* exp_r */ 1, /* exp_mntnf */ NULL ); test_get_mountinfo_for_mount_point( /* mount point */ "/", /* description */ "Missing the mount point field", /* input */ "12 34 567:10 /foo ", /* error */ "Mountinfo line does not have the mount point field\n", /* exp_r */ 2, /* exp_mntnf */ NULL ); test_get_mountinfo_for_mount_point( /* mount point */ "/", /* description */ "Not found mount point", /* input */ "12 34 567:10 /foo /bar ", /* error */ "", /* exp_r */ -ENOKEY, /* exp_mntnf */ NULL ); test_get_mountinfo_for_mount_point( /* mount point */ "/", /* description */ "Unexpected end of file", /* input */ "12 34 567:10 /foo / rw,noatime", /* error */ "Unexpected end of file\n", /* exp_r */ -ENODATA, /* exp_mntnf */ NULL ); test_get_mountinfo_for_mount_point( /* mount point */ "/", /* description */ "Escaped path: no mount point field", /* input */ "12 34 567:10 /foo\\ bar ", /* error */ "Mountinfo line does not have the mount point field\n", /* exp_r */ 2, /* exp_mntnf */ NULL ); test_get_mountinfo_for_mount_point( /* mount point */ "/", /* description */ "Correct, matching line", /* input */ "12 34 567:10 /foo / rw,noatime shared:1 - xfs /dev/sda1 rw,seclabel,attr2", /* error */ "", /* exp_r */ 0, /* exp_mntnf */ &TS_MOUNT_INFO("12", "34", "567:10", "/foo", "/", "rw,noatime", "shared:1", "xfs", "/dev/sda1", "rw,seclabel,attr2") ); test_get_mountinfo_for_mount_point( /* mount point */ "/", /* description */ "Correct, matching line, empty optional fields", /* input */ "12 34 567:10 /foo / rw,noatime - xfs /dev/sda1 rw,seclabel,attr2", /* error */ "", /* exp_r */ 0, /* exp_mntnf */ &TS_MOUNT_INFO("12", "34", "567:10", "/foo", "/", "rw,noatime", "", "xfs", "/dev/sda1", "rw,seclabel,attr2") ); test_get_mountinfo_for_mount_point( /* mount point */ "/", /* description */ "Correct, matching line, several optional fields", /* input */ "12 34 567:10 /foo / rw,noatime shared:1 master:2 unbindable:3 - xfs /dev/sda1 rw,seclabel,attr2", /* error */ "", /* exp_r */ 0, /* exp_mntnf */ &TS_MOUNT_INFO("12", "34", "567:10", "/foo", "/", "rw,noatime", "shared:1 master:2 unbindable:3", "xfs", "/dev/sda1", "rw,seclabel,attr2") ); test_get_mountinfo_for_mount_point( /* mount point */ "/", /* description */ "Escaped path: correct, matching line", /* input */ "12 34 567:10 /foo\\ bar / rw,noatime shared:1 - xfs /dev/sda1 rw,seclabel,attr2", /* error */ "", /* exp_r */ 0, /* exp_mntnf */ &TS_MOUNT_INFO("12", "34", "567:10", "/foo\\ bar", "/", "rw,noatime", "shared:1", "xfs", "/dev/sda1", "rw,seclabel,attr2") ); test_get_mountinfo_for_mount_point( /* mount point */ "/", /* description */ "Escaped path: correct, matching line + escaped mount source", /* input */ "12 34 567:10 /foo\\ bar / rw,noatime shared:1 - xfs /dev/sda\\ 1 rw,seclabel,attr2", /* error */ "", /* exp_r */ 0, /* exp_mntnf */ &TS_MOUNT_INFO("12", "34", "567:10", "/foo\\ bar", "/", "rw,noatime", "shared:1", "xfs", "/dev/sda\\ 1", "rw,seclabel,attr2") ); } TS_RETURN_MAIN ]]) ## ------------------- ## ## dump_namespace_diff ## ## ------------------- ## AT_TESTFUN([dump_namespace_diff], [[ #include "testsuite.h" #include #define FILENAME_FORMAT "/tmp/libreport.testsuite.namespace_diff.%d.%s" void check_file_contents(const char *filename) { char *expected; struct stat st; char const *pid_for_children = "default"; char const *cgroup = "default"; if (stat("/proc/self/ns/cgroup", &st) < 0 && errno == ENOENT) cgroup = "unknown"; if (stat("/proc/self/ns/pid_for_children", &st) < 0 && errno == ENOENT) pid_for_children = "unknown"; expected = xasprintf("ipc : default\n" "mnt : default\n" "net : default\n" "pid : default\n" "uts : default\n" "user : default\n" "cgroup : %s\n" "pid_for_children : %s\n", cgroup, pid_for_children); char *file = xmalloc_xopen_read_close(filename, NULL); TS_ASSERT_STRING_EQ(file, expected, "Namespaces"); free(file); free(expected); } TS_MAIN { { TS_PRINTF("%s\n", "dump_namespace_diff"); char filename[strlen(FILENAME_FORMAT) + sizeof(pid_t) * 3 + strlen("dump_namespace_diff")]; if (sizeof(filename) <= snprintf(filename, sizeof(filename), FILENAME_FORMAT, getpid(), "dump_namespace_diff")) { errx(EXIT_FAILURE, "too small buffer for file name"); } TS_ASSERT_FUNCTION(dump_namespace_diff(filename, getpid(), getppid())); struct stat st; TS_ASSERT_FUNCTION(stat(filename, &st)); if (g_testsuite_last_ok) { TS_ASSERT_SIGNED_EQ(st.st_mode & 0777, 0600); } check_file_contents(filename); unlink(filename); } { TS_PRINTF("%s\n", "dump_namespace_diff_ext"); char filename[strlen(FILENAME_FORMAT) + sizeof(pid_t) * 3 + strlen("dump_namespace_diff_ext")]; if (sizeof(filename) <= snprintf(filename, sizeof(filename), FILENAME_FORMAT, getpid(), "dump_namespace_diff_ext")) { errx(EXIT_FAILURE, "too small buffer for file name"); } const uid_t uid = getuid(); const gid_t gid = getgid(); TS_ASSERT_FUNCTION(dump_namespace_diff_ext(filename, getpid(), getppid(), uid, gid)); struct stat st; TS_ASSERT_FUNCTION(stat(filename, &st)); if (g_testsuite_last_ok) { TS_ASSERT_SIGNED_EQ(st.st_mode & 0777, 0600); } check_file_contents(filename); unlink(filename); } { TS_PRINTF("%s\n", "dump_namespace_diff_at"); char filename[strlen(FILENAME_FORMAT) + sizeof(pid_t) * 3 + strlen("dump_namespace_diff_at")]; if (sizeof(filename) <= snprintf(filename, sizeof(filename), FILENAME_FORMAT, getppid(), "dump_namespace_diff_at")) { errx(EXIT_FAILURE, "too small buffer for file name"); } FILE *dest = fopen(filename, "w"); assert(dest != NULL); const int pid_proc_fd = open_proc_pid_dir(getppid()); TS_ASSERT_FUNCTION(dump_namespace_diff_at(pid_proc_fd, pid_proc_fd, dest)); close(pid_proc_fd); fclose(dest); check_file_contents(filename); unlink(filename); } } TS_RETURN_MAIN ]]) ## -------------------- ## ## process_has_own_root ## ## -------------------- ## AT_TESTFUN([process_has_own_root], [[ #include "testsuite.h" #include #include void write_cmd_output_to_fd(int fd, const char *cmd) { FILE *proc = popen(cmd, "r"); if (proc == NULL) { err(EXIT_FAILURE, "popen(%s)", cmd); } char *output = xmalloc_fgetline(proc); TS_PRINTF("%s : %s\n", cmd, output); const int retcode = pclose(proc); if (retcode == -1) { err(EXIT_FAILURE, "pclose(%s)", cmd); } if (retcode != 0) { errx(EXIT_FAILURE, "non-0 status %d of '%s'", cmd); } if (output == NULL) { errx(EXIT_FAILURE, "no output of '%s'", cmd); } full_write_str(fd, output); free(output); } TS_MAIN { char mock_pid_proc[] = "/tmp/libreport.testsuite.pid.XXXXXX"; if (mkdtemp(mock_pid_proc) == NULL) { err(EXIT_FAILURE, "mkdtemp(%s)", mock_pid_proc); } const int mock_pid_proc_fd = open(mock_pid_proc, O_DIRECTORY); if (mock_pid_proc_fd < 0) { err(EXIT_FAILURE, "open(%s, O_DIRECTORY)", mock_pid_proc); } { /* TODO: add test for open file descriptors */ const int r = process_has_own_root_at(mock_pid_proc_fd); TS_ASSERT_SIGNED_EQ(r, -ENOENT); } /* Please, notice that the mode is intentionally 0000 - no read, no write, * no execute access */ int mntnf_fd = openat(mock_pid_proc_fd, "mountinfo", O_RDWR | O_CREAT | O_EXCL, 0000); { /* TODO: add test for open file descriptors */ const int r = process_has_own_root_at(mock_pid_proc_fd); TS_ASSERT_SIGNED_EQ(r, -EACCES); } /* Make the file readable & writable */ fchmod(mntnf_fd, 0600); { /* TODO: add test for open file descriptors */ const int r = process_has_own_root_at(mock_pid_proc_fd); TS_ASSERT_SIGNED_EQ(r, -ENOKEY); } full_write_str(mntnf_fd, "36 35 98:0 /madeuproot /foo rw,noatime master:1 - ext3 /dev/myroot rw,errors=continue\n"); full_write_str(mntnf_fd, "37 38 99:0 /mnt3 /mnt4 rw,noatime master:2 - ext3 /dev/boot rw,errors=continue\n"); fsync(mntnf_fd); lseek(mntnf_fd, 0, SEEK_SET); TS_PRINTF("Made-up mountinfo created in %s\n", mock_pid_proc); { /* TODO: add test for open file descriptors */ const int r = process_has_own_root_at(mock_pid_proc_fd); TS_ASSERT_SIGNED_EQ(r, -ENOKEY); } TS_PRINTF("Going to copy /proc/1/mountinfo to %s\n", mock_pid_proc); const int pid1_mntnf_fd = open("/proc/1/mountinfo", O_RDONLY); if (pid1_mntnf_fd < 0) { err(EXIT_FAILURE, "/proc/1/mountinfo"); } TS_PRINTF("Copying /proc/1/mountinfo to %s\n", mock_pid_proc); { int r = 0; while ((r = sendfile(mntnf_fd, pid1_mntnf_fd, NULL, 65535)) > 0) ; if (r < 0) { err(EXIT_FAILURE, "Cannot copy /proc/1/mountinfo to %s", mock_pid_proc); } } close(pid1_mntnf_fd); fsync(mntnf_fd); lseek(mntnf_fd, 0, SEEK_SET); TS_PRINTF("Copied /proc/1/mountinfo to %s\n", mock_pid_proc); { /* TODO: add test for open file descriptors */ const int r = process_has_own_root_at(mock_pid_proc_fd); TS_ASSERT_SIGNED_EQ(r, 0); } /* Test different source directory. Swap / with \ in the mock mountinfo. */ fsync(mntnf_fd); lseek(mntnf_fd, 0, SEEK_SET); full_write_str(mntnf_fd, "12 34 567:89 /madeuproot / "); write_cmd_output_to_fd(mntnf_fd, "findmnt -F /proc/1/mountinfo -r -n -o VFS-OPTIONS,OPT-FIELDS -T /"); full_write_str(mntnf_fd, " - "); write_cmd_output_to_fd(mntnf_fd, "findmnt -F /proc/1/mountinfo -r -n -o FSTYPE,SOURCE,FS-OPTIONS -T /"); fsync(mntnf_fd); lseek(mntnf_fd, 0, SEEK_SET); { /* TODO: add test for open file descriptors */ const int r = process_has_own_root_at(mock_pid_proc_fd); TS_ASSERT_SIGNED_EQ(r, 1); } close(mntnf_fd); if (unlinkat(mock_pid_proc_fd, "mountinfo", 0) < 0) { perror("unlinkat(fd, mountinfo)"); } if (rmdir(mock_pid_proc) < 0) { perror("rmdir(/mock_pid_dir)"); } } TS_RETURN_MAIN ]])