# -*- 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 <err.h>
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 <sys/sendfile.h>
#include <err.h>
#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 <sys/sendfile.h>
#include <err.h>
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 <sys/sendfile.h>
#include <err.h>
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 <sys/sendfile.h>
#include <err.h>
#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 <sys/sendfile.h>
#include <err.h>
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 <sys/sendfile.h>
#include <err.h>
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 <err.h>
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 <err.h>
#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 <sys/sendfile.h>
#include <err.h>
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
]])