notmuch/lib/open.cc
David Bremner e2a3e5fa51 lib: autocommit after some number of completed transactions
This change addresses two known issues with large sets of changes to
the database.  The first is that as reported by Steven Allen [1],
notmuch commits are not "flushed" when they complete, which means that
if there is an open transaction when the database closes (or e.g. the
program crashes) then all changes since the last commit will be
discarded (nothing is irrecoverably lost for "notmuch new", as the
indexing process just restarts next time it is run).  This does not
really "fix" the issue reported in [1]; that seems rather difficult
given how transactions work in Xapian. On the other hand, with the
default settings, this should mean one only loses less than a minutes
worth of work.  The second issue is the occasionally reported "storm"
of disk writes when notmuch finishes. I don't yet have a test for
this, but I think committing as we go should reduce the amount of work
when finalizing the database.

[1]: id:20151025210215.GA3754@stebalien.com
2021-06-27 14:03:00 -03:00

873 lines
22 KiB
C++

#include <unistd.h>
#include <libgen.h>
#include "database-private.h"
#include "parse-time-vrp.h"
#include "path-util.h"
#if HAVE_XAPIAN_DB_RETRY_LOCK
#define DB_ACTION (Xapian::DB_CREATE_OR_OPEN | Xapian::DB_RETRY_LOCK)
#else
#define DB_ACTION Xapian::DB_CREATE_OR_OPEN
#endif
notmuch_status_t
notmuch_database_open (const char *path,
notmuch_database_mode_t mode,
notmuch_database_t **database)
{
char *status_string = NULL;
notmuch_status_t status;
status = notmuch_database_open_verbose (path, mode, database,
&status_string);
if (status_string) {
fputs (status_string, stderr);
free (status_string);
}
return status;
}
notmuch_status_t
notmuch_database_open_verbose (const char *path,
notmuch_database_mode_t mode,
notmuch_database_t **database,
char **status_string)
{
return notmuch_database_open_with_config (path, mode, "", NULL,
database, status_string);
}
static const char *
_xdg_dir (void *ctx,
const char *xdg_root_variable,
const char *xdg_prefix,
const char *profile_name)
{
const char *xdg_root = getenv (xdg_root_variable);
if (! xdg_root) {
const char *home = getenv ("HOME");
if (! home) return NULL;
xdg_root = talloc_asprintf (ctx,
"%s/%s",
home,
xdg_prefix);
}
if (! profile_name)
profile_name = getenv ("NOTMUCH_PROFILE");
if (! profile_name)
profile_name = "default";
return talloc_asprintf (ctx,
"%s/notmuch/%s",
xdg_root,
profile_name);
}
static notmuch_status_t
_choose_dir (notmuch_database_t *notmuch,
const char *profile,
notmuch_config_key_t key,
const char *xdg_var,
const char *xdg_subdir,
const char *subdir,
char **message = NULL)
{
const char *parent;
const char *dir;
struct stat st;
int err;
dir = notmuch_config_get (notmuch, key);
if (dir)
return NOTMUCH_STATUS_SUCCESS;
parent = _xdg_dir (notmuch, xdg_var, xdg_subdir, profile);
if (! parent)
return NOTMUCH_STATUS_PATH_ERROR;
dir = talloc_asprintf (notmuch, "%s/%s", parent, subdir);
err = stat (dir, &st);
if (err) {
if (errno == ENOENT) {
char *notmuch_path = dirname (talloc_strdup (notmuch, notmuch->xapian_path));
dir = talloc_asprintf (notmuch, "%s/%s", notmuch_path, subdir);
} else {
IGNORE_RESULT (asprintf (message, "Error: Cannot stat %s: %s.\n",
dir, strerror (errno)));
return NOTMUCH_STATUS_FILE_ERROR;
}
}
_notmuch_config_cache (notmuch, key, dir);
return NOTMUCH_STATUS_SUCCESS;
}
static notmuch_status_t
_load_key_file (notmuch_database_t *notmuch,
const char *path,
const char *profile,
GKeyFile **key_file)
{
notmuch_status_t status = NOTMUCH_STATUS_SUCCESS;
if (path && EMPTY_STRING (path))
goto DONE;
if (! path)
path = getenv ("NOTMUCH_CONFIG");
if (path)
path = talloc_strdup (notmuch, path);
else {
const char *dir = _xdg_dir (notmuch, "XDG_CONFIG_HOME", ".config", profile);
if (dir) {
path = talloc_asprintf (notmuch, "%s/config", dir);
if (access (path, R_OK) != 0)
path = NULL;
}
}
if (! path) {
const char *home = getenv ("HOME");
path = talloc_asprintf (notmuch, "%s/.notmuch-config", home);
if (! profile)
profile = getenv ("NOTMUCH_PROFILE");
if (profile)
path = talloc_asprintf (notmuch, "%s.%s", path, profile);
}
*key_file = g_key_file_new ();
if (! g_key_file_load_from_file (*key_file, path, G_KEY_FILE_NONE, NULL)) {
status = NOTMUCH_STATUS_NO_CONFIG;
}
DONE:
if (path)
notmuch->config_path = path;
return status;
}
static notmuch_status_t
_db_dir_exists (const char *database_path, char **message)
{
struct stat st;
int err;
err = stat (database_path, &st);
if (err) {
IGNORE_RESULT (asprintf (message, "Error: Cannot open database at %s: %s.\n",
database_path, strerror (errno)));
return NOTMUCH_STATUS_FILE_ERROR;
}
if (! S_ISDIR (st.st_mode)) {
IGNORE_RESULT (asprintf (message, "Error: Cannot open database at %s: "
"Not a directory.\n",
database_path));
return NOTMUCH_STATUS_FILE_ERROR;
}
return NOTMUCH_STATUS_SUCCESS;
}
static notmuch_status_t
_choose_database_path (void *ctx,
const char *profile,
GKeyFile *key_file,
const char **database_path,
bool *split,
char **message)
{
if (! *database_path) {
*database_path = getenv ("NOTMUCH_DATABASE");
}
if (! *database_path && key_file) {
char *path = g_key_file_get_value (key_file, "database", "path", NULL);
if (path) {
if (path[0] == '/')
*database_path = talloc_strdup (ctx, path);
else
*database_path = talloc_asprintf (ctx, "%s/%s", getenv ("HOME"), path);
g_free (path);
}
}
if (! *database_path) {
notmuch_status_t status;
*database_path = _xdg_dir (ctx, "XDG_DATA_HOME", ".local/share", profile);
status = _db_dir_exists (*database_path, message);
if (status) {
*database_path = NULL;
} else {
*split = true;
}
}
if (! *database_path) {
notmuch_status_t status;
*database_path = talloc_asprintf (ctx, "%s/mail", getenv ("HOME"));
status = _db_dir_exists (*database_path, message);
if (status) {
*database_path = NULL;
}
}
if (*database_path == NULL) {
*message = strdup ("Error: could not locate database.\n");
return NOTMUCH_STATUS_NO_DATABASE;
}
if (*database_path[0] != '/') {
*message = strdup ("Error: Database path must be absolute.\n");
return NOTMUCH_STATUS_PATH_ERROR;
}
return NOTMUCH_STATUS_SUCCESS;
}
static notmuch_database_t *
_alloc_notmuch ()
{
notmuch_database_t *notmuch;
notmuch = talloc_zero (NULL, notmuch_database_t);
if (! notmuch)
return NULL;
notmuch->exception_reported = false;
notmuch->status_string = NULL;
notmuch->writable_xapian_db = NULL;
notmuch->config_path = NULL;
notmuch->atomic_nesting = 0;
notmuch->transaction_count = 0;
notmuch->transaction_threshold = 0;
notmuch->view = 1;
return notmuch;
}
static notmuch_status_t
_trial_open (const char *xapian_path, char **message_ptr)
{
try {
Xapian::Database db (xapian_path);
} catch (const Xapian::DatabaseOpeningError &error) {
IGNORE_RESULT (asprintf (message_ptr,
"Cannot open Xapian database at %s: %s\n",
xapian_path,
error.get_msg ().c_str ()));
return NOTMUCH_STATUS_PATH_ERROR;
} catch (const Xapian::Error &error) {
IGNORE_RESULT (asprintf (message_ptr,
"A Xapian exception occurred opening database: %s\n",
error.get_msg ().c_str ()));
return NOTMUCH_STATUS_XAPIAN_EXCEPTION;
}
return NOTMUCH_STATUS_SUCCESS;
}
notmuch_status_t
_notmuch_choose_xapian_path (void *ctx, const char *database_path,
const char **xapian_path, char **message_ptr)
{
notmuch_status_t status;
const char *trial_path, *notmuch_path;
status = _db_dir_exists (database_path, message_ptr);
if (status)
goto DONE;
trial_path = talloc_asprintf (ctx, "%s/xapian", database_path);
status = _trial_open (trial_path, message_ptr);
if (status != NOTMUCH_STATUS_PATH_ERROR)
goto DONE;
if (*message_ptr)
free (*message_ptr);
notmuch_path = talloc_asprintf (ctx, "%s/.notmuch", database_path);
status = _db_dir_exists (notmuch_path, message_ptr);
if (status)
goto DONE;
trial_path = talloc_asprintf (ctx, "%s/xapian", notmuch_path);
status = _trial_open (trial_path, message_ptr);
DONE:
if (status == NOTMUCH_STATUS_SUCCESS)
*xapian_path = trial_path;
return status;
}
static void
_set_database_path (notmuch_database_t *notmuch,
const char *database_path)
{
char *path = talloc_strdup (notmuch, database_path);
strip_trailing (path, '/');
_notmuch_config_cache (notmuch, NOTMUCH_CONFIG_DATABASE_PATH, path);
}
static void
_load_database_state (notmuch_database_t *notmuch)
{
std::string last_thread_id;
std::string last_mod;
notmuch->last_doc_id = notmuch->xapian_db->get_lastdocid ();
last_thread_id = notmuch->xapian_db->get_metadata ("last_thread_id");
if (last_thread_id.empty ()) {
notmuch->last_thread_id = 0;
} else {
const char *str;
char *end;
str = last_thread_id.c_str ();
notmuch->last_thread_id = strtoull (str, &end, 16);
if (*end != '\0')
INTERNAL_ERROR ("Malformed database last_thread_id: %s", str);
}
/* Get current highest revision number. */
last_mod = notmuch->xapian_db->get_value_upper_bound (
NOTMUCH_VALUE_LAST_MOD);
if (last_mod.empty ())
notmuch->revision = 0;
else
notmuch->revision = Xapian::sortable_unserialise (last_mod);
notmuch->uuid = talloc_strdup (
notmuch, notmuch->xapian_db->get_uuid ().c_str ());
}
static notmuch_status_t
_finish_open (notmuch_database_t *notmuch,
const char *profile,
notmuch_database_mode_t mode,
GKeyFile *key_file,
char **message_ptr)
{
notmuch_status_t status = NOTMUCH_STATUS_SUCCESS;
char *incompat_features;
char *message = NULL;
const char *autocommit_str;
char *autocommit_end;
unsigned int version;
const char *database_path = notmuch_database_get_path (notmuch);
try {
if (mode == NOTMUCH_DATABASE_MODE_READ_WRITE) {
notmuch->writable_xapian_db = new Xapian::WritableDatabase (notmuch->xapian_path,
DB_ACTION);
notmuch->xapian_db = notmuch->writable_xapian_db;
} else {
notmuch->xapian_db = new Xapian::Database (notmuch->xapian_path);
}
/* Check version. As of database version 3, we represent
* changes in terms of features, so assume a version bump
* means a dramatically incompatible change. */
version = notmuch_database_get_version (notmuch);
if (version > NOTMUCH_DATABASE_VERSION) {
IGNORE_RESULT (asprintf (&message,
"Error: Notmuch database at %s\n"
" has a newer database format version (%u) than supported by this\n"
" version of notmuch (%u).\n",
database_path, version, NOTMUCH_DATABASE_VERSION));
notmuch_database_destroy (notmuch);
notmuch = NULL;
status = NOTMUCH_STATUS_FILE_ERROR;
goto DONE;
}
/* Check features. */
incompat_features = NULL;
notmuch->features = _notmuch_database_parse_features (
notmuch, notmuch->xapian_db->get_metadata ("features").c_str (),
version, mode == NOTMUCH_DATABASE_MODE_READ_WRITE ? 'w' : 'r',
&incompat_features);
if (incompat_features) {
IGNORE_RESULT (asprintf (&message,
"Error: Notmuch database at %s\n"
" requires features (%s)\n"
" not supported by this version of notmuch.\n",
database_path, incompat_features));
notmuch_database_destroy (notmuch);
notmuch = NULL;
status = NOTMUCH_STATUS_FILE_ERROR;
goto DONE;
}
_load_database_state (notmuch);
notmuch->query_parser = new Xapian::QueryParser;
notmuch->term_gen = new Xapian::TermGenerator;
notmuch->term_gen->set_stemmer (Xapian::Stem ("english"));
notmuch->value_range_processor = new Xapian::NumberRangeProcessor (NOTMUCH_VALUE_TIMESTAMP);
notmuch->date_range_processor = new ParseTimeRangeProcessor (NOTMUCH_VALUE_TIMESTAMP,
"date:");
notmuch->last_mod_range_processor = new Xapian::NumberRangeProcessor (NOTMUCH_VALUE_LAST_MOD,
"lastmod:");
notmuch->query_parser->set_default_op (Xapian::Query::OP_AND);
notmuch->query_parser->set_database (*notmuch->xapian_db);
notmuch->query_parser->set_stemmer (Xapian::Stem ("english"));
notmuch->query_parser->set_stemming_strategy (Xapian::QueryParser::STEM_SOME);
notmuch->query_parser->add_rangeprocessor (notmuch->value_range_processor);
notmuch->query_parser->add_rangeprocessor (notmuch->date_range_processor);
notmuch->query_parser->add_rangeprocessor (notmuch->last_mod_range_processor);
/* Configuration information is needed to set up query parser */
status = _notmuch_config_load_from_database (notmuch);
if (status)
goto DONE;
if (key_file)
status = _notmuch_config_load_from_file (notmuch, key_file);
if (status)
goto DONE;
status = _choose_dir (notmuch, profile,
NOTMUCH_CONFIG_HOOK_DIR,
"XDG_CONFIG_HOME",
".config",
"hooks",
&message);
if (status)
goto DONE;
status = _choose_dir (notmuch, profile,
NOTMUCH_CONFIG_BACKUP_DIR,
"XDG_DATA_HOME",
".local/share",
"backups",
&message);
if (status)
goto DONE;
status = _notmuch_config_load_defaults (notmuch);
if (status)
goto DONE;
autocommit_str = notmuch_config_get (notmuch, NOTMUCH_CONFIG_AUTOCOMMIT);
if (unlikely (! autocommit_str)) {
INTERNAL_ERROR ("missing configuration for autocommit");
}
notmuch->transaction_threshold = strtoul (autocommit_str, &autocommit_end, 10);
if (*autocommit_end != '\0')
INTERNAL_ERROR ("Malformed database database.autocommit value: %s", autocommit_str);
status = _notmuch_database_setup_standard_query_fields (notmuch);
if (status)
goto DONE;
status = _notmuch_database_setup_user_query_fields (notmuch);
if (status)
goto DONE;
} catch (const Xapian::Error &error) {
IGNORE_RESULT (asprintf (&message, "A Xapian exception occurred opening database: %s\n",
error.get_msg ().c_str ()));
notmuch_database_destroy (notmuch);
notmuch = NULL;
status = NOTMUCH_STATUS_XAPIAN_EXCEPTION;
}
DONE:
if (message_ptr)
*message_ptr = message;
return status;
}
notmuch_status_t
notmuch_database_open_with_config (const char *database_path,
notmuch_database_mode_t mode,
const char *config_path,
const char *profile,
notmuch_database_t **database,
char **status_string)
{
notmuch_status_t status = NOTMUCH_STATUS_SUCCESS;
void *local = talloc_new (NULL);
notmuch_database_t *notmuch = NULL;
char *message = NULL;
GKeyFile *key_file = NULL;
bool split = false;
_notmuch_init ();
notmuch = _alloc_notmuch ();
if (! notmuch) {
status = NOTMUCH_STATUS_OUT_OF_MEMORY;
goto DONE;
}
status = _load_key_file (notmuch, config_path, profile, &key_file);
if (status) {
message = strdup ("Error: cannot load config file.\n");
goto DONE;
}
if ((status = _choose_database_path (local, profile, key_file,
&database_path, &split,
&message)))
goto DONE;
status = _db_dir_exists (database_path, &message);
if (status)
goto DONE;
_set_database_path (notmuch, database_path);
status = _notmuch_choose_xapian_path (notmuch, database_path,
&notmuch->xapian_path, &message);
if (status)
goto DONE;
status = _finish_open (notmuch, profile, mode, key_file, &message);
DONE:
talloc_free (local);
if (key_file)
g_key_file_free (key_file);
if (message) {
if (status_string)
*status_string = message;
else
free (message);
}
if (database)
*database = notmuch;
else
talloc_free (notmuch);
if (notmuch)
notmuch->open = true;
return status;
}
notmuch_status_t
notmuch_database_create (const char *path, notmuch_database_t **database)
{
char *status_string = NULL;
notmuch_status_t status;
status = notmuch_database_create_verbose (path, database,
&status_string);
if (status_string) {
fputs (status_string, stderr);
free (status_string);
}
return status;
}
notmuch_status_t
notmuch_database_create_verbose (const char *path,
notmuch_database_t **database,
char **status_string)
{
return notmuch_database_create_with_config (path, "", NULL, database, status_string);
}
notmuch_status_t
notmuch_database_create_with_config (const char *database_path,
const char *config_path,
const char *profile,
notmuch_database_t **database,
char **status_string)
{
notmuch_status_t status = NOTMUCH_STATUS_SUCCESS;
notmuch_database_t *notmuch = NULL;
const char *notmuch_path = NULL;
char *message = NULL;
GKeyFile *key_file = NULL;
void *local = talloc_new (NULL);
int err;
bool split = false;
_notmuch_init ();
notmuch = _alloc_notmuch ();
if (! notmuch) {
status = NOTMUCH_STATUS_OUT_OF_MEMORY;
goto DONE;
}
status = _load_key_file (notmuch, config_path, profile, &key_file);
if (status) {
message = strdup ("Error: cannot load config file.\n");
goto DONE;
}
if ((status = _choose_database_path (local, profile, key_file,
&database_path, &split, &message)))
goto DONE;
status = _db_dir_exists (database_path, &message);
if (status)
goto DONE;
_set_database_path (notmuch, database_path);
if (key_file && ! split) {
char *mail_root = notmuch_canonicalize_file_name (
g_key_file_get_value (key_file, "database", "mail_root", NULL));
char *db_path = notmuch_canonicalize_file_name (database_path);
split = (mail_root && (0 != strcmp (mail_root, db_path)));
free (mail_root);
free (db_path);
}
if (split) {
notmuch_path = database_path;
} else {
if (! (notmuch_path = talloc_asprintf (local, "%s/%s", database_path, ".notmuch"))) {
status = NOTMUCH_STATUS_OUT_OF_MEMORY;
goto DONE;
}
err = mkdir (notmuch_path, 0755);
if (err) {
if (errno == EEXIST) {
status = NOTMUCH_STATUS_DATABASE_EXISTS;
talloc_free (notmuch);
notmuch = NULL;
} else {
IGNORE_RESULT (asprintf (&message, "Error: Cannot create directory %s: %s.\n",
notmuch_path, strerror (errno)));
status = NOTMUCH_STATUS_FILE_ERROR;
}
goto DONE;
}
}
if (! (notmuch->xapian_path = talloc_asprintf (notmuch, "%s/%s", notmuch_path, "xapian"))) {
status = NOTMUCH_STATUS_OUT_OF_MEMORY;
goto DONE;
}
status = _trial_open (notmuch->xapian_path, &message);
if (status == NOTMUCH_STATUS_SUCCESS) {
notmuch_database_destroy (notmuch);
notmuch = NULL;
status = NOTMUCH_STATUS_DATABASE_EXISTS;
goto DONE;
}
if (message)
free (message);
status = _finish_open (notmuch,
profile,
NOTMUCH_DATABASE_MODE_READ_WRITE,
key_file,
&message);
if (status)
goto DONE;
/* Upgrade doesn't add these feature to existing databases, but
* new databases have them. */
notmuch->features |= NOTMUCH_FEATURE_FROM_SUBJECT_ID_VALUES;
notmuch->features |= NOTMUCH_FEATURE_INDEXED_MIMETYPES;
notmuch->features |= NOTMUCH_FEATURE_UNPREFIX_BODY_ONLY;
status = notmuch_database_upgrade (notmuch, NULL, NULL);
if (status) {
notmuch_database_close (notmuch);
notmuch = NULL;
}
DONE:
talloc_free (local);
if (key_file)
g_key_file_free (key_file);
if (message) {
if (status_string)
*status_string = message;
else
free (message);
}
if (database)
*database = notmuch;
else
talloc_free (notmuch);
return status;
}
notmuch_status_t
notmuch_database_reopen (notmuch_database_t *notmuch,
notmuch_database_mode_t new_mode)
{
notmuch_database_mode_t cur_mode = _notmuch_database_mode (notmuch);
if (notmuch->xapian_db == NULL) {
_notmuch_database_log (notmuch, "Cannot reopen closed or nonexistent database\n");
return NOTMUCH_STATUS_ILLEGAL_ARGUMENT;
}
try {
if (cur_mode == new_mode &&
new_mode == NOTMUCH_DATABASE_MODE_READ_ONLY) {
notmuch->xapian_db->reopen ();
} else {
notmuch->xapian_db->close ();
delete notmuch->xapian_db;
notmuch->xapian_db = NULL;
/* no need to free the same object twice */
notmuch->writable_xapian_db = NULL;
if (new_mode == NOTMUCH_DATABASE_MODE_READ_WRITE) {
notmuch->writable_xapian_db = new Xapian::WritableDatabase (notmuch->xapian_path,
DB_ACTION);
notmuch->xapian_db = notmuch->writable_xapian_db;
} else {
notmuch->xapian_db = new Xapian::Database (notmuch->xapian_path,
DB_ACTION);
}
}
_load_database_state (notmuch);
} catch (const Xapian::Error &error) {
if (! notmuch->exception_reported) {
_notmuch_database_log (notmuch, "Error: A Xapian exception reopening database: %s\n",
error.get_msg ().c_str ());
notmuch->exception_reported = true;
}
return NOTMUCH_STATUS_XAPIAN_EXCEPTION;
}
notmuch->view++;
notmuch->open = true;
return NOTMUCH_STATUS_SUCCESS;
}
static notmuch_status_t
_maybe_load_config_from_database (notmuch_database_t *notmuch,
GKeyFile *key_file,
const char *database_path,
const char *profile)
{
char *message; /* ignored */
if (_db_dir_exists (database_path, &message))
return NOTMUCH_STATUS_NO_DATABASE;
_set_database_path (notmuch, database_path);
if (_notmuch_choose_xapian_path (notmuch, database_path, &notmuch->xapian_path, &message))
return NOTMUCH_STATUS_NO_DATABASE;
(void) _finish_open (notmuch, profile, NOTMUCH_DATABASE_MODE_READ_ONLY, key_file, &message);
return NOTMUCH_STATUS_SUCCESS;
}
notmuch_status_t
notmuch_database_load_config (const char *database_path,
const char *config_path,
const char *profile,
notmuch_database_t **database,
char **status_string)
{
notmuch_status_t status = NOTMUCH_STATUS_SUCCESS, warning = NOTMUCH_STATUS_SUCCESS;
void *local = talloc_new (NULL);
notmuch_database_t *notmuch = NULL;
char *message = NULL;
GKeyFile *key_file = NULL;
bool split = false;
_notmuch_init ();
notmuch = _alloc_notmuch ();
if (! notmuch) {
status = NOTMUCH_STATUS_OUT_OF_MEMORY;
goto DONE;
}
status = _load_key_file (notmuch, config_path, profile, &key_file);
switch (status) {
case NOTMUCH_STATUS_SUCCESS:
break;
case NOTMUCH_STATUS_NO_CONFIG:
warning = status;
break;
default:
message = strdup ("Error: cannot load config file.\n");
goto DONE;
}
status = _choose_database_path (local, profile, key_file,
&database_path, &split, &message);
switch (status) {
case NOTMUCH_STATUS_NO_DATABASE:
case NOTMUCH_STATUS_SUCCESS:
if (! warning)
warning = status;
break;
default:
goto DONE;
}
if (database_path) {
status = _maybe_load_config_from_database (notmuch, key_file, database_path, profile);
switch (status) {
case NOTMUCH_STATUS_NO_DATABASE:
case NOTMUCH_STATUS_SUCCESS:
if (! warning)
warning = status;
break;
default:
goto DONE;
}
}
if (key_file) {
status = _notmuch_config_load_from_file (notmuch, key_file);
if (status)
goto DONE;
}
status = _notmuch_config_load_defaults (notmuch);
if (status)
goto DONE;
DONE:
talloc_free (local);
if (status_string)
*status_string = message;
if (database)
*database = notmuch;
if (status)
return status;
else
return warning;
}