]> git.notmuchmail.org Git - notmuch/blob - notmuch.c
debian/changelog: remove trailing whitespace
[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  * preferrably EXIT_SUCCESS or EXIT_FAILURE.
30  */
31 typedef int (*command_function_t) (notmuch_config_t *config, int argc, char *argv[]);
32
33 typedef struct command {
34     const char *name;
35     command_function_t function;
36     notmuch_config_mode_t config_mode;
37     const char *summary;
38 } command_t;
39
40 static int
41 notmuch_help_command (notmuch_config_t *config, int argc, char *argv[]);
42
43 static int
44 notmuch_command (notmuch_config_t *config, int argc, char *argv[]);
45
46 static int
47 _help_for (const char *topic);
48
49 static bool print_version = false, print_help = false;
50 const char *notmuch_requested_db_uuid = NULL;
51
52 const notmuch_opt_desc_t notmuch_shared_options [] = {
53     { .opt_bool = &print_version, .name = "version" },
54     { .opt_bool = &print_help, .name = "help" },
55     { .opt_string = &notmuch_requested_db_uuid, .name = "uuid" },
56     { }
57 };
58
59 /* any subcommand wanting to support these options should call
60  * inherit notmuch_shared_options and call
61  * notmuch_process_shared_options (subcommand_name);
62  */
63 void
64 notmuch_process_shared_options (const char *subcommand_name) {
65     if (print_version) {
66         printf ("notmuch " STRINGIFY(NOTMUCH_VERSION) "\n");
67         exit (EXIT_SUCCESS);
68     }
69
70     if (print_help) {
71         int ret = _help_for (subcommand_name);
72         exit (ret);
73     }
74 }
75
76 /* This is suitable for subcommands that do not actually open the
77  * database.
78  */
79 int notmuch_minimal_options (const char *subcommand_name,
80                                   int argc, char **argv)
81 {
82     int opt_index;
83
84     notmuch_opt_desc_t options[] = {
85         { .opt_inherit = notmuch_shared_options },
86         { }
87     };
88
89     opt_index = parse_arguments (argc, argv, options, 1);
90
91     if (opt_index < 0)
92         return -1;
93
94     /* We can't use argv here as it is sometimes NULL */
95     notmuch_process_shared_options (subcommand_name);
96     return opt_index;
97 }
98
99
100 struct _notmuch_client_indexing_cli_choices indexing_cli_choices = { };
101 const notmuch_opt_desc_t  notmuch_shared_indexing_options [] = {
102     { .opt_bool = &indexing_cli_choices.try_decrypt,
103       .present = &indexing_cli_choices.try_decrypt_set,
104       .name = "try-decrypt" },
105     { }
106 };
107
108
109 notmuch_status_t
110 notmuch_process_shared_indexing_options (notmuch_database_t *notmuch, g_mime_3_unused(notmuch_config_t *config))
111 {
112     if (indexing_cli_choices.opts == NULL)
113         indexing_cli_choices.opts = notmuch_database_get_default_indexopts (notmuch);
114     if (indexing_cli_choices.try_decrypt_set) {
115         notmuch_status_t status;
116         if (indexing_cli_choices.opts == NULL)
117             return NOTMUCH_STATUS_OUT_OF_MEMORY;
118         status = notmuch_indexopts_set_try_decrypt (indexing_cli_choices.opts, indexing_cli_choices.try_decrypt);
119         if (status != NOTMUCH_STATUS_SUCCESS) {
120             fprintf (stderr, "Error: Failed to set try_decrypt to %s. (%s)\n",
121                      indexing_cli_choices.try_decrypt ? "True" : "False", notmuch_status_to_string (status));
122             notmuch_indexopts_destroy (indexing_cli_choices.opts);
123             indexing_cli_choices.opts = NULL;
124             return status;
125         }
126     }
127 #if (GMIME_MAJOR_VERSION < 3)
128     if (indexing_cli_choices.opts && notmuch_indexopts_get_try_decrypt (indexing_cli_choices.opts)) {
129         const char* gpg_path = notmuch_config_get_crypto_gpg_path (config);
130         if (gpg_path && strcmp(gpg_path, "gpg"))
131             fprintf (stderr, "Warning: deprecated crypto.gpg_path is set to '%s'\n"
132                      "\tbut ignoring (use $PATH instead)\n", gpg_path);
133     }
134 #endif
135     return NOTMUCH_STATUS_SUCCESS;
136 }
137
138
139 static command_t commands[] = {
140     { NULL, notmuch_command, NOTMUCH_CONFIG_OPEN | NOTMUCH_CONFIG_CREATE,
141       "Notmuch main command." },
142     { "setup", notmuch_setup_command, NOTMUCH_CONFIG_OPEN | NOTMUCH_CONFIG_CREATE,
143       "Interactively set up notmuch for first use." },
144     { "new", notmuch_new_command, NOTMUCH_CONFIG_OPEN,
145       "Find and import new messages to the notmuch database." },
146     { "insert", notmuch_insert_command, NOTMUCH_CONFIG_OPEN,
147       "Add a new message into the maildir and notmuch database." },
148     { "search", notmuch_search_command, NOTMUCH_CONFIG_OPEN,
149       "Search for messages matching the given search terms." },
150     { "address", notmuch_address_command, NOTMUCH_CONFIG_OPEN,
151       "Get addresses from messages matching the given search terms." },
152     { "show", notmuch_show_command, NOTMUCH_CONFIG_OPEN,
153       "Show all messages matching the search terms." },
154     { "count", notmuch_count_command, NOTMUCH_CONFIG_OPEN,
155       "Count messages matching the search terms." },
156     { "reply", notmuch_reply_command, NOTMUCH_CONFIG_OPEN,
157       "Construct a reply template for a set of messages." },
158     { "tag", notmuch_tag_command, NOTMUCH_CONFIG_OPEN,
159       "Add/remove tags for all messages matching the search terms." },
160     { "dump", notmuch_dump_command, NOTMUCH_CONFIG_OPEN,
161       "Create a plain-text dump of the tags for each message." },
162     { "restore", notmuch_restore_command, NOTMUCH_CONFIG_OPEN,
163       "Restore the tags from the given dump file (see 'dump')." },
164     { "compact", notmuch_compact_command, NOTMUCH_CONFIG_OPEN,
165       "Compact the notmuch database." },
166     { "reindex", notmuch_reindex_command, NOTMUCH_CONFIG_OPEN,
167       "Re-index all messages matching the search terms." },
168     { "config", notmuch_config_command, NOTMUCH_CONFIG_OPEN,
169       "Get or set settings in the notmuch configuration file." },
170     { "help", notmuch_help_command, NOTMUCH_CONFIG_CREATE, /* create but don't save config */
171       "This message, or more detailed help for the named command." }
172 };
173
174 typedef struct help_topic {
175     const char *name;
176     const char *summary;
177 } help_topic_t;
178
179 static help_topic_t help_topics[] = {
180     { "search-terms",
181       "Common search term syntax." },
182     { "hooks",
183       "Hooks that will be run before or after certain commands." },
184 };
185
186 static command_t *
187 find_command (const char *name)
188 {
189     size_t i;
190
191     for (i = 0; i < ARRAY_SIZE (commands); i++)
192         if ((!name && !commands[i].name) ||
193             (name && commands[i].name && strcmp (name, commands[i].name) == 0))
194             return &commands[i];
195
196     return NULL;
197 }
198
199 int notmuch_format_version;
200
201 static void
202 usage (FILE *out)
203 {
204     command_t *command;
205     help_topic_t *topic;
206     unsigned int i;
207
208     fprintf (out,
209              "Usage: notmuch --help\n"
210              "       notmuch --version\n"
211              "       notmuch <command> [args...]\n");
212     fprintf (out, "\n");
213     fprintf (out, "The available commands are as follows:\n");
214     fprintf (out, "\n");
215
216     for (i = 0; i < ARRAY_SIZE (commands); i++) {
217         command = &commands[i];
218
219         if (command->name)
220             fprintf (out, "  %-12s  %s\n", command->name, command->summary);
221     }
222
223     fprintf (out, "\n");
224     fprintf (out, "Additional help topics are as follows:\n");
225     fprintf (out, "\n");
226
227     for (i = 0; i < ARRAY_SIZE (help_topics); i++) {
228         topic = &help_topics[i];
229         fprintf (out, "  %-12s  %s\n", topic->name, topic->summary);
230     }
231
232     fprintf (out, "\n");
233     fprintf (out,
234              "Use \"notmuch help <command or topic>\" for more details "
235              "on each command or topic.\n\n");
236 }
237
238 void
239 notmuch_exit_if_unsupported_format (void)
240 {
241     if (notmuch_format_version > NOTMUCH_FORMAT_CUR) {
242         fprintf (stderr, "\
243 A caller requested output format version %d, but the installed notmuch\n\
244 CLI only supports up to format version %d.  You may need to upgrade your\n\
245 notmuch CLI.\n",
246                  notmuch_format_version, NOTMUCH_FORMAT_CUR);
247         exit (NOTMUCH_EXIT_FORMAT_TOO_NEW);
248     } else if (notmuch_format_version < NOTMUCH_FORMAT_MIN) {
249         fprintf (stderr, "\
250 A caller requested output format version %d, which is no longer supported\n\
251 by the notmuch CLI (it requires at least version %d).  You may need to\n\
252 upgrade your notmuch front-end.\n",
253                  notmuch_format_version, NOTMUCH_FORMAT_MIN);
254         exit (NOTMUCH_EXIT_FORMAT_TOO_OLD);
255     } else if (notmuch_format_version < NOTMUCH_FORMAT_MIN_ACTIVE) {
256         /* Warn about old version requests so compatibility issues are
257          * less likely when we drop support for a deprecated format
258          * versions. */
259         fprintf (stderr, "\
260 A caller requested deprecated output format version %d, which may not\n\
261 be supported in the future.\n", notmuch_format_version);
262     }
263 }
264
265 void
266 notmuch_exit_if_unmatched_db_uuid (notmuch_database_t *notmuch)
267 {
268     const char *uuid = NULL;
269
270     if (!notmuch_requested_db_uuid)
271         return;
272     IGNORE_RESULT (notmuch_database_get_revision (notmuch, &uuid));
273
274     if (strcmp (notmuch_requested_db_uuid, uuid) != 0){
275         fprintf (stderr, "Error: requested database revision %s does not match %s\n",
276                  notmuch_requested_db_uuid, uuid);
277         exit (1);
278     }
279 }
280
281 static void
282 exec_man (const char *page)
283 {
284     if (execlp ("man", "man", page, (char *) NULL)) {
285         perror ("exec man");
286         exit (1);
287     }
288 }
289
290 static int
291 _help_for (const char *topic_name)
292 {
293     command_t *command;
294     help_topic_t *topic;
295     unsigned int i;
296
297     if (!topic_name) {
298         printf ("The notmuch mail system.\n\n");
299         usage (stdout);
300         return EXIT_SUCCESS;
301     }
302
303     if (strcmp (topic_name, "help") == 0) {
304         printf ("The notmuch help system.\n\n"
305                 "\tNotmuch uses the man command to display help. In case\n"
306                 "\tof difficulties check that MANPATH includes the pages\n"
307                 "\tinstalled by notmuch.\n\n"
308                 "\tTry \"notmuch help\" for a list of topics.\n");
309         return EXIT_SUCCESS;
310     }
311
312     command = find_command (topic_name);
313     if (command) {
314         char *page = talloc_asprintf (NULL, "notmuch-%s", command->name);
315         exec_man (page);
316     }
317
318     for (i = 0; i < ARRAY_SIZE (help_topics); i++) {
319         topic = &help_topics[i];
320         if (strcmp (topic_name, topic->name) == 0) {
321             char *page = talloc_asprintf (NULL, "notmuch-%s", topic->name);
322             exec_man (page);
323         }
324     }
325
326     fprintf (stderr,
327              "\nSorry, %s is not a known command. There's not much I can do to help.\n\n",
328              topic_name);
329     return EXIT_FAILURE;
330 }
331
332 static int
333 notmuch_help_command (unused (notmuch_config_t * config), int argc, char *argv[])
334 {
335     int opt_index;
336
337     opt_index = notmuch_minimal_options ("help", argc, argv);
338     if (opt_index < 0)
339         return EXIT_FAILURE;
340
341     /* skip at least subcommand argument */
342     argc-= opt_index;
343     argv+= opt_index;
344
345     if (argc == 0) {
346         return _help_for (NULL);
347     }
348
349     return _help_for (argv[0]);
350 }
351
352 /* Handle the case of "notmuch" being invoked with no command
353  * argument. For now we just call notmuch_setup_command, but we plan
354  * to be more clever about this in the future.
355  */
356 static int
357 notmuch_command (notmuch_config_t *config,
358                  unused(int argc), unused(char *argv[]))
359 {
360     char *db_path;
361     struct stat st;
362
363     /* If the user has never configured notmuch, then run
364      * notmuch_setup_command which will give a nice welcome message,
365      * and interactively guide the user through the configuration. */
366     if (notmuch_config_is_new (config))
367         return notmuch_setup_command (config, 0, NULL);
368
369     /* Notmuch is already configured, but is there a database? */
370     db_path = talloc_asprintf (config, "%s/%s",
371                                notmuch_config_get_database_path (config),
372                                ".notmuch");
373     if (stat (db_path, &st)) {
374         if (errno != ENOENT) {
375             fprintf (stderr, "Error looking for notmuch database at %s: %s\n",
376                      db_path, strerror (errno));
377             return EXIT_FAILURE;
378         }
379         printf ("Notmuch is configured, but there's not yet a database at\n\n\t%s\n\n",
380                 db_path);
381         printf ("You probably want to run \"notmuch new\" now to create that database.\n\n"
382                 "Note that the first run of \"notmuch new\" can take a very long time\n"
383                 "and that the resulting database will use roughly the same amount of\n"
384                 "storage space as the email being indexed.\n\n");
385         return EXIT_SUCCESS;
386     }
387
388     printf ("Notmuch is configured and appears to have a database. Excellent!\n\n"
389             "At this point you can start exploring the functionality of notmuch by\n"
390             "using commands such as:\n\n"
391             "\tnotmuch search tag:inbox\n\n"
392             "\tnotmuch search to:\"%s\"\n\n"
393             "\tnotmuch search from:\"%s\"\n\n"
394             "\tnotmuch search subject:\"my favorite things\"\n\n"
395             "See \"notmuch help search\" for more details.\n\n"
396             "You can also use \"notmuch show\" with any of the thread IDs resulting\n"
397             "from a search. Finally, you may want to explore using a more sophisticated\n"
398             "interface to notmuch such as the emacs interface implemented in notmuch.el\n"
399             "or any other interface described at https://notmuchmail.org\n\n"
400             "And don't forget to run \"notmuch new\" whenever new mail arrives.\n\n"
401             "Have fun, and may your inbox never have much mail.\n\n",
402             notmuch_config_get_user_name (config),
403             notmuch_config_get_user_primary_email (config));
404
405     return EXIT_SUCCESS;
406 }
407
408 /*
409  * Try to run subcommand in argv[0] as notmuch- prefixed external
410  * command. argv must be NULL terminated (argv passed to main always
411  * is).
412  *
413  * Does not return if the external command is found and
414  * executed. Return true if external command is not found. Return
415  * false on errors.
416  */
417 static bool try_external_command(char *argv[])
418 {
419     char *old_argv0 = argv[0];
420     bool ret = true;
421
422     argv[0] = talloc_asprintf (NULL, "notmuch-%s", old_argv0);
423
424     /*
425      * This will only return on errors. Not finding an external
426      * command (ENOENT) is not an error from our perspective.
427      */
428     execvp (argv[0], argv);
429     if (errno != ENOENT) {
430         fprintf (stderr, "Error: Running external command '%s' failed: %s\n",
431                  argv[0], strerror(errno));
432         ret = false;
433     }
434
435     talloc_free (argv[0]);
436     argv[0] = old_argv0;
437
438     return ret;
439 }
440
441 int
442 main (int argc, char *argv[])
443 {
444     void *local;
445     char *talloc_report;
446     const char *command_name = NULL;
447     command_t *command;
448     const char *config_file_name = NULL;
449     notmuch_config_t *config = NULL;
450     int opt_index;
451     int ret;
452
453     notmuch_opt_desc_t options[] = {
454         { .opt_string = &config_file_name, .name = "config" },
455         { .opt_inherit = notmuch_shared_options },
456         { }
457     };
458
459     talloc_enable_null_tracking ();
460
461     local = talloc_new (NULL);
462
463     g_mime_init (GMIME_ENABLE_RFC2047_WORKAROUNDS);
464 #if !GLIB_CHECK_VERSION(2, 35, 1)
465     g_type_init ();
466 #endif
467
468     /* Globally default to the current output format version. */
469     notmuch_format_version = NOTMUCH_FORMAT_CUR;
470
471     opt_index = parse_arguments (argc, argv, options, 1);
472     if (opt_index < 0) {
473         ret = EXIT_FAILURE;
474         goto DONE;
475     }
476
477     if (opt_index < argc)
478         command_name = argv[opt_index];
479
480     notmuch_process_shared_options (command_name);
481
482     command = find_command (command_name);
483     if (!command) {
484         /* This won't return if the external command is found. */
485         if (try_external_command(argv + opt_index))
486             fprintf (stderr, "Error: Unknown command '%s' (see \"notmuch help\")\n",
487                      command_name);
488         ret = EXIT_FAILURE;
489         goto DONE;
490     }
491
492     config = notmuch_config_open (local, config_file_name, command->config_mode);
493     if (!config) {
494         ret = EXIT_FAILURE;
495         goto DONE;
496     }
497
498     ret = (command->function)(config, argc - opt_index, argv + opt_index);
499
500   DONE:
501     if (config)
502         notmuch_config_close (config);
503
504     talloc_report = getenv ("NOTMUCH_TALLOC_REPORT");
505     if (talloc_report && strcmp (talloc_report, "") != 0) {
506         /* this relies on the previous call to
507          * talloc_enable_null_tracking
508          */
509
510         FILE *report = fopen (talloc_report, "w");
511         if (report) {
512             talloc_report_full (NULL, report);
513         } else {
514             ret = 1;
515             fprintf (stderr, "ERROR: unable to write talloc log. ");
516             perror (talloc_report);
517         }
518     }
519
520     talloc_free (local);
521
522     return ret;
523 }