mairix.c 25.4 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>
37
#include "imapinterface.h"
Richard Curnow's avatar
Richard Curnow committed
38

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

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

static char *folder_base = NULL;
47
static char *maildir_folders = NULL;
48
static char *mh_folders = NULL;
49
static char *mboxen = NULL;
50 51 52 53 54
static char *imap_folders = NULL;
static char *imap_pipe = NULL;
static char *imap_server = NULL;
static char *imap_username = NULL;
static char *imap_password = NULL;
55
static char *mfolder = NULL;
56
static char *omit = NULL;
Richard Curnow's avatar
Richard Curnow committed
57
static char *database_path = NULL;
58
static enum folder_type output_folder_type = FT_MAILDIR;
59
static int skip_integrity_checks = 0;
60
static int follow_mbox_symlinks = 0;
Richard Curnow's avatar
Richard Curnow committed
61

62 63 64 65 66
enum filetype {
  M_NONE, M_FILE, M_DIR, M_OTHER
};

static enum filetype classify_file(char *name)/*{{{*/
Richard Curnow's avatar
Richard Curnow committed
67 68 69
{
  struct stat sb;
  if (stat(name, &sb) < 0) {
70 71 72 73 74 75 76 77
    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
78 79 80
  }
}
/*}}}*/
81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99
/*{{{ 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:
100
      glob_and_expand_paths(folder_base, raw_paths, n_raw_paths, &paths, &n_paths, &maildir_traverse_methods, omit_globs);
101 102
      break;
    case FT_MH:
103
      glob_and_expand_paths(folder_base, raw_paths, n_raw_paths, &paths, &n_paths, &mh_traverse_methods, omit_globs);
104 105
      break;
    case FT_MBOX:
106
      glob_and_expand_paths(folder_base, raw_paths, n_raw_paths, &paths, &n_paths, &mbox_traverse_methods, omit_globs);
107
      break;
108
    case FT_RAW: /* cannot happen but to keep compiler happy */
109
    case FT_EXCERPT:
110
    case FT_IMAP:
Richard P. Curnow's avatar
Richard P. Curnow committed
111
      break;
112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129
  }
  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
130
  return 0;
131 132
}
/*}}}*/
Richard Curnow's avatar
Richard Curnow committed
133 134 135
static char *copy_value(char *text)/*{{{*/
{
  char *p;
136
  char *result;
Richard Curnow's avatar
Richard Curnow committed
137 138 139
  for (p = text; *p && (*p != '='); p++) ;
  if (!*p) return NULL;
  p++;
140 141
  result = expand_string(p);
  return result;
Richard Curnow's avatar
Richard Curnow committed
142 143
}
/*}}}*/
Richard Curnow's avatar
Richard Curnow committed
144 145 146
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
147

Richard Curnow's avatar
Richard Curnow committed
148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164
  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);
  }
}
/*}}}*/
165 166 167 168 169 170 171 172 173 174
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;
175 176
  } else if (!strncasecmp(temp, "excerpt", 3)) {
    output_folder_type = FT_EXCERPT;
177 178
  } else if (!strncasecmp(temp, "mbox", 4)) {
    output_folder_type = FT_MBOX;
179 180
  } else if (!strncasecmp(temp, "imap", 4)) {
    output_folder_type = FT_IMAP;
181 182
  }
  else {
183
    fprintf(stderr, "Unrecognized mformat <%s>\n", temp);
184 185 186 187
  }
  free(temp);
}
/*}}}*/
Richard Curnow's avatar
Richard Curnow committed
188 189 190
static void parse_rc_file(char *name)/*{{{*/
{
  FILE *in;
Richard Curnow's avatar
Richard Curnow committed
191
  char line[4096], *p;
Richard Curnow's avatar
Richard Curnow committed
192 193 194 195 196 197 198 199
  int len, lineno;
  int all_blank;
  int used_default_name = 0;

  if (!name) {
    /* open default file */
    struct passwd *pw;
    char *home;
200 201 202 203
    home = getenv("HOME");
    if (!home) {
      pw = getpwuid(getuid());
      if (!pw) {
204
        fprintf(stderr, "Cannot determine home directory\n");
205 206 207
        exit(2);
      }
      home = pw->pw_dir;
Richard Curnow's avatar
Richard Curnow committed
208 209 210 211 212 213
    }
    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
214

Richard Curnow's avatar
Richard Curnow committed
215 216 217
  in = fopen(name, "r");
  if (!in) {
    fprintf(stderr, "Cannot open %s, exiting\n", name);
218
    exit(2);
Richard Curnow's avatar
Richard Curnow committed
219 220 221 222 223 224 225 226
  }

  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);
227
      exit(2);
Richard Curnow's avatar
Richard Curnow committed
228 229 230 231 232
    }

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

Richard Curnow's avatar
Richard Curnow committed
234 235 236 237 238 239 240
    /* 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++) {
241
      if (!isspace(*(unsigned char *)p)) {
Richard Curnow's avatar
Richard Curnow committed
242 243 244 245
        all_blank = 0;
        break;
      }
    }
Richard P. Curnow's avatar
Richard P. Curnow committed
246

Richard Curnow's avatar
Richard Curnow committed
247
    if (all_blank) continue;
Richard P. Curnow's avatar
Richard P. Curnow committed
248

Richard Curnow's avatar
Richard Curnow committed
249 250
    /* Now a real line to parse */
    if (!strncasecmp(p, "base", 4)) folder_base = copy_value(p);
251 252 253 254 255 256 257 258
    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));
259
    }
260 261
    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));
262 263 264 265 266
    else if (!strncasecmp(p, "imap=", 5)) add_folders(&imap_folders, copy_value(p));
    else if (!strncasecmp(p, "imap_pipe=", 10)) imap_pipe = copy_value(p);
    else if (!strncasecmp(p, "imap_server=", 12)) imap_server = copy_value(p);
    else if (!strncasecmp(p, "imap_username=", 14)) imap_username = copy_value(p);
    else if (!strncasecmp(p, "imap_password=", 14)) imap_password = copy_value(p);
267
    else if (!strncasecmp(p, "follow_mbox_symlinks", 20)) follow_mbox_symlinks = 1;
268
    else if (!strncasecmp(p, "omit=", 5)) add_folders(&omit, copy_value(p));
Richard P. Curnow's avatar
Richard P. Curnow committed
269

270
    else if (!strncasecmp(p, "mformat=", 8)) {
271 272
      parse_output_folder(p);
    }
273
    else if (!strncasecmp(p, "mfolder=", 8)) mfolder = copy_value(p);
274
    else if (!strncasecmp(p, "database=", 9)) database_path = copy_value(p);
275
    else if (!strncasecmp(p, "nochecks", 8)) skip_integrity_checks = 1;
Richard Curnow's avatar
Richard Curnow committed
276
    else {
277 278 279
      if (verbose) {
        fprintf(stderr, "Unrecognized option at line %d in %s\n", lineno, name);
      }
Richard Curnow's avatar
Richard Curnow committed
280 281
    }
  }
282

Richard Curnow's avatar
Richard Curnow committed
283 284 285 286 287
  fclose(in);

  if (used_default_name) free(name);
}
/*}}}*/
288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304

static int message_compare(const void *a, const void *b)/*{{{*/
{
  /* FIXME : Is this a sensible way to do this with mbox messages in the picture? */
  struct msgpath *aa = (struct msgpath *) a;
  struct msgpath *bb = (struct msgpath *) b;
  if (aa->type < bb->type) return -1;
  if (aa->type > bb->type) return 1;
  return strcmp(aa->src.mpf.path, bb->src.mpf.path);
}
/*}}}*/
static void sort_message_list(struct msgpath_array *arr)/*{{{*/
{
  qsort(arr->paths, arr->n, sizeof(struct msgpath), message_compare);
}
/*}}}*/

Richard Curnow's avatar
Richard Curnow committed
305 306 307 308 309 310 311 312 313
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)/*{{{*/
{
314
  /* Caveat : only examines the file-per-message case */
315 316
  char **sorted_paths, **sorted_imap;
  int i, n, nn, imap_nn;
Richard Curnow's avatar
Richard Curnow committed
317 318 319 320
  int result;

  n = msgs->n;
  sorted_paths = new_array(char *, n);
321 322
  sorted_imap = new_array(char *, n);
  for (i=0, nn=0, imap_nn=0; i<n; i++) {
323
    switch (msgs->paths[i].type) {
324 325 326 327 328 329 330 331
      case MTY_MBOX:
        break;
      case MTY_DEAD:
        assert(0);
        break;
      case MTY_FILE:
        sorted_paths[nn++] = msgs->paths[i].src.mpf.path;
        break;
332 333 334
      case MTY_IMAP:
        sorted_imap[imap_nn++] = msgs->paths[i].src.mpf.path;
        break;
335
    }
Richard Curnow's avatar
Richard Curnow committed
336
  }
337
  qsort(sorted_paths, nn, sizeof(char *), compare_strings);
338
  qsort(sorted_imap, imap_nn, sizeof(char *), compare_strings);
339

Richard Curnow's avatar
Richard Curnow committed
340
  result = 0;
341
  for (i=1; i<nn; i++) {
Richard Curnow's avatar
Richard Curnow committed
342 343 344 345 346
    if (!strcmp(sorted_paths[i-1], sorted_paths[i])) {
      result = 1;
      break;
    }
  }
347 348 349 350 351 352
  for (i=1; i<imap_nn; i++) {
    if (!strcmp(sorted_imap[i-1], sorted_imap[i])) {
      result = 1;
      break;
    }
  }
Richard P. Curnow's avatar
Richard P. Curnow committed
353

Richard Curnow's avatar
Richard Curnow committed
354
  free(sorted_paths);
355
  free(sorted_imap);
Richard Curnow's avatar
Richard Curnow committed
356 357 358 359
  return result;
}
/*}}}*/

360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380
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
381
  write(2, buf2, q-buf2);
382 383 384
  return;
}
/*}}}*/
385
void out_of_mem(char *file, int line, size_t size)/*{{{*/
386 387 388 389 390 391
{
  /* 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
392

393 394 395 396 397 398
  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;
399
  write(2, msg1, sizeof(msg1)-1);
400 401 402 403 404
  write(2, file, filelen);
  write(2, ":", 1);
  emit_int(line);
  write(2, ", ", 2);
  emit_int(size);
405
  write(2, msg2, sizeof(msg2)-1);
Richard P. Curnow's avatar
Richard P. Curnow committed
406
  exit(2);
407 408
}
/*}}}*/
409 410 411 412 413 414 415 416 417 418 419 420 421 422
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
423 424 425
static void print_copyright(void)/*{{{*/
{
  fprintf(stderr,
426
          "mairix %s, Copyright (C) 2002-2010 Richard P. Curnow\n"
Richard Curnow's avatar
Richard Curnow committed
427 428 429
          "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",
430
          PROGRAM_VERSION);
Richard Curnow's avatar
Richard Curnow committed
431 432
}
/*}}}*/
433 434 435 436 437 438 439
static void print_version(void)/*{{{*/
{
  fprintf(stdout,
          "mairix %s\n",
          PROGRAM_VERSION);
}
/*}}}*/
440 441 442 443 444
static void handlesig(int signo)/*{{{*/
{
  unlock_and_exit(7);
}
/*}}}*/
445
static void usage(void)/*{{{*/
Richard Curnow's avatar
Richard Curnow committed
446 447
{
  print_copyright();
Richard P. Curnow's avatar
Richard P. Curnow committed
448

Richard Curnow's avatar
Richard Curnow committed
449
  printf("mairix [-h]                                    : Show help\n"
450
         "mairix [-f <rcfile>] [-v] [-p] [-F]            : Build index\n"
Richard Curnow's avatar
Richard Curnow committed
451
         "mairix [-f <rcfile>] [-a] [-t] expr1 ... exprN : Run search\n"
452 453 454
         "mairix [-f <rcfile>] -d                        : Dump database to stdout\n"
         "-h           : show this help\n"
         "-f <rcfile>  : use alternative rc file (default ~/.mairixrc)\n"
455
         "-V           : show version\n"
456 457
         "-v           : be verbose\n"
         "-p           : purge messages that no longer exist\n"
458
         "-F           : fast scan for maildir and MH folders (no mtime or size checks)\n"
459
         "-a           : add new matches to match folder (default : clear it first)\n"
460
         "-x           : display excerpt of message headers (default : use match folder)\n" 
461 462 463
         "-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"
464
         "-H           : force hard links rather than symbolic ones\n"
465
         "expr_i       : search expression (all expr's AND'ed together):\n"
466
         "    word          : match word in message body and major headers\n"
Richard Curnow's avatar
Richard Curnow committed
467 468 469 470 471 472
         "    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"
473
         "    m:word        : match word in Message-ID: header\n"
474
         "    n:word        : match name of attachment within message\n"
475
         "    F:flags       : match on message flags (s=seen,r=replied,f=flagged,-=negate)\n"
476 477 478
         "    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
479
         "    bs:word       : match word in Subject: header or body (or any other group of prefixes)\n"
480 481
         "    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
482 483
         "    s:~word       : match messages not containing word in Subject:\n"
         "    s:substring=  : match substring in any word in Subject:\n"
484
         "    s:^substring= : match left-anchored substring in any word in Subject:\n"
Richard Curnow's avatar
Richard Curnow committed
485 486 487 488 489
         "    s:substring=2 : match substring with <=2 errors in any word in Subject:\n"
         "\n"
         "    (See documentation for more examples)\n"
         );
}
490
    /*}}}*/
Richard Curnow's avatar
Richard Curnow committed
491
/* Notes on folder management: {{{
Richard P. Curnow's avatar
Richard P. Curnow committed
492

493
   Assumption is that the user wants to keep the 'mfolder' directories under a
Richard Curnow's avatar
Richard Curnow committed
494 495 496
   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
497
   ancestor, because it'll pick up its own mfolders.  So, use environment
Richard Curnow's avatar
Richard Curnow committed
498
   variables to tailor the folders.
Richard P. Curnow's avatar
Richard P. Curnow committed
499

500
   MAIRIX_FOLDER_BASE is the common parent directory of the folders (aka
Richard Curnow's avatar
Richard Curnow committed
501 502
   mutt's 'folder' variable)

503 504 505 506 507
   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
508

509
   For example, if
Richard Curnow's avatar
Richard Curnow committed
510 511
   MAIRIX_FOLDER_BASE = "/home/foobar/mail"
   MAIRIX_FOLDERS = "inbox:lists...:action:archive..."
512
   MAIRIX_MFOLDER = "mf"
Richard Curnow's avatar
Richard Curnow committed
513

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

Richard Curnow's avatar
Richard Curnow committed
517
int main (int argc, char **argv)/*{{{*/
Richard Curnow's avatar
Richard Curnow committed
518 519
{
  struct msgpath_array *msgs;
520
  struct database *db = NULL;
Richard Curnow's avatar
Richard Curnow committed
521 522

  char *arg_rc_file_path = NULL;
523
  char *arg_mfolder = NULL;
524
  char *e;
Richard Curnow's avatar
Richard Curnow committed
525 526 527 528 529 530
  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
531
  int do_help = 0;
532
  int do_raw_output = 0;
533
  int do_excerpt_output = 0;
534
  int do_dump = 0;
535
  int do_integrity_checks = 1;
536
  int do_forced_unlock = 0;
537
  int do_fast_index = 0;
538
  int do_mbox_symlinks = 0;
539
  struct imap_ll *imapc = NULL;
Richard Curnow's avatar
Richard Curnow committed
540

Richard P. Curnow's avatar
Richard P. Curnow committed
541 542
  unsigned int forced_hash_key = CREATE_RANDOM_DATABASE_HASH;

543 544
  struct globber_array *omit_globs;

545 546
  int result;

547 548
  setlocale(LC_CTYPE, "");

Richard Curnow's avatar
Richard Curnow committed
549 550 551
  while (++argv, --argc) {
    if (!*argv) {
      break;
Richard Curnow's avatar
Richard Curnow committed
552
    } else if (!strcmp(*argv, "-f") || !strcmp(*argv, "--rcfile")) {
Richard Curnow's avatar
Richard Curnow committed
553
      ++argv, --argc;
554 555 556 557
      if (!argc) {
        fprintf(stderr, "No filename given after -f argument\n");
        exit(1);
      }
Richard Curnow's avatar
Richard Curnow committed
558
      arg_rc_file_path = *argv;
Richard Curnow's avatar
Richard Curnow committed
559
    } else if (!strcmp(*argv, "-t") || !strcmp(*argv, "--threads")) {
Richard Curnow's avatar
Richard Curnow committed
560 561
      do_search = 1;
      do_threads = 1;
Richard Curnow's avatar
Richard Curnow committed
562
    } else if (!strcmp(*argv, "-a") || !strcmp(*argv, "--augment")) {
Richard Curnow's avatar
Richard Curnow committed
563 564
      do_search = 1;
      do_augment = 1;
565
    } else if (!strcmp(*argv, "-o") || !strcmp(*argv, "--mfolder")) {
Richard Curnow's avatar
Richard Curnow committed
566
      ++argv, --argc;
567 568 569 570
      if (!argc) {
        fprintf(stderr, "No folder name given after -o argument\n");
        exit(1);
      }
571
      arg_mfolder = *argv;
Richard Curnow's avatar
Richard Curnow committed
572
    } else if (!strcmp(*argv, "-p") || !strcmp(*argv, "--purge")) {
Richard Curnow's avatar
Richard Curnow committed
573
      do_purge = 1;
574 575
    } else if (!strcmp(*argv, "-d") || !strcmp(*argv, "--dump")) {
      do_dump = 1;
576 577
    } else if (!strcmp(*argv, "-r") || !strcmp(*argv, "--raw-output")) {
      do_raw_output = 1;
578 579
    } else if (!strcmp(*argv, "-x") || !strcmp(*argv, "--excerpt-output")) {
      do_excerpt_output = 1;
580 581
    } else if (!strcmp(*argv, "-H") || !strcmp(*argv, "--force-hardlinks")) {
      do_hardlinks = 1;
582 583
    } else if (!strcmp(*argv, "-Q") || !strcmp(*argv, "--no-integrity-checks")) {
      do_integrity_checks = 0;
584 585
    } else if (!strcmp(*argv, "--unlock")) {
      do_forced_unlock = 1;
586 587 588
    } else if (!strcmp(*argv, "-F") ||
               !strcmp(*argv, "--fast-index")) {
      do_fast_index = 1;
Richard P. Curnow's avatar
Richard P. Curnow committed
589 590 591 592 593 594 595 596 597 598 599
    } 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
600
    } else if (!strcmp(*argv, "-v") || !strcmp(*argv, "--verbose")) {
Richard Curnow's avatar
Richard Curnow committed
601
      verbose = 1;
602 603 604
    } else if (!strcmp(*argv, "-V") || !strcmp(*argv, "--version")) {
      print_version();
      exit(0);
Richard Curnow's avatar
Richard Curnow committed
605 606 607
    } else if (!strcmp(*argv, "-h") ||
               !strcmp(*argv, "--help")) {
      do_help = 1;
Richard Curnow's avatar
Richard Curnow committed
608 609 610 611 612 613 614 615 616 617 618
    } 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
619 620 621 622 623 624 625 626
  if (do_help) {
    usage();
    exit(0);
  }

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

Richard Curnow's avatar
Richard Curnow committed
628 629 630 631
  if (*argv) {
    /* There are still args to process */
    do_search = 1;
  }
Richard P. Curnow's avatar
Richard P. Curnow committed
632

Richard Curnow's avatar
Richard Curnow committed
633 634 635 636 637 638
  parse_rc_file(arg_rc_file_path);

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

639 640
  if (getenv("MAIRIX_MAILDIR_FOLDERS")) {
    maildir_folders = getenv("MAIRIX_MAIDIR_FOLDERS");
Richard Curnow's avatar
Richard Curnow committed
641 642
  }

643 644 645 646
  if (getenv("MAIRIX_MH_FOLDERS")) {
    mh_folders = getenv("MAIRIX_MH_FOLDERS");
  }

647 648 649 650
  if ((e = getenv("MAIRIX_MBOXEN"))) {
    mboxen = e;
  }

651 652
  if (getenv("MAIRIX_MFOLDER")) {
    mfolder = getenv("MAIRIX_MFOLDER");
Richard Curnow's avatar
Richard Curnow committed
653 654 655 656 657 658
  }

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

659 660
  if (arg_mfolder) {
    mfolder = arg_mfolder;
Richard Curnow's avatar
Richard Curnow committed
661
  }
Richard P. Curnow's avatar
Richard P. Curnow committed
662

663 664 665
  if (skip_integrity_checks) {
    do_integrity_checks = 0;
  }
Richard P. Curnow's avatar
Richard P. Curnow committed
666

667 668 669 670
  if (follow_mbox_symlinks) {
    do_mbox_symlinks = 1;
  }

Richard Curnow's avatar
Richard Curnow committed
671 672
  if (!folder_base) {
    fprintf(stderr, "No folder_base/MAIRIX_FOLDER_BASE set\n");
673
    exit(2);
Richard Curnow's avatar
Richard Curnow committed
674
  }
Richard P. Curnow's avatar
Richard P. Curnow committed
675

Richard Curnow's avatar
Richard Curnow committed
676 677
  if (!database_path) {
    fprintf(stderr, "No database/MAIRIX_DATABASE set\n");
678
    exit(2);
Richard Curnow's avatar
Richard Curnow committed
679
  }
680 681 682

  if (do_raw_output) {
    output_folder_type = FT_RAW;
683 684
  } else if (do_excerpt_output) {
    output_folder_type = FT_EXCERPT;
685 686
  }

687 688 689 690 691
  if (omit) {
    omit_globs = colon_sep_string_to_globber_array(omit);
  } else {
    omit_globs = NULL;
  }
692 693 694 695 696 697

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

698 699 700 701
  signal(SIGHUP, handlesig);
  signal(SIGINT, handlesig);
  signal(SIGQUIT, handlesig);

702
  lock_database(database_path, do_forced_unlock);
Richard P. Curnow's avatar
Richard P. Curnow committed
703

704 705
  if (do_dump) {
    dump_database(database_path);
706
    result = 0;
707 708

  } else if (do_search) {
709 710
    int len;
    char *complete_mfolder;
711
    enum filetype ftype;
712

713
    if (!mfolder) {
714 715 716 717 718 719 720
      switch (output_folder_type) {
        case FT_RAW:
        case FT_EXCERPT:
          break;
        default:
          fprintf(stderr, "No mfolder/MAIRIX_MFOLDER set\n");
          unlock_and_exit(2);
721
      }
722
      mfolder = new_string("");
Richard Curnow's avatar
Richard Curnow committed
723 724
    }

725
    if (output_folder_type == FT_IMAP) {
726 727
      complete_mfolder = new_string(mfolder);
    } else {
728 729 730 731 732 733 734 735 736 737 738 739
      /* complete_mfolder is needed by search_top() and member_of() so
         compute it once here rather than in search_top() as well */
      if ((mfolder[0] == '/') ||
          ((mfolder[0] == '.') && (mfolder[1] == '/'))) {
        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);
      }
740 741
    }
    /* check whether mfolder output would destroy a mail folder or mbox */
742 743 744 745
    switch (output_folder_type) {
      case FT_RAW:
      case FT_EXCERPT:
        break;
746 747 748
      case FT_IMAP:
        /* the same check as below could be implemented in the future */
        break;
749 750 751 752 753 754 755 756 757 758 759
      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);
        }
760 761
    }

762 763 764 765 766 767
    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);
    }
768
    result = search_top(do_threads, do_augment, database_path, complete_mfolder, argv, output_folder_type, verbose, imap_pipe, imap_server, imap_username, imap_password);
Richard P. Curnow's avatar
Richard P. Curnow committed
769

Richard Curnow's avatar
Richard Curnow committed
770
  } else {
771
    enum filetype ftype;
772

773 774 775 776 777 778 779 780 781 782 783 784
    if (imap_pipe && imap_server) {
      fprintf(stderr, "specify one of imap_pipe or imap_server, not both\n");
      unlock_and_exit(2);
    }

    if (imap_folders && (!(imap_pipe || imap_server))) {
      fprintf(stderr, "If imap is given, imap_pipe OR imap_server is required\n");
      imap_folders = NULL;
    }

    if (!maildir_folders && !mh_folders && !mboxen && !imap_folders) {
      fprintf(stderr, "No [mh_]folders/mboxen/imap/MAIRIX_[MH_]FOLDERS set\n");
785
      unlock_and_exit(2);
Richard Curnow's avatar
Richard Curnow committed
786
    }
787

Richard Curnow's avatar
Richard Curnow committed
788
    if (verbose) printf("Finding all currently existing messages...\n");
789
    msgs = new_msgpath_array();
790 791 792 793 794
    if (imap_folders) {
      imapc = imap_start(imap_pipe, imap_server, imap_username, imap_password);
      if (!imapc) unlock_and_exit(2);
      build_imap_message_list(imap_folders, msgs, omit_globs, imapc);
    }
795
    if (maildir_folders) {
796
      build_message_list(folder_base, maildir_folders, FT_MAILDIR, msgs, omit_globs);
797 798
    }
    if (mh_folders) {
799
      build_message_list(folder_base, mh_folders, FT_MH, msgs, omit_globs);
800
    }
801
    sort_message_list(msgs);
802

803
    /* The next call sorts the msgs array as part of looking for duplicates. */
Richard Curnow's avatar
Richard Curnow committed
804 805
    if (check_message_list_for_duplicates(msgs)) {
      fprintf(stderr, "Message list contains duplicates - check your 'folders' setting\n");
806
      unlock_and_exit(2);
Richard Curnow's avatar
Richard Curnow committed
807 808 809
    }

    /* Try to open existing database */
810 811
    ftype = classify_file(database_path);
    if (ftype == M_FILE) {
Richard Curnow's avatar
Richard Curnow committed
812
      if (verbose) printf("Reading existing database...\n");
813
      db = new_database_from_file(database_path, do_integrity_checks);
814
      if (verbose) printf("Loaded %d existing messages\n", db->n_msgs);
815
    } else if (ftype == M_NONE) {
Richard Curnow's avatar
Richard Curnow committed
816
      if (verbose) printf("Starting new database\n");
Richard P. Curnow's avatar
Richard P. Curnow committed
817
      db = new_database( forced_hash_key );
818 819 820
    } 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
821
    }
822

823
    build_mbox_lists(db, folder_base, mboxen, omit_globs, do_mbox_symlinks);
Richard P. Curnow's avatar
Richard P. Curnow committed
824

825
    any_updates = update_database(db, msgs->paths, msgs->n, do_fast_index, imapc);
Richard Curnow's avatar
Richard Curnow committed
826
    if (do_purge) {
827
      any_purges = cull_dead_messages(db, do_integrity_checks);
Richard Curnow's avatar
Richard Curnow committed
828
    }
829
    if (any_updates || any_purges) {
Richard Curnow's avatar
Richard Curnow committed
830
      /* For now write it every time.  This is obviously the most reliable method. */
831
      write_database(db, database_path, do_integrity_checks);
Richard Curnow's avatar
Richard Curnow committed
832 833 834 835 836 837 838 839
    }

#if 0
    get_db_stats(db);
#endif

    free_database(db);
    free_msgpath_array(msgs);
840

841
    result = 0;
Richard Curnow's avatar
Richard Curnow committed
842
  }
843

Richard P. Curnow's avatar
Richard P. Curnow committed
844
  unlock_database();
845 846

  return result;
Richard Curnow's avatar
Richard Curnow committed
847
}
Richard Curnow's avatar
Richard Curnow committed
848
/*}}}*/