]> granicus.if.org Git - transmission/commitdiff
(trunk, libT) mike.dld's 4160-02a-path.patch: portability wrapper around file paths.
authorJordan Lee <jordan@transmissionbt.com>
Thu, 3 Jul 2014 21:58:39 +0000 (21:58 +0000)
committerJordan Lee <jordan@transmissionbt.com>
Thu, 3 Jul 2014 21:58:39 +0000 (21:58 +0000)
Transmission.xcodeproj/project.pbxproj
libtransmission/Makefile.am
libtransmission/file-posix.c [new file with mode: 0644]
libtransmission/file-test.c [new file with mode: 0644]
libtransmission/file-win32.c [new file with mode: 0644]
libtransmission/file.h [new file with mode: 0644]
libtransmission/utils.c
libtransmission/utils.h

index 7f8af9cf1b30c778f49d1e3be5ff6d248c964244..7dd97593db6847e49a96bad88a8d480e004dd3b9 100644 (file)
                BEFC1E580C07861A00B0BB3C /* clients.c in Sources */ = {isa = PBXBuildFile; fileRef = BEFC1E1F0C07861A00B0BB3C /* clients.c */; };
                C1077A4E183EB29600634C22 /* error.c in Sources */ = {isa = PBXBuildFile; fileRef = C1077A4A183EB29600634C22 /* error.c */; };
                C1077A4F183EB29600634C22 /* error.h in Headers */ = {isa = PBXBuildFile; fileRef = C1077A4B183EB29600634C22 /* error.h */; };
+               C1077A50183EB29600634C22 /* file-posix.c in Sources */ = {isa = PBXBuildFile; fileRef = C1077A4C183EB29600634C22 /* file-posix.c */; };
+               C1077A51183EB29600634C22 /* file.h in Headers */ = {isa = PBXBuildFile; fileRef = C1077A4D183EB29600634C22 /* file.h */; };
                D4AF3B2F0C41F7A500D46B6B /* list.c in Sources */ = {isa = PBXBuildFile; fileRef = D4AF3B2D0C41F7A500D46B6B /* list.c */; };
                D4AF3B300C41F7A600D46B6B /* list.h in Headers */ = {isa = PBXBuildFile; fileRef = D4AF3B2E0C41F7A500D46B6B /* list.h */; };
                E138A9780C04D88F00C5426C /* ProgressGradients.m in Sources */ = {isa = PBXBuildFile; fileRef = E138A9760C04D88F00C5426C /* ProgressGradients.m */; };
                BEFC1E1F0C07861A00B0BB3C /* clients.c */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.c.c; name = clients.c; path = libtransmission/clients.c; sourceTree = "<group>"; };
                C1077A4A183EB29600634C22 /* error.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; name = error.c; path = libtransmission/error.c; sourceTree = "<group>"; };
                C1077A4B183EB29600634C22 /* error.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = error.h; path = libtransmission/error.h; sourceTree = "<group>"; };
+               C1077A4C183EB29600634C22 /* file-posix.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; name = "file-posix.c"; path = "libtransmission/file-posix.c"; sourceTree = "<group>"; };
+               C1077A4D183EB29600634C22 /* file.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = file.h; path = libtransmission/file.h; sourceTree = "<group>"; };
                D4AF3B2D0C41F7A500D46B6B /* list.c */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.c.c; name = list.c; path = libtransmission/list.c; sourceTree = "<group>"; };
                D4AF3B2E0C41F7A500D46B6B /* list.h */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.c.h; name = list.h; path = libtransmission/list.h; sourceTree = "<group>"; };
                E138A9750C04D88F00C5426C /* ProgressGradients.h */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.c.h; name = ProgressGradients.h; path = macosx/ProgressGradients.h; sourceTree = "<group>"; };
                        children = (
                                C1077A4A183EB29600634C22 /* error.c */,
                                C1077A4B183EB29600634C22 /* error.h */,
+                               C1077A4C183EB29600634C22 /* file-posix.c */,
+                               C1077A4D183EB29600634C22 /* file.h */,
                                4D80185710BBC0B0008A4AF2 /* magnet.c */,
                                4D80185810BBC0B0008A4AF2 /* magnet.h */,
                                4D8017E810BBC073008A4AF2 /* torrent-magnet.c */,
                        isa = PBXHeadersBuildPhase;
                        buildActionMask = 2147483647;
                        files = (
+                               C1077A51183EB29600634C22 /* file.h in Headers */,
                                BEFC1E290C07861A00B0BB3C /* version.h in Headers */,
                                BEFC1E2A0C07861A00B0BB3C /* utils.h in Headers */,
                                BEFC1E2C0C07861A00B0BB3C /* upnp.h in Headers */,
                                4D36BA720CA2F00800A63CA5 /* handshake.c in Sources */,
                                4D36BA740CA2F00800A63CA5 /* peer-io.c in Sources */,
                                4D36BA770CA2F00800A63CA5 /* peer-mgr.c in Sources */,
+                               C1077A50183EB29600634C22 /* file-posix.c in Sources */,
                                4D36BA790CA2F00800A63CA5 /* peer-msgs.c in Sources */,
                                A25D2CBD0CF4C73E0096A262 /* stats.c in Sources */,
                                A201527E0D1C270F0081714F /* torrent-ctor.c in Sources */,
index 4a66d9611e76dcda3dabd7547ed53aa95edb3c0b..e841a2d2bc384467d05a34c5aeef4afaed6027d9 100644 (file)
@@ -72,6 +72,12 @@ libtransmission_a_SOURCES = \
   webseed.c \
   wildmat.c
 
+if WIN32
+libtransmission_a_SOURCES += file-win32.c
+else
+libtransmission_a_SOURCES += file-posix.c
+endif
+
 noinst_HEADERS = \
   announcer.h \
   announcer-common.h \
@@ -85,6 +91,7 @@ noinst_HEADERS = \
   completion.h \
   error.h \
   fdlimit.h \
+  file.h \
   handshake.h \
   history.h \
   inout.h \
@@ -136,6 +143,7 @@ TESTS = \
   clients-test \
   crypto-test \
   error-test \
+  file-test \
   history-test \
   json-test \
   magnet-test \
@@ -191,6 +199,10 @@ error_test_SOURCES = error-test.c $(TEST_SOURCES)
 error_test_LDADD = ${apps_ldadd}
 error_test_LDFLAGS = ${apps_ldflags}
 
+file_test_SOURCES = file-test.c $(TEST_SOURCES)
+file_test_LDADD = ${apps_ldadd}
+file_test_LDFLAGS = ${apps_ldflags}
+
 history_test_SOURCES = history-test.c $(TEST_SOURCES)
 history_test_LDADD = ${apps_ldadd}
 history_test_LDFLAGS = ${apps_ldflags}
diff --git a/libtransmission/file-posix.c b/libtransmission/file-posix.c
new file mode 100644 (file)
index 0000000..f630d4a
--- /dev/null
@@ -0,0 +1,240 @@
+/*
+ * This file Copyright (C) 2013-2014 Mnemosyne LLC
+ *
+ * It may be used under the GNU GPL versions 2 or 3
+ * or any future license endorsed by Mnemosyne LLC.
+ *
+ * $Id$
+ */
+
+#if defined (HAVE_CANONICALIZE_FILE_NAME) && !defined (_GNU_SOURCE)
+ #define _GNU_SOURCE
+#endif
+
+#include <assert.h>
+#include <errno.h>
+#include <libgen.h> /* basename (), dirname () */
+#include <limits.h> /* PATH_MAX */
+#include <stdio.h>
+#include <stdlib.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <unistd.h>
+
+#include "transmission.h"
+#include "file.h"
+#include "utils.h"
+
+#ifndef PATH_MAX
+ #define PATH_MAX 4096
+#endif
+
+static void
+set_system_error (tr_error ** error,
+                  int         code)
+{
+  if (error == NULL)
+    return;
+
+  tr_error_set_literal (error, code, tr_strerror (code));
+}
+
+static void
+set_system_error_if_file_found (tr_error ** error,
+                                int         code)
+{
+  if (code != ENOENT)
+    set_system_error (error, code);
+}
+
+static void
+stat_to_sys_path_info (const struct stat * sb,
+                       tr_sys_path_info  * info)
+{
+  if (S_ISREG (sb->st_mode))
+    info->type = TR_SYS_PATH_IS_FILE;
+  else if (S_ISDIR (sb->st_mode))
+    info->type = TR_SYS_PATH_IS_DIRECTORY;
+  else
+    info->type = TR_SYS_PATH_IS_OTHER;
+
+  info->size = (uint64_t) sb->st_size;
+  info->last_modified_at = sb->st_mtime;
+}
+
+bool
+tr_sys_path_exists (const char  * path,
+                    tr_error   ** error)
+{
+  bool ret;
+
+  assert (path != NULL);
+
+  ret = access (path, F_OK) != -1;
+
+  if (!ret)
+    set_system_error_if_file_found (error, errno);
+
+  return ret;
+}
+
+bool
+tr_sys_path_get_info (const char        * path,
+                      int                 flags,
+                      tr_sys_path_info  * info,
+                      tr_error         ** error)
+{
+  bool ret;
+  struct stat sb;
+
+  assert (path != NULL);
+  assert (info != NULL);
+
+  if ((flags & TR_SYS_PATH_NO_FOLLOW) == 0)
+    ret = stat (path, &sb) != -1;
+  else
+    ret = lstat (path, &sb) != -1;
+
+  if (ret)
+    stat_to_sys_path_info (&sb, info);
+  else
+    set_system_error (error, errno);
+
+  return ret;
+}
+
+bool
+tr_sys_path_is_same (const char  * path1,
+                     const char  * path2,
+                     tr_error   ** error)
+{
+  bool ret = false;
+  struct stat sb1, sb2;
+
+  assert (path1 != NULL);
+  assert (path2 != NULL);
+
+  if (stat (path1, &sb1) != -1 && stat (path2, &sb2) != -1)
+    ret = sb1.st_dev == sb2.st_dev && sb1.st_ino == sb2.st_ino;
+  else
+    set_system_error_if_file_found (error, errno);
+
+  return ret;
+}
+
+char *
+tr_sys_path_resolve (const char  * path,
+                     tr_error   ** error)
+{
+  char * ret = NULL;
+  char * tmp = NULL;
+
+  assert (path != NULL);
+
+#if defined (HAVE_CANONICALIZE_FILE_NAME)
+
+  ret = canonicalize_file_name (path);
+
+#endif
+
+#if defined (_POSIX_C_SOURCE) && _POSIX_C_SOURCE >= 200809L
+
+  /* Better safe than sorry: realpath () officially supports NULL as destination
+     starting off POSIX.1-2008. */
+
+  if (ret == NULL)
+    ret = realpath (path, NULL);
+
+#endif
+
+  if (ret == NULL)
+    {
+      tmp = tr_new (char, PATH_MAX);
+      ret = realpath (path, tmp);
+      if (ret != NULL)
+        ret = tr_strdup (ret);
+    }
+
+  if (ret == NULL)
+    set_system_error (error, errno);
+
+  tr_free (tmp);
+
+  return ret;
+}
+
+char *
+tr_sys_path_basename (const char  * path,
+                      tr_error   ** error)
+{
+  char * ret = NULL;
+  char * tmp;
+
+  assert (path != NULL);
+
+  tmp = tr_strdup (path);
+  ret = basename (tmp);
+  if (ret != NULL)
+    ret = tr_strdup (ret);
+  else
+    set_system_error (error, errno);
+
+  tr_free (tmp);
+
+  return ret;
+}
+
+char *
+tr_sys_path_dirname (const char  * path,
+                     tr_error   ** error)
+{
+  char * ret = NULL;
+  char * tmp;
+
+  assert (path != NULL);
+
+  tmp = tr_strdup (path);
+  ret = dirname (tmp);
+  if (ret != NULL)
+    ret = tr_strdup (ret);
+  else
+    set_system_error (error, errno);
+
+  tr_free (tmp);
+
+  return ret;
+}
+
+bool
+tr_sys_path_rename (const char  * src_path,
+                    const char  * dst_path,
+                    tr_error   ** error)
+{
+  bool ret;
+
+  assert (src_path != NULL);
+  assert (dst_path != NULL);
+
+  ret = rename (src_path, dst_path) != -1;
+
+  if (!ret)
+    set_system_error (error, errno);
+
+  return ret;
+}
+
+bool
+tr_sys_path_remove (const char  * path,
+                    tr_error   ** error)
+{
+  bool ret;
+
+  assert (path != NULL);
+
+  ret = remove (path) != -1;
+
+  if (!ret)
+    set_system_error (error, errno);
+
+  return ret;
+}
diff --git a/libtransmission/file-test.c b/libtransmission/file-test.c
new file mode 100644 (file)
index 0000000..c34cf63
--- /dev/null
@@ -0,0 +1,802 @@
+/*
+ * This file Copyright (C) 2013-2014 Mnemosyne LLC
+ *
+ * It may be used under the GNU GPL versions 2 or 3
+ * or any future license endorsed by Mnemosyne LLC.
+ *
+ * $Id$
+ */
+
+#include <string.h>
+
+#ifndef WIN32
+ #include <sys/types.h>
+ #include <sys/stat.h>
+ #include <unistd.h>
+#else
+ #include <windows.h>
+#endif
+
+#include "transmission.h"
+#include "error.h"
+#include "file.h"
+
+#include "libtransmission-test.h"
+
+#ifndef WIN32
+ #define NATIVE_PATH_SEP "/"
+#else
+ #define NATIVE_PATH_SEP "\\"
+#endif
+
+static tr_session * session;
+
+static char *
+create_test_dir (const char * name)
+{
+  char * const test_dir = tr_buildPath (tr_sessionGetConfigDir (session), name, NULL);
+  tr_mkdirp (test_dir, 0777);
+  return test_dir;
+}
+
+static bool
+create_symlink (const char * dst_path, const char * src_path, bool dst_is_dir)
+{
+#ifndef WIN32
+
+  (void) dst_is_dir;
+
+  return symlink (src_path, dst_path) != -1;
+
+#else
+
+  wchar_t * wide_src_path;
+  wchar_t * wide_dst_path;
+  bool ret = false;
+
+  wide_src_path = tr_win32_utf8_to_native (src_path, -1);
+  wide_dst_path = tr_win32_utf8_to_native (dst_path, -1);
+
+  ret = CreateSymbolicLinkW (wide_dst_path, wide_src_path,
+                             dst_is_dir ? SYMBOLIC_LINK_FLAG_DIRECTORY : 0);
+
+  tr_free (wide_dst_path);
+  tr_free (wide_src_path);
+
+  return ret;
+
+#endif
+}
+
+static bool
+create_hardlink (const char * dst_path, const char * src_path)
+{
+#ifndef WIN32
+
+  return link (src_path, dst_path) != -1;
+
+#else
+
+  wchar_t * wide_src_path = tr_win32_utf8_to_native (src_path, -1);
+  wchar_t * wide_dst_path = tr_win32_utf8_to_native (dst_path, -1);
+
+  bool ret = CreateHardLinkW (wide_dst_path, wide_src_path, NULL);
+
+  tr_free (wide_dst_path);
+  tr_free (wide_src_path);
+
+  return ret;
+
+#endif
+}
+
+static void
+clear_path_info (tr_sys_path_info * info)
+{
+  info->type = (tr_sys_path_type_t)-1;
+  info->size = (uint64_t)-1;
+  info->last_modified_at = (time_t)-1;
+}
+
+static bool
+path_contains_no_symlinks (const char * path)
+{
+  const char * p = path;
+
+  while (*p != '\0')
+    {
+      tr_sys_path_info info;
+      char * pathPart;
+      const char * slashPos = strchr (p, '/');
+
+#ifdef WIN32
+
+      const char * backslashPos = strchr (p, '\\');
+      if (slashPos == NULL || (backslashPos != NULL && backslashPos < slashPos))
+        slashPos = backslashPos;
+
+#endif
+
+      if (slashPos == NULL)
+        slashPos = p + strlen (p) - 1;
+
+      pathPart = tr_strndup (path, slashPos - path + 1);
+
+      if (!tr_sys_path_get_info (pathPart, TR_SYS_PATH_NO_FOLLOW, &info, NULL) ||
+          (info.type != TR_SYS_PATH_IS_FILE && info.type != TR_SYS_PATH_IS_DIRECTORY))
+        {
+          tr_free (pathPart);
+          return false;
+        }
+
+      tr_free (pathPart);
+
+      p = slashPos + 1;
+    }
+
+  return true;
+}
+
+static int
+test_get_info (void)
+{
+  char * const test_dir = create_test_dir (__FUNCTION__);
+  tr_sys_path_info info;
+  tr_error * err = NULL;
+  char * path1, * path2;
+
+  path1 = tr_buildPath (test_dir, "a", NULL);
+  path2 = tr_buildPath (test_dir, "b", NULL);
+
+  /* Can't get info of non-existent file/directory */
+  check (!tr_sys_path_get_info (path1, 0, &info, &err));
+  check (err != NULL);
+  tr_error_clear (&err);
+
+  libtest_create_file_with_string_contents (path1, "test");
+
+  /* Good file info */
+  clear_path_info (&info);
+  check (tr_sys_path_get_info (path1, 0, &info, &err));
+  check (err == NULL);
+  check_int_eq (TR_SYS_PATH_IS_FILE, info.type);
+  check_int_eq (4, info.size);
+  check (info.last_modified_at >= time(0) - 1 && info.last_modified_at <= time(0));
+
+  tr_sys_path_remove (path1, NULL);
+
+  /* Good directory info */
+  tr_mkdirp (path1, 0777);
+  clear_path_info (&info);
+  check (tr_sys_path_get_info (path1, 0, &info, &err));
+  check (err == NULL);
+  check_int_eq (TR_SYS_PATH_IS_DIRECTORY, info.type);
+  check (info.size != (uint64_t)-1);
+  check (info.last_modified_at >= time(0) - 1 && info.last_modified_at <= time(0));
+  tr_sys_path_remove (path1, NULL);
+
+  if (create_symlink (path1, path2, false))
+    {
+      /* Can't get info of non-existent file/directory */
+      check (!tr_sys_path_get_info (path1, 0, &info, &err));
+      check (err != NULL);
+      tr_error_clear (&err);
+
+      libtest_create_file_with_string_contents (path2, "test");
+
+      /* Good file info */
+      clear_path_info (&info);
+      check (tr_sys_path_get_info (path1, 0, &info, &err));
+      check (err == NULL);
+      check_int_eq (TR_SYS_PATH_IS_FILE, info.type);
+      check_int_eq (4, info.size);
+      check (info.last_modified_at >= time(0) - 1 && info.last_modified_at <= time(0));
+
+      tr_sys_path_remove (path2, NULL);
+
+      /* Good directory info */
+      tr_mkdirp (path2, 0777);
+      clear_path_info (&info);
+      check (tr_sys_path_get_info (path1, 0, &info, &err));
+      check (err == NULL);
+      check_int_eq (TR_SYS_PATH_IS_DIRECTORY, info.type);
+      check (info.size != (uint64_t)-1);
+      check (info.last_modified_at >= time(0) - 1 && info.last_modified_at <= time(0));
+
+      tr_sys_path_remove (path2, NULL);
+      tr_sys_path_remove (path1, NULL);
+    }
+  else
+    {
+      fprintf (stderr, "WARNING: [%s] unable to run symlink tests\n", __FUNCTION__);
+    }
+
+  tr_free (path2);
+  tr_free (path1);
+
+  tr_free (test_dir);
+  return 0;
+}
+
+static int
+test_path_exists (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 (test_dir, "b", NULL);
+
+  /* Non-existent file does not exist */
+  check (!tr_sys_path_exists (path1, &err));
+  check (err == NULL);
+
+  /* Create file and see that it exists */
+  libtest_create_file_with_string_contents (path1, "test");
+  check (tr_sys_path_exists (path1, &err));
+  check (err == NULL);
+
+  tr_sys_path_remove (path1, NULL);
+
+  /* Create directory and see that it exists */
+  tr_mkdirp (path1, 0777);
+  check (tr_sys_path_exists (path1, &err));
+  check (err == NULL);
+
+  tr_sys_path_remove (path1, NULL);
+
+  if (create_symlink (path1, path2, false))
+    {
+      /* Non-existent file does not exist (via symlink) */
+      check (!tr_sys_path_exists (path1, &err));
+      check (err == NULL);
+
+      /* Create file and see that it exists (via symlink) */
+      libtest_create_file_with_string_contents (path2, "test");
+      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);
+      check (tr_sys_path_exists (path1, &err));
+      check (err == NULL);
+
+      tr_sys_path_remove (path2, NULL);
+      tr_sys_path_remove (path1, NULL);
+    }
+  else
+    {
+      fprintf (stderr, "WARNING: [%s] unable to run symlink tests\n", __FUNCTION__);
+    }
+
+  tr_free (path2);
+  tr_free (path1);
+
+  tr_free (test_dir);
+  return 0;
+}
+
+static int
+test_path_is_same (void)
+{
+  char * const test_dir = create_test_dir (__FUNCTION__);
+  tr_error * err = NULL;
+  char * path1, * path2, * path3;
+
+  path1 = tr_buildPath (test_dir, "a", NULL);
+  path2 = tr_buildPath (test_dir, "b", NULL);
+  path3 = tr_buildPath (path2, "c", NULL);
+
+  /* Two non-existent files are not the same */
+  check (!tr_sys_path_is_same (path1, path1, &err));
+  check (err == NULL);
+  check (!tr_sys_path_is_same (path1, path2, &err));
+  check (err == NULL);
+
+  /* Two same files are the same */
+  libtest_create_file_with_string_contents (path1, "test");
+  check (tr_sys_path_is_same (path1, path1, &err));
+  check (err == NULL);
+
+  /* Existent and non-existent files are not the same */
+  check (!tr_sys_path_is_same (path1, path2, &err));
+  check (err == NULL);
+  check (!tr_sys_path_is_same (path2, path1, &err));
+  check (err == NULL);
+
+  /* Two separate files (even with same content) are not the same */
+  libtest_create_file_with_string_contents (path2, "test");
+  check (!tr_sys_path_is_same (path1, path2, &err));
+  check (err == NULL);
+
+  tr_sys_path_remove (path1, NULL);
+
+  /* Two same directories are the same */
+  tr_mkdirp (path1, 0777);
+  check (tr_sys_path_is_same (path1, path1, &err));
+  check (err == NULL);
+
+  /* File and directory are not the same */
+  check (!tr_sys_path_is_same (path1, path2, &err));
+  check (err == NULL);
+  check (!tr_sys_path_is_same (path2, path1, &err));
+  check (err == NULL);
+
+  tr_sys_path_remove (path2, NULL);
+
+  /* Two separate directories are not the same */
+  tr_mkdirp (path2, 0777);
+  check (!tr_sys_path_is_same (path1, path2, &err));
+  check (err == NULL);
+
+  tr_sys_path_remove (path1, NULL);
+  tr_sys_path_remove (path2, NULL);
+
+  if (create_symlink (path1, ".", true))
+    {
+      /* Directory and symlink pointing to it are the same */
+      check (tr_sys_path_is_same (path1, test_dir, &err));
+      check (err == NULL);
+      check (tr_sys_path_is_same (test_dir, path1, &err));
+      check (err == NULL);
+
+      /* Non-existent file and symlink are not the same */
+      check (!tr_sys_path_is_same (path1, path2, &err));
+      check (err == NULL);
+      check (!tr_sys_path_is_same (path2, path1, &err));
+      check (err == NULL);
+
+      /* Symlinks pointing to different directories are not the same */
+      create_symlink (path2, "..", true);
+      check (!tr_sys_path_is_same (path1, path2, &err));
+      check (err == NULL);
+      check (!tr_sys_path_is_same (path2, path1, &err));
+      check (err == NULL);
+
+      tr_sys_path_remove (path2, NULL);
+
+      /* Symlinks pointing to same directory are the same */
+      create_symlink (path2, ".", true);
+      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);
+      check (!tr_sys_path_is_same (path1, path2, &err));
+      check (err == NULL);
+      check (!tr_sys_path_is_same (path2, path1, &err));
+      check (err == NULL);
+
+      /* Symlinks pointing to same directory are the same */
+      create_symlink (path3, "..", true);
+      check (tr_sys_path_is_same (path1, path3, &err));
+      check (err == NULL);
+
+      tr_sys_path_remove (path1, NULL);
+
+      /* File and symlink pointing to directory are not the same */
+      libtest_create_file_with_string_contents (path1, "test");
+      check (!tr_sys_path_is_same (path1, path3, &err));
+      check (err == NULL);
+      check (!tr_sys_path_is_same (path3, path1, &err));
+      check (err == NULL);
+
+      tr_sys_path_remove (path3, NULL);
+
+      /* File and symlink pointing to same file are the same */
+      create_symlink (path3, path1, false);
+      check (tr_sys_path_is_same (path1, path3, &err));
+      check (err == NULL);
+      check (tr_sys_path_is_same (path3, path1, &err));
+      check (err == NULL);
+
+      /* Symlinks pointing to non-existent files are not the same */
+      tr_sys_path_remove (path1, NULL);
+      create_symlink (path1, "missing", false);
+      tr_sys_path_remove (path3, NULL);
+      create_symlink (path3, "missing", false);
+      check (!tr_sys_path_is_same (path1, path3, &err));
+      check (err == NULL);
+      check (!tr_sys_path_is_same (path3, path1, &err));
+      check (err == NULL);
+
+      tr_sys_path_remove (path3, NULL);
+
+      /* Symlinks pointing to same non-existent file are not the same */
+      create_symlink (path3, ".." NATIVE_PATH_SEP "missing", false);
+      check (!tr_sys_path_is_same (path1, path3, &err));
+      check (err == NULL);
+      check (!tr_sys_path_is_same (path3, path1, &err));
+      check (err == NULL);
+
+      /* Non-existent file and symlink pointing to non-existent file are not the same */
+      tr_sys_path_remove (path3, NULL);
+      check (!tr_sys_path_is_same (path1, path3, &err));
+      check (err == NULL);
+      check (!tr_sys_path_is_same (path3, path1, &err));
+      check (err == NULL);
+
+      tr_sys_path_remove (path2, NULL);
+      tr_sys_path_remove (path1, NULL);
+    }
+  else
+    {
+      fprintf (stderr, "WARNING: [%s] unable to run symlink tests\n", __FUNCTION__);
+    }
+
+  tr_free (path3);
+  path3 = tr_buildPath (test_dir, "c", NULL);
+
+  libtest_create_file_with_string_contents (path1, "test");
+
+  if (create_hardlink (path2, path1))
+    {
+      /* File and hardlink to it are the same */
+      check (tr_sys_path_is_same (path1, path2, &err));
+      check (err == NULL);
+
+      /* Two hardlinks to the same file are the same */
+      create_hardlink (path3, path2);
+      check (tr_sys_path_is_same (path2, path3, &err));
+      check (err == NULL);
+      check (tr_sys_path_is_same (path1, path3, &err));
+      check (err == NULL);
+
+      tr_sys_path_remove (path2, NULL);
+
+      check (tr_sys_path_is_same (path1, path3, &err));
+      check (err == NULL);
+
+      tr_sys_path_remove (path3, NULL);
+
+      /* File and hardlink to another file are not the same */
+      libtest_create_file_with_string_contents (path3, "test");
+      create_hardlink (path2, path3);
+      check (!tr_sys_path_is_same (path1, path2, &err));
+      check (err == NULL);
+      check (!tr_sys_path_is_same (path2, path1, &err));
+      check (err == NULL);
+
+      tr_sys_path_remove (path3, NULL);
+      tr_sys_path_remove (path2, NULL);
+    }
+  else
+    {
+      fprintf (stderr, "WARNING: [%s] unable to run hardlink tests\n", __FUNCTION__);
+    }
+
+  if (create_symlink (path2, path1, false) && create_hardlink (path3, path1))
+    {
+      check (tr_sys_path_is_same (path2, path3, &err));
+      check (err == NULL);
+    }
+  else
+    {
+      fprintf (stderr, "WARNING: [%s] unable to run combined symlink and hardlink tests\n", __FUNCTION__);
+    }
+
+  tr_sys_path_remove (path3, NULL);
+  tr_sys_path_remove (path2, NULL);
+  tr_sys_path_remove (path1, NULL);
+
+  tr_free (path3);
+  tr_free (path2);
+  tr_free (path1);
+
+  tr_free (test_dir);
+  return 0;
+}
+
+static int
+test_path_resolve (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 (test_dir, "b", NULL);
+
+  libtest_create_file_with_string_contents (path1, "test");
+  if (create_symlink (path2, path1, false))
+    {
+      char * tmp;
+
+      tmp = tr_sys_path_resolve (path2, &err);
+      check (tmp != NULL);
+      check (err == NULL);
+      check (path_contains_no_symlinks (tmp));
+      tr_free (tmp);
+
+      tr_sys_path_remove (path1, NULL);
+      tr_mkdirp (path1, 0755);
+
+      tmp = tr_sys_path_resolve (path2, &err);
+      check (tmp != NULL);
+      check (err == NULL);
+      check (path_contains_no_symlinks (tmp));
+      tr_free (tmp);
+    }
+  else
+    {
+      fprintf (stderr, "WARNING: [%s] unable to run symlink tests\n", __FUNCTION__);
+    }
+
+  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_path_basename_dirname (void)
+{
+  tr_error * err = NULL;
+  char * name;
+
+  name = tr_sys_path_basename ("/a/b/c", &err);
+  check (name != NULL);
+  check (err == NULL);
+  check_streq ("c", name);
+  tr_free (name);
+
+  name = tr_sys_path_basename ("", &err);
+  check (name != NULL);
+  check (err == NULL);
+  check_streq (".", name);
+  tr_free (name);
+
+  name = tr_sys_path_dirname ("/a/b/c", &err);
+  check (name != NULL);
+  check (err == NULL);
+  check_streq ("/a/b", name);
+  tr_free (name);
+
+  name = tr_sys_path_dirname ("a/b/c", &err);
+  check (name != NULL);
+  check (err == NULL);
+  check_streq ("a/b", name);
+  tr_free (name);
+
+  name = tr_sys_path_dirname ("a", &err);
+  check (name != NULL);
+  check (err == NULL);
+  check_streq (".", name);
+  tr_free (name);
+
+  name = tr_sys_path_dirname ("", &err);
+  check (name != NULL);
+  check (err == NULL);
+  check_streq (".", name);
+  tr_free (name);
+
+#ifdef WIN32
+
+  name = tr_sys_path_basename ("c:\\a\\b\\c", &err);
+  check (name != NULL);
+  check (err == NULL);
+  check_streq ("c", name);
+  tr_free (name);
+
+  name = tr_sys_path_dirname ("C:\\a/b\\c", &err);
+  check (name != NULL);
+  check (err == NULL);
+  check_streq ("C:\\a/b", name);
+  tr_free (name);
+
+  name = tr_sys_path_dirname ("a/b\\c", &err);
+  check (name != NULL);
+  check (err == NULL);
+  check_streq ("a/b", name);
+  tr_free (name);
+
+#endif
+
+  return 0;
+}
+
+static int
+test_path_rename (void)
+{
+  char * const test_dir = create_test_dir (__FUNCTION__);
+  tr_error * err = NULL;
+  char * path1, * path2, * path3;
+
+  path1 = tr_buildPath (test_dir, "a", NULL);
+  path2 = tr_buildPath (test_dir, "b", NULL);
+  path3 = tr_buildPath (path2, "c", NULL);
+
+  libtest_create_file_with_string_contents (path1, "test");
+
+  /* Preconditions */
+  check (tr_sys_path_exists (path1, NULL));
+  check (!tr_sys_path_exists (path2, NULL));
+
+  /* Forward rename works */
+  check (tr_sys_path_rename (path1, path2, &err));
+  check (!tr_sys_path_exists (path1, NULL));
+  check (tr_sys_path_exists (path2, NULL));
+  check (err == NULL);
+
+  /* Backward rename works */
+  check (tr_sys_path_rename (path2, path1, &err));
+  check (tr_sys_path_exists (path1, NULL));
+  check (!tr_sys_path_exists (path2, NULL));
+  check (err == NULL);
+
+  /* Another backward rename [of non-existent file] does not work */
+  check (!tr_sys_path_rename (path2, path1, &err));
+  check (err != NULL);
+  tr_error_clear (&err);
+
+  /* Rename to file which couldn't be created does not work */
+  check (!tr_sys_path_rename (path1, path3, &err));
+  check (err != NULL);
+  tr_error_clear (&err);
+
+  /* Rename of non-existent file does not work */
+  check (!tr_sys_path_rename (path3, path2, &err));
+  check (err != NULL);
+  tr_error_clear (&err);
+
+  libtest_create_file_with_string_contents (path2, "test");
+
+  /* Renaming file does overwrite existing file */
+  check (tr_sys_path_rename (path2, path1, &err));
+  check (err == NULL);
+
+  tr_mkdirp (path2, 0777);
+
+  /* Renaming file does not overwrite existing directory, and vice versa */
+  check (!tr_sys_path_rename (path1, path2, &err));
+  check (err != NULL);
+  tr_error_clear (&err);
+  check (!tr_sys_path_rename (path2, path1, &err));
+  check (err != NULL);
+  tr_error_clear (&err);
+
+  tr_sys_path_remove (path2, NULL);
+
+  tr_free (path3);
+  path3 = tr_buildPath (test_dir, "c", NULL);
+
+  if (create_symlink (path2, path1, false))
+    {
+      /* Preconditions */
+      check (tr_sys_path_exists (path2, NULL));
+      check (!tr_sys_path_exists (path3, NULL));
+      check (tr_sys_path_is_same (path1, path2, NULL));
+
+      /* Rename of symlink works, files stay the same */
+      check (tr_sys_path_rename (path2, path3, &err));
+      check (err == NULL);
+      check (!tr_sys_path_exists (path2, NULL));
+      check (tr_sys_path_exists (path3, NULL));
+      check (tr_sys_path_is_same (path1, path3, NULL));
+
+      tr_sys_path_remove (path3, NULL);
+    }
+  else
+    {
+      fprintf (stderr, "WARNING: [%s] unable to run symlink tests\n", __FUNCTION__);
+    }
+
+  if (create_hardlink (path2, path1))
+    {
+      /* Preconditions */
+      check (tr_sys_path_exists (path2, NULL));
+      check (!tr_sys_path_exists (path3, NULL));
+      check (tr_sys_path_is_same (path1, path2, NULL));
+
+      /* Rename of hardlink works, files stay the same */
+      check (tr_sys_path_rename (path2, path3, &err));
+      check (err == NULL);
+      check (!tr_sys_path_exists (path2, NULL));
+      check (tr_sys_path_exists (path3, NULL));
+      check (tr_sys_path_is_same (path1, path3, NULL));
+
+      tr_sys_path_remove (path3, NULL);
+    }
+  else
+    {
+      fprintf (stderr, "WARNING: [%s] unable to run hardlink tests\n", __FUNCTION__);
+    }
+
+  tr_sys_path_remove (path1, NULL);
+
+  tr_free (path3);
+  tr_free (path2);
+  tr_free (path1);
+
+  tr_free (test_dir);
+  return 0;
+}
+
+static int
+test_path_remove (void)
+{
+  char * const test_dir = create_test_dir (__FUNCTION__);
+  tr_error * err = NULL;
+  char * path1, * path2, * path3;
+
+  path1 = tr_buildPath (test_dir, "a", NULL);
+  path2 = tr_buildPath (test_dir, "b", NULL);
+  path3 = tr_buildPath (path2, "c", NULL);
+
+  /* Can't remove non-existent file/directory */
+  check (!tr_sys_path_exists (path1, NULL));
+  check (!tr_sys_path_remove (path1, &err));
+  check (err != NULL);
+  check (!tr_sys_path_exists (path1, NULL));
+  tr_error_clear (&err);
+
+  /* Removing file works */
+  libtest_create_file_with_string_contents (path1, "test");
+  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 empty directory works */
+  tr_mkdirp (path1, 0777);
+  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);
+  libtest_create_file_with_string_contents (path3, "test");
+  check (tr_sys_path_exists (path2, NULL));
+  check (tr_sys_path_exists (path3, NULL));
+  check (!tr_sys_path_remove (path2, &err));
+  check (err != NULL);
+  check (tr_sys_path_exists (path2, NULL));
+  check (tr_sys_path_exists (path3, NULL));
+  tr_error_clear (&err);
+
+  tr_sys_path_remove (path3, NULL);
+  tr_sys_path_remove (path2, NULL);
+
+  tr_free (path3);
+  tr_free (path2);
+  tr_free (path1);
+
+  tr_free (test_dir);
+  return 0;
+}
+
+int
+main (void)
+{
+  const testFunc tests[] =
+    {
+      test_get_info,
+      test_path_exists,
+      test_path_is_same,
+      test_path_resolve,
+      test_path_basename_dirname,
+      test_path_rename,
+      test_path_remove
+    };
+  int ret;
+
+  /* init the session */
+  session = libttest_session_init (NULL);
+
+  ret = runTests (tests, NUM_TESTS (tests));
+
+  if (ret == 0)
+    libttest_session_close (session);
+
+  return ret;
+}
diff --git a/libtransmission/file-win32.c b/libtransmission/file-win32.c
new file mode 100644 (file)
index 0000000..0ea5a55
--- /dev/null
@@ -0,0 +1,452 @@
+/*
+ * This file Copyright (C) 2013-2014 Mnemosyne LLC
+ *
+ * It may be used under the GNU GPL versions 2 or 3
+ * or any future license endorsed by Mnemosyne LLC.
+ *
+ * $Id$
+ */
+
+#include <assert.h>
+#include <stdlib.h> /* _splitpath_s (), _makepath_s () */
+
+#include "transmission.h"
+#include "file.h"
+#include "utils.h"
+
+/* MSDN (http://msdn.microsoft.com/en-us/library/2k2xf226.aspx) only mentions
+   "i64" suffix for C code, but no warning is issued */
+#define DELTA_EPOCH_IN_MICROSECS 11644473600000000ULL
+
+static void
+set_system_error (tr_error ** error,
+                  DWORD       code)
+{
+  char * message;
+
+  if (error == NULL)
+    return;
+
+  message = tr_win32_format_message (code);
+
+  if (message != NULL)
+    {
+      tr_error_set_literal (error, code, message);
+      tr_free (message);
+    }
+  else
+    {
+      tr_error_set (error, code, "Unknown error: 0x%08x", code);
+    }
+}
+
+static void
+set_system_error_if_file_found (tr_error ** error,
+                                DWORD       code)
+{
+  if (code != ERROR_FILE_NOT_FOUND &&
+      code != ERROR_PATH_NOT_FOUND &&
+      code != ERROR_NO_MORE_FILES)
+    set_system_error (error, code);
+}
+
+static time_t
+filetime_to_unix_time (const FILETIME * t)
+{
+  uint64_t tmp = 0;
+
+  assert (t != NULL);
+
+  tmp |= t->dwHighDateTime;
+  tmp <<= 32;
+  tmp |= t->dwLowDateTime;
+  tmp /= 10; /* to microseconds */
+  tmp -= DELTA_EPOCH_IN_MICROSECS;
+
+  return tmp / 1000000UL;
+}
+
+static void
+stat_to_sys_path_info (DWORD              attributes,
+                       DWORD              size_low,
+                       DWORD              size_high,
+                       const FILETIME   * mtime,
+                       tr_sys_path_info * info)
+{
+  assert (mtime != NULL);
+  assert (info != NULL);
+
+  if (attributes & FILE_ATTRIBUTE_DIRECTORY)
+    info->type = TR_SYS_PATH_IS_DIRECTORY;
+  else if (!(attributes & (FILE_ATTRIBUTE_DEVICE | FILE_ATTRIBUTE_REPARSE_POINT | FILE_ATTRIBUTE_VIRTUAL)))
+    info->type = TR_SYS_PATH_IS_FILE;
+  else
+    info->type = TR_SYS_PATH_IS_OTHER;
+
+  info->size = size_high;
+  info->size <<= 32;
+  info->size |= size_low;
+
+  info->last_modified_at = filetime_to_unix_time (mtime);
+}
+
+static bool
+get_file_info (HANDLE              handle,
+               tr_sys_path_info  * info,
+               tr_error         ** error);
+
+bool
+tr_sys_path_exists (const char  * path,
+                    tr_error   ** error)
+{
+  bool ret = false;
+  wchar_t * wide_path;
+  HANDLE handle = INVALID_HANDLE_VALUE;
+
+  assert (path != NULL);
+
+  wide_path = tr_win32_utf8_to_native (path, -1);
+
+  if (wide_path != NULL)
+    {
+      DWORD attributes = GetFileAttributesW (wide_path);
+      if (attributes != INVALID_FILE_ATTRIBUTES)
+        {
+          if (attributes & FILE_ATTRIBUTE_REPARSE_POINT)
+            {
+              handle = CreateFileW (wide_path, 0, 0, NULL, OPEN_EXISTING,
+                                    FILE_FLAG_BACKUP_SEMANTICS, NULL);
+
+              ret = handle != INVALID_HANDLE_VALUE;
+            }
+          else
+            {
+              ret = true;
+            }
+        }
+    }
+
+  if (!ret)
+    set_system_error_if_file_found (error, GetLastError ());
+
+  if (handle != INVALID_HANDLE_VALUE)
+    CloseHandle (handle);
+
+  tr_free (wide_path);
+
+  return ret;
+}
+
+bool
+tr_sys_path_get_info (const char        * path,
+                      int                 flags,
+                      tr_sys_path_info  * info,
+                      tr_error         ** error)
+{
+  bool ret = false;
+  wchar_t * wide_path;
+
+  assert (path != NULL);
+  assert (info != NULL);
+
+  wide_path = tr_win32_utf8_to_native (path, -1);
+
+  if ((flags & TR_SYS_PATH_NO_FOLLOW) == 0)
+    {
+      HANDLE handle = INVALID_HANDLE_VALUE;
+
+      if (wide_path != NULL)
+        handle = CreateFileW (wide_path, 0, 0, NULL, OPEN_EXISTING, FILE_FLAG_BACKUP_SEMANTICS, NULL);
+
+      if (handle != INVALID_HANDLE_VALUE)
+        {
+          tr_error * my_error = NULL;
+          ret = get_file_info (handle, info, &my_error);
+          if (!ret)
+            tr_error_propagate (error, &my_error);
+          CloseHandle (handle);
+        }
+      else
+        {
+          set_system_error (error, GetLastError ());
+        }
+    }
+  else
+    {
+      WIN32_FILE_ATTRIBUTE_DATA attributes;
+
+      if (wide_path != NULL)
+        ret = GetFileAttributesExW (wide_path, GetFileExInfoStandard, &attributes);
+
+      if (ret)
+        stat_to_sys_path_info (attributes.dwFileAttributes, attributes.nFileSizeLow,
+                               attributes.nFileSizeHigh, &attributes.ftLastWriteTime,
+                               info);
+      else
+        set_system_error (error, GetLastError ());
+    }
+
+  tr_free (wide_path);
+
+  return ret;
+}
+
+bool
+tr_sys_path_is_same (const char  * path1,
+                     const char  * path2,
+                     tr_error   ** error)
+{
+  bool ret = false;
+  wchar_t * wide_path1 = NULL;
+  wchar_t * wide_path2 = NULL;
+  HANDLE handle1 = INVALID_HANDLE_VALUE;
+  HANDLE handle2 = INVALID_HANDLE_VALUE;
+  BY_HANDLE_FILE_INFORMATION fi1, fi2;
+
+  assert (path1 != NULL);
+  assert (path2 != NULL);
+
+  wide_path1 = tr_win32_utf8_to_native (path1, -1);
+  if (wide_path1 == NULL)
+    goto fail;
+
+  wide_path2 = tr_win32_utf8_to_native (path2, -1);
+  if (wide_path2 == NULL)
+    goto fail;
+
+  handle1 = CreateFileW (wide_path1, 0, 0, NULL, OPEN_EXISTING, FILE_FLAG_BACKUP_SEMANTICS, NULL);
+  if (handle1 == INVALID_HANDLE_VALUE)
+    goto fail;
+
+  handle2 = CreateFileW (wide_path2, 0, 0, NULL, OPEN_EXISTING, FILE_FLAG_BACKUP_SEMANTICS, NULL);
+  if (handle2 == INVALID_HANDLE_VALUE)
+    goto fail;
+
+  /* TODO: Use GetFileInformationByHandleEx on >= Server 2012 */
+
+  if (!GetFileInformationByHandle (handle1, &fi1) || !GetFileInformationByHandle (handle2, &fi2))
+    goto fail;
+
+  ret = fi1.dwVolumeSerialNumber == fi2.dwVolumeSerialNumber &&
+        fi1.nFileIndexHigh == fi2.nFileIndexHigh &&
+        fi1.nFileIndexLow  == fi2.nFileIndexLow;
+
+  goto cleanup;
+
+fail:
+  set_system_error_if_file_found (error, GetLastError ());
+
+cleanup:
+  CloseHandle (handle2);
+  CloseHandle (handle1);
+
+  tr_free (wide_path2);
+  tr_free (wide_path1);
+
+  return ret;
+}
+
+char *
+tr_sys_path_resolve (const char  * path,
+                     tr_error   ** error)
+{
+  char * ret = NULL;
+  wchar_t * wide_path;
+  wchar_t * wide_ret = NULL;
+  HANDLE handle;
+  DWORD wide_ret_size;
+
+  assert (path != NULL);
+
+  wide_path = tr_win32_utf8_to_native (path, -1);
+  if (wide_path == NULL)
+    goto fail;
+
+  handle = CreateFileW (wide_path, FILE_READ_EA,
+                        FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE,
+                        NULL, OPEN_EXISTING, FILE_FLAG_BACKUP_SEMANTICS, NULL);
+  if (handle == INVALID_HANDLE_VALUE)
+    goto fail;
+
+  wide_ret_size = GetFinalPathNameByHandleW (handle, NULL, 0, 0);
+  if (wide_ret_size == 0)
+    goto fail;
+
+  wide_ret = tr_new (wchar_t, wide_ret_size);
+  if (GetFinalPathNameByHandleW (handle, wide_ret, wide_ret_size, 0) != wide_ret_size - 1)
+    goto fail;
+
+  /* Resolved path always begins with "\\?\", so skip those first four chars. */
+  ret = tr_win32_native_to_utf8 (wide_ret + 4, -1);
+  if (ret != NULL)
+    goto cleanup;
+
+fail:
+  set_system_error (error, GetLastError ());
+
+  tr_free (ret);
+  ret = NULL;
+
+cleanup:
+  tr_free (wide_ret);
+  tr_free (wide_path);
+
+  if (handle != INVALID_HANDLE_VALUE)
+    CloseHandle (handle);
+
+  return ret;
+}
+
+char *
+tr_sys_path_basename (const char  * path,
+                      tr_error   ** error)
+{
+  char fname[_MAX_FNAME], ext[_MAX_EXT];
+
+  assert (path != NULL);
+
+  /* TODO: Error handling */
+
+  if (_splitpath_s (path, NULL, 0, NULL, 0, fname, sizeof (fname), ext, sizeof (ext)) == 0 &&
+      (*fname != '\0' || *ext != '\0'))
+    {
+      const size_t tmp_len = strlen (fname) + strlen (ext) + 2;
+      char * const tmp = tr_new (char, tmp_len);
+      if (_makepath_s (tmp, tmp_len, NULL, NULL, fname, ext) == 0)
+        return tmp;
+      tr_free (tmp);
+    }
+
+  return tr_strdup (".");
+}
+
+char *
+tr_sys_path_dirname (const char  * path,
+                     tr_error   ** error)
+{
+  char drive[_MAX_DRIVE], dir[_MAX_DIR];
+
+  assert (path != NULL);
+
+  /* TODO: Error handling */
+
+  if (_splitpath_s (path, drive, sizeof (drive), dir, sizeof (dir), NULL, 0, NULL, 0) == 0 &&
+      (*drive != '\0' || *dir != '\0'))
+    {
+      const size_t tmp_len = strlen (drive) + strlen (dir) + 2;
+      char * const tmp = tr_new (char, tmp_len);
+      if (_makepath_s (tmp, tmp_len, drive, dir, NULL, NULL) == 0)
+        {
+          size_t len = strlen(tmp);
+          while (len > 0 && (tmp[len - 1] == '/' || tmp[len - 1] == '\\'))
+            tmp[--len] = '\0';
+
+          return tmp;
+        }
+
+      tr_free (tmp);
+    }
+
+  return tr_strdup (".");
+}
+
+bool
+tr_sys_path_rename (const char  * src_path,
+                    const char  * dst_path,
+                    tr_error   ** error)
+{
+  bool ret = false;
+  wchar_t * wide_src_path;
+  wchar_t * wide_dst_path;
+
+  assert (src_path != NULL);
+  assert (dst_path != NULL);
+
+  wide_src_path = tr_win32_utf8_to_native (src_path, -1);
+  wide_dst_path = tr_win32_utf8_to_native (dst_path, -1);
+
+  if (wide_src_path != NULL && wide_dst_path != NULL)
+    {
+      DWORD flags = MOVEFILE_REPLACE_EXISTING;
+      DWORD attributes;
+
+      attributes = GetFileAttributesW (wide_src_path);
+      if (attributes != INVALID_FILE_ATTRIBUTES &&
+          (attributes & FILE_ATTRIBUTE_DIRECTORY) != 0)
+        {
+          flags = 0;
+        }
+      else
+        {
+          attributes = GetFileAttributesW (wide_dst_path);
+          if (attributes != INVALID_FILE_ATTRIBUTES &&
+              (attributes & FILE_ATTRIBUTE_DIRECTORY) != 0)
+            flags = 0;
+        }
+
+      ret = MoveFileExW (wide_src_path, wide_dst_path, flags);
+    }
+
+  if (!ret)
+    set_system_error (error, GetLastError ());
+
+  tr_free (wide_dst_path);
+  tr_free (wide_src_path);
+
+  return ret;
+}
+
+bool
+tr_sys_path_remove (const char  * path,
+                    tr_error   ** error)
+{
+  bool ret = false;
+  wchar_t * wide_path;
+
+  assert (path != NULL);
+
+  wide_path = tr_win32_utf8_to_native (path, -1);
+
+  if (wide_path != NULL)
+    {
+      const DWORD attributes = GetFileAttributesW (wide_path);
+
+      if (attributes != INVALID_FILE_ATTRIBUTES)
+        {
+          if ((attributes & FILE_ATTRIBUTE_DIRECTORY) != 0)
+            ret = RemoveDirectoryW (wide_path);
+          else
+            ret = DeleteFileW (wide_path);
+        }
+    }
+
+  if (!ret)
+    set_system_error (error, GetLastError ());
+
+  tr_free (wide_path);
+
+  return ret;
+}
+
+static bool
+get_file_info (HANDLE              handle,
+               tr_sys_path_info  * info,
+               tr_error         ** error)
+{
+  bool ret;
+  BY_HANDLE_FILE_INFORMATION attributes;
+
+  assert (handle != INVALID_HANDLE_VALUE);
+  assert (info != NULL);
+
+  ret = GetFileInformationByHandle (handle, &attributes);
+
+  if (ret)
+    stat_to_sys_path_info (attributes.dwFileAttributes, attributes.nFileSizeLow,
+                           attributes.nFileSizeHigh, &attributes.ftLastWriteTime,
+                           info);
+  else
+    set_system_error (error, GetLastError ());
+
+  return ret;
+}
diff --git a/libtransmission/file.h b/libtransmission/file.h
new file mode 100644 (file)
index 0000000..05fe1d2
--- /dev/null
@@ -0,0 +1,193 @@
+/*
+ * This file Copyright (C) 2013-2014 Mnemosyne LLC
+ *
+ * It may be used under the GNU GPL versions 2 or 3
+ * or any future license endorsed by Mnemosyne LLC.
+ *
+ * $Id$
+ */
+
+#ifndef TR_FILE_H
+#define TR_FILE_H
+
+#include <inttypes.h>
+#include <time.h>
+
+#ifdef WIN32
+ #include <windows.h>
+#endif
+
+#include "error.h"
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+/**
+ * @addtogroup file_io File IO
+ * @{
+ */
+
+typedef enum
+{
+    TR_SYS_PATH_NO_FOLLOW = 1 << 0
+}
+tr_sys_path_get_info_flags_t;
+
+typedef enum
+{
+  TR_SYS_PATH_IS_FILE,
+  TR_SYS_PATH_IS_DIRECTORY,
+  TR_SYS_PATH_IS_OTHER
+}
+tr_sys_path_type_t;
+
+typedef struct tr_sys_path_info
+{
+  tr_sys_path_type_t type;
+  uint64_t           size;
+  time_t             last_modified_at;
+}
+tr_sys_path_info;
+
+/**
+ * @name Platform-specific wrapper functions
+ *
+ * Following functions accept paths in UTF-8 encoding and convert them to native
+ * encoding internally if needed.
+ *
+ * @{
+ */
+
+/* Path-related wrappers */
+
+/**
+ * @brief Portability wrapper for `stat ()`.
+ *
+ * @param[in]  path  Path to file or directory.
+ * @param[in]  flags Combination of @ref tr_sys_path_get_info_flags_t values.
+ * @param[out] info  Result buffer.
+ * @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_path_get_info        (const char         * path,
+                                             int                  flags,
+                                             tr_sys_path_info   * info,
+                                             tr_error          ** error);
+
+/**
+ * @brief Portability wrapper for `access ()`.
+ *
+ * @param[in]  path  Path to file or directory.
+ * @param[out] error Pointer to error object. Optional, pass `NULL` if you are
+ *                   not interested in error details.
+ *
+ * @return `True` if path exists, `false` otherwise. Note that `false` will also
+ *         be returned in case of error; if you need to distinguish the two,
+ *         check if `error` is `NULL` afterwards.
+ */
+bool            tr_sys_path_exists          (const char         * path,
+                                             tr_error          ** error);
+
+/**
+ * @brief Test to see if the two filenames point to the same file.
+ *
+ * @param[in]  path1  Path to first file or directory.
+ * @param[in]  path2  Path to second file or directory.
+ * @param[out] error Pointer to error object. Optional, pass `NULL` if you are
+ *                   not interested in error details.
+ *
+ * @return `True` if two paths point to the same file or directory, `false`
+ *         otherwise. Note that `false` will also be returned in case of error;
+ *         if you need to distinguish the two, check if `error` is `NULL`
+ *         afterwards.
+ */
+bool            tr_sys_path_is_same         (const char         * path1,
+                                             const char         * path2,
+                                             tr_error          ** error);
+
+/**
+ * @brief Portability wrapper for `realpath ()`.
+ *
+ * @param[in]  path  Path to file or directory.
+ * @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 full path (with symbolic
+ *         links, `.` and `..` resolved) on success (use @ref tr_free to free it
+ *         when no longer needed), `NULL` otherwise (with `error` set
+ *         accordingly).
+ */
+char          * tr_sys_path_resolve         (const char         * path,
+                                             tr_error          ** error);
+
+/**
+ * @brief Portability wrapper for `basename ()`.
+ *
+ * @param[in]  path  Path to file or directory.
+ * @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 base name (last path
+ *         component; parent path removed) on success (use @ref tr_free to free
+ *         it when no longer needed), `NULL` otherwise (with `error` set
+ *         accordingly).
+ */
+char          * tr_sys_path_basename        (const char         * path,
+                                             tr_error          ** error);
+
+/**
+ * @brief Portability wrapper for `dirname ()`.
+ *
+ * @param[in]  path  Path to file or directory.
+ * @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 directory (parent path;
+ *         last path component removed) on success (use @ref tr_free to free it
+ *         when no longer needed), `NULL` otherwise (with `error` set
+ *         accordingly).
+ */
+char          * tr_sys_path_dirname         (const char         * path,
+                                             tr_error          ** error);
+
+/**
+ * @brief Portability wrapper for `rename ()`.
+ *
+ * @param[in]  src_path Path to source file or directory.
+ * @param[in]  dst_path Path to destination file or 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).
+ *         Rename will generally only succeed if both source and destination are
+ *         on the same partition.
+ */
+bool            tr_sys_path_rename          (const char         * src_path,
+                                             const char         * dst_path,
+                                             tr_error          ** error);
+
+/**
+ * @brief Portability wrapper for `remove ()`.
+ *
+ * @param[in]  path  Path to file or 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).
+ *         Directory removal will only succeed if it is empty (contains no other
+ *         files and directories).
+ */
+bool            tr_sys_path_remove          (const char         * path,
+                                             tr_error          ** error);
+
+/** @} */
+/** @} */
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif
index 5cf51c1833e19afabdd1ac8445f2da276ac4fef6..67cde5b81a418521e3428d9878aa03ad40c06c32 100644 (file)
@@ -1316,6 +1316,98 @@ tr_utf8clean (const char * str, int max_len)
   return ret;
 }
 
+#ifdef WIN32
+
+char *
+tr_win32_native_to_utf8 (const wchar_t * text,
+                         int             text_size)
+{
+  char * ret = NULL;
+  int size;
+
+  size = WideCharToMultiByte (CP_UTF8, 0, text, text_size, NULL, 0, NULL, NULL);
+  if (size == 0)
+    goto fail;
+
+  ret = tr_new (char, size + 1);
+  size = WideCharToMultiByte (CP_UTF8, 0, text, text_size, ret, size, NULL, NULL);
+  if (size == 0)
+    goto fail;
+
+  ret[size] = '\0';
+
+  return ret;
+
+fail:
+  tr_free (ret);
+
+  return NULL;
+}
+
+wchar_t *
+tr_win32_utf8_to_native (const char * text,
+                         int          text_size)
+{
+  return tr_win32_utf8_to_native_ex (text, text_size, 0);
+}
+
+wchar_t *
+tr_win32_utf8_to_native_ex (const char * text,
+                            int          text_size,
+                            int          extra_chars)
+{
+  wchar_t * ret = NULL;
+  int size;
+
+  size = MultiByteToWideChar (CP_UTF8, 0, text, text_size, NULL, 0);
+  if (size == 0)
+    goto fail;
+
+  ret = tr_new (wchar_t, size + extra_chars + 1);
+  size = MultiByteToWideChar (CP_UTF8, 0, text, text_size, ret, size);
+  if (size == 0)
+    goto fail;
+
+  ret[size] = L'\0';
+
+  return ret;
+
+fail:
+  tr_free (ret);
+
+  return NULL;
+}
+
+char *
+tr_win32_format_message (uint32_t code)
+{
+  wchar_t * wide_text = NULL;
+  DWORD wide_size;
+  char * text = NULL;
+  size_t text_size;
+
+  wide_size = FormatMessageW (FORMAT_MESSAGE_ALLOCATE_BUFFER |
+                              FORMAT_MESSAGE_FROM_SYSTEM |
+                              FORMAT_MESSAGE_IGNORE_INSERTS,
+                              NULL, code, 0, (LPWSTR)&wide_text, 0, NULL);
+
+  if (wide_size != 0 && wide_text != NULL)
+    text = tr_win32_native_to_utf8 (wide_text, wide_size);
+
+  LocalFree (wide_text);
+
+  /* Most (all?) messages contain "\r\n" in the end, chop it */
+  text_size = strlen (text);
+  while (text_size > 0 &&
+         text[text_size - 1] >= '\0' &&
+         text[text_size - 1] <= ' ')
+    text[--text_size] = '\0';
+
+  return text;
+}
+
+#endif
+
 /***
 ****
 ***/
index 80a6f4fbd2564cffdd87c7bc80f6ffe190c7c51f..b777a1c13c83798e924f4062c109747f1ecf416e 100644 (file)
@@ -178,6 +178,18 @@ void tr_wait_msec (long int delay_milliseconds);
  */
 char* tr_utf8clean (const char * str, int len) TR_GNUC_MALLOC;
 
+#ifdef WIN32
+
+char    * tr_win32_native_to_utf8    (const wchar_t * text,
+                                      int             text_size);
+wchar_t * tr_win32_utf8_to_native    (const char    * text,
+                                      int             text_size);
+wchar_t * tr_win32_utf8_to_native_ex (const char    * text,
+                                      int             text_size,
+                                      int             extra_chars);
+char    * tr_win32_format_message    (uint32_t        code);
+
+#endif
 
 /***
 ****