2010-05-17 16:41:59 +00:00
|
|
|
/*
|
2014-01-19 01:09:44 +00:00
|
|
|
* This file Copyright (C) 2005-2014 Mnemosyne LLC
|
2006-07-16 19:39:23 +00:00
|
|
|
*
|
2014-01-21 03:10:30 +00:00
|
|
|
* It may be used under the GNU GPL versions 2 or 3
|
2014-01-19 01:09:44 +00:00
|
|
|
* or any future license endorsed by Mnemosyne LLC.
|
2006-07-16 19:39:23 +00:00
|
|
|
*
|
2010-05-17 16:41:59 +00:00
|
|
|
*/
|
2006-07-16 19:39:23 +00:00
|
|
|
|
2007-07-29 18:11:21 +00:00
|
|
|
#include <errno.h>
|
2007-10-31 18:10:55 +00:00
|
|
|
#include <inttypes.h>
|
2007-07-29 18:11:21 +00:00
|
|
|
#include <string.h>
|
2010-02-02 03:01:25 +00:00
|
|
|
|
2014-12-13 15:22:39 +00:00
|
|
|
#ifndef _WIN32
|
2017-04-19 12:04:45 +00:00
|
|
|
#include <sys/time.h> /* getrlimit */
|
|
|
|
#include <sys/resource.h> /* getrlimit */
|
2014-12-13 15:22:39 +00:00
|
|
|
#endif
|
2009-01-25 23:40:08 +00:00
|
|
|
|
2006-07-16 19:39:23 +00:00
|
|
|
#include "transmission.h"
|
2014-07-28 04:13:38 +00:00
|
|
|
#include "error.h"
|
2014-12-10 18:23:11 +00:00
|
|
|
#include "error-types.h"
|
2008-08-11 19:05:02 +00:00
|
|
|
#include "fdlimit.h"
|
2014-07-08 00:08:43 +00:00
|
|
|
#include "file.h"
|
2013-01-25 23:34:20 +00:00
|
|
|
#include "log.h"
|
2009-10-23 03:41:36 +00:00
|
|
|
#include "session.h"
|
2017-04-21 07:40:57 +00:00
|
|
|
#include "torrent.h" /* tr_isTorrent() */
|
2017-06-08 07:24:12 +00:00
|
|
|
#include "tr-assert.h"
|
2006-07-16 19:39:23 +00:00
|
|
|
|
2017-05-22 20:12:57 +00:00
|
|
|
#define dbgmsg(...) tr_logAddDeepNamed(NULL, __VA_ARGS__)
|
2007-01-21 07:16:18 +00:00
|
|
|
|
2007-10-30 18:35:06 +00:00
|
|
|
/***
|
|
|
|
****
|
|
|
|
**** Local Files
|
|
|
|
****
|
|
|
|
***/
|
|
|
|
|
2017-04-19 12:04:45 +00:00
|
|
|
static bool preallocate_file_sparse(tr_sys_file_t fd, uint64_t length, tr_error** error)
|
2009-01-16 16:38:16 +00:00
|
|
|
{
|
2017-04-19 12:04:45 +00:00
|
|
|
tr_error* my_error = NULL;
|
2010-03-09 16:19:59 +00:00
|
|
|
|
2017-04-19 12:04:45 +00:00
|
|
|
if (length == 0)
|
|
|
|
{
|
|
|
|
return true;
|
|
|
|
}
|
2009-01-16 16:38:16 +00:00
|
|
|
|
2017-04-19 12:04:45 +00:00
|
|
|
if (tr_sys_file_preallocate(fd, length, TR_SYS_FILE_PREALLOC_SPARSE, &my_error))
|
|
|
|
{
|
|
|
|
return true;
|
|
|
|
}
|
2009-01-16 16:38:16 +00:00
|
|
|
|
2017-04-19 12:04:45 +00:00
|
|
|
dbgmsg("Preallocating (sparse, normal) failed (%d): %s", my_error->code, my_error->message);
|
2014-12-10 18:23:11 +00:00
|
|
|
|
2017-04-19 12:04:45 +00:00
|
|
|
if (!TR_ERROR_IS_ENOSPC(my_error->code))
|
2014-07-28 04:13:38 +00:00
|
|
|
{
|
2017-04-20 16:02:19 +00:00
|
|
|
char const zero = '\0';
|
2014-12-10 18:23:11 +00:00
|
|
|
|
2017-04-19 12:04:45 +00:00
|
|
|
tr_error_clear(&my_error);
|
2014-07-28 04:13:38 +00:00
|
|
|
|
2017-04-19 12:04:45 +00:00
|
|
|
/* fallback: the old-style seek-and-write */
|
|
|
|
if (tr_sys_file_write_at(fd, &zero, 1, length - 1, NULL, &my_error) && tr_sys_file_truncate(fd, length, &my_error))
|
|
|
|
{
|
|
|
|
return true;
|
|
|
|
}
|
2014-12-10 18:23:11 +00:00
|
|
|
|
2017-04-19 12:04:45 +00:00
|
|
|
dbgmsg("Preallocating (sparse, fallback) failed (%d): %s", my_error->code, my_error->message);
|
2014-07-28 04:13:38 +00:00
|
|
|
}
|
2009-01-16 16:38:16 +00:00
|
|
|
|
2017-04-19 12:04:45 +00:00
|
|
|
tr_error_propagate(error, &my_error);
|
|
|
|
return false;
|
2009-01-16 16:38:16 +00:00
|
|
|
}
|
|
|
|
|
2017-04-19 12:04:45 +00:00
|
|
|
static bool preallocate_file_full(tr_sys_file_t fd, uint64_t length, tr_error** error)
|
2008-11-05 05:56:06 +00:00
|
|
|
{
|
2017-04-19 12:04:45 +00:00
|
|
|
tr_error* my_error = NULL;
|
2014-12-10 18:23:11 +00:00
|
|
|
|
2017-04-19 12:04:45 +00:00
|
|
|
if (length == 0)
|
|
|
|
{
|
|
|
|
return true;
|
|
|
|
}
|
2008-11-15 19:59:18 +00:00
|
|
|
|
2017-04-19 12:04:45 +00:00
|
|
|
if (tr_sys_file_preallocate(fd, length, 0, &my_error))
|
|
|
|
{
|
|
|
|
return true;
|
|
|
|
}
|
2014-12-10 18:23:11 +00:00
|
|
|
|
2017-04-19 12:04:45 +00:00
|
|
|
dbgmsg("Preallocating (full, normal) failed (%d): %s", my_error->code, my_error->message);
|
2014-12-10 18:23:11 +00:00
|
|
|
|
2017-04-19 12:04:45 +00:00
|
|
|
if (!TR_ERROR_IS_ENOSPC(my_error->code))
|
2008-11-15 19:59:18 +00:00
|
|
|
{
|
2017-04-19 12:04:45 +00:00
|
|
|
uint8_t buf[4096];
|
|
|
|
bool success = true;
|
2014-12-10 18:23:11 +00:00
|
|
|
|
2017-04-19 12:04:45 +00:00
|
|
|
memset(buf, 0, sizeof(buf));
|
|
|
|
tr_error_clear(&my_error);
|
2008-11-15 19:59:18 +00:00
|
|
|
|
2017-04-19 12:04:45 +00:00
|
|
|
/* fallback: the old-fashioned way */
|
|
|
|
while (success && length > 0)
|
2009-06-16 17:10:47 +00:00
|
|
|
{
|
2017-04-20 16:02:19 +00:00
|
|
|
uint64_t const thisPass = MIN(length, sizeof(buf));
|
2017-04-19 12:04:45 +00:00
|
|
|
uint64_t bytes_written;
|
|
|
|
success = tr_sys_file_write(fd, buf, thisPass, &bytes_written, &my_error);
|
|
|
|
length -= bytes_written;
|
2009-06-16 17:10:47 +00:00
|
|
|
}
|
|
|
|
|
2017-04-19 12:04:45 +00:00
|
|
|
if (success)
|
|
|
|
{
|
|
|
|
return true;
|
|
|
|
}
|
2014-12-10 18:23:11 +00:00
|
|
|
|
2017-04-19 12:04:45 +00:00
|
|
|
dbgmsg("Preallocating (full, fallback) failed (%d): %s", my_error->code, my_error->message);
|
2008-11-15 19:59:18 +00:00
|
|
|
}
|
2008-11-05 05:56:06 +00:00
|
|
|
|
2017-04-19 12:04:45 +00:00
|
|
|
tr_error_propagate(error, &my_error);
|
|
|
|
return false;
|
2008-11-05 05:56:06 +00:00
|
|
|
}
|
|
|
|
|
2010-12-28 07:24:10 +00:00
|
|
|
/*****
|
|
|
|
******
|
|
|
|
******
|
|
|
|
******
|
|
|
|
*****/
|
|
|
|
|
|
|
|
struct tr_cached_file
|
|
|
|
{
|
2017-04-19 12:04:45 +00:00
|
|
|
bool is_writable;
|
|
|
|
tr_sys_file_t fd;
|
|
|
|
int torrent_id;
|
|
|
|
tr_file_index_t file_index;
|
|
|
|
time_t used_at;
|
2010-12-28 07:24:10 +00:00
|
|
|
};
|
|
|
|
|
2017-04-20 16:02:19 +00:00
|
|
|
static inline bool cached_file_is_open(struct tr_cached_file const* o)
|
2010-12-28 07:24:10 +00:00
|
|
|
{
|
2017-06-08 07:24:12 +00:00
|
|
|
TR_ASSERT(o != NULL);
|
2010-12-28 07:24:10 +00:00
|
|
|
|
2017-04-19 12:04:45 +00:00
|
|
|
return o->fd != TR_BAD_SYS_FILE;
|
2010-12-28 07:24:10 +00:00
|
|
|
}
|
|
|
|
|
2017-04-19 12:04:45 +00:00
|
|
|
static void cached_file_close(struct tr_cached_file* o)
|
2010-12-28 07:24:10 +00:00
|
|
|
{
|
2017-06-08 07:24:12 +00:00
|
|
|
TR_ASSERT(cached_file_is_open(o));
|
2010-12-28 07:24:10 +00:00
|
|
|
|
2017-04-19 12:04:45 +00:00
|
|
|
tr_sys_file_close(o->fd, NULL);
|
|
|
|
o->fd = TR_BAD_SYS_FILE;
|
2010-12-28 07:24:10 +00:00
|
|
|
}
|
|
|
|
|
2008-10-03 04:49:06 +00:00
|
|
|
/**
|
|
|
|
* returns 0 on success, or an errno value on failure.
|
|
|
|
* errno values include ENOENT if the parent folder doesn't exist,
|
2014-09-21 17:55:39 +00:00
|
|
|
* plus the errno values set by tr_sys_dir_create () and tr_sys_file_open ().
|
2008-10-03 04:49:06 +00:00
|
|
|
*/
|
2017-04-20 16:02:19 +00:00
|
|
|
static int cached_file_open(struct tr_cached_file* o, char const* filename, bool writable, tr_preallocation_mode allocation,
|
2017-04-19 12:04:45 +00:00
|
|
|
uint64_t file_size)
|
2007-10-30 18:35:06 +00:00
|
|
|
{
|
2017-04-19 12:04:45 +00:00
|
|
|
int flags;
|
|
|
|
tr_sys_path_info info;
|
|
|
|
bool already_existed;
|
|
|
|
bool resize_needed;
|
|
|
|
tr_sys_file_t fd = TR_BAD_SYS_FILE;
|
|
|
|
tr_error* error = NULL;
|
|
|
|
|
|
|
|
/* create subfolders, if any */
|
|
|
|
if (writable)
|
2008-09-23 19:11:04 +00:00
|
|
|
{
|
2017-04-19 12:04:45 +00:00
|
|
|
char* dir = tr_sys_path_dirname(filename, &error);
|
|
|
|
|
|
|
|
if (dir == NULL)
|
2016-03-13 10:41:52 +00:00
|
|
|
{
|
2017-04-19 12:04:45 +00:00
|
|
|
tr_logAddError(_("Couldn't get directory for \"%1$s\": %2$s"), filename, error->message);
|
|
|
|
goto fail;
|
2016-03-13 10:41:52 +00:00
|
|
|
}
|
2017-04-19 12:04:45 +00:00
|
|
|
|
|
|
|
if (!tr_sys_dir_create(dir, TR_SYS_DIR_CREATE_PARENTS, 0777, &error))
|
2012-12-18 03:03:23 +00:00
|
|
|
{
|
2017-04-19 12:04:45 +00:00
|
|
|
tr_logAddError(_("Couldn't create \"%1$s\": %2$s"), dir, error->message);
|
|
|
|
tr_free(dir);
|
|
|
|
goto fail;
|
2008-10-14 04:51:42 +00:00
|
|
|
}
|
2017-04-19 12:04:45 +00:00
|
|
|
|
|
|
|
tr_free(dir);
|
2007-10-30 18:35:06 +00:00
|
|
|
}
|
2006-07-16 19:39:23 +00:00
|
|
|
|
2017-04-19 12:04:45 +00:00
|
|
|
already_existed = tr_sys_path_get_info(filename, 0, &info, NULL) && info.type == TR_SYS_PATH_IS_FILE;
|
2008-11-15 19:59:18 +00:00
|
|
|
|
2017-04-19 12:04:45 +00:00
|
|
|
/* we can't resize the file w/o write permissions */
|
|
|
|
resize_needed = already_existed && (file_size < info.size);
|
|
|
|
writable |= resize_needed;
|
2013-07-27 16:18:12 +00:00
|
|
|
|
2017-04-19 12:04:45 +00:00
|
|
|
/* open the file */
|
|
|
|
flags = writable ? (TR_SYS_FILE_WRITE | TR_SYS_FILE_CREATE) : 0;
|
|
|
|
flags |= TR_SYS_FILE_READ | TR_SYS_FILE_SEQUENTIAL;
|
|
|
|
fd = tr_sys_file_open(filename, flags, 0666, &error);
|
2010-12-28 07:24:10 +00:00
|
|
|
|
2017-04-19 12:04:45 +00:00
|
|
|
if (fd == TR_BAD_SYS_FILE)
|
2008-09-23 19:11:04 +00:00
|
|
|
{
|
2017-04-19 12:04:45 +00:00
|
|
|
tr_logAddError(_("Couldn't open \"%1$s\": %2$s"), filename, error->message);
|
|
|
|
goto fail;
|
2014-12-10 18:23:11 +00:00
|
|
|
}
|
|
|
|
|
2017-04-19 12:04:45 +00:00
|
|
|
if (writable && !already_existed && allocation != TR_PREALLOCATE_NONE)
|
2014-12-10 18:23:11 +00:00
|
|
|
{
|
2017-04-19 12:04:45 +00:00
|
|
|
bool success = false;
|
2017-04-20 16:02:19 +00:00
|
|
|
char const* type = NULL;
|
2014-12-10 18:23:11 +00:00
|
|
|
|
2017-04-19 12:04:45 +00:00
|
|
|
if (allocation == TR_PREALLOCATE_FULL)
|
2014-12-10 18:23:11 +00:00
|
|
|
{
|
2017-04-19 12:04:45 +00:00
|
|
|
success = preallocate_file_full(fd, file_size, &error);
|
|
|
|
type = _("full");
|
2014-12-10 18:23:11 +00:00
|
|
|
}
|
2017-04-19 12:04:45 +00:00
|
|
|
else if (allocation == TR_PREALLOCATE_SPARSE)
|
2014-12-10 18:23:11 +00:00
|
|
|
{
|
2017-04-19 12:04:45 +00:00
|
|
|
success = preallocate_file_sparse(fd, file_size, &error);
|
|
|
|
type = _("sparse");
|
2014-12-10 18:23:11 +00:00
|
|
|
}
|
|
|
|
|
2017-06-08 07:24:12 +00:00
|
|
|
TR_ASSERT(type != NULL);
|
2014-12-10 18:23:11 +00:00
|
|
|
|
2017-04-19 12:04:45 +00:00
|
|
|
if (!success)
|
2014-12-10 18:23:11 +00:00
|
|
|
{
|
2017-04-19 12:04:45 +00:00
|
|
|
tr_logAddError(_("Couldn't preallocate file \"%1$s\" (%2$s, size: %3$" PRIu64 "): %4$s"),
|
|
|
|
filename, type, file_size, error->message);
|
|
|
|
goto fail;
|
2014-12-10 18:23:11 +00:00
|
|
|
}
|
|
|
|
|
2017-04-19 12:04:45 +00:00
|
|
|
tr_logAddDebug(_("Preallocated file \"%1$s\" (%2$s, size: %3$" PRIu64 ")"), filename, type, file_size);
|
2006-07-16 19:39:23 +00:00
|
|
|
}
|
|
|
|
|
2017-04-19 12:04:45 +00:00
|
|
|
/* If the file already exists and it's too large, truncate it.
|
|
|
|
* This is a fringe case that happens if a torrent's been updated
|
|
|
|
* and one of the updated torrent's files is smaller.
|
|
|
|
* https://trac.transmissionbt.com/ticket/2228
|
|
|
|
* https://bugs.launchpad.net/ubuntu/+source/transmission/+bug/318249
|
|
|
|
*/
|
|
|
|
if (resize_needed && !tr_sys_file_truncate(fd, file_size, &error))
|
2011-02-09 05:34:23 +00:00
|
|
|
{
|
2017-04-19 12:04:45 +00:00
|
|
|
tr_logAddError(_("Couldn't truncate \"%1$s\": %2$s"), filename, error->message);
|
|
|
|
goto fail;
|
2011-02-09 05:34:23 +00:00
|
|
|
}
|
2009-06-21 07:53:51 +00:00
|
|
|
|
2017-04-19 12:04:45 +00:00
|
|
|
o->fd = fd;
|
|
|
|
return 0;
|
2014-12-10 18:23:11 +00:00
|
|
|
|
|
|
|
fail:
|
2017-04-19 12:04:45 +00:00
|
|
|
{
|
2017-04-20 16:02:19 +00:00
|
|
|
int const err = error->code;
|
2017-04-19 12:04:45 +00:00
|
|
|
tr_error_free(error);
|
2014-12-10 18:23:11 +00:00
|
|
|
|
2017-04-19 12:04:45 +00:00
|
|
|
if (fd != TR_BAD_SYS_FILE)
|
|
|
|
{
|
|
|
|
tr_sys_file_close(fd, NULL);
|
|
|
|
}
|
2014-12-10 18:23:11 +00:00
|
|
|
|
2017-04-19 12:04:45 +00:00
|
|
|
return err;
|
|
|
|
}
|
2007-10-30 18:35:06 +00:00
|
|
|
}
|
|
|
|
|
2010-12-28 07:24:10 +00:00
|
|
|
/***
|
|
|
|
****
|
|
|
|
***/
|
|
|
|
|
|
|
|
struct tr_fileset
|
2007-10-30 18:35:06 +00:00
|
|
|
{
|
2017-04-19 12:04:45 +00:00
|
|
|
struct tr_cached_file* begin;
|
2017-04-20 16:02:19 +00:00
|
|
|
struct tr_cached_file const* end;
|
2010-12-28 07:24:10 +00:00
|
|
|
};
|
2006-07-16 19:39:23 +00:00
|
|
|
|
2017-04-19 12:04:45 +00:00
|
|
|
static void fileset_construct(struct tr_fileset* set, int n)
|
2007-10-30 18:35:06 +00:00
|
|
|
{
|
2017-06-24 10:17:04 +00:00
|
|
|
struct tr_cached_file const TR_CACHED_FILE_INIT =
|
|
|
|
{
|
|
|
|
.is_writable = false,
|
|
|
|
.fd = TR_BAD_SYS_FILE,
|
|
|
|
.torrent_id = 0,
|
|
|
|
.file_index = 0,
|
|
|
|
.used_at = 0
|
|
|
|
};
|
2006-07-16 19:39:23 +00:00
|
|
|
|
2017-04-19 12:04:45 +00:00
|
|
|
set->begin = tr_new(struct tr_cached_file, n);
|
|
|
|
set->end = set->begin + n;
|
2010-12-28 07:24:10 +00:00
|
|
|
|
2017-05-13 22:38:31 +00:00
|
|
|
for (struct tr_cached_file* o = set->begin; o != set->end; ++o)
|
2017-04-19 12:04:45 +00:00
|
|
|
{
|
|
|
|
*o = TR_CACHED_FILE_INIT;
|
|
|
|
}
|
2006-07-16 19:39:23 +00:00
|
|
|
}
|
|
|
|
|
2017-04-19 12:04:45 +00:00
|
|
|
static void fileset_close_all(struct tr_fileset* set)
|
2009-10-19 05:05:00 +00:00
|
|
|
{
|
2017-04-19 12:04:45 +00:00
|
|
|
if (set != NULL)
|
|
|
|
{
|
2017-05-13 22:38:31 +00:00
|
|
|
for (struct tr_cached_file* o = set->begin; o != set->end; ++o)
|
2017-04-19 12:04:45 +00:00
|
|
|
{
|
|
|
|
if (cached_file_is_open(o))
|
|
|
|
{
|
|
|
|
cached_file_close(o);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
2010-12-28 07:24:10 +00:00
|
|
|
}
|
2009-10-19 05:05:00 +00:00
|
|
|
|
2017-04-19 12:04:45 +00:00
|
|
|
static void fileset_destruct(struct tr_fileset* set)
|
2010-12-28 07:24:10 +00:00
|
|
|
{
|
2017-04-19 12:04:45 +00:00
|
|
|
fileset_close_all(set);
|
|
|
|
tr_free(set->begin);
|
|
|
|
set->end = set->begin = NULL;
|
2010-12-28 07:24:10 +00:00
|
|
|
}
|
2009-10-23 03:41:36 +00:00
|
|
|
|
2017-04-19 12:04:45 +00:00
|
|
|
static void fileset_close_torrent(struct tr_fileset* set, int torrent_id)
|
2010-12-28 07:24:10 +00:00
|
|
|
{
|
2017-04-19 12:04:45 +00:00
|
|
|
if (set != NULL)
|
|
|
|
{
|
2017-05-13 22:38:31 +00:00
|
|
|
for (struct tr_cached_file* o = set->begin; o != set->end; ++o)
|
2017-04-19 12:04:45 +00:00
|
|
|
{
|
2017-04-30 16:25:26 +00:00
|
|
|
if (o->torrent_id == torrent_id && cached_file_is_open(o))
|
2017-04-19 12:04:45 +00:00
|
|
|
{
|
|
|
|
cached_file_close(o);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
2010-12-28 07:24:10 +00:00
|
|
|
}
|
2009-10-19 05:05:00 +00:00
|
|
|
|
2017-04-19 12:04:45 +00:00
|
|
|
static struct tr_cached_file* fileset_lookup(struct tr_fileset* set, int torrent_id, tr_file_index_t i)
|
2010-12-28 07:24:10 +00:00
|
|
|
{
|
2017-04-19 12:04:45 +00:00
|
|
|
if (set != NULL)
|
|
|
|
{
|
2017-05-13 22:38:31 +00:00
|
|
|
for (struct tr_cached_file* o = set->begin; o != set->end; ++o)
|
2017-04-19 12:04:45 +00:00
|
|
|
{
|
2017-04-30 16:25:26 +00:00
|
|
|
if (torrent_id == o->torrent_id && i == o->file_index && cached_file_is_open(o))
|
2017-04-19 12:04:45 +00:00
|
|
|
{
|
|
|
|
return o;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
2009-10-19 05:05:00 +00:00
|
|
|
|
2017-04-19 12:04:45 +00:00
|
|
|
return NULL;
|
2009-10-19 05:05:00 +00:00
|
|
|
}
|
|
|
|
|
2017-04-19 12:04:45 +00:00
|
|
|
static struct tr_cached_file* fileset_get_empty_slot(struct tr_fileset* set)
|
2006-07-16 19:39:23 +00:00
|
|
|
{
|
2017-04-19 12:04:45 +00:00
|
|
|
struct tr_cached_file* cull = NULL;
|
2011-12-14 05:42:15 +00:00
|
|
|
|
2017-04-19 12:04:45 +00:00
|
|
|
if (set->begin != NULL)
|
2011-12-14 05:42:15 +00:00
|
|
|
{
|
2017-04-19 12:04:45 +00:00
|
|
|
/* try to find an unused slot */
|
2017-05-13 22:38:31 +00:00
|
|
|
for (struct tr_cached_file* o = set->begin; o != set->end; ++o)
|
2017-04-19 12:04:45 +00:00
|
|
|
{
|
|
|
|
if (!cached_file_is_open(o))
|
|
|
|
{
|
|
|
|
return o;
|
|
|
|
}
|
|
|
|
}
|
2011-12-14 05:42:15 +00:00
|
|
|
|
2017-04-19 12:04:45 +00:00
|
|
|
/* all slots are full... recycle the least recently used */
|
2017-05-13 22:38:31 +00:00
|
|
|
for (struct tr_cached_file* o = set->begin; o != set->end; ++o)
|
2017-04-19 12:04:45 +00:00
|
|
|
{
|
2017-04-30 16:25:26 +00:00
|
|
|
if (cull == NULL || o->used_at < cull->used_at)
|
2017-04-19 12:04:45 +00:00
|
|
|
{
|
|
|
|
cull = o;
|
|
|
|
}
|
|
|
|
}
|
2011-12-14 05:42:15 +00:00
|
|
|
|
2017-04-19 12:04:45 +00:00
|
|
|
cached_file_close(cull);
|
2011-12-14 05:42:15 +00:00
|
|
|
}
|
2010-12-28 07:24:10 +00:00
|
|
|
|
2017-04-19 12:04:45 +00:00
|
|
|
return cull;
|
2010-12-28 07:24:10 +00:00
|
|
|
}
|
2009-10-23 03:41:36 +00:00
|
|
|
|
2010-12-28 07:24:10 +00:00
|
|
|
/***
|
|
|
|
****
|
2011-06-24 22:39:20 +00:00
|
|
|
**** Startup / Shutdown
|
|
|
|
****
|
2010-12-28 07:24:10 +00:00
|
|
|
***/
|
2006-07-16 19:39:23 +00:00
|
|
|
|
2010-12-28 07:24:10 +00:00
|
|
|
struct tr_fdInfo
|
|
|
|
{
|
2017-04-19 12:04:45 +00:00
|
|
|
int peerCount;
|
|
|
|
struct tr_fileset fileset;
|
2010-12-28 07:24:10 +00:00
|
|
|
};
|
2007-10-30 18:35:06 +00:00
|
|
|
|
2017-04-19 12:04:45 +00:00
|
|
|
static void ensureSessionFdInfoExists(tr_session* session)
|
2011-06-24 22:39:20 +00:00
|
|
|
{
|
2017-06-08 07:24:12 +00:00
|
|
|
TR_ASSERT(tr_isSession(session));
|
2011-06-24 22:39:20 +00:00
|
|
|
|
2017-04-19 12:04:45 +00:00
|
|
|
if (session->fdInfo == NULL)
|
2011-06-24 22:39:20 +00:00
|
|
|
{
|
2017-04-19 12:04:45 +00:00
|
|
|
struct tr_fdInfo* i;
|
2017-04-20 16:02:19 +00:00
|
|
|
int const FILE_CACHE_SIZE = 32;
|
2011-07-11 20:48:06 +00:00
|
|
|
|
2017-04-19 12:04:45 +00:00
|
|
|
/* Create the local file cache */
|
|
|
|
i = tr_new0(struct tr_fdInfo, 1);
|
|
|
|
fileset_construct(&i->fileset, FILE_CACHE_SIZE);
|
|
|
|
session->fdInfo = i;
|
2011-07-11 20:48:06 +00:00
|
|
|
|
2014-12-13 15:22:39 +00:00
|
|
|
#ifndef _WIN32
|
2017-04-19 12:04:45 +00:00
|
|
|
|
|
|
|
/* set the open-file limit to the largest safe size wrt FD_SETSIZE */
|
|
|
|
struct rlimit limit;
|
|
|
|
|
2019-07-14 12:40:41 +00:00
|
|
|
if (getrlimit(RLIMIT_NOFILE, &limit) == 0)
|
2011-07-11 20:48:06 +00:00
|
|
|
{
|
2017-04-20 16:02:19 +00:00
|
|
|
int const old_limit = (int)limit.rlim_cur;
|
|
|
|
int const new_limit = MIN(limit.rlim_max, FD_SETSIZE);
|
2017-04-19 12:04:45 +00:00
|
|
|
|
|
|
|
if (new_limit != old_limit)
|
2011-07-11 20:48:06 +00:00
|
|
|
{
|
2017-04-19 12:04:45 +00:00
|
|
|
limit.rlim_cur = new_limit;
|
|
|
|
setrlimit(RLIMIT_NOFILE, &limit);
|
|
|
|
getrlimit(RLIMIT_NOFILE, &limit);
|
|
|
|
tr_logAddInfo("Changed open file limit from %d to %d", old_limit, (int)limit.rlim_cur);
|
2011-07-11 20:48:06 +00:00
|
|
|
}
|
|
|
|
}
|
2017-04-19 12:04:45 +00:00
|
|
|
|
2014-12-13 15:22:39 +00:00
|
|
|
#endif
|
2011-06-24 22:39:20 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2017-04-19 12:04:45 +00:00
|
|
|
void tr_fdClose(tr_session* session)
|
2011-06-24 22:39:20 +00:00
|
|
|
{
|
2017-04-30 16:25:26 +00:00
|
|
|
if (session != NULL && session->fdInfo != NULL)
|
2011-06-24 22:39:20 +00:00
|
|
|
{
|
2017-04-19 12:04:45 +00:00
|
|
|
struct tr_fdInfo* i = session->fdInfo;
|
|
|
|
fileset_destruct(&i->fileset);
|
|
|
|
tr_free(i);
|
|
|
|
session->fdInfo = NULL;
|
2011-06-24 22:39:20 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/***
|
|
|
|
****
|
|
|
|
***/
|
|
|
|
|
2017-04-19 12:04:45 +00:00
|
|
|
static struct tr_fileset* get_fileset(tr_session* session)
|
2010-12-28 07:24:10 +00:00
|
|
|
{
|
2017-04-30 16:25:26 +00:00
|
|
|
if (session == NULL)
|
2017-04-19 12:04:45 +00:00
|
|
|
{
|
|
|
|
return NULL;
|
|
|
|
}
|
2011-06-24 22:39:20 +00:00
|
|
|
|
2017-04-19 12:04:45 +00:00
|
|
|
ensureSessionFdInfoExists(session);
|
|
|
|
return &session->fdInfo->fileset;
|
2010-12-28 07:24:10 +00:00
|
|
|
}
|
2007-10-30 18:35:06 +00:00
|
|
|
|
2017-04-20 16:02:19 +00:00
|
|
|
void tr_fdFileClose(tr_session* s, tr_torrent const* tor, tr_file_index_t i)
|
2010-12-28 07:24:10 +00:00
|
|
|
{
|
2017-04-19 12:04:45 +00:00
|
|
|
struct tr_cached_file* o;
|
2007-10-30 18:35:06 +00:00
|
|
|
|
2017-04-30 16:25:26 +00:00
|
|
|
if ((o = fileset_lookup(get_fileset(s), tr_torrentId(tor), i)) != NULL)
|
2011-02-02 23:29:17 +00:00
|
|
|
{
|
2017-04-19 12:04:45 +00:00
|
|
|
/* flush writable files so that their mtimes will be
|
|
|
|
* up-to-date when this function returns to the caller... */
|
|
|
|
if (o->is_writable)
|
|
|
|
{
|
|
|
|
tr_sys_file_flush(o->fd, NULL);
|
|
|
|
}
|
2011-02-02 23:29:17 +00:00
|
|
|
|
2017-04-19 12:04:45 +00:00
|
|
|
cached_file_close(o);
|
2011-02-02 23:29:17 +00:00
|
|
|
}
|
2010-12-28 07:24:10 +00:00
|
|
|
}
|
2006-07-16 19:39:23 +00:00
|
|
|
|
2017-04-19 12:04:45 +00:00
|
|
|
tr_sys_file_t tr_fdFileGetCached(tr_session* s, int torrent_id, tr_file_index_t i, bool writable)
|
2010-12-28 07:24:10 +00:00
|
|
|
{
|
2017-04-19 12:04:45 +00:00
|
|
|
struct tr_cached_file* o = fileset_lookup(get_fileset(s), torrent_id, i);
|
2006-07-16 19:39:23 +00:00
|
|
|
|
2017-04-30 16:25:26 +00:00
|
|
|
if (o == NULL || (writable && !o->is_writable))
|
2017-04-19 12:04:45 +00:00
|
|
|
{
|
|
|
|
return TR_BAD_SYS_FILE;
|
|
|
|
}
|
2007-10-30 18:35:06 +00:00
|
|
|
|
2017-04-19 12:04:45 +00:00
|
|
|
o->used_at = tr_time();
|
|
|
|
return o->fd;
|
2010-12-28 07:24:10 +00:00
|
|
|
}
|
2007-10-30 18:35:06 +00:00
|
|
|
|
2017-04-19 12:04:45 +00:00
|
|
|
bool tr_fdFileGetCachedMTime(tr_session* s, int torrent_id, tr_file_index_t i, time_t* mtime)
|
2011-04-28 18:40:46 +00:00
|
|
|
{
|
2017-04-19 12:04:45 +00:00
|
|
|
bool success;
|
|
|
|
tr_sys_path_info info;
|
|
|
|
struct tr_cached_file* o = fileset_lookup(get_fileset(s), torrent_id, i);
|
2011-04-28 18:40:46 +00:00
|
|
|
|
2017-04-30 16:25:26 +00:00
|
|
|
if ((success = o != NULL && tr_sys_file_get_info(o->fd, &info, NULL)))
|
2017-04-19 12:04:45 +00:00
|
|
|
{
|
|
|
|
*mtime = info.last_modified_at;
|
|
|
|
}
|
2011-04-28 18:40:46 +00:00
|
|
|
|
2017-04-19 12:04:45 +00:00
|
|
|
return success;
|
2011-04-28 18:40:46 +00:00
|
|
|
}
|
|
|
|
|
2017-04-19 12:04:45 +00:00
|
|
|
void tr_fdTorrentClose(tr_session* session, int torrent_id)
|
2010-12-28 07:24:10 +00:00
|
|
|
{
|
2017-06-08 07:24:12 +00:00
|
|
|
TR_ASSERT(tr_sessionIsLocked(session));
|
2013-01-31 05:05:44 +00:00
|
|
|
|
2017-04-19 12:04:45 +00:00
|
|
|
fileset_close_torrent(get_fileset(session), torrent_id);
|
2010-12-28 07:24:10 +00:00
|
|
|
}
|
2006-07-16 19:39:23 +00:00
|
|
|
|
2014-07-28 04:13:38 +00:00
|
|
|
/* returns an fd on success, or a TR_BAD_SYS_FILE on failure and sets errno */
|
2017-04-20 16:02:19 +00:00
|
|
|
tr_sys_file_t tr_fdFileCheckout(tr_session* session, int torrent_id, tr_file_index_t i, char const* filename, bool writable,
|
2017-04-19 12:04:45 +00:00
|
|
|
tr_preallocation_mode allocation, uint64_t file_size)
|
2010-12-28 07:24:10 +00:00
|
|
|
{
|
2017-04-19 12:04:45 +00:00
|
|
|
struct tr_fileset* set = get_fileset(session);
|
|
|
|
struct tr_cached_file* o = fileset_lookup(set, torrent_id, i);
|
2009-08-10 20:04:08 +00:00
|
|
|
|
2017-04-30 16:25:26 +00:00
|
|
|
if (o != NULL && writable && !o->is_writable)
|
2017-04-19 12:04:45 +00:00
|
|
|
{
|
|
|
|
cached_file_close(o); /* close it so we can reopen in rw mode */
|
|
|
|
}
|
2017-04-30 16:25:26 +00:00
|
|
|
else if (o == NULL)
|
2017-04-19 12:04:45 +00:00
|
|
|
{
|
|
|
|
o = fileset_get_empty_slot(set);
|
|
|
|
}
|
2006-07-16 19:39:23 +00:00
|
|
|
|
2017-04-19 12:04:45 +00:00
|
|
|
if (!cached_file_is_open(o))
|
2007-01-14 12:00:21 +00:00
|
|
|
{
|
2017-04-20 16:02:19 +00:00
|
|
|
int const err = cached_file_open(o, filename, writable, allocation, file_size);
|
2017-04-19 12:04:45 +00:00
|
|
|
|
2017-04-30 16:25:26 +00:00
|
|
|
if (err != 0)
|
2012-12-18 03:03:23 +00:00
|
|
|
{
|
2017-04-19 12:04:45 +00:00
|
|
|
errno = err;
|
|
|
|
return TR_BAD_SYS_FILE;
|
2007-10-30 18:35:06 +00:00
|
|
|
}
|
|
|
|
|
2017-04-19 12:04:45 +00:00
|
|
|
dbgmsg("opened '%s' writable %c", filename, writable ? 'y' : 'n');
|
|
|
|
o->is_writable = writable;
|
2007-01-14 12:00:21 +00:00
|
|
|
}
|
2006-07-16 19:39:23 +00:00
|
|
|
|
2017-04-19 12:04:45 +00:00
|
|
|
dbgmsg("checking out '%s'", filename);
|
|
|
|
o->torrent_id = torrent_id;
|
|
|
|
o->file_index = i;
|
|
|
|
o->used_at = tr_time();
|
|
|
|
return o->fd;
|
2006-07-16 19:39:23 +00:00
|
|
|
}
|
|
|
|
|
2007-10-30 18:35:06 +00:00
|
|
|
/***
|
|
|
|
****
|
|
|
|
**** Sockets
|
|
|
|
****
|
|
|
|
***/
|
2007-01-21 19:42:11 +00:00
|
|
|
|
2017-04-19 12:04:45 +00:00
|
|
|
tr_socket_t tr_fdSocketCreate(tr_session* session, int domain, int type)
|
2007-01-21 19:42:11 +00:00
|
|
|
{
|
2017-06-13 02:24:09 +00:00
|
|
|
TR_ASSERT(tr_isSession(session));
|
|
|
|
|
2017-04-19 12:04:45 +00:00
|
|
|
tr_socket_t s = TR_BAD_SOCKET;
|
|
|
|
struct tr_fdInfo* gFd;
|
2008-09-23 19:11:04 +00:00
|
|
|
|
2017-04-19 12:04:45 +00:00
|
|
|
ensureSessionFdInfoExists(session);
|
|
|
|
gFd = session->fdInfo;
|
2009-10-23 03:41:36 +00:00
|
|
|
|
2017-04-19 12:04:45 +00:00
|
|
|
if (gFd->peerCount < session->peerLimit)
|
|
|
|
{
|
|
|
|
if ((s = socket(domain, type, 0)) == TR_BAD_SOCKET)
|
2015-07-01 00:54:41 +00:00
|
|
|
{
|
2017-04-19 12:04:45 +00:00
|
|
|
if (sockerrno != EAFNOSUPPORT)
|
|
|
|
{
|
|
|
|
char err_buf[512];
|
|
|
|
tr_logAddError(_("Couldn't create socket: %s"), tr_net_strerror(err_buf, sizeof(err_buf), sockerrno));
|
|
|
|
}
|
2015-07-01 00:54:41 +00:00
|
|
|
}
|
2017-04-19 12:04:45 +00:00
|
|
|
}
|
2007-07-21 17:35:47 +00:00
|
|
|
|
2017-04-19 12:04:45 +00:00
|
|
|
if (s != TR_BAD_SOCKET)
|
|
|
|
{
|
|
|
|
++gFd->peerCount;
|
|
|
|
}
|
2007-10-31 18:10:55 +00:00
|
|
|
|
2017-06-08 07:24:12 +00:00
|
|
|
TR_ASSERT(gFd->peerCount >= 0);
|
2009-11-29 08:53:14 +00:00
|
|
|
|
2017-04-19 12:04:45 +00:00
|
|
|
if (s != TR_BAD_SOCKET)
|
2009-11-29 08:53:14 +00:00
|
|
|
{
|
2017-04-19 12:04:45 +00:00
|
|
|
static bool buf_logged = false;
|
|
|
|
|
|
|
|
if (!buf_logged)
|
2009-11-29 08:53:14 +00:00
|
|
|
{
|
2017-05-20 20:31:56 +00:00
|
|
|
int i = 0;
|
|
|
|
socklen_t size = sizeof(i);
|
|
|
|
|
|
|
|
if (getsockopt(s, SOL_SOCKET, SO_SNDBUF, (void*)&i, &size) != -1)
|
|
|
|
{
|
|
|
|
tr_logAddDebug("SO_SNDBUF size is %d", i);
|
|
|
|
}
|
|
|
|
|
|
|
|
i = 0;
|
|
|
|
size = sizeof(i);
|
|
|
|
|
|
|
|
if (getsockopt(s, SOL_SOCKET, SO_RCVBUF, (void*)&i, &size) != -1)
|
|
|
|
{
|
|
|
|
tr_logAddDebug("SO_RCVBUF size is %d", i);
|
|
|
|
}
|
|
|
|
|
2017-04-19 12:04:45 +00:00
|
|
|
buf_logged = true;
|
2009-11-29 08:53:14 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2017-04-19 12:04:45 +00:00
|
|
|
return s;
|
2006-07-16 19:39:23 +00:00
|
|
|
}
|
|
|
|
|
2017-04-19 12:04:45 +00:00
|
|
|
tr_socket_t tr_fdSocketAccept(tr_session* s, tr_socket_t sockfd, tr_address* addr, tr_port* port)
|
2006-07-16 19:39:23 +00:00
|
|
|
{
|
2017-06-13 02:24:09 +00:00
|
|
|
TR_ASSERT(tr_isSession(s));
|
|
|
|
TR_ASSERT(addr != NULL);
|
|
|
|
TR_ASSERT(port != NULL);
|
|
|
|
|
2017-04-19 12:04:45 +00:00
|
|
|
tr_socket_t fd;
|
|
|
|
socklen_t len;
|
|
|
|
struct tr_fdInfo* gFd;
|
|
|
|
struct sockaddr_storage sock;
|
2006-07-16 19:39:23 +00:00
|
|
|
|
2017-04-19 12:04:45 +00:00
|
|
|
ensureSessionFdInfoExists(s);
|
|
|
|
gFd = s->fdInfo;
|
2009-10-23 03:41:36 +00:00
|
|
|
|
2017-04-19 12:04:45 +00:00
|
|
|
len = sizeof(struct sockaddr_storage);
|
|
|
|
fd = accept(sockfd, (struct sockaddr*)&sock, &len);
|
2009-06-06 16:19:34 +00:00
|
|
|
|
2017-04-19 12:04:45 +00:00
|
|
|
if (fd != TR_BAD_SOCKET)
|
2006-07-16 19:39:23 +00:00
|
|
|
{
|
2017-04-30 16:25:26 +00:00
|
|
|
if (gFd->peerCount < s->peerLimit && tr_address_from_sockaddr_storage(addr, port, &sock))
|
2011-03-05 16:29:19 +00:00
|
|
|
{
|
2017-04-19 12:04:45 +00:00
|
|
|
++gFd->peerCount;
|
2011-03-05 16:29:19 +00:00
|
|
|
}
|
2017-04-19 12:04:45 +00:00
|
|
|
else
|
2011-03-05 16:29:19 +00:00
|
|
|
{
|
2017-04-19 12:04:45 +00:00
|
|
|
tr_netCloseSocket(fd);
|
|
|
|
fd = TR_BAD_SOCKET;
|
2011-03-05 16:29:19 +00:00
|
|
|
}
|
2006-07-16 19:39:23 +00:00
|
|
|
}
|
2007-01-21 19:42:11 +00:00
|
|
|
|
2017-04-19 12:04:45 +00:00
|
|
|
return fd;
|
2007-01-21 19:42:11 +00:00
|
|
|
}
|
2006-07-16 19:39:23 +00:00
|
|
|
|
2017-04-19 12:04:45 +00:00
|
|
|
void tr_fdSocketClose(tr_session* session, tr_socket_t fd)
|
2007-10-30 18:35:06 +00:00
|
|
|
{
|
2017-06-08 07:24:12 +00:00
|
|
|
TR_ASSERT(tr_isSession(session));
|
2009-10-23 03:41:36 +00:00
|
|
|
|
2017-04-19 12:04:45 +00:00
|
|
|
if (session->fdInfo != NULL)
|
2008-09-23 19:11:04 +00:00
|
|
|
{
|
2017-04-19 12:04:45 +00:00
|
|
|
struct tr_fdInfo* gFd = session->fdInfo;
|
2007-10-31 18:10:55 +00:00
|
|
|
|
2017-04-19 12:04:45 +00:00
|
|
|
if (fd != TR_BAD_SOCKET)
|
2009-12-26 23:52:59 +00:00
|
|
|
{
|
2017-04-19 12:04:45 +00:00
|
|
|
tr_netCloseSocket(fd);
|
|
|
|
--gFd->peerCount;
|
2009-12-26 23:52:59 +00:00
|
|
|
}
|
|
|
|
|
2017-06-08 07:24:12 +00:00
|
|
|
TR_ASSERT(gFd->peerCount >= 0);
|
2009-12-26 23:52:59 +00:00
|
|
|
}
|
2006-07-16 19:39:23 +00:00
|
|
|
}
|