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