source: trunk/libtransmission/makemeta.c @ 13800

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

(utils) #4137 'support user-defined piece sizes in transmission-create' -- done. Initial patch by lav.

  • Property svn:keywords set to Date Rev Author Id
File size: 14.8 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 13800 2013-01-17 18:55:51Z 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    tr_metaInfoBuilderSetPieceSize (ret, bestPieceSize (ret->totalSize));
166
167    return ret;
168}
169
170void
171tr_metaInfoBuilderSetPieceSize (tr_metainfo_builder * b,
172                                uint32_t              bytes)
173{
174  b->pieceSize = bytes;
175
176  b->pieceCount = (int)(b->totalSize / b->pieceSize);
177  if (b->totalSize % b->pieceSize)
178    ++b->pieceCount;
179}
180
181
182void
183tr_metaInfoBuilderFree (tr_metainfo_builder * builder)
184{
185    if (builder)
186    {
187        tr_file_index_t t;
188        int             i;
189        for (t = 0; t < builder->fileCount; ++t)
190            tr_free (builder->files[t].filename);
191        tr_free (builder->files);
192        tr_free (builder->top);
193        tr_free (builder->comment);
194        for (i = 0; i < builder->trackerCount; ++i)
195            tr_free (builder->trackers[i].announce);
196        tr_free (builder->trackers);
197        tr_free (builder->outputFile);
198        tr_free (builder);
199    }
200}
201
202/****
203*****
204****/
205
206static uint8_t*
207getHashInfo (tr_metainfo_builder * b)
208{
209    uint32_t fileIndex = 0;
210    uint8_t *ret = tr_new0 (uint8_t, SHA_DIGEST_LENGTH * b->pieceCount);
211    uint8_t *walk = ret;
212    uint8_t *buf;
213    uint64_t totalRemain;
214    uint64_t off = 0;
215    int fd;
216
217    if (!b->totalSize)
218        return ret;
219
220    buf = tr_valloc (b->pieceSize);
221    b->pieceIndex = 0;
222    totalRemain = b->totalSize;
223    fd = tr_open_file_for_scanning (b->files[fileIndex].filename);
224    if (fd < 0)
225    {
226        b->my_errno = errno;
227        tr_strlcpy (b->errfile,
228                    b->files[fileIndex].filename,
229                    sizeof (b->errfile));
230        b->result = TR_MAKEMETA_IO_READ;
231        tr_free (buf);
232        tr_free (ret);
233        return NULL;
234    }
235    while (totalRemain)
236    {
237        uint8_t * bufptr = buf;
238        const uint32_t thisPieceSize = (uint32_t) MIN (b->pieceSize, totalRemain);
239        uint32_t leftInPiece = thisPieceSize;
240
241        assert (b->pieceIndex < b->pieceCount);
242
243        while (leftInPiece)
244        {
245            const size_t n_this_pass = (size_t) MIN ((b->files[fileIndex].size - off), leftInPiece);
246            const ssize_t n_read = read (fd, bufptr, n_this_pass);
247            bufptr += n_read;
248            off += n_read;
249            leftInPiece -= n_read;
250            if (off == b->files[fileIndex].size)
251            {
252                off = 0;
253                tr_close_file (fd);
254                fd = -1;
255                if (++fileIndex < b->fileCount)
256                {
257                    fd = tr_open_file_for_scanning (b->files[fileIndex].filename);
258                    if (fd < 0)
259                    {
260                        b->my_errno = errno;
261                        tr_strlcpy (b->errfile,
262                                    b->files[fileIndex].filename,
263                                    sizeof (b->errfile));
264                        b->result = TR_MAKEMETA_IO_READ;
265                        tr_free (buf);
266                        tr_free (ret);
267                        return NULL;
268                    }
269                }
270            }
271        }
272
273        assert (bufptr - buf == (int)thisPieceSize);
274        assert (leftInPiece == 0);
275        tr_sha1 (walk, buf, thisPieceSize, NULL);
276        walk += SHA_DIGEST_LENGTH;
277
278        if (b->abortFlag)
279        {
280            b->result = TR_MAKEMETA_CANCELLED;
281            break;
282        }
283
284        totalRemain -= thisPieceSize;
285        ++b->pieceIndex;
286    }
287
288    assert (b->abortFlag
289          || (walk - ret == (int)(SHA_DIGEST_LENGTH * b->pieceCount)));
290    assert (b->abortFlag || !totalRemain);
291
292    if (fd >= 0)
293        tr_close_file (fd);
294
295    tr_free (buf);
296    return ret;
297}
298
299static void
300getFileInfo (const char *                     topFile,
301             const tr_metainfo_builder_file * file,
302             tr_variant *                        uninitialized_length,
303             tr_variant *                        uninitialized_path)
304{
305    size_t offset;
306
307    /* get the file size */
308    tr_variantInitInt (uninitialized_length, file->size);
309
310    /* how much of file->filename to walk past */
311    offset = strlen (topFile);
312    if (offset>0 && topFile[offset-1]!=TR_PATH_DELIMITER)
313        ++offset; /* +1 for the path delimiter */
314
315    /* build the path list */
316    tr_variantInitList (uninitialized_path, 0);
317    if (strlen (file->filename) > offset) {
318        char * filename = tr_strdup (file->filename + offset);
319        char * walk = filename;
320        const char * token;
321        while ((token = tr_strsep (&walk, TR_PATH_DELIMITER_STR)))
322            tr_variantListAddStr (uninitialized_path, token);
323        tr_free (filename);
324    }
325}
326
327static void
328makeInfoDict (tr_variant *             dict,
329              tr_metainfo_builder * builder)
330{
331    uint8_t * pch;
332    char    * base;
333
334    tr_variantDictReserve (dict, 5);
335
336    if (builder->isSingleFile)
337    {
338        tr_variantDictAddInt (dict, TR_KEY_length, builder->files[0].size);
339    }
340    else /* root node is a directory */
341    {
342        uint32_t  i;
343        tr_variant * list = tr_variantDictAddList (dict, TR_KEY_files,
344                                             builder->fileCount);
345        for (i = 0; i < builder->fileCount; ++i)
346        {
347            tr_variant * d = tr_variantListAddDict (list, 2);
348            tr_variant * length = tr_variantDictAdd (d, TR_KEY_length);
349            tr_variant * pathVal = tr_variantDictAdd (d, TR_KEY_path);
350            getFileInfo (builder->top, &builder->files[i], length, pathVal);
351        }
352    }
353
354    base = tr_basename (builder->top);
355    tr_variantDictAddStr (dict, TR_KEY_name, base);
356    tr_free (base);
357
358    tr_variantDictAddInt (dict, TR_KEY_piece_length, builder->pieceSize);
359
360    if ((pch = getHashInfo (builder)))
361    {
362        tr_variantDictAddRaw (dict, TR_KEY_pieces, pch,
363                              SHA_DIGEST_LENGTH * builder->pieceCount);
364        tr_free (pch);
365    }
366
367    tr_variantDictAddInt (dict, TR_KEY_private, builder->isPrivate ? 1 : 0);
368}
369
370static void
371tr_realMakeMetaInfo (tr_metainfo_builder * builder)
372{
373    int     i;
374    tr_variant top;
375
376    /* allow an empty set, but if URLs *are* listed, verify them. #814, #971 */
377    for (i = 0; i < builder->trackerCount && !builder->result; ++i) {
378        if (!tr_urlIsValidTracker (builder->trackers[i].announce)) {
379            tr_strlcpy (builder->errfile, builder->trackers[i].announce,
380                       sizeof (builder->errfile));
381            builder->result = TR_MAKEMETA_URL;
382        }
383    }
384
385    tr_variantInitDict (&top, 6);
386
387    if (!builder->fileCount || !builder->totalSize ||
388        !builder->pieceSize || !builder->pieceCount)
389    {
390        builder->errfile[0] = '\0';
391        builder->my_errno = ENOENT;
392        builder->result = TR_MAKEMETA_IO_READ;
393        builder->isDone = true;
394    }
395
396    if (!builder->result && builder->trackerCount)
397    {
398        int       prevTier = -1;
399        tr_variant * tier = NULL;
400
401        if (builder->trackerCount > 1)
402        {
403            tr_variant * annList = tr_variantDictAddList (&top, TR_KEY_announce_list, 0);
404            for (i = 0; i < builder->trackerCount; ++i)
405            {
406                if (prevTier != builder->trackers[i].tier)
407                {
408                    prevTier = builder->trackers[i].tier;
409                    tier = tr_variantListAddList (annList, 0);
410                }
411                tr_variantListAddStr (tier, builder->trackers[i].announce);
412            }
413        }
414
415        tr_variantDictAddStr (&top, TR_KEY_announce, builder->trackers[0].announce);
416    }
417
418    if (!builder->result && !builder->abortFlag)
419    {
420        if (builder->comment && *builder->comment)
421            tr_variantDictAddStr (&top, TR_KEY_comment, builder->comment);
422        tr_variantDictAddStr (&top, TR_KEY_created_by,
423                           TR_NAME "/" LONG_VERSION_STRING);
424        tr_variantDictAddInt (&top, TR_KEY_creation_date, time (NULL));
425        tr_variantDictAddStr (&top, TR_KEY_encoding, "UTF-8");
426        makeInfoDict (tr_variantDictAddDict (&top, TR_KEY_info, 666), builder);
427    }
428
429    /* save the file */
430    if (!builder->result && !builder->abortFlag)
431    {
432        if (tr_variantToFile (&top, TR_VARIANT_FMT_BENC, builder->outputFile))
433        {
434            builder->my_errno = errno;
435            tr_strlcpy (builder->errfile, builder->outputFile,
436                       sizeof (builder->errfile));
437            builder->result = TR_MAKEMETA_IO_WRITE;
438        }
439    }
440
441    /* cleanup */
442    tr_variantFree (&top);
443    if (builder->abortFlag)
444        builder->result = TR_MAKEMETA_CANCELLED;
445    builder->isDone = 1;
446}
447
448/***
449****
450****  A threaded builder queue
451****
452***/
453
454static tr_metainfo_builder * queue = NULL;
455
456static tr_thread *           workerThread = NULL;
457
458static tr_lock*
459getQueueLock (void)
460{
461    static tr_lock * lock = NULL;
462
463    if (!lock)
464        lock = tr_lockNew ();
465
466    return lock;
467}
468
469static void
470makeMetaWorkerFunc (void * unused UNUSED)
471{
472    for (;;)
473    {
474        tr_metainfo_builder * builder = NULL;
475
476        /* find the next builder to process */
477        tr_lock * lock = getQueueLock ();
478        tr_lockLock (lock);
479        if (queue)
480        {
481            builder = queue;
482            queue = queue->nextBuilder;
483        }
484        tr_lockUnlock (lock);
485
486        /* if no builders, this worker thread is done */
487        if (builder == NULL)
488            break;
489
490        tr_realMakeMetaInfo (builder);
491    }
492
493    workerThread = NULL;
494}
495
496void
497tr_makeMetaInfo (tr_metainfo_builder *   builder,
498                 const char *            outputFile,
499                 const tr_tracker_info * trackers,
500                 int                     trackerCount,
501                 const char *            comment,
502                 int                     isPrivate)
503{
504    int       i;
505    tr_lock * lock;
506
507    /* free any variables from a previous run */
508    for (i = 0; i < builder->trackerCount; ++i)
509        tr_free (builder->trackers[i].announce);
510    tr_free (builder->trackers);
511    tr_free (builder->comment);
512    tr_free (builder->outputFile);
513
514    /* initialize the builder variables */
515    builder->abortFlag = 0;
516    builder->result = 0;
517    builder->isDone = 0;
518    builder->pieceIndex = 0;
519    builder->trackerCount = trackerCount;
520    builder->trackers = tr_new0 (tr_tracker_info, builder->trackerCount);
521    for (i = 0; i < builder->trackerCount; ++i) {
522        builder->trackers[i].tier = trackers[i].tier;
523        builder->trackers[i].announce = tr_strdup (trackers[i].announce);
524    }
525    builder->comment = tr_strdup (comment);
526    builder->isPrivate = isPrivate;
527    if (outputFile && *outputFile)
528        builder->outputFile = tr_strdup (outputFile);
529    else
530        builder->outputFile = tr_strdup_printf ("%s.torrent", builder->top);
531
532    /* enqueue the builder */
533    lock = getQueueLock ();
534    tr_lockLock (lock);
535    builder->nextBuilder = queue;
536    queue = builder;
537    if (!workerThread)
538        workerThread = tr_threadNew (makeMetaWorkerFunc, NULL);
539    tr_lockUnlock (lock);
540}
541
Note: See TracBrowser for help on using the repository browser.