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