test: copy files in test_expect_equal_file instead of moving them
[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 http://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
26 static const char toplevel_config_comment[] =
27     " .notmuch-config - Configuration file for the notmuch mail system\n"
28     "\n"
29     " For more information about notmuch, see http://notmuchmail.org";
30
31 static const char database_config_comment[] =
32     " Database configuration\n"
33     "\n"
34     " The only value supported here is 'path' which should be the top-level\n"
35     " directory where your mail currently exists and to where mail will be\n"
36     " delivered in the future. Files should be individual email messages.\n"
37     " Notmuch will store its database within a sub-directory of the path\n"
38     " configured here named \".notmuch\".\n";
39
40 static const char new_config_comment[] =
41     " Configuration for \"notmuch new\"\n"
42     "\n"
43     " The following options are supported here:\n"
44     "\n"
45     "\ttags     A list (separated by ';') of the tags that will be\n"
46     "\t added to all messages incorporated by \"notmuch new\".\n";
47
48 static const char user_config_comment[] =
49     " User configuration\n"
50     "\n"
51     " Here is where you can let notmuch know how you would like to be\n"
52     " addressed. Valid settings are\n"
53     "\n"
54     "\tname             Your full name.\n"
55     "\tprimary_email    Your primary email address.\n"
56     "\tother_email      A list (separated by ';') of other email addresses\n"
57     "\t         at which you receive email.\n"
58     "\n"
59     " Notmuch will use the various email addresses configured here when\n"
60     " formatting replies. It will avoid including your own addresses in the\n"
61     " recipient list of replies, and will set the From address based on the\n"
62     " address to which the original email was addressed.\n";
63
64 static const char maildir_config_comment[] =
65     " Maildir compatibility configuration\n"
66     "\n"
67     " The following option is supported here:\n"
68     "\n"
69     "\tsynchronize_flags      Valid values are true and false.\n"
70     "\n"
71     "\tIf true, then the following maildir flags (in message filenames)\n"
72     "\twill be syncrhonized with the corresponding notmuch tags:\n"
73     "\n"
74     "\t\tFlag   Tag\n"
75     "\t\t----   -------\n"
76     "\t\tD      draft\n"
77     "\t\tF      flagged\n"
78     "\t\tP      passed\n"
79     "\t\tR      replied\n"
80     "\t\tS      unread (added when 'S' flag is not present)\n"
81     "\n"
82     "\tThe \"notmuch new\" command will notice flag changes in filenames\n"
83     "\tand update tags, while the \"notmuch tag\" and \"notmuch restore\"\n"
84     "\tcommands will notice tag changes and update flags in filenames\n";
85
86 struct _notmuch_config {
87     char *filename;
88     GKeyFile *key_file;
89
90     char *database_path;
91     char *user_name;
92     char *user_primary_email;
93     const char **user_other_email;
94     size_t user_other_email_length;
95     const char **new_tags;
96     size_t new_tags_length;
97     notmuch_bool_t maildir_synchronize_flags;
98 };
99
100 static int
101 notmuch_config_destructor (notmuch_config_t *config)
102 {
103     if (config->key_file)
104         g_key_file_free (config->key_file);
105
106     return 0;
107 }
108
109 static char *
110 get_name_from_passwd_file (void *ctx)
111 {
112     long pw_buf_size;
113     char *pw_buf;
114     struct passwd passwd, *ignored;
115     char *name;
116     int e;
117
118     pw_buf_size = sysconf(_SC_GETPW_R_SIZE_MAX);
119     if (pw_buf_size == -1) pw_buf_size = 64;
120     pw_buf = talloc_size (ctx, pw_buf_size);
121
122     while ((e = getpwuid_r (getuid (), &passwd, pw_buf,
123                             pw_buf_size, &ignored)) == ERANGE) {
124         pw_buf_size = pw_buf_size * 2;
125         pw_buf = talloc_zero_size(ctx, pw_buf_size);
126     }
127
128     if (e == 0) {
129         char *comma = strchr (passwd.pw_gecos, ',');
130         if (comma)
131             name = talloc_strndup (ctx, passwd.pw_gecos,
132                                    comma - passwd.pw_gecos);
133         else
134             name = talloc_strdup (ctx, passwd.pw_gecos);
135     } else {
136         name = talloc_strdup (ctx, "");
137     }
138
139     talloc_free (pw_buf);
140
141     return name;
142 }
143
144 static char *
145 get_username_from_passwd_file (void *ctx)
146 {
147     long pw_buf_size;
148     char *pw_buf;
149     struct passwd passwd, *ignored;
150     char *name;
151     int e;
152
153     pw_buf_size = sysconf(_SC_GETPW_R_SIZE_MAX);
154     if (pw_buf_size == -1) pw_buf_size = 64;
155     pw_buf = talloc_zero_size (ctx, pw_buf_size);
156
157     while ((e = getpwuid_r (getuid (), &passwd, pw_buf,
158                             pw_buf_size, &ignored)) == ERANGE) {
159         pw_buf_size = pw_buf_size * 2;
160         pw_buf = talloc_zero_size(ctx, pw_buf_size);
161     }
162
163     if (e == 0)
164         name = talloc_strdup (ctx, passwd.pw_name);
165     else
166         name = talloc_strdup (ctx, "");
167
168     talloc_free (pw_buf);
169
170     return name;
171 }
172
173 /* Open the named notmuch configuration file. If the filename is NULL,
174  * the value of the environment variable $NOTMUCH_CONFIG will be used.
175  * If $NOTMUCH_CONFIG is unset, the default configuration file
176  * ($HOME/.notmuch-config) will be used.
177  *
178  * If any error occurs, (out of memory, or a permission-denied error,
179  * etc.), this function will print a message to stderr and return
180  * NULL.
181  *
182  * FILE NOT FOUND: When the specified configuration file (whether from
183  * 'filename' or the $NOTMUCH_CONFIG environment variable) does not
184  * exist, the behavior of this function depends on the 'is_new_ret'
185  * variable.
186  *
187  *      If is_new_ret is NULL, then a "file not found" message will be
188  *      printed to stderr and NULL will be returned.
189
190  *      If is_new_ret is non-NULL then a default configuration will be
191  *      returned and *is_new_ret will be set to 1 on return so that
192  *      the caller can recognize this case.
193  *
194  *      These default configuration settings are determined as
195  *      follows:
196  *
197  *              database_path:          $HOME/mail
198  *
199  *              user_name:              From /etc/passwd
200  *
201  *              user_primary_mail:      $EMAIL variable if set, otherwise
202  *                                      constructed from the username and
203  *                                      hostname of the current machine.
204  *
205  *              user_other_email:       Not set.
206  *
207  *      The default configuration also contains comments to guide the
208  *      user in editing the file directly.
209  */
210 notmuch_config_t *
211 notmuch_config_open (void *ctx,
212                      const char *filename,
213                      notmuch_bool_t *is_new_ret)
214 {
215     GError *error = NULL;
216     int is_new = 0;
217     size_t tmp;
218     char *notmuch_config_env = NULL;
219     int file_had_database_group;
220     int file_had_new_group;
221     int file_had_user_group;
222     int file_had_maildir_group;
223
224     if (is_new_ret)
225         *is_new_ret = 0;
226
227     notmuch_config_t *config = talloc (ctx, notmuch_config_t);
228     if (config == NULL) {
229         fprintf (stderr, "Out of memory.\n");
230         return NULL;
231     }
232     
233     talloc_set_destructor (config, notmuch_config_destructor);
234
235     if (filename) {
236         config->filename = talloc_strdup (config, filename);
237     } else if ((notmuch_config_env = getenv ("NOTMUCH_CONFIG"))) {
238         config->filename = talloc_strdup (config, notmuch_config_env);
239     } else {
240         config->filename = talloc_asprintf (config, "%s/.notmuch-config",
241                                             getenv ("HOME"));
242     }
243
244     config->key_file = g_key_file_new ();
245
246     config->database_path = NULL;
247     config->user_name = NULL;
248     config->user_primary_email = NULL;
249     config->user_other_email = NULL;
250     config->user_other_email_length = 0;
251     config->new_tags = NULL;
252     config->new_tags_length = 0;
253     config->maildir_synchronize_flags = TRUE;
254
255     if (! g_key_file_load_from_file (config->key_file,
256                                      config->filename,
257                                      G_KEY_FILE_KEEP_COMMENTS,
258                                      &error))
259     {
260         /* If the caller passed a non-NULL value for is_new_ret, then
261          * the caller is prepared for a default configuration file in
262          * the case of FILE NOT FOUND. Otherwise, any read failure is
263          * an error.
264          */
265         if (is_new_ret &&
266             error->domain == G_FILE_ERROR &&
267             error->code == G_FILE_ERROR_NOENT)
268         {
269             g_error_free (error);
270             is_new = 1;
271         }
272         else
273         {
274             fprintf (stderr, "Error reading configuration file %s: %s\n",
275                      config->filename, error->message);
276             talloc_free (config);
277             g_error_free (error);
278             return NULL;
279         }
280     }
281
282     /* Whenever we know of configuration sections that don't appear in
283      * the configuration file, we add some comments to help the user
284      * understand what can be done.
285      *
286      * It would be convenient to just add those comments now, but
287      * apparently g_key_file will clear any comments when keys are
288      * added later that create the groups. So we have to check for the
289      * groups now, but add the comments only after setting all of our
290      * values.
291      */
292     file_had_database_group = g_key_file_has_group (config->key_file,
293                                                     "database");
294     file_had_new_group = g_key_file_has_group (config->key_file, "new");
295     file_had_user_group = g_key_file_has_group (config->key_file, "user");
296     file_had_maildir_group = g_key_file_has_group (config->key_file, "maildir");
297
298
299     if (notmuch_config_get_database_path (config) == NULL) {
300         char *path = talloc_asprintf (config, "%s/mail",
301                                       getenv ("HOME"));
302         notmuch_config_set_database_path (config, path);
303         talloc_free (path);
304     }
305
306     if (notmuch_config_get_user_name (config) == NULL) {
307         char *name = get_name_from_passwd_file (config);
308         notmuch_config_set_user_name (config, name);
309         talloc_free (name);
310     }
311
312     if (notmuch_config_get_user_primary_email (config) == NULL) {
313         char *email = getenv ("EMAIL");
314         if (email) {
315             notmuch_config_set_user_primary_email (config, email);
316         } else {
317             char hostname[256];
318             struct hostent *hostent;
319             const char *domainname;
320
321             char *username = get_username_from_passwd_file (config);
322
323             gethostname (hostname, 256);
324             hostname[255] = '\0';
325
326             hostent = gethostbyname (hostname);
327             if (hostent && (domainname = strchr (hostent->h_name, '.')))
328                 domainname += 1;
329             else
330                 domainname = "(none)";
331
332             email = talloc_asprintf (config, "%s@%s.%s",
333                                      username, hostname, domainname);
334
335             notmuch_config_set_user_primary_email (config, email);
336
337             talloc_free (username);
338             talloc_free (email);
339         }
340     }
341
342     if (notmuch_config_get_new_tags (config, &tmp) == NULL) {
343         const char *tags[] = { "unread", "inbox" };
344         notmuch_config_set_new_tags (config, tags, 2);
345     }
346
347     error = NULL;
348     config->maildir_synchronize_flags =
349         g_key_file_get_boolean (config->key_file,
350                                 "maildir", "synchronize_flags", &error);
351     if (error) {
352         notmuch_config_set_maildir_synchronize_flags (config, TRUE);
353         g_error_free (error);
354     }
355
356     /* Whenever we know of configuration sections that don't appear in
357      * the configuration file, we add some comments to help the user
358      * understand what can be done. */
359     if (is_new)
360     {
361         g_key_file_set_comment (config->key_file, NULL, NULL,
362                                 toplevel_config_comment, NULL);
363     }
364
365     if (! file_had_database_group)
366     {
367         g_key_file_set_comment (config->key_file, "database", NULL,
368                                 database_config_comment, NULL);
369     }
370
371     if (! file_had_new_group)
372     {
373         g_key_file_set_comment (config->key_file, "new", NULL,
374                                 new_config_comment, NULL);
375     }
376
377     if (! file_had_user_group)
378     {
379         g_key_file_set_comment (config->key_file, "user", NULL,
380                                 user_config_comment, NULL);
381     }
382
383     if (! file_had_maildir_group)
384     {
385         g_key_file_set_comment (config->key_file, "maildir", NULL,
386                                 maildir_config_comment, NULL);
387     }
388
389     if (is_new_ret)
390         *is_new_ret = is_new;
391
392     return config;
393 }
394
395 /* Close the given notmuch_config_t object, freeing all resources.
396  * 
397  * Note: Any changes made to the configuration are *not* saved by this
398  * function. To save changes, call notmuch_config_save before
399  * notmuch_config_close.
400 */
401 void
402 notmuch_config_close (notmuch_config_t *config)
403 {
404     talloc_free (config);
405 }
406
407 /* Save any changes made to the notmuch configuration.
408  *
409  * Any comments originally in the file will be preserved.
410  *
411  * Returns 0 if successful, and 1 in case of any error, (after
412  * printing a description of the error to stderr).
413  */
414 int
415 notmuch_config_save (notmuch_config_t *config)
416 {
417     size_t length;
418     char *data;
419     GError *error = NULL;
420
421     data = g_key_file_to_data (config->key_file, &length, NULL);
422     if (data == NULL) {
423         fprintf (stderr, "Out of memory.\n");
424         return 1;
425     }
426
427     if (! g_file_set_contents (config->filename, data, length, &error)) {
428         fprintf (stderr, "Error saving configuration to %s: %s\n",
429                  config->filename, error->message);
430         g_error_free (error);
431         g_free (data);
432         return 1;
433     }
434
435     g_free (data);
436     return 0;
437 }
438
439 const char *
440 notmuch_config_get_database_path (notmuch_config_t *config)
441 {
442     char *path;
443
444     if (config->database_path == NULL) {
445         path = g_key_file_get_string (config->key_file,
446                                       "database", "path", NULL);
447         if (path) {
448             config->database_path = talloc_strdup (config, path);
449             free (path);
450         }
451     }
452
453     return config->database_path;
454 }
455
456 void
457 notmuch_config_set_database_path (notmuch_config_t *config,
458                                   const char *database_path)
459 {
460     g_key_file_set_string (config->key_file,
461                            "database", "path", database_path);
462
463     talloc_free (config->database_path);
464     config->database_path = NULL;
465 }
466
467 const char *
468 notmuch_config_get_user_name (notmuch_config_t *config)
469 {
470     char *name;
471
472     if (config->user_name == NULL) {
473         name = g_key_file_get_string (config->key_file,
474                                       "user", "name", NULL);
475         if (name) {
476             config->user_name = talloc_strdup (config, name);
477             free (name);
478         }
479     }
480
481     return config->user_name;
482 }
483
484 void
485 notmuch_config_set_user_name (notmuch_config_t *config,
486                               const char *user_name)
487 {
488     g_key_file_set_string (config->key_file,
489                            "user", "name", user_name);
490
491     talloc_free (config->user_name);
492     config->user_name = NULL;
493 }
494
495 const char *
496 notmuch_config_get_user_primary_email (notmuch_config_t *config)
497 {
498     char *email;
499
500     if (config->user_primary_email == NULL) {
501         email = g_key_file_get_string (config->key_file,
502                                        "user", "primary_email", NULL);
503         if (email) {
504             config->user_primary_email = talloc_strdup (config, email);
505             free (email);
506         }
507     }
508
509     return config->user_primary_email;
510 }
511
512 void
513 notmuch_config_set_user_primary_email (notmuch_config_t *config,
514                                        const char *primary_email)
515 {
516     g_key_file_set_string (config->key_file,
517                            "user", "primary_email", primary_email);
518
519     talloc_free (config->user_primary_email);
520     config->user_primary_email = NULL;
521 }
522
523 const char **
524 notmuch_config_get_user_other_email (notmuch_config_t *config,
525                                      size_t *length)
526 {
527     char **emails;
528     size_t emails_length;
529     unsigned int i;
530
531     if (config->user_other_email == NULL) {
532         emails = g_key_file_get_string_list (config->key_file,
533                                              "user", "other_email",
534                                              &emails_length, NULL);
535         if (emails) {
536             config->user_other_email = talloc_size (config,
537                                                     sizeof (char *) *
538                                                     (emails_length + 1));
539             for (i = 0; i < emails_length; i++)
540                 config->user_other_email[i] = talloc_strdup (config->user_other_email,
541                                                              emails[i]);
542             config->user_other_email[i] = NULL;
543
544             g_strfreev (emails);
545
546             config->user_other_email_length = emails_length;
547         }
548     }
549
550     *length = config->user_other_email_length;
551     return config->user_other_email;
552 }
553
554 void
555 notmuch_config_set_user_other_email (notmuch_config_t *config,
556                                      const char *other_email[],
557                                      size_t length)
558 {
559     g_key_file_set_string_list (config->key_file,
560                                 "user", "other_email",
561                                 other_email, length);
562
563     talloc_free (config->user_other_email);
564     config->user_other_email = NULL;
565 }
566
567 const char **
568 notmuch_config_get_new_tags (notmuch_config_t *config,
569                              size_t *length)
570 {
571     char **tags;
572     size_t tags_length;
573     unsigned int i;
574
575     if (config->new_tags == NULL) {
576         tags = g_key_file_get_string_list (config->key_file,
577                                            "new", "tags",
578                                            &tags_length, NULL);
579         if (tags) {
580             config->new_tags = talloc_size (config,
581                                             sizeof (char *) *
582                                             (tags_length + 1));
583             for (i = 0; i < tags_length; i++)
584                 config->new_tags[i] = talloc_strdup (config->new_tags,
585                                                      tags[i]);
586             config->new_tags[i] = NULL;
587
588             g_strfreev (tags);
589
590             config->new_tags_length = tags_length;
591         }
592     }
593
594     *length = config->new_tags_length;
595     return config->new_tags;
596 }
597
598 void
599 notmuch_config_set_new_tags (notmuch_config_t *config,
600                              const char *new_tags[],
601                              size_t length)
602 {
603     g_key_file_set_string_list (config->key_file,
604                                 "new", "tags",
605                                 new_tags, length);
606
607     talloc_free (config->new_tags);
608     config->new_tags = NULL;
609 }
610
611 /* Given a configuration item of the form <group>.<key> return the
612  * component group and key. If any error occurs, print a message on
613  * stderr and return 1. Otherwise, return 0.
614  *
615  * Note: This function modifies the original 'item' string.
616  */
617 static int
618 _item_split (char *item, char **group, char **key)
619 {
620     char *period;
621
622     *group = item;
623
624     period = index (item, '.');
625     if (period == NULL || *(period+1) == '\0') {
626         fprintf (stderr,
627                  "Invalid configuration name: %s\n"
628                  "(Should be of the form <section>.<item>)\n", item);
629         return 1;
630     }
631
632     *period = '\0';
633     *key = period + 1;
634
635     return 0;
636 }
637
638 static int
639 notmuch_config_command_get (void *ctx, char *item)
640 {
641     notmuch_config_t *config;
642
643     config = notmuch_config_open (ctx, NULL, NULL);
644     if (config == NULL)
645         return 1;
646
647     if (strcmp(item, "database.path") == 0) {
648         printf ("%s\n", notmuch_config_get_database_path (config));
649     } else if (strcmp(item, "user.name") == 0) {
650         printf ("%s\n", notmuch_config_get_user_name (config));
651     } else if (strcmp(item, "user.primary_email") == 0) {
652         printf ("%s\n", notmuch_config_get_user_primary_email (config));
653     } else if (strcmp(item, "user.other_email") == 0) {
654         const char **other_email;
655         size_t i, length;
656         
657         other_email = notmuch_config_get_user_other_email (config, &length);
658         for (i = 0; i < length; i++)
659             printf ("%s\n", other_email[i]);
660     } else if (strcmp(item, "new.tags") == 0) {
661         const char **tags;
662         size_t i, length;
663
664         tags = notmuch_config_get_new_tags (config, &length);
665         for (i = 0; i < length; i++)
666             printf ("%s\n", tags[i]);
667     } else {
668         char **value;
669         size_t i, length;
670         char *group, *key;
671
672         if (_item_split (item, &group, &key))
673             return 1;
674
675         value = g_key_file_get_string_list (config->key_file,
676                                             group, key,
677                                             &length, NULL);
678         if (value == NULL) {
679             fprintf (stderr, "Unknown configuration item: %s.%s\n",
680                      group, key);
681             return 1;
682         }
683
684         for (i = 0; i < length; i++)
685             printf ("%s\n", value[i]);
686
687         free (value);
688     }
689
690     notmuch_config_close (config);
691
692     return 0;
693 }
694
695 static int
696 notmuch_config_command_set (void *ctx, char *item, int argc, char *argv[])
697 {
698     notmuch_config_t *config;
699     char *group, *key;
700     int ret;
701
702     if (_item_split (item, &group, &key))
703         return 1;
704
705     config = notmuch_config_open (ctx, NULL, NULL);
706     if (config == NULL)
707         return 1;
708
709     /* With only the name of an item, we clear it from the
710      * configuration file.
711      *
712      * With a single value, we set it as a string.
713      *
714      * With multiple values, we set them as a string list.
715      */
716     switch (argc) {
717     case 0:
718         g_key_file_remove_key (config->key_file, group, key, NULL);
719         break;
720     case 1:
721         g_key_file_set_string (config->key_file, group, key, argv[0]);
722         break;
723     default:
724         g_key_file_set_string_list (config->key_file, group, key,
725                                     (const gchar **) argv, argc);
726         break;
727     }
728
729     ret = notmuch_config_save (config);
730     notmuch_config_close (config);
731
732     return ret;
733 }
734
735 int
736 notmuch_config_command (void *ctx, int argc, char *argv[])
737 {
738     if (argc < 2) {
739         fprintf (stderr, "Error: notmuch config requires at least two arguments.\n");
740         return 1;
741     }
742
743     if (strcmp (argv[0], "get") == 0)
744         return notmuch_config_command_get (ctx, argv[1]);
745     else if (strcmp (argv[0], "set") == 0)
746         return notmuch_config_command_set (ctx, argv[1], argc - 2, argv + 2);
747
748     fprintf (stderr, "Unrecognized argument for notmuch config: %s\n",
749              argv[0]);
750     return 1;
751 }
752
753 notmuch_bool_t
754 notmuch_config_get_maildir_synchronize_flags (notmuch_config_t *config)
755 {
756     return config->maildir_synchronize_flags;
757 }
758
759 void
760 notmuch_config_set_maildir_synchronize_flags (notmuch_config_t *config,
761                                               notmuch_bool_t synchronize_flags)
762 {
763     g_key_file_set_boolean (config->key_file,
764                             "maildir", "synchronize_flags", synchronize_flags);
765     config->maildir_synchronize_flags = synchronize_flags;
766 }