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