source: trunk/libtransmission/makemeta.c @ 13625

Last change on this file since 13625 was 13625, checked in by jordan, 9 years ago

Follow more common whitespace style conventions in the C code (libtransmission, daemon, utils, cli, gtk).

  • Property svn:keywords set to Date Rev Author Id
File size: 14.5 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 13625 2012-12-05 17:29:46Z 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 "bencode.h"
31#include "makemeta.h"
32#include "platform.h" /* threads, locks */
33#include "utils.h" /* buildpath */
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_benc *                        uninitialized_length,
294             tr_benc *                        uninitialized_path)
295{
296    size_t offset;
297
298    /* get the file size */
299    tr_bencInitInt (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_bencInitList (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_bencListAddStr (uninitialized_path, token);
314        tr_free (filename);
315    }
316}
317
318static void
319makeInfoDict (tr_benc *             dict,
320              tr_metainfo_builder * builder)
321{
322    uint8_t * pch;
323    char    * base;
324
325    tr_bencDictReserve (dict, 5);
326
327    if (builder->isSingleFile)
328    {
329        tr_bencDictAddInt (dict, "length", builder->files[0].size);
330    }
331    else /* root node is a directory */
332    {
333        uint32_t  i;
334        tr_benc * list = tr_bencDictAddList (dict, "files",
335                                             builder->fileCount);
336        for (i = 0; i < builder->fileCount; ++i)
337        {
338            tr_benc * d = tr_bencListAddDict (list, 2);
339            tr_benc * length = tr_bencDictAdd (d, "length");
340            tr_benc * pathVal = tr_bencDictAdd (d, "path");
341            getFileInfo (builder->top, &builder->files[i], length, pathVal);
342        }
343    }
344
345    base = tr_basename (builder->top);
346    tr_bencDictAddStr (dict, "name", base);
347    tr_free (base);
348
349    tr_bencDictAddInt (dict, "piece length", builder->pieceSize);
350
351    if ((pch = getHashInfo (builder)))
352    {
353        tr_bencDictAddRaw (dict, "pieces", pch,
354                           SHA_DIGEST_LENGTH * builder->pieceCount);
355        tr_free (pch);
356    }
357
358    tr_bencDictAddInt (dict, "private", builder->isPrivate ? 1 : 0);
359}
360
361static void
362tr_realMakeMetaInfo (tr_metainfo_builder * builder)
363{
364    int     i;
365    tr_benc 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_bencInitDict (&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_benc * tier = NULL;
391
392        if (builder->trackerCount > 1)
393        {
394            tr_benc * annList = tr_bencDictAddList (&top, "announce-list",
395                                                    0);
396            for (i = 0; i < builder->trackerCount; ++i)
397            {
398                if (prevTier != builder->trackers[i].tier)
399                {
400                    prevTier = builder->trackers[i].tier;
401                    tier = tr_bencListAddList (annList, 0);
402                }
403                tr_bencListAddStr (tier, builder->trackers[i].announce);
404            }
405        }
406
407        tr_bencDictAddStr (&top, "announce", builder->trackers[0].announce);
408    }
409
410    if (!builder->result && !builder->abortFlag)
411    {
412        if (builder->comment && *builder->comment)
413            tr_bencDictAddStr (&top, "comment", builder->comment);
414        tr_bencDictAddStr (&top, "created by",
415                           TR_NAME "/" LONG_VERSION_STRING);
416        tr_bencDictAddInt (&top, "creation date", time (NULL));
417        tr_bencDictAddStr (&top, "encoding", "UTF-8");
418        makeInfoDict (tr_bencDictAddDict (&top, "info", 666), builder);
419    }
420
421    /* save the file */
422    if (!builder->result && !builder->abortFlag)
423    {
424        if (tr_bencToFile (&top, TR_FMT_BENC, builder->outputFile))
425        {
426            builder->my_errno = errno;
427            tr_strlcpy (builder->errfile, builder->outputFile,
428                       sizeof (builder->errfile));
429            builder->result = TR_MAKEMETA_IO_WRITE;
430        }
431    }
432
433    /* cleanup */
434    tr_bencFree (&top);
435    if (builder->abortFlag)
436        builder->result = TR_MAKEMETA_CANCELLED;
437    builder->isDone = 1;
438}
439
440/***
441****
442****  A threaded builder queue
443****
444***/
445
446static tr_metainfo_builder * queue = NULL;
447
448static tr_thread *           workerThread = NULL;
449
450static tr_lock*
451getQueueLock (void)
452{
453    static tr_lock * lock = NULL;
454
455    if (!lock)
456        lock = tr_lockNew ();
457
458    return lock;
459}
460
461static void
462makeMetaWorkerFunc (void * unused UNUSED)
463{
464    for (;;)
465    {
466        tr_metainfo_builder * builder = NULL;
467
468        /* find the next builder to process */
469        tr_lock * lock = getQueueLock ();
470        tr_lockLock (lock);
471        if (queue)
472        {
473            builder = queue;
474            queue = queue->nextBuilder;
475        }
476        tr_lockUnlock (lock);
477
478        /* if no builders, this worker thread is done */
479        if (builder == NULL)
480            break;
481
482        tr_realMakeMetaInfo (builder);
483    }
484
485    workerThread = NULL;
486}
487
488void
489tr_makeMetaInfo (tr_metainfo_builder *   builder,
490                 const char *            outputFile,
491                 const tr_tracker_info * trackers,
492                 int                     trackerCount,
493                 const char *            comment,
494                 int                     isPrivate)
495{
496    int       i;
497    tr_lock * lock;
498
499    /* free any variables from a previous run */
500    for (i = 0; i < builder->trackerCount; ++i)
501        tr_free (builder->trackers[i].announce);
502    tr_free (builder->trackers);
503    tr_free (builder->comment);
504    tr_free (builder->outputFile);
505
506    /* initialize the builder variables */
507    builder->abortFlag = 0;
508    builder->result = 0;
509    builder->isDone = 0;
510    builder->pieceIndex = 0;
511    builder->trackerCount = trackerCount;
512    builder->trackers = tr_new0 (tr_tracker_info, builder->trackerCount);
513    for (i = 0; i < builder->trackerCount; ++i) {
514        builder->trackers[i].tier = trackers[i].tier;
515        builder->trackers[i].announce = tr_strdup (trackers[i].announce);
516    }
517    builder->comment = tr_strdup (comment);
518    builder->isPrivate = isPrivate;
519    if (outputFile && *outputFile)
520        builder->outputFile = tr_strdup (outputFile);
521    else
522        builder->outputFile = tr_strdup_printf ("%s.torrent", builder->top);
523
524    /* enqueue the builder */
525    lock = getQueueLock ();
526    tr_lockLock (lock);
527    builder->nextBuilder = queue;
528    queue = builder;
529    if (!workerThread)
530        workerThread = tr_threadNew (makeMetaWorkerFunc, NULL);
531    tr_lockUnlock (lock);
532}
533
Note: See TracBrowser for help on using the repository browser.