1954 lines
43 KiB
C
1954 lines
43 KiB
C
/*
|
|
* This file Copyright (C) Mnemosyne LLC
|
|
*
|
|
* This file is licensed by the GPL version 2. Works owned by the
|
|
* Transmission project are granted a special exemption to clause 2 (b)
|
|
* so that the bulk of its code can remain under the MIT license.
|
|
* This exemption does not extend to derived works not owned by
|
|
* the Transmission project.
|
|
*
|
|
* $Id$
|
|
*/
|
|
|
|
#ifdef HAVE_MEMMEM
|
|
#define _GNU_SOURCE /* glibc's string.h needs this to pick up memmem */
|
|
#endif
|
|
|
|
#if defined (SYS_DARWIN)
|
|
#define HAVE_GETPAGESIZE
|
|
#define HAVE_ICONV_OPEN
|
|
#define HAVE_MKDTEMP
|
|
#define HAVE_VALLOC
|
|
#endif
|
|
|
|
#include <assert.h>
|
|
#include <ctype.h> /* isdigit (), isalpha (), tolower () */
|
|
#include <errno.h>
|
|
#include <float.h> /* DBL_EPSILON */
|
|
#include <locale.h> /* localeconv () */
|
|
#include <math.h> /* pow (), fabs (), floor () */
|
|
#include <stdarg.h>
|
|
#include <stdio.h>
|
|
#include <stdlib.h>
|
|
#include <string.h> /* strerror (), memset (), memmem () */
|
|
#include <time.h> /* nanosleep () */
|
|
|
|
#ifdef HAVE_ICONV_OPEN
|
|
#include <iconv.h>
|
|
#endif
|
|
#include <libgen.h> /* basename () */
|
|
#include <sys/time.h>
|
|
#include <sys/types.h>
|
|
#include <sys/stat.h>
|
|
#include <unistd.h> /* stat (), getcwd (), getpagesize (), unlink () */
|
|
|
|
#include <event2/buffer.h>
|
|
#include <event2/event.h>
|
|
|
|
#ifdef WIN32
|
|
#include <w32api.h>
|
|
#define WINVER WindowsXP /* freeaddrinfo (), getaddrinfo (), getnameinfo () */
|
|
#include <direct.h> /* _getcwd () */
|
|
#include <windows.h> /* Sleep () */
|
|
#endif
|
|
|
|
#include "transmission.h"
|
|
#include "fdlimit.h"
|
|
#include "ConvertUTF.h"
|
|
#include "list.h"
|
|
#include "utils.h"
|
|
#include "platform.h" /* tr_lockLock (), TR_PATH_MAX */
|
|
#include "variant.h"
|
|
#include "version.h"
|
|
|
|
|
|
time_t __tr_current_time = 0;
|
|
tr_msg_level __tr_message_level = TR_MSG_ERR;
|
|
|
|
static bool messageQueuing = false;
|
|
static tr_msg_list * messageQueue = NULL;
|
|
static tr_msg_list ** messageQueueTail = &messageQueue;
|
|
static int messageQueueCount = 0;
|
|
|
|
#ifndef WIN32
|
|
/* make null versions of these win32 functions */
|
|
static inline int IsDebuggerPresent (void) { return false; }
|
|
static inline void OutputDebugString (const void * unused UNUSED) { }
|
|
#endif
|
|
|
|
/***
|
|
****
|
|
***/
|
|
|
|
static tr_lock*
|
|
getMessageLock (void)
|
|
{
|
|
static tr_lock * l = NULL;
|
|
|
|
if (!l)
|
|
l = tr_lockNew ();
|
|
|
|
return l;
|
|
}
|
|
|
|
void*
|
|
tr_getLog (void)
|
|
{
|
|
static bool initialized = false;
|
|
static FILE * file = NULL;
|
|
|
|
if (!initialized)
|
|
{
|
|
const char * str = getenv ("TR_DEBUG_FD");
|
|
int fd = 0;
|
|
if (str && *str)
|
|
fd = atoi (str);
|
|
switch (fd)
|
|
{
|
|
case 1:
|
|
file = stdout; break;
|
|
|
|
case 2:
|
|
file = stderr; break;
|
|
|
|
default:
|
|
file = NULL; break;
|
|
}
|
|
initialized = true;
|
|
}
|
|
|
|
return file;
|
|
}
|
|
|
|
void
|
|
tr_setMessageLevel (tr_msg_level level)
|
|
{
|
|
__tr_message_level = level;
|
|
}
|
|
|
|
void
|
|
tr_setMessageQueuing (bool enabled)
|
|
{
|
|
messageQueuing = enabled;
|
|
}
|
|
|
|
bool
|
|
tr_getMessageQueuing (void)
|
|
{
|
|
return messageQueuing != 0;
|
|
}
|
|
|
|
tr_msg_list *
|
|
tr_getQueuedMessages (void)
|
|
{
|
|
tr_msg_list * ret;
|
|
tr_lockLock (getMessageLock ());
|
|
|
|
ret = messageQueue;
|
|
messageQueue = NULL;
|
|
messageQueueTail = &messageQueue;
|
|
|
|
messageQueueCount = 0;
|
|
|
|
tr_lockUnlock (getMessageLock ());
|
|
return ret;
|
|
}
|
|
|
|
void
|
|
tr_freeMessageList (tr_msg_list * list)
|
|
{
|
|
tr_msg_list * next;
|
|
|
|
while (NULL != list)
|
|
{
|
|
next = list->next;
|
|
free (list->message);
|
|
free (list->name);
|
|
free (list);
|
|
list = next;
|
|
}
|
|
}
|
|
|
|
/**
|
|
***
|
|
**/
|
|
|
|
struct tm *
|
|
tr_localtime_r (const time_t *_clock, struct tm *_result)
|
|
{
|
|
#ifdef HAVE_LOCALTIME_R
|
|
return localtime_r (_clock, _result);
|
|
#else
|
|
struct tm *p = localtime (_clock);
|
|
if (p)
|
|
* (_result) = *p;
|
|
return p;
|
|
#endif
|
|
}
|
|
|
|
char*
|
|
tr_getLogTimeStr (char * buf, int buflen)
|
|
{
|
|
char tmp[64];
|
|
struct tm now_tm;
|
|
struct timeval tv;
|
|
time_t seconds;
|
|
int milliseconds;
|
|
|
|
gettimeofday (&tv, NULL);
|
|
|
|
seconds = tv.tv_sec;
|
|
tr_localtime_r (&seconds, &now_tm);
|
|
strftime (tmp, sizeof (tmp), "%H:%M:%S", &now_tm);
|
|
milliseconds = tv.tv_usec / 1000;
|
|
tr_snprintf (buf, buflen, "%s.%03d", tmp, milliseconds);
|
|
|
|
return buf;
|
|
}
|
|
|
|
bool
|
|
tr_deepLoggingIsActive (void)
|
|
{
|
|
static int8_t deepLoggingIsActive = -1;
|
|
|
|
if (deepLoggingIsActive < 0)
|
|
deepLoggingIsActive = IsDebuggerPresent () || (tr_getLog ()!=NULL);
|
|
|
|
return deepLoggingIsActive != 0;
|
|
}
|
|
|
|
void
|
|
tr_deepLog (const char * file,
|
|
int line,
|
|
const char * name,
|
|
const char * fmt,
|
|
...)
|
|
{
|
|
FILE * fp = tr_getLog ();
|
|
if (fp || IsDebuggerPresent ())
|
|
{
|
|
va_list args;
|
|
char timestr[64];
|
|
struct evbuffer * buf = evbuffer_new ();
|
|
char * base = tr_basename (file);
|
|
char * message;
|
|
|
|
evbuffer_add_printf (buf, "[%s] ",
|
|
tr_getLogTimeStr (timestr, sizeof (timestr)));
|
|
if (name)
|
|
evbuffer_add_printf (buf, "%s ", name);
|
|
va_start (args, fmt);
|
|
evbuffer_add_vprintf (buf, fmt, args);
|
|
va_end (args);
|
|
evbuffer_add_printf (buf, " (%s:%d)\n", base, line);
|
|
/* FIXME (libevent2) ifdef this out for nonwindows platforms */
|
|
message = evbuffer_free_to_str (buf);
|
|
OutputDebugString (message);
|
|
if (fp)
|
|
fputs (message, fp);
|
|
|
|
tr_free (message);
|
|
tr_free (base);
|
|
}
|
|
}
|
|
|
|
/***
|
|
****
|
|
***/
|
|
|
|
void
|
|
tr_msg (const char * file, int line,
|
|
tr_msg_level level,
|
|
const char * name,
|
|
const char * fmt, ...)
|
|
{
|
|
const int err = errno; /* message logging shouldn't affect errno */
|
|
char buf[1024];
|
|
va_list ap;
|
|
tr_lockLock (getMessageLock ());
|
|
|
|
/* build the text message */
|
|
*buf = '\0';
|
|
va_start (ap, fmt);
|
|
evutil_vsnprintf (buf, sizeof (buf), fmt, ap);
|
|
va_end (ap);
|
|
|
|
OutputDebugString (buf);
|
|
|
|
if (*buf)
|
|
{
|
|
if (messageQueuing)
|
|
{
|
|
tr_msg_list * newmsg;
|
|
newmsg = tr_new0 (tr_msg_list, 1);
|
|
newmsg->level = level;
|
|
newmsg->when = tr_time ();
|
|
newmsg->message = tr_strdup (buf);
|
|
newmsg->file = file;
|
|
newmsg->line = line;
|
|
newmsg->name = tr_strdup (name);
|
|
|
|
*messageQueueTail = newmsg;
|
|
messageQueueTail = &newmsg->next;
|
|
++messageQueueCount;
|
|
|
|
if (messageQueueCount > TR_MAX_MSG_LOG)
|
|
{
|
|
tr_msg_list * old = messageQueue;
|
|
messageQueue = old->next;
|
|
old->next = NULL;
|
|
tr_freeMessageList (old);
|
|
|
|
--messageQueueCount;
|
|
|
|
assert (messageQueueCount == TR_MAX_MSG_LOG);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
char timestr[64];
|
|
FILE * fp;
|
|
|
|
fp = tr_getLog ();
|
|
if (fp == NULL)
|
|
fp = stderr;
|
|
|
|
tr_getLogTimeStr (timestr, sizeof (timestr));
|
|
|
|
if (name)
|
|
fprintf (fp, "[%s] %s: %s\n", timestr, name, buf);
|
|
else
|
|
fprintf (fp, "[%s] %s\n", timestr, buf);
|
|
fflush (fp);
|
|
}
|
|
}
|
|
|
|
tr_lockUnlock (getMessageLock ());
|
|
errno = err;
|
|
}
|
|
|
|
/***
|
|
****
|
|
***/
|
|
|
|
void*
|
|
tr_malloc (size_t size)
|
|
{
|
|
return size ? malloc (size) : NULL;
|
|
}
|
|
|
|
void*
|
|
tr_malloc0 (size_t size)
|
|
{
|
|
return size ? calloc (1, size) : NULL;
|
|
}
|
|
|
|
void
|
|
tr_free (void * p)
|
|
{
|
|
if (p != NULL)
|
|
free (p);
|
|
}
|
|
|
|
void*
|
|
tr_memdup (const void * src, size_t byteCount)
|
|
{
|
|
return memcpy (tr_malloc (byteCount), src, byteCount);
|
|
}
|
|
|
|
/***
|
|
****
|
|
***/
|
|
|
|
const char*
|
|
tr_strip_positional_args (const char* str)
|
|
{
|
|
const char * in = str;
|
|
static size_t bufsize = 0;
|
|
static char * buf = NULL;
|
|
const size_t len = str ? strlen (str) : 0;
|
|
char * out;
|
|
|
|
if (!buf || (bufsize < len))
|
|
{
|
|
bufsize = len * 2 + 1;
|
|
buf = tr_renew (char, buf, bufsize);
|
|
}
|
|
|
|
for (out = buf; str && *str; ++str)
|
|
{
|
|
*out++ = *str;
|
|
|
|
if ((*str == '%') && isdigit (str[1]))
|
|
{
|
|
const char * tmp = str + 1;
|
|
while (isdigit (*tmp))
|
|
++tmp;
|
|
if (*tmp == '$')
|
|
str = tmp[1]=='\'' ? tmp+1 : tmp;
|
|
}
|
|
|
|
if ((*str == '%') && (str[1] == '\''))
|
|
str = str + 1;
|
|
|
|
}
|
|
*out = '\0';
|
|
|
|
return !in || strcmp (buf, in) ? buf : in;
|
|
}
|
|
|
|
/**
|
|
***
|
|
**/
|
|
|
|
void
|
|
tr_timerAdd (struct event * timer, int seconds, int microseconds)
|
|
{
|
|
struct timeval tv;
|
|
tv.tv_sec = seconds;
|
|
tv.tv_usec = microseconds;
|
|
|
|
assert (tv.tv_sec >= 0);
|
|
assert (tv.tv_usec >= 0);
|
|
assert (tv.tv_usec < 1000000);
|
|
|
|
evtimer_add (timer, &tv);
|
|
}
|
|
|
|
void
|
|
tr_timerAddMsec (struct event * timer, int msec)
|
|
{
|
|
const int seconds = msec / 1000;
|
|
const int usec = (msec%1000) * 1000;
|
|
tr_timerAdd (timer, seconds, usec);
|
|
}
|
|
|
|
/**
|
|
***
|
|
**/
|
|
|
|
uint8_t *
|
|
tr_loadFile (const char * path,
|
|
size_t * size)
|
|
{
|
|
uint8_t * buf;
|
|
struct stat sb;
|
|
int fd;
|
|
ssize_t n;
|
|
const char * const err_fmt = _("Couldn't read \"%1$s\": %2$s");
|
|
|
|
/* try to stat the file */
|
|
errno = 0;
|
|
if (stat (path, &sb))
|
|
{
|
|
const int err = errno;
|
|
tr_dbg (err_fmt, path, tr_strerror (errno));
|
|
errno = err;
|
|
return NULL;
|
|
}
|
|
|
|
if ((sb.st_mode & S_IFMT) != S_IFREG)
|
|
{
|
|
tr_err (err_fmt, path, _("Not a regular file"));
|
|
errno = EISDIR;
|
|
return NULL;
|
|
}
|
|
|
|
/* Load the torrent file into our buffer */
|
|
fd = tr_open_file_for_scanning (path);
|
|
if (fd < 0)
|
|
{
|
|
const int err = errno;
|
|
tr_err (err_fmt, path, tr_strerror (errno));
|
|
errno = err;
|
|
return NULL;
|
|
}
|
|
buf = tr_malloc (sb.st_size + 1);
|
|
if (!buf)
|
|
{
|
|
const int err = errno;
|
|
tr_err (err_fmt, path, _("Memory allocation failed"));
|
|
tr_close_file (fd);
|
|
errno = err;
|
|
return NULL;
|
|
}
|
|
n = read (fd, buf, (size_t)sb.st_size);
|
|
if (n == -1)
|
|
{
|
|
const int err = errno;
|
|
tr_err (err_fmt, path, tr_strerror (errno));
|
|
tr_close_file (fd);
|
|
free (buf);
|
|
errno = err;
|
|
return NULL;
|
|
}
|
|
|
|
tr_close_file (fd);
|
|
buf[ sb.st_size ] = '\0';
|
|
*size = sb.st_size;
|
|
return buf;
|
|
}
|
|
|
|
char*
|
|
tr_basename (const char * path)
|
|
{
|
|
char * tmp = tr_strdup (path);
|
|
char * ret = tr_strdup (basename (tmp));
|
|
tr_free (tmp);
|
|
return ret;
|
|
}
|
|
|
|
char*
|
|
tr_dirname (const char * path)
|
|
{
|
|
char * tmp = tr_strdup (path);
|
|
char * ret = tr_strdup (dirname (tmp));
|
|
tr_free (tmp);
|
|
return ret;
|
|
}
|
|
|
|
char*
|
|
tr_mkdtemp (char * template)
|
|
{
|
|
#ifdef HAVE_MKDTEMP
|
|
return mkdtemp (template);
|
|
#else
|
|
if (!mktemp (template) || mkdir (template, 0700))
|
|
return NULL;
|
|
return template;
|
|
#endif
|
|
}
|
|
|
|
/**
|
|
* @brief Portability wrapper for mkdir ()
|
|
*
|
|
* A portability wrapper around mkdir ().
|
|
* On WIN32, the `permissions' argument is unused.
|
|
*
|
|
* @return zero on success, or -1 if an error occurred
|
|
* (in which case errno is set appropriately).
|
|
*/
|
|
static int
|
|
tr_mkdir (const char * path, int permissions UNUSED)
|
|
{
|
|
#ifdef WIN32
|
|
if (path && isalpha (path[0]) && path[1] == ':' && !path[2])
|
|
return 0;
|
|
return mkdir (path);
|
|
#else
|
|
return mkdir (path, permissions);
|
|
#endif
|
|
}
|
|
|
|
int
|
|
tr_mkdirp (const char * path_in,
|
|
int permissions)
|
|
{
|
|
char * path = tr_strdup (path_in);
|
|
char * p, * pp;
|
|
struct stat sb;
|
|
int done;
|
|
|
|
/* walk past the root */
|
|
p = path;
|
|
while (*p == TR_PATH_DELIMITER)
|
|
++p;
|
|
|
|
pp = p;
|
|
done = 0;
|
|
while ((p =
|
|
strchr (pp, TR_PATH_DELIMITER)) || (p = strchr (pp, '\0')))
|
|
{
|
|
if (!*p)
|
|
done = 1;
|
|
else
|
|
*p = '\0';
|
|
|
|
if (stat (path, &sb))
|
|
{
|
|
/* Folder doesn't exist yet */
|
|
if (tr_mkdir (path, permissions))
|
|
{
|
|
const int err = errno;
|
|
tr_err (_(
|
|
"Couldn't create \"%1$s\": %2$s"), path,
|
|
tr_strerror (err));
|
|
tr_free (path);
|
|
errno = err;
|
|
return -1;
|
|
}
|
|
}
|
|
else if ((sb.st_mode & S_IFMT) != S_IFDIR)
|
|
{
|
|
/* Node exists but isn't a folder */
|
|
char * buf = tr_strdup_printf (_("File \"%s\" is in the way"), path);
|
|
tr_err (_("Couldn't create \"%1$s\": %2$s"), path_in, buf);
|
|
tr_free (buf);
|
|
tr_free (path);
|
|
errno = ENOTDIR;
|
|
return -1;
|
|
}
|
|
|
|
if (done)
|
|
break;
|
|
|
|
*p = TR_PATH_DELIMITER;
|
|
p++;
|
|
pp = p;
|
|
}
|
|
|
|
tr_free (path);
|
|
return 0;
|
|
}
|
|
|
|
char*
|
|
tr_buildPath (const char *first_element, ...)
|
|
{
|
|
size_t bufLen = 0;
|
|
const char * element;
|
|
char * buf;
|
|
char * pch;
|
|
va_list vl;
|
|
|
|
/* pass 1: allocate enough space for the string */
|
|
va_start (vl, first_element);
|
|
element = first_element;
|
|
while (element) {
|
|
bufLen += strlen (element) + 1;
|
|
element = va_arg (vl, const char*);
|
|
}
|
|
pch = buf = tr_new (char, bufLen);
|
|
va_end (vl);
|
|
|
|
/* pass 2: build the string piece by piece */
|
|
va_start (vl, first_element);
|
|
element = first_element;
|
|
while (element) {
|
|
const size_t elementLen = strlen (element);
|
|
memcpy (pch, element, elementLen);
|
|
pch += elementLen;
|
|
*pch++ = TR_PATH_DELIMITER;
|
|
element = va_arg (vl, const char*);
|
|
}
|
|
va_end (vl);
|
|
|
|
/* terminate the string. if nonempty, eat the unwanted trailing slash */
|
|
if (pch != buf)
|
|
--pch;
|
|
*pch++ = '\0';
|
|
|
|
/* sanity checks & return */
|
|
assert (pch - buf == (off_t)bufLen);
|
|
return buf;
|
|
}
|
|
|
|
#ifdef SYS_DARWIN
|
|
#define TR_STAT_MTIME(sb)((sb).st_mtimespec.tv_sec)
|
|
#else
|
|
#define TR_STAT_MTIME(sb)((sb).st_mtime)
|
|
#endif
|
|
|
|
bool
|
|
tr_fileExists (const char * filename, time_t * mtime)
|
|
{
|
|
struct stat sb;
|
|
const bool ok = !stat (filename, &sb);
|
|
|
|
if (ok && (mtime != NULL))
|
|
*mtime = TR_STAT_MTIME (sb);
|
|
|
|
return ok;
|
|
}
|
|
|
|
/****
|
|
*****
|
|
****/
|
|
|
|
char*
|
|
evbuffer_free_to_str (struct evbuffer * buf)
|
|
{
|
|
const size_t n = evbuffer_get_length (buf);
|
|
char * ret = tr_new (char, n + 1);
|
|
evbuffer_copyout (buf, ret, n);
|
|
evbuffer_free (buf);
|
|
ret[n] = '\0';
|
|
return ret;
|
|
}
|
|
|
|
char*
|
|
tr_strdup (const void * in)
|
|
{
|
|
return tr_strndup (in, in ? (int)strlen ((const char *)in) : 0);
|
|
}
|
|
|
|
char*
|
|
tr_strndup (const void * in, int len)
|
|
{
|
|
char * out = NULL;
|
|
|
|
if (len < 0)
|
|
{
|
|
out = tr_strdup (in);
|
|
}
|
|
else if (in)
|
|
{
|
|
out = tr_malloc (len + 1);
|
|
memcpy (out, in, len);
|
|
out[len] = '\0';
|
|
}
|
|
|
|
return out;
|
|
}
|
|
|
|
const char*
|
|
tr_memmem (const char * haystack, size_t haystacklen,
|
|
const char * needle, size_t needlelen)
|
|
{
|
|
#ifdef HAVE_MEMMEM
|
|
return memmem (haystack, haystacklen, needle, needlelen);
|
|
#else
|
|
size_t i;
|
|
if (!needlelen)
|
|
return haystack;
|
|
if (needlelen > haystacklen || !haystack || !needle)
|
|
return NULL;
|
|
for (i=0; i<=haystacklen-needlelen; ++i)
|
|
if (!memcmp (haystack+i, needle, needlelen))
|
|
return haystack+i;
|
|
return NULL;
|
|
#endif
|
|
}
|
|
|
|
char*
|
|
tr_strdup_printf (const char * fmt, ...)
|
|
{
|
|
va_list ap;
|
|
char * ret;
|
|
size_t len;
|
|
char statbuf[2048];
|
|
|
|
va_start (ap, fmt);
|
|
len = evutil_vsnprintf (statbuf, sizeof (statbuf), fmt, ap);
|
|
va_end (ap);
|
|
if (len < sizeof (statbuf))
|
|
ret = tr_strndup (statbuf, len);
|
|
else {
|
|
ret = tr_new (char, len + 1);
|
|
va_start (ap, fmt);
|
|
evutil_vsnprintf (ret, len + 1, fmt, ap);
|
|
va_end (ap);
|
|
}
|
|
|
|
return ret;
|
|
}
|
|
|
|
const char*
|
|
tr_strerror (int i)
|
|
{
|
|
const char * ret = strerror (i);
|
|
|
|
if (ret == NULL)
|
|
ret = "Unknown Error";
|
|
return ret;
|
|
}
|
|
|
|
int
|
|
tr_strcmp0 (const char * str1, const char * str2)
|
|
{
|
|
if (str1 && str2) return strcmp (str1, str2);
|
|
if (str1) return 1;
|
|
if (str2) return -1;
|
|
return 0;
|
|
}
|
|
|
|
/****
|
|
*****
|
|
****/
|
|
|
|
/* https://bugs.launchpad.net/percona-patches/+bug/526863/+attachment/1160199/+files/solaris_10_fix.patch */
|
|
char*
|
|
tr_strsep (char ** str, const char * delims)
|
|
{
|
|
#ifdef HAVE_STRSEP
|
|
return strsep (str, delims);
|
|
#else
|
|
char *token;
|
|
|
|
if (*str == NULL) {
|
|
/* No more tokens */
|
|
return NULL;
|
|
}
|
|
|
|
token = *str;
|
|
while (**str != '\0') {
|
|
if (strchr (delims, **str) != NULL) {
|
|
**str = '\0';
|
|
(*str)++;
|
|
return token;
|
|
}
|
|
(*str)++;
|
|
}
|
|
|
|
/* There is not another token */
|
|
*str = NULL;
|
|
|
|
return token;
|
|
#endif
|
|
}
|
|
|
|
char*
|
|
tr_strstrip (char * str)
|
|
{
|
|
if (str != NULL)
|
|
{
|
|
size_t pos;
|
|
size_t len = strlen (str);
|
|
|
|
while (len && isspace (str[len - 1]))
|
|
--len;
|
|
|
|
for (pos = 0; pos < len && isspace (str[pos]);)
|
|
++pos;
|
|
|
|
len -= pos;
|
|
memmove (str, str + pos, len);
|
|
str[len] = '\0';
|
|
}
|
|
|
|
return str;
|
|
}
|
|
|
|
bool
|
|
tr_str_has_suffix (const char *str, const char *suffix)
|
|
{
|
|
size_t str_len;
|
|
size_t suffix_len;
|
|
|
|
if (!str)
|
|
return false;
|
|
if (!suffix)
|
|
return true;
|
|
|
|
str_len = strlen (str);
|
|
suffix_len = strlen (suffix);
|
|
if (str_len < suffix_len)
|
|
return false;
|
|
|
|
return !evutil_ascii_strncasecmp (str + str_len - suffix_len, suffix, suffix_len);
|
|
}
|
|
|
|
/****
|
|
*****
|
|
****/
|
|
|
|
uint64_t
|
|
tr_time_msec (void)
|
|
{
|
|
struct timeval tv;
|
|
|
|
gettimeofday (&tv, NULL);
|
|
return (uint64_t) tv.tv_sec * 1000 + (tv.tv_usec / 1000);
|
|
}
|
|
|
|
void
|
|
tr_wait_msec (long int msec)
|
|
{
|
|
#ifdef WIN32
|
|
Sleep ((DWORD)msec);
|
|
#else
|
|
struct timespec ts;
|
|
ts.tv_sec = msec / 1000;
|
|
ts.tv_nsec = (msec % 1000) * 1000000;
|
|
nanosleep (&ts, NULL);
|
|
#endif
|
|
}
|
|
|
|
/***
|
|
****
|
|
***/
|
|
|
|
int
|
|
tr_snprintf (char * buf, size_t buflen, const char * fmt, ...)
|
|
{
|
|
int len;
|
|
va_list args;
|
|
|
|
va_start (args, fmt);
|
|
len = evutil_vsnprintf (buf, buflen, fmt, args);
|
|
va_end (args);
|
|
return len;
|
|
}
|
|
|
|
/*
|
|
* Copy src to string dst of size siz. At most siz-1 characters
|
|
* will be copied. Always NUL terminates (unless siz == 0).
|
|
* Returns strlen (src); if retval >= siz, truncation occurred.
|
|
*/
|
|
size_t
|
|
tr_strlcpy (char * dst, const void * src, size_t siz)
|
|
{
|
|
#ifdef HAVE_STRLCPY
|
|
return strlcpy (dst, src, siz);
|
|
#else
|
|
char * d = dst;
|
|
const char *s = src;
|
|
size_t n = siz;
|
|
|
|
assert (s);
|
|
assert (d);
|
|
|
|
/* Copy as many bytes as will fit */
|
|
if (n != 0)
|
|
{
|
|
while (--n != 0)
|
|
{
|
|
if ((*d++ = *s++) == '\0')
|
|
break;
|
|
}
|
|
}
|
|
|
|
/* Not enough room in dst, add NUL and traverse rest of src */
|
|
if (n == 0)
|
|
{
|
|
if (siz != 0)
|
|
*d = '\0'; /* NUL-terminate dst */
|
|
while (*s++)
|
|
;
|
|
}
|
|
|
|
return s - (char*)src - 1; /* count does not include NUL */
|
|
#endif
|
|
}
|
|
|
|
/***
|
|
****
|
|
***/
|
|
|
|
double
|
|
tr_getRatio (uint64_t numerator, uint64_t denominator)
|
|
{
|
|
double ratio;
|
|
|
|
if (denominator > 0)
|
|
ratio = numerator / (double)denominator;
|
|
else if (numerator > 0)
|
|
ratio = TR_RATIO_INF;
|
|
else
|
|
ratio = TR_RATIO_NA;
|
|
|
|
return ratio;
|
|
}
|
|
|
|
void
|
|
tr_sha1_to_hex (char * out, const uint8_t * sha1)
|
|
{
|
|
int i;
|
|
static const char hex[] = "0123456789abcdef";
|
|
|
|
for (i=0; i<20; ++i)
|
|
{
|
|
const unsigned int val = *sha1++;
|
|
*out++ = hex[val >> 4];
|
|
*out++ = hex[val & 0xf];
|
|
}
|
|
|
|
*out = '\0';
|
|
}
|
|
|
|
void
|
|
tr_hex_to_sha1 (uint8_t * out, const char * in)
|
|
{
|
|
int i;
|
|
static const char hex[] = "0123456789abcdef";
|
|
|
|
for (i=0; i<20; ++i)
|
|
{
|
|
const int hi = strchr (hex, tolower (*in++)) - hex;
|
|
const int lo = strchr (hex, tolower (*in++)) - hex;
|
|
*out++ = (uint8_t)((hi<<4) | lo);
|
|
}
|
|
}
|
|
|
|
/***
|
|
****
|
|
***/
|
|
|
|
static bool
|
|
isValidURLChars (const char * url, int url_len)
|
|
{
|
|
const char * c;
|
|
const char * end;
|
|
static const char * rfc2396_valid_chars =
|
|
"abcdefghijklmnopqrstuvwxyz" /* lowalpha */
|
|
"ABCDEFGHIJKLMNOPQRSTUVWXYZ" /* upalpha */
|
|
"0123456789" /* digit */
|
|
"-_.!~*'()" /* mark */
|
|
";/?:@&=+$," /* reserved */
|
|
"<>#%<\"" /* delims */
|
|
"{}|\\^[]`"; /* unwise */
|
|
|
|
if (url == NULL)
|
|
return false;
|
|
|
|
for (c=url, end=c+url_len; c && *c && c!=end; ++c)
|
|
if (!strchr (rfc2396_valid_chars, *c))
|
|
return false;
|
|
|
|
return true;
|
|
}
|
|
|
|
/** @brief return true if the URL is a http or https or UDP one that Transmission understands */
|
|
bool
|
|
tr_urlIsValidTracker (const char * url)
|
|
{
|
|
bool valid;
|
|
|
|
if (url == NULL)
|
|
{
|
|
valid = false;
|
|
}
|
|
else
|
|
{
|
|
const int len = strlen (url);
|
|
|
|
valid = isValidURLChars (url, len)
|
|
&& !tr_urlParse (url, len, NULL, NULL, NULL, NULL)
|
|
&& (!memcmp (url,"http://",7) || !memcmp (url,"https://",8) || !memcmp (url,"udp://",6));
|
|
}
|
|
|
|
return valid;
|
|
}
|
|
|
|
/** @brief return true if the URL is a http or https or ftp or sftp one that Transmission understands */
|
|
bool
|
|
tr_urlIsValid (const char * url, int url_len)
|
|
{
|
|
bool valid;
|
|
|
|
if (url == NULL)
|
|
{
|
|
valid = false;
|
|
}
|
|
else
|
|
{
|
|
if (url_len < 0)
|
|
url_len = strlen (url);
|
|
|
|
valid = isValidURLChars (url, url_len)
|
|
&& !tr_urlParse (url, url_len, NULL, NULL, NULL, NULL)
|
|
&& (!memcmp (url,"http://",7) || !memcmp (url,"https://",8) || !memcmp (url,"ftp://",6) || !memcmp (url,"sftp://",7));
|
|
}
|
|
|
|
return valid;
|
|
}
|
|
|
|
bool
|
|
tr_addressIsIP (const char * str)
|
|
{
|
|
tr_address tmp;
|
|
return tr_address_from_string (&tmp, str);
|
|
}
|
|
|
|
int
|
|
tr_urlParse (const char * url_in,
|
|
int len,
|
|
char ** setme_protocol,
|
|
char ** setme_host,
|
|
int * setme_port,
|
|
char ** setme_path)
|
|
{
|
|
int err;
|
|
int port = 0;
|
|
int n;
|
|
char * tmp;
|
|
char * pch;
|
|
size_t host_len;
|
|
size_t protocol_len;
|
|
const char * host = NULL;
|
|
const char * protocol = NULL;
|
|
const char * path = NULL;
|
|
|
|
tmp = tr_strndup (url_in, len);
|
|
if ((pch = strstr (tmp, "://")))
|
|
{
|
|
*pch = '\0';
|
|
protocol = tmp;
|
|
protocol_len = pch - protocol;
|
|
pch += 3;
|
|
/*fprintf (stderr, "protocol is [%s]... what's left is [%s]\n", protocol, pch);*/
|
|
if ((n = strcspn (pch, ":/")))
|
|
{
|
|
const int havePort = pch[n] == ':';
|
|
host = pch;
|
|
host_len = n;
|
|
pch += n;
|
|
if (pch && *pch)
|
|
*pch++ = '\0';
|
|
/*fprintf (stderr, "host is [%s]... what's left is [%s]\n", host, pch);*/
|
|
if (havePort)
|
|
{
|
|
char * end;
|
|
port = strtol (pch, &end, 10);
|
|
pch = end;
|
|
/*fprintf (stderr, "port is [%d]... what's left is [%s]\n", port, pch);*/
|
|
}
|
|
path = pch;
|
|
/*fprintf (stderr, "path is [%s]\n", path);*/
|
|
}
|
|
}
|
|
|
|
err = !host || !path || !protocol;
|
|
|
|
if (!err && !port)
|
|
{
|
|
if (!strcmp (protocol, "udp")) port = 80;
|
|
else if (!strcmp (protocol, "ftp")) port = 21;
|
|
else if (!strcmp (protocol, "sftp")) port = 22;
|
|
else if (!strcmp (protocol, "http")) port = 80;
|
|
else if (!strcmp (protocol, "https")) port = 443;
|
|
}
|
|
|
|
if (!err)
|
|
{
|
|
if (setme_protocol) *setme_protocol = tr_strndup (protocol, protocol_len);
|
|
|
|
if (setme_host){ ((char*)host)[-3] = ':'; *setme_host =
|
|
tr_strndup (host, host_len); }
|
|
if (setme_path){ if (!*path) *setme_path = tr_strdup ("/");
|
|
else if (path[0] == '/') *setme_path = tr_strdup (path);
|
|
else { ((char*)path)[-1] = '/'; *setme_path = tr_strdup (path - 1); } }
|
|
if (setme_port) *setme_port = port;
|
|
}
|
|
|
|
|
|
tr_free (tmp);
|
|
return err;
|
|
}
|
|
|
|
#include <string.h>
|
|
#include <openssl/sha.h>
|
|
#include <openssl/hmac.h>
|
|
#include <openssl/evp.h>
|
|
#include <openssl/bio.h>
|
|
#include <openssl/buffer.h>
|
|
|
|
char *
|
|
tr_base64_encode (const void * input, int length, int * setme_len)
|
|
{
|
|
int retlen = 0;
|
|
char * ret = NULL;
|
|
|
|
if (input != NULL)
|
|
{
|
|
BIO * b64;
|
|
BIO * bmem;
|
|
BUF_MEM * bptr;
|
|
|
|
if (length < 1)
|
|
length = (int)strlen (input);
|
|
|
|
bmem = BIO_new (BIO_s_mem ());
|
|
b64 = BIO_new (BIO_f_base64 ());
|
|
BIO_set_flags (b64, BIO_FLAGS_BASE64_NO_NL);
|
|
b64 = BIO_push (b64, bmem);
|
|
BIO_write (b64, input, length);
|
|
(void) BIO_flush (b64);
|
|
BIO_get_mem_ptr (b64, &bptr);
|
|
ret = tr_strndup (bptr->data, bptr->length);
|
|
retlen = bptr->length;
|
|
BIO_free_all (b64);
|
|
}
|
|
|
|
if (setme_len)
|
|
*setme_len = retlen;
|
|
|
|
return ret;
|
|
}
|
|
|
|
char *
|
|
tr_base64_decode (const void * input,
|
|
int length,
|
|
int * setme_len)
|
|
{
|
|
char * ret;
|
|
BIO * b64;
|
|
BIO * bmem;
|
|
int retlen;
|
|
|
|
if (length < 1)
|
|
length = strlen (input);
|
|
|
|
ret = tr_new0 (char, length);
|
|
b64 = BIO_new (BIO_f_base64 ());
|
|
bmem = BIO_new_mem_buf ((unsigned char*)input, length);
|
|
bmem = BIO_push (b64, bmem);
|
|
retlen = BIO_read (bmem, ret, length);
|
|
if (!retlen)
|
|
{
|
|
/* try again, but with the BIO_FLAGS_BASE64_NO_NL flag */
|
|
BIO_free_all (bmem);
|
|
b64 = BIO_new (BIO_f_base64 ());
|
|
BIO_set_flags (b64, BIO_FLAGS_BASE64_NO_NL);
|
|
bmem = BIO_new_mem_buf ((unsigned char*)input, length);
|
|
bmem = BIO_push (b64, bmem);
|
|
retlen = BIO_read (bmem, ret, length);
|
|
}
|
|
|
|
if (setme_len)
|
|
*setme_len = retlen;
|
|
|
|
BIO_free_all (bmem);
|
|
return ret;
|
|
}
|
|
|
|
/***
|
|
****
|
|
***/
|
|
|
|
void
|
|
tr_removeElementFromArray (void * array,
|
|
unsigned int index_to_remove,
|
|
size_t sizeof_element,
|
|
size_t nmemb)
|
|
{
|
|
char * a = array;
|
|
|
|
memmove (a + sizeof_element * index_to_remove,
|
|
a + sizeof_element * (index_to_remove + 1),
|
|
sizeof_element * (--nmemb - index_to_remove));
|
|
}
|
|
|
|
int
|
|
tr_lowerBound (const void * key,
|
|
const void * base,
|
|
size_t nmemb,
|
|
size_t size,
|
|
int (* compar)(const void* key, const void* arrayMember),
|
|
bool * exact_match)
|
|
{
|
|
size_t first = 0;
|
|
const char * cbase = base;
|
|
bool exact = false;
|
|
|
|
while (nmemb != 0)
|
|
{
|
|
const size_t half = nmemb / 2;
|
|
const size_t middle = first + half;
|
|
const int c = compar (key, cbase + size*middle);
|
|
|
|
if (c <= 0) {
|
|
if (c == 0)
|
|
exact = true;
|
|
nmemb = half;
|
|
} else {
|
|
first = middle + 1;
|
|
nmemb = nmemb - half - 1;
|
|
}
|
|
}
|
|
|
|
*exact_match = exact;
|
|
|
|
return first;
|
|
}
|
|
|
|
/***
|
|
****
|
|
****
|
|
***/
|
|
|
|
/* Byte-wise swap two items of size SIZE.
|
|
From glibc, written by Douglas C. Schmidt, LGPL 2.1 or higher */
|
|
#define SWAP(a, b, size) \
|
|
do { \
|
|
register size_t __size = (size); \
|
|
register char *__a = (a), *__b = (b); \
|
|
if (__a != __b) do { \
|
|
char __tmp = *__a; \
|
|
*__a++ = *__b; \
|
|
*__b++ = __tmp; \
|
|
} while (--__size > 0); \
|
|
} while (0)
|
|
|
|
|
|
static size_t
|
|
quickfindPartition (char * base, size_t left, size_t right, size_t size,
|
|
int (*compar)(const void *, const void *), size_t pivotIndex)
|
|
{
|
|
size_t i;
|
|
size_t storeIndex;
|
|
|
|
/* move pivot to the end */
|
|
SWAP (base+(size*pivotIndex), base+(size*right), size);
|
|
|
|
storeIndex = left;
|
|
for (i=left; i<=right-1; ++i)
|
|
{
|
|
if (compar (base+(size*i), base+(size*right)) <= 0)
|
|
{
|
|
SWAP (base+(size*storeIndex), base+(size*i), size);
|
|
++storeIndex;
|
|
}
|
|
}
|
|
|
|
/* move pivot to its final place */
|
|
SWAP (base+(size*right), base+(size*storeIndex), size);
|
|
|
|
/* sanity check the partition */
|
|
#ifndef NDEBUG
|
|
assert (storeIndex >= left);
|
|
assert (storeIndex <= right);
|
|
|
|
for (i=left; i<storeIndex; ++i)
|
|
assert (compar (base+(size*i), base+(size*storeIndex)) <= 0);
|
|
for (i=storeIndex+1; i<=right; ++i)
|
|
assert (compar (base+(size*i), base+(size*storeIndex)) >= 0);
|
|
#endif
|
|
|
|
return storeIndex;
|
|
}
|
|
|
|
static void
|
|
quickfindFirstK (char * base, size_t left, size_t right, size_t size,
|
|
int (*compar)(const void *, const void *), size_t k)
|
|
{
|
|
if (right > left)
|
|
{
|
|
const size_t pivotIndex = left + (right-left)/2u;
|
|
|
|
const size_t pivotNewIndex = quickfindPartition (base, left, right, size, compar, pivotIndex);
|
|
|
|
if (pivotNewIndex > left + k) /* new condition */
|
|
quickfindFirstK (base, left, pivotNewIndex-1, size, compar, k);
|
|
else if (pivotNewIndex < left + k)
|
|
quickfindFirstK (base, pivotNewIndex+1, right, size, compar, k+left-pivotNewIndex-1);
|
|
}
|
|
}
|
|
|
|
#ifndef NDEBUG
|
|
static void
|
|
checkBestScoresComeFirst (char * base, size_t nmemb, size_t size,
|
|
int (*compar)(const void *, const void *), size_t k)
|
|
{
|
|
size_t i;
|
|
size_t worstFirstPos = 0;
|
|
|
|
for (i=1; i<k; ++i)
|
|
if (compar (base+(size*worstFirstPos), base+(size*i)) < 0)
|
|
worstFirstPos = i;
|
|
|
|
for (i=0; i<k; ++i)
|
|
assert (compar (base+(size*i), base+(size*worstFirstPos)) <= 0);
|
|
for (i=k; i<nmemb; ++i)
|
|
assert (compar (base+(size*i), base+(size*worstFirstPos)) >= 0);
|
|
}
|
|
#endif
|
|
|
|
void
|
|
tr_quickfindFirstK (void * base, size_t nmemb, size_t size,
|
|
int (*compar)(const void *, const void *), size_t k)
|
|
{
|
|
if (k < nmemb)
|
|
{
|
|
quickfindFirstK (base, 0, nmemb-1, size, compar, k);
|
|
|
|
#ifndef NDEBUG
|
|
checkBestScoresComeFirst (base, nmemb, size, compar, k);
|
|
#endif
|
|
}
|
|
}
|
|
|
|
/***
|
|
****
|
|
***/
|
|
|
|
static char*
|
|
strip_non_utf8 (const char * in, size_t inlen)
|
|
{
|
|
const char * end;
|
|
const char zero = '\0';
|
|
struct evbuffer * buf = evbuffer_new ();
|
|
|
|
while (!tr_utf8_validate (in, inlen, &end))
|
|
{
|
|
const int good_len = end - in;
|
|
|
|
evbuffer_add (buf, in, good_len);
|
|
inlen -= (good_len + 1);
|
|
in += (good_len + 1);
|
|
evbuffer_add (buf, "?", 1);
|
|
}
|
|
|
|
evbuffer_add (buf, in, inlen);
|
|
evbuffer_add (buf, &zero, 1);
|
|
return evbuffer_free_to_str (buf);
|
|
}
|
|
|
|
static char*
|
|
to_utf8 (const char * in, size_t inlen)
|
|
{
|
|
char * ret = NULL;
|
|
|
|
#ifdef HAVE_ICONV_OPEN
|
|
int i;
|
|
const char * encodings[] = { "CURRENT", "ISO-8859-15" };
|
|
const int encoding_count = sizeof (encodings) / sizeof (encodings[1]);
|
|
const size_t buflen = inlen*4 + 10;
|
|
char * out = tr_new (char, buflen);
|
|
|
|
for (i=0; !ret && i<encoding_count; ++i)
|
|
{
|
|
char * inbuf = (char*) in;
|
|
char * outbuf = out;
|
|
size_t inbytesleft = inlen;
|
|
size_t outbytesleft = buflen;
|
|
const char * test_encoding = encodings[i];
|
|
|
|
iconv_t cd = iconv_open ("UTF-8", test_encoding);
|
|
if (cd != (iconv_t)-1) {
|
|
if (iconv (cd, &inbuf, &inbytesleft, &outbuf, &outbytesleft) != (size_t)-1)
|
|
ret = tr_strndup (out, buflen-outbytesleft);
|
|
iconv_close (cd);
|
|
}
|
|
}
|
|
|
|
tr_free (out);
|
|
#endif
|
|
|
|
if (ret == NULL)
|
|
ret = strip_non_utf8 (in, inlen);
|
|
|
|
return ret;
|
|
}
|
|
|
|
char*
|
|
tr_utf8clean (const char * str, int max_len)
|
|
{
|
|
char * ret;
|
|
const char * end;
|
|
|
|
if (max_len < 0)
|
|
max_len = (int) strlen (str);
|
|
|
|
if (tr_utf8_validate (str, max_len, &end))
|
|
ret = tr_strndup (str, max_len);
|
|
else
|
|
ret = to_utf8 (str, max_len);
|
|
|
|
assert (tr_utf8_validate (ret, -1, NULL));
|
|
return ret;
|
|
}
|
|
|
|
/***
|
|
****
|
|
***/
|
|
|
|
struct number_range
|
|
{
|
|
int low;
|
|
int high;
|
|
};
|
|
|
|
/**
|
|
* This should be a single number (ex. "6") or a range (ex. "6-9").
|
|
* Anything else is an error and will return failure.
|
|
*/
|
|
static bool
|
|
parseNumberSection (const char * str, int len, struct number_range * setme)
|
|
{
|
|
long a, b;
|
|
bool success;
|
|
char * end;
|
|
const int error = errno;
|
|
char * tmp = tr_strndup (str, len);
|
|
|
|
errno = 0;
|
|
a = b = strtol (tmp, &end, 10);
|
|
if (errno || (end == tmp)) {
|
|
success = false;
|
|
} else if (*end != '-') {
|
|
success = true;
|
|
} else {
|
|
const char * pch = end + 1;
|
|
b = strtol (pch, &end, 10);
|
|
if (errno || (pch == end))
|
|
success = false;
|
|
else if (*end) /* trailing data */
|
|
success = false;
|
|
else
|
|
success = true;
|
|
}
|
|
tr_free (tmp);
|
|
|
|
setme->low = MIN (a, b);
|
|
setme->high = MAX (a, b);
|
|
|
|
errno = error;
|
|
return success;
|
|
}
|
|
|
|
int
|
|
compareInt (const void * va, const void * vb)
|
|
{
|
|
const int a = * (const int *)va;
|
|
const int b = * (const int *)vb;
|
|
return a - b;
|
|
}
|
|
|
|
/**
|
|
* Given a string like "1-4" or "1-4,6,9,14-51", this allocates and returns an
|
|
* array of setmeCount ints of all the values in the array.
|
|
* For example, "5-8" will return [ 5, 6, 7, 8 ] and setmeCount will be 4.
|
|
* It's the caller's responsibility to call tr_free () on the returned array.
|
|
* If a fragment of the string can't be parsed, NULL is returned.
|
|
*/
|
|
int*
|
|
tr_parseNumberRange (const char * str_in, int len, int * setmeCount)
|
|
{
|
|
int n = 0;
|
|
int * uniq = NULL;
|
|
char * str = tr_strndup (str_in, len);
|
|
const char * walk;
|
|
tr_list * ranges = NULL;
|
|
bool success = true;
|
|
|
|
walk = str;
|
|
while (walk && *walk && success) {
|
|
struct number_range range;
|
|
const char * pch = strchr (walk, ',');
|
|
if (pch) {
|
|
success = parseNumberSection (walk, pch-walk, &range);
|
|
walk = pch + 1;
|
|
} else {
|
|
success = parseNumberSection (walk, strlen (walk), &range);
|
|
walk += strlen (walk);
|
|
}
|
|
if (success)
|
|
tr_list_append (&ranges, tr_memdup (&range, sizeof (struct number_range)));
|
|
}
|
|
|
|
if (!success)
|
|
{
|
|
*setmeCount = 0;
|
|
uniq = NULL;
|
|
}
|
|
else
|
|
{
|
|
int i;
|
|
int n2;
|
|
tr_list * l;
|
|
int * sorted = NULL;
|
|
|
|
/* build a sorted number array */
|
|
n = n2 = 0;
|
|
for (l=ranges; l!=NULL; l=l->next) {
|
|
const struct number_range * r = l->data;
|
|
n += r->high + 1 - r->low;
|
|
}
|
|
sorted = tr_new (int, n);
|
|
for (l=ranges; l!=NULL; l=l->next) {
|
|
const struct number_range * r = l->data;
|
|
int i;
|
|
for (i=r->low; i<=r->high; ++i)
|
|
sorted[n2++] = i;
|
|
}
|
|
qsort (sorted, n, sizeof (int), compareInt);
|
|
assert (n == n2);
|
|
|
|
/* remove duplicates */
|
|
uniq = tr_new (int, n);
|
|
for (i=n=0; i<n2; ++i)
|
|
if (!n || uniq[n-1] != sorted[i])
|
|
uniq[n++] = sorted[i];
|
|
|
|
tr_free (sorted);
|
|
}
|
|
|
|
/* cleanup */
|
|
tr_list_free (&ranges, tr_free);
|
|
tr_free (str);
|
|
|
|
/* return the result */
|
|
*setmeCount = n;
|
|
return uniq;
|
|
}
|
|
|
|
/***
|
|
****
|
|
***/
|
|
|
|
double
|
|
tr_truncd (double x, int precision)
|
|
{
|
|
char * pt;
|
|
char buf[128];
|
|
const int max_precision = (int) log10 (1.0 / DBL_EPSILON) - 1;
|
|
tr_snprintf (buf, sizeof (buf), "%.*f", max_precision, x);
|
|
if ((pt = strstr (buf, localeconv ()->decimal_point)))
|
|
pt[precision ? precision+1 : 0] = '\0';
|
|
return atof (buf);
|
|
}
|
|
|
|
/* return a truncated double as a string */
|
|
static char*
|
|
tr_strtruncd (char * buf, double x, int precision, size_t buflen)
|
|
{
|
|
tr_snprintf (buf, buflen, "%.*f", precision, tr_truncd (x, precision));
|
|
return buf;
|
|
}
|
|
|
|
char*
|
|
tr_strpercent (char * buf, double x, size_t buflen)
|
|
{
|
|
if (x < 10.0)
|
|
tr_strtruncd (buf, x, 2, buflen);
|
|
else if (x < 100.0)
|
|
tr_strtruncd (buf, x, 1, buflen);
|
|
else
|
|
tr_strtruncd (buf, x, 0, buflen);
|
|
return buf;
|
|
}
|
|
|
|
char*
|
|
tr_strratio (char * buf, size_t buflen, double ratio, const char * infinity)
|
|
{
|
|
if ((int)ratio == TR_RATIO_NA)
|
|
tr_strlcpy (buf, _("None"), buflen);
|
|
else if ((int)ratio == TR_RATIO_INF)
|
|
tr_strlcpy (buf, infinity, buflen);
|
|
else
|
|
tr_strpercent (buf, ratio, buflen);
|
|
return buf;
|
|
}
|
|
|
|
/***
|
|
****
|
|
***/
|
|
|
|
int
|
|
tr_moveFile (const char * oldpath, const char * newpath, bool * renamed)
|
|
{
|
|
int in;
|
|
int out;
|
|
char * buf;
|
|
struct stat st;
|
|
off_t bytesLeft;
|
|
const size_t buflen = 1024 * 128; /* 128 KiB buffer */
|
|
|
|
/* make sure the old file exists */
|
|
if (stat (oldpath, &st)) {
|
|
const int err = errno;
|
|
errno = err;
|
|
return -1;
|
|
}
|
|
if (!S_ISREG (st.st_mode)) {
|
|
errno = ENOENT;
|
|
return -1;
|
|
}
|
|
bytesLeft = st.st_size;
|
|
|
|
/* make sure the target directory exists */
|
|
{
|
|
char * newdir = tr_dirname (newpath);
|
|
int i = tr_mkdirp (newdir, 0777);
|
|
tr_free (newdir);
|
|
if (i)
|
|
return i;
|
|
}
|
|
|
|
/* they might be on the same filesystem... */
|
|
{
|
|
const int i = rename (oldpath, newpath);
|
|
if (renamed != NULL)
|
|
*renamed = i == 0;
|
|
if (!i)
|
|
return 0;
|
|
}
|
|
|
|
/* copy the file */
|
|
in = tr_open_file_for_scanning (oldpath);
|
|
out = tr_open_file_for_writing (newpath);
|
|
buf = tr_valloc (buflen);
|
|
while (bytesLeft > 0)
|
|
{
|
|
ssize_t bytesWritten;
|
|
const off_t bytesThisPass = MIN (bytesLeft, (off_t)buflen);
|
|
const int numRead = read (in, buf, bytesThisPass);
|
|
if (numRead < 0)
|
|
break;
|
|
bytesWritten = write (out, buf, numRead);
|
|
if (bytesWritten < 0)
|
|
break;
|
|
bytesLeft -= bytesWritten;
|
|
}
|
|
|
|
/* cleanup */
|
|
tr_free (buf);
|
|
tr_close_file (out);
|
|
tr_close_file (in);
|
|
if (bytesLeft != 0)
|
|
return -1;
|
|
|
|
unlink (oldpath);
|
|
return 0;
|
|
}
|
|
|
|
bool
|
|
tr_is_same_file (const char * filename1, const char * filename2)
|
|
{
|
|
struct stat sb1, sb2;
|
|
|
|
return !stat (filename1, &sb1)
|
|
&& !stat (filename2, &sb2)
|
|
&& (sb1.st_dev == sb2.st_dev)
|
|
&& (sb1.st_ino == sb2.st_ino);
|
|
}
|
|
|
|
/***
|
|
****
|
|
***/
|
|
|
|
void*
|
|
tr_valloc (size_t bufLen)
|
|
{
|
|
size_t allocLen;
|
|
void * buf = NULL;
|
|
static size_t pageSize = 0;
|
|
|
|
if (!pageSize) {
|
|
#ifdef HAVE_GETPAGESIZE
|
|
pageSize = (size_t) getpagesize ();
|
|
#else /* guess */
|
|
pageSize = 4096;
|
|
#endif
|
|
}
|
|
|
|
allocLen = pageSize;
|
|
while (allocLen < bufLen)
|
|
allocLen += pageSize;
|
|
|
|
#ifdef HAVE_POSIX_MEMALIGN
|
|
if (!buf)
|
|
if (posix_memalign (&buf, pageSize, allocLen))
|
|
buf = NULL; /* just retry with valloc/malloc */
|
|
#endif
|
|
#ifdef HAVE_VALLOC
|
|
if (!buf)
|
|
buf = valloc (allocLen);
|
|
#endif
|
|
if (!buf)
|
|
buf = tr_malloc (allocLen);
|
|
|
|
return buf;
|
|
}
|
|
|
|
char *
|
|
tr_realpath (const char * path, char * resolved_path)
|
|
{
|
|
#ifdef WIN32
|
|
/* From a message to the Mingw-msys list, Jun 2, 2005 by Mark Junker. */
|
|
if (GetFullPathNameA (path, TR_PATH_MAX, resolved_path, NULL) == 0)
|
|
return NULL;
|
|
return resolved_path;
|
|
#else
|
|
return realpath (path, resolved_path);
|
|
#endif
|
|
}
|
|
|
|
/***
|
|
****
|
|
***/
|
|
|
|
uint64_t
|
|
tr_htonll (uint64_t x)
|
|
{
|
|
#ifdef HAVE_HTONLL
|
|
return htonll (x);
|
|
#else
|
|
/* fallback code by bdonlan at
|
|
* http://stackoverflow.com/questions/809902/64-bit-ntohl-in-c/875505#875505 */
|
|
union { uint32_t lx[2]; uint64_t llx; } u;
|
|
u.lx[0] = htonl (x >> 32);
|
|
u.lx[1] = htonl (x & 0xFFFFFFFFULL);
|
|
return u.llx;
|
|
#endif
|
|
}
|
|
|
|
uint64_t
|
|
tr_ntohll (uint64_t x)
|
|
{
|
|
#ifdef HAVE_NTOHLL
|
|
return ntohll (x);
|
|
#else
|
|
/* fallback code by bdonlan at
|
|
* http://stackoverflow.com/questions/809902/64-bit-ntohl-in-c/875505#875505 */
|
|
union { uint32_t lx[2]; uint64_t llx; } u;
|
|
u.llx = x;
|
|
return ((uint64_t)ntohl (u.lx[0]) << 32) | (uint64_t)ntohl (u.lx[1]);
|
|
#endif
|
|
}
|
|
|
|
/***
|
|
****
|
|
****
|
|
****
|
|
***/
|
|
|
|
struct formatter_unit
|
|
{
|
|
char * name;
|
|
int64_t value;
|
|
};
|
|
|
|
struct formatter_units
|
|
{
|
|
struct formatter_unit units[4];
|
|
};
|
|
|
|
enum { TR_FMT_KB, TR_FMT_MB, TR_FMT_GB, TR_FMT_TB };
|
|
|
|
static void
|
|
formatter_init (struct formatter_units * units,
|
|
unsigned int kilo,
|
|
const char * kb, const char * mb,
|
|
const char * gb, const char * tb)
|
|
{
|
|
uint64_t value = kilo;
|
|
units->units[TR_FMT_KB].name = tr_strdup (kb);
|
|
units->units[TR_FMT_KB].value = value;
|
|
|
|
value *= kilo;
|
|
units->units[TR_FMT_MB].name = tr_strdup (mb);
|
|
units->units[TR_FMT_MB].value = value;
|
|
|
|
value *= kilo;
|
|
units->units[TR_FMT_GB].name = tr_strdup (gb);
|
|
units->units[TR_FMT_GB].value = value;
|
|
|
|
value *= kilo;
|
|
units->units[TR_FMT_TB].name = tr_strdup (tb);
|
|
units->units[TR_FMT_TB].value = value;
|
|
}
|
|
|
|
static char*
|
|
formatter_get_size_str (const struct formatter_units * u,
|
|
char * buf, int64_t bytes, size_t buflen)
|
|
{
|
|
int precision;
|
|
double value;
|
|
const char * units;
|
|
const struct formatter_unit * unit;
|
|
|
|
if (bytes < u->units[1].value) unit = &u->units[0];
|
|
else if (bytes < u->units[2].value) unit = &u->units[1];
|
|
else if (bytes < u->units[3].value) unit = &u->units[2];
|
|
else unit = &u->units[3];
|
|
|
|
value = (double)bytes / unit->value;
|
|
units = unit->name;
|
|
if (unit->value == 1)
|
|
precision = 0;
|
|
else if (value < 100)
|
|
precision = 2;
|
|
else
|
|
precision = 1;
|
|
tr_snprintf (buf, buflen, "%.*f %s", precision, value, units);
|
|
return buf;
|
|
}
|
|
|
|
static struct formatter_units size_units;
|
|
|
|
void
|
|
tr_formatter_size_init (unsigned int kilo,
|
|
const char * kb, const char * mb,
|
|
const char * gb, const char * tb)
|
|
{
|
|
formatter_init (&size_units, kilo, kb, mb, gb, tb);
|
|
}
|
|
|
|
char*
|
|
tr_formatter_size_B (char * buf, int64_t bytes, size_t buflen)
|
|
{
|
|
return formatter_get_size_str (&size_units, buf, bytes, buflen);
|
|
}
|
|
|
|
static struct formatter_units speed_units;
|
|
|
|
unsigned int tr_speed_K = 0u;
|
|
|
|
void
|
|
tr_formatter_speed_init (unsigned int kilo,
|
|
const char * kb, const char * mb,
|
|
const char * gb, const char * tb)
|
|
{
|
|
tr_speed_K = kilo;
|
|
formatter_init (&speed_units, kilo, kb, mb, gb, tb);
|
|
}
|
|
|
|
char*
|
|
tr_formatter_speed_KBps (char * buf, double KBps, size_t buflen)
|
|
{
|
|
const double K = speed_units.units[TR_FMT_KB].value;
|
|
double speed = KBps;
|
|
|
|
if (speed <= 999.95) /* 0.0 KB to 999.9 KB */
|
|
tr_snprintf (buf, buflen, "%d %s", (int)speed, speed_units.units[TR_FMT_KB].name);
|
|
else {
|
|
speed /= K;
|
|
if (speed <= 99.995) /* 0.98 MB to 99.99 MB */
|
|
tr_snprintf (buf, buflen, "%.2f %s", speed, speed_units.units[TR_FMT_MB].name);
|
|
else if (speed <= 999.95) /* 100.0 MB to 999.9 MB */
|
|
tr_snprintf (buf, buflen, "%.1f %s", speed, speed_units.units[TR_FMT_MB].name);
|
|
else {
|
|
speed /= K;
|
|
tr_snprintf (buf, buflen, "%.1f %s", speed, speed_units.units[TR_FMT_GB].name);
|
|
}
|
|
}
|
|
|
|
return buf;
|
|
}
|
|
|
|
static struct formatter_units mem_units;
|
|
|
|
unsigned int tr_mem_K = 0u;
|
|
|
|
void
|
|
tr_formatter_mem_init (unsigned int kilo,
|
|
const char * kb, const char * mb,
|
|
const char * gb, const char * tb)
|
|
{
|
|
tr_mem_K = kilo;
|
|
formatter_init (&mem_units, kilo, kb, mb, gb, tb);
|
|
}
|
|
|
|
char*
|
|
tr_formatter_mem_B (char * buf, int64_t bytes_per_second, size_t buflen)
|
|
{
|
|
return formatter_get_size_str (&mem_units, buf, bytes_per_second, buflen);
|
|
}
|
|
|
|
void
|
|
tr_formatter_get_units (void * vdict)
|
|
{
|
|
int i;
|
|
tr_variant * l;
|
|
tr_variant * dict = vdict;
|
|
|
|
tr_variantDictReserve (dict, 6);
|
|
|
|
tr_variantDictAddInt (dict, TR_KEY_memory_bytes, mem_units.units[TR_FMT_KB].value);
|
|
l = tr_variantDictAddList (dict, TR_KEY_memory_units, 4);
|
|
for (i=0; i<4; i++)
|
|
tr_variantListAddStr (l, mem_units.units[i].name);
|
|
|
|
tr_variantDictAddInt (dict, TR_KEY_size_bytes, size_units.units[TR_FMT_KB].value);
|
|
l = tr_variantDictAddList (dict, TR_KEY_size_units, 4);
|
|
for (i=0; i<4; i++)
|
|
tr_variantListAddStr (l, size_units.units[i].name);
|
|
|
|
tr_variantDictAddInt (dict, TR_KEY_speed_bytes, speed_units.units[TR_FMT_KB].value);
|
|
l = tr_variantDictAddList (dict, TR_KEY_speed_units, 4);
|
|
for (i=0; i<4; i++)
|
|
tr_variantListAddStr (l, speed_units.units[i].name);
|
|
}
|
|
|