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: $HOME/mail
222 * user_name: From /etc/passwd
224 * user_primary_mail: $EMAIL variable if set, otherwise
225 * constructed from the username and
226 * hostname of the current machine.
228 * user_other_email: Not set.
230 * The default configuration also contains comments to guide the
231 * user in editing the file directly.
234 notmuch_config_open (void *ctx,
235 const char *filename,
236 notmuch_bool_t create_new)
238 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;
247 notmuch_config_t *config = talloc (ctx, notmuch_config_t);
248 if (config == NULL) {
249 fprintf (stderr, "Out of memory.\n");
253 talloc_set_destructor (config, notmuch_config_destructor);
256 config->filename = talloc_strdup (config, filename);
257 } else if ((notmuch_config_env = getenv ("NOTMUCH_CONFIG"))) {
258 config->filename = talloc_strdup (config, notmuch_config_env);
260 config->filename = talloc_asprintf (config, "%s/.notmuch-config",
264 config->key_file = g_key_file_new ();
266 config->is_new = FALSE;
267 config->database_path = NULL;
268 config->user_name = NULL;
269 config->user_primary_email = NULL;
270 config->user_other_email = NULL;
271 config->user_other_email_length = 0;
272 config->new_tags = NULL;
273 config->new_tags_length = 0;
274 config->new_ignore = NULL;
275 config->new_ignore_length = 0;
276 config->maildir_synchronize_flags = TRUE;
277 config->search_exclude_tags = NULL;
278 config->search_exclude_tags_length = 0;
280 if (! g_key_file_load_from_file (config->key_file,
282 G_KEY_FILE_KEEP_COMMENTS,
285 /* If create_new is true, then the caller is prepared for a
286 * default configuration file in the case of FILE NOT
287 * FOUND. Otherwise, any read failure is an error.
290 error->domain == G_FILE_ERROR &&
291 error->code == G_FILE_ERROR_NOENT)
293 g_error_free (error);
294 config->is_new = TRUE;
298 fprintf (stderr, "Error reading configuration file %s: %s\n",
299 config->filename, error->message);
300 talloc_free (config);
301 g_error_free (error);
306 /* Whenever we know of configuration sections that don't appear in
307 * the configuration file, we add some comments to help the user
308 * understand what can be done.
310 * It would be convenient to just add those comments now, but
311 * apparently g_key_file will clear any comments when keys are
312 * added later that create the groups. So we have to check for the
313 * groups now, but add the comments only after setting all of our
316 file_had_database_group = g_key_file_has_group (config->key_file,
318 file_had_new_group = g_key_file_has_group (config->key_file, "new");
319 file_had_user_group = g_key_file_has_group (config->key_file, "user");
320 file_had_maildir_group = g_key_file_has_group (config->key_file, "maildir");
321 file_had_search_group = g_key_file_has_group (config->key_file, "search");
324 if (notmuch_config_get_database_path (config) == NULL) {
325 char *path = talloc_asprintf (config, "%s/mail",
327 notmuch_config_set_database_path (config, path);
331 if (notmuch_config_get_user_name (config) == NULL) {
332 char *name = get_name_from_passwd_file (config);
333 notmuch_config_set_user_name (config, name);
337 if (notmuch_config_get_user_primary_email (config) == NULL) {
338 char *email = getenv ("EMAIL");
340 notmuch_config_set_user_primary_email (config, email);
343 struct hostent *hostent;
344 const char *domainname;
346 char *username = get_username_from_passwd_file (config);
348 gethostname (hostname, 256);
349 hostname[255] = '\0';
351 hostent = gethostbyname (hostname);
352 if (hostent && (domainname = strchr (hostent->h_name, '.')))
355 domainname = "(none)";
357 email = talloc_asprintf (config, "%s@%s.%s",
358 username, hostname, domainname);
360 notmuch_config_set_user_primary_email (config, email);
362 talloc_free (username);
367 if (notmuch_config_get_new_tags (config, &tmp) == NULL) {
368 const char *tags[] = { "unread", "inbox" };
369 notmuch_config_set_new_tags (config, tags, 2);
372 if (notmuch_config_get_new_ignore (config, &tmp) == NULL) {
373 notmuch_config_set_new_ignore (config, NULL, 0);
376 if (notmuch_config_get_search_exclude_tags (config, &tmp) == NULL) {
377 if (config->is_new) {
378 const char *tags[] = { "deleted", "spam" };
379 notmuch_config_set_search_exclude_tags (config, tags, 2);
381 notmuch_config_set_search_exclude_tags (config, NULL, 0);
386 config->maildir_synchronize_flags =
387 g_key_file_get_boolean (config->key_file,
388 "maildir", "synchronize_flags", &error);
390 notmuch_config_set_maildir_synchronize_flags (config, TRUE);
391 g_error_free (error);
394 /* Whenever we know of configuration sections that don't appear in
395 * the configuration file, we add some comments to help the user
396 * understand what can be done. */
398 g_key_file_set_comment (config->key_file, NULL, NULL,
399 toplevel_config_comment, NULL);
401 if (! file_had_database_group)
402 g_key_file_set_comment (config->key_file, "database", NULL,
403 database_config_comment, NULL);
405 if (! file_had_new_group)
406 g_key_file_set_comment (config->key_file, "new", NULL,
407 new_config_comment, NULL);
409 if (! file_had_user_group)
410 g_key_file_set_comment (config->key_file, "user", NULL,
411 user_config_comment, NULL);
413 if (! file_had_maildir_group)
414 g_key_file_set_comment (config->key_file, "maildir", NULL,
415 maildir_config_comment, NULL);
417 if (! file_had_search_group)
418 g_key_file_set_comment (config->key_file, "search", NULL,
419 search_config_comment, NULL);
424 /* Close the given notmuch_config_t object, freeing all resources.
426 * Note: Any changes made to the configuration are *not* saved by this
427 * function. To save changes, call notmuch_config_save before
428 * notmuch_config_close.
431 notmuch_config_close (notmuch_config_t *config)
433 talloc_free (config);
436 /* Save any changes made to the notmuch configuration.
438 * Any comments originally in the file will be preserved.
440 * Returns 0 if successful, and 1 in case of any error, (after
441 * printing a description of the error to stderr).
444 notmuch_config_save (notmuch_config_t *config)
447 char *data, *filename;
448 GError *error = NULL;
450 data = g_key_file_to_data (config->key_file, &length, NULL);
452 fprintf (stderr, "Out of memory.\n");
456 /* Try not to overwrite symlinks. */
457 filename = realpath (config->filename, NULL);
459 if (errno == ENOENT) {
460 filename = strdup (config->filename);
462 fprintf (stderr, "Out of memory.\n");
467 fprintf (stderr, "Error canonicalizing %s: %s\n", config->filename,
474 if (! g_file_set_contents (filename, data, length, &error)) {
475 if (strcmp (filename, config->filename) != 0) {
476 fprintf (stderr, "Error saving configuration to %s (-> %s): %s\n",
477 config->filename, filename, error->message);
479 fprintf (stderr, "Error saving configuration to %s: %s\n",
480 filename, error->message);
482 g_error_free (error);
494 notmuch_config_is_new (notmuch_config_t *config)
496 return config->is_new;
501 _config_get_list (notmuch_config_t *config,
502 const char *section, const char *key,
503 const char ***outlist, size_t *list_length, size_t *ret_length)
507 if (*outlist == NULL) {
509 char **inlist = g_key_file_get_string_list (config->key_file,
510 section, key, list_length, NULL);
514 *outlist = talloc_size (config, sizeof (char *) * (*list_length + 1));
516 for (i = 0; i < *list_length; i++)
517 (*outlist)[i] = talloc_strdup (*outlist, inlist[i]);
519 (*outlist)[i] = NULL;
526 *ret_length = *list_length;
532 _config_set_list (notmuch_config_t *config,
533 const char *group, const char *name,
535 size_t length, const char ***config_var )
537 g_key_file_set_string_list (config->key_file, group, name, list, length);
538 talloc_free (*config_var);
543 notmuch_config_get_database_path (notmuch_config_t *config)
547 if (config->database_path == NULL) {
548 path = g_key_file_get_string (config->key_file,
549 "database", "path", NULL);
551 config->database_path = talloc_strdup (config, path);
556 return config->database_path;
560 notmuch_config_set_database_path (notmuch_config_t *config,
561 const char *database_path)
563 g_key_file_set_string (config->key_file,
564 "database", "path", database_path);
566 talloc_free (config->database_path);
567 config->database_path = NULL;
571 notmuch_config_get_user_name (notmuch_config_t *config)
575 if (config->user_name == NULL) {
576 name = g_key_file_get_string (config->key_file,
577 "user", "name", NULL);
579 config->user_name = talloc_strdup (config, name);
584 return config->user_name;
588 notmuch_config_set_user_name (notmuch_config_t *config,
589 const char *user_name)
591 g_key_file_set_string (config->key_file,
592 "user", "name", user_name);
594 talloc_free (config->user_name);
595 config->user_name = NULL;
599 notmuch_config_get_user_primary_email (notmuch_config_t *config)
603 if (config->user_primary_email == NULL) {
604 email = g_key_file_get_string (config->key_file,
605 "user", "primary_email", NULL);
607 config->user_primary_email = talloc_strdup (config, email);
612 return config->user_primary_email;
616 notmuch_config_set_user_primary_email (notmuch_config_t *config,
617 const char *primary_email)
619 g_key_file_set_string (config->key_file,
620 "user", "primary_email", primary_email);
622 talloc_free (config->user_primary_email);
623 config->user_primary_email = NULL;
627 notmuch_config_get_user_other_email (notmuch_config_t *config, size_t *length)
629 return _config_get_list (config, "user", "other_email",
630 &(config->user_other_email),
631 &(config->user_other_email_length), length);
635 notmuch_config_get_new_tags (notmuch_config_t *config, size_t *length)
637 return _config_get_list (config, "new", "tags",
639 &(config->new_tags_length), length);
643 notmuch_config_get_new_ignore (notmuch_config_t *config, size_t *length)
645 return _config_get_list (config, "new", "ignore",
646 &(config->new_ignore),
647 &(config->new_ignore_length), length);
651 notmuch_config_set_user_other_email (notmuch_config_t *config,
655 _config_set_list (config, "user", "other_email", list, length,
656 &(config->user_other_email));
660 notmuch_config_set_new_tags (notmuch_config_t *config,
664 _config_set_list (config, "new", "tags", list, length,
665 &(config->new_tags));
669 notmuch_config_set_new_ignore (notmuch_config_t *config,
673 _config_set_list (config, "new", "ignore", list, length,
674 &(config->new_ignore));
678 notmuch_config_get_search_exclude_tags (notmuch_config_t *config, size_t *length)
680 return _config_get_list (config, "search", "exclude_tags",
681 &(config->search_exclude_tags),
682 &(config->search_exclude_tags_length), length);
686 notmuch_config_set_search_exclude_tags (notmuch_config_t *config,
690 _config_set_list (config, "search", "exclude_tags", list, length,
691 &(config->search_exclude_tags));
694 /* Given a configuration item of the form <group>.<key> return the
695 * component group and key. If any error occurs, print a message on
696 * stderr and return 1. Otherwise, return 0.
698 * Note: This function modifies the original 'item' string.
701 _item_split (char *item, char **group, char **key)
707 period = index (item, '.');
708 if (period == NULL || *(period+1) == '\0') {
710 "Invalid configuration name: %s\n"
711 "(Should be of the form <section>.<item>)\n", item);
722 notmuch_config_command_get (notmuch_config_t *config, char *item)
724 if (strcmp(item, "database.path") == 0) {
725 printf ("%s\n", notmuch_config_get_database_path (config));
726 } else if (strcmp(item, "user.name") == 0) {
727 printf ("%s\n", notmuch_config_get_user_name (config));
728 } else if (strcmp(item, "user.primary_email") == 0) {
729 printf ("%s\n", notmuch_config_get_user_primary_email (config));
730 } else if (strcmp(item, "user.other_email") == 0) {
731 const char **other_email;
734 other_email = notmuch_config_get_user_other_email (config, &length);
735 for (i = 0; i < length; i++)
736 printf ("%s\n", other_email[i]);
737 } else if (strcmp(item, "new.tags") == 0) {
741 tags = notmuch_config_get_new_tags (config, &length);
742 for (i = 0; i < length; i++)
743 printf ("%s\n", tags[i]);
749 if (_item_split (item, &group, &key))
752 value = g_key_file_get_string_list (config->key_file,
756 fprintf (stderr, "Unknown configuration item: %s.%s\n",
761 for (i = 0; i < length; i++)
762 printf ("%s\n", value[i]);
771 notmuch_config_command_set (notmuch_config_t *config, char *item, int argc, char *argv[])
775 if (_item_split (item, &group, &key))
778 /* With only the name of an item, we clear it from the
779 * configuration file.
781 * With a single value, we set it as a string.
783 * With multiple values, we set them as a string list.
787 g_key_file_remove_key (config->key_file, group, key, NULL);
790 g_key_file_set_string (config->key_file, group, key, argv[0]);
793 g_key_file_set_string_list (config->key_file, group, key,
794 (const gchar **) argv, argc);
798 return notmuch_config_save (config);
802 notmuch_config_command_list (notmuch_config_t *config)
805 size_t g, groups_length;
807 groups = g_key_file_get_groups (config->key_file, &groups_length);
811 for (g = 0; g < groups_length; g++) {
813 size_t k, keys_length;
815 keys = g_key_file_get_keys (config->key_file,
816 groups[g], &keys_length, NULL);
820 for (k = 0; k < keys_length; k++) {
823 value = g_key_file_get_string (config->key_file,
824 groups[g], keys[k], NULL);
826 printf ("%s.%s=%s\n", groups[g], keys[k], value);
840 notmuch_config_command (notmuch_config_t *config, int argc, char *argv[])
842 argc--; argv++; /* skip subcommand argument */
845 fprintf (stderr, "Error: notmuch config requires at least one argument.\n");
849 if (strcmp (argv[0], "get") == 0) {
851 fprintf (stderr, "Error: notmuch config get requires exactly "
855 return notmuch_config_command_get (config, argv[1]);
856 } else if (strcmp (argv[0], "set") == 0) {
858 fprintf (stderr, "Error: notmuch config set requires at least "
862 return notmuch_config_command_set (config, argv[1], argc - 2, argv + 2);
863 } else if (strcmp (argv[0], "list") == 0) {
864 return notmuch_config_command_list (config);
867 fprintf (stderr, "Unrecognized argument for notmuch config: %s\n",
873 notmuch_config_get_maildir_synchronize_flags (notmuch_config_t *config)
875 return config->maildir_synchronize_flags;
879 notmuch_config_set_maildir_synchronize_flags (notmuch_config_t *config,
880 notmuch_bool_t synchronize_flags)
882 g_key_file_set_boolean (config->key_file,
883 "maildir", "synchronize_flags", synchronize_flags);
884 config->maildir_synchronize_flags = synchronize_flags;