/*
    Copyright (C) 2009  Zdenek Prikryl (zprikryl@redhat.com)
    Copyright (C) 2009  RedHat inc.

    This program is free software; you can redistribute it and/or modify
    it under the terms of the GNU General Public License as published by
    the Free Software Foundation; either version 2 of the License, or
    (at your option) any later version.

    This program is distributed in the hope that it will be useful,
    but WITHOUT ANY WARRANTY; without even the implied warranty of
    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    GNU General Public License for more details.

    You should have received a copy of the GNU General Public License along
    with this program; if not, write to the Free Software Foundation, Inc.,
    51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*/
#include <sys/utsname.h>
#include <libtar.h>
#include "internal_libreport.h"

// Locking logic:
//
// The directory is locked by creating a symlink named .lock inside it,
// whose value (where it "points to") is the pid of locking process.
// We use symlink, not an ordinary file, because symlink creation
// is an atomic operation.
//
// There are two cases where after .lock creation, we might discover
// that directory is not really free:
// * another process just created new directory, but didn't manage
//   to lock it before us.
// * another process is deleting the directory, and we managed to sneak in
//   and create .lock after it deleted all files (including .lock)
//   but before it rmdir'ed the empty directory.
//
// Both these cases are detected by the fact that file named "time"
// is not present (it must be present in any valid dump dir).
// If after locking the dir we don't see time file, we remove the lock
// at once and back off. What happens in concurrent processes
// we interfered with?
// * "create new dump dir" process just re-tries locking.
// * "delete dump dir" process just retries rmdir.
//
// There is another case when we don't find time file:
// when the directory is not really a *dump* dir - user gave us
// an ordinary directory name by mistake.
// We detect it by bailing out of "lock, check time file; sleep
// and retry if it doesn't exist" loop using a counter.
//
// To make locking work reliably, it's important to set timeouts
// correctly. For example, dd_create should retry locking
// its newly-created directory much faster than dd_opendir
// tries to lock the directory it tries to open.


// How long to sleep between "symlink fails with EEXIST,
// readlink fails with ENOENT" tries. Someone just unlocked the dir.
// We never bail out in this case, we retry forever.
// The value can be really small:
#define SYMLINK_RETRY_USLEEP           (10*1000)

// How long to sleep when lock file with valid pid is seen by dd_opendir
// (we are waiting for other process to unlock or die):
#define WAIT_FOR_OTHER_PROCESS_USLEEP (500*1000)

// How long to sleep when lock file with valid pid is seen by dd_create
// (some idiot jumped the gun and locked the dir we just created).
// Must not be the same as WAIT_FOR_OTHER_PROCESS_USLEEP (we depend on this)
// and should be small (we have the priority in locking, this is OUR dir):
#define CREATE_LOCK_USLEEP             (10*1000)

// How long to sleep after we locked a dir, found no time file
// (either we are racing with someone, or it's not a dump dir)
// and unlocked it;
// and after how many tries to give up and declare it's not a dump dir:
#define NO_TIME_FILE_USLEEP            (50*1000)
#define NO_TIME_FILE_COUNT                   10

// How long to sleep after we unlocked an empty dir, but then rmdir failed
// (some idiot jumped the gun and locked the dir we are deleting);
// and after how many tries to give up:
#define RMDIR_FAIL_USLEEP              (10*1000)
#define RMDIR_FAIL_COUNT                     50

// A sub-directory of a dump directory where the meta-data such as owner are
// stored. The meta-data directory must have same owner, group and mode as its
// parent dump directory. It is not a fatal error, if the meta-data directory
// does not exist (backward compatibility).
#define META_DATA_DIR_NAME             ".libreport"
#define META_DATA_FILE_OWNER           "owner"

enum {
    /* Try to create meta-data dir if it does not exist */
    DD_MD_GET_CREATE = 1 << 0,
};

// a little trick to copy read bits from file mode to exec bit of dir mode
// * mode of dump directory is in the form of 640 (no X) because we create
//   files more often then we play with directories
// * so if we want to get real mode of the directory we have to copy the read
//   bits
#define DD_MODE_TO_DIR_MODE(mode) ((mode) | (((mode) & 0444) >> 2))

/* Owner of trusted elements */
uid_t dd_g_super_user_uid = 0;

/* Group of new dump directories */
gid_t dd_g_fs_group_gid = (gid_t)-1;


char *load_text_file(const char *path, unsigned flags);
static char *load_text_file_at(int dir_fd, const char *name, unsigned flags);
static void copy_file_from_chroot(struct dump_dir* dd, const char *name,
        const char *chroot_dir, const char *file_path);
static bool save_binary_file_at(int dir_fd, const char *name, const char* data,
        unsigned size, uid_t uid, gid_t gid, mode_t mode);

static bool isdigit_str(const char *str)
{
    do
    {
        if (*str < '0' || *str > '9') return false;
        str++;
    } while (*str);
    return true;
}

static bool exist_file_dir_at(int dir_fd, const char *name)
{
    struct stat buf;
    if (fstatat(dir_fd, name, &buf, AT_SYMLINK_NOFOLLOW) == 0)
    {
        if (S_ISDIR(buf.st_mode) || S_ISREG(buf.st_mode))
        {
            return true;
        }
    }
    return false;
}

/* A valid dump dir element name is correct filename and is not a name of any
 * internal file or directory.
 */
#define dd_validate_element_name(name) \
    (str_is_correct_filename(name) && (strcmp(META_DATA_DIR_NAME, name) != 0))

/* nobody user should not own any file */
static int get_no_owner_uid(uid_t *uid)
{
    struct passwd *pw = getpwnam("nobody");
    if (pw == NULL)
    {
        perror_msg("can't get nobody's uid");
        if (errno == 0)
            return -ENOENT;
        return -errno;
    }

    *uid = pw->pw_uid;
    return 0;
}

/* Opens the file in the three following steps:
 * 1. open the file with O_PATH (get a file descriptor for operations with
 *    inode) and O_NOFOLLOW (do not dereference symbolick links)
 * 2. stat the resulting file descriptor and fail if the opened file is not a
 *    regular file or if the number of links is greater than 1 (that means that
 *    the inode has more names (hard links))
 * 3. "re-open" the file descriptor retrieved in the first step with O_RDONLY
 *    by opening /proc/self/fd/$fd (then close the former file descriptor and
 *    return the new one).
 */
int secure_openat_read(int dir_fd, const char *filename)
{
    if (strchr(filename, '/'))
    {
        error_msg("Path must be file name without directory: '%s'", filename);
        return -EFAULT;
    }

    static char reopen_buf[sizeof("/proc/self/fd/") + 3*sizeof(int) + 1];

    int path_fd = openat(dir_fd, filename, O_PATH | O_NOFOLLOW);
    if (path_fd < 0)
        return -errno;

    struct stat path_sb;
    int r = fstat(path_fd, &path_sb);
    if (r < 0)
    {
        perror_msg("stat");
        close(path_fd);
        return -EINVAL;
    }

    if (!S_ISREG(path_sb.st_mode) || path_sb.st_nlink > 1)
    {
        log_notice("Path isn't a regular file or has more links (%lu)", (unsigned long)path_sb.st_nlink);
        close(path_fd);
        return -EINVAL;
    }

    if (snprintf(reopen_buf, sizeof(reopen_buf), "/proc/self/fd/%d", path_fd) >= sizeof(reopen_buf)) {
        error_msg("BUG: too long path to a file descriptor");
        abort();
    }

    const int fd = open(reopen_buf, O_RDONLY);
    close(path_fd);

    return fd;
}

static int read_number_from_file_at(int dir_fd, const char *filename, const char *typename,
        size_t typesz, unsigned long long min, unsigned long long max, unsigned long long *value)
{
    const int fd = secure_openat_read(dir_fd, filename);
    if (fd < 0)
    {
        log_info("Can't open '%s'", filename);
        return fd;
    }

    int ret = 0;
    /* - xmalloc_read() does not count '\0' Byte
     * - count on sign
     * - count on '\n'
     */
    const size_t max_size = typesz * 3 + 2;
    /* allow to read one extra Byte to be able to identify longer text */
    size_t total_read = max_size + 1;
    char *const value_buf = xmalloc_read(fd, &total_read);
    /* Just reading, so no need to check the returned value. */

    if (value_buf == NULL)
    {
        log_info("Can't read from '%s'", filename);
        ret = -EBADFD;
        goto finito;
    }

    if (total_read >= max_size)
    {
        log_info("File '%s' is too long to be valid %s "
                   "(max size %u)", filename, typename, (int)sizeof(value_buf));
        ret = -EMSGSIZE;
        goto finito;
    }

    /* Our tools don't put trailing newline into one line files,
     * but we allow such format too:
     */
    if (value_buf[total_read - 1] == '\n')
        --total_read;
    value_buf[total_read] = '\0';

    const int neg = (value_buf[0] == '-');
    /* if min equals 0, then we shall be to converting an unsigned number */
    if (neg && 0 == min)
    {
        log_info("File '%s' contains a negative number ('%s')", filename, value_buf);
        ret = -ERANGE;
        goto finito;
    }

    errno = 0;    /* To distinguish success/failure after call */
    char *endptr;
    const unsigned long long res = strtoull(value_buf, &endptr, /* base */ 10);

    /* Check for various possible errors */
    if (errno == ERANGE && (res == 0 || res == ULLONG_MAX))
    {
        log_info("File '%s' contains a number out-of-range of %s "
                        "('%s')", filename, typename, value_buf);
        ret = -ERANGE;
        goto finito;
    }

    if (   (errno != 0 && res == 0)
        || (*endptr != '\0')
        || endptr == value_buf
    ) {
        log_info("File '%s' doesn't contain valid %s "
                        "('%s')", filename, typename, value_buf);
        ret = -EINVAL;
        goto finito;
    }

    if ((neg ? res < min : res > max))
    {
        log_info("File '%s' contains a number ('%s') %s of %s",
                        filename, value_buf,
                        neg ? "lower than minimum" : "greater than maximum",
                        typename);
        ret = -ERANGE;
        goto finito;
    }

    *value = res;

finito:
    close(fd);
    free(value_buf);
    /* If we got here, strtoll() successfully parsed a number */
    return ret;
}

/* Returns value less than 0 if the file is not readable or
 * if the file doesn't contain valid unixt time stamp.
 *
 * Any possible failure will be logged.
 */
static time_t parse_time_file_at(int dir_fd, const char *filename)
{
    /* Note that on some architectures (x32) time_t is "long long" */
    const long long MAX_TIME_T = (1ULL << (sizeof(time_t)*8 - 1)) - 1;
    unsigned long long value = (time_t)-1;
    read_number_from_file_at(dir_fd, filename, "unix time stamp", sizeof(time_t), 0, MAX_TIME_T, &value);
    return (time_t)value;
}

/* Return values:
 * -1: error (in this case, errno is 0 if error message is already logged)
 *  0: failed to lock (someone else has it locked)
 *  1: success
 */
int create_symlink_lockfile_at(int dir_fd, const char* lock_file, const char* pid)
{
    while (symlinkat(pid, dir_fd, lock_file) != 0)
    {
        if (errno != EEXIST)
        {
            if (errno != ENOENT && errno != ENOTDIR && errno != EACCES)
            {
                perror_msg("Can't create lock file '%s'", lock_file);
                errno = 0;
            }
            return -1;
        }

        char pid_buf[sizeof(pid_t)*3 + 4];
        ssize_t r = readlinkat(dir_fd, lock_file, pid_buf, sizeof(pid_buf) - 1);
        if (r < 0)
        {
            if (errno == ENOENT)
            {
                /* Looks like lock_file was deleted */
                usleep(SYMLINK_RETRY_USLEEP); /* avoid CPU eating loop */
                continue;
            }
            perror_msg("Can't read lock file '%s'", lock_file);
            errno = 0;
            return -1;
        }
        pid_buf[r] = '\0';

        if (strcmp(pid_buf, pid) == 0)
        {
            log_warning("Lock file '%s' is already locked by us", lock_file);
            errno = EALREADY;
            return 0;
        }
        if (isdigit_str(pid_buf))
        {
            char pid_str[sizeof("/proc/") + sizeof(pid_buf)];
            snprintf(pid_str, sizeof(pid_str), "/proc/%s", pid_buf);
            if (access(pid_str, F_OK) == 0)
            {
                log_warning("Lock file '%s' is locked by process %s", lock_file, pid_buf);
                return 0;
            }
            log_warning("Lock file '%s' was locked by process %s, but it crashed?", lock_file, pid_buf);
        }
        /* The file may be deleted by now by other process. Ignore ENOENT */
        if (unlinkat(dir_fd, lock_file, /*only files*/0) != 0 && errno != ENOENT)
        {
            perror_msg("Can't remove stale lock file '%s'", lock_file);
            errno = 0;
            return -1;
        }
    }

    log_info("Locked '%s'", lock_file);
    return 1;
}

int create_symlink_lockfile(const char *filename, const char *pid_str)
{
    return create_symlink_lockfile_at(AT_FDCWD, filename, pid_str);
}

static const char *dd_check(struct dump_dir *dd)
{
    dd->dd_time = parse_time_file_at(dd->dd_fd, FILENAME_TIME);
    if (dd->dd_time < 0)
    {
        log_debug("Missing file: "FILENAME_TIME);
        return FILENAME_TIME;
    }

    /* Do not warn about missing 'type' file in non-verbose modes.
     *
     * Handling of FILENAME_TYPE should be consistent with handling of
     * FILENAME_TIME in the function parse_time_file_at() where the missing
     * file message is printed by log_info() (in a verbose mode).
     */
    int load_flags = DD_LOAD_TEXT_RETURN_NULL_ON_FAILURE;
    if (g_verbose < 2) load_flags |= DD_FAIL_QUIETLY_ENOENT;

    dd->dd_type = load_text_file_at(dd->dd_fd, FILENAME_TYPE, load_flags);
    if (!dd->dd_type || (strlen(dd->dd_type) == 0))
    {
        log_debug("Missing or empty file: "FILENAME_TYPE);
        return FILENAME_TYPE;
    }

    return NULL;
}

static int dd_lock(struct dump_dir *dd, unsigned sleep_usec, int flags)
{
    if (dd->locked)
        error_msg_and_die("Locking bug on '%s'", dd->dd_dirname);

    char pid_buf[sizeof(long)*3 + 2];
    snprintf(pid_buf, sizeof(pid_buf), "%lu", (long)getpid());

    unsigned count = NO_TIME_FILE_COUNT;

 retry:
    while (1)
    {
        int r = create_symlink_lockfile_at(dd->dd_fd, ".lock", pid_buf);
        if (r < 0)
            return r; /* error */
        if (r > 0 || errno == EALREADY)
            break; /* locked successfully */
        if (flags & DD_DONT_WAIT_FOR_LOCK)
        {
            errno = EAGAIN;
            return -1;
        }
        /* Other process has the lock, wait for it to go away */
        usleep(sleep_usec);
    }

    /* Reset errno to 0 only if errno is EALREADY (used by
     * create_symlink_lockfile() to signal that the dump directory is already
     * locked by us) */
    if (!(dd->owns_lock = (errno != EALREADY)))
        errno = 0;

    /* Are we called by dd_opendir (as opposed to dd_create)? */
    if (sleep_usec == WAIT_FOR_OTHER_PROCESS_USLEEP) /* yes */
    {
        const char *missing_file = dd_check(dd);
        /* some of the required files don't exist. We managed to lock the directory
         * which was just created by somebody else, or is almost deleted
         * by delete_file_dir.
         * Unlock and back off.
         */
        if (missing_file)
        {
            if (dd->owns_lock)
                xunlinkat(dd->dd_fd, ".lock", /*only files*/0);

            log_notice("Unlocked '%s' (no or corrupted '%s' file)", dd->dd_dirname, missing_file);
            if (--count == 0 || flags & DD_DONT_WAIT_FOR_LOCK)
            {
                errno = EISDIR; /* "this is an ordinary dir, not dump dir" */
                return -1;
            }
            usleep(NO_TIME_FILE_USLEEP);
            goto retry;
        }
    }

    dd->locked = true;
    return 0;
}

static void dd_unlock(struct dump_dir *dd)
{
    if (dd->locked)
    {
        if (dd->owns_lock)
            xunlinkat(dd->dd_fd, ".lock", /*only files*/0);

        dd->owns_lock = 0;
        dd->locked = 0;

        log_info("Unlocked '%s/.lock'", dd->dd_dirname);
    }
}

static inline struct dump_dir *dd_init(void)
{
    struct dump_dir* dd = (struct dump_dir*)xzalloc(sizeof(struct dump_dir));
    dd->dd_time = (time_t)-1;
    dd->dd_fd = -1;
    dd->dd_md_fd = -1;
    return dd;
}

int dd_exist(const struct dump_dir *dd, const char *name)
{
    if (!dd_validate_element_name(name))
        error_msg_and_die("Cannot test existence. '%s' is not a valid file name", name);

    const int ret = exist_file_dir_at(dd->dd_fd, name);
    return ret;
}

static void dd_close_meta_data_dir(struct dump_dir *dd)
{
    if (dd->dd_md_fd < 0)
        return;

    close(dd->dd_md_fd);
    dd->dd_md_fd = -1;
}

void dd_close(struct dump_dir *dd)
{
    if (!dd)
        return;

    dd_unlock(dd);

    if (dd->dd_fd >= 0)
        close(dd->dd_fd);

    dd_close_meta_data_dir(dd);

    dd_clear_next_file(dd);

    free(dd->dd_type);
    free(dd->dd_dirname);
    free(dd);
}

static int dd_create_subdir(int dd_fd, const char *dirname, uid_t dd_uid, gid_t dd_gid, mode_t dd_mode)
{
    if (mkdirat(dd_fd, dirname, dd_mode) < 0)
    {
        perror_msg("Can't create directory '%s'", dirname);
        return -1;
    }

    int dd_md_fd = openat(dd_fd, dirname, O_DIRECTORY | O_NOFOLLOW);
    if (dd_md_fd < 0)
    {
        perror_msg("Can't open newly created directory '%s'", dirname);
        goto fail_open;
    }

    if (dd_uid != (uid_t)-1)
    {
        if (fchown(dd_md_fd, dd_uid, dd_gid) != 0)
        {
            perror_msg("Can't change owner and group of '%s'", dirname);
            goto fail_modify;
        }
    }

    /* mkdir's mode (above) can be affected by umask, fix it */
    if (fchmod(dd_md_fd, dd_mode) == -1)
    {
        perror_msg("Can't change mode of '%s'", dirname);
        goto fail_modify;
    }

    return dd_md_fd;
fail_modify:
    close(dd_md_fd);
fail_open:
    if (unlinkat(dd_fd, dirname, AT_REMOVEDIR) < 0)
        perror_msg("Fialed to unlink '%s' while cleaning up after failure", dirname);
    return -1;
}

/* Opens the meta-data directory, checks its file system attributes and returns
 * its file descriptor.
 *
 * The meta-data directory must have the same file system attributes as the
 * parent dump directory in order to avoid unexpected situations and detects
 * program errors (it is an error to modify bits of the dump directory and
 * forgot to update the meta-data directory).
 *
 * Keep on mind that the old dump directories might miss the meta-data directory
 * so the return value -ENOENT does not necessarily need to be fatal.
 */
static int dd_open_meta_data_dir(struct dump_dir *dd)
{
    int md_dir_fd = openat(dd->dd_fd, META_DATA_DIR_NAME, O_DIRECTORY | O_NOFOLLOW);
    if (md_dir_fd < 0)
    {
        md_dir_fd = -errno;

        /* ENOENT is not critical */
        if (errno != ENOENT)
            log_warning("Can't open meta-data '"META_DATA_DIR_NAME"'");
        else
            log_info("The dump dir doesn't contain '"META_DATA_DIR_NAME"'");

        goto finito;
    }

    struct stat md_sb;
    if (fstat(md_dir_fd, &md_sb) < 0)
    {
        log_debug("Can't stat '"META_DATA_DIR_NAME"'");
        goto fail;
    }

    /* Test only permission bits, ignore SUID, SGID, etc. */
    const mode_t md_mode = md_sb.st_mode & 0777;
    const mode_t dd_mode = DD_MODE_TO_DIR_MODE(dd->mode);

    if (   md_sb.st_uid != dd->dd_uid
        || md_sb.st_gid != dd->dd_gid
        || md_mode != dd_mode)
    {
        log_debug("'"META_DATA_DIR_NAME"' has different attributes than the dump dir, '%d'='%d', '%d'='%d', %o = %o",
                        md_sb.st_uid, dd->dd_uid, md_sb.st_gid, dd->dd_gid, md_mode, dd_mode);
        goto fail;
    }

finito:
    return md_dir_fd;

fail:
    close(md_dir_fd);

    return -EINVAL;
}

/* Returns a file descriptor to the meta-data directory. Can be configured to
 * create the directory if it does not exist.
 *
 * This function enables lazy initialization of the meta-data directory.
 */
static int dd_get_meta_data_dir_fd(struct dump_dir *dd, int flags)
{
    if (dd->dd_md_fd < 0)
    {
        dd->dd_md_fd = dd_open_meta_data_dir(dd);

        if (    dd->dd_md_fd == -ENOENT
             && (flags & DD_MD_GET_CREATE))
        {
            dd->dd_md_fd = dd_create_subdir(dd->dd_fd,
                                            META_DATA_DIR_NAME,
                                            dd->dd_uid,
                                            dd->dd_gid,
                                            DD_MODE_TO_DIR_MODE(dd->mode));
        }
    }

    return dd->dd_md_fd;
}

/* Tries to safely overwrite the existing file.
 *
 * The functions writes the new value to a temporary file and if the temporary
 * file is successfully created, then moves the tmp file to the old file name.
 *
 * If the meta-data directory does not exist, the function will try to create
 * it.
 */
static int dd_meta_data_save_text(struct dump_dir *dd, const char *name, const char *data)
{
    if (!dd->locked)
        error_msg_and_die("dump_dir is not opened"); /* bug */

    if (!str_is_correct_filename(name))
        error_msg_and_die("Cannot save meta-data. '%s' is not a valid file name", name);

    int dd_md_fd = dd_get_meta_data_dir_fd(dd, DD_MD_GET_CREATE);
    if (dd_md_fd < 0)
    {
        error_msg("Can't save meta-data: '%s'", name);
        return dd_md_fd;
    }

    char *tmp_name = xasprintf("~%s.tmp", name);

    int ret = -1;
    if (!save_binary_file_at(dd_md_fd, tmp_name, data, strlen(data), dd->dd_uid, dd->dd_gid, dd->mode))
        goto finito;

    /* man 2 rename
     *
     * If newpath  already exists it will be atomically replaced (subject to a
     * few conditions; see ERRORS below), so that there is no point at which
     * another process attempting to access newpath will find it missing.
     */
    if (renameat(dd_md_fd, tmp_name, dd_md_fd, name))
    {
        ret = -errno;
        perror_msg("Failed to move temporary file '%s' to '%s'", tmp_name, name);
        goto finito;
    }

    ret = 0;

finito:
    free(tmp_name);
    return ret;
}

int dd_set_owner(struct dump_dir *dd, uid_t owner)
{
    /* I was tempted to use the keyword static, but we should have reentracy
     * always on mind. Who knows! */
    char long_str[sizeof(long) * 3 + 2];

    if (owner == (uid_t)-1)
        owner = dd->dd_uid;

    snprintf(long_str, sizeof(long_str), "%li", (long)owner);
    const int ret = dd_meta_data_save_text(dd, META_DATA_FILE_OWNER, long_str);
    if (ret < 0)
        error_msg("The dump dir owner wasn't set to '%s'", long_str);
    return ret;
}

int dd_set_no_owner(struct dump_dir *dd)
{
    uid_t no_owner_uid = (uid_t)-1;
    int ret = get_no_owner_uid(&no_owner_uid);
    if (ret < 0)
        return ret;

    return dd_set_owner(dd, no_owner_uid);
}

uid_t dd_get_owner(struct dump_dir *dd)
{
    static const long long MAX_UID_T = (1ULL << (sizeof(uid_t)*8 - 1)) - 1;

    int dd_md_fd = dd_get_meta_data_dir_fd(dd, /*no create*/0);
    if (dd_md_fd < 0)
    {
        log_info("No meta-data, using fs owner.");
        return dd->dd_uid;
    }

    unsigned long long owner = 0;

    int ret = read_number_from_file_at(dd_md_fd, META_DATA_FILE_OWNER, "UID",
                                       sizeof(uid_t), 0, MAX_UID_T, &owner);

    if (ret < 0)
    {
        if (ret != -ENOENT)
            return ret;

        log_info("No meta-data 'owner', using fs owner.");
        return dd->dd_uid;
    }

    return (uid_t)owner;
}

time_t dd_get_first_occurrence(struct dump_dir *dd)
{
    if (dd->dd_time == (time_t)-1)
    {
        log_debug("The dump directory wasn't opened");
        errno = ENODATA;
    }

    return dd->dd_time;
}

time_t dd_get_last_occurrence(struct dump_dir *dd)
{
    const time_t first_occurrence = dd_get_first_occurrence(dd);
    if (first_occurrence == (time_t)-1)
        return -1;

    /* Do not touch errno in case of errors, because this function claims
     * to set errno only to ENODATA */
    const int old_errno = errno;
    time_t last_occurrence = parse_time_file_at(dd->dd_fd, FILENAME_LAST_OCCURRENCE);

    if (last_occurrence < first_occurrence)
    {
        log_info("Adapting '%s' to monotonic time (+%lds)",
                 FILENAME_LAST_OCCURRENCE, first_occurrence - last_occurrence);
        last_occurrence = first_occurrence;
    }
    else if (last_occurrence < 0)
        last_occurrence = first_occurrence;

    errno = old_errno;
    return last_occurrence;
}

/* A helper function useful for traversing directories.
 *
 * DIR* d opendir(dir_fd); ... closedir(d); closes also dir_fd but we want to
 * keep it opened.
 */
static int fdreopen(int dir_fd, DIR **d)
{
    int opendir_fd = dup(dir_fd);
    if (opendir_fd < 0)
    {
        perror_msg("dup(dir_fd)");
        return -EBADFD;
    }

    lseek(opendir_fd, SEEK_SET, 0);
    *d = fdopendir(opendir_fd);
    if (!*d)
    {
        int ret = -errno;
        close(opendir_fd);
        perror_msg("fdopendir(dir_fd)");
        return ret;
    }

    /* 'opendir_fd' will be closed with 'd' */
    return 0;
}

/* A macro for going through the entries of a directory referenced as a file
 * descriptor.
 *
 * Usage:
 *
 * FOREACH_REGULAR_FILE_AS_FD_AT_BEGIN(dir_fd)
 * {
 *      printf("Short name '%s'",    dent->d_name);
 *      printf("File descriptor %d", fd);
 * }
 * FOREACH_REGULAR_FILE_AS_FD_AT_END
 */

#define FOREACH_REGULAR_FILE_AS_FD_AT_BEGIN(dir_fd) \
    DIR *d; \
    struct dirent *dent; \
    if (fdreopen(dir_fd, &d) < 0) return -1; \
    while ((dent = readdir(d)) != NULL) \
    { \
        if (dot_or_dotdot(dent->d_name)) continue; \
        int fd = secure_openat_read(dirfd(d), dent->d_name); \
        if (fd >= 0)

#define FOREACH_REGULAR_FILE_AS_FD_AT_END \
        close(fd); \
    } \
    closedir(d);


/* Sets attributes of the meta-data directory and its contents to the same
 * attributes of the parent dump directory.
 */
static int dd_sanitize_mode_meta_data(struct dump_dir *dd)
{
    if (!dd->locked)
        error_msg_and_die("%s: dump_dir is not opened", __func__); /* bug */

    int dd_md_fd = dd_get_meta_data_dir_fd(dd, /*no create*/0);
    if (dd_md_fd < 0)
        return 0;

    int res = fchmod(dd_md_fd, DD_MODE_TO_DIR_MODE(dd->mode));
    if (res < 0)
    {
        perror_msg("Failed to chmod meta-data sub-dir");
        return res;
    }

    FOREACH_REGULAR_FILE_AS_FD_AT_BEGIN(dd_md_fd)
    {
        log_debug("chmoding %s", dent->d_name);

        res = fchmod(fd, dd->mode);
        if (res)
        {
            perror_msg("fchmod('%s')", dent->d_name);
            break;
        }
    }
    FOREACH_REGULAR_FILE_AS_FD_AT_END

    return 0;
}

/* Sets owner and group of the meta-data directory and its contents to the same
 * attributes of the parent dump directory.
 */

static int dd_chown_meta_data(struct dump_dir *dd, uid_t uid, gid_t gid)
{
    if (!dd->locked)
        error_msg_and_die("%s: dump_dir is not opened", __func__); /* bug */

    int dd_md_fd = dd_get_meta_data_dir_fd(dd, /*no create*/0);
    if (dd_md_fd < 0)
        return 0;

    int res = fchown(dd_md_fd, uid, gid);
    if (res < 0)
    {
        perror_msg("Failed to chown meta-data sub-dir");
        return res;
    }

    FOREACH_REGULAR_FILE_AS_FD_AT_BEGIN(dd_md_fd)
    {
        log_debug("%s: chowning %s", __func__, dent->d_name);

        res = fchown(fd, uid, gid);
        if (res)
        {
            perror_msg("fchown('%s')", dent->d_name);
            break;
        }
    }
    FOREACH_REGULAR_FILE_AS_FD_AT_END

    return res;
}

static char* rm_trailing_slashes(const char *dir)
{
    unsigned len = strlen(dir);
    while (len != 0 && dir[len-1] == '/')
        len--;
    return xstrndup(dir, len);
}

static struct dump_dir *dd_do_open(struct dump_dir *dd, const char *dir, int flags)
{
    if (dir != NULL)
    {
        dd->dd_dirname = rm_trailing_slashes(dir);
        /* dd_do_open validates dd_fd */
        dd->dd_fd = open(dd->dd_dirname, O_DIRECTORY | O_NOFOLLOW);

        struct stat stat_buf;
        if (dd->dd_fd < 0)
            goto cant_access;
        if (fstat(dd->dd_fd, &stat_buf) != 0)
            goto cant_access;

        /* & 0666 should remove the executable bit */
        dd->mode = (stat_buf.st_mode & 0666);

        /* We want to have dd_uid and dd_gid always initialized. But we have to
         * initialize it in the way which does not prevent non-privileged user
         * from saving data in their dump directories.
         *
         * Non-privileged users are not allowed to change the group to
         * 'abrt' so we have to use their GID.
         *
         * If the caller is super-user, we have to use dd's fs owner and fs
         * group, because he can do everything and the data must be readable by
         * the real owner.
         *
         * We always use fs uid, because non-privileged users must own the
         * directory and super-user must use fs owner.
         */
        dd->dd_uid = stat_buf.st_uid;

        /* We use fs group only if the caller is super-user, because we want to
         * make sure non-privileged users can modify elements (libreport call
         * chown(dd_uid, dd_gid) after modifying an element) and the modified
         * elements do not have super-user's group.
         */
        dd->dd_gid = getegid();
        if (geteuid() == 0)
            dd->dd_gid = stat_buf.st_gid;

        if ((flags & DD_OPEN_FD_ONLY))
        {
            dd->dd_md_fd = dd_open_meta_data_dir(dd);
            return dd;
        }
    }

    errno = 0;
    if (dd_lock(dd, WAIT_FOR_OTHER_PROCESS_USLEEP, flags) < 0)
    {
        if (errno == EISDIR)
        {
            /* EISDIR: dd_lock can lock the dir, but it sees no time file there,
             * even after it retried many times. It must be an ordinary directory!
             *
             * Without this check, e.g. abrt-action-print happily prints any current
             * directory when run without arguments, because its option -d DIR
             * defaults to "."!
             */
            error_msg("'%s' is not a problem directory", dd->dd_dirname);
            goto fail_with_close;
        }

        if (errno == EAGAIN && (flags & DD_DONT_WAIT_FOR_LOCK))
        {
            log_debug("Can't access locked directory '%s'", dd->dd_dirname);
            goto fail_with_close;
        }

        if (!(flags & DD_OPEN_READONLY))
        {
            log_debug("'%s' can't be opened for writing", dd->dd_dirname);
            goto fail_with_close;
        }

        if (errno != EACCES)
        {
            VERB3 perror_msg("failed to lock dump directory '%s'", dd->dd_dirname);
            goto fail_with_close;
        }

        /* Directory is not writable. If it seems to be readable,
         * return "read only" dd, not NULL
         *
         * Does the directory have 'r' flag?
         */
        if (faccessat(dd->dd_fd, ".", R_OK, AT_SYMLINK_NOFOLLOW) != 0)
        {
            VERB3 perror_msg("failed to lock dump directory '%s'", dd->dd_dirname);
            goto fail_with_close;
        }

        /* dd_check prints out good log messages */
        if(dd_check(dd) != NULL)
            goto fail_with_close;

        /* The dd is opened in READONLY moded, continue.*/
    }

    return dd;

cant_access:
    if (errno == ENOENT || errno == ENOTDIR)
    {
        if (!(flags & DD_FAIL_QUIETLY_ENOENT))
            error_msg("'%s' does not exist", dd->dd_dirname);
    }
    else
    {
        if (!(flags & DD_FAIL_QUIETLY_EACCES))
            perror_msg("Can't access '%s'", dd->dd_dirname);
    }

fail_with_close:
    dd_close(dd);
    return NULL;
}

struct dump_dir *dd_fdopendir(struct dump_dir *dd, int flags)
{
    if ((flags & DD_OPEN_FD_ONLY))
        error_msg_and_die("the passed flags must not contain DD_OPEN_FD_ONLY");

    if (dd->dd_fd < 0)
        error_msg_and_die("the dump directory was not initialized yet");

    if (dd->locked)
        error_msg_and_die("the dump directory is already locked");

    return dd_do_open(dd, NULL, flags);
}

struct dump_dir *dd_opendir(const char *dir, int flags)
{
    struct dump_dir *dd = dd_init();
    return dd_do_open(dd, dir, flags);
}

/* Create a fresh empty debug dump dir which is owned bu the calling user. If
 * you want to create the directory with meaningful ownership you should
 * consider using dd_create() function or you can modify the ownership
 * afterwards by calling dd_reset_ownership() function.
 *
 * ABRT owns dump dir:
 *   We should not allow users to write new files or write into existing ones,
 *   but they should be able to read them.
 *
 *   We set dir's gid to passwd(uid)->pw_gid parameter, and we set uid to
 *   abrt's user id. We do not allow write access to group. We can't set dir's
 *   uid to crashed applications's user uid because owner can modify dir's
 *   mode and ownership.
 *
 *   Advantages:
 *   Safeness
 *
 *   Disadvantages:
 *   This approach leads to stealing of directories because events requires
 *   write access to a dump directory and events are run under non root (abrt)
 *   user while reporting.
 *
 *   This approach allows group members to see crashes of other members.
 *   Institutions like schools uses one common group for all students.
 *
 * User owns dump dir:
 *   We grant ownership of dump directories to the user (read/write access).
 *
 *   We set set dir's uid to crashed applications's user uid, and we set gid to
 *   abrt's group id. We allow write access to group because we want to allow
 *   abrt binaries to process dump directories.
 *
 *   Advantages:
 *   No disadvantages from the previous approach
 *
 *   Disadvantages:
 *   In order to protect the system dump directories must be saved on
 *   noncritical filesystem (e.g. /tmp or /var/tmp).
 *
 *
 * @param uid
 *   Crashed application's User Id
 *
 * We currently have only three callers:
 *  kernel oops hook: uid -> not saved, so everyone can steal and work with it
 *   this hook runs under 0:0
 *
 *  ccpp hook: uid=uid of crashed user's binary
 *   this hook runs under 0:0
 *
 *  create_dump_dir_from_problem_data() function:
 *   Currently known callers:
 *    abrt server: uid=uid of user's executable
 *     this runs under 0:0
 *     - clinets: python hook, ruby hook
 *    abrt dbus: uid=uid of user's executable
 *     this runs under 0:0
 *     - clients: setroubleshootd, abrt python
 */
struct dump_dir *dd_create_skeleton(const char *dir, uid_t uid, mode_t mode, int flags)
{
    mode_t dir_mode = DD_MODE_TO_DIR_MODE(mode);
    struct dump_dir *dd = dd_init();

    dd->mode = mode;

    /* Unlike dd_opendir, can't use realpath: the directory doesn't exist yet,
     * realpath will always return NULL. We don't really have to:
     * dd_opendir(".") makes sense, dd_create(".") does not.
     */
    dir = dd->dd_dirname = rm_trailing_slashes(dir);

    const char *last_component = strrchr(dir, '/');
    if (last_component)
        last_component++;
    else
        last_component = dir;
    if (dot_or_dotdot(last_component))
    {
        /* dd_create("."), dd_create(".."), dd_create("dir/."),
         * dd_create("dir/..") and similar are madness, refuse them.
         */
        error_msg("Bad dir name '%s'", dir);
        goto fail;
    }

    /* Was creating it with mode 0700 and user as the owner, but this allows
     * the user to replace any file in the directory, changing security-sensitive data
     * (e.g. "uid", "analyzer", "executable")
     */
    int r;
    if ((flags & DD_CREATE_PARENTS))
        r = g_mkdir_with_parents(dd->dd_dirname, dir_mode);
    else
        r = mkdir(dd->dd_dirname, dir_mode);

    if (r != 0)
    {
        perror_msg("Can't create directory '%s'", dir);
        goto fail;
    }

    dd->dd_fd = open(dd->dd_dirname, O_DIRECTORY | O_NOFOLLOW);
    if (dd->dd_fd < 0)
    {
        perror_msg("Can't open newly created directory '%s'", dir);
        goto fail;
    }

    struct stat stat_sb;
    if (fstat(dd->dd_fd, &stat_sb) < 0)
    {
        perror_msg("stat(%s)", dd->dd_dirname);
        goto fail;
    }

    if (dd_lock(dd, CREATE_LOCK_USLEEP, /*flags:*/ 0) < 0)
        goto fail;

    /* mkdir's mode (above) can be affected by umask, fix it */
    if (fchmod(dd->dd_fd, dir_mode) == -1)
    {
        perror_msg("Can't change mode of '%s'", dir);
        goto fail;
    }

    /* Initiliaze dd_uid and dd_gid to sane values which reflect the reality.
     */
    dd->dd_uid = stat_sb.st_uid;
    dd->dd_gid = stat_sb.st_gid;

    /* Create META-DATA directory with real fs attributes which must be changed
     * in dd_reset_ownership(), when populating of a new dump directory is
     * done.
     *
     * It allows daemons to create a dump directory, populate the directory as
     * root and then switch the ownership to the real user.
     */
    dd->dd_md_fd = dd_create_subdir(dd->dd_fd, META_DATA_DIR_NAME, dd->dd_uid, dd->dd_gid, dir_mode);
    if (dd->dd_md_fd < 0)
    {
        error_msg("Can't create meta-data directory");
        goto fail;
    }

    if (dd_set_owner(dd, dd->dd_uid) < 0)
    {
        log_debug("Failed to initialized 'owner'");
        goto fail;
    }

    if (uid != (uid_t)-1L)
    {
        dd->dd_uid = 0;
        dd->dd_gid = 0;

#if DUMP_DIR_OWNED_BY_USER > 0
        /* Check crashed application's uid */
        struct passwd *pw = getpwuid(uid);
        if (pw)
            dd->dd_uid = pw->pw_uid;
        else
            error_msg("User %lu does not exist, using uid 0", (long)uid);

        if (dd_g_fs_group_gid == (uid_t)-1)
        {
            /* Get ABRT's group gid */
            struct group *gr = getgrnam("abrt");
            if (gr)
                dd->dd_gid = gr->gr_gid;
            else
                error_msg("Group 'abrt' does not exist, using gid 0");
        }
        else
            dd->dd_gid = dd_g_fs_group_gid;
#else
        /* Get ABRT's user uid */
        struct passwd *pw = getpwnam("abrt");
        if (pw)
            dd->dd_uid = pw->pw_uid;
        else
            error_msg("User 'abrt' does not exist, using uid 0");

        /* Get crashed application's gid */
        pw = getpwuid(uid);
        if (pw)
            dd->dd_gid = pw->pw_gid;
        else
            error_msg("User %lu does not exist, using gid 0", (long)uid);
#endif
    }

    /* Initialize dd_time to some sane value */
    dd->dd_time = time(NULL);

    return dd;

fail:
    dd_close(dd);
    return NULL;
}

/* Resets ownership of the given directory to UID and GID according to values
 * in dd_create_skeleton().
 */
int dd_reset_ownership(struct dump_dir *dd)
{
    if (!dd->locked)
        error_msg_and_die("dump_dir is not opened"); /* bug */

    int r = fchown(dd->dd_fd, dd->dd_uid, dd->dd_gid);
    if (r < 0)
    {
        perror_msg("Can't change '%s' ownership to %lu:%lu", dd->dd_dirname,
                   (long)dd->dd_uid, (long)dd->dd_gid);
    }

    if (dd_chown_meta_data(dd, dd->dd_uid, dd->dd_gid) != 0)
        error_msg("Failed to reset ownership of meta-data");

    /* We ignore failures above, so we will ignore failures here too.
     * The meta-data owner already exist (created by dd_create_skeleton).
     */
    dd_set_owner(dd, dd->dd_uid);

    return r;
}

/* Calls dd_create_skeleton() and dd_reset_ownership().
 */
struct dump_dir *dd_create(const char *dir, uid_t uid, mode_t mode)
{
    struct dump_dir *dd = dd_create_skeleton(dir, uid, mode, DD_CREATE_PARENTS);
    if (dd == NULL)
        return NULL;

    /* ignore results */
    dd_reset_ownership(dd);

    return dd;
}

void dd_create_basic_files(struct dump_dir *dd, uid_t uid, const char *chroot_dir)
{
    char long_str[sizeof(long) * 3 + 2];

    const time_t t = parse_time_file_at(dd->dd_fd, FILENAME_TIME);
    if (t < 0)
    {
        sprintf(long_str, "%lu", (long)dd->dd_time);
        /* first occurrence */
        dd_save_text(dd, FILENAME_TIME, long_str);
        /* last occurrence */
        dd_save_text(dd, FILENAME_LAST_OCCURRENCE, long_str);
    }
    else
    {
        dd->dd_time = t;
    }

    /* it doesn't make sense to create the uid file if uid == -1 */
    /* and 'owner' is set since dd_create_skeleton */
    if (uid != (uid_t)-1L)
    {
        /* Failure is not a problem here, because we still have the fs
         * attributes and there is only a little chance that the old value
         * gets lost. */
        dd_set_owner(dd, uid);

        snprintf(long_str, sizeof(long_str), "%li", (long)uid);
        dd_save_text(dd, FILENAME_UID, long_str);
    }

    struct utsname buf;
    uname(&buf); /* never fails */
    /* Check if files already exist in dumpdir as they might have
     * more relevant information about the problem
     */
    if (!dd_exist(dd, FILENAME_KERNEL))
        dd_save_text(dd, FILENAME_KERNEL, buf.release);
    if (!dd_exist(dd, FILENAME_ARCHITECTURE))
        dd_save_text(dd, FILENAME_ARCHITECTURE, buf.machine);
    if (!dd_exist(dd, FILENAME_HOSTNAME))
        dd_save_text(dd, FILENAME_HOSTNAME, buf.nodename);

    char *release = load_text_file("/etc/os-release",
                        DD_LOAD_TEXT_RETURN_NULL_ON_FAILURE | DD_OPEN_FOLLOW);
    if (release)
    {
        dd_save_text(dd, FILENAME_OS_INFO, release);
        free(release);
    }

    if (chroot_dir)
        copy_file_from_chroot(dd, FILENAME_OS_INFO_IN_ROOTDIR, chroot_dir, "/etc/os-release");

    /* if release exists in dumpdir don't create it, but don't warn
     * if it doesn't
     * i.e: anaconda doesn't have /etc/{fedora,redhat}-release and trying to load it
     * results in errors: rhbz#725857
     */
    release = dd_load_text_ext(dd, FILENAME_OS_RELEASE,
                    DD_FAIL_QUIETLY_ENOENT | DD_LOAD_TEXT_RETURN_NULL_ON_FAILURE);

    if (!release)
    {
        release = load_text_file("/etc/system-release",
                DD_LOAD_TEXT_RETURN_NULL_ON_FAILURE | DD_OPEN_FOLLOW);
        if (!release)
            release = load_text_file("/etc/redhat-release",
                    DD_LOAD_TEXT_RETURN_NULL_ON_FAILURE | DD_OPEN_FOLLOW);
        if (!release)
            release = load_text_file("/etc/SuSE-release", DD_OPEN_FOLLOW);

        char *newline = strchr(release, '\n');
        if (newline)
            *newline = '\0';

        dd_save_text(dd, FILENAME_OS_RELEASE, release);
        if (chroot_dir)
            copy_file_from_chroot(dd, FILENAME_OS_RELEASE_IN_ROOTDIR, chroot_dir, "/etc/system-release");
    }
    free(release);
}

void dd_sanitize_mode_and_owner(struct dump_dir *dd)
{
    /* Don't sanitize if we aren't run under root:
     * we assume that during file creation (by whatever means,
     * even by "hostname >file" in abrt_event.conf)
     * normal umask-based mode setting takes care of correct mode,
     * and uid:gid is, of course, set to user's uid and gid.
     *
     * For root operating on /var/spool/abrt/USERS_PROBLEM, this isn't true:
     * "hostname >file", for example, would create file OWNED BY ROOT!
     * This routine resets mode and uid:gid for all such files.
     */
    if (dd->dd_uid == (uid_t)-1)
        return;

    if (!dd->locked)
        error_msg_and_die("dump_dir is not opened"); /* bug */

    dd_init_next_file(dd);
    char *short_name;
    while (dd_get_next_file(dd, &short_name, /*full_name*/ NULL))
    {
        /* The current process has to have read access at least */
        int fd = secure_openat_read(dd->dd_fd, short_name);
        if (fd < 0)
            goto next;

        if (fchmod(fd, dd->mode) != 0)
            perror_msg("Can't change '%s/%s' mode to 0%o", dd->dd_dirname, short_name,
                       (unsigned)dd->mode);

        if (fchown(fd, dd->dd_uid, dd->dd_gid) != 0)
            perror_msg("Can't change '%s/%s' ownership to %lu:%lu", dd->dd_dirname, short_name,
                       (long)dd->dd_uid, (long)dd->dd_gid);

        close(fd);
next:
        free(short_name);
    }

    /* No need to check return value, the functions print good messages.
     * There are two approaches for handling errors in libreport:
     * - print out a warning message and keep status quo
     * - terminate the process
     */
    dd_sanitize_mode_meta_data(dd);
    dd_chown_meta_data(dd, dd->dd_uid, dd->dd_gid);
}

static int delete_file_dir(int dir_fd, bool skip_lock_file)
{
    DIR *d;
    int ret = fdreopen(dir_fd, &d);
    if (ret < 0)
    {
        /* The caller expects us to error out only if the directory
         * still exists (not deleted). If directory
         * *doesn't exist*, return 0 and clear errno.
         */
        if (ret == -ENOENT || ret == -ENOTDIR)
        {
            errno = 0;
            return 0;
        }
        return -1;
    }

    bool unlink_lock_file = false;
    struct dirent *dent;
    while ((dent = readdir(d)) != NULL)
    {
        if (dot_or_dotdot(dent->d_name))
            continue;
        if (skip_lock_file && strcmp(dent->d_name, ".lock") == 0)
        {
            unlink_lock_file = true;
            continue;
        }
        if (unlinkat(dir_fd, dent->d_name, /*only files*/0) == -1 && errno != ENOENT)
        {
            int err = 0;
            if (errno == EISDIR)
            {
                errno = 0;
                int subdir_fd = openat(dir_fd, dent->d_name, O_DIRECTORY);
                if (subdir_fd < 0)
                {
                    perror_msg("Can't open sub-dir'%s'", dent->d_name);
                    closedir(d);
                    return -1;
                }
                else
                {
                    err = delete_file_dir(subdir_fd, /*skip_lock_file:*/ false);
                    close(subdir_fd);
                    if (err == 0)
                        unlinkat(dir_fd, dent->d_name, AT_REMOVEDIR);
                }
            }
            if (errno || err)
            {
                perror_msg("Can't remove '%s'", dent->d_name);
                closedir(d);
                return -1;
            }
        }
    }

    /* Here we know for sure that all files/subdirs we found via readdir
     * were deleted successfully. If rmdir below fails, we assume someone
     * is racing with us and created a new file.
     */

    if (unlink_lock_file)
        xunlinkat(dir_fd, ".lock", /*only files*/0);

    closedir(d);

    return 0;
}

static int dd_delete_meta_data(struct dump_dir *dd)
{
    if (!dd->locked)
    {
        error_msg("Can't remove meta-data of unlocked problem directory %s", dd->dd_dirname);
        return -1;
    }

    int dd_md_fd = dd_get_meta_data_dir_fd(dd, /*no create*/0);
    if (dd_md_fd < 0)
        return 0;

    if (delete_file_dir(dd_md_fd, /*skip_lock_file:*/ true) != 0)
    {
        perror_msg("Can't remove meta-data from '"META_DATA_DIR_NAME"'");
        return -2;
    }

    dd_close_meta_data_dir(dd);

    if (unlinkat(dd->dd_fd, META_DATA_DIR_NAME, AT_REMOVEDIR))
    {
        perror_msg("Can't remove meta-data directory '"META_DATA_DIR_NAME"'");
        return -3;
    }

    return 0;
}

int dd_delete(struct dump_dir *dd)
{
    if (!dd->locked)
    {
        error_msg("unlocked problem directory %s cannot be deleted", dd->dd_dirname);
        return -1;
    }

    if (dd_delete_meta_data(dd) != 0)
        return -2;

    if (delete_file_dir(dd->dd_fd, /*skip_lock_file:*/ true) != 0)
    {
        perror_msg("Can't remove contents of directory '%s'", dd->dd_dirname);
        return -2;
    }

    unsigned cnt = RMDIR_FAIL_COUNT;
    do {
        if (rmdir(dd->dd_dirname) == 0)
            break;
        /* Someone locked the dir after unlink, but before rmdir.
         * This "someone" must be dd_lock().
         * It detects this (by seeing that there is no time file)
         * and backs off at once. So we need to just retry rmdir,
         * with minimal sleep.
         */
        usleep(RMDIR_FAIL_USLEEP);
    } while (--cnt != 0);

    if (cnt == 0)
    {
        perror_msg("Can't remove directory '%s'", dd->dd_dirname);
        return -3;
    }

    dd->locked = 0; /* delete_file_dir already removed .lock */
    dd_close(dd);
    return 0;
}

int dd_chown(struct dump_dir *dd, uid_t new_uid)
{
    if (!dd->locked)
        error_msg_and_die("dump_dir is not opened"); /* bug */

    struct stat statbuf;
    if (fstat(dd->dd_fd, &statbuf) != 0)
    {
        perror_msg("stat('%s')", dd->dd_dirname);
        return 1;
    }

    struct passwd *pw = getpwuid(new_uid);
    if (!pw)
    {
        error_msg("UID %ld is not found in user database", (long)new_uid);
        return 1;
    }

#if DUMP_DIR_OWNED_BY_USER > 0
    uid_t owners_uid = pw->pw_uid;
    gid_t groups_gid = statbuf.st_gid;
#else
    uid_t owners_uid = statbuf.st_uid;
    gid_t groups_gid = pw->pw_gid;
#endif

    int chown_res = fchown(dd->dd_fd, owners_uid, groups_gid);
    if (chown_res)
        perror_msg("fchown('%s')", dd->dd_dirname);
    else
    {
        dd_init_next_file(dd);
        char *short_name;
        while (chown_res == 0 && dd_get_next_file(dd, &short_name, /*full_name*/ NULL))
        {
            /* The current process has to have read access at least */
            int fd = secure_openat_read(dd->dd_fd, short_name);
            if (fd < 0)
                goto next;

            log_debug("chowning %s", short_name);

            chown_res = fchown(fd, owners_uid, groups_gid);
            if (chown_res)
            {
                perror_msg("fchownat('%s')", short_name);
                break;
            }

            close(fd);
next:
            free(short_name);
        }
    }

    if (chown_res == 0)
        chown_res = dd_chown_meta_data(dd, owners_uid, groups_gid);

    if (chown_res == 0)
    {
        dd->dd_uid = owners_uid;
        dd->dd_gid = groups_gid;
    }

    if (chown_res == 0)
        chown_res = dd_set_owner(dd, (long)dd->dd_uid);

    return chown_res;
}

static char *load_text_from_file_descriptor(int fd, const char *path, int flags)
{
    if (fd == -1)
    {
        if (!(flags & DD_FAIL_QUIETLY_ENOENT))
            perror_msg("Can't open file '%s' for reading", path);
        return (flags & DD_LOAD_TEXT_RETURN_NULL_ON_FAILURE ? NULL : xstrdup(""));
    }

    /* Why? Because half a million read syscalls of one byte each isn't fun.
     * FILE-based IO buffers reads.
     */
    FILE *fp = fdopen(fd, "r");
    if (!fp)
        die_out_of_memory();

    struct strbuf *buf_content = strbuf_new();
    int oneline = 0;
    int ch;
    while ((ch = fgetc(fp)) != EOF)
    {
//TODO? \r -> \n?
//TODO? strip trailing spaces/tabs?
        if (ch == '\n')
            oneline = (oneline << 1) | 1;
        if (ch == '\0')
            ch = ' ';
        if (isspace(ch) || ch >= ' ') /* used !iscntrl, but it failed on unicode */
            strbuf_append_char(buf_content, ch);
    }
    fclose(fp); /* this also closes fd */

    char last = oneline != 0 ? buf_content->buf[buf_content->len - 1] : 0;
    if (last == '\n')
    {
        /* If file contains exactly one '\n' and it is at the end, remove it.
         * This enables users to use simple "echo blah >file" in order to create
         * short string items in dump dirs.
         */
        if (oneline == 1)
            buf_content->buf[--buf_content->len] = '\0';
    }
    else /* last != '\n' */
    {
        /* Last line is unterminated, fix it */
        /* Cases: */
        /* oneline=0: "qwe" - DONT fix this! */
        /* oneline=1: "qwe\nrty" - two lines in fact */
        /* oneline>1: "qwe\nrty\uio" */
        if (oneline >= 1)
            strbuf_append_char(buf_content, '\n');
    }

    return strbuf_free_nobuf(buf_content);
}

static char *load_text_file_at(int dir_fd, const char *name, unsigned flags)
{
    assert(name[0] != '/');

    const int fd = openat(dir_fd, name, O_RDONLY | ((flags & DD_OPEN_FOLLOW) ? 0 : O_NOFOLLOW));
    return load_text_from_file_descriptor(fd, name, flags);
}

char *load_text_file(const char *path, unsigned flags)
{
    const int fd = open(path, O_RDONLY | ((flags & DD_OPEN_FOLLOW) ? 0 : O_NOFOLLOW));
    return load_text_from_file_descriptor(fd, path, flags);
}

static void copy_file_from_chroot(struct dump_dir* dd, const char *name, const char *chroot_dir, const char *file_path)
{
    char *chrooted_name = concat_path_file(chroot_dir, file_path);
    char *data = load_text_file(chrooted_name,
                    DD_LOAD_TEXT_RETURN_NULL_ON_FAILURE | DD_OPEN_FOLLOW);
    free(chrooted_name);
    if (data)
    {
        dd_save_text(dd, name, data);
        free(data);
    }
}

static int create_new_file_at(int dir_fd, int omode, const char *name, uid_t uid, gid_t gid, mode_t mode)
{
    assert(name[0] != '/');
    assert(omode == O_WRONLY || omode == O_RDWR);

    /* the mode is set by the caller, see dd_create() for security analysis */
    unlinkat(dir_fd, name, /*remove only files*/0);
    int fd = openat(dir_fd, name, omode | O_EXCL | O_CREAT | O_NOFOLLOW, mode);
    if (fd < 0)
    {
        perror_msg("Can't open file '%s' for writing", name);
        return -1;
    }

    if ((uid != (uid_t)-1L) && (fchown(fd, uid, gid) == -1))
    {
        perror_msg("Can't change '%s' ownership to %lu:%lu", name, (long)uid, (long)gid);
        close(fd);
        return -1;
    }

    /* O_CREAT in the open() call above causes that the permissions of the
     * created file are (mode & ~umask)
     *
     * This is true only if we did create file. We are not sure we created it
     * in this case - it may exist already.
     */
    if (fchmod(fd, mode) == -1)
    {
        perror_msg("Can't change mode of '%s'", name);
        close(fd);
        return -1;
    }

    return fd;
}

static bool save_binary_file_at(int dir_fd, const char *name, const char* data, unsigned size, uid_t uid, gid_t gid, mode_t mode)
{
    const int fd = create_new_file_at(dir_fd, O_WRONLY, name, uid, gid, mode);
    if (fd < 0)
        goto fail;

    const unsigned r = full_write(fd, data, size);
    close(fd);
    if (r != size)
        goto fail;

    return true;

fail:
    error_msg("Can't save file '%s'", name);
    return false;

}

char* dd_load_text_ext(const struct dump_dir *dd, const char *name, unsigned flags)
{
//    if (!dd->locked)
//        error_msg_and_die("dump_dir is not opened"); /* bug */

    if (!dd_validate_element_name(name))
    {
        error_msg("Cannot load text. '%s' is not a valid file name", name);
        if ((flags & DD_LOAD_TEXT_RETURN_NULL_ON_FAILURE))
            return NULL;

        xfunc_die();
    }

    /* Compat with old abrt dumps. Remove in abrt-2.1 */
    if (strcmp(name, "release") == 0)
        name = FILENAME_OS_RELEASE;

    return load_text_file_at(dd->dd_fd, name, flags);
}

char* dd_load_text(const struct dump_dir *dd, const char *name)
{
    return dd_load_text_ext(dd, name, /*flags:*/ 0);
}

int dd_load_int32(const struct dump_dir *dd, const char *name, int32_t *value)
{
    unsigned long long parsed = 0;
    const int ret = read_number_from_file_at(dd->dd_fd, name, "int32_t",
            sizeof(int32_t), INT32_MIN, INT32_MAX, &parsed);

    if (ret == 0)
        *value = (int32_t)parsed;

    return ret;
}

int dd_load_uint32(const struct dump_dir *dd, const char *name, uint32_t *value)
{
    unsigned long long parsed = 0;
    const int ret = read_number_from_file_at(dd->dd_fd, name, "uint32_t",
            sizeof(uint32_t), 0, UINT32_MAX, &parsed);

    if (ret == 0)
        *value = (uint32_t)parsed;

    return ret;
}

int dd_load_int64(const struct dump_dir *dd, const char *name, int64_t *value)
{
    unsigned long long parsed = 0;
    const int ret = read_number_from_file_at(dd->dd_fd, name, "int64_t",
            sizeof(int64_t), INT64_MIN, INT64_MAX, &parsed);

    if (ret == 0)
        *value = (int64_t)parsed;

    return ret;
}

int dd_load_uint64(const struct dump_dir *dd, const char *name, uint64_t *value)
{
    unsigned long long parsed = 0;
    const int ret = read_number_from_file_at(dd->dd_fd, name, "uint64_t",
            sizeof(uint64_t), 0, UINT64_MAX, &parsed);

    if (ret == 0)
        *value = (uint64_t)parsed;

    return ret;
}

int dd_get_env_variable(struct dump_dir *dd, const char *name, char **value)
{
    const int fd = openat(dd->dd_fd, FILENAME_ENVIRON, O_RDONLY | O_NOFOLLOW);
    if (fd < 0)
        return -errno;

    const int r  = get_env_variable_ext(fd, '\n', name, value);
    close(fd);
    return r;
}

void dd_save_text(struct dump_dir *dd, const char *name, const char *data)
{
    if (!dd->locked)
        error_msg_and_die("dump_dir is not opened"); /* bug */

    if (!dd_validate_element_name(name))
        error_msg_and_die("Cannot save text. '%s' is not a valid file name", name);

    save_binary_file_at(dd->dd_fd, name, data, strlen(data), dd->dd_uid, dd->dd_gid, dd->mode);
}

void dd_save_binary(struct dump_dir* dd, const char* name, const char* data, unsigned size)
{
    if (!dd->locked)
        error_msg_and_die("dump_dir is not opened"); /* bug */

    if (!dd_validate_element_name(name))
        error_msg_and_die("Cannot save binary. '%s' is not a valid file name", name);

    save_binary_file_at(dd->dd_fd, name, data, size, dd->dd_uid, dd->dd_gid, dd->mode);
}

int dd_item_stat(struct dump_dir *dd, const char *name, struct stat *statbuf)
{
    if (!dd_validate_element_name(name))
        return -EINVAL;

    int r = fstatat(dd->dd_fd, name, statbuf, AT_SYMLINK_NOFOLLOW);

    if (r != 0)
        return -errno;

    if (!S_ISREG(statbuf->st_mode))
        return -EMEDIUMTYPE;

    return 0;
}

long dd_get_item_size(struct dump_dir *dd, const char *name)
{
    long size = -1;
    struct stat statbuf;
    int r = dd_item_stat(dd, name, &statbuf);

    const char *error = NULL;
    if (r == 0)
        size = statbuf.st_size;
    else if (r == -ENOENT)
        size = 0;
    else if (r == -EINVAL)
        error = "Is an invalid item name";
    else if (r == -EMEDIUMTYPE)
        error = "Is not a regular file";
    else
        error = strerror(errno);

    if (error != NULL)
        error_msg("Cannot get item size ('%s'): %s", name, error);

    return size;
}

int dd_delete_item(struct dump_dir *dd, const char *name)
{
    if (!dd->locked)
        error_msg_and_die("dump_dir is not opened"); /* bug */

    if (!dd_validate_element_name(name))
    {
        error_msg("Cannot delete item. '%s' is not a valid file name", name);
        return -EINVAL;
    }

    int res = unlinkat(dd->dd_fd, name, /*only files*/0);

    if (res < 0)
    {
        if (errno == ENOENT)
            errno = res = 0;
        else
            perror_msg("Can't delete file '%s'", name);
    }

    return res;
}

int dd_open_item(struct dump_dir *dd, const char *name, int flag)
{
    if (!dd_validate_element_name(name))
    {
        error_msg("Cannot open item as FD. '%s' is not a valid file name", name);
        return -EINVAL;
    }

    if (flag == O_RDONLY)
        return openat(dd->dd_fd, name, O_RDONLY | O_NOFOLLOW | O_CLOEXEC);

    if (!dd->locked)
        error_msg_and_die("dump_dir is not locked"); /* bug */

    if (flag == O_RDWR)
        return create_new_file_at(dd->dd_fd, O_RDWR, name, dd->dd_uid, dd->dd_gid, dd->mode);

    error_msg("invalid open item flag");
    return -ENOTSUP;
}

FILE *dd_open_item_file(struct dump_dir *dd, const char *name, int flag)
{
    const int item_fd = dd_open_item(dd, name, flag);
    if (item_fd < 0)
        return NULL;

    const char *mode = flag == O_RDONLY ? "r" : "w+";

    return fdopen(item_fd, mode);
}

static int _dd_get_next_file_dent(struct dump_dir *dd, struct dirent **dent)
{
    if (dd->next_dir == NULL)
        return 0;

    while ((*dent = readdir(dd->next_dir)) != NULL)
    {
        if (is_regular_file_at(*dent, dd->dd_fd))
            return 1;
    }

    dd_clear_next_file(dd);
    return 0;
}

int dd_get_items_count(struct dump_dir *dd)
{
    int retval = 0;

    if (dd_init_next_file(dd) == NULL)
        return -EIO;

    while (dd_get_next_file(dd, NULL, NULL))
    {
        ++retval;

        /* Check overflow */
        if (retval < 0)
        {
            dd_clear_next_file(dd);
            return -E2BIG;
        }
    }

    return retval;
}

off_t dd_compute_size(struct dump_dir *dd, int flags)
{
    off_t retval = 0;

    if (dd_init_next_file(dd) == NULL)
        return -EIO;

    struct stat statbuf;
    struct dirent *dent;
    while (_dd_get_next_file_dent(dd, &dent))
    {
        if (fstatat(dd->dd_fd, dent->d_name, &statbuf, AT_SYMLINK_NOFOLLOW) != 0)
        {
            retval = -errno;
            goto finito;
        }

        retval += statbuf.st_size;
        /* Check overflow */
        if (retval < 0)
        {
            retval = -E2BIG;
            goto finito;
        }
    }

finito:
    dd_clear_next_file(dd);
    return retval;
}

DIR *dd_init_next_file(struct dump_dir *dd)
{
//    if (!dd->locked)
//        error_msg_and_die("dump_dir is not opened"); /* bug */
    int opendir_fd = dup(dd->dd_fd);
    if (opendir_fd < 0)
    {
        perror_msg("dd_init_next_file: dup(dd_fd)");
        return NULL;
    }

    dd_clear_next_file(dd);

    lseek(opendir_fd, SEEK_SET, 0);
    dd->next_dir = fdopendir(opendir_fd);
    if (!dd->next_dir)
    {
        error_msg("Can't open directory '%s'", dd->dd_dirname);
        close(opendir_fd);
    }

    return dd->next_dir;
}

void dd_clear_next_file(struct dump_dir *dd)
{
    if (dd->next_dir == NULL)
        return;

    closedir(dd->next_dir);
    dd->next_dir = NULL;
}

int dd_get_next_file(struct dump_dir *dd, char **short_name, char **full_name)
{
    struct dirent *dent;
    if (0 == _dd_get_next_file_dent(dd, &dent))
        return 0;

    if (short_name)
        *short_name = xstrdup(dent->d_name);
    if (full_name)
        *full_name = concat_path_file(dd->dd_dirname, dent->d_name);
    return 1;
}

/* reported_to handling */

void add_reported_to(struct dump_dir *dd, const char *line)
{
    if (!dd->locked)
        error_msg_and_die("dump_dir is not opened"); /* bug */

    char *reported_to = dd_load_text_ext(dd, FILENAME_REPORTED_TO, DD_FAIL_QUIETLY_ENOENT | DD_LOAD_TEXT_RETURN_NULL_ON_FAILURE);
    if (add_reported_to_data(&reported_to, line))
        dd_save_text(dd, FILENAME_REPORTED_TO, reported_to);

    free(reported_to);
}

void add_reported_to_entry(struct dump_dir *dd, struct report_result *result)
{
    if (!dd->locked)
        error_msg_and_die("dump_dir is not opened"); /* bug */

    char *reported_to = dd_load_text_ext(dd, FILENAME_REPORTED_TO, DD_FAIL_QUIETLY_ENOENT | DD_LOAD_TEXT_RETURN_NULL_ON_FAILURE);
    if (add_reported_to_entry_data(&reported_to, result))
        dd_save_text(dd, FILENAME_REPORTED_TO, reported_to);

    free(reported_to);
}

report_result_t *find_in_reported_to(struct dump_dir *dd, const char *report_label)
{
    char *reported_to = dd_load_text_ext(dd, FILENAME_REPORTED_TO,
                DD_FAIL_QUIETLY_ENOENT | DD_LOAD_TEXT_RETURN_NULL_ON_FAILURE);
    if (!reported_to)
        return NULL;

    report_result_t *result = find_in_reported_to_data(reported_to, report_label);

    free(reported_to);
    return result;
}

GList *read_entire_reported_to(struct dump_dir *dd)
{
    char *reported_to = dd_load_text_ext(dd, FILENAME_REPORTED_TO,
                DD_FAIL_QUIETLY_ENOENT | DD_LOAD_TEXT_RETURN_NULL_ON_FAILURE);
    if (!reported_to)
        return NULL;

    GList *result = read_entire_reported_to_data(reported_to);

    free(reported_to);
    return result;
}

/* reported_to handling end */

int dd_rename(struct dump_dir *dd, const char *new_path)
{
    if (!dd->locked)
    {
        error_msg("unlocked problem directory %s cannot be renamed", dd->dd_dirname);
        return -1;
    }

    /* Keeps the opened file descriptor valid */
    int res = rename(dd->dd_dirname, new_path);
    if (res == 0)
    {
        free(dd->dd_dirname);
        dd->dd_dirname = rm_trailing_slashes(new_path);
    }
    return res;
}

/* Utility function */

void delete_dump_dir(const char *dirname)
{
    struct dump_dir *dd = dd_opendir(dirname, /*flags:*/ 0);
    if (dd)
    {
        dd_delete(dd);
    }
}

bool uid_in_group(uid_t uid, gid_t gid)
{
    char **tmp;
    struct passwd *pwd = getpwuid(uid);

    if (!pwd)
        return FALSE;

    if (pwd->pw_gid == gid)
        return TRUE;

    struct group *grp = getgrgid(gid);
    if (!(grp && grp->gr_mem))
        return FALSE;

    for (tmp = grp->gr_mem; *tmp != NULL; tmp++)
    {
        if (g_strcmp0(*tmp, pwd->pw_name) == 0)
        {
            log_debug("user %s belongs to group: %s",  pwd->pw_name, grp->gr_name);
            return TRUE;
        }
    }

    log_info("user %s DOESN'T belong to group: %s",  pwd->pw_name, grp->gr_name);
    return FALSE;
}

int dd_stat_for_uid(struct dump_dir *dd, uid_t uid)
{
    int ddstat = 0;
    bool uid_test;

    if (uid == dd_g_super_user_uid)
    {
        log_debug("directory accessible by super-user");
        ddstat |= DD_STAT_ACCESSIBLE_BY_UID;
    }

#define DD_OWNER_FLAGS (DD_STAT_ACCESSIBLE_BY_UID | DD_STAT_OWNED_BY_UID)
    if (dd->dd_uid == dd_g_super_user_uid)
    {
        log_debug("directory owned by super-user: checking meta-data");

        const uid_t owner = dd_get_owner(dd);

        if (owner < 0)
            goto fsattributes;

        if (owner == uid)
        {
            log_debug("meta-data: %ld uid owns directory", (long)uid);
            ddstat |= DD_OWNER_FLAGS;
            goto finito;
        }

        uid_t no_owner_uid = (uid_t)-1;
        int ret = get_no_owner_uid(&no_owner_uid);
        if (   ret >= 0
            && owner == no_owner_uid)
        {
            log_debug("meta-data: directory is accessible by %ld uid", (long)uid);
            ddstat |= DD_STAT_ACCESSIBLE_BY_UID;
            ddstat |= DD_STAT_NO_OWNER;
        }

        goto finito;
    }

fsattributes:
    if (dd->mode & S_IROTH)
    {
        log_debug("directory is accessible by %ld uid", (long)uid);
        ddstat |= DD_STAT_ACCESSIBLE_BY_UID;
    }

#if DUMP_DIR_OWNED_BY_USER > 0
    uid_test = uid == dd->dd_uid;
#else
    uid_test = uid_in_group(uid, dd->dd_gid);
#endif
    if (uid_test)
    {
        log_debug("fs attributes: %ld uid owns directory", (long)uid);
        ddstat |= DD_OWNER_FLAGS;
    }

#undef DD_OWNER_FLAGS

finito:
    log_debug("UID=%d, %s: %o", uid, dd->dd_dirname, ddstat);
    return ddstat;
}

int dump_dir_stat_for_uid(const char *dirname, uid_t uid)
{
    struct dump_dir *dd = dd_opendir(dirname, DD_OPEN_FD_ONLY);
    if (dd == NULL)
        return -1;

    int r = dd_stat_for_uid(dd, uid);
    dd_close(dd);

    return r;
}

int dd_accessible_by_uid(struct dump_dir *dd, uid_t uid)
{
    int ddstat = dd_stat_for_uid(dd, uid);

    if (ddstat >= 0)
        return ddstat & DD_STAT_ACCESSIBLE_BY_UID;

    VERB3 pwarn_msg("can't determine accessibility for %ld uid", (long)uid);

    return 0;
}

int dump_dir_accessible_by_uid(const char *dirname, uid_t uid)
{
    int ddstat = dump_dir_stat_for_uid(dirname, uid);

    if (ddstat >= 0)
        return ddstat & DD_STAT_ACCESSIBLE_BY_UID;

    VERB3 pwarn_msg("can't determine accessibility of '%s' by %ld uid", dirname, (long)uid);

    return 0;
}

int dd_mark_as_notreportable(struct dump_dir *dd, const char *reason)
{
    if (!dd->locked)
    {
        error_msg("dump_dir is not locked for writing");
        return -1;
    }

    dd_save_text(dd, FILENAME_NOT_REPORTABLE, reason);
    return 0;
}

int dd_copy_file(struct dump_dir *dd, const char *name, const char *source_path)
{
    if (!dd_validate_element_name(name))
        error_msg_and_die("Cannot test existence. '%s' is not a valid file name", name);

    log_debug("copying '%s' to '%s' at '%s'", source_path, name, dd->dd_dirname);

    unlinkat(dd->dd_fd, name, /*remove only files*/0);
    off_t copied = copy_file_ext_at(source_path, dd->dd_fd, name, DEFAULT_DUMP_DIR_MODE,
            dd->dd_uid, dd->dd_gid, O_RDONLY, O_WRONLY | O_TRUNC | O_EXCL | O_CREAT);

    if (copied < 0)
        error_msg("Can't copy %s to %s at '%s'", source_path, name, dd->dd_dirname);
    else
        log_debug("copied %li bytes", (unsigned long)copied);

    return copied < 0;
}

int dd_copy_file_at(struct dump_dir *dd, const char *name, int src_dir_fd, const char *src_name)
{
    if (!dd_validate_element_name(name))
        error_msg_and_die("Cannot test existence. '%s' is not a valid file name", name);

    log_debug("copying file '%s' to element '%s' at '%s'", src_name, name, dd->dd_dirname);

    unlinkat(dd->dd_fd, name, /*remove only files*/0);
    off_t copied = copy_file_ext_2at(src_dir_fd, src_name, dd->dd_fd, name,
            DEFAULT_DUMP_DIR_MODE,
            dd->dd_uid, dd->dd_gid,
            O_RDONLY,
            O_WRONLY | O_TRUNC | O_EXCL | O_CREAT);

    if (copied < 0)
        error_msg("Can't copy file '%s' to element '%s' at '%s'", src_name, name, dd->dd_dirname);
    else
        log_debug("copied %li bytes", (unsigned long)copied);

    return copied < 0;
}

int dd_copy_file_unpack(struct dump_dir *dd, const char *name, const char *source_path)
{
    if (!dd_validate_element_name(name))
        error_msg_and_die("Cannot test existence. '%s' is not a valid file name", name);

    log_debug("unpacking '%s' to '%s' at '%s'", source_path, name, dd->dd_dirname);

    unlinkat(dd->dd_fd, name, /*remove only files*/0);
    off_t copied = decompress_file_ext_at(source_path, dd->dd_fd, name, DEFAULT_DUMP_DIR_MODE,
            dd->dd_uid, dd->dd_gid, O_RDONLY, O_WRONLY | O_TRUNC | O_EXCL | O_CREAT);

    if (copied != 0)
        error_msg("Can't copy %s to %s at '%s'", source_path, name, dd->dd_dirname);
    else
        log_debug("unpackaged file '%s'", source_path);

    return copied < 0;

}

/* flags - for future needs */
int dd_create_archive(struct dump_dir *dd, const char *archive_name,
        const_string_vector_const_ptr_t exclude_elements, int flags)
{
    if (suffixcmp(archive_name, ".tar.gz") != 0)
        return -ENOSYS;

    int result = 0;
    pid_t child;
    TAR* tar = NULL;
    int pipe_from_parent_to_child[2];
    xpipe(pipe_from_parent_to_child);
    child = fork();
    if (child < 0)
    {
        result = -errno;
        /* Don't die, let the caller to execute his clean-up code. */
        perror_msg("vfork");
        return result;
    }
    if (child == 0)
    {
        /* child */
        close(pipe_from_parent_to_child[1]);
        xmove_fd(pipe_from_parent_to_child[0], 0);

        int fd = open(archive_name, O_WRONLY | O_CREAT | O_EXCL, 0600);
        if (fd < 0)
        {
            /* This r might interfer with exit status of gzip, but it is
             * very unlikely (man 1 gzip):
             *   Exit status is normally 0; if an error occurs, exit status is
             *   1. If a warning occurs, exit status is 2.
             */
            result = errno == EEXIST ? 100 : 3;
            perror_msg("Can't open '%s'", archive_name);
            exit(result);
        }

        xmove_fd(fd, 1);
        execlp("gzip", "gzip", NULL);
        perror_msg_and_die("Can't execute '%s'", "gzip");
    }
    close(pipe_from_parent_to_child[0]);

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

    /* Create tar writer object */
    if (tar_fdopen(&tar, pipe_from_parent_to_child[1], archive_name,
                /*fileops:(standard)*/ NULL, O_WRONLY | O_CREAT, 0644, TAR_GNU) != 0)
    {
        result = -errno;
        log_warning(_("Failed to open TAR writer"));
        goto finito;
    }

    /* Write data to the tarball */
    dd_init_next_file(dd);
    char *short_name, *full_name;
    while (dd_get_next_file(dd, &short_name, &full_name))
    {
        if (!(exclude_elements && is_in_string_list(short_name, exclude_elements)))
        {
           if (tar_append_file(tar, full_name, short_name))
               result = -errno;
        }

        free(short_name);
        free(full_name);

        if (result != 0)
            goto finito;
    }

    /* Close tar writer... */
    if (tar_append_eof(tar) != 0)
    {
        result = -errno;
        log_warning(_("Failed to finalize TAR archive"));
        goto finito;
    }

finito:
    signal(SIGPIPE, old_handler);

    if (tar != NULL && tar_close(tar) != 0)
    {
        result = -errno;
        log_warning(_("Failed to close TAR writer"));
    }

    /* ...and check that gzip child finished successfully */
    int status;
    safe_waitpid(child, &status, 0);
    if (status != 0)
    {
        result = -ECHILD;
        if (WIFSIGNALED(status))
            log_warning(_("gzip killed with signal %d"), WTERMSIG(status));
        else if (WIFEXITED(status))
        {
            if (WEXITSTATUS(status) == 100)
                result = -EEXIST;
            else
                log_warning(_("gzip exited with %d"), WEXITSTATUS(status));
        }
        else
            log_warning(_("gzip process failed"));
    }

    return result;
}

off_t dd_copy_fd(struct dump_dir *dd, const char *name, int fd, int copy_flags, off_t maxsize)
{
    if (!dd_validate_element_name(name))
        error_msg_and_die("Cannot test existence. '%s' is not a valid file name", name);

    log_debug("Saving data from file descriptor %d to '%s' at '%s'", fd, name, dd->dd_dirname);

    unlinkat(dd->dd_fd, name, /*remove only files*/0);
    off_t read = copyfd_ext_at(fd, dd->dd_fd, name, DEFAULT_DUMP_DIR_MODE,
            dd->dd_uid, dd->dd_gid, O_WRONLY | O_CREAT | O_EXCL, copy_flags, maxsize);

    if (read < 0)
    {
        error_msg("Can't copy file descriptor %d to %s at '%s'", fd, name, dd->dd_dirname);
        /* Destroy the file to get rid of empty files and files with invalid owners */
        unlinkat(dd->dd_fd, name, /*remove only files*/0);
    }
    else if (read > maxsize)
        log_debug("Saved %lu Bytes (read %lu Bytes)", (unsigned long)maxsize, (unsigned long)read);
    else
        log_debug("Saved %lu Bytes", (unsigned long)read);

    return read;
}
