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 {
110 char *user_primary_email;
111 const char **user_other_email;
112 size_t user_other_email_length;
113 const char **new_tags;
114 size_t new_tags_length;
115 const char **new_ignore;
116 size_t new_ignore_length;
117 notmuch_bool_t maildir_synchronize_flags;
118 const char **search_exclude_tags;
119 size_t search_exclude_tags_length;
123 notmuch_config_destructor (notmuch_config_t *config)
125 if (config->key_file)
126 g_key_file_free (config->key_file);
132 get_name_from_passwd_file (void *ctx)
136 struct passwd passwd, *ignored;
140 pw_buf_size = sysconf(_SC_GETPW_R_SIZE_MAX);
141 if (pw_buf_size == -1) pw_buf_size = 64;
142 pw_buf = talloc_size (ctx, pw_buf_size);
144 while ((e = getpwuid_r (getuid (), &passwd, pw_buf,
145 pw_buf_size, &ignored)) == ERANGE) {
146 pw_buf_size = pw_buf_size * 2;
147 pw_buf = talloc_zero_size(ctx, pw_buf_size);
151 char *comma = strchr (passwd.pw_gecos, ',');
153 name = talloc_strndup (ctx, passwd.pw_gecos,
154 comma - passwd.pw_gecos);
156 name = talloc_strdup (ctx, passwd.pw_gecos);
158 name = talloc_strdup (ctx, "");
161 talloc_free (pw_buf);
167 get_username_from_passwd_file (void *ctx)
171 struct passwd passwd, *ignored;
175 pw_buf_size = sysconf(_SC_GETPW_R_SIZE_MAX);
176 if (pw_buf_size == -1) pw_buf_size = 64;
177 pw_buf = talloc_zero_size (ctx, pw_buf_size);
179 while ((e = getpwuid_r (getuid (), &passwd, pw_buf,
180 pw_buf_size, &ignored)) == ERANGE) {
181 pw_buf_size = pw_buf_size * 2;
182 pw_buf = talloc_zero_size(ctx, pw_buf_size);
186 name = talloc_strdup (ctx, passwd.pw_name);
188 name = talloc_strdup (ctx, "");
190 talloc_free (pw_buf);
195 /* Open the named notmuch configuration file. If the filename is NULL,
196 * the value of the environment variable $NOTMUCH_CONFIG will be used.
197 * If $NOTMUCH_CONFIG is unset, the default configuration file
198 * ($HOME/.notmuch-config) will be used.
200 * If any error occurs, (out of memory, or a permission-denied error,
201 * etc.), this function will print a message to stderr and return
204 * FILE NOT FOUND: When the specified configuration file (whether from
205 * 'filename' or the $NOTMUCH_CONFIG environment variable) does not
206 * exist, the behavior of this function depends on the 'is_new_ret'
209 * If is_new_ret is NULL, then a "file not found" message will be
210 * printed to stderr and NULL will be returned.
212 * If is_new_ret is non-NULL then a default configuration will be
213 * returned and *is_new_ret will be set to 1 on return so that
214 * the caller can recognize this case.
216 * These default configuration settings are determined as
219 * database_path: $HOME/mail
221 * user_name: From /etc/passwd
223 * user_primary_mail: $EMAIL variable if set, otherwise
224 * constructed from the username and
225 * hostname of the current machine.
227 * user_other_email: Not set.
229 * The default configuration also contains comments to guide the
230 * user in editing the file directly.
233 notmuch_config_open (void *ctx,
234 const char *filename,
235 notmuch_bool_t *is_new_ret)
237 GError *error = NULL;
240 char *notmuch_config_env = NULL;
241 int file_had_database_group;
242 int file_had_new_group;
243 int file_had_user_group;
244 int file_had_maildir_group;
245 int file_had_search_group;
250 notmuch_config_t *config = talloc (ctx, notmuch_config_t);
251 if (config == NULL) {
252 fprintf (stderr, "Out of memory.\n");
256 talloc_set_destructor (config, notmuch_config_destructor);
259 config->filename = talloc_strdup (config, filename);
260 } else if ((notmuch_config_env = getenv ("NOTMUCH_CONFIG"))) {
261 config->filename = talloc_strdup (config, notmuch_config_env);
263 config->filename = talloc_asprintf (config, "%s/.notmuch-config",
267 config->key_file = g_key_file_new ();
269 config->database_path = NULL;
270 config->user_name = NULL;
271 config->user_primary_email = NULL;
272 config->user_other_email = NULL;
273 config->user_other_email_length = 0;
274 config->new_tags = NULL;
275 config->new_tags_length = 0;
276 config->new_ignore = NULL;
277 config->new_ignore_length = 0;
278 config->maildir_synchronize_flags = TRUE;
279 config->search_exclude_tags = NULL;
280 config->search_exclude_tags_length = 0;
282 if (! g_key_file_load_from_file (config->key_file,
284 G_KEY_FILE_KEEP_COMMENTS,
287 /* If the caller passed a non-NULL value for is_new_ret, then
288 * the caller is prepared for a default configuration file in
289 * the case of FILE NOT FOUND. Otherwise, any read failure is
293 error->domain == G_FILE_ERROR &&
294 error->code == G_FILE_ERROR_NOENT)
296 g_error_free (error);
301 fprintf (stderr, "Error reading configuration file %s: %s\n",
302 config->filename, error->message);
303 talloc_free (config);
304 g_error_free (error);
309 /* Whenever we know of configuration sections that don't appear in
310 * the configuration file, we add some comments to help the user
311 * understand what can be done.
313 * It would be convenient to just add those comments now, but
314 * apparently g_key_file will clear any comments when keys are
315 * added later that create the groups. So we have to check for the
316 * groups now, but add the comments only after setting all of our
319 file_had_database_group = g_key_file_has_group (config->key_file,
321 file_had_new_group = g_key_file_has_group (config->key_file, "new");
322 file_had_user_group = g_key_file_has_group (config->key_file, "user");
323 file_had_maildir_group = g_key_file_has_group (config->key_file, "maildir");
324 file_had_search_group = g_key_file_has_group (config->key_file, "search");
327 if (notmuch_config_get_database_path (config) == NULL) {
328 char *path = talloc_asprintf (config, "%s/mail",
330 notmuch_config_set_database_path (config, path);
334 if (notmuch_config_get_user_name (config) == NULL) {
335 char *name = get_name_from_passwd_file (config);
336 notmuch_config_set_user_name (config, name);
340 if (notmuch_config_get_user_primary_email (config) == NULL) {
341 char *email = getenv ("EMAIL");
343 notmuch_config_set_user_primary_email (config, email);
346 struct hostent *hostent;
347 const char *domainname;
349 char *username = get_username_from_passwd_file (config);
351 gethostname (hostname, 256);
352 hostname[255] = '\0';
354 hostent = gethostbyname (hostname);
355 if (hostent && (domainname = strchr (hostent->h_name, '.')))
358 domainname = "(none)";
360 email = talloc_asprintf (config, "%s@%s.%s",
361 username, hostname, domainname);
363 notmuch_config_set_user_primary_email (config, email);
365 talloc_free (username);
370 if (notmuch_config_get_new_tags (config, &tmp) == NULL) {
371 const char *tags[] = { "unread", "inbox" };
372 notmuch_config_set_new_tags (config, tags, 2);
375 if (notmuch_config_get_new_ignore (config, &tmp) == NULL) {
376 notmuch_config_set_new_ignore (config, NULL, 0);
379 if (notmuch_config_get_search_exclude_tags (config, &tmp) == NULL) {
381 const char *tags[] = { "deleted", "spam" };
382 notmuch_config_set_search_exclude_tags (config, tags, 2);
384 notmuch_config_set_search_exclude_tags (config, NULL, 0);
389 config->maildir_synchronize_flags =
390 g_key_file_get_boolean (config->key_file,
391 "maildir", "synchronize_flags", &error);
393 notmuch_config_set_maildir_synchronize_flags (config, TRUE);
394 g_error_free (error);
397 /* Whenever we know of configuration sections that don't appear in
398 * the configuration file, we add some comments to help the user
399 * understand what can be done. */
402 g_key_file_set_comment (config->key_file, NULL, NULL,
403 toplevel_config_comment, NULL);
406 if (! file_had_database_group)
408 g_key_file_set_comment (config->key_file, "database", NULL,
409 database_config_comment, NULL);
412 if (! file_had_new_group)
414 g_key_file_set_comment (config->key_file, "new", NULL,
415 new_config_comment, NULL);
418 if (! file_had_user_group)
420 g_key_file_set_comment (config->key_file, "user", NULL,
421 user_config_comment, NULL);
424 if (! file_had_maildir_group)
426 g_key_file_set_comment (config->key_file, "maildir", NULL,
427 maildir_config_comment, NULL);
430 if (! file_had_search_group) {
431 g_key_file_set_comment (config->key_file, "search", NULL,
432 search_config_comment, NULL);
436 *is_new_ret = is_new;
441 /* Close the given notmuch_config_t object, freeing all resources.
443 * Note: Any changes made to the configuration are *not* saved by this
444 * function. To save changes, call notmuch_config_save before
445 * notmuch_config_close.
448 notmuch_config_close (notmuch_config_t *config)
450 talloc_free (config);
453 /* Save any changes made to the notmuch configuration.
455 * Any comments originally in the file will be preserved.
457 * Returns 0 if successful, and 1 in case of any error, (after
458 * printing a description of the error to stderr).
461 notmuch_config_save (notmuch_config_t *config)
465 GError *error = NULL;
467 data = g_key_file_to_data (config->key_file, &length, NULL);
469 fprintf (stderr, "Out of memory.\n");
473 if (! g_file_set_contents (config->filename, data, length, &error)) {
474 fprintf (stderr, "Error saving configuration to %s: %s\n",
475 config->filename, error->message);
476 g_error_free (error);
486 _config_get_list (notmuch_config_t *config,
487 const char *section, const char *key,
488 const char ***outlist, size_t *list_length, size_t *ret_length)
492 if (*outlist == NULL) {
494 char **inlist = g_key_file_get_string_list (config->key_file,
495 section, key, list_length, NULL);
499 *outlist = talloc_size (config, sizeof (char *) * (*list_length + 1));
501 for (i = 0; i < *list_length; i++)
502 (*outlist)[i] = talloc_strdup (*outlist, inlist[i]);
504 (*outlist)[i] = NULL;
511 *ret_length = *list_length;
517 _config_set_list (notmuch_config_t *config,
518 const char *group, const char *name,
520 size_t length, const char ***config_var )
522 g_key_file_set_string_list (config->key_file, group, name, list, length);
523 talloc_free (*config_var);
528 notmuch_config_get_database_path (notmuch_config_t *config)
532 if (config->database_path == NULL) {
533 path = g_key_file_get_string (config->key_file,
534 "database", "path", NULL);
536 config->database_path = talloc_strdup (config, path);
541 return config->database_path;
545 notmuch_config_set_database_path (notmuch_config_t *config,
546 const char *database_path)
548 g_key_file_set_string (config->key_file,
549 "database", "path", database_path);
551 talloc_free (config->database_path);
552 config->database_path = NULL;
556 notmuch_config_get_user_name (notmuch_config_t *config)
560 if (config->user_name == NULL) {
561 name = g_key_file_get_string (config->key_file,
562 "user", "name", NULL);
564 config->user_name = talloc_strdup (config, name);
569 return config->user_name;
573 notmuch_config_set_user_name (notmuch_config_t *config,
574 const char *user_name)
576 g_key_file_set_string (config->key_file,
577 "user", "name", user_name);
579 talloc_free (config->user_name);
580 config->user_name = NULL;
584 notmuch_config_get_user_primary_email (notmuch_config_t *config)
588 if (config->user_primary_email == NULL) {
589 email = g_key_file_get_string (config->key_file,
590 "user", "primary_email", NULL);
592 config->user_primary_email = talloc_strdup (config, email);
597 return config->user_primary_email;
601 notmuch_config_set_user_primary_email (notmuch_config_t *config,
602 const char *primary_email)
604 g_key_file_set_string (config->key_file,
605 "user", "primary_email", primary_email);
607 talloc_free (config->user_primary_email);
608 config->user_primary_email = NULL;
612 notmuch_config_get_user_other_email (notmuch_config_t *config, size_t *length)
614 return _config_get_list (config, "user", "other_email",
615 &(config->user_other_email),
616 &(config->user_other_email_length), length);
620 notmuch_config_get_new_tags (notmuch_config_t *config, size_t *length)
622 return _config_get_list (config, "new", "tags",
624 &(config->new_tags_length), length);
628 notmuch_config_get_new_ignore (notmuch_config_t *config, size_t *length)
630 return _config_get_list (config, "new", "ignore",
631 &(config->new_ignore),
632 &(config->new_ignore_length), length);
636 notmuch_config_set_user_other_email (notmuch_config_t *config,
640 _config_set_list (config, "user", "other_email", list, length,
641 &(config->user_other_email));
645 notmuch_config_set_new_tags (notmuch_config_t *config,
649 _config_set_list (config, "new", "tags", list, length,
650 &(config->new_tags));
654 notmuch_config_set_new_ignore (notmuch_config_t *config,
658 _config_set_list (config, "new", "ignore", list, length,
659 &(config->new_ignore));
663 notmuch_config_get_search_exclude_tags (notmuch_config_t *config, size_t *length)
665 return _config_get_list (config, "search", "exclude_tags",
666 &(config->search_exclude_tags),
667 &(config->search_exclude_tags_length), length);
671 notmuch_config_set_search_exclude_tags (notmuch_config_t *config,
675 _config_set_list (config, "search", "exclude_tags", list, length,
676 &(config->search_exclude_tags));
679 /* Given a configuration item of the form <group>.<key> return the
680 * component group and key. If any error occurs, print a message on
681 * stderr and return 1. Otherwise, return 0.
683 * Note: This function modifies the original 'item' string.
686 _item_split (char *item, char **group, char **key)
692 period = index (item, '.');
693 if (period == NULL || *(period+1) == '\0') {
695 "Invalid configuration name: %s\n"
696 "(Should be of the form <section>.<item>)\n", item);
707 notmuch_config_command_get (void *ctx, char *item)
709 notmuch_config_t *config;
711 config = notmuch_config_open (ctx, NULL, NULL);
715 if (strcmp(item, "database.path") == 0) {
716 printf ("%s\n", notmuch_config_get_database_path (config));
717 } else if (strcmp(item, "user.name") == 0) {
718 printf ("%s\n", notmuch_config_get_user_name (config));
719 } else if (strcmp(item, "user.primary_email") == 0) {
720 printf ("%s\n", notmuch_config_get_user_primary_email (config));
721 } else if (strcmp(item, "user.other_email") == 0) {
722 const char **other_email;
725 other_email = notmuch_config_get_user_other_email (config, &length);
726 for (i = 0; i < length; i++)
727 printf ("%s\n", other_email[i]);
728 } else if (strcmp(item, "new.tags") == 0) {
732 tags = notmuch_config_get_new_tags (config, &length);
733 for (i = 0; i < length; i++)
734 printf ("%s\n", tags[i]);
740 if (_item_split (item, &group, &key))
743 value = g_key_file_get_string_list (config->key_file,
747 fprintf (stderr, "Unknown configuration item: %s.%s\n",
752 for (i = 0; i < length; i++)
753 printf ("%s\n", value[i]);
758 notmuch_config_close (config);
764 notmuch_config_command_set (void *ctx, char *item, int argc, char *argv[])
766 notmuch_config_t *config;
770 if (_item_split (item, &group, &key))
773 config = notmuch_config_open (ctx, NULL, NULL);
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 ret = notmuch_config_save (config);
798 notmuch_config_close (config);
804 notmuch_config_command_list (void *ctx)
806 notmuch_config_t *config;
808 size_t g, groups_length;
810 config = notmuch_config_open (ctx, NULL, NULL);
814 groups = g_key_file_get_groups (config->key_file, &groups_length);
818 for (g = 0; g < groups_length; g++) {
820 size_t k, keys_length;
822 keys = g_key_file_get_keys (config->key_file,
823 groups[g], &keys_length, NULL);
827 for (k = 0; k < keys_length; k++) {
830 value = g_key_file_get_string (config->key_file,
831 groups[g], keys[k], NULL);
833 printf ("%s.%s=%s\n", groups[g], keys[k], value);
843 notmuch_config_close (config);
849 notmuch_config_command (void *ctx, int argc, char *argv[])
851 argc--; argv++; /* skip subcommand argument */
854 fprintf (stderr, "Error: notmuch config requires at least one argument.\n");
858 if (strcmp (argv[0], "get") == 0) {
860 fprintf (stderr, "Error: notmuch config get requires exactly "
864 return notmuch_config_command_get (ctx, argv[1]);
865 } else if (strcmp (argv[0], "set") == 0) {
867 fprintf (stderr, "Error: notmuch config set requires at least "
871 return notmuch_config_command_set (ctx, argv[1], argc - 2, argv + 2);
872 } else if (strcmp (argv[0], "list") == 0) {
873 return notmuch_config_command_list (ctx);
876 fprintf (stderr, "Unrecognized argument for notmuch config: %s\n",
882 notmuch_config_get_maildir_synchronize_flags (notmuch_config_t *config)
884 return config->maildir_synchronize_flags;
888 notmuch_config_set_maildir_synchronize_flags (notmuch_config_t *config,
889 notmuch_bool_t synchronize_flags)
891 g_key_file_set_boolean (config->key_file,
892 "maildir", "synchronize_flags", synchronize_flags);
893 config->maildir_synchronize_flags = synchronize_flags;