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