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