]> git.cworth.org Git - notmuch-old/blob - notmuch.c
More care in final status reporting.
[notmuch-old] / notmuch.c
1 /* notmuch - Not much of an email program, (just index and search)
2  *
3  * Copyright © 2009 Carl Worth
4  *
5  * This program is free software: you can redistribute it and/or modify
6  * it under the terms of the GNU General Public License as published by
7  * the Free Software Foundation, either version 3 of the License, or
8  * (at your option) any later version.
9  *
10  * This program is distributed in the hope that it will be useful,
11  * but WITHOUT ANY WARRANTY; without even the implied warranty of
12  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
13  * GNU General Public License for more details.
14  *
15  * You should have received a copy of the GNU General Public License
16  * along with this program.  If not, see http://www.gnu.org/licenses/ .
17  *
18  * Author: Carl Worth <cworth@cworth.org>
19  */
20
21 #ifndef _GNU_SOURCE
22 #define _GNU_SOURCE /* for getline */
23 #endif
24 #include <stdio.h>
25
26 #include "notmuch.h"
27
28 /* This is separate from notmuch-private.h because we're trying to
29  * keep notmuch.c from looking into any internals, (which helps us
30  * develop notmuch.h into a plausible library interface).
31  */
32 #include "xutil.h"
33
34 #include <stddef.h>
35 #include <string.h>
36 #include <sys/stat.h>
37 #include <sys/time.h>
38 #include <unistd.h>
39 #include <dirent.h>
40 #include <errno.h>
41
42 #include <talloc.h>
43
44 #include <glib.h> /* g_strdup_printf */
45
46 #define ARRAY_SIZE(arr) (sizeof (arr) / sizeof (arr[0]))
47
48 typedef int (*command_function_t) (int argc, char *argv[]);
49
50 typedef struct command {
51     const char *name;
52     command_function_t function;
53     const char *usage;
54 } command_t;
55
56 typedef struct {
57     int total_files;
58     int processed_files;
59     int added_messages;
60     struct timeval tv_start;
61 } add_files_state_t;
62
63 static void
64 chomp_newline (char *str)
65 {
66     if (str && str[strlen(str)-1] == '\n')
67         str[strlen(str)-1] = '\0';
68 }
69
70 /* Compute the number of seconds elapsed from start to end. */
71 double
72 tv_elapsed (struct timeval start, struct timeval end)
73 {
74     return ((end.tv_sec - start.tv_sec) +
75             (end.tv_usec - start.tv_usec) / 1e6);
76 }
77
78 void
79 print_formatted_seconds (double seconds)
80 {
81     int hours;
82     int minutes;
83
84     if (seconds < 1) {
85         printf ("almost no time");
86         return;
87     }
88
89     if (seconds > 3600) {
90         hours = (int) seconds / 3600;
91         printf ("%dh ", hours);
92         seconds -= hours * 3600;
93     }
94
95     if (seconds > 60) {
96         minutes = (int) seconds / 60;
97         printf ("%dm ", minutes);
98         seconds -= minutes * 60;
99     }
100
101     printf ("%ds", (int) seconds);
102 }
103
104 void
105 add_files_print_progress (add_files_state_t *state)
106 {
107     struct timeval tv_now;
108     double elapsed_overall, rate_overall;
109
110     gettimeofday (&tv_now, NULL);
111
112     elapsed_overall = tv_elapsed (state->tv_start, tv_now);
113     rate_overall = (state->processed_files) / elapsed_overall;
114
115     printf ("Processed %d", state->processed_files);
116
117     if (state->total_files) {
118         printf (" of %d files (", state->total_files);
119         print_formatted_seconds ((state->total_files - state->processed_files) /
120                                  rate_overall);
121         printf (" remaining).      \r");
122     } else {
123         printf (" files (%d files/sec.)    \r", (int) rate_overall);
124     }
125
126     fflush (stdout);
127 }
128
129 /* Examine 'path' recursively as follows:
130  *
131  *   o Ask the filesystem for the mtime of 'path' (path_mtime)
132  *
133  *   o Ask the database for its timestamp of 'path' (path_dbtime)
134  *
135  *   o If 'path_mtime' > 'path_dbtime'
136  *
137  *       o For each regular file in 'path' with mtime newer than the
138  *         'path_dbtime' call add_message to add the file to the
139  *         database.
140  *
141  *       o For each sub-directory of path, recursively call into this
142  *         same function.
143  *
144  *   o Tell the database to update its time of 'path' to 'path_mtime'
145  */
146 notmuch_status_t
147 add_files (notmuch_database_t *notmuch, const char *path,
148            add_files_state_t *state)
149 {
150     DIR *dir = NULL;
151     struct dirent *e, *entry = NULL;
152     int entry_length;
153     int err;
154     char *next = NULL;
155     struct stat st;
156     time_t path_mtime, path_dbtime;
157     notmuch_status_t status, ret = NOTMUCH_STATUS_SUCCESS;
158
159     if (stat (path, &st)) {
160         fprintf (stderr, "Error reading directory %s: %s\n",
161                  path, strerror (errno));
162         return NOTMUCH_STATUS_FILE_ERROR;
163     }
164
165     if (! S_ISDIR (st.st_mode)) {
166         fprintf (stderr, "Error: %s is not a directory.\n", path);
167         return NOTMUCH_STATUS_FILE_ERROR;
168     }
169
170     path_mtime = st.st_mtime;
171
172     path_dbtime = notmuch_database_get_timestamp (notmuch, path);
173
174     dir = opendir (path);
175     if (dir == NULL) {
176         fprintf (stderr, "Error opening directory %s: %s\n",
177                  path, strerror (errno));
178         ret = NOTMUCH_STATUS_FILE_ERROR;
179         goto DONE;
180     }
181
182     entry_length = offsetof (struct dirent, d_name) +
183         pathconf (path, _PC_NAME_MAX) + 1;
184     entry = malloc (entry_length);
185
186     while (1) {
187         err = readdir_r (dir, entry, &e);
188         if (err) {
189             fprintf (stderr, "Error reading directory: %s\n",
190                      strerror (errno));
191             ret = NOTMUCH_STATUS_FILE_ERROR;
192             goto DONE;
193         }
194
195         if (e == NULL)
196             break;
197
198         /* If this directory hasn't been modified since the last
199          * add_files, then we only need to look further for
200          * sub-directories. */
201         if (path_mtime <= path_dbtime && entry->d_type != DT_DIR)
202             continue;
203
204         /* Ignore special directories to avoid infinite recursion.
205          * Also ignore the .notmuch directory.
206          */
207         /* XXX: Eventually we'll want more sophistication to let the
208          * user specify files to be ignored. */
209         if (strcmp (entry->d_name, ".") == 0 ||
210             strcmp (entry->d_name, "..") == 0 ||
211             strcmp (entry->d_name, ".notmuch") ==0)
212         {
213             continue;
214         }
215
216         next = g_strdup_printf ("%s/%s", path, entry->d_name);
217
218         if (stat (next, &st)) {
219             fprintf (stderr, "Error reading %s: %s\n",
220                      next, strerror (errno));
221             ret = NOTMUCH_STATUS_FILE_ERROR;
222             continue;
223         }
224
225         if (S_ISREG (st.st_mode)) {
226             /* If the file hasn't been modified since the last
227              * add_files, then we need not look at it. */
228             if (st.st_mtime > path_dbtime) {
229                 state->processed_files++;
230
231                 status = notmuch_database_add_message (notmuch, next);
232                 switch (status) {
233                     /* success */
234                     case NOTMUCH_STATUS_SUCCESS:
235                         state->added_messages++;
236                         break;
237                     /* Non-fatal issues (go on to next file) */
238                     case NOTMUCH_STATUS_DUPLICATE_MESSAGE_ID:
239                         /* Stay silent on this one. */
240                         break;
241                     case NOTMUCH_STATUS_FILE_NOT_EMAIL:
242                         fprintf (stderr, "Note: Ignoring non-mail file: %s\n",
243                                  next);
244                         break;
245                     /* Fatal issues. Don't process anymore. */
246                     case NOTMUCH_STATUS_XAPIAN_EXCEPTION:
247                         fprintf (stderr, "A Xapian error was encountered. Halting processing.\n");
248                         ret = status;
249                         goto DONE;
250                     default:
251                         fprintf (stderr, "Internal error: add_message returned unexpected value: %d\n",  status);
252                         ret = status;
253                         goto DONE;
254                 }
255                 if (state->processed_files % 1000 == 0)
256                     add_files_print_progress (state);
257             }
258         } else if (S_ISDIR (st.st_mode)) {
259             status = add_files (notmuch, next, state);
260             if (status && ret == NOTMUCH_STATUS_SUCCESS)
261                 ret = status;
262         }
263
264         free (next);
265         next = NULL;
266     }
267
268     notmuch_database_set_timestamp (notmuch, path, path_mtime);
269
270   DONE:
271     if (next)
272         free (next);
273     if (entry)
274         free (entry);
275     if (dir)
276         closedir (dir);
277
278     return ret;
279 }
280
281 /* Recursively count all regular files in path and all sub-direcotries
282  * of path.  The result is added to *count (which should be
283  * initialized to zero by the top-level caller before calling
284  * count_files). */
285 void
286 count_files (const char *path, int *count)
287 {
288     DIR *dir;
289     struct dirent *entry, *e;
290     int entry_length;
291     int err;
292     char *next;
293     struct stat st;
294
295     dir = opendir (path);
296
297     if (dir == NULL) {
298         fprintf (stderr, "Warning: failed to open directory %s: %s\n",
299                  path, strerror (errno));
300         return;
301     }
302
303     entry_length = offsetof (struct dirent, d_name) +
304         pathconf (path, _PC_NAME_MAX) + 1;
305     entry = malloc (entry_length);
306
307     while (1) {
308         err = readdir_r (dir, entry, &e);
309         if (err) {
310             fprintf (stderr, "Error reading directory: %s\n",
311                      strerror (errno));
312             free (entry);
313             return;
314         }
315
316         if (e == NULL)
317             break;
318
319         /* Ignore special directories to avoid infinite recursion.
320          * Also ignore the .notmuch directory.
321          */
322         /* XXX: Eventually we'll want more sophistication to let the
323          * user specify files to be ignored. */
324         if (strcmp (entry->d_name, ".") == 0 ||
325             strcmp (entry->d_name, "..") == 0 ||
326             strcmp (entry->d_name, ".notmuch") == 0)
327         {
328             continue;
329         }
330
331         next = g_strdup_printf ("%s/%s", path, entry->d_name);
332
333         stat (next, &st);
334
335         if (S_ISREG (st.st_mode)) {
336             *count = *count + 1;
337             if (*count % 1000 == 0) {
338                 printf ("Found %d files so far.\r", *count);
339                 fflush (stdout);
340             }
341         } else if (S_ISDIR (st.st_mode)) {
342             count_files (next, count);
343         }
344
345         free (next);
346     }
347
348     free (entry);
349
350     closedir (dir);
351 }
352
353 int
354 setup_command (int argc, char *argv[])
355 {
356     notmuch_database_t *notmuch = NULL;
357     char *default_path, *mail_directory = NULL;
358     size_t line_size;
359     int count;
360     add_files_state_t add_files_state;
361     double elapsed;
362     struct timeval tv_now;
363     notmuch_status_t ret = NOTMUCH_STATUS_SUCCESS;
364
365     printf ("Welcome to notmuch!\n\n");
366
367     printf ("The goal of notmuch is to help you manage and search your collection of\n"
368             "email, and to efficiently keep up with the flow of email as it comes in.\n\n");
369
370     printf ("Notmuch needs to know the top-level directory of your email archive,\n"
371             "(where you already have mail stored and where messages will be delivered\n"
372             "in the future). This directory can contain any number of sub-directories\n"
373             "and primarily just files with indvidual email messages (eg. maildir or mh\n"
374             "archives are perfect). If there are other, non-email files (such as\n"
375             "indexes maintained by other email programs) then notmuch will do its\n"
376             "best to detect those and ignore them.\n\n");
377
378     printf ("Mail storage that uses mbox format, (where one mbox file contains many\n"
379             "messages), will not work with notmuch. If that's how your mail is currently\n"
380             "stored, we recommend you first convert it to maildir format with a utility\n"
381             "such as mb2md. In that case, press Control-C now and run notmuch again\n"
382             "once the conversion is complete.\n\n");
383
384
385     default_path = notmuch_database_default_path ();
386     printf ("Top-level mail directory [%s]: ", default_path);
387     fflush (stdout);
388
389     getline (&mail_directory, &line_size, stdin);
390     chomp_newline (mail_directory);
391
392     printf ("\n");
393
394     if (mail_directory == NULL || strlen (mail_directory) == 0) {
395         if (mail_directory)
396             free (mail_directory);
397         mail_directory = default_path;
398     } else {
399         /* XXX: Instead of telling the user to use an environment
400          * variable here, we should really be writing out a configuration
401          * file and loading that on the next run. */
402         if (strcmp (mail_directory, default_path)) {
403             printf ("Note: Since you are not using the default path, you will want to set\n"
404                     "the NOTMUCH_BASE environment variable to %s so that\n"
405                     "future calls to notmuch commands will know where to find your mail.\n",
406                     mail_directory);
407             printf ("For example, if you are using bash for your shell, add:\n\n");
408             printf ("\texport NOTMUCH_BASE=%s\n\n", mail_directory);
409             printf ("to your ~/.bashrc file.\n\n");
410         }
411         free (default_path);
412     }
413
414     notmuch = notmuch_database_create (mail_directory);
415     if (notmuch == NULL) {
416         fprintf (stderr, "Failed to create new notmuch database at %s\n",
417                  mail_directory);
418         ret = NOTMUCH_STATUS_FILE_ERROR;
419         goto DONE;
420     }
421
422     printf ("OK. Let's take a look at the mail we can find in the directory\n");
423     printf ("%s ...\n", mail_directory);
424
425     count = 0;
426     count_files (mail_directory, &count);
427
428     printf ("Found %d total files. That's not much mail.\n\n", count);
429
430     printf ("Next, we'll inspect the messages and create a database of threads:\n");
431
432     add_files_state.total_files = count;
433     add_files_state.processed_files = 0;
434     add_files_state.added_messages = 0;
435     gettimeofday (&add_files_state.tv_start, NULL);
436
437     ret = add_files (notmuch, mail_directory, &add_files_state);
438
439     gettimeofday (&tv_now, NULL);
440     elapsed = tv_elapsed (add_files_state.tv_start,
441                           tv_now);
442     printf ("Processed %d total files in ", add_files_state.processed_files);
443     print_formatted_seconds (elapsed);
444     if (elapsed > 1) {
445         printf (" (%d files/sec.).                 \n",
446                 (int) (add_files_state.processed_files / elapsed));
447     } else {
448         printf (".                    \n");
449     }
450     if (add_files_state.added_messages) {
451         printf ("Added %d %s to the database.\n\n",
452                 add_files_state.added_messages,
453                 add_files_state.added_messages == 1 ?
454                 "message" : "unique messages");
455     }
456
457     printf ("When new mail is delivered to %s in the future,\n"
458             "run \"notmuch new\" to add it to the database.\n",
459             mail_directory);
460
461     if (ret) {
462         printf ("Note: At least one error was encountered: %s\n",
463                 notmuch_status_to_string (ret));
464     }
465
466   DONE:
467     if (mail_directory)
468         free (mail_directory);
469     if (notmuch)
470         notmuch_database_close (notmuch);
471
472     return ret;
473 }
474
475 int
476 new_command (int argc, char *argv[])
477 {
478     notmuch_database_t *notmuch;
479     const char *mail_directory;
480     add_files_state_t add_files_state;
481     double elapsed;
482     struct timeval tv_now;
483     int ret = 0;
484
485     notmuch = notmuch_database_open (NULL);
486     if (notmuch == NULL) {
487         ret = 1;
488         goto DONE;
489     }
490
491     mail_directory = notmuch_database_get_path (notmuch);
492
493     add_files_state.total_files = 0;
494     add_files_state.processed_files = 0;
495     add_files_state.added_messages = 0;
496     gettimeofday (&add_files_state.tv_start, NULL);
497
498     ret = add_files (notmuch, mail_directory, &add_files_state);
499
500     gettimeofday (&tv_now, NULL);
501     elapsed = tv_elapsed (add_files_state.tv_start,
502                           tv_now);
503     if (add_files_state.processed_files) {
504         printf ("Processed %d total files in ", add_files_state.processed_files);
505         print_formatted_seconds (elapsed);
506         if (elapsed > 1) {
507             printf (" (%d files/sec.).                 \n",
508                     (int) (add_files_state.processed_files / elapsed));
509         } else {
510             printf (".                    \n");
511         }
512     }
513     if (add_files_state.added_messages) {
514         printf ("Added %d new %s to the database (not much, really).\n",
515                 add_files_state.added_messages,
516                 add_files_state.added_messages == 1 ?
517                 "message" : "messages");
518     } else {
519         printf ("No new mail---and that's not much!\n");
520     }
521
522     if (ret) {
523         printf ("Note: At least one error was encountered: %s\n",
524                 notmuch_status_to_string (ret));
525     }
526
527   DONE:
528     if (notmuch)
529         notmuch_database_close (notmuch);
530
531     return ret;
532 }
533
534 int
535 search_command (int argc, char *argv[])
536 {
537     fprintf (stderr, "Error: search is not implemented yet.\n");
538     return 1;
539 }
540
541 int
542 show_command (int argc, char *argv[])
543 {
544     fprintf (stderr, "Error: show is not implemented yet.\n");
545     return 1;
546 }
547
548 int
549 dump_command (int argc, char *argv[])
550 {
551     FILE *output;
552     notmuch_database_t *notmuch;
553     notmuch_query_t *query;
554     notmuch_results_t *results;
555     notmuch_message_t *message;
556     notmuch_tags_t *tags;
557     int ret = 0;
558
559     if (argc) {
560         output = fopen (argv[0], "w");
561         if (output == NULL) {
562             fprintf (stderr, "Error opening %s for writing: %s\n",
563                      argv[0], strerror (errno));
564             ret = 1;
565             goto DONE;
566         }
567     } else {
568         output = stdout;
569     }
570
571     notmuch = notmuch_database_open (NULL);
572     if (notmuch == NULL) {
573         ret = 1;
574         goto DONE;
575     }
576
577     query = notmuch_query_create (notmuch, "");
578     if (query == NULL) {
579         fprintf (stderr, "Out of memory\n");
580         ret = 1;
581         goto DONE;
582     }
583
584     notmuch_query_set_sort (query, NOTMUCH_SORT_MESSAGE_ID);
585
586     for (results = notmuch_query_search (query);
587          notmuch_results_has_more (results);
588          notmuch_results_advance (results))
589     {
590         int first = 1;
591         message = notmuch_results_get (results);
592
593         fprintf (output,
594                  "%s (", notmuch_message_get_message_id (message));
595
596         for (tags = notmuch_message_get_tags (message);
597              notmuch_tags_has_more (tags);
598              notmuch_tags_advance (tags))
599         {
600             if (! first)
601                 fprintf (output, " ");
602
603             fprintf (output, "%s", notmuch_tags_get (tags));
604
605             first = 0;
606         }
607
608         fprintf (output, ")\n");
609
610         notmuch_message_destroy (message);
611     }
612
613     notmuch_query_destroy (query);
614
615   DONE:
616     if (notmuch)
617         notmuch_database_close (notmuch);
618     if (output != stdout)
619         fclose (output);
620
621     return ret;
622 }
623
624 int
625 restore_command (int argc, char *argv[])
626 {
627     FILE *input;
628     notmuch_database_t *notmuch;
629     char *line = NULL;
630     size_t line_size, line_len;
631     regex_t regex;
632     int rerr;
633     int ret = 0;
634
635     if (argc) {
636         input = fopen (argv[0], "r");
637         if (input == NULL) {
638             fprintf (stderr, "Error opening %s for reading: %s\n",
639                      argv[0], strerror (errno));
640             ret = 1;
641             goto DONE;
642         }
643     } else {
644         printf ("No filename given. Reading dump from stdin.\n");
645         input = stdin;
646     }
647
648     notmuch = notmuch_database_open (NULL);
649     if (notmuch == NULL) {
650         ret = 1;
651         goto DONE;
652     }
653
654     /* Dump output is one line per message. We match a sequence of
655      * non-space characters for the message-id, then one or more
656      * spaces, then a list of space-separated tags as a sequence of
657      * characters within literal '(' and ')'. */
658     xregcomp (&regex,
659               "^([^ ]+) \\(([^)]*)\\)$",
660               REG_EXTENDED);
661
662     while ((line_len = getline (&line, &line_size, input)) != -1) {
663         regmatch_t match[3];
664         char *message_id, *tags, *tag, *next;
665         notmuch_message_t *message;
666         notmuch_status_t status;
667
668         chomp_newline (line);
669
670         rerr = xregexec (&regex, line, 3, match, 0);
671         if (rerr == REG_NOMATCH)
672         {
673             fprintf (stderr, "Warning: Ignoring invalid input line: %s\n",
674                      line);
675             continue;
676         }
677
678         message_id = xstrndup (line + match[1].rm_so,
679                                match[1].rm_eo - match[1].rm_so);
680         tags = xstrndup (line + match[2].rm_so,
681                          match[2].rm_eo - match[2].rm_so);
682
683         if (strlen (tags)) {
684
685             message = notmuch_database_find_message (notmuch, message_id);
686             if (message == NULL) {
687                 fprintf (stderr, "Warning: Cannot apply tags to missing message: %s (",
688                          message_id);
689             }
690
691             next = tags;
692             while (next) {
693                 tag = strsep (&next, " ");
694                 if (*tag == '\0')
695                     continue;
696                 if (message) {
697                     status = notmuch_message_add_tag (message, tag);
698                     if (status) {
699                         fprintf (stderr,
700                                  "Error applying tag %s to message %s:\n",
701                                  tag, message_id);
702                         fprintf (stderr, "%s\n",
703                                  notmuch_status_to_string (status));
704                     }
705                 } else {
706                     fprintf (stderr, "%s ", tag);
707                 }
708             }
709
710             if (message)
711                 notmuch_message_destroy (message);
712             else
713                 fprintf (stderr, ")\n");
714         }
715         free (message_id);
716         free (tags);
717     }
718
719     regfree (&regex);
720
721   DONE:
722     if (line)
723         free (line);
724     if (notmuch)
725         notmuch_database_close (notmuch);
726
727     return ret;
728 }
729
730 command_t commands[] = {
731     { "setup", setup_command,
732       "Interactively setup notmuch for first use.\n"
733       "\t\tInvoking notmuch with no command argument will run setup if\n"
734       "\t\tthe setup command has not previously been completed." },
735     { "new", new_command,
736       "Find and import any new messages."},
737     { "search", search_command,
738       "<search-term> [...]\n\n"
739       "\t\tSearch for threads matching the given search terms.\n"
740       "\t\tOnce we actually implement search we'll document the\n"
741       "\t\tsyntax here." },
742     { "show", show_command,
743       "<thread-id>\n\n"
744       "\t\tShow the thread with the given thread ID (see 'search')." },
745     { "dump", dump_command,
746       "[<filename>]\n\n"
747       "\t\tCreate a plain-text dump of the tags for each message\n"
748       "\t\twriting to the given filename, if any, or to stdout.\n"
749       "\t\tThese tags are the only data in the notmuch database\n"
750       "\t\tthat can't be recreated from the messages themselves.\n"
751       "\t\tThe output of notmuch dump is therefore the only\n"
752       "\t\tcritical thing to backup (and much more friendly to\n"
753       "\t\tincremental backup than the native database files." },
754     { "restore", restore_command,
755       "<filename>\n\n"
756       "\t\tRestore the tags from the given dump file (see 'dump')." }
757 };
758
759 void
760 usage (void)
761 {
762     command_t *command;
763     int i;
764
765     fprintf (stderr, "Usage: notmuch <command> [args...]\n");
766     fprintf (stderr, "\n");
767     fprintf (stderr, "Where <command> and [args...] are as follows:\n");
768     fprintf (stderr, "\n");
769
770     for (i = 0; i < ARRAY_SIZE (commands); i++) {
771         command = &commands[i];
772
773         fprintf (stderr, "\t%s\t%s\n\n", command->name, command->usage);
774     }
775 }
776     
777 int
778 main (int argc, char *argv[])
779 {
780     command_t *command;
781     int i;
782
783     if (argc == 1)
784         return setup_command (0, NULL);
785
786     for (i = 0; i < ARRAY_SIZE (commands); i++) {
787         command = &commands[i];
788
789         if (strcmp (argv[1], command->name) == 0)
790             return (command->function) (argc - 2, &argv[2]);
791     }
792
793     /* Don't complain about "help" being an unknown command when we're
794        about to provide exactly what's wanted anyway. */
795     if (strcmp (argv[1], "help") == 0 ||
796         strcmp (argv[1], "--help") == 0)
797     {
798         fprintf (stderr, "The notmuch mail system.\n\n");
799     } else {
800         fprintf (stderr, "Error: Unknown command '%s'\n\n", argv[1]);
801     }
802     usage ();
803     exit (1);
804
805     return 0;
806 }