]> git.cworth.org Git - notmuch/blob - notmuch.c
emacs: Add new option notmuch-search-hide-excluded
[notmuch] / notmuch.c
1 /* notmuch - Not much of an email program, (just index and search)
2  *
3  * Copyright © 2009 Carl Worth
4  * Copyright © 2009 Keith Packard
5  *
6  * This program is free software: you can redistribute it and/or modify
7  * it under the terms of the GNU General Public License as published by
8  * the Free Software Foundation, either version 3 of the License, or
9  * (at your option) any later version.
10  *
11  * This program is distributed in the hope that it will be useful,
12  * but WITHOUT ANY WARRANTY; without even the implied warranty of
13  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
14  * GNU General Public License for more details.
15  *
16  * You should have received a copy of the GNU General Public License
17  * along with this program.  If not, see https://www.gnu.org/licenses/ .
18  *
19  * Authors: Carl Worth <cworth@cworth.org>
20  *          Keith Packard <keithp@keithp.com>
21  */
22
23 #include "notmuch-client.h"
24
25 /*
26  * Notmuch subcommand hook.
27  *
28  * The return value will be used as notmuch exit status code,
29  * preferably EXIT_SUCCESS or EXIT_FAILURE.
30  *
31  * Each subcommand should be passed either a config object, or an open
32  * database
33  */
34 typedef int (*command_function_t) (notmuch_database_t *notmuch, int argc, char *argv[]);
35
36 typedef struct command {
37     const char *name;
38     command_function_t function;
39     notmuch_command_mode_t mode;
40     const char *summary;
41 } command_t;
42
43 static int
44 notmuch_help_command (notmuch_database_t *notmuch, int argc, char *argv[]);
45
46 static int
47 notmuch_command (notmuch_database_t *notmuch, int argc, char *argv[]);
48
49 static int
50 _help_for (const char *topic);
51
52 static void
53 notmuch_exit_if_unmatched_db_uuid (notmuch_database_t *notmuch);
54
55 static bool print_version = false, print_help = false;
56 static const char *notmuch_requested_db_uuid = NULL;
57 static int query_syntax = NOTMUCH_QUERY_SYNTAX_XAPIAN;
58
59 const notmuch_opt_desc_t notmuch_shared_options [] = {
60     { .opt_bool = &print_version, .name = "version" },
61     { .opt_bool = &print_help, .name = "help" },
62     { .opt_string = &notmuch_requested_db_uuid, .name = "uuid" },
63     { .opt_keyword = &query_syntax, .name = "query", .keywords =
64           (notmuch_keyword_t []){ { "infix", NOTMUCH_QUERY_SYNTAX_XAPIAN },
65                                   { "sexp", NOTMUCH_QUERY_SYNTAX_SEXP },
66                                   { 0, 0 } } },
67
68     { }
69 };
70
71 notmuch_query_syntax_t
72 shared_option_query_syntax ()
73 {
74     return query_syntax;
75 }
76
77 /* any subcommand wanting to support these options should call
78  * inherit notmuch_shared_options and call
79  * notmuch_process_shared_options (notmuch, subcommand_name);
80  * with notmuch = an open database, or NULL.
81  */
82 void
83 notmuch_process_shared_options (notmuch_database_t *notmuch, const char *subcommand_name)
84 {
85     if (print_version) {
86         printf ("notmuch " STRINGIFY (NOTMUCH_VERSION) "\n");
87         exit (EXIT_SUCCESS);
88     }
89
90     if (print_help) {
91         int ret = _help_for (subcommand_name);
92         exit (ret);
93     }
94
95     if (notmuch) {
96         notmuch_exit_if_unmatched_db_uuid (notmuch);
97     } else {
98         if (notmuch_requested_db_uuid)
99             fprintf (stderr, "Warning: ignoring --uuid=%s\n",
100                      notmuch_requested_db_uuid);
101     }
102 }
103
104 /* This is suitable for subcommands that do not actually open the
105  * database.
106  */
107 int
108 notmuch_minimal_options (const char *subcommand_name,
109                          int argc, char **argv)
110 {
111     int opt_index;
112
113     notmuch_opt_desc_t options[] = {
114         { .opt_inherit = notmuch_shared_options },
115         { }
116     };
117
118     opt_index = parse_arguments (argc, argv, options, 1);
119
120     if (opt_index < 0)
121         return -1;
122
123     /* We can't use argv here as it is sometimes NULL */
124     notmuch_process_shared_options (NULL, subcommand_name);
125     return opt_index;
126 }
127
128
129 struct _notmuch_client_indexing_cli_choices indexing_cli_choices = { };
130 const notmuch_opt_desc_t notmuch_shared_indexing_options [] = {
131     { .opt_keyword = &indexing_cli_choices.decrypt_policy,
132       .present = &indexing_cli_choices.decrypt_policy_set, .keywords =
133           (notmuch_keyword_t []){ { "false", NOTMUCH_DECRYPT_FALSE },
134                                   { "true", NOTMUCH_DECRYPT_TRUE },
135                                   { "auto", NOTMUCH_DECRYPT_AUTO },
136                                   { "nostash", NOTMUCH_DECRYPT_NOSTASH },
137                                   { 0, 0 } },
138       .name = "decrypt" },
139     { }
140 };
141
142
143 notmuch_status_t
144 notmuch_process_shared_indexing_options (notmuch_indexopts_t *opts)
145 {
146     if (opts == NULL)
147         return NOTMUCH_STATUS_NULL_POINTER;
148
149     if (indexing_cli_choices.decrypt_policy_set) {
150         notmuch_status_t status;
151         status = notmuch_indexopts_set_decrypt_policy (opts,
152                                                        indexing_cli_choices.decrypt_policy);
153         if (status != NOTMUCH_STATUS_SUCCESS) {
154             fprintf (stderr, "Error: Failed to set index decryption policy to %d. (%s)\n",
155                      indexing_cli_choices.decrypt_policy, notmuch_status_to_string (status));
156             return status;
157         }
158     }
159     return NOTMUCH_STATUS_SUCCESS;
160 }
161
162
163 static const command_t commands[] = {
164     { NULL, notmuch_command, NOTMUCH_COMMAND_CONFIG_CREATE | NOTMUCH_COMMAND_CONFIG_LOAD,
165       "Notmuch main command." },
166     { "setup", notmuch_setup_command, NOTMUCH_COMMAND_CONFIG_CREATE | NOTMUCH_COMMAND_CONFIG_LOAD,
167       "Interactively set up notmuch for first use." },
168     { "new", notmuch_new_command,
169       NOTMUCH_COMMAND_DATABASE_EARLY | NOTMUCH_COMMAND_DATABASE_WRITE |
170       NOTMUCH_COMMAND_DATABASE_CREATE,
171       "Find and import new messages to the notmuch database." },
172     { "insert", notmuch_insert_command, NOTMUCH_COMMAND_DATABASE_EARLY |
173       NOTMUCH_COMMAND_DATABASE_WRITE,
174       "Add a new message into the maildir and notmuch database." },
175     { "search", notmuch_search_command, NOTMUCH_COMMAND_DATABASE_EARLY,
176       "Search for messages matching the given search terms." },
177     { "address", notmuch_address_command, NOTMUCH_COMMAND_DATABASE_EARLY,
178       "Get addresses from messages matching the given search terms." },
179     { "show", notmuch_show_command, NOTMUCH_COMMAND_DATABASE_EARLY,
180       "Show all messages matching the search terms." },
181     { "count", notmuch_count_command, NOTMUCH_COMMAND_DATABASE_EARLY,
182       "Count messages matching the search terms." },
183     { "reply", notmuch_reply_command, NOTMUCH_COMMAND_DATABASE_EARLY,
184       "Construct a reply template for a set of messages." },
185     { "tag", notmuch_tag_command, NOTMUCH_COMMAND_DATABASE_EARLY | NOTMUCH_COMMAND_DATABASE_WRITE,
186       "Add/remove tags for all messages matching the search terms." },
187     { "dump", notmuch_dump_command, NOTMUCH_COMMAND_DATABASE_EARLY | NOTMUCH_COMMAND_DATABASE_WRITE,
188       "Create a plain-text dump of the tags for each message." },
189     { "restore", notmuch_restore_command, NOTMUCH_COMMAND_DATABASE_EARLY |
190       NOTMUCH_COMMAND_DATABASE_WRITE,
191       "Restore the tags from the given dump file (see 'dump')." },
192     { "compact", notmuch_compact_command, NOTMUCH_COMMAND_DATABASE_EARLY |
193       NOTMUCH_COMMAND_DATABASE_WRITE,
194       "Compact the notmuch database." },
195     { "reindex", notmuch_reindex_command, NOTMUCH_COMMAND_DATABASE_EARLY |
196       NOTMUCH_COMMAND_DATABASE_WRITE,
197       "Re-index all messages matching the search terms." },
198     { "config", notmuch_config_command, NOTMUCH_COMMAND_CONFIG_LOAD,
199       "Get or set settings in the notmuch configuration file." },
200 #if WITH_EMACS
201     { "emacs-mua", NULL, 0,
202       "send mail with notmuch and emacs." },
203 #endif
204     { "help", notmuch_help_command, NOTMUCH_COMMAND_CONFIG_CREATE, /* create but don't save config */
205       "This message, or more detailed help for the named command." }
206 };
207
208 typedef struct help_topic {
209     const char *name;
210     const char *summary;
211 } help_topic_t;
212
213 static const help_topic_t help_topics[] = {
214     { "hooks",
215       "Hooks that will be run before or after certain commands." },
216     { "properties",
217       "Message property conventions and documentation." },
218     { "search-terms",
219       "Common infix search term syntax." },
220     { "sexp-queries",
221       "Common s-expression search term syntax." },
222 };
223
224 static const command_t *
225 find_command (const char *name)
226 {
227     size_t i;
228
229     for (i = 0; i < ARRAY_SIZE (commands); i++)
230         if ((! name && ! commands[i].name) ||
231             (name && commands[i].name && strcmp (name, commands[i].name) == 0))
232             return &commands[i];
233
234     return NULL;
235 }
236
237 int notmuch_format_version;
238
239 static void
240 usage (FILE *out)
241 {
242     const command_t *command;
243     const help_topic_t *topic;
244     unsigned int i;
245
246     fprintf (out,
247              "Usage: notmuch --help\n"
248              "       notmuch --version\n"
249              "       notmuch <command> [args...]\n");
250     fprintf (out, "\n");
251     fprintf (out, "The available commands are as follows:\n");
252     fprintf (out, "\n");
253
254     for (i = 0; i < ARRAY_SIZE (commands); i++) {
255         command = &commands[i];
256
257         if (command->name)
258             fprintf (out, "  %-12s  %s\n", command->name, command->summary);
259     }
260
261     fprintf (out, "\n");
262     fprintf (out, "Additional help topics are as follows:\n");
263     fprintf (out, "\n");
264
265     for (i = 0; i < ARRAY_SIZE (help_topics); i++) {
266         topic = &help_topics[i];
267         fprintf (out, "  %-12s  %s\n", topic->name, topic->summary);
268     }
269
270     fprintf (out, "\n");
271     fprintf (out,
272              "Use \"notmuch help <command or topic>\" for more details "
273              "on each command or topic.\n\n");
274 }
275
276 void
277 notmuch_exit_if_unsupported_format (void)
278 {
279     if (notmuch_format_version > NOTMUCH_FORMAT_CUR) {
280         fprintf (stderr,
281                  "\
282 A caller requested output format version %d, but the installed notmuch\n\
283 CLI only supports up to format version %d.  You may need to upgrade your\n\
284 notmuch CLI.\n",
285                  notmuch_format_version, NOTMUCH_FORMAT_CUR);
286         exit (NOTMUCH_EXIT_FORMAT_TOO_NEW);
287     } else if (notmuch_format_version < NOTMUCH_FORMAT_MIN) {
288         fprintf (stderr,
289                  "\
290 A caller requested output format version %d, which is no longer supported\n\
291 by the notmuch CLI (it requires at least version %d).  You may need to\n\
292 upgrade your notmuch front-end.\n",
293                  notmuch_format_version, NOTMUCH_FORMAT_MIN);
294         exit (NOTMUCH_EXIT_FORMAT_TOO_OLD);
295     } else if (notmuch_format_version < NOTMUCH_FORMAT_MIN_ACTIVE) {
296         /* Warn about old version requests so compatibility issues are
297          * less likely when we drop support for a deprecated format
298          * versions. */
299         fprintf (stderr,
300                  "\
301 A caller requested deprecated output format version %d, which may not\n\
302 be supported in the future.\n", notmuch_format_version);
303     }
304 }
305
306 static void
307 notmuch_exit_if_unmatched_db_uuid (notmuch_database_t *notmuch)
308 {
309     const char *uuid = NULL;
310
311     if (! notmuch_requested_db_uuid)
312         return;
313     IGNORE_RESULT (notmuch_database_get_revision (notmuch, &uuid));
314
315     if (strcmp (notmuch_requested_db_uuid, uuid) != 0) {
316         fprintf (stderr, "Error: requested database revision %s does not match %s\n",
317                  notmuch_requested_db_uuid, uuid);
318         exit (1);
319     }
320 }
321
322 static void
323 exec_man (const char *page)
324 {
325     if (execlp ("man", "man", page, (char *) NULL)) {
326         perror ("exec man");
327         exit (1);
328     }
329 }
330
331 static int
332 _help_for (const char *topic_name)
333 {
334     char *page;
335
336     if (! topic_name) {
337         printf ("The notmuch mail system.\n\n");
338         usage (stdout);
339         return EXIT_SUCCESS;
340     }
341
342     if (strcmp (topic_name, "help") == 0) {
343         printf ("The notmuch help system.\n\n"
344                 "\tNotmuch uses the man command to display help. In case\n"
345                 "\tof difficulties check that MANPATH includes the pages\n"
346                 "\tinstalled by notmuch.\n\n"
347                 "\tTry \"notmuch help\" for a list of topics.\n");
348         return EXIT_SUCCESS;
349     }
350
351     page = talloc_asprintf (NULL, "notmuch-%s", topic_name);
352     exec_man (page);
353
354     return EXIT_FAILURE;
355 }
356
357 static int
358 notmuch_help_command (unused(notmuch_database_t *notmuch), int argc, char *argv[])
359 {
360     int opt_index;
361
362     opt_index = notmuch_minimal_options ("help", argc, argv);
363     if (opt_index < 0)
364         return EXIT_FAILURE;
365
366     /* skip at least subcommand argument */
367     argc -= opt_index;
368     argv += opt_index;
369
370     if (argc == 0) {
371         return _help_for (NULL);
372     }
373
374     return _help_for (argv[0]);
375 }
376
377 /* Handle the case of "notmuch" being invoked with no command
378  * argument. For now we just call notmuch_setup_command, but we plan
379  * to be more clever about this in the future.
380  */
381 static int
382 notmuch_command (notmuch_database_t *notmuch,
383                  unused(int argc), unused(char **argv))
384 {
385
386     const char *config_path;
387
388     /* If the user has not created a configuration file, then run
389      * notmuch_setup_command which will give a nice welcome message,
390      * and interactively guide the user through the configuration. */
391     config_path = notmuch_config_path (notmuch);
392     if (access (config_path, R_OK | F_OK) == -1) {
393         if (errno != ENOENT) {
394             fprintf (stderr, "Error: %s config file access failed: %s\n", config_path,
395                      strerror (errno));
396             return EXIT_FAILURE;
397         } else {
398             return notmuch_setup_command (notmuch, 0, NULL);
399         }
400     }
401
402     printf ("Notmuch is configured and appears to have a database. Excellent!\n\n"
403             "At this point you can start exploring the functionality of notmuch by\n"
404             "using commands such as:\n\n"
405             "\tnotmuch search tag:inbox\n\n"
406             "\tnotmuch search to:\"%s\"\n\n"
407             "\tnotmuch search from:\"%s\"\n\n"
408             "\tnotmuch search subject:\"my favorite things\"\n\n"
409             "See \"notmuch help search\" for more details.\n\n"
410             "You can also use \"notmuch show\" with any of the thread IDs resulting\n"
411             "from a search. Finally, you may want to explore using a more sophisticated\n"
412             "interface to notmuch such as the emacs interface implemented in notmuch.el\n"
413             "or any other interface described at https://notmuchmail.org\n\n"
414             "And don't forget to run \"notmuch new\" whenever new mail arrives.\n\n"
415             "Have fun, and may your inbox never have much mail.\n\n",
416             notmuch_config_get (notmuch, NOTMUCH_CONFIG_USER_NAME),
417             notmuch_config_get (notmuch, NOTMUCH_CONFIG_PRIMARY_EMAIL));
418
419     return EXIT_SUCCESS;
420 }
421
422 /*
423  * Try to run subcommand in argv[0] as notmuch- prefixed external
424  * command. argv must be NULL terminated (argv passed to main always
425  * is).
426  *
427  * Does not return if the external command is found and
428  * executed. Return true if external command is not found. Return
429  * false on errors.
430  */
431 static bool
432 try_external_command (const char *config_file_name, char *argv[])
433 {
434     char *old_argv0 = argv[0];
435     bool ret = true;
436
437     if (config_file_name) {
438         if (setenv ("NOTMUCH_CONFIG", config_file_name, 1)) {
439             perror ("setenv");
440             exit (1);
441         }
442     }
443
444     argv[0] = talloc_asprintf (NULL, "notmuch-%s", old_argv0);
445
446     /*
447      * This will only return on errors. Not finding an external
448      * command (ENOENT) is not an error from our perspective.
449      */
450     execvp (argv[0], argv);
451     if (errno != ENOENT) {
452         fprintf (stderr, "Error: Running external command '%s' failed: %s\n",
453                  argv[0], strerror (errno));
454         ret = false;
455     }
456
457     talloc_free (argv[0]);
458     argv[0] = old_argv0;
459
460     return ret;
461 }
462
463 int
464 main (int argc, char *argv[])
465 {
466     void *local;
467     char *talloc_report;
468     const char *command_name = NULL;
469     const command_t *command;
470     const char *config_file_name = NULL;
471     notmuch_database_t *notmuch = NULL;
472     int opt_index;
473     int ret = EXIT_SUCCESS;
474
475     notmuch_opt_desc_t options[] = {
476         { .opt_string = &config_file_name, .name = "config", .allow_empty = TRUE },
477         { .opt_inherit = notmuch_shared_options },
478         { }
479     };
480
481     notmuch_client_init ();
482
483     local = talloc_new (NULL);
484
485     /* Globally default to the current output format version. */
486     notmuch_format_version = NOTMUCH_FORMAT_CUR;
487
488     opt_index = parse_arguments (argc, argv, options, 1);
489     if (opt_index < 0) {
490         ret = EXIT_FAILURE;
491         goto DONE;
492     }
493
494     if (opt_index < argc)
495         command_name = argv[opt_index];
496
497     notmuch_process_shared_options (NULL, command_name);
498
499     command = find_command (command_name);
500     /* if command->function is NULL, try external command */
501     if (! command || ! command->function) {
502         /* This won't return if the external command is found. */
503         if (try_external_command (config_file_name, argv + opt_index))
504             fprintf (stderr, "Error: Unknown command '%s' (see \"notmuch help\")\n",
505                      command_name);
506         ret = EXIT_FAILURE;
507         goto DONE;
508     }
509
510     if (command->mode & NOTMUCH_COMMAND_DATABASE_EARLY) {
511         char *status_string = NULL;
512         notmuch_database_mode_t mode;
513         notmuch_status_t status;
514
515         if (command->mode & NOTMUCH_COMMAND_DATABASE_WRITE ||
516             command->mode & NOTMUCH_COMMAND_DATABASE_CREATE)
517             mode = NOTMUCH_DATABASE_MODE_READ_WRITE;
518         else
519             mode = NOTMUCH_DATABASE_MODE_READ_ONLY;
520
521         if (command->mode & NOTMUCH_COMMAND_DATABASE_CREATE) {
522             status = notmuch_database_create_with_config (NULL,
523                                                           config_file_name,
524                                                           NULL,
525                                                           &notmuch,
526                                                           &status_string);
527             if (status && status != NOTMUCH_STATUS_DATABASE_EXISTS) {
528                 if (status_string) {
529                     fputs (status_string, stderr);
530                     free (status_string);
531                 }
532
533                 if (status == NOTMUCH_STATUS_NO_CONFIG)
534                     fputs ("Try running 'notmuch setup' to create a configuration.", stderr);
535
536                 return EXIT_FAILURE;
537             }
538         }
539
540         if (notmuch == NULL) {
541             status = notmuch_database_open_with_config (NULL,
542                                                         mode,
543                                                         config_file_name,
544                                                         NULL,
545                                                         &notmuch,
546                                                         &status_string);
547             if (status) {
548                 if (status_string) {
549                     fputs (status_string, stderr);
550                     free (status_string);
551                 }
552
553                 return EXIT_FAILURE;
554             }
555         }
556     }
557
558     if (command->mode & NOTMUCH_COMMAND_CONFIG_LOAD) {
559         char *status_string = NULL;
560         notmuch_status_t status;
561         status = notmuch_database_load_config (NULL,
562                                                config_file_name,
563                                                NULL,
564                                                &notmuch,
565                                                &status_string);
566
567         if (status == NOTMUCH_STATUS_NO_CONFIG && ! (command->mode & NOTMUCH_COMMAND_CONFIG_CREATE)) {
568             fputs ("Try running 'notmuch setup' to create a configuration.", stderr);
569             goto DONE;
570         }
571         switch (status) {
572         case NOTMUCH_STATUS_NO_CONFIG:
573             if (! (command->mode & NOTMUCH_COMMAND_CONFIG_CREATE)) {
574                 fputs ("Try running 'notmuch setup' to create a configuration.", stderr);
575                 goto DONE;
576             }
577             break;
578         case NOTMUCH_STATUS_NO_DATABASE:
579             if (! command_name) {
580                 printf ("Notmuch is configured, but no database was found.\n");
581                 printf ("You probably want to run \"notmuch new\" now to create a database.\n\n"
582                         "Note that the first run of \"notmuch new\" can take a very long time\n"
583                         "and that the resulting database will use roughly the same amount of\n"
584                         "storage space as the email being indexed.\n\n");
585                 status = NOTMUCH_STATUS_SUCCESS;
586                 goto DONE;
587             }
588             break;
589         case NOTMUCH_STATUS_SUCCESS:
590             break;
591         default:
592             goto DONE;
593         }
594
595     }
596
597     ret = (command->function)(notmuch, argc - opt_index, argv + opt_index);
598
599   DONE:
600     talloc_report = getenv ("NOTMUCH_TALLOC_REPORT");
601     if (talloc_report && strcmp (talloc_report, "") != 0) {
602         /* this relies on the previous call to
603          * talloc_enable_null_tracking
604          */
605
606         FILE *report = fopen (talloc_report, "a");
607         if (report) {
608             talloc_report_full (NULL, report);
609         } else {
610             ret = 1;
611             fprintf (stderr, "ERROR: unable to write talloc log. ");
612             perror (talloc_report);
613         }
614     }
615
616     talloc_free (local);
617
618     return ret;
619 }