X-Git-Url: https://git.notmuchmail.org/git?p=notmuch;a=blobdiff_plain;f=lib%2Fconfig.cc;h=292f0288d7ef2f95e171efe9bf63b4bd8fe42c64;hp=da71c16e5adcea711bfa7ed6eab264caf7379a78;hb=HEAD;hpb=040c3236afcf95bead0324a48c2e0b9cd7934993 diff --git a/lib/config.cc b/lib/config.cc index da71c16e..6cd15fab 100644 --- a/lib/config.cc +++ b/lib/config.cc @@ -22,6 +22,9 @@ #include "notmuch-private.h" #include "database-private.h" +#include +#include + static const std::string CONFIG_PREFIX = "C"; struct _notmuch_config_list { @@ -31,6 +34,20 @@ struct _notmuch_config_list { char *current_val; }; +struct _notmuch_config_values { + const char *iterator; + size_t tok_len; + const char *string; + void *children; /* talloc_context */ +}; + +struct _notmuch_config_pairs { + notmuch_string_map_iterator_t *iter; +}; + +static const char *_notmuch_config_key_to_string (notmuch_config_key_t key); +static char *_expand_path (void *ctx, const char *key, const char *val); + static int _notmuch_config_list_destroy (notmuch_config_list_t *list) { @@ -45,21 +62,30 @@ notmuch_database_set_config (notmuch_database_t *notmuch, const char *value) { notmuch_status_t status; - Xapian::WritableDatabase *db; status = _notmuch_database_ensure_writable (notmuch); if (status) return status; + if (! notmuch->config) { + if ((status = _notmuch_config_load_from_database (notmuch))) + return status; + } + try { - db = static_cast (notmuch->xapian_db); - db->set_metadata (CONFIG_PREFIX + key, value); + notmuch->writable_xapian_db->set_metadata (CONFIG_PREFIX + key, value); } catch (const Xapian::Error &error) { status = NOTMUCH_STATUS_XAPIAN_EXCEPTION; notmuch->exception_reported = true; _notmuch_database_log (notmuch, "Error: A Xapian exception occurred setting metadata: %s\n", - error.get_msg().c_str()); + error.get_msg ().c_str ()); } + + if (status) + return status; + + _notmuch_string_map_set (notmuch->config, key, value); + return NOTMUCH_STATUS_SUCCESS; } @@ -76,7 +102,7 @@ _metadata_value (notmuch_database_t *notmuch, status = NOTMUCH_STATUS_XAPIAN_EXCEPTION; notmuch->exception_reported = true; _notmuch_database_log (notmuch, "Error: A Xapian exception occurred getting metadata: %s\n", - error.get_msg().c_str()); + error.get_msg ().c_str ()); } return status; } @@ -86,17 +112,25 @@ notmuch_database_get_config (notmuch_database_t *notmuch, const char *key, char **value) { - std::string strval; + const char *stored_val; notmuch_status_t status; + if (! notmuch->config) { + if ((status = _notmuch_config_load_from_database (notmuch))) + return status; + } + if (! value) return NOTMUCH_STATUS_NULL_POINTER; - status = _metadata_value (notmuch, key, strval); - if (status) - return status; - - *value = strdup (strval.c_str ()); + stored_val = _notmuch_string_map_get (notmuch->config, key); + if (! stored_val) { + /* XXX in principle this API should be fixed so empty string + * is distinguished from not found */ + *value = strdup (""); + } else { + *value = strdup (stored_val); + } return NOTMUCH_STATUS_SUCCESS; } @@ -115,7 +149,6 @@ notmuch_database_get_config_list (notmuch_database_t *notmuch, goto DONE; } - talloc_set_destructor (list, _notmuch_config_list_destroy); list->notmuch = notmuch; list->current_key = NULL; list->current_val = NULL; @@ -123,11 +156,13 @@ notmuch_database_get_config_list (notmuch_database_t *notmuch, try { new(&(list->iterator)) Xapian::TermIterator (notmuch->xapian_db->metadata_keys_begin - (CONFIG_PREFIX + (prefix ? prefix : ""))); + (CONFIG_PREFIX + (prefix ? prefix : ""))); + talloc_set_destructor (list, _notmuch_config_list_destroy); } catch (const Xapian::Error &error) { - _notmuch_database_log (notmuch, "A Xapian exception occurred getting metadata iterator: %s.\n", - error.get_msg().c_str()); + _notmuch_database_log (notmuch, + "A Xapian exception occurred getting metadata iterator: %s.\n", + error.get_msg ().c_str ()); notmuch->exception_reported = true; status = NOTMUCH_STATUS_XAPIAN_EXCEPTION; } @@ -135,8 +170,15 @@ notmuch_database_get_config_list (notmuch_database_t *notmuch, *out = list; DONE: - if (status && list) - talloc_free (list); + if (status) { + if (list) { + talloc_free (list); + if (status != NOTMUCH_STATUS_XAPIAN_EXCEPTION) + _notmuch_config_list_destroy (list); + } + } else { + talloc_set_destructor (list, _notmuch_config_list_destroy); + } return status; } @@ -150,13 +192,19 @@ notmuch_config_list_valid (notmuch_config_list_t *metadata) return true; } +static inline char * +_key_from_iterator (notmuch_config_list_t *list) +{ + return talloc_strdup (list, (*list->iterator).c_str () + CONFIG_PREFIX.length ()); +} + const char * notmuch_config_list_key (notmuch_config_list_t *list) { if (list->current_key) talloc_free (list->current_key); - list->current_key = talloc_strdup (list, (*list->iterator).c_str () + CONFIG_PREFIX.length ()); + list->current_key = _key_from_iterator (list); return list->current_key; } @@ -166,7 +214,7 @@ notmuch_config_list_value (notmuch_config_list_t *list) { std::string strval; notmuch_status_t status; - const char *key = notmuch_config_list_key (list); + char *key = _key_from_iterator (list); /* TODO: better error reporting?? */ status = _metadata_value (list->notmuch, key, strval); @@ -177,6 +225,7 @@ notmuch_config_list_value (notmuch_config_list_t *list) talloc_free (list->current_val); list->current_val = talloc_strdup (list, strval.c_str ()); + talloc_free (key); return list->current_val; } @@ -191,3 +240,487 @@ notmuch_config_list_destroy (notmuch_config_list_t *list) { talloc_free (list); } + +notmuch_status_t +_notmuch_config_load_from_database (notmuch_database_t *notmuch) +{ + notmuch_status_t status = NOTMUCH_STATUS_SUCCESS; + notmuch_config_list_t *list; + + if (notmuch->config == NULL) + notmuch->config = _notmuch_string_map_create (notmuch); + + if (unlikely (notmuch->config == NULL)) + return NOTMUCH_STATUS_OUT_OF_MEMORY; + + status = notmuch_database_get_config_list (notmuch, "", &list); + if (status) + return status; + + for (; notmuch_config_list_valid (list); notmuch_config_list_move_to_next (list)) { + const char *key = notmuch_config_list_key (list); + char *normalized_val = NULL; + + /* If we opened from a given path, do not overwrite it */ + if (strcmp (key, "database.path") == 0 && + (notmuch->params & NOTMUCH_PARAM_DATABASE) && + notmuch->xapian_db) + continue; + + normalized_val = _expand_path (list, key, notmuch_config_list_value (list)); + _notmuch_string_map_append (notmuch->config, key, normalized_val); + talloc_free (normalized_val); + } + + return status; +} + +notmuch_config_values_t * +notmuch_config_get_values (notmuch_database_t *notmuch, notmuch_config_key_t key) +{ + const char *key_str = _notmuch_config_key_to_string (key); + + if (! key_str) + return NULL; + + return notmuch_config_get_values_string (notmuch, key_str); +} + +notmuch_config_values_t * +notmuch_config_get_values_string (notmuch_database_t *notmuch, const char *key_str) +{ + notmuch_config_values_t *values = NULL; + bool ok = false; + + values = talloc (notmuch, notmuch_config_values_t); + if (unlikely (! values)) + goto DONE; + + values->children = talloc_new (values); + + values->string = _notmuch_string_map_get (notmuch->config, key_str); + if (! values->string) + goto DONE; + + values->iterator = strsplit_len (values->string, ';', &(values->tok_len)); + ok = true; + + DONE: + if (! ok) { + if (values) + talloc_free (values); + return NULL; + } + return values; +} + +notmuch_bool_t +notmuch_config_values_valid (notmuch_config_values_t *values) +{ + if (! values) + return false; + + return (values->iterator != NULL); +} + +const char * +notmuch_config_values_get (notmuch_config_values_t *values) +{ + return talloc_strndup (values->children, values->iterator, values->tok_len); +} + +void +notmuch_config_values_start (notmuch_config_values_t *values) +{ + if (values == NULL) + return; + if (values->children) { + talloc_free (values->children); + } + + values->children = talloc_new (values); + + values->iterator = strsplit_len (values->string, ';', &(values->tok_len)); +} + +void +notmuch_config_values_move_to_next (notmuch_config_values_t *values) +{ + values->iterator += values->tok_len; + values->iterator = strsplit_len (values->iterator, ';', &(values->tok_len)); +} + +void +notmuch_config_values_destroy (notmuch_config_values_t *values) +{ + talloc_free (values); +} + +notmuch_config_pairs_t * +notmuch_config_get_pairs (notmuch_database_t *notmuch, + const char *prefix) +{ + notmuch_config_pairs_t *pairs = talloc (notmuch, notmuch_config_pairs_t); + + pairs->iter = _notmuch_string_map_iterator_create (notmuch->config, prefix, false); + return pairs; +} + +notmuch_bool_t +notmuch_config_pairs_valid (notmuch_config_pairs_t *pairs) +{ + return _notmuch_string_map_iterator_valid (pairs->iter); +} + +void +notmuch_config_pairs_move_to_next (notmuch_config_pairs_t *pairs) +{ + _notmuch_string_map_iterator_move_to_next (pairs->iter); +} + +const char * +notmuch_config_pairs_key (notmuch_config_pairs_t *pairs) +{ + return _notmuch_string_map_iterator_key (pairs->iter); +} + +const char * +notmuch_config_pairs_value (notmuch_config_pairs_t *pairs) +{ + return _notmuch_string_map_iterator_value (pairs->iter); +} + +void +notmuch_config_pairs_destroy (notmuch_config_pairs_t *pairs) +{ + _notmuch_string_map_iterator_destroy (pairs->iter); + talloc_free (pairs); +} + +static char * +_expand_path (void *ctx, const char *key, const char *val) +{ + char *expanded_val; + + if ((strcmp (key, "database.path") == 0 || + strcmp (key, "database.mail_root") == 0 || + strcmp (key, "database.hook_dir") == 0 || + strcmp (key, "database.backup_path") == 0 ) && + val[0] != '/') + expanded_val = talloc_asprintf (ctx, "%s/%s", getenv ("HOME"), val); + else + expanded_val = talloc_strdup (ctx, val); + + return expanded_val; +} + +notmuch_status_t +_notmuch_config_load_from_file (notmuch_database_t *notmuch, + GKeyFile *file, + char **status_string) +{ + notmuch_status_t status = NOTMUCH_STATUS_SUCCESS; + gchar **groups = NULL, **keys, *val; + + if (notmuch->config == NULL) + notmuch->config = _notmuch_string_map_create (notmuch); + + if (unlikely (notmuch->config == NULL)) { + status = NOTMUCH_STATUS_OUT_OF_MEMORY; + goto DONE; + } + + groups = g_key_file_get_groups (file, NULL); + for (gchar **grp = groups; *grp; grp++) { + keys = g_key_file_get_keys (file, *grp, NULL, NULL); + for (gchar **keys_p = keys; *keys_p; keys_p++) { + char *absolute_key = talloc_asprintf (notmuch, "%s.%s", *grp, *keys_p); + char *normalized_val; + GError *gerr = NULL; + + /* If we opened from a given path, do not overwrite it */ + if (strcmp (absolute_key, "database.path") == 0 && + (notmuch->params & NOTMUCH_PARAM_DATABASE) && + notmuch->xapian_db) + continue; + + val = g_key_file_get_string (file, *grp, *keys_p, &gerr); + if (gerr) { + if (status_string) + IGNORE_RESULT (asprintf (status_string, + "GLib: %s\n", + gerr->message)); + g_error_free (gerr); + } + if (! val) { + status = NOTMUCH_STATUS_FILE_ERROR; + goto DONE; + } + + normalized_val = _expand_path (notmuch, absolute_key, val); + _notmuch_string_map_set (notmuch->config, absolute_key, normalized_val); + g_free (val); + talloc_free (absolute_key); + talloc_free (normalized_val); + if (status) + goto DONE; + } + g_strfreev (keys); + } + + DONE: + if (groups) + g_strfreev (groups); + + return status; +} + +notmuch_status_t +notmuch_config_get_bool (notmuch_database_t *notmuch, notmuch_config_key_t key, notmuch_bool_t *val) +{ + const char *key_string, *val_string; + + key_string = _notmuch_config_key_to_string (key); + if (! key_string) { + return NOTMUCH_STATUS_ILLEGAL_ARGUMENT; + } + + val_string = _notmuch_string_map_get (notmuch->config, key_string); + if (! val_string) { + *val = FALSE; + return NOTMUCH_STATUS_SUCCESS; + } + + if (strcase_equal (val_string, "false") || strcase_equal (val_string, "no")) + *val = FALSE; + else if (strcase_equal (val_string, "true") || strcase_equal (val_string, "yes")) + *val = TRUE; + else + return NOTMUCH_STATUS_ILLEGAL_ARGUMENT; + + return NOTMUCH_STATUS_SUCCESS; +} + +static const char * +_get_name_from_passwd_file (void *ctx) +{ + long pw_buf_size; + char *pw_buf; + struct passwd passwd, *ignored; + const char *name; + int e; + + pw_buf_size = sysconf (_SC_GETPW_R_SIZE_MAX); + if (pw_buf_size == -1) pw_buf_size = 64; + pw_buf = (char *) talloc_size (ctx, pw_buf_size); + + while ((e = getpwuid_r (getuid (), &passwd, pw_buf, + pw_buf_size, &ignored)) == ERANGE) { + pw_buf_size = pw_buf_size * 2; + pw_buf = (char *) talloc_zero_size (ctx, pw_buf_size); + } + + if (e == 0) { + char *comma = strchr (passwd.pw_gecos, ','); + if (comma) + name = talloc_strndup (ctx, passwd.pw_gecos, + comma - passwd.pw_gecos); + else + name = talloc_strdup (ctx, passwd.pw_gecos); + } else { + name = talloc_strdup (ctx, ""); + } + + talloc_free (pw_buf); + + return name; +} + +static char * +_get_username_from_passwd_file (void *ctx) +{ + long pw_buf_size; + char *pw_buf; + struct passwd passwd, *ignored; + char *name; + int e; + + pw_buf_size = sysconf (_SC_GETPW_R_SIZE_MAX); + if (pw_buf_size == -1) pw_buf_size = 64; + pw_buf = (char *) talloc_zero_size (ctx, pw_buf_size); + + while ((e = getpwuid_r (getuid (), &passwd, pw_buf, + pw_buf_size, &ignored)) == ERANGE) { + pw_buf_size = pw_buf_size * 2; + pw_buf = (char *) talloc_zero_size (ctx, pw_buf_size); + } + + if (e == 0) + name = talloc_strdup (ctx, passwd.pw_name); + else + name = talloc_strdup (ctx, ""); + + talloc_free (pw_buf); + + return name; +} + +static const char * +_get_email_from_passwd_file (void *ctx) +{ + char *email; + + char *username = _get_username_from_passwd_file (ctx); + + email = talloc_asprintf (ctx, "%s@localhost", username); + + talloc_free (username); + return email; +} + +static const char * +_notmuch_config_key_to_string (notmuch_config_key_t key) +{ + switch (key) { + case NOTMUCH_CONFIG_DATABASE_PATH: + return "database.path"; + case NOTMUCH_CONFIG_MAIL_ROOT: + return "database.mail_root"; + case NOTMUCH_CONFIG_HOOK_DIR: + return "database.hook_dir"; + case NOTMUCH_CONFIG_BACKUP_DIR: + return "database.backup_dir"; + case NOTMUCH_CONFIG_EXCLUDE_TAGS: + return "search.exclude_tags"; + case NOTMUCH_CONFIG_NEW_TAGS: + return "new.tags"; + case NOTMUCH_CONFIG_NEW_IGNORE: + return "new.ignore"; + case NOTMUCH_CONFIG_SYNC_MAILDIR_FLAGS: + return "maildir.synchronize_flags"; + case NOTMUCH_CONFIG_PRIMARY_EMAIL: + return "user.primary_email"; + case NOTMUCH_CONFIG_OTHER_EMAIL: + return "user.other_email"; + case NOTMUCH_CONFIG_USER_NAME: + return "user.name"; + case NOTMUCH_CONFIG_AUTOCOMMIT: + return "database.autocommit"; + case NOTMUCH_CONFIG_EXTRA_HEADERS: + return "show.extra_headers"; + case NOTMUCH_CONFIG_INDEX_AS_TEXT: + return "index.as_text"; + default: + return NULL; + } +} + +static const char * +_notmuch_config_default (notmuch_database_t *notmuch, notmuch_config_key_t key) +{ + char *path; + const char *name, *email; + + switch (key) { + case NOTMUCH_CONFIG_DATABASE_PATH: + path = getenv ("MAILDIR"); + if (path) + path = talloc_strdup (notmuch, path); + else + path = talloc_asprintf (notmuch, "%s/mail", + getenv ("HOME")); + return path; + case NOTMUCH_CONFIG_MAIL_ROOT: + /* by default, mail root is the same as database path */ + return notmuch_database_get_path (notmuch); + case NOTMUCH_CONFIG_EXCLUDE_TAGS: + return ""; + case NOTMUCH_CONFIG_NEW_TAGS: + return "unread;inbox"; + case NOTMUCH_CONFIG_SYNC_MAILDIR_FLAGS: + return "true"; + case NOTMUCH_CONFIG_USER_NAME: + name = getenv ("NAME"); + if (name) + name = talloc_strdup (notmuch, name); + else + name = _get_name_from_passwd_file (notmuch); + return name; + case NOTMUCH_CONFIG_PRIMARY_EMAIL: + email = getenv ("EMAIL"); + if (email) + email = talloc_strdup (notmuch, email); + else + email = _get_email_from_passwd_file (notmuch); + return email; + case NOTMUCH_CONFIG_INDEX_AS_TEXT: + case NOTMUCH_CONFIG_NEW_IGNORE: + return ""; + case NOTMUCH_CONFIG_AUTOCOMMIT: + return "8000"; + case NOTMUCH_CONFIG_EXTRA_HEADERS: + case NOTMUCH_CONFIG_HOOK_DIR: + case NOTMUCH_CONFIG_BACKUP_DIR: + case NOTMUCH_CONFIG_OTHER_EMAIL: + return NULL; + default: + case NOTMUCH_CONFIG_LAST: + INTERNAL_ERROR ("illegal key enum %d", key); + } +} + +notmuch_status_t +_notmuch_config_load_defaults (notmuch_database_t *notmuch) +{ + notmuch_config_key_t key; + notmuch_status_t status = NOTMUCH_STATUS_SUCCESS; + + if (notmuch->config == NULL) + notmuch->config = _notmuch_string_map_create (notmuch); + + for (key = NOTMUCH_CONFIG_FIRST; + key < NOTMUCH_CONFIG_LAST; + key = notmuch_config_key_t (key + 1)) { + const char *val = notmuch_config_get (notmuch, key); + const char *key_string = _notmuch_config_key_to_string (key); + + val = _notmuch_string_map_get (notmuch->config, key_string); + if (! val) { + if (key == NOTMUCH_CONFIG_MAIL_ROOT && (notmuch->params & NOTMUCH_PARAM_SPLIT)) + status = NOTMUCH_STATUS_NO_MAIL_ROOT; + + _notmuch_string_map_set (notmuch->config, key_string, _notmuch_config_default (notmuch, + key)); + } + } + return status; +} + +const char * +notmuch_config_get (notmuch_database_t *notmuch, notmuch_config_key_t key) +{ + + return _notmuch_string_map_get (notmuch->config, _notmuch_config_key_to_string (key)); +} + +const char * +notmuch_config_path (notmuch_database_t *notmuch) +{ + return notmuch->config_path; +} + +notmuch_status_t +notmuch_config_set (notmuch_database_t *notmuch, notmuch_config_key_t key, const char *val) +{ + + return notmuch_database_set_config (notmuch, _notmuch_config_key_to_string (key), val); +} + +void +_notmuch_config_cache (notmuch_database_t *notmuch, notmuch_config_key_t key, const char *val) +{ + if (notmuch->config == NULL) + notmuch->config = _notmuch_string_map_create (notmuch); + + _notmuch_string_map_set (notmuch->config, _notmuch_config_key_to_string (key), val); +}