Blob Blame History Raw
# -*- Autotest -*-

AT_BANNER([dump_dir])

## --------- ##
## dd_sanity ##
## --------- ##

AT_TESTFUN([dd_sanity],
[[
#include "testsuite.h"

void validate_dump_dir_contents(struct dump_dir *dd)
{
    int items = 0;
    assert(dd_exist(dd, FILENAME_TIME));
    ++items;

    assert(dd_exist(dd, FILENAME_KERNEL));
    ++items;

    assert(dd_exist(dd, FILENAME_HOSTNAME));
    ++items;

    assert(dd_exist(dd, FILENAME_ARCHITECTURE));
    ++items;

    assert(dd_exist(dd, FILENAME_OS_INFO));
    ++items;

    assert(dd_exist(dd, FILENAME_OS_RELEASE));
    ++items;

    assert(dd_exist(dd, FILENAME_OS_RELEASE));
    ++items;

    assert(dd_exist(dd, FILENAME_TYPE));
    ++items;

    assert(dd_exist(dd, FILENAME_LAST_OCCURRENCE));
    ++items;

    assert(dd_exist(dd, "at_test_text"));
    assert(dd_get_item_size(dd, "at_test_text") == 3);
    ++items;

    assert(dd_exist(dd, "at_test_binary"));
    assert(dd_get_item_size(dd, "at_test_binary") == 4);
    ++items;

    struct stat srv_buf;
    stat("/etc/services", &srv_buf);

    assert(dd_exist(dd, "at_test_services"));
    assert(dd_get_item_size(dd, "at_test_services") == srv_buf.st_size);
    ++items;

    struct stat grp_buf;
    stat("/etc/group", &grp_buf);
    assert(dd_exist(dd, "at_test_group"));
    assert(dd_get_item_size(dd, "at_test_group") == grp_buf.st_size);
    ++items;

    struct stat pwd_buf;
    stat("/etc/passwd", &pwd_buf);
    assert(dd_exist(dd, "at_test_passwd"));
    assert(dd_get_item_size(dd, "at_test_passwd") == pwd_buf.st_size);
    ++items;

    dd_save_text(dd, "at_test_to_delete", "deleted");
    assert(dd_exist(dd, "at_test_to_delete"));
    dd_delete_item(dd, "at_test_to_delete");
    assert(!dd_exist(dd, "at_test_to_delete"));

    DIR *d1 = dd_init_next_file(dd);
    assert(d1 != NULL);

    int items_counter = 0;
    char *short_name, *full_name;
    while (dd_get_next_file(dd, &short_name, &full_name))
    {
        ++items_counter;

        TS_ASSERT_PTR_IS_NOT_NULL(short_name);
        TS_ASSERT_PTR_IS_NOT_NULL(full_name);
        TS_ASSERT_STRING_EQ(short_name, (strrchr(full_name, '/') + 1), NULL);
        TS_ASSERT_STRING_BEGINS_WITH(full_name, dd->dd_dirname, NULL);
        TS_ASSERT_CHAR_EQ_MESSAGE(full_name[strlen(dd->dd_dirname)], '/', full_name);
    }

    TS_ASSERT_SIGNED_EQ(items, items_counter);
    TS_ASSERT_SIGNED_EQ(dd_get_items_count(dd), items);
    TS_ASSERT_PTR_IS_NULL(dd->next_dir);
    TS_ASSERT_SIGNED_EQ(dd_get_next_file(dd, NULL, NULL), 0);

    DIR *iterator_second_run = dd_init_next_file(dd);
    TS_ASSERT_PTR_IS_NOT_NULL(iterator_second_run);

    while (dd_get_next_file(dd, &short_name, &full_name))
        --items_counter;

    TS_ASSERT_SIGNED_OP_MESSAGE(items_counter, ==, 0, "Second run iterator goes through all items");

    DIR *iterator_third_run = dd_init_next_file(dd);
    TS_ASSERT_PTR_IS_NOT_NULL(iterator_third_run);
    TS_ASSERT_PTR_IS_NOT_NULL(dd->next_dir);
    dd_clear_next_file(dd);
    TS_ASSERT_PTR_IS_NULL(dd->next_dir);
    TS_ASSERT_SIGNED_OP_MESSAGE(dd_get_next_file(dd, NULL, NULL), ==, 0, "dd_clear_next_file(dd) stops iteration");
}

TS_MAIN
{
    char template[] = "/tmp/XXXXXX/dump_dir";

    char *last_slash = strrchr(template, '/');
    *last_slash = '\0';

    if (mkdtemp(template) == NULL) {
        perror("mkdtemp()");
        return EXIT_FAILURE;
    }

    *last_slash = '/';

    printf("Dump dir path: %s\n", template);

    fprintf(stderr, "Create new dump directory\n");
    struct dump_dir *dd = dd_create(template, (uid_t)-1, 0640);
    assert(dd != NULL || !"Cannot create new dump directory");

    dd_create_basic_files(dd, geteuid(), NULL);
    dd_save_text(dd, FILENAME_TYPE, "attest");

    dd_save_text(dd, "at_test_text", "foo");
    assert(dd_exist(dd, "at_test_text"));

    dd_save_binary(dd, "at_test_binary", "blah", 4);
    assert(dd_exist(dd, "at_test_binary"));

    dd_copy_file(dd, "at_test_services", "/etc/services");

    const int etc_dir_fd = open("/etc", O_DIRECTORY | O_PATH | O_CLOEXEC | O_EXCL);
    assert(etc_dir_fd >= 0);
    dd_copy_file_at(dd, "at_test_group", etc_dir_fd, "group");
    close(etc_dir_fd);

    int passwd_fd = open("/etc/passwd", O_RDONLY);
    assert(passwd_fd >= 0);
    dd_copy_fd(dd, "at_test_passwd", passwd_fd, 0, 0);
    close(passwd_fd);

    fprintf(stderr, "Test newly created dump directory\n");
    validate_dump_dir_contents(dd);
    dd_close(dd);


    fprintf(stderr, "Test opened dump directory\n");
    dd = dd_opendir(template, /*for writing*/0);
    assert(dd != NULL || !"Cannot open the dump directory");
    validate_dump_dir_contents(dd);
    dd_close(dd);


    fprintf(stderr, "Test renamed dump directory\n");
    dd = dd_opendir(template, /*for writing*/0);
    assert(dd != NULL || !"Cannot open the dump directory second time");

    *(last_slash+1) = 'X';
    assert(dd_rename(dd, template) == 0 || !"Cannot rename the dump directory");

    validate_dump_dir_contents(dd);
    dd_close(dd);


    fprintf(stderr, "Test opened renamed dump directory\n");
    assert(dd != NULL || !"Cannot open the renamed dump directory");
    dd = dd_opendir(template, /*for writing*/0);
    validate_dump_dir_contents(dd);

    assert(dd_delete(dd) == 0);

    *last_slash = '\0';
    assert(rmdir(template) == 0);
}
TS_RETURN_MAIN
]])

## --------------------- ##
## dd_create_open_delete ##
## --------------------- ##

AT_TESTFUN([dd_create_open_delete],
[[
#include "internal_libreport.h"
#include <errno.h>
#include <assert.h>

int main(int argc, char **argv)
{
    g_verbose = 3;

    char template[] = "/tmp/XXXXXX/dump_dir";

    char *last_slash = strrchr(template, '/');
    *last_slash = '\0';

    if (mkdtemp(template) == NULL) {
        perror("mkdtemp()");
        return EXIT_FAILURE;
    }

    *last_slash = '/';

    struct dump_dir *dd = dd_create(template, (uid_t)-1, 0640);
    assert(strcmp(dd->dd_dirname, template) == 0);
    assert(dd->dd_fd >= 0);
    assert(dd->dd_md_fd >= 0);

    struct stat dd_st;
    assert(fstat(dd->dd_fd, &dd_st) == 0);

    struct stat md_st;
    assert(fstat(dd->dd_md_fd, &md_st) == 0);

    assert(dd_st.st_uid == md_st.st_uid);
    assert(dd_st.st_gid == md_st.st_gid);
    assert((dd_st.st_mode & 0666) == (md_st.st_mode & 0666));

    struct stat path_md_st;
    assert(fstatat(dd->dd_fd, ".libreport", &path_md_st, 0) == 0);
    assert(md_st.st_ino = path_md_st.st_ino);

    struct stat owner_md_st;
    assert(fstatat(dd->dd_md_fd, "owner", &owner_md_st, 0) == 0);
    assert((dd_st.st_mode & 0666) == (owner_md_st.st_mode & 0666));
    assert(geteuid() == dd_get_owner(dd));

    dd_create_basic_files(dd, (uid_t)-1, NULL);
    dd_save_text(dd, FILENAME_TYPE, "attest");

    dd_close(dd);
    dd = NULL;

    dd = dd_opendir(template, 0);
    assert(dd != NULL);
    assert(strcmp(dd->dd_dirname, template) == 0);
    assert(dd->dd_fd >= 0);
    assert(dd->dd_md_fd < 0);

    dd_delete(dd);

    assert(stat(template, &dd_st) != 0);

    *last_slash = '\0';
    assert(rmdir(template) == 0);
    return EXIT_SUCCESS;
}
]])

## -------------------------- ##
## dd_sanitize_mode_and_owner ##
## -------------------------- ##

AT_TESTFUN([dd_sanitize_mode_and_owner],
[[
#include "internal_libreport.h"
#include <errno.h>
#include <assert.h>

int main(int argc, char **argv)
{
    g_verbose = 3;

    char template[] = "/tmp/XXXXXX/dump_dir";

    char *last_slash = strrchr(template, '/');
    *last_slash = '\0';

    if (mkdtemp(template) == NULL) {
        perror("mkdtemp()");
        return EXIT_FAILURE;
    }

    *last_slash = '/';

    /* Prepare a directory for chmod test, use mode 0600 and chmod it to 0640 */
    struct dump_dir *dd = dd_create(template, (uid_t)-1, 0600);
    assert(dd != NULL);

    {
        struct stat path_md_st;
        assert((fstatat(dd->dd_fd, ".libreport", &path_md_st, 0) == 0) || !"Failed initialize meta-data");
        assert((path_md_st.st_mode & 0077) == 0);
    }

    dd_create_basic_files(dd, (uid_t)-1, NULL);
    dd_save_text(dd, FILENAME_TYPE, "attest");

    dd_close(dd);

    /* initialize meta-data */
    dd = dd_opendir(template, DD_OPEN_FD_ONLY);
    /* reopen for writing */
    dd = dd_fdopendir(dd, /*for writing*/0);
    assert(dd != NULL);

    assert(fchmod(dd->dd_fd, 0750) == 0);
    dd->mode = 0640;

    fprintf(stderr, "Going to sanitize\n");
    dd_sanitize_mode_and_owner(dd);
    fprintf(stderr, "Sanitized\n");

    {
        DIR *d = opendir(template);
        struct dirent *dent;
        while ((dent = readdir(d)) != NULL)
        {
            if (   strcmp(".", dent->d_name) == 0
                || strcmp("..", dent->d_name) == 0
                || strcmp(".lock", dent->d_name) == 0
                || strcmp(".libreport", dent->d_name) == 0
                )
                continue;

            struct stat sb;
            printf("Testing element: %s\n", dent->d_name);
            assert(fstatat(dd->dd_fd, dent->d_name, &sb, 0) == 0 || !"Cannot stat a regular element");
            assert((sb.st_mode & 0777) == 0640 || !"Failed to chmod a regular element");
        }
        closedir(d);
    }

    {
        struct stat path_md_st;
        assert(fstatat(dd->dd_fd, ".libreport", &path_md_st, 0) == 0 || !"Cannot stat meta-data directory");
        assert((path_md_st.st_mode & 0777) == 0750 || !"Failed chmod meta-data");
    }

    int md_dir_fd = openat(dd->dd_fd, ".libreport", O_DIRECTORY);
    assert(md_dir_fd >= 0 || !"Cannot open meta-data directory");
    DIR *d = fdopendir(md_dir_fd);
    struct dirent *dent;
    while ((dent = readdir(d)) != NULL)
    {
        if (strcmp(".", dent->d_name) == 0 || strcmp("..", dent->d_name) == 0)
            continue;

        struct stat sb;
        printf("Testing meta-data: %s\n", dent->d_name);
        assert(fstatat(md_dir_fd, dent->d_name, &sb, 0) == 0 || !"Cannot stat meta-data file");
        assert((sb.st_mode & 0777) == 0640 || !"Failed to chmod a meta-data file");
    }

    closedir(d);
    dd_delete(dd);

    *last_slash = '\0';
    assert(rmdir(template) == 0);
    return EXIT_SUCCESS;
}
]])

## -------- ##
## dd_owner ##
## -------- ##

AT_TESTFUN([dd_owner],
[[
#include "internal_libreport.h"
#include <errno.h>
#include <assert.h>

int main(int argc, char **argv)
{
    g_verbose = 3;

    char template[] = "/tmp/XXXXXX/dump_dir";

    char *last_slash = strrchr(template, '/');
    *last_slash = '\0';

    if (mkdtemp(template) == NULL) {
        perror("mkdtemp()");
        return EXIT_FAILURE;
    }

    *last_slash = '/';

    printf("Dump dir path: %s\n", template);

    {
        fprintf(stderr, "TEST === default owner\n");
        struct dump_dir *dd = dd_create(template, (uid_t)-1, 0640);
        assert(dd != NULL);

        assert(geteuid() == dd_get_owner(dd));

        assert(dd_delete(dd) == 0);
    }

    {
        fprintf(stderr, "TEST === create basic files w/o UID\n");
        struct dump_dir *dd = dd_create(template, (uid_t)-1, 0640);
        assert(dd != NULL);

        dd_create_basic_files(dd, (uid_t)-1, NULL);
        assert(geteuid() == dd_get_owner(dd));

        assert(dd_delete(dd) == 0);
    }

    {
        fprintf(stderr, "TEST === create basic files with UID\n");
        struct dump_dir *dd = dd_create(template, (uid_t)-1, 0640);
        assert(dd != NULL);

        dd_create_basic_files(dd, (geteuid() + 1), NULL);
        assert((geteuid() + 1) == dd_get_owner(dd));

        assert(dd_delete(dd) == 0);
    }

    {
        fprintf(stderr, "TEST === set no owner\n");
        struct dump_dir *dd = dd_create(template, (uid_t)-1, 0640);
        assert(dd != NULL);

        dd_set_no_owner(dd);

        struct passwd *nobody_pw= getpwnam("nobody");
        assert(nobody_pw != NULL);
        assert(nobody_pw->pw_uid == dd_get_owner(dd));

        assert(dd_delete(dd) == 0);
    }

    {
        fprintf(stderr, "TEST === set artibrary owner\n");
        struct dump_dir *dd = dd_create(template, (uid_t)-1, 0640);
        assert(dd != NULL);

        dd_set_owner(dd, (geteuid() + 2));
        assert((geteuid() + 2) == dd_get_owner(dd));

        assert(dd_delete(dd) == 0);
    }

    {
        fprintf(stderr, "TEST === chown no owner\n");
        struct dump_dir *dd = dd_create(template, (uid_t)-1, 0640);
        assert(dd != NULL);

        dd_set_no_owner(dd);
        assert(geteuid() != dd_get_owner(dd));

        dd_chown(dd, geteuid());
        assert(geteuid() == dd_get_owner(dd));

        assert(dd_delete(dd) == 0);
    }
    *last_slash = '\0';
    assert(rmdir(template) == 0);

    return EXIT_SUCCESS;
}
]])

## ------- ##
## dd_stat ##
## ------- ##

AT_TESTFUN([dd_stat],
[[
#include "internal_libreport.h"
#include <errno.h>
#include <assert.h>

int main(int argc, char **argv)
{
    g_verbose = 3;

    char template[] = "/tmp/XXXXXX/dump_dir";

    char *last_slash = strrchr(template, '/');
    *last_slash = '\0';

    if (mkdtemp(template) == NULL) {
        perror("mkdtemp()");
        return EXIT_FAILURE;
    }

    *last_slash = '/';

    printf("Dump dir path: %s\n", template);

    dd_g_super_user_uid = geteuid();

    struct dump_dir *dd = dd_create(template, (uid_t)-1, 0640);
    assert(dd != NULL || !"create new");
    dd_create_basic_files(dd, geteuid(), NULL);
    dd_save_text(dd, "type", "custom");
    dd_close(dd);
    dd = NULL;

    dd = dd_opendir(template, DD_OPEN_FD_ONLY);
    assert(dd != NULL || !"open fds");
    {
        const int fst_stat = dd_stat_for_uid(dd, geteuid());

        assert(fst_stat & DD_STAT_OWNED_BY_UID);
        assert(fst_stat & DD_STAT_ACCESSIBLE_BY_UID);
        assert(!(fst_stat & DD_STAT_NO_OWNER));
    }

    dd = dd_fdopendir(dd, /*for writing*/0);
    assert(dd != NULL || !"reopen for writing");

    assert(dd_set_owner(dd, geteuid() + 1) == 0);
    assert(dd_get_owner(dd) == geteuid() + 1);

    {
        const int scn_stat = dd_stat_for_uid(dd, geteuid() + 2);
        assert(scn_stat == 0);
    }

    {
        const int thr_stat = dd_stat_for_uid(dd, dd_g_super_user_uid);
        assert(!(thr_stat & DD_STAT_OWNED_BY_UID));
        assert(thr_stat & DD_STAT_ACCESSIBLE_BY_UID);
        assert(!(thr_stat & DD_STAT_NO_OWNER));
    }

    assert(dd_set_no_owner(dd) == 0);
    assert(dd_get_owner(dd) != geteuid() + 3);

    {
        const int frt_stat = dd_stat_for_uid(dd, geteuid() + 3);
        assert(!(frt_stat & DD_STAT_OWNED_BY_UID));
        assert(frt_stat & DD_STAT_ACCESSIBLE_BY_UID);
        assert(frt_stat & DD_STAT_NO_OWNER);
    }

    {
        const int fft_stat = dd_stat_for_uid(dd, dd_g_super_user_uid);
        assert(!(fft_stat & DD_STAT_OWNED_BY_UID));
        assert(fft_stat & DD_STAT_ACCESSIBLE_BY_UID);
        assert(fft_stat & DD_STAT_NO_OWNER);
    }

    assert(dd_delete(dd) == 0);

    *last_slash = '\0';
    assert(rmdir(template) == 0);

    return EXIT_SUCCESS;
}
]])

## -------------- ##
## recursive_lock ##
## -------------- ##

AT_TESTFUN([recursive_lock],
[[
#include "internal_libreport.h"
#include <errno.h>
#include <assert.h>

int main(int argc, char **argv)
{
    g_verbose = 3;

    char *path = tmpnam(NULL);
    struct dump_dir *dd = dd_create(path, -1L, DEFAULT_DUMP_DIR_MODE);

    char *lock_path = concat_path_file(path, ".lock");
    struct stat buf;

    assert(dd);

    assert(lstat(lock_path, &buf) == 0 && S_ISLNK(buf.st_mode));

    dd_create_basic_files(dd, -1L, "/");
    dd_save_text(dd, "type", "custom");

    struct dump_dir *dd2 = dd_opendir(path, DD_OPEN_READONLY);
    assert(dd2->owns_lock == 0);

    struct dump_dir *dd3 = dd_opendir(path, 0);
    assert(dd3->owns_lock == 0);

    dd_close(dd2);
    assert(lstat(lock_path, &buf) == 0 && S_ISLNK(buf.st_mode));

    dd_close(dd3);
    assert(lstat(lock_path, &buf) == 0 && S_ISLNK(buf.st_mode));

    dd_close(dd);

    assert(stat(lock_path, &buf) != 0 && errno == ENOENT);
    free(lock_path);

    return 0;
}
]])

## ----------------------- ##
## str_is_correct_filename ##
## ----------------------- ##

AT_TESTFUN([str_is_correct_filename],
[[
#include "internal_libreport.h"
#include <assert.h>
#
int main(void)
{
    g_verbose = 3;

    assert(str_is_correct_filename("") == false);
    assert(str_is_correct_filename("/") == false);
    assert(str_is_correct_filename("//") == false);
    assert(str_is_correct_filename("/a") == false);
    assert(str_is_correct_filename("a/") == false);
    assert(str_is_correct_filename(".") == false);
    assert(str_is_correct_filename("..") == false);
    assert(str_is_correct_filename("/.") == false);
    assert(str_is_correct_filename("//.") == false);
    assert(str_is_correct_filename("./") == false);
    assert(str_is_correct_filename(".//") == false);
    assert(str_is_correct_filename("/./") == false);
    assert(str_is_correct_filename("/..") == false);
    assert(str_is_correct_filename("//..") == false);
    assert(str_is_correct_filename("../") == false);
    assert(str_is_correct_filename("..//") == false);
    assert(str_is_correct_filename("/../") == false);
    assert(str_is_correct_filename("/.././") == false);

    assert(str_is_correct_filename("looks-good-but-evil/") == false);
    assert(str_is_correct_filename("looks-good-but-evil/../../") == false);

    assert(str_is_correct_filename(".meta-data") == true);
    assert(str_is_correct_filename("..meta-meta-data") == true);
    assert(str_is_correct_filename("meta-..-data") == true);

    assert(str_is_correct_filename("a") == true);
    assert(str_is_correct_filename(".a") == true);
    assert(str_is_correct_filename("a.") == true);
    assert(str_is_correct_filename("ab") == true);
    assert(str_is_correct_filename("abc") == true);
    assert(str_is_correct_filename("abcd") == true);
    assert(str_is_correct_filename("abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890+-") == true);
    assert(str_is_correct_filename("abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890+-=") == false);

    return 0;
}
]])


## ----------------##
## create_dump_dir ##
## ----------------##

AT_TESTFUN([create_dump_dir],
[[
#include "testsuite.h"

static int kill_canary(struct dump_dir *dd, void *args)
{
    int *cb_canary = (int *)args;
    *cb_canary = 0;
    return 0;
}

static int revive_canary(struct dump_dir *dd, void *args)
{
    int *cb_canary = (int *)args;
    *cb_canary = 1;
    return 1;
}

static int lay_an_egg(struct dump_dir *dd, void *args)
{
    const char *bird_kind = (const char *)args;
    dd_save_text(dd, "egg", bird_kind);
    return 0;
}

static int create_type(struct dump_dir *dd, void *args)
{
    const char *bird_kind = (const char *)args;
    dd_save_text(dd, FILENAME_TYPE, bird_kind);
    return 0;
}

TS_MAIN
{
    char template[] = "/tmp/XXXXXX";

    if (mkdtemp(template) == NULL) {
        perror("mkdtemp()");
        return EXIT_FAILURE;
    }

    printf("Base dump dir path: %s\n", template);

    {
        int cb_canary = 1;
        struct dump_dir *dd = create_dump_dir(template, "../attest", (uid_t)-1, &kill_canary, &cb_canary);
        TS_ASSERT_PTR_IS_NULL_MESSAGE(dd, "TYPE cannot be relative path");
        TS_ASSERT_SIGNED_EQ(1, cb_canary);
    }

    {
        int cb_canary = 1;
        struct dump_dir *dd = create_dump_dir(template, "/tmp/attest", (uid_t)-1, &kill_canary, &cb_canary);
        TS_ASSERT_PTR_IS_NULL_MESSAGE(dd, "TYPE cannot be absolute path");
        TS_ASSERT_SIGNED_EQ(1, cb_canary);
    }

    {
        int cb_canary = 0;
        struct dump_dir *dd = create_dump_dir(template, "attest", (uid_t)-1, &revive_canary, &cb_canary);
        TS_ASSERT_PTR_IS_NULL_MESSAGE(dd, "Non-zero callback return value stops dump dir creation");
        TS_ASSERT_SIGNED_EQ(1, cb_canary);
    }

    {
        struct dump_dir *dd = create_dump_dir(template, "birds", (uid_t)-1, lay_an_egg, (void *)"canary");
        TS_ASSERT_PTR_IS_NOT_NULL_MESSAGE(dd, "Created dump directory with valid TYPE");
        TS_ASSERT_STRING_BEGINS_WITH(strrchr(dd->dd_dirname, '/') + 1, "birds", "Type was honored");
        TS_ASSERT_STRING_EQ(dd_load_text(dd, FILENAME_TYPE), "birds", "'type' is created from the passed string");
        TS_ASSERT_STRING_EQ(dd_load_text(dd, "egg"), "canary", "Call back saves durable data");
        TS_ASSERT_SIGNED_EQ(dd_delete(dd), 0);
    }

    {
        struct dump_dir *dd = create_dump_dir(template, "birds", (uid_t)-1, create_type, (void *)"canary");
        TS_ASSERT_PTR_IS_NOT_NULL_MESSAGE(dd, "Created dump directory with valid TYPE");
        TS_ASSERT_STRING_BEGINS_WITH(strrchr(dd->dd_dirname, '/') + 1, "birds", "Type was honored");
        TS_ASSERT_STRING_EQ(dd_load_text(dd, FILENAME_TYPE), "canary", "'type' was not overwritten");
        TS_ASSERT_SIGNED_EQ(dd_delete(dd), 0);
    }
}
TS_RETURN_MAIN

]])


## --------------------------------- ##
## create_dump_dir_from_problem_data ##
## --------------------------------- ##

AT_TESTFUN([create_dump_dir_from_problem_data],
[[
#include "testsuite.h"

TS_MAIN
{
    char template[] = "/tmp/XXXXXX";

    if (mkdtemp(template) == NULL) {
        perror("mkdtemp()");
        return EXIT_FAILURE;
    }

    printf("Base dump dir path: %s\n", template);

    dd_g_fs_group_gid = getegid();

    problem_data_t *pd = problem_data_new();

    {
        fprintf(stderr, "=== No FILENAME_TYPE in Problem Data ===\n");
        struct dump_dir *no_type = create_dump_dir_from_problem_data(pd, template);
        TS_ASSERT_PTR_IS_NULL_MESSAGE(no_type, "It's not possible to create dump directory without TYPE");
    }

    {
        fprintf(stderr, "=== Prohibited name in FILENAME_TYPE in Problem Data ===\n");
        problem_data_add_text_editable(pd, FILENAME_TYPE, "../attest");
        struct dump_dir *prohibited_type = create_dump_dir_from_problem_data(pd, template);
        TS_ASSERT_PTR_IS_NULL_MESSAGE(prohibited_type, "It's not possible to create dump directory with invalid TYPE");
    }

    problem_data_add_text_editable(pd, FILENAME_TYPE, "attest");

    {
        fprintf(stderr, "=== NO UID - geteuid() ===\n");
        struct dump_dir *no_uid_dd_geteuid = create_dump_dir_from_problem_data_ext(pd, template, geteuid());
        assert(no_uid_dd_geteuid != NULL);
        assert(dd_get_owner(no_uid_dd_geteuid) == geteuid());
        assert(dd_delete(no_uid_dd_geteuid) == 0);
        fprintf(stderr, "=== NO UID - geteuid() ===\n");
    }

    {
        fprintf(stderr, "=== NO UID - NO UID ===\n");
        struct dump_dir *no_uid_dd_nouid = create_dump_dir_from_problem_data_ext(pd, template, (uid_t)-1L);
        assert(no_uid_dd_nouid != NULL);
        assert(dd_get_owner(no_uid_dd_nouid) == geteuid());
        assert(dd_delete(no_uid_dd_nouid) == 0);
        fprintf(stderr, "=== NO UID - NO UID ===\n");
    }

    char buf[sizeof(long)*3 + 2];
    snprintf(buf, sizeof(buf), "%ld", (long)(geteuid() + 1));
    problem_data_add_text_editable(pd, FILENAME_UID, buf);

    {
        fprintf(stderr, "=== UID - geteuid() ===\n");
        struct dump_dir *uid_dd_geteuid = create_dump_dir_from_problem_data_ext(pd, template, geteuid());
        assert(uid_dd_geteuid != NULL);
        assert(dd_get_owner(uid_dd_geteuid) == (geteuid() + 1));
        assert(dd_delete(uid_dd_geteuid) == 0);
        fprintf(stderr, "=== UID - geteuid() ===\n");
    }

    {
        fprintf(stderr, "=== UID - NO UID ===\n");
        struct dump_dir *uid_dd_nouid = create_dump_dir_from_problem_data_ext(pd, template, (uid_t)-1L);
        assert(uid_dd_nouid != NULL);
        assert(dd_get_owner(uid_dd_nouid) == (geteuid() + 1));
        assert(dd_delete(uid_dd_nouid) == 0);
        fprintf(stderr, "=== UID - NO UID ===\n");
    }

    assert(rmdir(template) == 0);
}
TS_RETURN_MAIN

]])

## ---------- ##
## dd_copy_fd ##
## ---------- ##

AT_TESTFUN([dd_copy_fd],
[[
#include "testsuite.h"

void test(const char buffer[], const size_t buffer_size)
{
    char template[] = "/tmp/XXXXXX";

    if (mkdtemp(template) == NULL) {
        perror("mkdtemp()");
        abort();
    }

    printf("Dump dir path: %s\n", template);

    struct dump_dir *dd = dd_create(template, (uid_t)-1, 0640);
    assert(dd != NULL || !"Cannot create new dump directory");

    dd_create_basic_files(dd, geteuid(), NULL);

    char tmpfile[] = "/tmp/libreport-attestsuite-dd_copy_fd.XXXXXX";
    int tmpfd = mkstemp(tmpfile);
    full_write(tmpfd, buffer, buffer_size);

    {
        assert((-1) != lseek(tmpfd, 0, SEEK_SET));

        const off_t read_truncated = dd_copy_fd(dd, "truncated", tmpfd, 0, buffer_size/2);
        TS_ASSERT_SIGNED_GE(read_truncated, buffer_size/2);
        TS_ASSERT_SIGNED_EQ(dd_get_item_size(dd, "truncated"), buffer_size/2);
        TS_ASSERT_SIGNED_EQ(dd_delete_item(dd, "truncated"), 0);
    }

    {
        assert((-1) != lseek(tmpfd, 0, SEEK_SET));

        const off_t read_exact = dd_copy_fd(dd, "exact", tmpfd, 0, buffer_size);
        TS_ASSERT_SIGNED_EQ(read_exact, buffer_size);
        TS_ASSERT_SIGNED_EQ(dd_get_item_size(dd, "exact"), buffer_size);
        TS_ASSERT_SIGNED_EQ(dd_delete_item(dd, "exact"), 0);
    }

    {
        assert((-1) != lseek(tmpfd, 0, SEEK_SET));

        const off_t read_bigger = dd_copy_fd(dd, "bigger", tmpfd, 0, buffer_size * 2);
        TS_ASSERT_SIGNED_EQ(read_bigger, buffer_size);
        TS_ASSERT_SIGNED_EQ(dd_get_item_size(dd, "bigger"), buffer_size);
        TS_ASSERT_SIGNED_EQ(dd_delete_item(dd, "bigger"), 0);
    }

    {
        assert((-1) != lseek(tmpfd, 0, SEEK_SET));

        const off_t read_no_limit = dd_copy_fd(dd, "no_limit", tmpfd, 0, 0);
        TS_ASSERT_SIGNED_EQ(read_no_limit, buffer_size);
        TS_ASSERT_SIGNED_EQ(dd_get_item_size(dd, "no_limit"), buffer_size);
        TS_ASSERT_SIGNED_EQ(dd_delete_item(dd, "no_limit"), 0);
    }

    close(tmpfd);
    unlink(tmpfile);
    assert(dd_delete(dd) == 0);
}

TS_MAIN
{
    {
        char buffer[1024*2];
        memset(buffer, 'x', sizeof(buffer));
        test(buffer, sizeof(buffer));
    }

    {
        char buffer[1024*4];
        memset(buffer, 'y', sizeof(buffer));
        test(buffer, sizeof(buffer));
    }

    {
        char buffer[1024*6];
        memset(buffer, 'z', sizeof(buffer));
        test(buffer, sizeof(buffer));
    }


    {
        char template[] = "/tmp/XXXXXX";

        if (mkdtemp(template) == NULL) {
            perror("mkdtemp()");
            return EXIT_FAILURE;
        }

        printf("Dump dir path: %s\n", template);

        struct dump_dir *dd = dd_create(template, (uid_t)-1, 0640);
        assert(dd != NULL || !"Cannot create new dump directory");

        dd_create_basic_files(dd, geteuid(), NULL);

        {
            int opath_fd = open("/etc/services", O_PATH);
            char buf[16] = {0};
            if (read(opath_fd, buf, sizeof(buf)/sizeof(buf[0])) == -1)
            {
                assert(errno == EBADF || !"O_PATH fd can be read");
                close(opath_fd);

                opath_fd = open("/etc/services", O_PATH);
                assert(opath_fd >= 0);

                const off_t opath_read = dd_copy_fd(dd, "opath", opath_fd, 0, 0);
                TS_ASSERT_SIGNED_EQ(opath_read, -1);
                TS_ASSERT_SIGNED_EQ(dd_exist(dd, "opath"), 0);
            }
            close(opath_fd);
        }

        {
            int wronly_fd = open("/tmp/libreport.testsuite", O_WRONLY | O_CREAT | O_TRUNC, 0600);
            assert(wronly_fd >= 0 || !"Cannot create temporary file");
            char buf[] = "Hello, world!";
            assert(write(wronly_fd, buf, sizeof(buf)/sizeof(buf[0])) == sizeof(buf)/sizeof(buf[0]));
            close(wronly_fd);

            wronly_fd = open("/tmp/libreport.testsuite", O_WRONLY);
            assert(wronly_fd >= 0 || !"Cannot re-open temporary file");

            const off_t wronly_read = dd_copy_fd(dd, "wronly", wronly_fd, 0, 0);
            TS_ASSERT_SIGNED_EQ(wronly_read, -1);
            TS_ASSERT_SIGNED_EQ(dd_exist(dd, "wronly"), 0);

            close(wronly_fd);
        }

        dd_delete(dd);
    }
}
TS_RETURN_MAIN

]])

## ------------- ##
## dd_load_int32 ##
## ------------- ##

AT_TESTFUN([dd_load_int32],
[[
#include "internal_libreport.h"
#include <assert.h>
#
int main(void)
{
    g_verbose = 3;

    char template[] = "/tmp/XXXXXX";

    if (mkdtemp(template) == NULL) {
        perror("mkdtemp()");
        return EXIT_FAILURE;
    }

    printf("Dump dir path: %s\n", template);

    struct dump_dir *dd = dd_create(template, (uid_t)-1, 0640);
    assert(dd != NULL || !"Cannot create new dump directory");

    dd_create_basic_files(dd, geteuid(), NULL);
    dd_save_text(dd, FILENAME_TYPE, "attest");

    int32_t value;

    dd_save_text(dd, "empty", "");
    assert(dd_load_int32(dd, "empty", &value) == -EINVAL);

    char *buf = NULL;
    asprintf(&buf, "%lld", -1LL + INT32_MIN);
    assert(buf != NULL);

    dd_save_text(dd, "below_min", buf);
    free(buf);

    assert(dd_load_int32(dd, "below_min", &value) == -ERANGE);

    asprintf(&buf, "%"PRId32, INT32_MIN);
    dd_save_text(dd, "min", buf);
    free(buf);

    assert(dd_load_int32(dd, "min", &value) == 0);
    assert(value == INT32_MIN || !"min");

    asprintf(&buf, "%"PRId32, INT32_MAX);
    assert(buf != NULL);

    dd_save_text(dd, "max", buf);
    free(buf);

    assert(dd_load_int32(dd, "max", &value) == 0);
    assert(value == INT32_MAX);

    asprintf(&buf, "%lld", 1LL + INT32_MAX);
    assert(buf != NULL);

    dd_save_text(dd, "above_max", buf);
    free(buf);

    assert(dd_load_int32(dd, "above_max", &value) == -ERANGE);

    dd_save_text(dd, "string_suffix", "123abc");
    assert(dd_load_int32(dd, "string_suffix", &value) == -EINVAL);

    dd_save_text(dd, "string", "abc");
    assert(dd_load_int32(dd, "string", &value) == -EINVAL);

    assert(dd_delete(dd) == 0);

    return 0;
}
]])

## -------------- ##
## dd_load_uint32 ##
## -------------- ##

AT_TESTFUN([dd_load_uint32],
[[
#include "internal_libreport.h"
#include <assert.h>
#
int main(void)
{
    g_verbose = 3;

    char template[] = "/tmp/XXXXXX";

    if (mkdtemp(template) == NULL) {
        perror("mkdtemp()");
        return EXIT_FAILURE;
    }

    printf("Dump dir path: %s\n", template);

    struct dump_dir *dd = dd_create(template, (uid_t)-1, 0640);
    assert(dd != NULL || !"Cannot create new dump directory");

    dd_create_basic_files(dd, geteuid(), NULL);
    dd_save_text(dd, FILENAME_TYPE, "attest");

    uint32_t value;

    dd_save_text(dd, "empty", "");
    assert(dd_load_uint32(dd, "empty", &value) == -EINVAL);

    dd_save_text(dd, "below_min", "-1");
    assert(dd_load_uint32(dd, "below_min", &value) == -ERANGE);

    dd_save_text(dd, "min", "0");
    assert(dd_load_uint32(dd, "min", &value) == 0);
    assert(value == 0 || !"min");

    char *buf = NULL;
    asprintf(&buf, "%"PRIu32, UINT32_MAX);
    assert(buf != NULL);

    dd_save_text(dd, "max", buf);
    assert(dd_load_uint32(dd, "max", &value) == 0);
    assert(value == UINT32_MAX);

    free(buf);
    asprintf(&buf, "%lld", 1LL + UINT32_MAX);
    assert(buf != NULL);

    dd_save_text(dd, "above_max", buf);
    assert(dd_load_uint32(dd, "above_max", &value) == -ERANGE);

    dd_save_text(dd, "string_suffix", "123abc");
    assert(dd_load_uint32(dd, "string_suffix", &value) == -EINVAL);

    dd_save_text(dd, "string", "abc");
    assert(dd_load_uint32(dd, "string", &value) == -EINVAL);

    assert(dd_delete(dd) == 0);

    return 0;
}
]])

## ------------- ##
## dd_load_int64 ##
## ------------- ##

AT_TESTFUN([dd_load_int64],
[[
#include "internal_libreport.h"
#include <assert.h>

int main(void)
{
    g_verbose = 3;

    char template[] = "/tmp/XXXXXX";

    if (mkdtemp(template) == NULL) {
        perror("mkdtemp()");
        return EXIT_FAILURE;
    }

    printf("Dump dir path: %s\n", template);

    struct dump_dir *dd = dd_create(template, (uid_t)-1, 0640);
    assert(dd != NULL || !"Cannot create new dump directory");

    dd_create_basic_files(dd, geteuid(), NULL);
    dd_save_text(dd, FILENAME_TYPE, "attest");

    int64_t value;

    dd_save_text(dd, "empty", "");
    assert(dd_load_int64(dd, "empty", &value) == -EINVAL);

    dd_save_text(dd, "below_min", "-9223372036854775809");
    assert(dd_load_int64(dd, "below_min", &value) == -ERANGE);

    char *buf = NULL;
    asprintf(&buf, "%"PRId64, INT64_MIN);
    assert(buf != NULL);
    dd_save_text(dd, "min", buf);
    assert(dd_load_int64(dd, "min", &value) == 0);
    assert(value == INT64_MIN || !"min");
    free(buf);

    asprintf(&buf, "%"PRId64, INT64_MAX);
    assert(buf != NULL);
    dd_save_text(dd, "max", buf);
    assert(dd_load_int64(dd, "max", &value) == 0);
    assert(value == INT64_MAX);
    free(buf);

    dd_save_text(dd, "above_max", "9223372036854775808");
    assert(dd_load_int64(dd, "above_max", &value) == -ERANGE);

    dd_save_text(dd, "string_suffix", "123abc");
    assert(dd_load_int64(dd, "string_suffix", &value) == -EINVAL);

    dd_save_text(dd, "string", "abc");
    assert(dd_load_int64(dd, "string", &value) == -EINVAL);

    assert(dd_delete(dd) == 0);

    return 0;
}
]])

## --------------- ##
## dd_load_uint64  ##
## --------------- ##

AT_TESTFUN([dd_load_uint64],
[[
#include "internal_libreport.h"
#include <assert.h>

int main(void)
{
    g_verbose = 3;

    char template[] = "/tmp/XXXXXX";

    if (mkdtemp(template) == NULL) {
        perror("mkdtemp()");
        return EXIT_FAILURE;
    }

    printf("Dump dir path: %s\n", template);

    struct dump_dir *dd = dd_create(template, (uid_t)-1, 0640);
    assert(dd != NULL || !"Cannot create new dump directory");

    dd_create_basic_files(dd, geteuid(), NULL);
    dd_save_text(dd, FILENAME_TYPE, "attest");

    uint64_t value;

    dd_save_text(dd, "empty", "");
    assert(dd_load_uint64(dd, "empty", &value) == -EINVAL);

    dd_save_text(dd, "below_min", "-1");
    assert(dd_load_uint64(dd, "below_min", &value) == -ERANGE);

    dd_save_text(dd, "min", "0");
    assert(dd_load_uint64(dd, "min", &value) == 0);
    assert(value == 0 || !"min");

    char *buf = NULL;
    asprintf(&buf, "%"PRIu64, UINT64_MAX);
    assert(buf != NULL);
    dd_save_text(dd, "max", buf);
    assert(dd_load_uint64(dd, "max", &value) == 0);
    assert(value == UINT64_MAX);
    free(buf);

    dd_save_text(dd, "above_max", "18446744073709551616");
    assert(dd_load_uint64(dd, "above_max", &value) == -ERANGE);

    dd_save_text(dd, "string_suffix", "123abc");
    assert(dd_load_uint64(dd, "string_suffix", &value) == -EINVAL);

    dd_save_text(dd, "string", "abc");
    assert(dd_load_uint64(dd, "string", &value) == -EINVAL);

    assert(dd_delete(dd) == 0);

    return 0;
}
]])

## ----------------- ##
## dd_create_archive ##
## ----------------- ##

AT_TESTFUN([dd_create_archive],
[[
#include "internal_libreport.h"
#include <libtar.h>
#include <assert.h>

void verify_archive(struct dump_dir *dd, const char *file_name,
    const_string_vector_const_ptr_t included_files,
    const_string_vector_const_ptr_t excluded_files)
{
    unsigned c = 0;
    for (const_string_vector_const_ptr_t i = included_files; i && *i; ++i)
        ++c;
    int *check_array = xzalloc(c * sizeof(int));

    int pipe_from_parent_to_child[2];
    xpipe(pipe_from_parent_to_child);
    pid_t child = fork();
    if (child < 0)
        perror_msg_and_die("vfork");

    if (child == 0)
    {
        /* child */
        close(pipe_from_parent_to_child[0]);
        xmove_fd(xopen(file_name, O_RDONLY), 0);
        xmove_fd(pipe_from_parent_to_child[1], 1);
        execlp("gzip", "gzip", "-d", NULL);
        perror_msg_and_die("Can't execute '%s'", "gzip");
    }
    close(pipe_from_parent_to_child[1]);

    /* If child died (say, in xopen), then parent might get SIGPIPE.
     * We want to properly unlock dd, therefore we must not die on SIGPIPE:
     */
    signal(SIGPIPE, SIG_IGN);

    TAR* tar = NULL;
    /* Create tar writer object */
    if (tar_fdopen(&tar, pipe_from_parent_to_child[0], file_name,
                /*fileops:(standard)*/ NULL, O_RDONLY, 0644, TAR_GNU) != 0)
    {
        fprintf(stderr, "Failed to open the pipe to gzip for archive: '%s'\n", file_name);
        abort();
    }

    int r = 0;
    const char *real_file = "/tmp/libreport-attest-extracted";
    while ((r = th_read(tar)) == 0)
    {
        char *path = th_get_pathname(tar);

        if (!TH_ISREG(tar))
        {
            fprintf(stderr, "Not regular file: '%s', found in archive: '%s'\n", path, file_name);
            continue;
        }

        const_string_vector_const_ptr_t i = included_files;
        for (c = 0; i && *i; ++i, ++c)
        {
            if (strcmp(*i, path) == 0)
                break;
        }

        if (i && *i != NULL)
        {
            printf("Included file: '%s', found in archive '%s'\n", path, file_name);
            check_array[c] += 1;

            unlink(real_file);
            if(tar_extract_regfile(tar, xstrdup(real_file)) != 0)
            {
                fprintf(stderr, "TAR failed to extract '%s' to '%s': %s\n", path, real_file, strerror(errno));
                abort();
            }

            char *original = dd_load_text(dd, path);
            assert(original != NULL);
            assert(original[0] != '\0');

            char *extracted = xmalloc_xopen_read_close("/tmp/libreport-attest-extracted", NULL);
            assert(extracted != NULL);

            if (strcmp(extracted, original) != 0)
            {
                fprintf(stderr, "Invalid file contents: '%s'\nExp: '%s'\nGot: '%s'\n", path, original, extracted);
                abort();
            }

            free(original);
            free(extracted);

            continue;
        }

        i = excluded_files;
        for (; i && *i; ++i)
        {
            if (strcmp(*i, path) == 0)
                break;
        }

        if (i && *i != NULL)
        {
            fprintf(stderr, "Excluded file: '%s', found in archive '%s'\n", path, file_name);
            abort();
        }

        fprintf(stderr, "Uncategorized file: '%s', found in archive '%s'\n", path, file_name);
    }

    if (r != 1)
    {
        fprintf(stderr, "th_read() failed: %s\n", strerror(errno));
        abort();
    }

    tar_close(tar);

    int status;
    safe_waitpid(child, &status, 0);
    if (status != 0)
    {
        fprintf(stderr, "gzip status code '%d'\n", status);
        abort();
    }

    int err = 0;
    const_string_vector_const_ptr_t i = included_files;
    for (c = 0; i && *i; ++i, ++c)
    {
        switch (check_array[c])
        {
            case 0:
                fprintf(stderr, "Not found included file: '%s', in archive: %s\n", *i, file_name);
                ++err;
                break;
            case 1:
                fprintf(stdout, "Found included file: '%s', in archive: %s\n", *i, file_name);
                break;
            default:
                fprintf(stderr, "%d occurrences of included file: '%s', in archive: %s\n", check_array[c], *i, file_name);
                ++err;
                break;
        }
    }

    if (err)
        abort();

    return;
}

int main(void)
{
    g_verbose = 3;

    char template[] = "/tmp/XXXXXX";

    if (mkdtemp(template) == NULL) {
        perror("mkdtemp()");
        return EXIT_FAILURE;
    }

    printf("Dump dir path: %s\n", template);

    struct dump_dir *dd = dd_create(template, (uid_t)-1, 0640);
    assert(dd != NULL || !"Cannot create new dump directory");


#define COMMON_FILES "time", "last_occurrence", "uid", "kernel", \
                     "architecture", "hostname", "os_info", "os_release", \
                     "type", "count", "component", "program_log"
#define SENSITIVE_FILES "environ", "backtrace", "secret_file", "private_file", \
                        "useless_file"

    dd_create_basic_files(dd, geteuid(), NULL);
    dd_save_text(dd, FILENAME_TYPE, "attest");
    dd_save_text(dd, FILENAME_COUNT, "1");
    dd_save_text(dd, FILENAME_COMPONENT, "libreport-attest");
    dd_save_text(dd, "program_log", "Something very important!");

    const gchar *excluded_files[] = {
        SENSITIVE_FILES,
        NULL,
    };

    for (const gchar **iter = excluded_files; *iter; ++iter)
        dd_save_text(dd, *iter, *iter);

    /* Un-supported archive type */
    {
        fprintf(stderr, "TEST-CASE: Un-supported type\n");
        fprintf(stdout, "TEST-CASE: Un-supported type\n");
        const int r = dd_create_archive(dd, "/tmp/libreport-attest.omg", NULL, 0);
        printf("dd_create_archive() == %d\n", r);
        assert(r == -ENOSYS || !"Not supported");
    }

    /* File already exists. */
    {
        fprintf(stderr, "TEST-CASE: File exists\n");
        fprintf(stdout, "TEST-CASE: File exists\n");
        char file_contents[] = "Non emtpy file";
        const char *file_name = "/tmp/libreport-attest.tar.gz";
        FILE *test_file = fopen(file_name, "w");
        assert(test_file != NULL);
        assert(fprintf(test_file, "%s", file_contents) == strlen(file_contents));
        fclose(test_file);

        assert(dd_create_archive(dd, file_name, NULL, 0) == -EEXIST || !"Exists");

        char *canary = xmalloc_xopen_read_close(file_name, NULL);
        assert(canary != NULL);
        assert(strcmp(canary, file_contents) == 0);
    }

    /* All elements */
    {
        fprintf(stderr, "TEST-CASE: Compress all elements\n");
        fprintf(stdout, "TEST-CASE: Compress all elements\n");

        const gchar *included_files[] = {
            COMMON_FILES,
            SENSITIVE_FILES,
            NULL,
        };

        const char *file_name = "/tmp/libreport-attest-all.tar.gz";
        unlink(file_name);
        assert(dd_create_archive(dd, file_name, NULL, 0) == 0 || !"All elements");

        verify_archive(dd, file_name, included_files, NULL);

        unlink(file_name);
    }

    /* Excluded elements */
    {
        fprintf(stderr, "TEST-CASE: Exclude elements\n");
        fprintf(stdout, "TEST-CASE: Exclude elements\n");

        const char *included_files[] = {
            COMMON_FILES,
            NULL,
        };

        const char *file_name = "/tmp/libreport-attest-excluded.tar.gz";
        unlink(file_name);
        assert(dd_create_archive(dd, file_name, excluded_files, 0) == 0 || !"Excluded elements");

        verify_archive(dd, file_name, included_files, excluded_files);

        unlink(file_name);
    }

    assert(dd_delete(dd) == 0);

    return 0;
}
]])

## --------------- ##
## dd_compute_size ##
## --------------- ##

AT_TESTFUN([dd_compute_size],
[[
#include "testsuite.h"

TS_MAIN
{
    char template[] = "/tmp/XXXXXX/dump_dir";

    char *last_slash = strrchr(template, '/');
    *last_slash = '\0';

    if (mkdtemp(template) == NULL) {
        perror("mkdtemp()");
        return EXIT_FAILURE;
    }

    struct dump_dir *dd = dd_create(template, (uid_t)-1, 0640);

    off_t expected = 0;
    struct string_pair {
        const char *first;
        const char *second;
        } pairs [] = {
        { .first = "type", .second = "attestsuite" },
        { .first = "analyzer", .second = "dd_compute_size" },
        { .first = "time", .second = "1" },
        { .first = "first", .second = "second" },
        { .first = "foo", .second = "blah" },
        { .first = "moo", .second = "cow" },
        { .first = "rooster", .second = "cock-a-doodle-doo" },
        { .first = "frog", .second = "ribbit" },
        { .first = "pig", .second = "oink" },
    };

    for (size_t i = 0; i < ARRAY_SIZE(pairs); ++i)
    {
        dd_save_text(dd, pairs[i].first, pairs[i].second);
        expected += strlen(pairs[i].second);
        assert(expected >= 0);
    }

    TS_ASSERT_SIGNED_EQ(dd_compute_size(dd, 0), expected);
}
TS_RETURN_MAIN

]])


## ----------------- ##
## dd_items_handling ##
## ----------------- ##

AT_TESTFUN([dd_items_handling],
[[
#include "testsuite.h"
#include "testsuite_tools.h"

TS_MAIN
{
    struct dump_dir *dd = testsuite_dump_dir_create(-1, -1, 0);
    dd_create_basic_files(dd, geteuid(), NULL);

    char *base_dir = xstrndup(dd->dd_dirname, strrchr(dd->dd_dirname, '/') - dd->dd_dirname);
    assert(base_dir != NULL);

    char *test_file_full_path = concat_path_file(base_dir, "libreport-test");
    FILE *test_file = fopen(test_file_full_path, "w");
    assert(test_file != NULL);
    fprintf(test_file, "dd_items_handling");
    fclose(test_file);

    assert(symlinkat(test_file_full_path, dd->dd_fd, "link-item") == 0);
    assert(mkdirat(dd->dd_fd, "dir-item", 0777) == 0);

    {
        struct stat buf;
        TS_ASSERT_SIGNED_EQ(dd_item_stat(dd, test_file_full_path, &buf), -EINVAL);
        TS_ASSERT_SIGNED_EQ(dd_item_stat(dd, "../libreport-test", &buf), -EINVAL);
        TS_ASSERT_SIGNED_EQ(dd_item_stat(dd, "foo-blah-does-not-exist", &buf), -ENOENT);
        TS_ASSERT_SIGNED_EQ(dd_item_stat(dd, "link-item", &buf), -EMEDIUMTYPE);
        TS_ASSERT_SIGNED_EQ(dd_item_stat(dd, "dir-item", &buf), -EMEDIUMTYPE);
    }

    {
        TS_ASSERT_SIGNED_EQ(dd_get_item_size(dd, test_file_full_path), -1);
        TS_ASSERT_SIGNED_EQ(dd_get_item_size(dd, "../libreport-test"), -1);
        TS_ASSERT_SIGNED_EQ(dd_get_item_size(dd, "foo-blah-does-not-exist"), 0);
        TS_ASSERT_SIGNED_EQ(dd_get_item_size(dd, "link-item"), -1);
        TS_ASSERT_SIGNED_EQ(dd_get_item_size(dd, "dir-item"), -1);
    }

    {
        struct stat buf, real_buf;
        assert(fstatat(dd->dd_fd, FILENAME_TIME, &real_buf, 0) == 0);
        TS_ASSERT_SIGNED_EQ(dd_item_stat(dd, FILENAME_TIME, &buf), 0);
        TS_ASSERT_SIGNED_EQ(real_buf.st_ino, buf.st_ino);

        TS_ASSERT_SIGNED_EQ(real_buf.st_size, dd_get_item_size(dd, FILENAME_TIME));
    }

    {
        TS_ASSERT_SIGNED_EQ(dd_delete_item(dd, test_file_full_path), -EINVAL);
        TS_ASSERT_SIGNED_EQ(dd_delete_item(dd, "../libreport-test"), -EINVAL);
        TS_ASSERT_SIGNED_EQ(dd_delete_item(dd, "foo-blah-does-not-exist"), 0);
        TS_ASSERT_SIGNED_EQ(dd_delete_item(dd, "link-item"), 0);
        TS_ASSERT_SIGNED_EQ(dd_delete_item(dd, "dir-item"), -1);
    }

    /* A T _REMOVEDIR (0x200) interferes with autconf */
    assert(unlinkat(dd->dd_fd, "dir-item", 0x200) == 0);
    assert(unlink(test_file_full_path) == 0);

    testsuite_dump_dir_delete(dd);
}
TS_RETURN_MAIN
]])


## ------------------- ##
## dd_get_env_variable ##
## ------------------- ##

AT_TESTFUN([dd_get_env_variable],
[[
#include "testsuite.h"
#include "testsuite_tools.h"

TS_MAIN
{
    struct dump_dir *dd = testsuite_dump_dir_create(-1, -1, 0);

    dd_create_basic_files(dd, geteuid(), NULL);

    char *value = NULL;
    TS_ASSERT_SIGNED_EQ(dd_get_env_variable(dd, "FROG", &value), -ENOENT);
    TS_ASSERT_PTR_IS_NULL_MESSAGE(value, "Untouched return value on -ENOENT");

    dd_save_text(dd, FILENAME_ENVIRON, "FROG=ribbit\nROOSTER=cockle-doodle-doo");

    TS_ASSERT_FUNCTION(dd_get_env_variable(dd, "HORSE", &value));
    TS_ASSERT_PTR_IS_NULL_MESSAGE(value, "Untouched return value on missing variable");

    TS_ASSERT_FUNCTION(dd_get_env_variable(dd, "ROOSTER", &value));
    TS_ASSERT_STRING_EQ(value, "cockle-doodle-doo", "Variable ROOSTER loaded from the dump dir");

    testsuite_dump_dir_delete(dd);
}
TS_RETURN_MAIN
]])


## ----------------------- ##
## dd_get_first_occurrence ##
## ----------------------- ##

AT_TESTFUN([dd_get_first_occurrence],
[[
#include "testsuite.h"
#include "testsuite_tools.h"

TS_MAIN
{
    {   /* No time file */
        const time_t time_before_create = time(NULL);
        struct dump_dir *dd = testsuite_dump_dir_create(-1, -1, 0);
        TS_ASSERT_SIGNED_GE(dd_get_first_occurrence(dd), time_before_create);

        const time_t time_backup = dd_get_first_occurrence(dd);
        sleep(2);
        TS_ASSERT_SIGNED_EQ(dd_get_first_occurrence(dd), time_backup);

        sleep(2);
        dd_create_basic_files(dd, geteuid(), NULL);
        TS_ASSERT_SIGNED_EQ(dd_get_first_occurrence(dd), time_backup);

        uint64_t saved;
        TS_ASSERT_FUNCTION(dd_load_uint64(dd, FILENAME_TIME, &saved));
        TS_ASSERT_SIGNED_EQ(saved, dd_get_first_occurrence(dd));

        testsuite_dump_dir_delete(dd);
    }

    {   /* Invalid time file */
        struct dump_dir *dd = testsuite_dump_dir_create(-1, -1, 0);
        const time_t time_backup = dd_get_first_occurrence(dd);
        dd_save_text(dd, FILENAME_TIME, "foo");
        sleep(2);
        dd_create_basic_files(dd, geteuid(), NULL);
        TS_ASSERT_SIGNED_EQ(dd_get_first_occurrence(dd), time_backup);

        testsuite_dump_dir_delete(dd);
    }

    {   /* Valid time file */
        struct dump_dir *dd = testsuite_dump_dir_create(-1, -1, 0);
        dd_save_text(dd, FILENAME_TIME, "1234567");
        sleep(2);
        dd_create_basic_files(dd, geteuid(), NULL);
        TS_ASSERT_SIGNED_EQ(dd_get_first_occurrence(dd), 1234567);

        testsuite_dump_dir_delete(dd);
    }

    { /* Impossible case = dd->dd_time == -1 */
        struct dump_dir *dd = testsuite_dump_dir_create(-1, -1, 0);
        dd->dd_time = -1;
        errno = 0;
        TS_ASSERT_SIGNED_EQ(dd_get_first_occurrence(dd), -1);
        TS_ASSERT_SIGNED_EQ(errno, ENODATA);

        testsuite_dump_dir_delete(dd);
    }
}
TS_RETURN_MAIN
]])

## ----------------------- ##
## dd_get_last_occurrence ##
## ----------------------- ##

AT_TESTFUN([dd_get_last_occurrence],
[[
#include "testsuite.h"
#include "testsuite_tools.h"

TS_MAIN
{
    struct dump_dir *dd = testsuite_dump_dir_create(-1, -1, 0);
    TS_ASSERT_SIGNED_EQ(dd_get_last_occurrence(dd), dd_get_first_occurrence(dd));

    dd->dd_time = (time_t)1234567;
    dd_create_basic_files(dd, geteuid(), NULL);

    TS_ASSERT_SIGNED_EQ(dd_get_last_occurrence(dd), dd_get_first_occurrence(dd));
    TS_ASSERT_SIGNED_EQ(dd_get_last_occurrence(dd), 1234567);

    dd_save_text(dd, FILENAME_LAST_OCCURRENCE, "123");
    TS_ASSERT_SIGNED_EQ(dd_get_last_occurrence(dd), dd_get_first_occurrence(dd));
    TS_ASSERT_SIGNED_EQ(dd_get_last_occurrence(dd), 1234567);

    dd_save_text(dd, FILENAME_LAST_OCCURRENCE, "7654321");
    TS_ASSERT_SIGNED_NEQ(dd_get_last_occurrence(dd), dd_get_first_occurrence(dd));
    TS_ASSERT_SIGNED_EQ(dd_get_last_occurrence(dd), 7654321);

    dd->dd_time = -1;
    errno = 0;
    TS_ASSERT_SIGNED_EQ(dd_get_last_occurrence(dd), -1);
    TS_ASSERT_SIGNED_EQ(errno, ENODATA);

    dd_delete_item(dd, FILENAME_LAST_OCCURRENCE);
    errno = 0;
    TS_ASSERT_SIGNED_EQ(dd_get_last_occurrence(dd), -1);
    TS_ASSERT_SIGNED_EQ(errno, ENODATA);

    testsuite_dump_dir_delete(dd);
}
TS_RETURN_MAIN
]])

## ------------ ##
## dd_open_item ##
## ------------ ##

AT_TESTFUN([dd_open_item], [[
#include "testsuite.h"
#include "testsuite_tools.h"

TS_MAIN
{
    struct dump_dir *dd = testsuite_dump_dir_create(-1, -1, 0);
    dd->dd_time = (time_t)1234567;
    dd_create_basic_files(dd, geteuid(), NULL);

    TS_ASSERT_SIGNED_EQ(dd_open_item(dd, "", O_RDWR), -EINVAL);
    TS_ASSERT_SIGNED_EQ(dd_open_item(dd, "/", O_RDWR), -EINVAL);
    TS_ASSERT_SIGNED_EQ(dd_open_item(dd, "//", O_RDWR), -EINVAL);
    TS_ASSERT_SIGNED_EQ(dd_open_item(dd, "/a", O_RDWR), -EINVAL);
    TS_ASSERT_SIGNED_EQ(dd_open_item(dd, "a/", O_RDWR), -EINVAL);
    TS_ASSERT_SIGNED_EQ(dd_open_item(dd, ".", O_RDWR), -EINVAL);
    TS_ASSERT_SIGNED_EQ(dd_open_item(dd, "..", O_RDWR), -EINVAL);
    TS_ASSERT_SIGNED_EQ(dd_open_item(dd, "/.", O_RDWR), -EINVAL);
    TS_ASSERT_SIGNED_EQ(dd_open_item(dd, "//.", O_RDWR), -EINVAL);
    TS_ASSERT_SIGNED_EQ(dd_open_item(dd, "./", O_RDWR), -EINVAL);
    TS_ASSERT_SIGNED_EQ(dd_open_item(dd, ".//", O_RDWR), -EINVAL);
    TS_ASSERT_SIGNED_EQ(dd_open_item(dd, "/./", O_RDWR), -EINVAL);
    TS_ASSERT_SIGNED_EQ(dd_open_item(dd, "/..", O_RDWR), -EINVAL);
    TS_ASSERT_SIGNED_EQ(dd_open_item(dd, "//..", O_RDWR), -EINVAL);
    TS_ASSERT_SIGNED_EQ(dd_open_item(dd, "../", O_RDWR), -EINVAL);
    TS_ASSERT_SIGNED_EQ(dd_open_item(dd, "..//", O_RDWR), -EINVAL);
    TS_ASSERT_SIGNED_EQ(dd_open_item(dd, "/../", O_RDWR), -EINVAL);
    TS_ASSERT_SIGNED_EQ(dd_open_item(dd, "/.././", O_RDWR), -EINVAL);
    TS_ASSERT_SIGNED_EQ(dd_open_item(dd, "looks-good-but-evil/", O_RDWR), -EINVAL);
    TS_ASSERT_SIGNED_EQ(dd_open_item(dd, "looks-good-but-evil/../../", O_RDWR), -EINVAL);
    TS_ASSERT_SIGNED_EQ(dd_open_item(dd, "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890+-=", O_RDWR), -EINVAL);

    const int fd_rdonly_noent = dd_open_item(dd, "nofile", O_RDONLY);
    TS_ASSERT_SIGNED_LT(fd_rdonly_noent, 0);

    const int fd_wronly_noent = dd_open_item(dd, "nofile", O_RDWR);
    TS_ASSERT_SIGNED_GE(fd_wronly_noent, 0);
    if (g_testsuite_last_ok) {
        full_write_str(fd_wronly_noent, "fd_wronly_noent");
        close(fd_wronly_noent);

        char *const noent_contents = dd_load_text(dd, "nofile");
        TS_ASSERT_STRING_EQ(noent_contents, "fd_wronly_noent", "Successfully wrote data");
        free(noent_contents);
    }

    TS_ASSERT_SIGNED_EQ(dd_open_item(dd, "time", O_RDONLY | O_EXCL), -ENOTSUP);

    const int fd_rdonly_time = dd_open_item(dd, "time", O_RDONLY);
    TS_ASSERT_SIGNED_GE(fd_rdonly_time, 0);
    if (g_testsuite_last_ok) {
        char *time = dd_load_text(dd, "time");
        TS_ASSERT_PTR_IS_NOT_NULL(time);

        char rdonly_time_contents[16];
        int bytes_rdonly_time = full_read(fd_rdonly_time, rdonly_time_contents, sizeof(rdonly_time_contents));
        TS_ASSERT_SIGNED_GT(bytes_rdonly_time, 0);
        if (bytes_rdonly_time > 0) {
            rdonly_time_contents[bytes_rdonly_time] = '\0';
            TS_ASSERT_STRING_EQ(rdonly_time_contents, time, "Read only time");
        }
        else {
            TS_PRINTF("FD %d read error: %s\n", fd_rdonly_time, strerror(errno));
        }
        free(time);
        close(fd_rdonly_time);
    }

    TS_ASSERT_SIGNED_EQ(dd_open_item(dd, "time", O_RDWR | O_EXCL), -ENOTSUP);

    const int fd_rdwr_time = dd_open_item(dd, "time", O_RDWR);
    TS_ASSERT_SIGNED_GE(fd_rdwr_time, 0);
    if (g_testsuite_last_ok) {
        full_write_str(fd_rdwr_time, "7654321");

        TS_ASSERT_FUNCTION(lseek(fd_rdwr_time, 0, SEEK_SET));

        char rdwr_time_contents[16];
        int bytes_rdwr_time = full_read(fd_rdwr_time, rdwr_time_contents, sizeof(rdwr_time_contents));
        close(fd_rdwr_time);

        TS_ASSERT_SIGNED_GT(bytes_rdwr_time, 0);
        if (g_testsuite_last_ok) {
            rdwr_time_contents[bytes_rdwr_time] = '\0';

            char *const time_contents = dd_load_text(dd, "time");
            TS_ASSERT_STRING_EQ(rdwr_time_contents, "7654321", "Successfully wrote time data");
            TS_ASSERT_STRING_EQ(time_contents, "7654321", "Successfully wrote time data");
            TS_ASSERT_STRING_EQ(rdwr_time_contents, time_contents, "Read only time");
            free(time_contents);

        }
        else {
            TS_PRINTF("FD %d read error: %s\n", fd_rdwr_time, strerror(errno));
        }
    }

    testsuite_dump_dir_delete(dd);
}
TS_RETURN_MAIN
]])


## ----------------- ##
## dd_open_item_file ##
## ----------------- ##

AT_TESTFUN([dd_open_item_file], [[
#include "testsuite.h"
#include "testsuite_tools.h"

TS_MAIN
{
    struct dump_dir *dd = testsuite_dump_dir_create(-1, -1, 0);
    dd->dd_time = (time_t)1234567;
    dd_create_basic_files(dd, geteuid(), NULL);

    TS_ASSERT_PTR_IS_NULL(dd_open_item_file(dd, "", O_RDWR));
    TS_ASSERT_PTR_IS_NULL(dd_open_item_file(dd, "/", O_RDWR));
    TS_ASSERT_PTR_IS_NULL(dd_open_item_file(dd, "//", O_RDWR));
    TS_ASSERT_PTR_IS_NULL(dd_open_item_file(dd, "/a", O_RDWR));
    TS_ASSERT_PTR_IS_NULL(dd_open_item_file(dd, "a/", O_RDWR));
    TS_ASSERT_PTR_IS_NULL(dd_open_item_file(dd, ".", O_RDWR));
    TS_ASSERT_PTR_IS_NULL(dd_open_item_file(dd, "..", O_RDWR));
    TS_ASSERT_PTR_IS_NULL(dd_open_item_file(dd, "/.", O_RDWR));
    TS_ASSERT_PTR_IS_NULL(dd_open_item_file(dd, "//.", O_RDWR));
    TS_ASSERT_PTR_IS_NULL(dd_open_item_file(dd, "./", O_RDWR));
    TS_ASSERT_PTR_IS_NULL(dd_open_item_file(dd, ".//", O_RDWR));
    TS_ASSERT_PTR_IS_NULL(dd_open_item_file(dd, "/./", O_RDWR));
    TS_ASSERT_PTR_IS_NULL(dd_open_item_file(dd, "/..", O_RDWR));
    TS_ASSERT_PTR_IS_NULL(dd_open_item_file(dd, "//..", O_RDWR));
    TS_ASSERT_PTR_IS_NULL(dd_open_item_file(dd, "../", O_RDWR));
    TS_ASSERT_PTR_IS_NULL(dd_open_item_file(dd, "..//", O_RDWR));
    TS_ASSERT_PTR_IS_NULL(dd_open_item_file(dd, "/../", O_RDWR));
    TS_ASSERT_PTR_IS_NULL(dd_open_item_file(dd, "/.././", O_RDWR));
    TS_ASSERT_PTR_IS_NULL(dd_open_item_file(dd, "looks-good-but-evil/", O_RDWR));
    TS_ASSERT_PTR_IS_NULL(dd_open_item_file(dd, "looks-good-but-evil/../../", O_RDWR));
    TS_ASSERT_PTR_IS_NULL(dd_open_item_file(dd, "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890+-=", O_RDWR));

    TS_ASSERT_PTR_IS_NULL(dd_open_item_file(dd, "nofile", O_RDONLY));

    {
        FILE *const f_rdwr_noent = dd_open_item_file(dd, "nofile", O_RDWR);
        TS_ASSERT_PTR_IS_NOT_NULL(f_rdwr_noent);
        if (g_testsuite_last_ok) {
            fprintf(f_rdwr_noent, "%s", "f_rdwr_noent");
            rewind(f_rdwr_noent);

            char rdwr_contents[256];
            TS_ASSERT_PTR_IS_NOT_NULL(fgets(rdwr_contents, sizeof(rdwr_contents), f_rdwr_noent));
            TS_ASSERT_STRING_EQ(rdwr_contents, "f_rdwr_noent", "Successfully read data");
            fclose(f_rdwr_noent);

            char *const noent_contents = dd_load_text(dd, "nofile");
            TS_ASSERT_STRING_EQ(noent_contents, "f_rdwr_noent", "Successfully wrote data");
            free(noent_contents);
        }
    }

    TS_ASSERT_PTR_IS_NULL(dd_open_item_file(dd, "time", O_RDONLY | O_EXCL));

    {
        FILE *const f_rdonly_time = dd_open_item_file(dd, "time", O_RDONLY);
        TS_ASSERT_PTR_IS_NOT_NULL(f_rdonly_time);
        if (g_testsuite_last_ok) {
            char *time = dd_load_text(dd, "time");
            TS_ASSERT_PTR_IS_NOT_NULL(time);

            char rdonly_time_contents[16];
            char *const res = fgets(rdonly_time_contents, sizeof(rdonly_time_contents), f_rdonly_time);
            TS_ASSERT_PTR_EQ(rdonly_time_contents, res);
            if (g_testsuite_last_ok) {
                TS_ASSERT_STRING_EQ(rdonly_time_contents, time, "Read only time");
            }
            else {
                TS_PRINTF("File 'time' read error: %s\n", strerror(errno));
            }
            fclose(f_rdonly_time);
        }
    }

    TS_ASSERT_PTR_IS_NULL(dd_open_item_file(dd, "time", O_RDWR | O_EXCL));

    {
        FILE *const f_rdwr_time = dd_open_item_file(dd, "time", O_RDWR);
        TS_ASSERT_PTR_IS_NOT_NULL(f_rdwr_time);
        if (g_testsuite_last_ok) {
            fprintf(f_rdwr_time, "7654321");
            rewind(f_rdwr_time);

            char rdwr_contents[256];
            TS_ASSERT_PTR_IS_NOT_NULL(fgets(rdwr_contents, sizeof(rdwr_contents), f_rdwr_time));
            TS_ASSERT_STRING_EQ(rdwr_contents, "7654321", "Successfully read time data");
            fclose(f_rdwr_time);

            char *const time_contents = dd_load_text(dd, "time");
            TS_ASSERT_STRING_EQ(time_contents, "7654321", "Successfully wrote time data");
            free(time_contents);
        }
    }

    testsuite_dump_dir_delete(dd);
}
TS_RETURN_MAIN
]])