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