]> git.notmuchmail.org Git - notmuch/blob - notmuch-config.c
Merge tag '0.31.4'
[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 *_notmuch_config_get_path (notmuch_config_t *config) {
514     return config->filename;
515 }
516 /* Save any changes made to the notmuch configuration.
517  *
518  * Any comments originally in the file will be preserved.
519  *
520  * Returns 0 if successful, and 1 in case of any error, (after
521  * printing a description of the error to stderr).
522  */
523 int
524 notmuch_config_save (notmuch_config_t *config)
525 {
526     size_t length;
527     char *data, *filename;
528     GError *error = NULL;
529
530     data = g_key_file_to_data (config->key_file, &length, NULL);
531     if (data == NULL) {
532         fprintf (stderr, "Out of memory.\n");
533         return 1;
534     }
535
536     /* Try not to overwrite symlinks. */
537     filename = canonicalize_file_name (config->filename);
538     if (! filename) {
539         if (errno == ENOENT) {
540             filename = strdup (config->filename);
541             if (! filename) {
542                 fprintf (stderr, "Out of memory.\n");
543                 g_free (data);
544                 return 1;
545             }
546         } else {
547             fprintf (stderr, "Error canonicalizing %s: %s\n", config->filename,
548                      strerror (errno));
549             g_free (data);
550             return 1;
551         }
552     }
553
554     if (! g_file_set_contents (filename, data, length, &error)) {
555         if (strcmp (filename, config->filename) != 0) {
556             fprintf (stderr, "Error saving configuration to %s (-> %s): %s\n",
557                      config->filename, filename, error->message);
558         } else {
559             fprintf (stderr, "Error saving configuration to %s: %s\n",
560                      filename, error->message);
561         }
562         g_error_free (error);
563         free (filename);
564         g_free (data);
565         return 1;
566     }
567
568     free (filename);
569     g_free (data);
570     return 0;
571 }
572
573 bool
574 notmuch_config_is_new (notmuch_config_t *config)
575 {
576     return config->is_new;
577 }
578
579 static const char *
580 _config_get (notmuch_config_t *config, char **field,
581              const char *group, const char *key)
582 {
583     /* read from config file and cache value, if not cached already */
584     if (*field == NULL) {
585         char *value;
586         value = g_key_file_get_string (config->key_file, group, key, NULL);
587         if (value) {
588             *field = talloc_strdup (config, value);
589             free (value);
590         }
591     }
592     return *field;
593 }
594
595 static void
596 _config_set (notmuch_config_t *config, char **field,
597              const char *group, const char *key, const char *value)
598 {
599     g_key_file_set_string (config->key_file, group, key, value);
600
601     /* drop the cached value */
602     talloc_free (*field);
603     *field = NULL;
604 }
605
606 static const char **
607 _config_get_list (notmuch_config_t *config,
608                   const char *section, const char *key,
609                   const char ***outlist, size_t *list_length, size_t *ret_length)
610 {
611     assert (outlist);
612
613     /* read from config file and cache value, if not cached already */
614     if (*outlist == NULL) {
615
616         char **inlist = g_key_file_get_string_list (config->key_file,
617                                                     section, key, list_length, NULL);
618         if (inlist) {
619             unsigned int i;
620
621             *outlist = talloc_size (config, sizeof (char *) * (*list_length + 1));
622
623             for (i = 0; i < *list_length; i++)
624                 (*outlist)[i] = talloc_strdup (*outlist, inlist[i]);
625
626             (*outlist)[i] = NULL;
627
628             g_strfreev (inlist);
629         }
630     }
631
632     if (ret_length)
633         *ret_length = *list_length;
634
635     return *outlist;
636 }
637
638 static void
639 _config_set_list (notmuch_config_t *config,
640                   const char *group, const char *key,
641                   const char *list[],
642                   size_t length, const char ***config_var )
643 {
644     g_key_file_set_string_list (config->key_file, group, key, list, length);
645
646     /* drop the cached value */
647     talloc_free (*config_var);
648     *config_var = NULL;
649 }
650
651 const char *
652 notmuch_config_get_database_path (notmuch_config_t *config)
653 {
654     char *db_path = (char *) _config_get (config, &config->database_path, "database", "path");
655
656     if (db_path && *db_path != '/') {
657         /* If the path in the configuration file begins with any
658          * character other than /, presume that it is relative to
659          * $HOME and update as appropriate.
660          */
661         char *abs_path = talloc_asprintf (config, "%s/%s", getenv ("HOME"), db_path);
662         talloc_free (db_path);
663         db_path = config->database_path = abs_path;
664     }
665
666     return db_path;
667 }
668
669 void
670 notmuch_config_set_database_path (notmuch_config_t *config,
671                                   const char *database_path)
672 {
673     _config_set (config, &config->database_path, "database", "path", database_path);
674 }
675
676 const char *
677 notmuch_config_get_user_name (notmuch_config_t *config)
678 {
679     return _config_get (config, &config->user_name, "user", "name");
680 }
681
682 void
683 notmuch_config_set_user_name (notmuch_config_t *config,
684                               const char *user_name)
685 {
686     _config_set (config, &config->user_name, "user", "name", user_name);
687 }
688
689 const char *
690 notmuch_config_get_user_primary_email (notmuch_config_t *config)
691 {
692     return _config_get (config, &config->user_primary_email, "user", "primary_email");
693 }
694
695 void
696 notmuch_config_set_user_primary_email (notmuch_config_t *config,
697                                        const char *primary_email)
698 {
699     _config_set (config, &config->user_primary_email, "user", "primary_email", primary_email);
700 }
701
702 const char **
703 notmuch_config_get_user_other_email (notmuch_config_t *config,   size_t *length)
704 {
705     return _config_get_list (config, "user", "other_email",
706                              &(config->user_other_email),
707                              &(config->user_other_email_length), length);
708 }
709
710 const char **
711 notmuch_config_get_new_tags (notmuch_config_t *config,   size_t *length)
712 {
713     return _config_get_list (config, "new", "tags",
714                              &(config->new_tags),
715                              &(config->new_tags_length), length);
716 }
717
718 const char **
719 notmuch_config_get_new_ignore (notmuch_config_t *config, size_t *length)
720 {
721     return _config_get_list (config, "new", "ignore",
722                              &(config->new_ignore),
723                              &(config->new_ignore_length), length);
724 }
725
726 void
727 notmuch_config_set_user_other_email (notmuch_config_t *config,
728                                      const char *list[],
729                                      size_t length)
730 {
731     _config_set_list (config, "user", "other_email", list, length,
732                       &(config->user_other_email));
733 }
734
735 void
736 notmuch_config_set_new_tags (notmuch_config_t *config,
737                              const char *list[],
738                              size_t length)
739 {
740     _config_set_list (config, "new", "tags", list, length,
741                       &(config->new_tags));
742 }
743
744 void
745 notmuch_config_set_new_ignore (notmuch_config_t *config,
746                                const char *list[],
747                                size_t length)
748 {
749     _config_set_list (config, "new", "ignore", list, length,
750                       &(config->new_ignore));
751 }
752
753 const char **
754 notmuch_config_get_search_exclude_tags (notmuch_config_t *config, size_t *length)
755 {
756     return _config_get_list (config, "search", "exclude_tags",
757                              &(config->search_exclude_tags),
758                              &(config->search_exclude_tags_length), length);
759 }
760
761 void
762 notmuch_config_set_search_exclude_tags (notmuch_config_t *config,
763                                         const char *list[],
764                                         size_t length)
765 {
766     _config_set_list (config, "search", "exclude_tags", list, length,
767                       &(config->search_exclude_tags));
768 }
769
770
771 /* Given a configuration item of the form <group>.<key> return the
772  * component group and key. If any error occurs, print a message on
773  * stderr and return 1. Otherwise, return 0.
774  *
775  * Note: This function modifies the original 'item' string.
776  */
777 static int
778 _item_split (char *item, char **group, char **key)
779 {
780     char *period;
781
782     *group = item;
783
784     period = strchr (item, '.');
785     if (period == NULL || *(period + 1) == '\0') {
786         fprintf (stderr,
787                  "Invalid configuration name: %s\n"
788                  "(Should be of the form <section>.<item>)\n", item);
789         return 1;
790     }
791
792     *period = '\0';
793     *key = period + 1;
794
795     return 0;
796 }
797
798 /* These are more properly called Xapian fields, but the user facing
799  * docs call them prefixes, so make the error message match */
800 static bool
801 validate_field_name (const char *str)
802 {
803     const char *key;
804
805     if (! g_utf8_validate (str, -1, NULL)) {
806         fprintf (stderr, "Invalid utf8: %s\n", str);
807         return false;
808     }
809
810     key = g_utf8_strrchr (str, -1, '.');
811     if (! key ) {
812         INTERNAL_ERROR ("Impossible code path on input: %s\n", str);
813     }
814
815     key++;
816
817     if (! *key) {
818         fprintf (stderr, "Empty prefix name: %s\n", str);
819         return false;
820     }
821
822     if (! unicode_word_utf8 (key)) {
823         fprintf (stderr, "Non-word character in prefix name: %s\n", key);
824         return false;
825     }
826
827     if (key[0] >= 'a' && key[0] <= 'z') {
828         fprintf (stderr, "Prefix names starting with lower case letters are reserved: %s\n", key);
829         return false;
830     }
831
832     return true;
833 }
834
835 #define BUILT_WITH_PREFIX "built_with."
836
837 typedef struct config_key {
838     const char *name;
839     bool in_db;
840     bool prefix;
841     bool (*validate)(const char *);
842 } config_key_info_t;
843
844 static struct config_key
845     config_key_table[] = {
846     { "index.decrypt",   true,   false,  NULL },
847     { "index.header.",   true,   true,   validate_field_name },
848     { "query.",          true,   true,   NULL },
849 };
850
851 static config_key_info_t *
852 _config_key_info (const char *item)
853 {
854     for (size_t i = 0; i < ARRAY_SIZE (config_key_table); i++) {
855         if (config_key_table[i].prefix &&
856             strncmp (item, config_key_table[i].name,
857                      strlen (config_key_table[i].name)) == 0)
858             return config_key_table + i;
859         if (strcmp (item, config_key_table[i].name) == 0)
860             return config_key_table + i;
861     }
862     return NULL;
863 }
864
865 static bool
866 _stored_in_db (const char *item)
867 {
868     config_key_info_t *info;
869
870     info = _config_key_info (item);
871
872     return (info && info->in_db);
873 }
874
875 static int
876 _print_db_config (notmuch_config_t *config, const char *name)
877 {
878     notmuch_database_t *notmuch;
879     char *val;
880
881     if (notmuch_database_open (notmuch_config_get_database_path (config),
882                                NOTMUCH_DATABASE_MODE_READ_ONLY, &notmuch))
883         return EXIT_FAILURE;
884
885     /* XXX Handle UUID mismatch? */
886
887     if (print_status_database ("notmuch config", notmuch,
888                                notmuch_database_get_config (notmuch, name, &val)))
889         return EXIT_FAILURE;
890
891     puts (val);
892
893     return EXIT_SUCCESS;
894 }
895
896 static int
897 notmuch_config_command_get (notmuch_config_t *config, char *item)
898 {
899     if (strcmp (item, "database.path") == 0) {
900         printf ("%s\n", notmuch_config_get_database_path (config));
901     } else if (strcmp (item, "user.name") == 0) {
902         printf ("%s\n", notmuch_config_get_user_name (config));
903     } else if (strcmp (item, "user.primary_email") == 0) {
904         printf ("%s\n", notmuch_config_get_user_primary_email (config));
905     } else if (strcmp (item, "user.other_email") == 0) {
906         const char **other_email;
907         size_t i, length;
908
909         other_email = notmuch_config_get_user_other_email (config, &length);
910         for (i = 0; i < length; i++)
911             printf ("%s\n", other_email[i]);
912     } else if (strcmp (item, "new.tags") == 0) {
913         const char **tags;
914         size_t i, length;
915
916         tags = notmuch_config_get_new_tags (config, &length);
917         for (i = 0; i < length; i++)
918             printf ("%s\n", tags[i]);
919     } else if (STRNCMP_LITERAL (item, BUILT_WITH_PREFIX) == 0) {
920         printf ("%s\n",
921                 notmuch_built_with (item + strlen (BUILT_WITH_PREFIX)) ? "true" : "false");
922     } else if (_stored_in_db (item)) {
923         return _print_db_config (config, item);
924     } else {
925         char **value;
926         size_t i, length;
927         char *group, *key;
928
929         if (_item_split (item, &group, &key))
930             return 1;
931
932         value = g_key_file_get_string_list (config->key_file,
933                                             group, key,
934                                             &length, NULL);
935         if (value == NULL) {
936             fprintf (stderr, "Unknown configuration item: %s.%s\n",
937                      group, key);
938             return 1;
939         }
940
941         for (i = 0; i < length; i++)
942             printf ("%s\n", value[i]);
943
944         g_strfreev (value);
945     }
946
947     return 0;
948 }
949
950 static int
951 _set_db_config (notmuch_config_t *config, const char *key, int argc, char **argv)
952 {
953     notmuch_database_t *notmuch;
954     const char *val = "";
955
956     if (argc > 1) {
957         /* XXX handle lists? */
958         fprintf (stderr, "notmuch config set: at most one value expected for %s\n", key);
959         return EXIT_FAILURE;
960     }
961
962     if (argc > 0) {
963         val = argv[0];
964     }
965
966     if (notmuch_database_open (notmuch_config_get_database_path (config),
967                                NOTMUCH_DATABASE_MODE_READ_WRITE, &notmuch))
968         return EXIT_FAILURE;
969
970     /* XXX Handle UUID mismatch? */
971
972     if (print_status_database ("notmuch config", notmuch,
973                                notmuch_database_set_config (notmuch, key, val)))
974         return EXIT_FAILURE;
975
976     if (print_status_database ("notmuch config", notmuch,
977                                notmuch_database_close (notmuch)))
978         return EXIT_FAILURE;
979
980     return EXIT_SUCCESS;
981 }
982
983 static int
984 notmuch_config_command_set (notmuch_config_t *config, char *item, int argc, char *argv[])
985 {
986     char *group, *key;
987     config_key_info_t *key_info;
988
989     if (STRNCMP_LITERAL (item, BUILT_WITH_PREFIX) == 0) {
990         fprintf (stderr, "Error: read only option: %s\n", item);
991         return 1;
992     }
993
994     key_info = _config_key_info (item);
995     if (key_info && key_info->validate && (! key_info->validate (item)))
996         return 1;
997
998     if (key_info && key_info->in_db) {
999         return _set_db_config (config, item, argc, argv);
1000     }
1001
1002     if (_item_split (item, &group, &key))
1003         return 1;
1004
1005     /* With only the name of an item, we clear it from the
1006      * configuration file.
1007      *
1008      * With a single value, we set it as a string.
1009      *
1010      * With multiple values, we set them as a string list.
1011      */
1012     switch (argc) {
1013     case 0:
1014         g_key_file_remove_key (config->key_file, group, key, NULL);
1015         break;
1016     case 1:
1017         g_key_file_set_string (config->key_file, group, key, argv[0]);
1018         break;
1019     default:
1020         g_key_file_set_string_list (config->key_file, group, key,
1021                                     (const gchar **) argv, argc);
1022         break;
1023     }
1024
1025     return notmuch_config_save (config);
1026 }
1027
1028 static
1029 void
1030 _notmuch_config_list_built_with ()
1031 {
1032     printf ("%scompact=%s\n",
1033             BUILT_WITH_PREFIX,
1034             notmuch_built_with ("compact") ? "true" : "false");
1035     printf ("%sfield_processor=%s\n",
1036             BUILT_WITH_PREFIX,
1037             notmuch_built_with ("field_processor") ? "true" : "false");
1038     printf ("%sretry_lock=%s\n",
1039             BUILT_WITH_PREFIX,
1040             notmuch_built_with ("retry_lock") ? "true" : "false");
1041 }
1042
1043 static int
1044 _list_db_config (notmuch_config_t *config)
1045 {
1046     notmuch_database_t *notmuch;
1047     notmuch_config_list_t *list;
1048
1049     if (notmuch_database_open (notmuch_config_get_database_path (config),
1050                                NOTMUCH_DATABASE_MODE_READ_ONLY, &notmuch))
1051         return EXIT_FAILURE;
1052
1053     /* XXX Handle UUID mismatch? */
1054
1055
1056     if (print_status_database ("notmuch config", notmuch,
1057                                notmuch_database_get_config_list (notmuch, "", &list)))
1058         return EXIT_FAILURE;
1059
1060     for (; notmuch_config_list_valid (list); notmuch_config_list_move_to_next (list)) {
1061         printf ("%s=%s\n", notmuch_config_list_key (list), notmuch_config_list_value (list));
1062     }
1063     notmuch_config_list_destroy (list);
1064
1065     return EXIT_SUCCESS;
1066 }
1067
1068 static int
1069 notmuch_config_command_list (notmuch_config_t *config)
1070 {
1071     char **groups;
1072     size_t g, groups_length;
1073
1074     groups = g_key_file_get_groups (config->key_file, &groups_length);
1075     if (groups == NULL)
1076         return 1;
1077
1078     for (g = 0; g < groups_length; g++) {
1079         char **keys;
1080         size_t k, keys_length;
1081
1082         keys = g_key_file_get_keys (config->key_file,
1083                                     groups[g], &keys_length, NULL);
1084         if (keys == NULL)
1085             continue;
1086
1087         for (k = 0; k < keys_length; k++) {
1088             char *value;
1089
1090             value = g_key_file_get_string (config->key_file,
1091                                            groups[g], keys[k], NULL);
1092             if (value != NULL) {
1093                 printf ("%s.%s=%s\n", groups[g], keys[k], value);
1094                 free (value);
1095             }
1096         }
1097
1098         g_strfreev (keys);
1099     }
1100
1101     g_strfreev (groups);
1102
1103     _notmuch_config_list_built_with ();
1104     return _list_db_config (config);
1105 }
1106
1107 int
1108 notmuch_config_command (notmuch_config_t *config, unused(notmuch_database_t *notmuch), int argc, char *argv[])
1109 {
1110     int ret;
1111     int opt_index;
1112
1113     opt_index = notmuch_minimal_options ("config", argc, argv);
1114     if (opt_index < 0)
1115         return EXIT_FAILURE;
1116
1117     if (notmuch_requested_db_uuid)
1118         fprintf (stderr, "Warning: ignoring --uuid=%s\n",
1119                  notmuch_requested_db_uuid);
1120
1121     /* skip at least subcommand argument */
1122     argc -= opt_index;
1123     argv += opt_index;
1124
1125     if (argc < 1) {
1126         fprintf (stderr, "Error: notmuch config requires at least one argument.\n");
1127         return EXIT_FAILURE;
1128     }
1129
1130     if (strcmp (argv[0], "get") == 0) {
1131         if (argc != 2) {
1132             fprintf (stderr, "Error: notmuch config get requires exactly "
1133                      "one argument.\n");
1134             return EXIT_FAILURE;
1135         }
1136         ret = notmuch_config_command_get (config, argv[1]);
1137     } else if (strcmp (argv[0], "set") == 0) {
1138         if (argc < 2) {
1139             fprintf (stderr, "Error: notmuch config set requires at least "
1140                      "one argument.\n");
1141             return EXIT_FAILURE;
1142         }
1143         ret = notmuch_config_command_set (config, argv[1], argc - 2, argv + 2);
1144     } else if (strcmp (argv[0], "list") == 0) {
1145         ret = notmuch_config_command_list (config);
1146     } else {
1147         fprintf (stderr, "Unrecognized argument for notmuch config: %s\n",
1148                  argv[0]);
1149         return EXIT_FAILURE;
1150     }
1151
1152     return ret ? EXIT_FAILURE : EXIT_SUCCESS;
1153
1154 }
1155
1156 bool
1157 notmuch_config_get_maildir_synchronize_flags (notmuch_config_t *config)
1158 {
1159     return config->maildir_synchronize_flags;
1160 }
1161
1162 void
1163 notmuch_config_set_maildir_synchronize_flags (notmuch_config_t *config,
1164                                               bool synchronize_flags)
1165 {
1166     g_key_file_set_boolean (config->key_file,
1167                             "maildir", "synchronize_flags", synchronize_flags);
1168     config->maildir_synchronize_flags = synchronize_flags;
1169 }