]> git.notmuchmail.org Git - notmuch/blob - notmuch-config.c
CLI/config: use notmuch_database_reopen
[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     char *database_path;
123     char *user_name;
124     char *user_primary_email;
125     const char **user_other_email;
126     size_t user_other_email_length;
127     const char **new_tags;
128     size_t new_tags_length;
129     const char **new_ignore;
130     size_t new_ignore_length;
131     bool maildir_synchronize_flags;
132     const char **search_exclude_tags;
133     size_t search_exclude_tags_length;
134 };
135
136 static int
137 notmuch_config_destructor (notmuch_config_t *config)
138 {
139     if (config->key_file)
140         g_key_file_free (config->key_file);
141
142     return 0;
143 }
144
145
146 static bool
147 get_config_from_file (notmuch_config_t *config, bool create_new)
148 {
149     #define BUF_SIZE 4096
150     char *config_str = NULL;
151     int config_len = 0;
152     int config_bufsize = BUF_SIZE;
153     size_t len;
154     GError *error = NULL;
155     bool ret = false;
156
157     FILE *fp = fopen (config->filename, "r");
158     if (fp == NULL) {
159         if (errno == ENOENT) {
160             /* If create_new is true, then the caller is prepared for a
161              * default configuration file in the case of FILE NOT FOUND.
162              */
163             if (create_new) {
164                 config->is_new = true;
165                 ret = true;
166             } else {
167                 fprintf (stderr, "Configuration file %s not found.\n"
168                          "Try running 'notmuch setup' to create a configuration.\n",
169                          config->filename);
170             }
171         } else {
172             fprintf (stderr, "Error opening config file '%s': %s\n",
173                      config->filename, strerror (errno));
174         }
175         goto out;
176     }
177
178     config_str = talloc_zero_array (config, char, config_bufsize);
179     if (config_str == NULL) {
180         fprintf (stderr, "Error reading '%s': Out of memory\n", config->filename);
181         goto out;
182     }
183
184     while ((len = fread (config_str + config_len, 1,
185                          config_bufsize - config_len, fp)) > 0) {
186         config_len += len;
187         if (config_len == config_bufsize) {
188             config_bufsize += BUF_SIZE;
189             config_str = talloc_realloc (config, config_str, char, config_bufsize);
190             if (config_str == NULL) {
191                 fprintf (stderr, "Error reading '%s': Failed to reallocate memory\n",
192                          config->filename);
193                 goto out;
194             }
195         }
196     }
197
198     if (ferror (fp)) {
199         fprintf (stderr, "Error reading '%s': I/O error\n", config->filename);
200         goto out;
201     }
202
203     if (g_key_file_load_from_data (config->key_file, config_str, config_len,
204                                    G_KEY_FILE_KEEP_COMMENTS, &error)) {
205         ret = true;
206         goto out;
207     }
208
209     fprintf (stderr, "Error parsing config file '%s': %s\n",
210              config->filename, error->message);
211
212     g_error_free (error);
213
214   out:
215     if (fp)
216         fclose (fp);
217
218     if (config_str)
219         talloc_free (config_str);
220
221     return ret;
222 }
223
224 /* Open the named notmuch configuration file. If the filename is NULL,
225  * the value of the environment variable $NOTMUCH_CONFIG will be used.
226  * If $NOTMUCH_CONFIG is unset, the default configuration file
227  * ($HOME/.notmuch-config) will be used.
228  *
229  * If any error occurs, (out of memory, or a permission-denied error,
230  * etc.), this function will print a message to stderr and return
231  * NULL.
232  *
233  * FILE NOT FOUND: When the specified configuration file (whether from
234  * 'filename' or the $NOTMUCH_CONFIG environment variable) does not
235  * exist, the behavior of this function depends on the 'is_new_ret'
236  * variable.
237  *
238  *      If is_new_ret is NULL, then a "file not found" message will be
239  *      printed to stderr and NULL will be returned.
240  *
241  *      If is_new_ret is non-NULL then a default configuration will be
242  *      returned and *is_new_ret will be set to 1 on return so that
243  *      the caller can recognize this case.
244  *
245  *      These default configuration settings are determined as
246  *      follows:
247  *
248  *              database_path:          $MAILDIR, otherwise $HOME/mail
249  *
250  *              user_name:              $NAME variable if set, otherwise
251  *                                      read from /etc/passwd
252  *
253  *              user_primary_mail:      $EMAIL variable if set, otherwise
254  *                                      constructed from the username and
255  *                                      hostname of the current machine.
256  *
257  *              user_other_email:       Not set.
258  *
259  *      The default configuration also contains comments to guide the
260  *      user in editing the file directly.
261  */
262 notmuch_config_t *
263 notmuch_config_open (notmuch_database_t *notmuch,
264                      const char *filename,
265                      notmuch_command_mode_t config_mode)
266 {
267     char *notmuch_config_env = NULL;
268
269     notmuch_config_t *config = talloc_zero (notmuch, notmuch_config_t);
270
271     if (config == NULL) {
272         fprintf (stderr, "Out of memory.\n");
273         return NULL;
274     }
275
276     talloc_set_destructor (config, notmuch_config_destructor);
277
278     /* non-zero defaults */
279     config->maildir_synchronize_flags = true;
280
281     if (filename) {
282         config->filename = talloc_strdup (config, filename);
283     } else if ((notmuch_config_env = getenv ("NOTMUCH_CONFIG"))) {
284         config->filename = talloc_strdup (config, notmuch_config_env);
285     } else {
286         config->filename = talloc_asprintf (config, "%s/.notmuch-config",
287                                             getenv ("HOME"));
288     }
289
290     config->key_file = g_key_file_new ();
291
292     if (config_mode & NOTMUCH_COMMAND_CONFIG_OPEN) {
293         bool create_new = (config_mode & NOTMUCH_COMMAND_CONFIG_CREATE) != 0;
294
295         if (! get_config_from_file (config, create_new)) {
296             talloc_free (config);
297             return NULL;
298         }
299     }
300
301     if (config->is_new)
302         g_key_file_set_comment (config->key_file, NULL, NULL,
303                                 toplevel_config_comment, NULL);
304
305     for (size_t i = 0; i < ARRAY_SIZE (group_comment_table); i++) {
306         const char *name = group_comment_table[i].group_name;
307         if (! g_key_file_has_group (config->key_file,  name)) {
308             /* Force group to exist before adding comment */
309             g_key_file_set_value (config->key_file, name, "dummy_key", "dummy_val");
310             g_key_file_remove_key (config->key_file, name, "dummy_key", NULL);
311             g_key_file_set_comment (config->key_file, name, NULL,
312                                     group_comment_table[i].comment, NULL);
313         }
314     }
315     return config;
316 }
317
318 /* Close the given notmuch_config_t object, freeing all resources.
319  *
320  * Note: Any changes made to the configuration are *not* saved by this
321  * function. To save changes, call notmuch_config_save before
322  * notmuch_config_close.
323  */
324 void
325 notmuch_config_close (notmuch_config_t *config)
326 {
327     talloc_free (config);
328 }
329
330 const char *
331 _notmuch_config_get_path (notmuch_config_t *config)
332 {
333     return config->filename;
334 }
335 /* Save any changes made to the notmuch configuration.
336  *
337  * Any comments originally in the file will be preserved.
338  *
339  * Returns 0 if successful, and 1 in case of any error, (after
340  * printing a description of the error to stderr).
341  */
342 int
343 notmuch_config_save (notmuch_config_t *config)
344 {
345     size_t length;
346     char *data, *filename;
347     GError *error = NULL;
348
349     data = g_key_file_to_data (config->key_file, &length, NULL);
350     if (data == NULL) {
351         fprintf (stderr, "Out of memory.\n");
352         return 1;
353     }
354
355     /* Try not to overwrite symlinks. */
356     filename = canonicalize_file_name (config->filename);
357     if (! filename) {
358         if (errno == ENOENT) {
359             filename = strdup (config->filename);
360             if (! filename) {
361                 fprintf (stderr, "Out of memory.\n");
362                 g_free (data);
363                 return 1;
364             }
365         } else {
366             fprintf (stderr, "Error canonicalizing %s: %s\n", config->filename,
367                      strerror (errno));
368             g_free (data);
369             return 1;
370         }
371     }
372
373     if (! g_file_set_contents (filename, data, length, &error)) {
374         if (strcmp (filename, config->filename) != 0) {
375             fprintf (stderr, "Error saving configuration to %s (-> %s): %s\n",
376                      config->filename, filename, error->message);
377         } else {
378             fprintf (stderr, "Error saving configuration to %s: %s\n",
379                      filename, error->message);
380         }
381         g_error_free (error);
382         free (filename);
383         g_free (data);
384         return 1;
385     }
386
387     free (filename);
388     g_free (data);
389     return 0;
390 }
391
392 bool
393 notmuch_config_is_new (notmuch_config_t *config)
394 {
395     return config->is_new;
396 }
397
398 static const char *
399 _config_get (notmuch_config_t *config, char **field,
400              const char *group, const char *key)
401 {
402     /* read from config file and cache value, if not cached already */
403     if (*field == NULL) {
404         char *value;
405         value = g_key_file_get_string (config->key_file, group, key, NULL);
406         if (value) {
407             *field = talloc_strdup (config, value);
408             free (value);
409         }
410     }
411     return *field;
412 }
413
414 static void
415 _config_set (notmuch_config_t *config, char **field,
416              const char *group, const char *key, const char *value)
417 {
418     g_key_file_set_string (config->key_file, group, key, value);
419
420     /* drop the cached value */
421     talloc_free (*field);
422     *field = NULL;
423 }
424
425 static const char **
426 _config_get_list (notmuch_config_t *config,
427                   const char *section, const char *key,
428                   const char ***outlist, size_t *list_length, size_t *ret_length)
429 {
430     assert (outlist);
431
432     /* read from config file and cache value, if not cached already */
433     if (*outlist == NULL) {
434
435         char **inlist = g_key_file_get_string_list (config->key_file,
436                                                     section, key, list_length, NULL);
437         if (inlist) {
438             unsigned int i;
439
440             *outlist = talloc_size (config, sizeof (char *) * (*list_length + 1));
441
442             for (i = 0; i < *list_length; i++)
443                 (*outlist)[i] = talloc_strdup (*outlist, inlist[i]);
444
445             (*outlist)[i] = NULL;
446
447             g_strfreev (inlist);
448         }
449     }
450
451     if (ret_length)
452         *ret_length = *list_length;
453
454     return *outlist;
455 }
456
457 static void
458 _config_set_list (notmuch_config_t *config,
459                   const char *group, const char *key,
460                   const char *list[],
461                   size_t length, const char ***config_var )
462 {
463     g_key_file_set_string_list (config->key_file, group, key, list, length);
464
465     /* drop the cached value */
466     talloc_free (*config_var);
467     *config_var = NULL;
468 }
469
470 const char *
471 notmuch_config_get_database_path (notmuch_config_t *config)
472 {
473     char *db_path = (char *) _config_get (config, &config->database_path, "database", "path");
474
475     if (db_path && *db_path != '/') {
476         /* If the path in the configuration file begins with any
477          * character other than /, presume that it is relative to
478          * $HOME and update as appropriate.
479          */
480         char *abs_path = talloc_asprintf (config, "%s/%s", getenv ("HOME"), db_path);
481         talloc_free (db_path);
482         db_path = config->database_path = abs_path;
483     }
484
485     return db_path;
486 }
487
488 void
489 notmuch_config_set_database_path (notmuch_config_t *config,
490                                   const char *database_path)
491 {
492     _config_set (config, &config->database_path, "database", "path", database_path);
493 }
494
495 const char *
496 notmuch_config_get_user_name (notmuch_config_t *config)
497 {
498     return _config_get (config, &config->user_name, "user", "name");
499 }
500
501 void
502 notmuch_config_set_user_name (notmuch_config_t *config,
503                               const char *user_name)
504 {
505     _config_set (config, &config->user_name, "user", "name", user_name);
506 }
507
508 const char *
509 notmuch_config_get_user_primary_email (notmuch_config_t *config)
510 {
511     return _config_get (config, &config->user_primary_email, "user", "primary_email");
512 }
513
514 void
515 notmuch_config_set_user_primary_email (notmuch_config_t *config,
516                                        const char *primary_email)
517 {
518     _config_set (config, &config->user_primary_email, "user", "primary_email", primary_email);
519 }
520
521 const char **
522 notmuch_config_get_user_other_email (notmuch_config_t *config,   size_t *length)
523 {
524     return _config_get_list (config, "user", "other_email",
525                              &(config->user_other_email),
526                              &(config->user_other_email_length), length);
527 }
528
529 const char **
530 notmuch_config_get_new_tags (notmuch_config_t *config,   size_t *length)
531 {
532     return _config_get_list (config, "new", "tags",
533                              &(config->new_tags),
534                              &(config->new_tags_length), length);
535 }
536
537 const char **
538 notmuch_config_get_new_ignore (notmuch_config_t *config, size_t *length)
539 {
540     return _config_get_list (config, "new", "ignore",
541                              &(config->new_ignore),
542                              &(config->new_ignore_length), length);
543 }
544
545 void
546 notmuch_config_set_user_other_email (notmuch_config_t *config,
547                                      const char *list[],
548                                      size_t length)
549 {
550     _config_set_list (config, "user", "other_email", list, length,
551                       &(config->user_other_email));
552 }
553
554 void
555 notmuch_config_set_new_tags (notmuch_config_t *config,
556                              const char *list[],
557                              size_t length)
558 {
559     _config_set_list (config, "new", "tags", list, length,
560                       &(config->new_tags));
561 }
562
563 void
564 notmuch_config_set_new_ignore (notmuch_config_t *config,
565                                const char *list[],
566                                size_t length)
567 {
568     _config_set_list (config, "new", "ignore", list, length,
569                       &(config->new_ignore));
570 }
571
572 const char **
573 notmuch_config_get_search_exclude_tags (notmuch_config_t *config, size_t *length)
574 {
575     return _config_get_list (config, "search", "exclude_tags",
576                              &(config->search_exclude_tags),
577                              &(config->search_exclude_tags_length), length);
578 }
579
580 void
581 notmuch_config_set_search_exclude_tags (notmuch_config_t *config,
582                                         const char *list[],
583                                         size_t length)
584 {
585     _config_set_list (config, "search", "exclude_tags", list, length,
586                       &(config->search_exclude_tags));
587 }
588
589
590 /* Given a configuration item of the form <group>.<key> return the
591  * component group and key. If any error occurs, print a message on
592  * stderr and return 1. Otherwise, return 0.
593  *
594  * Note: This function modifies the original 'item' string.
595  */
596 static int
597 _item_split (char *item, char **group, char **key)
598 {
599     char *period;
600
601     *group = item;
602
603     period = strchr (item, '.');
604     if (period == NULL || *(period + 1) == '\0') {
605         fprintf (stderr,
606                  "Invalid configuration name: %s\n"
607                  "(Should be of the form <section>.<item>)\n", item);
608         return 1;
609     }
610
611     *period = '\0';
612     *key = period + 1;
613
614     return 0;
615 }
616
617 /* These are more properly called Xapian fields, but the user facing
618  * docs call them prefixes, so make the error message match */
619 static bool
620 validate_field_name (const char *str)
621 {
622     const char *key;
623
624     if (! g_utf8_validate (str, -1, NULL)) {
625         fprintf (stderr, "Invalid utf8: %s\n", str);
626         return false;
627     }
628
629     key = g_utf8_strrchr (str, -1, '.');
630     if (! key ) {
631         INTERNAL_ERROR ("Impossible code path on input: %s\n", str);
632     }
633
634     key++;
635
636     if (! *key) {
637         fprintf (stderr, "Empty prefix name: %s\n", str);
638         return false;
639     }
640
641     if (! unicode_word_utf8 (key)) {
642         fprintf (stderr, "Non-word character in prefix name: %s\n", key);
643         return false;
644     }
645
646     if (key[0] >= 'a' && key[0] <= 'z') {
647         fprintf (stderr, "Prefix names starting with lower case letters are reserved: %s\n", key);
648         return false;
649     }
650
651     return true;
652 }
653
654 #define BUILT_WITH_PREFIX "built_with."
655
656 typedef struct config_key {
657     const char *name;
658     bool in_db;
659     bool prefix;
660     bool (*validate)(const char *);
661 } config_key_info_t;
662
663 static struct config_key
664     config_key_table[] = {
665     { "index.decrypt",   true,   false,  NULL },
666     { "index.header.",   true,   true,   validate_field_name },
667     { "query.",          true,   true,   NULL },
668 };
669
670 static config_key_info_t *
671 _config_key_info (const char *item)
672 {
673     for (size_t i = 0; i < ARRAY_SIZE (config_key_table); i++) {
674         if (config_key_table[i].prefix &&
675             strncmp (item, config_key_table[i].name,
676                      strlen (config_key_table[i].name)) == 0)
677             return config_key_table + i;
678         if (strcmp (item, config_key_table[i].name) == 0)
679             return config_key_table + i;
680     }
681     return NULL;
682 }
683
684 static int
685 notmuch_config_command_get (notmuch_database_t *notmuch, char *item)
686 {
687     notmuch_config_values_t *list;
688
689     for (list = notmuch_config_get_values_string (notmuch, item);
690          notmuch_config_values_valid (list);
691          notmuch_config_values_move_to_next (list)) {
692         const char *val = notmuch_config_values_get (list);
693         puts (val);
694     }
695     return EXIT_SUCCESS;
696 }
697
698 static int
699 _set_db_config (notmuch_database_t *notmuch, const char *key, int argc, char **argv)
700 {
701     const char *val = "";
702
703     if (argc > 1) {
704         /* XXX handle lists? */
705         fprintf (stderr, "notmuch config set: at most one value expected for %s\n", key);
706         return EXIT_FAILURE;
707     }
708
709     if (argc > 0) {
710         val = argv[0];
711     }
712
713     if (print_status_database ("notmuch config", notmuch,
714                                notmuch_database_reopen (notmuch,
715                                                         NOTMUCH_DATABASE_MODE_READ_WRITE)))
716         return EXIT_FAILURE;
717
718     if (print_status_database ("notmuch config", notmuch,
719                                notmuch_database_set_config (notmuch, key, val)))
720         return EXIT_FAILURE;
721
722     if (print_status_database ("notmuch config", notmuch,
723                                notmuch_database_close (notmuch)))
724         return EXIT_FAILURE;
725
726     return EXIT_SUCCESS;
727 }
728
729 static int
730 notmuch_config_command_set (notmuch_config_t *config, notmuch_database_t *notmuch, char *item,
731                             int argc, char *argv[])
732 {
733     char *group, *key;
734     config_key_info_t *key_info;
735
736     if (STRNCMP_LITERAL (item, BUILT_WITH_PREFIX) == 0) {
737         fprintf (stderr, "Error: read only option: %s\n", item);
738         return 1;
739     }
740
741     key_info = _config_key_info (item);
742     if (key_info && key_info->validate && (! key_info->validate (item)))
743         return 1;
744
745     if (key_info && key_info->in_db) {
746         return _set_db_config (notmuch, item, argc, argv);
747     }
748
749     if (_item_split (item, &group, &key))
750         return 1;
751
752     /* With only the name of an item, we clear it from the
753      * configuration file.
754      *
755      * With a single value, we set it as a string.
756      *
757      * With multiple values, we set them as a string list.
758      */
759     switch (argc) {
760     case 0:
761         g_key_file_remove_key (config->key_file, group, key, NULL);
762         break;
763     case 1:
764         g_key_file_set_string (config->key_file, group, key, argv[0]);
765         break;
766     default:
767         g_key_file_set_string_list (config->key_file, group, key,
768                                     (const gchar **) argv, argc);
769         break;
770     }
771
772     return notmuch_config_save (config);
773 }
774
775 static
776 void
777 _notmuch_config_list_built_with ()
778 {
779     printf ("%scompact=%s\n",
780             BUILT_WITH_PREFIX,
781             notmuch_built_with ("compact") ? "true" : "false");
782     printf ("%sfield_processor=%s\n",
783             BUILT_WITH_PREFIX,
784             notmuch_built_with ("field_processor") ? "true" : "false");
785     printf ("%sretry_lock=%s\n",
786             BUILT_WITH_PREFIX,
787             notmuch_built_with ("retry_lock") ? "true" : "false");
788 }
789
790 static int
791 notmuch_config_command_list (notmuch_database_t *notmuch)
792 {
793     notmuch_config_pairs_t *list;
794
795     _notmuch_config_list_built_with ();
796     for (list = notmuch_config_get_pairs (notmuch, "");
797          notmuch_config_pairs_valid (list);
798          notmuch_config_pairs_move_to_next (list)) {
799         const char *value = notmuch_config_pairs_value (list);
800         if (value)
801             printf ("%s=%s\n", notmuch_config_pairs_key (list), value);
802     }
803     notmuch_config_pairs_destroy (list);
804     return EXIT_SUCCESS;
805 }
806
807 int
808 notmuch_config_command (notmuch_config_t *config, notmuch_database_t *notmuch,
809                         int argc, char *argv[])
810 {
811     int ret;
812     int opt_index;
813
814     opt_index = notmuch_minimal_options ("config", argc, argv);
815     if (opt_index < 0)
816         return EXIT_FAILURE;
817
818     if (notmuch_requested_db_uuid)
819         fprintf (stderr, "Warning: ignoring --uuid=%s\n",
820                  notmuch_requested_db_uuid);
821
822     /* skip at least subcommand argument */
823     argc -= opt_index;
824     argv += opt_index;
825
826     if (argc < 1) {
827         fprintf (stderr, "Error: notmuch config requires at least one argument.\n");
828         return EXIT_FAILURE;
829     }
830
831     if (strcmp (argv[0], "get") == 0) {
832         if (argc != 2) {
833             fprintf (stderr, "Error: notmuch config get requires exactly "
834                      "one argument.\n");
835             return EXIT_FAILURE;
836         }
837         ret = notmuch_config_command_get (notmuch, argv[1]);
838     } else if (strcmp (argv[0], "set") == 0) {
839         if (argc < 2) {
840             fprintf (stderr, "Error: notmuch config set requires at least "
841                      "one argument.\n");
842             return EXIT_FAILURE;
843         }
844         ret = notmuch_config_command_set (config, notmuch, argv[1], argc - 2, argv + 2);
845     } else if (strcmp (argv[0], "list") == 0) {
846         ret = notmuch_config_command_list (notmuch);
847     } else {
848         fprintf (stderr, "Unrecognized argument for notmuch config: %s\n",
849                  argv[0]);
850         return EXIT_FAILURE;
851     }
852
853     return ret ? EXIT_FAILURE : EXIT_SUCCESS;
854
855 }
856
857 bool
858 notmuch_config_get_maildir_synchronize_flags (notmuch_config_t *config)
859 {
860     return config->maildir_synchronize_flags;
861 }
862
863 void
864 notmuch_config_set_maildir_synchronize_flags (notmuch_config_t *config,
865                                               bool synchronize_flags)
866 {
867     g_key_file_set_boolean (config->key_file,
868                             "maildir", "synchronize_flags", synchronize_flags);
869     config->maildir_synchronize_flags = synchronize_flags;
870 }