Merge branch 'release'
[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 /* Open the named notmuch configuration file. If the filename is NULL,
206  * the value of the environment variable $NOTMUCH_CONFIG will be used.
207  * If $NOTMUCH_CONFIG is unset, the default configuration file
208  * ($HOME/.notmuch-config) will be used.
209  *
210  * If any error occurs, (out of memory, or a permission-denied error,
211  * etc.), this function will print a message to stderr and return
212  * NULL.
213  *
214  * FILE NOT FOUND: When the specified configuration file (whether from
215  * 'filename' or the $NOTMUCH_CONFIG environment variable) does not
216  * exist, the behavior of this function depends on the 'is_new_ret'
217  * variable.
218  *
219  *      If is_new_ret is NULL, then a "file not found" message will be
220  *      printed to stderr and NULL will be returned.
221
222  *      If is_new_ret is non-NULL then a default configuration will be
223  *      returned and *is_new_ret will be set to 1 on return so that
224  *      the caller can recognize this case.
225  *
226  *      These default configuration settings are determined as
227  *      follows:
228  *
229  *              database_path:          $MAILDIR, otherwise $HOME/mail
230  *
231  *              user_name:              $NAME variable if set, otherwise
232  *                                      read from /etc/passwd
233  *
234  *              user_primary_mail:      $EMAIL variable if set, otherwise
235  *                                      constructed from the username and
236  *                                      hostname of the current machine.
237  *
238  *              user_other_email:       Not set.
239  *
240  *      The default configuration also contains comments to guide the
241  *      user in editing the file directly.
242  */
243 notmuch_config_t *
244 notmuch_config_open (void *ctx,
245                      const char *filename,
246                      notmuch_bool_t create_new)
247 {
248     GError *error = NULL;
249     size_t tmp;
250     char *notmuch_config_env = NULL;
251     int file_had_database_group;
252     int file_had_new_group;
253     int file_had_user_group;
254     int file_had_maildir_group;
255     int file_had_search_group;
256     int file_had_crypto_group;
257
258     notmuch_config_t *config = talloc (ctx, notmuch_config_t);
259     if (config == NULL) {
260         fprintf (stderr, "Out of memory.\n");
261         return NULL;
262     }
263     
264     talloc_set_destructor (config, notmuch_config_destructor);
265
266     if (filename) {
267         config->filename = talloc_strdup (config, filename);
268     } else if ((notmuch_config_env = getenv ("NOTMUCH_CONFIG"))) {
269         config->filename = talloc_strdup (config, notmuch_config_env);
270     } else {
271         config->filename = talloc_asprintf (config, "%s/.notmuch-config",
272                                             getenv ("HOME"));
273     }
274
275     config->key_file = g_key_file_new ();
276
277     config->is_new = FALSE;
278     config->database_path = NULL;
279     config->user_name = NULL;
280     config->user_primary_email = NULL;
281     config->user_other_email = NULL;
282     config->user_other_email_length = 0;
283     config->new_tags = NULL;
284     config->new_tags_length = 0;
285     config->new_ignore = NULL;
286     config->new_ignore_length = 0;
287     config->maildir_synchronize_flags = TRUE;
288     config->search_exclude_tags = NULL;
289     config->search_exclude_tags_length = 0;
290     config->crypto_gpg_path = NULL;
291
292     if (! g_key_file_load_from_file (config->key_file,
293                                      config->filename,
294                                      G_KEY_FILE_KEEP_COMMENTS,
295                                      &error))
296     {
297         if (error->domain == G_FILE_ERROR && error->code == G_FILE_ERROR_NOENT) {
298             /* If create_new is true, then the caller is prepared for a
299              * default configuration file in the case of FILE NOT
300              * FOUND.
301              */
302             if (create_new) {
303                 g_error_free (error);
304                 config->is_new = TRUE;
305             } else {
306                 fprintf (stderr, "Configuration file %s not found.\n"
307                          "Try running 'notmuch setup' to create a configuration.\n",
308                          config->filename);
309                 talloc_free (config);
310                 g_error_free (error);
311                 return NULL;
312             }
313         }
314         else
315         {
316             fprintf (stderr, "Error reading configuration file %s: %s\n",
317                      config->filename, error->message);
318             talloc_free (config);
319             g_error_free (error);
320             return NULL;
321         }
322     }
323
324     /* Whenever we know of configuration sections that don't appear in
325      * the configuration file, we add some comments to help the user
326      * understand what can be done.
327      *
328      * It would be convenient to just add those comments now, but
329      * apparently g_key_file will clear any comments when keys are
330      * added later that create the groups. So we have to check for the
331      * groups now, but add the comments only after setting all of our
332      * values.
333      */
334     file_had_database_group = g_key_file_has_group (config->key_file,
335                                                     "database");
336     file_had_new_group = g_key_file_has_group (config->key_file, "new");
337     file_had_user_group = g_key_file_has_group (config->key_file, "user");
338     file_had_maildir_group = g_key_file_has_group (config->key_file, "maildir");
339     file_had_search_group = g_key_file_has_group (config->key_file, "search");
340     file_had_crypto_group = g_key_file_has_group (config->key_file, "crypto");
341
342     if (notmuch_config_get_database_path (config) == NULL) {
343         char *path = getenv ("MAILDIR");
344         if (path)
345             path = talloc_strdup (config, path);
346         else
347             path = talloc_asprintf (config, "%s/mail",
348                                     getenv ("HOME"));
349         notmuch_config_set_database_path (config, path);
350         talloc_free (path);
351     }
352
353     if (notmuch_config_get_user_name (config) == NULL) {
354         char *name = getenv ("NAME");
355         if (name)
356             name = talloc_strdup (config, name);
357         else
358             name = get_name_from_passwd_file (config);
359         notmuch_config_set_user_name (config, name);
360         talloc_free (name);
361     }
362
363     if (notmuch_config_get_user_primary_email (config) == NULL) {
364         char *email = getenv ("EMAIL");
365         if (email) {
366             notmuch_config_set_user_primary_email (config, email);
367         } else {
368             char hostname[256];
369             struct hostent *hostent;
370             const char *domainname;
371
372             char *username = get_username_from_passwd_file (config);
373
374             gethostname (hostname, 256);
375             hostname[255] = '\0';
376
377             hostent = gethostbyname (hostname);
378             if (hostent && (domainname = strchr (hostent->h_name, '.')))
379                 domainname += 1;
380             else
381                 domainname = "(none)";
382
383             email = talloc_asprintf (config, "%s@%s.%s",
384                                      username, hostname, domainname);
385
386             notmuch_config_set_user_primary_email (config, email);
387
388             talloc_free (username);
389             talloc_free (email);
390         }
391     }
392
393     if (notmuch_config_get_new_tags (config, &tmp) == NULL) {
394         const char *tags[] = { "unread", "inbox" };
395         notmuch_config_set_new_tags (config, tags, 2);
396     }
397
398     if (notmuch_config_get_new_ignore (config, &tmp) == NULL) {
399         notmuch_config_set_new_ignore (config, NULL, 0);
400     }
401
402     if (notmuch_config_get_search_exclude_tags (config, &tmp) == NULL) {
403         if (config->is_new) {
404             const char *tags[] = { "deleted", "spam" };
405             notmuch_config_set_search_exclude_tags (config, tags, 2);
406         } else {
407             notmuch_config_set_search_exclude_tags (config, NULL, 0);
408         }
409     }
410
411     error = NULL;
412     config->maildir_synchronize_flags =
413         g_key_file_get_boolean (config->key_file,
414                                 "maildir", "synchronize_flags", &error);
415     if (error) {
416         notmuch_config_set_maildir_synchronize_flags (config, TRUE);
417         g_error_free (error);
418     }
419
420     if (notmuch_config_get_crypto_gpg_path (config) == NULL) {
421         notmuch_config_set_crypto_gpg_path (config, "gpg");
422     }
423     
424     /* Whenever we know of configuration sections that don't appear in
425      * the configuration file, we add some comments to help the user
426      * understand what can be done. */
427     if (config->is_new)
428         g_key_file_set_comment (config->key_file, NULL, NULL,
429                                 toplevel_config_comment, NULL);
430
431     if (! file_had_database_group)
432         g_key_file_set_comment (config->key_file, "database", NULL,
433                                 database_config_comment, NULL);
434
435     if (! file_had_new_group)
436         g_key_file_set_comment (config->key_file, "new", NULL,
437                                 new_config_comment, NULL);
438
439     if (! file_had_user_group)
440         g_key_file_set_comment (config->key_file, "user", NULL,
441                                 user_config_comment, NULL);
442
443     if (! file_had_maildir_group)
444         g_key_file_set_comment (config->key_file, "maildir", NULL,
445                                 maildir_config_comment, NULL);
446
447     if (! file_had_search_group)
448         g_key_file_set_comment (config->key_file, "search", NULL,
449                                 search_config_comment, NULL);
450
451     if (! file_had_crypto_group)
452         g_key_file_set_comment (config->key_file, "crypto", NULL,
453                                 crypto_config_comment, NULL);
454
455     return config;
456 }
457
458 /* Close the given notmuch_config_t object, freeing all resources.
459  * 
460  * Note: Any changes made to the configuration are *not* saved by this
461  * function. To save changes, call notmuch_config_save before
462  * notmuch_config_close.
463 */
464 void
465 notmuch_config_close (notmuch_config_t *config)
466 {
467     talloc_free (config);
468 }
469
470 /* Save any changes made to the notmuch configuration.
471  *
472  * Any comments originally in the file will be preserved.
473  *
474  * Returns 0 if successful, and 1 in case of any error, (after
475  * printing a description of the error to stderr).
476  */
477 int
478 notmuch_config_save (notmuch_config_t *config)
479 {
480     size_t length;
481     char *data, *filename;
482     GError *error = NULL;
483
484     data = g_key_file_to_data (config->key_file, &length, NULL);
485     if (data == NULL) {
486         fprintf (stderr, "Out of memory.\n");
487         return 1;
488     }
489
490     /* Try not to overwrite symlinks. */
491     filename = canonicalize_file_name (config->filename);
492     if (! filename) {
493         if (errno == ENOENT) {
494             filename = strdup (config->filename);
495             if (! filename) {
496                 fprintf (stderr, "Out of memory.\n");
497                 g_free (data);
498                 return 1;
499             }
500         } else {
501             fprintf (stderr, "Error canonicalizing %s: %s\n", config->filename,
502                      strerror (errno));
503             g_free (data);
504             return 1;
505         }
506     }
507
508     if (! g_file_set_contents (filename, data, length, &error)) {
509         if (strcmp (filename, config->filename) != 0) {
510             fprintf (stderr, "Error saving configuration to %s (-> %s): %s\n",
511                      config->filename, filename, error->message);
512         } else {
513             fprintf (stderr, "Error saving configuration to %s: %s\n",
514                      filename, error->message);
515         }
516         g_error_free (error);
517         free (filename);
518         g_free (data);
519         return 1;
520     }
521
522     free (filename);
523     g_free (data);
524     return 0;
525 }
526
527 notmuch_bool_t
528 notmuch_config_is_new (notmuch_config_t *config)
529 {
530     return config->is_new;
531 }
532
533 static const char *
534 _config_get (notmuch_config_t *config, char **field,
535              const char *group, const char *key)
536 {
537     /* read from config file and cache value, if not cached already */
538     if (*field == NULL) {
539         char *value;
540         value = g_key_file_get_string (config->key_file, group, key, NULL);
541         if (value) {
542             *field = talloc_strdup (config, value);
543             free (value);
544         }
545     }
546     return *field;
547 }
548
549 static void
550 _config_set (notmuch_config_t *config, char **field,
551              const char *group, const char *key, const char *value)
552 {
553     g_key_file_set_string (config->key_file, group, key, value);
554
555     /* drop the cached value */
556     talloc_free (*field);
557     *field = NULL;
558 }
559
560 static const char **
561 _config_get_list (notmuch_config_t *config,
562                   const char *section, const char *key,
563                   const char ***outlist, size_t *list_length, size_t *ret_length)
564 {
565     assert(outlist);
566
567     /* read from config file and cache value, if not cached already */
568     if (*outlist == NULL) {
569
570         char **inlist = g_key_file_get_string_list (config->key_file,
571                                              section, key, list_length, NULL);
572         if (inlist) {
573             unsigned int i;
574
575             *outlist = talloc_size (config, sizeof (char *) * (*list_length + 1));
576
577             for (i = 0; i < *list_length; i++)
578                 (*outlist)[i] = talloc_strdup (*outlist, inlist[i]);
579
580             (*outlist)[i] = NULL;
581
582             g_strfreev (inlist);
583         }
584     }
585
586     if (ret_length)
587         *ret_length = *list_length;
588
589     return *outlist;
590 }
591
592 static void
593 _config_set_list (notmuch_config_t *config,
594                   const char *group, const char *name,
595                   const char *list[],
596                   size_t length, const char ***config_var )
597 {
598     g_key_file_set_string_list (config->key_file, group, name, list, length);
599
600     /* drop the cached value */
601     talloc_free (*config_var);
602     *config_var = NULL;
603 }
604
605 const char *
606 notmuch_config_get_database_path (notmuch_config_t *config)
607 {
608     return _config_get (config, &config->database_path, "database", "path");
609 }
610
611 void
612 notmuch_config_set_database_path (notmuch_config_t *config,
613                                   const char *database_path)
614 {
615     _config_set (config, &config->database_path, "database", "path", database_path);
616 }
617
618 const char *
619 notmuch_config_get_user_name (notmuch_config_t *config)
620 {
621     return _config_get (config, &config->user_name, "user", "name");
622 }
623
624 void
625 notmuch_config_set_user_name (notmuch_config_t *config,
626                               const char *user_name)
627 {
628     _config_set (config, &config->user_name, "user", "name", user_name);
629 }
630
631 const char *
632 notmuch_config_get_user_primary_email (notmuch_config_t *config)
633 {
634     return _config_get (config, &config->user_primary_email, "user", "primary_email");
635 }
636
637 void
638 notmuch_config_set_user_primary_email (notmuch_config_t *config,
639                                        const char *primary_email)
640 {
641     _config_set (config, &config->user_primary_email, "user", "primary_email", primary_email);
642 }
643
644 const char **
645 notmuch_config_get_user_other_email (notmuch_config_t *config,   size_t *length)
646 {
647     return _config_get_list (config, "user", "other_email",
648                              &(config->user_other_email),
649                              &(config->user_other_email_length), length);
650 }
651
652 const char **
653 notmuch_config_get_new_tags (notmuch_config_t *config,   size_t *length)
654 {
655     return _config_get_list (config, "new", "tags",
656                              &(config->new_tags),
657                              &(config->new_tags_length), length);
658 }
659
660 const char **
661 notmuch_config_get_new_ignore (notmuch_config_t *config, size_t *length)
662 {
663     return _config_get_list (config, "new", "ignore",
664                              &(config->new_ignore),
665                              &(config->new_ignore_length), length);
666 }
667
668 void
669 notmuch_config_set_user_other_email (notmuch_config_t *config,
670                                      const char *list[],
671                                      size_t length)
672 {
673     _config_set_list (config, "user", "other_email", list, length,
674                      &(config->user_other_email));
675 }
676
677 void
678 notmuch_config_set_new_tags (notmuch_config_t *config,
679                                      const char *list[],
680                                      size_t length)
681 {
682     _config_set_list (config, "new", "tags", list, length,
683                      &(config->new_tags));
684 }
685
686 void
687 notmuch_config_set_new_ignore (notmuch_config_t *config,
688                                const char *list[],
689                                size_t length)
690 {
691     _config_set_list (config, "new", "ignore", list, length,
692                      &(config->new_ignore));
693 }
694
695 const char **
696 notmuch_config_get_search_exclude_tags (notmuch_config_t *config, size_t *length)
697 {
698     return _config_get_list (config, "search", "exclude_tags",
699                              &(config->search_exclude_tags),
700                              &(config->search_exclude_tags_length), length);
701 }
702
703 void
704 notmuch_config_set_search_exclude_tags (notmuch_config_t *config,
705                                       const char *list[],
706                                       size_t length)
707 {
708     _config_set_list (config, "search", "exclude_tags", list, length,
709                       &(config->search_exclude_tags));
710 }
711
712 const char *
713 notmuch_config_get_crypto_gpg_path (notmuch_config_t *config)
714 {
715     return _config_get (config, &config->crypto_gpg_path, "crypto", "gpg_path");
716 }
717
718 void
719 notmuch_config_set_crypto_gpg_path (notmuch_config_t *config,
720                               const char *gpg_path)
721 {
722     _config_set (config, &config->crypto_gpg_path, "crypto", "gpg_path", gpg_path);
723 }
724
725
726 /* Given a configuration item of the form <group>.<key> return the
727  * component group and key. If any error occurs, print a message on
728  * stderr and return 1. Otherwise, return 0.
729  *
730  * Note: This function modifies the original 'item' string.
731  */
732 static int
733 _item_split (char *item, char **group, char **key)
734 {
735     char *period;
736
737     *group = item;
738
739     period = strchr (item, '.');
740     if (period == NULL || *(period+1) == '\0') {
741         fprintf (stderr,
742                  "Invalid configuration name: %s\n"
743                  "(Should be of the form <section>.<item>)\n", item);
744         return 1;
745     }
746
747     *period = '\0';
748     *key = period + 1;
749
750     return 0;
751 }
752
753 #define BUILT_WITH_PREFIX "built_with."
754 #define QUERY_PREFIX "query."
755
756 static int
757 _print_db_config(notmuch_config_t *config, const char *name)
758 {
759     notmuch_database_t *notmuch;
760     char *val;
761
762     if (notmuch_database_open (notmuch_config_get_database_path (config),
763                                NOTMUCH_DATABASE_MODE_READ_ONLY, &notmuch))
764         return EXIT_FAILURE;
765
766     /* XXX Handle UUID mismatch? */
767
768     if (print_status_database ("notmuch config", notmuch,
769                                notmuch_database_get_config (notmuch, name, &val)))
770         return EXIT_FAILURE;
771
772      puts (val);
773
774     return EXIT_SUCCESS;
775 }
776
777 static int
778 notmuch_config_command_get (notmuch_config_t *config, char *item)
779 {
780     if (strcmp(item, "database.path") == 0) {
781         printf ("%s\n", notmuch_config_get_database_path (config));
782     } else if (strcmp(item, "user.name") == 0) {
783         printf ("%s\n", notmuch_config_get_user_name (config));
784     } else if (strcmp(item, "user.primary_email") == 0) {
785         printf ("%s\n", notmuch_config_get_user_primary_email (config));
786     } else if (strcmp(item, "user.other_email") == 0) {
787         const char **other_email;
788         size_t i, length;
789         
790         other_email = notmuch_config_get_user_other_email (config, &length);
791         for (i = 0; i < length; i++)
792             printf ("%s\n", other_email[i]);
793     } else if (strcmp(item, "new.tags") == 0) {
794         const char **tags;
795         size_t i, length;
796
797         tags = notmuch_config_get_new_tags (config, &length);
798         for (i = 0; i < length; i++)
799             printf ("%s\n", tags[i]);
800     } else if (STRNCMP_LITERAL (item, BUILT_WITH_PREFIX) == 0) {
801         printf ("%s\n",
802                 notmuch_built_with (item + strlen (BUILT_WITH_PREFIX)) ? "true" : "false");
803     } else if (STRNCMP_LITERAL (item, QUERY_PREFIX) == 0) {
804         return _print_db_config (config, item);
805     } else {
806         char **value;
807         size_t i, length;
808         char *group, *key;
809
810         if (_item_split (item, &group, &key))
811             return 1;
812
813         value = g_key_file_get_string_list (config->key_file,
814                                             group, key,
815                                             &length, NULL);
816         if (value == NULL) {
817             fprintf (stderr, "Unknown configuration item: %s.%s\n",
818                      group, key);
819             return 1;
820         }
821
822         for (i = 0; i < length; i++)
823             printf ("%s\n", value[i]);
824
825         g_strfreev (value);
826     }
827
828     return 0;
829 }
830
831 static int
832 _set_db_config(notmuch_config_t *config, const char *key, int argc, char **argv)
833 {
834     notmuch_database_t *notmuch;
835     const char *val = "";
836
837     if (argc > 1) {
838         /* XXX handle lists? */
839         fprintf (stderr, "notmuch config set: at most one value expected for %s\n", key);
840         return EXIT_FAILURE;
841     }
842
843     if (argc > 0) {
844         val = argv[0];
845     }
846
847     if (notmuch_database_open (notmuch_config_get_database_path (config),
848                                NOTMUCH_DATABASE_MODE_READ_WRITE, &notmuch))
849         return EXIT_FAILURE;
850
851     /* XXX Handle UUID mismatch? */
852
853     if (print_status_database ("notmuch config", notmuch,
854                                notmuch_database_set_config (notmuch, key, val)))
855         return EXIT_FAILURE;
856
857     if (print_status_database ("notmuch config", notmuch,
858                                notmuch_database_close (notmuch)))
859         return EXIT_FAILURE;
860
861     return EXIT_SUCCESS;
862 }
863
864 static int
865 notmuch_config_command_set (notmuch_config_t *config, char *item, int argc, char *argv[])
866 {
867     char *group, *key;
868
869     if (STRNCMP_LITERAL (item, BUILT_WITH_PREFIX) == 0) {
870         fprintf (stderr, "Error: read only option: %s\n", item);
871         return 1;
872     }
873
874     if (STRNCMP_LITERAL (item, QUERY_PREFIX) == 0) {
875         return _set_db_config (config, item, argc, argv);
876     }
877
878     if (_item_split (item, &group, &key))
879         return 1;
880
881     /* With only the name of an item, we clear it from the
882      * configuration file.
883      *
884      * With a single value, we set it as a string.
885      *
886      * With multiple values, we set them as a string list.
887      */
888     switch (argc) {
889     case 0:
890         g_key_file_remove_key (config->key_file, group, key, NULL);
891         break;
892     case 1:
893         g_key_file_set_string (config->key_file, group, key, argv[0]);
894         break;
895     default:
896         g_key_file_set_string_list (config->key_file, group, key,
897                                     (const gchar **) argv, argc);
898         break;
899     }
900
901     return notmuch_config_save (config);
902 }
903
904 static
905 void
906 _notmuch_config_list_built_with ()
907 {
908     printf("%scompact=%s\n",
909            BUILT_WITH_PREFIX,
910            notmuch_built_with ("compact") ? "true" : "false");
911     printf("%sfield_processor=%s\n",
912            BUILT_WITH_PREFIX,
913            notmuch_built_with ("field_processor") ? "true" : "false");
914     printf("%sretry_lock=%s\n",
915            BUILT_WITH_PREFIX,
916            notmuch_built_with ("retry_lock") ? "true" : "false");
917 }
918
919 static int
920 _list_db_config (notmuch_config_t *config)
921 {
922     notmuch_database_t *notmuch;
923     notmuch_config_list_t *list;
924
925     if (notmuch_database_open (notmuch_config_get_database_path (config),
926                                NOTMUCH_DATABASE_MODE_READ_ONLY, &notmuch))
927         return EXIT_FAILURE;
928
929     /* XXX Handle UUID mismatch? */
930
931
932     if (print_status_database ("notmuch config", notmuch,
933                                notmuch_database_get_config_list (notmuch, "", &list)))
934         return EXIT_FAILURE;
935
936     for (; notmuch_config_list_valid (list); notmuch_config_list_move_to_next (list)) {
937         printf("%s=%s\n", notmuch_config_list_key (list), notmuch_config_list_value(list));
938     }
939     notmuch_config_list_destroy (list);
940
941    return EXIT_SUCCESS;
942 }
943
944 static int
945 notmuch_config_command_list (notmuch_config_t *config)
946 {
947     char **groups;
948     size_t g, groups_length;
949
950     groups = g_key_file_get_groups (config->key_file, &groups_length);
951     if (groups == NULL)
952         return 1;
953
954     for (g = 0; g < groups_length; g++) {
955         char **keys;
956         size_t k, keys_length;
957
958         keys = g_key_file_get_keys (config->key_file,
959                                     groups[g], &keys_length, NULL);
960         if (keys == NULL)
961             continue;
962
963         for (k = 0; k < keys_length; k++) {
964             char *value;
965
966             value = g_key_file_get_string (config->key_file,
967                                            groups[g], keys[k], NULL);
968             if (value != NULL) {
969                 printf ("%s.%s=%s\n", groups[g], keys[k], value);
970                 free (value);
971             }
972         }
973
974         g_strfreev (keys);
975     }
976
977     g_strfreev (groups);
978
979     _notmuch_config_list_built_with ();
980     return _list_db_config (config);
981 }
982
983 int
984 notmuch_config_command (notmuch_config_t *config, int argc, char *argv[])
985 {
986     int ret;
987     int opt_index;
988
989     opt_index = notmuch_minimal_options ("config", argc, argv);
990     if (opt_index < 0)
991         return EXIT_FAILURE;
992
993     if (notmuch_requested_db_uuid)
994         fprintf (stderr, "Warning: ignoring --uuid=%s\n",
995                  notmuch_requested_db_uuid);
996
997     /* skip at least subcommand argument */
998     argc-= opt_index;
999     argv+= opt_index;
1000
1001     if (argc < 1) {
1002         fprintf (stderr, "Error: notmuch config requires at least one argument.\n");
1003         return EXIT_FAILURE;
1004     }
1005
1006     if (strcmp (argv[0], "get") == 0) {
1007         if (argc != 2) {
1008             fprintf (stderr, "Error: notmuch config get requires exactly "
1009                      "one argument.\n");
1010             return EXIT_FAILURE;
1011         }
1012         ret = notmuch_config_command_get (config, argv[1]);
1013     } else if (strcmp (argv[0], "set") == 0) {
1014         if (argc < 2) {
1015             fprintf (stderr, "Error: notmuch config set requires at least "
1016                      "one argument.\n");
1017             return EXIT_FAILURE;
1018         }
1019         ret = notmuch_config_command_set (config, argv[1], argc - 2, argv + 2);
1020     } else if (strcmp (argv[0], "list") == 0) {
1021         ret = notmuch_config_command_list (config);
1022     } else {
1023         fprintf (stderr, "Unrecognized argument for notmuch config: %s\n",
1024                  argv[0]);
1025         return EXIT_FAILURE;
1026     }
1027
1028     return ret ? EXIT_FAILURE : EXIT_SUCCESS;
1029
1030 }
1031
1032 notmuch_bool_t
1033 notmuch_config_get_maildir_synchronize_flags (notmuch_config_t *config)
1034 {
1035     return config->maildir_synchronize_flags;
1036 }
1037
1038 void
1039 notmuch_config_set_maildir_synchronize_flags (notmuch_config_t *config,
1040                                               notmuch_bool_t synchronize_flags)
1041 {
1042     g_key_file_set_boolean (config->key_file,
1043                             "maildir", "synchronize_flags", synchronize_flags);
1044     config->maildir_synchronize_flags = synchronize_flags;
1045 }