Blame SOURCES/0231-dd-add-functions-for-opening-dd-item.patch

4b6aa8
From 12f813825e09e16f0c9b4f7ef4fe89ca73baf886 Mon Sep 17 00:00:00 2001
4b6aa8
From: Martin Kutlak <mkutlak@redhat.com>
4b6aa8
Date: Wed, 26 Sep 2018 14:45:57 +0200
4b6aa8
Subject: [PATCH] dd: add functions for opening dd item
4b6aa8
4b6aa8
In cases where libreport users don't want to build contents of a dump
4b6aa8
dir element in memory and save to disk using dd_save_* functions, they
4b6aa8
had to guess file name and take care of file attributes. Forcing users
4b6aa8
to take of this is a security risk.
4b6aa8
4b6aa8
This commit introduces new functions that will create a file inside of
4b6aa8
a dump directory with correct name and file attributes.
4b6aa8
4b6aa8
For simplicity, only read only mode and read-write mode are allowed.
4b6aa8
4b6aa8
The read-write mode cause removal of the original item element as we
4b6aa8
must never use truncate mode because of hard link threat (libreport
4b6aa8
code runs under privileged user, so libreport must avoid rewriting
4b6aa8
files - the correct approach is to remove the old one and create the new
4b6aa8
one).
4b6aa8
4b6aa8
Sometimes we need to be able write some data and immediately read them.
4b6aa8
This can be done by opening the file, writing the contents, closing the
4b6aa8
file and re-opening it for reading. However, if we need to split the
4b6aa8
work into chunks, than this approach becomes quite expensive.
4b6aa8
4b6aa8
Signed-off-by: Martin Kutlak <mkutlak@redhat.com>
4b6aa8
---
4b6aa8
 src/include/dump_dir.h |  44 +++++++++
4b6aa8
 src/lib/dump_dir.c     |  86 +++++++++++++----
4b6aa8
 tests/dump_dir.at      | 205 +++++++++++++++++++++++++++++++++++++++++
4b6aa8
 3 files changed, 318 insertions(+), 17 deletions(-)
4b6aa8
4b6aa8
diff --git a/src/include/dump_dir.h b/src/include/dump_dir.h
4b6aa8
index 690695a0..badef179 100644
4b6aa8
--- a/src/include/dump_dir.h
4b6aa8
+++ b/src/include/dump_dir.h
4b6aa8
@@ -24,6 +24,9 @@
4b6aa8
 /* For const_string_vector_const_ptr_t */
4b6aa8
 #include "libreport_types.h"
4b6aa8
 
4b6aa8
+#include <stdint.h>
4b6aa8
+#include <stdio.h>
4b6aa8
+
4b6aa8
 /* For DIR */
4b6aa8
 #include <sys/types.h>
4b6aa8
 #include <dirent.h>
4b6aa8
@@ -75,10 +78,24 @@ void dd_close(struct dump_dir *dd);
4b6aa8
 /* Opens the given path and returns the resulting file descriptor.
4b6aa8
  */
4b6aa8
 int dd_openfd(const char *dir);
4b6aa8
+/* Opens the given path
4b6aa8
+ */
4b6aa8
 struct dump_dir *dd_opendir(const char *dir, int flags);
4b6aa8
+
4b6aa8
+/* Re-opens a dump_dir opened with DD_OPEN_FD_ONLY.
4b6aa8
+ *
4b6aa8
+ * The passed dump_dir must not be used any more and the return value must be
4b6aa8
+ * used instead.
4b6aa8
+ *
4b6aa8
+ * The passed flags must not contain DD_OPEN_FD_ONLY.
4b6aa8
+ *
4b6aa8
+ * The passed dump_dir must not be already locked.
4b6aa8
+ */
4b6aa8
+
4b6aa8
 /* Skips dd_openfd(dir) and uses the given file descriptor instead
4b6aa8
  */
4b6aa8
 struct dump_dir *dd_fdopendir(int dir_fd, const char *dir, int flags);
4b6aa8
+
4b6aa8
 struct dump_dir *dd_create_skeleton(const char *dir, uid_t uid, mode_t mode, int flags);
4b6aa8
 int dd_reset_ownership(struct dump_dir *dd);
4b6aa8
 /* Pass uid = (uid_t)-1L to disable chown'ing of newly created files
4b6aa8
@@ -108,6 +125,33 @@ long dd_get_item_size(struct dump_dir *dd, const char *name);
4b6aa8
  * For more about errno see unlink documentation
4b6aa8
  */
4b6aa8
 int dd_delete_item(struct dump_dir *dd, const char *name);
4b6aa8
+
4b6aa8
+/* Returns a file descriptor for the given name. The function is limited to open
4b6aa8
+ * an element read only, write only or create new.
4b6aa8
+ *
4b6aa8
+ * O_RDONLY - opens an existing item for reading
4b6aa8
+ * O_RDWR - removes an item, creates its file and opens the file for reading and writing
4b6aa8
+ *
4b6aa8
+ * @param dd Dump directory
4b6aa8
+ * @param name The name of the item
4b6aa8
+ * @param flags One of these : O_RDONLY, O_RDWR
4b6aa8
+ * @return Negative number on error
4b6aa8
+ */
4b6aa8
+int dd_open_item(struct dump_dir *dd, const char *name, int flags);
4b6aa8
+
4b6aa8
+/* Returns a FILE for the given name. The function is limited to open
4b6aa8
+ * an element read only, write only or create new.
4b6aa8
+ *
4b6aa8
+ * O_RDONLY - opens an existing file for reading
4b6aa8
+ * O_RDWR - removes an item, creates its file and opens the file for reading and writing
4b6aa8
+ *
4b6aa8
+ * @param dd Dump directory
4b6aa8
+ * @param name The name of the item
4b6aa8
+ * @param flags One of these : O_RDONLY, O_RDWR
4b6aa8
+ * @return NULL on error
4b6aa8
+ */
4b6aa8
+FILE *dd_open_item_file(struct dump_dir *dd, const char *name, int flags);
4b6aa8
+
4b6aa8
 /* Returns 0 if directory is deleted or not found */
4b6aa8
 int dd_delete(struct dump_dir *dd);
4b6aa8
 int dd_rename(struct dump_dir *dd, const char *new_path);
4b6aa8
diff --git a/src/lib/dump_dir.c b/src/lib/dump_dir.c
4b6aa8
index c0117380..7e8ee017 100644
4b6aa8
--- a/src/lib/dump_dir.c
4b6aa8
+++ b/src/lib/dump_dir.c
4b6aa8
@@ -84,6 +84,12 @@
4b6aa8
 #define RMDIR_FAIL_USLEEP              (10*1000)
4b6aa8
 #define RMDIR_FAIL_COUNT                     50
4b6aa8
 
4b6aa8
+// A sub-directory of a dump directory where the meta-data such as owner are
4b6aa8
+// stored. The meta-data directory must have same owner, group and mode as its
4b6aa8
+// parent dump directory. It is not a fatal error, if the meta-data directory
4b6aa8
+// does not exist (backward compatibility).
4b6aa8
+#define META_DATA_DIR_NAME             ".libreport"
4b6aa8
+#define META_DATA_FILE_OWNER           "owner"
4b6aa8
 
4b6aa8
 static char *load_text_file(const char *path, unsigned flags);
4b6aa8
 static char *load_text_file_at(int dir_fd, const char *name, unsigned flags);
4b6aa8
@@ -113,6 +119,12 @@ static bool exist_file_dir_at(int dir_fd, const char *name)
4b6aa8
     return false;
4b6aa8
 }
4b6aa8
 
4b6aa8
+/* A valid dump dir element name is correct filename and is not a name of any
4b6aa8
+ * internal file or directory.
4b6aa8
+ */
4b6aa8
+#define dd_validate_element_name(name) \
4b6aa8
+    (str_is_correct_filename(name) && (strcmp(META_DATA_DIR_NAME, name) != 0))
4b6aa8
+
4b6aa8
 /* Opens the file in the three following steps:
4b6aa8
  * 1. open the file with O_PATH (get a file descriptor for operations with
4b6aa8
  *    inode) and O_NOFOLLOW (do not dereference symbolick links)
4b6aa8
@@ -1126,30 +1138,28 @@ static void copy_file_from_chroot(struct dump_dir* dd, const char *name, const c
4b6aa8
     }
4b6aa8
 }
4b6aa8
 
4b6aa8
-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)
4b6aa8
+static int create_new_file_at(int dir_fd, int omode, const char *name, uid_t uid, gid_t gid, mode_t mode)
4b6aa8
 {
4b6aa8
     assert(name[0] != '/');
4b6aa8
+    assert(omode == O_WRONLY || omode == O_RDWR);
4b6aa8
 
4b6aa8
     /* the mode is set by the caller, see dd_create() for security analysis */
4b6aa8
     unlinkat(dir_fd, name, /*remove only files*/0);
4b6aa8
-    int fd = openat(dir_fd, name, O_WRONLY | O_EXCL | O_CREAT | O_NOFOLLOW, mode);
4b6aa8
+    int fd = openat(dir_fd, name, omode | O_EXCL | O_CREAT | O_NOFOLLOW, mode);
4b6aa8
     if (fd < 0)
4b6aa8
     {
4b6aa8
         perror_msg("Can't open file '%s'", name);
4b6aa8
-        return false;
4b6aa8
+        return -1;
4b6aa8
     }
4b6aa8
 
4b6aa8
-    if (uid != (uid_t)-1L)
4b6aa8
+    if ((uid != (uid_t)-1L) && (fchown(fd, uid, gid) == -1))
4b6aa8
     {
4b6aa8
-        if (fchown(fd, uid, gid) == -1)
4b6aa8
-        {
4b6aa8
-            perror_msg("Can't change '%s' ownership to %lu:%lu", name, (long)uid, (long)gid);
4b6aa8
-            close(fd);
4b6aa8
-            return false;
4b6aa8
-        }
4b6aa8
+        perror_msg("Can't change '%s' ownership to %lu:%lu", name, (long)uid, (long)gid);
4b6aa8
+        close(fd);
4b6aa8
+        return -1;
4b6aa8
     }
4b6aa8
 
4b6aa8
-    /* O_CREATE in the open() call above causes that the permissions of the
4b6aa8
+    /* O_CREAT in the open() call above causes that the permissions of the
4b6aa8
      * created file are (mode & ~umask)
4b6aa8
      *
4b6aa8
      * This is true only if we did create file. We are not sure we created it
4b6aa8
@@ -1159,18 +1169,28 @@ static bool save_binary_file_at(int dir_fd, const char *name, const char* data,
4b6aa8
     {
4b6aa8
         perror_msg("Can't change mode of '%s'", name);
4b6aa8
         close(fd);
4b6aa8
-        return false;
4b6aa8
+        return -1;
4b6aa8
     }
4b6aa8
 
4b6aa8
-    unsigned r = full_write(fd, data, size);
4b6aa8
+    return fd;
4b6aa8
+}
4b6aa8
+
4b6aa8
+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)
4b6aa8
+{
4b6aa8
+    const int fd = create_new_file_at(dir_fd, O_WRONLY, name, uid, gid, mode);
4b6aa8
+    if (fd < 0)
4b6aa8
+        goto fail;
4b6aa8
+
4b6aa8
+    const unsigned r = full_write(fd, data, size);
4b6aa8
     close(fd);
4b6aa8
     if (r != size)
4b6aa8
-    {
4b6aa8
-        error_msg("Can't save file '%s'", name);
4b6aa8
-        return false;
4b6aa8
-    }
4b6aa8
+        goto fail;
4b6aa8
 
4b6aa8
     return true;
4b6aa8
+
4b6aa8
+fail:
4b6aa8
+    error_msg("Can't save file '%s'", name);
4b6aa8
+    return false;
4b6aa8
 }
4b6aa8
 
4b6aa8
 char* dd_load_text_ext(const struct dump_dir *dd, const char *name, unsigned flags)
4b6aa8
@@ -1264,6 +1284,38 @@ int dd_delete_item(struct dump_dir *dd, const char *name)
4b6aa8
     return res;
4b6aa8
 }
4b6aa8
 
4b6aa8
+int dd_open_item(struct dump_dir *dd, const char *name, int flag)
4b6aa8
+{
4b6aa8
+    if (!dd_validate_element_name(name))
4b6aa8
+    {
4b6aa8
+        error_msg("Cannot open item as FD. '%s' is not a valid file name", name);
4b6aa8
+        return -EINVAL;
4b6aa8
+    }
4b6aa8
+
4b6aa8
+    if (flag == O_RDONLY)
4b6aa8
+        return openat(dd->dd_fd, name, O_RDONLY | O_NOFOLLOW | O_CLOEXEC);
4b6aa8
+
4b6aa8
+    if (!dd->locked)
4b6aa8
+        error_msg_and_die("dump_dir is not locked"); /* bug */
4b6aa8
+
4b6aa8
+    if (flag == O_RDWR)
4b6aa8
+        return create_new_file_at(dd->dd_fd, O_RDWR, name, dd->dd_uid, dd->dd_gid, dd->mode);
4b6aa8
+
4b6aa8
+    error_msg("invalid open item flag");
4b6aa8
+    return -ENOTSUP;
4b6aa8
+}
4b6aa8
+
4b6aa8
+FILE *dd_open_item_file(struct dump_dir *dd, const char *name, int flag)
4b6aa8
+{
4b6aa8
+    const int item_fd = dd_open_item(dd, name, flag);
4b6aa8
+    if (item_fd < 0)
4b6aa8
+        return NULL;
4b6aa8
+
4b6aa8
+    const char *mode = flag == O_RDONLY ? "r" : "w+";
4b6aa8
+
4b6aa8
+    return fdopen(item_fd, mode);
4b6aa8
+}
4b6aa8
+
4b6aa8
 DIR *dd_init_next_file(struct dump_dir *dd)
4b6aa8
 {
4b6aa8
 //    if (!dd->locked)
4b6aa8
diff --git a/tests/dump_dir.at b/tests/dump_dir.at
4b6aa8
index 70a97e6e..78ea60d1 100644
4b6aa8
--- a/tests/dump_dir.at
4b6aa8
+++ b/tests/dump_dir.at
4b6aa8
@@ -355,3 +355,208 @@ int main(void)
4b6aa8
     return 0;
4b6aa8
 }
4b6aa8
 ]])
4b6aa8
+
4b6aa8
+
4b6aa8
+## ------------ ##
4b6aa8
+## dd_open_item ##
4b6aa8
+## ------------ ##
4b6aa8
+
4b6aa8
+AT_TESTFUN([dd_open_item], [[
4b6aa8
+#include "testsuite.h"
4b6aa8
+#include "testsuite_tools.h"
4b6aa8
+
4b6aa8
+TS_MAIN
4b6aa8
+{
4b6aa8
+    struct dump_dir *dd = testsuite_dump_dir_create(-1, -1, 0);
4b6aa8
+    dd->dd_time = (time_t)1234567;
4b6aa8
+    dd_create_basic_files(dd, geteuid(), NULL);
4b6aa8
+
4b6aa8
+    TS_ASSERT_SIGNED_EQ(dd_open_item(dd, "", O_RDWR), -EINVAL);
4b6aa8
+    TS_ASSERT_SIGNED_EQ(dd_open_item(dd, "/", O_RDWR), -EINVAL);
4b6aa8
+    TS_ASSERT_SIGNED_EQ(dd_open_item(dd, "//", O_RDWR), -EINVAL);
4b6aa8
+    TS_ASSERT_SIGNED_EQ(dd_open_item(dd, "/a", O_RDWR), -EINVAL);
4b6aa8
+    TS_ASSERT_SIGNED_EQ(dd_open_item(dd, "a/", O_RDWR), -EINVAL);
4b6aa8
+    TS_ASSERT_SIGNED_EQ(dd_open_item(dd, ".", O_RDWR), -EINVAL);
4b6aa8
+    TS_ASSERT_SIGNED_EQ(dd_open_item(dd, "..", O_RDWR), -EINVAL);
4b6aa8
+    TS_ASSERT_SIGNED_EQ(dd_open_item(dd, "/.", O_RDWR), -EINVAL);
4b6aa8
+    TS_ASSERT_SIGNED_EQ(dd_open_item(dd, "//.", O_RDWR), -EINVAL);
4b6aa8
+    TS_ASSERT_SIGNED_EQ(dd_open_item(dd, "./", O_RDWR), -EINVAL);
4b6aa8
+    TS_ASSERT_SIGNED_EQ(dd_open_item(dd, ".//", O_RDWR), -EINVAL);
4b6aa8
+    TS_ASSERT_SIGNED_EQ(dd_open_item(dd, "/./", O_RDWR), -EINVAL);
4b6aa8
+    TS_ASSERT_SIGNED_EQ(dd_open_item(dd, "/..", O_RDWR), -EINVAL);
4b6aa8
+    TS_ASSERT_SIGNED_EQ(dd_open_item(dd, "//..", O_RDWR), -EINVAL);
4b6aa8
+    TS_ASSERT_SIGNED_EQ(dd_open_item(dd, "../", O_RDWR), -EINVAL);
4b6aa8
+    TS_ASSERT_SIGNED_EQ(dd_open_item(dd, "..//", O_RDWR), -EINVAL);
4b6aa8
+    TS_ASSERT_SIGNED_EQ(dd_open_item(dd, "/../", O_RDWR), -EINVAL);
4b6aa8
+    TS_ASSERT_SIGNED_EQ(dd_open_item(dd, "/.././", O_RDWR), -EINVAL);
4b6aa8
+    TS_ASSERT_SIGNED_EQ(dd_open_item(dd, "looks-good-but-evil/", O_RDWR), -EINVAL);
4b6aa8
+    TS_ASSERT_SIGNED_EQ(dd_open_item(dd, "looks-good-but-evil/../../", O_RDWR), -EINVAL);
4b6aa8
+    TS_ASSERT_SIGNED_EQ(dd_open_item(dd, "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890+-=", O_RDWR), -EINVAL);
4b6aa8
+
4b6aa8
+    const int fd_rdonly_noent = dd_open_item(dd, "nofile", O_RDONLY);
4b6aa8
+    TS_ASSERT_SIGNED_LT(fd_rdonly_noent, 0);
4b6aa8
+
4b6aa8
+    const int fd_wronly_noent = dd_open_item(dd, "nofile", O_RDWR);
4b6aa8
+    TS_ASSERT_SIGNED_GE(fd_wronly_noent, 0);
4b6aa8
+    if (g_testsuite_last_ok) {
4b6aa8
+        full_write_str(fd_wronly_noent, "fd_wronly_noent");
4b6aa8
+        close(fd_wronly_noent);
4b6aa8
+
4b6aa8
+        char *const noent_contents = dd_load_text(dd, "nofile");
4b6aa8
+        TS_ASSERT_STRING_EQ(noent_contents, "fd_wronly_noent", "Successfully wrote data");
4b6aa8
+        free(noent_contents);
4b6aa8
+    }
4b6aa8
+
4b6aa8
+    TS_ASSERT_SIGNED_EQ(dd_open_item(dd, "time", O_RDONLY | O_EXCL), -ENOTSUP);
4b6aa8
+
4b6aa8
+    const int fd_rdonly_time = dd_open_item(dd, "time", O_RDONLY);
4b6aa8
+    TS_ASSERT_SIGNED_GE(fd_rdonly_time, 0);
4b6aa8
+    if (g_testsuite_last_ok) {
4b6aa8
+        char *time = dd_load_text(dd, "time");
4b6aa8
+        TS_ASSERT_PTR_IS_NOT_NULL(time);
4b6aa8
+
4b6aa8
+        char rdonly_time_contents[16];
4b6aa8
+        int bytes_rdonly_time = full_read(fd_rdonly_time, rdonly_time_contents, sizeof(rdonly_time_contents));
4b6aa8
+        TS_ASSERT_SIGNED_GT(bytes_rdonly_time, 0);
4b6aa8
+        if (bytes_rdonly_time > 0) {
4b6aa8
+            rdonly_time_contents[bytes_rdonly_time] = '\0';
4b6aa8
+            TS_ASSERT_STRING_EQ(rdonly_time_contents, time, "Read only time");
4b6aa8
+        }
4b6aa8
+        else {
4b6aa8
+            TS_PRINTF("FD %d read error: %s\n", fd_rdonly_time, strerror(errno));
4b6aa8
+        }
4b6aa8
+        free(time);
4b6aa8
+        close(fd_rdonly_time);
4b6aa8
+    }
4b6aa8
+
4b6aa8
+    TS_ASSERT_SIGNED_EQ(dd_open_item(dd, "time", O_RDWR | O_EXCL), -ENOTSUP);
4b6aa8
+
4b6aa8
+    const int fd_rdwr_time = dd_open_item(dd, "time", O_RDWR);
4b6aa8
+    TS_ASSERT_SIGNED_GE(fd_rdwr_time, 0);
4b6aa8
+    if (g_testsuite_last_ok) {
4b6aa8
+        full_write_str(fd_rdwr_time, "7654321");
4b6aa8
+
4b6aa8
+        TS_ASSERT_FUNCTION(lseek(fd_rdwr_time, 0, SEEK_SET));
4b6aa8
+
4b6aa8
+        char rdwr_time_contents[16];
4b6aa8
+        int bytes_rdwr_time = full_read(fd_rdwr_time, rdwr_time_contents, sizeof(rdwr_time_contents));
4b6aa8
+        close(fd_rdwr_time);
4b6aa8
+
4b6aa8
+        TS_ASSERT_SIGNED_GT(bytes_rdwr_time, 0);
4b6aa8
+        if (g_testsuite_last_ok) {
4b6aa8
+            rdwr_time_contents[bytes_rdwr_time] = '\0';
4b6aa8
+
4b6aa8
+            char *const time_contents = dd_load_text(dd, "time");
4b6aa8
+            TS_ASSERT_STRING_EQ(rdwr_time_contents, "7654321", "Successfully wrote time data");
4b6aa8
+            TS_ASSERT_STRING_EQ(time_contents, "7654321", "Successfully wrote time data");
4b6aa8
+            TS_ASSERT_STRING_EQ(rdwr_time_contents, time_contents, "Read only time");
4b6aa8
+            free(time_contents);
4b6aa8
+
4b6aa8
+        }
4b6aa8
+        else {
4b6aa8
+            TS_PRINTF("FD %d read error: %s\n", fd_rdwr_time, strerror(errno));
4b6aa8
+        }
4b6aa8
+    }
4b6aa8
+
4b6aa8
+    testsuite_dump_dir_delete(dd);
4b6aa8
+}
4b6aa8
+TS_RETURN_MAIN
4b6aa8
+]])
4b6aa8
+
4b6aa8
+
4b6aa8
+## ----------------- ##
4b6aa8
+## dd_open_item_file ##
4b6aa8
+## ----------------- ##
4b6aa8
+
4b6aa8
+AT_TESTFUN([dd_open_item_file], [[
4b6aa8
+#include "testsuite.h"
4b6aa8
+#include "testsuite_tools.h"
4b6aa8
+
4b6aa8
+TS_MAIN
4b6aa8
+{
4b6aa8
+    struct dump_dir *dd = testsuite_dump_dir_create(-1, -1, 0);
4b6aa8
+    dd->dd_time = (time_t)1234567;
4b6aa8
+    dd_create_basic_files(dd, geteuid(), NULL);
4b6aa8
+
4b6aa8
+    TS_ASSERT_PTR_IS_NULL(dd_open_item_file(dd, "", O_RDWR));
4b6aa8
+    TS_ASSERT_PTR_IS_NULL(dd_open_item_file(dd, "/", O_RDWR));
4b6aa8
+    TS_ASSERT_PTR_IS_NULL(dd_open_item_file(dd, "//", O_RDWR));
4b6aa8
+    TS_ASSERT_PTR_IS_NULL(dd_open_item_file(dd, "/a", O_RDWR));
4b6aa8
+    TS_ASSERT_PTR_IS_NULL(dd_open_item_file(dd, "a/", O_RDWR));
4b6aa8
+    TS_ASSERT_PTR_IS_NULL(dd_open_item_file(dd, ".", O_RDWR));
4b6aa8
+    TS_ASSERT_PTR_IS_NULL(dd_open_item_file(dd, "..", O_RDWR));
4b6aa8
+    TS_ASSERT_PTR_IS_NULL(dd_open_item_file(dd, "/.", O_RDWR));
4b6aa8
+    TS_ASSERT_PTR_IS_NULL(dd_open_item_file(dd, "//.", O_RDWR));
4b6aa8
+    TS_ASSERT_PTR_IS_NULL(dd_open_item_file(dd, "./", O_RDWR));
4b6aa8
+    TS_ASSERT_PTR_IS_NULL(dd_open_item_file(dd, ".//", O_RDWR));
4b6aa8
+    TS_ASSERT_PTR_IS_NULL(dd_open_item_file(dd, "/./", O_RDWR));
4b6aa8
+    TS_ASSERT_PTR_IS_NULL(dd_open_item_file(dd, "/..", O_RDWR));
4b6aa8
+    TS_ASSERT_PTR_IS_NULL(dd_open_item_file(dd, "//..", O_RDWR));
4b6aa8
+    TS_ASSERT_PTR_IS_NULL(dd_open_item_file(dd, "../", O_RDWR));
4b6aa8
+    TS_ASSERT_PTR_IS_NULL(dd_open_item_file(dd, "..//", O_RDWR));
4b6aa8
+    TS_ASSERT_PTR_IS_NULL(dd_open_item_file(dd, "/../", O_RDWR));
4b6aa8
+    TS_ASSERT_PTR_IS_NULL(dd_open_item_file(dd, "/.././", O_RDWR));
4b6aa8
+    TS_ASSERT_PTR_IS_NULL(dd_open_item_file(dd, "looks-good-but-evil/", O_RDWR));
4b6aa8
+    TS_ASSERT_PTR_IS_NULL(dd_open_item_file(dd, "looks-good-but-evil/../../", O_RDWR));
4b6aa8
+    TS_ASSERT_PTR_IS_NULL(dd_open_item_file(dd, "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890+-=", O_RDWR));
4b6aa8
+
4b6aa8
+    TS_ASSERT_PTR_IS_NULL(dd_open_item_file(dd, "nofile", O_RDONLY));
4b6aa8
+
4b6aa8
+    FILE *const f_rdwr_noent = dd_open_item_file(dd, "nofile", O_RDWR);
4b6aa8
+    TS_ASSERT_PTR_IS_NOT_NULL(f_rdwr_noent);
4b6aa8
+    if (g_testsuite_last_ok) {
4b6aa8
+        fprintf(f_rdwr_noent, "%s", "f_rdwr_noent");
4b6aa8
+        rewind(f_rdwr_noent);
4b6aa8
+
4b6aa8
+        char rdwr_contents[256];
4b6aa8
+        TS_ASSERT_PTR_IS_NOT_NULL(fgets(rdwr_contents, sizeof(rdwr_contents), f_rdwr_noent));
4b6aa8
+        TS_ASSERT_STRING_EQ(rdwr_contents, "f_rdwr_noent", "Successfully read data");
4b6aa8
+        fclose(f_rdwr_noent);
4b6aa8
+
4b6aa8
+        char *const noent_contents = dd_load_text(dd, "nofile");
4b6aa8
+        TS_ASSERT_STRING_EQ(noent_contents, "f_rdwr_noent", "Successfully wrote data");
4b6aa8
+        free(noent_contents);
4b6aa8
+    }
4b6aa8
+
4b6aa8
+    TS_ASSERT_PTR_IS_NULL(dd_open_item_file(dd, "time", O_RDONLY | O_EXCL));
4b6aa8
+
4b6aa8
+    FILE *const f_rdonly_time = dd_open_item_file(dd, "time", O_RDONLY);
4b6aa8
+    TS_ASSERT_PTR_IS_NOT_NULL(f_rdonly_time);
4b6aa8
+    if (g_testsuite_last_ok) {
4b6aa8
+        char *time = dd_load_text(dd, "time");
4b6aa8
+        TS_ASSERT_PTR_IS_NOT_NULL(time);
4b6aa8
+
4b6aa8
+        char rdonly_time_contents[16];
4b6aa8
+        char *const res = fgets(rdonly_time_contents, sizeof(rdonly_time_contents), f_rdonly_time);
4b6aa8
+        TS_ASSERT_PTR_EQ(rdonly_time_contents, res);
4b6aa8
+        if (g_testsuite_last_ok) {
4b6aa8
+            TS_ASSERT_STRING_EQ(rdonly_time_contents, time, "Read only time");
4b6aa8
+        }
4b6aa8
+        else {
4b6aa8
+            TS_PRINTF("File 'time' read error: %s\n", strerror(errno));
4b6aa8
+        }
4b6aa8
+        fclose(f_rdonly_time);
4b6aa8
+    }
4b6aa8
+
4b6aa8
+    TS_ASSERT_PTR_IS_NULL(dd_open_item_file(dd, "time", O_RDWR | O_EXCL));
4b6aa8
+
4b6aa8
+    FILE *const f_rdwr_time = dd_open_item_file(dd, "time", O_RDWR);
4b6aa8
+    TS_ASSERT_PTR_IS_NOT_NULL(f_rdwr_time);
4b6aa8
+    if (g_testsuite_last_ok) {
4b6aa8
+        fprintf(f_rdwr_time, "7654321");
4b6aa8
+        rewind(f_rdwr_noent);
4b6aa8
+
4b6aa8
+        char rdwr_contents[256];
4b6aa8
+        TS_ASSERT_PTR_IS_NOT_NULL(fgets(rdwr_contents, sizeof(rdwr_contents), f_rdwr_noent));
4b6aa8
+        TS_ASSERT_STRING_EQ(rdwr_contents, "7654321", "Successfully read time data");
4b6aa8
+        fclose(f_rdwr_time);
4b6aa8
+
4b6aa8
+        char *const time_contents = dd_load_text(dd, "time");
4b6aa8
+        TS_ASSERT_STRING_EQ(time_contents, "7654321", "Successfully wrote time data");
4b6aa8
+        free(time_contents);
4b6aa8
+    }
4b6aa8
+
4b6aa8
+    testsuite_dump_dir_delete(dd);
4b6aa8
+}
4b6aa8
+TS_RETURN_MAIN
4b6aa8
+]])
4b6aa8
-- 
4b6aa8
2.17.2
4b6aa8