Blame 0079-dd-add-support-for-meta-data.patch

3a204fb
From fdc64c5aafe435e34c4369ae8504d98ff57d7350 Mon Sep 17 00:00:00 2001
3a204fb
From: Jakub Filak <jfilak@redhat.com>
3a204fb
Date: Thu, 28 May 2015 12:32:01 +0200
3a204fb
Subject: [PATCH] dd: add support for meta-data
3a204fb
3a204fb
Store the meta-data as plain text files in the dump directory's
3a204fb
sub-directory called '.libreport'.
3a204fb
3a204fb
I chose this name to allow other tools (which do not exist yet) to create
3a204fb
their own directories and files.
3a204fb
3a204fb
Related: #354
3a204fb
3a204fb
Signed-off-by: Jakub Filak <jfilak@redhat.com>
3a204fb
---
3a204fb
 src/include/dump_dir.h |   4 +
3a204fb
 src/lib/dump_dir.c     | 511 ++++++++++++++++++++++++++++++++++++++++++-------
3a204fb
 tests/dump_dir.at      | 171 +++++++++++++++++
3a204fb
 3 files changed, 618 insertions(+), 68 deletions(-)
3a204fb
3a204fb
diff --git a/src/include/dump_dir.h b/src/include/dump_dir.h
3a204fb
index b613e0b..f33eb99 100644
3a204fb
--- a/src/include/dump_dir.h
3a204fb
+++ b/src/include/dump_dir.h
3a204fb
@@ -75,6 +75,10 @@ struct dump_dir {
3a204fb
      */
3a204fb
     int owns_lock;
3a204fb
     int dd_fd;
3a204fb
+    /* Never use this member directly, it is intialized on demand in
3a204fb
+     * dd_get_meta_data_dir_fd()
3a204fb
+     */
3a204fb
+    int dd_md_fd;
3a204fb
 };
3a204fb
 
3a204fb
 void dd_close(struct dump_dir *dd);
3a204fb
diff --git a/src/lib/dump_dir.c b/src/lib/dump_dir.c
3a204fb
index 4dcfa66..aed2de0 100644
3a204fb
--- a/src/lib/dump_dir.c
3a204fb
+++ b/src/lib/dump_dir.c
3a204fb
@@ -83,6 +83,24 @@
3a204fb
 #define RMDIR_FAIL_USLEEP              (10*1000)
3a204fb
 #define RMDIR_FAIL_COUNT                     50
3a204fb
 
3a204fb
+// A sub-directory of a dump directory where the meta-data such as owner are
3a204fb
+// stored. The meta-data directory must have same owner, group and mode as its
3a204fb
+// parent dump directory. It is not a fatal error, if the meta-data directory
3a204fb
+// does not exist (backward compatibility).
3a204fb
+#define META_DATA_DIR_NAME             ".libreport"
3a204fb
+
3a204fb
+enum {
3a204fb
+    /* Try to create meta-data dir if it does not exist */
3a204fb
+    DD_MD_GET_CREATE = 1 << 0,
3a204fb
+};
3a204fb
+
3a204fb
+// a little trick to copy read bits from file mode to exec bit of dir mode
3a204fb
+// * mode of dump directory is in the form of 640 (no X) because we create
3a204fb
+//   files more often then we play with directories
3a204fb
+// * so if we want to get real mode of the directory we have to copy the read
3a204fb
+//   bits
3a204fb
+#define DD_MODE_TO_DIR_MODE(mode) ((mode) | (((mode) & 0444) >> 2))
3a204fb
+
3a204fb
 
3a204fb
 static char *load_text_file(const char *path, unsigned flags);
3a204fb
 static char *load_text_file_at(int dir_fd, const char *name, unsigned flags);
3a204fb
@@ -112,6 +130,12 @@ static bool exist_file_dir_at(int dir_fd, const char *name)
3a204fb
     return false;
3a204fb
 }
3a204fb
 
3a204fb
+/* A valid dump dir element name is correct filename and is not a name of any
3a204fb
+ * internal file or directory.
3a204fb
+ */
3a204fb
+#define dd_validate_element_name(name) \
3a204fb
+    (str_is_correct_filename(name) && (strcmp(META_DATA_DIR_NAME, name) != 0))
3a204fb
+
3a204fb
 /* Opens the file in the three following steps:
3a204fb
  * 1. open the file with O_PATH (get a file descriptor for operations with
3a204fb
  *    inode) and O_NOFOLLOW (do not dereference symbolick links)
3a204fb
@@ -397,18 +421,28 @@ static inline struct dump_dir *dd_init(void)
3a204fb
     struct dump_dir* dd = (struct dump_dir*)xzalloc(sizeof(struct dump_dir));
3a204fb
     dd->dd_time = -1;
3a204fb
     dd->dd_fd = -1;
3a204fb
+    dd->dd_md_fd = -1;
3a204fb
     return dd;
3a204fb
 }
3a204fb
 
3a204fb
 int dd_exist(const struct dump_dir *dd, const char *name)
3a204fb
 {
3a204fb
-    if (!str_is_correct_filename(name))
3a204fb
+    if (!dd_validate_element_name(name))
3a204fb
         error_msg_and_die("Cannot test existence. '%s' is not a valid file name", name);
3a204fb
 
3a204fb
     const int ret = exist_file_dir_at(dd->dd_fd, name);
3a204fb
     return ret;
3a204fb
 }
3a204fb
 
3a204fb
+static void dd_close_meta_data_dir(struct dump_dir *dd)
3a204fb
+{
3a204fb
+    if (dd->dd_md_fd < 0)
3a204fb
+        return;
3a204fb
+
3a204fb
+    close(dd->dd_md_fd);
3a204fb
+    dd->dd_md_fd = -1;
3a204fb
+}
3a204fb
+
3a204fb
 void dd_close(struct dump_dir *dd)
3a204fb
 {
3a204fb
     if (!dd)
3a204fb
@@ -419,6 +453,8 @@ void dd_close(struct dump_dir *dd)
3a204fb
     if (dd->dd_fd >= 0)
3a204fb
         close(dd->dd_fd);
3a204fb
 
3a204fb
+    dd_close_meta_data_dir(dd);
3a204fb
+
3a204fb
     if (dd->next_dir)
3a204fb
     {
3a204fb
         closedir(dd->next_dir);
3a204fb
@@ -430,6 +466,255 @@ void dd_close(struct dump_dir *dd)
3a204fb
     free(dd);
3a204fb
 }
3a204fb
 
3a204fb
+static int dd_create_subdir(int dd_fd, const char *dirname, uid_t dd_uid, gid_t dd_gid, mode_t dd_mode)
3a204fb
+{
3a204fb
+    if (mkdirat(dd_fd, dirname, dd_mode) < 0)
3a204fb
+    {
3a204fb
+        perror_msg("Can't create directory '%s'", dirname);
3a204fb
+        return -1;
3a204fb
+    }
3a204fb
+
3a204fb
+    int dd_md_fd = openat(dd_fd, dirname, O_DIRECTORY | O_NOFOLLOW);
3a204fb
+    if (dd_md_fd < 0)
3a204fb
+    {
3a204fb
+        perror_msg("Can't open newly created directory '%s'", dirname);
3a204fb
+        goto fail_open;
3a204fb
+    }
3a204fb
+
3a204fb
+    if (dd_uid != (uid_t)-1)
3a204fb
+    {
3a204fb
+        if (fchown(dd_md_fd, dd_uid, dd_gid) != 0)
3a204fb
+        {
3a204fb
+            perror_msg("Can't change owner and group of '%s'", dirname);
3a204fb
+            goto fail_modify;
3a204fb
+        }
3a204fb
+    }
3a204fb
+
3a204fb
+    /* mkdir's mode (above) can be affected by umask, fix it */
3a204fb
+    if (fchmod(dd_md_fd, dd_mode) == -1)
3a204fb
+    {
3a204fb
+        perror_msg("Can't change mode of '%s'", dirname);
3a204fb
+        goto fail_modify;
3a204fb
+    }
3a204fb
+
3a204fb
+    return dd_md_fd;
3a204fb
+fail_modify:
3a204fb
+    close(dd_md_fd);
3a204fb
+fail_open:
3a204fb
+    if (unlinkat(dd_fd, dirname, AT_REMOVEDIR) < 0)
3a204fb
+        perror_msg("Fialed to unlink '%s' while cleaning up after failure", dirname);
3a204fb
+    return -1;
3a204fb
+}
3a204fb
+
3a204fb
+/* Opens the meta-data directory, checks its file system attributes and returns
3a204fb
+ * its file descriptor.
3a204fb
+ *
3a204fb
+ * The meta-data directory must have the same file system attributes as the
3a204fb
+ * parent dump directory in order to avoid unexpected situations and detects
3a204fb
+ * program errors (it is an error to modify bits of the dump directory and
3a204fb
+ * forgot to update the meta-data directory).
3a204fb
+ *
3a204fb
+ * Keep on mind that the old dump directories might miss the meta-data directory
3a204fb
+ * so the return value -ENOENT does not necessarily need to be fatal.
3a204fb
+ */
3a204fb
+static int dd_open_meta_data_dir(struct dump_dir *dd)
3a204fb
+{
3a204fb
+    int md_dir_fd = openat(dd->dd_fd, META_DATA_DIR_NAME, O_DIRECTORY | O_NOFOLLOW);
3a204fb
+    if (md_dir_fd < 0)
3a204fb
+    {
3a204fb
+        md_dir_fd = -errno;
3a204fb
+
3a204fb
+        /* ENOENT is not critical */
3a204fb
+        if (errno != ENOENT)
3a204fb
+            log_warning("Can't open meta-data '"META_DATA_DIR_NAME"'");
3a204fb
+        else
3a204fb
+            log_info("The dump dir doesn't contain '"META_DATA_DIR_NAME"'");
3a204fb
+
3a204fb
+        goto finito;
3a204fb
+    }
3a204fb
+
3a204fb
+    struct stat md_sb;
3a204fb
+    if (fstat(md_dir_fd, &md_sb) < 0)
3a204fb
+    {
3a204fb
+        log_debug("Can't stat '"META_DATA_DIR_NAME"'");
3a204fb
+        goto fail;
3a204fb
+    }
3a204fb
+
3a204fb
+    /* Test only permission bits, ignore SUID, SGID, etc. */
3a204fb
+    const mode_t md_mode = md_sb.st_mode & 0777;
3a204fb
+    const mode_t dd_mode = DD_MODE_TO_DIR_MODE(dd->mode);
3a204fb
+
3a204fb
+    if (   md_sb.st_uid != dd->dd_uid
3a204fb
+        || md_sb.st_gid != dd->dd_gid
3a204fb
+        || md_mode != dd_mode)
3a204fb
+    {
3a204fb
+        log_debug("'"META_DATA_DIR_NAME"' has different attributes than the dump dir, '%d'='%d', '%d'='%d', %o = %o",
3a204fb
+                        md_sb.st_uid, dd->dd_uid, md_sb.st_gid, dd->dd_gid, md_mode, dd_mode);
3a204fb
+        goto fail;
3a204fb
+    }
3a204fb
+
3a204fb
+finito:
3a204fb
+    return md_dir_fd;
3a204fb
+
3a204fb
+fail:
3a204fb
+    close(md_dir_fd);
3a204fb
+
3a204fb
+    return -EINVAL;
3a204fb
+}
3a204fb
+
3a204fb
+/* Returns a file descriptor to the meta-data directory. Can be configured to
3a204fb
+ * create the directory if it does not exist.
3a204fb
+ *
3a204fb
+ * This function enables lazy initialization of the meta-data directory.
3a204fb
+ */
3a204fb
+static int dd_get_meta_data_dir_fd(struct dump_dir *dd, int flags)
3a204fb
+{
3a204fb
+    if (dd->dd_md_fd < 0)
3a204fb
+    {
3a204fb
+        dd->dd_md_fd = dd_open_meta_data_dir(dd);
3a204fb
+
3a204fb
+        if (    dd->dd_md_fd == -ENOENT
3a204fb
+             && (flags & DD_MD_GET_CREATE))
3a204fb
+        {
3a204fb
+            dd->dd_md_fd = dd_create_subdir(dd->dd_fd,
3a204fb
+                                            META_DATA_DIR_NAME,
3a204fb
+                                            dd->dd_uid,
3a204fb
+                                            dd->dd_gid,
3a204fb
+                                            DD_MODE_TO_DIR_MODE(dd->mode));
3a204fb
+        }
3a204fb
+    }
3a204fb
+
3a204fb
+    return dd->dd_md_fd;
3a204fb
+}
3a204fb
+
3a204fb
+/* A helper function useful for traversing directories.
3a204fb
+ *
3a204fb
+ * DIR* d opendir(dir_fd); ... closedir(d); closes also dir_fd but we want to
3a204fb
+ * keep it opened.
3a204fb
+ */
3a204fb
+static int fdreopen(int dir_fd, DIR **d)
3a204fb
+{
3a204fb
+    int opendir_fd = dup(dir_fd);
3a204fb
+    if (opendir_fd < 0)
3a204fb
+    {
3a204fb
+        perror_msg("dup(dir_fd)");
3a204fb
+        return -EBADFD;
3a204fb
+    }
3a204fb
+
3a204fb
+    lseek(opendir_fd, SEEK_SET, 0);
3a204fb
+    *d = fdopendir(opendir_fd);
3a204fb
+    if (!*d)
3a204fb
+    {
3a204fb
+        int ret = -errno;
3a204fb
+        close(opendir_fd);
3a204fb
+        perror_msg("fdopendir(dir_fd)");
3a204fb
+        return ret;
3a204fb
+    }
3a204fb
+
3a204fb
+    /* 'opendir_fd' will be closed with 'd' */
3a204fb
+    return 0;
3a204fb
+}
3a204fb
+
3a204fb
+/* A macro for going through the entries of a directory referenced as a file
3a204fb
+ * descriptor.
3a204fb
+ *
3a204fb
+ * Usage:
3a204fb
+ *
3a204fb
+ * FOREACH_REGULAR_FILE_AS_FD_AT_BEGIN(dir_fd)
3a204fb
+ * {
3a204fb
+ *      printf("Short name '%s'",    dent->d_name);
3a204fb
+ *      printf("File descriptor %d", fd);
3a204fb
+ * }
3a204fb
+ * FOREACH_REGULAR_FILE_AS_FD_AT_END
3a204fb
+ */
3a204fb
+
3a204fb
+#define FOREACH_REGULAR_FILE_AS_FD_AT_BEGIN(dir_fd) \
3a204fb
+    DIR *d; \
3a204fb
+    struct dirent *dent; \
3a204fb
+    if (fdreopen(dir_fd, &d) < 0) return -1; \
3a204fb
+    while ((dent = readdir(d)) != NULL) \
3a204fb
+    { \
3a204fb
+        if (dot_or_dotdot(dent->d_name)) continue; \
3a204fb
+        int fd = secure_openat_read(dirfd(d), dent->d_name); \
3a204fb
+        if (fd >= 0)
3a204fb
+
3a204fb
+#define FOREACH_REGULAR_FILE_AS_FD_AT_END \
3a204fb
+        close(fd); \
3a204fb
+    } \
3a204fb
+    closedir(d);
3a204fb
+
3a204fb
+
3a204fb
+/* Sets attributes of the meta-data directory and its contents to the same
3a204fb
+ * attributes of the parent dump directory.
3a204fb
+ */
3a204fb
+static int dd_sanitize_mode_meta_data(struct dump_dir *dd)
3a204fb
+{
3a204fb
+    if (!dd->locked)
3a204fb
+        error_msg_and_die("%s: dump_dir is not opened", __func__); /* bug */
3a204fb
+
3a204fb
+    int dd_md_fd = dd_get_meta_data_dir_fd(dd, /*no create*/0);
3a204fb
+    if (dd_md_fd < 0)
3a204fb
+        return 0;
3a204fb
+
3a204fb
+    int res = fchmod(dd_md_fd, DD_MODE_TO_DIR_MODE(dd->mode));
3a204fb
+    if (res < 0)
3a204fb
+    {
3a204fb
+        perror_msg("Failed to chmod meta-data sub-dir");
3a204fb
+        return res;
3a204fb
+    }
3a204fb
+
3a204fb
+    FOREACH_REGULAR_FILE_AS_FD_AT_BEGIN(dd_md_fd)
3a204fb
+    {
3a204fb
+        log_debug("chmoding %s", dent->d_name);
3a204fb
+
3a204fb
+        res = fchmod(fd, dd->mode);
3a204fb
+        if (res)
3a204fb
+        {
3a204fb
+            perror_msg("fchmod('%s')", dent->d_name);
3a204fb
+            break;
3a204fb
+        }
3a204fb
+    }
3a204fb
+    FOREACH_REGULAR_FILE_AS_FD_AT_END
3a204fb
+
3a204fb
+    return 0;
3a204fb
+}
3a204fb
+
3a204fb
+/* Sets owner and group of the meta-data directory and its contents to the same
3a204fb
+ * attributes of the parent dump directory.
3a204fb
+ */
3a204fb
+
3a204fb
+static int dd_chown_meta_data(struct dump_dir *dd, uid_t uid, gid_t gid)
3a204fb
+{
3a204fb
+    if (!dd->locked)
3a204fb
+        error_msg_and_die("%s: dump_dir is not opened", __func__); /* bug */
3a204fb
+
3a204fb
+    int dd_md_fd = dd_get_meta_data_dir_fd(dd, /*no create*/0);
3a204fb
+    if (dd_md_fd < 0)
3a204fb
+        return 0;
3a204fb
+
3a204fb
+    int res = fchown(dd_md_fd, uid, gid);
3a204fb
+    if (res < 0)
3a204fb
+    {
3a204fb
+        perror_msg("Failed to chown meta-data sub-dir");
3a204fb
+        return res;
3a204fb
+    }
3a204fb
+
3a204fb
+    FOREACH_REGULAR_FILE_AS_FD_AT_BEGIN(dd_md_fd)
3a204fb
+    {
3a204fb
+        log_debug("%s: chowning %s", __func__, dent->d_name);
3a204fb
+
3a204fb
+        res = fchown(fd, uid, gid);
3a204fb
+        if (res)
3a204fb
+        {
3a204fb
+            perror_msg("fchown('%s')", dent->d_name);
3a204fb
+            break;
3a204fb
+        }
3a204fb
+    }
3a204fb
+    FOREACH_REGULAR_FILE_AS_FD_AT_END
3a204fb
+
3a204fb
+    return res;
3a204fb
+}
3a204fb
+
3a204fb
 static char* rm_trailing_slashes(const char *dir)
3a204fb
 {
3a204fb
     unsigned len = strlen(dir);
3a204fb
@@ -455,45 +740,41 @@ static struct dump_dir *dd_do_open(struct dump_dir *dd, const char *dir, int fla
3a204fb
         /* & 0666 should remove the executable bit */
3a204fb
         dd->mode = (stat_buf.st_mode & 0666);
3a204fb
 
3a204fb
-        dd->dd_uid = (uid_t)-1L;
3a204fb
-        dd->dd_gid = (gid_t)-1L;
3a204fb
+        /* We want to have dd_uid and dd_gid always initialized. But we have to
3a204fb
+         * initialize it in the way which does not prevent non-privileged user
3a204fb
+         * from saving data in their dump directories.
3a204fb
+         *
3a204fb
+         * Non-privileged users are not allowed to change the group to
3a204fb
+         * 'abrt' so we have to use their GID.
3a204fb
+         *
3a204fb
+         * If the caller is super-user, we have to use dd's fs owner and fs
3a204fb
+         * group, because he can do everything and the data must be readable by
3a204fb
+         * the real owner.
3a204fb
+         *
3a204fb
+         * We always use fs uid, because non-privileged users must own the
3a204fb
+         * directory and super-user must use fs owner.
3a204fb
+         */
3a204fb
+        dd->dd_uid = stat_buf.st_uid;
3a204fb
+
3a204fb
+        /* We use fs group only if the caller is super-user, because we want to
3a204fb
+         * make sure non-privileged users can modify elements (libreport call
3a204fb
+         * chown(dd_uid, dd_gid) after modifying an element) and the modified
3a204fb
+         * elements do not have super-user's group.
3a204fb
+         */
3a204fb
+        dd->dd_gid = getegid();
3a204fb
         if (geteuid() == 0)
3a204fb
-        {
3a204fb
-            /* In case caller would want to create more files, he'll need uid:gid */
3a204fb
-            if (fstat(dd->dd_fd, &stat_buf) != 0)
3a204fb
-            {
3a204fb
-                error_msg("Can't stat '%s'", dd->dd_dirname);
3a204fb
-                dd_close(dd);
3a204fb
-                return NULL;
3a204fb
-            }
3a204fb
-            dd->dd_uid = stat_buf.st_uid;
3a204fb
             dd->dd_gid = stat_buf.st_gid;
3a204fb
-        }
3a204fb
 
3a204fb
         if ((flags & DD_OPEN_FD_ONLY))
3a204fb
+        {
3a204fb
+            dd->dd_md_fd = dd_open_meta_data_dir(dd);
3a204fb
             return dd;
3a204fb
+        }
3a204fb
     }
3a204fb
 
3a204fb
     errno = 0;
3a204fb
     if (dd_lock(dd, WAIT_FOR_OTHER_PROCESS_USLEEP, flags) < 0)
3a204fb
     {
3a204fb
-        if ((flags & DD_OPEN_READONLY) && errno == EACCES)
3a204fb
-        {
3a204fb
-            /* Directory is not writable. If it seems to be readable,
3a204fb
-             * return "read only" dd, not NULL
3a204fb
-             *
3a204fb
-             * Does the directory have 'r' flag?
3a204fb
-             */
3a204fb
-            if (faccessat(dd->dd_fd, ".", R_OK, AT_SYMLINK_NOFOLLOW) == 0)
3a204fb
-            {
3a204fb
-                if(dd_check(dd) != NULL)
3a204fb
-                {
3a204fb
-                    dd_close(dd);
3a204fb
-                    dd = NULL;
3a204fb
-                }
3a204fb
-                return dd;
3a204fb
-            }
3a204fb
-        }
3a204fb
         if (errno == EISDIR)
3a204fb
         {
3a204fb
             /* EISDIR: dd_lock can lock the dir, but it sees no time file there,
3a204fb
@@ -504,26 +785,56 @@ static struct dump_dir *dd_do_open(struct dump_dir *dd, const char *dir, int fla
3a204fb
              * defaults to "."!
3a204fb
              */
3a204fb
             error_msg("'%s' is not a problem directory", dd->dd_dirname);
3a204fb
+            goto fail_with_close;
3a204fb
         }
3a204fb
-        else
3a204fb
+
3a204fb
+        if (!(flags & DD_OPEN_READONLY))
3a204fb
         {
3a204fb
- cant_access:
3a204fb
-            if (errno == ENOENT || errno == ENOTDIR)
3a204fb
-            {
3a204fb
-                if (!(flags & DD_FAIL_QUIETLY_ENOENT))
3a204fb
-                    error_msg("'%s' does not exist", dd->dd_dirname);
3a204fb
-            }
3a204fb
-            else
3a204fb
-            {
3a204fb
-                if (!(flags & DD_FAIL_QUIETLY_EACCES))
3a204fb
-                    perror_msg("Can't access '%s'", dd->dd_dirname);
3a204fb
-            }
3a204fb
+            log_debug("'%s' can't be opened for writing", dd->dd_dirname);
3a204fb
+            goto fail_with_close;
3a204fb
         }
3a204fb
-        dd_close(dd);
3a204fb
-        return NULL;
3a204fb
+
3a204fb
+        if (errno != EACCES)
3a204fb
+        {
3a204fb
+            VERB3 perror_msg("failed to lock dump directory '%s'", dd->dd_dirname);
3a204fb
+            goto fail_with_close;
3a204fb
+        }
3a204fb
+
3a204fb
+        /* Directory is not writable. If it seems to be readable,
3a204fb
+         * return "read only" dd, not NULL
3a204fb
+         *
3a204fb
+         * Does the directory have 'r' flag?
3a204fb
+         */
3a204fb
+        if (faccessat(dd->dd_fd, ".", R_OK, AT_SYMLINK_NOFOLLOW) != 0)
3a204fb
+        {
3a204fb
+            VERB3 perror_msg("failed to lock dump directory '%s'", dd->dd_dirname);
3a204fb
+            goto fail_with_close;
3a204fb
+        }
3a204fb
+
3a204fb
+        /* dd_check prints out good log messages */
3a204fb
+        if(dd_check(dd) != NULL)
3a204fb
+            goto fail_with_close;
3a204fb
+
3a204fb
+        /* The dd is opened in READONLY moded, continue.*/
3a204fb
     }
3a204fb
 
3a204fb
     return dd;
3a204fb
+
3a204fb
+cant_access:
3a204fb
+    if (errno == ENOENT || errno == ENOTDIR)
3a204fb
+    {
3a204fb
+        if (!(flags & DD_FAIL_QUIETLY_ENOENT))
3a204fb
+            error_msg("'%s' does not exist", dd->dd_dirname);
3a204fb
+    }
3a204fb
+    else
3a204fb
+    {
3a204fb
+        if (!(flags & DD_FAIL_QUIETLY_EACCES))
3a204fb
+            perror_msg("Can't access '%s'", dd->dd_dirname);
3a204fb
+    }
3a204fb
+
3a204fb
+fail_with_close:
3a204fb
+    dd_close(dd);
3a204fb
+    return NULL;
3a204fb
 }
3a204fb
 
3a204fb
 struct dump_dir *dd_fdopendir(struct dump_dir *dd, int flags)
3a204fb
@@ -607,8 +918,7 @@ struct dump_dir *dd_opendir(const char *dir, int flags)
3a204fb
  */
3a204fb
 struct dump_dir *dd_create_skeleton(const char *dir, uid_t uid, mode_t mode, int flags)
3a204fb
 {
3a204fb
-    /* a little trick to copy read bits from file mode to exec bit of dir mode*/
3a204fb
-    mode_t dir_mode = mode | ((mode & 0444) >> 2);
3a204fb
+    mode_t dir_mode = DD_MODE_TO_DIR_MODE(mode);
3a204fb
     struct dump_dir *dd = dd_init();
3a204fb
 
3a204fb
     dd->mode = mode;
3a204fb
@@ -673,8 +983,25 @@ struct dump_dir *dd_create_skeleton(const char *dir, uid_t uid, mode_t mode, int
3a204fb
         goto fail;
3a204fb
     }
3a204fb
 
3a204fb
-    dd->dd_uid = (uid_t)-1L;
3a204fb
-    dd->dd_gid = (gid_t)-1L;
3a204fb
+    /* Initiliaze dd_uid and dd_gid to sane values which reflect the reality.
3a204fb
+     */
3a204fb
+    dd->dd_uid = stat_sb.st_uid;
3a204fb
+    dd->dd_gid = stat_sb.st_gid;
3a204fb
+
3a204fb
+    /* Create META-DATA directory with real fs attributes which must be changed
3a204fb
+     * in dd_reset_ownership(), when populating of a new dump directory is
3a204fb
+     * done.
3a204fb
+     *
3a204fb
+     * It allows daemons to create a dump directory, populate the directory as
3a204fb
+     * root and then switch the ownership to the real user.
3a204fb
+     */
3a204fb
+    dd->dd_md_fd = dd_create_subdir(dd->dd_fd, META_DATA_DIR_NAME, dd->dd_uid, dd->dd_gid, dir_mode);
3a204fb
+    if (dd->dd_md_fd < 0)
3a204fb
+    {
3a204fb
+        error_msg("Can't create meta-data directory");
3a204fb
+        goto fail;
3a204fb
+    }
3a204fb
+
3a204fb
     if (uid != (uid_t)-1L)
3a204fb
     {
3a204fb
         dd->dd_uid = 0;
3a204fb
@@ -726,12 +1053,16 @@ int dd_reset_ownership(struct dump_dir *dd)
3a204fb
     if (!dd->locked)
3a204fb
         error_msg_and_die("dump_dir is not opened"); /* bug */
3a204fb
 
3a204fb
-    const int r = fchown(dd->dd_fd, dd->dd_uid, dd->dd_gid);
3a204fb
+    int r = fchown(dd->dd_fd, dd->dd_uid, dd->dd_gid);
3a204fb
     if (r < 0)
3a204fb
     {
3a204fb
         perror_msg("Can't change '%s' ownership to %lu:%lu", dd->dd_dirname,
3a204fb
                    (long)dd->dd_uid, (long)dd->dd_gid);
3a204fb
     }
3a204fb
+
3a204fb
+    if (dd_chown_meta_data(dd, dd->dd_uid, dd->dd_gid) != 0)
3a204fb
+        error_msg("Failed to reset ownership of meta-data");
3a204fb
+
3a204fb
     return r;
3a204fb
 }
3a204fb
 
3a204fb
@@ -864,27 +1195,27 @@ void dd_sanitize_mode_and_owner(struct dump_dir *dd)
3a204fb
 next:
3a204fb
         free(short_name);
3a204fb
     }
3a204fb
+
3a204fb
+    /* No need to check return value, the functions print good messages.
3a204fb
+     * There are two approaches for handling errors in libreport:
3a204fb
+     * - print out a warning message and keep status quo
3a204fb
+     * - terminate the process
3a204fb
+     */
3a204fb
+    dd_sanitize_mode_meta_data(dd);
3a204fb
+    dd_chown_meta_data(dd, dd->dd_uid, dd->dd_gid);
3a204fb
 }
3a204fb
 
3a204fb
 static int delete_file_dir(int dir_fd, bool skip_lock_file)
3a204fb
 {
3a204fb
-    int opendir_fd = dup(dir_fd);
3a204fb
-    if (opendir_fd < 0)
3a204fb
+    DIR *d;
3a204fb
+    int ret = fdreopen(dir_fd, &d);
3a204fb
+    if (ret < 0)
3a204fb
     {
3a204fb
-        perror_msg("delete_file_dir: dup(dir_fd)");
3a204fb
-        return -1;
3a204fb
-    }
3a204fb
-
3a204fb
-    lseek(opendir_fd, SEEK_SET, 0);
3a204fb
-    DIR *d = fdopendir(opendir_fd);
3a204fb
-    if (!d)
3a204fb
-    {
3a204fb
-        close(opendir_fd);
3a204fb
         /* The caller expects us to error out only if the directory
3a204fb
          * still exists (not deleted). If directory
3a204fb
          * *doesn't exist*, return 0 and clear errno.
3a204fb
          */
3a204fb
-        if (errno == ENOENT || errno == ENOTDIR)
3a204fb
+        if (ret == -ENOENT || ret == -ENOTDIR)
3a204fb
         {
3a204fb
             errno = 0;
3a204fb
             return 0;
3a204fb
@@ -946,6 +1277,35 @@ static int delete_file_dir(int dir_fd, bool skip_lock_file)
3a204fb
     return 0;
3a204fb
 }
3a204fb
 
3a204fb
+static int dd_delete_meta_data(struct dump_dir *dd)
3a204fb
+{
3a204fb
+    if (!dd->locked)
3a204fb
+    {
3a204fb
+        error_msg("Can't remove meta-data of unlocked problem directory %s", dd->dd_dirname);
3a204fb
+        return -1;
3a204fb
+    }
3a204fb
+
3a204fb
+    int dd_md_fd = dd_get_meta_data_dir_fd(dd, /*no create*/0);
3a204fb
+    if (dd_md_fd < 0)
3a204fb
+        return 0;
3a204fb
+
3a204fb
+    if (delete_file_dir(dd_md_fd, /*skip_lock_file:*/ true) != 0)
3a204fb
+    {
3a204fb
+        perror_msg("Can't remove meta-data from '"META_DATA_DIR_NAME"'");
3a204fb
+        return -2;
3a204fb
+    }
3a204fb
+
3a204fb
+    dd_close_meta_data_dir(dd);
3a204fb
+
3a204fb
+    if (unlinkat(dd->dd_fd, META_DATA_DIR_NAME, AT_REMOVEDIR))
3a204fb
+    {
3a204fb
+        perror_msg("Can't remove meta-data directory '"META_DATA_DIR_NAME"'");
3a204fb
+        return -3;
3a204fb
+    }
3a204fb
+
3a204fb
+    return 0;
3a204fb
+}
3a204fb
+
3a204fb
 int dd_delete(struct dump_dir *dd)
3a204fb
 {
3a204fb
     if (!dd->locked)
3a204fb
@@ -954,6 +1314,9 @@ int dd_delete(struct dump_dir *dd)
3a204fb
         return -1;
3a204fb
     }
3a204fb
 
3a204fb
+    if (dd_delete_meta_data(dd) != 0)
3a204fb
+        return -2;
3a204fb
+
3a204fb
     if (delete_file_dir(dd->dd_fd, /*skip_lock_file:*/ true) != 0)
3a204fb
     {
3a204fb
         perror_msg("Can't remove contents of directory '%s'", dd->dd_dirname);
3a204fb
@@ -1029,7 +1392,10 @@ int dd_chown(struct dump_dir *dd, uid_t new_uid)
3a204fb
 
3a204fb
             chown_res = fchown(fd, owners_uid, groups_gid);
3a204fb
             if (chown_res)
3a204fb
+            {
3a204fb
                 perror_msg("fchownat('%s')", short_name);
3a204fb
+                break;
3a204fb
+            }
3a204fb
 
3a204fb
             close(fd);
3a204fb
 next:
3a204fb
@@ -1037,6 +1403,15 @@ next:
3a204fb
         }
3a204fb
     }
3a204fb
 
3a204fb
+    if (chown_res == 0)
3a204fb
+        chown_res = dd_chown_meta_data(dd, owners_uid, groups_gid);
3a204fb
+
3a204fb
+    if (chown_res == 0)
3a204fb
+    {
3a204fb
+        dd->dd_uid = owners_uid;
3a204fb
+        dd->dd_gid = groups_gid;
3a204fb
+    }
3a204fb
+
3a204fb
     return chown_res;
3a204fb
 }
3a204fb
 
3a204fb
@@ -1175,7 +1550,7 @@ char* dd_load_text_ext(const struct dump_dir *dd, const char *name, unsigned fla
3a204fb
 //    if (!dd->locked)
3a204fb
 //        error_msg_and_die("dump_dir is not opened"); /* bug */
3a204fb
 
3a204fb
-    if (!str_is_correct_filename(name))
3a204fb
+    if (!dd_validate_element_name(name))
3a204fb
     {
3a204fb
         error_msg("Cannot load text. '%s' is not a valid file name", name);
3a204fb
         if ((flags & DD_LOAD_TEXT_RETURN_NULL_ON_FAILURE))
3a204fb
@@ -1201,7 +1576,7 @@ void dd_save_text(struct dump_dir *dd, const char *name, const char *data)
3a204fb
     if (!dd->locked)
3a204fb
         error_msg_and_die("dump_dir is not opened"); /* bug */
3a204fb
 
3a204fb
-    if (!str_is_correct_filename(name))
3a204fb
+    if (!dd_validate_element_name(name))
3a204fb
         error_msg_and_die("Cannot save text. '%s' is not a valid file name", name);
3a204fb
 
3a204fb
     save_binary_file_at(dd->dd_fd, name, data, strlen(data), dd->dd_uid, dd->dd_gid, dd->mode);
3a204fb
@@ -1212,7 +1587,7 @@ void dd_save_binary(struct dump_dir* dd, const char* name, const char* data, uns
3a204fb
     if (!dd->locked)
3a204fb
         error_msg_and_die("dump_dir is not opened"); /* bug */
3a204fb
 
3a204fb
-    if (!str_is_correct_filename(name))
3a204fb
+    if (!dd_validate_element_name(name))
3a204fb
         error_msg_and_die("Cannot save binary. '%s' is not a valid file name", name);
3a204fb
 
3a204fb
     save_binary_file_at(dd->dd_fd, name, data, size, dd->dd_uid, dd->dd_gid, dd->mode);
3a204fb
@@ -1220,7 +1595,7 @@ void dd_save_binary(struct dump_dir* dd, const char* name, const char* data, uns
3a204fb
 
3a204fb
 long dd_get_item_size(struct dump_dir *dd, const char *name)
3a204fb
 {
3a204fb
-    if (!str_is_correct_filename(name))
3a204fb
+    if (!dd_validate_element_name(name))
3a204fb
         error_msg_and_die("Cannot get item size. '%s' is not a valid file name", name);
3a204fb
 
3a204fb
     long size = -1;
3a204fb
@@ -1245,7 +1620,7 @@ int dd_delete_item(struct dump_dir *dd, const char *name)
3a204fb
     if (!dd->locked)
3a204fb
         error_msg_and_die("dump_dir is not opened"); /* bug */
3a204fb
 
3a204fb
-    if (!str_is_correct_filename(name))
3a204fb
+    if (!dd_validate_element_name(name))
3a204fb
         error_msg_and_die("Cannot delete item. '%s' is not a valid file name", name);
3a204fb
 
3a204fb
     int res = unlinkat(dd->dd_fd, name, /*only files*/0);
3a204fb
@@ -1483,7 +1858,7 @@ int dd_mark_as_notreportable(struct dump_dir *dd, const char *reason)
3a204fb
 
3a204fb
 int dd_copy_file(struct dump_dir *dd, const char *name, const char *source_path)
3a204fb
 {
3a204fb
-    if (!str_is_correct_filename(name))
3a204fb
+    if (!dd_validate_element_name(name))
3a204fb
         error_msg_and_die("Cannot test existence. '%s' is not a valid file name", name);
3a204fb
 
3a204fb
     log_debug("copying '%s' to '%s' at '%s'", source_path, name, dd->dd_dirname);
3a204fb
@@ -1502,7 +1877,7 @@ int dd_copy_file(struct dump_dir *dd, const char *name, const char *source_path)
3a204fb
 
3a204fb
 int dd_copy_file_unpack(struct dump_dir *dd, const char *name, const char *source_path)
3a204fb
 {
3a204fb
-    if (!str_is_correct_filename(name))
3a204fb
+    if (!dd_validate_element_name(name))
3a204fb
         error_msg_and_die("Cannot test existence. '%s' is not a valid file name", name);
3a204fb
 
3a204fb
     log_debug("unpacking '%s' to '%s' at '%s'", source_path, name, dd->dd_dirname);
3a204fb
diff --git a/tests/dump_dir.at b/tests/dump_dir.at
3a204fb
index 31c320e..4bf479e 100644
3a204fb
--- a/tests/dump_dir.at
3a204fb
+++ b/tests/dump_dir.at
3a204fb
@@ -158,6 +158,177 @@ int main(int argc, char **argv)
3a204fb
 }
3a204fb
 ]])
3a204fb
 
3a204fb
+## --------------------- ##
3a204fb
+## dd_create_open_delete ##
3a204fb
+## --------------------- ##
3a204fb
+
3a204fb
+AT_TESTFUN([dd_create_open_delete],
3a204fb
+[[
3a204fb
+#include "internal_libreport.h"
3a204fb
+#include <errno.h>
3a204fb
+#include <assert.h>
3a204fb
+
3a204fb
+int main(int argc, char **argv)
3a204fb
+{
3a204fb
+    g_verbose = 3;
3a204fb
+
3a204fb
+    char template[] = "/tmp/XXXXXX/dump_dir";
3a204fb
+
3a204fb
+    char *last_slash = strrchr(template, '/');
3a204fb
+    *last_slash = '\0';
3a204fb
+
3a204fb
+    if (mkdtemp(template) == NULL) {
3a204fb
+        perror("mkdtemp()");
3a204fb
+        return EXIT_FAILURE;
3a204fb
+    }
3a204fb
+
3a204fb
+    *last_slash = '/';
3a204fb
+
3a204fb
+    struct dump_dir *dd = dd_create(template, (uid_t)-1, 0640);
3a204fb
+    assert(strcmp(dd->dd_dirname, template) == 0);
3a204fb
+    assert(dd->dd_fd >= 0);
3a204fb
+    assert(dd->dd_md_fd >= 0);
3a204fb
+
3a204fb
+    struct stat dd_st;
3a204fb
+    assert(fstat(dd->dd_fd, &dd_st) == 0);
3a204fb
+
3a204fb
+    struct stat md_st;
3a204fb
+    assert(fstat(dd->dd_md_fd, &md_st) == 0);
3a204fb
+
3a204fb
+    assert(dd_st.st_uid == md_st.st_uid);
3a204fb
+    assert(dd_st.st_gid == md_st.st_gid);
3a204fb
+    assert((dd_st.st_mode & 0666) == (md_st.st_mode & 0666));
3a204fb
+
3a204fb
+    struct stat path_md_st;
3a204fb
+    assert(fstatat(dd->dd_fd, ".libreport", &path_md_st, 0) == 0);
3a204fb
+    assert(md_st.st_ino = path_md_st.st_ino);
3a204fb
+
3a204fb
+    dd_create_basic_files(dd, (uid_t)-1, NULL);
3a204fb
+    dd_save_text(dd, FILENAME_TYPE, "attest");
3a204fb
+
3a204fb
+    dd_close(dd);
3a204fb
+    dd = NULL;
3a204fb
+
3a204fb
+    dd = dd_opendir(template, 0);
3a204fb
+    assert(dd != NULL);
3a204fb
+    assert(strcmp(dd->dd_dirname, template) == 0);
3a204fb
+    assert(dd->dd_fd >= 0);
3a204fb
+    assert(dd->dd_md_fd < 0);
3a204fb
+
3a204fb
+    dd_delete(dd);
3a204fb
+
3a204fb
+    assert(stat(template, &dd_st) != 0);
3a204fb
+
3a204fb
+    *last_slash = '\0';
3a204fb
+    assert(rmdir(template) == 0);
3a204fb
+    return EXIT_SUCCESS;
3a204fb
+}
3a204fb
+]])
3a204fb
+
3a204fb
+## -------------------------- ##
3a204fb
+## dd_sanitize_mode_and_owner ##
3a204fb
+## -------------------------- ##
3a204fb
+
3a204fb
+AT_TESTFUN([dd_sanitize_mode_and_owner],
3a204fb
+[[
3a204fb
+#include "internal_libreport.h"
3a204fb
+#include <errno.h>
3a204fb
+#include <assert.h>
3a204fb
+
3a204fb
+int main(int argc, char **argv)
3a204fb
+{
3a204fb
+    g_verbose = 3;
3a204fb
+
3a204fb
+    char template[] = "/tmp/XXXXXX/dump_dir";
3a204fb
+
3a204fb
+    char *last_slash = strrchr(template, '/');
3a204fb
+    *last_slash = '\0';
3a204fb
+
3a204fb
+    if (mkdtemp(template) == NULL) {
3a204fb
+        perror("mkdtemp()");
3a204fb
+        return EXIT_FAILURE;
3a204fb
+    }
3a204fb
+
3a204fb
+    *last_slash = '/';
3a204fb
+
3a204fb
+    /* Prepare a directory for chmod test, use mode 0600 and chmod it to 0640 */
3a204fb
+    struct dump_dir *dd = dd_create(template, (uid_t)-1, 0600);
3a204fb
+    assert(dd != NULL);
3a204fb
+
3a204fb
+    {
3a204fb
+        struct stat path_md_st;
3a204fb
+        assert((fstatat(dd->dd_fd, ".libreport", &path_md_st, 0) == 0) || !"Failed initialize meta-data");
3a204fb
+        assert((path_md_st.st_mode & 0077) == 0);
3a204fb
+    }
3a204fb
+
3a204fb
+    dd_create_basic_files(dd, (uid_t)-1, NULL);
3a204fb
+    dd_save_text(dd, FILENAME_TYPE, "attest");
3a204fb
+
3a204fb
+    dd_close(dd);
3a204fb
+
3a204fb
+    /* initialize meta-data */
3a204fb
+    dd = dd_opendir(template, DD_OPEN_FD_ONLY);
3a204fb
+    /* reopen for writing */
3a204fb
+    dd = dd_fdopendir(dd, /*for writing*/0);
3a204fb
+    assert(dd != NULL);
3a204fb
+
3a204fb
+    assert(fchmod(dd->dd_fd, 0750) == 0);
3a204fb
+    dd->mode = 0640;
3a204fb
+
3a204fb
+    fprintf(stderr, "Going to sanitize\n");
3a204fb
+    dd_sanitize_mode_and_owner(dd);
3a204fb
+    fprintf(stderr, "Sanitized\n");
3a204fb
+
3a204fb
+    {
3a204fb
+        DIR *d = opendir(template);
3a204fb
+        struct dirent *dent;
3a204fb
+        while ((dent = readdir(d)) != NULL)
3a204fb
+        {
3a204fb
+            if (   strcmp(".", dent->d_name) == 0
3a204fb
+                || strcmp("..", dent->d_name) == 0
3a204fb
+                || strcmp(".lock", dent->d_name) == 0
3a204fb
+                || strcmp(".libreport", dent->d_name) == 0
3a204fb
+                )
3a204fb
+                continue;
3a204fb
+
3a204fb
+            struct stat sb;
3a204fb
+            printf("Testing element: %s\n", dent->d_name);
3a204fb
+            assert(fstatat(dd->dd_fd, dent->d_name, &sb, 0) == 0 || !"Cannot stat a regular element");
3a204fb
+            assert((sb.st_mode & 0777) == 0640 || !"Failed to chmod a regular element");
3a204fb
+        }
3a204fb
+        closedir(d);
3a204fb
+    }
3a204fb
+
3a204fb
+    {
3a204fb
+        struct stat path_md_st;
3a204fb
+        assert(fstatat(dd->dd_fd, ".libreport", &path_md_st, 0) == 0 || !"Cannot stat meta-data directory");
3a204fb
+        assert((path_md_st.st_mode & 0777) == 0750 || !"Failed chmod meta-data");
3a204fb
+    }
3a204fb
+
3a204fb
+    int md_dir_fd = openat(dd->dd_fd, ".libreport", O_DIRECTORY);
3a204fb
+    assert(md_dir_fd >= 0 || !"Cannot open meta-data directory");
3a204fb
+    DIR *d = fdopendir(md_dir_fd);
3a204fb
+    struct dirent *dent;
3a204fb
+    while ((dent = readdir(d)) != NULL)
3a204fb
+    {
3a204fb
+        if (strcmp(".", dent->d_name) == 0 || strcmp("..", dent->d_name) == 0)
3a204fb
+            continue;
3a204fb
+
3a204fb
+        struct stat sb;
3a204fb
+        printf("Testing meta-data: %s\n", dent->d_name);
3a204fb
+        assert(fstatat(md_dir_fd, dent->d_name, &sb, 0) == 0 || !"Cannot stat meta-data file");
3a204fb
+        assert((sb.st_mode & 0777) == 0640 || !"Failed to chmod a meta-data file");
3a204fb
+    }
3a204fb
+
3a204fb
+    closedir(d);
3a204fb
+    dd_delete(dd);
3a204fb
+
3a204fb
+    *last_slash = '\0';
3a204fb
+    assert(rmdir(template) == 0);
3a204fb
+    return EXIT_SUCCESS;
3a204fb
+}
3a204fb
+]])
3a204fb
+
3a204fb
 ## -------------- ##
3a204fb
 ## recursive_lock ##
3a204fb
 ## -------------- ##
3a204fb
-- 
3a204fb
2.1.0
3a204fb