twx
[mkp224o.git] / main.c
blob6fc55b61c945e39c1ab0886f2e2f2f846656be1f
1 #ifdef __linux__
2 #define _POSIX_C_SOURCE 200112L
3 #endif
5 #include <stdio.h>
6 #include <stdlib.h>
7 #include <stdint.h>
8 #include <string.h>
9 #include <time.h>
10 #include <pthread.h>
11 #include <signal.h>
12 #include <sodium/randombytes.h>
13 #include <sodium/utils.h>
15 #include "types.h"
16 #include "likely.h"
17 #include "vec.h"
18 #include "base32.h"
19 #include "cpucount.h"
20 #include "keccak.h"
21 #include "ed25519/ed25519.h"
22 #include "ioutil.h"
24 #ifndef _WIN32
25 #define FSZ "%zu"
26 #else
27 #define FSZ "%Iu"
28 #endif
30 // additional 0 terminator is added by C
31 static const char * const pkprefix = "== ed25519v1-public: type0 ==\0\0";
32 #define pkprefixlen (29 + 3)
33 static const char * const skprefix = "== ed25519v1-secret: type0 ==\0\0";
34 #define skprefixlen (29 + 3)
35 static const char * const checksumstr = ".onion checksum";
36 #define checksumstrlen 15
38 // output directory
39 static char *workdir = 0;
40 static size_t workdirlen = 0;
42 static int quietflag = 0;
43 //static int wantdedup = 0;
44 #define wantdedup 0
46 #define SECRET_LEN 64
47 #define PUBLIC_LEN 32
48 #define SEED_LEN 32
49 // with checksum + version num
50 #define PUBONION_LEN (PUBLIC_LEN + 3)
51 // with newline included
52 #define ONIONLEN 62
54 static size_t onionendpos; // end of .onion within string
55 static size_t direndpos; // end of dir before .onion within string
56 static size_t printstartpos; // where to start printing from
57 static size_t printlen; // precalculated, related to printstartpos
59 static pthread_mutex_t fout_mutex;
60 static FILE *fout;
61 static size_t numneedgenerate = 0;
62 static int numwords = 1;
63 static pthread_mutex_t keysgenerated_mutex;
64 static volatile size_t keysgenerated = 0;
65 static volatile int endwork = 0;
67 static void termhandler(int sig)
69 switch (sig) {
70 case SIGTERM:
71 case SIGINT:
72 endwork = 1;
73 break;
77 #include "filters.h"
79 #ifdef STATISTICS
80 #define ADDNUMSUCCESS ++st->numsuccess.v
81 #else
82 #define ADDNUMSUCCESS do ; while (0)
83 #endif
85 // statistics, if enabled
86 #ifdef STATISTICS
87 struct statstruct {
88 union {
89 u32 v;
90 size_t align;
91 } numcalc;
92 union {
93 u32 v;
94 size_t align;
95 } numsuccess;
96 union {
97 u32 v;
98 size_t align;
99 } numrestart;
101 VEC_STRUCT(statsvec,struct statstruct);
103 struct tstatstruct {
104 u64 numcalc;
105 u64 numsuccess;
106 u64 numrestart;
107 u32 oldnumcalc;
108 u32 oldnumsuccess;
109 u32 oldnumrestart;
111 VEC_STRUCT(tstatsvec,struct tstatstruct);
112 #endif
115 static void onionready(char *sname,const u8 *secret,const u8 *pubonion)
117 if (endwork)
118 return;
120 if (numneedgenerate) {
121 pthread_mutex_lock(&keysgenerated_mutex);
122 if (keysgenerated >= numneedgenerate) {
123 pthread_mutex_unlock(&keysgenerated_mutex);
124 return;
128 if (createdir(sname,1) != 0) {
129 if (numneedgenerate)
130 pthread_mutex_unlock(&keysgenerated_mutex);
131 return;
134 if (numneedgenerate) {
135 ++keysgenerated;
136 if (keysgenerated >= numneedgenerate)
137 endwork = 1;
138 pthread_mutex_unlock(&keysgenerated_mutex);
141 strcpy(&sname[onionendpos],"/hs_ed25519_secret_key");
142 writetofile(sname,secret,skprefixlen + SECRET_LEN,1);
144 strcpy(&sname[onionendpos],"/hostname");
145 FILE *hfile = fopen(sname,"w");
146 if (hfile) {
147 sname[onionendpos] = '\n';
148 fwrite(&sname[direndpos],ONIONLEN + 1,1,hfile);
149 fclose(hfile);
152 strcpy(&sname[onionendpos],"/hs_ed25519_public_key");
153 writetofile(sname,pubonion,pkprefixlen + PUBLIC_LEN,0);
155 if (fout) {
156 sname[onionendpos] = '\n';
157 pthread_mutex_lock(&fout_mutex);
158 fwrite(&sname[printstartpos],printlen,1,fout);
159 fflush(fout);
160 pthread_mutex_unlock(&fout_mutex);
164 // little endian inc
165 static void addsk32(u8 *sk)
167 register unsigned int c = 8;
168 for (size_t i = 0;i < 32;++i) {
169 c = (unsigned int)sk[i] + c; sk[i] = c & 0xFF; c >>= 8;
170 // unsure if needed
171 if (!c) break;
175 // 0123 4567 xxxx --3--> 3456 7xxx
176 // 0123 4567 xxxx --1--> 1234 567x
177 static inline void shiftpk(u8 *dst,const u8 *src,size_t sbits)
179 size_t i,sbytes = sbits / 8;
180 sbits %= 8;
181 for (i = 0;i + sbytes < PUBLIC_LEN;++i) {
182 dst[i] = (u8) ((src[i+sbytes] << sbits) |
183 (src[i+sbytes+1] >> (8 - sbits)));
185 for(;i < PUBLIC_LEN;++i)
186 dst[i] = 0;
189 static void *dowork(void *task)
191 union pubonionunion {
192 u8 raw[pkprefixlen + PUBLIC_LEN + 32];
193 struct {
194 u64 prefix[4];
195 u64 key[4];
196 u64 hash[4];
197 } i;
198 } pubonion;
199 u8 * const pk = &pubonion.raw[pkprefixlen];
200 u8 secret[skprefixlen + SECRET_LEN];
201 u8 * const sk = &secret[skprefixlen];
202 u8 seed[SEED_LEN];
203 u8 hashsrc[checksumstrlen + PUBLIC_LEN + 1];
204 u8 wpk[PUBLIC_LEN + 1];
205 size_t i;
206 char *sname;
207 #ifdef STATISTICS
208 struct statstruct *st = (struct statstruct *)task;
209 #endif
210 PREFILTER
212 memcpy(secret,skprefix,skprefixlen);
213 wpk[PUBLIC_LEN] = 0;
214 memset(&pubonion,0,sizeof(pubonion));
215 memcpy(pubonion.raw,pkprefix,pkprefixlen);
216 // write version later as it will be overwritten by hash
217 memcpy(hashsrc,checksumstr,checksumstrlen);
218 hashsrc[checksumstrlen + PUBLIC_LEN] = 0x03; // version
220 sname = malloc(workdirlen + ONIONLEN + 63 + 1);
221 if (!sname)
222 abort();
223 if (workdir)
224 memcpy(sname,workdir,workdirlen);
226 initseed:
227 randombytes(seed,sizeof(seed));
228 ed25519_seckey_expand(sk,seed);
229 #ifdef STATISTICS
230 ++st->numrestart.v;
231 #endif
233 again:
234 if (unlikely(endwork))
235 goto end;
237 ed25519_pubkey(pk,sk);
239 #ifdef STATISTICS
240 ++st->numcalc.v;
241 #endif
243 DOFILTER(i,pk,{
244 if (numwords > 1) {
245 shiftpk(wpk,pk,filter_len(i));
246 size_t j;
247 for (int w = 1;;) {
248 DOFILTER(j,wpk,goto secondfind);
249 goto next;
250 secondfind:
251 if (++w >= numwords)
252 break;
253 shiftpk(wpk,wpk,filter_len(j));
256 // sanity check
257 if ((sk[0] & 248) != sk[0] || ((sk[31] & 63) | 64) != sk[31])
258 goto initseed;
260 ADDNUMSUCCESS;
262 // calc checksum
263 memcpy(&hashsrc[checksumstrlen],pk,PUBLIC_LEN);
264 FIPS202_SHA3_256(hashsrc,sizeof(hashsrc),&pk[PUBLIC_LEN]);
265 // version byte
266 pk[PUBLIC_LEN + 2] = 0x03;
267 // base32
268 strcpy(base32_to(&sname[direndpos],pk,PUBONION_LEN),".onion");
269 onionready(sname,secret,pubonion.raw);
270 pk[PUBLIC_LEN] = 0;
271 goto initseed;
273 next:
274 addsk32(sk);
275 goto again;
277 end:
278 free(sname);
279 POSTFILTER
280 sodium_memzero(secret,sizeof(secret));
281 sodium_memzero(seed,sizeof(seed));
282 return 0;
285 static void addsztoscalar32(u8 *dst,size_t v)
287 int i;
288 u32 c = 0;
289 for (i = 0;i < 32;++i) {
290 c += *dst + (v & 0xFF); *dst = c & 0xFF; c >>= 8;
291 v >>= 8;
292 ++dst;
296 static void *dofastwork(void *task)
298 union pubonionunion {
299 u8 raw[pkprefixlen + PUBLIC_LEN + 32];
300 struct {
301 u64 prefix[4];
302 u64 key[4];
303 u64 hash[4];
304 } i;
305 } pubonion;
306 u8 * const pk = &pubonion.raw[pkprefixlen];
307 u8 secret[skprefixlen + SECRET_LEN];
308 u8 * const sk = &secret[skprefixlen];
309 u8 seed[SEED_LEN];
310 u8 hashsrc[checksumstrlen + PUBLIC_LEN + 1];
311 u8 wpk[PUBLIC_LEN + 1];
312 ge_p3 ge_public;
313 size_t counter;
314 size_t i;
315 char *sname;
316 #ifdef STATISTICS
317 struct statstruct *st = (struct statstruct *)task;
318 #endif
319 PREFILTER
321 memcpy(secret,skprefix,skprefixlen);
322 wpk[PUBLIC_LEN] = 0;
323 memset(&pubonion,0,sizeof(pubonion));
324 memcpy(pubonion.raw,pkprefix,pkprefixlen);
325 // write version later as it will be overwritten by hash
326 memcpy(hashsrc,checksumstr,checksumstrlen);
327 hashsrc[checksumstrlen + PUBLIC_LEN] = 0x03; // version
329 sname = malloc(workdirlen + ONIONLEN + 63 + 1);
330 if (!sname)
331 abort();
332 if (workdir)
333 memcpy(sname,workdir,workdirlen);
335 initseed:
336 #ifdef STATISTICS
337 ++st->numrestart.v;
338 #endif
339 randombytes(seed,sizeof(seed));
340 ed25519_seckey_expand(sk,seed);
342 ge_scalarmult_base(&ge_public,sk);
343 ge_p3_tobytes(pk,&ge_public);
345 for (counter = 0;counter < SIZE_MAX-8;counter += 8) {
346 ge_p1p1 sum;
348 if (unlikely(endwork))
349 goto end;
351 DOFILTER(i,pk,{
352 if (numwords > 1) {
353 shiftpk(wpk,pk,filter_len(i));
354 size_t j;
355 for (int w = 1;;) {
356 DOFILTER(j,wpk,goto secondfind);
357 goto next;
358 secondfind:
359 if (++w >= numwords)
360 break;
361 shiftpk(wpk,wpk,filter_len(j));
364 // found!
365 // update secret key with counter
366 addsztoscalar32(sk,counter);
367 // sanity check
368 if ((sk[0] & 248) != sk[0] || ((sk[31] & 63) | 64) != sk[31])
369 goto initseed;
371 ADDNUMSUCCESS;
373 // calc checksum
374 memcpy(&hashsrc[checksumstrlen],pk,PUBLIC_LEN);
375 FIPS202_SHA3_256(hashsrc,sizeof(hashsrc),&pk[PUBLIC_LEN]);
376 // version byte
377 pk[PUBLIC_LEN + 2] = 0x03;
378 // full name
379 strcpy(base32_to(&sname[direndpos],pk,PUBONION_LEN),".onion");
380 onionready(sname,secret,pubonion.raw);
381 pk[PUBLIC_LEN] = 0;
382 // don't reuse same seed
383 goto initseed;
385 next:
386 ge_add(&sum, &ge_public,&ge_eightpoint);
387 ge_p1p1_to_p3(&ge_public,&sum);
388 ge_p3_tobytes(pk,&ge_public);
389 #ifdef STATISTICS
390 ++st->numcalc.v;
391 #endif
393 goto initseed;
395 end:
396 free(sname);
397 POSTFILTER
398 sodium_memzero(secret,sizeof(secret));
399 sodium_memzero(seed,sizeof(seed));
400 return 0;
403 static void printhelp(FILE *out,const char *progname)
405 fprintf(out,
406 "Usage: %s filter [filter...] [options]\n"
407 " %s -f filterfile [options]\n"
408 "Options:\n"
409 "\t-h - print help to stdout and quit\n"
410 "\t-f - instead of specifying filter(s) via commandline, specify filter file which contains filters separated by newlines\n"
411 "\t-q - do not print diagnostic output to stderr\n"
412 "\t-x - do not print onion names\n"
413 "\t-o filename - output onion names to specified file\n"
414 "\t-F - include directory names in onion names output\n"
415 "\t-d dirname - output directory\n"
416 "\t-t numthreads - specify number of threads (default - auto)\n"
417 "\t-j numthreads - same as -t\n"
418 "\t-n numkeys - specify number of keys (default - 0 - unlimited)\n"
419 "\t-N numwords - specify number of words per key (default - 1)\n"
420 "\t-z - use faster key generation method. this is now default\n"
421 "\t-Z - use slower key generation method\n"
422 "\t-s - print statistics each 10 seconds\n"
423 "\t-S t - print statistics every specified ammount of seconds\n"
424 "\t-T - do not reset statistics counters when printing\n"
425 ,progname,progname);
426 fflush(out);
429 enum {
430 Q_ADDITIONAL = 100,
431 Q_UNRECOGNISED = 101,
432 Q_NOSTATISTICS = 102,
433 Q_FAILOPENOUTPUT = 103,
434 Q_FAILTHREAD = 104,
435 Q_FAILTIME = 105,
438 static void e_additional()
440 fprintf(stderr,"additional argument required\n");
441 exit(Q_ADDITIONAL);
444 #ifndef STATISTICS
445 static void e_nostatistics()
447 fprintf(stderr,"statistics support not compiled in\n");
448 exit(Q_NOSTATISTICS);
450 #endif
452 static void setworkdir(const char *wd)
454 free(workdir);
455 size_t l = strlen(wd);
456 if (!l) {
457 workdir = 0;
458 workdirlen = 0;
459 if (!quietflag)
460 fprintf(stderr,"unset workdir\n");
461 return;
463 unsigned needslash = 0;
464 if (wd[l-1] != '/')
465 needslash = 1;
466 char *s = malloc(l + needslash + 1);
467 if (!s)
468 abort();
469 memcpy(s,wd,l);
470 if (needslash)
471 s[l++] = '/';
472 s[l] = 0;
474 workdir = s;
475 workdirlen = l;
476 if (!quietflag)
477 fprintf(stderr,"set workdir: %s\n",workdir);
480 VEC_STRUCT(threadvec, pthread_t);
482 int main(int argc,char **argv)
484 char *outfile = 0;
485 const char *arg;
486 int ignoreargs = 0;
487 int dirnameflag = 0;
488 int numthreads = 0;
489 int fastkeygen = 1;
490 struct threadvec threads;
491 #ifdef STATISTICS
492 struct statsvec stats;
493 struct tstatsvec tstats;
494 u64 reportdelay = 0;
495 int realtimestats = 1;
496 #endif
497 int tret;
499 ge_initeightpoint();
500 filters_init();
502 setvbuf(stderr,0,_IONBF,0);
503 fout = stdout;
504 pthread_mutex_init(&keysgenerated_mutex,0);
505 pthread_mutex_init(&fout_mutex,0);
507 const char *progname = argv[0];
508 if (argc <= 1) {
509 printhelp(stderr,progname);
510 exit(1);
512 argc--; argv++;
514 while (argc--) {
515 arg = *argv++;
516 if (!ignoreargs && *arg == '-') {
517 int numargit = 0;
518 nextarg:
519 ++arg;
520 ++numargit;
521 if (*arg == '-') {
522 if (numargit > 1) {
523 fprintf(stderr,"unrecognised argument: -\n");
524 exit(Q_UNRECOGNISED);
526 ++arg;
527 if (!*arg)
528 ignoreargs = 1;
529 else if (!strcmp(arg,"help") || !strcmp(arg,"usage")) {
530 printhelp(stdout,progname);
531 exit(0);
533 else {
534 fprintf(stderr,"unrecognised argument: --%s\n",arg);
535 exit(Q_UNRECOGNISED);
537 numargit = 0;
539 else if (*arg == 0) {
540 if (numargit == 1)
541 ignoreargs = 1;
542 continue;
544 else if (*arg == 'h') {
545 printhelp(stdout,progname);
546 exit(0);
548 else if (*arg == 'f') {
549 if (argc--)
550 loadfilterfile(*argv++);
551 else
552 e_additional();
554 else if (*arg == 'q')
555 ++quietflag;
556 else if (*arg == 'x')
557 fout = 0;
558 else if (*arg == 'o') {
559 if (argc--)
560 outfile = *argv++;
561 else
562 e_additional();
564 else if (*arg == 'F')
565 dirnameflag = 1;
566 else if (*arg == 'd') {
567 if (argc--)
568 setworkdir(*argv++);
569 else
570 e_additional();
572 else if (*arg == 't' || *arg == 'j') {
573 if (argc--)
574 numthreads = atoi(*argv++);
575 else
576 e_additional();
578 else if (*arg == 'n') {
579 if (argc--)
580 numneedgenerate = (size_t)atoll(*argv++);
581 else
582 e_additional();
584 else if (*arg == 'N') {
585 if (argc--)
586 numwords = atoi(*argv++);
587 else
588 e_additional();
590 else if (*arg == 'Z')
591 fastkeygen = 0;
592 else if (*arg == 'z')
593 fastkeygen = 1;
594 else if (*arg == 's') {
595 #ifdef STATISTICS
596 reportdelay = 10000000;
597 #else
598 e_nostatistics();
599 #endif
601 else if (*arg == 'S') {
602 #ifdef STATISTICS
603 if (argc--)
604 reportdelay = (u64)atoll(*argv++) * 1000000;
605 else
606 e_additional();
607 #else
608 e_nostatistics();
609 #endif
611 else if (*arg == 'T') {
612 #ifdef STATISTICS
613 realtimestats = 0;
614 #else
615 e_nostatistics();
616 #endif
618 else {
619 fprintf(stderr,"unrecognised argument: -%c\n",*arg);
620 exit(Q_UNRECOGNISED);
622 if (numargit)
623 goto nextarg;
625 else filters_add(arg);
628 filters_prepare();
630 filters_print();
632 #ifdef STATISTICS
633 if (!filters_count() && !reportdelay)
634 #else
635 if (!filters_count())
636 #endif
637 return 0;
639 #ifdef EXPANDMASK
640 if (numwords > 1 && flattened)
641 fprintf(stderr,"WARNING: -N switch will produce bogus results because we can't know filter width. reconfigure with --enable-besort and recompile.\n");
642 #endif
644 if (outfile) {
645 fout = fopen(outfile,"w");
646 if (!fout) {
647 perror("failed to open output file");
648 exit(Q_FAILOPENOUTPUT);
652 if (workdir)
653 createdir(workdir,1);
655 direndpos = workdirlen;
656 onionendpos = workdirlen + ONIONLEN;
658 if (!dirnameflag) {
659 printstartpos = direndpos;
660 printlen = ONIONLEN + 1;
661 } else {
662 printstartpos = 0;
663 printlen = onionendpos + 1;
666 if (numthreads <= 0) {
667 numthreads = cpucount();
668 if (numthreads <= 0)
669 numthreads = 1;
670 if (!quietflag)
671 fprintf(stderr,"using %d %s\n",
672 numthreads,numthreads == 1 ? "thread" : "threads");
675 signal(SIGTERM,termhandler);
676 signal(SIGINT,termhandler);
678 VEC_INIT(threads);
679 VEC_ADDN(threads,numthreads);
680 #ifdef STATISTICS
681 VEC_INIT(stats);
682 VEC_ADDN(stats,numthreads);
683 VEC_ZERO(stats);
684 VEC_INIT(tstats);
685 VEC_ADDN(tstats,numthreads);
686 VEC_ZERO(tstats);
687 #endif
689 for (size_t i = 0;i < VEC_LENGTH(threads);++i) {
690 void *tp = 0;
691 #ifdef STATISTICS
692 tp = &VEC_BUF(stats,i);
693 #endif
694 tret = pthread_create(&VEC_BUF(threads,i),0,fastkeygen ? dofastwork : dowork,tp);
695 if (tret) {
696 fprintf(stderr,"error while making " FSZ "th thread: %d (%s)\n",i,tret,strerror(tret));
697 exit(Q_FAILTHREAD);
701 #ifdef STATISTICS
702 struct timespec nowtime;
703 u64 istarttime,inowtime,ireporttime = 0,elapsedoffset = 0;
704 if (clock_gettime(CLOCK_MONOTONIC,&nowtime) < 0) {
705 perror("failed to get time");
706 exit(Q_FAILTIME);
708 istarttime = (1000000 * (u64)nowtime.tv_sec) + ((u64)nowtime.tv_nsec / 1000);
709 #endif
710 struct timespec ts;
711 memset(&ts,0,sizeof(ts));
712 ts.tv_nsec = 100000000;
713 while (!endwork) {
714 if (numneedgenerate && keysgenerated >= numneedgenerate) {
715 endwork = 1;
716 break;
718 nanosleep(&ts,0);
720 #ifdef STATISTICS
721 clock_gettime(CLOCK_MONOTONIC,&nowtime);
722 inowtime = (1000000 * (u64)nowtime.tv_sec) + ((u64)nowtime.tv_nsec / 1000);
723 u64 sumcalc = 0,sumsuccess = 0,sumrestart = 0;
724 for (int i = 0;i < numthreads;++i) {
725 u32 newt,tdiff;
726 // numcalc
727 newt = VEC_BUF(stats,i).numcalc.v;
728 tdiff = newt - VEC_BUF(tstats,i).oldnumcalc;
729 VEC_BUF(tstats,i).oldnumcalc = newt;
730 VEC_BUF(tstats,i).numcalc += (u64)tdiff;
731 sumcalc += VEC_BUF(tstats,i).numcalc;
732 // numsuccess
733 newt = VEC_BUF(stats,i).numsuccess.v;
734 tdiff = newt - VEC_BUF(tstats,i).oldnumsuccess;
735 VEC_BUF(tstats,i).oldnumsuccess = newt;
736 VEC_BUF(tstats,i).numsuccess += (u64)tdiff;
737 sumsuccess += VEC_BUF(tstats,i).numsuccess;
738 // numrestart
739 newt = VEC_BUF(stats,i).numrestart.v;
740 tdiff = newt - VEC_BUF(tstats,i).oldnumrestart;
741 VEC_BUF(tstats,i).oldnumrestart = newt;
742 VEC_BUF(tstats,i).numrestart += (u64)tdiff;
743 sumrestart += VEC_BUF(tstats,i).numrestart;
745 if (reportdelay && (!ireporttime || inowtime - ireporttime >= reportdelay)) {
746 if (ireporttime)
747 ireporttime += reportdelay;
748 else
749 ireporttime = inowtime;
750 if (!ireporttime)
751 ireporttime = 1;
753 double calcpersec = (1000000.0 * sumcalc) / (inowtime - istarttime);
754 double succpersec = (1000000.0 * sumsuccess) / (inowtime - istarttime);
755 double restpersec = (1000000.0 * sumrestart) / (inowtime - istarttime);
756 fprintf(stderr,">calc/sec:%8lf, succ/sec:%8lf, rest/sec:%8lf, elapsed:%5.6lfsec\n",
757 calcpersec,succpersec,restpersec,
758 (inowtime - istarttime + elapsedoffset) / 1000000.0);
760 if (realtimestats) {
761 for (int i = 0;i < numthreads;++i) {
762 VEC_BUF(tstats,i).numcalc = 0;
763 VEC_BUF(tstats,i).numsuccess = 0;
764 VEC_BUF(tstats,i).numrestart = 0;
766 elapsedoffset += inowtime - istarttime;
767 istarttime = inowtime;
770 if (sumcalc > U64_MAX / 2) {
771 for (int i = 0;i < numthreads;++i) {
772 VEC_BUF(tstats,i).numcalc /= 2;
773 VEC_BUF(tstats,i).numsuccess /= 2;
774 VEC_BUF(tstats,i).numrestart /= 2;
776 u64 timediff = (inowtime - istarttime + 1) / 2;
777 elapsedoffset += timediff;
778 istarttime += timediff;
780 #endif
783 if (!quietflag)
784 fprintf(stderr,"waiting for threads to finish...");
785 for (size_t i = 0;i < VEC_LENGTH(threads);++i)
786 pthread_join(VEC_BUF(threads,i),0);
787 if (!quietflag)
788 fprintf(stderr," done.\n");
790 pthread_mutex_destroy(&keysgenerated_mutex);
791 pthread_mutex_destroy(&fout_mutex);
792 filters_clean();
794 if (outfile)
795 fclose(fout);
797 return 0;