]> git.cworth.org Git - notmuch/blob - notmuch-config.c
CLI/config: use merged config for "config get"
[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 static const char database_config_comment[] =
35     " Database configuration\n"
36     "\n"
37     " The only value supported here is 'path' which should be the top-level\n"
38     " directory where your mail currently exists and to where mail will be\n"
39     " delivered in the future. Files should be individual email messages.\n"
40     " Notmuch will store its database within a sub-directory of the path\n"
41     " configured here named \".notmuch\".\n";
42
43 static const char new_config_comment[] =
44     " Configuration for \"notmuch new\"\n"
45     "\n"
46     " The following options are supported here:\n"
47     "\n"
48     "\ttags     A list (separated by ';') of the tags that will be\n"
49     "\t added to all messages incorporated by \"notmuch new\".\n"
50     "\n"
51     "\tignore   A list (separated by ';') of file and directory names\n"
52     "\t that will not be searched for messages by \"notmuch new\".\n"
53     "\n"
54     "\t NOTE: *Every* file/directory that goes by one of those\n"
55     "\t names will be ignored, independent of its depth/location\n"
56     "\t in the mail store.\n";
57
58 static const char user_config_comment[] =
59     " User configuration\n"
60     "\n"
61     " Here is where you can let notmuch know how you would like to be\n"
62     " addressed. Valid settings are\n"
63     "\n"
64     "\tname             Your full name.\n"
65     "\tprimary_email    Your primary email address.\n"
66     "\tother_email      A list (separated by ';') of other email addresses\n"
67     "\t         at which you receive email.\n"
68     "\n"
69     " Notmuch will use the various email addresses configured here when\n"
70     " formatting replies. It will avoid including your own addresses in the\n"
71     " recipient list of replies, and will set the From address based on the\n"
72     " address to which the original email was addressed.\n";
73
74 static const char maildir_config_comment[] =
75     " Maildir compatibility configuration\n"
76     "\n"
77     " The following option is supported here:\n"
78     "\n"
79     "\tsynchronize_flags      Valid values are true and false.\n"
80     "\n"
81     "\tIf true, then the following maildir flags (in message filenames)\n"
82     "\twill be synchronized with the corresponding notmuch tags:\n"
83     "\n"
84     "\t\tFlag   Tag\n"
85     "\t\t----   -------\n"
86     "\t\tD      draft\n"
87     "\t\tF      flagged\n"
88     "\t\tP      passed\n"
89     "\t\tR      replied\n"
90     "\t\tS      unread (added when 'S' flag is not present)\n"
91     "\n"
92     "\tThe \"notmuch new\" command will notice flag changes in filenames\n"
93     "\tand update tags, while the \"notmuch tag\" and \"notmuch restore\"\n"
94     "\tcommands will notice tag changes and update flags in filenames\n";
95
96 static const char search_config_comment[] =
97     " Search configuration\n"
98     "\n"
99     " The following option is supported here:\n"
100     "\n"
101     "\texclude_tags\n"
102     "\t\tA ;-separated list of tags that will be excluded from\n"
103     "\t\tsearch results by default.  Using an excluded tag in a\n"
104     "\t\tquery will override that exclusion.\n";
105
106 static const char crypto_config_comment[] =
107     " Cryptography related configuration\n"
108     "\n"
109     " The following old option is now ignored:\n"
110     "\n"
111     "\tgpgpath\n"
112     "\t\tThis option was used by older builds of notmuch to choose\n"
113     "\t\tthe version of gpg to use.\n"
114     "\t\tSetting $PATH is a better approach.\n";
115
116 struct _notmuch_config {
117     char *filename;
118     GKeyFile *key_file;
119     bool is_new;
120
121     char *database_path;
122     char *user_name;
123     char *user_primary_email;
124     const char **user_other_email;
125     size_t user_other_email_length;
126     const char **new_tags;
127     size_t new_tags_length;
128     const char **new_ignore;
129     size_t new_ignore_length;
130     bool maildir_synchronize_flags;
131     const char **search_exclude_tags;
132     size_t search_exclude_tags_length;
133 };
134
135 static int
136 notmuch_config_destructor (notmuch_config_t *config)
137 {
138     if (config->key_file)
139         g_key_file_free (config->key_file);
140
141     return 0;
142 }
143
144 static char *
145 get_name_from_passwd_file (void *ctx)
146 {
147     long pw_buf_size;
148     char *pw_buf;
149     struct passwd passwd, *ignored;
150     char *name;
151     int e;
152
153     pw_buf_size = sysconf (_SC_GETPW_R_SIZE_MAX);
154     if (pw_buf_size == -1) pw_buf_size = 64;
155     pw_buf = talloc_size (ctx, pw_buf_size);
156
157     while ((e = getpwuid_r (getuid (), &passwd, pw_buf,
158                             pw_buf_size, &ignored)) == ERANGE) {
159         pw_buf_size = pw_buf_size * 2;
160         pw_buf = talloc_zero_size (ctx, pw_buf_size);
161     }
162
163     if (e == 0) {
164         char *comma = strchr (passwd.pw_gecos, ',');
165         if (comma)
166             name = talloc_strndup (ctx, passwd.pw_gecos,
167                                    comma - passwd.pw_gecos);
168         else
169             name = talloc_strdup (ctx, passwd.pw_gecos);
170     } else {
171         name = talloc_strdup (ctx, "");
172     }
173
174     talloc_free (pw_buf);
175
176     return name;
177 }
178
179 static char *
180 get_username_from_passwd_file (void *ctx)
181 {
182     long pw_buf_size;
183     char *pw_buf;
184     struct passwd passwd, *ignored;
185     char *name;
186     int e;
187
188     pw_buf_size = sysconf (_SC_GETPW_R_SIZE_MAX);
189     if (pw_buf_size == -1) pw_buf_size = 64;
190     pw_buf = talloc_zero_size (ctx, pw_buf_size);
191
192     while ((e = getpwuid_r (getuid (), &passwd, pw_buf,
193                             pw_buf_size, &ignored)) == ERANGE) {
194         pw_buf_size = pw_buf_size * 2;
195         pw_buf = talloc_zero_size (ctx, pw_buf_size);
196     }
197
198     if (e == 0)
199         name = talloc_strdup (ctx, passwd.pw_name);
200     else
201         name = talloc_strdup (ctx, "");
202
203     talloc_free (pw_buf);
204
205     return name;
206 }
207
208 static bool
209 get_config_from_file (notmuch_config_t *config, bool create_new)
210 {
211     #define BUF_SIZE 4096
212     char *config_str = NULL;
213     int config_len = 0;
214     int config_bufsize = BUF_SIZE;
215     size_t len;
216     GError *error = NULL;
217     bool ret = false;
218
219     FILE *fp = fopen (config->filename, "r");
220     if (fp == NULL) {
221         if (errno == ENOENT) {
222             /* If create_new is true, then the caller is prepared for a
223              * default configuration file in the case of FILE NOT FOUND.
224              */
225             if (create_new) {
226                 config->is_new = true;
227                 ret = true;
228             } else {
229                 fprintf (stderr, "Configuration file %s not found.\n"
230                          "Try running 'notmuch setup' to create a configuration.\n",
231                          config->filename);
232             }
233         } else {
234             fprintf (stderr, "Error opening config file '%s': %s\n",
235                      config->filename, strerror (errno));
236         }
237         goto out;
238     }
239
240     config_str = talloc_zero_array (config, char, config_bufsize);
241     if (config_str == NULL) {
242         fprintf (stderr, "Error reading '%s': Out of memory\n", config->filename);
243         goto out;
244     }
245
246     while ((len = fread (config_str + config_len, 1,
247                          config_bufsize - config_len, fp)) > 0) {
248         config_len += len;
249         if (config_len == config_bufsize) {
250             config_bufsize += BUF_SIZE;
251             config_str = talloc_realloc (config, config_str, char, config_bufsize);
252             if (config_str == NULL) {
253                 fprintf (stderr, "Error reading '%s': Failed to reallocate memory\n",
254                          config->filename);
255                 goto out;
256             }
257         }
258     }
259
260     if (ferror (fp)) {
261         fprintf (stderr, "Error reading '%s': I/O error\n", config->filename);
262         goto out;
263     }
264
265     if (g_key_file_load_from_data (config->key_file, config_str, config_len,
266                                    G_KEY_FILE_KEEP_COMMENTS, &error)) {
267         ret = true;
268         goto out;
269     }
270
271     fprintf (stderr, "Error parsing config file '%s': %s\n",
272              config->filename, error->message);
273
274     g_error_free (error);
275
276   out:
277     if (fp)
278         fclose (fp);
279
280     if (config_str)
281         talloc_free (config_str);
282
283     return ret;
284 }
285
286 /* Open the named notmuch configuration file. If the filename is NULL,
287  * the value of the environment variable $NOTMUCH_CONFIG will be used.
288  * If $NOTMUCH_CONFIG is unset, the default configuration file
289  * ($HOME/.notmuch-config) will be used.
290  *
291  * If any error occurs, (out of memory, or a permission-denied error,
292  * etc.), this function will print a message to stderr and return
293  * NULL.
294  *
295  * FILE NOT FOUND: When the specified configuration file (whether from
296  * 'filename' or the $NOTMUCH_CONFIG environment variable) does not
297  * exist, the behavior of this function depends on the 'is_new_ret'
298  * variable.
299  *
300  *      If is_new_ret is NULL, then a "file not found" message will be
301  *      printed to stderr and NULL will be returned.
302  *
303  *      If is_new_ret is non-NULL then a default configuration will be
304  *      returned and *is_new_ret will be set to 1 on return so that
305  *      the caller can recognize this case.
306  *
307  *      These default configuration settings are determined as
308  *      follows:
309  *
310  *              database_path:          $MAILDIR, otherwise $HOME/mail
311  *
312  *              user_name:              $NAME variable if set, otherwise
313  *                                      read from /etc/passwd
314  *
315  *              user_primary_mail:      $EMAIL variable if set, otherwise
316  *                                      constructed from the username and
317  *                                      hostname of the current machine.
318  *
319  *              user_other_email:       Not set.
320  *
321  *      The default configuration also contains comments to guide the
322  *      user in editing the file directly.
323  */
324 notmuch_config_t *
325 notmuch_config_open (void *ctx,
326                      const char *filename,
327                      notmuch_command_mode_t config_mode)
328 {
329     GError *error = NULL;
330     size_t tmp;
331     char *notmuch_config_env = NULL;
332     int file_had_database_group;
333     int file_had_new_group;
334     int file_had_user_group;
335     int file_had_maildir_group;
336     int file_had_search_group;
337     int file_had_crypto_group;
338
339     notmuch_config_t *config = talloc_zero (ctx, notmuch_config_t);
340
341     if (config == NULL) {
342         fprintf (stderr, "Out of memory.\n");
343         return NULL;
344     }
345
346     talloc_set_destructor (config, notmuch_config_destructor);
347
348     /* non-zero defaults */
349     config->maildir_synchronize_flags = true;
350
351     if (filename) {
352         config->filename = talloc_strdup (config, filename);
353     } else if ((notmuch_config_env = getenv ("NOTMUCH_CONFIG"))) {
354         config->filename = talloc_strdup (config, notmuch_config_env);
355     } else {
356         config->filename = talloc_asprintf (config, "%s/.notmuch-config",
357                                             getenv ("HOME"));
358     }
359
360     config->key_file = g_key_file_new ();
361
362     if (config_mode & NOTMUCH_COMMAND_CONFIG_OPEN) {
363         bool create_new = (config_mode & NOTMUCH_COMMAND_CONFIG_CREATE) != 0;
364
365         if (! get_config_from_file (config, create_new)) {
366             talloc_free (config);
367             return NULL;
368         }
369     }
370
371     /* Whenever we know of configuration sections that don't appear in
372      * the configuration file, we add some comments to help the user
373      * understand what can be done.
374      *
375      * It would be convenient to just add those comments now, but
376      * apparently g_key_file will clear any comments when keys are
377      * added later that create the groups. So we have to check for the
378      * groups now, but add the comments only after setting all of our
379      * values.
380      */
381     file_had_database_group = g_key_file_has_group (config->key_file,
382                                                     "database");
383     file_had_new_group = g_key_file_has_group (config->key_file, "new");
384     file_had_user_group = g_key_file_has_group (config->key_file, "user");
385     file_had_maildir_group = g_key_file_has_group (config->key_file, "maildir");
386     file_had_search_group = g_key_file_has_group (config->key_file, "search");
387     file_had_crypto_group = g_key_file_has_group (config->key_file, "crypto");
388
389     if (notmuch_config_get_database_path (config) == NULL) {
390         char *path = getenv ("MAILDIR");
391         if (path)
392             path = talloc_strdup (config, path);
393         else
394             path = talloc_asprintf (config, "%s/mail",
395                                     getenv ("HOME"));
396         notmuch_config_set_database_path (config, path);
397         talloc_free (path);
398     }
399
400     if (notmuch_config_get_user_name (config) == NULL) {
401         char *name = getenv ("NAME");
402         if (name)
403             name = talloc_strdup (config, name);
404         else
405             name = get_name_from_passwd_file (config);
406         notmuch_config_set_user_name (config, name);
407         talloc_free (name);
408     }
409
410     if (notmuch_config_get_user_primary_email (config) == NULL) {
411         char *email = getenv ("EMAIL");
412         if (email) {
413             notmuch_config_set_user_primary_email (config, email);
414         } else {
415             char hostname[256];
416             struct hostent *hostent;
417             const char *domainname;
418
419             char *username = get_username_from_passwd_file (config);
420
421             gethostname (hostname, 256);
422             hostname[255] = '\0';
423
424             hostent = gethostbyname (hostname);
425             if (hostent && (domainname = strchr (hostent->h_name, '.')))
426                 domainname += 1;
427             else
428                 domainname = "(none)";
429
430             email = talloc_asprintf (config, "%s@%s.%s",
431                                      username, hostname, domainname);
432
433             notmuch_config_set_user_primary_email (config, email);
434
435             talloc_free (username);
436             talloc_free (email);
437         }
438     }
439
440     if (notmuch_config_get_new_tags (config, &tmp) == NULL) {
441         const char *tags[] = { "unread", "inbox" };
442         notmuch_config_set_new_tags (config, tags, 2);
443     }
444
445     if (notmuch_config_get_new_ignore (config, &tmp) == NULL) {
446         notmuch_config_set_new_ignore (config, NULL, 0);
447     }
448
449     if (notmuch_config_get_search_exclude_tags (config, &tmp) == NULL) {
450         if (config->is_new) {
451             const char *tags[] = { "deleted", "spam" };
452             notmuch_config_set_search_exclude_tags (config, tags, 2);
453         } else {
454             notmuch_config_set_search_exclude_tags (config, NULL, 0);
455         }
456     }
457
458     error = NULL;
459     config->maildir_synchronize_flags =
460         g_key_file_get_boolean (config->key_file,
461                                 "maildir", "synchronize_flags", &error);
462     if (error) {
463         notmuch_config_set_maildir_synchronize_flags (config, true);
464         g_error_free (error);
465     }
466
467     /* Whenever we know of configuration sections that don't appear in
468      * the configuration file, we add some comments to help the user
469      * understand what can be done. */
470     if (config->is_new)
471         g_key_file_set_comment (config->key_file, NULL, NULL,
472                                 toplevel_config_comment, NULL);
473
474     if (! file_had_database_group)
475         g_key_file_set_comment (config->key_file, "database", NULL,
476                                 database_config_comment, NULL);
477
478     if (! file_had_new_group)
479         g_key_file_set_comment (config->key_file, "new", NULL,
480                                 new_config_comment, NULL);
481
482     if (! file_had_user_group)
483         g_key_file_set_comment (config->key_file, "user", NULL,
484                                 user_config_comment, NULL);
485
486     if (! file_had_maildir_group)
487         g_key_file_set_comment (config->key_file, "maildir", NULL,
488                                 maildir_config_comment, NULL);
489
490     if (! file_had_search_group)
491         g_key_file_set_comment (config->key_file, "search", NULL,
492                                 search_config_comment, NULL);
493
494     if (! file_had_crypto_group)
495         g_key_file_set_comment (config->key_file, "crypto", NULL,
496                                 crypto_config_comment, NULL);
497
498     return config;
499 }
500
501 /* Close the given notmuch_config_t object, freeing all resources.
502  *
503  * Note: Any changes made to the configuration are *not* saved by this
504  * function. To save changes, call notmuch_config_save before
505  * notmuch_config_close.
506  */
507 void
508 notmuch_config_close (notmuch_config_t *config)
509 {
510     talloc_free (config);
511 }
512
513 const char *
514 _notmuch_config_get_path (notmuch_config_t *config)
515 {
516     return config->filename;
517 }
518 /* Save any changes made to the notmuch configuration.
519  *
520  * Any comments originally in the file will be preserved.
521  *
522  * Returns 0 if successful, and 1 in case of any error, (after
523  * printing a description of the error to stderr).
524  */
525 int
526 notmuch_config_save (notmuch_config_t *config)
527 {
528     size_t length;
529     char *data, *filename;
530     GError *error = NULL;
531
532     data = g_key_file_to_data (config->key_file, &length, NULL);
533     if (data == NULL) {
534         fprintf (stderr, "Out of memory.\n");
535         return 1;
536     }
537
538     /* Try not to overwrite symlinks. */
539     filename = canonicalize_file_name (config->filename);
540     if (! filename) {
541         if (errno == ENOENT) {
542             filename = strdup (config->filename);
543             if (! filename) {
544                 fprintf (stderr, "Out of memory.\n");
545                 g_free (data);
546                 return 1;
547             }
548         } else {
549             fprintf (stderr, "Error canonicalizing %s: %s\n", config->filename,
550                      strerror (errno));
551             g_free (data);
552             return 1;
553         }
554     }
555
556     if (! g_file_set_contents (filename, data, length, &error)) {
557         if (strcmp (filename, config->filename) != 0) {
558             fprintf (stderr, "Error saving configuration to %s (-> %s): %s\n",
559                      config->filename, filename, error->message);
560         } else {
561             fprintf (stderr, "Error saving configuration to %s: %s\n",
562                      filename, error->message);
563         }
564         g_error_free (error);
565         free (filename);
566         g_free (data);
567         return 1;
568     }
569
570     free (filename);
571     g_free (data);
572     return 0;
573 }
574
575 bool
576 notmuch_config_is_new (notmuch_config_t *config)
577 {
578     return config->is_new;
579 }
580
581 static const char *
582 _config_get (notmuch_config_t *config, char **field,
583              const char *group, const char *key)
584 {
585     /* read from config file and cache value, if not cached already */
586     if (*field == NULL) {
587         char *value;
588         value = g_key_file_get_string (config->key_file, group, key, NULL);
589         if (value) {
590             *field = talloc_strdup (config, value);
591             free (value);
592         }
593     }
594     return *field;
595 }
596
597 static void
598 _config_set (notmuch_config_t *config, char **field,
599              const char *group, const char *key, const char *value)
600 {
601     g_key_file_set_string (config->key_file, group, key, value);
602
603     /* drop the cached value */
604     talloc_free (*field);
605     *field = NULL;
606 }
607
608 static const char **
609 _config_get_list (notmuch_config_t *config,
610                   const char *section, const char *key,
611                   const char ***outlist, size_t *list_length, size_t *ret_length)
612 {
613     assert (outlist);
614
615     /* read from config file and cache value, if not cached already */
616     if (*outlist == NULL) {
617
618         char **inlist = g_key_file_get_string_list (config->key_file,
619                                                     section, key, list_length, NULL);
620         if (inlist) {
621             unsigned int i;
622
623             *outlist = talloc_size (config, sizeof (char *) * (*list_length + 1));
624
625             for (i = 0; i < *list_length; i++)
626                 (*outlist)[i] = talloc_strdup (*outlist, inlist[i]);
627
628             (*outlist)[i] = NULL;
629
630             g_strfreev (inlist);
631         }
632     }
633
634     if (ret_length)
635         *ret_length = *list_length;
636
637     return *outlist;
638 }
639
640 static void
641 _config_set_list (notmuch_config_t *config,
642                   const char *group, const char *key,
643                   const char *list[],
644                   size_t length, const char ***config_var )
645 {
646     g_key_file_set_string_list (config->key_file, group, key, list, length);
647
648     /* drop the cached value */
649     talloc_free (*config_var);
650     *config_var = NULL;
651 }
652
653 const char *
654 notmuch_config_get_database_path (notmuch_config_t *config)
655 {
656     char *db_path = (char *) _config_get (config, &config->database_path, "database", "path");
657
658     if (db_path && *db_path != '/') {
659         /* If the path in the configuration file begins with any
660          * character other than /, presume that it is relative to
661          * $HOME and update as appropriate.
662          */
663         char *abs_path = talloc_asprintf (config, "%s/%s", getenv ("HOME"), db_path);
664         talloc_free (db_path);
665         db_path = config->database_path = abs_path;
666     }
667
668     return db_path;
669 }
670
671 void
672 notmuch_config_set_database_path (notmuch_config_t *config,
673                                   const char *database_path)
674 {
675     _config_set (config, &config->database_path, "database", "path", database_path);
676 }
677
678 const char *
679 notmuch_config_get_user_name (notmuch_config_t *config)
680 {
681     return _config_get (config, &config->user_name, "user", "name");
682 }
683
684 void
685 notmuch_config_set_user_name (notmuch_config_t *config,
686                               const char *user_name)
687 {
688     _config_set (config, &config->user_name, "user", "name", user_name);
689 }
690
691 const char *
692 notmuch_config_get_user_primary_email (notmuch_config_t *config)
693 {
694     return _config_get (config, &config->user_primary_email, "user", "primary_email");
695 }
696
697 void
698 notmuch_config_set_user_primary_email (notmuch_config_t *config,
699                                        const char *primary_email)
700 {
701     _config_set (config, &config->user_primary_email, "user", "primary_email", primary_email);
702 }
703
704 const char **
705 notmuch_config_get_user_other_email (notmuch_config_t *config,   size_t *length)
706 {
707     return _config_get_list (config, "user", "other_email",
708                              &(config->user_other_email),
709                              &(config->user_other_email_length), length);
710 }
711
712 const char **
713 notmuch_config_get_new_tags (notmuch_config_t *config,   size_t *length)
714 {
715     return _config_get_list (config, "new", "tags",
716                              &(config->new_tags),
717                              &(config->new_tags_length), length);
718 }
719
720 const char **
721 notmuch_config_get_new_ignore (notmuch_config_t *config, size_t *length)
722 {
723     return _config_get_list (config, "new", "ignore",
724                              &(config->new_ignore),
725                              &(config->new_ignore_length), length);
726 }
727
728 void
729 notmuch_config_set_user_other_email (notmuch_config_t *config,
730                                      const char *list[],
731                                      size_t length)
732 {
733     _config_set_list (config, "user", "other_email", list, length,
734                       &(config->user_other_email));
735 }
736
737 void
738 notmuch_config_set_new_tags (notmuch_config_t *config,
739                              const char *list[],
740                              size_t length)
741 {
742     _config_set_list (config, "new", "tags", list, length,
743                       &(config->new_tags));
744 }
745
746 void
747 notmuch_config_set_new_ignore (notmuch_config_t *config,
748                                const char *list[],
749                                size_t length)
750 {
751     _config_set_list (config, "new", "ignore", list, length,
752                       &(config->new_ignore));
753 }
754
755 const char **
756 notmuch_config_get_search_exclude_tags (notmuch_config_t *config, size_t *length)
757 {
758     return _config_get_list (config, "search", "exclude_tags",
759                              &(config->search_exclude_tags),
760                              &(config->search_exclude_tags_length), length);
761 }
762
763 void
764 notmuch_config_set_search_exclude_tags (notmuch_config_t *config,
765                                         const char *list[],
766                                         size_t length)
767 {
768     _config_set_list (config, "search", "exclude_tags", list, length,
769                       &(config->search_exclude_tags));
770 }
771
772
773 /* Given a configuration item of the form <group>.<key> return the
774  * component group and key. If any error occurs, print a message on
775  * stderr and return 1. Otherwise, return 0.
776  *
777  * Note: This function modifies the original 'item' string.
778  */
779 static int
780 _item_split (char *item, char **group, char **key)
781 {
782     char *period;
783
784     *group = item;
785
786     period = strchr (item, '.');
787     if (period == NULL || *(period + 1) == '\0') {
788         fprintf (stderr,
789                  "Invalid configuration name: %s\n"
790                  "(Should be of the form <section>.<item>)\n", item);
791         return 1;
792     }
793
794     *period = '\0';
795     *key = period + 1;
796
797     return 0;
798 }
799
800 /* These are more properly called Xapian fields, but the user facing
801  * docs call them prefixes, so make the error message match */
802 static bool
803 validate_field_name (const char *str)
804 {
805     const char *key;
806
807     if (! g_utf8_validate (str, -1, NULL)) {
808         fprintf (stderr, "Invalid utf8: %s\n", str);
809         return false;
810     }
811
812     key = g_utf8_strrchr (str, -1, '.');
813     if (! key ) {
814         INTERNAL_ERROR ("Impossible code path on input: %s\n", str);
815     }
816
817     key++;
818
819     if (! *key) {
820         fprintf (stderr, "Empty prefix name: %s\n", str);
821         return false;
822     }
823
824     if (! unicode_word_utf8 (key)) {
825         fprintf (stderr, "Non-word character in prefix name: %s\n", key);
826         return false;
827     }
828
829     if (key[0] >= 'a' && key[0] <= 'z') {
830         fprintf (stderr, "Prefix names starting with lower case letters are reserved: %s\n", key);
831         return false;
832     }
833
834     return true;
835 }
836
837 #define BUILT_WITH_PREFIX "built_with."
838
839 typedef struct config_key {
840     const char *name;
841     bool in_db;
842     bool prefix;
843     bool (*validate)(const char *);
844 } config_key_info_t;
845
846 static struct config_key
847     config_key_table[] = {
848     { "index.decrypt",   true,   false,  NULL },
849     { "index.header.",   true,   true,   validate_field_name },
850     { "query.",          true,   true,   NULL },
851 };
852
853 static config_key_info_t *
854 _config_key_info (const char *item)
855 {
856     for (size_t i = 0; i < ARRAY_SIZE (config_key_table); i++) {
857         if (config_key_table[i].prefix &&
858             strncmp (item, config_key_table[i].name,
859                      strlen (config_key_table[i].name)) == 0)
860             return config_key_table + i;
861         if (strcmp (item, config_key_table[i].name) == 0)
862             return config_key_table + i;
863     }
864     return NULL;
865 }
866
867 static int
868 notmuch_config_command_get (notmuch_database_t *notmuch, char *item)
869 {
870     notmuch_config_values_t *list;
871
872     for (list = notmuch_config_get_values_string (notmuch, item);
873          notmuch_config_values_valid (list);
874          notmuch_config_values_move_to_next (list)) {
875         const char *val = notmuch_config_values_get (list);
876         puts (val);
877     }
878     return EXIT_SUCCESS;
879 }
880
881 static int
882 _set_db_config (notmuch_config_t *config, const char *key, int argc, char **argv)
883 {
884     notmuch_database_t *notmuch;
885     const char *val = "";
886
887     if (argc > 1) {
888         /* XXX handle lists? */
889         fprintf (stderr, "notmuch config set: at most one value expected for %s\n", key);
890         return EXIT_FAILURE;
891     }
892
893     if (argc > 0) {
894         val = argv[0];
895     }
896
897     if (notmuch_database_open (notmuch_config_get_database_path (config),
898                                NOTMUCH_DATABASE_MODE_READ_WRITE, &notmuch))
899         return EXIT_FAILURE;
900
901     /* XXX Handle UUID mismatch? */
902
903     if (print_status_database ("notmuch config", notmuch,
904                                notmuch_database_set_config (notmuch, key, val)))
905         return EXIT_FAILURE;
906
907     if (print_status_database ("notmuch config", notmuch,
908                                notmuch_database_close (notmuch)))
909         return EXIT_FAILURE;
910
911     return EXIT_SUCCESS;
912 }
913
914 static int
915 notmuch_config_command_set (notmuch_config_t *config, char *item, int argc, char *argv[])
916 {
917     char *group, *key;
918     config_key_info_t *key_info;
919
920     if (STRNCMP_LITERAL (item, BUILT_WITH_PREFIX) == 0) {
921         fprintf (stderr, "Error: read only option: %s\n", item);
922         return 1;
923     }
924
925     key_info = _config_key_info (item);
926     if (key_info && key_info->validate && (! key_info->validate (item)))
927         return 1;
928
929     if (key_info && key_info->in_db) {
930         return _set_db_config (config, item, argc, argv);
931     }
932
933     if (_item_split (item, &group, &key))
934         return 1;
935
936     /* With only the name of an item, we clear it from the
937      * configuration file.
938      *
939      * With a single value, we set it as a string.
940      *
941      * With multiple values, we set them as a string list.
942      */
943     switch (argc) {
944     case 0:
945         g_key_file_remove_key (config->key_file, group, key, NULL);
946         break;
947     case 1:
948         g_key_file_set_string (config->key_file, group, key, argv[0]);
949         break;
950     default:
951         g_key_file_set_string_list (config->key_file, group, key,
952                                     (const gchar **) argv, argc);
953         break;
954     }
955
956     return notmuch_config_save (config);
957 }
958
959 static
960 void
961 _notmuch_config_list_built_with ()
962 {
963     printf ("%scompact=%s\n",
964             BUILT_WITH_PREFIX,
965             notmuch_built_with ("compact") ? "true" : "false");
966     printf ("%sfield_processor=%s\n",
967             BUILT_WITH_PREFIX,
968             notmuch_built_with ("field_processor") ? "true" : "false");
969     printf ("%sretry_lock=%s\n",
970             BUILT_WITH_PREFIX,
971             notmuch_built_with ("retry_lock") ? "true" : "false");
972 }
973
974 static int
975 _list_db_config (notmuch_config_t *config)
976 {
977     notmuch_database_t *notmuch;
978     notmuch_config_list_t *list;
979
980     if (notmuch_database_open (notmuch_config_get_database_path (config),
981                                NOTMUCH_DATABASE_MODE_READ_ONLY, &notmuch))
982         return EXIT_FAILURE;
983
984     /* XXX Handle UUID mismatch? */
985
986
987     if (print_status_database ("notmuch config", notmuch,
988                                notmuch_database_get_config_list (notmuch, "", &list)))
989         return EXIT_FAILURE;
990
991     for (; notmuch_config_list_valid (list); notmuch_config_list_move_to_next (list)) {
992         printf ("%s=%s\n", notmuch_config_list_key (list), notmuch_config_list_value (list));
993     }
994     notmuch_config_list_destroy (list);
995
996     return EXIT_SUCCESS;
997 }
998
999 static int
1000 notmuch_config_command_list (notmuch_config_t *config)
1001 {
1002     char **groups;
1003     size_t g, groups_length;
1004
1005     groups = g_key_file_get_groups (config->key_file, &groups_length);
1006     if (groups == NULL)
1007         return 1;
1008
1009     for (g = 0; g < groups_length; g++) {
1010         char **keys;
1011         size_t k, keys_length;
1012
1013         keys = g_key_file_get_keys (config->key_file,
1014                                     groups[g], &keys_length, NULL);
1015         if (keys == NULL)
1016             continue;
1017
1018         for (k = 0; k < keys_length; k++) {
1019             char *value;
1020
1021             value = g_key_file_get_string (config->key_file,
1022                                            groups[g], keys[k], NULL);
1023             if (value != NULL) {
1024                 printf ("%s.%s=%s\n", groups[g], keys[k], value);
1025                 free (value);
1026             }
1027         }
1028
1029         g_strfreev (keys);
1030     }
1031
1032     g_strfreev (groups);
1033
1034     _notmuch_config_list_built_with ();
1035     return _list_db_config (config);
1036 }
1037
1038 int
1039 notmuch_config_command (notmuch_config_t *config, notmuch_database_t *notmuch,
1040                         int argc, char *argv[])
1041 {
1042     int ret;
1043     int opt_index;
1044
1045     opt_index = notmuch_minimal_options ("config", argc, argv);
1046     if (opt_index < 0)
1047         return EXIT_FAILURE;
1048
1049     if (notmuch_requested_db_uuid)
1050         fprintf (stderr, "Warning: ignoring --uuid=%s\n",
1051                  notmuch_requested_db_uuid);
1052
1053     /* skip at least subcommand argument */
1054     argc -= opt_index;
1055     argv += opt_index;
1056
1057     if (argc < 1) {
1058         fprintf (stderr, "Error: notmuch config requires at least one argument.\n");
1059         return EXIT_FAILURE;
1060     }
1061
1062     if (strcmp (argv[0], "get") == 0) {
1063         if (argc != 2) {
1064             fprintf (stderr, "Error: notmuch config get requires exactly "
1065                      "one argument.\n");
1066             return EXIT_FAILURE;
1067         }
1068         ret = notmuch_config_command_get (notmuch, argv[1]);
1069     } else if (strcmp (argv[0], "set") == 0) {
1070         if (argc < 2) {
1071             fprintf (stderr, "Error: notmuch config set requires at least "
1072                      "one argument.\n");
1073             return EXIT_FAILURE;
1074         }
1075         ret = notmuch_config_command_set (config, argv[1], argc - 2, argv + 2);
1076     } else if (strcmp (argv[0], "list") == 0) {
1077         ret = notmuch_config_command_list (config);
1078     } else {
1079         fprintf (stderr, "Unrecognized argument for notmuch config: %s\n",
1080                  argv[0]);
1081         return EXIT_FAILURE;
1082     }
1083
1084     return ret ? EXIT_FAILURE : EXIT_SUCCESS;
1085
1086 }
1087
1088 bool
1089 notmuch_config_get_maildir_synchronize_flags (notmuch_config_t *config)
1090 {
1091     return config->maildir_synchronize_flags;
1092 }
1093
1094 void
1095 notmuch_config_set_maildir_synchronize_flags (notmuch_config_t *config,
1096                                               bool synchronize_flags)
1097 {
1098     g_key_file_set_boolean (config->key_file,
1099                             "maildir", "synchronize_flags", synchronize_flags);
1100     config->maildir_synchronize_flags = synchronize_flags;
1101 }