]> git.cworth.org Git - notmuch/blob - notmuch-config.c
CLI/config: migrate notmuch_config_open to new config
[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_config_t *config, const char *key, int argc, char **argv)
700 {
701     notmuch_database_t *notmuch;
702     const char *val = "";
703
704     if (argc > 1) {
705         /* XXX handle lists? */
706         fprintf (stderr, "notmuch config set: at most one value expected for %s\n", key);
707         return EXIT_FAILURE;
708     }
709
710     if (argc > 0) {
711         val = argv[0];
712     }
713
714     if (notmuch_database_open (notmuch_config_get_database_path (config),
715                                NOTMUCH_DATABASE_MODE_READ_WRITE, &notmuch))
716         return EXIT_FAILURE;
717
718     /* XXX Handle UUID mismatch? */
719
720     if (print_status_database ("notmuch config", notmuch,
721                                notmuch_database_set_config (notmuch, key, val)))
722         return EXIT_FAILURE;
723
724     if (print_status_database ("notmuch config", notmuch,
725                                notmuch_database_close (notmuch)))
726         return EXIT_FAILURE;
727
728     return EXIT_SUCCESS;
729 }
730
731 static int
732 notmuch_config_command_set (notmuch_config_t *config, char *item, int argc, char *argv[])
733 {
734     char *group, *key;
735     config_key_info_t *key_info;
736
737     if (STRNCMP_LITERAL (item, BUILT_WITH_PREFIX) == 0) {
738         fprintf (stderr, "Error: read only option: %s\n", item);
739         return 1;
740     }
741
742     key_info = _config_key_info (item);
743     if (key_info && key_info->validate && (! key_info->validate (item)))
744         return 1;
745
746     if (key_info && key_info->in_db) {
747         return _set_db_config (config, item, argc, argv);
748     }
749
750     if (_item_split (item, &group, &key))
751         return 1;
752
753     /* With only the name of an item, we clear it from the
754      * configuration file.
755      *
756      * With a single value, we set it as a string.
757      *
758      * With multiple values, we set them as a string list.
759      */
760     switch (argc) {
761     case 0:
762         g_key_file_remove_key (config->key_file, group, key, NULL);
763         break;
764     case 1:
765         g_key_file_set_string (config->key_file, group, key, argv[0]);
766         break;
767     default:
768         g_key_file_set_string_list (config->key_file, group, key,
769                                     (const gchar **) argv, argc);
770         break;
771     }
772
773     return notmuch_config_save (config);
774 }
775
776 static
777 void
778 _notmuch_config_list_built_with ()
779 {
780     printf ("%scompact=%s\n",
781             BUILT_WITH_PREFIX,
782             notmuch_built_with ("compact") ? "true" : "false");
783     printf ("%sfield_processor=%s\n",
784             BUILT_WITH_PREFIX,
785             notmuch_built_with ("field_processor") ? "true" : "false");
786     printf ("%sretry_lock=%s\n",
787             BUILT_WITH_PREFIX,
788             notmuch_built_with ("retry_lock") ? "true" : "false");
789 }
790
791 static int
792 notmuch_config_command_list (notmuch_database_t *notmuch)
793 {
794     notmuch_config_pairs_t *list;
795
796     _notmuch_config_list_built_with ();
797     for (list = notmuch_config_get_pairs (notmuch, "");
798          notmuch_config_pairs_valid (list);
799          notmuch_config_pairs_move_to_next (list)) {
800         const char *value = notmuch_config_pairs_value (list);
801         if (value)
802             printf ("%s=%s\n", notmuch_config_pairs_key (list), value);
803     }
804     notmuch_config_pairs_destroy (list);
805     return EXIT_SUCCESS;
806 }
807
808 int
809 notmuch_config_command (notmuch_config_t *config, notmuch_database_t *notmuch,
810                         int argc, char *argv[])
811 {
812     int ret;
813     int opt_index;
814
815     opt_index = notmuch_minimal_options ("config", argc, argv);
816     if (opt_index < 0)
817         return EXIT_FAILURE;
818
819     if (notmuch_requested_db_uuid)
820         fprintf (stderr, "Warning: ignoring --uuid=%s\n",
821                  notmuch_requested_db_uuid);
822
823     /* skip at least subcommand argument */
824     argc -= opt_index;
825     argv += opt_index;
826
827     if (argc < 1) {
828         fprintf (stderr, "Error: notmuch config requires at least one argument.\n");
829         return EXIT_FAILURE;
830     }
831
832     if (strcmp (argv[0], "get") == 0) {
833         if (argc != 2) {
834             fprintf (stderr, "Error: notmuch config get requires exactly "
835                      "one argument.\n");
836             return EXIT_FAILURE;
837         }
838         ret = notmuch_config_command_get (notmuch, argv[1]);
839     } else if (strcmp (argv[0], "set") == 0) {
840         if (argc < 2) {
841             fprintf (stderr, "Error: notmuch config set requires at least "
842                      "one argument.\n");
843             return EXIT_FAILURE;
844         }
845         ret = notmuch_config_command_set (config, argv[1], argc - 2, argv + 2);
846     } else if (strcmp (argv[0], "list") == 0) {
847         ret = notmuch_config_command_list (notmuch);
848     } else {
849         fprintf (stderr, "Unrecognized argument for notmuch config: %s\n",
850                  argv[0]);
851         return EXIT_FAILURE;
852     }
853
854     return ret ? EXIT_FAILURE : EXIT_SUCCESS;
855
856 }
857
858 bool
859 notmuch_config_get_maildir_synchronize_flags (notmuch_config_t *config)
860 {
861     return config->maildir_synchronize_flags;
862 }
863
864 void
865 notmuch_config_set_maildir_synchronize_flags (notmuch_config_t *config,
866                                               bool synchronize_flags)
867 {
868     g_key_file_set_boolean (config->key_file,
869                             "maildir", "synchronize_flags", synchronize_flags);
870     config->maildir_synchronize_flags = synchronize_flags;
871 }