Updated the usage and man page.
[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"
49
50 #define MAX_NB_SEARCH_REQUESTS 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
59 int paranoid;
60 int action_index;
61
62 /********************************************************************/
63
64 enum {
65   ID_MAIL = 0,
66   ID_FROM,
67   ID_TO,
68   ID_SUBJECT,
69   ID_DATE,
70   ID_PARTICIPANT,
71   MAX_ID
72 };
73
74 static char *field_names[] = {
75   "mail",
76   "from",
77   "to",
78   "subject",
79   "date",
80   "part"
81 };
82
83 /********************************************************************/
84
85 struct search_request {
86   int field_id;
87   int negation;
88   regex_t regexp;
89 };
90
91 /********************************************************************/
92
93 struct parsable_field {
94   int id;
95   char *regexp_string;
96   regex_t regexp;
97 };
98
99 static struct parsable_field fields_to_parse[] = {
100   {
101     ID_FROM,
102     "^\\(From \\|[Ff][Rr][Oo][Mm]:\\|[R][r][E][e][P][p][L][l][Y][y]-[T][t][O][o]:\\)",
103     { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }
104   },
105
106   {
107     ID_TO,
108     "^\\([Tt][Oo]\\|[Cc][Cc]\\|[Bb][Cc][Cc]\\): ",
109     { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }
110   },
111
112   {
113     ID_SUBJECT,
114     "^[Ss][Uu][Bb][Jj][Ee][Cc][Tt]: ",
115     { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }
116   },
117
118   {
119     ID_DATE,
120     "^[Dd][Aa][Tt][Ee]: ",
121     { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }
122   },
123
124 };
125
126 /********************************************************************/
127
128 int xor(int a, int b) {
129   return (a && !b) || (!a && b);
130 }
131
132 char *segment_next_field(char *current) {
133   while(*current && *current != ' ') current++;
134   *current = '\0'; current++;
135   while(*current && *current == ' ') current++;
136   return current;
137 }
138
139 void remove_eof(char *c) {
140   while(*c && *c != '\n' && *c != '\r') c++;
141   *c = '\0';
142 }
143
144 /********************************************************************/
145
146 /* malloc with error checking.  */
147
148 void *safe_malloc(size_t n) {
149   void *p = malloc(n);
150   if(!p && n != 0) {
151     fprintf(stderr,
152             "mymail: cannot allocate memory: %s\n", strerror(errno));
153     exit(EXIT_FAILURE);
154   }
155   return p;
156 }
157
158 /*********************************************************************/
159
160 void print_version(FILE *out) {
161   fprintf(out, "mymail version %s (%s)\n", VERSION, UNAME);
162 }
163
164 void print_usage(FILE *out) {
165   print_version(out);
166   fprintf(out, "Written by Francois Fleuret <francois@fleuret.org>.\n");
167   fprintf(out, "\n");
168   fprintf(out, "Usage: mymail [options] [<mbox dir1> [<mbox dir2> ...]|<db file1> [<db file2> ...]]\n");
169   fprintf(out, "\n");
170   fprintf(out, " -h, --help\n");
171   fprintf(out, "         show this help\n");
172   fprintf(out, " -v, --version\n");
173   fprintf(out, "         print the version number\n");
174   fprintf(out, " -p <db filename pattern>, --db-pattern <db filename pattern>\n");
175   fprintf(out, "         set the db filename pattern for recursive search\n");
176   fprintf(out, " -r <db root path>, --db-root <db root path>\n");
177   fprintf(out, "         set the db root path for recursive search\n");
178   fprintf(out, " -l <db filename list>, --db-list <db filename list>\n");
179   fprintf(out, "         set the semicolon-separated list of db files for search\n");
180   fprintf(out, " -s <search pattern>, --search <search pattern>\n");
181   fprintf(out, "         search for matching mails in the db file\n");
182   fprintf(out, " -d <db filename>, --db-file <db filename>\n");
183   fprintf(out, "         set the db filename for indexing\n");
184   fprintf(out, " -i, --index\n");
185   fprintf(out, "         index mails\n");
186 }
187
188 /*********************************************************************/
189
190 int ignore_entry(const char *name) {
191   return
192     /* strcmp(name, ".") == 0 || */
193     /* strcmp(name, "..") == 0 || */
194     (name[0] == '.' && name[1] != '/');
195 }
196
197 int mbox_line_match_search(struct search_request *request,
198                            int mbox_id, char *mbox_value) {
199   return
200     (request->field_id == mbox_id ||
201      (request->field_id == ID_PARTICIPANT && (mbox_id == ID_FROM || mbox_id == ID_TO)))
202     &&
203     regexec(&request->regexp, mbox_value, 0, 0, 0) == 0;
204 }
205
206 void search_in_db(int nb_search_requests,
207                   struct search_request *search_requests,
208                   FILE *db_file) {
209   int hits[MAX_NB_SEARCH_REQUESTS];
210   char raw_db_line[BUFFER_SIZE];
211   char raw_mbox_line[BUFFER_SIZE];
212   char current_mail_filename[PATH_MAX + 1];
213   unsigned long int current_position_in_mail;
214   char *mbox_name, *mbox_value;
215   int mbox_id;
216   int already_written, m, n;
217   int last_mbox_line_was_empty;
218
219   current_position_in_mail = 0;
220   already_written = 0;
221
222   for(n = 0; n < nb_search_requests; n++) { hits[n] = 0; }
223
224   while(fgets(raw_db_line, BUFFER_SIZE, db_file)) {
225     mbox_name = raw_db_line;
226     mbox_value = segment_next_field(raw_db_line);
227
228     if(strcmp("mail", mbox_name) == 0) {
229       char *position_in_file_string;
230       char *mail_filename;
231
232       for(n = 0; n < nb_search_requests && xor(hits[n], search_requests[n].negation); n++);
233
234       /* for(n = 0; n < nb_search_requests && */
235             /* ((hits[n] && !search_requests[n].negation) || */
236              /* (!hits[n] && search_requests[n].negation)); n++); */
237
238       if(n == nb_search_requests) {
239         FILE *mail_file;
240
241         mail_file = fopen(current_mail_filename, "r");
242
243         if(!mail_file) {
244           fprintf(stderr, "mymail: Cannot open mbox '%s'.\n", current_mail_filename);
245           exit(EXIT_FAILURE);
246         }
247
248         fseek(mail_file, current_position_in_mail, SEEK_SET);
249
250         if(fgets(raw_mbox_line, BUFFER_SIZE, mail_file)) {
251           last_mbox_line_was_empty = 1;
252           printf("%s", raw_mbox_line);
253           while(1) {
254             if(!fgets(raw_mbox_line, BUFFER_SIZE, mail_file) ||
255                (last_mbox_line_was_empty && strncmp(raw_mbox_line, "From ", 5) == 0)) break;
256             last_mbox_line_was_empty = (raw_mbox_line[0] == '\n');
257             printf("%s", raw_mbox_line);
258           }
259         }
260
261         fclose(mail_file);
262       }
263
264       for(n = 0; n < nb_search_requests; n++) { hits[n] = 0; }
265
266       position_in_file_string = mbox_value;
267       mail_filename = segment_next_field(mbox_value);
268       current_position_in_mail = atol(position_in_file_string);
269       strcpy(current_mail_filename, mail_filename);
270
271       remove_eof(current_mail_filename);
272       already_written = 0;
273     }
274
275     else {
276       mbox_id = -1;
277       for(m = 0; (m < MAX_ID) && mbox_id == -1; m++) {
278         if(strncmp(field_names[m], mbox_name, strlen(mbox_name)) == 0) {
279           mbox_id = m;
280         }
281       }
282       for(n = 0; n < nb_search_requests; n++) {
283         hits[n] |= mbox_line_match_search(&search_requests[n],
284                                           mbox_id, mbox_value);
285       }
286     }
287   }
288 }
289
290 void recursive_search_in_db(const char *entry_name, regex_t *db_filename_regexp,
291                             int nb_search_requests, struct search_request *search_requests) {
292   DIR *dir;
293   struct dirent *dir_e;
294   struct stat sb;
295   char raw_db_line[BUFFER_SIZE];
296   char subname[PATH_MAX + 1];
297
298   if(lstat(entry_name, &sb) != 0) {
299     fprintf(stderr,
300             "mymail: Cannot stat \"%s\": %s\n",
301             entry_name,
302             strerror(errno));
303     exit(EXIT_FAILURE);
304   }
305
306   dir = opendir(entry_name);
307
308   if(dir) {
309     while((dir_e = readdir(dir))) {
310       if(!ignore_entry(dir_e->d_name)) {
311         snprintf(subname, PATH_MAX, "%s/%s", entry_name, dir_e->d_name);
312         recursive_search_in_db(subname, db_filename_regexp,
313                                nb_search_requests, search_requests);
314       }
315     }
316     closedir(dir);
317   }
318
319   else {
320     const char *s = entry_name, *filename = entry_name;
321     while(*s) { if(*s == '/') { filename = s+1; } s++; }
322
323     if(regexec(db_filename_regexp, filename, 0, 0, 0) == 0) {
324       FILE *db_file = fopen(entry_name, "r");
325
326       if(!db_file) {
327         fprintf(stderr,
328                 "mymail: Cannot open \"%s\" for reading: %s\n",
329                 db_filename,
330                 strerror(errno));
331         exit(EXIT_FAILURE);
332       }
333
334       if(fgets(raw_db_line, BUFFER_SIZE, db_file)) {
335         if(strncmp(raw_db_line, MYMAIL_DB_MAGIC_TOKEN, strlen(MYMAIL_DB_MAGIC_TOKEN))) {
336           fprintf(stderr,
337                   "mymail: Header line in '%s' does not match the mymail db format.\n",
338                   entry_name);
339           exit(EXIT_FAILURE);
340         }
341       } else {
342         fprintf(stderr,
343                 "mymail: Cannot read the header line in '%s'.\n",
344                 entry_name);
345         exit(EXIT_FAILURE);
346       }
347
348       search_in_db(nb_search_requests, search_requests, db_file);
349
350       fclose(db_file);
351     }
352   }
353 }
354
355 /*********************************************************************/
356
357 void index_one_mbox_line(int nb_fields_to_parse, struct parsable_field *fields_to_parse,
358                          char *raw_mbox_line, FILE *db_file) {
359   regmatch_t matches;
360   int f;
361   for(f = 0; f < nb_fields_to_parse; f++) {
362     if(regexec(&fields_to_parse[f].regexp, raw_mbox_line, 1, &matches, 0) == 0) {
363       fprintf(db_file, "%s %s\n",
364               field_names[fields_to_parse[f].id],
365               raw_mbox_line + matches.rm_eo);
366     }
367   }
368 }
369
370 void index_mbox(const char *mbox_filename,
371                 int nb_fields_to_parse, struct parsable_field *fields_to_parse,
372                 FILE *db_file) {
373   char raw_mbox_line[BUFFER_SIZE], full_line[BUFFER_SIZE];
374   char *end_of_full_line;
375   FILE *file;
376   int in_header, new_header, last_mbox_line_was_empty;
377   unsigned long int position_in_file;
378
379   file = fopen(mbox_filename, "r");
380
381   if(!file) {
382     fprintf(stderr, "mymail: Cannot open '%s'.\n", mbox_filename);
383     if(paranoid) { exit(EXIT_FAILURE); }
384     return;
385   }
386
387   in_header = 0;
388   new_header = 0;
389
390   position_in_file = 0;
391   end_of_full_line = 0;
392   full_line[0] = '\0';
393   last_mbox_line_was_empty = 1;
394
395   while(fgets(raw_mbox_line, BUFFER_SIZE, file)) {
396     if(last_mbox_line_was_empty && strncmp(raw_mbox_line, "From ", 5) == 0) {
397       if(in_header) {
398         fprintf(stderr,
399                 "Got a ^\"From \" in the header in %s:%lu.\n",
400                 mbox_filename, position_in_file);
401         fprintf(stderr, "%s", raw_mbox_line);
402         if(paranoid) { exit(EXIT_FAILURE); }
403       }
404       in_header = 1;
405       new_header = 1;
406     } else if(raw_mbox_line[0] == '\n') {
407       if(in_header) { in_header = 0; }
408     }
409
410     last_mbox_line_was_empty = (raw_mbox_line[0] == '\n');
411
412     if(in_header) {
413       if(new_header) {
414         fprintf(db_file, "mail %lu %s\n", position_in_file, mbox_filename);
415         new_header = 0;
416       }
417
418       if(raw_mbox_line[0] == ' ' || raw_mbox_line[0] == '\t') {
419         char *start = raw_mbox_line;
420         while(*start == ' ' || *start == '\t') start++;
421         *(end_of_full_line++) = ' ';
422         strcpy(end_of_full_line, start);
423         while(*end_of_full_line && *end_of_full_line != '\n') {
424           end_of_full_line++;
425         }
426         *end_of_full_line = '\0';
427       }
428
429       else {
430         /*
431           if(!((raw_mbox_line[0] >= 'a' && raw_mbox_line[0] <= 'z') ||
432           (raw_mbox_line[0] >= 'A' && raw_mbox_line[0] <= 'Z'))) {
433           fprintf(stderr,
434           "Header line syntax error %s:%lu.\n",
435           mbox_filename, position_in_file);
436           fprintf(stderr, "%s", raw_mbox_line);
437           }
438         */
439
440         if(full_line[0]) {
441           index_one_mbox_line(nb_fields_to_parse, fields_to_parse, full_line, db_file);
442         }
443
444         end_of_full_line = full_line;
445         strcpy(end_of_full_line, raw_mbox_line);
446         while(*end_of_full_line && *end_of_full_line != '\n') {
447           end_of_full_line++;
448         }
449         *end_of_full_line = '\0';
450       }
451
452     }
453
454     position_in_file += strlen(raw_mbox_line);
455   }
456
457   fclose(file);
458 }
459
460 void recursive_index_mbox(FILE *db_file,
461                           const char *entry_name,
462                           int nb_fields_to_parse, struct parsable_field *fields_to_parse) {
463   DIR *dir;
464   struct dirent *dir_e;
465   struct stat sb;
466   char subname[PATH_MAX + 1];
467
468   if(lstat(entry_name, &sb) != 0) {
469     fprintf(stderr,
470             "mymail: Cannot stat \"%s\": %s\n",
471             entry_name,
472             strerror(errno));
473     exit(EXIT_FAILURE);
474   }
475
476   dir = opendir(entry_name);
477
478   if(dir) {
479     while((dir_e = readdir(dir))) {
480       if(!ignore_entry(dir_e->d_name)) {
481         snprintf(subname, PATH_MAX, "%s/%s", entry_name, dir_e->d_name);
482         recursive_index_mbox(db_file, subname, nb_fields_to_parse, fields_to_parse);
483       }
484     }
485     closedir(dir);
486   } else {
487     index_mbox(entry_name, nb_fields_to_parse, fields_to_parse, db_file);
488   }
489 }
490
491 /*********************************************************************/
492
493 /* For long options that have no equivalent short option, use a
494    non-character as a pseudo short option, starting with CHAR_MAX + 1.  */
495 enum {
496   OPT_BASH_MODE = CHAR_MAX + 1
497 };
498
499 static struct option long_options[] = {
500   { "help", no_argument, 0, 'h' },
501   { "version", no_argument, 0, 'v' },
502   { "db-file", 1, 0, 'd' },
503   { "db-pattern", 1, 0, 'p' },
504   { "db-root", 1, 0, 'r' },
505   { "db-list", 1, 0, 'l' },
506   { "search", 1, 0, 's' },
507   { "index", 0, 0, 'i' },
508   { 0, 0, 0, 0 }
509 };
510
511 /*********************************************************************/
512
513 int main(int argc, char **argv) {
514   int error = 0, show_help = 0;
515   const int nb_fields_to_parse = sizeof(fields_to_parse) / sizeof(struct parsable_field);
516   char c;
517   int f;
518   int nb_search_requests;
519   char *search_request_strings[MAX_NB_SEARCH_REQUESTS];
520
521   /* for(f = 0; f < argc; f++) { */
522   /* printf("arg %d \"%s\"\n", f, argv[f]); */
523   /* } */
524
525   paranoid = 0;
526   action_index = 0;
527   db_filename = 0;
528   db_root_path = 0;
529   db_filename_list = 0;
530
531   setlocale(LC_ALL, "");
532
533   nb_search_requests = 0;
534
535   while ((c = getopt_long(argc, argv, "hvip:s:d:r:l:",
536                           long_options, NULL)) != -1) {
537
538     switch(c) {
539
540     case 'h':
541       show_help = 1;
542       break;
543
544     case 'v':
545       print_version(stdout);
546       break;
547
548     case 'i':
549       action_index = 1;
550       break;
551
552     case 'd':
553       db_filename = strdup(optarg);
554       break;
555
556     case 'p':
557       db_filename_regexp_string = strdup(optarg);
558       break;
559
560     case 'r':
561       db_root_path = strdup(optarg);
562       break;
563
564     case 'l':
565       db_filename_list = strdup(optarg);
566       break;
567
568     case 's':
569       if(nb_search_requests == MAX_NB_SEARCH_REQUESTS) {
570         fprintf(stderr, "mymail: Too many search patterns.\n");
571         exit(EXIT_FAILURE);
572       }
573       search_request_strings[nb_search_requests++] = strdup(optarg);
574       break;
575
576     default:
577       error = 1;
578       break;
579     }
580   }
581
582   if(!db_filename) {
583     char *default_db_filename = getenv("MYMAIL_DB_FILE");
584
585     if(!default_db_filename) {
586       default_db_filename = "mymail.db";
587     }
588
589     db_filename = strdup(default_db_filename);
590   }
591
592   if(!db_filename_regexp_string) {
593     char *default_db_filename_regexp_string = getenv("MYMAIL_DB_PATTERN");
594
595     if(!default_db_filename_regexp_string) {
596       default_db_filename_regexp_string = "^mymail.db$";
597     }
598
599     db_filename_regexp_string = strdup(default_db_filename_regexp_string);
600   }
601
602   if(!db_root_path) {
603     char *default_db_root_path = getenv("MYMAIL_DB_ROOT");
604
605     if(default_db_root_path) {
606       db_root_path = strdup(default_db_root_path);
607     }
608   }
609
610   if(!db_filename_list) {
611     char *default_db_filename_list = getenv("MYMAIL_DB_LIST");
612
613     if(default_db_filename_list) {
614       db_filename_list = strdup(default_db_filename_list);
615     }
616   }
617
618   if(error) {
619     print_usage(stderr);
620     exit(EXIT_FAILURE);
621   }
622
623   if(show_help) {
624     print_usage(stdout);
625     exit(EXIT_SUCCESS);
626   }
627
628   if(action_index) {
629     FILE *db_file;
630
631     db_file = fopen(db_filename, "w");
632
633     if(!db_file) {
634       fprintf(stderr,
635               "mymail: Cannot open \"%s\" for writing: %s\n",
636               db_filename,
637               strerror(errno));
638       exit(EXIT_FAILURE);
639     }
640
641     for(f = 0; f < nb_fields_to_parse; f++) {
642       if(regcomp(&fields_to_parse[f].regexp,
643                  fields_to_parse[f].regexp_string,
644                  REG_ICASE)) {
645         fprintf(stderr,
646                 "mymail: Syntax error in regexp \"%s\" for field \"%s\".\n",
647                 fields_to_parse[f].regexp_string,
648                 field_names[fields_to_parse[f].id]);
649         exit(EXIT_FAILURE);
650       }
651     }
652
653     fprintf(db_file, "%s version_%s raw\n", MYMAIL_DB_MAGIC_TOKEN, VERSION);
654
655     while(optind < argc) {
656       recursive_index_mbox(db_file,
657                            argv[optind],
658                            nb_fields_to_parse, fields_to_parse);
659       optind++;
660     }
661
662     fclose(db_file);
663
664     for(f = 0; f < nb_fields_to_parse; f++) {
665       regfree(&fields_to_parse[f].regexp);
666     }
667   }
668
669   else {
670
671     if(nb_search_requests > 0) {
672       struct search_request search_requests[MAX_NB_SEARCH_REQUESTS];
673       char *search_field, *search_regexp_string;
674       int m, n;
675
676       for(n = 0; n < nb_search_requests; n++) {
677         search_field = search_request_strings[n];
678         search_regexp_string = segment_next_field(search_request_strings[n]);
679
680         if(search_field[0] == '!') {
681           search_field++;
682           search_requests[n].negation = 1;
683         } else {
684           search_requests[n].negation = 0;
685         }
686
687         search_requests[n].field_id = -1;
688         for(m = 0; (m < MAX_ID) && search_requests[n].field_id == -1; m++) {
689           if(strncmp(field_names[m], search_field, strlen(search_field)) == 0) {
690             search_requests[n].field_id = m;
691           }
692         }
693
694         if(search_requests[n].field_id == -1) {
695           fprintf(stderr,
696                   "mymail: Syntax error in field name \"%s\".\n",
697                   search_field);
698           exit(EXIT_FAILURE);
699         }
700
701         if(regcomp(&search_requests[n].regexp,
702                    search_regexp_string,
703                    REG_ICASE)) {
704           fprintf(stderr,
705                   "mymail: Syntax error in regexp \"%s\" for field \"%s\".\n",
706                   search_regexp_string,
707                   field_names[search_requests[n].field_id]);
708           exit(EXIT_FAILURE);
709         }
710       }
711
712       /* Recursive search if db_root_path is set */
713
714       if(db_root_path) {
715         regex_t db_filename_regexp;
716         if(regcomp(&db_filename_regexp,
717                    db_filename_regexp_string,
718                    0)) {
719           fprintf(stderr,
720                   "mymail: Syntax error in regexp \"%s\".\n",
721                   db_filename_regexp_string);
722           exit(EXIT_FAILURE);
723         }
724
725         recursive_search_in_db(db_root_path, &db_filename_regexp,
726                                nb_search_requests, search_requests);
727
728         regfree(&db_filename_regexp);
729       }
730
731       /* Search in all db files listed in db_filename_list */
732
733       if(db_filename_list) {
734         char db_filename[PATH_MAX + 1];
735         char *s, *t;
736         FILE *db_file;
737
738         s = db_filename_list;
739
740         while(*s) {
741           t = db_filename;
742           while(*s == ';') { s++; }
743           while(*s && *s != ';') { *t++ = *s++; }
744           *t++ = '\0';
745
746           if(db_filename[0]) {
747             db_file = fopen(db_filename, "r");
748
749             if(!db_file) {
750               fprintf(stderr,
751                       "mymail: Cannot open \"%s\" for reading: %s\n",
752                       argv[optind],
753                       strerror(errno));
754               exit(EXIT_FAILURE);
755             }
756
757             search_in_db(nb_search_requests, search_requests, db_file);
758
759             fclose(db_file);
760           }
761         }
762       }
763
764       /* Search in all db files listed in the command arguments */
765
766       while(optind < argc) {
767         FILE *db_file = fopen(argv[optind], "r");
768
769         if(!db_file) {
770           fprintf(stderr,
771                   "mymail: Cannot open \"%s\" for reading: %s\n",
772                   argv[optind],
773                   strerror(errno));
774           exit(EXIT_FAILURE);
775         }
776
777         search_in_db(nb_search_requests, search_requests, db_file);
778
779         fclose(db_file);
780         optind++;
781       }
782
783       for(n = 0; n < nb_search_requests; n++) {
784         regfree(&search_requests[n].regexp);
785         free(search_request_strings[n]);
786       }
787     }
788   }
789
790   free(db_filename);
791   free(db_filename_regexp_string);
792   free(db_root_path);
793   free(db_filename_list);
794
795   exit(EXIT_SUCCESS);
796 }