]> git.cworth.org Git - notmuch/blob - notmuch-config.c
emacs: Add new option notmuch-search-hide-excluded
[notmuch] / notmuch-config.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 https://www.gnu.org/licenses/ .
17  *
18  * Author: Carl Worth <cworth@cworth.org>
19  */
20
21 #include "notmuch-client.h"
22
23 #include <pwd.h>
24 #include <netdb.h>
25 #include <assert.h>
26
27 #include "path-util.h"
28 #include "unicode-util.h"
29
30 static const char toplevel_config_comment[] =
31     " .notmuch-config - Configuration file for the notmuch mail system\n"
32     "\n"
33     " For more information about notmuch, see https://notmuchmail.org";
34
35 static const struct config_group {
36     const char *group_name;
37     const char *comment;
38 } group_comment_table [] = {
39     {
40         "database",
41         " Database configuration\n"
42         "\n"
43         " The only value supported here is 'path' which should be the top-level\n"
44         " directory where your mail currently exists and to where mail will be\n"
45         " delivered in the future. Files should be individual email messages.\n"
46         " Notmuch will store its database within a sub-directory of the path\n"
47         " configured here named \".notmuch\".\n"
48     },
49     {
50         "user",
51         " User configuration\n"
52         "\n"
53         " Here is where you can let notmuch know how you would like to be\n"
54         " addressed. Valid settings are\n"
55         "\n"
56         "\tname         Your full name.\n"
57         "\tprimary_email        Your primary email address.\n"
58         "\tother_email  A list (separated by ';') of other email addresses\n"
59         "\t             at which you receive email.\n"
60         "\n"
61         " Notmuch will use the various email addresses configured here when\n"
62         " formatting replies. It will avoid including your own addresses in the\n"
63         " recipient list of replies, and will set the From address based on the\n"
64         " address to which the original email was addressed.\n"
65     },
66     {
67         "new",
68         " Configuration for \"notmuch new\"\n"
69         "\n"
70         " The following options are supported here:\n"
71         "\n"
72         "\ttags A list (separated by ';') of the tags that will be\n"
73         "\t     added to all messages incorporated by \"notmuch new\".\n"
74         "\n"
75         "\tignore       A list (separated by ';') of file and directory names\n"
76         "\t     that will not be searched for messages by \"notmuch new\".\n"
77         "\n"
78         "\t     NOTE: *Every* file/directory that goes by one of those\n"
79         "\t     names will be ignored, independent of its depth/location\n"
80         "\t     in the mail store.\n"
81     },
82     {
83         "search",
84         " Search configuration\n"
85         "\n"
86         " The following option is supported here:\n"
87         "\n"
88         "\texclude_tags\n"
89         "\t\tA ;-separated list of tags that will be excluded from\n"
90         "\t\tsearch results by default.  Using an excluded tag in a\n"
91         "\t\tquery will override that exclusion.\n"
92     },
93     {
94         "maildir",
95         " Maildir compatibility configuration\n"
96         "\n"
97         " The following option is supported here:\n"
98         "\n"
99         "\tsynchronize_flags      Valid values are true and false.\n"
100         "\n"
101         "\tIf true, then the following maildir flags (in message filenames)\n"
102         "\twill be synchronized with the corresponding notmuch tags:\n"
103         "\n"
104         "\t\tFlag       Tag\n"
105         "\t\t----       -------\n"
106         "\t\tD  draft\n"
107         "\t\tF  flagged\n"
108         "\t\tP  passed\n"
109         "\t\tR  replied\n"
110         "\t\tS  unread (added when 'S' flag is not present)\n"
111         "\n"
112         "\tThe \"notmuch new\" command will notice flag changes in filenames\n"
113         "\tand update tags, while the \"notmuch tag\" and \"notmuch restore\"\n"
114         "\tcommands will notice tag changes and update flags in filenames\n"
115     },
116 };
117
118 struct _notmuch_conffile {
119     char *filename;
120     GKeyFile *key_file;
121     bool is_new;
122 };
123
124 static int
125 notmuch_conffile_destructor (notmuch_conffile_t *config)
126 {
127     if (config->key_file)
128         g_key_file_free (config->key_file);
129
130     return 0;
131 }
132
133 static bool
134 get_config_from_file (notmuch_conffile_t *config, bool create_new)
135 {
136     #define BUF_SIZE 4096
137     char *config_str = NULL;
138     int config_len = 0;
139     int config_bufsize = BUF_SIZE;
140     size_t len;
141     GError *error = NULL;
142     bool ret = false;
143
144     FILE *fp = fopen (config->filename, "r");
145     if (fp == NULL) {
146         if (errno == ENOENT) {
147             /* If create_new is true, then the caller is prepared for a
148              * default configuration file in the case of FILE NOT FOUND.
149              */
150             if (create_new) {
151                 config->is_new = true;
152                 ret = true;
153             } else {
154                 fprintf (stderr, "Configuration file %s not found.\n"
155                          "Try running 'notmuch setup' to create a configuration.\n",
156                          config->filename);
157             }
158         } else {
159             fprintf (stderr, "Error opening config file '%s': %s\n",
160                      config->filename, strerror (errno));
161         }
162         goto out;
163     }
164
165     config_str = talloc_zero_array (config, char, config_bufsize);
166     if (config_str == NULL) {
167         fprintf (stderr, "Error reading '%s': Out of memory\n", config->filename);
168         goto out;
169     }
170
171     while ((len = fread (config_str + config_len, 1,
172                          config_bufsize - config_len, fp)) > 0) {
173         config_len += len;
174         if (config_len == config_bufsize) {
175             config_bufsize += BUF_SIZE;
176             config_str = talloc_realloc (config, config_str, char, config_bufsize);
177             if (config_str == NULL) {
178                 fprintf (stderr, "Error reading '%s': Failed to reallocate memory\n",
179                          config->filename);
180                 goto out;
181             }
182         }
183     }
184
185     if (ferror (fp)) {
186         fprintf (stderr, "Error reading '%s': I/O error\n", config->filename);
187         goto out;
188     }
189
190     if (g_key_file_load_from_data (config->key_file, config_str, config_len,
191                                    G_KEY_FILE_KEEP_COMMENTS, &error)) {
192         ret = true;
193         goto out;
194     }
195
196     fprintf (stderr, "Error parsing config file '%s': %s\n",
197              config->filename, error->message);
198
199     g_error_free (error);
200
201   out:
202     if (fp)
203         fclose (fp);
204
205     if (config_str)
206         talloc_free (config_str);
207
208     return ret;
209 }
210
211 /* Open the named notmuch configuration file. If the filename is NULL,
212  * the value of the environment variable $NOTMUCH_CONFIG will be used.
213  * If $NOTMUCH_CONFIG is unset, the default configuration file
214  * ($HOME/.notmuch-config) will be used.
215  *
216  * If any error occurs, (out of memory, or a permission-denied error,
217  * etc.), this function will print a message to stderr and return
218  * NULL.
219  *
220  * FILE NOT FOUND: When the specified configuration file (whether from
221  * 'filename' or the $NOTMUCH_CONFIG environment variable) does not
222  * exist, the behavior of this function depends on the 'is_new_ret'
223  * variable.
224  *
225  *      If is_new_ret is NULL, then a "file not found" message will be
226  *      printed to stderr and NULL will be returned.
227  *
228  *      If is_new_ret is non-NULL then a default configuration will be
229  *      returned and *is_new_ret will be set to 1 on return so that
230  *      the caller can recognize this case.
231  *
232  *      These default configuration settings are determined as
233  *      follows:
234  *
235  *              database_path:          $MAILDIR, otherwise $HOME/mail
236  *
237  *              user_name:              $NAME variable if set, otherwise
238  *                                      read from /etc/passwd
239  *
240  *              user_primary_mail:      $EMAIL variable if set, otherwise
241  *                                      constructed from the username and
242  *                                      hostname of the current machine.
243  *
244  *              user_other_email:       Not set.
245  *
246  *      The default configuration also contains comments to guide the
247  *      user in editing the file directly.
248  */
249 notmuch_conffile_t *
250 notmuch_conffile_open (notmuch_database_t *notmuch,
251                        const char *filename,
252                        bool create)
253 {
254     char *notmuch_config_env = NULL;
255
256     notmuch_conffile_t *config = talloc_zero (notmuch, notmuch_conffile_t);
257
258     if (config == NULL) {
259         fprintf (stderr, "Out of memory.\n");
260         return NULL;
261     }
262
263     talloc_set_destructor (config, notmuch_conffile_destructor);
264
265     if (filename) {
266         config->filename = talloc_strdup (config, filename);
267     } else if ((notmuch_config_env = getenv ("NOTMUCH_CONFIG"))) {
268         config->filename = talloc_strdup (config, notmuch_config_env);
269     } else {
270         config->filename = talloc_asprintf (config, "%s/.notmuch-config",
271                                             getenv ("HOME"));
272     }
273
274     config->key_file = g_key_file_new ();
275
276     if (! get_config_from_file (config, create)) {
277         talloc_free (config);
278         return NULL;
279     }
280
281     for (size_t i = 0; i < ARRAY_SIZE (group_comment_table); i++) {
282         const char *name = group_comment_table[i].group_name;
283         if (! g_key_file_has_group (config->key_file,  name)) {
284             /* Force group to exist before adding comment */
285             g_key_file_set_value (config->key_file, name, "dummy_key", "dummy_val");
286             g_key_file_remove_key (config->key_file, name, "dummy_key", NULL);
287             if (config->is_new && (i == 0) ) {
288                 const char *comment;
289
290                 comment = talloc_asprintf (config, "%s\n%s",
291                                            toplevel_config_comment,
292                                            group_comment_table[i].comment);
293                 g_key_file_set_comment (config->key_file, name, NULL, comment,
294                                         NULL);
295             } else {
296                 g_key_file_set_comment (config->key_file, name, NULL,
297                                         group_comment_table[i].comment, NULL);
298             }
299         }
300     }
301     return config;
302 }
303
304 /* Close the given notmuch_conffile_t object, freeing all resources.
305  *
306  * Note: Any changes made to the configuration are *not* saved by this
307  * function. To save changes, call notmuch_conffile_save before
308  * notmuch_conffile_close.
309  */
310 void
311 notmuch_conffile_close (notmuch_conffile_t *config)
312 {
313     talloc_free (config);
314 }
315
316 /* Save any changes made to the notmuch configuration.
317  *
318  * Any comments originally in the file will be preserved.
319  *
320  * Returns 0 if successful, and 1 in case of any error, (after
321  * printing a description of the error to stderr).
322  */
323 int
324 notmuch_conffile_save (notmuch_conffile_t *config)
325 {
326     size_t length;
327     char *data, *filename;
328     GError *error = NULL;
329
330     data = g_key_file_to_data (config->key_file, &length, NULL);
331     if (data == NULL) {
332         fprintf (stderr, "Out of memory.\n");
333         return 1;
334     }
335
336     /* Try not to overwrite symlinks. */
337     filename = notmuch_canonicalize_file_name (config->filename);
338     if (! filename) {
339         if (errno == ENOENT) {
340             filename = strdup (config->filename);
341             if (! filename) {
342                 fprintf (stderr, "Out of memory.\n");
343                 g_free (data);
344                 return 1;
345             }
346         } else {
347             fprintf (stderr, "Error canonicalizing %s: %s\n", config->filename,
348                      strerror (errno));
349             g_free (data);
350             return 1;
351         }
352     }
353
354     if (! g_file_set_contents (filename, data, length, &error)) {
355         if (strcmp (filename, config->filename) != 0) {
356             fprintf (stderr, "Error saving configuration to %s (-> %s): %s\n",
357                      config->filename, filename, error->message);
358         } else {
359             fprintf (stderr, "Error saving configuration to %s: %s\n",
360                      filename, error->message);
361         }
362         g_error_free (error);
363         free (filename);
364         g_free (data);
365         return 1;
366     }
367
368     free (filename);
369     g_free (data);
370     return 0;
371 }
372
373 bool
374 notmuch_conffile_is_new (notmuch_conffile_t *config)
375 {
376     return config->is_new;
377 }
378
379 static void
380 _config_set (notmuch_conffile_t *config,
381              const char *group, const char *key, const char *value)
382 {
383     g_key_file_set_string (config->key_file, group, key, value);
384 }
385
386 static void
387 _config_set_list (notmuch_conffile_t *config,
388                   const char *group, const char *key,
389                   const char *list[],
390                   size_t length)
391 {
392     if (length > 1)
393         g_key_file_set_string_list (config->key_file, group, key, list, length);
394     else
395         g_key_file_set_string (config->key_file, group, key, list[0]);
396 }
397
398 void
399 notmuch_conffile_set_database_path (notmuch_conffile_t *config,
400                                     const char *database_path)
401 {
402     _config_set (config, "database", "path", database_path);
403 }
404
405 void
406 notmuch_conffile_set_user_name (notmuch_conffile_t *config,
407                                 const char *user_name)
408 {
409     _config_set (config, "user", "name", user_name);
410 }
411
412 void
413 notmuch_conffile_set_user_primary_email (notmuch_conffile_t *config,
414                                          const char *primary_email)
415 {
416     _config_set (config, "user", "primary_email", primary_email);
417 }
418
419 void
420 notmuch_conffile_set_user_other_email (notmuch_conffile_t *config,
421                                        const char *list[],
422                                        size_t length)
423 {
424     _config_set_list (config, "user", "other_email", list, length);
425 }
426
427 void
428 notmuch_conffile_set_new_tags (notmuch_conffile_t *config,
429                                const char *list[],
430                                size_t length)
431 {
432     _config_set_list (config, "new", "tags", list, length);
433 }
434
435 void
436 notmuch_conffile_set_new_ignore (notmuch_conffile_t *config,
437                                  const char *list[],
438                                  size_t length)
439 {
440     _config_set_list (config, "new", "ignore", list, length);
441 }
442
443 void
444 notmuch_conffile_set_search_exclude_tags (notmuch_conffile_t *config,
445                                           const char *list[],
446                                           size_t length)
447 {
448     _config_set_list (config, "search", "exclude_tags", list, length);
449 }
450
451
452 /* Given a configuration item of the form <group>.<key> return the
453  * component group and key. If any error occurs, print a message on
454  * stderr and return 1. Otherwise, return 0.
455  *
456  * Note: This function modifies the original 'item' string.
457  */
458 static int
459 _item_split (char *item, char **group, char **key)
460 {
461     char *period;
462
463     *group = item;
464
465     period = strchr (item, '.');
466     if (period == NULL || *(period + 1) == '\0') {
467         fprintf (stderr,
468                  "Invalid configuration name: %s\n"
469                  "(Should be of the form <section>.<item>)\n", item);
470         return 1;
471     }
472
473     *period = '\0';
474     *key = period + 1;
475
476     return 0;
477 }
478
479 /* These are more properly called Xapian fields, but the user facing
480  * docs call them prefixes, so make the error message match */
481 static bool
482 validate_field_name (const char *str)
483 {
484     const char *key;
485
486     if (! g_utf8_validate (str, -1, NULL)) {
487         fprintf (stderr, "Invalid utf8: %s\n", str);
488         return false;
489     }
490
491     key = g_utf8_strrchr (str, -1, '.');
492     if (! key ) {
493         INTERNAL_ERROR ("Impossible code path on input: %s\n", str);
494     }
495
496     key++;
497
498     if (! *key) {
499         fprintf (stderr, "Empty prefix name: %s\n", str);
500         return false;
501     }
502
503     if (! unicode_word_utf8 (key)) {
504         fprintf (stderr, "Non-word character in prefix name: %s\n", key);
505         return false;
506     }
507
508     if (key[0] >= 'a' && key[0] <= 'z') {
509         fprintf (stderr, "Prefix names starting with lower case letters are reserved: %s\n", key);
510         return false;
511     }
512
513     return true;
514 }
515
516 #define BUILT_WITH_PREFIX "built_with."
517
518 typedef struct config_key {
519     const char *name;
520     bool prefix;
521     bool (*validate)(const char *);
522 } config_key_info_t;
523
524 static const struct config_key
525     config_key_table[] = {
526     { "index.decrypt",   false,  NULL },
527     { "index.header.",   true,   validate_field_name },
528     { "query.",          true,   NULL },
529     { "squery.",         true,   validate_field_name },
530 };
531
532 static const config_key_info_t *
533 _config_key_info (const char *item)
534 {
535     for (size_t i = 0; i < ARRAY_SIZE (config_key_table); i++) {
536         if (config_key_table[i].prefix &&
537             strncmp (item, config_key_table[i].name,
538                      strlen (config_key_table[i].name)) == 0)
539             return config_key_table + i;
540         if (strcmp (item, config_key_table[i].name) == 0)
541             return config_key_table + i;
542     }
543     return NULL;
544 }
545
546 static int
547 notmuch_config_command_get (notmuch_database_t *notmuch, char *item)
548 {
549     notmuch_config_values_t *list;
550
551     if (STRNCMP_LITERAL (item, BUILT_WITH_PREFIX) == 0) {
552         if (notmuch_built_with (item + strlen (BUILT_WITH_PREFIX)))
553             puts ("true");
554         else
555             puts ("false");
556     } else {
557         for (list = notmuch_config_get_values_string (notmuch, item);
558              notmuch_config_values_valid (list);
559              notmuch_config_values_move_to_next (list)) {
560             const char *val = notmuch_config_values_get (list);
561             puts (val);
562         }
563     }
564     return EXIT_SUCCESS;
565 }
566
567 static int
568 _set_db_config (notmuch_database_t *notmuch, const char *key, int argc, char **argv)
569 {
570     const char *val = "";
571
572     if (argc > 1) {
573         /* XXX handle lists? */
574         fprintf (stderr, "notmuch config set: at most one value expected for %s\n", key);
575         return EXIT_FAILURE;
576     }
577
578     if (argc > 0) {
579         val = argv[0];
580     }
581
582     if (print_status_database ("notmuch config", notmuch,
583                                notmuch_database_reopen (notmuch,
584                                                         NOTMUCH_DATABASE_MODE_READ_WRITE)))
585         return EXIT_FAILURE;
586
587     if (print_status_database ("notmuch config", notmuch,
588                                notmuch_database_set_config (notmuch, key, val)))
589         return EXIT_FAILURE;
590
591     if (print_status_database ("notmuch config", notmuch,
592                                notmuch_database_close (notmuch)))
593         return EXIT_FAILURE;
594
595     return EXIT_SUCCESS;
596 }
597
598 static int
599 notmuch_config_command_set (notmuch_database_t *notmuch,
600                             int argc, char *argv[])
601 {
602     char *group, *key;
603     const config_key_info_t *key_info;
604     notmuch_conffile_t *config;
605     bool update_database = false;
606     int opt_index, ret;
607     char *item;
608
609     notmuch_opt_desc_t options[] = {
610         { .opt_bool = &update_database, .name = "database" },
611         { }
612     };
613
614     opt_index = parse_arguments (argc, argv, options, 1);
615     if (opt_index < 0)
616         return EXIT_FAILURE;
617
618     argc -= opt_index;
619     argv += opt_index;
620
621     if (argc < 1) {
622         fprintf (stderr, "Error: notmuch config set requires at least "
623                  "one argument.\n");
624         return EXIT_FAILURE;
625     }
626
627     item = argv[0];
628     argv++;
629     argc--;
630
631     if (STRNCMP_LITERAL (item, BUILT_WITH_PREFIX) == 0) {
632         fprintf (stderr, "Error: read only option: %s\n", item);
633         return 1;
634     }
635
636     key_info = _config_key_info (item);
637     if (key_info && key_info->validate && (! key_info->validate (item)))
638         return 1;
639
640     if (update_database) {
641         return _set_db_config (notmuch, item, argc, argv);
642     }
643
644     if (_item_split (item, &group, &key))
645         return 1;
646
647     config = notmuch_conffile_open (notmuch,
648                                     notmuch_config_path (notmuch), false);
649     if (! config)
650         return 1;
651
652     /* With only the name of an item, we clear it from the
653      * configuration file.
654      *
655      * With a single value, we set it as a string.
656      *
657      * With multiple values, we set them as a string list.
658      */
659     switch (argc) {
660     case 0:
661         g_key_file_remove_key (config->key_file, group, key, NULL);
662         break;
663     case 1:
664         g_key_file_set_string (config->key_file, group, key, argv[0]);
665         break;
666     default:
667         g_key_file_set_string_list (config->key_file, group, key,
668                                     (const gchar **) argv, argc);
669         break;
670     }
671
672     ret = notmuch_conffile_save (config);
673
674     notmuch_conffile_close (config);
675
676     return ret;
677 }
678
679 static
680 void
681 _notmuch_config_list_built_with ()
682 {
683     printf ("%scompact=%s\n",
684             BUILT_WITH_PREFIX,
685             notmuch_built_with ("compact") ? "true" : "false");
686     printf ("%sfield_processor=%s\n",
687             BUILT_WITH_PREFIX,
688             notmuch_built_with ("field_processor") ? "true" : "false");
689     printf ("%sretry_lock=%s\n",
690             BUILT_WITH_PREFIX,
691             notmuch_built_with ("retry_lock") ? "true" : "false");
692     printf ("%ssexp_queries=%s\n",
693             BUILT_WITH_PREFIX,
694             notmuch_built_with ("sexp_queries") ? "true" : "false");
695 }
696
697 static int
698 notmuch_config_command_list (notmuch_database_t *notmuch)
699 {
700     notmuch_config_pairs_t *list;
701
702     _notmuch_config_list_built_with ();
703     for (list = notmuch_config_get_pairs (notmuch, "");
704          notmuch_config_pairs_valid (list);
705          notmuch_config_pairs_move_to_next (list)) {
706         const char *value = notmuch_config_pairs_value (list);
707         if (value)
708             printf ("%s=%s\n", notmuch_config_pairs_key (list), value);
709     }
710     notmuch_config_pairs_destroy (list);
711     return EXIT_SUCCESS;
712 }
713
714 int
715 notmuch_config_command (notmuch_database_t *notmuch, int argc, char *argv[])
716 {
717     int ret;
718     int opt_index;
719
720     opt_index = notmuch_minimal_options ("config", argc, argv);
721     if (opt_index < 0)
722         return EXIT_FAILURE;
723
724     /* skip at least subcommand argument */
725     argc -= opt_index;
726     argv += opt_index;
727
728     if (argc < 1) {
729         fprintf (stderr, "Error: notmuch config requires at least one argument.\n");
730         return EXIT_FAILURE;
731     }
732
733     if (strcmp (argv[0], "get") == 0) {
734         if (argc != 2) {
735             fprintf (stderr, "Error: notmuch config get requires exactly "
736                      "one argument.\n");
737             return EXIT_FAILURE;
738         }
739         ret = notmuch_config_command_get (notmuch, argv[1]);
740     } else if (strcmp (argv[0], "set") == 0) {
741         ret = notmuch_config_command_set (notmuch, argc, argv);
742     } else if (strcmp (argv[0], "list") == 0) {
743         ret = notmuch_config_command_list (notmuch);
744     } else {
745         fprintf (stderr, "Unrecognized argument for notmuch config: %s\n",
746                  argv[0]);
747         return EXIT_FAILURE;
748     }
749
750     return ret ? EXIT_FAILURE : EXIT_SUCCESS;
751
752 }
753
754 void
755 notmuch_conffile_set_maildir_synchronize_flags (notmuch_conffile_t *config,
756                                                 bool synchronize_flags)
757 {
758     g_key_file_set_boolean (config->key_file,
759                             "maildir", "synchronize_flags", synchronize_flags);
760 }