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