1
0
Fork 0
mirror of https://github.com/transmission/transmission synced 2024-12-26 01:27:28 +00:00
transmission/libtransmission/file-posix.c
Mike Gelfand a2037bdbbd Define one of LFS macros instead of using xxx64 functions directly.
There're too many functions and types to consider, and benefits of not
using LFS macros aren't that big (I was thinking of using fts(3) but
that may not happen soon or at all).
2015-03-10 22:31:09 +00:00

1079 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)
#undef _XOPEN_SOURCE
#define _XOPEN_SOURCE 700
#elif (defined (HAVE_POSIX_FADVISE) || defined (HAVE_POSIX_FALLOCATE)) && (!defined (_XOPEN_SOURCE) || _XOPEN_SOURCE < 600)
#undef _XOPEN_SOURCE
#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 != -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;
}