]> git.cworth.org Git - notmuch/blob - lib/open.cc
lib: parse index.as_text
[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     return NOTMUCH_STATUS_SUCCESS;
253 }
254
255 static notmuch_status_t
256 _mkdir (const char *path, char **message)
257 {
258     if (g_mkdir_with_parents (path, 0755)) {
259         IGNORE_RESULT (asprintf (message, "Error: Cannot create directory %s: %s.\n",
260                                  path, strerror (errno)));
261         return NOTMUCH_STATUS_FILE_ERROR;
262     }
263     return NOTMUCH_STATUS_SUCCESS;
264 }
265
266 static notmuch_status_t
267 _create_database_path (notmuch_database_t *notmuch,
268                        const char *profile,
269                        GKeyFile *key_file,
270                        const char **database_path,
271                        char **message)
272 {
273     notmuch_status_t status;
274
275     if (! *database_path) {
276         *database_path = getenv ("NOTMUCH_DATABASE");
277     }
278
279     if (! *database_path && key_file) {
280         char *path = g_key_file_get_string (key_file, "database", "path", NULL);
281         if (path) {
282             if (path[0] == '/')
283                 *database_path = talloc_strdup (notmuch, path);
284             else
285                 *database_path = talloc_asprintf (notmuch, "%s/%s", getenv ("HOME"), path);
286             g_free (path);
287         }
288     }
289
290     if (! *database_path) {
291         *database_path = _xdg_dir (notmuch, "XDG_DATA_HOME", ".local/share", profile);
292         notmuch->params |= NOTMUCH_PARAM_SPLIT;
293     }
294
295     if (*database_path[0] != '/') {
296         *message = strdup ("Error: Database path must be absolute.\n");
297         return NOTMUCH_STATUS_PATH_ERROR;
298     }
299
300     if ((status = _mkdir (*database_path, message)))
301         return status;
302
303     return NOTMUCH_STATUS_SUCCESS;
304 }
305
306 static notmuch_database_t *
307 _alloc_notmuch (const char *database_path, const char *config_path, const char *profile)
308 {
309     notmuch_database_t *notmuch;
310
311     notmuch = talloc_zero (NULL, notmuch_database_t);
312     if (! notmuch)
313         return NULL;
314
315     notmuch->exception_reported = false;
316     notmuch->status_string = NULL;
317     notmuch->writable_xapian_db = NULL;
318     notmuch->config_path = NULL;
319     notmuch->atomic_nesting = 0;
320     notmuch->transaction_count = 0;
321     notmuch->transaction_threshold = 0;
322     notmuch->view = 1;
323     notmuch->index_as_text = NULL;
324     notmuch->index_as_text_length = 0;
325
326     notmuch->params = NOTMUCH_PARAM_NONE;
327     if (database_path)
328         notmuch->params |= NOTMUCH_PARAM_DATABASE;
329     if (config_path)
330         notmuch->params |= NOTMUCH_PARAM_CONFIG;
331     if (profile)
332         notmuch->params |= NOTMUCH_PARAM_PROFILE;
333
334     return notmuch;
335 }
336
337 static notmuch_status_t
338 _trial_open (const char *xapian_path, char **message_ptr)
339 {
340     try {
341         Xapian::Database db (xapian_path);
342     } catch (const Xapian::DatabaseOpeningError &error) {
343         IGNORE_RESULT (asprintf (message_ptr,
344                                  "Cannot open Xapian database at %s: %s\n",
345                                  xapian_path,
346                                  error.get_msg ().c_str ()));
347         return NOTMUCH_STATUS_PATH_ERROR;
348     } catch (const Xapian::Error &error) {
349         IGNORE_RESULT (asprintf (message_ptr,
350                                  "A Xapian exception occurred opening database: %s\n",
351                                  error.get_msg ().c_str ()));
352         return NOTMUCH_STATUS_XAPIAN_EXCEPTION;
353     }
354     return NOTMUCH_STATUS_SUCCESS;
355 }
356
357 notmuch_status_t
358 _notmuch_choose_xapian_path (void *ctx, const char *database_path,
359                              const char **xapian_path, char **message_ptr)
360 {
361     notmuch_status_t status;
362     const char *trial_path, *notmuch_path;
363
364     status = _db_dir_exists (database_path, message_ptr);
365     if (status)
366         goto DONE;
367
368     trial_path = talloc_asprintf (ctx, "%s/xapian", database_path);
369     status = _trial_open (trial_path, message_ptr);
370     if (status != NOTMUCH_STATUS_PATH_ERROR)
371         goto DONE;
372
373     if (*message_ptr)
374         free (*message_ptr);
375
376     notmuch_path = talloc_asprintf (ctx, "%s/.notmuch", database_path);
377     status = _db_dir_exists (notmuch_path, message_ptr);
378     if (status)
379         goto DONE;
380
381     trial_path = talloc_asprintf (ctx, "%s/xapian", notmuch_path);
382     status = _trial_open (trial_path, message_ptr);
383
384   DONE:
385     if (status == NOTMUCH_STATUS_SUCCESS)
386         *xapian_path = trial_path;
387     return status;
388 }
389
390 static void
391 _set_database_path (notmuch_database_t *notmuch,
392                     const char *database_path)
393 {
394     char *path = talloc_strdup (notmuch, database_path);
395
396     strip_trailing (path, '/');
397
398     _notmuch_config_cache (notmuch, NOTMUCH_CONFIG_DATABASE_PATH, path);
399 }
400
401 static void
402 _load_database_state (notmuch_database_t *notmuch)
403 {
404     std::string last_thread_id;
405     std::string last_mod;
406
407     notmuch->last_doc_id = notmuch->xapian_db->get_lastdocid ();
408     last_thread_id = notmuch->xapian_db->get_metadata ("last_thread_id");
409     if (last_thread_id.empty ()) {
410         notmuch->last_thread_id = 0;
411     } else {
412         const char *str;
413         char *end;
414
415         str = last_thread_id.c_str ();
416         notmuch->last_thread_id = strtoull (str, &end, 16);
417         if (*end != '\0')
418             INTERNAL_ERROR ("Malformed database last_thread_id: %s", str);
419     }
420
421     /* Get current highest revision number. */
422     last_mod = notmuch->xapian_db->get_value_upper_bound (
423         NOTMUCH_VALUE_LAST_MOD);
424     if (last_mod.empty ())
425         notmuch->revision = 0;
426     else
427         notmuch->revision = Xapian::sortable_unserialise (last_mod);
428     notmuch->uuid = talloc_strdup (
429         notmuch, notmuch->xapian_db->get_uuid ().c_str ());
430 }
431
432 /* XXX This should really be done lazily, but the error reporting path in the indexing code
433  * would need to be redone to report any errors.
434  */
435 notmuch_status_t
436 _ensure_index_as_text (notmuch_database_t *notmuch, char **message)
437 {
438     int nregex = 0;
439     regex_t *regexv = NULL;
440
441     if (notmuch->index_as_text)
442         return NOTMUCH_STATUS_SUCCESS;
443
444     for (notmuch_config_values_t *list = notmuch_config_get_values (notmuch,
445                                                                     NOTMUCH_CONFIG_INDEX_AS_TEXT);
446          notmuch_config_values_valid (list);
447          notmuch_config_values_move_to_next (list)) {
448         regex_t *new_regex;
449         int rerr;
450         const char *str = notmuch_config_values_get (list);
451         size_t len = strlen (str);
452
453         /* str must be non-empty, because n_c_get_values skips empty
454          * strings */
455         assert (len > 0);
456
457         regexv = talloc_realloc (notmuch, regexv, regex_t, nregex + 1);
458         new_regex = &regexv[nregex];
459
460         rerr = regcomp (new_regex, str, REG_EXTENDED | REG_NOSUB);
461         if (rerr) {
462             size_t error_size = regerror (rerr, new_regex, NULL, 0);
463             char *error = (char *) talloc_size (str, error_size);
464
465             regerror (rerr, new_regex, error, error_size);
466             IGNORE_RESULT (asprintf (message, "Error in index.as_text: %s: %s\n", error, str));
467
468             return NOTMUCH_STATUS_ILLEGAL_ARGUMENT;
469         }
470         nregex++;
471     }
472
473     notmuch->index_as_text = regexv;
474     notmuch->index_as_text_length = nregex;
475
476     return NOTMUCH_STATUS_SUCCESS;
477 }
478
479 static notmuch_status_t
480 _finish_open (notmuch_database_t *notmuch,
481               const char *profile,
482               notmuch_database_mode_t mode,
483               GKeyFile *key_file,
484               char **message_ptr)
485 {
486     notmuch_status_t status = NOTMUCH_STATUS_SUCCESS;
487     char *incompat_features;
488     char *message = NULL;
489     const char *autocommit_str;
490     char *autocommit_end;
491     unsigned int version;
492     const char *database_path = notmuch_database_get_path (notmuch);
493
494     try {
495
496         if (mode == NOTMUCH_DATABASE_MODE_READ_WRITE) {
497             notmuch->writable_xapian_db = new Xapian::WritableDatabase (notmuch->xapian_path,
498                                                                         DB_ACTION);
499             notmuch->xapian_db = notmuch->writable_xapian_db;
500         } else {
501             notmuch->xapian_db = new Xapian::Database (notmuch->xapian_path);
502         }
503
504         /* Check version.  As of database version 3, we represent
505          * changes in terms of features, so assume a version bump
506          * means a dramatically incompatible change. */
507         version = notmuch_database_get_version (notmuch);
508         if (version > NOTMUCH_DATABASE_VERSION) {
509             IGNORE_RESULT (asprintf (&message,
510                                      "Error: Notmuch database at %s\n"
511                                      "       has a newer database format version (%u) than supported by this\n"
512                                      "       version of notmuch (%u).\n",
513                                      database_path, version, NOTMUCH_DATABASE_VERSION));
514             status = NOTMUCH_STATUS_FILE_ERROR;
515             goto DONE;
516         }
517
518         /* Check features. */
519         incompat_features = NULL;
520         notmuch->features = _notmuch_database_parse_features (
521             notmuch, notmuch->xapian_db->get_metadata ("features").c_str (),
522             version, mode == NOTMUCH_DATABASE_MODE_READ_WRITE ? 'w' : 'r',
523             &incompat_features);
524         if (incompat_features) {
525             IGNORE_RESULT (asprintf (&message,
526                                      "Error: Notmuch database at %s\n"
527                                      "       requires features (%s)\n"
528                                      "       not supported by this version of notmuch.\n",
529                                      database_path, incompat_features));
530             status = NOTMUCH_STATUS_FILE_ERROR;
531             goto DONE;
532         }
533
534         _load_database_state (notmuch);
535
536         notmuch->query_parser = new Xapian::QueryParser;
537         notmuch->term_gen = new Xapian::TermGenerator;
538         notmuch->term_gen->set_stemmer (Xapian::Stem ("english"));
539         notmuch->value_range_processor = new Xapian::NumberRangeProcessor (NOTMUCH_VALUE_TIMESTAMP);
540         notmuch->date_range_processor = new ParseTimeRangeProcessor (NOTMUCH_VALUE_TIMESTAMP,
541                                                                      "date:");
542         notmuch->last_mod_range_processor = new LastModRangeProcessor (notmuch, "lastmod:");
543         notmuch->query_parser->set_default_op (Xapian::Query::OP_AND);
544         notmuch->query_parser->set_database (*notmuch->xapian_db);
545         notmuch->stemmer = new Xapian::Stem ("english");
546         notmuch->query_parser->set_stemmer (*notmuch->stemmer);
547         notmuch->query_parser->set_stemming_strategy (Xapian::QueryParser::STEM_SOME);
548         notmuch->query_parser->add_rangeprocessor (notmuch->value_range_processor);
549         notmuch->query_parser->add_rangeprocessor (notmuch->date_range_processor);
550         notmuch->query_parser->add_rangeprocessor (notmuch->last_mod_range_processor);
551
552         /* Configuration information is needed to set up query parser */
553         status = _notmuch_config_load_from_database (notmuch);
554         if (status)
555             goto DONE;
556
557         if (key_file)
558             status = _notmuch_config_load_from_file (notmuch, key_file);
559         if (status)
560             goto DONE;
561
562         status = _choose_dir (notmuch, profile,
563                               NOTMUCH_CONFIG_HOOK_DIR,
564                               "XDG_CONFIG_HOME",
565                               ".config",
566                               "hooks",
567                               &message);
568         if (status)
569             goto DONE;
570
571         status = _choose_dir (notmuch, profile,
572                               NOTMUCH_CONFIG_BACKUP_DIR,
573                               "XDG_DATA_HOME",
574                               ".local/share",
575                               "backups",
576                               &message);
577         if (status)
578             goto DONE;
579         status = _notmuch_config_load_defaults (notmuch);
580         if (status)
581             goto DONE;
582
583         status = _ensure_index_as_text (notmuch, &message);
584         if (status)
585             goto DONE;
586
587         autocommit_str = notmuch_config_get (notmuch, NOTMUCH_CONFIG_AUTOCOMMIT);
588         if (unlikely (! autocommit_str)) {
589             INTERNAL_ERROR ("missing configuration for autocommit");
590         }
591         notmuch->transaction_threshold = strtoul (autocommit_str, &autocommit_end, 10);
592         if (*autocommit_end != '\0')
593             INTERNAL_ERROR ("Malformed database database.autocommit value: %s", autocommit_str);
594
595         status = _notmuch_database_setup_standard_query_fields (notmuch);
596         if (status)
597             goto DONE;
598
599         status = _notmuch_database_setup_user_query_fields (notmuch);
600         if (status)
601             goto DONE;
602
603     } catch (const Xapian::Error &error) {
604         IGNORE_RESULT (asprintf (&message, "A Xapian exception occurred opening database: %s\n",
605                                  error.get_msg ().c_str ()));
606         status = NOTMUCH_STATUS_XAPIAN_EXCEPTION;
607     }
608   DONE:
609     if (message_ptr)
610         *message_ptr = message;
611     return status;
612 }
613
614 notmuch_status_t
615 notmuch_database_open_with_config (const char *database_path,
616                                    notmuch_database_mode_t mode,
617                                    const char *config_path,
618                                    const char *profile,
619                                    notmuch_database_t **database,
620                                    char **status_string)
621 {
622     notmuch_status_t status = NOTMUCH_STATUS_SUCCESS;
623     notmuch_database_t *notmuch = NULL;
624     char *message = NULL;
625     GKeyFile *key_file = NULL;
626
627     _notmuch_init ();
628
629     notmuch = _alloc_notmuch (database_path, config_path, profile);
630     if (! notmuch) {
631         status = NOTMUCH_STATUS_OUT_OF_MEMORY;
632         goto DONE;
633     }
634
635     status = _load_key_file (notmuch, config_path, profile, &key_file);
636     if (status) {
637         message = strdup ("Error: cannot load config file.\n");
638         goto DONE;
639     }
640
641     if ((status = _choose_database_path (notmuch, profile, key_file,
642                                          &database_path,
643                                          &message)))
644         goto DONE;
645
646     status = _db_dir_exists (database_path, &message);
647     if (status)
648         goto DONE;
649
650     _set_database_path (notmuch, database_path);
651
652     status = _notmuch_choose_xapian_path (notmuch, database_path,
653                                           &notmuch->xapian_path, &message);
654     if (status)
655         goto DONE;
656
657     status = _finish_open (notmuch, profile, mode, key_file, &message);
658
659   DONE:
660     if (key_file)
661         g_key_file_free (key_file);
662
663     if (message) {
664         if (status_string)
665             *status_string = message;
666         else
667             free (message);
668     }
669
670     if (status && notmuch) {
671         notmuch_database_destroy (notmuch);
672         notmuch = NULL;
673     }
674
675     if (database)
676         *database = notmuch;
677
678     if (notmuch)
679         notmuch->open = true;
680
681     return status;
682 }
683
684 notmuch_status_t
685 notmuch_database_create (const char *path, notmuch_database_t **database)
686 {
687     char *status_string = NULL;
688     notmuch_status_t status;
689
690     status = notmuch_database_create_verbose (path, database,
691                                               &status_string);
692
693     if (status_string) {
694         fputs (status_string, stderr);
695         free (status_string);
696     }
697
698     return status;
699 }
700
701 notmuch_status_t
702 notmuch_database_create_verbose (const char *path,
703                                  notmuch_database_t **database,
704                                  char **status_string)
705 {
706     return notmuch_database_create_with_config (path, "", NULL, database, status_string);
707 }
708
709 notmuch_status_t
710 notmuch_database_create_with_config (const char *database_path,
711                                      const char *config_path,
712                                      const char *profile,
713                                      notmuch_database_t **database,
714                                      char **status_string)
715 {
716     notmuch_status_t status = NOTMUCH_STATUS_SUCCESS;
717     notmuch_database_t *notmuch = NULL;
718     const char *notmuch_path = NULL;
719     char *message = NULL;
720     GKeyFile *key_file = NULL;
721
722     _notmuch_init ();
723
724     notmuch = _alloc_notmuch (database_path, config_path, profile);
725     if (! notmuch) {
726         status = NOTMUCH_STATUS_OUT_OF_MEMORY;
727         goto DONE;
728     }
729
730     status = _load_key_file (notmuch, config_path, profile, &key_file);
731     if (status) {
732         message = strdup ("Error: cannot load config file.\n");
733         goto DONE;
734     }
735
736     status = _choose_database_path (notmuch, profile, key_file,
737                                     &database_path, &message);
738     switch (status) {
739     case NOTMUCH_STATUS_SUCCESS:
740         break;
741     case NOTMUCH_STATUS_NO_DATABASE:
742         if ((status = _create_database_path (notmuch, profile, key_file,
743                                              &database_path, &message)))
744             goto DONE;
745         break;
746     default:
747         goto DONE;
748     }
749
750     _set_database_path (notmuch, database_path);
751
752     if (key_file && ! (notmuch->params & NOTMUCH_PARAM_SPLIT)) {
753         char *mail_root = notmuch_canonicalize_file_name (
754             g_key_file_get_string (key_file, "database", "mail_root", NULL));
755         char *db_path = notmuch_canonicalize_file_name (database_path);
756
757         if (mail_root && (0 != strcmp (mail_root, db_path)))
758             notmuch->params |= NOTMUCH_PARAM_SPLIT;
759
760         free (mail_root);
761         free (db_path);
762     }
763
764     if (notmuch->params & NOTMUCH_PARAM_SPLIT) {
765         notmuch_path = database_path;
766     } else {
767         if (! (notmuch_path = talloc_asprintf (notmuch, "%s/%s", database_path, ".notmuch"))) {
768             status = NOTMUCH_STATUS_OUT_OF_MEMORY;
769             goto DONE;
770         }
771
772         status = _mkdir (notmuch_path, &message);
773         if (status)
774             goto DONE;
775     }
776
777     if (! (notmuch->xapian_path = talloc_asprintf (notmuch, "%s/%s", notmuch_path, "xapian"))) {
778         status = NOTMUCH_STATUS_OUT_OF_MEMORY;
779         goto DONE;
780     }
781
782     status = _trial_open (notmuch->xapian_path, &message);
783     if (status == NOTMUCH_STATUS_SUCCESS) {
784         notmuch_database_destroy (notmuch);
785         notmuch = NULL;
786         status = NOTMUCH_STATUS_DATABASE_EXISTS;
787         goto DONE;
788     }
789
790     if (message)
791         free (message);
792
793     status = _finish_open (notmuch,
794                            profile,
795                            NOTMUCH_DATABASE_MODE_READ_WRITE,
796                            key_file,
797                            &message);
798     if (status)
799         goto DONE;
800
801     /* Upgrade doesn't add these feature to existing databases, but
802      * new databases have them. */
803     notmuch->features |= NOTMUCH_FEATURE_FROM_SUBJECT_ID_VALUES;
804     notmuch->features |= NOTMUCH_FEATURE_INDEXED_MIMETYPES;
805     notmuch->features |= NOTMUCH_FEATURE_UNPREFIX_BODY_ONLY;
806
807     status = notmuch_database_upgrade (notmuch, NULL, NULL);
808     if (status) {
809         notmuch_database_close (notmuch);
810         notmuch = NULL;
811     }
812
813   DONE:
814     if (key_file)
815         g_key_file_free (key_file);
816
817     if (message) {
818         if (status_string)
819             *status_string = message;
820         else
821             free (message);
822     }
823     if (status && notmuch) {
824         notmuch_database_destroy (notmuch);
825         notmuch = NULL;
826     }
827
828     if (database)
829         *database = notmuch;
830
831     if (notmuch)
832         notmuch->open = true;
833     return status;
834 }
835
836 notmuch_status_t
837 notmuch_database_reopen (notmuch_database_t *notmuch,
838                          notmuch_database_mode_t new_mode)
839 {
840     notmuch_database_mode_t cur_mode = _notmuch_database_mode (notmuch);
841
842     if (notmuch->xapian_db == NULL) {
843         _notmuch_database_log (notmuch, "Cannot reopen closed or nonexistent database\n");
844         return NOTMUCH_STATUS_ILLEGAL_ARGUMENT;
845     }
846
847     try {
848         if (cur_mode == new_mode &&
849             new_mode == NOTMUCH_DATABASE_MODE_READ_ONLY) {
850             notmuch->xapian_db->reopen ();
851         } else {
852             notmuch->xapian_db->close ();
853
854             delete notmuch->xapian_db;
855             notmuch->xapian_db = NULL;
856             /* no need to free the same object twice */
857             notmuch->writable_xapian_db = NULL;
858
859             if (new_mode == NOTMUCH_DATABASE_MODE_READ_WRITE) {
860                 notmuch->writable_xapian_db = new Xapian::WritableDatabase (notmuch->xapian_path,
861                                                                             DB_ACTION);
862                 notmuch->xapian_db = notmuch->writable_xapian_db;
863             } else {
864                 notmuch->xapian_db = new Xapian::Database (notmuch->xapian_path,
865                                                            DB_ACTION);
866             }
867         }
868
869         _load_database_state (notmuch);
870     } catch (const Xapian::Error &error) {
871         if (! notmuch->exception_reported) {
872             _notmuch_database_log (notmuch, "Error: A Xapian exception reopening database: %s\n",
873                                    error.get_msg ().c_str ());
874             notmuch->exception_reported = true;
875         }
876         return NOTMUCH_STATUS_XAPIAN_EXCEPTION;
877     }
878
879     notmuch->view++;
880     notmuch->open = true;
881     return NOTMUCH_STATUS_SUCCESS;
882 }
883
884 static notmuch_status_t
885 _maybe_load_config_from_database (notmuch_database_t *notmuch,
886                                   GKeyFile *key_file,
887                                   const char *database_path,
888                                   const char *profile)
889 {
890     char *message; /* ignored */
891
892     if (_db_dir_exists (database_path, &message))
893         return NOTMUCH_STATUS_NO_DATABASE;
894
895     _set_database_path (notmuch, database_path);
896
897     if (_notmuch_choose_xapian_path (notmuch, database_path, &notmuch->xapian_path, &message))
898         return NOTMUCH_STATUS_NO_DATABASE;
899
900     (void) _finish_open (notmuch, profile, NOTMUCH_DATABASE_MODE_READ_ONLY, key_file, &message);
901
902     return NOTMUCH_STATUS_SUCCESS;
903 }
904
905 notmuch_status_t
906 notmuch_database_load_config (const char *database_path,
907                               const char *config_path,
908                               const char *profile,
909                               notmuch_database_t **database,
910                               char **status_string)
911 {
912     notmuch_status_t status = NOTMUCH_STATUS_SUCCESS, warning = NOTMUCH_STATUS_SUCCESS;
913     notmuch_database_t *notmuch = NULL;
914     char *message = NULL;
915     GKeyFile *key_file = NULL;
916
917     _notmuch_init ();
918
919     notmuch = _alloc_notmuch (database_path, config_path, profile);
920     if (! notmuch) {
921         status = NOTMUCH_STATUS_OUT_OF_MEMORY;
922         goto DONE;
923     }
924
925     status = _load_key_file (notmuch, config_path, profile, &key_file);
926     switch (status) {
927     case NOTMUCH_STATUS_SUCCESS:
928         break;
929     case NOTMUCH_STATUS_NO_CONFIG:
930         warning = status;
931         break;
932     default:
933         message = strdup ("Error: cannot load config file.\n");
934         goto DONE;
935     }
936
937     status = _choose_database_path (notmuch, profile, key_file,
938                                     &database_path, &message);
939     switch (status) {
940     case NOTMUCH_STATUS_NO_DATABASE:
941     case NOTMUCH_STATUS_SUCCESS:
942         if (! warning)
943             warning = status;
944         break;
945     default:
946         goto DONE;
947     }
948
949
950     if (database_path) {
951         status = _maybe_load_config_from_database (notmuch, key_file, database_path, profile);
952         switch (status) {
953         case NOTMUCH_STATUS_NO_DATABASE:
954         case NOTMUCH_STATUS_SUCCESS:
955             if (! warning)
956                 warning = status;
957             break;
958         default:
959             goto DONE;
960         }
961     }
962
963     if (key_file) {
964         status = _notmuch_config_load_from_file (notmuch, key_file);
965         if (status)
966             goto DONE;
967     }
968     status = _notmuch_config_load_defaults (notmuch);
969     if (status)
970         goto DONE;
971
972   DONE:
973     if (status_string)
974         *status_string = message;
975
976     if (status &&
977         status != NOTMUCH_STATUS_NO_DATABASE
978         && status != NOTMUCH_STATUS_NO_CONFIG) {
979         notmuch_database_destroy (notmuch);
980         notmuch = NULL;
981     }
982
983     if (database)
984         *database = notmuch;
985
986     if (status)
987         return status;
988     else
989         return warning;
990 }