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