source: trunk/libtransmission/makemeta.c

Last change on this file was 14717, checked in by mikedld, 5 years ago

Handle potential dirname/basename errors where needed

  • Property svn:keywords set to Date Rev Author Id
File size: 15.0 KB
Line 
1/*
2 * This file Copyright (C) 2010-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: makemeta.c 14717 2016-03-13 10:41:52Z mikedld $
8 */
9
10#include <assert.h>
11#include <errno.h>
12#include <stdlib.h> /* qsort */
13#include <string.h> /* strcmp, strlen */
14
15#include <event2/util.h> /* evutil_ascii_strcasecmp () */
16
17#include "transmission.h"
18#include "crypto-utils.h" /* tr_sha1 */
19#include "error.h"
20#include "file.h"
21#include "log.h"
22#include "session.h"
23#include "makemeta.h"
24#include "platform.h" /* threads, locks */
25#include "utils.h" /* buildpath */
26#include "variant.h"
27#include "version.h"
28
29/****
30*****
31****/
32
33struct FileList
34{
35  uint64_t size;
36  char * filename;
37  struct FileList * next;
38};
39
40static struct FileList *
41getFiles (const char      * dir,
42          const char      * base,
43          struct FileList * list)
44{
45  if (dir == NULL || base == NULL)
46    return NULL;
47
48  tr_sys_dir_t odir;
49  char * buf;
50  tr_sys_path_info info;
51  tr_error * error = NULL;
52
53  buf = tr_buildPath (dir, base, NULL);
54  if (!tr_sys_path_get_info (buf, 0, &info, &error))
55    {
56      tr_logAddError (_("Torrent Creator is skipping file \"%s\": %s"),
57                      buf, error->message);
58      tr_free (buf);
59      tr_error_free (error);
60      return list;
61    }
62
63  if (info.type == TR_SYS_PATH_IS_DIRECTORY &&
64      (odir = tr_sys_dir_open (buf, NULL)) != TR_BAD_SYS_DIR)
65    {
66      const char * name;
67      while ((name = tr_sys_dir_read_name (odir, NULL)) != NULL)
68        if (name[0] != '.') /* skip dotfiles */
69          list = getFiles (buf, name, list);
70      tr_sys_dir_close (odir, NULL);
71    }
72  else if (info.type == TR_SYS_PATH_IS_FILE && info.size > 0)
73    {
74      struct FileList * node = tr_new (struct FileList, 1);
75      node->size = info.size;
76      node->filename = tr_strdup (buf);
77      node->next = list;
78      list = node;
79    }
80
81  tr_free (buf);
82  return list;
83}
84
85static uint32_t
86bestPieceSize (uint64_t totalSize)
87{
88  const uint32_t KiB = 1024;
89  const uint32_t MiB = 1048576;
90  const uint32_t GiB = 1073741824;
91
92  if (totalSize >= (2 * GiB)) return   2 * MiB;
93  if (totalSize >= (1 * GiB)) return   1 * MiB;
94  if (totalSize >= (512 * MiB)) return 512 * KiB;
95  if (totalSize >= (350 * MiB)) return 256 * KiB;
96  if (totalSize >= (150 * MiB)) return 128 * KiB;
97  if (totalSize >= (50 * MiB)) return  64 * KiB;
98  return 32 * KiB;  /* less than 50 meg */
99}
100
101static int
102builderFileCompare (const void * va, const void * vb)
103{
104  const tr_metainfo_builder_file * a = va;
105  const tr_metainfo_builder_file * b = vb;
106
107  return evutil_ascii_strcasecmp (a->filename, b->filename);
108}
109
110tr_metainfo_builder*
111tr_metaInfoBuilderCreate (const char * topFileArg)
112{
113  int i;
114  struct FileList * files;
115  struct FileList * walk;
116  tr_metainfo_builder * ret = tr_new0 (tr_metainfo_builder, 1);
117
118  ret->top = tr_sys_path_resolve (topFileArg, NULL);
119
120  {
121    tr_sys_path_info info;
122    ret->isFolder = tr_sys_path_get_info (ret->top, 0, &info, NULL) &&
123                    info.type == TR_SYS_PATH_IS_DIRECTORY;
124  }
125
126  /* build a list of files containing top file and,
127     if it's a directory, all of its children */
128  {
129    char * dir = tr_sys_path_dirname (ret->top, NULL);
130    char * base = tr_sys_path_basename (ret->top, NULL);
131    files = getFiles (dir, base, NULL);
132    tr_free (base);
133    tr_free (dir);
134  }
135
136  for (walk=files; walk!=NULL; walk=walk->next)
137    ++ret->fileCount;
138
139  ret->files = tr_new0 (tr_metainfo_builder_file, ret->fileCount);
140
141  for (i=0, walk=files; walk!=NULL; ++i)
142    {
143      struct FileList *  tmp = walk;
144      tr_metainfo_builder_file * file = &ret->files[i];
145      walk = walk->next;
146      file->filename = tmp->filename;
147      file->size = tmp->size;
148      ret->totalSize += tmp->size;
149      tr_free (tmp);
150    }
151
152  qsort (ret->files,
153         ret->fileCount,
154         sizeof (tr_metainfo_builder_file),
155         builderFileCompare);
156
157  tr_metaInfoBuilderSetPieceSize (ret, bestPieceSize (ret->totalSize));
158
159  return ret;
160}
161
162static bool
163isValidPieceSize (uint32_t n)
164{
165  const bool isPowerOfTwo = !(n == 0) && !(n & (n - 1));
166
167  return isPowerOfTwo;
168}
169
170bool
171tr_metaInfoBuilderSetPieceSize (tr_metainfo_builder * b,
172                                uint32_t              bytes)
173{
174  if (!isValidPieceSize (bytes))
175    {
176      char wanted[32];
177      char gotten[32];
178      tr_formatter_mem_B (wanted, bytes, sizeof(wanted));
179      tr_formatter_mem_B (gotten, b->pieceSize, sizeof(gotten));
180      tr_logAddError (_("Failed to set piece size to %s, leaving it at %s"),
181                      wanted,
182                      gotten);
183      return false;
184    }
185
186  b->pieceSize = bytes;
187
188  b->pieceCount = (int)(b->totalSize / b->pieceSize);
189  if (b->totalSize % b->pieceSize)
190    ++b->pieceCount;
191
192  return true;
193}
194
195
196void
197tr_metaInfoBuilderFree (tr_metainfo_builder * builder)
198{
199  if (builder)
200    {
201      int i;
202      tr_file_index_t t;
203
204      for (t=0; t<builder->fileCount; ++t)
205        tr_free (builder->files[t].filename);
206      tr_free (builder->files);
207      tr_free (builder->top);
208      tr_free (builder->comment);
209      for (i=0; i<builder->trackerCount; ++i)
210        tr_free (builder->trackers[i].announce);
211      tr_free (builder->trackers);
212      tr_free (builder->outputFile);
213      tr_free (builder);
214    }
215}
216
217/****
218*****
219****/
220
221static uint8_t*
222getHashInfo (tr_metainfo_builder * b)
223{
224  uint32_t fileIndex = 0;
225  uint8_t *ret = tr_new0 (uint8_t, SHA_DIGEST_LENGTH * b->pieceCount);
226  uint8_t *walk = ret;
227  uint8_t *buf;
228  uint64_t totalRemain;
229  uint64_t off = 0;
230  tr_sys_file_t fd;
231  tr_error * error = NULL;
232
233  if (!b->totalSize)
234    return ret;
235
236  buf = tr_valloc (b->pieceSize);
237  b->pieceIndex = 0;
238  totalRemain = b->totalSize;
239  fd = tr_sys_file_open (b->files[fileIndex].filename, TR_SYS_FILE_READ |
240                         TR_SYS_FILE_SEQUENTIAL, 0, &error);
241  if (fd == TR_BAD_SYS_FILE)
242    {
243      b->my_errno = error->code;
244      tr_strlcpy (b->errfile,
245                  b->files[fileIndex].filename,
246                  sizeof (b->errfile));
247      b->result = TR_MAKEMETA_IO_READ;
248      tr_free (buf);
249      tr_free (ret);
250      tr_error_free (error);
251      return NULL;
252    }
253
254  while (totalRemain)
255    {
256      uint8_t * bufptr = buf;
257      const uint32_t thisPieceSize = (uint32_t) MIN (b->pieceSize, totalRemain);
258      uint64_t leftInPiece = thisPieceSize;
259
260      assert (b->pieceIndex < b->pieceCount);
261
262      while (leftInPiece)
263        {
264          const uint64_t n_this_pass = MIN (b->files[fileIndex].size - off, leftInPiece);
265          uint64_t n_read = 0;
266          tr_sys_file_read (fd, bufptr, n_this_pass, &n_read, NULL);
267          bufptr += n_read;
268          off += n_read;
269          leftInPiece -= n_read;
270          if (off == b->files[fileIndex].size)
271            {
272              off = 0;
273              tr_sys_file_close (fd, NULL);
274              fd = TR_BAD_SYS_FILE;
275              if (++fileIndex < b->fileCount)
276                {
277                  fd = tr_sys_file_open (b->files[fileIndex].filename, TR_SYS_FILE_READ |
278                                         TR_SYS_FILE_SEQUENTIAL, 0, &error);
279                  if (fd == TR_BAD_SYS_FILE)
280                    {
281                      b->my_errno = error->code;
282                      tr_strlcpy (b->errfile,
283                                  b->files[fileIndex].filename,
284                                  sizeof (b->errfile));
285                      b->result = TR_MAKEMETA_IO_READ;
286                      tr_free (buf);
287                      tr_free (ret);
288                      tr_error_free (error);
289                      return NULL;
290                    }
291                }
292            }
293        }
294
295      assert (bufptr - buf == (int)thisPieceSize);
296      assert (leftInPiece == 0);
297      tr_sha1 (walk, buf, thisPieceSize, NULL);
298      walk += SHA_DIGEST_LENGTH;
299
300      if (b->abortFlag)
301        {
302          b->result = TR_MAKEMETA_CANCELLED;
303          break;
304        }
305
306      totalRemain -= thisPieceSize;
307      ++b->pieceIndex;
308    }
309
310  assert (b->abortFlag
311        || (walk - ret == (int)(SHA_DIGEST_LENGTH * b->pieceCount)));
312  assert (b->abortFlag || !totalRemain);
313
314  if (fd != TR_BAD_SYS_FILE)
315    tr_sys_file_close (fd, NULL);
316
317  tr_free (buf);
318  return ret;
319}
320
321static void
322getFileInfo (const char                      * topFile,
323             const tr_metainfo_builder_file  * file,
324             tr_variant                      * uninitialized_length,
325             tr_variant                      * uninitialized_path)
326{
327  size_t offset;
328
329  /* get the file size */
330  tr_variantInitInt (uninitialized_length, file->size);
331
332  /* how much of file->filename to walk past */
333  offset = strlen (topFile);
334  if (offset>0 && topFile[offset-1]!=TR_PATH_DELIMITER)
335    ++offset; /* +1 for the path delimiter */
336
337  /* build the path list */
338  tr_variantInitList (uninitialized_path, 0);
339  if (strlen (file->filename) > offset)
340    {
341      char * filename = tr_strdup (file->filename + offset);
342      char * walk = filename;
343      const char * token;
344      while ((token = tr_strsep (&walk, TR_PATH_DELIMITER_STR)))
345        if (*token)
346          tr_variantListAddStr (uninitialized_path, token);
347      tr_free (filename);
348    }
349}
350
351static void
352makeInfoDict (tr_variant          * dict,
353              tr_metainfo_builder * builder)
354{
355  char * base;
356  uint8_t * pch;
357
358  tr_variantDictReserve (dict, 5);
359
360  if (builder->isFolder) /* root node is a directory */
361    {
362      uint32_t  i;
363      tr_variant * list = tr_variantDictAddList (dict, TR_KEY_files,
364                                                 builder->fileCount);
365      for (i=0; i<builder->fileCount; ++i)
366        {
367          tr_variant * d = tr_variantListAddDict (list, 2);
368          tr_variant * length = tr_variantDictAdd (d, TR_KEY_length);
369          tr_variant * pathVal = tr_variantDictAdd (d, TR_KEY_path);
370          getFileInfo (builder->top, &builder->files[i], length, pathVal);
371        }
372    }
373  else
374    {
375      tr_variantDictAddInt (dict, TR_KEY_length, builder->files[0].size);
376    }
377
378  base = tr_sys_path_basename (builder->top, NULL);
379  if (base != NULL)
380    {
381      tr_variantDictAddStr (dict, TR_KEY_name, base);
382      tr_free (base);
383    }
384
385  tr_variantDictAddInt (dict, TR_KEY_piece_length, builder->pieceSize);
386
387  if ((pch = getHashInfo (builder)))
388    {
389      tr_variantDictAddRaw (dict, TR_KEY_pieces,
390                            pch,
391                            SHA_DIGEST_LENGTH * builder->pieceCount);
392      tr_free (pch);
393    }
394
395  tr_variantDictAddInt (dict, TR_KEY_private, builder->isPrivate ? 1 : 0);
396}
397
398static void
399tr_realMakeMetaInfo (tr_metainfo_builder * builder)
400{
401  int i;
402  tr_variant top;
403
404  /* allow an empty set, but if URLs *are* listed, verify them. #814, #971 */
405  for (i=0; i<builder->trackerCount && !builder->result; ++i)
406    {
407      if (!tr_urlIsValidTracker (builder->trackers[i].announce))
408        {
409          tr_strlcpy (builder->errfile, builder->trackers[i].announce,
410                      sizeof (builder->errfile));
411          builder->result = TR_MAKEMETA_URL;
412        }
413    }
414
415  tr_variantInitDict (&top, 6);
416
417  if (!builder->fileCount || !builder->totalSize ||
418      !builder->pieceSize || !builder->pieceCount)
419    {
420      builder->errfile[0] = '\0';
421      builder->my_errno = ENOENT;
422      builder->result = TR_MAKEMETA_IO_READ;
423      builder->isDone = true;
424    }
425
426  if (!builder->result && builder->trackerCount)
427    {
428      int prevTier = -1;
429      tr_variant * tier = NULL;
430
431      if (builder->trackerCount > 1)
432        {
433          tr_variant * annList = tr_variantDictAddList (&top, TR_KEY_announce_list, 0);
434          for (i=0; i<builder->trackerCount; ++i)
435            {
436              if (prevTier != builder->trackers[i].tier)
437                {
438                  prevTier = builder->trackers[i].tier;
439                  tier = tr_variantListAddList (annList, 0);
440                }
441              tr_variantListAddStr (tier, builder->trackers[i].announce);
442            }
443        }
444
445      tr_variantDictAddStr (&top, TR_KEY_announce, builder->trackers[0].announce);
446    }
447
448  if (!builder->result && !builder->abortFlag)
449    {
450      if (builder->comment && *builder->comment)
451        tr_variantDictAddStr (&top, TR_KEY_comment, builder->comment);
452      tr_variantDictAddStr (&top, TR_KEY_created_by,
453                            TR_NAME "/" LONG_VERSION_STRING);
454      tr_variantDictAddInt (&top, TR_KEY_creation_date, time (NULL));
455      tr_variantDictAddStr (&top, TR_KEY_encoding, "UTF-8");
456      makeInfoDict (tr_variantDictAddDict (&top, TR_KEY_info, 666), builder);
457    }
458
459  /* save the file */
460  if (!builder->result && !builder->abortFlag)
461    {
462      if (tr_variantToFile (&top, TR_VARIANT_FMT_BENC, builder->outputFile))
463        {
464          builder->my_errno = errno;
465          tr_strlcpy (builder->errfile, builder->outputFile,
466                     sizeof (builder->errfile));
467          builder->result = TR_MAKEMETA_IO_WRITE;
468        }
469    }
470
471  /* cleanup */
472  tr_variantFree (&top);
473  if (builder->abortFlag)
474    builder->result = TR_MAKEMETA_CANCELLED;
475  builder->isDone = true;
476}
477
478/***
479****
480****  A threaded builder queue
481****
482***/
483
484static tr_metainfo_builder * queue = NULL;
485
486static tr_thread * workerThread = NULL;
487
488static tr_lock*
489getQueueLock (void)
490{
491  static tr_lock * lock = NULL;
492
493  if (!lock)
494    lock = tr_lockNew ();
495
496  return lock;
497}
498
499static void
500makeMetaWorkerFunc (void * unused UNUSED)
501{
502  for (;;)
503    {
504      tr_metainfo_builder * builder = NULL;
505
506      /* find the next builder to process */
507      tr_lock * lock = getQueueLock ();
508      tr_lockLock (lock);
509      if (queue)
510        {
511          builder = queue;
512          queue = queue->nextBuilder;
513        }
514      tr_lockUnlock (lock);
515
516      /* if no builders, this worker thread is done */
517      if (builder == NULL)
518        break;
519
520      tr_realMakeMetaInfo (builder);
521    }
522
523  workerThread = NULL;
524}
525
526void
527tr_makeMetaInfo (tr_metainfo_builder   * builder,
528                 const char            * outputFile,
529                 const tr_tracker_info * trackers,
530                 int                     trackerCount,
531                 const char            * comment,
532                 bool                    isPrivate)
533{
534  int i;
535  tr_lock * lock;
536
537  assert (tr_isBool (isPrivate));
538
539  /* free any variables from a previous run */
540  for (i=0; i<builder->trackerCount; ++i)
541    tr_free (builder->trackers[i].announce);
542  tr_free (builder->trackers);
543  tr_free (builder->comment);
544  tr_free (builder->outputFile);
545
546  /* initialize the builder variables */
547  builder->abortFlag = false;
548  builder->result = 0;
549  builder->isDone = false;
550  builder->pieceIndex = 0;
551  builder->trackerCount = trackerCount;
552  builder->trackers = tr_new0 (tr_tracker_info, builder->trackerCount);
553  for (i=0; i<builder->trackerCount; ++i)
554    {
555      builder->trackers[i].tier = trackers[i].tier;
556      builder->trackers[i].announce = tr_strdup (trackers[i].announce);
557    }
558  builder->comment = tr_strdup (comment);
559  builder->isPrivate = isPrivate;
560  if (outputFile && *outputFile)
561    builder->outputFile = tr_strdup (outputFile);
562  else
563    builder->outputFile = tr_strdup_printf ("%s.torrent", builder->top);
564
565  /* enqueue the builder */
566  lock = getQueueLock ();
567  tr_lockLock (lock);
568  builder->nextBuilder = queue;
569  queue = builder;
570  if (!workerThread)
571    workerThread = tr_threadNew (makeMetaWorkerFunc, NULL);
572  tr_lockUnlock (lock);
573}
574
Note: See TracBrowser for help on using the repository browser.