]> git.notmuchmail.org Git - notmuch/blob - lib/open.cc
emacs: Add new option notmuch-search-hide-excluded
[notmuch] / lib / open.cc
1 #include <unistd.h>
2 #include <libgen.h>
3
4 #include "database-private.h"
5 #include "parse-time-vrp.h"
6 #include "lastmod-fp.h"
7 #include "path-util.h"
8
9 #if HAVE_XAPIAN_DB_RETRY_LOCK
10 #define DB_ACTION (Xapian::DB_CREATE_OR_OPEN | Xapian::DB_RETRY_LOCK)
11 #else
12 #define DB_ACTION Xapian::DB_CREATE_OR_OPEN
13 #endif
14
15 notmuch_status_t
16 notmuch_database_open (const char *path,
17                        notmuch_database_mode_t mode,
18                        notmuch_database_t **database)
19 {
20     char *status_string = NULL;
21     notmuch_status_t status;
22
23     status = notmuch_database_open_with_config (path, mode, "", NULL,
24                                                 database, &status_string);
25     if (status_string) {
26         fputs (status_string, stderr);
27         free (status_string);
28     }
29
30     return status;
31 }
32
33 notmuch_status_t
34 notmuch_database_open_verbose (const char *path,
35                                notmuch_database_mode_t mode,
36                                notmuch_database_t **database,
37                                char **status_string)
38 {
39     return notmuch_database_open_with_config (path, mode, "", NULL,
40                                               database, status_string);
41 }
42
43 static const char *
44 _xdg_dir (void *ctx,
45           const char *xdg_root_variable,
46           const char *xdg_prefix,
47           const char *profile_name)
48 {
49     const char *xdg_root = getenv (xdg_root_variable);
50
51     if (! xdg_root) {
52         const char *home = getenv ("HOME");
53
54         if (! home) return NULL;
55
56         xdg_root = talloc_asprintf (ctx,
57                                     "%s/%s",
58                                     home,
59                                     xdg_prefix);
60     }
61
62     if (! profile_name)
63         profile_name = getenv ("NOTMUCH_PROFILE");
64
65     if (! profile_name)
66         profile_name = "default";
67
68     return talloc_asprintf (ctx,
69                             "%s/notmuch/%s",
70                             xdg_root,
71                             profile_name);
72 }
73
74 static notmuch_status_t
75 _choose_dir (notmuch_database_t *notmuch,
76              const char *profile,
77              notmuch_config_key_t key,
78              const char *xdg_var,
79              const char *xdg_subdir,
80              const char *subdir,
81              char **message = NULL)
82 {
83     const char *parent;
84     const char *dir;
85     struct stat st;
86     int err;
87
88     dir = notmuch_config_get (notmuch, key);
89
90     if (dir)
91         return NOTMUCH_STATUS_SUCCESS;
92
93     parent = _xdg_dir (notmuch, xdg_var, xdg_subdir, profile);
94     if (! parent)
95         return NOTMUCH_STATUS_PATH_ERROR;
96
97     dir = talloc_asprintf (notmuch, "%s/%s", parent, subdir);
98
99     err = stat (dir, &st);
100     if (err) {
101         if (errno == ENOENT) {
102             char *notmuch_path = dirname (talloc_strdup (notmuch, notmuch->xapian_path));
103             dir = talloc_asprintf (notmuch, "%s/%s", notmuch_path, subdir);
104         } else {
105             IGNORE_RESULT (asprintf (message, "Error: Cannot stat %s: %s.\n",
106                                      dir, strerror (errno)));
107             return NOTMUCH_STATUS_FILE_ERROR;
108         }
109     }
110
111     _notmuch_config_cache (notmuch, key, dir);
112
113     return NOTMUCH_STATUS_SUCCESS;
114 }
115
116 static notmuch_status_t
117 _load_key_file (notmuch_database_t *notmuch,
118                 const char *path,
119                 const char *profile,
120                 GKeyFile **key_file)
121 {
122     notmuch_status_t status = NOTMUCH_STATUS_SUCCESS;
123
124     if (path && EMPTY_STRING (path))
125         goto DONE;
126
127     if (! path)
128         path = getenv ("NOTMUCH_CONFIG");
129
130     if (path)
131         path = talloc_strdup (notmuch, path);
132     else {
133         const char *dir = _xdg_dir (notmuch, "XDG_CONFIG_HOME", ".config", profile);
134
135         if (dir) {
136             path = talloc_asprintf (notmuch, "%s/config", dir);
137             if (access (path, R_OK) != 0)
138                 path = NULL;
139         }
140     }
141
142     if (! path) {
143         const char *home = getenv ("HOME");
144
145         path = talloc_asprintf (notmuch, "%s/.notmuch-config", home);
146
147         if (! profile)
148             profile = getenv ("NOTMUCH_PROFILE");
149
150         if (profile)
151             path = talloc_asprintf (notmuch, "%s.%s", path, profile);
152     }
153
154     *key_file = g_key_file_new ();
155     if (! g_key_file_load_from_file (*key_file, path, G_KEY_FILE_NONE, NULL)) {
156         status = NOTMUCH_STATUS_NO_CONFIG;
157     }
158
159   DONE:
160     if (path)
161         notmuch->config_path = path;
162
163     return status;
164 }
165
166 static notmuch_status_t
167 _db_dir_exists (const char *database_path, char **message)
168 {
169     struct stat st;
170     int err;
171
172     err = stat (database_path, &st);
173     if (err) {
174         IGNORE_RESULT (asprintf (message, "Error: Cannot open database at %s: %s.\n",
175                                  database_path, strerror (errno)));
176         return NOTMUCH_STATUS_FILE_ERROR;
177     }
178
179     if (! S_ISDIR (st.st_mode)) {
180         IGNORE_RESULT (asprintf (message, "Error: Cannot open database at %s: "
181                                  "Not a directory.\n",
182                                  database_path));
183         return NOTMUCH_STATUS_FILE_ERROR;
184     }
185
186     return NOTMUCH_STATUS_SUCCESS;
187 }
188
189 static notmuch_status_t
190 _choose_database_path (notmuch_database_t *notmuch,
191                        const char *profile,
192                        GKeyFile *key_file,
193                        const char **database_path,
194                        char **message)
195 {
196     notmuch_status_t status;
197
198     if (! *database_path) {
199         *database_path = getenv ("NOTMUCH_DATABASE");
200     }
201
202     if (! *database_path && key_file) {
203         char *path = g_key_file_get_string (key_file, "database", "path", NULL);
204         if (path) {
205             if (path[0] == '/')
206                 *database_path = talloc_strdup (notmuch, path);
207             else
208                 *database_path = talloc_asprintf (notmuch, "%s/%s", getenv ("HOME"), path);
209             g_free (path);
210         }
211     }
212     if (! *database_path) {
213         *database_path = _xdg_dir (notmuch, "XDG_DATA_HOME", ".local/share", profile);
214         status = _db_dir_exists (*database_path, message);
215         if (status) {
216             *database_path = NULL;
217         } else {
218             notmuch->params |= NOTMUCH_PARAM_SPLIT;
219         }
220     }
221
222     if (! *database_path) {
223         *database_path = getenv ("MAILDIR");
224     }
225
226     if (! *database_path) {
227         *database_path = talloc_asprintf (notmuch, "%s/mail", getenv ("HOME"));
228         status = _db_dir_exists (*database_path, message);
229         if (status) {
230             *database_path = NULL;
231         }
232     }
233
234     if (*database_path == NULL) {
235         *message = strdup ("Error: could not locate database.\n");
236         return NOTMUCH_STATUS_NO_DATABASE;
237     }
238
239     if (*database_path[0] != '/') {
240         *message = strdup ("Error: Database path must be absolute.\n");
241         return NOTMUCH_STATUS_PATH_ERROR;
242     }
243
244     status = _db_dir_exists (*database_path, message);
245     if (status) {
246         IGNORE_RESULT (asprintf (message,
247                                  "Error: database path '%s' does not exist or is not a directory.\n",
248                                  *database_path));
249         return NOTMUCH_STATUS_NO_DATABASE;
250     }
251
252     if (*message) {
253         free (*message);
254         *message = NULL;
255     }
256
257     return NOTMUCH_STATUS_SUCCESS;
258 }
259
260 static notmuch_status_t
261 _mkdir (const char *path, char **message)
262 {
263     if (g_mkdir_with_parents (path, 0755)) {
264         IGNORE_RESULT (asprintf (message, "Error: Cannot create directory %s: %s.\n",
265                                  path, strerror (errno)));
266         return NOTMUCH_STATUS_FILE_ERROR;
267     }
268     return NOTMUCH_STATUS_SUCCESS;
269 }
270
271 static notmuch_status_t
272 _create_database_path (notmuch_database_t *notmuch,
273                        const char *profile,
274                        GKeyFile *key_file,
275                        const char **database_path,
276                        char **message)
277 {
278     notmuch_status_t status;
279
280     if (! *database_path) {
281         *database_path = getenv ("NOTMUCH_DATABASE");
282     }
283
284     if (! *database_path && key_file) {
285         char *path = g_key_file_get_string (key_file, "database", "path", NULL);
286         if (path) {
287             if (path[0] == '/')
288                 *database_path = talloc_strdup (notmuch, path);
289             else
290                 *database_path = talloc_asprintf (notmuch, "%s/%s", getenv ("HOME"), path);
291             g_free (path);
292         }
293     }
294
295     if (! *database_path) {
296         *database_path = _xdg_dir (notmuch, "XDG_DATA_HOME", ".local/share", profile);
297         notmuch->params |= NOTMUCH_PARAM_SPLIT;
298     }
299
300     if (*database_path[0] != '/') {
301         *message = strdup ("Error: Database path must be absolute.\n");
302         return NOTMUCH_STATUS_PATH_ERROR;
303     }
304
305     if ((status = _mkdir (*database_path, message)))
306         return status;
307
308     return NOTMUCH_STATUS_SUCCESS;
309 }
310
311 static notmuch_database_t *
312 _alloc_notmuch (const char *database_path, const char *config_path, const char *profile)
313 {
314     notmuch_database_t *notmuch;
315
316     notmuch = talloc_zero (NULL, notmuch_database_t);
317     if (! notmuch)
318         return NULL;
319
320     notmuch->exception_reported = false;
321     notmuch->status_string = NULL;
322     notmuch->writable_xapian_db = NULL;
323     notmuch->config_path = NULL;
324     notmuch->atomic_nesting = 0;
325     notmuch->transaction_count = 0;
326     notmuch->transaction_threshold = 0;
327     notmuch->view = 1;
328     notmuch->index_as_text = NULL;
329     notmuch->index_as_text_length = 0;
330
331     notmuch->params = NOTMUCH_PARAM_NONE;
332     if (database_path)
333         notmuch->params |= NOTMUCH_PARAM_DATABASE;
334     if (config_path)
335         notmuch->params |= NOTMUCH_PARAM_CONFIG;
336     if (profile)
337         notmuch->params |= NOTMUCH_PARAM_PROFILE;
338
339     return notmuch;
340 }
341
342 static notmuch_status_t
343 _trial_open (const char *xapian_path, char **message_ptr)
344 {
345     try {
346         Xapian::Database db (xapian_path);
347     } catch (const Xapian::DatabaseOpeningError &error) {
348         IGNORE_RESULT (asprintf (message_ptr,
349                                  "Cannot open Xapian database at %s: %s\n",
350                                  xapian_path,
351                                  error.get_msg ().c_str ()));
352         return NOTMUCH_STATUS_PATH_ERROR;
353     } catch (const Xapian::Error &error) {
354         IGNORE_RESULT (asprintf (message_ptr,
355                                  "A Xapian exception occurred opening database: %s\n",
356                                  error.get_msg ().c_str ()));
357         return NOTMUCH_STATUS_XAPIAN_EXCEPTION;
358     }
359     return NOTMUCH_STATUS_SUCCESS;
360 }
361
362 notmuch_status_t
363 _notmuch_choose_xapian_path (void *ctx, const char *database_path,
364                              const char **xapian_path, char **message_ptr)
365 {
366     notmuch_status_t status;
367     const char *trial_path, *notmuch_path;
368
369     status = _db_dir_exists (database_path, message_ptr);
370     if (status)
371         goto DONE;
372
373     trial_path = talloc_asprintf (ctx, "%s/xapian", database_path);
374     status = _trial_open (trial_path, message_ptr);
375     if (status != NOTMUCH_STATUS_PATH_ERROR)
376         goto DONE;
377
378     if (*message_ptr)
379         free (*message_ptr);
380
381     notmuch_path = talloc_asprintf (ctx, "%s/.notmuch", database_path);
382     status = _db_dir_exists (notmuch_path, message_ptr);
383     if (status)
384         goto DONE;
385
386     trial_path = talloc_asprintf (ctx, "%s/xapian", notmuch_path);
387     status = _trial_open (trial_path, message_ptr);
388
389   DONE:
390     if (status == NOTMUCH_STATUS_SUCCESS)
391         *xapian_path = trial_path;
392     return status;
393 }
394
395 static void
396 _set_database_path (notmuch_database_t *notmuch,
397                     const char *database_path)
398 {
399     char *path = talloc_strdup (notmuch, database_path);
400
401     strip_trailing (path, '/');
402
403     _notmuch_config_cache (notmuch, NOTMUCH_CONFIG_DATABASE_PATH, path);
404 }
405
406 static void
407 _load_database_state (notmuch_database_t *notmuch)
408 {
409     std::string last_thread_id;
410     std::string last_mod;
411
412     notmuch->last_doc_id = notmuch->xapian_db->get_lastdocid ();
413     last_thread_id = notmuch->xapian_db->get_metadata ("last_thread_id");
414     if (last_thread_id.empty ()) {
415         notmuch->last_thread_id = 0;
416     } else {
417         const char *str;
418         char *end;
419
420         str = last_thread_id.c_str ();
421         notmuch->last_thread_id = strtoull (str, &end, 16);
422         if (*end != '\0')
423             INTERNAL_ERROR ("Malformed database last_thread_id: %s", str);
424     }
425
426     /* Get current highest revision number. */
427     last_mod = notmuch->xapian_db->get_value_upper_bound (
428         NOTMUCH_VALUE_LAST_MOD);
429     if (last_mod.empty ())
430         notmuch->revision = 0;
431     else
432         notmuch->revision = Xapian::sortable_unserialise (last_mod);
433     notmuch->uuid = talloc_strdup (
434         notmuch, notmuch->xapian_db->get_uuid ().c_str ());
435 }
436
437 /* XXX This should really be done lazily, but the error reporting path in the indexing code
438  * would need to be redone to report any errors.
439  */
440 notmuch_status_t
441 _ensure_index_as_text (notmuch_database_t *notmuch, char **message)
442 {
443     int nregex = 0;
444     regex_t *regexv = NULL;
445
446     if (notmuch->index_as_text)
447         return NOTMUCH_STATUS_SUCCESS;
448
449     for (notmuch_config_values_t *list = notmuch_config_get_values (notmuch,
450                                                                     NOTMUCH_CONFIG_INDEX_AS_TEXT);
451          notmuch_config_values_valid (list);
452          notmuch_config_values_move_to_next (list)) {
453         regex_t *new_regex;
454         int rerr;
455         const char *str = notmuch_config_values_get (list);
456         size_t len = strlen (str);
457
458         /* str must be non-empty, because n_c_get_values skips empty
459          * strings */
460         assert (len > 0);
461
462         regexv = talloc_realloc (notmuch, regexv, regex_t, nregex + 1);
463         new_regex = &regexv[nregex];
464
465         rerr = regcomp (new_regex, str, REG_EXTENDED | REG_NOSUB);
466         if (rerr) {
467             size_t error_size = regerror (rerr, new_regex, NULL, 0);
468             char *error = (char *) talloc_size (str, error_size);
469
470             regerror (rerr, new_regex, error, error_size);
471             IGNORE_RESULT (asprintf (message, "Error in index.as_text: %s: %s\n", error, str));
472
473             return NOTMUCH_STATUS_ILLEGAL_ARGUMENT;
474         }
475         nregex++;
476     }
477
478     notmuch->index_as_text = regexv;
479     notmuch->index_as_text_length = nregex;
480
481     return NOTMUCH_STATUS_SUCCESS;
482 }
483
484 static notmuch_status_t
485 _finish_open (notmuch_database_t *notmuch,
486               const char *profile,
487               notmuch_database_mode_t mode,
488               GKeyFile *key_file,
489               char **message_ptr)
490 {
491     notmuch_status_t status = NOTMUCH_STATUS_SUCCESS;
492     char *incompat_features;
493     char *message = NULL;
494     const char *autocommit_str;
495     char *autocommit_end;
496     unsigned int version;
497     const char *database_path = notmuch_database_get_path (notmuch);
498
499     try {
500
501         if (mode == NOTMUCH_DATABASE_MODE_READ_WRITE) {
502             notmuch->writable_xapian_db = new Xapian::WritableDatabase (notmuch->xapian_path,
503                                                                         DB_ACTION);
504             notmuch->xapian_db = notmuch->writable_xapian_db;
505         } else {
506             notmuch->xapian_db = new Xapian::Database (notmuch->xapian_path);
507         }
508
509         /* Check version.  As of database version 3, we represent
510          * changes in terms of features, so assume a version bump
511          * means a dramatically incompatible change. */
512         version = notmuch_database_get_version (notmuch);
513         if (version > NOTMUCH_DATABASE_VERSION) {
514             IGNORE_RESULT (asprintf (&message,
515                                      "Error: Notmuch database at %s\n"
516                                      "       has a newer database format version (%u) than supported by this\n"
517                                      "       version of notmuch (%u).\n",
518                                      database_path, version, NOTMUCH_DATABASE_VERSION));
519             status = NOTMUCH_STATUS_FILE_ERROR;
520             goto DONE;
521         }
522
523         /* Check features. */
524         incompat_features = NULL;
525         notmuch->features = _notmuch_database_parse_features (
526             notmuch, notmuch->xapian_db->get_metadata ("features").c_str (),
527             version, mode == NOTMUCH_DATABASE_MODE_READ_WRITE ? 'w' : 'r',
528             &incompat_features);
529         if (incompat_features) {
530             IGNORE_RESULT (asprintf (&message,
531                                      "Error: Notmuch database at %s\n"
532                                      "       requires features (%s)\n"
533                                      "       not supported by this version of notmuch.\n",
534                                      database_path, incompat_features));
535             status = NOTMUCH_STATUS_FILE_ERROR;
536             goto DONE;
537         }
538
539         _load_database_state (notmuch);
540
541         notmuch->query_parser = new Xapian::QueryParser;
542         notmuch->term_gen = new Xapian::TermGenerator;
543         notmuch->term_gen->set_stemmer (Xapian::Stem ("english"));
544         notmuch->value_range_processor = new Xapian::NumberRangeProcessor (NOTMUCH_VALUE_TIMESTAMP);
545         notmuch->date_range_processor = new ParseTimeRangeProcessor (NOTMUCH_VALUE_TIMESTAMP,
546                                                                      "date:");
547         notmuch->last_mod_range_processor = new LastModRangeProcessor (notmuch, "lastmod:");
548         notmuch->query_parser->set_default_op (Xapian::Query::OP_AND);
549         notmuch->query_parser->set_database (*notmuch->xapian_db);
550         notmuch->stemmer = new Xapian::Stem ("english");
551         notmuch->query_parser->set_stemmer (*notmuch->stemmer);
552         notmuch->query_parser->set_stemming_strategy (Xapian::QueryParser::STEM_SOME);
553         notmuch->query_parser->add_rangeprocessor (notmuch->value_range_processor);
554         notmuch->query_parser->add_rangeprocessor (notmuch->date_range_processor);
555         notmuch->query_parser->add_rangeprocessor (notmuch->last_mod_range_processor);
556
557         /* Configuration information is needed to set up query parser */
558         status = _notmuch_config_load_from_database (notmuch);
559         if (status)
560             goto DONE;
561
562         if (key_file)
563             status = _notmuch_config_load_from_file (notmuch, key_file, &message);
564         if (status)
565             goto DONE;
566
567         status = _choose_dir (notmuch, profile,
568                               NOTMUCH_CONFIG_HOOK_DIR,
569                               "XDG_CONFIG_HOME",
570                               ".config",
571                               "hooks",
572                               &message);
573         if (status)
574             goto DONE;
575
576         status = _choose_dir (notmuch, profile,
577                               NOTMUCH_CONFIG_BACKUP_DIR,
578                               "XDG_DATA_HOME",
579                               ".local/share",
580                               "backups",
581                               &message);
582         if (status)
583             goto DONE;
584         status = _notmuch_config_load_defaults (notmuch);
585         if (status)
586             goto DONE;
587
588         status = _ensure_index_as_text (notmuch, &message);
589         if (status)
590             goto DONE;
591
592         autocommit_str = notmuch_config_get (notmuch, NOTMUCH_CONFIG_AUTOCOMMIT);
593         if (unlikely (! autocommit_str)) {
594             INTERNAL_ERROR ("missing configuration for autocommit");
595         }
596         notmuch->transaction_threshold = strtoul (autocommit_str, &autocommit_end, 10);
597         if (*autocommit_end != '\0')
598             INTERNAL_ERROR ("Malformed database database.autocommit value: %s", autocommit_str);
599
600         status = _notmuch_database_setup_standard_query_fields (notmuch);
601         if (status)
602             goto DONE;
603
604         status = _notmuch_database_setup_user_query_fields (notmuch);
605         if (status)
606             goto DONE;
607
608     } catch (const Xapian::Error &error) {
609         IGNORE_RESULT (asprintf (&message, "A Xapian exception occurred opening database: %s\n",
610                                  error.get_msg ().c_str ()));
611         status = NOTMUCH_STATUS_XAPIAN_EXCEPTION;
612     }
613   DONE:
614     if (message_ptr)
615         *message_ptr = message;
616     return status;
617 }
618
619 notmuch_status_t
620 notmuch_database_open_with_config (const char *database_path,
621                                    notmuch_database_mode_t mode,
622                                    const char *config_path,
623                                    const char *profile,
624                                    notmuch_database_t **database,
625                                    char **status_string)
626 {
627     notmuch_status_t status = NOTMUCH_STATUS_SUCCESS;
628     notmuch_database_t *notmuch = NULL;
629     char *message = NULL;
630     GKeyFile *key_file = NULL;
631
632     _notmuch_init ();
633
634     notmuch = _alloc_notmuch (database_path, config_path, profile);
635     if (! notmuch) {
636         status = NOTMUCH_STATUS_OUT_OF_MEMORY;
637         goto DONE;
638     }
639
640     status = _load_key_file (notmuch, config_path, profile, &key_file);
641     if (status) {
642         message = strdup ("Error: cannot load config file.\n");
643         goto DONE;
644     }
645
646     if ((status = _choose_database_path (notmuch, profile, key_file,
647                                          &database_path,
648                                          &message)))
649         goto DONE;
650
651     status = _db_dir_exists (database_path, &message);
652     if (status)
653         goto DONE;
654
655     _set_database_path (notmuch, database_path);
656
657     status = _notmuch_choose_xapian_path (notmuch, database_path,
658                                           &notmuch->xapian_path, &message);
659     if (status)
660         goto DONE;
661
662     status = _finish_open (notmuch, profile, mode, key_file, &message);
663
664   DONE:
665     if (key_file)
666         g_key_file_free (key_file);
667
668     if (message) {
669         if (status_string)
670             *status_string = message;
671         else
672             free (message);
673     }
674
675     if (status && notmuch) {
676         notmuch_database_destroy (notmuch);
677         notmuch = NULL;
678     }
679
680     if (database)
681         *database = notmuch;
682
683     if (notmuch)
684         notmuch->open = true;
685
686     return status;
687 }
688
689 notmuch_status_t
690 notmuch_database_create (const char *path, notmuch_database_t **database)
691 {
692     char *status_string = NULL;
693     notmuch_status_t status;
694
695     status = notmuch_database_create_verbose (path, database,
696                                               &status_string);
697
698     if (status_string) {
699         fputs (status_string, stderr);
700         free (status_string);
701     }
702
703     return status;
704 }
705
706 notmuch_status_t
707 notmuch_database_create_verbose (const char *path,
708                                  notmuch_database_t **database,
709                                  char **status_string)
710 {
711     return notmuch_database_create_with_config (path, "", NULL, database, status_string);
712 }
713
714 notmuch_status_t
715 notmuch_database_create_with_config (const char *database_path,
716                                      const char *config_path,
717                                      const char *profile,
718                                      notmuch_database_t **database,
719                                      char **status_string)
720 {
721     notmuch_status_t status = NOTMUCH_STATUS_SUCCESS;
722     notmuch_database_t *notmuch = NULL;
723     const char *notmuch_path = NULL;
724     char *message = NULL;
725     GKeyFile *key_file = NULL;
726
727     _notmuch_init ();
728
729     notmuch = _alloc_notmuch (database_path, config_path, profile);
730     if (! notmuch) {
731         status = NOTMUCH_STATUS_OUT_OF_MEMORY;
732         goto DONE;
733     }
734
735     status = _load_key_file (notmuch, config_path, profile, &key_file);
736     if (status) {
737         message = strdup ("Error: cannot load config file.\n");
738         goto DONE;
739     }
740
741     status = _choose_database_path (notmuch, profile, key_file,
742                                     &database_path, &message);
743     switch (status) {
744     case NOTMUCH_STATUS_SUCCESS:
745         break;
746     case NOTMUCH_STATUS_NO_DATABASE:
747         if ((status = _create_database_path (notmuch, profile, key_file,
748                                              &database_path, &message)))
749             goto DONE;
750         break;
751     default:
752         goto DONE;
753     }
754
755     _set_database_path (notmuch, database_path);
756
757     if (key_file && ! (notmuch->params & NOTMUCH_PARAM_SPLIT)) {
758         char *mail_root = notmuch_canonicalize_file_name (
759             g_key_file_get_string (key_file, "database", "mail_root", NULL));
760         char *db_path = notmuch_canonicalize_file_name (database_path);
761
762         if (mail_root && (0 != strcmp (mail_root, db_path)))
763             notmuch->params |= NOTMUCH_PARAM_SPLIT;
764
765         free (mail_root);
766         free (db_path);
767     }
768
769     if (notmuch->params & NOTMUCH_PARAM_SPLIT) {
770         notmuch_path = database_path;
771     } else {
772         if (! (notmuch_path = talloc_asprintf (notmuch, "%s/%s", database_path, ".notmuch"))) {
773             status = NOTMUCH_STATUS_OUT_OF_MEMORY;
774             goto DONE;
775         }
776
777         status = _mkdir (notmuch_path, &message);
778         if (status)
779             goto DONE;
780     }
781
782     if (! (notmuch->xapian_path = talloc_asprintf (notmuch, "%s/%s", notmuch_path, "xapian"))) {
783         status = NOTMUCH_STATUS_OUT_OF_MEMORY;
784         goto DONE;
785     }
786
787     status = _trial_open (notmuch->xapian_path, &message);
788     if (status == NOTMUCH_STATUS_SUCCESS) {
789         notmuch_database_destroy (notmuch);
790         notmuch = NULL;
791         status = NOTMUCH_STATUS_DATABASE_EXISTS;
792         goto DONE;
793     }
794
795     if (message)
796         free (message);
797
798     status = _finish_open (notmuch,
799                            profile,
800                            NOTMUCH_DATABASE_MODE_READ_WRITE,
801                            key_file,
802                            &message);
803     if (status)
804         goto DONE;
805
806     /* Upgrade doesn't add these feature to existing databases, but
807      * new databases have them. */
808     notmuch->features |= NOTMUCH_FEATURE_FROM_SUBJECT_ID_VALUES;
809     notmuch->features |= NOTMUCH_FEATURE_INDEXED_MIMETYPES;
810     notmuch->features |= NOTMUCH_FEATURE_UNPREFIX_BODY_ONLY;
811
812     status = notmuch_database_upgrade (notmuch, NULL, NULL);
813     if (status) {
814         notmuch_database_close (notmuch);
815         notmuch = NULL;
816     }
817
818   DONE:
819     if (key_file)
820         g_key_file_free (key_file);
821
822     if (message) {
823         if (status_string)
824             *status_string = message;
825         else
826             free (message);
827     }
828     if (status && notmuch) {
829         notmuch_database_destroy (notmuch);
830         notmuch = NULL;
831     }
832
833     if (database)
834         *database = notmuch;
835
836     if (notmuch)
837         notmuch->open = true;
838     return status;
839 }
840
841 notmuch_status_t
842 notmuch_database_reopen (notmuch_database_t *notmuch,
843                          notmuch_database_mode_t new_mode)
844 {
845     notmuch_database_mode_t cur_mode = _notmuch_database_mode (notmuch);
846
847     if (notmuch->xapian_db == NULL) {
848         _notmuch_database_log (notmuch, "Cannot reopen closed or nonexistent database\n");
849         return NOTMUCH_STATUS_ILLEGAL_ARGUMENT;
850     }
851
852     try {
853         if (cur_mode == new_mode &&
854             new_mode == NOTMUCH_DATABASE_MODE_READ_ONLY) {
855             notmuch->xapian_db->reopen ();
856         } else {
857             notmuch->xapian_db->close ();
858
859             delete notmuch->xapian_db;
860             notmuch->xapian_db = NULL;
861             /* no need to free the same object twice */
862             notmuch->writable_xapian_db = NULL;
863
864             if (new_mode == NOTMUCH_DATABASE_MODE_READ_WRITE) {
865                 notmuch->writable_xapian_db = new Xapian::WritableDatabase (notmuch->xapian_path,
866                                                                             DB_ACTION);
867                 notmuch->xapian_db = notmuch->writable_xapian_db;
868             } else {
869                 notmuch->xapian_db = new Xapian::Database (notmuch->xapian_path,
870                                                            DB_ACTION);
871             }
872         }
873
874         _load_database_state (notmuch);
875     } catch (const Xapian::Error &error) {
876         if (! notmuch->exception_reported) {
877             _notmuch_database_log (notmuch, "Error: A Xapian exception reopening database: %s\n",
878                                    error.get_msg ().c_str ());
879             notmuch->exception_reported = true;
880         }
881         return NOTMUCH_STATUS_XAPIAN_EXCEPTION;
882     }
883
884     notmuch->view++;
885     notmuch->open = true;
886     return NOTMUCH_STATUS_SUCCESS;
887 }
888
889 static notmuch_status_t
890 _maybe_load_config_from_database (notmuch_database_t *notmuch,
891                                   GKeyFile *key_file,
892                                   const char *database_path,
893                                   const char *profile)
894 {
895     char *message; /* ignored */
896
897     if (_db_dir_exists (database_path, &message))
898         return NOTMUCH_STATUS_NO_DATABASE;
899
900     _set_database_path (notmuch, database_path);
901
902     if (_notmuch_choose_xapian_path (notmuch, database_path, &notmuch->xapian_path, &message))
903         return NOTMUCH_STATUS_NO_DATABASE;
904
905     (void) _finish_open (notmuch, profile, NOTMUCH_DATABASE_MODE_READ_ONLY, key_file, &message);
906
907     return NOTMUCH_STATUS_SUCCESS;
908 }
909
910 notmuch_status_t
911 notmuch_database_load_config (const char *database_path,
912                               const char *config_path,
913                               const char *profile,
914                               notmuch_database_t **database,
915                               char **status_string)
916 {
917     notmuch_status_t status = NOTMUCH_STATUS_SUCCESS, warning = NOTMUCH_STATUS_SUCCESS;
918     notmuch_database_t *notmuch = NULL;
919     char *message = NULL;
920     GKeyFile *key_file = NULL;
921
922     _notmuch_init ();
923
924     notmuch = _alloc_notmuch (database_path, config_path, profile);
925     if (! notmuch) {
926         status = NOTMUCH_STATUS_OUT_OF_MEMORY;
927         goto DONE;
928     }
929
930     status = _load_key_file (notmuch, config_path, profile, &key_file);
931     switch (status) {
932     case NOTMUCH_STATUS_SUCCESS:
933         break;
934     case NOTMUCH_STATUS_NO_CONFIG:
935         warning = status;
936         break;
937     default:
938         message = strdup ("Error: cannot load config file.\n");
939         goto DONE;
940     }
941
942     status = _choose_database_path (notmuch, profile, key_file,
943                                     &database_path, &message);
944     switch (status) {
945     case NOTMUCH_STATUS_NO_DATABASE:
946     case NOTMUCH_STATUS_SUCCESS:
947         if (! warning)
948             warning = status;
949         break;
950     default:
951         goto DONE;
952     }
953
954
955     if (database_path) {
956         status = _maybe_load_config_from_database (notmuch, key_file, database_path, profile);
957         switch (status) {
958         case NOTMUCH_STATUS_NO_DATABASE:
959         case NOTMUCH_STATUS_SUCCESS:
960             if (! warning)
961                 warning = status;
962             break;
963         default:
964             goto DONE;
965         }
966     }
967
968     if (key_file) {
969         status = _notmuch_config_load_from_file (notmuch, key_file, &message);
970         if (status)
971             goto DONE;
972     }
973     status = _notmuch_config_load_defaults (notmuch);
974     if (status)
975         goto DONE;
976
977   DONE:
978     if (status_string)
979         *status_string = message;
980
981     if (status &&
982         status != NOTMUCH_STATUS_NO_DATABASE
983         && status != NOTMUCH_STATUS_NO_CONFIG) {
984         notmuch_database_destroy (notmuch);
985         notmuch = NULL;
986     }
987
988     if (database)
989         *database = notmuch;
990
991     if (status)
992         return status;
993     else
994         return warning;
995 }