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