]> git.cworth.org Git - notmuch/blob - notmuch-config.c
CLI/config: drop cached data from notmuch_config_t
[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 "unicode-util.h"
28
29 static const char toplevel_config_comment[] =
30     " .notmuch-config - Configuration file for the notmuch mail system\n"
31     "\n"
32     " For more information about notmuch, see https://notmuchmail.org";
33
34 struct config_group {
35     const char *group_name;
36     const char *comment;
37 } group_comment_table [] = {
38     {
39         "database",
40         " Database configuration\n"
41         "\n"
42         " The only value supported here is 'path' which should be the top-level\n"
43         " directory where your mail currently exists and to where mail will be\n"
44         " delivered in the future. Files should be individual email messages.\n"
45         " Notmuch will store its database within a sub-directory of the path\n"
46         " configured here named \".notmuch\".\n"
47     },
48     {
49         "user",
50         " User configuration\n"
51         "\n"
52         " Here is where you can let notmuch know how you would like to be\n"
53         " addressed. Valid settings are\n"
54         "\n"
55         "\tname         Your full name.\n"
56         "\tprimary_email        Your primary email address.\n"
57         "\tother_email  A list (separated by ';') of other email addresses\n"
58         "\t             at which you receive email.\n"
59         "\n"
60         " Notmuch will use the various email addresses configured here when\n"
61         " formatting replies. It will avoid including your own addresses in the\n"
62         " recipient list of replies, and will set the From address based on the\n"
63         " address to which the original email was addressed.\n"
64     },
65     {
66         "new",
67         " Configuration for \"notmuch new\"\n"
68         "\n"
69         " The following options are supported here:\n"
70         "\n"
71         "\ttags A list (separated by ';') of the tags that will be\n"
72         "\t     added to all messages incorporated by \"notmuch new\".\n"
73         "\n"
74         "\tignore       A list (separated by ';') of file and directory names\n"
75         "\t     that will not be searched for messages by \"notmuch new\".\n"
76         "\n"
77         "\t     NOTE: *Every* file/directory that goes by one of those\n"
78         "\t     names will be ignored, independent of its depth/location\n"
79         "\t     in the mail store.\n"
80     },
81     {
82         "search",
83         " Search configuration\n"
84         "\n"
85         " The following option is supported here:\n"
86         "\n"
87         "\texclude_tags\n"
88         "\t\tA ;-separated list of tags that will be excluded from\n"
89         "\t\tsearch results by default.  Using an excluded tag in a\n"
90         "\t\tquery will override that exclusion.\n"
91     },
92     {
93         "maildir",
94         " Maildir compatibility configuration\n"
95         "\n"
96         " The following option is supported here:\n"
97         "\n"
98         "\tsynchronize_flags      Valid values are true and false.\n"
99         "\n"
100         "\tIf true, then the following maildir flags (in message filenames)\n"
101         "\twill be synchronized with the corresponding notmuch tags:\n"
102         "\n"
103         "\t\tFlag       Tag\n"
104         "\t\t----       -------\n"
105         "\t\tD  draft\n"
106         "\t\tF  flagged\n"
107         "\t\tP  passed\n"
108         "\t\tR  replied\n"
109         "\t\tS  unread (added when 'S' flag is not present)\n"
110         "\n"
111         "\tThe \"notmuch new\" command will notice flag changes in filenames\n"
112         "\tand update tags, while the \"notmuch tag\" and \"notmuch restore\"\n"
113         "\tcommands will notice tag changes and update flags in filenames\n"
114     },
115 };
116
117 struct _notmuch_config {
118     char *filename;
119     GKeyFile *key_file;
120     bool is_new;
121 };
122
123 static int
124 notmuch_config_destructor (notmuch_config_t *config)
125 {
126     if (config->key_file)
127         g_key_file_free (config->key_file);
128
129     return 0;
130 }
131
132 static bool
133 get_config_from_file (notmuch_config_t *config, bool create_new)
134 {
135     #define BUF_SIZE 4096
136     char *config_str = NULL;
137     int config_len = 0;
138     int config_bufsize = BUF_SIZE;
139     size_t len;
140     GError *error = NULL;
141     bool ret = false;
142
143     FILE *fp = fopen (config->filename, "r");
144     if (fp == NULL) {
145         if (errno == ENOENT) {
146             /* If create_new is true, then the caller is prepared for a
147              * default configuration file in the case of FILE NOT FOUND.
148              */
149             if (create_new) {
150                 config->is_new = true;
151                 ret = true;
152             } else {
153                 fprintf (stderr, "Configuration file %s not found.\n"
154                          "Try running 'notmuch setup' to create a configuration.\n",
155                          config->filename);
156             }
157         } else {
158             fprintf (stderr, "Error opening config file '%s': %s\n",
159                      config->filename, strerror (errno));
160         }
161         goto out;
162     }
163
164     config_str = talloc_zero_array (config, char, config_bufsize);
165     if (config_str == NULL) {
166         fprintf (stderr, "Error reading '%s': Out of memory\n", config->filename);
167         goto out;
168     }
169
170     while ((len = fread (config_str + config_len, 1,
171                          config_bufsize - config_len, fp)) > 0) {
172         config_len += len;
173         if (config_len == config_bufsize) {
174             config_bufsize += BUF_SIZE;
175             config_str = talloc_realloc (config, config_str, char, config_bufsize);
176             if (config_str == NULL) {
177                 fprintf (stderr, "Error reading '%s': Failed to reallocate memory\n",
178                          config->filename);
179                 goto out;
180             }
181         }
182     }
183
184     if (ferror (fp)) {
185         fprintf (stderr, "Error reading '%s': I/O error\n", config->filename);
186         goto out;
187     }
188
189     if (g_key_file_load_from_data (config->key_file, config_str, config_len,
190                                    G_KEY_FILE_KEEP_COMMENTS, &error)) {
191         ret = true;
192         goto out;
193     }
194
195     fprintf (stderr, "Error parsing config file '%s': %s\n",
196              config->filename, error->message);
197
198     g_error_free (error);
199
200   out:
201     if (fp)
202         fclose (fp);
203
204     if (config_str)
205         talloc_free (config_str);
206
207     return ret;
208 }
209
210 /* Open the named notmuch configuration file. If the filename is NULL,
211  * the value of the environment variable $NOTMUCH_CONFIG will be used.
212  * If $NOTMUCH_CONFIG is unset, the default configuration file
213  * ($HOME/.notmuch-config) will be used.
214  *
215  * If any error occurs, (out of memory, or a permission-denied error,
216  * etc.), this function will print a message to stderr and return
217  * NULL.
218  *
219  * FILE NOT FOUND: When the specified configuration file (whether from
220  * 'filename' or the $NOTMUCH_CONFIG environment variable) does not
221  * exist, the behavior of this function depends on the 'is_new_ret'
222  * variable.
223  *
224  *      If is_new_ret is NULL, then a "file not found" message will be
225  *      printed to stderr and NULL will be returned.
226  *
227  *      If is_new_ret is non-NULL then a default configuration will be
228  *      returned and *is_new_ret will be set to 1 on return so that
229  *      the caller can recognize this case.
230  *
231  *      These default configuration settings are determined as
232  *      follows:
233  *
234  *              database_path:          $MAILDIR, otherwise $HOME/mail
235  *
236  *              user_name:              $NAME variable if set, otherwise
237  *                                      read from /etc/passwd
238  *
239  *              user_primary_mail:      $EMAIL variable if set, otherwise
240  *                                      constructed from the username and
241  *                                      hostname of the current machine.
242  *
243  *              user_other_email:       Not set.
244  *
245  *      The default configuration also contains comments to guide the
246  *      user in editing the file directly.
247  */
248 notmuch_config_t *
249 notmuch_config_open (notmuch_database_t *notmuch,
250                      const char *filename,
251                      notmuch_command_mode_t config_mode)
252 {
253     char *notmuch_config_env = NULL;
254
255     notmuch_config_t *config = talloc_zero (notmuch, notmuch_config_t);
256
257     if (config == NULL) {
258         fprintf (stderr, "Out of memory.\n");
259         return NULL;
260     }
261
262     talloc_set_destructor (config, notmuch_config_destructor);
263
264     if (filename) {
265         config->filename = talloc_strdup (config, filename);
266     } else if ((notmuch_config_env = getenv ("NOTMUCH_CONFIG"))) {
267         config->filename = talloc_strdup (config, notmuch_config_env);
268     } else {
269         config->filename = talloc_asprintf (config, "%s/.notmuch-config",
270                                             getenv ("HOME"));
271     }
272
273     config->key_file = g_key_file_new ();
274
275     if (config_mode & NOTMUCH_COMMAND_CONFIG_OPEN) {
276         bool create_new = (config_mode & NOTMUCH_COMMAND_CONFIG_CREATE) != 0;
277
278         if (! get_config_from_file (config, create_new)) {
279             talloc_free (config);
280             return NULL;
281         }
282     }
283
284     if (config->is_new)
285         g_key_file_set_comment (config->key_file, NULL, NULL,
286                                 toplevel_config_comment, NULL);
287
288     for (size_t i = 0; i < ARRAY_SIZE (group_comment_table); i++) {
289         const char *name = group_comment_table[i].group_name;
290         if (! g_key_file_has_group (config->key_file,  name)) {
291             /* Force group to exist before adding comment */
292             g_key_file_set_value (config->key_file, name, "dummy_key", "dummy_val");
293             g_key_file_remove_key (config->key_file, name, "dummy_key", NULL);
294             g_key_file_set_comment (config->key_file, name, NULL,
295                                     group_comment_table[i].comment, NULL);
296         }
297     }
298     return config;
299 }
300
301 /* Close the given notmuch_config_t object, freeing all resources.
302  *
303  * Note: Any changes made to the configuration are *not* saved by this
304  * function. To save changes, call notmuch_config_save before
305  * notmuch_config_close.
306  */
307 void
308 notmuch_config_close (notmuch_config_t *config)
309 {
310     talloc_free (config);
311 }
312
313 /* Save any changes made to the notmuch configuration.
314  *
315  * Any comments originally in the file will be preserved.
316  *
317  * Returns 0 if successful, and 1 in case of any error, (after
318  * printing a description of the error to stderr).
319  */
320 int
321 notmuch_config_save (notmuch_config_t *config)
322 {
323     size_t length;
324     char *data, *filename;
325     GError *error = NULL;
326
327     data = g_key_file_to_data (config->key_file, &length, NULL);
328     if (data == NULL) {
329         fprintf (stderr, "Out of memory.\n");
330         return 1;
331     }
332
333     /* Try not to overwrite symlinks. */
334     filename = canonicalize_file_name (config->filename);
335     if (! filename) {
336         if (errno == ENOENT) {
337             filename = strdup (config->filename);
338             if (! filename) {
339                 fprintf (stderr, "Out of memory.\n");
340                 g_free (data);
341                 return 1;
342             }
343         } else {
344             fprintf (stderr, "Error canonicalizing %s: %s\n", config->filename,
345                      strerror (errno));
346             g_free (data);
347             return 1;
348         }
349     }
350
351     if (! g_file_set_contents (filename, data, length, &error)) {
352         if (strcmp (filename, config->filename) != 0) {
353             fprintf (stderr, "Error saving configuration to %s (-> %s): %s\n",
354                      config->filename, filename, error->message);
355         } else {
356             fprintf (stderr, "Error saving configuration to %s: %s\n",
357                      filename, error->message);
358         }
359         g_error_free (error);
360         free (filename);
361         g_free (data);
362         return 1;
363     }
364
365     free (filename);
366     g_free (data);
367     return 0;
368 }
369
370 bool
371 notmuch_config_is_new (notmuch_config_t *config)
372 {
373     return config->is_new;
374 }
375
376 static void
377 _config_set (notmuch_config_t *config,
378              const char *group, const char *key, const char *value)
379 {
380     g_key_file_set_string (config->key_file, group, key, value);
381 }
382
383 static void
384 _config_set_list (notmuch_config_t *config,
385                   const char *group, const char *key,
386                   const char *list[],
387                   size_t length)
388 {
389     g_key_file_set_string_list (config->key_file, group, key, list, length);
390 }
391
392 void
393 notmuch_config_set_database_path (notmuch_config_t *config,
394                                   const char *database_path)
395 {
396     _config_set (config, "database", "path", database_path);
397 }
398
399 void
400 notmuch_config_set_user_name (notmuch_config_t *config,
401                               const char *user_name)
402 {
403     _config_set (config, "user", "name", user_name);
404 }
405
406 void
407 notmuch_config_set_user_primary_email (notmuch_config_t *config,
408                                        const char *primary_email)
409 {
410     _config_set (config, "user", "primary_email", primary_email);
411 }
412
413 void
414 notmuch_config_set_user_other_email (notmuch_config_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_config_set_new_tags (notmuch_config_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_config_set_new_ignore (notmuch_config_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_config_set_search_exclude_tags (notmuch_config_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 in_db;
515     bool prefix;
516     bool (*validate)(const char *);
517 } config_key_info_t;
518
519 static struct config_key
520     config_key_table[] = {
521     { "index.decrypt",   true,   false,  NULL },
522     { "index.header.",   true,   true,   validate_field_name },
523     { "query.",          true,   true,   NULL },
524 };
525
526 static 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     for (list = notmuch_config_get_values_string (notmuch, item);
546          notmuch_config_values_valid (list);
547          notmuch_config_values_move_to_next (list)) {
548         const char *val = notmuch_config_values_get (list);
549         puts (val);
550     }
551     return EXIT_SUCCESS;
552 }
553
554 static int
555 _set_db_config (notmuch_database_t *notmuch, const char *key, int argc, char **argv)
556 {
557     const char *val = "";
558
559     if (argc > 1) {
560         /* XXX handle lists? */
561         fprintf (stderr, "notmuch config set: at most one value expected for %s\n", key);
562         return EXIT_FAILURE;
563     }
564
565     if (argc > 0) {
566         val = argv[0];
567     }
568
569     if (print_status_database ("notmuch config", notmuch,
570                                notmuch_database_reopen (notmuch,
571                                                         NOTMUCH_DATABASE_MODE_READ_WRITE)))
572         return EXIT_FAILURE;
573
574     if (print_status_database ("notmuch config", notmuch,
575                                notmuch_database_set_config (notmuch, key, val)))
576         return EXIT_FAILURE;
577
578     if (print_status_database ("notmuch config", notmuch,
579                                notmuch_database_close (notmuch)))
580         return EXIT_FAILURE;
581
582     return EXIT_SUCCESS;
583 }
584
585 static int
586 notmuch_config_command_set (notmuch_config_t *config, notmuch_database_t *notmuch, char *item,
587                             int argc, char *argv[])
588 {
589     char *group, *key;
590     config_key_info_t *key_info;
591
592     if (STRNCMP_LITERAL (item, BUILT_WITH_PREFIX) == 0) {
593         fprintf (stderr, "Error: read only option: %s\n", item);
594         return 1;
595     }
596
597     key_info = _config_key_info (item);
598     if (key_info && key_info->validate && (! key_info->validate (item)))
599         return 1;
600
601     if (key_info && key_info->in_db) {
602         return _set_db_config (notmuch, item, argc, argv);
603     }
604
605     if (_item_split (item, &group, &key))
606         return 1;
607
608     /* With only the name of an item, we clear it from the
609      * configuration file.
610      *
611      * With a single value, we set it as a string.
612      *
613      * With multiple values, we set them as a string list.
614      */
615     switch (argc) {
616     case 0:
617         g_key_file_remove_key (config->key_file, group, key, NULL);
618         break;
619     case 1:
620         g_key_file_set_string (config->key_file, group, key, argv[0]);
621         break;
622     default:
623         g_key_file_set_string_list (config->key_file, group, key,
624                                     (const gchar **) argv, argc);
625         break;
626     }
627
628     return notmuch_config_save (config);
629 }
630
631 static
632 void
633 _notmuch_config_list_built_with ()
634 {
635     printf ("%scompact=%s\n",
636             BUILT_WITH_PREFIX,
637             notmuch_built_with ("compact") ? "true" : "false");
638     printf ("%sfield_processor=%s\n",
639             BUILT_WITH_PREFIX,
640             notmuch_built_with ("field_processor") ? "true" : "false");
641     printf ("%sretry_lock=%s\n",
642             BUILT_WITH_PREFIX,
643             notmuch_built_with ("retry_lock") ? "true" : "false");
644 }
645
646 static int
647 notmuch_config_command_list (notmuch_database_t *notmuch)
648 {
649     notmuch_config_pairs_t *list;
650
651     _notmuch_config_list_built_with ();
652     for (list = notmuch_config_get_pairs (notmuch, "");
653          notmuch_config_pairs_valid (list);
654          notmuch_config_pairs_move_to_next (list)) {
655         const char *value = notmuch_config_pairs_value (list);
656         if (value)
657             printf ("%s=%s\n", notmuch_config_pairs_key (list), value);
658     }
659     notmuch_config_pairs_destroy (list);
660     return EXIT_SUCCESS;
661 }
662
663 int
664 notmuch_config_command (notmuch_config_t *config, notmuch_database_t *notmuch,
665                         int argc, char *argv[])
666 {
667     int ret;
668     int opt_index;
669
670     opt_index = notmuch_minimal_options ("config", argc, argv);
671     if (opt_index < 0)
672         return EXIT_FAILURE;
673
674     if (notmuch_requested_db_uuid)
675         fprintf (stderr, "Warning: ignoring --uuid=%s\n",
676                  notmuch_requested_db_uuid);
677
678     /* skip at least subcommand argument */
679     argc -= opt_index;
680     argv += opt_index;
681
682     if (argc < 1) {
683         fprintf (stderr, "Error: notmuch config requires at least one argument.\n");
684         return EXIT_FAILURE;
685     }
686
687     if (strcmp (argv[0], "get") == 0) {
688         if (argc != 2) {
689             fprintf (stderr, "Error: notmuch config get requires exactly "
690                      "one argument.\n");
691             return EXIT_FAILURE;
692         }
693         ret = notmuch_config_command_get (notmuch, argv[1]);
694     } else if (strcmp (argv[0], "set") == 0) {
695         if (argc < 2) {
696             fprintf (stderr, "Error: notmuch config set requires at least "
697                      "one argument.\n");
698             return EXIT_FAILURE;
699         }
700         ret = notmuch_config_command_set (config, notmuch, argv[1], argc - 2, argv + 2);
701     } else if (strcmp (argv[0], "list") == 0) {
702         ret = notmuch_config_command_list (notmuch);
703     } else {
704         fprintf (stderr, "Unrecognized argument for notmuch config: %s\n",
705                  argv[0]);
706         return EXIT_FAILURE;
707     }
708
709     return ret ? EXIT_FAILURE : EXIT_SUCCESS;
710
711 }
712
713 void
714 notmuch_config_set_maildir_synchronize_flags (notmuch_config_t *config,
715                                               bool synchronize_flags)
716 {
717     g_key_file_set_boolean (config->key_file,
718                             "maildir", "synchronize_flags", synchronize_flags);
719 }