mairix.c 22.7 KB
Newer Older
Richard Curnow's avatar
Richard Curnow committed
1 2 3 4
/*
  mairix - message index builder and finder for maildir folders.

 **********************************************************************
5
 * Copyright (C) Richard P. Curnow  2002,2003,2004,2005,2006,2007,2008
Richard Curnow's avatar
Richard Curnow committed
6 7
 * Copyright (C) Sanjoy Mahajan 2005
 * - mfolder validation code
8 9
 * Copyright (C) James Cameron 2005
 * Copyright (C) Paul Fox 2006
Richard P. Curnow's avatar
Richard P. Curnow committed
10
 *
Richard Curnow's avatar
Richard Curnow committed
11 12 13
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of version 2 of the GNU General Public License as
 * published by the Free Software Foundation.
Richard P. Curnow's avatar
Richard P. Curnow committed
14
 *
Richard Curnow's avatar
Richard Curnow committed
15 16 17 18
 * This program is distributed in the hope that it will be useful, but
 * WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 * General Public License for more details.
Richard P. Curnow's avatar
Richard P. Curnow committed
19
 *
Richard Curnow's avatar
Richard Curnow committed
20 21
 * You should have received a copy of the GNU General Public License along
 * with this program; if not, write to the Free Software Foundation, Inc.,
22
 * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
Richard P. Curnow's avatar
Richard P. Curnow committed
23
 *
Richard Curnow's avatar
Richard Curnow committed
24 25 26 27
 **********************************************************************
 */

#include "mairix.h"
28
#include "version.h"
29
#include <assert.h>
Richard Curnow's avatar
Richard Curnow committed
30 31 32 33 34
#include <sys/types.h>
#include <sys/stat.h>
#include <pwd.h>
#include <unistd.h>
#include <ctype.h>
35
#include <locale.h>
36
#include <signal.h>
Richard Curnow's avatar
Richard Curnow committed
37

38 39 40 41
#ifdef TEST_OOM
int total_bytes=0;
#endif

Richard Curnow's avatar
Richard Curnow committed
42
int verbose = 0;
43
int do_hardlinks = 0;
Richard Curnow's avatar
Richard Curnow committed
44 45

static char *folder_base = NULL;
46
static char *maildir_folders = NULL;
47
static char *mh_folders = NULL;
48
static char *mboxen = NULL;
49
static char *mfolder = NULL;
50
static char *omit = NULL;
Richard Curnow's avatar
Richard Curnow committed
51
static char *database_path = NULL;
52
static enum folder_type output_folder_type = FT_MAILDIR;
53
static int skip_integrity_checks = 0;
Richard Curnow's avatar
Richard Curnow committed
54

55 56 57 58 59
enum filetype {
  M_NONE, M_FILE, M_DIR, M_OTHER
};

static enum filetype classify_file(char *name)/*{{{*/
Richard Curnow's avatar
Richard Curnow committed
60 61 62
{
  struct stat sb;
  if (stat(name, &sb) < 0) {
63 64 65 66 67 68 69 70
    return M_NONE;
  }
  if (S_ISREG(sb.st_mode)) {
    return M_FILE;
  } else if (S_ISDIR(sb.st_mode)) {
    return M_DIR;
  } else {
    return M_OTHER;
Richard Curnow's avatar
Richard Curnow committed
71 72 73
  }
}
/*}}}*/
74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92
/*{{{ member of*/
/* returns 1 iff COMPLETE_MFOLDER (i.e. the match folder with
   folder_base prepended if needed) matches one of the FOLDERS after
   expanding the wildcards and recursion. Used to make sure that the
   match folder will not overwrite a valuable mail file or
   directory.  */
int member_of (const char *complete_mfolder,
    const char *folder_base,
    const char *folders,
    enum folder_type ft,
    struct globber_array *omit_globs) {
  char **raw_paths, **paths;
  int n_raw_paths, n_paths, i;

  if (!folders)
    return 0;
  split_on_colons(folders, &n_raw_paths, &raw_paths);
  switch (ft) {
    case FT_MAILDIR:
93
      glob_and_expand_paths(folder_base, raw_paths, n_raw_paths, &paths, &n_paths, &maildir_traverse_methods, omit_globs);
94 95
      break;
    case FT_MH:
96
      glob_and_expand_paths(folder_base, raw_paths, n_raw_paths, &paths, &n_paths, &mh_traverse_methods, omit_globs);
97 98
      break;
    case FT_MBOX:
99
      glob_and_expand_paths(folder_base, raw_paths, n_raw_paths, &paths, &n_paths, &mbox_traverse_methods, omit_globs);
100
      break;
101
    case FT_RAW: /* cannot happen but to keep compiler happy */
102
    case FT_EXCERPT:
Richard P. Curnow's avatar
Richard P. Curnow committed
103
      break;
104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121
  }
  for (i=0; i<n_paths; i++) {
    struct stat mfolder_sb, src_folder_sb; /* for checking inode numbers */

    /* if the complete path names are the same, definitely a match */
    if (strcmp (complete_mfolder, paths[i]) == 0)
      return 1;
    /* also a match if they point to the same file or directory but
       via different routes (e.g. absolute path for one but path with
       ../.. for the other), so check inode numbers */
    /* if cannot even get stat() info, probably not wrecking any mail
       files or dirs, so continue, i.e. skip inode check. */
    if (stat (complete_mfolder, &mfolder_sb) != 0 ||
        stat (paths[i], &src_folder_sb) != 0)
      continue;
    if (mfolder_sb.st_ino == src_folder_sb.st_ino)
      return 1;
  }
Richard P. Curnow's avatar
Richard P. Curnow committed
122
  return 0;
123 124
}
/*}}}*/
Richard Curnow's avatar
Richard Curnow committed
125 126 127
static char *copy_value(char *text)/*{{{*/
{
  char *p;
128
  char *result;
Richard Curnow's avatar
Richard Curnow committed
129 130 131
  for (p = text; *p && (*p != '='); p++) ;
  if (!*p) return NULL;
  p++;
132 133
  result = expand_string(p);
  return result;
Richard Curnow's avatar
Richard Curnow committed
134 135
}
/*}}}*/
Richard Curnow's avatar
Richard Curnow committed
136 137 138
static void add_folders(char **folders, char *extra_folders)/*{{{*/
{
  /* note : extra_pointers is stale after this routine exits. */
Richard P. Curnow's avatar
Richard P. Curnow committed
139

Richard Curnow's avatar
Richard Curnow committed
140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156
  if (!*folders) {
    *folders = extra_folders;
  } else {
    char *old_folders = *folders;
    char *new_folders;
    int old_len, extra_len;
    old_len = strlen(old_folders);
    extra_len = strlen(extra_folders);
    new_folders = new_array(char, old_len + extra_len + 2);
    strcpy(new_folders, old_folders);
    strcpy(new_folders + old_len, ":");
    strcpy(new_folders + old_len + 1, extra_folders);
    *folders = new_folders;
    free(old_folders);
  }
}
/*}}}*/
157 158 159 160 161 162 163 164 165 166
static void parse_output_folder(char *p)/*{{{*/
{
  char *temp;
  temp = copy_value(p);
  if (!strncasecmp(temp, "mh", 2)) {
    output_folder_type = FT_MH;
  } else if (!strncasecmp(temp, "maildir", 7)) {
    output_folder_type = FT_MAILDIR;
  } else if (!strncasecmp(temp, "raw", 3)) {
    output_folder_type = FT_RAW;
167 168
  } else if (!strncasecmp(temp, "excerpt", 3)) {
    output_folder_type = FT_EXCERPT;
169 170 171 172
  } else if (!strncasecmp(temp, "mbox", 4)) {
    output_folder_type = FT_MBOX;
  }
  else {
173
    fprintf(stderr, "Unrecognized mformat <%s>\n", temp);
174 175 176 177
  }
  free(temp);
}
/*}}}*/
Richard Curnow's avatar
Richard Curnow committed
178 179 180
static void parse_rc_file(char *name)/*{{{*/
{
  FILE *in;
Richard Curnow's avatar
Richard Curnow committed
181
  char line[4096], *p;
Richard Curnow's avatar
Richard Curnow committed
182 183 184 185 186 187 188 189
  int len, lineno;
  int all_blank;
  int used_default_name = 0;

  if (!name) {
    /* open default file */
    struct passwd *pw;
    char *home;
190 191 192 193
    home = getenv("HOME");
    if (!home) {
      pw = getpwuid(getuid());
      if (!pw) {
194
        fprintf(stderr, "Cannot determine home directory\n");
195 196 197
        exit(2);
      }
      home = pw->pw_dir;
Richard Curnow's avatar
Richard Curnow committed
198 199 200 201 202 203
    }
    name = new_array(char, strlen(home) + 12);
    strcpy(name, home);
    strcat(name, "/.mairixrc");
    used_default_name = 1;
  }
Richard P. Curnow's avatar
Richard P. Curnow committed
204

Richard Curnow's avatar
Richard Curnow committed
205 206 207
  in = fopen(name, "r");
  if (!in) {
    fprintf(stderr, "Cannot open %s, exiting\n", name);
208
    exit(2);
Richard Curnow's avatar
Richard Curnow committed
209 210 211 212 213 214 215 216
  }

  lineno = 0;
  while(fgets(line, sizeof(line), in)) {
    lineno++;
    len = strlen(line);
    if (len > sizeof(line) - 4) {
      fprintf(stderr, "Line %d in %s too long, exiting\n", lineno, name);
217
      exit(2);
Richard Curnow's avatar
Richard Curnow committed
218 219 220 221 222
    }

    if (line[len-1] == '\n') {
      line[len-1] = '\0';
    }
Richard P. Curnow's avatar
Richard P. Curnow committed
223

Richard Curnow's avatar
Richard Curnow committed
224 225 226 227 228 229 230
    /* Strip trailing comments. */
    for (p=line; *p && !strchr("#!;%", *p); p++) ;
    if (*p) *p = '\0';

    /* Discard blank lines */
    all_blank = 1;
    for (p=line; *p; p++) {
231
      if (!isspace(*(unsigned char *)p)) {
Richard Curnow's avatar
Richard Curnow committed
232 233 234 235
        all_blank = 0;
        break;
      }
    }
Richard P. Curnow's avatar
Richard P. Curnow committed
236

Richard Curnow's avatar
Richard Curnow committed
237
    if (all_blank) continue;
Richard P. Curnow's avatar
Richard P. Curnow committed
238

Richard Curnow's avatar
Richard Curnow committed
239 240
    /* Now a real line to parse */
    if (!strncasecmp(p, "base", 4)) folder_base = copy_value(p);
241 242 243 244 245 246 247 248
    else if (!strncasecmp(p, "folders", 7)) {
      fprintf(stderr, "'folders=' option in rc file is depracated, use 'maildir='\n");
      add_folders(&maildir_folders, copy_value(p));
    }
    else if (!strncasecmp(p, "maildir=", 8)) add_folders(&maildir_folders, copy_value(p));
    else if (!strncasecmp(p, "mh_folders=", 11)) {
      fprintf(stderr, "'mh_folders=' option in rc file is depracated, use 'mh='\n");
      add_folders(&mh_folders, copy_value(p));
249
    }
250 251
    else if (!strncasecmp(p, "mh=", 3)) add_folders(&mh_folders, copy_value(p));
    else if (!strncasecmp(p, "mbox=", 5)) add_folders(&mboxen, copy_value(p));
252
    else if (!strncasecmp(p, "omit=", 5)) add_folders(&omit, copy_value(p));
Richard P. Curnow's avatar
Richard P. Curnow committed
253

254
    else if (!strncasecmp(p, "mformat=", 8)) {
255 256
      parse_output_folder(p);
    }
257
    else if (!strncasecmp(p, "mfolder=", 8)) mfolder = copy_value(p);
258
    else if (!strncasecmp(p, "database=", 9)) database_path = copy_value(p);
259
    else if (!strncasecmp(p, "nochecks", 8)) skip_integrity_checks = 1;
Richard Curnow's avatar
Richard Curnow committed
260
    else {
261 262 263
      if (verbose) {
        fprintf(stderr, "Unrecognized option at line %d in %s\n", lineno, name);
      }
Richard Curnow's avatar
Richard Curnow committed
264 265
    }
  }
266

Richard Curnow's avatar
Richard Curnow committed
267 268 269 270 271 272 273 274 275 276 277 278 279 280
  fclose(in);

  if (used_default_name) free(name);
}
/*}}}*/
static int compare_strings(const void *a, const void *b)/*{{{*/
{
  const char **aa = (const char **) a;
  const char **bb = (const char **) b;
  return strcmp(*aa, *bb);
}
/*}}}*/
static int check_message_list_for_duplicates(struct msgpath_array *msgs)/*{{{*/
{
281
  /* Caveat : only examines the file-per-message case */
Richard Curnow's avatar
Richard Curnow committed
282
  char **sorted_paths;
283
  int i, n, nn;
Richard Curnow's avatar
Richard Curnow committed
284 285 286 287
  int result;

  n = msgs->n;
  sorted_paths = new_array(char *, n);
288 289 290 291 292 293 294 295 296 297 298
  for (i=0, nn=0; i<n; i++) {
    switch (msgs->type[i]) {
      case MTY_MBOX:
        break;
      case MTY_DEAD:
        assert(0);
        break;
      case MTY_FILE:
        sorted_paths[nn++] = msgs->paths[i].src.mpf.path;
        break;
    }
Richard Curnow's avatar
Richard Curnow committed
299
  }
300 301
  qsort(sorted_paths, nn, sizeof(char *), compare_strings);

Richard Curnow's avatar
Richard Curnow committed
302
  result = 0;
303
  for (i=1; i<nn; i++) {
Richard Curnow's avatar
Richard Curnow committed
304 305 306 307 308
    if (!strcmp(sorted_paths[i-1], sorted_paths[i])) {
      result = 1;
      break;
    }
  }
Richard P. Curnow's avatar
Richard P. Curnow committed
309

Richard Curnow's avatar
Richard Curnow committed
310 311 312 313 314
  free(sorted_paths);
  return result;
}
/*}}}*/

315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335
static void emit_int(int x)/*{{{*/
{
  char buf1[20], buf2[20];
  char *p, *q;
  int neg=0;
  p = buf1;
  *p = '0'; /* In case x is zero */
  if (x < 0) {
    neg = 1;
    x = -x;
  }
  while (x) {
    *p++ = '0' + (x % 10);
    x /= 10;
  }
  p--;
  q = buf2;
  if (neg) *q++ = '-';
  while (p >= buf1) {
    *q++ = *p--;
  }
Richard P. Curnow's avatar
Richard P. Curnow committed
336
  write(2, buf2, q-buf2);
337 338 339
  return;
}
/*}}}*/
340
void out_of_mem(char *file, int line, size_t size)/*{{{*/
341 342 343 344 345 346
{
  /* Hairy coding ahead - can't use any [s]printf, itoa etc because
   * those might try to use the heap! */

  int filelen;
  char *p;
Richard P. Curnow's avatar
Richard P. Curnow committed
347

348 349 350 351 352 353
  static char msg1[] = "Out of memory (at ";
  static char msg2[] = " bytes)\n";
  /* Perhaps even strlen is unsafe in this situation? */
  p = file;
  while (*p) p++;
  filelen = p - file;
354
  write(2, msg1, sizeof(msg1)-1);
355 356 357 358 359
  write(2, file, filelen);
  write(2, ":", 1);
  emit_int(line);
  write(2, ", ", 2);
  emit_int(size);
360
  write(2, msg2, sizeof(msg2)-1);
Richard P. Curnow's avatar
Richard P. Curnow committed
361
  exit(2);
362 363
}
/*}}}*/
364 365 366 367 368 369 370 371 372 373 374 375 376 377
void report_error(const char *str, const char *filename)/*{{{*/
{
  if (filename) {
    int len = strlen(str) + strlen(filename) + 4;
    char *t;
    t = new_array(char, len);
    sprintf(t, "%s '%s'", str, filename);
    perror(t);
    free(t);
  } else {
    perror(str);
  }
}
/*}}}*/
Richard Curnow's avatar
Richard Curnow committed
378 379 380
static void print_copyright(void)/*{{{*/
{
  fprintf(stderr,
381
          "mairix %s, Copyright (C) 2002-2010 Richard P. Curnow\n"
Richard Curnow's avatar
Richard Curnow committed
382 383 384
          "mairix comes with ABSOLUTELY NO WARRANTY.\n"
          "This is free software, and you are welcome to redistribute it\n"
          "under certain conditions; see the GNU General Public License for details.\n\n",
385
          PROGRAM_VERSION);
Richard Curnow's avatar
Richard Curnow committed
386 387
}
/*}}}*/
388 389 390 391 392 393 394
static void print_version(void)/*{{{*/
{
  fprintf(stdout,
          "mairix %s\n",
          PROGRAM_VERSION);
}
/*}}}*/
395 396 397 398 399
static void handlesig(int signo)/*{{{*/
{
  unlock_and_exit(7);
}
/*}}}*/
400
static void usage(void)/*{{{*/
Richard Curnow's avatar
Richard Curnow committed
401 402
{
  print_copyright();
Richard P. Curnow's avatar
Richard P. Curnow committed
403

Richard Curnow's avatar
Richard Curnow committed
404
  printf("mairix [-h]                                    : Show help\n"
405
         "mairix [-f <rcfile>] [-v] [-p] [-F]            : Build index\n"
Richard Curnow's avatar
Richard Curnow committed
406
         "mairix [-f <rcfile>] [-a] [-t] expr1 ... exprN : Run search\n"
407 408 409
         "mairix [-f <rcfile>] -d                        : Dump database to stdout\n"
         "-h           : show this help\n"
         "-f <rcfile>  : use alternative rc file (default ~/.mairixrc)\n"
410
         "-V           : show version\n"
411 412
         "-v           : be verbose\n"
         "-p           : purge messages that no longer exist\n"
413
         "-F           : fast scan for maildir and MH folders (no mtime or size checks)\n"
414
         "-a           : add new matches to match folder (default : clear it first)\n"
415
         "-x           : display excerpt of message headers (default : use match folder)\n" 
416 417 418
         "-t           : include all messages in same threads as matching messages\n"
         "-o <mfolder> : override setting of mfolder from mairixrc file\n"
         "-r           : force raw output regardless of mformat setting in mairixrc file\n"
419
         "-H           : force hard links rather than symbolic ones\n"
420
         "expr_i       : search expression (all expr's AND'ed together):\n"
421
         "    word          : match word in message body and major headers\n"
Richard Curnow's avatar
Richard Curnow committed
422 423 424 425 426 427
         "    t:word        : match word in To: header\n"
         "    c:word        : match word in Cc: header\n"
         "    f:word        : match word in From: header\n"
         "    a:word        : match word in To:, Cc: or From: headers (address)\n"
         "    s:word        : match word in Subject: header\n"
         "    b:word        : match word in message body\n"
428
         "    m:word        : match word in Message-ID: header\n"
429
         "    n:word        : match name of attachment within message\n"
430
         "    F:flags       : match on message flags (s=seen,r=replied,f=flagged,-=negate)\n"
431 432 433
         "    p:substring   : match substring of path\n"
         "    d:start-end   : match date range\n"
         "    z:low-high    : match messages in size range\n"
Richard Curnow's avatar
Richard Curnow committed
434
         "    bs:word       : match word in Subject: header or body (or any other group of prefixes)\n"
435 436
         "    s:word1,word2 : match both words in Subject:\n"
         "    s:word1/word2 : match either word or both words in Subject:\n"
Richard Curnow's avatar
Richard Curnow committed
437 438
         "    s:~word       : match messages not containing word in Subject:\n"
         "    s:substring=  : match substring in any word in Subject:\n"
439
         "    s:^substring= : match left-anchored substring in any word in Subject:\n"
Richard Curnow's avatar
Richard Curnow committed
440 441 442 443 444
         "    s:substring=2 : match substring with <=2 errors in any word in Subject:\n"
         "\n"
         "    (See documentation for more examples)\n"
         );
}
445
    /*}}}*/
Richard Curnow's avatar
Richard Curnow committed
446
/* Notes on folder management: {{{
Richard P. Curnow's avatar
Richard P. Curnow committed
447

448
   Assumption is that the user wants to keep the 'mfolder' directories under a
Richard Curnow's avatar
Richard Curnow committed
449 450 451
   common root with the real maildir folders.  This allows a common value for
   mutt's 'folder' variable => the '+' and '=' prefixes work better.  This
   means the indexer here can't just scan down all subdirectories of a single
452
   ancestor, because it'll pick up its own mfolders.  So, use environment
Richard Curnow's avatar
Richard Curnow committed
453
   variables to tailor the folders.
Richard P. Curnow's avatar
Richard P. Curnow committed
454

455
   MAIRIX_FOLDER_BASE is the common parent directory of the folders (aka
Richard Curnow's avatar
Richard Curnow committed
456 457
   mutt's 'folder' variable)

458 459 460 461 462
   MAIRIX_MAILDIR_FOLDERS, MAIRIX_MH_FOLDERS, MAIRIX_MBOXEN are
   colon-separated lists of folders to index, with '...' after a
   component meaning any maildir underneath it.

   MAIRIX_MFOLDER is the folder to put the match data.
Richard Curnow's avatar
Richard Curnow committed
463

464
   For example, if
Richard Curnow's avatar
Richard Curnow committed
465 466
   MAIRIX_FOLDER_BASE = "/home/foobar/mail"
   MAIRIX_FOLDERS = "inbox:lists...:action:archive..."
467
   MAIRIX_MFOLDER = "mf"
Richard Curnow's avatar
Richard Curnow committed
468

469
   then /home/foobar/mail/mf/{new,cur,tmp} contain the output of the search.
Richard Curnow's avatar
Richard Curnow committed
470
   }}} */
Richard Curnow's avatar
Richard Curnow committed
471

Richard Curnow's avatar
Richard Curnow committed
472
int main (int argc, char **argv)/*{{{*/
Richard Curnow's avatar
Richard Curnow committed
473 474
{
  struct msgpath_array *msgs;
475
  struct database *db = NULL;
Richard Curnow's avatar
Richard Curnow committed
476 477

  char *arg_rc_file_path = NULL;
478
  char *arg_mfolder = NULL;
479
  char *e;
Richard Curnow's avatar
Richard Curnow committed
480 481 482 483 484 485
  int do_augment = 0;
  int do_threads = 0;
  int do_search = 0;
  int do_purge = 0;
  int any_updates = 0;
  int any_purges = 0;
Richard Curnow's avatar
Richard Curnow committed
486
  int do_help = 0;
487
  int do_raw_output = 0;
488
  int do_excerpt_output = 0;
489
  int do_dump = 0;
490
  int do_integrity_checks = 1;
491
  int do_forced_unlock = 0;
492
  int do_fast_index = 0;
Richard Curnow's avatar
Richard Curnow committed
493

Richard P. Curnow's avatar
Richard P. Curnow committed
494 495
  unsigned int forced_hash_key = CREATE_RANDOM_DATABASE_HASH;

496 497
  struct globber_array *omit_globs;

498 499
  int result;

500 501
  setlocale(LC_CTYPE, "");

Richard Curnow's avatar
Richard Curnow committed
502 503 504
  while (++argv, --argc) {
    if (!*argv) {
      break;
Richard Curnow's avatar
Richard Curnow committed
505
    } else if (!strcmp(*argv, "-f") || !strcmp(*argv, "--rcfile")) {
Richard Curnow's avatar
Richard Curnow committed
506
      ++argv, --argc;
507 508 509 510
      if (!argc) {
        fprintf(stderr, "No filename given after -f argument\n");
        exit(1);
      }
Richard Curnow's avatar
Richard Curnow committed
511
      arg_rc_file_path = *argv;
Richard Curnow's avatar
Richard Curnow committed
512
    } else if (!strcmp(*argv, "-t") || !strcmp(*argv, "--threads")) {
Richard Curnow's avatar
Richard Curnow committed
513 514
      do_search = 1;
      do_threads = 1;
Richard Curnow's avatar
Richard Curnow committed
515
    } else if (!strcmp(*argv, "-a") || !strcmp(*argv, "--augment")) {
Richard Curnow's avatar
Richard Curnow committed
516 517
      do_search = 1;
      do_augment = 1;
518
    } else if (!strcmp(*argv, "-o") || !strcmp(*argv, "--mfolder")) {
Richard Curnow's avatar
Richard Curnow committed
519
      ++argv, --argc;
520 521 522 523
      if (!argc) {
        fprintf(stderr, "No folder name given after -o argument\n");
        exit(1);
      }
524
      arg_mfolder = *argv;
Richard Curnow's avatar
Richard Curnow committed
525
    } else if (!strcmp(*argv, "-p") || !strcmp(*argv, "--purge")) {
Richard Curnow's avatar
Richard Curnow committed
526
      do_purge = 1;
527 528
    } else if (!strcmp(*argv, "-d") || !strcmp(*argv, "--dump")) {
      do_dump = 1;
529 530
    } else if (!strcmp(*argv, "-r") || !strcmp(*argv, "--raw-output")) {
      do_raw_output = 1;
531 532
    } else if (!strcmp(*argv, "-x") || !strcmp(*argv, "--excerpt-output")) {
      do_excerpt_output = 1;
533 534
    } else if (!strcmp(*argv, "-H") || !strcmp(*argv, "--force-hardlinks")) {
      do_hardlinks = 1;
535 536
    } else if (!strcmp(*argv, "-Q") || !strcmp(*argv, "--no-integrity-checks")) {
      do_integrity_checks = 0;
537 538
    } else if (!strcmp(*argv, "--unlock")) {
      do_forced_unlock = 1;
539 540 541
    } else if (!strcmp(*argv, "-F") ||
               !strcmp(*argv, "--fast-index")) {
      do_fast_index = 1;
Richard P. Curnow's avatar
Richard P. Curnow committed
542 543 544 545 546 547 548 549 550 551 552
    } else if (!strcmp(*argv, "--force-hash-key-new-database")) {
      ++argv, --argc;
      if (!argc) {
        fprintf(stderr, "No hash key given after --force-hash-key-new-database\n");
        exit(1);
      }
      if ( 1 != sscanf(*argv, "%u", &forced_hash_key) )
	{
        fprintf(stderr, "Hash key given after --force-hash-key-new-database could not be parsed\n");
        exit(1);
	}
Richard Curnow's avatar
Richard Curnow committed
553
    } else if (!strcmp(*argv, "-v") || !strcmp(*argv, "--verbose")) {
Richard Curnow's avatar
Richard Curnow committed
554
      verbose = 1;
555 556 557
    } else if (!strcmp(*argv, "-V") || !strcmp(*argv, "--version")) {
      print_version();
      exit(0);
Richard Curnow's avatar
Richard Curnow committed
558 559 560
    } else if (!strcmp(*argv, "-h") ||
               !strcmp(*argv, "--help")) {
      do_help = 1;
Richard Curnow's avatar
Richard Curnow committed
561 562 563 564 565 566 567 568 569 570 571
    } else if ((*argv)[0] == '-') {
      fprintf(stderr, "Unrecognized option %s\n", *argv);
    } else if (!strcmp(*argv, "--")) {
      /* End of args */
      break;
    } else {
      /* standard args start */
      break;
    }
  }

Richard Curnow's avatar
Richard Curnow committed
572 573 574 575 576 577 578 579
  if (do_help) {
    usage();
    exit(0);
  }

  if (verbose) {
    print_copyright();
  }
Richard P. Curnow's avatar
Richard P. Curnow committed
580

Richard Curnow's avatar
Richard Curnow committed
581 582 583 584
  if (*argv) {
    /* There are still args to process */
    do_search = 1;
  }
Richard P. Curnow's avatar
Richard P. Curnow committed
585

Richard Curnow's avatar
Richard Curnow committed
586 587 588 589 590 591
  parse_rc_file(arg_rc_file_path);

  if (getenv("MAIRIX_FOLDER_BASE")) {
    folder_base = getenv("MAIRIX_FOLDER_BASE");
  }

592 593
  if (getenv("MAIRIX_MAILDIR_FOLDERS")) {
    maildir_folders = getenv("MAIRIX_MAIDIR_FOLDERS");
Richard Curnow's avatar
Richard Curnow committed
594 595
  }

596 597 598 599
  if (getenv("MAIRIX_MH_FOLDERS")) {
    mh_folders = getenv("MAIRIX_MH_FOLDERS");
  }

600 601 602 603
  if ((e = getenv("MAIRIX_MBOXEN"))) {
    mboxen = e;
  }

604 605
  if (getenv("MAIRIX_MFOLDER")) {
    mfolder = getenv("MAIRIX_MFOLDER");
Richard Curnow's avatar
Richard Curnow committed
606 607 608 609 610 611
  }

  if (getenv("MAIRIX_DATABASE")) {
    database_path = getenv("MAIRIX_DATABASE");
  }

612 613
  if (arg_mfolder) {
    mfolder = arg_mfolder;
Richard Curnow's avatar
Richard Curnow committed
614
  }
Richard P. Curnow's avatar
Richard P. Curnow committed
615

616 617 618
  if (skip_integrity_checks) {
    do_integrity_checks = 0;
  }
Richard P. Curnow's avatar
Richard P. Curnow committed
619

Richard Curnow's avatar
Richard Curnow committed
620 621
  if (!folder_base) {
    fprintf(stderr, "No folder_base/MAIRIX_FOLDER_BASE set\n");
622
    exit(2);
Richard Curnow's avatar
Richard Curnow committed
623
  }
Richard P. Curnow's avatar
Richard P. Curnow committed
624

Richard Curnow's avatar
Richard Curnow committed
625 626
  if (!database_path) {
    fprintf(stderr, "No database/MAIRIX_DATABASE set\n");
627
    exit(2);
Richard Curnow's avatar
Richard Curnow committed
628
  }
629 630 631

  if (do_raw_output) {
    output_folder_type = FT_RAW;
632 633
  } else if (do_excerpt_output) {
    output_folder_type = FT_EXCERPT;
634 635
  }

636 637 638 639 640
  if (omit) {
    omit_globs = colon_sep_string_to_globber_array(omit);
  } else {
    omit_globs = NULL;
  }
641 642 643 644 645 646

  /* Lock database.
   * Prevent concurrent updates due to parallel indexing (e.g. due to stuck
   * cron jobs).
   * Prevent concurrent searching and indexing. */

647 648 649 650
  signal(SIGHUP, handlesig);
  signal(SIGINT, handlesig);
  signal(SIGQUIT, handlesig);

651
  lock_database(database_path, do_forced_unlock);
Richard P. Curnow's avatar
Richard P. Curnow committed
652

653 654
  if (do_dump) {
    dump_database(database_path);
655
    result = 0;
656 657

  } else if (do_search) {
658 659
    int len;
    char *complete_mfolder;
660
    enum filetype ftype;
661

662
    if (!mfolder) {
663 664 665 666 667 668 669
      switch (output_folder_type) {
        case FT_RAW:
        case FT_EXCERPT:
          break;
        default:
          fprintf(stderr, "No mfolder/MAIRIX_MFOLDER set\n");
          unlock_and_exit(2);
670
      }
671
      mfolder = new_string("");
Richard Curnow's avatar
Richard Curnow committed
672 673
    }

674 675
    /* complete_mfolder is needed by search_top() and member_of() so
       compute it once here rather than in search_top() as well */
Richard P. Curnow's avatar
Richard P. Curnow committed
676
    if ((mfolder[0] == '/') ||
677
        ((mfolder[0] == '.') && (mfolder[1] == '/'))) {
678 679 680 681 682 683 684 685 686
      complete_mfolder = new_string(mfolder);
    } else {
      len = strlen(folder_base) + strlen(mfolder) + 2;
      complete_mfolder = new_array(char, len);
      strcpy(complete_mfolder, folder_base);
      strcat(complete_mfolder, "/");
      strcat(complete_mfolder, mfolder);
    }
    /* check whether mfolder output would destroy a mail folder or mbox */
687 688 689 690 691 692 693 694 695 696 697 698 699 700 701
    switch (output_folder_type) {
      case FT_RAW:
      case FT_EXCERPT:
        break;
      default:
        if ((member_of(complete_mfolder,folder_base, maildir_folders, FT_MAILDIR, omit_globs)||
             member_of (complete_mfolder, folder_base, mh_folders, FT_MH, omit_globs) ||
             member_of (complete_mfolder, folder_base, mboxen, FT_MBOX, omit_globs))) {
          fprintf (stderr,
              "You asked search results to go to the folder '%s'.\n"
              "That folder appears to be one of the indexed mail folders!\n"
              "For your own good, I refuse to output search results to an indexed mail folder.\n",
              mfolder);
          unlock_and_exit(3);
        }
702 703
    }

704 705 706 707 708 709
    ftype = classify_file(database_path);
    if (ftype != M_FILE) {
      fprintf(stderr, "No database file '%s' is present.\nYou need to do an indexing run first.\n",
          database_path);
      unlock_and_exit(3);
    }
710
    result = search_top(do_threads, do_augment, database_path, complete_mfolder, argv, output_folder_type, verbose);
Richard P. Curnow's avatar
Richard P. Curnow committed
711

Richard Curnow's avatar
Richard Curnow committed
712
  } else {
713
    enum filetype ftype;
714

715 716
    if (!maildir_folders && !mh_folders && !mboxen) {
      fprintf(stderr, "No [mh_]folders/mboxen/MAIRIX_[MH_]FOLDERS set\n");
717
      unlock_and_exit(2);
Richard Curnow's avatar
Richard Curnow committed
718
    }
719

Richard Curnow's avatar
Richard Curnow committed
720
    if (verbose) printf("Finding all currently existing messages...\n");
721
    msgs = new_msgpath_array();
722
    if (maildir_folders) {
723
      build_message_list(folder_base, maildir_folders, FT_MAILDIR, msgs, omit_globs);
724 725
    }
    if (mh_folders) {
726
      build_message_list(folder_base, mh_folders, FT_MH, msgs, omit_globs);
727 728
    }

729
    /* The next call sorts the msgs array as part of looking for duplicates. */
Richard Curnow's avatar
Richard Curnow committed
730 731
    if (check_message_list_for_duplicates(msgs)) {
      fprintf(stderr, "Message list contains duplicates - check your 'folders' setting\n");
732
      unlock_and_exit(2);
Richard Curnow's avatar
Richard Curnow committed
733 734 735
    }

    /* Try to open existing database */
736 737
    ftype = classify_file(database_path);
    if (ftype == M_FILE) {
Richard Curnow's avatar
Richard Curnow committed
738
      if (verbose) printf("Reading existing database...\n");
739
      db = new_database_from_file(database_path, do_integrity_checks);
740
      if (verbose) printf("Loaded %d existing messages\n", db->n_msgs);
741
    } else if (ftype == M_NONE) {
Richard Curnow's avatar
Richard Curnow committed
742
      if (verbose) printf("Starting new database\n");
Richard P. Curnow's avatar
Richard P. Curnow committed
743
      db = new_database( forced_hash_key );
744 745 746
    } else {
      fprintf(stderr, "database path %s is not a file; you can't put the database there\n", database_path);
      unlock_and_exit(2);
Richard Curnow's avatar
Richard Curnow committed
747
    }
748

749
    build_mbox_lists(db, folder_base, mboxen, omit_globs);
Richard P. Curnow's avatar
Richard P. Curnow committed
750

751
    any_updates = update_database(db, msgs->paths, msgs->n, do_fast_index);
Richard Curnow's avatar
Richard Curnow committed
752
    if (do_purge) {
753
      any_purges = cull_dead_messages(db, do_integrity_checks);
Richard Curnow's avatar
Richard Curnow committed
754
    }
755
    if (any_updates || any_purges) {
Richard Curnow's avatar
Richard Curnow committed
756
      /* For now write it every time.  This is obviously the most reliable method. */
757
      write_database(db, database_path, do_integrity_checks);
Richard Curnow's avatar
Richard Curnow committed
758 759 760 761 762 763 764 765
    }

#if 0
    get_db_stats(db);
#endif

    free_database(db);
    free_msgpath_array(msgs);
766

767
    result = 0;
Richard Curnow's avatar
Richard Curnow committed
768
  }
769

Richard P. Curnow's avatar
Richard P. Curnow committed
770
  unlock_database();
771 772

  return result;
Richard Curnow's avatar
Richard Curnow committed
773
}
Richard Curnow's avatar
Richard Curnow committed
774
/*}}}*/