3 * Copyright (c) 2013 Francois Fleuret
4 * Written by Francois Fleuret <francois@fleuret.org>
6 * This file is part of mymail.
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.
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.
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/>.
24 mymail is a simple mail indexer. It can:
26 (1) scan mbox files, and create a db file containing for each mail a
27 list of fields computed from its header.
29 (2) read such a db file, gets all the mails matching regexp-defined
30 conditions on the fields, and generates a resulting mbox file.
32 It is low-tech, simple, light and fast.
50 #define MYMAIL_DB_MAGIC_TOKEN "mymail_index_file"
51 #define MYMAIL_VERSION "0.9.9"
53 #define MYMAIL_DB_FORMAT_VERSION 1
55 #define MAX_NB_SEARCH_CONDITIONS 32
57 #define BUFFER_SIZE 65536
58 #define TOKEN_BUFFER_SIZE 1024
60 #define LEADING_FROM_LINE_REGEXP_STRING "^From .*\\(Mon\\|Tue\\|Wed\\|Thu\\|Fri\\|Sat\\|Sun\\) \\(Jan\\|Feb\\|Mar\\|Apr\\|May\\|Jun\\|Jul\\|Aug\\|Sep\\|Oct\\|Nov\\|Dec\\) [ 0123][0-9] [0-9][0-9]:[0-9][0-9]:[0-9][0-9] [0-9][0-9][0-9][0-9]\n$"
62 /********************************************************************/
66 struct alias_node *next;
69 /* Global variables! */
72 int global_use_leading_time;
73 int global_nb_mails_max;
74 regex_t global_leading_from_line_regexp;
75 struct alias_node *global_alias_list;
77 /********************************************************************/
94 static char *field_keys[] = {
108 /********************************************************************/
110 struct search_condition {
112 regex_t db_value_regexp;
114 time_t time_start, time_stop;
117 /********************************************************************/
119 struct parsable_field {
126 static struct parsable_field fields_to_parse[] = {
131 { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }
137 "^\\(from\\|reply-to\\|sender\\|return-path\\): ",
138 { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }
144 "^\\(to\\|cc\\|bcc\\): ",
145 { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }
152 { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }
159 { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }
166 { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }
172 "^\\(in-reply-to\\|references\\): ",
173 { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }
178 /********************************************************************/
180 int xor(int a, int b) {
181 return (a && !b) || (!a && b);
184 const char *parse_token(char *token_buffer, size_t token_buffer_size,
185 char separator, const char *string) {
186 char *u = token_buffer;
188 while(*string == separator) { string++; }
190 while(u < token_buffer + token_buffer_size - 1 && *string && *string != separator) {
191 *(u++) = *(string++);
194 while(*string == separator) { string++; }
200 char *default_value(char *current_value,
201 const char *env_variable,
202 const char *hard_default_value) {
204 return current_value;
206 char *env_value = getenv(env_variable);
208 return strdup(env_value);
209 } else if(hard_default_value) {
210 return strdup(hard_default_value);
217 /********************************************************************/
219 void *safe_malloc(size_t n) {
223 "mymail: cannot allocate memory: %s\n", strerror(errno));
229 FILE *safe_fopen(const char *path, const char *mode, const char *comment) {
230 FILE *result = fopen(path, mode);
235 "mymail: Cannot open file '%s' (%s) with mode \"%s\": %s\n",
242 /*********************************************************************/
244 void print_version(FILE *out) {
245 fprintf(out, "mymail version %s (%s)\n", MYMAIL_VERSION, UNAME);
248 void print_usage(FILE *out) {
250 fprintf(out, "Written by Francois Fleuret <francois@fleuret.org>.\n");
252 fprintf(out, "Usage: mymail [options] [<mbox dir1> [<mbox dir2> ...]|<db file1> [<db file2> ...]]\n");
254 fprintf(out, " -h, --help\n");
255 fprintf(out, " show this help\n");
256 fprintf(out, " -v, --version\n");
257 fprintf(out, " print the version number\n");
258 fprintf(out, " -q, --quiet\n");
259 fprintf(out, " do not print information during search\n");
260 fprintf(out, " -t, --use-leading-time\n");
261 fprintf(out, " use the time stamp from the leading line of each mail and not the Date:\n");
262 fprintf(out, " field\n");
263 fprintf(out, " -p <db filename pattern>, --db-pattern <db filename pattern>\n");
264 fprintf(out, " set the db filename pattern for recursive search\n");
265 fprintf(out, " -r <db root path>, --db-root <db root path>\n");
266 fprintf(out, " set the db root path for recursive search\n");
267 fprintf(out, " -l <db filename list>, --db-list <db filename list>\n");
268 fprintf(out, " set the semicolon-separated list of db files for search\n");
269 fprintf(out, " -m <mbox filename pattern>, --mbox-pattern <mbox filename pattern>\n");
270 fprintf(out, " set the mbox filename pattern for recursive search\n");
271 fprintf(out, " -s <search pattern>, --search <search pattern>\n");
272 fprintf(out, " search for matching mails in the db file\n");
273 fprintf(out, " -d <db filename>, --db-file-output <db filename>\n");
274 fprintf(out, " set the db filename for indexing\n");
275 fprintf(out, " -i, --index\n");
276 fprintf(out, " index mails\n");
277 fprintf(out, " -o <output filename>, --output <output filename>\n");
278 fprintf(out, " set the result file, use stdout if unset\n");
279 fprintf(out, " -n <max number of mails>, --nb-mails-max <max number of mails>\n");
280 fprintf(out, " set the maximum number of mails to extract\n");
281 fprintf(out, " -a <search field>, --default-search <search field>\n");
282 fprintf(out, " set the default search field\n");
285 /*********************************************************************/
287 int ignore_entry(const char *name) {
289 strcmp(name, ".") == 0 ||
290 strcmp(name, "..") == 0 ||
291 (name[0] == '.' && name[1] != '/');
294 int is_a_leading_from_line(char *mbox_line) {
296 strncmp(mbox_line, "From ", 5) == 0 &&
297 regexec(&global_leading_from_line_regexp, mbox_line, 0, 0, 0) == 0;
300 int db_line_match_search(struct search_condition *condition,
301 int db_key, const char *db_value) {
305 (condition->db_key == db_key)
309 (condition->db_key == ID_PARTICIPANT && (db_key == ID_LEADING_LINE ||
314 (condition->db_key == ID_FROM && db_key == ID_LEADING_LINE)
320 regexec(&condition->db_value_regexp, db_value, 0, 0, 0) == 0;
323 void update_body_hits(char *mail_filename, int position_in_mail,
324 int nb_search_conditions, struct search_condition *search_conditions,
325 int nb_body_conditions,
329 char raw_mbox_line[BUFFER_SIZE];
335 mail_file = safe_fopen(mail_filename, "r", "mbox for body scan");
337 fseek(mail_file, position_in_mail, SEEK_SET);
339 if(fgets(raw_mbox_line, BUFFER_SIZE, mail_file)) {
340 while(nb_body_hits < nb_body_conditions) {
341 if(raw_mbox_line[0] == '\n') { header = 0; }
344 for(n = 0; n < nb_search_conditions; n++) {
345 if(search_conditions[n].db_key == ID_BODY && !hits[n]) {
347 (regexec(&search_conditions[n].db_value_regexp, raw_mbox_line, 0, 0, 0) == 0);
355 if(!fgets(raw_mbox_line, BUFFER_SIZE, mail_file) ||
356 (is_a_leading_from_line(raw_mbox_line)))
364 void extract_mail(const char *mail_filename, unsigned long int position_in_mail,
366 char raw_mbox_line[BUFFER_SIZE];
369 /* printf("Extract\n"); */
371 mail_file = safe_fopen(mail_filename, "r", "mbox for mail extraction");
372 fseek(mail_file, position_in_mail, SEEK_SET);
374 if(fgets(raw_mbox_line, BUFFER_SIZE, mail_file)) {
375 fprintf(output_file, "%s", raw_mbox_line);
377 if(!fgets(raw_mbox_line, BUFFER_SIZE, mail_file) ||
378 is_a_leading_from_line(raw_mbox_line))
380 fprintf(output_file, "%s", raw_mbox_line);
387 int check_full_mail_match(char *current_mail_filename,
389 int nb_search_conditions,
390 struct search_condition *search_conditions,
391 int nb_body_conditions,
393 int current_position_in_mail) {
394 int n, nb_fulfilled_body_conditions;
396 for(n = 0; n < nb_search_conditions; n++) {
397 if(search_conditions[n].db_key == ID_TIME_INTERVAL) {
398 hits[n] = (mail_time >= search_conditions[n].time_start &&
399 (search_conditions[n].time_stop == 0 ||
400 mail_time <= search_conditions[n].time_stop));
404 /* We first check all conditions but the body ones */
406 for(n = 0; n < nb_search_conditions &&
407 ((search_conditions[n].db_key == ID_BODY) ||
408 xor(hits[n], search_conditions[n].negation)); n++);
410 if(n == nb_search_conditions) {
412 /* Now check the body ones */
414 nb_fulfilled_body_conditions = 0;
416 if(nb_body_conditions > 0) {
417 update_body_hits(current_mail_filename, current_position_in_mail,
418 nb_search_conditions, search_conditions,
422 for(n = 0; n < nb_search_conditions; n++) {
423 if(search_conditions[n].db_key == ID_BODY &&
424 xor(hits[n], search_conditions[n].negation)) {
425 nb_fulfilled_body_conditions++;
429 return nb_body_conditions == nb_fulfilled_body_conditions;
435 /* We use the mail leading line time by default, and if we should and
436 can, we update with the Date: field */
438 void update_time(int db_key, const char *db_value, time_t *t) {
442 memset(&tm, 0, sizeof(struct tm));
444 if(db_key == ID_LEADING_LINE) {
446 while(*c && *c != ' ') c++; while(*c && *c == ' ') c++;
447 /* printf("From %s", db_value); */
448 strptime(c, "%a %b %e %k:%M:%S %Y", &tm);
451 if(!global_use_leading_time) {
452 if(db_key == ID_DATE) {
453 if(strptime(db_value, "%a, %d %b %Y %k:%M:%S", &tm) ||
454 strptime(db_value, "%d %b %Y %k:%M:%S", &tm)) {
455 /* printf("Date: %s", db_value); */
463 int search_in_db(const char *db_filename,
464 int nb_extracted_mails,
465 int nb_search_conditions,
466 struct search_condition *search_conditions,
470 char raw_db_line[BUFFER_SIZE];
471 char current_mail_filename[PATH_MAX + 1];
472 char db_key_string[TOKEN_BUFFER_SIZE];
473 char position_in_file_string[TOKEN_BUFFER_SIZE];
474 unsigned long int current_position_in_mail;
475 const char *db_value;
477 int hits[MAX_NB_SEARCH_CONDITIONS];
478 int nb_body_conditions, need_time;
484 printf("Searching in '%s' ... ", db_filename);
488 db_file = safe_fopen(db_filename, "r", "index file for search");
490 /* First, check the db file leading line integrity */
492 if(fgets(raw_db_line, BUFFER_SIZE, db_file)) {
493 if(strncmp(raw_db_line, MYMAIL_DB_MAGIC_TOKEN, strlen(MYMAIL_DB_MAGIC_TOKEN))) {
495 "mymail: Header line in '%s' does not match the mymail db format.\n",
501 "mymail: Cannot read the header line in '%s'.\n",
506 /* Then parse the said db file */
508 current_position_in_mail = 0;
510 for(n = 0; n < nb_search_conditions; n++) { hits[n] = 0; }
512 nb_body_conditions = 0;
516 for(n = 0; n < nb_search_conditions; n++) {
517 if(search_conditions[n].db_key == ID_BODY) {
518 nb_body_conditions++;
520 else if(search_conditions[n].db_key == ID_TIME_INTERVAL) {
525 strcpy(current_mail_filename, "");
527 while(nb_extracted_mails < global_nb_mails_max &&
528 fgets(raw_db_line, BUFFER_SIZE, db_file)) {
531 char *s = raw_db_line;
532 while(*s && *s != '\n') { s++; }
535 db_value = parse_token(db_key_string, TOKEN_BUFFER_SIZE, ' ', raw_db_line);
537 if(strcmp("mail", db_key_string) == 0) {
538 if(current_mail_filename[0]) {
539 if(check_full_mail_match(current_mail_filename,
541 nb_search_conditions, search_conditions,
542 nb_body_conditions, hits, current_position_in_mail)) {
543 extract_mail(current_mail_filename, current_position_in_mail, output_file);
544 nb_extracted_mails++;
548 for(n = 0; n < nb_search_conditions; n++) { hits[n] = 0; }
549 db_value = parse_token(position_in_file_string, TOKEN_BUFFER_SIZE, ' ', db_value);
550 strncpy(current_mail_filename, db_value, PATH_MAX + 1);
551 current_position_in_mail = atol(position_in_file_string);
556 for(m = 0; (m < MAX_ID) && db_key == -1; m++) {
557 if(strncmp(field_keys[m], db_key_string, strlen(db_key_string)) == 0) {
562 for(n = 0; n < nb_search_conditions; n++) {
563 hits[n] |= db_line_match_search(&search_conditions[n],
568 update_time(db_key, db_value, &mail_time);
573 if(nb_extracted_mails < global_nb_mails_max &&
574 current_mail_filename[0] &&
575 check_full_mail_match(current_mail_filename,
577 nb_search_conditions, search_conditions,
578 nb_body_conditions, hits, current_position_in_mail)) {
579 extract_mail(current_mail_filename, current_position_in_mail, output_file);
580 nb_extracted_mails++;
590 return nb_extracted_mails;
593 int recursive_search_in_db(const char *entry_name, regex_t *db_filename_regexp,
594 int nb_extracted_mails,
595 int nb_search_conditions,
596 struct search_condition *search_conditions,
599 struct dirent *dir_e;
601 char subname[PATH_MAX + 1];
603 if(lstat(entry_name, &sb) != 0) {
605 "mymail: Cannot stat \"%s\": %s\n",
611 /* printf("recursive_search_in_db %s\n", entry_name); */
613 dir = opendir(entry_name);
616 while((dir_e = readdir(dir)) &&
617 nb_extracted_mails < global_nb_mails_max) {
618 if(!ignore_entry(dir_e->d_name)) {
619 snprintf(subname, PATH_MAX, "%s/%s", entry_name, dir_e->d_name);
620 nb_extracted_mails = recursive_search_in_db(subname, db_filename_regexp,
622 nb_search_conditions, search_conditions,
630 const char *s = entry_name, *filename = entry_name;
631 while(*s) { if(*s == '/') { filename = s+1; } s++; }
633 if(regexec(db_filename_regexp, filename, 0, 0, 0) == 0) {
635 search_in_db(entry_name,
637 nb_search_conditions, search_conditions, output_file);
641 return nb_extracted_mails;
644 /*********************************************************************/
646 void index_one_mbox_line(unsigned int nb_fields_to_parse,
647 struct parsable_field *fields_to_parse,
648 char *raw_mbox_line, FILE *db_file) {
651 for(f = 0; f < nb_fields_to_parse; f++) {
652 if(regexec(&fields_to_parse[f].regexp, raw_mbox_line, 1, &matches, 0) == 0) {
653 fprintf(db_file, "%s %s\n",
654 field_keys[fields_to_parse[f].id],
655 raw_mbox_line + matches.rm_eo);
660 void index_mbox(const char *mbox_filename,
661 int nb_fields_to_parse, struct parsable_field *fields_to_parse,
663 char raw_mbox_line[BUFFER_SIZE], full_line[BUFFER_SIZE];
664 char *end_of_full_line;
666 int in_header, new_header;
667 unsigned long int position_in_file;
669 file = safe_fopen(mbox_filename, "r", "mbox for indexing");
674 position_in_file = 0;
675 end_of_full_line = 0;
678 while(fgets(raw_mbox_line, BUFFER_SIZE, file)) {
679 if(is_a_leading_from_line(raw_mbox_line)) {
680 /* This starts a new mail */
683 "Got a ^\"From \" in the header in %s:%lu.\n",
684 mbox_filename, position_in_file);
685 fprintf(stderr, "%s", raw_mbox_line);
688 /* printf("LEADING_LINE %s", raw_mbox_line); */
692 } else if(raw_mbox_line[0] == '\n') {
695 /* We leave the header, index the current line */
697 /* printf("INDEX %s\n", full_line); */
698 index_one_mbox_line(nb_fields_to_parse, fields_to_parse, full_line, db_file);
700 end_of_full_line = full_line;
701 *end_of_full_line = '\0';
707 fprintf(db_file, "mail %lu %s\n", position_in_file, mbox_filename);
711 if(raw_mbox_line[0] == ' ' || raw_mbox_line[0] == '\t') {
712 /* Continuation of a line */
713 char *start = raw_mbox_line;
714 while(*start == ' ' || *start == '\t') start++;
715 *(end_of_full_line++) = ' ';
716 strcpy(end_of_full_line, start);
717 while(*end_of_full_line && *end_of_full_line != '\n') {
720 *end_of_full_line = '\0';
724 /* Start a new header line, not a continuation */
727 /* printf("INDEX %s\n", full_line); */
728 index_one_mbox_line(nb_fields_to_parse, fields_to_parse, full_line, db_file);
731 end_of_full_line = full_line;
732 strcpy(end_of_full_line, raw_mbox_line);
733 while(*end_of_full_line && *end_of_full_line != '\n') {
736 *end_of_full_line = '\0';
741 position_in_file += strlen(raw_mbox_line);
747 void recursive_index_mbox(FILE *db_file,
748 const char *entry_name, regex_t *mbox_filename_regexp,
749 int nb_fields_to_parse, struct parsable_field *fields_to_parse) {
751 struct dirent *dir_e;
753 char subname[PATH_MAX + 1];
755 if(lstat(entry_name, &sb) != 0) {
757 "mymail: Cannot stat \"%s\": %s\n",
763 dir = opendir(entry_name);
766 while((dir_e = readdir(dir))) {
767 if(!ignore_entry(dir_e->d_name)) {
768 snprintf(subname, PATH_MAX, "%s/%s", entry_name, dir_e->d_name);
769 recursive_index_mbox(db_file, subname, mbox_filename_regexp,
770 nb_fields_to_parse, fields_to_parse);
775 const char *s = entry_name, *filename = s;
776 while(*s) { if(*s == '/') { filename = s+1; }; s++; }
777 if(!mbox_filename_regexp || regexec(mbox_filename_regexp, filename, 0, 0, 0) == 0) {
778 index_mbox(entry_name, nb_fields_to_parse, fields_to_parse, db_file);
783 /*********************************************************************/
785 /* For long options that have no equivalent short option, use a
786 non-character as a pseudo short option, starting with CHAR_MAX + 1. */
788 OPT_BASH_MODE = CHAR_MAX + 1
791 static struct option long_options[] = {
792 { "help", no_argument, 0, 'h' },
793 { "version", no_argument, 0, 'v' },
794 { "quiet", no_argument, 0, 'q' },
795 { "use-leading-time", no_argument, 0, 't' },
796 { "db-file-output", 1, 0, 'd' },
797 { "db-pattern", 1, 0, 'p' },
798 { "db-root", 1, 0, 'r' },
799 { "db-list", 1, 0, 'l' },
800 { "mbox-pattern", 1, 0, 'm' },
801 { "search", 1, 0, 's' },
802 { "index", 0, 0, 'i' },
803 { "output", 1, 0, 'o' },
804 { "default-search", 1, 0, 'a' },
805 { "nb-mails-max", 1, 0, 'n' },
809 struct time_criterion {
812 int start_hour, end_hour;
816 /*********************************************************************/
818 static struct time_criterion time_criteria[] = {
820 { "8h", 0, 8, -1, -1 },
821 { "24h", 0, 24, -1, -1 },
822 { "48h", 0, 48, -1, -1 },
823 { "week", 0, 24 * 7, -1, -1 },
824 { "month", 0, 24 * 31, -1, -1 },
825 { "trimester", 0, 24 * 92, -1, -1 },
826 { "year", 0, 24 * 365, -1, -1 },
828 { "yesterday", 1, -1, -1, -1 },
829 { "today", 1, -1, -1, 0 },
831 { "monday", 1, -1, -1, 1 },
832 { "tuesday", 1, -1, -1, 2 },
833 { "wednesday", 1, -1, -1, 3 },
834 { "thursday", 1, -1, -1, 4 },
835 { "friday", 1, -1, -1, 5 },
836 { "saturday", 1, -1, -1, 6 },
837 { "sunday", 1, -1, -1, 7 },
841 /*********************************************************************/
843 time_t time_for_past_day(int day) {
850 delta_day = (7 + tm->tm_wday - day) % 7;
854 return t - (delta_day * 3600 * 24 + tm->tm_sec + 60 * tm->tm_min + 3600 * tm->tm_hour);
857 void init_condition(struct search_condition *condition, const char *full_string,
858 const char *default_search_field) {
859 char full_search_field[TOKEN_BUFFER_SIZE], *search_field;
862 struct alias_node *a;
864 for(a = global_alias_list; a; a = a->next) {
865 if(strcmp(full_string, a->alias) == 0) {
866 full_string = a->value;
871 string = parse_token(full_search_field, TOKEN_BUFFER_SIZE, ' ', full_string);
872 search_field = full_search_field;
874 if(search_field[0] == '!') {
876 condition->negation = 1;
878 condition->negation = 0;
881 condition->db_key = -1;
885 for(k = 0; k < sizeof(time_criteria) / sizeof(struct time_criterion); k++) {
886 if(strcmp(time_criteria[k].label, search_field) == 0) {
887 condition->db_key = ID_TIME_INTERVAL;
888 if(time_criteria[k].day_criterion) {
889 condition->time_start = time_for_past_day(time_criteria[k].past_week_day);
890 condition->time_stop = condition->time_start + 3600 * 24;
892 condition->time_start = time(0) - 3600 * time_criteria[k].start_hour;
893 if(time_criteria[k].end_hour >= 0) {
894 condition->time_stop = time(0) - 3600 * time_criteria[k].end_hour;
896 condition->time_stop = 0;
904 if(condition->db_key == -1) {
906 /* No time condition matched, look for the search fields */
908 for(m = 0; (m < MAX_ID) && condition->db_key == -1; m++) {
909 if(strncmp(field_keys[m], search_field, strlen(search_field)) == 0) {
910 condition->db_key = m;
914 /* None match, if there is a default search field, re-run the search with it */
916 if(condition->db_key == -1) {
917 if(default_search_field) {
918 for(m = 0; (m < MAX_ID) && condition->db_key == -1; m++) {
919 if(strncmp(field_keys[m],
920 default_search_field, strlen(default_search_field)) == 0) {
921 condition->db_key = m;
924 string = full_string;
925 if(string[0] == '!') { string++; }
929 if(condition->db_key == -1) {
931 "mymail: Syntax error in field key \"%s\".\n",
936 if(regcomp(&condition->db_value_regexp,
940 "mymail: Syntax error in regexp \"%s\" for field \"%s\".\n",
942 field_keys[condition->db_key]);
948 void free_condition(struct search_condition *condition) {
949 if(condition->db_key != ID_TIME_INTERVAL) {
950 regfree(&condition->db_value_regexp);
954 const char *eat_space(const char *s) {
955 while(*s == ' ' || *s == '\t') { s++; }
959 void read_rc_file(const char *rc_filename) {
960 char raw_line[BUFFER_SIZE];
961 char command[TOKEN_BUFFER_SIZE], tmp_token[TOKEN_BUFFER_SIZE];
968 rc_file = fopen(rc_filename, "r");
972 while(fgets(raw_line, BUFFER_SIZE, rc_file)) {
974 while(*t) { if(*t == '\n') { *t = '\0'; }; t++; }
979 if(*s && *s != '#') {
980 s = parse_token(command, TOKEN_BUFFER_SIZE, ' ', s);
982 if(strcmp(command, "alias") == 0) {
983 struct alias_node *a = safe_malloc(sizeof(struct alias_node));
984 a->next = global_alias_list;
985 global_alias_list = a;
988 s = parse_token(tmp_token, TOKEN_BUFFER_SIZE, '=', s);
989 a->alias = strdup(tmp_token);
992 a->value = strdup(s);
994 fprintf(stderr, "%s:%d syntax error, missing alias value.\n",
1000 fprintf(stderr, "%s:%d syntax error, missing alias key.\n",
1006 fprintf(stderr, "%s:%d syntax error, unknown command '%s'.\n",
1020 /*********************************************************************/
1021 /*********************************************************************/
1022 /*********************************************************************/
1024 int main(int argc, char **argv) {
1025 char *db_filename = 0;
1026 char *db_filename_regexp_string = 0;
1027 char *db_root_path = 0;
1028 char *db_filename_list = 0;
1029 char *mbox_filename_regexp_string = 0;
1030 char *default_search_field;
1031 char output_filename[PATH_MAX + 1];
1032 char rc_filename[PATH_MAX + 1];
1033 int action_index = 0;
1034 int error = 0, show_help = 0;
1035 const unsigned int nb_fields_to_parse =
1036 sizeof(fields_to_parse) / sizeof(struct parsable_field);
1039 unsigned int nb_search_conditions;
1040 struct search_condition search_conditions[MAX_NB_SEARCH_CONDITIONS];
1041 struct alias_node *a, *b;
1043 if(regcomp(&global_leading_from_line_regexp, LEADING_FROM_LINE_REGEXP_STRING, 0)) {
1045 "mymail: Cannot compile leading \"from\" line regexp. That is strange.\n");
1049 if(getenv("MYMAILRC")) {
1050 sprintf(rc_filename, "%s", getenv("MYMAILRC"));
1051 } else if(getenv("HOME")) {
1052 sprintf(rc_filename, "%s/.mymailrc", getenv("HOME"));
1054 rc_filename[0] = '\0';
1057 global_alias_list = 0;
1059 global_use_leading_time = 0;
1060 global_nb_mails_max = 250;
1062 default_search_field = 0;
1063 strncpy(output_filename, "", PATH_MAX);
1065 if(rc_filename[0]) {
1066 read_rc_file(rc_filename);
1071 #warning Test code added on 2013 May 02 11:17:01
1072 struct alias_node *a;
1073 for(a = global_alias_list; a; a = a->next) {
1074 printf ("ALIAS [%s] [%s]\n", a->alias, a->value);
1079 setlocale(LC_ALL, "");
1081 nb_search_conditions = 0;
1083 while ((c = getopt_long(argc, argv, "hvqip:s:d:r:l:o:a:m:",
1084 long_options, NULL)) != -1) {
1093 print_version(stdout);
1101 global_use_leading_time = 1;
1110 fprintf(stderr, "mymail: Can not set the db filename twice.\n");
1113 db_filename = strdup(optarg);
1117 if(db_filename_regexp_string) {
1118 fprintf(stderr, "mymail: Can not set the db filename pattern twice.\n");
1121 db_filename_regexp_string = strdup(optarg);
1125 if(mbox_filename_regexp_string) {
1126 fprintf(stderr, "mymail: Can not set the mbox filename pattern twice.\n");
1129 mbox_filename_regexp_string = strdup(optarg);
1133 strncpy(output_filename, optarg, PATH_MAX);
1138 fprintf(stderr, "mymail: Can not set the db root path twice.\n");
1141 db_root_path = strdup(optarg);
1145 if(db_filename_list) {
1146 fprintf(stderr, "mymail: Can not set the db filename list twice.\n");
1149 db_filename_list = strdup(optarg);
1153 if(nb_search_conditions == MAX_NB_SEARCH_CONDITIONS) {
1154 fprintf(stderr, "mymail: Too many search patterns.\n");
1157 init_condition(&search_conditions[nb_search_conditions], optarg, default_search_field);
1158 nb_search_conditions++;
1162 default_search_field = optarg;
1166 global_nb_mails_max = atoi(optarg);
1176 print_usage(stderr);
1181 print_usage(stdout);
1185 /* Set all the values that may defined in the arguments, through
1186 environment variables, or hard-coded */
1188 db_filename = default_value(db_filename,
1192 db_filename_regexp_string = default_value(db_filename_regexp_string,
1196 db_root_path = default_value(db_root_path,
1200 db_filename_list = default_value(db_filename_list,
1204 mbox_filename_regexp_string = default_value(mbox_filename_regexp_string,
1205 "MYMAIL_MBOX_PATTERN",
1212 regex_t mbox_filename_regexp_static;
1213 regex_t *mbox_filename_regexp;
1215 if(mbox_filename_regexp_string) {
1216 if(regcomp(&mbox_filename_regexp_static,
1217 mbox_filename_regexp_string,
1220 "mymail: Syntax error in regexp \"%s\".\n",
1221 mbox_filename_regexp_string);
1224 mbox_filename_regexp = &mbox_filename_regexp_static;
1226 mbox_filename_regexp = 0;
1229 db_file = safe_fopen(db_filename, "w", "index file for indexing");
1231 for(f = 0; f < nb_fields_to_parse; f++) {
1232 if(regcomp(&fields_to_parse[f].regexp,
1233 fields_to_parse[f].regexp_string,
1234 fields_to_parse[f].cflags)) {
1236 "mymail: Syntax error in regexp \"%s\" for field \"%s\".\n",
1237 fields_to_parse[f].regexp_string,
1238 field_keys[fields_to_parse[f].id]);
1244 "%s version_%s format_%d raw\n",
1245 MYMAIL_DB_MAGIC_TOKEN,
1247 MYMAIL_DB_FORMAT_VERSION);
1249 while(optind < argc) {
1250 recursive_index_mbox(db_file,
1251 argv[optind], mbox_filename_regexp,
1252 nb_fields_to_parse, fields_to_parse);
1259 if(mbox_filename_regexp) {
1260 regfree(mbox_filename_regexp);
1263 for(f = 0; f < nb_fields_to_parse; f++) {
1264 regfree(&fields_to_parse[f].regexp);
1273 int nb_extracted_mails = 0;
1275 if(output_filename[0]) {
1276 output_file = safe_fopen(output_filename, "w", "result mbox");
1278 output_file = stdout;
1282 if(nb_search_conditions > 0) {
1284 /* Recursive search if db_root_path is set */
1287 regex_t db_filename_regexp;
1288 if(regcomp(&db_filename_regexp,
1289 db_filename_regexp_string,
1292 "mymail: Syntax error in regexp \"%s\".\n",
1293 db_filename_regexp_string);
1297 nb_extracted_mails = recursive_search_in_db(db_root_path, &db_filename_regexp,
1299 nb_search_conditions, search_conditions,
1302 regfree(&db_filename_regexp);
1305 /* Search in all db files listed in db_filename_list */
1307 if(db_filename_list) {
1308 char db_filename[PATH_MAX + 1];
1311 s = db_filename_list;
1314 s = parse_token(db_filename, PATH_MAX + 1, ';', s);
1316 if(db_filename[0]) {
1317 nb_extracted_mails =
1318 search_in_db(db_filename,
1320 nb_search_conditions, search_conditions, output_file);
1325 /* Search in all db files listed in the command arguments */
1327 while(optind < argc) {
1328 nb_extracted_mails =
1329 search_in_db(argv[optind],
1331 nb_search_conditions, search_conditions, output_file);
1337 if(nb_extracted_mails > 0) {
1338 printf("Found %d matching mails.\n", nb_extracted_mails);
1340 printf("No matching mail found.\n");
1344 fflush(output_file);
1346 if(output_file != stdout) {
1347 fclose(output_file);
1351 for(n = 0; n < nb_search_conditions; n++) {
1352 free_condition(&search_conditions[n]);
1355 a = global_alias_list;
1365 free(db_filename_regexp_string);
1367 free(db_filename_list);
1368 free(mbox_filename_regexp_string);
1370 regfree(&global_leading_from_line_regexp);