]> granicus.if.org Git - transmission/commitdiff
(trunk, libT) #4160 'foreign character support' -- merge mike.dld's 4160-03a-file...
authorJordan Lee <jordan@transmissionbt.com>
Tue, 8 Jul 2014 00:15:12 +0000 (00:15 +0000)
committerJordan Lee <jordan@transmissionbt.com>
Tue, 8 Jul 2014 00:15:12 +0000 (00:15 +0000)
libtransmission/file-posix.c
libtransmission/file-test.c
libtransmission/file-win32.c
libtransmission/file.h
libtransmission/tr-lpd.c
libtransmission/tr-lpd.h
libtransmission/utils.h

index f630d4aad49b6792de9ce77844cff392d1bd1cfe..afcc0b21254cc4f1a190473d9814590492d510f7 100644 (file)
@@ -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 <assert.h>
 #include <errno.h>
+#include <fcntl.h> /* O_LARGEFILE, posix_fadvise (), [posix_]fallocate () */
 #include <libgen.h> /* basename (), dirname () */
 #include <limits.h> /* PATH_MAX */
 #include <stdio.h>
 #include <stdlib.h>
+#include <string.h>
+#include <sys/mman.h> /* mmap (), munmap () */
 #include <sys/types.h>
 #include <sys/stat.h>
-#include <unistd.h>
+#include <unistd.h> /* lseek (), write (), ftruncate (), pread (), pwrite (), pathconf (), etc */
+
+#ifdef HAVE_XFS_XFS_H
+ #include <xfs/xfs.h>
+#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;
+}
index c34cf63cfa23e4319a23beacb4a3bf910a1d23ef..19a3d69b60299e3691f0338744c30011b1fcb48a 100644 (file)
@@ -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;
 
index 0ea5a555cb0af2499a72960d1d49e6ff9c9af2b0..f5340d31f34d893601aab51740374ed2e42a02f1 100644 (file)
 #include <assert.h>
 #include <stdlib.h> /* _splitpath_s (), _makepath_s () */
 
+#include <winioctl.h> /* 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;
+}
index 05fe1d27d1e3fe83dbee5c41f3b1e5753e5184f9..d75131015ab61c1db42291604d9261d2d0dc8d96 100644 (file)
@@ -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);
+
 /** @} */
 /** @} */
 
index cb0e05f665df90d42d8514d9a6b76437e8ceafc5..90613b8d092eec156237eaaf4a102fa1df2bc124 100644 (file)
@@ -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 */
index 11f524db03c1dd22de465bda0c9996eedbc1c668..e14ca63691ae39a7bf2daf08702d76c59b8c263f 100644 (file)
@@ -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; }
-
 /**
 * @} */
 
index abbdb55a9553471f7067ec3a141d712ef670d29e..bc139c02b707650deab613091a36f35c1bd1acca 100644 (file)
@@ -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
+
+
 /***
 ****
 ***/