]> git.cworth.org Git - notmuch/blob - notmuch-config.c
CLI/setup: special case single item lists
[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     if (config->is_new)
282         g_key_file_set_comment (config->key_file, NULL, NULL,
283                                 toplevel_config_comment, NULL);
284
285     for (size_t i = 0; i < ARRAY_SIZE (group_comment_table); i++) {
286         const char *name = group_comment_table[i].group_name;
287         if (! g_key_file_has_group (config->key_file,  name)) {
288             /* Force group to exist before adding comment */
289             g_key_file_set_value (config->key_file, name, "dummy_key", "dummy_val");
290             g_key_file_remove_key (config->key_file, name, "dummy_key", NULL);
291             g_key_file_set_comment (config->key_file, name, NULL,
292                                     group_comment_table[i].comment, NULL);
293         }
294     }
295     return config;
296 }
297
298 /* Close the given notmuch_conffile_t object, freeing all resources.
299  *
300  * Note: Any changes made to the configuration are *not* saved by this
301  * function. To save changes, call notmuch_conffile_save before
302  * notmuch_conffile_close.
303  */
304 void
305 notmuch_conffile_close (notmuch_conffile_t *config)
306 {
307     talloc_free (config);
308 }
309
310 /* Save any changes made to the notmuch configuration.
311  *
312  * Any comments originally in the file will be preserved.
313  *
314  * Returns 0 if successful, and 1 in case of any error, (after
315  * printing a description of the error to stderr).
316  */
317 int
318 notmuch_conffile_save (notmuch_conffile_t *config)
319 {
320     size_t length;
321     char *data, *filename;
322     GError *error = NULL;
323
324     data = g_key_file_to_data (config->key_file, &length, NULL);
325     if (data == NULL) {
326         fprintf (stderr, "Out of memory.\n");
327         return 1;
328     }
329
330     /* Try not to overwrite symlinks. */
331     filename = notmuch_canonicalize_file_name (config->filename);
332     if (! filename) {
333         if (errno == ENOENT) {
334             filename = strdup (config->filename);
335             if (! filename) {
336                 fprintf (stderr, "Out of memory.\n");
337                 g_free (data);
338                 return 1;
339             }
340         } else {
341             fprintf (stderr, "Error canonicalizing %s: %s\n", config->filename,
342                      strerror (errno));
343             g_free (data);
344             return 1;
345         }
346     }
347
348     if (! g_file_set_contents (filename, data, length, &error)) {
349         if (strcmp (filename, config->filename) != 0) {
350             fprintf (stderr, "Error saving configuration to %s (-> %s): %s\n",
351                      config->filename, filename, error->message);
352         } else {
353             fprintf (stderr, "Error saving configuration to %s: %s\n",
354                      filename, error->message);
355         }
356         g_error_free (error);
357         free (filename);
358         g_free (data);
359         return 1;
360     }
361
362     free (filename);
363     g_free (data);
364     return 0;
365 }
366
367 bool
368 notmuch_conffile_is_new (notmuch_conffile_t *config)
369 {
370     return config->is_new;
371 }
372
373 static void
374 _config_set (notmuch_conffile_t *config,
375              const char *group, const char *key, const char *value)
376 {
377     g_key_file_set_string (config->key_file, group, key, value);
378 }
379
380 static void
381 _config_set_list (notmuch_conffile_t *config,
382                   const char *group, const char *key,
383                   const char *list[],
384                   size_t length)
385 {
386     if (length > 1)
387         g_key_file_set_string_list (config->key_file, group, key, list, length);
388     else
389         g_key_file_set_string (config->key_file, group, key, list[0]);
390 }
391
392 void
393 notmuch_conffile_set_database_path (notmuch_conffile_t *config,
394                                     const char *database_path)
395 {
396     _config_set (config, "database", "path", database_path);
397 }
398
399 void
400 notmuch_conffile_set_user_name (notmuch_conffile_t *config,
401                                 const char *user_name)
402 {
403     _config_set (config, "user", "name", user_name);
404 }
405
406 void
407 notmuch_conffile_set_user_primary_email (notmuch_conffile_t *config,
408                                          const char *primary_email)
409 {
410     _config_set (config, "user", "primary_email", primary_email);
411 }
412
413 void
414 notmuch_conffile_set_user_other_email (notmuch_conffile_t *config,
415                                        const char *list[],
416                                        size_t length)
417 {
418     _config_set_list (config, "user", "other_email", list, length);
419 }
420
421 void
422 notmuch_conffile_set_new_tags (notmuch_conffile_t *config,
423                                const char *list[],
424                                size_t length)
425 {
426     _config_set_list (config, "new", "tags", list, length);
427 }
428
429 void
430 notmuch_conffile_set_new_ignore (notmuch_conffile_t *config,
431                                  const char *list[],
432                                  size_t length)
433 {
434     _config_set_list (config, "new", "ignore", list, length);
435 }
436
437 void
438 notmuch_conffile_set_search_exclude_tags (notmuch_conffile_t *config,
439                                           const char *list[],
440                                           size_t length)
441 {
442     _config_set_list (config, "search", "exclude_tags", list, length);
443 }
444
445
446 /* Given a configuration item of the form <group>.<key> return the
447  * component group and key. If any error occurs, print a message on
448  * stderr and return 1. Otherwise, return 0.
449  *
450  * Note: This function modifies the original 'item' string.
451  */
452 static int
453 _item_split (char *item, char **group, char **key)
454 {
455     char *period;
456
457     *group = item;
458
459     period = strchr (item, '.');
460     if (period == NULL || *(period + 1) == '\0') {
461         fprintf (stderr,
462                  "Invalid configuration name: %s\n"
463                  "(Should be of the form <section>.<item>)\n", item);
464         return 1;
465     }
466
467     *period = '\0';
468     *key = period + 1;
469
470     return 0;
471 }
472
473 /* These are more properly called Xapian fields, but the user facing
474  * docs call them prefixes, so make the error message match */
475 static bool
476 validate_field_name (const char *str)
477 {
478     const char *key;
479
480     if (! g_utf8_validate (str, -1, NULL)) {
481         fprintf (stderr, "Invalid utf8: %s\n", str);
482         return false;
483     }
484
485     key = g_utf8_strrchr (str, -1, '.');
486     if (! key ) {
487         INTERNAL_ERROR ("Impossible code path on input: %s\n", str);
488     }
489
490     key++;
491
492     if (! *key) {
493         fprintf (stderr, "Empty prefix name: %s\n", str);
494         return false;
495     }
496
497     if (! unicode_word_utf8 (key)) {
498         fprintf (stderr, "Non-word character in prefix name: %s\n", key);
499         return false;
500     }
501
502     if (key[0] >= 'a' && key[0] <= 'z') {
503         fprintf (stderr, "Prefix names starting with lower case letters are reserved: %s\n", key);
504         return false;
505     }
506
507     return true;
508 }
509
510 #define BUILT_WITH_PREFIX "built_with."
511
512 typedef struct config_key {
513     const char *name;
514     bool prefix;
515     bool (*validate)(const char *);
516 } config_key_info_t;
517
518 static const struct config_key
519     config_key_table[] = {
520     { "index.decrypt",   false,  NULL },
521     { "index.header.",   true,   validate_field_name },
522     { "query.",          true,   NULL },
523     { "squery.",         true,   validate_field_name },
524 };
525
526 static const config_key_info_t *
527 _config_key_info (const char *item)
528 {
529     for (size_t i = 0; i < ARRAY_SIZE (config_key_table); i++) {
530         if (config_key_table[i].prefix &&
531             strncmp (item, config_key_table[i].name,
532                      strlen (config_key_table[i].name)) == 0)
533             return config_key_table + i;
534         if (strcmp (item, config_key_table[i].name) == 0)
535             return config_key_table + i;
536     }
537     return NULL;
538 }
539
540 static int
541 notmuch_config_command_get (notmuch_database_t *notmuch, char *item)
542 {
543     notmuch_config_values_t *list;
544
545     if (STRNCMP_LITERAL (item, BUILT_WITH_PREFIX) == 0) {
546         if (notmuch_built_with (item + strlen (BUILT_WITH_PREFIX)))
547             puts ("true");
548         else
549             puts ("false");
550     } else {
551         for (list = notmuch_config_get_values_string (notmuch, item);
552              notmuch_config_values_valid (list);
553              notmuch_config_values_move_to_next (list)) {
554             const char *val = notmuch_config_values_get (list);
555             puts (val);
556         }
557     }
558     return EXIT_SUCCESS;
559 }
560
561 static int
562 _set_db_config (notmuch_database_t *notmuch, const char *key, int argc, char **argv)
563 {
564     const char *val = "";
565
566     if (argc > 1) {
567         /* XXX handle lists? */
568         fprintf (stderr, "notmuch config set: at most one value expected for %s\n", key);
569         return EXIT_FAILURE;
570     }
571
572     if (argc > 0) {
573         val = argv[0];
574     }
575
576     if (print_status_database ("notmuch config", notmuch,
577                                notmuch_database_reopen (notmuch,
578                                                         NOTMUCH_DATABASE_MODE_READ_WRITE)))
579         return EXIT_FAILURE;
580
581     if (print_status_database ("notmuch config", notmuch,
582                                notmuch_database_set_config (notmuch, key, val)))
583         return EXIT_FAILURE;
584
585     if (print_status_database ("notmuch config", notmuch,
586                                notmuch_database_close (notmuch)))
587         return EXIT_FAILURE;
588
589     return EXIT_SUCCESS;
590 }
591
592 static int
593 notmuch_config_command_set (notmuch_database_t *notmuch,
594                             int argc, char *argv[])
595 {
596     char *group, *key;
597     const config_key_info_t *key_info;
598     notmuch_conffile_t *config;
599     bool update_database = false;
600     int opt_index, ret;
601     char *item;
602
603     notmuch_opt_desc_t options[] = {
604         { .opt_bool = &update_database, .name = "database" },
605         { }
606     };
607
608     opt_index = parse_arguments (argc, argv, options, 1);
609     if (opt_index < 0)
610         return EXIT_FAILURE;
611
612     argc -= opt_index;
613     argv += opt_index;
614
615     if (argc < 1) {
616         fprintf (stderr, "Error: notmuch config set requires at least "
617                  "one argument.\n");
618         return EXIT_FAILURE;
619     }
620
621     item = argv[0];
622     argv++;
623     argc--;
624
625     if (STRNCMP_LITERAL (item, BUILT_WITH_PREFIX) == 0) {
626         fprintf (stderr, "Error: read only option: %s\n", item);
627         return 1;
628     }
629
630     key_info = _config_key_info (item);
631     if (key_info && key_info->validate && (! key_info->validate (item)))
632         return 1;
633
634     if (update_database) {
635         return _set_db_config (notmuch, item, argc, argv);
636     }
637
638     if (_item_split (item, &group, &key))
639         return 1;
640
641     config = notmuch_conffile_open (notmuch,
642                                     notmuch_config_path (notmuch), false);
643     if (! config)
644         return 1;
645
646     /* With only the name of an item, we clear it from the
647      * configuration file.
648      *
649      * With a single value, we set it as a string.
650      *
651      * With multiple values, we set them as a string list.
652      */
653     switch (argc) {
654     case 0:
655         g_key_file_remove_key (config->key_file, group, key, NULL);
656         break;
657     case 1:
658         g_key_file_set_string (config->key_file, group, key, argv[0]);
659         break;
660     default:
661         g_key_file_set_string_list (config->key_file, group, key,
662                                     (const gchar **) argv, argc);
663         break;
664     }
665
666     ret = notmuch_conffile_save (config);
667
668     notmuch_conffile_close (config);
669
670     return ret;
671 }
672
673 static
674 void
675 _notmuch_config_list_built_with ()
676 {
677     printf ("%scompact=%s\n",
678             BUILT_WITH_PREFIX,
679             notmuch_built_with ("compact") ? "true" : "false");
680     printf ("%sfield_processor=%s\n",
681             BUILT_WITH_PREFIX,
682             notmuch_built_with ("field_processor") ? "true" : "false");
683     printf ("%sretry_lock=%s\n",
684             BUILT_WITH_PREFIX,
685             notmuch_built_with ("retry_lock") ? "true" : "false");
686     printf ("%ssexp_queries=%s\n",
687             BUILT_WITH_PREFIX,
688             notmuch_built_with ("sexp_queries") ? "true" : "false");
689 }
690
691 static int
692 notmuch_config_command_list (notmuch_database_t *notmuch)
693 {
694     notmuch_config_pairs_t *list;
695
696     _notmuch_config_list_built_with ();
697     for (list = notmuch_config_get_pairs (notmuch, "");
698          notmuch_config_pairs_valid (list);
699          notmuch_config_pairs_move_to_next (list)) {
700         const char *value = notmuch_config_pairs_value (list);
701         if (value)
702             printf ("%s=%s\n", notmuch_config_pairs_key (list), value);
703     }
704     notmuch_config_pairs_destroy (list);
705     return EXIT_SUCCESS;
706 }
707
708 int
709 notmuch_config_command (notmuch_database_t *notmuch, int argc, char *argv[])
710 {
711     int ret;
712     int opt_index;
713
714     opt_index = notmuch_minimal_options ("config", argc, argv);
715     if (opt_index < 0)
716         return EXIT_FAILURE;
717
718     /* skip at least subcommand argument */
719     argc -= opt_index;
720     argv += opt_index;
721
722     if (argc < 1) {
723         fprintf (stderr, "Error: notmuch config requires at least one argument.\n");
724         return EXIT_FAILURE;
725     }
726
727     if (strcmp (argv[0], "get") == 0) {
728         if (argc != 2) {
729             fprintf (stderr, "Error: notmuch config get requires exactly "
730                      "one argument.\n");
731             return EXIT_FAILURE;
732         }
733         ret = notmuch_config_command_get (notmuch, argv[1]);
734     } else if (strcmp (argv[0], "set") == 0) {
735         ret = notmuch_config_command_set (notmuch, argc, argv);
736     } else if (strcmp (argv[0], "list") == 0) {
737         ret = notmuch_config_command_list (notmuch);
738     } else {
739         fprintf (stderr, "Unrecognized argument for notmuch config: %s\n",
740                  argv[0]);
741         return EXIT_FAILURE;
742     }
743
744     return ret ? EXIT_FAILURE : EXIT_SUCCESS;
745
746 }
747
748 void
749 notmuch_conffile_set_maildir_synchronize_flags (notmuch_conffile_t *config,
750                                                 bool synchronize_flags)
751 {
752     g_key_file_set_boolean (config->key_file,
753                             "maildir", "synchronize_flags", synchronize_flags);
754 }