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 "path-util.h"
28 #include "unicode-util.h"
30 static const char toplevel_config_comment[] =
31 " .notmuch-config - Configuration file for the notmuch mail system\n"
33 " For more information about notmuch, see https://notmuchmail.org";
35 static const struct config_group {
36 const char *group_name;
38 } group_comment_table [] = {
41 " Database configuration\n"
43 " The only value supported here is 'path' which should be the top-level\n"
44 " directory where your mail currently exists and to where mail will be\n"
45 " delivered in the future. Files should be individual email messages.\n"
46 " Notmuch will store its database within a sub-directory of the path\n"
47 " configured here named \".notmuch\".\n"
51 " User configuration\n"
53 " Here is where you can let notmuch know how you would like to be\n"
54 " addressed. Valid settings are\n"
56 "\tname Your full name.\n"
57 "\tprimary_email Your primary email address.\n"
58 "\tother_email A list (separated by ';') of other email addresses\n"
59 "\t at which you receive email.\n"
61 " Notmuch will use the various email addresses configured here when\n"
62 " formatting replies. It will avoid including your own addresses in the\n"
63 " recipient list of replies, and will set the From address based on the\n"
64 " address to which the original email was addressed.\n"
68 " Configuration for \"notmuch new\"\n"
70 " The following options are supported here:\n"
72 "\ttags A list (separated by ';') of the tags that will be\n"
73 "\t added to all messages incorporated by \"notmuch new\".\n"
75 "\tignore A list (separated by ';') of file and directory names\n"
76 "\t that will not be searched for messages by \"notmuch new\".\n"
78 "\t NOTE: *Every* file/directory that goes by one of those\n"
79 "\t names will be ignored, independent of its depth/location\n"
80 "\t in the mail store.\n"
84 " Search configuration\n"
86 " The following option is supported here:\n"
89 "\t\tA ;-separated list of tags that will be excluded from\n"
90 "\t\tsearch results by default. Using an excluded tag in a\n"
91 "\t\tquery will override that exclusion.\n"
95 " Maildir compatibility configuration\n"
97 " The following option is supported here:\n"
99 "\tsynchronize_flags Valid values are true and false.\n"
101 "\tIf true, then the following maildir flags (in message filenames)\n"
102 "\twill be synchronized with the corresponding notmuch tags:\n"
110 "\t\tS unread (added when 'S' flag is not present)\n"
112 "\tThe \"notmuch new\" command will notice flag changes in filenames\n"
113 "\tand update tags, while the \"notmuch tag\" and \"notmuch restore\"\n"
114 "\tcommands will notice tag changes and update flags in filenames\n"
118 struct _notmuch_conffile {
125 notmuch_conffile_destructor (notmuch_conffile_t *config)
127 if (config->key_file)
128 g_key_file_free (config->key_file);
134 get_config_from_file (notmuch_conffile_t *config, bool create_new)
136 #define BUF_SIZE 4096
137 char *config_str = NULL;
139 int config_bufsize = BUF_SIZE;
141 GError *error = NULL;
144 FILE *fp = fopen (config->filename, "r");
146 if (errno == ENOENT) {
147 /* If create_new is true, then the caller is prepared for a
148 * default configuration file in the case of FILE NOT FOUND.
151 config->is_new = true;
154 fprintf (stderr, "Configuration file %s not found.\n"
155 "Try running 'notmuch setup' to create a configuration.\n",
159 fprintf (stderr, "Error opening config file '%s': %s\n",
160 config->filename, strerror (errno));
165 config_str = talloc_zero_array (config, char, config_bufsize);
166 if (config_str == NULL) {
167 fprintf (stderr, "Error reading '%s': Out of memory\n", config->filename);
171 while ((len = fread (config_str + config_len, 1,
172 config_bufsize - config_len, fp)) > 0) {
174 if (config_len == config_bufsize) {
175 config_bufsize += BUF_SIZE;
176 config_str = talloc_realloc (config, config_str, char, config_bufsize);
177 if (config_str == NULL) {
178 fprintf (stderr, "Error reading '%s': Failed to reallocate memory\n",
186 fprintf (stderr, "Error reading '%s': I/O error\n", config->filename);
190 if (g_key_file_load_from_data (config->key_file, config_str, config_len,
191 G_KEY_FILE_KEEP_COMMENTS, &error)) {
196 fprintf (stderr, "Error parsing config file '%s': %s\n",
197 config->filename, error->message);
199 g_error_free (error);
206 talloc_free (config_str);
211 /* Open the named notmuch configuration file. If the filename is NULL,
212 * the value of the environment variable $NOTMUCH_CONFIG will be used.
213 * If $NOTMUCH_CONFIG is unset, the default configuration file
214 * ($HOME/.notmuch-config) will be used.
216 * If any error occurs, (out of memory, or a permission-denied error,
217 * etc.), this function will print a message to stderr and return
220 * FILE NOT FOUND: When the specified configuration file (whether from
221 * 'filename' or the $NOTMUCH_CONFIG environment variable) does not
222 * exist, the behavior of this function depends on the 'is_new_ret'
225 * If is_new_ret is NULL, then a "file not found" message will be
226 * printed to stderr and NULL will be returned.
228 * If is_new_ret is non-NULL then a default configuration will be
229 * returned and *is_new_ret will be set to 1 on return so that
230 * the caller can recognize this case.
232 * These default configuration settings are determined as
235 * database_path: $MAILDIR, otherwise $HOME/mail
237 * user_name: $NAME variable if set, otherwise
238 * read from /etc/passwd
240 * user_primary_mail: $EMAIL variable if set, otherwise
241 * constructed from the username and
242 * hostname of the current machine.
244 * user_other_email: Not set.
246 * The default configuration also contains comments to guide the
247 * user in editing the file directly.
250 notmuch_conffile_open (notmuch_database_t *notmuch,
251 const char *filename,
254 char *notmuch_config_env = NULL;
256 notmuch_conffile_t *config = talloc_zero (notmuch, notmuch_conffile_t);
258 if (config == NULL) {
259 fprintf (stderr, "Out of memory.\n");
263 talloc_set_destructor (config, notmuch_conffile_destructor);
266 config->filename = talloc_strdup (config, filename);
267 } else if ((notmuch_config_env = getenv ("NOTMUCH_CONFIG"))) {
268 config->filename = talloc_strdup (config, notmuch_config_env);
270 config->filename = talloc_asprintf (config, "%s/.notmuch-config",
274 config->key_file = g_key_file_new ();
276 if (! get_config_from_file (config, create)) {
277 talloc_free (config);
282 g_key_file_set_comment (config->key_file, NULL, NULL,
283 toplevel_config_comment, NULL);
285 for (size_t i = 0; i < ARRAY_SIZE (group_comment_table); i++) {
286 const char *name = group_comment_table[i].group_name;
287 if (! g_key_file_has_group (config->key_file, name)) {
288 /* Force group to exist before adding comment */
289 g_key_file_set_value (config->key_file, name, "dummy_key", "dummy_val");
290 g_key_file_remove_key (config->key_file, name, "dummy_key", NULL);
291 g_key_file_set_comment (config->key_file, name, NULL,
292 group_comment_table[i].comment, NULL);
298 /* Close the given notmuch_conffile_t object, freeing all resources.
300 * Note: Any changes made to the configuration are *not* saved by this
301 * function. To save changes, call notmuch_conffile_save before
302 * notmuch_conffile_close.
305 notmuch_conffile_close (notmuch_conffile_t *config)
307 talloc_free (config);
310 /* Save any changes made to the notmuch configuration.
312 * Any comments originally in the file will be preserved.
314 * Returns 0 if successful, and 1 in case of any error, (after
315 * printing a description of the error to stderr).
318 notmuch_conffile_save (notmuch_conffile_t *config)
321 char *data, *filename;
322 GError *error = NULL;
324 data = g_key_file_to_data (config->key_file, &length, NULL);
326 fprintf (stderr, "Out of memory.\n");
330 /* Try not to overwrite symlinks. */
331 filename = notmuch_canonicalize_file_name (config->filename);
333 if (errno == ENOENT) {
334 filename = strdup (config->filename);
336 fprintf (stderr, "Out of memory.\n");
341 fprintf (stderr, "Error canonicalizing %s: %s\n", config->filename,
348 if (! g_file_set_contents (filename, data, length, &error)) {
349 if (strcmp (filename, config->filename) != 0) {
350 fprintf (stderr, "Error saving configuration to %s (-> %s): %s\n",
351 config->filename, filename, error->message);
353 fprintf (stderr, "Error saving configuration to %s: %s\n",
354 filename, error->message);
356 g_error_free (error);
368 notmuch_conffile_is_new (notmuch_conffile_t *config)
370 return config->is_new;
374 _config_set (notmuch_conffile_t *config,
375 const char *group, const char *key, const char *value)
377 g_key_file_set_string (config->key_file, group, key, value);
381 _config_set_list (notmuch_conffile_t *config,
382 const char *group, const char *key,
387 g_key_file_set_string_list (config->key_file, group, key, list, length);
389 g_key_file_set_string (config->key_file, group, key, list[0]);
393 notmuch_conffile_set_database_path (notmuch_conffile_t *config,
394 const char *database_path)
396 _config_set (config, "database", "path", database_path);
400 notmuch_conffile_set_user_name (notmuch_conffile_t *config,
401 const char *user_name)
403 _config_set (config, "user", "name", user_name);
407 notmuch_conffile_set_user_primary_email (notmuch_conffile_t *config,
408 const char *primary_email)
410 _config_set (config, "user", "primary_email", primary_email);
414 notmuch_conffile_set_user_other_email (notmuch_conffile_t *config,
418 _config_set_list (config, "user", "other_email", list, length);
422 notmuch_conffile_set_new_tags (notmuch_conffile_t *config,
426 _config_set_list (config, "new", "tags", list, length);
430 notmuch_conffile_set_new_ignore (notmuch_conffile_t *config,
434 _config_set_list (config, "new", "ignore", list, length);
438 notmuch_conffile_set_search_exclude_tags (notmuch_conffile_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 const struct config_key
519 config_key_table[] = {
520 { "index.decrypt", false, NULL },
521 { "index.header.", true, validate_field_name },
522 { "query.", true, NULL },
523 { "squery.", true, validate_field_name },
526 static const config_key_info_t *
527 _config_key_info (const char *item)
529 for (size_t i = 0; i < ARRAY_SIZE (config_key_table); i++) {
530 if (config_key_table[i].prefix &&
531 strncmp (item, config_key_table[i].name,
532 strlen (config_key_table[i].name)) == 0)
533 return config_key_table + i;
534 if (strcmp (item, config_key_table[i].name) == 0)
535 return config_key_table + i;
541 notmuch_config_command_get (notmuch_database_t *notmuch, char *item)
543 notmuch_config_values_t *list;
545 if (STRNCMP_LITERAL (item, BUILT_WITH_PREFIX) == 0) {
546 if (notmuch_built_with (item + strlen (BUILT_WITH_PREFIX)))
551 for (list = notmuch_config_get_values_string (notmuch, item);
552 notmuch_config_values_valid (list);
553 notmuch_config_values_move_to_next (list)) {
554 const char *val = notmuch_config_values_get (list);
562 _set_db_config (notmuch_database_t *notmuch, const char *key, int argc, char **argv)
564 const char *val = "";
567 /* XXX handle lists? */
568 fprintf (stderr, "notmuch config set: at most one value expected for %s\n", key);
576 if (print_status_database ("notmuch config", notmuch,
577 notmuch_database_reopen (notmuch,
578 NOTMUCH_DATABASE_MODE_READ_WRITE)))
581 if (print_status_database ("notmuch config", notmuch,
582 notmuch_database_set_config (notmuch, key, val)))
585 if (print_status_database ("notmuch config", notmuch,
586 notmuch_database_close (notmuch)))
593 notmuch_config_command_set (notmuch_database_t *notmuch,
594 int argc, char *argv[])
597 const config_key_info_t *key_info;
598 notmuch_conffile_t *config;
599 bool update_database = false;
603 notmuch_opt_desc_t options[] = {
604 { .opt_bool = &update_database, .name = "database" },
608 opt_index = parse_arguments (argc, argv, options, 1);
616 fprintf (stderr, "Error: notmuch config set requires at least "
625 if (STRNCMP_LITERAL (item, BUILT_WITH_PREFIX) == 0) {
626 fprintf (stderr, "Error: read only option: %s\n", item);
630 key_info = _config_key_info (item);
631 if (key_info && key_info->validate && (! key_info->validate (item)))
634 if (update_database) {
635 return _set_db_config (notmuch, item, argc, argv);
638 if (_item_split (item, &group, &key))
641 config = notmuch_conffile_open (notmuch,
642 notmuch_config_path (notmuch), false);
646 /* With only the name of an item, we clear it from the
647 * configuration file.
649 * With a single value, we set it as a string.
651 * With multiple values, we set them as a string list.
655 g_key_file_remove_key (config->key_file, group, key, NULL);
658 g_key_file_set_string (config->key_file, group, key, argv[0]);
661 g_key_file_set_string_list (config->key_file, group, key,
662 (const gchar **) argv, argc);
666 ret = notmuch_conffile_save (config);
668 notmuch_conffile_close (config);
675 _notmuch_config_list_built_with ()
677 printf ("%scompact=%s\n",
679 notmuch_built_with ("compact") ? "true" : "false");
680 printf ("%sfield_processor=%s\n",
682 notmuch_built_with ("field_processor") ? "true" : "false");
683 printf ("%sretry_lock=%s\n",
685 notmuch_built_with ("retry_lock") ? "true" : "false");
686 printf ("%ssexp_queries=%s\n",
688 notmuch_built_with ("sexp_queries") ? "true" : "false");
692 notmuch_config_command_list (notmuch_database_t *notmuch)
694 notmuch_config_pairs_t *list;
696 _notmuch_config_list_built_with ();
697 for (list = notmuch_config_get_pairs (notmuch, "");
698 notmuch_config_pairs_valid (list);
699 notmuch_config_pairs_move_to_next (list)) {
700 const char *value = notmuch_config_pairs_value (list);
702 printf ("%s=%s\n", notmuch_config_pairs_key (list), value);
704 notmuch_config_pairs_destroy (list);
709 notmuch_config_command (notmuch_database_t *notmuch, int argc, char *argv[])
714 opt_index = notmuch_minimal_options ("config", argc, argv);
718 /* skip at least subcommand argument */
723 fprintf (stderr, "Error: notmuch config requires at least one argument.\n");
727 if (strcmp (argv[0], "get") == 0) {
729 fprintf (stderr, "Error: notmuch config get requires exactly "
733 ret = notmuch_config_command_get (notmuch, argv[1]);
734 } else if (strcmp (argv[0], "set") == 0) {
735 ret = notmuch_config_command_set (notmuch, argc, argv);
736 } else if (strcmp (argv[0], "list") == 0) {
737 ret = notmuch_config_command_list (notmuch);
739 fprintf (stderr, "Unrecognized argument for notmuch config: %s\n",
744 return ret ? EXIT_FAILURE : EXIT_SUCCESS;
749 notmuch_conffile_set_maildir_synchronize_flags (notmuch_conffile_t *config,
750 bool synchronize_flags)
752 g_key_file_set_boolean (config->key_file,
753 "maildir", "synchronize_flags", synchronize_flags);