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 https://www.gnu.org/licenses/ .
18 * Author: Carl Worth <cworth@cworth.org>
21 #include "notmuch-client.h"
27 #include "unicode-util.h"
29 static const char toplevel_config_comment[] =
30 " .notmuch-config - Configuration file for the notmuch mail system\n"
32 " For more information about notmuch, see https://notmuchmail.org";
35 const char *group_name;
37 } group_comment_table [] = {
40 " Database configuration\n"
42 " The only value supported here is 'path' which should be the top-level\n"
43 " directory where your mail currently exists and to where mail will be\n"
44 " delivered in the future. Files should be individual email messages.\n"
45 " Notmuch will store its database within a sub-directory of the path\n"
46 " configured here named \".notmuch\".\n"
50 " User configuration\n"
52 " Here is where you can let notmuch know how you would like to be\n"
53 " addressed. Valid settings are\n"
55 "\tname Your full name.\n"
56 "\tprimary_email Your primary email address.\n"
57 "\tother_email A list (separated by ';') of other email addresses\n"
58 "\t at which you receive email.\n"
60 " Notmuch will use the various email addresses configured here when\n"
61 " formatting replies. It will avoid including your own addresses in the\n"
62 " recipient list of replies, and will set the From address based on the\n"
63 " address to which the original email was addressed.\n"
67 " Configuration for \"notmuch new\"\n"
69 " The following options are supported here:\n"
71 "\ttags A list (separated by ';') of the tags that will be\n"
72 "\t added to all messages incorporated by \"notmuch new\".\n"
74 "\tignore A list (separated by ';') of file and directory names\n"
75 "\t that will not be searched for messages by \"notmuch new\".\n"
77 "\t NOTE: *Every* file/directory that goes by one of those\n"
78 "\t names will be ignored, independent of its depth/location\n"
79 "\t in the mail store.\n"
83 " Search configuration\n"
85 " The following option is supported here:\n"
88 "\t\tA ;-separated list of tags that will be excluded from\n"
89 "\t\tsearch results by default. Using an excluded tag in a\n"
90 "\t\tquery will override that exclusion.\n"
94 " Maildir compatibility configuration\n"
96 " The following option is supported here:\n"
98 "\tsynchronize_flags Valid values are true and false.\n"
100 "\tIf true, then the following maildir flags (in message filenames)\n"
101 "\twill be synchronized with the corresponding notmuch tags:\n"
109 "\t\tS unread (added when 'S' flag is not present)\n"
111 "\tThe \"notmuch new\" command will notice flag changes in filenames\n"
112 "\tand update tags, while the \"notmuch tag\" and \"notmuch restore\"\n"
113 "\tcommands will notice tag changes and update flags in filenames\n"
117 struct _notmuch_config {
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;
137 notmuch_config_destructor (notmuch_config_t *config)
139 if (config->key_file)
140 g_key_file_free (config->key_file);
147 get_config_from_file (notmuch_config_t *config, bool create_new)
149 #define BUF_SIZE 4096
150 char *config_str = NULL;
152 int config_bufsize = BUF_SIZE;
154 GError *error = NULL;
157 FILE *fp = fopen (config->filename, "r");
159 if (errno == ENOENT) {
160 /* If create_new is true, then the caller is prepared for a
161 * default configuration file in the case of FILE NOT FOUND.
164 config->is_new = true;
167 fprintf (stderr, "Configuration file %s not found.\n"
168 "Try running 'notmuch setup' to create a configuration.\n",
172 fprintf (stderr, "Error opening config file '%s': %s\n",
173 config->filename, strerror (errno));
178 config_str = talloc_zero_array (config, char, config_bufsize);
179 if (config_str == NULL) {
180 fprintf (stderr, "Error reading '%s': Out of memory\n", config->filename);
184 while ((len = fread (config_str + config_len, 1,
185 config_bufsize - config_len, fp)) > 0) {
187 if (config_len == config_bufsize) {
188 config_bufsize += BUF_SIZE;
189 config_str = talloc_realloc (config, config_str, char, config_bufsize);
190 if (config_str == NULL) {
191 fprintf (stderr, "Error reading '%s': Failed to reallocate memory\n",
199 fprintf (stderr, "Error reading '%s': I/O error\n", config->filename);
203 if (g_key_file_load_from_data (config->key_file, config_str, config_len,
204 G_KEY_FILE_KEEP_COMMENTS, &error)) {
209 fprintf (stderr, "Error parsing config file '%s': %s\n",
210 config->filename, error->message);
212 g_error_free (error);
219 talloc_free (config_str);
224 /* Open the named notmuch configuration file. If the filename is NULL,
225 * the value of the environment variable $NOTMUCH_CONFIG will be used.
226 * If $NOTMUCH_CONFIG is unset, the default configuration file
227 * ($HOME/.notmuch-config) will be used.
229 * If any error occurs, (out of memory, or a permission-denied error,
230 * etc.), this function will print a message to stderr and return
233 * FILE NOT FOUND: When the specified configuration file (whether from
234 * 'filename' or the $NOTMUCH_CONFIG environment variable) does not
235 * exist, the behavior of this function depends on the 'is_new_ret'
238 * If is_new_ret is NULL, then a "file not found" message will be
239 * printed to stderr and NULL will be returned.
241 * If is_new_ret is non-NULL then a default configuration will be
242 * returned and *is_new_ret will be set to 1 on return so that
243 * the caller can recognize this case.
245 * These default configuration settings are determined as
248 * database_path: $MAILDIR, otherwise $HOME/mail
250 * user_name: $NAME variable if set, otherwise
251 * read from /etc/passwd
253 * user_primary_mail: $EMAIL variable if set, otherwise
254 * constructed from the username and
255 * hostname of the current machine.
257 * user_other_email: Not set.
259 * The default configuration also contains comments to guide the
260 * user in editing the file directly.
263 notmuch_config_open (notmuch_database_t *notmuch,
264 const char *filename,
265 notmuch_command_mode_t config_mode)
267 char *notmuch_config_env = NULL;
269 notmuch_config_t *config = talloc_zero (notmuch, notmuch_config_t);
271 if (config == NULL) {
272 fprintf (stderr, "Out of memory.\n");
276 talloc_set_destructor (config, notmuch_config_destructor);
278 /* non-zero defaults */
279 config->maildir_synchronize_flags = true;
282 config->filename = talloc_strdup (config, filename);
283 } else if ((notmuch_config_env = getenv ("NOTMUCH_CONFIG"))) {
284 config->filename = talloc_strdup (config, notmuch_config_env);
286 config->filename = talloc_asprintf (config, "%s/.notmuch-config",
290 config->key_file = g_key_file_new ();
292 if (config_mode & NOTMUCH_COMMAND_CONFIG_OPEN) {
293 bool create_new = (config_mode & NOTMUCH_COMMAND_CONFIG_CREATE) != 0;
295 if (! get_config_from_file (config, create_new)) {
296 talloc_free (config);
302 g_key_file_set_comment (config->key_file, NULL, NULL,
303 toplevel_config_comment, NULL);
305 for (size_t i = 0; i < ARRAY_SIZE (group_comment_table); i++) {
306 const char *name = group_comment_table[i].group_name;
307 if (! g_key_file_has_group (config->key_file, name)) {
308 /* Force group to exist before adding comment */
309 g_key_file_set_value (config->key_file, name, "dummy_key", "dummy_val");
310 g_key_file_remove_key (config->key_file, name, "dummy_key", NULL);
311 g_key_file_set_comment (config->key_file, name, NULL,
312 group_comment_table[i].comment, NULL);
318 /* Close the given notmuch_config_t object, freeing all resources.
320 * Note: Any changes made to the configuration are *not* saved by this
321 * function. To save changes, call notmuch_config_save before
322 * notmuch_config_close.
325 notmuch_config_close (notmuch_config_t *config)
327 talloc_free (config);
330 /* Save any changes made to the notmuch configuration.
332 * Any comments originally in the file will be preserved.
334 * Returns 0 if successful, and 1 in case of any error, (after
335 * printing a description of the error to stderr).
338 notmuch_config_save (notmuch_config_t *config)
341 char *data, *filename;
342 GError *error = NULL;
344 data = g_key_file_to_data (config->key_file, &length, NULL);
346 fprintf (stderr, "Out of memory.\n");
350 /* Try not to overwrite symlinks. */
351 filename = canonicalize_file_name (config->filename);
353 if (errno == ENOENT) {
354 filename = strdup (config->filename);
356 fprintf (stderr, "Out of memory.\n");
361 fprintf (stderr, "Error canonicalizing %s: %s\n", config->filename,
368 if (! g_file_set_contents (filename, data, length, &error)) {
369 if (strcmp (filename, config->filename) != 0) {
370 fprintf (stderr, "Error saving configuration to %s (-> %s): %s\n",
371 config->filename, filename, error->message);
373 fprintf (stderr, "Error saving configuration to %s: %s\n",
374 filename, error->message);
376 g_error_free (error);
388 notmuch_config_is_new (notmuch_config_t *config)
390 return config->is_new;
394 _config_set (notmuch_config_t *config, char **field,
395 const char *group, const char *key, const char *value)
397 g_key_file_set_string (config->key_file, group, key, value);
399 /* drop the cached value */
400 talloc_free (*field);
405 _config_set_list (notmuch_config_t *config,
406 const char *group, const char *key,
408 size_t length, const char ***config_var )
410 g_key_file_set_string_list (config->key_file, group, key, list, length);
412 /* drop the cached value */
413 talloc_free (*config_var);
418 notmuch_config_set_database_path (notmuch_config_t *config,
419 const char *database_path)
421 _config_set (config, &config->database_path, "database", "path", database_path);
425 notmuch_config_set_user_name (notmuch_config_t *config,
426 const char *user_name)
428 _config_set (config, &config->user_name, "user", "name", user_name);
432 notmuch_config_set_user_primary_email (notmuch_config_t *config,
433 const char *primary_email)
435 _config_set (config, &config->user_primary_email, "user", "primary_email", primary_email);
439 notmuch_config_set_user_other_email (notmuch_config_t *config,
443 _config_set_list (config, "user", "other_email", list, length,
444 &(config->user_other_email));
448 notmuch_config_set_new_tags (notmuch_config_t *config,
452 _config_set_list (config, "new", "tags", list, length,
453 &(config->new_tags));
457 notmuch_config_set_new_ignore (notmuch_config_t *config,
461 _config_set_list (config, "new", "ignore", list, length,
462 &(config->new_ignore));
466 notmuch_config_set_search_exclude_tags (notmuch_config_t *config,
470 _config_set_list (config, "search", "exclude_tags", list, length,
471 &(config->search_exclude_tags));
475 /* Given a configuration item of the form <group>.<key> return the
476 * component group and key. If any error occurs, print a message on
477 * stderr and return 1. Otherwise, return 0.
479 * Note: This function modifies the original 'item' string.
482 _item_split (char *item, char **group, char **key)
488 period = strchr (item, '.');
489 if (period == NULL || *(period + 1) == '\0') {
491 "Invalid configuration name: %s\n"
492 "(Should be of the form <section>.<item>)\n", item);
502 /* These are more properly called Xapian fields, but the user facing
503 * docs call them prefixes, so make the error message match */
505 validate_field_name (const char *str)
509 if (! g_utf8_validate (str, -1, NULL)) {
510 fprintf (stderr, "Invalid utf8: %s\n", str);
514 key = g_utf8_strrchr (str, -1, '.');
516 INTERNAL_ERROR ("Impossible code path on input: %s\n", str);
522 fprintf (stderr, "Empty prefix name: %s\n", str);
526 if (! unicode_word_utf8 (key)) {
527 fprintf (stderr, "Non-word character in prefix name: %s\n", key);
531 if (key[0] >= 'a' && key[0] <= 'z') {
532 fprintf (stderr, "Prefix names starting with lower case letters are reserved: %s\n", key);
539 #define BUILT_WITH_PREFIX "built_with."
541 typedef struct config_key {
545 bool (*validate)(const char *);
548 static struct config_key
549 config_key_table[] = {
550 { "index.decrypt", true, false, NULL },
551 { "index.header.", true, true, validate_field_name },
552 { "query.", true, true, NULL },
555 static config_key_info_t *
556 _config_key_info (const char *item)
558 for (size_t i = 0; i < ARRAY_SIZE (config_key_table); i++) {
559 if (config_key_table[i].prefix &&
560 strncmp (item, config_key_table[i].name,
561 strlen (config_key_table[i].name)) == 0)
562 return config_key_table + i;
563 if (strcmp (item, config_key_table[i].name) == 0)
564 return config_key_table + i;
570 notmuch_config_command_get (notmuch_database_t *notmuch, char *item)
572 notmuch_config_values_t *list;
574 for (list = notmuch_config_get_values_string (notmuch, item);
575 notmuch_config_values_valid (list);
576 notmuch_config_values_move_to_next (list)) {
577 const char *val = notmuch_config_values_get (list);
584 _set_db_config (notmuch_database_t *notmuch, const char *key, int argc, char **argv)
586 const char *val = "";
589 /* XXX handle lists? */
590 fprintf (stderr, "notmuch config set: at most one value expected for %s\n", key);
598 if (print_status_database ("notmuch config", notmuch,
599 notmuch_database_reopen (notmuch,
600 NOTMUCH_DATABASE_MODE_READ_WRITE)))
603 if (print_status_database ("notmuch config", notmuch,
604 notmuch_database_set_config (notmuch, key, val)))
607 if (print_status_database ("notmuch config", notmuch,
608 notmuch_database_close (notmuch)))
615 notmuch_config_command_set (notmuch_config_t *config, notmuch_database_t *notmuch, char *item,
616 int argc, char *argv[])
619 config_key_info_t *key_info;
621 if (STRNCMP_LITERAL (item, BUILT_WITH_PREFIX) == 0) {
622 fprintf (stderr, "Error: read only option: %s\n", item);
626 key_info = _config_key_info (item);
627 if (key_info && key_info->validate && (! key_info->validate (item)))
630 if (key_info && key_info->in_db) {
631 return _set_db_config (notmuch, item, argc, argv);
634 if (_item_split (item, &group, &key))
637 /* With only the name of an item, we clear it from the
638 * configuration file.
640 * With a single value, we set it as a string.
642 * With multiple values, we set them as a string list.
646 g_key_file_remove_key (config->key_file, group, key, NULL);
649 g_key_file_set_string (config->key_file, group, key, argv[0]);
652 g_key_file_set_string_list (config->key_file, group, key,
653 (const gchar **) argv, argc);
657 return notmuch_config_save (config);
662 _notmuch_config_list_built_with ()
664 printf ("%scompact=%s\n",
666 notmuch_built_with ("compact") ? "true" : "false");
667 printf ("%sfield_processor=%s\n",
669 notmuch_built_with ("field_processor") ? "true" : "false");
670 printf ("%sretry_lock=%s\n",
672 notmuch_built_with ("retry_lock") ? "true" : "false");
676 notmuch_config_command_list (notmuch_database_t *notmuch)
678 notmuch_config_pairs_t *list;
680 _notmuch_config_list_built_with ();
681 for (list = notmuch_config_get_pairs (notmuch, "");
682 notmuch_config_pairs_valid (list);
683 notmuch_config_pairs_move_to_next (list)) {
684 const char *value = notmuch_config_pairs_value (list);
686 printf ("%s=%s\n", notmuch_config_pairs_key (list), value);
688 notmuch_config_pairs_destroy (list);
693 notmuch_config_command (notmuch_config_t *config, notmuch_database_t *notmuch,
694 int argc, char *argv[])
699 opt_index = notmuch_minimal_options ("config", argc, argv);
703 if (notmuch_requested_db_uuid)
704 fprintf (stderr, "Warning: ignoring --uuid=%s\n",
705 notmuch_requested_db_uuid);
707 /* skip at least subcommand argument */
712 fprintf (stderr, "Error: notmuch config requires at least one argument.\n");
716 if (strcmp (argv[0], "get") == 0) {
718 fprintf (stderr, "Error: notmuch config get requires exactly "
722 ret = notmuch_config_command_get (notmuch, argv[1]);
723 } else if (strcmp (argv[0], "set") == 0) {
725 fprintf (stderr, "Error: notmuch config set requires at least "
729 ret = notmuch_config_command_set (config, notmuch, argv[1], argc - 2, argv + 2);
730 } else if (strcmp (argv[0], "list") == 0) {
731 ret = notmuch_config_command_list (notmuch);
733 fprintf (stderr, "Unrecognized argument for notmuch config: %s\n",
738 return ret ? EXIT_FAILURE : EXIT_SUCCESS;
743 notmuch_config_set_maildir_synchronize_flags (notmuch_config_t *config,
744 bool synchronize_flags)
746 g_key_file_set_boolean (config->key_file,
747 "maildir", "synchronize_flags", synchronize_flags);
748 config->maildir_synchronize_flags = synchronize_flags;