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

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