diff --git a/Transmission.xcodeproj/project.pbxproj b/Transmission.xcodeproj/project.pbxproj index 7f8af9cf1..7dd97593d 100644 --- a/Transmission.xcodeproj/project.pbxproj +++ b/Transmission.xcodeproj/project.pbxproj @@ -460,6 +460,8 @@ 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 */; }; @@ -1196,6 +1198,8 @@ BEFC1E1F0C07861A00B0BB3C /* clients.c */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.c.c; name = clients.c; path = libtransmission/clients.c; sourceTree = ""; }; C1077A4A183EB29600634C22 /* error.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; name = error.c; path = libtransmission/error.c; sourceTree = ""; }; C1077A4B183EB29600634C22 /* error.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = error.h; path = libtransmission/error.h; sourceTree = ""; }; + C1077A4C183EB29600634C22 /* file-posix.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; name = "file-posix.c"; path = "libtransmission/file-posix.c"; sourceTree = ""; }; + C1077A4D183EB29600634C22 /* file.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = file.h; path = libtransmission/file.h; sourceTree = ""; }; D4AF3B2D0C41F7A500D46B6B /* list.c */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.c.c; name = list.c; path = libtransmission/list.c; sourceTree = ""; }; D4AF3B2E0C41F7A500D46B6B /* list.h */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.c.h; name = list.h; path = libtransmission/list.h; sourceTree = ""; }; E138A9750C04D88F00C5426C /* ProgressGradients.h */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.c.h; name = ProgressGradients.h; path = macosx/ProgressGradients.h; sourceTree = ""; }; @@ -1652,6 +1656,8 @@ children = ( C1077A4A183EB29600634C22 /* error.c */, C1077A4B183EB29600634C22 /* error.h */, + C1077A4C183EB29600634C22 /* file-posix.c */, + C1077A4D183EB29600634C22 /* file.h */, 4D80185710BBC0B0008A4AF2 /* magnet.c */, 4D80185810BBC0B0008A4AF2 /* magnet.h */, 4D8017E810BBC073008A4AF2 /* torrent-magnet.c */, @@ -2047,6 +2053,7 @@ 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 */, @@ -2678,6 +2685,7 @@ 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 */, diff --git a/libtransmission/Makefile.am b/libtransmission/Makefile.am index 4a66d9611..e841a2d2b 100644 --- a/libtransmission/Makefile.am +++ b/libtransmission/Makefile.am @@ -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 index 000000000..f630d4aad --- /dev/null +++ b/libtransmission/file-posix.c @@ -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 +#include +#include /* basename (), dirname () */ +#include /* PATH_MAX */ +#include +#include +#include +#include +#include + +#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 index 000000000..c34cf63cf --- /dev/null +++ b/libtransmission/file-test.c @@ -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 + +#ifndef WIN32 + #include + #include + #include +#else + #include +#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 index 000000000..0ea5a555c --- /dev/null +++ b/libtransmission/file-win32.c @@ -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 +#include /* _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 index 000000000..05fe1d27d --- /dev/null +++ b/libtransmission/file.h @@ -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 +#include + +#ifdef WIN32 + #include +#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 diff --git a/libtransmission/utils.c b/libtransmission/utils.c index 5cf51c183..67cde5b81 100644 --- a/libtransmission/utils.c +++ b/libtransmission/utils.c @@ -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 + /*** **** ***/ diff --git a/libtransmission/utils.h b/libtransmission/utils.h index 80a6f4fbd..b777a1c13 100644 --- a/libtransmission/utils.h +++ b/libtransmission/utils.h @@ -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 /*** ****