]> git.notmuchmail.org Git - notmuch/blob - lib/database.cc
lib: factor out prefix related code to its own file
[notmuch] / lib / database.cc
1 /* database.cc - The database interfaces of the notmuch mail library
2  *
3  * Copyright © 2009 Carl Worth
4  *
5  * This program is free software: you can redistribute it and/or modify
6  * it under the terms of the GNU General Public License as published by
7  * the Free Software Foundation, either version 3 of the License, or
8  * (at your option) any later version.
9  *
10  * This program is distributed in the hope that it will be useful,
11  * but WITHOUT ANY WARRANTY; without even the implied warranty of
12  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
13  * GNU General Public License for more details.
14  *
15  * You should have received a copy of the GNU General Public License
16  * along with this program.  If not, see https://www.gnu.org/licenses/ .
17  *
18  * Author: Carl Worth <cworth@cworth.org>
19  */
20
21 #include "database-private.h"
22 #include "parse-time-vrp.h"
23 #include "query-fp.h"
24 #include "thread-fp.h"
25 #include "regexp-fields.h"
26 #include "string-util.h"
27
28 #include <iostream>
29
30 #include <sys/time.h>
31 #include <sys/stat.h>
32 #include <signal.h>
33 #include <ftw.h>
34
35 #include <glib.h>               /* g_free, GPtrArray, GHashTable */
36 #include <glib-object.h>        /* g_type_init */
37
38 #include <gmime/gmime.h>        /* g_mime_init */
39
40 using namespace std;
41
42 typedef struct {
43     const char *name;
44     const char *prefix;
45     notmuch_field_flag_t flags;
46 } prefix_t;
47
48 #define NOTMUCH_DATABASE_VERSION 3
49
50 #define STRINGIFY(s) _SUB_STRINGIFY (s)
51 #define _SUB_STRINGIFY(s) #s
52
53 #if HAVE_XAPIAN_DB_RETRY_LOCK
54 #define DB_ACTION (Xapian::DB_CREATE_OR_OPEN | Xapian::DB_RETRY_LOCK)
55 #else
56 #define DB_ACTION Xapian::DB_CREATE_OR_OPEN
57 #endif
58
59 #define LOG_XAPIAN_EXCEPTION(message, error) _log_xapian_exception (__location__, message, error)
60
61 static void
62 _log_xapian_exception (const char *where, notmuch_database_t *notmuch,  const Xapian::Error error) {
63     _notmuch_database_log (notmuch,
64                            "A Xapian exception occurred at %s: %s\n",
65                            where,
66                            error.get_msg ().c_str ());
67     notmuch->exception_reported = true;
68 }
69
70 notmuch_database_mode_t
71 _notmuch_database_mode (notmuch_database_t *notmuch)
72 {
73     if (notmuch->writable_xapian_db)
74         return NOTMUCH_DATABASE_MODE_READ_WRITE;
75     else
76         return NOTMUCH_DATABASE_MODE_READ_ONLY;
77 }
78
79 /* Here's the current schema for our database (for NOTMUCH_DATABASE_VERSION):
80  *
81  * We currently have three different types of documents (mail, ghost,
82  * and directory) and also some metadata.
83  *
84  * There are two kinds of prefixes used in notmuch. There are the
85  * human friendly 'prefix names' like "thread:", which are also used
86  * in the query parser, and the actual prefix terms in the database
87  * (e.g. "G"). The correspondence is maintained in the file scope data
88  * structure 'prefix_table'.
89  *
90  * Mail document
91  * -------------
92  * A mail document is associated with a particular email message. It
93  * is stored in one or more files on disk and is uniquely identified
94  * by its "id" field (which is generally the message ID). It is
95  * indexed with the following prefixed terms which the database uses
96  * to construct threads, etc.:
97  *
98  *    Single terms of given prefix:
99  *
100  *      type:   mail
101  *
102  *      id:     Unique ID of mail. This is from the Message-ID header
103  *              if present and not too long (see NOTMUCH_MESSAGE_ID_MAX).
104  *              If it's present and too long, then we use
105  *              "notmuch-sha1-<sha1_sum_of_message_id>".
106  *              If this header is not present, we use
107  *              "notmuch-sha1-<sha1_sum_of_entire_file>".
108  *
109  *      thread: The ID of the thread to which the mail belongs
110  *
111  *      replyto: The ID from the In-Reply-To header of the mail (if any).
112  *
113  *    Multiple terms of given prefix:
114  *
115  *      reference: All message IDs from In-Reply-To and References
116  *                 headers in the message.
117  *
118  *      tag:       Any tags associated with this message by the user.
119  *
120  *      file-direntry:  A colon-separated pair of values
121  *                      (INTEGER:STRING), where INTEGER is the
122  *                      document ID of a directory document, and
123  *                      STRING is the name of a file within that
124  *                      directory for this mail message.
125  *
126  *      property:       Has a property with key=value
127  *                 FIXME: if no = is present, should match on any value
128  *
129  *    A mail document also has four values:
130  *
131  *      TIMESTAMP:      The time_t value corresponding to the message's
132  *                      Date header.
133  *
134  *      MESSAGE_ID:     The unique ID of the mail mess (see "id" above)
135  *
136  *      FROM:           The value of the "From" header
137  *
138  *      SUBJECT:        The value of the "Subject" header
139  *
140  *      LAST_MOD:       The revision number as of the last tag or
141  *                      filename change.
142  *
143  * The prefixed terms described above are also searchable without an
144  * explicit field name, but as of notmuch 0.29 this is due to
145  * query-parser setup, not extra terms in the database.  In addition,
146  * terms from the content of the message are added without a prefix
147  * for use by the user in searching. Note that the prefix name "body"
148  * is used to refer to the empty prefix string in the database.
149  *
150  * The path of the containing folder is added with the "folder" prefix
151  * (see _notmuch_message_add_folder_terms).  Sub-paths of the the path
152  * of the mail message are added with the "path" prefix.
153  *
154  * The data portion of a mail document is empty.
155  *
156  * Ghost mail document [if NOTMUCH_FEATURE_GHOSTS]
157  * -----------------------------------------------
158  * A ghost mail document is like a mail document, but where we don't
159  * have the message content.  These are used to track thread reference
160  * information for messages we haven't received.
161  *
162  * A ghost mail document has type: ghost; id and thread fields that
163  * are identical to the mail document fields; and a MESSAGE_ID value.
164  *
165  * Directory document
166  * ------------------
167  * A directory document is used by a client of the notmuch library to
168  * maintain data necessary to allow for efficient polling of mail
169  * directories.
170  *
171  * All directory documents contain one term:
172  *
173  *      directory:      The directory path (relative to the database path)
174  *                      Or the SHA1 sum of the directory path (if the
175  *                      path itself is too long to fit in a Xapian
176  *                      term).
177  *
178  * And all directory documents for directories other than top-level
179  * directories also contain the following term:
180  *
181  *      directory-direntry: A colon-separated pair of values
182  *                          (INTEGER:STRING), where INTEGER is the
183  *                          document ID of the parent directory
184  *                          document, and STRING is the name of this
185  *                          directory within that parent.
186  *
187  * All directory documents have a single value:
188  *
189  *      TIMESTAMP:      The mtime of the directory (at last scan)
190  *
191  * The data portion of a directory document contains the path of the
192  * directory (relative to the database path).
193  *
194  * Database metadata
195  * -----------------
196  * Xapian allows us to store arbitrary name-value pairs as
197  * "metadata". We currently use the following metadata names with the
198  * given meanings:
199  *
200  *      version         The database schema version, (which is distinct
201  *                      from both the notmuch package version (see
202  *                      notmuch --version) and the libnotmuch library
203  *                      version. The version is stored as an base-10
204  *                      ASCII integer. The initial database version
205  *                      was 1, (though a schema existed before that
206  *                      were no "version" database value existed at
207  *                      all). Successive versions are allocated as
208  *                      changes are made to the database (such as by
209  *                      indexing new fields).
210  *
211  *      features        The set of features supported by this
212  *                      database. This consists of a set of
213  *                      '\n'-separated lines, where each is a feature
214  *                      name, a '\t', and compatibility flags.  If the
215  *                      compatibility flags contain 'w', then the
216  *                      opener must support this feature to safely
217  *                      write this database.  If the compatibility
218  *                      flags contain 'r', then the opener must
219  *                      support this feature to read this database.
220  *                      Introduced in database version 3.
221  *
222  *      last_thread_id  The last thread ID generated. This is stored
223  *                      as a 16-byte hexadecimal ASCII representation
224  *                      of a 64-bit unsigned integer. The first ID
225  *                      generated is 1 and the value will be
226  *                      incremented for each thread ID.
227  *
228  *      C*              metadata keys starting with C indicate
229  *                      configuration data. It can be managed with the
230  *                      n_database_*config* API.  There is a convention
231  *                      of hierarchical keys separated by '.' (e.g.
232  *                      query.notmuch stores the value for the named
233  *                      query 'notmuch'), but it is not enforced by the
234  *                      API.
235  *
236  * Obsolete metadata
237  * -----------------
238  *
239  * If ! NOTMUCH_FEATURE_GHOSTS, there are no ghost mail documents.
240  * Instead, the database has the following additional database
241  * metadata:
242  *
243  *      thread_id_*     A pre-allocated thread ID for a particular
244  *                      message. This is actually an arbitrarily large
245  *                      family of metadata name. Any particular name is
246  *                      formed by concatenating "thread_id_" with a message
247  *                      ID (or the SHA1 sum of a message ID if it is very
248  *                      long---see description of 'id' in the mail
249  *                      document). The value stored is a thread ID.
250  *
251  *                      These thread ID metadata values are stored
252  *                      whenever a message references a parent message
253  *                      that does not yet exist in the database. A
254  *                      thread ID will be allocated and stored, and if
255  *                      the message is later added, the stored thread
256  *                      ID will be used (and the metadata value will
257  *                      be cleared).
258  *
259  *                      Even before a message is added, it's
260  *                      pre-allocated thread ID is useful so that all
261  *                      descendant messages that reference this common
262  *                      parent can be recognized as belonging to the
263  *                      same thread.
264  */
265
266
267 notmuch_string_map_iterator_t *
268 _notmuch_database_user_headers (notmuch_database_t *notmuch)
269 {
270     return _notmuch_string_map_iterator_create (notmuch->user_header, "", false);
271 }
272
273 const char *
274 notmuch_status_to_string (notmuch_status_t status)
275 {
276     switch (status) {
277     case NOTMUCH_STATUS_SUCCESS:
278         return "No error occurred";
279     case NOTMUCH_STATUS_OUT_OF_MEMORY:
280         return "Out of memory";
281     case NOTMUCH_STATUS_READ_ONLY_DATABASE:
282         return "Attempt to write to a read-only database";
283     case NOTMUCH_STATUS_XAPIAN_EXCEPTION:
284         return "A Xapian exception occurred";
285     case NOTMUCH_STATUS_FILE_ERROR:
286         return "Something went wrong trying to read or write a file";
287     case NOTMUCH_STATUS_FILE_NOT_EMAIL:
288         return "File is not an email";
289     case NOTMUCH_STATUS_DUPLICATE_MESSAGE_ID:
290         return "Message ID is identical to a message in database";
291     case NOTMUCH_STATUS_NULL_POINTER:
292         return "Erroneous NULL pointer";
293     case NOTMUCH_STATUS_TAG_TOO_LONG:
294         return "Tag value is too long (exceeds NOTMUCH_TAG_MAX)";
295     case NOTMUCH_STATUS_UNBALANCED_FREEZE_THAW:
296         return "Unbalanced number of calls to notmuch_message_freeze/thaw";
297     case NOTMUCH_STATUS_UNBALANCED_ATOMIC:
298         return "Unbalanced number of calls to notmuch_database_begin_atomic/end_atomic";
299     case NOTMUCH_STATUS_UNSUPPORTED_OPERATION:
300         return "Unsupported operation";
301     case NOTMUCH_STATUS_UPGRADE_REQUIRED:
302         return "Operation requires a database upgrade";
303     case NOTMUCH_STATUS_PATH_ERROR:
304         return "Path supplied is illegal for this function";
305     case NOTMUCH_STATUS_MALFORMED_CRYPTO_PROTOCOL:
306         return "Crypto protocol missing, malformed, or unintelligible";
307     case NOTMUCH_STATUS_FAILED_CRYPTO_CONTEXT_CREATION:
308         return "Crypto engine initialization failure";
309     case NOTMUCH_STATUS_UNKNOWN_CRYPTO_PROTOCOL:
310         return "Unknown crypto protocol";
311     default:
312     case NOTMUCH_STATUS_LAST_STATUS:
313         return "Unknown error status value";
314     }
315 }
316
317 void
318 _notmuch_database_log (notmuch_database_t *notmuch,
319                        const char *format,
320                        ...)
321 {
322     va_list va_args;
323
324     va_start (va_args, format);
325
326     if (notmuch->status_string)
327         talloc_free (notmuch->status_string);
328
329     notmuch->status_string = talloc_vasprintf (notmuch, format, va_args);
330     va_end (va_args);
331 }
332
333 void
334 _notmuch_database_log_append (notmuch_database_t *notmuch,
335                               const char *format,
336                               ...)
337 {
338     va_list va_args;
339
340     va_start (va_args, format);
341
342     if (notmuch->status_string)
343         notmuch->status_string = talloc_vasprintf_append (notmuch->status_string, format, va_args);
344     else
345         notmuch->status_string = talloc_vasprintf (notmuch, format, va_args);
346
347     va_end (va_args);
348 }
349
350 static void
351 find_doc_ids_for_term (notmuch_database_t *notmuch,
352                        const char *term,
353                        Xapian::PostingIterator *begin,
354                        Xapian::PostingIterator *end)
355 {
356     *begin = notmuch->xapian_db->postlist_begin (term);
357
358     *end = notmuch->xapian_db->postlist_end (term);
359 }
360
361 void
362 _notmuch_database_find_doc_ids (notmuch_database_t *notmuch,
363                                 const char *prefix_name,
364                                 const char *value,
365                                 Xapian::PostingIterator *begin,
366                                 Xapian::PostingIterator *end)
367 {
368     char *term;
369
370     term = talloc_asprintf (notmuch, "%s%s",
371                             _find_prefix (prefix_name), value);
372
373     find_doc_ids_for_term (notmuch, term, begin, end);
374
375     talloc_free (term);
376 }
377
378 notmuch_private_status_t
379 _notmuch_database_find_unique_doc_id (notmuch_database_t *notmuch,
380                                       const char *prefix_name,
381                                       const char *value,
382                                       unsigned int *doc_id)
383 {
384     Xapian::PostingIterator i, end;
385
386     _notmuch_database_find_doc_ids (notmuch, prefix_name, value, &i, &end);
387
388     if (i == end) {
389         *doc_id = 0;
390         return NOTMUCH_PRIVATE_STATUS_NO_DOCUMENT_FOUND;
391     }
392
393     *doc_id = *i;
394
395 #if DEBUG_DATABASE_SANITY
396     i++;
397
398     if (i != end)
399         INTERNAL_ERROR ("Term %s:%s is not unique as expected.\n",
400                         prefix_name, value);
401 #endif
402
403     return NOTMUCH_PRIVATE_STATUS_SUCCESS;
404 }
405
406 static Xapian::Document
407 find_document_for_doc_id (notmuch_database_t *notmuch, unsigned doc_id)
408 {
409     return notmuch->xapian_db->get_document (doc_id);
410 }
411
412 /* Generate a compressed version of 'message_id' of the form:
413  *
414  *      notmuch-sha1-<sha1_sum_of_message_id>
415  */
416 char *
417 _notmuch_message_id_compressed (void *ctx, const char *message_id)
418 {
419     char *sha1, *compressed;
420
421     sha1 = _notmuch_sha1_of_string (message_id);
422
423     compressed = talloc_asprintf (ctx, "notmuch-sha1-%s", sha1);
424     free (sha1);
425
426     return compressed;
427 }
428
429 notmuch_status_t
430 notmuch_database_find_message (notmuch_database_t *notmuch,
431                                const char *message_id,
432                                notmuch_message_t **message_ret)
433 {
434     notmuch_private_status_t status;
435     unsigned int doc_id;
436
437     if (message_ret == NULL)
438         return NOTMUCH_STATUS_NULL_POINTER;
439
440     if (strlen (message_id) > NOTMUCH_MESSAGE_ID_MAX)
441         message_id = _notmuch_message_id_compressed (notmuch, message_id);
442
443     try {
444         status = _notmuch_database_find_unique_doc_id (notmuch, "id",
445                                                        message_id, &doc_id);
446
447         if (status == NOTMUCH_PRIVATE_STATUS_NO_DOCUMENT_FOUND)
448             *message_ret = NULL;
449         else {
450             *message_ret = _notmuch_message_create (notmuch, notmuch, doc_id,
451                                                     NULL);
452             if (*message_ret == NULL)
453                 return NOTMUCH_STATUS_OUT_OF_MEMORY;
454         }
455
456         return NOTMUCH_STATUS_SUCCESS;
457     } catch (const Xapian::Error &error) {
458         _notmuch_database_log (notmuch, "A Xapian exception occurred finding message: %s.\n",
459                                error.get_msg ().c_str ());
460         notmuch->exception_reported = true;
461         *message_ret = NULL;
462         return NOTMUCH_STATUS_XAPIAN_EXCEPTION;
463     }
464 }
465
466 notmuch_status_t
467 notmuch_database_create (const char *path, notmuch_database_t **database)
468 {
469     char *status_string = NULL;
470     notmuch_status_t status;
471
472     status = notmuch_database_create_verbose (path, database,
473                                               &status_string);
474
475     if (status_string) {
476         fputs (status_string, stderr);
477         free (status_string);
478     }
479
480     return status;
481 }
482
483 notmuch_status_t
484 notmuch_database_create_verbose (const char *path,
485                                  notmuch_database_t **database,
486                                  char **status_string)
487 {
488     notmuch_status_t status = NOTMUCH_STATUS_SUCCESS;
489     notmuch_database_t *notmuch = NULL;
490     char *notmuch_path = NULL;
491     char *message = NULL;
492     struct stat st;
493     int err;
494
495     if (path == NULL) {
496         message = strdup ("Error: Cannot create a database for a NULL path.\n");
497         status = NOTMUCH_STATUS_NULL_POINTER;
498         goto DONE;
499     }
500
501     if (path[0] != '/') {
502         message = strdup ("Error: Database path must be absolute.\n");
503         status = NOTMUCH_STATUS_PATH_ERROR;
504         goto DONE;
505     }
506
507     err = stat (path, &st);
508     if (err) {
509         IGNORE_RESULT (asprintf (&message, "Error: Cannot create database at %s: %s.\n",
510                                  path, strerror (errno)));
511         status = NOTMUCH_STATUS_FILE_ERROR;
512         goto DONE;
513     }
514
515     if (! S_ISDIR (st.st_mode)) {
516         IGNORE_RESULT (asprintf (&message, "Error: Cannot create database at %s: "
517                                  "Not a directory.\n",
518                                  path));
519         status = NOTMUCH_STATUS_FILE_ERROR;
520         goto DONE;
521     }
522
523     notmuch_path = talloc_asprintf (NULL, "%s/%s", path, ".notmuch");
524
525     err = mkdir (notmuch_path, 0755);
526
527     if (err) {
528         IGNORE_RESULT (asprintf (&message, "Error: Cannot create directory %s: %s.\n",
529                                  notmuch_path, strerror (errno)));
530         status = NOTMUCH_STATUS_FILE_ERROR;
531         goto DONE;
532     }
533
534     status = notmuch_database_open_verbose (path,
535                                             NOTMUCH_DATABASE_MODE_READ_WRITE,
536                                             &notmuch, &message);
537     if (status)
538         goto DONE;
539
540     /* Upgrade doesn't add these feature to existing databases, but
541      * new databases have them. */
542     notmuch->features |= NOTMUCH_FEATURE_FROM_SUBJECT_ID_VALUES;
543     notmuch->features |= NOTMUCH_FEATURE_INDEXED_MIMETYPES;
544     notmuch->features |= NOTMUCH_FEATURE_UNPREFIX_BODY_ONLY;
545
546     status = notmuch_database_upgrade (notmuch, NULL, NULL);
547     if (status) {
548         notmuch_database_close (notmuch);
549         notmuch = NULL;
550     }
551
552   DONE:
553     if (notmuch_path)
554         talloc_free (notmuch_path);
555
556     if (message) {
557         if (status_string)
558             *status_string = message;
559         else
560             free (message);
561     }
562     if (database)
563         *database = notmuch;
564     else
565         talloc_free (notmuch);
566     return status;
567 }
568
569 notmuch_status_t
570 _notmuch_database_ensure_writable (notmuch_database_t *notmuch)
571 {
572     if (_notmuch_database_mode (notmuch) == NOTMUCH_DATABASE_MODE_READ_ONLY) {
573         _notmuch_database_log (notmuch, "Cannot write to a read-only database.\n");
574         return NOTMUCH_STATUS_READ_ONLY_DATABASE;
575     }
576
577     return NOTMUCH_STATUS_SUCCESS;
578 }
579
580 /* Allocate a revision number for the next change. */
581 unsigned long
582 _notmuch_database_new_revision (notmuch_database_t *notmuch)
583 {
584     unsigned long new_revision = notmuch->revision + 1;
585
586     /* If we're in an atomic section, hold off on updating the
587      * committed revision number until we commit the atomic section.
588      */
589     if (notmuch->atomic_nesting)
590         notmuch->atomic_dirty = true;
591     else
592         notmuch->revision = new_revision;
593
594     return new_revision;
595 }
596
597 notmuch_status_t
598 notmuch_database_open (const char *path,
599                        notmuch_database_mode_t mode,
600                        notmuch_database_t **database)
601 {
602     char *status_string = NULL;
603     notmuch_status_t status;
604
605     status = notmuch_database_open_verbose (path, mode, database,
606                                             &status_string);
607
608     if (status_string) {
609         fputs (status_string, stderr);
610         free (status_string);
611     }
612
613     return status;
614 }
615
616 notmuch_status_t
617 notmuch_database_open_verbose (const char *path,
618                                notmuch_database_mode_t mode,
619                                notmuch_database_t **database,
620                                char **status_string)
621 {
622     notmuch_status_t status = NOTMUCH_STATUS_SUCCESS;
623     void *local = talloc_new (NULL);
624     notmuch_database_t *notmuch = NULL;
625     char *notmuch_path, *xapian_path, *incompat_features;
626     char *message = NULL;
627     struct stat st;
628     int err;
629     unsigned int i, version;
630     static int initialized = 0;
631
632     if (path == NULL) {
633         message = strdup ("Error: Cannot open a database for a NULL path.\n");
634         status = NOTMUCH_STATUS_NULL_POINTER;
635         goto DONE;
636     }
637
638     if (path[0] != '/') {
639         message = strdup ("Error: Database path must be absolute.\n");
640         status = NOTMUCH_STATUS_PATH_ERROR;
641         goto DONE;
642     }
643
644     if (! (notmuch_path = talloc_asprintf (local, "%s/%s", path, ".notmuch"))) {
645         message = strdup ("Out of memory\n");
646         status = NOTMUCH_STATUS_OUT_OF_MEMORY;
647         goto DONE;
648     }
649
650     err = stat (notmuch_path, &st);
651     if (err) {
652         IGNORE_RESULT (asprintf (&message, "Error opening database at %s: %s\n",
653                                  notmuch_path, strerror (errno)));
654         status = NOTMUCH_STATUS_FILE_ERROR;
655         goto DONE;
656     }
657
658     if (! (xapian_path = talloc_asprintf (local, "%s/%s", notmuch_path, "xapian"))) {
659         message = strdup ("Out of memory\n");
660         status = NOTMUCH_STATUS_OUT_OF_MEMORY;
661         goto DONE;
662     }
663
664     /* Initialize the GLib type system and threads */
665 #if ! GLIB_CHECK_VERSION (2, 35, 1)
666     g_type_init ();
667 #endif
668
669     /* Initialize gmime */
670     if (! initialized) {
671         g_mime_init ();
672         initialized = 1;
673     }
674
675     notmuch = talloc_zero (NULL, notmuch_database_t);
676     notmuch->exception_reported = false;
677     notmuch->status_string = NULL;
678     notmuch->path = talloc_strdup (notmuch, path);
679
680     strip_trailing (notmuch->path, '/');
681
682     notmuch->writable_xapian_db = NULL;
683     notmuch->atomic_nesting = 0;
684     notmuch->view = 1;
685     try {
686         string last_thread_id;
687         string last_mod;
688
689         if (mode == NOTMUCH_DATABASE_MODE_READ_WRITE) {
690             notmuch->writable_xapian_db = new Xapian::WritableDatabase (xapian_path,
691                                                                         DB_ACTION);
692             notmuch->xapian_db = notmuch->writable_xapian_db;
693         } else {
694             notmuch->xapian_db = new Xapian::Database (xapian_path);
695         }
696
697         /* Check version.  As of database version 3, we represent
698          * changes in terms of features, so assume a version bump
699          * means a dramatically incompatible change. */
700         version = notmuch_database_get_version (notmuch);
701         if (version > NOTMUCH_DATABASE_VERSION) {
702             IGNORE_RESULT (asprintf (&message,
703                                      "Error: Notmuch database at %s\n"
704                                      "       has a newer database format version (%u) than supported by this\n"
705                                      "       version of notmuch (%u).\n",
706                                      notmuch_path, version, NOTMUCH_DATABASE_VERSION));
707             notmuch_database_destroy (notmuch);
708             notmuch = NULL;
709             status = NOTMUCH_STATUS_FILE_ERROR;
710             goto DONE;
711         }
712
713         /* Check features. */
714         incompat_features = NULL;
715         notmuch->features = _notmuch_database_parse_features (
716             local, notmuch->xapian_db->get_metadata ("features").c_str (),
717             version, mode == NOTMUCH_DATABASE_MODE_READ_WRITE ? 'w' : 'r',
718             &incompat_features);
719         if (incompat_features) {
720             IGNORE_RESULT (asprintf (&message,
721                                      "Error: Notmuch database at %s\n"
722                                      "       requires features (%s)\n"
723                                      "       not supported by this version of notmuch.\n",
724                                      notmuch_path, incompat_features));
725             notmuch_database_destroy (notmuch);
726             notmuch = NULL;
727             status = NOTMUCH_STATUS_FILE_ERROR;
728             goto DONE;
729         }
730
731         notmuch->last_doc_id = notmuch->xapian_db->get_lastdocid ();
732         last_thread_id = notmuch->xapian_db->get_metadata ("last_thread_id");
733         if (last_thread_id.empty ()) {
734             notmuch->last_thread_id = 0;
735         } else {
736             const char *str;
737             char *end;
738
739             str = last_thread_id.c_str ();
740             notmuch->last_thread_id = strtoull (str, &end, 16);
741             if (*end != '\0')
742                 INTERNAL_ERROR ("Malformed database last_thread_id: %s", str);
743         }
744
745         /* Get current highest revision number. */
746         last_mod = notmuch->xapian_db->get_value_upper_bound (
747             NOTMUCH_VALUE_LAST_MOD);
748         if (last_mod.empty ())
749             notmuch->revision = 0;
750         else
751             notmuch->revision = Xapian::sortable_unserialise (last_mod);
752         notmuch->uuid = talloc_strdup (
753             notmuch, notmuch->xapian_db->get_uuid ().c_str ());
754
755         notmuch->query_parser = new Xapian::QueryParser;
756         notmuch->term_gen = new Xapian::TermGenerator;
757         notmuch->term_gen->set_stemmer (Xapian::Stem ("english"));
758         notmuch->value_range_processor = new Xapian::NumberRangeProcessor (NOTMUCH_VALUE_TIMESTAMP);
759         notmuch->date_range_processor = new ParseTimeRangeProcessor (NOTMUCH_VALUE_TIMESTAMP, "date:");
760         notmuch->last_mod_range_processor = new Xapian::NumberRangeProcessor (NOTMUCH_VALUE_LAST_MOD, "lastmod:");
761         notmuch->query_parser->set_default_op (Xapian::Query::OP_AND);
762         notmuch->query_parser->set_database (*notmuch->xapian_db);
763         notmuch->query_parser->set_stemmer (Xapian::Stem ("english"));
764         notmuch->query_parser->set_stemming_strategy (Xapian::QueryParser::STEM_SOME);
765         notmuch->query_parser->add_rangeprocessor (notmuch->value_range_processor);
766         notmuch->query_parser->add_rangeprocessor (notmuch->date_range_processor);
767         notmuch->query_parser->add_rangeprocessor (notmuch->last_mod_range_processor);
768
769         status = _notmuch_database_setup_standard_query_fields (notmuch);
770         if (status)
771             goto DONE;
772
773         status = _notmuch_database_setup_user_query_fields (notmuch);
774         if (status)
775             goto DONE;
776
777     } catch (const Xapian::Error &error) {
778         IGNORE_RESULT (asprintf (&message, "A Xapian exception occurred opening database: %s\n",
779                                  error.get_msg ().c_str ()));
780         notmuch_database_destroy (notmuch);
781         notmuch = NULL;
782         status = NOTMUCH_STATUS_XAPIAN_EXCEPTION;
783     }
784
785   DONE:
786     talloc_free (local);
787
788     if (message) {
789         if (status_string)
790             *status_string = message;
791         else
792             free (message);
793     }
794
795     if (database)
796         *database = notmuch;
797     else
798         talloc_free (notmuch);
799
800     if (notmuch)
801         notmuch->open = true;
802
803     return status;
804 }
805
806 notmuch_status_t
807 notmuch_database_close (notmuch_database_t *notmuch)
808 {
809     notmuch_status_t status = NOTMUCH_STATUS_SUCCESS;
810
811     /* Many Xapian objects (and thus notmuch objects) hold references to
812      * the database, so merely deleting the database may not suffice to
813      * close it.  Thus, we explicitly close it here. */
814     if (notmuch->open) {
815         try {
816             /* If there's an outstanding transaction, it's unclear if
817              * closing the Xapian database commits everything up to
818              * that transaction, or may discard committed (but
819              * unflushed) transactions.  To be certain, explicitly
820              * cancel any outstanding transaction before closing. */
821             if (_notmuch_database_mode (notmuch) == NOTMUCH_DATABASE_MODE_READ_WRITE &&
822                 notmuch->atomic_nesting)
823                 notmuch->writable_xapian_db->cancel_transaction ();
824
825             /* Close the database.  This implicitly flushes
826              * outstanding changes. */
827             notmuch->xapian_db->close ();
828         } catch (const Xapian::Error &error) {
829             status = NOTMUCH_STATUS_XAPIAN_EXCEPTION;
830             if (! notmuch->exception_reported) {
831                 _notmuch_database_log (notmuch, "Error: A Xapian exception occurred closing database: %s\n",
832                                        error.get_msg ().c_str ());
833             }
834         }
835     }
836     notmuch->open = false;
837     return status;
838 }
839
840 notmuch_status_t
841 _notmuch_database_reopen (notmuch_database_t *notmuch)
842 {
843     if (_notmuch_database_mode (notmuch) != NOTMUCH_DATABASE_MODE_READ_ONLY)
844         return NOTMUCH_STATUS_UNSUPPORTED_OPERATION;
845
846     try {
847         notmuch->xapian_db->reopen ();
848     } catch (const Xapian::Error &error) {
849         if (! notmuch->exception_reported) {
850             _notmuch_database_log (notmuch, "Error: A Xapian exception reopening database: %s\n",
851                                    error.get_msg ().c_str ());
852             notmuch->exception_reported = true;
853         }
854         return NOTMUCH_STATUS_XAPIAN_EXCEPTION;
855     }
856
857     notmuch->view++;
858
859     return NOTMUCH_STATUS_SUCCESS;
860 }
861
862 static int
863 unlink_cb (const char *path,
864            unused (const struct stat *sb),
865            unused (int type),
866            unused (struct FTW *ftw))
867 {
868     return remove (path);
869 }
870
871 static int
872 rmtree (const char *path)
873 {
874     return nftw (path, unlink_cb, 64, FTW_DEPTH | FTW_PHYS);
875 }
876
877 class NotmuchCompactor : public Xapian::Compactor
878 {
879     notmuch_compact_status_cb_t status_cb;
880     void *status_closure;
881
882 public:
883     NotmuchCompactor(notmuch_compact_status_cb_t cb, void *closure) :
884         status_cb (cb), status_closure (closure)
885     {
886     }
887
888     virtual void
889     set_status (const std::string &table, const std::string &status)
890     {
891         char *msg;
892
893         if (status_cb == NULL)
894             return;
895
896         if (status.length () == 0)
897             msg = talloc_asprintf (NULL, "compacting table %s", table.c_str ());
898         else
899             msg = talloc_asprintf (NULL, "     %s", status.c_str ());
900
901         if (msg == NULL) {
902             return;
903         }
904
905         status_cb (msg, status_closure);
906         talloc_free (msg);
907     }
908 };
909
910 /* Compacts the given database, optionally saving the original database
911  * in backup_path. Additionally, a callback function can be provided to
912  * give the user feedback on the progress of the (likely long-lived)
913  * compaction process.
914  *
915  * The backup path must point to a directory on the same volume as the
916  * original database. Passing a NULL backup_path will result in the
917  * uncompacted database being deleted after compaction has finished.
918  * Note that the database write lock will be held during the
919  * compaction process to protect data integrity.
920  */
921 notmuch_status_t
922 notmuch_database_compact (const char *path,
923                           const char *backup_path,
924                           notmuch_compact_status_cb_t status_cb,
925                           void *closure)
926 {
927     void *local;
928     char *notmuch_path, *xapian_path, *compact_xapian_path;
929     notmuch_status_t ret = NOTMUCH_STATUS_SUCCESS;
930     notmuch_database_t *notmuch = NULL;
931     struct stat statbuf;
932     bool keep_backup;
933     char *message = NULL;
934
935     local = talloc_new (NULL);
936     if (! local)
937         return NOTMUCH_STATUS_OUT_OF_MEMORY;
938
939     ret = notmuch_database_open_verbose (path,
940                                          NOTMUCH_DATABASE_MODE_READ_WRITE,
941                                          &notmuch,
942                                          &message);
943     if (ret) {
944         if (status_cb) status_cb (message, closure);
945         goto DONE;
946     }
947
948     if (! (notmuch_path = talloc_asprintf (local, "%s/%s", path, ".notmuch"))) {
949         ret = NOTMUCH_STATUS_OUT_OF_MEMORY;
950         goto DONE;
951     }
952
953     if (! (xapian_path = talloc_asprintf (local, "%s/%s", notmuch_path, "xapian"))) {
954         ret = NOTMUCH_STATUS_OUT_OF_MEMORY;
955         goto DONE;
956     }
957
958     if (! (compact_xapian_path = talloc_asprintf (local, "%s.compact", xapian_path))) {
959         ret = NOTMUCH_STATUS_OUT_OF_MEMORY;
960         goto DONE;
961     }
962
963     if (backup_path == NULL) {
964         if (! (backup_path = talloc_asprintf (local, "%s.old", xapian_path))) {
965             ret = NOTMUCH_STATUS_OUT_OF_MEMORY;
966             goto DONE;
967         }
968         keep_backup = false;
969     } else {
970         keep_backup = true;
971     }
972
973     if (stat (backup_path, &statbuf) != -1) {
974         _notmuch_database_log (notmuch, "Path already exists: %s\n", backup_path);
975         ret = NOTMUCH_STATUS_FILE_ERROR;
976         goto DONE;
977     }
978     if (errno != ENOENT) {
979         _notmuch_database_log (notmuch, "Unknown error while stat()ing path: %s\n",
980                                strerror (errno));
981         ret = NOTMUCH_STATUS_FILE_ERROR;
982         goto DONE;
983     }
984
985     /* Unconditionally attempt to remove old work-in-progress database (if
986      * any). This is "protected" by database lock. If this fails due to write
987      * errors (etc), the following code will fail and provide error message.
988      */
989     (void) rmtree (compact_xapian_path);
990
991     try {
992         NotmuchCompactor compactor (status_cb, closure);
993         notmuch->xapian_db->compact (compact_xapian_path, Xapian::DBCOMPACT_NO_RENUMBER, 0, compactor);
994     } catch (const Xapian::Error &error) {
995         _notmuch_database_log (notmuch, "Error while compacting: %s\n", error.get_msg ().c_str ());
996         ret = NOTMUCH_STATUS_XAPIAN_EXCEPTION;
997         goto DONE;
998     }
999
1000     if (rename (xapian_path, backup_path)) {
1001         _notmuch_database_log (notmuch, "Error moving %s to %s: %s\n",
1002                                xapian_path, backup_path, strerror (errno));
1003         ret = NOTMUCH_STATUS_FILE_ERROR;
1004         goto DONE;
1005     }
1006
1007     if (rename (compact_xapian_path, xapian_path)) {
1008         _notmuch_database_log (notmuch, "Error moving %s to %s: %s\n",
1009                                compact_xapian_path, xapian_path, strerror (errno));
1010         ret = NOTMUCH_STATUS_FILE_ERROR;
1011         goto DONE;
1012     }
1013
1014     if (! keep_backup) {
1015         if (rmtree (backup_path)) {
1016             _notmuch_database_log (notmuch, "Error removing old database %s: %s\n",
1017                                    backup_path, strerror (errno));
1018             ret = NOTMUCH_STATUS_FILE_ERROR;
1019             goto DONE;
1020         }
1021     }
1022
1023   DONE:
1024     if (notmuch) {
1025         notmuch_status_t ret2;
1026
1027         const char *str = notmuch_database_status_string (notmuch);
1028         if (status_cb && str)
1029             status_cb (str, closure);
1030
1031         ret2 = notmuch_database_destroy (notmuch);
1032
1033         /* don't clobber previous error status */
1034         if (ret == NOTMUCH_STATUS_SUCCESS && ret2 != NOTMUCH_STATUS_SUCCESS)
1035             ret = ret2;
1036     }
1037
1038     talloc_free (local);
1039
1040     return ret;
1041 }
1042
1043 notmuch_status_t
1044 notmuch_database_destroy (notmuch_database_t *notmuch)
1045 {
1046     notmuch_status_t status;
1047
1048     status = notmuch_database_close (notmuch);
1049
1050     delete notmuch->term_gen;
1051     notmuch->term_gen = NULL;
1052     delete notmuch->query_parser;
1053     notmuch->query_parser = NULL;
1054     delete notmuch->xapian_db;
1055     notmuch->xapian_db = NULL;
1056     delete notmuch->value_range_processor;
1057     notmuch->value_range_processor = NULL;
1058     delete notmuch->date_range_processor;
1059     notmuch->date_range_processor = NULL;
1060     delete notmuch->last_mod_range_processor;
1061     notmuch->last_mod_range_processor = NULL;
1062
1063     talloc_free (notmuch);
1064
1065     return status;
1066 }
1067
1068 const char *
1069 notmuch_database_get_path (notmuch_database_t *notmuch)
1070 {
1071     return notmuch->path;
1072 }
1073
1074 unsigned int
1075 notmuch_database_get_version (notmuch_database_t *notmuch)
1076 {
1077     unsigned int version;
1078     string version_string;
1079     const char *str;
1080     char *end;
1081
1082     try {
1083         version_string = notmuch->xapian_db->get_metadata ("version");
1084     } catch (const Xapian::Error &error) {
1085         LOG_XAPIAN_EXCEPTION (notmuch, error);
1086         return 0;
1087     }
1088
1089     if (version_string.empty ())
1090         return 0;
1091
1092     str = version_string.c_str ();
1093     if (str == NULL || *str == '\0')
1094         return 0;
1095
1096     version = strtoul (str, &end, 10);
1097     if (*end != '\0')
1098         INTERNAL_ERROR ("Malformed database version: %s", str);
1099
1100     return version;
1101 }
1102
1103 notmuch_bool_t
1104 notmuch_database_needs_upgrade (notmuch_database_t *notmuch)
1105 {
1106     unsigned int version;
1107
1108     if (_notmuch_database_mode (notmuch) != NOTMUCH_DATABASE_MODE_READ_WRITE)
1109         return FALSE;
1110
1111     if (NOTMUCH_FEATURES_CURRENT & ~notmuch->features)
1112         return TRUE;
1113
1114     version = notmuch_database_get_version (notmuch);
1115
1116     return (version > 0 && version < NOTMUCH_DATABASE_VERSION);
1117 }
1118
1119 static volatile sig_atomic_t do_progress_notify = 0;
1120
1121 static void
1122 handle_sigalrm (unused (int signal))
1123 {
1124     do_progress_notify = 1;
1125 }
1126
1127 /* Upgrade the current database.
1128  *
1129  * After opening a database in read-write mode, the client should
1130  * check if an upgrade is needed (notmuch_database_needs_upgrade) and
1131  * if so, upgrade with this function before making any modifications.
1132  *
1133  * The optional progress_notify callback can be used by the caller to
1134  * provide progress indication to the user. If non-NULL it will be
1135  * called periodically with 'count' as the number of messages upgraded
1136  * so far and 'total' the overall number of messages that will be
1137  * converted.
1138  */
1139 notmuch_status_t
1140 notmuch_database_upgrade (notmuch_database_t *notmuch,
1141                           void (*progress_notify) (void *closure,
1142                                                    double progress),
1143                           void *closure)
1144 {
1145     void *local = talloc_new (NULL);
1146     Xapian::TermIterator t, t_end;
1147     Xapian::WritableDatabase *db;
1148     struct sigaction action;
1149     struct itimerval timerval;
1150     bool timer_is_active = false;
1151     enum _notmuch_features target_features, new_features;
1152     notmuch_status_t status;
1153     notmuch_private_status_t private_status;
1154     notmuch_query_t *query = NULL;
1155     unsigned int count = 0, total = 0;
1156
1157     status = _notmuch_database_ensure_writable (notmuch);
1158     if (status)
1159         return status;
1160
1161     db = notmuch->writable_xapian_db;
1162
1163     target_features = notmuch->features | NOTMUCH_FEATURES_CURRENT;
1164     new_features = NOTMUCH_FEATURES_CURRENT & ~notmuch->features;
1165
1166     if (! notmuch_database_needs_upgrade (notmuch))
1167         return NOTMUCH_STATUS_SUCCESS;
1168
1169     if (progress_notify) {
1170         /* Set up our handler for SIGALRM */
1171         memset (&action, 0, sizeof (struct sigaction));
1172         action.sa_handler = handle_sigalrm;
1173         sigemptyset (&action.sa_mask);
1174         action.sa_flags = SA_RESTART;
1175         sigaction (SIGALRM, &action, NULL);
1176
1177         /* Then start a timer to send SIGALRM once per second. */
1178         timerval.it_interval.tv_sec = 1;
1179         timerval.it_interval.tv_usec = 0;
1180         timerval.it_value.tv_sec = 1;
1181         timerval.it_value.tv_usec = 0;
1182         setitimer (ITIMER_REAL, &timerval, NULL);
1183
1184         timer_is_active = true;
1185     }
1186
1187     /* Figure out how much total work we need to do. */
1188     if (new_features &
1189         (NOTMUCH_FEATURE_FILE_TERMS | NOTMUCH_FEATURE_BOOL_FOLDER |
1190          NOTMUCH_FEATURE_LAST_MOD)) {
1191         query = notmuch_query_create (notmuch, "");
1192         unsigned msg_count;
1193
1194         status = notmuch_query_count_messages (query, &msg_count);
1195         if (status)
1196             goto DONE;
1197
1198         total += msg_count;
1199         notmuch_query_destroy (query);
1200         query = NULL;
1201     }
1202     if (new_features & NOTMUCH_FEATURE_DIRECTORY_DOCS) {
1203         t_end = db->allterms_end ("XTIMESTAMP");
1204         for (t = db->allterms_begin ("XTIMESTAMP"); t != t_end; t++)
1205             ++total;
1206     }
1207     if (new_features & NOTMUCH_FEATURE_GHOSTS) {
1208         /* The ghost message upgrade converts all thread_id_*
1209          * metadata values into ghost message documents. */
1210         t_end = db->metadata_keys_end ("thread_id_");
1211         for (t = db->metadata_keys_begin ("thread_id_"); t != t_end; ++t)
1212             ++total;
1213     }
1214
1215     /* Perform the upgrade in a transaction. */
1216     db->begin_transaction (true);
1217
1218     /* Set the target features so we write out changes in the desired
1219      * format. */
1220     notmuch->features = target_features;
1221
1222     /* Perform per-message upgrades. */
1223     if (new_features &
1224         (NOTMUCH_FEATURE_FILE_TERMS | NOTMUCH_FEATURE_BOOL_FOLDER |
1225          NOTMUCH_FEATURE_LAST_MOD)) {
1226         notmuch_messages_t *messages;
1227         notmuch_message_t *message;
1228         char *filename;
1229
1230         query = notmuch_query_create (notmuch, "");
1231
1232         status = notmuch_query_search_messages (query, &messages);
1233         if (status)
1234             goto DONE;
1235         for (;
1236              notmuch_messages_valid (messages);
1237              notmuch_messages_move_to_next (messages)) {
1238             if (do_progress_notify) {
1239                 progress_notify (closure, (double) count / total);
1240                 do_progress_notify = 0;
1241             }
1242
1243             message = notmuch_messages_get (messages);
1244
1245             /* Before version 1, each message document had its
1246              * filename in the data field. Copy that into the new
1247              * format by calling notmuch_message_add_filename.
1248              */
1249             if (new_features & NOTMUCH_FEATURE_FILE_TERMS) {
1250                 filename = _notmuch_message_talloc_copy_data (message);
1251                 if (filename && *filename != '\0') {
1252                     _notmuch_message_add_filename (message, filename);
1253                     _notmuch_message_clear_data (message);
1254                 }
1255                 talloc_free (filename);
1256             }
1257
1258             /* Prior to version 2, the "folder:" prefix was
1259              * probabilistic and stemmed. Change it to the current
1260              * boolean prefix. Add "path:" prefixes while at it.
1261              */
1262             if (new_features & NOTMUCH_FEATURE_BOOL_FOLDER)
1263                 _notmuch_message_upgrade_folder (message);
1264
1265             /* Prior to NOTMUCH_FEATURE_LAST_MOD, messages did not
1266              * track modification revisions.  Give all messages the
1267              * next available revision; since we just started tracking
1268              * revisions for this database, that will be 1.
1269              */
1270             if (new_features & NOTMUCH_FEATURE_LAST_MOD)
1271                 _notmuch_message_upgrade_last_mod (message);
1272
1273             _notmuch_message_sync (message);
1274
1275             notmuch_message_destroy (message);
1276
1277             count++;
1278         }
1279
1280         notmuch_query_destroy (query);
1281         query = NULL;
1282     }
1283
1284     /* Perform per-directory upgrades. */
1285
1286     /* Before version 1 we stored directory timestamps in
1287      * XTIMESTAMP documents instead of the current XDIRECTORY
1288      * documents. So copy those as well. */
1289     if (new_features & NOTMUCH_FEATURE_DIRECTORY_DOCS) {
1290         t_end = notmuch->xapian_db->allterms_end ("XTIMESTAMP");
1291
1292         for (t = notmuch->xapian_db->allterms_begin ("XTIMESTAMP");
1293              t != t_end;
1294              t++) {
1295             Xapian::PostingIterator p, p_end;
1296             std::string term = *t;
1297
1298             p_end = notmuch->xapian_db->postlist_end (term);
1299
1300             for (p = notmuch->xapian_db->postlist_begin (term);
1301                  p != p_end;
1302                  p++) {
1303                 Xapian::Document document;
1304                 time_t mtime;
1305                 notmuch_directory_t *directory;
1306
1307                 if (do_progress_notify) {
1308                     progress_notify (closure, (double) count / total);
1309                     do_progress_notify = 0;
1310                 }
1311
1312                 document = find_document_for_doc_id (notmuch, *p);
1313                 mtime = Xapian::sortable_unserialise (
1314                     document.get_value (NOTMUCH_VALUE_TIMESTAMP));
1315
1316                 directory = _notmuch_directory_find_or_create (notmuch, term.c_str () + 10,
1317                                                                NOTMUCH_FIND_CREATE, &status);
1318                 notmuch_directory_set_mtime (directory, mtime);
1319                 notmuch_directory_destroy (directory);
1320
1321                 db->delete_document (*p);
1322             }
1323
1324             ++count;
1325         }
1326     }
1327
1328     /* Perform metadata upgrades. */
1329
1330     /* Prior to NOTMUCH_FEATURE_GHOSTS, thread IDs for missing
1331      * messages were stored as database metadata. Change these to
1332      * ghost messages.
1333      */
1334     if (new_features & NOTMUCH_FEATURE_GHOSTS) {
1335         notmuch_message_t *message;
1336         std::string message_id, thread_id;
1337
1338         t_end = db->metadata_keys_end (NOTMUCH_METADATA_THREAD_ID_PREFIX);
1339         for (t = db->metadata_keys_begin (NOTMUCH_METADATA_THREAD_ID_PREFIX);
1340              t != t_end; ++t) {
1341             if (do_progress_notify) {
1342                 progress_notify (closure, (double) count / total);
1343                 do_progress_notify = 0;
1344             }
1345
1346             message_id = (*t).substr (
1347                 strlen (NOTMUCH_METADATA_THREAD_ID_PREFIX));
1348             thread_id = db->get_metadata (*t);
1349
1350             /* Create ghost message */
1351             message = _notmuch_message_create_for_message_id (
1352                 notmuch, message_id.c_str (), &private_status);
1353             if (private_status == NOTMUCH_PRIVATE_STATUS_SUCCESS) {
1354                 /* Document already exists; ignore the stored thread ID */
1355             } else if (private_status ==
1356                        NOTMUCH_PRIVATE_STATUS_NO_DOCUMENT_FOUND) {
1357                 private_status = _notmuch_message_initialize_ghost (
1358                     message, thread_id.c_str ());
1359                 if (! private_status)
1360                     _notmuch_message_sync (message);
1361             }
1362
1363             if (private_status) {
1364                 _notmuch_database_log (notmuch,
1365                                        "Upgrade failed while creating ghost messages.\n");
1366                 status = COERCE_STATUS (private_status, "Unexpected status from _notmuch_message_initialize_ghost");
1367                 goto DONE;
1368             }
1369
1370             /* Clear saved metadata thread ID */
1371             db->set_metadata (*t, "");
1372
1373             ++count;
1374         }
1375     }
1376
1377     status = NOTMUCH_STATUS_SUCCESS;
1378     db->set_metadata ("features", _notmuch_database_print_features (local, notmuch->features));
1379     db->set_metadata ("version", STRINGIFY (NOTMUCH_DATABASE_VERSION));
1380
1381   DONE:
1382     if (status == NOTMUCH_STATUS_SUCCESS)
1383         db->commit_transaction ();
1384     else
1385         db->cancel_transaction ();
1386
1387     if (timer_is_active) {
1388         /* Now stop the timer. */
1389         timerval.it_interval.tv_sec = 0;
1390         timerval.it_interval.tv_usec = 0;
1391         timerval.it_value.tv_sec = 0;
1392         timerval.it_value.tv_usec = 0;
1393         setitimer (ITIMER_REAL, &timerval, NULL);
1394
1395         /* And disable the signal handler. */
1396         action.sa_handler = SIG_IGN;
1397         sigaction (SIGALRM, &action, NULL);
1398     }
1399
1400     if (query)
1401         notmuch_query_destroy (query);
1402
1403     talloc_free (local);
1404     return status;
1405 }
1406
1407 notmuch_status_t
1408 notmuch_database_begin_atomic (notmuch_database_t *notmuch)
1409 {
1410     if (_notmuch_database_mode (notmuch) == NOTMUCH_DATABASE_MODE_READ_ONLY ||
1411         notmuch->atomic_nesting > 0)
1412         goto DONE;
1413
1414     if (notmuch_database_needs_upgrade (notmuch))
1415         return NOTMUCH_STATUS_UPGRADE_REQUIRED;
1416
1417     try {
1418         notmuch->writable_xapian_db->begin_transaction (false);
1419     } catch (const Xapian::Error &error) {
1420         _notmuch_database_log (notmuch, "A Xapian exception occurred beginning transaction: %s.\n",
1421                                error.get_msg ().c_str ());
1422         notmuch->exception_reported = true;
1423         return NOTMUCH_STATUS_XAPIAN_EXCEPTION;
1424     }
1425
1426   DONE:
1427     notmuch->atomic_nesting++;
1428     return NOTMUCH_STATUS_SUCCESS;
1429 }
1430
1431 notmuch_status_t
1432 notmuch_database_end_atomic (notmuch_database_t *notmuch)
1433 {
1434     Xapian::WritableDatabase *db;
1435
1436     if (notmuch->atomic_nesting == 0)
1437         return NOTMUCH_STATUS_UNBALANCED_ATOMIC;
1438
1439     if (_notmuch_database_mode (notmuch) == NOTMUCH_DATABASE_MODE_READ_ONLY ||
1440         notmuch->atomic_nesting > 1)
1441         goto DONE;
1442
1443     db = notmuch->writable_xapian_db;
1444     try {
1445         db->commit_transaction ();
1446
1447         /* This is a hack for testing.  Xapian never flushes on a
1448          * non-flushed commit, even if the flush threshold is 1.
1449          * However, we rely on flushing to test atomicity. */
1450         const char *thresh = getenv ("XAPIAN_FLUSH_THRESHOLD");
1451         if (thresh && atoi (thresh) == 1)
1452             db->commit ();
1453     } catch (const Xapian::Error &error) {
1454         _notmuch_database_log (notmuch, "A Xapian exception occurred committing transaction: %s.\n",
1455                                error.get_msg ().c_str ());
1456         notmuch->exception_reported = true;
1457         return NOTMUCH_STATUS_XAPIAN_EXCEPTION;
1458     }
1459
1460     if (notmuch->atomic_dirty) {
1461         ++notmuch->revision;
1462         notmuch->atomic_dirty = false;
1463     }
1464
1465   DONE:
1466     notmuch->atomic_nesting--;
1467     return NOTMUCH_STATUS_SUCCESS;
1468 }
1469
1470 unsigned long
1471 notmuch_database_get_revision (notmuch_database_t *notmuch,
1472                                const char **uuid)
1473 {
1474     if (uuid)
1475         *uuid = notmuch->uuid;
1476     return notmuch->revision;
1477 }
1478
1479 /* We allow the user to use arbitrarily long paths for directories. But
1480  * we have a term-length limit. So if we exceed that, we'll use the
1481  * SHA-1 of the path for the database term.
1482  *
1483  * Note: This function may return the original value of 'path'. If it
1484  * does not, then the caller is responsible to free() the returned
1485  * value.
1486  */
1487 const char *
1488 _notmuch_database_get_directory_db_path (const char *path)
1489 {
1490     int term_len = strlen (_find_prefix ("directory")) + strlen (path);
1491
1492     if (term_len > NOTMUCH_TERM_MAX)
1493         return _notmuch_sha1_of_string (path);
1494     else
1495         return path;
1496 }
1497
1498 /* Given a path, split it into two parts: the directory part is all
1499  * components except for the last, and the basename is that last
1500  * component. Getting the return-value for either part is optional
1501  * (the caller can pass NULL).
1502  *
1503  * The original 'path' can represent either a regular file or a
1504  * directory---the splitting will be carried out in the same way in
1505  * either case. Trailing slashes on 'path' will be ignored, and any
1506  * cases of multiple '/' characters appearing in series will be
1507  * treated as a single '/'.
1508  *
1509  * Allocation (if any) will have 'ctx' as the talloc owner. But
1510  * pointers will be returned within the original path string whenever
1511  * possible.
1512  *
1513  * Note: If 'path' is non-empty and contains no non-trailing slash,
1514  * (that is, consists of a filename with no parent directory), then
1515  * the directory returned will be an empty string. However, if 'path'
1516  * is an empty string, then both directory and basename will be
1517  * returned as NULL.
1518  */
1519 notmuch_status_t
1520 _notmuch_database_split_path (void *ctx,
1521                               const char *path,
1522                               const char **directory,
1523                               const char **basename)
1524 {
1525     const char *slash;
1526
1527     if (path == NULL || *path == '\0') {
1528         if (directory)
1529             *directory = NULL;
1530         if (basename)
1531             *basename = NULL;
1532         return NOTMUCH_STATUS_SUCCESS;
1533     }
1534
1535     /* Find the last slash (not counting a trailing slash), if any. */
1536
1537     slash = path + strlen (path) - 1;
1538
1539     /* First, skip trailing slashes. */
1540     while (slash != path && *slash == '/')
1541         --slash;
1542
1543     /* Then, find a slash. */
1544     while (slash != path && *slash != '/') {
1545         if (basename)
1546             *basename = slash;
1547
1548         --slash;
1549     }
1550
1551     /* Finally, skip multiple slashes. */
1552     while (slash != path && *(slash - 1) == '/')
1553         --slash;
1554
1555     if (slash == path) {
1556         if (directory)
1557             *directory = talloc_strdup (ctx, "");
1558         if (basename)
1559             *basename = path;
1560     } else {
1561         if (directory)
1562             *directory = talloc_strndup (ctx, path, slash - path);
1563     }
1564
1565     return NOTMUCH_STATUS_SUCCESS;
1566 }
1567
1568 /* Find the document ID of the specified directory.
1569  *
1570  * If (flags & NOTMUCH_FIND_CREATE), a new directory document will be
1571  * created if one does not exist for 'path'.  Otherwise, if the
1572  * directory document does not exist, this sets *directory_id to
1573  * ((unsigned int)-1) and returns NOTMUCH_STATUS_SUCCESS.
1574  */
1575 notmuch_status_t
1576 _notmuch_database_find_directory_id (notmuch_database_t *notmuch,
1577                                      const char *path,
1578                                      notmuch_find_flags_t flags,
1579                                      unsigned int *directory_id)
1580 {
1581     notmuch_directory_t *directory;
1582     notmuch_status_t status;
1583
1584     if (path == NULL) {
1585         *directory_id = 0;
1586         return NOTMUCH_STATUS_SUCCESS;
1587     }
1588
1589     directory = _notmuch_directory_find_or_create (notmuch, path, flags, &status);
1590     if (status || ! directory) {
1591         *directory_id = -1;
1592         return status;
1593     }
1594
1595     *directory_id = _notmuch_directory_get_document_id (directory);
1596
1597     notmuch_directory_destroy (directory);
1598
1599     return NOTMUCH_STATUS_SUCCESS;
1600 }
1601
1602 const char *
1603 _notmuch_database_get_directory_path (void *ctx,
1604                                       notmuch_database_t *notmuch,
1605                                       unsigned int doc_id)
1606 {
1607     Xapian::Document document;
1608
1609     document = find_document_for_doc_id (notmuch, doc_id);
1610
1611     return talloc_strdup (ctx, document.get_data ().c_str ());
1612 }
1613
1614 /* Given a legal 'filename' for the database, (either relative to
1615  * database path or absolute with initial components identical to
1616  * database path), return a new string (with 'ctx' as the talloc
1617  * owner) suitable for use as a direntry term value.
1618  *
1619  * If (flags & NOTMUCH_FIND_CREATE), the necessary directory documents
1620  * will be created in the database as needed.  Otherwise, if the
1621  * necessary directory documents do not exist, this sets
1622  * *direntry to NULL and returns NOTMUCH_STATUS_SUCCESS.
1623  */
1624 notmuch_status_t
1625 _notmuch_database_filename_to_direntry (void *ctx,
1626                                         notmuch_database_t *notmuch,
1627                                         const char *filename,
1628                                         notmuch_find_flags_t flags,
1629                                         char **direntry)
1630 {
1631     const char *relative, *directory, *basename;
1632     Xapian::docid directory_id;
1633     notmuch_status_t status;
1634
1635     relative = _notmuch_database_relative_path (notmuch, filename);
1636
1637     status = _notmuch_database_split_path (ctx, relative,
1638                                            &directory, &basename);
1639     if (status)
1640         return status;
1641
1642     status = _notmuch_database_find_directory_id (notmuch, directory, flags,
1643                                                   &directory_id);
1644     if (status || directory_id == (unsigned int) -1) {
1645         *direntry = NULL;
1646         return status;
1647     }
1648
1649     *direntry = talloc_asprintf (ctx, "%u:%s", directory_id, basename);
1650
1651     return NOTMUCH_STATUS_SUCCESS;
1652 }
1653
1654 /* Given a legal 'path' for the database, return the relative path.
1655  *
1656  * The return value will be a pointer to the original path contents,
1657  * and will be either the original string (if 'path' was relative) or
1658  * a portion of the string (if path was absolute and begins with the
1659  * database path).
1660  */
1661 const char *
1662 _notmuch_database_relative_path (notmuch_database_t *notmuch,
1663                                  const char *path)
1664 {
1665     const char *db_path, *relative;
1666     unsigned int db_path_len;
1667
1668     db_path = notmuch_database_get_path (notmuch);
1669     db_path_len = strlen (db_path);
1670
1671     relative = path;
1672
1673     if (*relative == '/') {
1674         while (*relative == '/' && *(relative + 1) == '/')
1675             relative++;
1676
1677         if (strncmp (relative, db_path, db_path_len) == 0) {
1678             relative += db_path_len;
1679             while (*relative == '/')
1680                 relative++;
1681         }
1682     }
1683
1684     return relative;
1685 }
1686
1687 notmuch_status_t
1688 notmuch_database_get_directory (notmuch_database_t *notmuch,
1689                                 const char *path,
1690                                 notmuch_directory_t **directory)
1691 {
1692     notmuch_status_t status;
1693
1694     if (directory == NULL)
1695         return NOTMUCH_STATUS_NULL_POINTER;
1696     *directory = NULL;
1697
1698     try {
1699         *directory = _notmuch_directory_find_or_create (notmuch, path,
1700                                                         NOTMUCH_FIND_LOOKUP, &status);
1701     } catch (const Xapian::Error &error) {
1702         _notmuch_database_log (notmuch, "A Xapian exception occurred getting directory: %s.\n",
1703                                error.get_msg ().c_str ());
1704         notmuch->exception_reported = true;
1705         status = NOTMUCH_STATUS_XAPIAN_EXCEPTION;
1706     }
1707     return status;
1708 }
1709
1710 /* Allocate a document ID that satisfies the following criteria:
1711  *
1712  * 1. The ID does not exist for any document in the Xapian database
1713  *
1714  * 2. The ID was not previously returned from this function
1715  *
1716  * 3. The ID is the smallest integer satisfying (1) and (2)
1717  *
1718  * This function will trigger an internal error if these constraints
1719  * cannot all be satisfied, (that is, the pool of available document
1720  * IDs has been exhausted).
1721  */
1722 unsigned int
1723 _notmuch_database_generate_doc_id (notmuch_database_t *notmuch)
1724 {
1725     assert (notmuch->last_doc_id >= notmuch->xapian_db->get_lastdocid ());
1726
1727     notmuch->last_doc_id++;
1728
1729     if (notmuch->last_doc_id == 0)
1730         INTERNAL_ERROR ("Xapian document IDs are exhausted.\n");
1731
1732     return notmuch->last_doc_id;
1733 }
1734
1735 notmuch_status_t
1736 notmuch_database_remove_message (notmuch_database_t *notmuch,
1737                                  const char *filename)
1738 {
1739     notmuch_status_t status;
1740     notmuch_message_t *message;
1741
1742     status = notmuch_database_find_message_by_filename (notmuch, filename,
1743                                                         &message);
1744
1745     if (status == NOTMUCH_STATUS_SUCCESS && message) {
1746         status = _notmuch_message_remove_filename (message, filename);
1747         if (status == NOTMUCH_STATUS_SUCCESS)
1748             _notmuch_message_delete (message);
1749         else if (status == NOTMUCH_STATUS_DUPLICATE_MESSAGE_ID)
1750             _notmuch_message_sync (message);
1751
1752         notmuch_message_destroy (message);
1753     }
1754
1755     return status;
1756 }
1757
1758 notmuch_status_t
1759 notmuch_database_find_message_by_filename (notmuch_database_t *notmuch,
1760                                            const char *filename,
1761                                            notmuch_message_t **message_ret)
1762 {
1763     void *local;
1764     const char *prefix = _find_prefix ("file-direntry");
1765     char *direntry, *term;
1766     Xapian::PostingIterator i, end;
1767     notmuch_status_t status;
1768
1769     if (message_ret == NULL)
1770         return NOTMUCH_STATUS_NULL_POINTER;
1771
1772     if (! (notmuch->features & NOTMUCH_FEATURE_FILE_TERMS))
1773         return NOTMUCH_STATUS_UPGRADE_REQUIRED;
1774
1775     /* return NULL on any failure */
1776     *message_ret = NULL;
1777
1778     local = talloc_new (notmuch);
1779
1780     try {
1781         status = _notmuch_database_filename_to_direntry (
1782             local, notmuch, filename, NOTMUCH_FIND_LOOKUP, &direntry);
1783         if (status || ! direntry)
1784             goto DONE;
1785
1786         term = talloc_asprintf (local, "%s%s", prefix, direntry);
1787
1788         find_doc_ids_for_term (notmuch, term, &i, &end);
1789
1790         if (i != end) {
1791             notmuch_private_status_t private_status;
1792
1793             *message_ret = _notmuch_message_create (notmuch, notmuch, *i,
1794                                                     &private_status);
1795             if (*message_ret == NULL)
1796                 status = NOTMUCH_STATUS_OUT_OF_MEMORY;
1797         }
1798     } catch (const Xapian::Error &error) {
1799         _notmuch_database_log (notmuch, "Error: A Xapian exception occurred finding message by filename: %s\n",
1800                                error.get_msg ().c_str ());
1801         notmuch->exception_reported = true;
1802         status = NOTMUCH_STATUS_XAPIAN_EXCEPTION;
1803     }
1804
1805   DONE:
1806     talloc_free (local);
1807
1808     if (status && *message_ret) {
1809         notmuch_message_destroy (*message_ret);
1810         *message_ret = NULL;
1811     }
1812     return status;
1813 }
1814
1815 notmuch_string_list_t *
1816 _notmuch_database_get_terms_with_prefix (void *ctx, Xapian::TermIterator &i,
1817                                          Xapian::TermIterator &end,
1818                                          const char *prefix)
1819 {
1820     int prefix_len = strlen (prefix);
1821     notmuch_string_list_t *list;
1822
1823     list = _notmuch_string_list_create (ctx);
1824     if (unlikely (list == NULL))
1825         return NULL;
1826
1827     for (i.skip_to (prefix); i != end; i++) {
1828         /* Terminate loop at first term without desired prefix. */
1829         if (strncmp ((*i).c_str (), prefix, prefix_len))
1830             break;
1831
1832         _notmuch_string_list_append (list, (*i).c_str () + prefix_len);
1833     }
1834
1835     return list;
1836 }
1837
1838 notmuch_tags_t *
1839 notmuch_database_get_all_tags (notmuch_database_t *db)
1840 {
1841     Xapian::TermIterator i, end;
1842     notmuch_string_list_t *tags;
1843
1844     try {
1845         i = db->xapian_db->allterms_begin ();
1846         end = db->xapian_db->allterms_end ();
1847         tags = _notmuch_database_get_terms_with_prefix (db, i, end,
1848                                                         _find_prefix ("tag"));
1849         _notmuch_string_list_sort (tags);
1850         return _notmuch_tags_create (db, tags);
1851     } catch (const Xapian::Error &error) {
1852         _notmuch_database_log (db, "A Xapian exception occurred getting tags: %s.\n",
1853                                error.get_msg ().c_str ());
1854         db->exception_reported = true;
1855         return NULL;
1856     }
1857 }
1858
1859 const char *
1860 notmuch_database_status_string (const notmuch_database_t *notmuch)
1861 {
1862     return notmuch->status_string;
1863 }