* $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
#endif
#include <assert.h>
+#include <dirent.h>
#include <errno.h>
#include <fcntl.h> /* O_LARGEFILE, posix_fadvise (), [posix_]fallocate () */
#include <libgen.h> /* basename (), dirname () */
#include "transmission.h"
#include "file.h"
+#include "log.h"
#include "platform.h"
#include "utils.h"
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)
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;
+}
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;
}
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);
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);
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);
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);
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);
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);
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));
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);
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));
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));
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);
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)
{
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;
#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)
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),
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;
+}
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,
}
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,
*
* 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.
*
* @{
*/
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);
+
/** @} */
/** @} */
#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;
*/
tr_quark tr_quark_new (const void * str, size_t len);
+/***
+****
+***/
+
+#ifdef __cplusplus
+}
+#endif
#endif