source: trunk/libtransmission/makemeta.c @ 13683

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

(trunk, libT) first drop of the tr_quark patch.

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