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);
281 for (size_t i = 0; i < ARRAY_SIZE (group_comment_table); i++) {
282 const char *name = group_comment_table[i].group_name;
283 if (! g_key_file_has_group (config->key_file, name)) {
284 /* Force group to exist before adding comment */
285 g_key_file_set_value (config->key_file, name, "dummy_key", "dummy_val");
286 g_key_file_remove_key (config->key_file, name, "dummy_key", NULL);
287 if (config->is_new && (i == 0) ) {
290 comment = talloc_asprintf (config, "%s\n%s",
291 toplevel_config_comment,
292 group_comment_table[i].comment);
293 g_key_file_set_comment (config->key_file, name, NULL, comment,
296 g_key_file_set_comment (config->key_file, name, NULL,
297 group_comment_table[i].comment, NULL);
304 /* Close the given notmuch_conffile_t object, freeing all resources.
306 * Note: Any changes made to the configuration are *not* saved by this
307 * function. To save changes, call notmuch_conffile_save before
308 * notmuch_conffile_close.
311 notmuch_conffile_close (notmuch_conffile_t *config)
313 talloc_free (config);
316 /* Save any changes made to the notmuch configuration.
318 * Any comments originally in the file will be preserved.
320 * Returns 0 if successful, and 1 in case of any error, (after
321 * printing a description of the error to stderr).
324 notmuch_conffile_save (notmuch_conffile_t *config)
327 char *data, *filename;
328 GError *error = NULL;
330 data = g_key_file_to_data (config->key_file, &length, NULL);
332 fprintf (stderr, "Out of memory.\n");
336 /* Try not to overwrite symlinks. */
337 filename = notmuch_canonicalize_file_name (config->filename);
339 if (errno == ENOENT) {
340 filename = strdup (config->filename);
342 fprintf (stderr, "Out of memory.\n");
347 fprintf (stderr, "Error canonicalizing %s: %s\n", config->filename,
354 if (! g_file_set_contents (filename, data, length, &error)) {
355 if (strcmp (filename, config->filename) != 0) {
356 fprintf (stderr, "Error saving configuration to %s (-> %s): %s\n",
357 config->filename, filename, error->message);
359 fprintf (stderr, "Error saving configuration to %s: %s\n",
360 filename, error->message);
362 g_error_free (error);
374 notmuch_conffile_is_new (notmuch_conffile_t *config)
376 return config->is_new;
380 _config_set (notmuch_conffile_t *config,
381 const char *group, const char *key, const char *value)
383 g_key_file_set_string (config->key_file, group, key, value);
387 _config_set_list (notmuch_conffile_t *config,
388 const char *group, const char *key,
393 g_key_file_set_string_list (config->key_file, group, key, list, length);
395 g_key_file_set_string (config->key_file, group, key, list[0]);
399 notmuch_conffile_set_database_path (notmuch_conffile_t *config,
400 const char *database_path)
402 _config_set (config, "database", "path", database_path);
406 notmuch_conffile_set_user_name (notmuch_conffile_t *config,
407 const char *user_name)
409 _config_set (config, "user", "name", user_name);
413 notmuch_conffile_set_user_primary_email (notmuch_conffile_t *config,
414 const char *primary_email)
416 _config_set (config, "user", "primary_email", primary_email);
420 notmuch_conffile_set_user_other_email (notmuch_conffile_t *config,
424 _config_set_list (config, "user", "other_email", list, length);
428 notmuch_conffile_set_new_tags (notmuch_conffile_t *config,
432 _config_set_list (config, "new", "tags", list, length);
436 notmuch_conffile_set_new_ignore (notmuch_conffile_t *config,
440 _config_set_list (config, "new", "ignore", list, length);
444 notmuch_conffile_set_search_exclude_tags (notmuch_conffile_t *config,
448 _config_set_list (config, "search", "exclude_tags", list, length);
452 /* Given a configuration item of the form <group>.<key> return the
453 * component group and key. If any error occurs, print a message on
454 * stderr and return 1. Otherwise, return 0.
456 * Note: This function modifies the original 'item' string.
459 _item_split (char *item, char **group, char **key)
465 period = strchr (item, '.');
466 if (period == NULL || *(period + 1) == '\0') {
468 "Invalid configuration name: %s\n"
469 "(Should be of the form <section>.<item>)\n", item);
479 /* These are more properly called Xapian fields, but the user facing
480 * docs call them prefixes, so make the error message match */
482 validate_field_name (const char *str)
486 if (! g_utf8_validate (str, -1, NULL)) {
487 fprintf (stderr, "Invalid utf8: %s\n", str);
491 key = g_utf8_strrchr (str, -1, '.');
493 INTERNAL_ERROR ("Impossible code path on input: %s\n", str);
499 fprintf (stderr, "Empty prefix name: %s\n", str);
503 if (! unicode_word_utf8 (key)) {
504 fprintf (stderr, "Non-word character in prefix name: %s\n", key);
508 if (key[0] >= 'a' && key[0] <= 'z') {
509 fprintf (stderr, "Prefix names starting with lower case letters are reserved: %s\n", key);
516 #define BUILT_WITH_PREFIX "built_with."
518 typedef struct config_key {
521 bool (*validate)(const char *);
524 static const struct config_key
525 config_key_table[] = {
526 { "index.decrypt", false, NULL },
527 { "index.header.", true, validate_field_name },
528 { "query.", true, NULL },
529 { "squery.", true, validate_field_name },
532 static const config_key_info_t *
533 _config_key_info (const char *item)
535 for (size_t i = 0; i < ARRAY_SIZE (config_key_table); i++) {
536 if (config_key_table[i].prefix &&
537 strncmp (item, config_key_table[i].name,
538 strlen (config_key_table[i].name)) == 0)
539 return config_key_table + i;
540 if (strcmp (item, config_key_table[i].name) == 0)
541 return config_key_table + i;
547 notmuch_config_command_get (notmuch_database_t *notmuch, char *item)
549 notmuch_config_values_t *list;
551 if (STRNCMP_LITERAL (item, BUILT_WITH_PREFIX) == 0) {
552 if (notmuch_built_with (item + strlen (BUILT_WITH_PREFIX)))
557 for (list = notmuch_config_get_values_string (notmuch, item);
558 notmuch_config_values_valid (list);
559 notmuch_config_values_move_to_next (list)) {
560 const char *val = notmuch_config_values_get (list);
568 _set_db_config (notmuch_database_t *notmuch, const char *key, int argc, char **argv)
570 const char *val = "";
573 /* XXX handle lists? */
574 fprintf (stderr, "notmuch config set: at most one value expected for %s\n", key);
582 if (print_status_database ("notmuch config", notmuch,
583 notmuch_database_reopen (notmuch,
584 NOTMUCH_DATABASE_MODE_READ_WRITE)))
587 if (print_status_database ("notmuch config", notmuch,
588 notmuch_database_set_config (notmuch, key, val)))
591 if (print_status_database ("notmuch config", notmuch,
592 notmuch_database_close (notmuch)))
599 notmuch_config_command_set (notmuch_database_t *notmuch,
600 int argc, char *argv[])
603 const config_key_info_t *key_info;
604 notmuch_conffile_t *config;
605 bool update_database = false;
609 notmuch_opt_desc_t options[] = {
610 { .opt_bool = &update_database, .name = "database" },
614 opt_index = parse_arguments (argc, argv, options, 1);
622 fprintf (stderr, "Error: notmuch config set requires at least "
631 if (STRNCMP_LITERAL (item, BUILT_WITH_PREFIX) == 0) {
632 fprintf (stderr, "Error: read only option: %s\n", item);
636 key_info = _config_key_info (item);
637 if (key_info && key_info->validate && (! key_info->validate (item)))
640 if (update_database) {
641 return _set_db_config (notmuch, item, argc, argv);
644 if (_item_split (item, &group, &key))
647 config = notmuch_conffile_open (notmuch,
648 notmuch_config_path (notmuch), false);
652 /* With only the name of an item, we clear it from the
653 * configuration file.
655 * With a single value, we set it as a string.
657 * With multiple values, we set them as a string list.
661 g_key_file_remove_key (config->key_file, group, key, NULL);
664 g_key_file_set_string (config->key_file, group, key, argv[0]);
667 g_key_file_set_string_list (config->key_file, group, key,
668 (const gchar **) argv, argc);
672 ret = notmuch_conffile_save (config);
674 notmuch_conffile_close (config);
681 _notmuch_config_list_built_with ()
683 printf ("%scompact=%s\n",
685 notmuch_built_with ("compact") ? "true" : "false");
686 printf ("%sfield_processor=%s\n",
688 notmuch_built_with ("field_processor") ? "true" : "false");
689 printf ("%sretry_lock=%s\n",
691 notmuch_built_with ("retry_lock") ? "true" : "false");
692 printf ("%ssexp_queries=%s\n",
694 notmuch_built_with ("sexp_queries") ? "true" : "false");
698 notmuch_config_command_list (notmuch_database_t *notmuch)
700 notmuch_config_pairs_t *list;
702 _notmuch_config_list_built_with ();
703 for (list = notmuch_config_get_pairs (notmuch, "");
704 notmuch_config_pairs_valid (list);
705 notmuch_config_pairs_move_to_next (list)) {
706 const char *value = notmuch_config_pairs_value (list);
708 printf ("%s=%s\n", notmuch_config_pairs_key (list), value);
710 notmuch_config_pairs_destroy (list);
715 notmuch_config_command (notmuch_database_t *notmuch, int argc, char *argv[])
720 opt_index = notmuch_minimal_options ("config", argc, argv);
724 /* skip at least subcommand argument */
729 fprintf (stderr, "Error: notmuch config requires at least one argument.\n");
733 if (strcmp (argv[0], "get") == 0) {
735 fprintf (stderr, "Error: notmuch config get requires exactly "
739 ret = notmuch_config_command_get (notmuch, argv[1]);
740 } else if (strcmp (argv[0], "set") == 0) {
741 ret = notmuch_config_command_set (notmuch, argc, argv);
742 } else if (strcmp (argv[0], "list") == 0) {
743 ret = notmuch_config_command_list (notmuch);
745 fprintf (stderr, "Unrecognized argument for notmuch config: %s\n",
750 return ret ? EXIT_FAILURE : EXIT_SUCCESS;
755 notmuch_conffile_set_maildir_synchronize_flags (notmuch_conffile_t *config,
756 bool synchronize_flags)
758 g_key_file_set_boolean (config->key_file,
759 "maildir", "synchronize_flags", synchronize_flags);