Now flushes stdout when writing progress in non-quiet mode.
[mymail.git] / mymail.c
1
2 /*
3  *  Copyright (c) 2013 Francois Fleuret
4  *  Written by Francois Fleuret <francois@fleuret.org>
5  *
6  *  This file is part of mymail.
7  *
8  *  mymail is free software: you can redistribute it and/or modify
9  *  it under the terms of the GNU General Public License version 3 as
10  *  published by the Free Software Foundation.
11  *
12  *  mymail is distributed in the hope that it will be useful, but
13  *  WITHOUT ANY WARRANTY; without even the implied warranty of
14  *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
15  *  General Public License for more details.
16  *
17  *  You should have received a copy of the GNU General Public License
18  *  along with mymail.  If not, see <http://www.gnu.org/licenses/>.
19  *
20  */
21
22 /*
23
24   This command is a dumb mail indexer. It can either (1) scan
25   directories containing mbox files, and create a db file containing
26   for each mail a list of fields computed from the header, or (2)
27   read such a db file and get all the mails matching regexp-defined
28   conditions on the fields.
29
30   It is low-tech, simple, light and fast.
31
32 */
33
34 #define _GNU_SOURCE
35
36 #include <stdio.h>
37 #include <stdlib.h>
38 #include <string.h>
39 #include <errno.h>
40 #include <fcntl.h>
41 #include <locale.h>
42 #include <getopt.h>
43 #include <limits.h>
44 #include <dirent.h>
45 #include <regex.h>
46
47 #define MYMAIL_DB_MAGIC_TOKEN "mymail_index_file"
48 #define VERSION "0.9.1"
49
50 #define MAX_NB_SEARCH_CONDITIONS 10
51
52 #define BUFFER_SIZE 65536
53
54 char *db_filename;
55 char *db_filename_regexp_string;
56 char *db_root_path;
57 char *db_filename_list;
58 char output_filename[PATH_MAX + 1];
59
60 int paranoid;
61 int action_index;
62 int quiet;
63
64 /********************************************************************/
65
66 enum {
67   ID_MAIL = 0,
68   ID_FROM,
69   ID_TO,
70   ID_SUBJECT,
71   ID_DATE,
72   ID_PARTICIPANT,
73   ID_BODY,
74   MAX_ID
75 };
76
77 static char *field_names[] = {
78   "mail",
79   "from",
80   "to",
81   "subject",
82   "date",
83   "part",
84   "body"
85 };
86
87 /********************************************************************/
88
89 struct search_condition {
90   int field_id;
91   int negation;
92   regex_t regexp;
93 };
94
95 /********************************************************************/
96
97 struct parsable_field {
98   int id;
99   char *regexp_string;
100   regex_t regexp;
101 };
102
103 static struct parsable_field fields_to_parse[] = {
104   {
105     ID_FROM,
106     "^\\(From \\|[Ff][Rr][Oo][Mm]:\\|[R][r][E][e][P][p][L][l][Y][y]-[T][t][O][o]:\\)",
107     { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }
108   },
109
110   {
111     ID_TO,
112     "^\\([Tt][Oo]\\|[Cc][Cc]\\|[Bb][Cc][Cc]\\): ",
113     { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }
114   },
115
116   {
117     ID_SUBJECT,
118     "^[Ss][Uu][Bb][Jj][Ee][Cc][Tt]: ",
119     { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }
120   },
121
122   {
123     ID_DATE,
124     "^[Dd][Aa][Tt][Ee]: ",
125     { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }
126   },
127
128 };
129
130 /********************************************************************/
131
132 int xor(int a, int b) {
133   return (a && !b) || (!a && b);
134 }
135
136 char *segment_next_field(char *current) {
137   while(*current && *current != ' ') current++;
138   *current = '\0'; current++;
139   while(*current && *current == ' ') current++;
140   return current;
141 }
142
143 void remove_eof(char *c) {
144   while(*c && *c != '\n' && *c != '\r') c++;
145   *c = '\0';
146 }
147
148 /********************************************************************/
149
150 /* malloc with error checking.  */
151
152 void *safe_malloc(size_t n) {
153   void *p = malloc(n);
154   if(!p && n != 0) {
155     fprintf(stderr,
156             "mymail: cannot allocate memory: %s\n", strerror(errno));
157     exit(EXIT_FAILURE);
158   }
159   return p;
160 }
161
162 /*********************************************************************/
163
164 void print_version(FILE *out) {
165   fprintf(out, "mymail version %s (%s)\n", VERSION, UNAME);
166 }
167
168 void print_usage(FILE *out) {
169   print_version(out);
170   fprintf(out, "Written by Francois Fleuret <francois@fleuret.org>.\n");
171   fprintf(out, "\n");
172   fprintf(out, "Usage: mymail [options] [<mbox dir1> [<mbox dir2> ...]|<db file1> [<db file2> ...]]\n");
173   fprintf(out, "\n");
174   fprintf(out, " -h, --help\n");
175   fprintf(out, "         show this help\n");
176   fprintf(out, " -v, --version\n");
177   fprintf(out, "         print the version number\n");
178   fprintf(out, " -q, --quiet\n");
179   fprintf(out, "         do not print information during search\n");
180   fprintf(out, " -p <db filename pattern>, --db-pattern <db filename pattern>\n");
181   fprintf(out, "         set the db filename pattern for recursive search\n");
182   fprintf(out, " -r <db root path>, --db-root <db root path>\n");
183   fprintf(out, "         set the db root path for recursive search\n");
184   fprintf(out, " -l <db filename list>, --db-list <db filename list>\n");
185   fprintf(out, "         set the semicolon-separated list of db files for search\n");
186   fprintf(out, " -s <search pattern>, --search <search pattern>\n");
187   fprintf(out, "         search for matching mails in the db file\n");
188   fprintf(out, " -d <db filename>, --db-file <db filename>\n");
189   fprintf(out, "         set the db filename for indexing\n");
190   fprintf(out, " -i, --index\n");
191   fprintf(out, "         index mails\n");
192   fprintf(out, " -o <output filename>, --output <output filename>\n");
193   fprintf(out, "         set the result file, use stdout if unset\n");
194 }
195
196 /*********************************************************************/
197
198 int ignore_entry(const char *name) {
199   return
200     /* strcmp(name, ".") == 0 || */
201     /* strcmp(name, "..") == 0 || */
202     (name[0] == '.' && name[1] != '/');
203 }
204
205 int mbox_line_match_search(struct search_condition *condition,
206                            int mbox_id, char *mbox_value) {
207   return
208     (condition->field_id == mbox_id ||
209      (condition->field_id == ID_PARTICIPANT && (mbox_id == ID_FROM || mbox_id == ID_TO)))
210     &&
211     regexec(&condition->regexp, mbox_value, 0, 0, 0) == 0;
212 }
213
214 void search_in_db(FILE *db_file,
215                   int nb_search_conditions,
216                   struct search_condition *search_conditions,
217                   FILE *output_file) {
218
219   int hits[MAX_NB_SEARCH_CONDITIONS];
220   char raw_db_line[BUFFER_SIZE];
221   char raw_mbox_line[BUFFER_SIZE];
222   char current_mail_filename[PATH_MAX + 1];
223   unsigned long int current_position_in_mail;
224   char *mbox_name, *mbox_value;
225   int mbox_id;
226   int already_written, m, n;
227   int last_mbox_line_was_empty;
228   int nb_body_conditions, nb_fulfilled_body_conditions;
229
230   current_position_in_mail = 0;
231   already_written = 0;
232
233   for(n = 0; n < nb_search_conditions; n++) { hits[n] = 0; }
234
235   nb_body_conditions = 0;
236   for(n = 0; n < nb_search_conditions; n++) {
237     if(search_conditions[n].field_id == ID_BODY) {
238       nb_body_conditions++;
239     }
240   }
241
242   strcpy(current_mail_filename, "");
243
244   while(fgets(raw_db_line, BUFFER_SIZE, db_file)) {
245     mbox_name = raw_db_line;
246     mbox_value = segment_next_field(raw_db_line);
247
248     if(strcmp("mail", mbox_name) == 0) {
249       char *position_in_file_string;
250       char *mail_filename;
251
252       if(current_mail_filename[0]) {
253
254         /* We first check all conditions but the body ones */
255
256         for(n = 0; n < nb_search_conditions &&
257               ((search_conditions[n].field_id == ID_BODY) ||
258                xor(hits[n], search_conditions[n].negation)); n++);
259
260         if(n == nb_search_conditions) {
261
262           /* all conditions but the body ones are fine, check the body
263              ones */
264
265           nb_fulfilled_body_conditions = 0;
266
267           if(nb_body_conditions > 0) {
268             FILE *mail_file;
269             int header;
270
271             header = 1;
272             mail_file = fopen(current_mail_filename, "r");
273
274             if(!mail_file) {
275               fprintf(stderr,
276                       "mymail: Cannot open mbox '%s' for body scan.\n",
277                       current_mail_filename);
278               exit(EXIT_FAILURE);
279             }
280
281             fseek(mail_file, current_position_in_mail, SEEK_SET);
282
283             if(fgets(raw_mbox_line, BUFFER_SIZE, mail_file)) {
284               while(nb_fulfilled_body_conditions < nb_body_conditions) {
285                 last_mbox_line_was_empty = (raw_mbox_line[0] == '\n');
286
287                 if(last_mbox_line_was_empty) { header = 0; }
288
289                 if(!header) {
290                   for(n = 0; n < nb_search_conditions; n++) {
291                     if(search_conditions[n].field_id == ID_BODY && !hits[n]) {
292                       hits[n] =
293                         (regexec(&search_conditions[n].regexp, raw_mbox_line, 0, 0, 0) == 0);
294                       if(hits[n]) {
295                         nb_fulfilled_body_conditions++;
296                       }
297                     }
298                   }
299                 }
300
301                 if(!fgets(raw_mbox_line, BUFFER_SIZE, mail_file) ||
302                    (last_mbox_line_was_empty && strncmp(raw_mbox_line, "From ", 5) == 0))
303                   break;
304               }
305             }
306
307             fclose(mail_file);
308           }
309
310           if(nb_body_conditions == nb_fulfilled_body_conditions) {
311             FILE *mail_file;
312
313             mail_file = fopen(current_mail_filename, "r");
314
315             if(!mail_file) {
316               fprintf(stderr,
317                       "mymail: Cannot open mbox '%s' for mail extraction.\n",
318                       current_mail_filename);
319               exit(EXIT_FAILURE);
320             }
321
322             fseek(mail_file, current_position_in_mail, SEEK_SET);
323
324             if(fgets(raw_mbox_line, BUFFER_SIZE, mail_file)) {
325               last_mbox_line_was_empty = 1;
326               fprintf(output_file, "%s", raw_mbox_line);
327               while(1) {
328                 if(!fgets(raw_mbox_line, BUFFER_SIZE, mail_file) ||
329                    (last_mbox_line_was_empty && strncmp(raw_mbox_line, "From ", 5) == 0))
330                   break;
331                 last_mbox_line_was_empty = (raw_mbox_line[0] == '\n');
332                 fprintf(output_file, "%s", raw_mbox_line);
333               }
334             }
335
336             fclose(mail_file);
337           }
338         }
339       }
340
341       for(n = 0; n < nb_search_conditions; n++) { hits[n] = 0; }
342
343       position_in_file_string = mbox_value;
344       mail_filename = segment_next_field(mbox_value);
345       current_position_in_mail = atol(position_in_file_string);
346       strcpy(current_mail_filename, mail_filename);
347
348       remove_eof(current_mail_filename);
349       already_written = 0;
350     }
351
352     else {
353       mbox_id = -1;
354       for(m = 0; (m < MAX_ID) && mbox_id == -1; m++) {
355         if(strncmp(field_names[m], mbox_name, strlen(mbox_name)) == 0) {
356           mbox_id = m;
357         }
358       }
359       for(n = 0; n < nb_search_conditions; n++) {
360         hits[n] |= mbox_line_match_search(&search_conditions[n],
361                                           mbox_id, mbox_value);
362       }
363     }
364   }
365 }
366
367 void recursive_search_in_db(const char *entry_name, regex_t *db_filename_regexp,
368                             int nb_search_conditions,
369                             struct search_condition *search_conditions,
370                             FILE *output_file) {
371   DIR *dir;
372   struct dirent *dir_e;
373   struct stat sb;
374   char raw_db_line[BUFFER_SIZE];
375   char subname[PATH_MAX + 1];
376
377   if(lstat(entry_name, &sb) != 0) {
378     fprintf(stderr,
379             "mymail: Cannot stat \"%s\": %s\n",
380             entry_name,
381             strerror(errno));
382     exit(EXIT_FAILURE);
383   }
384
385   dir = opendir(entry_name);
386
387   if(dir) {
388     while((dir_e = readdir(dir))) {
389       if(!ignore_entry(dir_e->d_name)) {
390         snprintf(subname, PATH_MAX, "%s/%s", entry_name, dir_e->d_name);
391         recursive_search_in_db(subname, db_filename_regexp,
392                                nb_search_conditions, search_conditions,
393                                output_file);
394       }
395     }
396     closedir(dir);
397   }
398
399   else {
400     const char *s = entry_name, *filename = entry_name;
401     while(*s) { if(*s == '/') { filename = s+1; } s++; }
402
403     if(regexec(db_filename_regexp, filename, 0, 0, 0) == 0) {
404       FILE *db_file = fopen(entry_name, "r");
405
406       if(!quiet) {
407         printf("Searching in '%s' ... ", entry_name);
408         fflush(stdout);
409       }
410
411       if(!db_file) {
412         fprintf(stderr,
413                 "mymail: Cannot open \"%s\" for reading: %s\n",
414                 db_filename,
415                 strerror(errno));
416         exit(EXIT_FAILURE);
417       }
418
419       if(fgets(raw_db_line, BUFFER_SIZE, db_file)) {
420         if(strncmp(raw_db_line, MYMAIL_DB_MAGIC_TOKEN, strlen(MYMAIL_DB_MAGIC_TOKEN))) {
421           fprintf(stderr,
422                   "mymail: Header line in '%s' does not match the mymail db format.\n",
423                   entry_name);
424           exit(EXIT_FAILURE);
425         }
426       } else {
427         fprintf(stderr,
428                 "mymail: Cannot read the header line in '%s'.\n",
429                 entry_name);
430         exit(EXIT_FAILURE);
431       }
432
433       search_in_db(db_file, nb_search_conditions, search_conditions, output_file);
434
435       fclose(db_file);
436
437       if(!quiet) {
438         printf("done.\n");
439         fflush(stdout);
440       }
441     }
442   }
443 }
444
445 /*********************************************************************/
446
447 void index_one_mbox_line(int nb_fields_to_parse, struct parsable_field *fields_to_parse,
448                          char *raw_mbox_line, FILE *db_file) {
449   regmatch_t matches;
450   int f;
451   for(f = 0; f < nb_fields_to_parse; f++) {
452     if(regexec(&fields_to_parse[f].regexp, raw_mbox_line, 1, &matches, 0) == 0) {
453       fprintf(db_file, "%s %s\n",
454               field_names[fields_to_parse[f].id],
455               raw_mbox_line + matches.rm_eo);
456     }
457   }
458 }
459
460 void index_mbox(const char *mbox_filename,
461                 int nb_fields_to_parse, struct parsable_field *fields_to_parse,
462                 FILE *db_file) {
463   char raw_mbox_line[BUFFER_SIZE], full_line[BUFFER_SIZE];
464   char *end_of_full_line;
465   FILE *file;
466   int in_header, new_header, last_mbox_line_was_empty;
467   unsigned long int position_in_file;
468
469   file = fopen(mbox_filename, "r");
470
471   if(!file) {
472     fprintf(stderr, "mymail: Cannot open '%s'.\n", mbox_filename);
473     if(paranoid) { exit(EXIT_FAILURE); }
474     return;
475   }
476
477   in_header = 0;
478   new_header = 0;
479
480   position_in_file = 0;
481   end_of_full_line = 0;
482   full_line[0] = '\0';
483   last_mbox_line_was_empty = 1;
484
485   while(fgets(raw_mbox_line, BUFFER_SIZE, file)) {
486     if(last_mbox_line_was_empty && strncmp(raw_mbox_line, "From ", 5) == 0) {
487       if(in_header) {
488         fprintf(stderr,
489                 "Got a ^\"From \" in the header in %s:%lu.\n",
490                 mbox_filename, position_in_file);
491         fprintf(stderr, "%s", raw_mbox_line);
492         if(paranoid) { exit(EXIT_FAILURE); }
493       }
494       in_header = 1;
495       new_header = 1;
496     } else if(raw_mbox_line[0] == '\n') {
497       if(in_header) { in_header = 0; }
498     }
499
500     last_mbox_line_was_empty = (raw_mbox_line[0] == '\n');
501
502     if(in_header) {
503       if(new_header) {
504         fprintf(db_file, "mail %lu %s\n", position_in_file, mbox_filename);
505         new_header = 0;
506       }
507
508       if(raw_mbox_line[0] == ' ' || raw_mbox_line[0] == '\t') {
509         char *start = raw_mbox_line;
510         while(*start == ' ' || *start == '\t') start++;
511         *(end_of_full_line++) = ' ';
512         strcpy(end_of_full_line, start);
513         while(*end_of_full_line && *end_of_full_line != '\n') {
514           end_of_full_line++;
515         }
516         *end_of_full_line = '\0';
517       }
518
519       else {
520         /*
521           if(!((raw_mbox_line[0] >= 'a' && raw_mbox_line[0] <= 'z') ||
522           (raw_mbox_line[0] >= 'A' && raw_mbox_line[0] <= 'Z'))) {
523           fprintf(stderr,
524           "Header line syntax error %s:%lu.\n",
525           mbox_filename, position_in_file);
526           fprintf(stderr, "%s", raw_mbox_line);
527           }
528         */
529
530         if(full_line[0]) {
531           index_one_mbox_line(nb_fields_to_parse, fields_to_parse, full_line, db_file);
532         }
533
534         end_of_full_line = full_line;
535         strcpy(end_of_full_line, raw_mbox_line);
536         while(*end_of_full_line && *end_of_full_line != '\n') {
537           end_of_full_line++;
538         }
539         *end_of_full_line = '\0';
540       }
541
542     }
543
544     position_in_file += strlen(raw_mbox_line);
545   }
546
547   fclose(file);
548 }
549
550 void recursive_index_mbox(FILE *db_file,
551                           const char *entry_name,
552                           int nb_fields_to_parse, struct parsable_field *fields_to_parse) {
553   DIR *dir;
554   struct dirent *dir_e;
555   struct stat sb;
556   char subname[PATH_MAX + 1];
557
558   if(lstat(entry_name, &sb) != 0) {
559     fprintf(stderr,
560             "mymail: Cannot stat \"%s\": %s\n",
561             entry_name,
562             strerror(errno));
563     exit(EXIT_FAILURE);
564   }
565
566   dir = opendir(entry_name);
567
568   if(dir) {
569     while((dir_e = readdir(dir))) {
570       if(!ignore_entry(dir_e->d_name)) {
571         snprintf(subname, PATH_MAX, "%s/%s", entry_name, dir_e->d_name);
572         recursive_index_mbox(db_file, subname, nb_fields_to_parse, fields_to_parse);
573       }
574     }
575     closedir(dir);
576   } else {
577     index_mbox(entry_name, nb_fields_to_parse, fields_to_parse, db_file);
578   }
579 }
580
581 /*********************************************************************/
582
583 /* For long options that have no equivalent short option, use a
584    non-character as a pseudo short option, starting with CHAR_MAX + 1.  */
585 enum {
586   OPT_BASH_MODE = CHAR_MAX + 1
587 };
588
589 static struct option long_options[] = {
590   { "help", no_argument, 0, 'h' },
591   { "version", no_argument, 0, 'v' },
592   { "quiet", no_argument, 0, 'q' },
593   { "db-file", 1, 0, 'd' },
594   { "db-pattern", 1, 0, 'p' },
595   { "db-root", 1, 0, 'r' },
596   { "db-list", 1, 0, 'l' },
597   { "search", 1, 0, 's' },
598   { "index", 0, 0, 'i' },
599   { "output", 1, 0, 'o' },
600   { 0, 0, 0, 0 }
601 };
602
603 /*********************************************************************/
604
605 int main(int argc, char **argv) {
606   int error = 0, show_help = 0;
607   const int nb_fields_to_parse = sizeof(fields_to_parse) / sizeof(struct parsable_field);
608   char c;
609   int f;
610   int nb_search_conditions;
611   char *search_condition_strings[MAX_NB_SEARCH_CONDITIONS];
612   FILE *output_file;
613
614   paranoid = 0;
615   action_index = 0;
616   db_filename = 0;
617   db_root_path = 0;
618   db_filename_list = 0;
619   quiet = 0;
620
621   setlocale(LC_ALL, "");
622
623   nb_search_conditions = 0;
624
625   while ((c = getopt_long(argc, argv, "hvqip:s:d:r:l:o:",
626                           long_options, NULL)) != -1) {
627
628     switch(c) {
629
630     case 'h':
631       show_help = 1;
632       break;
633
634     case 'v':
635       print_version(stdout);
636       break;
637
638     case 'q':
639       quiet = 1;
640       break;
641
642     case 'i':
643       action_index = 1;
644       break;
645
646     case 'd':
647       db_filename = strdup(optarg);
648       break;
649
650     case 'p':
651       db_filename_regexp_string = strdup(optarg);
652       break;
653
654     case 'o':
655       strncpy(output_filename, optarg, PATH_MAX);
656       break;
657
658     case 'r':
659       db_root_path = strdup(optarg);
660       break;
661
662     case 'l':
663       db_filename_list = strdup(optarg);
664       break;
665
666     case 's':
667       if(nb_search_conditions == MAX_NB_SEARCH_CONDITIONS) {
668         fprintf(stderr, "mymail: Too many search patterns.\n");
669         exit(EXIT_FAILURE);
670       }
671       search_condition_strings[nb_search_conditions++] = strdup(optarg);
672       break;
673
674     default:
675       error = 1;
676       break;
677     }
678   }
679
680   if(!db_filename) {
681     char *default_db_filename = getenv("MYMAIL_DB_FILE");
682
683     if(!default_db_filename) {
684       default_db_filename = "mymail.db";
685     }
686
687     db_filename = strdup(default_db_filename);
688   }
689
690   if(!db_filename_regexp_string) {
691     char *default_db_filename_regexp_string = getenv("MYMAIL_DB_PATTERN");
692
693     if(!default_db_filename_regexp_string) {
694       default_db_filename_regexp_string = "^mymail.db$";
695     }
696
697     db_filename_regexp_string = strdup(default_db_filename_regexp_string);
698   }
699
700   if(!db_root_path) {
701     char *default_db_root_path = getenv("MYMAIL_DB_ROOT");
702
703     if(default_db_root_path) {
704       db_root_path = strdup(default_db_root_path);
705     }
706   }
707
708   if(!db_filename_list) {
709     char *default_db_filename_list = getenv("MYMAIL_DB_LIST");
710
711     if(default_db_filename_list) {
712       db_filename_list = strdup(default_db_filename_list);
713     }
714   }
715
716   if(output_filename[0]) {
717     output_file = fopen(output_filename, "w");
718
719     if(!output_file) {
720       fprintf(stderr,
721               "mymail: Cannot open result file \"%s\" for writing: %s\n",
722               output_filename,
723               strerror(errno));
724       exit(EXIT_FAILURE);
725     }
726   } else {
727     output_file = stdout;
728     quiet = 1;
729   }
730
731   if(error) {
732     print_usage(stderr);
733     exit(EXIT_FAILURE);
734   }
735
736   if(show_help) {
737     print_usage(stdout);
738     exit(EXIT_SUCCESS);
739   }
740
741   if(action_index) {
742     FILE *db_file;
743
744     db_file = fopen(db_filename, "w");
745
746     if(!db_file) {
747       fprintf(stderr,
748               "mymail: Cannot open \"%s\" for writing: %s\n",
749               db_filename,
750               strerror(errno));
751       exit(EXIT_FAILURE);
752     }
753
754     for(f = 0; f < nb_fields_to_parse; f++) {
755       if(regcomp(&fields_to_parse[f].regexp,
756                  fields_to_parse[f].regexp_string,
757                  REG_ICASE)) {
758         fprintf(stderr,
759                 "mymail: Syntax error in regexp \"%s\" for field \"%s\".\n",
760                 fields_to_parse[f].regexp_string,
761                 field_names[fields_to_parse[f].id]);
762         exit(EXIT_FAILURE);
763       }
764     }
765
766     fprintf(db_file, "%s version_%s raw\n", MYMAIL_DB_MAGIC_TOKEN, VERSION);
767
768     while(optind < argc) {
769       recursive_index_mbox(db_file,
770                            argv[optind],
771                            nb_fields_to_parse, fields_to_parse);
772       optind++;
773     }
774
775     fclose(db_file);
776
777     for(f = 0; f < nb_fields_to_parse; f++) {
778       regfree(&fields_to_parse[f].regexp);
779     }
780   }
781
782   else {
783
784     if(nb_search_conditions > 0) {
785       struct search_condition search_conditions[MAX_NB_SEARCH_CONDITIONS];
786       char *search_field, *search_regexp_string;
787       int m, n;
788
789       for(n = 0; n < nb_search_conditions; n++) {
790         search_field = search_condition_strings[n];
791         search_regexp_string = segment_next_field(search_condition_strings[n]);
792
793         if(search_field[0] == '!') {
794           search_field++;
795           search_conditions[n].negation = 1;
796         } else {
797           search_conditions[n].negation = 0;
798         }
799
800         search_conditions[n].field_id = -1;
801         for(m = 0; (m < MAX_ID) && search_conditions[n].field_id == -1; m++) {
802           if(strncmp(field_names[m], search_field, strlen(search_field)) == 0) {
803             search_conditions[n].field_id = m;
804           }
805         }
806
807         if(search_conditions[n].field_id == -1) {
808           fprintf(stderr,
809                   "mymail: Syntax error in field name \"%s\".\n",
810                   search_field);
811           exit(EXIT_FAILURE);
812         }
813
814         if(regcomp(&search_conditions[n].regexp,
815                    search_regexp_string,
816                    REG_ICASE)) {
817           fprintf(stderr,
818                   "mymail: Syntax error in regexp \"%s\" for field \"%s\".\n",
819                   search_regexp_string,
820                   field_names[search_conditions[n].field_id]);
821           exit(EXIT_FAILURE);
822         }
823       }
824
825       /* Recursive search if db_root_path is set */
826
827       if(db_root_path) {
828         regex_t db_filename_regexp;
829         if(regcomp(&db_filename_regexp,
830                    db_filename_regexp_string,
831                    0)) {
832           fprintf(stderr,
833                   "mymail: Syntax error in regexp \"%s\".\n",
834                   db_filename_regexp_string);
835           exit(EXIT_FAILURE);
836         }
837
838         recursive_search_in_db(db_root_path, &db_filename_regexp,
839                                nb_search_conditions, search_conditions,
840                                output_file);
841
842         regfree(&db_filename_regexp);
843       }
844
845       /* Search in all db files listed in db_filename_list */
846
847       if(db_filename_list) {
848         char db_filename[PATH_MAX + 1];
849         char *s, *t;
850         FILE *db_file;
851
852         s = db_filename_list;
853
854         while(*s) {
855           t = db_filename;
856           while(*s == ';') { s++; }
857           while(*s && *s != ';') { *t++ = *s++; }
858           *t++ = '\0';
859
860           if(db_filename[0]) {
861             db_file = fopen(db_filename, "r");
862
863             if(!db_file) {
864               fprintf(stderr,
865                       "mymail: Cannot open \"%s\" for reading: %s\n",
866                       argv[optind],
867                       strerror(errno));
868               exit(EXIT_FAILURE);
869             }
870
871             search_in_db(db_file, nb_search_conditions, search_conditions, output_file);
872
873             fclose(db_file);
874           }
875         }
876       }
877
878       /* Search in all db files listed in the command arguments */
879
880       while(optind < argc) {
881         FILE *db_file = fopen(argv[optind], "r");
882
883         if(!db_file) {
884           fprintf(stderr,
885                   "mymail: Cannot open \"%s\" for reading: %s\n",
886                   argv[optind],
887                   strerror(errno));
888           exit(EXIT_FAILURE);
889         }
890
891         search_in_db(db_file, nb_search_conditions, search_conditions, output_file);
892
893         fclose(db_file);
894         optind++;
895       }
896
897       for(n = 0; n < nb_search_conditions; n++) {
898         regfree(&search_conditions[n].regexp);
899         free(search_condition_strings[n]);
900       }
901     }
902   }
903
904   if(output_file != stdout) {
905     fclose(output_file);
906   }
907
908   free(db_filename);
909   free(db_filename_regexp_string);
910   free(db_root_path);
911   free(db_filename_list);
912
913   exit(EXIT_SUCCESS);
914 }