cascade of code simplifications (6) and misc code style fixes
[buildtorrent:buildtorrent.git] / buildtorrent.c
1 /*
2 buildtorrent -- torrent file creation program
3 Copyright (C) 2007,2008,2009,2010 Claude Heiland-Allen
4
5
6 This program is free software; you can redistribute it and/or
7 modify it under the terms of the GNU General Public License
8 as published by the Free Software Foundation; either version 2
9 of the License, or (at your option) any later version.
10
11 This program is distributed in the hope that it will be useful,
12 but WITHOUT ANY WARRANTY; without even the implied warranty of
13 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
14 GNU General Public License for more details.
15
16 You should have received a copy of the GNU General Public License
17 along with this program; if not, write to the Free Software
18 Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
19 */
20
21 /* define this as GNU source, so that the versionsort will work with scandir */
22 #define _GNU_SOURCE
23
24 #include "config.h"
25
26 #include <assert.h>
27 #include <getopt.h>
28 #include <stdio.h>
29 #include <stdlib.h>
30 #include <string.h>
31 #include <sys/stat.h>
32 #include <inttypes.h>
33 #include <dirent.h>
34 #include <time.h>
35 /* for correct behaviour ensure this is the POSIX and not the GNU basename:
36    "With glibc, one gets the POSIX version of basename() when <libgen.h> is
37     included, and the GNU version otherwise." */
38 #include <libgen.h>
39
40 #include "sha1.h"
41 #include "md5.h"
42
43
44 /******************************************************************************
45 program version string
46 ******************************************************************************/
47 #define bt_version "0.9~git"
48 const char* __version__ = bt_version;
49
50 /******************************************************************************
51 torrent data structure declarations
52 ******************************************************************************/
53
54 /* structure for torrent file data */
55 struct _bt_data;
56 typedef struct _bt_data *bt_data;
57
58 /* error flags */
59 enum _bt_error {
60   BT_ERROR_NONE = 0,
61   BT_ERROR_IO,
62   BT_ERROR_OVERFLOW,
63   BT_ERROR_PARSE
64 };
65 typedef enum _bt_error bt_error;
66
67 /* attempt to support large files */
68 typedef int64_t integer;
69
70 /* constructors */
71 bt_data bt_string(const unsigned int length, const char *data);
72 bt_data bt_integer(const integer number);
73 bt_data bt_list(void);
74 bt_data bt_dictionary(void);
75
76 /* append to a list */
77 void bt_list_append(bt_data list, const bt_data data);
78
79 /* insert into a dictionary, maintaining sorted keys */
80 void bt_dictionary_insert(
81   bt_data dictionary,
82   const unsigned int keylength, const char *keyname,
83   const bt_data data
84 );
85
86 /* deep-copy a structure */
87 bt_data bt_copy(const bt_data data);
88
89 /* write as a torrent file */
90 bt_error bt_write(FILE *f, const bt_data bd);
91
92 /* types of structures */
93 enum _bt_type {
94   BT_TYPE_STRING = 1000,
95   BT_TYPE_INTEGER,
96   BT_TYPE_LIST,
97   BT_TYPE_DICTIONARY
98 };
99 typedef enum _bt_type bt_type;
100
101 /* string structure */
102 struct _bt_string {
103   unsigned int length;
104   char *data;
105 };
106
107 /* integer structure */
108 struct _bt_integer {
109   integer number;
110 };
111
112 /* list structure */
113 struct _bt_list {
114   struct _bt_data *item;
115   struct _bt_list *next;
116 };
117
118 /* dictionary structure */
119 struct _bt_dictionary {
120   struct _bt_string *key;
121   struct _bt_data *value;
122   struct _bt_dictionary *next;
123 };
124
125 /* general structure */
126 struct _bt_data {
127   bt_type type;
128   union {
129     struct _bt_string *string;
130     struct _bt_integer *integer;
131     struct _bt_list *list;
132     struct _bt_dictionary *dictionary;
133   } b;
134 };
135
136 /******************************************************************************
137 abort on memory allocation failure
138 ******************************************************************************/
139 void *bt_malloc(size_t size) {
140   void *mem = malloc(size);
141   if (!mem) {
142     fprintf(stderr, "buildtorrent: out of memory\n");
143     exit(1);
144   }
145   return mem;
146 }
147
148 /******************************************************************************
149 allocate a new string structure
150 ******************************************************************************/
151 bt_data bt_string(const unsigned int length, const char *data) {
152   bt_data bd = bt_malloc(sizeof(struct _bt_data));
153   struct _bt_string *s = bt_malloc(sizeof(struct _bt_string));
154   s->data = bt_malloc(length);
155   s->length = length;
156   memcpy(s->data, data, length);
157   bd->type = BT_TYPE_STRING;
158   bd->b.string = s;
159   return bd;
160 }
161 #define bt_string0(s) bt_string(strlen((s)),(s))
162
163 /******************************************************************************
164 allocate a new integer structure
165 ******************************************************************************/
166 bt_data bt_integer(const integer number) {
167   bt_data bd = bt_malloc(sizeof(struct _bt_data));
168   struct _bt_integer *n = bt_malloc(sizeof(struct _bt_integer));
169   n->number = number;
170   bd->type = BT_TYPE_INTEGER;
171   bd->b.integer = n;
172   return bd;
173 }
174
175 /******************************************************************************
176 allocate a new empty list structure
177 invariant: bd->b.list != NULL && last(bd->b.list).{item, next} == NULL
178 ******************************************************************************/
179 bt_data bt_list(void) {
180   bt_data bd = bt_malloc(sizeof(struct _bt_data));
181   struct _bt_list *l = bt_malloc(sizeof(struct _bt_list));
182   l->item = NULL;
183   l->next = NULL;
184   bd->type = BT_TYPE_LIST;
185   bd->b.list = l;
186   return bd;
187 }
188
189 /******************************************************************************
190 allocate a new empty dictionary structure
191 invariant: bd->b.dictionary != NULL &&
192            last(bd->b.list).{key, value, next} == NULL &&
193            ordered ascending by key
194 ******************************************************************************/
195 bt_data bt_dictionary(void) {
196   bt_data bd = bt_malloc(sizeof(struct _bt_data));
197   struct _bt_dictionary *d = bt_malloc(sizeof(struct _bt_dictionary));
198   d->key = NULL;
199   d->value = NULL;
200   d->next = NULL;
201   bd->type = BT_TYPE_DICTIONARY;
202   bd->b.dictionary = d;
203   return bd;
204 }
205
206 /******************************************************************************
207 precondition: list is a valid list
208 append an item to a list
209 the item is not copied
210 ******************************************************************************/
211 void bt_list_append(bt_data list, const bt_data data) {
212   assert(list);
213   assert(BT_TYPE_LIST == list->type);
214   assert(list->b.list);
215   {
216   struct _bt_list *current;
217   struct _bt_list *end = bt_malloc(sizeof(struct _bt_list));
218   end->item = NULL;
219   end->next = NULL;
220   current = list->b.list;
221   while (current->next) {
222     current = current->next;
223   }
224   current->item = data;
225   current->next = end;
226   }
227 }
228
229 /******************************************************************************
230 precondition: dictionary is a valid dictionary
231 insert an item into a dictionary
232 the value is not copied, the key is copied
233 maintains an ascending ordering of key names
234 FIXME: assumes key names are null terminated
235 ******************************************************************************/
236 void bt_dictionary_insert(
237   bt_data dictionary,
238   const unsigned int keylength, const char *keyname,
239   const bt_data data
240 ) {
241   assert(dictionary);
242   assert(BT_TYPE_DICTIONARY == dictionary->type);
243   assert(dictionary->b.dictionary);
244   {
245   struct _bt_dictionary *current;
246   struct _bt_dictionary *lastcurrent = NULL;
247   struct _bt_dictionary *insertee;
248   struct _bt_string *key = bt_malloc(sizeof(struct _bt_string));
249   key->data = bt_malloc(keylength);
250   insertee = bt_malloc(sizeof(struct _bt_dictionary));
251   memcpy(key->data, keyname, keylength);
252   key->length = keylength;
253   insertee->key = key;
254   insertee->value = data;
255   insertee->next = NULL;
256   current = dictionary->b.dictionary;
257   while (
258     current->next && current->key && (0 < strcmp(keyname, current->key->data))
259   ) {
260     lastcurrent = current;
261     current = current->next;
262   }
263   if (lastcurrent) {
264     insertee->next = current;
265     lastcurrent->next = insertee;
266   } else {
267     insertee->next = dictionary->b.dictionary;
268     dictionary->b.dictionary = insertee;
269   }
270   }
271 }
272
273 #define bt_dictionary_insert0(dict,key,data) \
274 bt_dictionary_insert((dict),strlen((key)),(key),(data))
275
276 /******************************************************************************
277 precondition: data is valid
278 deep copy a data structure
279 FIXME: doesn't handle dictionaries yet
280 ******************************************************************************/
281 bt_data bt_copy(const bt_data data) {
282   assert(data);
283   switch (data->type) {
284   case (BT_TYPE_STRING): {
285     return bt_string(data->b.string->length, data->b.string->data);
286   }
287   case (BT_TYPE_INTEGER): {
288     return bt_integer(data->b.integer->number);
289   }
290   case (BT_TYPE_LIST): {
291     struct _bt_list *current;
292     bt_error err;
293     bt_data list = bt_list();
294     current = data->b.list;
295     while (current && current->item) {
296       bt_list_append(list, bt_copy(current->item));
297       current = current->next;
298     }
299     return list;
300   }
301   default: {
302     fprintf(stderr, "buildtorrent: BUG! can't copy this\n");
303     exit(1);
304   }
305   }
306 }
307
308 /******************************************************************************
309 precondition: s is valid
310 write a string in torrent encoding
311 ******************************************************************************/
312 bt_error bt_write_string(FILE *f, const struct _bt_string *s) {
313   assert(s);
314   assert(s->data);
315   if (0 > fprintf(f, "%d:", s->length)) {
316     return BT_ERROR_IO;
317   }
318   if (1 != fwrite(s->data, s->length, 1, f)) {
319     return BT_ERROR_IO;
320   }
321   return BT_ERROR_NONE;
322 }
323
324 /******************************************************************************
325 precondition: bd is valid
326 write a data structure in torrent encoding
327 ******************************************************************************/
328 bt_error bt_write(FILE *f, const bt_data bd) {
329   assert(bd);
330   switch (bd->type) {
331   case (BT_TYPE_STRING): {
332     return bt_write_string(f, bd->b.string);
333     break;
334   }
335   case (BT_TYPE_INTEGER): {
336     assert(bd->b.integer);
337     if (0 > fprintf(f, "i%" PRId64 "e", bd->b.integer->number)) {
338       return BT_ERROR_IO;
339     }
340     break;
341   }
342   case (BT_TYPE_LIST): {
343     struct _bt_list *current;
344     bt_error err;
345     current = bd->b.list;
346     if (0 > fprintf(f, "l")) {
347       return BT_ERROR_IO;
348     }
349     while (current->next) {
350       if (current->item) {
351         if ((err = bt_write(f, current->item))) {
352           return err;
353         }
354       }
355       current = current->next;
356     }
357     if (0 > fprintf(f, "e")) {
358       return BT_ERROR_IO;
359     }
360     break;
361   }
362   case (BT_TYPE_DICTIONARY): {
363     struct _bt_dictionary *current;
364     bt_error err;
365     current = bd->b.dictionary;
366     if (0 > fprintf(f, "d")) {
367       return BT_ERROR_IO;
368     }
369     while (current->next) {
370       if (current->key) {
371         if ((err = bt_write_string(f, current->key))) {
372           return err;
373         }
374         if ((err = bt_write(f, current->value))) {
375           return err;
376         }
377       }
378       current = current->next;
379     }
380     if (0 > fprintf(f, "e")) {
381       return BT_ERROR_IO;
382     }
383     break;
384   }
385   default: {
386     fprintf(stderr, "buildtorrent: BUG! can't write this\n");
387     exit(1);
388     break;
389   }
390   }
391   return BT_ERROR_NONE;
392 }
393
394 /******************************************************************************
395 pretty print torrent data
396 ******************************************************************************/
397 void bt_show(bt_data bd, int piecestoo, int indent, int indentstep, int comma) {
398   int i;
399   if (!bd) {
400     for(i = 0; i < indent; i++) {
401       printf(" ");
402     }
403     printf("NULL%s\n", comma ? "," : "");
404     return;
405   }
406   switch (bd->type) {
407   case (BT_TYPE_STRING): {
408     char strbuf[512];
409     int len = bd->b.string->length > 500 ? 500 : bd->b.string->length;
410     memcpy(strbuf, bd->b.string->data, len);
411     strbuf[len] = '\0';
412     for(i = 0; i < indent; i++) {
413       printf(" ");
414     }
415     printf(
416       "\"%s\"%s%s\n", strbuf, comma ? "," : "", len == 500 ? " // truncated!" : ""
417     );
418     break;
419   }
420   case (BT_TYPE_INTEGER): {
421     for(i = 0; i < indent; i++) {
422       printf(" ");
423     }
424     printf("%" PRId64 "%s\n", bd->b.integer->number, comma ? "," : "");
425     break;
426   }
427   case (BT_TYPE_LIST): {
428     struct _bt_list *current;
429     for(i = 0; i < indent; i++) {
430       printf(" ");
431     }
432     printf("[\n");
433     current = bd->b.list;
434     while (current->next) {
435       bt_show(
436         current->item, piecestoo,
437         indent + indentstep, indentstep,
438         current->next->next != NULL
439       );
440       current = current->next;
441     }
442     for(i = 0; i < indent; i++) {
443       printf(" ");
444     }
445     printf("]%s\n", comma ? "," : "");
446     break;
447   }
448   case (BT_TYPE_DICTIONARY): {
449     struct _bt_dictionary *current;
450     char strbuf[512];
451     int len;
452     for(i = 0; i < indent; i++) {
453       printf(" ");
454     }
455     printf("{\n");
456     current = bd->b.dictionary;
457     while (current->next) {
458       len = current->key->length > 500 ? 500 : current->key->length;
459       memcpy(strbuf, current->key->data, len);
460       strbuf[len] = '\0';
461       for(i = 0; i < indent + indentstep; i++) {
462         printf(" ");
463       }
464       printf("\"%s\" =>%s\n", strbuf, len == 500 ? " // truncated!" : "");
465       if (strcmp("pieces", strbuf) == 0) {
466         if (piecestoo) {
467           printf("----------------------------------------");
468           char *hexdigits = "0123456789abcdef";
469           for (i = 0; i < current->value->b.string->length; i++) {
470             if ((i % 20) == 0) {
471               printf("\n");
472             }
473             printf("%c%c",
474               hexdigits[(current->value->b.string->data[i] & 0xF0) >> 4],
475               hexdigits[(current->value->b.string->data[i] & 0x0F)]
476             );
477           }
478           printf("\n----------------------------------------\n");
479         } else {
480           for(i = 0; i < indent + indentstep + indentstep; i++) {
481             printf(" ");
482           }
483           printf("\"...\"%s // pieces not shown\n", current->next->next ? "," : "");
484         }
485       } else {
486         bt_show(
487           current->value, piecestoo, indent + indentstep + indentstep,
488           indentstep, current->next->next != NULL
489         );
490       }
491       current = current->next;
492     }
493     for(i = 0; i < indent; i++) {
494       printf(" ");
495     }
496     printf("}%s\n", comma ? "," : "");
497     break;
498   }
499   }
500 }
501
502
503 /******************************************************************************
504 file list data structure
505 the first in the list is a dummy
506 ******************************************************************************/
507 struct _bt_file_list {
508   char *file;
509   bt_data path;
510   struct _bt_file_list *next;
511 };
512 typedef struct _bt_file_list *bt_file_list;
513
514 /******************************************************************************
515 create the dummy first list element
516 ******************************************************************************/
517 bt_file_list bt_create_file_list() {
518   bt_file_list flist = bt_malloc(sizeof(struct _bt_file_list));
519   flist->file = NULL;
520   flist->path = NULL;
521   flist->next = NULL;
522   return flist;
523 }
524
525 /******************************************************************************
526 precondition: flist and file must be valid
527 prepend to the file list
528 copies the arguments
529 path may be NULL, but only in single file mode
530 ******************************************************************************/
531 void bt_file_list_prepend(
532   bt_file_list flist, const char *file, bt_data path
533 ) {
534   assert(flist);
535   assert(file);
536   {
537   bt_file_list node = bt_malloc(sizeof(struct _bt_file_list));
538   node->file = bt_malloc(strlen(file) + 1);
539   memcpy(node->file, file, strlen(file) + 1);
540   if (path) {
541     node->path = bt_copy(path);
542   } else {
543     node->path = NULL;
544   }
545   node->next = flist->next;
546   flist->next = node;
547   }
548 }
549
550
551 /******************************************************************************
552 annotated file list data structure
553 the first in the list is a dummy
554 ******************************************************************************/
555 struct _bt_afile_list {
556   char *file;
557   bt_data path;
558   integer length;
559   bt_data md5sum;
560   struct _bt_afile_list *next;
561 };
562 typedef struct _bt_afile_list *bt_afile_list;
563
564 /******************************************************************************
565 create the dummy first list element
566 ******************************************************************************/
567 bt_afile_list bt_create_afile_list() {
568   bt_afile_list aflist = bt_malloc(sizeof(struct _bt_afile_list));
569   aflist->file = NULL;
570   aflist->path = NULL;
571   aflist->length = 0;
572   aflist->md5sum = NULL;
573   aflist->next = NULL;
574   return aflist;
575 }
576
577 /******************************************************************************
578 preconditions: aflist and fnode are valid
579 prepend to the annotated file list
580 aflist is an annotated file list to prepend to
581 fnode is a file list node to annotate
582 length is the length to annotate with
583 md5sum is the md5sum to annotate with (may be NULL)
584 NOTE does NOT create a new copy of the existing data
585 ******************************************************************************/
586 void bt_afile_list_prepend(
587   bt_afile_list aflist, bt_file_list fnode, integer length, bt_data md5sum
588 ) {
589   assert(aflist);
590   assert(fnode);
591   {
592   bt_afile_list node = bt_malloc(sizeof(struct _bt_afile_list));
593   node->file = fnode->file;
594   node->path = fnode->path;
595   node->length = length;
596   node->md5sum = md5sum;
597   node->next = aflist->next;
598   aflist->next = node;
599   }
600 }
601
602
603 /******************************************************************************
604 append a file to a path string
605 path must be at least length maxlength bytes
606 returns an error flag
607 ******************************************************************************/
608 bt_error bt_joinpath(size_t maxlength, char *path, const char *file) {
609   if (strlen(path) + strlen(file) + 1 + 1 > maxlength) {
610     return BT_ERROR_OVERFLOW;
611   } else {
612     size_t oldlength = strlen(path);
613     if (oldlength > 0) {
614       path[oldlength] = '/';
615       memcpy(path + oldlength + 1, file, strlen(file) + 1);
616     } else {
617       memcpy(path, file, strlen(file) + 1);
618     }
619     return BT_ERROR_NONE;
620   }
621 }
622
623
624 /******************************************************************************
625 md5sum a file
626 ******************************************************************************/
627 bt_data bt_md5sum_file(const char *filename, integer length) {
628   struct MD5Context context;
629   char *hexdigits = "0123456789abcdef";
630   unsigned char *buffer = NULL;
631   unsigned char digest[16];
632   char hexdump[33];
633   integer buflen = 262144;
634   integer size = 0;
635   integer left = length;
636   integer i;
637   FILE *file = NULL;
638   if (!filename) {
639     return NULL;
640   }
641   buffer = bt_malloc(buflen);
642   if (!(file = fopen(filename, "rb"))) {
643     free(buffer);
644     return NULL;
645   }
646   MD5Init(&context);
647   while (left > 0) {
648     if (left > buflen) {
649        size = buflen;
650     } else {
651        size = left;
652     }
653     if (1 != fread(buffer, size, 1, file)) {
654       free(buffer);
655       fclose(file);
656       return NULL;
657     }
658     MD5Update(&context, buffer, size);
659     left -= size;
660   }
661   MD5Final(digest, &context);
662   for (i = 0; i < 16; ++i) {
663     hexdump[i+i+0] = hexdigits[(digest[i] & 0xF0) >> 4];
664     hexdump[i+i+1] = hexdigits[(digest[i] & 0x0F)];
665   }
666   free(buffer);
667   fclose(file);
668   return bt_string(32, hexdump);
669 }
670
671
672 /******************************************************************************
673 find files recursively
674 directory is the initial directory
675 path is a buffer of maxlength bytes to store the accumulated path
676 pathlist is the accumulated path exploded as a list
677 files is a file list to add the found files to
678 returns an error flag
679 ******************************************************************************/
680 bt_error bt_find_files(
681   char sort, size_t maxlength, char *path, bt_data pathlist, bt_file_list files
682 ) {
683   struct dirent **entry;
684   bt_data filename = NULL;
685   bt_data filepath = NULL;
686   bt_error err = BT_ERROR_NONE;
687   int i, n;
688   /* replaced readdir with scandir */
689   /* for directory listings in alphabetical order */
690   switch(sort) {
691     default:
692 #ifdef _GNU_SOURCE
693     case 'v':
694        /* read directory entry in "version" order, ie. 1 2 10, not 1 10 2 */
695       i = scandir(path, &entry, 0, versionsort);
696       break;
697 #endif
698     case 'a':
699     case 's':
700       i = scandir(path, &entry, 0, alphasort);
701       break;
702     case 'u':
703       /* read directory entry normal order (directory order, seemingly random) */
704       i = scandir(path, &entry, 0, 0);
705       break;
706   }
707   if (i >= 0) {
708   for (n = 0; n < i; n++) {
709     if (strcmp(entry[n]->d_name, ".") && strcmp(entry[n]->d_name, "..")) {
710       struct stat s;
711       size_t oldlength = strlen(path);
712       if (!(err = bt_joinpath(maxlength, path, entry[n]->d_name))) {
713         if (!stat(path, &s)) {
714           if (S_ISREG(s.st_mode)) {
715             filename = bt_string0(entry[n]->d_name);
716             filepath = bt_copy(pathlist);
717             bt_list_append(filepath, filename);
718             bt_file_list_prepend(files, path, filepath);
719           } else if (S_ISDIR(s.st_mode)) {
720             filename = bt_string0(entry[n]->d_name);
721             filepath = bt_copy(pathlist);
722             bt_list_append(filepath, filename);
723             err = bt_find_files(sort, maxlength, path, filepath, files);
724           } else {
725             /* FIXME neither regular file nor directory, what to do? */
726           }
727         } else {
728           err = BT_ERROR_IO;
729         }
730       }
731       path[oldlength] = '\0';
732     }
733     if (err) {
734       break;
735     }
736   }
737   } else {
738     err = BT_ERROR_IO;
739   }
740   return err;
741 }
742
743
744 /******************************************************************************
745 annotate a file list
746 files is the file list to annotate
747 afiles is the file list to add to
748 returns an error flag
749 ******************************************************************************/
750 bt_error bt_annotate_files(
751   bt_file_list files, bt_afile_list afiles
752 ) {
753   bt_error err = BT_ERROR_NONE;
754   bt_file_list fnode = files;
755   while ((fnode = fnode->next)) {
756     struct stat s;
757     if (!stat(fnode->file, &s)) {
758       if (S_ISREG(s.st_mode)) {
759         integer length = s.st_size;
760         bt_afile_list_prepend(afiles, fnode, length, NULL);
761       } else {
762         err = BT_ERROR_IO;
763       }
764     } else {
765       err = BT_ERROR_IO;
766     }
767     if (err) {
768       break;
769     }
770   }
771   return err;
772 }
773
774
775 /******************************************************************************
776 convert an annotated file list into torrent format
777 aflist is the list to convert
778 returns the torrent format file list
779 ******************************************************************************/
780 bt_data bt_afile_list_info(bt_afile_list aflist) {
781   bt_afile_list node;
782   bt_data files;
783   files = bt_list();
784   node = aflist;
785   while ((node = node->next)) {
786     bt_data file;
787     bt_data filesize;
788     bt_data filepath;
789     bt_data md5sum;
790     file = bt_dictionary();
791     filesize = bt_integer(node->length);
792     if (!(filepath = node->path)) {
793       fprintf(stderr, "buildtorrent: BUG! path missing from node\n");
794       exit(1);
795     }
796     bt_dictionary_insert0(file, "length", filesize);
797     bt_dictionary_insert0(file, "path", filepath);
798     if ((md5sum = node->md5sum)) {
799       bt_dictionary_insert0(file, "md5sum", md5sum);
800     }
801     bt_list_append(files, file);
802   }
803   return files;
804 }
805
806
807 /******************************************************************************
808 draw progressbar
809 ******************************************************************************/
810 void bt_progressbar(integer piece, integer count) {
811   integer oldblocks = ((piece - 1) * 50) / count;
812   integer blocks = (piece * 50) / count;
813   integer i;
814   char s[53];
815   if (blocks != oldblocks) {
816     s[0] = '[';
817     for (i = 0; i < 50; i++) {
818       s[i+1] = (i < blocks) ? '=' : ((i == blocks) ? '>' : ' ');
819     }
820     s[51] = ']';
821     s[52] = '\0';
822     fprintf(
823       stderr,
824       "\b\b\b\b\b\b\b\b\b\b"
825       "\b\b\b\b\b\b\b\b\b\b"
826       "\b\b\b\b\b\b\b\b\b\b"
827       "\b\b\b\b\b\b\b\b\b\b"
828       "\b\b\b\b\b\b\b\b\b\b"
829       "\b\b%s",
830       s
831     );
832   }
833 }
834
835
836 /******************************************************************************
837 hash files
838 return piece hash as a string
839 data side effect: if domd5sum then add md5sums to aflist
840 output side effect: if verbose then show file summary
841 ******************************************************************************/
842 bt_data bt_hash_pieces(bt_afile_list aflist, integer size, int domd5sum, int verbose) {
843   bt_afile_list node = NULL;
844   char *hashdata = NULL;
845   integer total = 0;
846   unsigned char *buffer = NULL;
847   unsigned char *bufptr = NULL;
848   integer remain = size;
849   integer left;
850   FILE *file = NULL;
851   integer piececount;
852   integer i;
853   sha1_byte digest[SHA1_DIGEST_LENGTH];
854   if (!aflist) {
855     return NULL;
856   }
857   buffer = bt_malloc(size);
858   node = aflist;
859   while ((node = node->next)) {
860     if (verbose) {
861       fprintf(stderr, "%20" PRId64 " : %s\n", node->length, node->file);
862     }
863     if (domd5sum) {
864       if (!(node->md5sum = bt_md5sum_file(node->file, node->length))) {
865         fprintf(stderr, "buildtorrent: error computing md5sum for \"%s\"\n", node->file);
866       }
867     }
868     total += node->length;
869   }
870   piececount = (total + size - 1) / size; /* ceil(total/size) */
871   if (piececount <= 0) { /* FIXME: no idea what to do if there's no data */
872     free(buffer);
873     fprintf(stderr, "torrent has no data, aborting!\n");
874     return NULL;
875   }
876   if (verbose) {
877     fprintf(stderr, "hashing %" PRId64 " pieces\n", piececount);
878     fprintf(stderr, "["
879                     "          "
880                     "          "
881                     "          "
882                     "          "
883                     "          "
884                     "]");
885   }
886   hashdata = bt_malloc(piececount * SHA1_DIGEST_LENGTH);
887   node = aflist->next;
888   file = fopen(node->file, "rb");
889   if (!file) {
890     free(buffer);
891     return NULL;
892   }
893   left = node->length;
894   bufptr = buffer;
895   for (i = 0; i < piececount; ++i) {
896     do {
897       if (left <= remain) {
898         /* take all */
899         if (left != 0) { /* don't fail on empty files */
900           if (1 != fread(bufptr, left, 1, file)) {
901             fclose(file);
902             free(buffer);
903             return NULL;
904           }
905           bufptr += left;
906           remain -= left;
907           fclose(file);
908           file = NULL;
909         }
910         node = node->next;
911         if (node) {
912           file = fopen(node->file, "rb");
913           if (!file) {
914             free(buffer);
915             return NULL;
916           }
917           left = node->length;
918         }
919       } else { /* left > remain */
920         /* take as much as we can */
921         if (remain != 0) { /* don't fail on empty files */
922           if (1 != fread(bufptr, remain, 1, file)) {
923             free(buffer);
924             return NULL;
925           }
926           bufptr += remain;
927           left -= remain;
928           remain = 0;
929         }
930       }
931     } while (remain != 0 && node);
932     if (!node && i != piececount - 1) {
933       /* somehow the pieces don't add up */
934       if (file) {
935         fclose(file);
936       }
937       free(buffer);
938       return NULL;
939     }
940     /* remain == 0 || i == piececount - 1 */
941     if (verbose) {
942       bt_progressbar(i, piececount);
943     }
944     SHA1(buffer, size - remain, digest);
945     memcpy(hashdata + i * SHA1_DIGEST_LENGTH, digest, SHA1_DIGEST_LENGTH);
946     bufptr = buffer;
947     remain = size;
948   }
949   if (verbose) {
950     bt_progressbar(piececount, piececount);
951     fprintf(stderr, "\n");
952   }
953   return bt_string(SHA1_DIGEST_LENGTH * piececount, hashdata);
954 }
955
956
957 /******************************************************************************
958 parse an announce list
959 format = "url|url|url,url|url|url,url|url|url"
960 ******************************************************************************/
961 bt_data bt_parse_announcelist(const char *urls) {
962   bt_data announcelist;
963   bt_data tier;
964   const char *s;
965   const char *t;
966   const char *t1;
967   const char *t2;
968   if (!urls) {
969     return NULL;
970   }
971   if (strcmp("", urls) == 0) {
972     return NULL;
973   }
974   announcelist = bt_list();
975   s = urls;
976   tier = bt_list();
977   do {
978     t = NULL;
979     t1 = strchr(s, '|');
980     t2 = strchr(s, ',');
981     if (!t1 && !t2) {
982       t = s + strlen(s);
983     } else if (!t1) {
984       t = t2;
985     } else if (!t2) {
986       t = t1;
987     } else {
988       t = (t1 < t2) ? t1 : t2;
989     }
990     if (t <= s) {
991       return NULL;
992     }
993     bt_list_append(tier, bt_string(t - s, s));
994     if (t[0] == ',' || t[0] == '\0') {
995       bt_list_append(announcelist, tier);
996       if (t[0] != '\0') {
997         tier = bt_list();
998       } else {
999         tier = NULL;
1000       }
1001     }
1002     s = t + 1;
1003   } while (t[0] != '\0');
1004   return announcelist;
1005 }
1006
1007 /******************************************************************************
1008 parse a webseed list
1009 format = "url,url,url"
1010 ******************************************************************************/
1011 bt_data bt_parse_webseedlist(const char *urls) {
1012   bt_data webseedlist;
1013   const char *s;
1014   const char *t;
1015   const char *t2;
1016   if (!urls) {
1017     return NULL;
1018   }
1019   if (strcmp("", urls) == 0) {
1020     return NULL;
1021   }
1022   webseedlist = bt_list();
1023   s = urls;
1024   do {
1025     t = NULL;
1026     t2 = strchr(s, ',');
1027     if (!t2) {
1028       t = s + strlen(s);
1029     } else {
1030       t = t2;
1031     }
1032     if (t <= s) {
1033       return NULL;
1034     }
1035     bt_list_append(webseedlist, bt_string(t - s, s));
1036     s = t + 1;
1037   } while (t[0] != '\0');
1038   return webseedlist;
1039 }
1040
1041 /******************************************************************************
1042 read and parse a filelist file
1043 line format = "real/file/system/path|tor/rent/path/file"
1044 delimiters: '|', '\n'
1045 escape character: '\\'
1046 ******************************************************************************/
1047 enum _bt_parse_state {
1048   BT_PARSE_LEFT = 0,
1049   BT_PARSE_LEFT_ESCAPED,
1050   BT_PARSE_RIGHT,
1051   BT_PARSE_RIGHT_ESCAPED,
1052   BT_PARSE_OK,
1053   BT_PARSE_ERROR
1054 };
1055 typedef enum _bt_parse_state bt_parse_state;
1056 bt_error bt_parse_filelist(bt_file_list flist, FILE *infile) {
1057   char filename[8192];
1058   char torrname[8192];
1059   bt_data torrpath = NULL;
1060   int c;
1061   int i = 0;
1062   int state = BT_PARSE_LEFT;
1063   while(state != BT_PARSE_OK && state != BT_PARSE_ERROR) {
1064     c = getc(infile);
1065     switch (state) {
1066     case (BT_PARSE_LEFT):
1067       if (c < 0) {
1068         state = BT_PARSE_OK;
1069       } else if (c == '\\') {
1070         state = BT_PARSE_LEFT_ESCAPED;
1071       } else if (c == '|') {
1072         filename[i] = '\0';
1073         i = 0;
1074         torrpath = bt_list();
1075         state = BT_PARSE_RIGHT;
1076       } else {
1077         filename[i++] = c;
1078         if (i > 8190) {
1079           state = BT_PARSE_ERROR;
1080         } else {
1081           state = BT_PARSE_LEFT;
1082         }
1083       }
1084       break;
1085     case (BT_PARSE_LEFT_ESCAPED):
1086       if (c < 0) {
1087         state = BT_PARSE_ERROR;
1088       } else {
1089         filename[i++] = c;
1090         if (i > 8190) {
1091           state = BT_PARSE_ERROR;
1092         } else {
1093           state = BT_PARSE_LEFT;
1094         }
1095       }
1096       break;
1097     case (BT_PARSE_RIGHT):
1098       if (c < 0) {
1099         state = BT_PARSE_ERROR;
1100       } else if (c == '\\') {
1101         state = BT_PARSE_RIGHT_ESCAPED;
1102       } else if (c == '/' || c == '\n') {
1103         bt_data torrnamestr;
1104         torrname[i] = '\0';
1105         i = 0;
1106         if (0 == strcmp("", torrname)) {
1107           state = BT_PARSE_ERROR;
1108         } else {
1109           torrnamestr = bt_string0(torrname);
1110           bt_list_append(torrpath, torrnamestr);
1111           if (c == '\n') {
1112             bt_file_list_prepend(flist, filename, torrpath);
1113             state = BT_PARSE_LEFT;
1114           } else {
1115             state = BT_PARSE_RIGHT;
1116           }
1117         }
1118       } else {
1119         torrname[i++] = c;
1120         if (i > 8190) {
1121           state = BT_PARSE_ERROR;
1122         } else {
1123           state = BT_PARSE_RIGHT;
1124         }
1125       }
1126       break;
1127     case (BT_PARSE_RIGHT_ESCAPED):
1128       if (c < 0) {
1129         state = BT_PARSE_ERROR;
1130       } else {
1131         torrname[i++] = c;
1132         if (i > 8190) {
1133           state = BT_PARSE_ERROR;
1134         } else {
1135           state = BT_PARSE_RIGHT;
1136         }
1137       }
1138       break;
1139     default:
1140       fprintf(stderr, "buildtorrent: BUG! internal error parsing file list (%d)\n", state);
1141       exit(1);
1142     }
1143   }
1144   if (state == BT_PARSE_OK) {
1145     return BT_ERROR_NONE;
1146   } else {
1147     return BT_ERROR_PARSE;
1148   }
1149 }
1150
1151 /******************************************************************************
1152 show usage message
1153 ******************************************************************************/
1154 void bt_usage(void) {
1155   printf(
1156     "Usage:\n"
1157     "  buildtorrent [OPTIONS] -a announceurl input output\n"
1158     "  buildtorrent [OPTIONS] -a announceurl -f filelist -n name output\n"
1159     "\n"
1160     "options:\n"
1161     "--announce        -a  <announce>   : announce url (required)\n"
1162     "--filelist        -f  <filelist>   : external file list (requires '-n')\n"
1163     "--name            -n  <name>       : torrent name, default based on input\n"
1164     "--announcelist    -A  <announces>  : announce url list (format: a,b1|b2,c)\n"
1165     "--webseeds        -w  <webseeds>   : webseed url list (format: a,b,c)\n"
1166     "--piecelength     -l  <length>     : piece length in bytes, default 262144\n"
1167     "--piecesize       -L  <size>       : use 2^size as piece length, default 18\n"
1168     "--comment         -c  <comment>    : user comment, omitted by default\n"
1169     "--private         -p  <private>    : private flag, either 0 or 1\n"
1170     "--nodate          -D               : omit 'creation date' field\n"
1171     "--nocreator       -C               : omit 'created by' field\n"
1172     "--md5sum          -m               : add an 'md5sum' field for each file\n"
1173     "--sort            -s <sortorder>   : sort files ([U]nsorted, "
1174 #ifdef _GNU_SOURCE
1175     "[A]lpha, [V]ersion)\n"
1176 #else
1177     "[A]lpha)\n"
1178 #endif
1179     "--quiet           -q               : quiet operation\n"
1180     "--verbose         -v               : verbose. More -v means more verbose\n"
1181     "--version         -V               : show version of buildtorrent\n"
1182     "--help            -h               : show this help screen\n"
1183   );
1184 }
1185
1186 /******************************************************************************
1187 main program
1188 ******************************************************************************/
1189 int main(int argc, char **argv) {
1190
1191   char *url = NULL;
1192   char *urls = NULL;
1193   char *wurls = NULL;
1194   char *inname = NULL;
1195   char *nameflag = NULL;
1196   char *namebase = NULL;
1197   char *outfile = NULL;
1198   char *commentstr = NULL;
1199   char *filelistfilename = NULL;
1200 #ifdef _GNU_SOURCE
1201   char sort = 'v'; /* default to version sort, if available */
1202 #else
1203   char sort = 'a'; /* otherwise default to alpha sort */
1204 #endif
1205   int lplen = -1;
1206   unsigned int plen = 262144;
1207   int verbose = 1;
1208   int nodate = 0;
1209   int nocreator = 0;
1210   int privated = 0;
1211   int privateopt = 0;
1212   int domd5sum = 0;
1213   int show = 0;
1214   int slen;
1215   int i;
1216
1217   FILE *output = NULL;
1218   bt_data torrent = NULL;
1219   bt_data announce = NULL;
1220   bt_data announcelist = NULL;
1221   bt_data webseedlist = NULL;
1222   bt_data info = NULL;
1223   bt_data piecelength = NULL;
1224   bt_data pieces = NULL;
1225   bt_data files = NULL;
1226   bt_data name = NULL;
1227   bt_data length = NULL;
1228   bt_data pathlist = NULL;
1229   bt_data creator = NULL;
1230   bt_data creationdate = NULL;
1231   bt_data comment = NULL;
1232   bt_data private = NULL;
1233   bt_data md5sum = NULL;
1234
1235   int multifile = 0;
1236   bt_file_list flist = NULL;
1237   bt_afile_list aflist = NULL;
1238
1239   struct stat s;
1240   char path[8192];
1241   char nametemp[8192];
1242
1243   while (1) {
1244     int optidx = 0;
1245     static struct option options[] = {
1246       { "announce", 1, 0, 'a' },
1247       { "filelist", 1, 0, 'f' },
1248       { "name", 1, 0, 'n' },
1249       { "announcelist", 1, 0, 'A' },
1250       { "webseeds", 1, 0, 'w' },
1251       { "piecelength", 1, 0, 'l' },
1252       { "piecesize", 1, 0, 'L' },
1253       { "comment", 1, 0, 'c' },
1254       { "private", 1, 0, 'p' },
1255       { "sort", 1, 0, 's' },
1256       { "nodate", 0, 0, 'D' },
1257       { "nocreator", 0, 0, 'C' },
1258       { "md5sum", 0, 0, 'm' },
1259       { "quiet", 0, 0, 'q' },
1260       { "verbose", 0, 0, 'v' },
1261       { "version", 0, 0, 'V' },
1262       { "help", 0, 0, 'h' },
1263       { 0, 0, 0, 0 }
1264     };
1265     char c = getopt_long(argc, argv, "hVqvmCDs:a:f:n:A:w:l:L:c:p:", options, &optidx );
1266     if (c == -1) {
1267       break;
1268     }
1269     switch (c) {
1270     case ('?'):
1271       return 1;
1272     case ('a'):
1273       url = optarg;
1274       break;
1275     case ('f'):
1276       filelistfilename = optarg;
1277       break;
1278     case ('n'):
1279       nameflag = optarg;
1280       break;
1281     case ('A'):
1282       urls = optarg;
1283       break;
1284     case ('w'):
1285       wurls = optarg;
1286       break;
1287     case ('l'):
1288       plen = atoi(optarg);
1289       break;
1290     case ('L'):
1291       lplen = atoi(optarg);
1292       break;
1293     case ('c'):
1294       commentstr = optarg;
1295       break;
1296     case ('p'):
1297       privated = 1;
1298       privateopt = (strcmp(optarg, "0") == 0) ? 0 : 1;
1299       break;
1300     case ('s'):
1301       sort = tolower(optarg[0]);
1302       break;
1303     case ('D'):
1304       nodate = 1;
1305       break;
1306     case ('C'):
1307       nocreator = 1;
1308       break;
1309     case ('m'):
1310       domd5sum = 1;
1311       break;
1312     case ('v'):
1313       show++;
1314       if(show > 2)
1315         show=2;
1316       break;
1317     case ('q'):
1318       verbose = 0;
1319       break;
1320     case ('V'):
1321       printf(
1322         "buildtorrent " bt_version "\n"
1323         "Copyright (C) 2007-2010 Claude Heiland-Allen <claudiusmaximus@goto10.org>\n"
1324         "License GPLv2+: GNU GPL version 2 or later <http://gnu.org/licenses/gpl.html>\n"
1325       );
1326       return 0;
1327     case ('h'):
1328       bt_usage();
1329       return 0;
1330     }
1331   }
1332   if (!url) {
1333     fprintf(stderr, "buildtorrent: announce url required\n");
1334     return 1;
1335   }
1336   if (0 <= lplen && lplen < 31) {
1337     plen = 1 << lplen;
1338   }
1339   if (plen <= 0) { /* avoid division by zero */
1340     fprintf(stderr, "buildtorrent: piece length must be greater than 0\n");
1341     return 1;
1342   }
1343
1344   if (filelistfilename) {
1345     if (optind + 1 < argc) {
1346       fprintf(stderr, "buildtorrent: too many arguments\n");
1347       return 1;
1348     }
1349     if (optind + 1 > argc) {
1350       fprintf(stderr, "buildtorrent: too few arguments\n");
1351       return 1;
1352     }
1353     if (!nameflag) {
1354       fprintf(stderr, "buildtorrent: missing '-n', required when using '-f'\n");
1355       return 1;
1356     }
1357     inname = NULL;
1358     outfile = argv[optind];
1359   } else {
1360     if (optind + 2 < argc) {
1361       fprintf(stderr, "buildtorrent: too many arguments\n");
1362       return 1;
1363     }
1364     if (optind + 2 > argc) {
1365       fprintf(stderr, "buildtorrent: too few arguments\n");
1366       return 1;
1367     }
1368     inname  = argv[optind];
1369     outfile = argv[optind + 1];
1370   }
1371
1372   /* handle paths correctly (note: requires POSIX basename(), not GNU) */
1373   if (inname) {
1374     if (strlen(inname) > 8190) {
1375       fprintf(stderr, "buildtorrent: 'input' argument too long\n");
1376       return 1;
1377     }
1378     strncpy(nametemp, inname, 8191);
1379     nametemp[8191] = '\0';
1380     namebase = basename(nametemp);
1381     slen = strlen(namebase);
1382     for (i = 0; i < slen; ++i) {
1383       if (namebase[i] == '/') {
1384         fprintf(
1385           stderr,
1386           "buildtorrent: BUG! input (\"%s\") munged (\"%s\") contains '/'.\n",
1387           inname,
1388           namebase
1389         );
1390         return 1;
1391       }
1392     }
1393   }
1394
1395   if (inname) {
1396     if (stat(inname, &s)) {
1397       fprintf(stderr, "buildtorrent: could not stat \"%s\"\n", inname);
1398       return 1;
1399     }
1400   }
1401
1402   torrent = bt_dictionary();
1403   info = bt_dictionary();
1404   announce = bt_string0(url);
1405   piecelength = bt_integer(plen);
1406   if (nameflag) {
1407     name = bt_string0(nameflag);
1408   } else {
1409     name = bt_string0(namebase);
1410   }
1411   bt_dictionary_insert0(info, "name", name);
1412   bt_dictionary_insert0(info, "piece length", piecelength);
1413   if (urls) {
1414     if (!(announcelist = bt_parse_announcelist(urls))) {
1415       fprintf(stderr, "buildtorrent: error parsing announce-list argument\n");
1416       return 1;
1417     }
1418   }
1419   if (wurls) {
1420     if (!(webseedlist = bt_parse_webseedlist(wurls))) {
1421       fprintf(stderr, "buildtorrent: error parsing webseed list argument\n");
1422       return 1;
1423     }
1424   }
1425   if (!(flist = bt_create_file_list())) {
1426     fprintf(stderr, "buildtorrent: couldn't allocate file list\n");
1427     return 1;
1428   }
1429   if (!(aflist = bt_create_afile_list())) {
1430     fprintf(stderr, "buildtorrent: couldn't allocate annotated file list\n");
1431     return 1;
1432   }
1433
1434   if (inname && S_ISDIR(s.st_mode)) {
1435
1436     multifile = 1;
1437     pathlist = bt_list();
1438     memcpy(path, inname, strlen(inname) + 1);
1439     if (bt_find_files(sort, 8192, path, pathlist, flist)) {
1440       fprintf(stderr, "buildtorrent: error finding files\n");
1441       return 1;
1442     }
1443
1444   } else if (inname && S_ISREG(s.st_mode)) {
1445
1446     multifile = 0;
1447     bt_file_list_prepend(flist, inname, NULL);
1448
1449   } else if (!inname) {
1450  
1451     multifile = 1;
1452     bt_error err = BT_ERROR_NONE;
1453     if (0 == strcmp("-", filelistfilename)) {
1454       err = bt_parse_filelist(flist, stdin);
1455     } else {
1456       FILE *filelistfile;
1457       if ((filelistfile = fopen(filelistfilename, "rb"))) {
1458         err = bt_parse_filelist(flist, filelistfile);
1459         fclose(filelistfile);
1460       } else {
1461         fprintf(stderr, "buildtorrent: couldn't open file list \"%s\"\n", filelistfilename);
1462         return 1;        
1463       }
1464     }
1465     if (err) {
1466       fprintf(stderr, "buildtorrent: error processing file list\n");
1467       return 1;
1468     }
1469
1470   } else {
1471
1472     fprintf(
1473       stderr, "buildtorrent: \"%s\" is neither file nor directory\n", inname
1474     );
1475     return 1;
1476
1477   }
1478
1479   if ((bt_annotate_files(flist, aflist))) {
1480     fprintf(stderr, "buildtorrent: error annotating file list\n");
1481     return 1;
1482   }
1483
1484   if (privated) {
1485     private = bt_integer(privateopt);
1486     bt_dictionary_insert0(info, "private", private);
1487   }  
1488   bt_dictionary_insert0(torrent, "announce", announce);
1489   if (urls) {
1490     bt_dictionary_insert0(torrent, "announce-list", announcelist);
1491   }
1492   if (wurls) {
1493     bt_dictionary_insert0(torrent, "url-list", webseedlist);
1494   }
1495   if (!nodate) {
1496     creationdate = bt_integer(time(NULL));
1497     bt_dictionary_insert0(torrent, "creation date", creationdate);
1498   }
1499   if (!nocreator) {
1500     creator = bt_string0("buildtorrent/" bt_version);
1501     bt_dictionary_insert0(torrent, "created by", creator);
1502   }
1503   if (commentstr) {
1504     comment = bt_string0(commentstr);
1505     bt_dictionary_insert0(torrent, "comment", comment);
1506   }
1507   if (!(output = fopen(outfile, "wb"))) {
1508     fprintf(stderr, "buildtorrent: couldn't open \"%s\" for writing\n", outfile);
1509     return 1;
1510   }
1511
1512   if (!(pieces = bt_hash_pieces(aflist, plen, domd5sum, verbose))) {
1513     fprintf(stderr, "buildtorrent: error hashing files\n");
1514     return 1;
1515   }
1516
1517   if (multifile) {
1518     files = bt_afile_list_info(aflist);
1519     bt_dictionary_insert0(info, "files", files);
1520   } else {
1521     bt_afile_list node = aflist->next;
1522     length = bt_integer(node->length);
1523     bt_dictionary_insert0(info, "length", length);
1524     if (node->md5sum) {
1525       bt_dictionary_insert0(info, "md5sum", node->md5sum);
1526     }
1527   }
1528
1529   bt_dictionary_insert0(info, "pieces", pieces);
1530   bt_dictionary_insert0(torrent, "info", info);
1531
1532   if (bt_write(output, torrent)) {
1533     fprintf(stderr, "buildtorrent: error writing \"%s\"\n", outfile);
1534     return 1;
1535   }
1536   if (show) {
1537     printf("torrent =>\n");
1538     bt_show(torrent, show == 2, 2, 2, 0);
1539   }
1540   fclose(output);
1541   return 0;
1542 }
1543
1544 /* EOF */