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 {
516 bool (*validate)(const char *);
519 static struct config_key
520 config_key_table[] = {
521 { "index.decrypt", true, false, NULL },
522 { "index.header.", true, true, validate_field_name },
523 { "query.", true, true, NULL },
526 static 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 for (list = notmuch_config_get_values_string (notmuch, item);
546 notmuch_config_values_valid (list);
547 notmuch_config_values_move_to_next (list)) {
548 const char *val = notmuch_config_values_get (list);
555 _set_db_config (notmuch_database_t *notmuch, const char *key, int argc, char **argv)
557 const char *val = "";
560 /* XXX handle lists? */
561 fprintf (stderr, "notmuch config set: at most one value expected for %s\n", key);
569 if (print_status_database ("notmuch config", notmuch,
570 notmuch_database_reopen (notmuch,
571 NOTMUCH_DATABASE_MODE_READ_WRITE)))
574 if (print_status_database ("notmuch config", notmuch,
575 notmuch_database_set_config (notmuch, key, val)))
578 if (print_status_database ("notmuch config", notmuch,
579 notmuch_database_close (notmuch)))
586 notmuch_config_command_set (notmuch_config_t *config, notmuch_database_t *notmuch, char *item,
587 int argc, char *argv[])
590 config_key_info_t *key_info;
592 if (STRNCMP_LITERAL (item, BUILT_WITH_PREFIX) == 0) {
593 fprintf (stderr, "Error: read only option: %s\n", item);
597 key_info = _config_key_info (item);
598 if (key_info && key_info->validate && (! key_info->validate (item)))
601 if (key_info && key_info->in_db) {
602 return _set_db_config (notmuch, item, argc, argv);
605 if (_item_split (item, &group, &key))
608 /* With only the name of an item, we clear it from the
609 * configuration file.
611 * With a single value, we set it as a string.
613 * With multiple values, we set them as a string list.
617 g_key_file_remove_key (config->key_file, group, key, NULL);
620 g_key_file_set_string (config->key_file, group, key, argv[0]);
623 g_key_file_set_string_list (config->key_file, group, key,
624 (const gchar **) argv, argc);
628 return notmuch_config_save (config);
633 _notmuch_config_list_built_with ()
635 printf ("%scompact=%s\n",
637 notmuch_built_with ("compact") ? "true" : "false");
638 printf ("%sfield_processor=%s\n",
640 notmuch_built_with ("field_processor") ? "true" : "false");
641 printf ("%sretry_lock=%s\n",
643 notmuch_built_with ("retry_lock") ? "true" : "false");
647 notmuch_config_command_list (notmuch_database_t *notmuch)
649 notmuch_config_pairs_t *list;
651 _notmuch_config_list_built_with ();
652 for (list = notmuch_config_get_pairs (notmuch, "");
653 notmuch_config_pairs_valid (list);
654 notmuch_config_pairs_move_to_next (list)) {
655 const char *value = notmuch_config_pairs_value (list);
657 printf ("%s=%s\n", notmuch_config_pairs_key (list), value);
659 notmuch_config_pairs_destroy (list);
664 notmuch_config_command (notmuch_config_t *config, notmuch_database_t *notmuch,
665 int argc, char *argv[])
670 opt_index = notmuch_minimal_options ("config", argc, argv);
674 if (notmuch_requested_db_uuid)
675 fprintf (stderr, "Warning: ignoring --uuid=%s\n",
676 notmuch_requested_db_uuid);
678 /* skip at least subcommand argument */
683 fprintf (stderr, "Error: notmuch config requires at least one argument.\n");
687 if (strcmp (argv[0], "get") == 0) {
689 fprintf (stderr, "Error: notmuch config get requires exactly "
693 ret = notmuch_config_command_get (notmuch, argv[1]);
694 } else if (strcmp (argv[0], "set") == 0) {
696 fprintf (stderr, "Error: notmuch config set requires at least "
700 ret = notmuch_config_command_set (config, notmuch, argv[1], argc - 2, argv + 2);
701 } else if (strcmp (argv[0], "list") == 0) {
702 ret = notmuch_config_command_list (notmuch);
704 fprintf (stderr, "Unrecognized argument for notmuch config: %s\n",
709 return ret ? EXIT_FAILURE : EXIT_SUCCESS;
714 notmuch_config_set_maildir_synchronize_flags (notmuch_config_t *config,
715 bool synchronize_flags)
717 g_key_file_set_boolean (config->key_file,
718 "maildir", "synchronize_flags", synchronize_flags);