(trunk, libT) mike.dld's 4160-02a-path.patch: portability wrapper around file paths.

This commit is contained in:
Jordan Lee 2014-07-03 21:58:39 +00:00
parent db39d4096e
commit 60a5c793d9
8 changed files with 1811 additions and 0 deletions

View File

@ -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 = "<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>"; };
@ -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 */,

View 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}

View File

@ -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;
}

802
libtransmission/file-test.c Normal file
View File

@ -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;
}

View File

@ -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;
}

193
libtransmission/file.h Normal file
View File

@ -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

View 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
/***
****
***/

View 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
/***
****