1 /* notmuch - Not much of an email program, (just index and search)
3 * Copyright © 2009 Carl Worth
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.
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.
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/ .
18 * Author: Carl Worth <cworth@cworth.org>
21 #include "notmuch-client.h"
27 static const char toplevel_config_comment[] =
28 " .notmuch-config - Configuration file for the notmuch mail system\n"
30 " For more information about notmuch, see http://notmuchmail.org";
32 static const char database_config_comment[] =
33 " Database configuration\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";
41 static const char new_config_comment[] =
42 " Configuration for \"notmuch new\"\n"
44 " The following options are supported here:\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"
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"
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";
56 static const char user_config_comment[] =
57 " User configuration\n"
59 " Here is where you can let notmuch know how you would like to be\n"
60 " addressed. Valid settings are\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"
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";
72 static const char maildir_config_comment[] =
73 " Maildir compatibility configuration\n"
75 " The following option is supported here:\n"
77 "\tsynchronize_flags Valid values are true and false.\n"
79 "\tIf true, then the following maildir flags (in message filenames)\n"
80 "\twill be synchronized with the corresponding notmuch tags:\n"
88 "\t\tS unread (added when 'S' flag is not present)\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";
94 static const char search_config_comment[] =
95 " Search configuration\n"
97 " The following option is supported here:\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";
104 struct _notmuch_config {
107 notmuch_bool_t is_new;
111 char *user_primary_email;
112 const char **user_other_email;
113 size_t user_other_email_length;
114 const char **new_tags;
115 size_t new_tags_length;
116 const char **new_ignore;
117 size_t new_ignore_length;
118 notmuch_bool_t maildir_synchronize_flags;
119 const char **search_exclude_tags;
120 size_t search_exclude_tags_length;
124 notmuch_config_destructor (notmuch_config_t *config)
126 if (config->key_file)
127 g_key_file_free (config->key_file);
133 get_name_from_passwd_file (void *ctx)
137 struct passwd passwd, *ignored;
141 pw_buf_size = sysconf(_SC_GETPW_R_SIZE_MAX);
142 if (pw_buf_size == -1) pw_buf_size = 64;
143 pw_buf = talloc_size (ctx, pw_buf_size);
145 while ((e = getpwuid_r (getuid (), &passwd, pw_buf,
146 pw_buf_size, &ignored)) == ERANGE) {
147 pw_buf_size = pw_buf_size * 2;
148 pw_buf = talloc_zero_size(ctx, pw_buf_size);
152 char *comma = strchr (passwd.pw_gecos, ',');
154 name = talloc_strndup (ctx, passwd.pw_gecos,
155 comma - passwd.pw_gecos);
157 name = talloc_strdup (ctx, passwd.pw_gecos);
159 name = talloc_strdup (ctx, "");
162 talloc_free (pw_buf);
168 get_username_from_passwd_file (void *ctx)
172 struct passwd passwd, *ignored;
176 pw_buf_size = sysconf(_SC_GETPW_R_SIZE_MAX);
177 if (pw_buf_size == -1) pw_buf_size = 64;
178 pw_buf = talloc_zero_size (ctx, pw_buf_size);
180 while ((e = getpwuid_r (getuid (), &passwd, pw_buf,
181 pw_buf_size, &ignored)) == ERANGE) {
182 pw_buf_size = pw_buf_size * 2;
183 pw_buf = talloc_zero_size(ctx, pw_buf_size);
187 name = talloc_strdup (ctx, passwd.pw_name);
189 name = talloc_strdup (ctx, "");
191 talloc_free (pw_buf);
196 /* Open the named notmuch configuration file. If the filename is NULL,
197 * the value of the environment variable $NOTMUCH_CONFIG will be used.
198 * If $NOTMUCH_CONFIG is unset, the default configuration file
199 * ($HOME/.notmuch-config) will be used.
201 * If any error occurs, (out of memory, or a permission-denied error,
202 * etc.), this function will print a message to stderr and return
205 * FILE NOT FOUND: When the specified configuration file (whether from
206 * 'filename' or the $NOTMUCH_CONFIG environment variable) does not
207 * exist, the behavior of this function depends on the 'is_new_ret'
210 * If is_new_ret is NULL, then a "file not found" message will be
211 * printed to stderr and NULL will be returned.
213 * If is_new_ret is non-NULL then a default configuration will be
214 * returned and *is_new_ret will be set to 1 on return so that
215 * the caller can recognize this case.
217 * These default configuration settings are determined as
220 * database_path: $MAILDIR, otherwise $HOME/mail
222 * user_name: $NAME variable if set, otherwise
223 * read from /etc/passwd
225 * user_primary_mail: $EMAIL variable if set, otherwise
226 * constructed from the username and
227 * hostname of the current machine.
229 * user_other_email: Not set.
231 * The default configuration also contains comments to guide the
232 * user in editing the file directly.
235 notmuch_config_open (void *ctx,
236 const char *filename,
237 notmuch_bool_t create_new)
239 GError *error = NULL;
241 char *notmuch_config_env = NULL;
242 int file_had_database_group;
243 int file_had_new_group;
244 int file_had_user_group;
245 int file_had_maildir_group;
246 int file_had_search_group;
248 notmuch_config_t *config = talloc (ctx, notmuch_config_t);
249 if (config == NULL) {
250 fprintf (stderr, "Out of memory.\n");
254 talloc_set_destructor (config, notmuch_config_destructor);
257 config->filename = talloc_strdup (config, filename);
258 } else if ((notmuch_config_env = getenv ("NOTMUCH_CONFIG"))) {
259 config->filename = talloc_strdup (config, notmuch_config_env);
261 config->filename = talloc_asprintf (config, "%s/.notmuch-config",
265 config->key_file = g_key_file_new ();
267 config->is_new = FALSE;
268 config->database_path = NULL;
269 config->user_name = NULL;
270 config->user_primary_email = NULL;
271 config->user_other_email = NULL;
272 config->user_other_email_length = 0;
273 config->new_tags = NULL;
274 config->new_tags_length = 0;
275 config->new_ignore = NULL;
276 config->new_ignore_length = 0;
277 config->maildir_synchronize_flags = TRUE;
278 config->search_exclude_tags = NULL;
279 config->search_exclude_tags_length = 0;
281 if (! g_key_file_load_from_file (config->key_file,
283 G_KEY_FILE_KEEP_COMMENTS,
286 if (error->domain == G_FILE_ERROR && error->code == G_FILE_ERROR_NOENT) {
287 /* If create_new is true, then the caller is prepared for a
288 * default configuration file in the case of FILE NOT
292 g_error_free (error);
293 config->is_new = TRUE;
295 fprintf (stderr, "Configuration file %s not found.\n"
296 "Try running 'notmuch setup' to create a configuration.\n",
298 talloc_free (config);
299 g_error_free (error);
305 fprintf (stderr, "Error reading configuration file %s: %s\n",
306 config->filename, error->message);
307 talloc_free (config);
308 g_error_free (error);
313 /* Whenever we know of configuration sections that don't appear in
314 * the configuration file, we add some comments to help the user
315 * understand what can be done.
317 * It would be convenient to just add those comments now, but
318 * apparently g_key_file will clear any comments when keys are
319 * added later that create the groups. So we have to check for the
320 * groups now, but add the comments only after setting all of our
323 file_had_database_group = g_key_file_has_group (config->key_file,
325 file_had_new_group = g_key_file_has_group (config->key_file, "new");
326 file_had_user_group = g_key_file_has_group (config->key_file, "user");
327 file_had_maildir_group = g_key_file_has_group (config->key_file, "maildir");
328 file_had_search_group = g_key_file_has_group (config->key_file, "search");
331 if (notmuch_config_get_database_path (config) == NULL) {
332 char *path = getenv ("MAILDIR");
334 path = talloc_strdup (config, path);
336 path = talloc_asprintf (config, "%s/mail",
338 notmuch_config_set_database_path (config, path);
342 if (notmuch_config_get_user_name (config) == NULL) {
343 char *name = getenv ("NAME");
345 name = talloc_strdup (config, name);
347 name = get_name_from_passwd_file (config);
348 notmuch_config_set_user_name (config, name);
352 if (notmuch_config_get_user_primary_email (config) == NULL) {
353 char *email = getenv ("EMAIL");
355 notmuch_config_set_user_primary_email (config, email);
358 struct hostent *hostent;
359 const char *domainname;
361 char *username = get_username_from_passwd_file (config);
363 gethostname (hostname, 256);
364 hostname[255] = '\0';
366 hostent = gethostbyname (hostname);
367 if (hostent && (domainname = strchr (hostent->h_name, '.')))
370 domainname = "(none)";
372 email = talloc_asprintf (config, "%s@%s.%s",
373 username, hostname, domainname);
375 notmuch_config_set_user_primary_email (config, email);
377 talloc_free (username);
382 if (notmuch_config_get_new_tags (config, &tmp) == NULL) {
383 const char *tags[] = { "unread", "inbox" };
384 notmuch_config_set_new_tags (config, tags, 2);
387 if (notmuch_config_get_new_ignore (config, &tmp) == NULL) {
388 notmuch_config_set_new_ignore (config, NULL, 0);
391 if (notmuch_config_get_search_exclude_tags (config, &tmp) == NULL) {
392 if (config->is_new) {
393 const char *tags[] = { "deleted", "spam" };
394 notmuch_config_set_search_exclude_tags (config, tags, 2);
396 notmuch_config_set_search_exclude_tags (config, NULL, 0);
401 config->maildir_synchronize_flags =
402 g_key_file_get_boolean (config->key_file,
403 "maildir", "synchronize_flags", &error);
405 notmuch_config_set_maildir_synchronize_flags (config, TRUE);
406 g_error_free (error);
409 /* Whenever we know of configuration sections that don't appear in
410 * the configuration file, we add some comments to help the user
411 * understand what can be done. */
413 g_key_file_set_comment (config->key_file, NULL, NULL,
414 toplevel_config_comment, NULL);
416 if (! file_had_database_group)
417 g_key_file_set_comment (config->key_file, "database", NULL,
418 database_config_comment, NULL);
420 if (! file_had_new_group)
421 g_key_file_set_comment (config->key_file, "new", NULL,
422 new_config_comment, NULL);
424 if (! file_had_user_group)
425 g_key_file_set_comment (config->key_file, "user", NULL,
426 user_config_comment, NULL);
428 if (! file_had_maildir_group)
429 g_key_file_set_comment (config->key_file, "maildir", NULL,
430 maildir_config_comment, NULL);
432 if (! file_had_search_group)
433 g_key_file_set_comment (config->key_file, "search", NULL,
434 search_config_comment, NULL);
439 /* Close the given notmuch_config_t object, freeing all resources.
441 * Note: Any changes made to the configuration are *not* saved by this
442 * function. To save changes, call notmuch_config_save before
443 * notmuch_config_close.
446 notmuch_config_close (notmuch_config_t *config)
448 talloc_free (config);
451 /* Save any changes made to the notmuch configuration.
453 * Any comments originally in the file will be preserved.
455 * Returns 0 if successful, and 1 in case of any error, (after
456 * printing a description of the error to stderr).
459 notmuch_config_save (notmuch_config_t *config)
462 char *data, *filename;
463 GError *error = NULL;
465 data = g_key_file_to_data (config->key_file, &length, NULL);
467 fprintf (stderr, "Out of memory.\n");
471 /* Try not to overwrite symlinks. */
472 filename = canonicalize_file_name (config->filename);
474 if (errno == ENOENT) {
475 filename = strdup (config->filename);
477 fprintf (stderr, "Out of memory.\n");
482 fprintf (stderr, "Error canonicalizing %s: %s\n", config->filename,
489 if (! g_file_set_contents (filename, data, length, &error)) {
490 if (strcmp (filename, config->filename) != 0) {
491 fprintf (stderr, "Error saving configuration to %s (-> %s): %s\n",
492 config->filename, filename, error->message);
494 fprintf (stderr, "Error saving configuration to %s: %s\n",
495 filename, error->message);
497 g_error_free (error);
509 notmuch_config_is_new (notmuch_config_t *config)
511 return config->is_new;
515 _config_get (notmuch_config_t *config, char **field,
516 const char *group, const char *key)
518 /* read from config file and cache value, if not cached already */
519 if (*field == NULL) {
521 value = g_key_file_get_string (config->key_file, group, key, NULL);
523 *field = talloc_strdup (config, value);
531 _config_set (notmuch_config_t *config, char **field,
532 const char *group, const char *key, const char *value)
534 g_key_file_set_string (config->key_file, group, key, value);
536 /* drop the cached value */
537 talloc_free (*field);
542 _config_get_list (notmuch_config_t *config,
543 const char *section, const char *key,
544 const char ***outlist, size_t *list_length, size_t *ret_length)
548 /* read from config file and cache value, if not cached already */
549 if (*outlist == NULL) {
551 char **inlist = g_key_file_get_string_list (config->key_file,
552 section, key, list_length, NULL);
556 *outlist = talloc_size (config, sizeof (char *) * (*list_length + 1));
558 for (i = 0; i < *list_length; i++)
559 (*outlist)[i] = talloc_strdup (*outlist, inlist[i]);
561 (*outlist)[i] = NULL;
568 *ret_length = *list_length;
574 _config_set_list (notmuch_config_t *config,
575 const char *group, const char *name,
577 size_t length, const char ***config_var )
579 g_key_file_set_string_list (config->key_file, group, name, list, length);
581 /* drop the cached value */
582 talloc_free (*config_var);
587 notmuch_config_get_database_path (notmuch_config_t *config)
589 return _config_get (config, &config->database_path, "database", "path");
593 notmuch_config_set_database_path (notmuch_config_t *config,
594 const char *database_path)
596 _config_set (config, &config->database_path, "database", "path", database_path);
600 notmuch_config_get_user_name (notmuch_config_t *config)
602 return _config_get (config, &config->user_name, "user", "name");
606 notmuch_config_set_user_name (notmuch_config_t *config,
607 const char *user_name)
609 _config_set (config, &config->user_name, "user", "name", user_name);
613 notmuch_config_get_user_primary_email (notmuch_config_t *config)
615 return _config_get (config, &config->user_primary_email, "user", "primary_email");
619 notmuch_config_set_user_primary_email (notmuch_config_t *config,
620 const char *primary_email)
622 _config_set (config, &config->user_primary_email, "user", "primary_email", primary_email);
626 notmuch_config_get_user_other_email (notmuch_config_t *config, size_t *length)
628 return _config_get_list (config, "user", "other_email",
629 &(config->user_other_email),
630 &(config->user_other_email_length), length);
634 notmuch_config_get_new_tags (notmuch_config_t *config, size_t *length)
636 return _config_get_list (config, "new", "tags",
638 &(config->new_tags_length), length);
642 notmuch_config_get_new_ignore (notmuch_config_t *config, size_t *length)
644 return _config_get_list (config, "new", "ignore",
645 &(config->new_ignore),
646 &(config->new_ignore_length), length);
650 notmuch_config_set_user_other_email (notmuch_config_t *config,
654 _config_set_list (config, "user", "other_email", list, length,
655 &(config->user_other_email));
659 notmuch_config_set_new_tags (notmuch_config_t *config,
663 _config_set_list (config, "new", "tags", list, length,
664 &(config->new_tags));
668 notmuch_config_set_new_ignore (notmuch_config_t *config,
672 _config_set_list (config, "new", "ignore", list, length,
673 &(config->new_ignore));
677 notmuch_config_get_search_exclude_tags (notmuch_config_t *config, size_t *length)
679 return _config_get_list (config, "search", "exclude_tags",
680 &(config->search_exclude_tags),
681 &(config->search_exclude_tags_length), length);
685 notmuch_config_set_search_exclude_tags (notmuch_config_t *config,
689 _config_set_list (config, "search", "exclude_tags", list, length,
690 &(config->search_exclude_tags));
693 /* Given a configuration item of the form <group>.<key> return the
694 * component group and key. If any error occurs, print a message on
695 * stderr and return 1. Otherwise, return 0.
697 * Note: This function modifies the original 'item' string.
700 _item_split (char *item, char **group, char **key)
706 period = strchr (item, '.');
707 if (period == NULL || *(period+1) == '\0') {
709 "Invalid configuration name: %s\n"
710 "(Should be of the form <section>.<item>)\n", item);
721 notmuch_config_command_get (notmuch_config_t *config, char *item)
723 if (strcmp(item, "database.path") == 0) {
724 printf ("%s\n", notmuch_config_get_database_path (config));
725 } else if (strcmp(item, "user.name") == 0) {
726 printf ("%s\n", notmuch_config_get_user_name (config));
727 } else if (strcmp(item, "user.primary_email") == 0) {
728 printf ("%s\n", notmuch_config_get_user_primary_email (config));
729 } else if (strcmp(item, "user.other_email") == 0) {
730 const char **other_email;
733 other_email = notmuch_config_get_user_other_email (config, &length);
734 for (i = 0; i < length; i++)
735 printf ("%s\n", other_email[i]);
736 } else if (strcmp(item, "new.tags") == 0) {
740 tags = notmuch_config_get_new_tags (config, &length);
741 for (i = 0; i < length; i++)
742 printf ("%s\n", tags[i]);
748 if (_item_split (item, &group, &key))
751 value = g_key_file_get_string_list (config->key_file,
755 fprintf (stderr, "Unknown configuration item: %s.%s\n",
760 for (i = 0; i < length; i++)
761 printf ("%s\n", value[i]);
770 notmuch_config_command_set (notmuch_config_t *config, char *item, int argc, char *argv[])
774 if (_item_split (item, &group, &key))
777 /* With only the name of an item, we clear it from the
778 * configuration file.
780 * With a single value, we set it as a string.
782 * With multiple values, we set them as a string list.
786 g_key_file_remove_key (config->key_file, group, key, NULL);
789 g_key_file_set_string (config->key_file, group, key, argv[0]);
792 g_key_file_set_string_list (config->key_file, group, key,
793 (const gchar **) argv, argc);
797 return notmuch_config_save (config);
801 notmuch_config_command_list (notmuch_config_t *config)
804 size_t g, groups_length;
806 groups = g_key_file_get_groups (config->key_file, &groups_length);
810 for (g = 0; g < groups_length; g++) {
812 size_t k, keys_length;
814 keys = g_key_file_get_keys (config->key_file,
815 groups[g], &keys_length, NULL);
819 for (k = 0; k < keys_length; k++) {
822 value = g_key_file_get_string (config->key_file,
823 groups[g], keys[k], NULL);
825 printf ("%s.%s=%s\n", groups[g], keys[k], value);
839 notmuch_config_command (notmuch_config_t *config, int argc, char *argv[])
843 argc--; argv++; /* skip subcommand argument */
846 fprintf (stderr, "Error: notmuch config requires at least one argument.\n");
850 if (strcmp (argv[0], "get") == 0) {
852 fprintf (stderr, "Error: notmuch config get requires exactly "
856 ret = notmuch_config_command_get (config, argv[1]);
857 } else if (strcmp (argv[0], "set") == 0) {
859 fprintf (stderr, "Error: notmuch config set requires at least "
863 ret = notmuch_config_command_set (config, argv[1], argc - 2, argv + 2);
864 } else if (strcmp (argv[0], "list") == 0) {
865 ret = notmuch_config_command_list (config);
867 fprintf (stderr, "Unrecognized argument for notmuch config: %s\n",
872 return ret ? EXIT_FAILURE : EXIT_SUCCESS;
877 notmuch_config_get_maildir_synchronize_flags (notmuch_config_t *config)
879 return config->maildir_synchronize_flags;
883 notmuch_config_set_maildir_synchronize_flags (notmuch_config_t *config,
884 notmuch_bool_t synchronize_flags)
886 g_key_file_set_boolean (config->key_file,
887 "maildir", "synchronize_flags", synchronize_flags);
888 config->maildir_synchronize_flags = synchronize_flags;