source: trunk/libtransmission/utils.c @ 14320

Last change on this file since 14320 was 14320, checked in by jordan, 8 years ago

(trunk, libT) #4160 'foreign character support' -- merge mike.dld's 4160-02b-path.patch, which updates the codebase to use the new tr_sys_path_*() portability wrappers introduced in 4160-02a

  • Property svn:keywords set to Date Rev Author Id
File size: 38.4 KB
Line 
1/*
2 * This file Copyright (C) 2009-2014 Mnemosyne LLC
3 *
4 * It may be used under the GNU GPL versions 2 or 3
5 * or any future license endorsed by Mnemosyne LLC.
6 *
7 * $Id: utils.c 14320 2014-07-08 00:08:43Z jordan $
8 */
9
10#ifdef HAVE_MEMMEM
11 #define _GNU_SOURCE /* glibc's string.h needs this to pick up memmem */
12#endif
13
14#if defined (XCODE_BUILD)
15 #define HAVE_GETPAGESIZE
16 #define HAVE_ICONV_OPEN
17 #define HAVE_MKDTEMP
18 #define HAVE_VALLOC
19#endif
20
21#include <assert.h>
22#include <ctype.h> /* isdigit (), isalpha (), tolower () */
23#include <errno.h>
24#include <float.h> /* DBL_EPSILON */
25#include <locale.h> /* localeconv () */
26#include <math.h> /* pow (), fabs (), floor () */
27#include <stdio.h>
28#include <stdlib.h>
29#include <string.h> /* strerror (), memset (), memmem () */
30#include <time.h> /* nanosleep () */
31
32#ifdef HAVE_ICONV_OPEN
33 #include <iconv.h>
34#endif
35#include <sys/time.h>
36#include <sys/types.h>
37#include <sys/stat.h>
38#include <unistd.h> /* stat (), getpagesize () */
39
40#include <event2/buffer.h>
41#include <event2/event.h>
42
43#ifdef _WIN32
44 #include <w32api.h>
45 #define WINVER WindowsXP /* freeaddrinfo (), getaddrinfo (), getnameinfo () */
46 #include <windows.h> /* Sleep (), GetSystemTimeAsFileTime () */
47#endif
48
49#include "transmission.h"
50#include "error.h"
51#include "fdlimit.h"
52#include "file.h"
53#include "ConvertUTF.h"
54#include "list.h"
55#include "log.h"
56#include "net.h"
57#include "utils.h"
58#include "platform.h" /* tr_lockLock () */
59#include "platform-quota.h" /* tr_device_info_create(), tr_device_info_get_free_space(), tr_device_info_free() */
60#include "variant.h"
61#include "version.h"
62
63
64time_t __tr_current_time   = 0;
65
66/***
67****
68***/
69
70struct tm *
71tr_localtime_r (const time_t *_clock, struct tm *_result)
72{
73#ifdef HAVE_LOCALTIME_R
74  return localtime_r (_clock, _result);
75#else
76  struct tm *p = localtime (_clock);
77  if (p)
78    * (_result) = *p;
79  return p;
80#endif
81}
82
83int
84tr_gettimeofday (struct timeval * tv)
85{
86#ifdef _MSC_VER
87#define DELTA_EPOCH_IN_MICROSECS 11644473600000000Ui64
88
89  FILETIME ft;
90  uint64_t tmp = 0;
91
92  if (tv == NULL)
93    {
94      errno = EINVAL;
95      return -1;
96    }
97
98  GetSystemTimeAsFileTime(&ft);
99  tmp |= ft.dwHighDateTime;
100  tmp <<= 32;
101  tmp |= ft.dwLowDateTime;
102  tmp /= 10; /* to microseconds */
103  tmp -= DELTA_EPOCH_IN_MICROSECS;
104
105  tv->tv_sec = tmp / 1000000UL;
106  tv->tv_usec = tmp % 1000000UL;
107
108  return 0;
109
110#undef DELTA_EPOCH_IN_MICROSECS
111#else
112
113  return gettimeofday (tv, NULL);
114
115#endif
116}
117
118/***
119****
120***/
121
122void*
123tr_malloc (size_t size)
124{
125  return size ? malloc (size) : NULL;
126}
127
128void*
129tr_malloc0 (size_t size)
130{
131  return size ? calloc (1, size) : NULL;
132}
133
134void
135tr_free (void * p)
136{
137  if (p != NULL)
138    free (p);
139}
140
141void*
142tr_memdup (const void * src, size_t byteCount)
143{
144  return memcpy (tr_malloc (byteCount), src, byteCount);
145}
146
147/***
148****
149***/
150
151const char*
152tr_strip_positional_args (const char* str)
153{
154  char * out;
155  static size_t bufsize = 0;
156  static char * buf = NULL;
157  const char * in = str;
158  const size_t  len = str ? strlen (str) : 0;
159
160  if (!buf || (bufsize < len))
161    {
162      bufsize = len * 2 + 1;
163      buf = tr_renew (char, buf, bufsize);
164    }
165
166  for (out = buf; str && *str; ++str)
167    {
168      *out++ = *str;
169
170      if ((*str == '%') && isdigit (str[1]))
171        {
172          const char * tmp = str + 1;
173          while (isdigit (*tmp))
174            ++tmp;
175          if (*tmp == '$')
176            str = tmp[1]=='\'' ? tmp+1 : tmp;
177        }
178
179      if ((*str == '%') && (str[1] == '\''))
180        str = str + 1;
181
182    }
183
184  *out = '\0';
185  return !in || strcmp (buf, in) ? buf : in;
186}
187
188/**
189***
190**/
191
192void
193tr_timerAdd (struct event * timer, int seconds, int microseconds)
194{
195  struct timeval tv;
196  tv.tv_sec = seconds;
197  tv.tv_usec = microseconds;
198
199  assert (tv.tv_sec >= 0);
200  assert (tv.tv_usec >= 0);
201  assert (tv.tv_usec < 1000000);
202
203  evtimer_add (timer, &tv);
204}
205
206void
207tr_timerAddMsec (struct event * timer, int msec)
208{
209  const int seconds =  msec / 1000;
210  const int usec = (msec%1000) * 1000;
211  tr_timerAdd (timer, seconds, usec);
212}
213
214/**
215***
216**/
217
218uint8_t *
219tr_loadFile (const char * path,
220             size_t     * size)
221{
222  uint8_t * buf;
223  tr_sys_path_info info;
224  int fd;
225  ssize_t n;
226  tr_error * error = NULL;
227  const char * const err_fmt = _("Couldn't read \"%1$s\": %2$s");
228
229  /* try to stat the file */
230  if (!tr_sys_path_get_info (path, 0, &info, &error))
231    {
232      const int err = error->code;
233      tr_logAddDebug (err_fmt, path, error->message);
234      tr_error_free (error);
235      errno = err;
236      return NULL;
237    }
238
239  if (info.type != TR_SYS_PATH_IS_FILE)
240    {
241      tr_logAddError (err_fmt, path, _("Not a regular file"));
242      errno = EISDIR;
243      return NULL;
244    }
245
246  /* file size should be able to fit into size_t */
247  if (sizeof(info.size) > sizeof(*size))
248    assert (info.size <= SIZE_MAX);
249
250  /* Load the torrent file into our buffer */
251  fd = tr_open_file_for_scanning (path);
252  if (fd < 0)
253    {
254      const int err = errno;
255      tr_logAddError (err_fmt, path, tr_strerror (errno));
256      errno = err;
257      return NULL;
258    }
259  buf = tr_malloc (info.size + 1);
260  if (!buf)
261    {
262      const int err = errno;
263      tr_logAddError (err_fmt, path, _("Memory allocation failed"));
264      tr_close_file (fd);
265      errno = err;
266      return NULL;
267    }
268  n = read (fd, buf, (size_t)info.size);
269  if (n == -1)
270    {
271      const int err = errno;
272      tr_logAddError (err_fmt, path, tr_strerror (errno));
273      tr_close_file (fd);
274      free (buf);
275      errno = err;
276      return NULL;
277    }
278
279  tr_close_file (fd);
280  buf[info.size] = '\0';
281  *size = info.size;
282  return buf;
283}
284
285char*
286tr_mkdtemp (char * template)
287{
288#ifdef HAVE_MKDTEMP
289  return mkdtemp (template);
290#else
291  if (!mktemp (template) || mkdir (template, 0700))
292    return NULL;
293  return template;
294#endif
295}
296
297/**
298 * @brief Portability wrapper for mkdir ()
299 *
300 * A portability wrapper around mkdir ().
301 * On WIN32, the `permissions' argument is unused.
302 *
303 * @return zero on success, or -1 if an error occurred
304 * (in which case errno is set appropriately).
305 */
306static int
307tr_mkdir (const char * path, int permissions UNUSED)
308{
309#ifdef _WIN32
310  if (path && isalpha (path[0]) && path[1] == ':' && !path[2])
311    return 0;
312  return mkdir (path);
313#else
314  return mkdir (path, permissions);
315#endif
316}
317
318int
319tr_mkdirp (const char * path_in,
320           int          permissions)
321{
322  char * p;
323  char * pp;
324  bool done;
325  int tmperr;
326  int rv;
327  struct stat sb;
328  char * path;
329
330  /* make a temporary copy of path */
331  path = tr_strdup (path_in);
332  if (path == NULL)
333    {
334      errno = ENOMEM;
335      return -1;
336    }
337
338  /* walk past the root */
339  p = path;
340  while (*p == TR_PATH_DELIMITER)
341    ++p;
342
343  pp = p;
344  done = false;
345  while ((p = strchr (pp, TR_PATH_DELIMITER)) || (p = strchr (pp, '\0')))
346    {
347      if (!*p)
348        done = true;
349      else
350        *p = '\0';
351
352      tmperr = errno;
353      rv = stat (path, &sb);
354      errno = tmperr;
355      if (rv)
356        {
357          /* Folder doesn't exist yet */
358          if (tr_mkdir (path, permissions))
359            {
360              tmperr = errno;
361              tr_logAddError (_("Couldn't create \"%1$s\": %2$s"), path, tr_strerror (tmperr));
362              tr_free (path);
363              errno = tmperr;
364              return -1;
365            }
366        }
367      else if ((sb.st_mode & S_IFMT) != S_IFDIR)
368        {
369          /* Node exists but isn't a folder */
370          char * buf = tr_strdup_printf (_("File \"%s\" is in the way"), path);
371          tr_logAddError (_("Couldn't create \"%1$s\": %2$s"), path_in, buf);
372          tr_free (buf);
373          tr_free (path);
374          errno = ENOTDIR;
375          return -1;
376        }
377
378      if (done)
379        break;
380
381      *p = TR_PATH_DELIMITER;
382      p++;
383      pp = p;
384    }
385
386  tr_free (path);
387  return 0;
388}
389
390char*
391tr_buildPath (const char *first_element, ...)
392{
393  const char * element;
394  char * buf;
395  char * pch;
396  va_list vl;
397  size_t bufLen = 0;
398
399  /* pass 1: allocate enough space for the string */
400  va_start (vl, first_element);
401  element = first_element;
402  while (element)
403    {
404      bufLen += strlen (element) + 1;
405      element = va_arg (vl, const char*);
406    }
407  pch = buf = tr_new (char, bufLen);
408  va_end (vl);
409  if (buf == NULL)
410    return NULL;
411
412  /* pass 2: build the string piece by piece */
413  va_start (vl, first_element);
414  element = first_element;
415  while (element)
416    {
417      const size_t elementLen = strlen (element);
418      memcpy (pch, element, elementLen);
419      pch += elementLen;
420      *pch++ = TR_PATH_DELIMITER;
421      element = va_arg (vl, const char*);
422    }
423  va_end (vl);
424
425  /* terminate the string. if nonempty, eat the unwanted trailing slash */
426  if (pch != buf)
427    --pch;
428  *pch++ = '\0';
429
430  /* sanity checks & return */
431  assert (pch - buf == (off_t)bufLen);
432  return buf;
433}
434
435int64_t
436tr_getDirFreeSpace (const char * dir)
437{
438  int64_t free_space;
439
440  if (!dir || !*dir)
441    {
442      errno = EINVAL;
443      free_space = -1;
444    }
445  else
446    {
447      struct tr_device_info * info;
448      info = tr_device_info_create (dir);
449      free_space = tr_device_info_get_free_space (info);
450      tr_device_info_free (info);
451    }
452
453  return free_space;
454}
455
456/****
457*****
458****/
459
460char*
461evbuffer_free_to_str (struct evbuffer * buf)
462{
463  const size_t n = evbuffer_get_length (buf);
464  char * ret = tr_new (char, n + 1);
465  evbuffer_copyout (buf, ret, n);
466  evbuffer_free (buf);
467  ret[n] = '\0';
468  return ret;
469}
470
471char*
472tr_strdup (const void * in)
473{
474  return tr_strndup (in, in ? (int)strlen ((const char *)in) : 0);
475}
476
477char*
478tr_strndup (const void * in, int len)
479{
480  char * out = NULL;
481
482  if (len < 0)
483    {
484      out = tr_strdup (in);
485    }
486  else if (in)
487    {
488      out = tr_malloc (len + 1);
489
490      if (out != NULL)
491        {
492          memcpy (out, in, len);
493          out[len] = '\0';
494        }
495    }
496
497  return out;
498}
499
500const char*
501tr_memmem (const char * haystack, size_t haystacklen,
502           const char * needle, size_t needlelen)
503{
504#ifdef HAVE_MEMMEM
505  return memmem (haystack, haystacklen, needle, needlelen);
506#else
507  size_t i;
508  if (!needlelen)
509    return haystack;
510  if (needlelen > haystacklen || !haystack || !needle)
511    return NULL;
512  for (i=0; i<=haystacklen-needlelen; ++i)
513    if (!memcmp (haystack+i, needle, needlelen))
514      return haystack+i;
515  return NULL;
516#endif
517}
518
519char*
520tr_strdup_printf (const char * fmt, ...)
521{
522  va_list ap;
523  char * ret;
524
525  va_start (ap, fmt);
526  ret = tr_strdup_vprintf (fmt, ap);
527  va_end (ap);
528
529  return ret;
530}
531
532char *
533tr_strdup_vprintf (const char * fmt,
534                   va_list      args)
535{
536  struct evbuffer * buf = evbuffer_new ();
537  evbuffer_add_vprintf (buf, fmt, args);
538  return evbuffer_free_to_str (buf);
539}
540
541const char*
542tr_strerror (int i)
543{
544  const char * ret = strerror (i);
545
546  if (ret == NULL)
547    ret = "Unknown Error";
548
549  return ret;
550}
551
552int
553tr_strcmp0 (const char * str1, const char * str2)
554{
555  if (str1 && str2) return strcmp (str1, str2);
556  if (str1) return 1;
557  if (str2) return -1;
558  return 0;
559}
560
561/****
562*****
563****/
564
565/* https://bugs.launchpad.net/percona-patches/+bug/526863/+attachment/1160199/+files/solaris_10_fix.patch */
566char*
567tr_strsep (char ** str, const char * delims)
568{
569#ifdef HAVE_STRSEP
570  return strsep (str, delims);
571#else
572  char *token;
573
574  if (*str == NULL) /* no more tokens */
575    return NULL;
576
577  token = *str;
578  while (**str != '\0')
579    {
580      if (strchr (delims, **str) != NULL)
581        {
582          **str = '\0';
583          (*str)++;
584            return token;
585        }
586      (*str)++;
587    }
588
589  /* there is not another token */
590  *str = NULL;
591
592  return token;
593#endif
594}
595
596char*
597tr_strstrip (char * str)
598{
599  if (str != NULL)
600    {
601      size_t pos;
602      size_t len = strlen (str);
603
604      while (len && isspace (str[len - 1]))
605        --len;
606
607      for (pos = 0; pos < len && isspace (str[pos]);)
608        ++pos;
609
610      len -= pos;
611      memmove (str, str + pos, len);
612      str[len] = '\0';
613    }
614
615  return str;
616}
617
618bool
619tr_str_has_suffix (const char *str, const char *suffix)
620{
621  size_t str_len;
622  size_t suffix_len;
623
624  if (!str)
625    return false;
626  if (!suffix)
627    return true;
628
629  str_len = strlen (str);
630  suffix_len = strlen (suffix);
631  if (str_len < suffix_len)
632    return false;
633
634  return !evutil_ascii_strncasecmp (str + str_len - suffix_len, suffix, suffix_len);
635}
636
637/****
638*****
639****/
640
641uint64_t
642tr_time_msec (void)
643{
644  struct timeval tv;
645
646  tr_gettimeofday (&tv);
647  return (uint64_t) tv.tv_sec * 1000 + (tv.tv_usec / 1000);
648}
649
650void
651tr_wait_msec (long int msec)
652{
653#ifdef _WIN32
654  Sleep ((DWORD)msec);
655#else
656  struct timespec ts;
657  ts.tv_sec = msec / 1000;
658  ts.tv_nsec = (msec % 1000) * 1000000;
659  nanosleep (&ts, NULL);
660#endif
661}
662
663/***
664****
665***/
666
667int
668tr_snprintf (char * buf, size_t buflen, const char * fmt, ...)
669{
670  int len;
671  va_list args;
672
673  va_start (args, fmt);
674  len = evutil_vsnprintf (buf, buflen, fmt, args);
675  va_end (args);
676  return len;
677}
678
679/*
680 * Copy src to string dst of size siz. At most siz-1 characters
681 * will be copied. Always NUL terminates (unless siz == 0).
682 * Returns strlen (src); if retval >= siz, truncation occurred.
683 */
684size_t
685tr_strlcpy (char * dst, const void * src, size_t siz)
686{
687#ifdef HAVE_STRLCPY
688  return strlcpy (dst, src, siz);
689#else
690  char *      d = dst;
691  const char *s = src;
692  size_t      n = siz;
693
694  assert (s);
695  assert (d);
696
697  /* Copy as many bytes as will fit */
698  if (n != 0)
699    {
700      while (--n != 0)
701        {
702          if ((*d++ = *s++) == '\0')
703            break;
704        }
705    }
706
707  /* Not enough room in dst, add NUL and traverse rest of src */
708  if (n == 0)
709    {
710      if (siz != 0)
711        *d = '\0'; /* NUL-terminate dst */
712      while (*s++)
713        ;
714    }
715
716  return s - (char*)src - 1;  /* count does not include NUL */
717#endif
718}
719
720/***
721****
722***/
723
724double
725tr_getRatio (uint64_t numerator, uint64_t denominator)
726{
727  double ratio;
728
729  if (denominator > 0)
730    ratio = numerator / (double)denominator;
731  else if (numerator > 0)
732    ratio = TR_RATIO_INF;
733  else
734    ratio = TR_RATIO_NA;
735
736  return ratio;
737}
738
739void
740tr_sha1_to_hex (char * out, const uint8_t * sha1)
741{
742  int i;
743  static const char hex[] = "0123456789abcdef";
744
745  for (i=0; i<20; ++i)
746    {
747      const unsigned int val = *sha1++;
748      *out++ = hex[val >> 4];
749      *out++ = hex[val & 0xf];
750    }
751
752  *out = '\0';
753}
754
755void
756tr_hex_to_sha1 (uint8_t * out, const char * in)
757{
758  int i;
759  static const char hex[] = "0123456789abcdef";
760
761  for (i=0; i<20; ++i)
762    {
763      const int hi = strchr (hex, tolower (*in++)) - hex;
764      const int lo = strchr (hex, tolower (*in++)) - hex;
765      *out++ = (uint8_t)((hi<<4) | lo);
766    }
767}
768
769/***
770****
771***/
772
773static bool
774isValidURLChars (const char * url, int url_len)
775{
776  const char * c;
777  const char * end;
778  static const char * rfc2396_valid_chars =
779    "abcdefghijklmnopqrstuvwxyz" /* lowalpha */
780    "ABCDEFGHIJKLMNOPQRSTUVWXYZ" /* upalpha */
781    "0123456789"                 /* digit */
782    "-_.!~*'()"                  /* mark */
783    ";/?:@&=+$,"                 /* reserved */
784    "<>#%<\""                    /* delims */
785    "{}|\\^[]`";                 /* unwise */
786
787  if (url == NULL)
788    return false;
789
790  for (c=url, end=c+url_len; c && *c && c!=end; ++c)
791    if (!strchr (rfc2396_valid_chars, *c))
792      return false;
793
794  return true;
795}
796
797/** @brief return true if the URL is a http or https or UDP one that Transmission understands */
798bool
799tr_urlIsValidTracker (const char * url)
800{
801  bool valid;
802
803  if (url == NULL)
804    {
805      valid = false;
806    }
807  else
808    {
809      const int len = strlen (url);
810
811      valid = isValidURLChars (url, len)
812           && !tr_urlParse (url, len, NULL, NULL, NULL, NULL)
813           && (!memcmp (url,"http://",7) || !memcmp (url,"https://",8) || !memcmp (url,"udp://",6));
814    }
815
816  return valid;
817}
818
819/** @brief return true if the URL is a http or https or ftp or sftp one that Transmission understands */
820bool
821tr_urlIsValid (const char * url, int url_len)
822{
823  bool valid;
824
825  if (url == NULL)
826    {
827      valid = false;
828    }
829  else
830    {
831      if (url_len < 0)
832        url_len = strlen (url);
833
834      valid = isValidURLChars (url, url_len)
835           && !tr_urlParse (url, url_len, NULL, NULL, NULL, NULL)
836           && (!memcmp (url,"http://",7) || !memcmp (url,"https://",8) || !memcmp (url,"ftp://",6) || !memcmp (url,"sftp://",7));
837    }
838
839  return valid;
840}
841
842bool
843tr_addressIsIP (const char * str)
844{
845  tr_address tmp;
846  return tr_address_from_string (&tmp, str);
847}
848
849int
850tr_urlParse (const char * url_in,
851             int          len,
852             char **      setme_protocol,
853             char **      setme_host,
854             int *        setme_port,
855             char **      setme_path)
856{
857  int err;
858  int port = 0;
859  int n;
860  char * tmp;
861  char * pch;
862  size_t host_len;
863  size_t protocol_len;
864  const char * host = NULL;
865  const char * protocol = NULL;
866  const char * path = NULL;
867
868  tmp = tr_strndup (url_in, len);
869  if ((pch = strstr (tmp, "://")))
870    {
871      *pch = '\0';
872      protocol = tmp;
873      protocol_len = pch - protocol;
874      pch += 3;
875      if ((n = strcspn (pch, ":/")))
876        {
877          const int havePort = pch[n] == ':';
878          host = pch;
879          host_len = n;
880          pch += n;
881          if (pch && *pch)
882            *pch++ = '\0';
883          if (havePort)
884            {
885              char * end;
886              port = strtol (pch, &end, 10);
887              pch = end;
888            }
889          path = pch;
890        }
891    }
892
893  err = !host || !path || !protocol;
894
895  if (!err && !port)
896    {
897      if (!strcmp (protocol, "udp")) port = 80;
898      else if (!strcmp (protocol, "ftp")) port = 21;
899      else if (!strcmp (protocol, "sftp")) port = 22;
900      else if (!strcmp (protocol, "http")) port = 80;
901      else if (!strcmp (protocol, "https")) port = 443;
902    }
903
904  if (!err)
905    {
906      if (setme_protocol) *setme_protocol = tr_strndup (protocol, protocol_len);
907
908      if (setme_host){ ((char*)host)[-3] = ':'; *setme_host =
909                        tr_strndup (host, host_len); }
910
911      if (setme_path){ if (!*path) *setme_path = tr_strdup ("/");
912                       else if (path[0] == '/') *setme_path = tr_strdup (path);
913                       else { ((char*)path)[-1] = '/'; *setme_path = tr_strdup (path - 1); } }
914
915      if (setme_port) *setme_port = port;
916    }
917
918
919  tr_free (tmp);
920  return err;
921}
922
923#include <string.h>
924#include <openssl/sha.h>
925#include <openssl/hmac.h>
926#include <openssl/evp.h>
927#include <openssl/bio.h>
928#include <openssl/buffer.h>
929
930char *
931tr_base64_encode (const void * input, int length, int * setme_len)
932{
933  int retlen = 0;
934  char * ret = NULL;
935
936  if (input != NULL)
937    {
938      BIO * b64;
939      BIO * bmem;
940      BUF_MEM * bptr;
941
942      if (length < 1)
943        length = (int)strlen (input);
944
945      bmem = BIO_new (BIO_s_mem ());
946      b64 = BIO_new (BIO_f_base64 ());
947      BIO_set_flags (b64, BIO_FLAGS_BASE64_NO_NL);
948      b64 = BIO_push (b64, bmem);
949      BIO_write (b64, input, length);
950      (void) BIO_flush (b64);
951      BIO_get_mem_ptr (b64, &bptr);
952      ret = tr_strndup (bptr->data, bptr->length);
953      retlen = bptr->length;
954      BIO_free_all (b64);
955    }
956
957  if (setme_len)
958    *setme_len = retlen;
959
960  return ret;
961}
962
963char *
964tr_base64_decode (const void * input,
965                  int          length,
966                  int *        setme_len)
967{
968  char * ret;
969  BIO * b64;
970  BIO * bmem;
971  int retlen;
972
973  if (length < 1)
974    length = strlen (input);
975
976  ret = tr_new0 (char, length);
977  b64 = BIO_new (BIO_f_base64 ());
978  bmem = BIO_new_mem_buf ((unsigned char*)input, length);
979  bmem = BIO_push (b64, bmem);
980  retlen = BIO_read (bmem, ret, length);
981  if (!retlen)
982    {
983      /* try again, but with the BIO_FLAGS_BASE64_NO_NL flag */
984      BIO_free_all (bmem);
985      b64 = BIO_new (BIO_f_base64 ());
986      BIO_set_flags (b64, BIO_FLAGS_BASE64_NO_NL);
987      bmem = BIO_new_mem_buf ((unsigned char*)input, length);
988      bmem = BIO_push (b64, bmem);
989      retlen = BIO_read (bmem, ret, length);
990    }
991
992  if (setme_len)
993    *setme_len = retlen;
994
995  BIO_free_all (bmem);
996  return ret;
997}
998
999/***
1000****
1001***/
1002
1003void
1004tr_removeElementFromArray (void         * array,
1005                           unsigned int   index_to_remove,
1006                           size_t         sizeof_element,
1007                           size_t         nmemb)
1008{
1009  char * a = array;
1010
1011  memmove (a + sizeof_element * index_to_remove,
1012           a + sizeof_element * (index_to_remove  + 1),
1013           sizeof_element * (--nmemb - index_to_remove));
1014}
1015
1016int
1017tr_lowerBound (const void * key,
1018               const void * base,
1019               size_t       nmemb,
1020               size_t       size,
1021               int     (* compar)(const void* key, const void* arrayMember),
1022               bool       * exact_match)
1023{
1024  size_t first = 0;
1025  const char * cbase = base;
1026  bool exact = false;
1027
1028  while (nmemb != 0)
1029    {
1030      const size_t half = nmemb / 2;
1031      const size_t middle = first + half;
1032      const int c = compar (key, cbase + size*middle);
1033
1034      if (c <= 0)
1035        {
1036          if (c == 0)
1037            exact = true;
1038          nmemb = half;
1039        }
1040      else
1041        {
1042          first = middle + 1;
1043          nmemb = nmemb - half - 1;
1044        }
1045    }
1046
1047  *exact_match = exact;
1048  return first;
1049}
1050
1051/***
1052****
1053****
1054***/
1055
1056/* Byte-wise swap two items of size SIZE.
1057   From glibc, written by Douglas C. Schmidt, LGPL 2.1 or higher */
1058#define SWAP(a, b, size) \
1059  do { \
1060    register size_t __size = (size); \
1061    register char *__a = (a), *__b = (b); \
1062    if (__a != __b) do { \
1063      char __tmp = *__a; \
1064      *__a++ = *__b; \
1065      *__b++ = __tmp; \
1066    } while (--__size > 0); \
1067  } while (0)
1068
1069
1070static size_t
1071quickfindPartition (char * base, size_t left, size_t right, size_t size,
1072                    int (*compar)(const void *, const void *), size_t pivotIndex)
1073{
1074  size_t i;
1075  size_t storeIndex;
1076
1077  /* move pivot to the end */
1078  SWAP (base+(size*pivotIndex), base+(size*right), size);
1079
1080  storeIndex = left;
1081  for (i=left; i<=right-1; ++i)
1082    {
1083      if (compar (base+(size*i), base+(size*right)) <= 0)
1084        {
1085          SWAP (base+(size*storeIndex), base+(size*i), size);
1086          ++storeIndex;
1087        }
1088    }
1089
1090  /* move pivot to its final place */
1091  SWAP (base+(size*right), base+(size*storeIndex), size);
1092
1093  /* sanity check the partition */
1094#ifndef NDEBUG
1095  assert (storeIndex >= left);
1096  assert (storeIndex <= right);
1097
1098  for (i=left; i<storeIndex; ++i)
1099    assert (compar (base+(size*i), base+(size*storeIndex)) <= 0);
1100  for (i=storeIndex+1; i<=right; ++i)
1101    assert (compar (base+(size*i), base+(size*storeIndex)) >= 0);
1102#endif
1103
1104  return storeIndex;
1105}
1106
1107static void
1108quickfindFirstK (char * base, size_t left, size_t right, size_t size,
1109                 int (*compar)(const void *, const void *), size_t k)
1110{
1111  if (right > left)
1112    {
1113      const size_t pivotIndex = left + (right-left)/2u;
1114
1115      const size_t pivotNewIndex = quickfindPartition (base, left, right, size, compar, pivotIndex);
1116
1117      if (pivotNewIndex > left + k) /* new condition */
1118        quickfindFirstK (base, left, pivotNewIndex-1, size, compar, k);
1119      else if (pivotNewIndex < left + k)
1120        quickfindFirstK (base, pivotNewIndex+1, right, size, compar, k+left-pivotNewIndex-1);
1121    }
1122}
1123
1124#ifndef NDEBUG
1125static void
1126checkBestScoresComeFirst (char * base, size_t nmemb, size_t size,
1127                          int (*compar)(const void *, const void *), size_t k)
1128{
1129  size_t i;
1130  size_t worstFirstPos = 0;
1131
1132  for (i=1; i<k; ++i)
1133    if (compar (base+(size*worstFirstPos), base+(size*i)) < 0)
1134      worstFirstPos = i;
1135
1136  for (i=0; i<k; ++i)
1137    assert (compar (base+(size*i), base+(size*worstFirstPos)) <= 0);
1138  for (i=k; i<nmemb; ++i)
1139    assert (compar (base+(size*i), base+(size*worstFirstPos)) >= 0);
1140}
1141#endif
1142
1143void
1144tr_quickfindFirstK (void * base, size_t nmemb, size_t size,
1145                    int (*compar)(const void *, const void *), size_t k)
1146{
1147  if (k < nmemb)
1148    {
1149      quickfindFirstK (base, 0, nmemb-1, size, compar, k);
1150
1151#ifndef NDEBUG
1152      checkBestScoresComeFirst (base, nmemb, size, compar, k);
1153#endif
1154    }
1155}
1156
1157/***
1158****
1159***/
1160
1161static char*
1162strip_non_utf8 (const char * in, size_t inlen)
1163{
1164  const char * end;
1165  const char zero = '\0';
1166  struct evbuffer * buf = evbuffer_new ();
1167
1168  while (!tr_utf8_validate (in, inlen, &end))
1169    {
1170      const int good_len = end - in;
1171
1172      evbuffer_add (buf, in, good_len);
1173      inlen -= (good_len + 1);
1174      in += (good_len + 1);
1175      evbuffer_add (buf, "?", 1);
1176    }
1177
1178  evbuffer_add (buf, in, inlen);
1179  evbuffer_add (buf, &zero, 1);
1180  return evbuffer_free_to_str (buf);
1181}
1182
1183static char*
1184to_utf8 (const char * in, size_t inlen)
1185{
1186  char * ret = NULL;
1187
1188#ifdef HAVE_ICONV_OPEN
1189  int i;
1190  const char * encodings[] = { "CURRENT", "ISO-8859-15" };
1191  const int encoding_count = sizeof (encodings) / sizeof (encodings[1]);
1192  const size_t buflen = inlen*4 + 10;
1193  char * out = tr_new (char, buflen);
1194
1195  for (i=0; !ret && i<encoding_count; ++i)
1196    {
1197      char * inbuf = (char*) in;
1198      char * outbuf = out;
1199      size_t inbytesleft = inlen;
1200      size_t outbytesleft = buflen;
1201      const char * test_encoding = encodings[i];
1202
1203      iconv_t cd = iconv_open ("UTF-8", test_encoding);
1204      if (cd != (iconv_t)-1)
1205        {
1206          if (iconv (cd, &inbuf, &inbytesleft, &outbuf, &outbytesleft) != (size_t)-1)
1207            ret = tr_strndup (out, buflen-outbytesleft);
1208          iconv_close (cd);
1209        }
1210    }
1211
1212  tr_free (out);
1213#endif
1214
1215  if (ret == NULL)
1216    ret = strip_non_utf8 (in, inlen);
1217
1218  return ret;
1219}
1220
1221char*
1222tr_utf8clean (const char * str, int max_len)
1223{
1224  char * ret;
1225  const char * end;
1226
1227  if (max_len < 0)
1228    max_len = (int) strlen (str);
1229
1230  if (tr_utf8_validate (str, max_len, &end))
1231    ret = tr_strndup (str, max_len);
1232  else
1233    ret = to_utf8 (str, max_len);
1234
1235  assert (tr_utf8_validate (ret, -1, NULL));
1236  return ret;
1237}
1238
1239#ifdef WIN32
1240
1241char *
1242tr_win32_native_to_utf8 (const wchar_t * text,
1243                         int             text_size)
1244{
1245  char * ret = NULL;
1246  int size;
1247
1248  size = WideCharToMultiByte (CP_UTF8, 0, text, text_size, NULL, 0, NULL, NULL);
1249  if (size == 0)
1250    goto fail;
1251
1252  ret = tr_new (char, size + 1);
1253  size = WideCharToMultiByte (CP_UTF8, 0, text, text_size, ret, size, NULL, NULL);
1254  if (size == 0)
1255    goto fail;
1256
1257  ret[size] = '\0';
1258
1259  return ret;
1260
1261fail:
1262  tr_free (ret);
1263
1264  return NULL;
1265}
1266
1267wchar_t *
1268tr_win32_utf8_to_native (const char * text,
1269                         int          text_size)
1270{
1271  return tr_win32_utf8_to_native_ex (text, text_size, 0);
1272}
1273
1274wchar_t *
1275tr_win32_utf8_to_native_ex (const char * text,
1276                            int          text_size,
1277                            int          extra_chars)
1278{
1279  wchar_t * ret = NULL;
1280  int size;
1281
1282  size = MultiByteToWideChar (CP_UTF8, 0, text, text_size, NULL, 0);
1283  if (size == 0)
1284    goto fail;
1285
1286  ret = tr_new (wchar_t, size + extra_chars + 1);
1287  size = MultiByteToWideChar (CP_UTF8, 0, text, text_size, ret, size);
1288  if (size == 0)
1289    goto fail;
1290
1291  ret[size] = L'\0';
1292
1293  return ret;
1294
1295fail:
1296  tr_free (ret);
1297
1298  return NULL;
1299}
1300
1301char *
1302tr_win32_format_message (uint32_t code)
1303{
1304  wchar_t * wide_text = NULL;
1305  DWORD wide_size;
1306  char * text = NULL;
1307  size_t text_size;
1308
1309  wide_size = FormatMessageW (FORMAT_MESSAGE_ALLOCATE_BUFFER |
1310                              FORMAT_MESSAGE_FROM_SYSTEM |
1311                              FORMAT_MESSAGE_IGNORE_INSERTS,
1312                              NULL, code, 0, (LPWSTR)&wide_text, 0, NULL);
1313
1314  if (wide_size != 0 && wide_text != NULL)
1315    text = tr_win32_native_to_utf8 (wide_text, wide_size);
1316
1317  LocalFree (wide_text);
1318
1319  /* Most (all?) messages contain "\r\n" in the end, chop it */
1320  text_size = strlen (text);
1321  while (text_size > 0 &&
1322         text[text_size - 1] >= '\0' &&
1323         text[text_size - 1] <= ' ')
1324    text[--text_size] = '\0';
1325
1326  return text;
1327}
1328
1329#endif
1330
1331/***
1332****
1333***/
1334
1335struct number_range
1336{
1337  int low;
1338  int high;
1339};
1340
1341/**
1342 * This should be a single number (ex. "6") or a range (ex. "6-9").
1343 * Anything else is an error and will return failure.
1344 */
1345static bool
1346parseNumberSection (const char * str, int len, struct number_range * setme)
1347{
1348  long a, b;
1349  bool success;
1350  char * end;
1351  const int error = errno;
1352  char * tmp = tr_strndup (str, len);
1353
1354  errno = 0;
1355  a = b = strtol (tmp, &end, 10);
1356  if (errno || (end == tmp))
1357    {
1358      success = false;
1359    }
1360  else if (*end != '-')
1361    {
1362      success = true;
1363    }
1364  else
1365    {
1366      const char * pch = end + 1;
1367      b = strtol (pch, &end, 10);
1368      if (errno || (pch == end))
1369        success = false;
1370      else if (*end) /* trailing data */
1371        success = false;
1372      else
1373        success = true;
1374    }
1375
1376  tr_free (tmp);
1377
1378  setme->low = MIN (a, b);
1379  setme->high = MAX (a, b);
1380
1381  errno = error;
1382  return success;
1383}
1384
1385int
1386compareInt (const void * va, const void * vb)
1387{
1388  const int a = * (const int *)va;
1389  const int b = * (const int *)vb;
1390  return a - b;
1391}
1392
1393/**
1394 * Given a string like "1-4" or "1-4,6,9,14-51", this allocates and returns an
1395 * array of setmeCount ints of all the values in the array.
1396 * For example, "5-8" will return [ 5, 6, 7, 8 ] and setmeCount will be 4.
1397 * It's the caller's responsibility to call tr_free () on the returned array.
1398 * If a fragment of the string can't be parsed, NULL is returned.
1399 */
1400int*
1401tr_parseNumberRange (const char * str_in, int len, int * setmeCount)
1402{
1403  int n = 0;
1404  int * uniq = NULL;
1405  char * str = tr_strndup (str_in, len);
1406  const char * walk;
1407  tr_list * ranges = NULL;
1408  bool success = true;
1409
1410  walk = str;
1411  while (walk && *walk && success)
1412    {
1413      struct number_range range;
1414      const char * pch = strchr (walk, ',');
1415      if (pch)
1416        {
1417          success = parseNumberSection (walk, pch-walk, &range);
1418          walk = pch + 1;
1419        }
1420      else
1421        {
1422          success = parseNumberSection (walk, strlen (walk), &range);
1423          walk += strlen (walk);
1424        }
1425      if (success)
1426        tr_list_append (&ranges, tr_memdup (&range, sizeof (struct number_range)));
1427    }
1428
1429  if (!success)
1430    {
1431      *setmeCount = 0;
1432      uniq = NULL;
1433    }
1434  else
1435    {
1436      int i;
1437      int n2;
1438      tr_list * l;
1439      int * sorted = NULL;
1440
1441      /* build a sorted number array */
1442      n = n2 = 0;
1443      for (l=ranges; l!=NULL; l=l->next)
1444        {
1445          const struct number_range * r = l->data;
1446          n += r->high + 1 - r->low;
1447        }
1448      sorted = tr_new (int, n);
1449      if (sorted == NULL)
1450        {
1451          n = 0;
1452          uniq = NULL;
1453        }
1454      else
1455        {
1456          for (l=ranges; l!=NULL; l=l->next)
1457            {
1458              int i;
1459              const struct number_range * r = l->data;
1460              for (i=r->low; i<=r->high; ++i)
1461                sorted[n2++] = i;
1462            }
1463          qsort (sorted, n, sizeof (int), compareInt);
1464          assert (n == n2);
1465
1466          /* remove duplicates */
1467          uniq = tr_new (int, n);
1468          if (uniq == NULL)
1469            {
1470              n = 0;
1471            }
1472          else
1473            {
1474              for (i=n=0; i<n2; ++i)
1475                if (!n || uniq[n-1] != sorted[i])
1476                  uniq[n++] = sorted[i];
1477            }
1478
1479          tr_free (sorted);
1480        }
1481    }
1482
1483  /* cleanup */
1484  tr_list_free (&ranges, tr_free);
1485  tr_free (str);
1486
1487  /* return the result */
1488  *setmeCount = n;
1489  return uniq;
1490}
1491
1492/***
1493****
1494***/
1495
1496double
1497tr_truncd (double x, int precision)
1498{
1499  char * pt;
1500  char buf[128];
1501  const int max_precision = (int) log10 (1.0 / DBL_EPSILON) - 1;
1502  tr_snprintf (buf, sizeof (buf), "%.*f", max_precision, x);
1503  if ((pt = strstr (buf, localeconv ()->decimal_point)))
1504    pt[precision ? precision+1 : 0] = '\0';
1505  return atof (buf);
1506}
1507
1508/* return a truncated double as a string */
1509static char*
1510tr_strtruncd (char * buf, double x, int precision, size_t buflen)
1511{
1512  tr_snprintf (buf, buflen, "%.*f", precision, tr_truncd (x, precision));
1513  return buf;
1514}
1515
1516char*
1517tr_strpercent (char * buf, double x, size_t buflen)
1518{
1519  if (x < 100.0)
1520    tr_strtruncd (buf, x, 1, buflen);
1521  else
1522    tr_strtruncd (buf, x, 0, buflen);
1523
1524  return buf;
1525}
1526
1527char*
1528tr_strratio (char * buf, size_t buflen, double ratio, const char * infinity)
1529{
1530  if ((int)ratio == TR_RATIO_NA)
1531    tr_strlcpy (buf, _("None"), buflen);
1532  else if ((int)ratio == TR_RATIO_INF)
1533    tr_strlcpy (buf, infinity, buflen);
1534  else
1535    tr_strpercent (buf, ratio, buflen);
1536
1537  return buf;
1538}
1539
1540/***
1541****
1542***/
1543
1544int
1545tr_moveFile (const char * oldpath, const char * newpath, bool * renamed)
1546{
1547  int in;
1548  int out;
1549  char * buf;
1550  tr_sys_path_info info;
1551  off_t bytesLeft;
1552  const size_t buflen = 1024 * 128; /* 128 KiB buffer */
1553  tr_error * error = NULL;
1554
1555  /* make sure the old file exists */
1556  if (!tr_sys_path_get_info (oldpath, 0, &info, &error))
1557    {
1558      const int err = error->code;
1559      tr_error_free (error);
1560      errno = err;
1561      return -1;
1562    }
1563  if (info.type != TR_SYS_PATH_IS_FILE)
1564    {
1565      errno = ENOENT;
1566      return -1;
1567    }
1568  bytesLeft = info.size;
1569
1570  /* make sure the target directory exists */
1571  {
1572    char * newdir = tr_sys_path_dirname (newpath, NULL);
1573    int i = tr_mkdirp (newdir, 0777);
1574    tr_free (newdir);
1575    if (i)
1576      return i;
1577  }
1578
1579  /* they might be on the same filesystem... */
1580  {
1581    const bool i = tr_sys_path_rename (oldpath, newpath, NULL);
1582    if (renamed != NULL)
1583      *renamed = i;
1584    if (i)
1585      return 0;
1586  }
1587
1588  /* copy the file */
1589  in = tr_open_file_for_scanning (oldpath);
1590  out = tr_open_file_for_writing (newpath);
1591  buf = tr_valloc (buflen);
1592  while (bytesLeft > 0)
1593    {
1594      ssize_t bytesWritten;
1595      const off_t bytesThisPass = MIN (bytesLeft, (off_t)buflen);
1596      const int numRead = read (in, buf, bytesThisPass);
1597      if (numRead < 0)
1598        break;
1599      bytesWritten = write (out, buf, numRead);
1600      if (bytesWritten < 0)
1601        break;
1602      bytesLeft -= bytesWritten;
1603    }
1604
1605  /* cleanup */
1606  tr_free (buf);
1607  tr_close_file (out);
1608  tr_close_file (in);
1609  if (bytesLeft != 0)
1610    return -1;
1611
1612  tr_sys_path_remove (oldpath, NULL);
1613  return 0;
1614}
1615
1616/***
1617****
1618***/
1619
1620void*
1621tr_valloc (size_t bufLen)
1622{
1623  size_t allocLen;
1624  void * buf = NULL;
1625  static size_t pageSize = 0;
1626
1627  if (!pageSize)
1628    {
1629#ifdef HAVE_GETPAGESIZE
1630      pageSize = (size_t) getpagesize ();
1631#else /* guess */
1632      pageSize = 4096;
1633#endif
1634    }
1635
1636  allocLen = pageSize;
1637  while (allocLen < bufLen)
1638    allocLen += pageSize;
1639
1640#ifdef HAVE_POSIX_MEMALIGN
1641  if (!buf)
1642    if (posix_memalign (&buf, pageSize, allocLen))
1643      buf = NULL; /* just retry with valloc/malloc */
1644#endif
1645#ifdef HAVE_VALLOC
1646  if (!buf)
1647    buf = valloc (allocLen);
1648#endif
1649  if (!buf)
1650    buf = tr_malloc (allocLen);
1651
1652  return buf;
1653}
1654
1655/***
1656****
1657***/
1658
1659uint64_t
1660tr_htonll (uint64_t x)
1661{
1662#ifdef HAVE_HTONLL
1663  return htonll (x);
1664#else
1665  /* fallback code by bdonlan at
1666   * http://stackoverflow.com/questions/809902/64-bit-ntohl-in-c/875505#875505 */
1667  union { uint32_t lx[2]; uint64_t llx; } u;
1668  u.lx[0] = htonl (x >> 32);
1669  u.lx[1] = htonl (x & 0xFFFFFFFFULL);
1670  return u.llx;
1671#endif
1672}
1673
1674uint64_t
1675tr_ntohll (uint64_t x)
1676{
1677#ifdef HAVE_NTOHLL
1678  return ntohll (x);
1679#else
1680  /* fallback code by bdonlan at
1681   * http://stackoverflow.com/questions/809902/64-bit-ntohl-in-c/875505#875505 */
1682  union { uint32_t lx[2]; uint64_t llx; } u;
1683  u.llx = x;
1684  return ((uint64_t)ntohl (u.lx[0]) << 32) | (uint64_t)ntohl (u.lx[1]);
1685#endif
1686}
1687
1688/***
1689****
1690****
1691****
1692***/
1693
1694struct formatter_unit
1695{
1696  char * name;
1697  int64_t value;
1698};
1699
1700struct formatter_units
1701{
1702  struct formatter_unit units[4];
1703};
1704
1705enum { TR_FMT_KB, TR_FMT_MB, TR_FMT_GB, TR_FMT_TB };
1706
1707static void
1708formatter_init (struct formatter_units * units,
1709                unsigned int kilo,
1710                const char * kb, const char * mb,
1711                const char * gb, const char * tb)
1712{
1713  uint64_t value;
1714
1715  value = kilo;
1716  units->units[TR_FMT_KB].name = tr_strdup (kb);
1717  units->units[TR_FMT_KB].value = value;
1718
1719  value *= kilo;
1720  units->units[TR_FMT_MB].name = tr_strdup (mb);
1721  units->units[TR_FMT_MB].value = value;
1722
1723  value *= kilo;
1724  units->units[TR_FMT_GB].name = tr_strdup (gb);
1725  units->units[TR_FMT_GB].value = value;
1726
1727  value *= kilo;
1728  units->units[TR_FMT_TB].name = tr_strdup (tb);
1729  units->units[TR_FMT_TB].value = value;
1730}
1731
1732static char*
1733formatter_get_size_str (const struct formatter_units * u,
1734                        char * buf, int64_t bytes, size_t buflen)
1735{
1736  int precision;
1737  double value;
1738  const char * units;
1739  const struct formatter_unit * unit;
1740
1741       if (bytes < u->units[1].value) unit = &u->units[0];
1742  else if (bytes < u->units[2].value) unit = &u->units[1];
1743  else if (bytes < u->units[3].value) unit = &u->units[2];
1744  else                                unit = &u->units[3];
1745
1746  value = (double)bytes / unit->value;
1747  units = unit->name;
1748
1749  if (unit->value == 1)
1750    precision = 0;
1751  else if (value < 100)
1752    precision = 2;
1753  else
1754    precision = 1;
1755
1756  tr_snprintf (buf, buflen, "%.*f %s", precision, value, units);
1757  return buf;
1758}
1759
1760static struct formatter_units size_units;
1761
1762void
1763tr_formatter_size_init (unsigned int kilo,
1764                        const char * kb, const char * mb,
1765                        const char * gb, const char * tb)
1766{
1767  formatter_init (&size_units, kilo, kb, mb, gb, tb);
1768}
1769
1770char*
1771tr_formatter_size_B (char * buf, int64_t bytes, size_t buflen)
1772{
1773  return formatter_get_size_str (&size_units, buf, bytes, buflen);
1774}
1775
1776static struct formatter_units speed_units;
1777
1778unsigned int tr_speed_K = 0u;
1779
1780void
1781tr_formatter_speed_init (unsigned int kilo,
1782                         const char * kb, const char * mb,
1783                         const char * gb, const char * tb)
1784{
1785  tr_speed_K = kilo;
1786  formatter_init (&speed_units, kilo, kb, mb, gb, tb);
1787}
1788
1789char*
1790tr_formatter_speed_KBps (char * buf, double KBps, size_t buflen)
1791{
1792  const double K = speed_units.units[TR_FMT_KB].value;
1793  double speed = KBps;
1794
1795  if (speed <= 999.95) /* 0.0 KB to 999.9 KB */
1796    {
1797      tr_snprintf (buf, buflen, "%d %s", (int)speed, speed_units.units[TR_FMT_KB].name);
1798    }
1799  else
1800    {
1801      speed /= K;
1802
1803      if (speed <= 99.995) /* 0.98 MB to 99.99 MB */
1804        tr_snprintf (buf, buflen, "%.2f %s", speed, speed_units.units[TR_FMT_MB].name);
1805      else if (speed <= 999.95) /* 100.0 MB to 999.9 MB */
1806        tr_snprintf (buf, buflen, "%.1f %s", speed, speed_units.units[TR_FMT_MB].name);
1807      else
1808        tr_snprintf (buf, buflen, "%.1f %s", speed/K, speed_units.units[TR_FMT_GB].name);
1809    }
1810
1811  return buf;
1812}
1813
1814static struct formatter_units mem_units;
1815
1816unsigned int tr_mem_K = 0u;
1817
1818void
1819tr_formatter_mem_init (unsigned int kilo,
1820                       const char * kb, const char * mb,
1821                       const char * gb, const char * tb)
1822{
1823  tr_mem_K = kilo;
1824  formatter_init (&mem_units, kilo, kb, mb, gb, tb);
1825}
1826
1827char*
1828tr_formatter_mem_B (char * buf, int64_t bytes_per_second, size_t buflen)
1829{
1830  return formatter_get_size_str (&mem_units, buf, bytes_per_second, buflen);
1831}
1832
1833void
1834tr_formatter_get_units (void * vdict)
1835{
1836  int i;
1837  tr_variant * l;
1838  tr_variant * dict = vdict;
1839
1840  tr_variantDictReserve (dict, 6);
1841
1842  tr_variantDictAddInt (dict, TR_KEY_memory_bytes, mem_units.units[TR_FMT_KB].value);
1843  l = tr_variantDictAddList (dict, TR_KEY_memory_units, 4);
1844  for (i=0; i<4; i++)
1845    tr_variantListAddStr (l, mem_units.units[i].name);
1846
1847  tr_variantDictAddInt (dict, TR_KEY_size_bytes,   size_units.units[TR_FMT_KB].value);
1848  l = tr_variantDictAddList (dict, TR_KEY_size_units, 4);
1849  for (i=0; i<4; i++)
1850    tr_variantListAddStr (l, size_units.units[i].name);
1851
1852  tr_variantDictAddInt (dict, TR_KEY_speed_bytes,  speed_units.units[TR_FMT_KB].value);
1853  l = tr_variantDictAddList (dict, TR_KEY_speed_units, 4);
1854  for (i=0; i<4; i++)
1855    tr_variantListAddStr (l, speed_units.units[i].name);
1856}
1857
Note: See TracBrowser for help on using the repository browser.