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