]> git.notmuchmail.org Git - notmuch/blob - lib/open.cc
lib/open: factor out library initialization
[notmuch] / lib / open.cc
1 #include <unistd.h>
2 #include "database-private.h"
3 #include "parse-time-vrp.h"
4
5 #if HAVE_XAPIAN_DB_RETRY_LOCK
6 #define DB_ACTION (Xapian::DB_CREATE_OR_OPEN | Xapian::DB_RETRY_LOCK)
7 #else
8 #define DB_ACTION Xapian::DB_CREATE_OR_OPEN
9 #endif
10
11 notmuch_status_t
12 notmuch_database_open (const char *path,
13                        notmuch_database_mode_t mode,
14                        notmuch_database_t **database)
15 {
16     char *status_string = NULL;
17     notmuch_status_t status;
18
19     status = notmuch_database_open_verbose (path, mode, database,
20                                             &status_string);
21
22     if (status_string) {
23         fputs (status_string, stderr);
24         free (status_string);
25     }
26
27     return status;
28 }
29
30 notmuch_status_t
31 notmuch_database_open_verbose (const char *path,
32                                notmuch_database_mode_t mode,
33                                notmuch_database_t **database,
34                                char **status_string)
35 {
36     return notmuch_database_open_with_config (path, mode, "", NULL,
37                                               database, status_string);
38 }
39
40 static const char *
41 _xdg_dir (void *ctx,
42           const char *xdg_root_variable,
43           const char *xdg_prefix,
44           const char *profile_name)
45 {
46     const char *xdg_root = getenv (xdg_root_variable);
47
48     if (! xdg_root) {
49         const char *home = getenv ("HOME");
50
51         if (! home) return NULL;
52
53         xdg_root = talloc_asprintf (ctx,
54                                     "%s/%s",
55                                     home,
56                                     xdg_prefix);
57     }
58
59     if (! profile_name)
60         profile_name = getenv ("NOTMUCH_PROFILE");
61
62     if (! profile_name)
63         profile_name = "default";
64
65     return talloc_asprintf (ctx,
66                             "%s/notmuch/%s",
67                             xdg_root,
68                             profile_name);
69 }
70
71 static notmuch_status_t
72 _choose_hook_dir (notmuch_database_t *notmuch,
73                   const char *profile,
74                   char **message)
75 {
76     const char *config;
77     const char *hook_dir;
78     struct stat st;
79     int err;
80
81     hook_dir = notmuch_config_get (notmuch, NOTMUCH_CONFIG_HOOK_DIR);
82
83     if (hook_dir)
84         return NOTMUCH_STATUS_SUCCESS;
85
86     config = _xdg_dir (notmuch, "XDG_CONFIG_HOME", ".config", profile);
87     if (! config)
88         return NOTMUCH_STATUS_PATH_ERROR;
89
90     hook_dir = talloc_asprintf (notmuch, "%s/hooks", config);
91
92     err = stat (hook_dir, &st);
93     if (err) {
94         if (errno == ENOENT) {
95             const char *database_path = notmuch_database_get_path (notmuch);
96             hook_dir = talloc_asprintf (notmuch, "%s/.notmuch/hooks", database_path);
97         } else {
98             IGNORE_RESULT (asprintf (message, "Error: Cannot stat %s: %s.\n",
99                                      hook_dir, strerror (errno)));
100             return NOTMUCH_STATUS_FILE_ERROR;
101         }
102     }
103
104     _notmuch_config_cache (notmuch, NOTMUCH_CONFIG_HOOK_DIR, hook_dir);
105
106     return NOTMUCH_STATUS_SUCCESS;
107 }
108
109 static notmuch_status_t
110 _load_key_file (const char *path,
111                 const char *profile,
112                 GKeyFile **key_file)
113 {
114     notmuch_status_t status = NOTMUCH_STATUS_SUCCESS;
115     void *local = talloc_new (NULL);
116
117     if (path && EMPTY_STRING (path))
118         goto DONE;
119
120     if (! path)
121         path = getenv ("NOTMUCH_CONFIG");
122
123     if (! path) {
124         const char *dir = _xdg_dir (local, "XDG_CONFIG_HOME", ".config", profile);
125
126         if (dir) {
127             path = talloc_asprintf (local, "%s/config", dir);
128             if (access (path, R_OK) != 0)
129                 path = NULL;
130         }
131     }
132
133     if (! path) {
134         const char *home = getenv ("HOME");
135
136         path = talloc_asprintf (local, "%s/.notmuch-config", home);
137
138         if (! profile)
139             profile = getenv ("NOTMUCH_PROFILE");
140
141         if (profile)
142             path = talloc_asprintf (local, "%s.%s", path, profile);
143     }
144
145     *key_file = g_key_file_new ();
146     if (! g_key_file_load_from_file (*key_file, path, G_KEY_FILE_NONE, NULL)) {
147         status = NOTMUCH_STATUS_NO_CONFIG;
148     }
149
150   DONE:
151     talloc_free (local);
152     return status;
153 }
154
155 static notmuch_status_t
156 _choose_database_path (void *ctx,
157                        const char *config_path,
158                        const char *profile,
159                        GKeyFile **key_file,
160                        const char **database_path,
161                        char **message)
162 {
163     notmuch_status_t status;
164
165     status = _load_key_file (config_path, profile, key_file);
166     if (status) {
167         *message = strdup ("Error: cannot load config file.\n");
168         return status;
169     }
170
171     if (! *database_path) {
172         *database_path = getenv ("NOTMUCH_DATABASE");
173     }
174
175     if (! *database_path && *key_file) {
176         char *path = g_key_file_get_value (*key_file, "database", "path", NULL);
177         if (path) {
178             *database_path = talloc_strdup (ctx, path);
179             g_free (path);
180         }
181     }
182
183     if (*database_path == NULL) {
184         *message = strdup ("Error: Cannot open a database for a NULL path.\n");
185         return NOTMUCH_STATUS_NULL_POINTER;
186     }
187
188     if (*database_path[0] != '/') {
189         *message = strdup ("Error: Database path must be absolute.\n");
190         return NOTMUCH_STATUS_PATH_ERROR;
191     }
192     return NOTMUCH_STATUS_SUCCESS;
193 }
194
195 notmuch_database_t *
196 _alloc_notmuch ()
197 {
198     notmuch_database_t *notmuch;
199
200     notmuch = talloc_zero (NULL, notmuch_database_t);
201     if (! notmuch)
202         return NULL;
203
204     notmuch->exception_reported = false;
205     notmuch->status_string = NULL;
206     notmuch->writable_xapian_db = NULL;
207     notmuch->atomic_nesting = 0;
208     notmuch->view = 1;
209     return notmuch;
210 }
211
212 static void
213 _set_database_path (notmuch_database_t *notmuch,
214                     const char *database_path)
215 {
216     char *path = talloc_strdup (notmuch, database_path);
217
218     strip_trailing (path, '/');
219
220     _notmuch_config_cache (notmuch, NOTMUCH_CONFIG_DATABASE_PATH, path);
221 }
222
223 static void
224 _init_libs ()
225 {
226
227     static int initialized = 0;
228
229     /* Initialize the GLib type system and threads */
230 #if ! GLIB_CHECK_VERSION (2, 35, 1)
231     g_type_init ();
232 #endif
233
234     /* Initialize gmime */
235     if (! initialized) {
236         g_mime_init ();
237         initialized = 1;
238     }
239 }
240
241 notmuch_status_t
242 notmuch_database_open_with_config (const char *database_path,
243                                    notmuch_database_mode_t mode,
244                                    const char *config_path,
245                                    const char *profile,
246                                    notmuch_database_t **database,
247                                    char **status_string)
248 {
249     notmuch_status_t status = NOTMUCH_STATUS_SUCCESS;
250     void *local = talloc_new (NULL);
251     notmuch_database_t *notmuch = NULL;
252     char *notmuch_path, *incompat_features;
253     char *message = NULL;
254     struct stat st;
255     int err;
256     unsigned int version;
257     GKeyFile *key_file = NULL;
258
259     _init_libs ();
260
261     notmuch = _alloc_notmuch ();
262     if (! notmuch) {
263         status = NOTMUCH_STATUS_OUT_OF_MEMORY;
264         goto DONE;
265     }
266
267     if ((status = _choose_database_path (local, config_path, profile,
268                                          &key_file, &database_path, &message)))
269         goto DONE;
270
271     _set_database_path (notmuch, database_path);
272
273     if (! (notmuch_path = talloc_asprintf (local, "%s/%s", database_path, ".notmuch"))) {
274         message = strdup ("Out of memory\n");
275         status = NOTMUCH_STATUS_OUT_OF_MEMORY;
276         goto DONE;
277     }
278
279     err = stat (notmuch_path, &st);
280     if (err) {
281         IGNORE_RESULT (asprintf (&message, "Error opening database at %s: %s\n",
282                                  notmuch_path, strerror (errno)));
283         status = NOTMUCH_STATUS_FILE_ERROR;
284         goto DONE;
285     }
286
287     if (! (notmuch->xapian_path = talloc_asprintf (notmuch, "%s/%s", notmuch_path, "xapian"))) {
288         message = strdup ("Out of memory\n");
289         status = NOTMUCH_STATUS_OUT_OF_MEMORY;
290         goto DONE;
291     }
292
293     try {
294         std::string last_thread_id;
295         std::string last_mod;
296
297         if (mode == NOTMUCH_DATABASE_MODE_READ_WRITE) {
298             notmuch->writable_xapian_db = new Xapian::WritableDatabase (notmuch->xapian_path,
299                                                                         DB_ACTION);
300             notmuch->xapian_db = notmuch->writable_xapian_db;
301         } else {
302             notmuch->xapian_db = new Xapian::Database (notmuch->xapian_path);
303         }
304
305         /* Check version.  As of database version 3, we represent
306          * changes in terms of features, so assume a version bump
307          * means a dramatically incompatible change. */
308         version = notmuch_database_get_version (notmuch);
309         if (version > NOTMUCH_DATABASE_VERSION) {
310             IGNORE_RESULT (asprintf (&message,
311                                      "Error: Notmuch database at %s\n"
312                                      "       has a newer database format version (%u) than supported by this\n"
313                                      "       version of notmuch (%u).\n",
314                                      notmuch_path, version, NOTMUCH_DATABASE_VERSION));
315             notmuch_database_destroy (notmuch);
316             notmuch = NULL;
317             status = NOTMUCH_STATUS_FILE_ERROR;
318             goto DONE;
319         }
320
321         /* Check features. */
322         incompat_features = NULL;
323         notmuch->features = _notmuch_database_parse_features (
324             local, notmuch->xapian_db->get_metadata ("features").c_str (),
325             version, mode == NOTMUCH_DATABASE_MODE_READ_WRITE ? 'w' : 'r',
326             &incompat_features);
327         if (incompat_features) {
328             IGNORE_RESULT (asprintf (&message,
329                                      "Error: Notmuch database at %s\n"
330                                      "       requires features (%s)\n"
331                                      "       not supported by this version of notmuch.\n",
332                                      notmuch_path, incompat_features));
333             notmuch_database_destroy (notmuch);
334             notmuch = NULL;
335             status = NOTMUCH_STATUS_FILE_ERROR;
336             goto DONE;
337         }
338
339         notmuch->last_doc_id = notmuch->xapian_db->get_lastdocid ();
340         last_thread_id = notmuch->xapian_db->get_metadata ("last_thread_id");
341         if (last_thread_id.empty ()) {
342             notmuch->last_thread_id = 0;
343         } else {
344             const char *str;
345             char *end;
346
347             str = last_thread_id.c_str ();
348             notmuch->last_thread_id = strtoull (str, &end, 16);
349             if (*end != '\0')
350                 INTERNAL_ERROR ("Malformed database last_thread_id: %s", str);
351         }
352
353         /* Get current highest revision number. */
354         last_mod = notmuch->xapian_db->get_value_upper_bound (
355             NOTMUCH_VALUE_LAST_MOD);
356         if (last_mod.empty ())
357             notmuch->revision = 0;
358         else
359             notmuch->revision = Xapian::sortable_unserialise (last_mod);
360         notmuch->uuid = talloc_strdup (
361             notmuch, notmuch->xapian_db->get_uuid ().c_str ());
362
363         notmuch->query_parser = new Xapian::QueryParser;
364         notmuch->term_gen = new Xapian::TermGenerator;
365         notmuch->term_gen->set_stemmer (Xapian::Stem ("english"));
366         notmuch->value_range_processor = new Xapian::NumberRangeProcessor (NOTMUCH_VALUE_TIMESTAMP);
367         notmuch->date_range_processor = new ParseTimeRangeProcessor (NOTMUCH_VALUE_TIMESTAMP,
368                                                                      "date:");
369         notmuch->last_mod_range_processor = new Xapian::NumberRangeProcessor (NOTMUCH_VALUE_LAST_MOD,
370                                                                               "lastmod:");
371         notmuch->query_parser->set_default_op (Xapian::Query::OP_AND);
372         notmuch->query_parser->set_database (*notmuch->xapian_db);
373         notmuch->query_parser->set_stemmer (Xapian::Stem ("english"));
374         notmuch->query_parser->set_stemming_strategy (Xapian::QueryParser::STEM_SOME);
375         notmuch->query_parser->add_rangeprocessor (notmuch->value_range_processor);
376         notmuch->query_parser->add_rangeprocessor (notmuch->date_range_processor);
377         notmuch->query_parser->add_rangeprocessor (notmuch->last_mod_range_processor);
378
379         /* Configuration information is needed to set up query parser */
380         status = _notmuch_config_load_from_database (notmuch);
381         if (status)
382             goto DONE;
383
384         if (key_file)
385             status = _notmuch_config_load_from_file (notmuch, key_file);
386         if (status)
387             goto DONE;
388
389         status = _choose_hook_dir (notmuch, profile, &message);
390         if (status)
391             goto DONE;
392
393         status = _notmuch_config_load_defaults (notmuch);
394         if (status)
395             goto DONE;
396
397         status = _notmuch_database_setup_standard_query_fields (notmuch);
398         if (status)
399             goto DONE;
400
401         status = _notmuch_database_setup_user_query_fields (notmuch);
402         if (status)
403             goto DONE;
404
405     } catch (const Xapian::Error &error) {
406         IGNORE_RESULT (asprintf (&message, "A Xapian exception occurred opening database: %s\n",
407                                  error.get_msg ().c_str ()));
408         notmuch_database_destroy (notmuch);
409         notmuch = NULL;
410         status = NOTMUCH_STATUS_XAPIAN_EXCEPTION;
411     }
412
413   DONE:
414     talloc_free (local);
415
416     if (key_file)
417         g_key_file_free (key_file);
418
419     if (message) {
420         if (status_string)
421             *status_string = message;
422         else
423             free (message);
424     }
425
426     if (database)
427         *database = notmuch;
428     else
429         talloc_free (notmuch);
430
431     if (notmuch)
432         notmuch->open = true;
433
434     return status;
435 }
436
437 notmuch_status_t
438 notmuch_database_create (const char *path, notmuch_database_t **database)
439 {
440     char *status_string = NULL;
441     notmuch_status_t status;
442
443     status = notmuch_database_create_verbose (path, database,
444                                               &status_string);
445
446     if (status_string) {
447         fputs (status_string, stderr);
448         free (status_string);
449     }
450
451     return status;
452 }
453
454 notmuch_status_t
455 notmuch_database_create_verbose (const char *path,
456                                  notmuch_database_t **database,
457                                  char **status_string)
458 {
459     return notmuch_database_create_with_config (path, "", NULL, database, status_string);
460 }
461
462 notmuch_status_t
463 notmuch_database_create_with_config (const char *database_path,
464                                      const char *config_path,
465                                      const char *profile,
466                                      notmuch_database_t **database,
467                                      char **status_string)
468 {
469     notmuch_status_t status = NOTMUCH_STATUS_SUCCESS;
470     notmuch_database_t *notmuch = NULL;
471     char *notmuch_path = NULL;
472     char *message = NULL;
473     GKeyFile *key_file = NULL;
474     struct stat st;
475     int err;
476     void *local = talloc_new (NULL);
477
478     _init_libs ();
479
480     if ((status = _choose_database_path (local, config_path, profile,
481                                          &key_file, &database_path, &message)))
482         goto DONE;
483
484     err = stat (database_path, &st);
485     if (err) {
486         IGNORE_RESULT (asprintf (&message, "Error: Cannot create database at %s: %s.\n",
487                                  database_path, strerror (errno)));
488         status = NOTMUCH_STATUS_FILE_ERROR;
489         goto DONE;
490     }
491
492     if (! S_ISDIR (st.st_mode)) {
493         IGNORE_RESULT (asprintf (&message, "Error: Cannot create database at %s: "
494                                  "Not a directory.\n",
495                                  database_path));
496         status = NOTMUCH_STATUS_FILE_ERROR;
497         goto DONE;
498     }
499
500     notmuch_path = talloc_asprintf (local, "%s/%s", database_path, ".notmuch");
501
502     err = mkdir (notmuch_path, 0755);
503     if (err) {
504         if (errno == EEXIST) {
505             status = NOTMUCH_STATUS_DATABASE_EXISTS;
506         } else {
507             IGNORE_RESULT (asprintf (&message, "Error: Cannot create directory %s: %s.\n",
508                                      notmuch_path, strerror (errno)));
509             status = NOTMUCH_STATUS_FILE_ERROR;
510         }
511         goto DONE;
512     }
513
514     /* XXX this reads the config file twice, which is a bit wasteful */
515     status = notmuch_database_open_with_config (database_path,
516                                                 NOTMUCH_DATABASE_MODE_READ_WRITE,
517                                                 config_path,
518                                                 profile,
519                                                 &notmuch, &message);
520     if (status)
521         goto DONE;
522
523     /* Upgrade doesn't add these feature to existing databases, but
524      * new databases have them. */
525     notmuch->features |= NOTMUCH_FEATURE_FROM_SUBJECT_ID_VALUES;
526     notmuch->features |= NOTMUCH_FEATURE_INDEXED_MIMETYPES;
527     notmuch->features |= NOTMUCH_FEATURE_UNPREFIX_BODY_ONLY;
528
529     status = notmuch_database_upgrade (notmuch, NULL, NULL);
530     if (status) {
531         notmuch_database_close (notmuch);
532         notmuch = NULL;
533     }
534
535   DONE:
536     talloc_free (local);
537
538     if (key_file)
539         g_key_file_free (key_file);
540
541     if (message) {
542         if (status_string)
543             *status_string = message;
544         else
545             free (message);
546     }
547     if (database)
548         *database = notmuch;
549     else
550         talloc_free (notmuch);
551     return status;
552 }
553
554 notmuch_status_t
555 notmuch_database_reopen (notmuch_database_t *notmuch,
556                          notmuch_database_mode_t new_mode)
557 {
558     notmuch_database_mode_t cur_mode = _notmuch_database_mode (notmuch);
559
560     if (notmuch->xapian_db == NULL) {
561         _notmuch_database_log (notmuch, "Cannot reopen closed or nonexistent database\n");
562         return NOTMUCH_STATUS_ILLEGAL_ARGUMENT;
563     }
564
565     try {
566         if (cur_mode == new_mode &&
567             new_mode == NOTMUCH_DATABASE_MODE_READ_ONLY) {
568             notmuch->xapian_db->reopen ();
569         } else {
570             notmuch->xapian_db->close ();
571
572             delete notmuch->xapian_db;
573             notmuch->xapian_db = NULL;
574             /* no need to free the same object twice */
575             notmuch->writable_xapian_db = NULL;
576
577             if (new_mode == NOTMUCH_DATABASE_MODE_READ_WRITE) {
578                 notmuch->writable_xapian_db = new Xapian::WritableDatabase (notmuch->xapian_path,
579                                                                             DB_ACTION);
580                 notmuch->xapian_db = notmuch->writable_xapian_db;
581             } else {
582                 notmuch->xapian_db = new Xapian::Database (notmuch->xapian_path,
583                                                            DB_ACTION);
584             }
585         }
586     } catch (const Xapian::Error &error) {
587         if (! notmuch->exception_reported) {
588             _notmuch_database_log (notmuch, "Error: A Xapian exception reopening database: %s\n",
589                                    error.get_msg ().c_str ());
590             notmuch->exception_reported = true;
591         }
592         return NOTMUCH_STATUS_XAPIAN_EXCEPTION;
593     }
594
595     notmuch->view++;
596     notmuch->open = true;
597     return NOTMUCH_STATUS_SUCCESS;
598 }