From: Jordan Lee Date: Tue, 8 Jul 2014 00:15:12 +0000 (+0000) Subject: (trunk, libT) #4160 'foreign character support' -- merge mike.dld's 4160-03a-file... X-Git-Tag: 2.90~371 X-Git-Url: https://granicus.if.org/sourcecode?a=commitdiff_plain;h=c1beabfea6ddd7330211d7b6b2b467a49e9ff3d6;p=transmission (trunk, libT) #4160 'foreign character support' -- merge mike.dld's 4160-03a-file.platch, which introduces tr_sys_file_*() portability wrappers --- diff --git a/libtransmission/file-posix.c b/libtransmission/file-posix.c index f630d4aad..afcc0b212 100644 --- a/libtransmission/file-posix.c +++ b/libtransmission/file-posix.c @@ -7,28 +7,86 @@ * $Id$ */ -#if defined (HAVE_CANONICALIZE_FILE_NAME) && !defined (_GNU_SOURCE) +#if (defined (HAVE_POSIX_FADVISE) || defined (HAVE_POSIX_FALLOCATE)) && (!defined (_XOPEN_SOURCE) || _XOPEN_SOURCE < 600) + #ifdef _XOPEN_SOURCE + #undef _XOPEN_SOURCE + #endif + #define _XOPEN_SOURCE 600 +#endif + +#if (defined (HAVE_FALLOCATE64) || defined (HAVE_CANONICALIZE_FILE_NAME)) && !defined (_GNU_SOURCE) #define _GNU_SOURCE #endif +#if defined (__APPLE__) && !defined (_DARWIN_C_SOURCE) + #define _DARWIN_C_SOURCE +#endif + #include #include +#include /* O_LARGEFILE, posix_fadvise (), [posix_]fallocate () */ #include /* basename (), dirname () */ #include /* PATH_MAX */ #include #include +#include +#include /* mmap (), munmap () */ #include #include -#include +#include /* lseek (), write (), ftruncate (), pread (), pwrite (), pathconf (), etc */ + +#ifdef HAVE_XFS_XFS_H + #include +#endif #include "transmission.h" #include "file.h" +#include "platform.h" #include "utils.h" +#ifndef O_LARGEFILE + #define O_LARGEFILE 0 +#endif +#ifndef O_BINARY + #define O_BINARY 0 +#endif +#ifndef O_SEQUENTIAL + #define O_SEQUENTIAL 0 +#endif +#ifndef O_CLOEXEC + #define O_CLOEXEC 0 +#endif + #ifndef PATH_MAX #define PATH_MAX 4096 #endif +/* don't use pread/pwrite on old versions of uClibc because they're buggy. + * https://trac.transmissionbt.com/ticket/3826 */ +#ifdef __UCLIBC__ +#define TR_UCLIBC_CHECK_VERSION(major,minor,micro) \ + (__UCLIBC_MAJOR__ > (major) || \ + (__UCLIBC_MAJOR__ == (major) && __UCLIBC_MINOR__ > (minor)) || \ + (__UCLIBC_MAJOR__ == (major) && __UCLIBC_MINOR__ == (minor) && \ + __UCLIBC_SUBLEVEL__ >= (micro))) +#if !TR_UCLIBC_CHECK_VERSION (0,9,28) + #undef HAVE_PREAD + #undef HAVE_PWRITE +#endif +#endif + +#ifdef __APPLE__ + #ifndef HAVE_PREAD + #define HAVE_PREAD + #endif + #ifndef HAVE_PWRITE + #define HAVE_PWRITE + #endif + #ifndef HAVE_MKDTEMP + #define HAVE_MKDTEMP + #endif +#endif + static void set_system_error (tr_error ** error, int code) @@ -62,6 +120,33 @@ stat_to_sys_path_info (const struct stat * sb, info->last_modified_at = sb->st_mtime; } +static void +set_file_for_single_pass (tr_sys_file_t handle) +{ + /* Set hints about the lookahead buffer and caching. It's okay + for these to fail silently, so don't let them affect errno */ + + const int err = errno; + + if (handle == TR_BAD_SYS_FILE) + return; + +#ifdef HAVE_POSIX_FADVISE + + posix_fadvise (handle, 0, 0, POSIX_FADV_SEQUENTIAL); + +#endif + +#ifdef __APPLE__ + + fcntl (handle, F_RDAHEAD, 1); + fcntl (handle, F_NOCACHE, 1); + +#endif + + errno = err; +} + bool tr_sys_path_exists (const char * path, tr_error ** error) @@ -238,3 +323,504 @@ tr_sys_path_remove (const char * path, return ret; } + +tr_sys_file_t +tr_sys_file_get_std (tr_std_sys_file_t std_file, + tr_error ** error) +{ + tr_sys_file_t ret = TR_BAD_SYS_FILE; + + switch (std_file) + { + case TR_STD_SYS_FILE_IN: + ret = STDIN_FILENO; + break; + case TR_STD_SYS_FILE_OUT: + ret = STDOUT_FILENO; + break; + case TR_STD_SYS_FILE_ERR: + ret = STDERR_FILENO; + break; + default: + assert (0 && "Unknown standard file"); + set_system_error (error, EINVAL); + } + + return ret; +} + +tr_sys_file_t +tr_sys_file_open (const char * path, + int flags, + int permissions, + tr_error ** error) +{ + tr_sys_file_t ret; + int native_flags = 0; + + assert (path != NULL); + assert ((flags & (TR_SYS_FILE_READ | TR_SYS_FILE_WRITE)) != 0); + + if ((flags & (TR_SYS_FILE_READ | TR_SYS_FILE_WRITE)) == (TR_SYS_FILE_READ | TR_SYS_FILE_WRITE)) + native_flags |= O_RDWR; + else if (flags & TR_SYS_FILE_READ) + native_flags |= O_RDONLY; + else if (flags & TR_SYS_FILE_WRITE) + native_flags |= O_WRONLY; + + native_flags |= + (flags & TR_SYS_FILE_CREATE ? O_CREAT : 0) | + (flags & TR_SYS_FILE_CREATE_NEW ? O_CREAT | O_EXCL : 0) | + (flags & TR_SYS_FILE_APPEND ? O_APPEND : 0) | + (flags & TR_SYS_FILE_TRUNCATE ? O_TRUNC : 0) | + (flags & TR_SYS_FILE_SEQUENTIAL ? O_SEQUENTIAL : 0) | + O_BINARY | O_LARGEFILE | O_CLOEXEC; + + ret = open (path, native_flags, permissions); + + if (ret != TR_BAD_SYS_FILE) + { + if (flags & TR_SYS_FILE_SEQUENTIAL) + set_file_for_single_pass (ret); + } + else + { + set_system_error (error, errno); + } + + return ret; +} + +tr_sys_file_t +tr_sys_file_open_temp (char * path_template, + tr_error ** error) +{ + tr_sys_file_t ret; + + assert (path_template != NULL); + + ret = mkstemp (path_template); + + if (ret == TR_BAD_SYS_FILE) + set_system_error (error, errno); + + set_file_for_single_pass (ret); + + return ret; +} + +bool +tr_sys_file_close (tr_sys_file_t handle, + tr_error ** error) +{ + bool ret; + + assert (handle != TR_BAD_SYS_FILE); + + ret = close (handle) != -1; + + if (!ret) + set_system_error (error, errno); + + return ret; +} + +bool +tr_sys_file_get_info (tr_sys_file_t handle, + tr_sys_path_info * info, + tr_error ** error) +{ + bool ret; + struct stat sb; + + assert (handle != TR_BAD_SYS_FILE); + assert (info != NULL); + + ret = fstat (handle, &sb) != -1; + + if (ret) + stat_to_sys_path_info (&sb, info); + else + set_system_error (error, errno); + + return ret; +} + +bool +tr_sys_file_seek (tr_sys_file_t handle, + int64_t offset, + tr_seek_origin_t origin, + uint64_t * new_offset, + tr_error ** error) +{ + bool ret = false; + off_t my_new_offset; + + TR_STATIC_ASSERT (TR_SEEK_SET == SEEK_SET, "values should match"); + TR_STATIC_ASSERT (TR_SEEK_CUR == SEEK_CUR, "values should match"); + TR_STATIC_ASSERT (TR_SEEK_END == SEEK_END, "values should match"); + + TR_STATIC_ASSERT (sizeof (*new_offset) >= sizeof (my_new_offset), ""); + + assert (handle != TR_BAD_SYS_FILE); + assert (origin == TR_SEEK_SET || origin == TR_SEEK_CUR || origin == TR_SEEK_END); + + my_new_offset = lseek (handle, offset, origin); + + if (my_new_offset != (off_t)-1) + { + if (new_offset != NULL) + *new_offset = my_new_offset; + ret = true; + } + else + { + set_system_error (error, errno); + } + + return ret; +} + +bool +tr_sys_file_read (tr_sys_file_t handle, + void * buffer, + uint64_t size, + uint64_t * bytes_read, + tr_error ** error) +{ + bool ret = false; + ssize_t my_bytes_read; + + TR_STATIC_ASSERT (sizeof (*bytes_read) >= sizeof (my_bytes_read), ""); + + assert (handle != TR_BAD_SYS_FILE); + assert (buffer != NULL || size == 0); + + my_bytes_read = read (handle, buffer, size); + + if (my_bytes_read != -1) + { + if (bytes_read != NULL) + *bytes_read = my_bytes_read; + ret = true; + } + else + { + set_system_error (error, errno); + } + + return ret; +} + +bool +tr_sys_file_read_at (tr_sys_file_t handle, + void * buffer, + uint64_t size, + uint64_t offset, + uint64_t * bytes_read, + tr_error ** error) +{ + bool ret = false; + ssize_t my_bytes_read; + + TR_STATIC_ASSERT (sizeof (*bytes_read) >= sizeof (my_bytes_read), ""); + + assert (handle != TR_BAD_SYS_FILE); + assert (buffer != NULL || size == 0); + +#ifdef HAVE_PREAD + + my_bytes_read = pread (handle, buffer, size, offset); + +#else + + if (lseek (handle, offset, SEEK_SET) != -1) + my_bytes_read = read (handle, buffer, size); + else + my_bytes_read = -1; + +#endif + + if (my_bytes_read != -1) + { + if (bytes_read != NULL) + *bytes_read = my_bytes_read; + ret = true; + } + else + { + set_system_error (error, errno); + } + + return ret; +} + +bool +tr_sys_file_write (tr_sys_file_t handle, + const void * buffer, + uint64_t size, + uint64_t * bytes_written, + tr_error ** error) +{ + bool ret = false; + ssize_t my_bytes_written; + + TR_STATIC_ASSERT (sizeof (*bytes_written) >= sizeof (my_bytes_written), ""); + + assert (handle != TR_BAD_SYS_FILE); + assert (buffer != NULL || size == 0); + + my_bytes_written = write (handle, buffer, size); + + if (my_bytes_written != -1) + { + if (bytes_written != NULL) + *bytes_written = my_bytes_written; + ret = true; + } + else + { + set_system_error (error, errno); + } + + return ret; +} + +bool +tr_sys_file_write_at (tr_sys_file_t handle, + const void * buffer, + uint64_t size, + uint64_t offset, + uint64_t * bytes_written, + tr_error ** error) +{ + bool ret = false; + ssize_t my_bytes_written; + + TR_STATIC_ASSERT (sizeof (*bytes_written) >= sizeof (my_bytes_written), ""); + + assert (handle != TR_BAD_SYS_FILE); + assert (buffer != NULL || size == 0); + +#ifdef HAVE_PWRITE + + my_bytes_written = pwrite (handle, buffer, size, offset); + +#else + + if (lseek (handle, offset, SEEK_SET) != -1) + my_bytes_written = write (handle, buffer, size); + else + my_bytes_written = -1; + +#endif + + if (my_bytes_written != -1) + { + if (bytes_written != NULL) + *bytes_written = my_bytes_written; + ret = true; + } + else + { + set_system_error (error, errno); + } + + return ret; +} + +bool +tr_sys_file_flush (tr_sys_file_t handle, + tr_error ** error) +{ + bool ret; + + assert (handle != TR_BAD_SYS_FILE); + + ret = fsync (handle) != -1; + + if (!ret) + set_system_error (error, errno); + + return ret; +} + +bool +tr_sys_file_truncate (tr_sys_file_t handle, + uint64_t size, + tr_error ** error) +{ + bool ret; + + assert (handle != TR_BAD_SYS_FILE); + + ret = ftruncate (handle, size) != -1; + + if (!ret) + set_system_error (error, errno); + + return ret; +} + +bool +tr_sys_file_prefetch (tr_sys_file_t handle, + uint64_t offset, + uint64_t size, + tr_error ** error) +{ + bool ret = false; + +#if defined (HAVE_POSIX_FADVISE) + + int code; + + assert (handle != TR_BAD_SYS_FILE); + assert (size > 0); + + code = posix_fadvise (handle, offset, size, POSIX_FADV_WILLNEED); + + if (code == 0) + ret = true; + else + set_system_error (error, code); + +#elif defined (__APPLE__) + + struct radvisory radv; + + assert (handle != TR_BAD_SYS_FILE); + assert (size > 0); + + radv.ra_offset = offset; + radv.ra_count = size; + + ret = fcntl (handle, F_RDADVISE, &radv) != -1; + + if (!ret) + set_system_error (error, errno); + +#endif + + return ret; +} + +bool +tr_sys_file_preallocate (tr_sys_file_t handle, + uint64_t size, + int flags, + tr_error ** error) +{ + bool ret = false; + + assert (handle != TR_BAD_SYS_FILE); + + errno = 0; + +#ifdef HAVE_FALLOCATE64 + + /* fallocate64 is always preferred, so try it first */ + ret = fallocate64 (handle, 0, 0, size) != -1; + +#endif + + if (!ret && (flags & TR_SYS_FILE_PREALLOC_SPARSE) == 0) + { + int code = errno; + +#ifdef HAVE_XFS_XFS_H + + if (!ret && platform_test_xfs_fd (handle)) + { + xfs_flock64_t fl; + + fl.l_whence = 0; + fl.l_start = 0; + fl.l_len = size; + + ret = xfsctl (NULL, handle, XFS_IOC_RESVSP64, &fl) != -1; + + code = errno; + } + +#endif + +#ifdef __APPLE__ + + if (!ret) + { + fstore_t fst; + + fst.fst_flags = F_ALLOCATECONTIG; + fst.fst_posmode = F_PEOFPOSMODE; + fst.fst_offset = 0; + fst.fst_length = size; + fst.fst_bytesalloc = 0; + + ret = fcntl (handle, F_PREALLOCATE, &fst) != -1; + + if (ret) + ret = ftruncate (handle, size) != -1; + + code = errno; + } + +#endif + +#ifdef HAVE_POSIX_FALLOCATE + + if (!ret) + { + code = posix_fallocate (handle, 0, size); + ret = code == 0; + } + +#endif + + errno = code; + } + + if (!ret) + set_system_error (error, errno); + + return ret; +} + +void * +tr_sys_file_map_for_reading (tr_sys_file_t handle, + uint64_t offset, + uint64_t size, + tr_error ** error) +{ + void * ret; + + assert (handle != TR_BAD_SYS_FILE); + assert (size > 0); + + ret = mmap (NULL, size, PROT_READ, MAP_SHARED, handle, offset); + + if (ret == MAP_FAILED) + { + set_system_error (error, errno); + ret = NULL; + } + + return ret; +} + +bool +tr_sys_file_unmap (const void * address, + uint64_t size, + tr_error ** error) +{ + bool ret; + + assert (address != NULL); + assert (size > 0); + + ret = munmap ((void *) address, size) != -1; + + if (!ret) + set_system_error (error, errno); + + return ret; +} diff --git a/libtransmission/file-test.c b/libtransmission/file-test.c index c34cf63cf..19a3d69b6 100644 --- a/libtransmission/file-test.c +++ b/libtransmission/file-test.c @@ -137,11 +137,29 @@ path_contains_no_symlinks (const char * path) return true; } +static bool +validate_permissions (const char * path, + unsigned int permissions) +{ +#ifndef _WIN32 + + struct stat sb; + return stat (path, &sb) != -1 && (sb.st_mode & 0777) == permissions; + +#else + + /* No UNIX permissions on Windows */ + return true; + +#endif +} + static int test_get_info (void) { char * const test_dir = create_test_dir (__FUNCTION__); tr_sys_path_info info; + tr_sys_file_t fd; tr_error * err = NULL; char * path1, * path2; @@ -163,6 +181,16 @@ test_get_info (void) check_int_eq (4, info.size); check (info.last_modified_at >= time(0) - 1 && info.last_modified_at <= time(0)); + /* Good file info (by handle) */ + fd = tr_sys_file_open (path1, TR_SYS_FILE_READ, 0, NULL); + clear_path_info (&info); + check (tr_sys_file_get_info (fd, &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_file_close (fd, NULL); + tr_sys_path_remove (path1, NULL); /* Good directory info */ @@ -192,6 +220,16 @@ test_get_info (void) check_int_eq (4, info.size); check (info.last_modified_at >= time(0) - 1 && info.last_modified_at <= time(0)); + /* Good file info (by handle) */ + fd = tr_sys_file_open (path1, TR_SYS_FILE_READ, 0, NULL); + clear_path_info (&info); + check (tr_sys_file_get_info (fd, &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_file_close (fd, NULL); + tr_sys_path_remove (path2, NULL); /* Good directory info */ @@ -775,6 +813,345 @@ test_path_remove (void) return 0; } +static int +test_file_open (void) +{ + char * const test_dir = create_test_dir (__FUNCTION__); + tr_error * err = NULL; + char * path1; + tr_sys_file_t fd; + uint64_t n; + tr_sys_path_info info; + + path1 = tr_buildPath (test_dir, "a", NULL); + + /* Can't open non-existent file */ + check (!tr_sys_path_exists (path1, NULL)); + check (tr_sys_file_open (path1, TR_SYS_FILE_READ, 0600, &err) == TR_BAD_SYS_FILE); + check (err != NULL); + check (!tr_sys_path_exists (path1, NULL)); + tr_error_clear (&err); + check (tr_sys_file_open (path1, TR_SYS_FILE_WRITE, 0600, &err) == TR_BAD_SYS_FILE); + check (err != NULL); + check (!tr_sys_path_exists (path1, NULL)); + tr_error_clear (&err); + + /* Can't open directory */ + tr_mkdirp (path1, 0777); +#ifdef _WIN32 + /* This works on *NIX */ + check (tr_sys_file_open (path1, TR_SYS_FILE_READ, 0600, &err) == TR_BAD_SYS_FILE); + check (err != NULL); + tr_error_clear (&err); +#endif + check (tr_sys_file_open (path1, TR_SYS_FILE_WRITE, 0600, &err) == TR_BAD_SYS_FILE); + check (err != NULL); + tr_error_clear (&err); + + tr_sys_path_remove (path1, NULL); + + /* Can create non-existent file */ + fd = tr_sys_file_open (path1, TR_SYS_FILE_WRITE | TR_SYS_FILE_CREATE, 0640, &err); + check (fd != TR_BAD_SYS_FILE); + check (err == NULL); + tr_sys_file_close (fd, NULL); + check (tr_sys_path_exists (path1, NULL)); + check (validate_permissions (path1, 0640)); + + /* Can open existing file */ + check (tr_sys_path_exists (path1, NULL)); + fd = tr_sys_file_open (path1, TR_SYS_FILE_READ, 0600, &err); + check (fd != TR_BAD_SYS_FILE); + check (err == NULL); + tr_sys_file_close (fd, NULL); + fd = tr_sys_file_open (path1, TR_SYS_FILE_WRITE, 0600, &err); + check (fd != TR_BAD_SYS_FILE); + check (err == NULL); + tr_sys_file_close (fd, NULL); + + tr_sys_path_remove (path1, NULL); + libtest_create_file_with_string_contents (path1, "test"); + + /* Can't create new file if it already exists */ + fd = tr_sys_file_open (path1, TR_SYS_FILE_WRITE | TR_SYS_FILE_CREATE_NEW, 0640, &err); + check (fd == TR_BAD_SYS_FILE); + check (err != NULL); + tr_error_clear (&err); + tr_sys_path_get_info (path1, TR_SYS_PATH_NO_FOLLOW, &info, NULL); + check_int_eq (4, info.size); + + /* Pointer is at the end of file */ + tr_sys_path_get_info (path1, TR_SYS_PATH_NO_FOLLOW, &info, NULL); + check_int_eq (4, info.size); + fd = tr_sys_file_open (path1, TR_SYS_FILE_WRITE | TR_SYS_FILE_APPEND, 0600, &err); + check (fd != TR_BAD_SYS_FILE); + check (err == NULL); + tr_sys_file_write (fd, "s", 1, NULL, NULL); /* On *NIX, pointer is positioned on each write but not initially */ + tr_sys_file_seek (fd, 0, TR_SEEK_CUR, &n, NULL); + check_int_eq (5, n); + tr_sys_file_close (fd, NULL); + + /* File gets truncated */ + tr_sys_path_get_info (path1, TR_SYS_PATH_NO_FOLLOW, &info, NULL); + check_int_eq (5, info.size); + fd = tr_sys_file_open (path1, TR_SYS_FILE_WRITE | TR_SYS_FILE_TRUNCATE, 0600, &err); + check (fd != TR_BAD_SYS_FILE); + check (err == NULL); + tr_sys_file_get_info (fd, &info, NULL); + check_int_eq (0, info.size); + tr_sys_file_close (fd, NULL); + tr_sys_path_get_info (path1, TR_SYS_PATH_NO_FOLLOW, &info, NULL); + check_int_eq (0, info.size); + + /* TODO: symlink and hardlink tests */ + + tr_sys_path_remove (path1, NULL); + + tr_free (path1); + + tr_free (test_dir); + return 0; +} + +static int +test_file_read_write_seek (void) +{ + char * const test_dir = create_test_dir (__FUNCTION__); + tr_error * err = NULL; + char * path1; + tr_sys_file_t fd; + uint64_t n; + char buf[100]; + + path1 = tr_buildPath (test_dir, "a", NULL); + + fd = tr_sys_file_open (path1, TR_SYS_FILE_READ | TR_SYS_FILE_WRITE | TR_SYS_FILE_CREATE, 0600, NULL); + + check (tr_sys_file_seek (fd, 0, TR_SEEK_CUR, &n, &err)); + check (err == NULL); + check_int_eq (0, n); + + check (tr_sys_file_write (fd, "test", 4, &n, &err)); + check (err == NULL); + check_int_eq (4, n); + + check (tr_sys_file_seek (fd, 0, TR_SEEK_CUR, &n, &err)); + check (err == NULL); + check_int_eq (4, n); + + check (tr_sys_file_seek (fd, 0, TR_SEEK_SET, &n, &err)); + check (err == NULL); + check_int_eq (0, n); + + check (tr_sys_file_read (fd, buf, sizeof (buf), &n, &err)); + check (err == NULL); + check_int_eq (4, n); + + check_int_eq (0, memcmp (buf, "test", 4)); + + check (tr_sys_file_seek (fd, -3, TR_SEEK_CUR, &n, &err)); + check (err == NULL); + check_int_eq (1, n); + + check (tr_sys_file_write (fd, "E", 1, &n, &err)); + check (err == NULL); + check_int_eq (1, n); + + check (tr_sys_file_seek (fd, -2, TR_SEEK_CUR, &n, &err)); + check (err == NULL); + check_int_eq (0, n); + + check (tr_sys_file_read (fd, buf, sizeof (buf), &n, &err)); + check (err == NULL); + check_int_eq (4, n); + + check_int_eq (0, memcmp (buf, "tEst", 4)); + + check (tr_sys_file_seek (fd, 0, TR_SEEK_END, &n, &err)); + check (err == NULL); + check_int_eq (4, n); + + check (tr_sys_file_write (fd, " ok", 3, &n, &err)); + check (err == NULL); + check_int_eq (3, n); + + check (tr_sys_file_seek (fd, 0, TR_SEEK_SET, &n, &err)); + check (err == NULL); + check_int_eq (0, n); + + check (tr_sys_file_read (fd, buf, sizeof (buf), &n, &err)); + check (err == NULL); + check_int_eq (7, n); + + check_int_eq (0, memcmp (buf, "tEst ok", 7)); + + check (tr_sys_file_write_at (fd, "-", 1, 4, &n, &err)); + check (err == NULL); + check_int_eq (1, n); + + check (tr_sys_file_read_at (fd, buf, 5, 2, &n, &err)); + check (err == NULL); + check_int_eq (5, n); + + check_int_eq (0, memcmp (buf, "st-ok", 5)); + + tr_sys_file_close (fd, NULL); + + tr_sys_path_remove (path1, NULL); + + tr_free (path1); + + tr_free (test_dir); + return 0; +} + +static int +test_file_truncate (void) +{ + char * const test_dir = create_test_dir (__FUNCTION__); + tr_error * err = NULL; + char * path1; + tr_sys_file_t fd; + tr_sys_path_info info; + + path1 = tr_buildPath (test_dir, "a", NULL); + + fd = tr_sys_file_open (path1, TR_SYS_FILE_WRITE | TR_SYS_FILE_CREATE, 0600, NULL); + + check (tr_sys_file_truncate (fd, 10, &err)); + check (err == NULL); + tr_sys_file_get_info (fd, &info, NULL); + check_int_eq (10, info.size); + + check (tr_sys_file_truncate (fd, 20, &err)); + check (err == NULL); + tr_sys_file_get_info (fd, &info, NULL); + check_int_eq (20, info.size); + + check (tr_sys_file_truncate (fd, 0, &err)); + check (err == NULL); + tr_sys_file_get_info (fd, &info, NULL); + check_int_eq (0, info.size); + + check (tr_sys_file_truncate (fd, 50, &err)); + check (err == NULL); + + tr_sys_file_close (fd, NULL); + + tr_sys_path_get_info (path1, 0, &info, NULL); + check_int_eq (50, info.size); + + fd = tr_sys_file_open (path1, TR_SYS_FILE_WRITE | TR_SYS_FILE_CREATE, 0600, NULL); + + check (tr_sys_file_truncate (fd, 25, &err)); + check (err == NULL); + + tr_sys_file_close (fd, NULL); + + tr_sys_path_get_info (path1, 0, &info, NULL); + check_int_eq (25, info.size); + + tr_sys_path_remove (path1, NULL); + + tr_free (path1); + + tr_free (test_dir); + return 0; +} + +static int +test_file_preallocate (void) +{ + char * const test_dir = create_test_dir (__FUNCTION__); + tr_error * err = NULL; + char * path1; + tr_sys_file_t fd; + tr_sys_path_info info; + + path1 = tr_buildPath (test_dir, "a", NULL); + + fd = tr_sys_file_open (path1, TR_SYS_FILE_WRITE | TR_SYS_FILE_CREATE, 0600, NULL); + + if (tr_sys_file_preallocate (fd, 50, 0, &err)) + { + check (err == NULL); + tr_sys_file_get_info (fd, &info, NULL); + check_int_eq (50, info.size); + } + else + { + check (err != NULL); + fprintf (stderr, "WARNING: [%s] unable to preallocate file (full): %s (%d)\n", __FUNCTION__, err->message, err->code); + tr_error_clear (&err); + } + + tr_sys_file_close (fd, NULL); + + tr_sys_path_remove (path1, NULL); + + fd = tr_sys_file_open (path1, TR_SYS_FILE_WRITE | TR_SYS_FILE_CREATE, 0600, NULL); + + if (tr_sys_file_preallocate (fd, 500 * 1024 * 1024, TR_SYS_FILE_PREALLOC_SPARSE, &err)) + { + check (err == NULL); + tr_sys_file_get_info (fd, &info, NULL); + check_int_eq (500 * 1024 * 1024, info.size); + } + else + { + check (err != NULL); + fprintf (stderr, "WARNING: [%s] unable to preallocate file (sparse): %s (%d)\n", __FUNCTION__, err->message, err->code); + tr_error_clear (&err); + } + + tr_sys_file_close (fd, NULL); + + tr_sys_path_remove (path1, NULL); + + tr_free (path1); + + tr_free (test_dir); + return 0; +} + +static int +test_file_map (void) +{ + char * const test_dir = create_test_dir (__FUNCTION__); + tr_error * err = NULL; + char * path1; + tr_sys_file_t fd; + char * view; + + path1 = tr_buildPath (test_dir, "a", NULL); + + libtest_create_file_with_string_contents (path1, "test"); + + fd = tr_sys_file_open (path1, TR_SYS_FILE_READ | TR_SYS_FILE_WRITE, 0600, NULL); + + view = tr_sys_file_map_for_reading (fd, 0, 4, &err); + check (view != NULL); + check (err == NULL); + + check_int_eq (0, memcmp (view, "test", 4)); + + tr_sys_file_write_at (fd, "E", 1, 1, NULL, NULL); + + check_int_eq (0, memcmp (view, "tEst", 4)); + + check (tr_sys_file_unmap (view, 4, &err)); + check (err == NULL); + + tr_sys_file_close (fd, NULL); + + tr_sys_path_remove (path1, NULL); + + tr_free (path1); + + tr_free (test_dir); + return 0; +} + int main (void) { @@ -786,7 +1163,12 @@ main (void) test_path_resolve, test_path_basename_dirname, test_path_rename, - test_path_remove + test_path_remove, + test_file_open, + test_file_read_write_seek, + test_file_truncate, + test_file_preallocate, + test_file_map }; int ret; diff --git a/libtransmission/file-win32.c b/libtransmission/file-win32.c index 0ea5a555c..f5340d31f 100644 --- a/libtransmission/file-win32.c +++ b/libtransmission/file-win32.c @@ -10,10 +10,17 @@ #include #include /* _splitpath_s (), _makepath_s () */ +#include /* FSCTL_SET_SPARSE */ + #include "transmission.h" +#include "crypto.h" /* tr_cryptoRandInt () */ #include "file.h" #include "utils.h" +#ifndef MAXSIZE_T + #define MAXSIZE_T ((SIZE_T)~((SIZE_T)0)) +#endif + /* 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 @@ -90,10 +97,85 @@ stat_to_sys_path_info (DWORD attributes, info->last_modified_at = filetime_to_unix_time (mtime); } -static bool -get_file_info (HANDLE handle, - tr_sys_path_info * info, - tr_error ** error); +static tr_sys_file_t +open_file (const char * path, + DWORD access, + DWORD disposition, + DWORD flags, + tr_error ** error) +{ + tr_sys_file_t ret = TR_BAD_SYS_FILE; + wchar_t * wide_path; + + assert (path != NULL); + + wide_path = tr_win32_utf8_to_native (path, -1); + + if (wide_path != NULL) + ret = CreateFileW (wide_path, access, FILE_SHARE_READ | FILE_SHARE_WRITE, + NULL, disposition, flags, NULL); + + if (ret == TR_BAD_SYS_FILE) + set_system_error (error, GetLastError ()); + + tr_free (wide_path); + + return ret; +} + +static void +create_temp_path (char * path_template, + void (* callback) (const char * path, void * param, tr_error ** error), + void * callback_param, + tr_error ** error) +{ + char * path; + size_t path_size; + int attempt; + tr_error * my_error = NULL; + + assert (path_template != NULL); + assert (callback != NULL); + + path = tr_strdup (path_template); + path_size = strlen (path); + + assert (path_size > 0); + + for (attempt = 0; attempt < 100; ++attempt) + { + size_t i = path_size; + + while (i > 0 && path_template[i - 1] == 'X') + { + const int c = tr_cryptoRandInt (26 + 26 + 10); + path[i - 1] = c < 26 ? c + 'A' : (c < 26 + 26 ? (c - 26) + 'a' : (c - 26 - 26) + '0'); + --i; + } + + assert (path_size >= i + 6); + + tr_error_clear (&my_error); + + (*callback) (path, callback_param, &my_error); + + if (my_error == NULL) + break; + } + + if (my_error != NULL) + tr_error_propagate(error, &my_error); + else + memcpy (path_template, path, path_size); + + goto cleanup; + +fail: + set_system_error (error, GetLastError ()); + +cleanup: + tr_free (path); +} bool tr_sys_path_exists (const char * path, @@ -161,7 +243,7 @@ tr_sys_path_get_info (const char * path, if (handle != INVALID_HANDLE_VALUE) { tr_error * my_error = NULL; - ret = get_file_info (handle, info, &my_error); + ret = tr_sys_file_get_info (handle, info, &my_error); if (!ret) tr_error_propagate (error, &my_error); CloseHandle (handle); @@ -428,15 +510,140 @@ tr_sys_path_remove (const char * path, return ret; } -static bool -get_file_info (HANDLE handle, - tr_sys_path_info * info, - tr_error ** error) +tr_sys_file_t +tr_sys_file_get_std (tr_std_sys_file_t std_file, + tr_error ** error) +{ + tr_sys_file_t ret = TR_BAD_SYS_FILE; + + switch (std_file) + { + case TR_STD_SYS_FILE_IN: + ret = GetStdHandle (STD_INPUT_HANDLE); + break; + case TR_STD_SYS_FILE_OUT: + ret = GetStdHandle (STD_OUTPUT_HANDLE); + break; + case TR_STD_SYS_FILE_ERR: + ret = GetStdHandle (STD_ERROR_HANDLE); + break; + default: + assert (0 && "Unknown standard file"); + set_system_error (error, ERROR_INVALID_PARAMETER); + return TR_BAD_SYS_FILE; + } + + if (ret == TR_BAD_SYS_FILE) + set_system_error (error, GetLastError ()); + else if (ret == NULL) + ret = TR_BAD_SYS_FILE; + + return ret; +} + +tr_sys_file_t +tr_sys_file_open (const char * path, + int flags, + int permissions, + tr_error ** error) +{ + tr_sys_file_t ret; + DWORD native_access = 0; + DWORD native_disposition = OPEN_EXISTING; + DWORD native_flags = FILE_ATTRIBUTE_NORMAL; + bool success; + + assert (path != NULL); + assert ((flags & (TR_SYS_FILE_READ | TR_SYS_FILE_WRITE)) != 0); + + if (flags & TR_SYS_FILE_READ) + native_access |= GENERIC_READ; + if (flags & TR_SYS_FILE_WRITE) + native_access |= GENERIC_WRITE; + + if (flags & TR_SYS_FILE_CREATE_NEW) + native_disposition = CREATE_NEW; + else if (flags & TR_SYS_FILE_CREATE) + native_disposition = flags & TR_SYS_FILE_TRUNCATE ? CREATE_ALWAYS : OPEN_ALWAYS; + else if (flags & TR_SYS_FILE_TRUNCATE) + native_disposition = TRUNCATE_EXISTING; + + if (flags & TR_SYS_FILE_SEQUENTIAL) + native_flags |= FILE_FLAG_SEQUENTIAL_SCAN; + + ret = open_file (path, native_access, native_disposition, native_flags, error); + + success = ret != TR_BAD_SYS_FILE; + + if (success && (flags & TR_SYS_FILE_APPEND)) + success = SetFilePointer (ret, 0, NULL, FILE_END) != INVALID_SET_FILE_POINTER; + + if (!success) + { + if (error == NULL) + set_system_error (error, GetLastError ()); + + CloseHandle (ret); + ret = TR_BAD_SYS_FILE; + } + + return ret; +} + +static void +file_open_temp_callback (const char * path, + void * param, + tr_error ** error) +{ + tr_sys_file_t * result = (tr_sys_file_t *) param; + + assert (result != NULL); + + *result = open_file (path, + GENERIC_READ | GENERIC_WRITE, + CREATE_NEW, + FILE_ATTRIBUTE_TEMPORARY, + error); +} + +tr_sys_file_t +tr_sys_file_open_temp (char * path_template, + tr_error ** error) +{ + tr_sys_file_t ret = TR_BAD_SYS_FILE; + + assert (path_template != NULL); + + create_temp_path (path_template, file_open_temp_callback, &ret, error); + + return ret; +} + +bool +tr_sys_file_close (tr_sys_file_t handle, + tr_error ** error) +{ + bool ret; + + assert (handle != TR_BAD_SYS_FILE); + + ret = CloseHandle (handle); + + if (!ret) + set_system_error (error, GetLastError ()); + + return ret; +} + +bool +tr_sys_file_get_info (tr_sys_file_t handle, + tr_sys_path_info * info, + tr_error ** error) { bool ret; BY_HANDLE_FILE_INFORMATION attributes; - assert (handle != INVALID_HANDLE_VALUE); + assert (handle != TR_BAD_SYS_FILE); assert (info != NULL); ret = GetFileInformationByHandle (handle, &attributes); @@ -450,3 +657,311 @@ get_file_info (HANDLE handle, return ret; } + +bool +tr_sys_file_seek (tr_sys_file_t handle, + int64_t offset, + tr_seek_origin_t origin, + uint64_t * new_offset, + tr_error ** error) +{ + bool ret = false; + LARGE_INTEGER native_offset, new_native_pointer; + + TR_STATIC_ASSERT (TR_SEEK_SET == FILE_BEGIN, "values should match"); + TR_STATIC_ASSERT (TR_SEEK_CUR == FILE_CURRENT, "values should match"); + TR_STATIC_ASSERT (TR_SEEK_END == FILE_END, "values should match"); + + assert (handle != TR_BAD_SYS_FILE); + assert (origin == TR_SEEK_SET || origin == TR_SEEK_CUR || origin == TR_SEEK_END); + + native_offset.QuadPart = offset; + + if (SetFilePointerEx (handle, native_offset, &new_native_pointer, origin)) + { + if (new_offset != NULL) + *new_offset = new_native_pointer.QuadPart; + ret = true; + } + else + { + set_system_error (error, GetLastError ()); + } + + return ret; +} + +bool +tr_sys_file_read (tr_sys_file_t handle, + void * buffer, + uint64_t size, + uint64_t * bytes_read, + tr_error ** error) +{ + bool ret = false; + DWORD my_bytes_read; + + assert (handle != TR_BAD_SYS_FILE); + assert (buffer != NULL || size == 0); + + if (size > MAXDWORD) + { + set_system_error (error, ERROR_INVALID_PARAMETER); + return false; + } + + if (ReadFile (handle, buffer, (DWORD)size, &my_bytes_read, NULL)) + { + if (bytes_read != NULL) + *bytes_read = my_bytes_read; + ret = true; + } + else + { + set_system_error (error, GetLastError ()); + } + + return ret; +} + +bool +tr_sys_file_read_at (tr_sys_file_t handle, + void * buffer, + uint64_t size, + uint64_t offset, + uint64_t * bytes_read, + tr_error ** error) +{ + bool ret = false; + OVERLAPPED overlapped; + DWORD my_bytes_read; + + assert (handle != TR_BAD_SYS_FILE); + assert (buffer != NULL || size == 0); + + if (size > MAXDWORD) + { + set_system_error (error, ERROR_INVALID_PARAMETER); + return false; + } + + overlapped.Offset = (DWORD)offset; + offset >>= 32; + overlapped.OffsetHigh = (DWORD)offset; + overlapped.hEvent = NULL; + + if (ReadFile (handle, buffer, (DWORD)size, &my_bytes_read, &overlapped)) + { + if (bytes_read != NULL) + *bytes_read = my_bytes_read; + ret = true; + } + else + { + set_system_error (error, GetLastError ()); + } + + return ret; +} + +bool +tr_sys_file_write (tr_sys_file_t handle, + const void * buffer, + uint64_t size, + uint64_t * bytes_written, + tr_error ** error) +{ + bool ret = false; + DWORD my_bytes_written; + + assert (handle != TR_BAD_SYS_FILE); + assert (buffer != NULL || size == 0); + + if (size > MAXDWORD) + { + set_system_error (error, ERROR_INVALID_PARAMETER); + return false; + } + + if (WriteFile (handle, buffer, (DWORD)size, &my_bytes_written, NULL)) + { + if (bytes_written != NULL) + *bytes_written = my_bytes_written; + ret = true; + } + else + { + set_system_error (error, GetLastError ()); + } + + return ret; +} + +bool +tr_sys_file_write_at (tr_sys_file_t handle, + const void * buffer, + uint64_t size, + uint64_t offset, + uint64_t * bytes_written, + tr_error ** error) +{ + bool ret = false; + OVERLAPPED overlapped; + DWORD my_bytes_written; + + assert (handle != TR_BAD_SYS_FILE); + assert (buffer != NULL || size == 0); + + if (size > MAXDWORD) + { + set_system_error (error, ERROR_INVALID_PARAMETER); + return false; + } + + overlapped.Offset = (DWORD)offset; + offset >>= 32; + overlapped.OffsetHigh = (DWORD)offset; + overlapped.hEvent = NULL; + + if (WriteFile (handle, buffer, (DWORD)size, &my_bytes_written, &overlapped)) + { + if (bytes_written != NULL) + *bytes_written = my_bytes_written; + ret = true; + } + else + { + set_system_error (error, GetLastError ()); + } + + return ret; +} + +bool +tr_sys_file_flush (tr_sys_file_t handle, + tr_error ** error) +{ + bool ret; + + assert (handle != TR_BAD_SYS_FILE); + + ret = FlushFileBuffers (handle); + + if (!ret) + set_system_error (error, GetLastError ()); + + return ret; +} + +bool +tr_sys_file_truncate (tr_sys_file_t handle, + uint64_t size, + tr_error ** error) +{ + bool ret = false; + FILE_END_OF_FILE_INFO info; + + assert (handle != TR_BAD_SYS_FILE); + + info.EndOfFile.QuadPart = size; + + ret = SetFileInformationByHandle (handle, FileEndOfFileInfo, &info, sizeof (info)); + + if (!ret) + set_system_error (error, GetLastError ()); + + return ret; +} + +bool +tr_sys_file_prefetch (tr_sys_file_t handle, + uint64_t offset, + uint64_t size, + tr_error ** error) +{ + bool ret = false; + + assert (handle != TR_BAD_SYS_FILE); + assert (size > 0); + + /* ??? */ + + return ret; +} + +bool +tr_sys_file_preallocate (tr_sys_file_t handle, + uint64_t size, + int flags, + tr_error ** error) +{ + assert (handle != TR_BAD_SYS_FILE); + + if ((flags & TR_SYS_FILE_PREALLOC_SPARSE) != 0) + { + DWORD tmp; + if (!DeviceIoControl (handle, FSCTL_SET_SPARSE, NULL, 0, NULL, 0, &tmp, NULL)) + { + set_system_error (error, GetLastError ()); + return false; + } + } + + return tr_sys_file_truncate (handle, size, error); +} + +void * +tr_sys_file_map_for_reading (tr_sys_file_t handle, + uint64_t offset, + uint64_t size, + tr_error ** error) +{ + void * ret = NULL; + HANDLE mappingHandle; + + assert (handle != TR_BAD_SYS_FILE); + assert (size > 0); + + if (size > MAXSIZE_T) + { + set_system_error (error, ERROR_INVALID_PARAMETER); + return false; + } + + mappingHandle = CreateFileMappingW (handle, NULL, PAGE_READONLY, 0, 0, NULL); + + if (mappingHandle != NULL) + { + ULARGE_INTEGER native_offset; + + native_offset.QuadPart = offset; + + ret = MapViewOfFile (mappingHandle, FILE_MAP_READ, native_offset.u.HighPart, + native_offset.u.LowPart, (SIZE_T)size); + } + + if (ret == NULL) + set_system_error (error, GetLastError ()); + + CloseHandle (mappingHandle); + + return ret; +} + +bool +tr_sys_file_unmap (const void * address, + uint64_t size, + tr_error ** error) +{ + bool ret; + + assert (address != NULL); + assert (size > 0); + + ret = UnmapViewOfFile (address); + + if (!ret) + set_system_error (error, GetLastError ()); + + return ret; +} diff --git a/libtransmission/file.h b/libtransmission/file.h index 05fe1d27d..d75131015 100644 --- a/libtransmission/file.h +++ b/libtransmission/file.h @@ -28,12 +28,56 @@ extern "C" { * @{ */ +#ifndef _WIN32 + /** @brief Platform-specific file descriptor type. */ + typedef int tr_sys_file_t; + /** @brief Platform-specific invalid file descriptor constant. */ + #define TR_BAD_SYS_FILE (-1) +#else + typedef HANDLE tr_sys_file_t; + #define TR_BAD_SYS_FILE INVALID_HANDLE_VALUE +#endif + +typedef enum +{ + TR_STD_SYS_FILE_IN, + TR_STD_SYS_FILE_OUT, + TR_STD_SYS_FILE_ERR +} +tr_std_sys_file_t; + +typedef enum +{ + TR_SYS_FILE_READ = 1 << 0, + TR_SYS_FILE_WRITE = 1 << 1, + TR_SYS_FILE_CREATE = 1 << 2, + TR_SYS_FILE_CREATE_NEW = 1 << 3, + TR_SYS_FILE_APPEND = 1 << 4, + TR_SYS_FILE_TRUNCATE = 1 << 5, + TR_SYS_FILE_SEQUENTIAL = 1 << 6 +} +tr_sys_file_open_flags_t; + +typedef enum +{ + TR_SEEK_SET, + TR_SEEK_CUR, + TR_SEEK_END +} +tr_seek_origin_t; + typedef enum { TR_SYS_PATH_NO_FOLLOW = 1 << 0 } tr_sys_path_get_info_flags_t; +typedef enum +{ + TR_SYS_FILE_PREALLOC_SPARSE = 1 << 0 +} +tr_sys_file_preallocate_flags_t; + typedef enum { TR_SYS_PATH_IS_FILE, @@ -55,6 +99,9 @@ tr_sys_path_info; * * Following functions accept paths in UTF-8 encoding and convert them to native * encoding internally if needed. + * Descriptors returned (@ref tr_sys_file_t) may have different type depending + * on platform and should generally not be passed to native functions, but to + * wrapper functions instead. * * @{ */ @@ -183,6 +230,273 @@ bool tr_sys_path_rename (const char * src_path, bool tr_sys_path_remove (const char * path, tr_error ** error); +/* File-related wrappers */ + +/** + * @brief Get handle to one of standard I/O files. + * + * @param[in] std_file Standard file identifier. + * @param[out] error Pointer to error object. Optional, pass `NULL` if you + * are not interested in error details. + * + * @return Opened file descriptor on success, `TR_BAD_SYS_FILE` otherwise (with + * `error` set accordingly). DO NOT pass this descriptor to + * @ref tr_sys_file_close (unless you know what you are doing). + */ +tr_sys_file_t tr_sys_file_get_std (tr_std_sys_file_t std_file, + tr_error ** error); + +/** + * @brief Portability wrapper for `open ()`. + * + * @param[in] path Path to file. + * @param[in] flags Combination of @ref tr_sys_file_open_flags_t values. + * @param[in] permissions Permissions to create file with (in case + @ref TR_SYS_FILE_CREATE is used). Not used on Windows. + * @param[out] error Pointer to error object. Optional, pass `NULL` if you + * are not interested in error details. + * + * @return Opened file descriptor on success, `TR_BAD_SYS_FILE` otherwise (with + * `error` set accordingly). + */ +tr_sys_file_t tr_sys_file_open (const char * path, + int flags, + int permissions, + tr_error ** error); + +/** + * @brief Portability wrapper for `mkstemp ()`. + * + * @param[in,out] path_template Template path to file. Should end with at least + * six 'X' characters. Upon success, trailing 'X' + * characters are replaced with actual random + * characters used to form a unique path to + * temporary file. + * @param[out] error Pointer to error object. Optional, pass `NULL` + * if you are not interested in error details. + * + * @return Opened file descriptor on success, `TR_BAD_SYS_FILE` otherwise (with + * `error` set accordingly). + */ +tr_sys_file_t tr_sys_file_open_temp (char * path_template, + tr_error ** error); + +/** + * @brief Portability wrapper for `close ()`. + * + * @param[in] handle Valid file descriptor. + * @param[out] error Pointer to error object. Optional, pass `NULL` if you are + * not interested in error details. + * + * @return `True` on success, `false` otherwise (with `error` set accordingly). + */ +bool tr_sys_file_close (tr_sys_file_t handle, + tr_error ** error); + +/** + * @brief Portability wrapper for `fstat ()`. + * + * @param[in] handle Valid file descriptor. + * @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_file_get_info (tr_sys_file_t handle, + tr_sys_path_info * info, + tr_error ** error); + +/** + * @brief Portability wrapper for `lseek ()`. + * + * @param[in] handle Valid file descriptor. + * @param[in] offset Relative file offset in bytes to seek to. + * @param[in] origin Offset origin. + * @param[out] new_offset New offset in bytes from beginning of file. Optional, + * pass `NULL` if you are not interested. + * @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_file_seek (tr_sys_file_t handle, + int64_t offset, + tr_seek_origin_t origin, + uint64_t * new_offset, + tr_error ** error); + +/** + * @brief Portability wrapper for `read ()`. + * + * @param[in] handle Valid file descriptor. + * @param[out] buffer Buffer to store read data to. + * @param[in] size Number of bytes to read. + * @param[out] bytes_read Number of bytes actually read. Optional, pass `NULL` + * if you are not interested. + * @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_file_read (tr_sys_file_t handle, + void * buffer, + uint64_t size, + uint64_t * bytes_read, + tr_error ** error); + +/** + * @brief Like `pread ()`, except that the position is undefined afterwards. + * Not thread-safe. + * + * @param[in] handle Valid file descriptor. + * @param[out] buffer Buffer to store read data to. + * @param[in] size Number of bytes to read. + * @param[in] offset File offset in bytes to start reading from. + * @param[out] bytes_read Number of bytes actually read. Optional, pass `NULL` + * if you are not interested. + * @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_file_read_at (tr_sys_file_t handle, + void * buffer, + uint64_t size, + uint64_t offset, + uint64_t * bytes_read, + tr_error ** error); + +/** + * @brief Portability wrapper for `write ()`. + * + * @param[in] handle Valid file descriptor. + * @param[in] buffer Buffer to get data being written from. + * @param[in] size Number of bytes to write. + * @param[out] bytes_written Number of bytes actually written. Optional, pass + * `NULL` if you are not interested. + * @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_file_write (tr_sys_file_t handle, + const void * buffer, + uint64_t size, + uint64_t * bytes_written, + tr_error ** error); + +/** + * @brief Like `pwrite ()`, except that the position is undefined afterwards. + * Not thread-safe. + * + * @param[in] handle Valid file descriptor. + * @param[in] buffer Buffer to get data being written from. + * @param[in] size Number of bytes to write. + * @param[in] offset File offset in bytes to start writing from. + * @param[out] bytes_written Number of bytes actually written. Optional, pass + * `NULL` if you are not interested. + * @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_file_write_at (tr_sys_file_t handle, + const void * buffer, + uint64_t size, + uint64_t offset, + uint64_t * bytes_written, + tr_error ** error); + +/** + * @brief Portability wrapper for `fsync ()`. + * + * @param[in] handle Valid file descriptor. + * @param[out] error Pointer to error object. Optional, pass `NULL` if you are + * not interested in error details. + * + * @return `True` on success, `false` otherwise (with `error` set accordingly). + */ +bool tr_sys_file_flush (tr_sys_file_t handle, + tr_error ** error); + +/** + * @brief Portability wrapper for `ftruncate ()`. + * + * @param[in] handle Valid file descriptor. + * @param[in] size Number of bytes to truncate (or extend) file to. + * @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_file_truncate (tr_sys_file_t handle, + uint64_t size, + tr_error ** error); + +/** + * @brief Tell system to prefetch some part of file which is to be read soon. + * + * @param[in] handle Valid file descriptor. + * @param[in] offset Offset in file to prefetch from. + * @param[in] size Number of bytes to prefetch. + * @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_file_prefetch (tr_sys_file_t handle, + uint64_t offset, + uint64_t size, + tr_error ** error); + +/** + * @brief Preallocate file to specified size in full or sparse mode. + * + * @param[in] handle Valid file descriptor. + * @param[in] size Number of bytes to preallocate file to. + * @param[in] flags Combination of @ref tr_sys_file_preallocate_flags_t values. + * @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_file_preallocate (tr_sys_file_t handle, + uint64_t size, + int flags, + tr_error ** error); + +/** + * @brief Portability wrapper for `mmap ()` for files. + * + * @param[in] handle Valid file descriptor. + * @param[in] offset Offset in file to map from. + * @param[in] size Number of bytes to map. + * @param[out] error Pointer to error object. Optional, pass `NULL` if you are + * not interested in error details. + * + * @return Pointer to mapped file data on success, `NULL` otherwise (with + * `error` set accordingly). + */ +void * tr_sys_file_map_for_reading (tr_sys_file_t handle, + uint64_t offset, + uint64_t size, + tr_error ** error); + +/** + * @brief Portability wrapper for `munmap ()` for files. + * + * @param[in] address Pointer to mapped file data. + * @param[in] size Size of mapped data in bytes. + * @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_file_unmap (const void * address, + uint64_t size, + tr_error ** error); + /** @} */ /** @} */ diff --git a/libtransmission/tr-lpd.c b/libtransmission/tr-lpd.c index cb0e05f66..90613b8d0 100644 --- a/libtransmission/tr-lpd.c +++ b/libtransmission/tr-lpd.c @@ -411,7 +411,7 @@ static inline void lpd_consistencyCheck (void) * without our knowledge; revise string handling in functions tr_lpdSendAnnounce * and tr_lpdConsiderAnnounce. However, the code is designed to function as long * as interfaces to the rest of the lib remain compatible with char* strings. */ - STATIC_ASSERT (sizeof (lpd_torStaticType->info.hashString[0]) == sizeof (char)); + TR_STATIC_ASSERT (sizeof (lpd_torStaticType->info.hashString[0]) == sizeof (char), ""); } /** * @endcond */ diff --git a/libtransmission/tr-lpd.h b/libtransmission/tr-lpd.h index 11f524db0..e14ca6369 100644 --- a/libtransmission/tr-lpd.h +++ b/libtransmission/tr-lpd.h @@ -45,11 +45,6 @@ bool tr_lpdSendAnnounce (const tr_torrent*); * Meaningful return values are only guaranteed for true array types. */ #define lengthof(arr)(sizeof (* (arr)) > 0 ? sizeof (arr) / sizeof (* (arr)) : 0) -/** -* @def STATIC_ASSERT -* @brief This helper allows to perform static checks at compile time */ -#define STATIC_ASSERT(x) { const char static_check[ ((x) ? 1 : -1)] UNUSED; } - /** * @} */ diff --git a/libtransmission/utils.h b/libtransmission/utils.h index abbdb55a9..bc139c02b 100644 --- a/libtransmission/utils.h +++ b/libtransmission/utils.h @@ -70,6 +70,26 @@ extern "C" { #endif +#ifndef __has_feature + #define __has_feature(x) 0 +#endif +#ifndef __has_extension + #define __has_extension __has_feature +#endif + +/** + * @def TR_STATIC_ASSERT + * @brief This helper allows to perform static checks at compile time + */ +#if defined (static_assert) + #define TR_STATIC_ASSERT static_assert +#elif __has_feature (c_static_assert) || __has_extension (c_static_assert) + #define TR_STATIC_ASSERT _Static_assert +#else + #define TR_STATIC_ASSERT(x, msg) { const char static_check[((x) ? 1 : -1)] UNUSED; } +#endif + + /*** **** ***/