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