]> git.notmuchmail.org Git - notmuch/blob - lib/open.cc
lib/open: Use check for existing database by trial opening
[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 _db_dir_exists (const char *database_path, char **message)
157 {
158     struct stat st;
159     int err;
160
161     err = stat (database_path, &st);
162     if (err) {
163         IGNORE_RESULT (asprintf (message, "Error: Cannot open database at %s: %s.\n",
164                                  database_path, strerror (errno)));
165         return NOTMUCH_STATUS_FILE_ERROR;
166     }
167
168     if (! S_ISDIR (st.st_mode)) {
169         IGNORE_RESULT (asprintf (message, "Error: Cannot open database at %s: "
170                                  "Not a directory.\n",
171                                  database_path));
172         return NOTMUCH_STATUS_FILE_ERROR;
173     }
174
175     return NOTMUCH_STATUS_SUCCESS;
176 }
177
178 static notmuch_status_t
179 _choose_database_path (void *ctx,
180                        const char *config_path,
181                        const char *profile,
182                        GKeyFile **key_file,
183                        const char **database_path,
184                        char **message)
185 {
186     notmuch_status_t status;
187
188     status = _load_key_file (config_path, profile, key_file);
189     if (status) {
190         *message = strdup ("Error: cannot load config file.\n");
191         return status;
192     }
193
194     if (! *database_path) {
195         *database_path = getenv ("NOTMUCH_DATABASE");
196     }
197
198     if (! *database_path && *key_file) {
199         char *path = g_key_file_get_value (*key_file, "database", "path", NULL);
200         if (path) {
201             *database_path = talloc_strdup (ctx, path);
202             g_free (path);
203         }
204     }
205
206     if (*database_path == NULL) {
207         *message = strdup ("Error: Cannot open a database for a NULL path.\n");
208         return NOTMUCH_STATUS_NULL_POINTER;
209     }
210
211     if (*database_path[0] != '/') {
212         *message = strdup ("Error: Database path must be absolute.\n");
213         return NOTMUCH_STATUS_PATH_ERROR;
214     }
215     return NOTMUCH_STATUS_SUCCESS;
216 }
217
218 notmuch_database_t *
219 _alloc_notmuch ()
220 {
221     notmuch_database_t *notmuch;
222
223     notmuch = talloc_zero (NULL, notmuch_database_t);
224     if (! notmuch)
225         return NULL;
226
227     notmuch->exception_reported = false;
228     notmuch->status_string = NULL;
229     notmuch->writable_xapian_db = NULL;
230     notmuch->atomic_nesting = 0;
231     notmuch->view = 1;
232     return notmuch;
233 }
234
235 static notmuch_status_t
236 _trial_open (const char *xapian_path, char **message_ptr)
237 {
238     try {
239         Xapian::Database db (xapian_path);
240     } catch (const Xapian::DatabaseOpeningError &error) {
241         IGNORE_RESULT (asprintf (message_ptr,
242                                  "Cannot open Xapian database at %s: %s\n",
243                                  xapian_path,
244                                  error.get_msg ().c_str ()));
245         return NOTMUCH_STATUS_PATH_ERROR;
246     } catch (const Xapian::Error &error) {
247         IGNORE_RESULT (asprintf (message_ptr,
248                                  "A Xapian exception occurred opening database: %s\n",
249                                  error.get_msg ().c_str ()));
250         return NOTMUCH_STATUS_XAPIAN_EXCEPTION;
251     }
252     return NOTMUCH_STATUS_SUCCESS;
253 }
254
255 static notmuch_status_t
256 _choose_xapian_path (void *ctx, const char *database_path, const char **xapian_path,
257                      char **message_ptr)
258 {
259     notmuch_status_t status;
260     const char *trial_path, *notmuch_path;
261
262     status = _db_dir_exists (database_path, message_ptr);
263     if (status)
264         goto DONE;
265
266     notmuch_path = talloc_asprintf (ctx, "%s/.notmuch", database_path);
267     status = _db_dir_exists (notmuch_path, message_ptr);
268     if (status)
269         goto DONE;
270
271     trial_path = talloc_asprintf (ctx, "%s/xapian", notmuch_path);
272     status = _trial_open (trial_path, message_ptr);
273
274   DONE:
275     if (status == NOTMUCH_STATUS_SUCCESS)
276         *xapian_path = trial_path;
277     return status;
278 }
279
280 static void
281 _set_database_path (notmuch_database_t *notmuch,
282                     const char *database_path)
283 {
284     char *path = talloc_strdup (notmuch, database_path);
285
286     strip_trailing (path, '/');
287
288     _notmuch_config_cache (notmuch, NOTMUCH_CONFIG_DATABASE_PATH, path);
289 }
290
291 static void
292 _init_libs ()
293 {
294
295     static int initialized = 0;
296
297     /* Initialize the GLib type system and threads */
298 #if ! GLIB_CHECK_VERSION (2, 35, 1)
299     g_type_init ();
300 #endif
301
302     /* Initialize gmime */
303     if (! initialized) {
304         g_mime_init ();
305         initialized = 1;
306     }
307 }
308
309 static notmuch_status_t
310 _finish_open (notmuch_database_t *notmuch,
311               const char *profile,
312               notmuch_database_mode_t mode,
313               GKeyFile *key_file,
314               char **message_ptr)
315 {
316     notmuch_status_t status = NOTMUCH_STATUS_SUCCESS;
317     char *incompat_features;
318     char *message = NULL;
319     unsigned int version;
320     const char *database_path = notmuch_database_get_path (notmuch);
321
322     try {
323         std::string last_thread_id;
324         std::string last_mod;
325
326         if (mode == NOTMUCH_DATABASE_MODE_READ_WRITE) {
327             notmuch->writable_xapian_db = new Xapian::WritableDatabase (notmuch->xapian_path,
328                                                                         DB_ACTION);
329             notmuch->xapian_db = notmuch->writable_xapian_db;
330         } else {
331             notmuch->xapian_db = new Xapian::Database (notmuch->xapian_path);
332         }
333
334         /* Check version.  As of database version 3, we represent
335          * changes in terms of features, so assume a version bump
336          * means a dramatically incompatible change. */
337         version = notmuch_database_get_version (notmuch);
338         if (version > NOTMUCH_DATABASE_VERSION) {
339             IGNORE_RESULT (asprintf (&message,
340                                      "Error: Notmuch database at %s\n"
341                                      "       has a newer database format version (%u) than supported by this\n"
342                                      "       version of notmuch (%u).\n",
343                                      database_path, version, NOTMUCH_DATABASE_VERSION));
344             notmuch_database_destroy (notmuch);
345             notmuch = NULL;
346             status = NOTMUCH_STATUS_FILE_ERROR;
347             goto DONE;
348         }
349
350         /* Check features. */
351         incompat_features = NULL;
352         notmuch->features = _notmuch_database_parse_features (
353             notmuch, notmuch->xapian_db->get_metadata ("features").c_str (),
354             version, mode == NOTMUCH_DATABASE_MODE_READ_WRITE ? 'w' : 'r',
355             &incompat_features);
356         if (incompat_features) {
357             IGNORE_RESULT (asprintf (&message,
358                                      "Error: Notmuch database at %s\n"
359                                      "       requires features (%s)\n"
360                                      "       not supported by this version of notmuch.\n",
361                                      database_path, incompat_features));
362             notmuch_database_destroy (notmuch);
363             notmuch = NULL;
364             status = NOTMUCH_STATUS_FILE_ERROR;
365             goto DONE;
366         }
367
368         notmuch->last_doc_id = notmuch->xapian_db->get_lastdocid ();
369         last_thread_id = notmuch->xapian_db->get_metadata ("last_thread_id");
370         if (last_thread_id.empty ()) {
371             notmuch->last_thread_id = 0;
372         } else {
373             const char *str;
374             char *end;
375
376             str = last_thread_id.c_str ();
377             notmuch->last_thread_id = strtoull (str, &end, 16);
378             if (*end != '\0')
379                 INTERNAL_ERROR ("Malformed database last_thread_id: %s", str);
380         }
381
382         /* Get current highest revision number. */
383         last_mod = notmuch->xapian_db->get_value_upper_bound (
384             NOTMUCH_VALUE_LAST_MOD);
385         if (last_mod.empty ())
386             notmuch->revision = 0;
387         else
388             notmuch->revision = Xapian::sortable_unserialise (last_mod);
389         notmuch->uuid = talloc_strdup (
390             notmuch, notmuch->xapian_db->get_uuid ().c_str ());
391
392         notmuch->query_parser = new Xapian::QueryParser;
393         notmuch->term_gen = new Xapian::TermGenerator;
394         notmuch->term_gen->set_stemmer (Xapian::Stem ("english"));
395         notmuch->value_range_processor = new Xapian::NumberRangeProcessor (NOTMUCH_VALUE_TIMESTAMP);
396         notmuch->date_range_processor = new ParseTimeRangeProcessor (NOTMUCH_VALUE_TIMESTAMP,
397                                                                      "date:");
398         notmuch->last_mod_range_processor = new Xapian::NumberRangeProcessor (NOTMUCH_VALUE_LAST_MOD,
399                                                                               "lastmod:");
400         notmuch->query_parser->set_default_op (Xapian::Query::OP_AND);
401         notmuch->query_parser->set_database (*notmuch->xapian_db);
402         notmuch->query_parser->set_stemmer (Xapian::Stem ("english"));
403         notmuch->query_parser->set_stemming_strategy (Xapian::QueryParser::STEM_SOME);
404         notmuch->query_parser->add_rangeprocessor (notmuch->value_range_processor);
405         notmuch->query_parser->add_rangeprocessor (notmuch->date_range_processor);
406         notmuch->query_parser->add_rangeprocessor (notmuch->last_mod_range_processor);
407
408         /* Configuration information is needed to set up query parser */
409         status = _notmuch_config_load_from_database (notmuch);
410         if (status)
411             goto DONE;
412
413         if (key_file)
414             status = _notmuch_config_load_from_file (notmuch, key_file);
415         if (status)
416             goto DONE;
417
418         status = _choose_hook_dir (notmuch, profile, &message);
419         if (status)
420             goto DONE;
421
422         status = _notmuch_config_load_defaults (notmuch);
423         if (status)
424             goto DONE;
425
426         status = _notmuch_database_setup_standard_query_fields (notmuch);
427         if (status)
428             goto DONE;
429
430         status = _notmuch_database_setup_user_query_fields (notmuch);
431         if (status)
432             goto DONE;
433
434     } catch (const Xapian::Error &error) {
435         IGNORE_RESULT (asprintf (&message, "A Xapian exception occurred opening database: %s\n",
436                                  error.get_msg ().c_str ()));
437         notmuch_database_destroy (notmuch);
438         notmuch = NULL;
439         status = NOTMUCH_STATUS_XAPIAN_EXCEPTION;
440     }
441   DONE:
442     if (message_ptr)
443         *message_ptr = message;
444     return status;
445 }
446
447 notmuch_status_t
448 notmuch_database_open_with_config (const char *database_path,
449                                    notmuch_database_mode_t mode,
450                                    const char *config_path,
451                                    const char *profile,
452                                    notmuch_database_t **database,
453                                    char **status_string)
454 {
455     notmuch_status_t status = NOTMUCH_STATUS_SUCCESS;
456     void *local = talloc_new (NULL);
457     notmuch_database_t *notmuch = NULL;
458     char *message = NULL;
459     GKeyFile *key_file = NULL;
460
461     _init_libs ();
462
463     notmuch = _alloc_notmuch ();
464     if (! notmuch) {
465         status = NOTMUCH_STATUS_OUT_OF_MEMORY;
466         goto DONE;
467     }
468
469     if ((status = _choose_database_path (local, config_path, profile, &key_file, &database_path,
470                                          &message)))
471         goto DONE;
472
473     status = _db_dir_exists (database_path, &message);
474     if (status)
475         goto DONE;
476
477     _set_database_path (notmuch, database_path);
478
479     status = _choose_xapian_path (notmuch, database_path, &notmuch->xapian_path, &message);
480     if (status)
481         goto DONE;
482
483     status = _finish_open (notmuch, profile, mode, key_file, &message);
484
485   DONE:
486     talloc_free (local);
487
488     if (key_file)
489         g_key_file_free (key_file);
490
491     if (message) {
492         if (status_string)
493             *status_string = message;
494         else
495             free (message);
496     }
497
498     if (database)
499         *database = notmuch;
500     else
501         talloc_free (notmuch);
502
503     if (notmuch)
504         notmuch->open = true;
505
506     return status;
507 }
508
509 notmuch_status_t
510 notmuch_database_create (const char *path, notmuch_database_t **database)
511 {
512     char *status_string = NULL;
513     notmuch_status_t status;
514
515     status = notmuch_database_create_verbose (path, database,
516                                               &status_string);
517
518     if (status_string) {
519         fputs (status_string, stderr);
520         free (status_string);
521     }
522
523     return status;
524 }
525
526 notmuch_status_t
527 notmuch_database_create_verbose (const char *path,
528                                  notmuch_database_t **database,
529                                  char **status_string)
530 {
531     return notmuch_database_create_with_config (path, "", NULL, database, status_string);
532 }
533
534 notmuch_status_t
535 notmuch_database_create_with_config (const char *database_path,
536                                      const char *config_path,
537                                      const char *profile,
538                                      notmuch_database_t **database,
539                                      char **status_string)
540 {
541     notmuch_status_t status = NOTMUCH_STATUS_SUCCESS;
542     notmuch_database_t *notmuch = NULL;
543     char *notmuch_path = NULL;
544     char *message = NULL;
545     GKeyFile *key_file = NULL;
546     void *local = talloc_new (NULL);
547     int err;
548
549     _init_libs ();
550
551     notmuch = _alloc_notmuch ();
552     if (! notmuch) {
553         status = NOTMUCH_STATUS_OUT_OF_MEMORY;
554         goto DONE;
555     }
556
557     _init_libs ();
558
559     if ((status = _choose_database_path (local, config_path, profile,
560                                          &key_file, &database_path, &message)))
561         goto DONE;
562
563     status = _db_dir_exists (database_path, &message);
564     if (status)
565         goto DONE;
566
567     _set_database_path (notmuch, database_path);
568
569     notmuch_path = talloc_asprintf (local, "%s/%s", database_path, ".notmuch");
570
571     err = mkdir (notmuch_path, 0755);
572     if (err) {
573         if (errno == EEXIST) {
574             status = NOTMUCH_STATUS_DATABASE_EXISTS;
575             talloc_free (notmuch);
576             notmuch = NULL;
577         } else {
578             IGNORE_RESULT (asprintf (&message, "Error: Cannot create directory %s: %s.\n",
579                                      notmuch_path, strerror (errno)));
580             status = NOTMUCH_STATUS_FILE_ERROR;
581         }
582         goto DONE;
583     }
584
585     if (! (notmuch->xapian_path = talloc_asprintf (notmuch, "%s/%s", notmuch_path, "xapian"))) {
586         status = NOTMUCH_STATUS_OUT_OF_MEMORY;
587         goto DONE;
588     }
589
590     status = _finish_open (notmuch,
591                            profile,
592                            NOTMUCH_DATABASE_MODE_READ_WRITE,
593                            key_file,
594                            &message);
595     if (status)
596         goto DONE;
597
598     /* Upgrade doesn't add these feature to existing databases, but
599      * new databases have them. */
600     notmuch->features |= NOTMUCH_FEATURE_FROM_SUBJECT_ID_VALUES;
601     notmuch->features |= NOTMUCH_FEATURE_INDEXED_MIMETYPES;
602     notmuch->features |= NOTMUCH_FEATURE_UNPREFIX_BODY_ONLY;
603
604     status = notmuch_database_upgrade (notmuch, NULL, NULL);
605     if (status) {
606         notmuch_database_close (notmuch);
607         notmuch = NULL;
608     }
609
610   DONE:
611     talloc_free (local);
612
613     if (key_file)
614         g_key_file_free (key_file);
615
616     if (message) {
617         if (status_string)
618             *status_string = message;
619         else
620             free (message);
621     }
622     if (database)
623         *database = notmuch;
624     else
625         talloc_free (notmuch);
626     return status;
627 }
628
629 notmuch_status_t
630 notmuch_database_reopen (notmuch_database_t *notmuch,
631                          notmuch_database_mode_t new_mode)
632 {
633     notmuch_database_mode_t cur_mode = _notmuch_database_mode (notmuch);
634
635     if (notmuch->xapian_db == NULL) {
636         _notmuch_database_log (notmuch, "Cannot reopen closed or nonexistent database\n");
637         return NOTMUCH_STATUS_ILLEGAL_ARGUMENT;
638     }
639
640     try {
641         if (cur_mode == new_mode &&
642             new_mode == NOTMUCH_DATABASE_MODE_READ_ONLY) {
643             notmuch->xapian_db->reopen ();
644         } else {
645             notmuch->xapian_db->close ();
646
647             delete notmuch->xapian_db;
648             notmuch->xapian_db = NULL;
649             /* no need to free the same object twice */
650             notmuch->writable_xapian_db = NULL;
651
652             if (new_mode == NOTMUCH_DATABASE_MODE_READ_WRITE) {
653                 notmuch->writable_xapian_db = new Xapian::WritableDatabase (notmuch->xapian_path,
654                                                                             DB_ACTION);
655                 notmuch->xapian_db = notmuch->writable_xapian_db;
656             } else {
657                 notmuch->xapian_db = new Xapian::Database (notmuch->xapian_path,
658                                                            DB_ACTION);
659             }
660         }
661     } catch (const Xapian::Error &error) {
662         if (! notmuch->exception_reported) {
663             _notmuch_database_log (notmuch, "Error: A Xapian exception reopening database: %s\n",
664                                    error.get_msg ().c_str ());
665             notmuch->exception_reported = true;
666         }
667         return NOTMUCH_STATUS_XAPIAN_EXCEPTION;
668     }
669
670     notmuch->view++;
671     notmuch->open = true;
672     return NOTMUCH_STATUS_SUCCESS;
673 }