]> git.notmuchmail.org Git - notmuch/blob - lib/open.cc
lib/open: remove incorrect unused attribute
[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 (const char *config_path,
157                        const char *profile,
158                        GKeyFile **key_file,
159                        const char **database_path,
160                        char **message)
161 {
162     notmuch_status_t status;
163
164     status =_load_key_file (config_path, profile, key_file);
165     if (status) {
166         *message = strdup ("Error: cannot load config file.\n");
167         return status;
168     }
169
170     if (! *database_path && *key_file)
171         *database_path = g_key_file_get_value (*key_file, "database", "path", NULL);
172
173     if (*database_path == NULL) {
174         *message = strdup ("Error: Cannot open a database for a NULL path.\n");
175         return NOTMUCH_STATUS_NULL_POINTER;
176     }
177
178     if (*database_path[0] != '/') {
179         *message = strdup ("Error: Database path must be absolute.\n");
180         return NOTMUCH_STATUS_PATH_ERROR;
181     }
182     return NOTMUCH_STATUS_SUCCESS;
183 }
184
185 notmuch_status_t
186 notmuch_database_open_with_config (const char *database_path,
187                                    notmuch_database_mode_t mode,
188                                    const char *config_path,
189                                    const char *profile,
190                                    notmuch_database_t **database,
191                                    char **status_string)
192 {
193     notmuch_status_t status = NOTMUCH_STATUS_SUCCESS;
194     void *local = talloc_new (NULL);
195     notmuch_database_t *notmuch = NULL;
196     char *notmuch_path, *xapian_path, *incompat_features;
197     char *message = NULL;
198     struct stat st;
199     int err;
200     unsigned int version;
201     GKeyFile *key_file = NULL;
202     static int initialized = 0;
203
204     if ((status = _choose_database_path (config_path, profile, &key_file, &database_path, &message)))
205         goto DONE;
206
207     if (! (notmuch_path = talloc_asprintf (local, "%s/%s", database_path, ".notmuch"))) {
208         message = strdup ("Out of memory\n");
209         status = NOTMUCH_STATUS_OUT_OF_MEMORY;
210         goto DONE;
211     }
212
213     err = stat (notmuch_path, &st);
214     if (err) {
215         IGNORE_RESULT (asprintf (&message, "Error opening database at %s: %s\n",
216                                  notmuch_path, strerror (errno)));
217         status = NOTMUCH_STATUS_FILE_ERROR;
218         goto DONE;
219     }
220
221     if (! (xapian_path = talloc_asprintf (local, "%s/%s", notmuch_path, "xapian"))) {
222         message = strdup ("Out of memory\n");
223         status = NOTMUCH_STATUS_OUT_OF_MEMORY;
224         goto DONE;
225     }
226
227     /* Initialize the GLib type system and threads */
228 #if ! GLIB_CHECK_VERSION (2, 35, 1)
229     g_type_init ();
230 #endif
231
232     /* Initialize gmime */
233     if (! initialized) {
234         g_mime_init ();
235         initialized = 1;
236     }
237
238     notmuch = talloc_zero (NULL, notmuch_database_t);
239     notmuch->exception_reported = false;
240     notmuch->status_string = NULL;
241     notmuch->path = talloc_strdup (notmuch, database_path);
242
243     strip_trailing (notmuch->path, '/');
244
245     notmuch->writable_xapian_db = NULL;
246     notmuch->atomic_nesting = 0;
247     notmuch->view = 1;
248     try {
249         std::string last_thread_id;
250         std::string last_mod;
251
252         if (mode == NOTMUCH_DATABASE_MODE_READ_WRITE) {
253             notmuch->writable_xapian_db = new Xapian::WritableDatabase (xapian_path,
254                                                                         DB_ACTION);
255             notmuch->xapian_db = notmuch->writable_xapian_db;
256         } else {
257             notmuch->xapian_db = new Xapian::Database (xapian_path);
258         }
259
260         /* Check version.  As of database version 3, we represent
261          * changes in terms of features, so assume a version bump
262          * means a dramatically incompatible change. */
263         version = notmuch_database_get_version (notmuch);
264         if (version > NOTMUCH_DATABASE_VERSION) {
265             IGNORE_RESULT (asprintf (&message,
266                                      "Error: Notmuch database at %s\n"
267                                      "       has a newer database format version (%u) than supported by this\n"
268                                      "       version of notmuch (%u).\n",
269                                      notmuch_path, version, NOTMUCH_DATABASE_VERSION));
270             notmuch_database_destroy (notmuch);
271             notmuch = NULL;
272             status = NOTMUCH_STATUS_FILE_ERROR;
273             goto DONE;
274         }
275
276         /* Check features. */
277         incompat_features = NULL;
278         notmuch->features = _notmuch_database_parse_features (
279             local, notmuch->xapian_db->get_metadata ("features").c_str (),
280             version, mode == NOTMUCH_DATABASE_MODE_READ_WRITE ? 'w' : 'r',
281             &incompat_features);
282         if (incompat_features) {
283             IGNORE_RESULT (asprintf (&message,
284                                      "Error: Notmuch database at %s\n"
285                                      "       requires features (%s)\n"
286                                      "       not supported by this version of notmuch.\n",
287                                      notmuch_path, incompat_features));
288             notmuch_database_destroy (notmuch);
289             notmuch = NULL;
290             status = NOTMUCH_STATUS_FILE_ERROR;
291             goto DONE;
292         }
293
294         notmuch->last_doc_id = notmuch->xapian_db->get_lastdocid ();
295         last_thread_id = notmuch->xapian_db->get_metadata ("last_thread_id");
296         if (last_thread_id.empty ()) {
297             notmuch->last_thread_id = 0;
298         } else {
299             const char *str;
300             char *end;
301
302             str = last_thread_id.c_str ();
303             notmuch->last_thread_id = strtoull (str, &end, 16);
304             if (*end != '\0')
305                 INTERNAL_ERROR ("Malformed database last_thread_id: %s", str);
306         }
307
308         /* Get current highest revision number. */
309         last_mod = notmuch->xapian_db->get_value_upper_bound (
310             NOTMUCH_VALUE_LAST_MOD);
311         if (last_mod.empty ())
312             notmuch->revision = 0;
313         else
314             notmuch->revision = Xapian::sortable_unserialise (last_mod);
315         notmuch->uuid = talloc_strdup (
316             notmuch, notmuch->xapian_db->get_uuid ().c_str ());
317
318         notmuch->query_parser = new Xapian::QueryParser;
319         notmuch->term_gen = new Xapian::TermGenerator;
320         notmuch->term_gen->set_stemmer (Xapian::Stem ("english"));
321         notmuch->value_range_processor = new Xapian::NumberRangeProcessor (NOTMUCH_VALUE_TIMESTAMP);
322         notmuch->date_range_processor = new ParseTimeRangeProcessor (NOTMUCH_VALUE_TIMESTAMP, "date:");
323         notmuch->last_mod_range_processor = new Xapian::NumberRangeProcessor (NOTMUCH_VALUE_LAST_MOD, "lastmod:");
324         notmuch->query_parser->set_default_op (Xapian::Query::OP_AND);
325         notmuch->query_parser->set_database (*notmuch->xapian_db);
326         notmuch->query_parser->set_stemmer (Xapian::Stem ("english"));
327         notmuch->query_parser->set_stemming_strategy (Xapian::QueryParser::STEM_SOME);
328         notmuch->query_parser->add_rangeprocessor (notmuch->value_range_processor);
329         notmuch->query_parser->add_rangeprocessor (notmuch->date_range_processor);
330         notmuch->query_parser->add_rangeprocessor (notmuch->last_mod_range_processor);
331
332         /* Configuration information is needed to set up query parser */
333         status = _notmuch_config_load_from_database (notmuch);
334         if (status)
335             goto DONE;
336
337         if (key_file)
338             status = _notmuch_config_load_from_file (notmuch, key_file);
339         if (status)
340             goto DONE;
341
342         status = _choose_hook_dir (notmuch, profile, &message);
343         if (status)
344             goto DONE;
345
346         status = _notmuch_config_load_defaults (notmuch);
347         if (status)
348             goto DONE;
349
350         status = _notmuch_database_setup_standard_query_fields (notmuch);
351         if (status)
352             goto DONE;
353
354         status = _notmuch_database_setup_user_query_fields (notmuch);
355         if (status)
356             goto DONE;
357
358     } catch (const Xapian::Error &error) {
359         IGNORE_RESULT (asprintf (&message, "A Xapian exception occurred opening database: %s\n",
360                                  error.get_msg ().c_str ()));
361         notmuch_database_destroy (notmuch);
362         notmuch = NULL;
363         status = NOTMUCH_STATUS_XAPIAN_EXCEPTION;
364     }
365
366   DONE:
367     talloc_free (local);
368
369     if (message) {
370         if (status_string)
371             *status_string = message;
372         else
373             free (message);
374     }
375
376     if (database)
377         *database = notmuch;
378     else
379         talloc_free (notmuch);
380
381     if (notmuch)
382         notmuch->open = true;
383
384     return status;
385 }
386
387 notmuch_status_t
388 notmuch_database_create (const char *path, notmuch_database_t **database)
389 {
390     char *status_string = NULL;
391     notmuch_status_t status;
392
393     status = notmuch_database_create_verbose (path, database,
394                                               &status_string);
395
396     if (status_string) {
397         fputs (status_string, stderr);
398         free (status_string);
399     }
400
401     return status;
402 }
403
404 notmuch_status_t
405 notmuch_database_create_verbose (const char *path,
406                                  notmuch_database_t **database,
407                                  char **status_string)
408 {
409     return notmuch_database_create_with_config (path, "", NULL, database, status_string);
410 }
411
412 notmuch_status_t
413 notmuch_database_create_with_config (const char *database_path,
414                                      const char *config_path,
415                                      const char *profile,
416                                      notmuch_database_t **database,
417                                      char **status_string)
418 {
419     notmuch_status_t status = NOTMUCH_STATUS_SUCCESS;
420     notmuch_database_t *notmuch = NULL;
421     char *notmuch_path = NULL;
422     char *message = NULL;
423     GKeyFile *key_file = NULL;
424     struct stat st;
425     int err;
426
427     if ((status = _choose_database_path (config_path, profile, &key_file, &database_path, &message)))
428         goto DONE;
429
430     err = stat (database_path, &st);
431     if (err) {
432         IGNORE_RESULT (asprintf (&message, "Error: Cannot create database at %s: %s.\n",
433                                  database_path, strerror (errno)));
434         status = NOTMUCH_STATUS_FILE_ERROR;
435         goto DONE;
436     }
437
438     if (! S_ISDIR (st.st_mode)) {
439         IGNORE_RESULT (asprintf (&message, "Error: Cannot create database at %s: "
440                                  "Not a directory.\n",
441                                  database_path));
442         status = NOTMUCH_STATUS_FILE_ERROR;
443         goto DONE;
444     }
445
446     notmuch_path = talloc_asprintf (NULL, "%s/%s", database_path, ".notmuch");
447
448     err = mkdir (notmuch_path, 0755);
449     if (err) {
450         if (errno == EEXIST) {
451             status = NOTMUCH_STATUS_DATABASE_EXISTS;
452         } else {
453             IGNORE_RESULT (asprintf (&message, "Error: Cannot create directory %s: %s.\n",
454                                      notmuch_path, strerror (errno)));
455             status = NOTMUCH_STATUS_FILE_ERROR;
456         }
457         goto DONE;
458     }
459
460     /* XXX this reads the config file twice, which is a bit wasteful */
461     status = notmuch_database_open_with_config (database_path,
462                                                 NOTMUCH_DATABASE_MODE_READ_WRITE,
463                                                 config_path,
464                                                 profile,
465                                                 &notmuch, &message);
466     if (status)
467         goto DONE;
468
469     /* Upgrade doesn't add these feature to existing databases, but
470      * new databases have them. */
471     notmuch->features |= NOTMUCH_FEATURE_FROM_SUBJECT_ID_VALUES;
472     notmuch->features |= NOTMUCH_FEATURE_INDEXED_MIMETYPES;
473     notmuch->features |= NOTMUCH_FEATURE_UNPREFIX_BODY_ONLY;
474
475     status = notmuch_database_upgrade (notmuch, NULL, NULL);
476     if (status) {
477         notmuch_database_close (notmuch);
478         notmuch = NULL;
479     }
480
481   DONE:
482     if (notmuch_path)
483         talloc_free (notmuch_path);
484
485     if (message) {
486         if (status_string)
487             *status_string = message;
488         else
489             free (message);
490     }
491     if (database)
492         *database = notmuch;
493     else
494         talloc_free (notmuch);
495     return status;
496 }