]> granicus.if.org Git - transmission/commitdiff
(trunk, libt) #4160 - the slow slog to catch trunk up to mike.dld's 4160 diff continu...
authorJordan Lee <jordan@transmissionbt.com>
Sun, 21 Sep 2014 17:52:36 +0000 (17:52 +0000)
committerJordan Lee <jordan@transmissionbt.com>
Sun, 21 Sep 2014 17:52:36 +0000 (17:52 +0000)
libtransmission/file-posix.c
libtransmission/file-test.c
libtransmission/file-win32.c
libtransmission/file.h
libtransmission/quark.h

index afcc0b21254cc4f1a190473d9814590492d510f7..a5fa7ec0dd0c594f4916b05ce1e7557ed87c0f0f 100644 (file)
@@ -7,7 +7,12 @@
  * $Id$
  */
 
-#if (defined (HAVE_POSIX_FADVISE) || defined (HAVE_POSIX_FALLOCATE)) && (!defined (_XOPEN_SOURCE) || _XOPEN_SOURCE < 600)
+#if defined (HAVE_MKDTEMP) && (!defined (_XOPEN_SOURCE) || _XOPEN_SOURCE < 700)
+ #ifdef _XOPEN_SOURCE
+  #undef _XOPEN_SOURCE
+ #endif
+ #define _XOPEN_SOURCE 700
+#elif (defined (HAVE_POSIX_FADVISE) || defined (HAVE_POSIX_FALLOCATE)) && (!defined (_XOPEN_SOURCE) || _XOPEN_SOURCE < 600)
  #ifdef _XOPEN_SOURCE
   #undef _XOPEN_SOURCE
  #endif
@@ -23,6 +28,7 @@
 #endif
 
 #include <assert.h>
+#include <dirent.h>
 #include <errno.h>
 #include <fcntl.h> /* O_LARGEFILE, posix_fadvise (), [posix_]fallocate () */
 #include <libgen.h> /* basename (), dirname () */
@@ -41,6 +47,7 @@
 
 #include "transmission.h"
 #include "file.h"
+#include "log.h"
 #include "platform.h"
 #include "utils.h"
 
@@ -147,6 +154,79 @@ set_file_for_single_pass (tr_sys_file_t handle)
   errno = err;
 }
 
+#ifndef HAVE_MKDIRP
+
+static bool
+create_path (const char  * path_in,
+             int           permissions,
+             tr_error   ** error)
+{
+  char * p;
+  char * pp;
+  bool done;
+  int tmperr;
+  int rv;
+  struct stat sb;
+  char * path;
+
+  /* make a temporary copy of path */
+  path = tr_strdup (path_in);
+
+  /* walk past the root */
+  p = path;
+  while (*p == TR_PATH_DELIMITER)
+    ++p;
+
+  pp = p;
+  done = false;
+  while ((p = strchr (pp, TR_PATH_DELIMITER)) || (p = strchr (pp, '\0')))
+    {
+      if (!*p)
+        done = true;
+      else
+        *p = '\0';
+
+      tmperr = errno;
+      rv = stat (path, &sb);
+      errno = tmperr;
+      if (rv)
+        {
+          tr_error * my_error = NULL;
+
+          /* Folder doesn't exist yet */
+          if (!tr_sys_dir_create (path, 0, permissions, &my_error))
+            {
+              tr_logAddError (_ ("Couldn't create \"%1$s\": %2$s"), path, my_error->message);
+              tr_free (path);
+              tr_error_propagate (error, &my_error);
+              return false;
+            }
+        }
+      else if ((sb.st_mode & S_IFMT) != S_IFDIR)
+        {
+          /* Node exists but isn't a folder */
+          char * const buf = tr_strdup_printf (_ ("File \"%s\" is in the way"), path);
+          tr_logAddError (_ ("Couldn't create \"%1$s\": %2$s"), path_in, buf);
+          tr_free (buf);
+          tr_free (path);
+          set_system_error (error, ENOTDIR);
+          return false;
+        }
+
+      if (done)
+        break;
+
+      *p = TR_PATH_DELIMITER;
+      p++;
+      pp = p;
+    }
+
+  tr_free (path);
+  return true;
+}
+
+#endif
+
 bool
 tr_sys_path_exists (const char  * path,
                     tr_error   ** error)
@@ -824,3 +904,166 @@ tr_sys_file_unmap (const void  * address,
 
   return ret;
 }
+
+char *
+tr_sys_dir_get_current (tr_error ** error)
+{
+  char * ret;
+
+  ret = getcwd (NULL, 0);
+
+  if (ret == NULL && (errno == EINVAL || errno == ERANGE))
+    {
+      size_t size = PATH_MAX;
+      char * tmp = NULL;
+
+      do
+        {
+          tmp = tr_renew (char, tmp, size);
+          if (tmp == NULL)
+            break;
+          ret = getcwd (tmp, size);
+          size += 2048;
+        }
+      while (ret == NULL && errno == ERANGE);
+
+      if (ret == NULL)
+        {
+          const int err = errno;
+          tr_free (tmp);
+          errno = err;
+        }
+    }
+
+  if (ret == NULL)
+    set_system_error (error, errno);
+
+  return ret;
+}
+
+bool
+tr_sys_dir_create (const char  * path,
+                   int           flags,
+                   int           permissions,
+                   tr_error   ** error)
+{
+  bool ret;
+  tr_error * my_error = NULL;
+
+  assert (path != NULL);
+
+  if ((flags & TR_SYS_DIR_CREATE_PARENTS) != 0)
+#ifdef HAVE_MKDIRP
+    ret = mkdirp (path, permissions) != -1;
+#else
+    ret = create_path (path, permissions, &my_error);
+#endif
+  else
+    ret = mkdir (path, permissions) != -1;
+
+  if (!ret && errno == EEXIST)
+    {
+      struct stat sb;
+
+      if (stat (path, &sb) != -1 && S_ISDIR (sb.st_mode))
+        {
+          tr_error_clear (&my_error);
+          ret = true;
+        }
+      else
+        {
+          errno = EEXIST;
+        }
+    }
+
+  if (!ret)
+    {
+      if (my_error != NULL)
+        tr_error_propagate (error, &my_error);
+      else
+        set_system_error (error, errno);
+    }
+
+  return ret;
+}
+
+bool
+tr_sys_dir_create_temp (char      * path_template,
+                        tr_error ** error)
+{
+  bool ret;
+
+  assert (path_template != NULL);
+
+#ifdef HAVE_MKDTEMP
+
+  ret = mkdtemp (path_template) != NULL;
+
+#else
+
+  ret = mktemp (path_template) != NULL && mkdir (path_template, 0700) != -1;
+
+#endif
+
+  if (!ret)
+    set_system_error (error, errno);
+
+  return ret;
+}
+
+tr_sys_dir_t
+tr_sys_dir_open (const char  * path,
+                 tr_error   ** error)
+{
+  tr_sys_dir_t ret;
+
+#ifndef __clang__
+  /* Clang gives "static_assert expression is not an integral constant expression" error */
+  TR_STATIC_ASSERT (TR_BAD_SYS_DIR == NULL, "values should match");
+#endif
+
+  assert (path != NULL);
+
+  ret = opendir (path);
+
+  if (ret == TR_BAD_SYS_DIR)
+    set_system_error (error, errno);
+
+  return ret;
+}
+
+const char *
+tr_sys_dir_read_name (tr_sys_dir_t    handle,
+                      tr_error     ** error)
+{
+  const char * ret = NULL;
+  struct dirent * entry;
+
+  assert (handle != TR_BAD_SYS_DIR);
+
+  errno = 0;
+  entry = readdir (handle);
+
+  if (entry != NULL)
+    ret = entry->d_name;
+  else if (errno != 0)
+    set_system_error (error, errno);
+
+  return ret;
+}
+
+bool
+tr_sys_dir_close (tr_sys_dir_t    handle,
+                  tr_error     ** error)
+{
+  bool ret;
+
+  assert (handle != TR_BAD_SYS_DIR);
+
+  ret = closedir (handle) != -1;
+
+  if (!ret)
+    set_system_error (error, errno);
+
+  return ret;
+}
index 19a3d69b60299e3691f0338744c30011b1fcb48a..f21b9eaefd03a3eeeba4f1555de757fbfcd08d1b 100644 (file)
@@ -35,7 +35,7 @@ static char *
 create_test_dir (const char * name)
 {
   char * const test_dir = tr_buildPath (tr_sessionGetConfigDir (session), name, NULL);
-  tr_mkdirp (test_dir, 0777);
+  tr_sys_dir_create (test_dir, 0, 0777, NULL);
   return test_dir;
 }
 
@@ -194,7 +194,7 @@ test_get_info (void)
   tr_sys_path_remove (path1, NULL);
 
   /* Good directory info */
-  tr_mkdirp (path1, 0777);
+  tr_sys_dir_create (path1, 0, 0777, NULL);
   clear_path_info (&info);
   check (tr_sys_path_get_info (path1, 0, &info, &err));
   check (err == NULL);
@@ -233,7 +233,7 @@ test_get_info (void)
       tr_sys_path_remove (path2, NULL);
 
       /* Good directory info */
-      tr_mkdirp (path2, 0777);
+      tr_sys_dir_create (path2, 0, 0777, NULL);
       clear_path_info (&info);
       check (tr_sys_path_get_info (path1, 0, &info, &err));
       check (err == NULL);
@@ -278,7 +278,7 @@ test_path_exists (void)
   tr_sys_path_remove (path1, NULL);
 
   /* Create directory and see that it exists */
-  tr_mkdirp (path1, 0777);
+  tr_sys_dir_create (path1, 0, 0777, NULL);
   check (tr_sys_path_exists (path1, &err));
   check (err == NULL);
 
@@ -298,7 +298,7 @@ test_path_exists (void)
       tr_sys_path_remove (path2, NULL);
 
       /* Create directory and see that it exists (via symlink) */
-      tr_mkdirp (path2, 0777);
+      tr_sys_dir_create (path2, 0, 0777, NULL);
       check (tr_sys_path_exists (path1, &err));
       check (err == NULL);
 
@@ -353,7 +353,7 @@ test_path_is_same (void)
   tr_sys_path_remove (path1, NULL);
 
   /* Two same directories are the same */
-  tr_mkdirp (path1, 0777);
+  tr_sys_dir_create (path1, 0, 0777, NULL);
   check (tr_sys_path_is_same (path1, path1, &err));
   check (err == NULL);
 
@@ -366,7 +366,7 @@ test_path_is_same (void)
   tr_sys_path_remove (path2, NULL);
 
   /* Two separate directories are not the same */
-  tr_mkdirp (path2, 0777);
+  tr_sys_dir_create (path2, 0, 0777, NULL);
   check (!tr_sys_path_is_same (path1, path2, &err));
   check (err == NULL);
 
@@ -404,7 +404,7 @@ test_path_is_same (void)
       tr_sys_path_remove (path2, NULL);
 
       /* Directory and symlink pointing to another directory are not the same */
-      tr_mkdirp (path2, 0777);
+      tr_sys_dir_create (path2, 0, 0777, NULL);
       check (!tr_sys_path_is_same (path1, path2, &err));
       check (err == NULL);
       check (!tr_sys_path_is_same (path2, path1, &err));
@@ -552,7 +552,7 @@ test_path_resolve (void)
       tr_free (tmp);
 
       tr_sys_path_remove (path1, NULL);
-      tr_mkdirp (path1, 0755);
+      tr_sys_dir_create (path1, 0, 0755, NULL);
 
       tmp = tr_sys_path_resolve (path2, &err);
       check (tmp != NULL);
@@ -692,7 +692,7 @@ test_path_rename (void)
   check (tr_sys_path_rename (path2, path1, &err));
   check (err == NULL);
 
-  tr_mkdirp (path2, 0777);
+  tr_sys_dir_create (path2, 0, 0777, NULL);
 
   /* Renaming file does not overwrite existing directory, and vice versa */
   check (!tr_sys_path_rename (path1, path2, &err));
@@ -785,14 +785,14 @@ test_path_remove (void)
   check (!tr_sys_path_exists (path1, NULL));
 
   /* Removing empty directory works */
-  tr_mkdirp (path1, 0777);
+  tr_sys_dir_create (path1, 0, 0777, NULL);
   check (tr_sys_path_exists (path1, NULL));
   check (tr_sys_path_remove (path1, &err));
   check (err == NULL);
   check (!tr_sys_path_exists (path1, NULL));
 
   /* Removing non-empty directory fails */
-  tr_mkdirp (path2, 0777);
+  tr_sys_dir_create (path2, 0, 0777, NULL);
   libtest_create_file_with_string_contents (path3, "test");
   check (tr_sys_path_exists (path2, NULL));
   check (tr_sys_path_exists (path3, NULL));
@@ -837,7 +837,7 @@ test_file_open (void)
   tr_error_clear (&err);
 
   /* Can't open directory */
-  tr_mkdirp (path1, 0777);
+  tr_sys_dir_create (path1, 0, 0777, NULL);
 #ifdef _WIN32
   /* This works on *NIX */
   check (tr_sys_file_open (path1, TR_SYS_FILE_READ, 0600, &err) == TR_BAD_SYS_FILE);
@@ -1152,6 +1152,146 @@ test_file_map (void)
   return 0;
 }
 
+static int
+test_dir_create (void)
+{
+  char * const test_dir = create_test_dir (__FUNCTION__);
+  tr_error * err = NULL;
+  char * path1, * path2;
+
+  path1 = tr_buildPath (test_dir, "a", NULL);
+  path2 = tr_buildPath (path1, "b", NULL);
+
+  /* Can create directory which has parent */
+  check (tr_sys_dir_create (path1, 0, 0700, &err));
+  check (err == NULL);
+  check (tr_sys_path_exists (path1, NULL));
+  check (validate_permissions (path1, 0700));
+
+  tr_sys_path_remove (path1, NULL);
+  libtest_create_file_with_string_contents (path1, "test");
+
+  /* Can't create directory where file already exists */
+  check (!tr_sys_dir_create (path1, 0, 0700, &err));
+  check (err != NULL);
+  tr_error_clear (&err);
+  check (!tr_sys_dir_create (path1, TR_SYS_DIR_CREATE_PARENTS, 0700, &err));
+  check (err != NULL);
+  tr_error_clear (&err);
+
+  tr_sys_path_remove (path1, NULL);
+
+  /* Can't create directory which has no parent */
+  check (!tr_sys_dir_create (path2, 0, 0700, &err));
+  check (err != NULL);
+  check (!tr_sys_path_exists (path2, NULL));
+  tr_error_clear (&err);
+
+  /* Can create directory with parent directories */
+  check (tr_sys_dir_create (path2, TR_SYS_DIR_CREATE_PARENTS, 0751, &err));
+  check (err == NULL);
+  check (tr_sys_path_exists (path1, NULL));
+  check (tr_sys_path_exists (path2, NULL));
+  check (validate_permissions (path1, 0751));
+  check (validate_permissions (path2, 0751));
+
+  /* Can create existing directory (no-op) */
+  check (tr_sys_dir_create (path1, 0, 0700, &err));
+  check (err == NULL);
+  check (tr_sys_dir_create (path1, TR_SYS_DIR_CREATE_PARENTS, 0700, &err));
+  check (err == NULL);
+
+  tr_sys_path_remove (path2, NULL);
+  tr_sys_path_remove (path1, NULL);
+
+  tr_free (path2);
+  tr_free (path1);
+
+  tr_free (test_dir);
+  return 0;
+}
+
+static int
+test_dir_read_impl (const char * path,
+                      bool       * have1,
+                      bool       * have2)
+{
+  tr_error * err = NULL;
+  tr_sys_dir_t dd;
+  const char * name;
+
+  *have1 = *have2 = false;
+
+  dd = tr_sys_dir_open (path, &err);
+  check (dd != TR_BAD_SYS_DIR);
+  check (err == NULL);
+
+  while ((name = tr_sys_dir_read_name (dd, &err)) != NULL)
+    {
+      check (err == NULL);
+
+      if (strcmp (name, ".") == 0 || strcmp (name, "..") == 0)
+        continue;
+
+      if (strcmp (name, "a") == 0)
+        *have1 = true;
+      else if (strcmp (name, "b") == 0)
+        *have2 = true;
+      else
+        check (false);
+    }
+
+  check (err == NULL);
+
+  check (tr_sys_dir_close (dd, &err));
+  check (err == NULL);
+
+  return 0;
+}
+
+static int
+test_dir_read (void)
+{
+  char * const test_dir = create_test_dir (__FUNCTION__);
+  char * path1, * path2;
+  bool have1, have2;
+
+  path1 = tr_buildPath (test_dir, "a", NULL);
+  path2 = tr_buildPath (test_dir, "b", NULL);
+
+  if (test_dir_read_impl (test_dir, &have1, &have2) != 0)
+    return 1;
+  check (!have1);
+  check (!have2);
+
+  libtest_create_file_with_string_contents (path1, "test");
+
+  if (test_dir_read_impl (test_dir, &have1, &have2) != 0)
+    return 1;
+  check (have1);
+  check (!have2);
+
+  libtest_create_file_with_string_contents (path2, "test");
+
+  if (test_dir_read_impl (test_dir, &have1, &have2) != 0)
+    return 1;
+  check (have1);
+  check (have2);
+
+  tr_sys_path_remove (path1, NULL);
+
+  if (test_dir_read_impl (test_dir, &have1, &have2) != 0)
+    return 1;
+  check (!have1);
+  check (have2);
+
+  tr_free (path2);
+  tr_free (path1);
+
+  tr_free (test_dir);
+  return 0;
+}
+
 int
 main (void)
 {
@@ -1168,7 +1308,9 @@ main (void)
       test_file_read_write_seek,
       test_file_truncate,
       test_file_preallocate,
-      test_file_map
+      test_file_map,
+      test_dir_create,
+      test_dir_read
     };
   int ret;
 
index f5340d31f34d893601aab51740374ed2e42a02f1..d9028ca942456cf5a8e329dd1705c5707f845026 100644 (file)
@@ -10,6 +10,7 @@
 #include <assert.h>
 #include <stdlib.h> /* _splitpath_s (), _makepath_s () */
 
+#include <shlobj.h> /* SHCreateDirectoryEx () */
 #include <winioctl.h> /* FSCTL_SET_SPARSE */
 
 #include "transmission.h"
    "i64" suffix for C code, but no warning is issued */
 #define DELTA_EPOCH_IN_MICROSECS 11644473600000000ULL
 
+struct tr_sys_dir_win32
+{
+  wchar_t * pattern;
+  HANDLE find_handle;
+  WIN32_FIND_DATAW find_data;
+  char * utf8_name;
+};
+
 static void
 set_system_error (tr_error ** error,
                   DWORD       code)
@@ -123,6 +132,54 @@ open_file (const char  * path,
   return ret;
 }
 
+static bool
+create_dir (const char  * path,
+            int           flags,
+            int           permissions,
+            bool          okay_if_exists,
+            tr_error   ** error)
+{
+  bool ret;
+  wchar_t * wide_path;
+  DWORD error_code = ERROR_SUCCESS;
+
+  assert (path != NULL);
+
+  wide_path = tr_win32_utf8_to_native (path, -1);
+
+  if ((flags & TR_SYS_DIR_CREATE_PARENTS) != 0)
+    {
+      /* For some reason SHCreateDirectoryEx has issues with forward slashes */
+      wchar_t * p = wide_path;
+      while ((p = wcschr (p, L'/')) != NULL)
+        *p++ = L'\\';
+
+      error_code = SHCreateDirectoryExW (NULL, wide_path, NULL);
+      ret = error_code == ERROR_SUCCESS;
+    }
+  else
+    {
+      ret = CreateDirectoryW (wide_path, NULL);
+      if (!ret)
+        error_code = GetLastError ();
+    }
+
+  if (!ret && error_code == ERROR_ALREADY_EXISTS && okay_if_exists)
+    {
+      const DWORD attributes = GetFileAttributesW (wide_path);
+      if (attributes != INVALID_FILE_ATTRIBUTES &&
+          (attributes & FILE_ATTRIBUTE_DIRECTORY) != 0)
+        ret = true;
+    }
+
+  if (!ret)
+    set_system_error (error, error_code);
+
+  tr_free (wide_path);
+
+  return ret;
+}
+
 static void
 create_temp_path (char      * path_template,
                   void     (* callback) (const char * path, void * param, tr_error ** error),
@@ -965,3 +1022,162 @@ tr_sys_file_unmap (const void  * address,
 
   return ret;
 }
+
+char *
+tr_sys_dir_get_current (tr_error ** error)
+{
+  char * ret = NULL;
+  wchar_t * wide_ret = NULL;
+  DWORD size;
+
+  size = GetCurrentDirectoryW (0, NULL);
+
+  if (size != 0)
+    {
+      wide_ret = tr_new (wchar_t, size);
+      if (GetCurrentDirectoryW (size, wide_ret) != 0)
+        ret = tr_win32_native_to_utf8 (wide_ret, size);
+    }
+
+  if (ret == NULL)
+    set_system_error (error, GetLastError ());
+
+  tr_free (wide_ret);
+
+  return ret;
+}
+
+bool
+tr_sys_dir_create (const char  * path,
+                   int           flags,
+                   int           permissions,
+                   tr_error   ** error)
+{
+  return create_dir (path, flags, permissions, true, error);
+}
+
+static void
+dir_create_temp_callback (const char  * path,
+                          void        * param,
+                          tr_error   ** error)
+{
+  bool * result = (bool *) param;
+
+  assert (result != NULL);
+
+  *result = create_dir (path, 0, 0, false, error);
+}
+
+bool
+tr_sys_dir_create_temp (char      * path_template,
+                        tr_error ** error)
+{
+  bool ret = false;
+
+  assert (path_template != NULL);
+
+  create_temp_path (path_template, dir_create_temp_callback, &ret, error);
+
+  return ret;
+}
+
+tr_sys_dir_t
+tr_sys_dir_open (const char  * path,
+                 tr_error   ** error)
+{
+  tr_sys_dir_t ret;
+
+#ifndef __clang__
+  /* Clang gives "static_assert expression is not an integral constant expression" error */
+  TR_STATIC_ASSERT (TR_BAD_SYS_DIR == NULL, "values should match");
+#endif
+
+  assert (path != NULL);
+
+  ret = tr_new (struct tr_sys_dir_win32, 1);
+  ret->pattern = tr_win32_utf8_to_native_ex (path, -1, 2);
+
+  if (ret->pattern != NULL)
+    {
+      const size_t pattern_size = wcslen (ret->pattern);
+      ret->pattern[pattern_size + 0] = L'\\';
+      ret->pattern[pattern_size + 1] = L'*';
+      ret->pattern[pattern_size + 2] = L'\0';
+
+      ret->find_handle = INVALID_HANDLE_VALUE;
+      ret->utf8_name = NULL;
+    }
+  else
+    {
+      set_system_error (error, GetLastError ());
+
+      tr_free (ret->pattern);
+      tr_free (ret);
+
+      ret = NULL;
+    }
+
+  return ret;
+}
+
+const char *
+tr_sys_dir_read_name (tr_sys_dir_t    handle,
+                      tr_error     ** error)
+{
+  char * ret;
+  DWORD error_code = ERROR_SUCCESS;
+
+  assert (handle != TR_BAD_SYS_DIR);
+
+  if (handle->find_handle == INVALID_HANDLE_VALUE)
+    {
+      handle->find_handle = FindFirstFileW (handle->pattern, &handle->find_data);
+      if (handle->find_handle == INVALID_HANDLE_VALUE)
+        error_code = GetLastError ();
+    }
+  else
+    {
+      if (!FindNextFileW (handle->find_handle, &handle->find_data))
+        error_code = GetLastError ();
+    }
+
+  if (error_code != ERROR_SUCCESS)
+    {
+      set_system_error_if_file_found (error, error_code);
+      return NULL;
+    }
+
+  ret = tr_win32_native_to_utf8 (handle->find_data.cFileName, -1);
+
+  if (ret != NULL)
+    {
+      tr_free (handle->utf8_name);
+      handle->utf8_name = ret;
+    }
+  else
+    {
+      set_system_error (error, GetLastError ());
+    }
+
+  return ret;
+}
+
+bool
+tr_sys_dir_close (tr_sys_dir_t    handle,
+                  tr_error     ** error)
+{
+  bool ret;
+
+  assert (handle != TR_BAD_SYS_DIR);
+
+  ret = FindClose (handle->find_handle);
+
+  if (!ret)
+    set_system_error (error, GetLastError ());
+
+  tr_free (handle->utf8_name);
+  tr_free (handle->pattern);
+  tr_free (handle);
+
+  return ret;
+}
index d75131015ab61c1db42291604d9261d2d0dc8d96..5ab191380c11344c09eb98df261ed314c2f60961 100644 (file)
@@ -33,11 +33,17 @@ extern "C" {
  typedef int tr_sys_file_t;
  /** @brief Platform-specific invalid file descriptor constant. */
  #define TR_BAD_SYS_FILE (-1)
+ /** @brief Platform-specific directory descriptor type. */
+ typedef void * tr_sys_dir_t;
 #else
  typedef HANDLE tr_sys_file_t;
  #define TR_BAD_SYS_FILE INVALID_HANDLE_VALUE
+ typedef struct tr_sys_dir_win32 * tr_sys_dir_t;
 #endif
 
+/** @brief Platform-specific invalid directory descriptor constant. */
+#define TR_BAD_SYS_DIR ((tr_sys_dir_t)NULL)
+
 typedef enum
 {
   TR_STD_SYS_FILE_IN,
@@ -78,6 +84,12 @@ typedef enum
 }
 tr_sys_file_preallocate_flags_t;
 
+typedef enum
+{
+    TR_SYS_DIR_CREATE_PARENTS = 1 << 0
+}
+tr_sys_dir_create_flags_t;
+
 typedef enum
 {
   TR_SYS_PATH_IS_FILE,
@@ -99,9 +111,9 @@ tr_sys_path_info;
  *
  * Following functions accept paths in UTF-8 encoding and convert them to native
  * encoding internally if needed.
- * Descriptors returned (@ref tr_sys_file_t) may have different type depending
- * on platform and should generally not be passed to native functions, but to
- * wrapper functions instead.
+ * Descriptors returned (@ref tr_sys_file_t and @ref tr_sys_dir_t) may have
+ * different type depending on platform and should generally not be passed to
+ * native functions, but to wrapper functions instead.
  *
  * @{
  */
@@ -497,6 +509,94 @@ bool            tr_sys_file_unmap           (const void         * address,
                                              uint64_t             size,
                                              tr_error          ** error);
 
+/* Directory-related wrappers */
+
+/**
+ * @brief Portability wrapper for `getcwd ()`.
+ *
+ * @param[out] error Pointer to error object. Optional, pass `NULL` if you are
+ *                   not interested in error details.
+ *
+ * @return Pointer to newly allocated buffer containing path to current
+ *         directory (use @ref tr_free to free it when no longer needed) on
+ *         success, `NULL` otherwise (with `error` set accordingly).
+ */
+char          * tr_sys_dir_get_current      (tr_error          ** error);
+
+/**
+ * @brief Like `mkdir ()`, but makes parent directories if needed.
+ *
+ * @param[in]  path        Path to directory.
+ * @param[in]  flags       Combination of @ref tr_sys_dir_create_flags_t values.
+ * @param[in]  permissions Permissions to create directory with. Not used on
+                           Windows.
+ * @param[out] error       Pointer to error object. Optional, pass `NULL` if you
+ *                         are not interested in error details.
+ *
+ * @return `True` on success, `false` otherwise (with `error` set accordingly).
+ */
+bool            tr_sys_dir_create           (const char         * path,
+                                             int                  flags,
+                                             int                  permissions,
+                                             tr_error          ** error);
+
+/**
+ * @brief Portability wrapper for `mkdtemp ()`.
+ *
+ * @param[in,out] path_template Template path to directory. Should end with at
+ *                              least six 'X' characters. Upon success, trailing
+ *                              'X' characters are replaced with actual random
+ *                              characters used to form a unique path to
+ *                              temporary directory.
+ * @param[out]    error         Pointer to error object. Optional, pass `NULL`
+ *                              if you are not interested in error details.
+ *
+ * @return `True` on success, `false` otherwise (with `error` set accordingly).
+ */
+bool            tr_sys_dir_create_temp      (char               * path_template,
+                                             tr_error          ** error);
+
+/**
+ * @brief Portability wrapper for `opendir ()`.
+ *
+ * @param[in]  path  Path to directory.
+ * @param[out] error Pointer to error object. Optional, pass `NULL` if you are
+ *                   not interested in error details.
+ *
+ * @return Opened directory descriptor on success, `TR_BAD_SYS_DIR` otherwise
+ *         (with `error` set accordingly).
+ */
+tr_sys_dir_t    tr_sys_dir_open             (const char         * path,
+                                             tr_error          ** error);
+
+/**
+ * @brief Portability wrapper for `readdir ()`.
+ *
+ * @param[in]  handle Valid directory descriptor.
+ * @param[out] error  Pointer to error object. Optional, pass `NULL` if you are
+ *                    not interested in error details.
+ *
+ * @return Pointer to next directory entry name (stored internally, DO NOT pass
+ *         it to @ref tr_free) on success, `NULL` otherwise (with `error` set
+ *         accordingly). Note that `NULL` will also be returned in case of end
+ *         of directory; if you need to distinguish the two, check if `error`
+ *         is `NULL` afterwards.
+ */
+const char    * tr_sys_dir_read_name        (tr_sys_dir_t         handle,
+                                             tr_error          ** error);
+
+/**
+ * @brief Portability wrapper for `closedir ()`.
+ *
+ * @param[in]  handle Valid directory descriptor.
+ * @param[out] error  Pointer to error object. Optional, pass `NULL` if you are
+ *                    not interested in error details.
+ *
+ * @return `True` on success, `false` otherwise (with `error` set accordingly).
+ */
+bool            tr_sys_dir_close            (tr_sys_dir_t         handle,
+                                             tr_error          ** error);
+
 /** @} */
 /** @} */
 
index 2e3a9fbf273061b9b21f6f1bbdb3ac127d4d7dd6..545fc68a78472d3a8d682c0b0f5ef768d108227d 100644 (file)
 #ifndef TR_QUARK_H
 #define TR_QUARK_H 1
 
+#ifdef __cplusplus
+extern "C" {
+#endif
+
 /* Quarks — a 2-way association between a string and a unique integer identifier */
 typedef size_t tr_quark;
 
@@ -420,5 +424,12 @@ const char * tr_quark_get_string (tr_quark quark, size_t * len);
  */
 tr_quark tr_quark_new (const void * str, size_t len);
 
+/***
+****
+***/
+
+#ifdef __cplusplus
+}
+#endif
 
 #endif