1084 lines
21 KiB
C
1084 lines
21 KiB
C
/*
|
|
* This file Copyright (C) 2013-2014 Mnemosyne LLC
|
|
*
|
|
* It may be used under the GNU GPL versions 2 or 3
|
|
* or any future license endorsed by Mnemosyne LLC.
|
|
*
|
|
* $Id$
|
|
*/
|
|
|
|
#if defined (HAVE_MKDTEMP) && (!defined (_XOPEN_SOURCE) || _XOPEN_SOURCE < 700)
|
|
#ifdef _XOPEN_SOURCE
|
|
#undef _XOPEN_SOURCE
|
|
#endif
|
|
#define _XOPEN_SOURCE 700
|
|
#elif (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 <dirent.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> /* lseek (), write (), ftruncate (), pread (), pwrite (), pathconf (), etc */
|
|
|
|
#ifdef HAVE_XFS_XFS_H
|
|
#include <xfs/xfs.h>
|
|
#endif
|
|
|
|
#include "transmission.h"
|
|
#include "file.h"
|
|
#include "log.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)
|
|
{
|
|
if (error == NULL)
|
|
return;
|
|
|
|
tr_error_set_literal (error, code, tr_strerror (code));
|
|
}
|
|
|
|
static void
|
|
set_system_error_if_file_found (tr_error ** error,
|
|
int code)
|
|
{
|
|
if (code != ENOENT)
|
|
set_system_error (error, code);
|
|
}
|
|
|
|
static void
|
|
stat_to_sys_path_info (const struct stat * sb,
|
|
tr_sys_path_info * info)
|
|
{
|
|
if (S_ISREG (sb->st_mode))
|
|
info->type = TR_SYS_PATH_IS_FILE;
|
|
else if (S_ISDIR (sb->st_mode))
|
|
info->type = TR_SYS_PATH_IS_DIRECTORY;
|
|
else
|
|
info->type = TR_SYS_PATH_IS_OTHER;
|
|
|
|
info->size = (uint64_t) sb->st_size;
|
|
info->last_modified_at = sb->st_mtime;
|
|
}
|
|
|
|
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;
|
|
}
|
|
|
|
#ifndef HAVE_MKDIRP
|
|
|
|
static bool
|
|
create_path (const char * path_in,
|
|
int permissions,
|
|
tr_error ** error)
|
|
{
|
|
char * p;
|
|
char * pp;
|
|
bool done;
|
|
int tmperr;
|
|
int rv;
|
|
struct stat sb;
|
|
char * path;
|
|
|
|
/* make a temporary copy of path */
|
|
path = tr_strdup (path_in);
|
|
|
|
/* walk past the root */
|
|
p = path;
|
|
while (*p == TR_PATH_DELIMITER)
|
|
++p;
|
|
|
|
pp = p;
|
|
done = false;
|
|
while ((p = strchr (pp, TR_PATH_DELIMITER)) || (p = strchr (pp, '\0')))
|
|
{
|
|
if (!*p)
|
|
done = true;
|
|
else
|
|
*p = '\0';
|
|
|
|
tmperr = errno;
|
|
rv = stat (path, &sb);
|
|
errno = tmperr;
|
|
if (rv)
|
|
{
|
|
tr_error * my_error = NULL;
|
|
|
|
/* Folder doesn't exist yet */
|
|
if (!tr_sys_dir_create (path, 0, permissions, &my_error))
|
|
{
|
|
tr_logAddError (_ ("Couldn't create \"%1$s\": %2$s"), path, my_error->message);
|
|
tr_free (path);
|
|
tr_error_propagate (error, &my_error);
|
|
return false;
|
|
}
|
|
}
|
|
else if ((sb.st_mode & S_IFMT) != S_IFDIR)
|
|
{
|
|
/* Node exists but isn't a folder */
|
|
char * const buf = tr_strdup_printf (_ ("File \"%s\" is in the way"), path);
|
|
tr_logAddError (_ ("Couldn't create \"%1$s\": %2$s"), path_in, buf);
|
|
tr_free (buf);
|
|
tr_free (path);
|
|
set_system_error (error, ENOTDIR);
|
|
return false;
|
|
}
|
|
|
|
if (done)
|
|
break;
|
|
|
|
*p = TR_PATH_DELIMITER;
|
|
p++;
|
|
pp = p;
|
|
}
|
|
|
|
tr_free (path);
|
|
return true;
|
|
}
|
|
|
|
#endif
|
|
|
|
bool
|
|
tr_sys_path_exists (const char * path,
|
|
tr_error ** error)
|
|
{
|
|
bool ret;
|
|
|
|
assert (path != NULL);
|
|
|
|
ret = access (path, F_OK) != -1;
|
|
|
|
if (!ret)
|
|
set_system_error_if_file_found (error, errno);
|
|
|
|
return ret;
|
|
}
|
|
|
|
bool
|
|
tr_sys_path_get_info (const char * path,
|
|
int flags,
|
|
tr_sys_path_info * info,
|
|
tr_error ** error)
|
|
{
|
|
bool ret;
|
|
struct stat sb;
|
|
|
|
assert (path != NULL);
|
|
assert (info != NULL);
|
|
|
|
if ((flags & TR_SYS_PATH_NO_FOLLOW) == 0)
|
|
ret = stat (path, &sb) != -1;
|
|
else
|
|
ret = lstat (path, &sb) != -1;
|
|
|
|
if (ret)
|
|
stat_to_sys_path_info (&sb, info);
|
|
else
|
|
set_system_error (error, errno);
|
|
|
|
return ret;
|
|
}
|
|
|
|
bool
|
|
tr_sys_path_is_same (const char * path1,
|
|
const char * path2,
|
|
tr_error ** error)
|
|
{
|
|
bool ret = false;
|
|
struct stat sb1, sb2;
|
|
|
|
assert (path1 != NULL);
|
|
assert (path2 != NULL);
|
|
|
|
if (stat (path1, &sb1) != -1 && stat (path2, &sb2) != -1)
|
|
ret = sb1.st_dev == sb2.st_dev && sb1.st_ino == sb2.st_ino;
|
|
else
|
|
set_system_error_if_file_found (error, errno);
|
|
|
|
return ret;
|
|
}
|
|
|
|
char *
|
|
tr_sys_path_resolve (const char * path,
|
|
tr_error ** error)
|
|
{
|
|
char * ret = NULL;
|
|
char * tmp = NULL;
|
|
|
|
assert (path != NULL);
|
|
|
|
#if defined (HAVE_CANONICALIZE_FILE_NAME)
|
|
|
|
ret = canonicalize_file_name (path);
|
|
|
|
#endif
|
|
|
|
#if defined (_POSIX_C_SOURCE) && _POSIX_C_SOURCE >= 200809L
|
|
|
|
/* Better safe than sorry: realpath () officially supports NULL as destination
|
|
starting off POSIX.1-2008. */
|
|
|
|
if (ret == NULL)
|
|
ret = realpath (path, NULL);
|
|
|
|
#endif
|
|
|
|
if (ret == NULL)
|
|
{
|
|
tmp = tr_new (char, PATH_MAX);
|
|
ret = realpath (path, tmp);
|
|
if (ret != NULL)
|
|
ret = tr_strdup (ret);
|
|
}
|
|
|
|
if (ret == NULL)
|
|
set_system_error (error, errno);
|
|
|
|
tr_free (tmp);
|
|
|
|
return ret;
|
|
}
|
|
|
|
char *
|
|
tr_sys_path_basename (const char * path,
|
|
tr_error ** error)
|
|
{
|
|
char * ret = NULL;
|
|
char * tmp;
|
|
|
|
assert (path != NULL);
|
|
|
|
tmp = tr_strdup (path);
|
|
ret = basename (tmp);
|
|
if (ret != NULL)
|
|
ret = tr_strdup (ret);
|
|
else
|
|
set_system_error (error, errno);
|
|
|
|
tr_free (tmp);
|
|
|
|
return ret;
|
|
}
|
|
|
|
char *
|
|
tr_sys_path_dirname (const char * path,
|
|
tr_error ** error)
|
|
{
|
|
char * ret = NULL;
|
|
char * tmp;
|
|
|
|
assert (path != NULL);
|
|
|
|
tmp = tr_strdup (path);
|
|
ret = dirname (tmp);
|
|
if (ret != NULL)
|
|
ret = tr_strdup (ret);
|
|
else
|
|
set_system_error (error, errno);
|
|
|
|
tr_free (tmp);
|
|
|
|
return ret;
|
|
}
|
|
|
|
bool
|
|
tr_sys_path_rename (const char * src_path,
|
|
const char * dst_path,
|
|
tr_error ** error)
|
|
{
|
|
bool ret;
|
|
|
|
assert (src_path != NULL);
|
|
assert (dst_path != NULL);
|
|
|
|
ret = rename (src_path, dst_path) != -1;
|
|
|
|
if (!ret)
|
|
set_system_error (error, errno);
|
|
|
|
return ret;
|
|
}
|
|
|
|
bool
|
|
tr_sys_path_remove (const char * path,
|
|
tr_error ** error)
|
|
{
|
|
bool ret;
|
|
|
|
assert (path != NULL);
|
|
|
|
ret = remove (path) != -1;
|
|
|
|
if (!ret)
|
|
set_system_error (error, errno);
|
|
|
|
return ret;
|
|
}
|
|
|
|
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);
|
|
/* seek requires signed offset, so it should be in mod range */
|
|
assert (offset < UINT64_MAX / 2);
|
|
|
|
#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);
|
|
/* seek requires signed offset, so it should be in mod range */
|
|
assert (offset < UINT64_MAX / 2);
|
|
|
|
#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;
|
|
|
|
if (ret || errno == ENOSPC)
|
|
goto out;
|
|
|
|
#endif
|
|
|
|
if ((flags & TR_SYS_FILE_PREALLOC_SPARSE) == 0)
|
|
{
|
|
int code = errno;
|
|
|
|
#ifdef HAVE_XFS_XFS_H
|
|
|
|
if (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;
|
|
|
|
if (ret)
|
|
ret = ftruncate (handle, size) != -1;
|
|
|
|
code = errno;
|
|
|
|
if (ret || code == ENOSPC)
|
|
goto non_sparse_out;
|
|
}
|
|
|
|
#endif
|
|
|
|
#ifdef __APPLE__
|
|
|
|
{
|
|
fstore_t fst;
|
|
|
|
fst.fst_flags = F_ALLOCATEALL;
|
|
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;
|
|
|
|
if (ret || code == ENOSPC)
|
|
goto non_sparse_out;
|
|
}
|
|
|
|
#endif
|
|
|
|
#ifdef HAVE_POSIX_FALLOCATE
|
|
|
|
code = posix_fallocate (handle, 0, size);
|
|
ret = code == 0;
|
|
|
|
#endif
|
|
|
|
non_sparse_out:
|
|
errno = code;
|
|
}
|
|
|
|
out:
|
|
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;
|
|
}
|
|
|
|
char *
|
|
tr_sys_dir_get_current (tr_error ** error)
|
|
{
|
|
char * ret;
|
|
|
|
ret = getcwd (NULL, 0);
|
|
|
|
if (ret == NULL && (errno == EINVAL || errno == ERANGE))
|
|
{
|
|
size_t size = PATH_MAX;
|
|
char * tmp = NULL;
|
|
|
|
do
|
|
{
|
|
tmp = tr_renew (char, tmp, size);
|
|
if (tmp == NULL)
|
|
break;
|
|
ret = getcwd (tmp, size);
|
|
size += 2048;
|
|
}
|
|
while (ret == NULL && errno == ERANGE);
|
|
|
|
if (ret == NULL)
|
|
{
|
|
const int err = errno;
|
|
tr_free (tmp);
|
|
errno = err;
|
|
}
|
|
}
|
|
|
|
if (ret == NULL)
|
|
set_system_error (error, errno);
|
|
|
|
return ret;
|
|
}
|
|
|
|
bool
|
|
tr_sys_dir_create (const char * path,
|
|
int flags,
|
|
int permissions,
|
|
tr_error ** error)
|
|
{
|
|
bool ret;
|
|
tr_error * my_error = NULL;
|
|
|
|
assert (path != NULL);
|
|
|
|
if ((flags & TR_SYS_DIR_CREATE_PARENTS) != 0)
|
|
#ifdef HAVE_MKDIRP
|
|
ret = mkdirp (path, permissions) != -1;
|
|
#else
|
|
ret = create_path (path, permissions, &my_error);
|
|
#endif
|
|
else
|
|
ret = mkdir (path, permissions) != -1;
|
|
|
|
if (!ret && errno == EEXIST)
|
|
{
|
|
struct stat sb;
|
|
|
|
if (stat (path, &sb) != -1 && S_ISDIR (sb.st_mode))
|
|
{
|
|
tr_error_clear (&my_error);
|
|
ret = true;
|
|
}
|
|
else
|
|
{
|
|
errno = EEXIST;
|
|
}
|
|
}
|
|
|
|
if (!ret)
|
|
{
|
|
if (my_error != NULL)
|
|
tr_error_propagate (error, &my_error);
|
|
else
|
|
set_system_error (error, errno);
|
|
}
|
|
|
|
return ret;
|
|
}
|
|
|
|
bool
|
|
tr_sys_dir_create_temp (char * path_template,
|
|
tr_error ** error)
|
|
{
|
|
bool ret;
|
|
|
|
assert (path_template != NULL);
|
|
|
|
#ifdef HAVE_MKDTEMP
|
|
|
|
ret = mkdtemp (path_template) != NULL;
|
|
|
|
#else
|
|
|
|
ret = mktemp (path_template) != NULL && mkdir (path_template, 0700) != -1;
|
|
|
|
#endif
|
|
|
|
if (!ret)
|
|
set_system_error (error, errno);
|
|
|
|
return ret;
|
|
}
|
|
|
|
tr_sys_dir_t
|
|
tr_sys_dir_open (const char * path,
|
|
tr_error ** error)
|
|
{
|
|
tr_sys_dir_t ret;
|
|
|
|
#ifndef __clang__
|
|
/* Clang gives "static_assert expression is not an integral constant expression" error */
|
|
TR_STATIC_ASSERT (TR_BAD_SYS_DIR == NULL, "values should match");
|
|
#endif
|
|
|
|
assert (path != NULL);
|
|
|
|
ret = opendir (path);
|
|
|
|
if (ret == TR_BAD_SYS_DIR)
|
|
set_system_error (error, errno);
|
|
|
|
return ret;
|
|
}
|
|
|
|
const char *
|
|
tr_sys_dir_read_name (tr_sys_dir_t handle,
|
|
tr_error ** error)
|
|
{
|
|
const char * ret = NULL;
|
|
struct dirent * entry;
|
|
|
|
assert (handle != TR_BAD_SYS_DIR);
|
|
|
|
errno = 0;
|
|
entry = readdir (handle);
|
|
|
|
if (entry != NULL)
|
|
ret = entry->d_name;
|
|
else if (errno != 0)
|
|
set_system_error (error, errno);
|
|
|
|
return ret;
|
|
}
|
|
|
|
bool
|
|
tr_sys_dir_close (tr_sys_dir_t handle,
|
|
tr_error ** error)
|
|
{
|
|
bool ret;
|
|
|
|
assert (handle != TR_BAD_SYS_DIR);
|
|
|
|
ret = closedir (handle) != -1;
|
|
|
|
if (!ret)
|
|
set_system_error (error, errno);
|
|
|
|
return ret;
|
|
}
|