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