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 notmuch_config_destructor (notmuch_config_t *config)
126 if (config->key_file)
127 g_key_file_free (config->key_file);
133 get_config_from_file (notmuch_config_t *config, bool create_new)
135 #define BUF_SIZE 4096
136 char *config_str = NULL;
138 int config_bufsize = BUF_SIZE;
140 GError *error = NULL;
143 FILE *fp = fopen (config->filename, "r");
145 if (errno == ENOENT) {
146 /* If create_new is true, then the caller is prepared for a
147 * default configuration file in the case of FILE NOT FOUND.
150 config->is_new = true;
153 fprintf (stderr, "Configuration file %s not found.\n"
154 "Try running 'notmuch setup' to create a configuration.\n",
158 fprintf (stderr, "Error opening config file '%s': %s\n",
159 config->filename, strerror (errno));
164 config_str = talloc_zero_array (config, char, config_bufsize);
165 if (config_str == NULL) {
166 fprintf (stderr, "Error reading '%s': Out of memory\n", config->filename);
170 while ((len = fread (config_str + config_len, 1,
171 config_bufsize - config_len, fp)) > 0) {
173 if (config_len == config_bufsize) {
174 config_bufsize += BUF_SIZE;
175 config_str = talloc_realloc (config, config_str, char, config_bufsize);
176 if (config_str == NULL) {
177 fprintf (stderr, "Error reading '%s': Failed to reallocate memory\n",
185 fprintf (stderr, "Error reading '%s': I/O error\n", config->filename);
189 if (g_key_file_load_from_data (config->key_file, config_str, config_len,
190 G_KEY_FILE_KEEP_COMMENTS, &error)) {
195 fprintf (stderr, "Error parsing config file '%s': %s\n",
196 config->filename, error->message);
198 g_error_free (error);
205 talloc_free (config_str);
210 /* Open the named notmuch configuration file. If the filename is NULL,
211 * the value of the environment variable $NOTMUCH_CONFIG will be used.
212 * If $NOTMUCH_CONFIG is unset, the default configuration file
213 * ($HOME/.notmuch-config) will be used.
215 * If any error occurs, (out of memory, or a permission-denied error,
216 * etc.), this function will print a message to stderr and return
219 * FILE NOT FOUND: When the specified configuration file (whether from
220 * 'filename' or the $NOTMUCH_CONFIG environment variable) does not
221 * exist, the behavior of this function depends on the 'is_new_ret'
224 * If is_new_ret is NULL, then a "file not found" message will be
225 * printed to stderr and NULL will be returned.
227 * If is_new_ret is non-NULL then a default configuration will be
228 * returned and *is_new_ret will be set to 1 on return so that
229 * the caller can recognize this case.
231 * These default configuration settings are determined as
234 * database_path: $MAILDIR, otherwise $HOME/mail
236 * user_name: $NAME variable if set, otherwise
237 * read from /etc/passwd
239 * user_primary_mail: $EMAIL variable if set, otherwise
240 * constructed from the username and
241 * hostname of the current machine.
243 * user_other_email: Not set.
245 * The default configuration also contains comments to guide the
246 * user in editing the file directly.
249 notmuch_config_open (notmuch_database_t *notmuch,
250 const char *filename,
251 notmuch_command_mode_t config_mode)
253 char *notmuch_config_env = NULL;
255 notmuch_config_t *config = talloc_zero (notmuch, notmuch_config_t);
257 if (config == NULL) {
258 fprintf (stderr, "Out of memory.\n");
262 talloc_set_destructor (config, notmuch_config_destructor);
265 config->filename = talloc_strdup (config, filename);
266 } else if ((notmuch_config_env = getenv ("NOTMUCH_CONFIG"))) {
267 config->filename = talloc_strdup (config, notmuch_config_env);
269 config->filename = talloc_asprintf (config, "%s/.notmuch-config",
273 config->key_file = g_key_file_new ();
275 if (config_mode & NOTMUCH_COMMAND_CONFIG_OPEN) {
276 bool create_new = (config_mode & NOTMUCH_COMMAND_CONFIG_CREATE) != 0;
278 if (! get_config_from_file (config, create_new)) {
279 talloc_free (config);
285 g_key_file_set_comment (config->key_file, NULL, NULL,
286 toplevel_config_comment, NULL);
288 for (size_t i = 0; i < ARRAY_SIZE (group_comment_table); i++) {
289 const char *name = group_comment_table[i].group_name;
290 if (! g_key_file_has_group (config->key_file, name)) {
291 /* Force group to exist before adding comment */
292 g_key_file_set_value (config->key_file, name, "dummy_key", "dummy_val");
293 g_key_file_remove_key (config->key_file, name, "dummy_key", NULL);
294 g_key_file_set_comment (config->key_file, name, NULL,
295 group_comment_table[i].comment, NULL);
301 /* Close the given notmuch_config_t object, freeing all resources.
303 * Note: Any changes made to the configuration are *not* saved by this
304 * function. To save changes, call notmuch_config_save before
305 * notmuch_config_close.
308 notmuch_config_close (notmuch_config_t *config)
310 talloc_free (config);
313 /* Save any changes made to the notmuch configuration.
315 * Any comments originally in the file will be preserved.
317 * Returns 0 if successful, and 1 in case of any error, (after
318 * printing a description of the error to stderr).
321 notmuch_config_save (notmuch_config_t *config)
324 char *data, *filename;
325 GError *error = NULL;
327 data = g_key_file_to_data (config->key_file, &length, NULL);
329 fprintf (stderr, "Out of memory.\n");
333 /* Try not to overwrite symlinks. */
334 filename = canonicalize_file_name (config->filename);
336 if (errno == ENOENT) {
337 filename = strdup (config->filename);
339 fprintf (stderr, "Out of memory.\n");
344 fprintf (stderr, "Error canonicalizing %s: %s\n", config->filename,
351 if (! g_file_set_contents (filename, data, length, &error)) {
352 if (strcmp (filename, config->filename) != 0) {
353 fprintf (stderr, "Error saving configuration to %s (-> %s): %s\n",
354 config->filename, filename, error->message);
356 fprintf (stderr, "Error saving configuration to %s: %s\n",
357 filename, error->message);
359 g_error_free (error);
371 notmuch_config_is_new (notmuch_config_t *config)
373 return config->is_new;
377 _config_set (notmuch_config_t *config,
378 const char *group, const char *key, const char *value)
380 g_key_file_set_string (config->key_file, group, key, value);
384 _config_set_list (notmuch_config_t *config,
385 const char *group, const char *key,
389 g_key_file_set_string_list (config->key_file, group, key, list, length);
393 notmuch_config_set_database_path (notmuch_config_t *config,
394 const char *database_path)
396 _config_set (config, "database", "path", database_path);
400 notmuch_config_set_user_name (notmuch_config_t *config,
401 const char *user_name)
403 _config_set (config, "user", "name", user_name);
407 notmuch_config_set_user_primary_email (notmuch_config_t *config,
408 const char *primary_email)
410 _config_set (config, "user", "primary_email", primary_email);
414 notmuch_config_set_user_other_email (notmuch_config_t *config,
418 _config_set_list (config, "user", "other_email", list, length);
422 notmuch_config_set_new_tags (notmuch_config_t *config,
426 _config_set_list (config, "new", "tags", list, length);
430 notmuch_config_set_new_ignore (notmuch_config_t *config,
434 _config_set_list (config, "new", "ignore", list, length);
438 notmuch_config_set_search_exclude_tags (notmuch_config_t *config,
442 _config_set_list (config, "search", "exclude_tags", list, length);
446 /* Given a configuration item of the form <group>.<key> return the
447 * component group and key. If any error occurs, print a message on
448 * stderr and return 1. Otherwise, return 0.
450 * Note: This function modifies the original 'item' string.
453 _item_split (char *item, char **group, char **key)
459 period = strchr (item, '.');
460 if (period == NULL || *(period + 1) == '\0') {
462 "Invalid configuration name: %s\n"
463 "(Should be of the form <section>.<item>)\n", item);
473 /* These are more properly called Xapian fields, but the user facing
474 * docs call them prefixes, so make the error message match */
476 validate_field_name (const char *str)
480 if (! g_utf8_validate (str, -1, NULL)) {
481 fprintf (stderr, "Invalid utf8: %s\n", str);
485 key = g_utf8_strrchr (str, -1, '.');
487 INTERNAL_ERROR ("Impossible code path on input: %s\n", str);
493 fprintf (stderr, "Empty prefix name: %s\n", str);
497 if (! unicode_word_utf8 (key)) {
498 fprintf (stderr, "Non-word character in prefix name: %s\n", key);
502 if (key[0] >= 'a' && key[0] <= 'z') {
503 fprintf (stderr, "Prefix names starting with lower case letters are reserved: %s\n", key);
510 #define BUILT_WITH_PREFIX "built_with."
512 typedef struct config_key {
515 bool (*validate)(const char *);
518 static struct config_key
519 config_key_table[] = {
520 { "index.decrypt", false, NULL },
521 { "index.header.", true, validate_field_name },
522 { "query.", true, NULL },
525 static config_key_info_t *
526 _config_key_info (const char *item)
528 for (size_t i = 0; i < ARRAY_SIZE (config_key_table); i++) {
529 if (config_key_table[i].prefix &&
530 strncmp (item, config_key_table[i].name,
531 strlen (config_key_table[i].name)) == 0)
532 return config_key_table + i;
533 if (strcmp (item, config_key_table[i].name) == 0)
534 return config_key_table + i;
540 notmuch_config_command_get (notmuch_database_t *notmuch, char *item)
542 notmuch_config_values_t *list;
544 for (list = notmuch_config_get_values_string (notmuch, item);
545 notmuch_config_values_valid (list);
546 notmuch_config_values_move_to_next (list)) {
547 const char *val = notmuch_config_values_get (list);
554 _set_db_config (notmuch_database_t *notmuch, const char *key, int argc, char **argv)
556 const char *val = "";
559 /* XXX handle lists? */
560 fprintf (stderr, "notmuch config set: at most one value expected for %s\n", key);
568 if (print_status_database ("notmuch config", notmuch,
569 notmuch_database_reopen (notmuch,
570 NOTMUCH_DATABASE_MODE_READ_WRITE)))
573 if (print_status_database ("notmuch config", notmuch,
574 notmuch_database_set_config (notmuch, key, val)))
577 if (print_status_database ("notmuch config", notmuch,
578 notmuch_database_close (notmuch)))
585 notmuch_config_command_set (notmuch_config_t *config, notmuch_database_t *notmuch,
586 int argc, char *argv[])
589 config_key_info_t *key_info;
590 bool update_database = false;
594 notmuch_opt_desc_t options[] = {
595 { .opt_bool = &update_database, .name = "database" },
599 opt_index = parse_arguments (argc, argv, options, 1);
607 fprintf (stderr, "Error: notmuch config set requires at least "
616 if (STRNCMP_LITERAL (item, BUILT_WITH_PREFIX) == 0) {
617 fprintf (stderr, "Error: read only option: %s\n", item);
621 key_info = _config_key_info (item);
622 if (key_info && key_info->validate && (! key_info->validate (item)))
625 if (update_database) {
626 return _set_db_config (notmuch, item, argc, argv);
629 if (_item_split (item, &group, &key))
632 /* With only the name of an item, we clear it from the
633 * configuration file.
635 * With a single value, we set it as a string.
637 * With multiple values, we set them as a string list.
641 g_key_file_remove_key (config->key_file, group, key, NULL);
644 g_key_file_set_string (config->key_file, group, key, argv[0]);
647 g_key_file_set_string_list (config->key_file, group, key,
648 (const gchar **) argv, argc);
652 return notmuch_config_save (config);
657 _notmuch_config_list_built_with ()
659 printf ("%scompact=%s\n",
661 notmuch_built_with ("compact") ? "true" : "false");
662 printf ("%sfield_processor=%s\n",
664 notmuch_built_with ("field_processor") ? "true" : "false");
665 printf ("%sretry_lock=%s\n",
667 notmuch_built_with ("retry_lock") ? "true" : "false");
671 notmuch_config_command_list (notmuch_database_t *notmuch)
673 notmuch_config_pairs_t *list;
675 _notmuch_config_list_built_with ();
676 for (list = notmuch_config_get_pairs (notmuch, "");
677 notmuch_config_pairs_valid (list);
678 notmuch_config_pairs_move_to_next (list)) {
679 const char *value = notmuch_config_pairs_value (list);
681 printf ("%s=%s\n", notmuch_config_pairs_key (list), value);
683 notmuch_config_pairs_destroy (list);
688 notmuch_config_command (notmuch_config_t *config, notmuch_database_t *notmuch,
689 int argc, char *argv[])
694 opt_index = notmuch_minimal_options ("config", argc, argv);
698 if (notmuch_requested_db_uuid)
699 fprintf (stderr, "Warning: ignoring --uuid=%s\n",
700 notmuch_requested_db_uuid);
702 /* skip at least subcommand argument */
707 fprintf (stderr, "Error: notmuch config requires at least one argument.\n");
711 if (strcmp (argv[0], "get") == 0) {
713 fprintf (stderr, "Error: notmuch config get requires exactly "
717 ret = notmuch_config_command_get (notmuch, argv[1]);
718 } else if (strcmp (argv[0], "set") == 0) {
719 ret = notmuch_config_command_set (config, notmuch, argc, argv);
720 } else if (strcmp (argv[0], "list") == 0) {
721 ret = notmuch_config_command_list (notmuch);
723 fprintf (stderr, "Unrecognized argument for notmuch config: %s\n",
728 return ret ? EXIT_FAILURE : EXIT_SUCCESS;
733 notmuch_config_set_maildir_synchronize_flags (notmuch_config_t *config,
734 bool synchronize_flags)
736 g_key_file_set_boolean (config->key_file,
737 "maildir", "synchronize_flags", synchronize_flags);