]> git.notmuchmail.org Git - notmuch/blob - lib/database.cc
49b3849c32586124db4364c4c6815a25316f2bc7
[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 "regexp-fields.h"
25 #include "string-util.h"
26
27 #include <iostream>
28
29 #include <sys/time.h>
30 #include <sys/stat.h>
31 #include <signal.h>
32 #include <ftw.h>
33
34 #include <glib.h> /* g_free, GPtrArray, GHashTable */
35 #include <glib-object.h> /* g_type_init */
36
37 #include <gmime/gmime.h> /* g_mime_init */
38
39 using namespace std;
40
41 #define ARRAY_SIZE(arr) (sizeof (arr) / sizeof (arr[0]))
42
43 typedef struct {
44     const char *name;
45     const char *prefix;
46     notmuch_field_flag_t flags;
47 } prefix_t;
48
49 #define NOTMUCH_DATABASE_VERSION 3
50
51 #define STRINGIFY(s) _SUB_STRINGIFY(s)
52 #define _SUB_STRINGIFY(s) #s
53
54 #if HAVE_XAPIAN_DB_RETRY_LOCK
55 #define DB_ACTION (Xapian::DB_CREATE_OR_OPEN | Xapian::DB_RETRY_LOCK)
56 #else
57 #define DB_ACTION Xapian::DB_CREATE_OR_OPEN
58 #endif
59
60 /* Here's the current schema for our database (for NOTMUCH_DATABASE_VERSION):
61  *
62  * We currently have three different types of documents (mail, ghost,
63  * and directory) and also some metadata.
64  *
65  * Mail document
66  * -------------
67  * A mail document is associated with a particular email message. It
68  * is stored in one or more files on disk (though only one has its
69  * content indexed) and is uniquely identified  by its "id" field
70  * (which is generally the message ID). It is indexed with the
71  * following prefixed terms which the database uses to construct
72  * threads, etc.:
73  *
74  *    Single terms of given prefix:
75  *
76  *      type:   mail
77  *
78  *      id:     Unique ID of mail. This is from the Message-ID header
79  *              if present and not too long (see NOTMUCH_MESSAGE_ID_MAX).
80  *              If it's present and too long, then we use
81  *              "notmuch-sha1-<sha1_sum_of_message_id>".
82  *              If this header is not present, we use
83  *              "notmuch-sha1-<sha1_sum_of_entire_file>".
84  *
85  *      thread: The ID of the thread to which the mail belongs
86  *
87  *      replyto: The ID from the In-Reply-To header of the mail (if any).
88  *
89  *    Multiple terms of given prefix:
90  *
91  *      reference: All message IDs from In-Reply-To and References
92  *                 headers in the message.
93  *
94  *      tag:       Any tags associated with this message by the user.
95  *
96  *      file-direntry:  A colon-separated pair of values
97  *                      (INTEGER:STRING), where INTEGER is the
98  *                      document ID of a directory document, and
99  *                      STRING is the name of a file within that
100  *                      directory for this mail message.
101  *
102  *      property:       Has a property with key=value
103  *                 FIXME: if no = is present, should match on any value
104  *
105  *    A mail document also has four values:
106  *
107  *      TIMESTAMP:      The time_t value corresponding to the message's
108  *                      Date header.
109  *
110  *      MESSAGE_ID:     The unique ID of the mail mess (see "id" above)
111  *
112  *      FROM:           The value of the "From" header
113  *
114  *      SUBJECT:        The value of the "Subject" header
115  *
116  *      LAST_MOD:       The revision number as of the last tag or
117  *                      filename change.
118  *
119  * In addition, terms from the content of the message are added with
120  * "from", "to", "attachment", and "subject" prefixes for use by the
121  * user in searching. Similarly, terms from the path of the mail
122  * message are added with "folder" and "path" prefixes. But the
123  * database doesn't really care itself about any of these.
124  *
125  * The data portion of a mail document is empty.
126  *
127  * Ghost mail document [if NOTMUCH_FEATURE_GHOSTS]
128  * -----------------------------------------------
129  * A ghost mail document is like a mail document, but where we don't
130  * have the message content.  These are used to track thread reference
131  * information for messages we haven't received.
132  *
133  * A ghost mail document has type: ghost; id and thread fields that
134  * are identical to the mail document fields; and a MESSAGE_ID value.
135  *
136  * Directory document
137  * ------------------
138  * A directory document is used by a client of the notmuch library to
139  * maintain data necessary to allow for efficient polling of mail
140  * directories.
141  *
142  * All directory documents contain one term:
143  *
144  *      directory:      The directory path (relative to the database path)
145  *                      Or the SHA1 sum of the directory path (if the
146  *                      path itself is too long to fit in a Xapian
147  *                      term).
148  *
149  * And all directory documents for directories other than top-level
150  * directories also contain the following term:
151  *
152  *      directory-direntry: A colon-separated pair of values
153  *                          (INTEGER:STRING), where INTEGER is the
154  *                          document ID of the parent directory
155  *                          document, and STRING is the name of this
156  *                          directory within that parent.
157  *
158  * All directory documents have a single value:
159  *
160  *      TIMESTAMP:      The mtime of the directory (at last scan)
161  *
162  * The data portion of a directory document contains the path of the
163  * directory (relative to the database path).
164  *
165  * Database metadata
166  * -----------------
167  * Xapian allows us to store arbitrary name-value pairs as
168  * "metadata". We currently use the following metadata names with the
169  * given meanings:
170  *
171  *      version         The database schema version, (which is distinct
172  *                      from both the notmuch package version (see
173  *                      notmuch --version) and the libnotmuch library
174  *                      version. The version is stored as an base-10
175  *                      ASCII integer. The initial database version
176  *                      was 1, (though a schema existed before that
177  *                      were no "version" database value existed at
178  *                      all). Successive versions are allocated as
179  *                      changes are made to the database (such as by
180  *                      indexing new fields).
181  *
182  *      features        The set of features supported by this
183  *                      database. This consists of a set of
184  *                      '\n'-separated lines, where each is a feature
185  *                      name, a '\t', and compatibility flags.  If the
186  *                      compatibility flags contain 'w', then the
187  *                      opener must support this feature to safely
188  *                      write this database.  If the compatibility
189  *                      flags contain 'r', then the opener must
190  *                      support this feature to read this database.
191  *                      Introduced in database version 3.
192  *
193  *      last_thread_id  The last thread ID generated. This is stored
194  *                      as a 16-byte hexadecimal ASCII representation
195  *                      of a 64-bit unsigned integer. The first ID
196  *                      generated is 1 and the value will be
197  *                      incremented for each thread ID.
198  *
199  *      C*              metadata keys starting with C indicate
200  *                      configuration data. It can be managed with the
201  *                      n_database_*config* API.  There is a convention
202  *                      of hierarchical keys separated by '.' (e.g.
203  *                      query.notmuch stores the value for the named
204  *                      query 'notmuch'), but it is not enforced by the
205  *                      API.
206  *
207  * Obsolete metadata
208  * -----------------
209  *
210  * If ! NOTMUCH_FEATURE_GHOSTS, there are no ghost mail documents.
211  * Instead, the database has the following additional database
212  * metadata:
213  *
214  *      thread_id_*     A pre-allocated thread ID for a particular
215  *                      message. This is actually an arbitrarily large
216  *                      family of metadata name. Any particular name is
217  *                      formed by concatenating "thread_id_" with a message
218  *                      ID (or the SHA1 sum of a message ID if it is very
219  *                      long---see description of 'id' in the mail
220  *                      document). The value stored is a thread ID.
221  *
222  *                      These thread ID metadata values are stored
223  *                      whenever a message references a parent message
224  *                      that does not yet exist in the database. A
225  *                      thread ID will be allocated and stored, and if
226  *                      the message is later added, the stored thread
227  *                      ID will be used (and the metadata value will
228  *                      be cleared).
229  *
230  *                      Even before a message is added, it's
231  *                      pre-allocated thread ID is useful so that all
232  *                      descendant messages that reference this common
233  *                      parent can be recognized as belonging to the
234  *                      same thread.
235  */
236
237 /* With these prefix values we follow the conventions published here:
238  *
239  * https://xapian.org/docs/omega/termprefixes.html
240  *
241  * as much as makes sense. Note that I took some liberty in matching
242  * the reserved prefix values to notmuch concepts, (for example, 'G'
243  * is documented as "newsGroup (or similar entity - e.g. a web forum
244  * name)", for which I think the thread is the closest analogue in
245  * notmuch. This in spite of the fact that we will eventually be
246  * storing mailing-list messages where 'G' for "mailing list name"
247  * might be even a closer analogue. I'm treating the single-character
248  * prefixes preferentially for core notmuch concepts (which will be
249  * nearly universal to all mail messages).
250  */
251
252 static const
253 prefix_t prefix_table[] = {
254     /* name                     term prefix     flags */
255     { "type",                   "T",            NOTMUCH_FIELD_NO_FLAGS },
256     { "reference",              "XREFERENCE",   NOTMUCH_FIELD_NO_FLAGS },
257     { "replyto",                "XREPLYTO",     NOTMUCH_FIELD_NO_FLAGS },
258     { "directory",              "XDIRECTORY",   NOTMUCH_FIELD_NO_FLAGS },
259     { "file-direntry",          "XFDIRENTRY",   NOTMUCH_FIELD_NO_FLAGS },
260     { "directory-direntry",     "XDDIRENTRY",   NOTMUCH_FIELD_NO_FLAGS },
261     { "thread",                 "G",            NOTMUCH_FIELD_EXTERNAL },
262     { "tag",                    "K",            NOTMUCH_FIELD_EXTERNAL },
263     { "is",                     "K",            NOTMUCH_FIELD_EXTERNAL },
264     { "id",                     "Q",            NOTMUCH_FIELD_EXTERNAL },
265     { "mid",                    "Q",            NOTMUCH_FIELD_EXTERNAL |
266                                                 NOTMUCH_FIELD_PROCESSOR },
267     { "path",                   "P",            NOTMUCH_FIELD_EXTERNAL },
268     { "property",               "XPROPERTY",    NOTMUCH_FIELD_EXTERNAL },
269     /*
270      * Unconditionally add ':' to reduce potential ambiguity with
271      * overlapping prefixes and/or terms that start with capital
272      * letters. See Xapian document termprefixes.html for related
273      * discussion.
274      */
275     { "folder",                 "XFOLDER:",     NOTMUCH_FIELD_EXTERNAL },
276 #if HAVE_XAPIAN_FIELD_PROCESSOR
277     { "date",                   NULL,           NOTMUCH_FIELD_EXTERNAL |
278                                                 NOTMUCH_FIELD_PROCESSOR },
279     { "query",                  NULL,           NOTMUCH_FIELD_EXTERNAL |
280                                                 NOTMUCH_FIELD_PROCESSOR },
281 #endif
282     { "from",                   "XFROM",        NOTMUCH_FIELD_EXTERNAL |
283                                                 NOTMUCH_FIELD_PROBABILISTIC |
284                                                 NOTMUCH_FIELD_PROCESSOR },
285     { "to",                     "XTO",          NOTMUCH_FIELD_EXTERNAL |
286                                                 NOTMUCH_FIELD_PROBABILISTIC },
287     { "attachment",             "XATTACHMENT",  NOTMUCH_FIELD_EXTERNAL |
288                                                 NOTMUCH_FIELD_PROBABILISTIC },
289     { "mimetype",               "XMIMETYPE",    NOTMUCH_FIELD_EXTERNAL |
290                                                 NOTMUCH_FIELD_PROBABILISTIC },
291     { "subject",                "XSUBJECT",     NOTMUCH_FIELD_EXTERNAL |
292                                                 NOTMUCH_FIELD_PROBABILISTIC |
293                                                 NOTMUCH_FIELD_PROCESSOR},
294 };
295
296 static void
297 _setup_query_field_default (const prefix_t *prefix, notmuch_database_t *notmuch)
298 {
299     if (prefix->flags & NOTMUCH_FIELD_PROBABILISTIC)
300         notmuch->query_parser->add_prefix (prefix->name, prefix->prefix);
301     else
302         notmuch->query_parser->add_boolean_prefix (prefix->name, prefix->prefix);
303 }
304
305 #if HAVE_XAPIAN_FIELD_PROCESSOR
306 static void
307 _setup_query_field (const prefix_t *prefix, notmuch_database_t *notmuch)
308 {
309     if (prefix->flags & NOTMUCH_FIELD_PROCESSOR) {
310         Xapian::FieldProcessor *fp;
311
312         if (STRNCMP_LITERAL (prefix->name, "date") == 0)
313             fp = (new DateFieldProcessor())->release ();
314         else if (STRNCMP_LITERAL(prefix->name, "query") == 0)
315             fp = (new QueryFieldProcessor (*notmuch->query_parser, notmuch))->release ();
316         else
317             fp = (new RegexpFieldProcessor (prefix->name, prefix->flags,
318                                             *notmuch->query_parser, notmuch))->release ();
319
320         /* we treat all field-processor fields as boolean in order to get the raw input */
321         notmuch->query_parser->add_boolean_prefix (prefix->name, fp);
322     } else {
323         _setup_query_field_default (prefix, notmuch);
324     }
325 }
326 #else
327 static inline void
328 _setup_query_field (const prefix_t *prefix, notmuch_database_t *notmuch)
329 {
330     _setup_query_field_default (prefix, notmuch);
331 }
332 #endif
333
334 const char *
335 _find_prefix (const char *name)
336 {
337     unsigned int i;
338
339     for (i = 0; i < ARRAY_SIZE (prefix_table); i++) {
340         if (strcmp (name, prefix_table[i].name) == 0)
341             return prefix_table[i].prefix;
342     }
343
344     INTERNAL_ERROR ("No prefix exists for '%s'\n", name);
345
346     return "";
347 }
348
349 static const struct {
350     /* NOTMUCH_FEATURE_* value. */
351     _notmuch_features value;
352     /* Feature name as it appears in the database.  This name should
353      * be appropriate for displaying to the user if an older version
354      * of notmuch doesn't support this feature. */
355     const char *name;
356     /* Compatibility flags when this feature is declared. */
357     const char *flags;
358 } feature_names[] = {
359     { NOTMUCH_FEATURE_FILE_TERMS,
360       "multiple paths per message", "rw" },
361     { NOTMUCH_FEATURE_DIRECTORY_DOCS,
362       "relative directory paths", "rw" },
363     /* Header values are not required for reading a database because a
364      * reader can just refer to the message file. */
365     { NOTMUCH_FEATURE_FROM_SUBJECT_ID_VALUES,
366       "from/subject/message-ID in database", "w" },
367     { NOTMUCH_FEATURE_BOOL_FOLDER,
368       "exact folder:/path: search", "rw" },
369     { NOTMUCH_FEATURE_GHOSTS,
370       "mail documents for missing messages", "w"},
371     /* Knowledge of the index mime-types are not required for reading
372      * a database because a reader will just be unable to query
373      * them. */
374     { NOTMUCH_FEATURE_INDEXED_MIMETYPES,
375       "indexed MIME types", "w"},
376     { NOTMUCH_FEATURE_LAST_MOD,
377       "modification tracking", "w"},
378 };
379
380 const char *
381 notmuch_status_to_string (notmuch_status_t status)
382 {
383     switch (status) {
384     case NOTMUCH_STATUS_SUCCESS:
385         return "No error occurred";
386     case NOTMUCH_STATUS_OUT_OF_MEMORY:
387         return "Out of memory";
388     case NOTMUCH_STATUS_READ_ONLY_DATABASE:
389         return "Attempt to write to a read-only database";
390     case NOTMUCH_STATUS_XAPIAN_EXCEPTION:
391         return "A Xapian exception occurred";
392     case NOTMUCH_STATUS_FILE_ERROR:
393         return "Something went wrong trying to read or write a file";
394     case NOTMUCH_STATUS_FILE_NOT_EMAIL:
395         return "File is not an email";
396     case NOTMUCH_STATUS_DUPLICATE_MESSAGE_ID:
397         return "Message ID is identical to a message in database";
398     case NOTMUCH_STATUS_NULL_POINTER:
399         return "Erroneous NULL pointer";
400     case NOTMUCH_STATUS_TAG_TOO_LONG:
401         return "Tag value is too long (exceeds NOTMUCH_TAG_MAX)";
402     case NOTMUCH_STATUS_UNBALANCED_FREEZE_THAW:
403         return "Unbalanced number of calls to notmuch_message_freeze/thaw";
404     case NOTMUCH_STATUS_UNBALANCED_ATOMIC:
405         return "Unbalanced number of calls to notmuch_database_begin_atomic/end_atomic";
406     case NOTMUCH_STATUS_UNSUPPORTED_OPERATION:
407         return "Unsupported operation";
408     case NOTMUCH_STATUS_UPGRADE_REQUIRED:
409         return "Operation requires a database upgrade";
410     case NOTMUCH_STATUS_PATH_ERROR:
411         return "Path supplied is illegal for this function";
412     default:
413     case NOTMUCH_STATUS_LAST_STATUS:
414         return "Unknown error status value";
415     }
416 }
417
418 void
419 _notmuch_database_log (notmuch_database_t *notmuch,
420                       const char *format,
421                       ...)
422 {
423     va_list va_args;
424
425     va_start (va_args, format);
426
427     if (notmuch->status_string)
428         talloc_free (notmuch->status_string);
429
430     notmuch->status_string = talloc_vasprintf (notmuch, format, va_args);
431     va_end (va_args);
432 }
433
434 void
435 _notmuch_database_log_append (notmuch_database_t *notmuch,
436                       const char *format,
437                       ...)
438 {
439     va_list va_args;
440
441     va_start (va_args, format);
442
443     if (notmuch->status_string)
444         notmuch->status_string = talloc_vasprintf_append (notmuch->status_string, format, va_args);
445     else
446         notmuch->status_string = talloc_vasprintf (notmuch, format, va_args);
447
448     va_end (va_args);
449 }
450
451 static void
452 find_doc_ids_for_term (notmuch_database_t *notmuch,
453                        const char *term,
454                        Xapian::PostingIterator *begin,
455                        Xapian::PostingIterator *end)
456 {
457     *begin = notmuch->xapian_db->postlist_begin (term);
458
459     *end = notmuch->xapian_db->postlist_end (term);
460 }
461
462 static void
463 find_doc_ids (notmuch_database_t *notmuch,
464               const char *prefix_name,
465               const char *value,
466               Xapian::PostingIterator *begin,
467               Xapian::PostingIterator *end)
468 {
469     char *term;
470
471     term = talloc_asprintf (notmuch, "%s%s",
472                             _find_prefix (prefix_name), value);
473
474     find_doc_ids_for_term (notmuch, term, begin, end);
475
476     talloc_free (term);
477 }
478
479 notmuch_private_status_t
480 _notmuch_database_find_unique_doc_id (notmuch_database_t *notmuch,
481                                       const char *prefix_name,
482                                       const char *value,
483                                       unsigned int *doc_id)
484 {
485     Xapian::PostingIterator i, end;
486
487     find_doc_ids (notmuch, prefix_name, value, &i, &end);
488
489     if (i == end) {
490         *doc_id = 0;
491         return NOTMUCH_PRIVATE_STATUS_NO_DOCUMENT_FOUND;
492     }
493
494     *doc_id = *i;
495
496 #if DEBUG_DATABASE_SANITY
497     i++;
498
499     if (i != end)
500         INTERNAL_ERROR ("Term %s:%s is not unique as expected.\n",
501                         prefix_name, value);
502 #endif
503
504     return NOTMUCH_PRIVATE_STATUS_SUCCESS;
505 }
506
507 static Xapian::Document
508 find_document_for_doc_id (notmuch_database_t *notmuch, unsigned doc_id)
509 {
510     return notmuch->xapian_db->get_document (doc_id);
511 }
512
513 /* Generate a compressed version of 'message_id' of the form:
514  *
515  *      notmuch-sha1-<sha1_sum_of_message_id>
516  */
517 char *
518 _notmuch_message_id_compressed (void *ctx, const char *message_id)
519 {
520     char *sha1, *compressed;
521
522     sha1 = _notmuch_sha1_of_string (message_id);
523
524     compressed = talloc_asprintf (ctx, "notmuch-sha1-%s", sha1);
525     free (sha1);
526
527     return compressed;
528 }
529
530 notmuch_status_t
531 notmuch_database_find_message (notmuch_database_t *notmuch,
532                                const char *message_id,
533                                notmuch_message_t **message_ret)
534 {
535     notmuch_private_status_t status;
536     unsigned int doc_id;
537
538     if (message_ret == NULL)
539         return NOTMUCH_STATUS_NULL_POINTER;
540
541     if (strlen (message_id) > NOTMUCH_MESSAGE_ID_MAX)
542         message_id = _notmuch_message_id_compressed (notmuch, message_id);
543
544     try {
545         status = _notmuch_database_find_unique_doc_id (notmuch, "id",
546                                                        message_id, &doc_id);
547
548         if (status == NOTMUCH_PRIVATE_STATUS_NO_DOCUMENT_FOUND)
549             *message_ret = NULL;
550         else {
551             *message_ret = _notmuch_message_create (notmuch, notmuch, doc_id,
552                                                     NULL);
553             if (*message_ret == NULL)
554                 return NOTMUCH_STATUS_OUT_OF_MEMORY;
555         }
556
557         return NOTMUCH_STATUS_SUCCESS;
558     } catch (const Xapian::Error &error) {
559         _notmuch_database_log (notmuch, "A Xapian exception occurred finding message: %s.\n",
560                  error.get_msg().c_str());
561         notmuch->exception_reported = TRUE;
562         *message_ret = NULL;
563         return NOTMUCH_STATUS_XAPIAN_EXCEPTION;
564     }
565 }
566
567 /* Advance 'str' past any whitespace or RFC 822 comments. A comment is
568  * a (potentially nested) parenthesized sequence with '\' used to
569  * escape any character (including parentheses).
570  *
571  * If the sequence to be skipped continues to the end of the string,
572  * then 'str' will be left pointing at the final terminating '\0'
573  * character.
574  */
575 static void
576 skip_space_and_comments (const char **str)
577 {
578     const char *s;
579
580     s = *str;
581     while (*s && (isspace (*s) || *s == '(')) {
582         while (*s && isspace (*s))
583             s++;
584         if (*s == '(') {
585             int nesting = 1;
586             s++;
587             while (*s && nesting) {
588                 if (*s == '(') {
589                     nesting++;
590                 } else if (*s == ')') {
591                     nesting--;
592                 } else if (*s == '\\') {
593                     if (*(s+1))
594                         s++;
595                 }
596                 s++;
597             }
598         }
599     }
600
601     *str = s;
602 }
603
604 /* Parse an RFC 822 message-id, discarding whitespace, any RFC 822
605  * comments, and the '<' and '>' delimiters.
606  *
607  * If not NULL, then *next will be made to point to the first character
608  * not parsed, (possibly pointing to the final '\0' terminator.
609  *
610  * Returns a newly talloc'ed string belonging to 'ctx'.
611  *
612  * Returns NULL if there is any error parsing the message-id. */
613 static char *
614 _parse_message_id (void *ctx, const char *message_id, const char **next)
615 {
616     const char *s, *end;
617     char *result;
618
619     if (message_id == NULL || *message_id == '\0')
620         return NULL;
621
622     s = message_id;
623
624     skip_space_and_comments (&s);
625
626     /* Skip any unstructured text as well. */
627     while (*s && *s != '<')
628         s++;
629
630     if (*s == '<') {
631         s++;
632     } else {
633         if (next)
634             *next = s;
635         return NULL;
636     }
637
638     skip_space_and_comments (&s);
639
640     end = s;
641     while (*end && *end != '>')
642         end++;
643     if (next) {
644         if (*end)
645             *next = end + 1;
646         else
647             *next = end;
648     }
649
650     if (end > s && *end == '>')
651         end--;
652     if (end <= s)
653         return NULL;
654
655     result = talloc_strndup (ctx, s, end - s + 1);
656
657     /* Finally, collapse any whitespace that is within the message-id
658      * itself. */
659     {
660         char *r;
661         int len;
662
663         for (r = result, len = strlen (r); *r; r++, len--)
664             if (*r == ' ' || *r == '\t')
665                 memmove (r, r+1, len);
666     }
667
668     return result;
669 }
670
671 /* Parse a References header value, putting a (talloc'ed under 'ctx')
672  * copy of each referenced message-id into 'hash'.
673  *
674  * We explicitly avoid including any reference identical to
675  * 'message_id' in the result (to avoid mass confusion when a single
676  * message references itself cyclically---and yes, mail messages are
677  * not infrequent in the wild that do this---don't ask me why).
678  *
679  * Return the last reference parsed, if it is not equal to message_id.
680  */
681 static char *
682 parse_references (void *ctx,
683                   const char *message_id,
684                   GHashTable *hash,
685                   const char *refs)
686 {
687     char *ref, *last_ref = NULL;
688
689     if (refs == NULL || *refs == '\0')
690         return NULL;
691
692     while (*refs) {
693         ref = _parse_message_id (ctx, refs, &refs);
694
695         if (ref && strcmp (ref, message_id)) {
696             g_hash_table_add (hash, ref);
697             last_ref = ref;
698         }
699     }
700
701     /* The return value of this function is used to add a parent
702      * reference to the database.  We should avoid making a message
703      * its own parent, thus the above check.
704      */
705     return talloc_strdup(ctx, last_ref);
706 }
707
708 notmuch_status_t
709 notmuch_database_create (const char *path, notmuch_database_t **database)
710 {
711     char *status_string = NULL;
712     notmuch_status_t status;
713
714     status = notmuch_database_create_verbose (path, database,
715                                               &status_string);
716
717     if (status_string) {
718         fputs (status_string, stderr);
719         free (status_string);
720     }
721
722     return status;
723 }
724
725 notmuch_status_t
726 notmuch_database_create_verbose (const char *path,
727                                  notmuch_database_t **database,
728                                  char **status_string)
729 {
730     notmuch_status_t status = NOTMUCH_STATUS_SUCCESS;
731     notmuch_database_t *notmuch = NULL;
732     char *notmuch_path = NULL;
733     char *message = NULL;
734     struct stat st;
735     int err;
736
737     if (path == NULL) {
738         message = strdup ("Error: Cannot create a database for a NULL path.\n");
739         status = NOTMUCH_STATUS_NULL_POINTER;
740         goto DONE;
741     }
742
743     if (path[0] != '/') {
744         message = strdup ("Error: Database path must be absolute.\n");
745         status = NOTMUCH_STATUS_PATH_ERROR;
746         goto DONE;
747     }
748
749     err = stat (path, &st);
750     if (err) {
751         IGNORE_RESULT (asprintf (&message, "Error: Cannot create database at %s: %s.\n",
752                                 path, strerror (errno)));
753         status = NOTMUCH_STATUS_FILE_ERROR;
754         goto DONE;
755     }
756
757     if (! S_ISDIR (st.st_mode)) {
758         IGNORE_RESULT (asprintf (&message, "Error: Cannot create database at %s: "
759                                  "Not a directory.\n",
760                                  path));
761         status = NOTMUCH_STATUS_FILE_ERROR;
762         goto DONE;
763     }
764
765     notmuch_path = talloc_asprintf (NULL, "%s/%s", path, ".notmuch");
766
767     err = mkdir (notmuch_path, 0755);
768
769     if (err) {
770         IGNORE_RESULT (asprintf (&message, "Error: Cannot create directory %s: %s.\n",
771                                  notmuch_path, strerror (errno)));
772         status = NOTMUCH_STATUS_FILE_ERROR;
773         goto DONE;
774     }
775
776     status = notmuch_database_open_verbose (path,
777                                             NOTMUCH_DATABASE_MODE_READ_WRITE,
778                                             &notmuch, &message);
779     if (status)
780         goto DONE;
781
782     /* Upgrade doesn't add these feature to existing databases, but
783      * new databases have them. */
784     notmuch->features |= NOTMUCH_FEATURE_FROM_SUBJECT_ID_VALUES;
785     notmuch->features |= NOTMUCH_FEATURE_INDEXED_MIMETYPES;
786
787     status = notmuch_database_upgrade (notmuch, NULL, NULL);
788     if (status) {
789         notmuch_database_close(notmuch);
790         notmuch = NULL;
791     }
792
793   DONE:
794     if (notmuch_path)
795         talloc_free (notmuch_path);
796
797     if (message) {
798         if (status_string)
799             *status_string = message;
800         else
801             free (message);
802     }
803     if (database)
804         *database = notmuch;
805     else
806         talloc_free (notmuch);
807     return status;
808 }
809
810 notmuch_status_t
811 _notmuch_database_ensure_writable (notmuch_database_t *notmuch)
812 {
813     if (notmuch->mode == NOTMUCH_DATABASE_MODE_READ_ONLY) {
814         _notmuch_database_log (notmuch, "Cannot write to a read-only database.\n");
815         return NOTMUCH_STATUS_READ_ONLY_DATABASE;
816     }
817
818     return NOTMUCH_STATUS_SUCCESS;
819 }
820
821 /* Allocate a revision number for the next change. */
822 unsigned long
823 _notmuch_database_new_revision (notmuch_database_t *notmuch)
824 {
825     unsigned long new_revision = notmuch->revision + 1;
826
827     /* If we're in an atomic section, hold off on updating the
828      * committed revision number until we commit the atomic section.
829      */
830     if (notmuch->atomic_nesting)
831         notmuch->atomic_dirty = TRUE;
832     else
833         notmuch->revision = new_revision;
834
835     return new_revision;
836 }
837
838 /* Parse a database features string from the given database version.
839  * Returns the feature bit set.
840  *
841  * For version < 3, this ignores the features string and returns a
842  * hard-coded set of features.
843  *
844  * If there are unrecognized features that are required to open the
845  * database in mode (which should be 'r' or 'w'), return a
846  * comma-separated list of unrecognized but required features in
847  * *incompat_out suitable for presenting to the user.  *incompat_out
848  * will be allocated from ctx.
849  */
850 static _notmuch_features
851 _parse_features (const void *ctx, const char *features, unsigned int version,
852                  char mode, char **incompat_out)
853 {
854     _notmuch_features res = static_cast<_notmuch_features>(0);
855     unsigned int namelen, i;
856     size_t llen = 0;
857     const char *flags;
858
859     /* Prior to database version 3, features were implied by the
860      * version number. */
861     if (version == 0)
862         return NOTMUCH_FEATURES_V0;
863     else if (version == 1)
864         return NOTMUCH_FEATURES_V1;
865     else if (version == 2)
866         return NOTMUCH_FEATURES_V2;
867
868     /* Parse the features string */
869     while ((features = strtok_len_c (features + llen, "\n", &llen)) != NULL) {
870         flags = strchr (features, '\t');
871         if (! flags || flags > features + llen)
872             continue;
873         namelen = flags - features;
874
875         for (i = 0; i < ARRAY_SIZE (feature_names); ++i) {
876             if (strlen (feature_names[i].name) == namelen &&
877                 strncmp (feature_names[i].name, features, namelen) == 0) {
878                 res |= feature_names[i].value;
879                 break;
880             }
881         }
882
883         if (i == ARRAY_SIZE (feature_names) && incompat_out) {
884             /* Unrecognized feature */
885             const char *have = strchr (flags, mode);
886             if (have && have < features + llen) {
887                 /* This feature is required to access this database in
888                  * 'mode', but we don't understand it. */
889                 if (! *incompat_out)
890                     *incompat_out = talloc_strdup (ctx, "");
891                 *incompat_out = talloc_asprintf_append_buffer (
892                     *incompat_out, "%s%.*s", **incompat_out ? ", " : "",
893                     namelen, features);
894             }
895         }
896     }
897
898     return res;
899 }
900
901 static char *
902 _print_features (const void *ctx, unsigned int features)
903 {
904     unsigned int i;
905     char *res = talloc_strdup (ctx, "");
906
907     for (i = 0; i < ARRAY_SIZE (feature_names); ++i)
908         if (features & feature_names[i].value)
909             res = talloc_asprintf_append_buffer (
910                 res, "%s\t%s\n", feature_names[i].name, feature_names[i].flags);
911
912     return res;
913 }
914
915 notmuch_status_t
916 notmuch_database_open (const char *path,
917                        notmuch_database_mode_t mode,
918                        notmuch_database_t **database)
919 {
920     char *status_string = NULL;
921     notmuch_status_t status;
922
923     status = notmuch_database_open_verbose (path, mode, database,
924                                            &status_string);
925
926     if (status_string) {
927         fputs (status_string, stderr);
928         free (status_string);
929     }
930
931     return status;
932 }
933
934 notmuch_status_t
935 notmuch_database_open_verbose (const char *path,
936                                notmuch_database_mode_t mode,
937                                notmuch_database_t **database,
938                                char **status_string)
939 {
940     notmuch_status_t status = NOTMUCH_STATUS_SUCCESS;
941     void *local = talloc_new (NULL);
942     notmuch_database_t *notmuch = NULL;
943     char *notmuch_path, *xapian_path, *incompat_features;
944     char *message = NULL;
945     struct stat st;
946     int err;
947     unsigned int i, version;
948     static int initialized = 0;
949
950     if (path == NULL) {
951         message = strdup ("Error: Cannot open a database for a NULL path.\n");
952         status = NOTMUCH_STATUS_NULL_POINTER;
953         goto DONE;
954     }
955
956     if (path[0] != '/') {
957         message = strdup ("Error: Database path must be absolute.\n");
958         status = NOTMUCH_STATUS_PATH_ERROR;
959         goto DONE;
960     }
961
962     if (! (notmuch_path = talloc_asprintf (local, "%s/%s", path, ".notmuch"))) {
963         message = strdup ("Out of memory\n");
964         status = NOTMUCH_STATUS_OUT_OF_MEMORY;
965         goto DONE;
966     }
967
968     err = stat (notmuch_path, &st);
969     if (err) {
970         IGNORE_RESULT (asprintf (&message, "Error opening database at %s: %s\n",
971                                  notmuch_path, strerror (errno)));
972         status = NOTMUCH_STATUS_FILE_ERROR;
973         goto DONE;
974     }
975
976     if (! (xapian_path = talloc_asprintf (local, "%s/%s", notmuch_path, "xapian"))) {
977         message = strdup ("Out of memory\n");
978         status = NOTMUCH_STATUS_OUT_OF_MEMORY;
979         goto DONE;
980     }
981
982     /* Initialize the GLib type system and threads */
983 #if !GLIB_CHECK_VERSION(2, 35, 1)
984     g_type_init ();
985 #endif
986
987     /* Initialize gmime */
988     if (! initialized) {
989         g_mime_init (GMIME_ENABLE_RFC2047_WORKAROUNDS);
990         initialized = 1;
991     }
992
993     notmuch = talloc_zero (NULL, notmuch_database_t);
994     notmuch->exception_reported = FALSE;
995     notmuch->status_string = NULL;
996     notmuch->path = talloc_strdup (notmuch, path);
997
998     if (notmuch->path[strlen (notmuch->path) - 1] == '/')
999         notmuch->path[strlen (notmuch->path) - 1] = '\0';
1000
1001     notmuch->mode = mode;
1002     notmuch->atomic_nesting = 0;
1003     notmuch->view = 1;
1004     try {
1005         string last_thread_id;
1006         string last_mod;
1007
1008         if (mode == NOTMUCH_DATABASE_MODE_READ_WRITE) {
1009             notmuch->xapian_db = new Xapian::WritableDatabase (xapian_path,
1010                                                                DB_ACTION);
1011         } else {
1012             notmuch->xapian_db = new Xapian::Database (xapian_path);
1013         }
1014
1015         /* Check version.  As of database version 3, we represent
1016          * changes in terms of features, so assume a version bump
1017          * means a dramatically incompatible change. */
1018         version = notmuch_database_get_version (notmuch);
1019         if (version > NOTMUCH_DATABASE_VERSION) {
1020             IGNORE_RESULT (asprintf (&message,
1021                       "Error: Notmuch database at %s\n"
1022                       "       has a newer database format version (%u) than supported by this\n"
1023                       "       version of notmuch (%u).\n",
1024                                      notmuch_path, version, NOTMUCH_DATABASE_VERSION));
1025             notmuch->mode = NOTMUCH_DATABASE_MODE_READ_ONLY;
1026             notmuch_database_destroy (notmuch);
1027             notmuch = NULL;
1028             status = NOTMUCH_STATUS_FILE_ERROR;
1029             goto DONE;
1030         }
1031
1032         /* Check features. */
1033         incompat_features = NULL;
1034         notmuch->features = _parse_features (
1035             local, notmuch->xapian_db->get_metadata ("features").c_str (),
1036             version, mode == NOTMUCH_DATABASE_MODE_READ_WRITE ? 'w' : 'r',
1037             &incompat_features);
1038         if (incompat_features) {
1039             IGNORE_RESULT (asprintf (&message,
1040                 "Error: Notmuch database at %s\n"
1041                 "       requires features (%s)\n"
1042                 "       not supported by this version of notmuch.\n",
1043                                      notmuch_path, incompat_features));
1044             notmuch->mode = NOTMUCH_DATABASE_MODE_READ_ONLY;
1045             notmuch_database_destroy (notmuch);
1046             notmuch = NULL;
1047             status = NOTMUCH_STATUS_FILE_ERROR;
1048             goto DONE;
1049         }
1050
1051         notmuch->last_doc_id = notmuch->xapian_db->get_lastdocid ();
1052         last_thread_id = notmuch->xapian_db->get_metadata ("last_thread_id");
1053         if (last_thread_id.empty ()) {
1054             notmuch->last_thread_id = 0;
1055         } else {
1056             const char *str;
1057             char *end;
1058
1059             str = last_thread_id.c_str ();
1060             notmuch->last_thread_id = strtoull (str, &end, 16);
1061             if (*end != '\0')
1062                 INTERNAL_ERROR ("Malformed database last_thread_id: %s", str);
1063         }
1064
1065         /* Get current highest revision number. */
1066         last_mod = notmuch->xapian_db->get_value_upper_bound (
1067             NOTMUCH_VALUE_LAST_MOD);
1068         if (last_mod.empty ())
1069             notmuch->revision = 0;
1070         else
1071             notmuch->revision = Xapian::sortable_unserialise (last_mod);
1072         notmuch->uuid = talloc_strdup (
1073             notmuch, notmuch->xapian_db->get_uuid ().c_str ());
1074
1075         notmuch->query_parser = new Xapian::QueryParser;
1076         notmuch->term_gen = new Xapian::TermGenerator;
1077         notmuch->term_gen->set_stemmer (Xapian::Stem ("english"));
1078         notmuch->value_range_processor = new Xapian::NumberValueRangeProcessor (NOTMUCH_VALUE_TIMESTAMP);
1079         notmuch->date_range_processor = new ParseTimeValueRangeProcessor (NOTMUCH_VALUE_TIMESTAMP);
1080         notmuch->last_mod_range_processor = new Xapian::NumberValueRangeProcessor (NOTMUCH_VALUE_LAST_MOD, "lastmod:");
1081
1082         notmuch->query_parser->set_default_op (Xapian::Query::OP_AND);
1083         notmuch->query_parser->set_database (*notmuch->xapian_db);
1084         notmuch->query_parser->set_stemmer (Xapian::Stem ("english"));
1085         notmuch->query_parser->set_stemming_strategy (Xapian::QueryParser::STEM_SOME);
1086         notmuch->query_parser->add_valuerangeprocessor (notmuch->value_range_processor);
1087         notmuch->query_parser->add_valuerangeprocessor (notmuch->date_range_processor);
1088         notmuch->query_parser->add_valuerangeprocessor (notmuch->last_mod_range_processor);
1089
1090         for (i = 0; i < ARRAY_SIZE (prefix_table); i++) {
1091             const prefix_t *prefix = &prefix_table[i];
1092             if (prefix->flags & NOTMUCH_FIELD_EXTERNAL) {
1093                 _setup_query_field (prefix, notmuch);
1094             }
1095         }
1096     } catch (const Xapian::Error &error) {
1097         IGNORE_RESULT (asprintf (&message, "A Xapian exception occurred opening database: %s\n",
1098                                  error.get_msg().c_str()));
1099         notmuch_database_destroy (notmuch);
1100         notmuch = NULL;
1101         status = NOTMUCH_STATUS_XAPIAN_EXCEPTION;
1102     }
1103
1104   DONE:
1105     talloc_free (local);
1106
1107     if (message) {
1108         if (status_string)
1109             *status_string = message;
1110         else
1111             free (message);
1112     }
1113
1114     if (database)
1115         *database = notmuch;
1116     else
1117         talloc_free (notmuch);
1118     return status;
1119 }
1120
1121 notmuch_status_t
1122 notmuch_database_close (notmuch_database_t *notmuch)
1123 {
1124     notmuch_status_t status = NOTMUCH_STATUS_SUCCESS;
1125
1126     /* Many Xapian objects (and thus notmuch objects) hold references to
1127      * the database, so merely deleting the database may not suffice to
1128      * close it.  Thus, we explicitly close it here. */
1129     if (notmuch->xapian_db != NULL) {
1130         try {
1131             /* If there's an outstanding transaction, it's unclear if
1132              * closing the Xapian database commits everything up to
1133              * that transaction, or may discard committed (but
1134              * unflushed) transactions.  To be certain, explicitly
1135              * cancel any outstanding transaction before closing. */
1136             if (notmuch->mode == NOTMUCH_DATABASE_MODE_READ_WRITE &&
1137                 notmuch->atomic_nesting)
1138                 (static_cast <Xapian::WritableDatabase *> (notmuch->xapian_db))
1139                     ->cancel_transaction ();
1140
1141             /* Close the database.  This implicitly flushes
1142              * outstanding changes. */
1143             notmuch->xapian_db->close();
1144         } catch (const Xapian::Error &error) {
1145             status = NOTMUCH_STATUS_XAPIAN_EXCEPTION;
1146             if (! notmuch->exception_reported) {
1147                 _notmuch_database_log (notmuch, "Error: A Xapian exception occurred closing database: %s\n",
1148                          error.get_msg().c_str());
1149             }
1150         }
1151     }
1152
1153     delete notmuch->term_gen;
1154     notmuch->term_gen = NULL;
1155     delete notmuch->query_parser;
1156     notmuch->query_parser = NULL;
1157     delete notmuch->xapian_db;
1158     notmuch->xapian_db = NULL;
1159     delete notmuch->value_range_processor;
1160     notmuch->value_range_processor = NULL;
1161     delete notmuch->date_range_processor;
1162     notmuch->date_range_processor = NULL;
1163     delete notmuch->last_mod_range_processor;
1164     notmuch->last_mod_range_processor = NULL;
1165
1166     return status;
1167 }
1168
1169 notmuch_status_t
1170 _notmuch_database_reopen (notmuch_database_t *notmuch)
1171 {
1172     if (notmuch->mode != NOTMUCH_DATABASE_MODE_READ_ONLY)
1173         return NOTMUCH_STATUS_UNSUPPORTED_OPERATION;
1174
1175     try {
1176         notmuch->xapian_db->reopen ();
1177     } catch (const Xapian::Error &error) {
1178         if (! notmuch->exception_reported) {
1179             _notmuch_database_log (notmuch, "Error: A Xapian exception reopening database: %s\n",
1180                                    error.get_msg ().c_str ());
1181             notmuch->exception_reported = TRUE;
1182         }
1183         return NOTMUCH_STATUS_XAPIAN_EXCEPTION;
1184     }
1185
1186     notmuch->view++;
1187
1188     return NOTMUCH_STATUS_SUCCESS;
1189 }
1190
1191 static int
1192 unlink_cb (const char *path,
1193            unused (const struct stat *sb),
1194            unused (int type),
1195            unused (struct FTW *ftw))
1196 {
1197     return remove (path);
1198 }
1199
1200 static int
1201 rmtree (const char *path)
1202 {
1203     return nftw (path, unlink_cb, 64, FTW_DEPTH | FTW_PHYS);
1204 }
1205
1206 class NotmuchCompactor : public Xapian::Compactor
1207 {
1208     notmuch_compact_status_cb_t status_cb;
1209     void *status_closure;
1210
1211 public:
1212     NotmuchCompactor(notmuch_compact_status_cb_t cb, void *closure) :
1213         status_cb (cb), status_closure (closure) { }
1214
1215     virtual void
1216     set_status (const std::string &table, const std::string &status)
1217     {
1218         char *msg;
1219
1220         if (status_cb == NULL)
1221             return;
1222
1223         if (status.length () == 0)
1224             msg = talloc_asprintf (NULL, "compacting table %s", table.c_str());
1225         else
1226             msg = talloc_asprintf (NULL, "     %s", status.c_str());
1227
1228         if (msg == NULL) {
1229             return;
1230         }
1231
1232         status_cb (msg, status_closure);
1233         talloc_free (msg);
1234     }
1235 };
1236
1237 /* Compacts the given database, optionally saving the original database
1238  * in backup_path. Additionally, a callback function can be provided to
1239  * give the user feedback on the progress of the (likely long-lived)
1240  * compaction process.
1241  *
1242  * The backup path must point to a directory on the same volume as the
1243  * original database. Passing a NULL backup_path will result in the
1244  * uncompacted database being deleted after compaction has finished.
1245  * Note that the database write lock will be held during the
1246  * compaction process to protect data integrity.
1247  */
1248 notmuch_status_t
1249 notmuch_database_compact (const char *path,
1250                           const char *backup_path,
1251                           notmuch_compact_status_cb_t status_cb,
1252                           void *closure)
1253 {
1254     void *local;
1255     char *notmuch_path, *xapian_path, *compact_xapian_path;
1256     notmuch_status_t ret = NOTMUCH_STATUS_SUCCESS;
1257     notmuch_database_t *notmuch = NULL;
1258     struct stat statbuf;
1259     notmuch_bool_t keep_backup;
1260     char *message = NULL;
1261
1262     local = talloc_new (NULL);
1263     if (! local)
1264         return NOTMUCH_STATUS_OUT_OF_MEMORY;
1265
1266     ret = notmuch_database_open_verbose (path,
1267                                          NOTMUCH_DATABASE_MODE_READ_WRITE,
1268                                          &notmuch,
1269                                          &message);
1270     if (ret) {
1271         if (status_cb) status_cb (message, closure);
1272         goto DONE;
1273     }
1274
1275     if (! (notmuch_path = talloc_asprintf (local, "%s/%s", path, ".notmuch"))) {
1276         ret = NOTMUCH_STATUS_OUT_OF_MEMORY;
1277         goto DONE;
1278     }
1279
1280     if (! (xapian_path = talloc_asprintf (local, "%s/%s", notmuch_path, "xapian"))) {
1281         ret = NOTMUCH_STATUS_OUT_OF_MEMORY;
1282         goto DONE;
1283     }
1284
1285     if (! (compact_xapian_path = talloc_asprintf (local, "%s.compact", xapian_path))) {
1286         ret = NOTMUCH_STATUS_OUT_OF_MEMORY;
1287         goto DONE;
1288     }
1289
1290     if (backup_path == NULL) {
1291         if (! (backup_path = talloc_asprintf (local, "%s.old", xapian_path))) {
1292             ret = NOTMUCH_STATUS_OUT_OF_MEMORY;
1293             goto DONE;
1294         }
1295         keep_backup = FALSE;
1296     }
1297     else {
1298         keep_backup = TRUE;
1299     }
1300
1301     if (stat (backup_path, &statbuf) != -1) {
1302         _notmuch_database_log (notmuch, "Path already exists: %s\n", backup_path);
1303         ret = NOTMUCH_STATUS_FILE_ERROR;
1304         goto DONE;
1305     }
1306     if (errno != ENOENT) {
1307         _notmuch_database_log (notmuch, "Unknown error while stat()ing path: %s\n",
1308                  strerror (errno));
1309         ret = NOTMUCH_STATUS_FILE_ERROR;
1310         goto DONE;
1311     }
1312
1313     /* Unconditionally attempt to remove old work-in-progress database (if
1314      * any). This is "protected" by database lock. If this fails due to write
1315      * errors (etc), the following code will fail and provide error message.
1316      */
1317     (void) rmtree (compact_xapian_path);
1318
1319     try {
1320         NotmuchCompactor compactor (status_cb, closure);
1321
1322         compactor.set_renumber (false);
1323         compactor.add_source (xapian_path);
1324         compactor.set_destdir (compact_xapian_path);
1325         compactor.compact ();
1326     } catch (const Xapian::Error &error) {
1327         _notmuch_database_log (notmuch, "Error while compacting: %s\n", error.get_msg().c_str());
1328         ret = NOTMUCH_STATUS_XAPIAN_EXCEPTION;
1329         goto DONE;
1330     }
1331
1332     if (rename (xapian_path, backup_path)) {
1333         _notmuch_database_log (notmuch, "Error moving %s to %s: %s\n",
1334                  xapian_path, backup_path, strerror (errno));
1335         ret = NOTMUCH_STATUS_FILE_ERROR;
1336         goto DONE;
1337     }
1338
1339     if (rename (compact_xapian_path, xapian_path)) {
1340         _notmuch_database_log (notmuch, "Error moving %s to %s: %s\n",
1341                  compact_xapian_path, xapian_path, strerror (errno));
1342         ret = NOTMUCH_STATUS_FILE_ERROR;
1343         goto DONE;
1344     }
1345
1346     if (! keep_backup) {
1347         if (rmtree (backup_path)) {
1348             _notmuch_database_log (notmuch, "Error removing old database %s: %s\n",
1349                      backup_path, strerror (errno));
1350             ret = NOTMUCH_STATUS_FILE_ERROR;
1351             goto DONE;
1352         }
1353     }
1354
1355   DONE:
1356     if (notmuch) {
1357         notmuch_status_t ret2;
1358
1359         const char *str = notmuch_database_status_string (notmuch);
1360         if (status_cb && str)
1361             status_cb (str, closure);
1362
1363         ret2 = notmuch_database_destroy (notmuch);
1364
1365         /* don't clobber previous error status */
1366         if (ret == NOTMUCH_STATUS_SUCCESS && ret2 != NOTMUCH_STATUS_SUCCESS)
1367             ret = ret2;
1368     }
1369
1370     talloc_free (local);
1371
1372     return ret;
1373 }
1374
1375 notmuch_status_t
1376 notmuch_database_destroy (notmuch_database_t *notmuch)
1377 {
1378     notmuch_status_t status;
1379
1380     status = notmuch_database_close (notmuch);
1381     talloc_free (notmuch);
1382
1383     return status;
1384 }
1385
1386 const char *
1387 notmuch_database_get_path (notmuch_database_t *notmuch)
1388 {
1389     return notmuch->path;
1390 }
1391
1392 unsigned int
1393 notmuch_database_get_version (notmuch_database_t *notmuch)
1394 {
1395     unsigned int version;
1396     string version_string;
1397     const char *str;
1398     char *end;
1399
1400     version_string = notmuch->xapian_db->get_metadata ("version");
1401     if (version_string.empty ())
1402         return 0;
1403
1404     str = version_string.c_str ();
1405     if (str == NULL || *str == '\0')
1406         return 0;
1407
1408     version = strtoul (str, &end, 10);
1409     if (*end != '\0')
1410         INTERNAL_ERROR ("Malformed database version: %s", str);
1411
1412     return version;
1413 }
1414
1415 notmuch_bool_t
1416 notmuch_database_needs_upgrade (notmuch_database_t *notmuch)
1417 {
1418     return notmuch->mode == NOTMUCH_DATABASE_MODE_READ_WRITE &&
1419         ((NOTMUCH_FEATURES_CURRENT & ~notmuch->features) ||
1420          (notmuch_database_get_version (notmuch) < NOTMUCH_DATABASE_VERSION));
1421 }
1422
1423 static volatile sig_atomic_t do_progress_notify = 0;
1424
1425 static void
1426 handle_sigalrm (unused (int signal))
1427 {
1428     do_progress_notify = 1;
1429 }
1430
1431 /* Upgrade the current database.
1432  *
1433  * After opening a database in read-write mode, the client should
1434  * check if an upgrade is needed (notmuch_database_needs_upgrade) and
1435  * if so, upgrade with this function before making any modifications.
1436  *
1437  * The optional progress_notify callback can be used by the caller to
1438  * provide progress indication to the user. If non-NULL it will be
1439  * called periodically with 'count' as the number of messages upgraded
1440  * so far and 'total' the overall number of messages that will be
1441  * converted.
1442  */
1443 notmuch_status_t
1444 notmuch_database_upgrade (notmuch_database_t *notmuch,
1445                           void (*progress_notify) (void *closure,
1446                                                    double progress),
1447                           void *closure)
1448 {
1449     void *local = talloc_new (NULL);
1450     Xapian::TermIterator t, t_end;
1451     Xapian::WritableDatabase *db;
1452     struct sigaction action;
1453     struct itimerval timerval;
1454     notmuch_bool_t timer_is_active = FALSE;
1455     enum _notmuch_features target_features, new_features;
1456     notmuch_status_t status;
1457     notmuch_private_status_t private_status;
1458     notmuch_query_t *query = NULL;
1459     unsigned int count = 0, total = 0;
1460
1461     status = _notmuch_database_ensure_writable (notmuch);
1462     if (status)
1463         return status;
1464
1465     db = static_cast <Xapian::WritableDatabase *> (notmuch->xapian_db);
1466
1467     target_features = notmuch->features | NOTMUCH_FEATURES_CURRENT;
1468     new_features = NOTMUCH_FEATURES_CURRENT & ~notmuch->features;
1469
1470     if (! notmuch_database_needs_upgrade (notmuch))
1471         return NOTMUCH_STATUS_SUCCESS;
1472
1473     if (progress_notify) {
1474         /* Set up our handler for SIGALRM */
1475         memset (&action, 0, sizeof (struct sigaction));
1476         action.sa_handler = handle_sigalrm;
1477         sigemptyset (&action.sa_mask);
1478         action.sa_flags = SA_RESTART;
1479         sigaction (SIGALRM, &action, NULL);
1480
1481         /* Then start a timer to send SIGALRM once per second. */
1482         timerval.it_interval.tv_sec = 1;
1483         timerval.it_interval.tv_usec = 0;
1484         timerval.it_value.tv_sec = 1;
1485         timerval.it_value.tv_usec = 0;
1486         setitimer (ITIMER_REAL, &timerval, NULL);
1487
1488         timer_is_active = TRUE;
1489     }
1490
1491     /* Figure out how much total work we need to do. */
1492     if (new_features &
1493         (NOTMUCH_FEATURE_FILE_TERMS | NOTMUCH_FEATURE_BOOL_FOLDER |
1494          NOTMUCH_FEATURE_LAST_MOD)) {
1495         query = notmuch_query_create (notmuch, "");
1496         unsigned msg_count;
1497
1498         status = notmuch_query_count_messages (query, &msg_count);
1499         if (status)
1500             goto DONE;
1501
1502         total += msg_count;
1503         notmuch_query_destroy (query);
1504         query = NULL;
1505     }
1506     if (new_features & NOTMUCH_FEATURE_DIRECTORY_DOCS) {
1507         t_end = db->allterms_end ("XTIMESTAMP");
1508         for (t = db->allterms_begin ("XTIMESTAMP"); t != t_end; t++)
1509             ++total;
1510     }
1511     if (new_features & NOTMUCH_FEATURE_GHOSTS) {
1512         /* The ghost message upgrade converts all thread_id_*
1513          * metadata values into ghost message documents. */
1514         t_end = db->metadata_keys_end ("thread_id_");
1515         for (t = db->metadata_keys_begin ("thread_id_"); t != t_end; ++t)
1516             ++total;
1517     }
1518
1519     /* Perform the upgrade in a transaction. */
1520     db->begin_transaction (true);
1521
1522     /* Set the target features so we write out changes in the desired
1523      * format. */
1524     notmuch->features = target_features;
1525
1526     /* Perform per-message upgrades. */
1527     if (new_features &
1528         (NOTMUCH_FEATURE_FILE_TERMS | NOTMUCH_FEATURE_BOOL_FOLDER |
1529          NOTMUCH_FEATURE_LAST_MOD)) {
1530         notmuch_messages_t *messages;
1531         notmuch_message_t *message;
1532         char *filename;
1533
1534         query = notmuch_query_create (notmuch, "");
1535
1536         status = notmuch_query_search_messages (query, &messages);
1537         if (status)
1538             goto DONE;
1539         for (;
1540              notmuch_messages_valid (messages);
1541              notmuch_messages_move_to_next (messages))
1542         {
1543             if (do_progress_notify) {
1544                 progress_notify (closure, (double) count / total);
1545                 do_progress_notify = 0;
1546             }
1547
1548             message = notmuch_messages_get (messages);
1549
1550             /* Before version 1, each message document had its
1551              * filename in the data field. Copy that into the new
1552              * format by calling notmuch_message_add_filename.
1553              */
1554             if (new_features & NOTMUCH_FEATURE_FILE_TERMS) {
1555                 filename = _notmuch_message_talloc_copy_data (message);
1556                 if (filename && *filename != '\0') {
1557                     _notmuch_message_add_filename (message, filename);
1558                     _notmuch_message_clear_data (message);
1559                 }
1560                 talloc_free (filename);
1561             }
1562
1563             /* Prior to version 2, the "folder:" prefix was
1564              * probabilistic and stemmed. Change it to the current
1565              * boolean prefix. Add "path:" prefixes while at it.
1566              */
1567             if (new_features & NOTMUCH_FEATURE_BOOL_FOLDER)
1568                 _notmuch_message_upgrade_folder (message);
1569
1570             /* Prior to NOTMUCH_FEATURE_LAST_MOD, messages did not
1571              * track modification revisions.  Give all messages the
1572              * next available revision; since we just started tracking
1573              * revisions for this database, that will be 1.
1574              */
1575             if (new_features & NOTMUCH_FEATURE_LAST_MOD)
1576                 _notmuch_message_upgrade_last_mod (message);
1577
1578             _notmuch_message_sync (message);
1579
1580             notmuch_message_destroy (message);
1581
1582             count++;
1583         }
1584
1585         notmuch_query_destroy (query);
1586         query = NULL;
1587     }
1588
1589     /* Perform per-directory upgrades. */
1590
1591     /* Before version 1 we stored directory timestamps in
1592      * XTIMESTAMP documents instead of the current XDIRECTORY
1593      * documents. So copy those as well. */
1594     if (new_features & NOTMUCH_FEATURE_DIRECTORY_DOCS) {
1595         t_end = notmuch->xapian_db->allterms_end ("XTIMESTAMP");
1596
1597         for (t = notmuch->xapian_db->allterms_begin ("XTIMESTAMP");
1598              t != t_end;
1599              t++)
1600         {
1601             Xapian::PostingIterator p, p_end;
1602             std::string term = *t;
1603
1604             p_end = notmuch->xapian_db->postlist_end (term);
1605
1606             for (p = notmuch->xapian_db->postlist_begin (term);
1607                  p != p_end;
1608                  p++)
1609             {
1610                 Xapian::Document document;
1611                 time_t mtime;
1612                 notmuch_directory_t *directory;
1613
1614                 if (do_progress_notify) {
1615                     progress_notify (closure, (double) count / total);
1616                     do_progress_notify = 0;
1617                 }
1618
1619                 document = find_document_for_doc_id (notmuch, *p);
1620                 mtime = Xapian::sortable_unserialise (
1621                     document.get_value (NOTMUCH_VALUE_TIMESTAMP));
1622
1623                 directory = _notmuch_directory_create (notmuch, term.c_str() + 10,
1624                                                        NOTMUCH_FIND_CREATE, &status);
1625                 notmuch_directory_set_mtime (directory, mtime);
1626                 notmuch_directory_destroy (directory);
1627
1628                 db->delete_document (*p);
1629             }
1630
1631             ++count;
1632         }
1633     }
1634
1635     /* Perform metadata upgrades. */
1636
1637     /* Prior to NOTMUCH_FEATURE_GHOSTS, thread IDs for missing
1638      * messages were stored as database metadata. Change these to
1639      * ghost messages.
1640      */
1641     if (new_features & NOTMUCH_FEATURE_GHOSTS) {
1642         notmuch_message_t *message;
1643         std::string message_id, thread_id;
1644
1645         t_end = db->metadata_keys_end (NOTMUCH_METADATA_THREAD_ID_PREFIX);
1646         for (t = db->metadata_keys_begin (NOTMUCH_METADATA_THREAD_ID_PREFIX);
1647              t != t_end; ++t) {
1648             if (do_progress_notify) {
1649                 progress_notify (closure, (double) count / total);
1650                 do_progress_notify = 0;
1651             }
1652
1653             message_id = (*t).substr (
1654                 strlen (NOTMUCH_METADATA_THREAD_ID_PREFIX));
1655             thread_id = db->get_metadata (*t);
1656
1657             /* Create ghost message */
1658             message = _notmuch_message_create_for_message_id (
1659                 notmuch, message_id.c_str (), &private_status);
1660             if (private_status == NOTMUCH_PRIVATE_STATUS_SUCCESS) {
1661                 /* Document already exists; ignore the stored thread ID */
1662             } else if (private_status ==
1663                        NOTMUCH_PRIVATE_STATUS_NO_DOCUMENT_FOUND) {
1664                 private_status = _notmuch_message_initialize_ghost (
1665                     message, thread_id.c_str ());
1666                 if (! private_status)
1667                     _notmuch_message_sync (message);
1668             }
1669
1670             if (private_status) {
1671                 _notmuch_database_log (notmuch,
1672                          "Upgrade failed while creating ghost messages.\n");
1673                 status = COERCE_STATUS (private_status, "Unexpected status from _notmuch_message_initialize_ghost");
1674                 goto DONE;
1675             }
1676
1677             /* Clear saved metadata thread ID */
1678             db->set_metadata (*t, "");
1679
1680             ++count;
1681         }
1682     }
1683
1684     status = NOTMUCH_STATUS_SUCCESS;
1685     db->set_metadata ("features", _print_features (local, notmuch->features));
1686     db->set_metadata ("version", STRINGIFY (NOTMUCH_DATABASE_VERSION));
1687
1688  DONE:
1689     if (status == NOTMUCH_STATUS_SUCCESS)
1690         db->commit_transaction ();
1691     else
1692         db->cancel_transaction ();
1693
1694     if (timer_is_active) {
1695         /* Now stop the timer. */
1696         timerval.it_interval.tv_sec = 0;
1697         timerval.it_interval.tv_usec = 0;
1698         timerval.it_value.tv_sec = 0;
1699         timerval.it_value.tv_usec = 0;
1700         setitimer (ITIMER_REAL, &timerval, NULL);
1701
1702         /* And disable the signal handler. */
1703         action.sa_handler = SIG_IGN;
1704         sigaction (SIGALRM, &action, NULL);
1705     }
1706
1707     if (query)
1708         notmuch_query_destroy (query);
1709
1710     talloc_free (local);
1711     return status;
1712 }
1713
1714 notmuch_status_t
1715 notmuch_database_begin_atomic (notmuch_database_t *notmuch)
1716 {
1717     if (notmuch->mode == NOTMUCH_DATABASE_MODE_READ_ONLY ||
1718         notmuch->atomic_nesting > 0)
1719         goto DONE;
1720
1721     if (notmuch_database_needs_upgrade (notmuch))
1722         return NOTMUCH_STATUS_UPGRADE_REQUIRED;
1723
1724     try {
1725         (static_cast <Xapian::WritableDatabase *> (notmuch->xapian_db))->begin_transaction (false);
1726     } catch (const Xapian::Error &error) {
1727         _notmuch_database_log (notmuch, "A Xapian exception occurred beginning transaction: %s.\n",
1728                  error.get_msg().c_str());
1729         notmuch->exception_reported = TRUE;
1730         return NOTMUCH_STATUS_XAPIAN_EXCEPTION;
1731     }
1732
1733 DONE:
1734     notmuch->atomic_nesting++;
1735     return NOTMUCH_STATUS_SUCCESS;
1736 }
1737
1738 notmuch_status_t
1739 notmuch_database_end_atomic (notmuch_database_t *notmuch)
1740 {
1741     Xapian::WritableDatabase *db;
1742
1743     if (notmuch->atomic_nesting == 0)
1744         return NOTMUCH_STATUS_UNBALANCED_ATOMIC;
1745
1746     if (notmuch->mode == NOTMUCH_DATABASE_MODE_READ_ONLY ||
1747         notmuch->atomic_nesting > 1)
1748         goto DONE;
1749
1750     db = static_cast <Xapian::WritableDatabase *> (notmuch->xapian_db);
1751     try {
1752         db->commit_transaction ();
1753
1754         /* This is a hack for testing.  Xapian never flushes on a
1755          * non-flushed commit, even if the flush threshold is 1.
1756          * However, we rely on flushing to test atomicity. */
1757         const char *thresh = getenv ("XAPIAN_FLUSH_THRESHOLD");
1758         if (thresh && atoi (thresh) == 1)
1759             db->commit ();
1760     } catch (const Xapian::Error &error) {
1761         _notmuch_database_log (notmuch, "A Xapian exception occurred committing transaction: %s.\n",
1762                  error.get_msg().c_str());
1763         notmuch->exception_reported = TRUE;
1764         return NOTMUCH_STATUS_XAPIAN_EXCEPTION;
1765     }
1766
1767     if (notmuch->atomic_dirty) {
1768         ++notmuch->revision;
1769         notmuch->atomic_dirty = FALSE;
1770     }
1771
1772 DONE:
1773     notmuch->atomic_nesting--;
1774     return NOTMUCH_STATUS_SUCCESS;
1775 }
1776
1777 unsigned long
1778 notmuch_database_get_revision (notmuch_database_t *notmuch,
1779                                 const char **uuid)
1780 {
1781     if (uuid)
1782         *uuid = notmuch->uuid;
1783     return notmuch->revision;
1784 }
1785
1786 /* We allow the user to use arbitrarily long paths for directories. But
1787  * we have a term-length limit. So if we exceed that, we'll use the
1788  * SHA-1 of the path for the database term.
1789  *
1790  * Note: This function may return the original value of 'path'. If it
1791  * does not, then the caller is responsible to free() the returned
1792  * value.
1793  */
1794 const char *
1795 _notmuch_database_get_directory_db_path (const char *path)
1796 {
1797     int term_len = strlen (_find_prefix ("directory")) + strlen (path);
1798
1799     if (term_len > NOTMUCH_TERM_MAX)
1800         return _notmuch_sha1_of_string (path);
1801     else
1802         return path;
1803 }
1804
1805 /* Given a path, split it into two parts: the directory part is all
1806  * components except for the last, and the basename is that last
1807  * component. Getting the return-value for either part is optional
1808  * (the caller can pass NULL).
1809  *
1810  * The original 'path' can represent either a regular file or a
1811  * directory---the splitting will be carried out in the same way in
1812  * either case. Trailing slashes on 'path' will be ignored, and any
1813  * cases of multiple '/' characters appearing in series will be
1814  * treated as a single '/'.
1815  *
1816  * Allocation (if any) will have 'ctx' as the talloc owner. But
1817  * pointers will be returned within the original path string whenever
1818  * possible.
1819  *
1820  * Note: If 'path' is non-empty and contains no non-trailing slash,
1821  * (that is, consists of a filename with no parent directory), then
1822  * the directory returned will be an empty string. However, if 'path'
1823  * is an empty string, then both directory and basename will be
1824  * returned as NULL.
1825  */
1826 notmuch_status_t
1827 _notmuch_database_split_path (void *ctx,
1828                               const char *path,
1829                               const char **directory,
1830                               const char **basename)
1831 {
1832     const char *slash;
1833
1834     if (path == NULL || *path == '\0') {
1835         if (directory)
1836             *directory = NULL;
1837         if (basename)
1838             *basename = NULL;
1839         return NOTMUCH_STATUS_SUCCESS;
1840     }
1841
1842     /* Find the last slash (not counting a trailing slash), if any. */
1843
1844     slash = path + strlen (path) - 1;
1845
1846     /* First, skip trailing slashes. */
1847     while (slash != path && *slash == '/')
1848         --slash;
1849
1850     /* Then, find a slash. */
1851     while (slash != path && *slash != '/') {
1852         if (basename)
1853             *basename = slash;
1854
1855         --slash;
1856     }
1857
1858     /* Finally, skip multiple slashes. */
1859     while (slash != path && *(slash - 1) == '/')
1860         --slash;
1861
1862     if (slash == path) {
1863         if (directory)
1864             *directory = talloc_strdup (ctx, "");
1865         if (basename)
1866             *basename = path;
1867     } else {
1868         if (directory)
1869             *directory = talloc_strndup (ctx, path, slash - path);
1870     }
1871
1872     return NOTMUCH_STATUS_SUCCESS;
1873 }
1874
1875 /* Find the document ID of the specified directory.
1876  *
1877  * If (flags & NOTMUCH_FIND_CREATE), a new directory document will be
1878  * created if one does not exist for 'path'.  Otherwise, if the
1879  * directory document does not exist, this sets *directory_id to
1880  * ((unsigned int)-1) and returns NOTMUCH_STATUS_SUCCESS.
1881  */
1882 notmuch_status_t
1883 _notmuch_database_find_directory_id (notmuch_database_t *notmuch,
1884                                      const char *path,
1885                                      notmuch_find_flags_t flags,
1886                                      unsigned int *directory_id)
1887 {
1888     notmuch_directory_t *directory;
1889     notmuch_status_t status;
1890
1891     if (path == NULL) {
1892         *directory_id = 0;
1893         return NOTMUCH_STATUS_SUCCESS;
1894     }
1895
1896     directory = _notmuch_directory_create (notmuch, path, flags, &status);
1897     if (status || !directory) {
1898         *directory_id = -1;
1899         return status;
1900     }
1901
1902     *directory_id = _notmuch_directory_get_document_id (directory);
1903
1904     notmuch_directory_destroy (directory);
1905
1906     return NOTMUCH_STATUS_SUCCESS;
1907 }
1908
1909 const char *
1910 _notmuch_database_get_directory_path (void *ctx,
1911                                       notmuch_database_t *notmuch,
1912                                       unsigned int doc_id)
1913 {
1914     Xapian::Document document;
1915
1916     document = find_document_for_doc_id (notmuch, doc_id);
1917
1918     return talloc_strdup (ctx, document.get_data ().c_str ());
1919 }
1920
1921 /* Given a legal 'filename' for the database, (either relative to
1922  * database path or absolute with initial components identical to
1923  * database path), return a new string (with 'ctx' as the talloc
1924  * owner) suitable for use as a direntry term value.
1925  *
1926  * If (flags & NOTMUCH_FIND_CREATE), the necessary directory documents
1927  * will be created in the database as needed.  Otherwise, if the
1928  * necessary directory documents do not exist, this sets
1929  * *direntry to NULL and returns NOTMUCH_STATUS_SUCCESS.
1930  */
1931 notmuch_status_t
1932 _notmuch_database_filename_to_direntry (void *ctx,
1933                                         notmuch_database_t *notmuch,
1934                                         const char *filename,
1935                                         notmuch_find_flags_t flags,
1936                                         char **direntry)
1937 {
1938     const char *relative, *directory, *basename;
1939     Xapian::docid directory_id;
1940     notmuch_status_t status;
1941
1942     relative = _notmuch_database_relative_path (notmuch, filename);
1943
1944     status = _notmuch_database_split_path (ctx, relative,
1945                                            &directory, &basename);
1946     if (status)
1947         return status;
1948
1949     status = _notmuch_database_find_directory_id (notmuch, directory, flags,
1950                                                   &directory_id);
1951     if (status || directory_id == (unsigned int)-1) {
1952         *direntry = NULL;
1953         return status;
1954     }
1955
1956     *direntry = talloc_asprintf (ctx, "%u:%s", directory_id, basename);
1957
1958     return NOTMUCH_STATUS_SUCCESS;
1959 }
1960
1961 /* Given a legal 'path' for the database, return the relative path.
1962  *
1963  * The return value will be a pointer to the original path contents,
1964  * and will be either the original string (if 'path' was relative) or
1965  * a portion of the string (if path was absolute and begins with the
1966  * database path).
1967  */
1968 const char *
1969 _notmuch_database_relative_path (notmuch_database_t *notmuch,
1970                                  const char *path)
1971 {
1972     const char *db_path, *relative;
1973     unsigned int db_path_len;
1974
1975     db_path = notmuch_database_get_path (notmuch);
1976     db_path_len = strlen (db_path);
1977
1978     relative = path;
1979
1980     if (*relative == '/') {
1981         while (*relative == '/' && *(relative+1) == '/')
1982             relative++;
1983
1984         if (strncmp (relative, db_path, db_path_len) == 0)
1985         {
1986             relative += db_path_len;
1987             while (*relative == '/')
1988                 relative++;
1989         }
1990     }
1991
1992     return relative;
1993 }
1994
1995 notmuch_status_t
1996 notmuch_database_get_directory (notmuch_database_t *notmuch,
1997                                 const char *path,
1998                                 notmuch_directory_t **directory)
1999 {
2000     notmuch_status_t status;
2001
2002     if (directory == NULL)
2003         return NOTMUCH_STATUS_NULL_POINTER;
2004     *directory = NULL;
2005
2006     try {
2007         *directory = _notmuch_directory_create (notmuch, path,
2008                                                 NOTMUCH_FIND_LOOKUP, &status);
2009     } catch (const Xapian::Error &error) {
2010         _notmuch_database_log (notmuch, "A Xapian exception occurred getting directory: %s.\n",
2011                  error.get_msg().c_str());
2012         notmuch->exception_reported = TRUE;
2013         status = NOTMUCH_STATUS_XAPIAN_EXCEPTION;
2014     }
2015     return status;
2016 }
2017
2018 /* Allocate a document ID that satisfies the following criteria:
2019  *
2020  * 1. The ID does not exist for any document in the Xapian database
2021  *
2022  * 2. The ID was not previously returned from this function
2023  *
2024  * 3. The ID is the smallest integer satisfying (1) and (2)
2025  *
2026  * This function will trigger an internal error if these constraints
2027  * cannot all be satisfied, (that is, the pool of available document
2028  * IDs has been exhausted).
2029  */
2030 unsigned int
2031 _notmuch_database_generate_doc_id (notmuch_database_t *notmuch)
2032 {
2033     assert (notmuch->last_doc_id >= notmuch->xapian_db->get_lastdocid ());
2034
2035     notmuch->last_doc_id++;
2036
2037     if (notmuch->last_doc_id == 0)
2038         INTERNAL_ERROR ("Xapian document IDs are exhausted.\n");
2039
2040     return notmuch->last_doc_id;
2041 }
2042
2043 static const char *
2044 _notmuch_database_generate_thread_id (notmuch_database_t *notmuch)
2045 {
2046     /* 16 bytes (+ terminator) for hexadecimal representation of
2047      * a 64-bit integer. */
2048     static char thread_id[17];
2049     Xapian::WritableDatabase *db;
2050
2051     db = static_cast <Xapian::WritableDatabase *> (notmuch->xapian_db);
2052
2053     notmuch->last_thread_id++;
2054
2055     sprintf (thread_id, "%016" PRIx64, notmuch->last_thread_id);
2056
2057     db->set_metadata ("last_thread_id", thread_id);
2058
2059     return thread_id;
2060 }
2061
2062 static char *
2063 _get_metadata_thread_id_key (void *ctx, const char *message_id)
2064 {
2065     if (strlen (message_id) > NOTMUCH_MESSAGE_ID_MAX)
2066         message_id = _notmuch_message_id_compressed (ctx, message_id);
2067
2068     return talloc_asprintf (ctx, NOTMUCH_METADATA_THREAD_ID_PREFIX "%s",
2069                             message_id);
2070 }
2071
2072 static notmuch_status_t
2073 _resolve_message_id_to_thread_id_old (notmuch_database_t *notmuch,
2074                                       void *ctx,
2075                                       const char *message_id,
2076                                       const char **thread_id_ret);
2077
2078 /* Find the thread ID to which the message with 'message_id' belongs.
2079  *
2080  * Note: 'thread_id_ret' must not be NULL!
2081  * On success '*thread_id_ret' is set to a newly talloced string belonging to
2082  * 'ctx'.
2083  *
2084  * Note: If there is no message in the database with the given
2085  * 'message_id' then a new thread_id will be allocated for this
2086  * message ID and stored in the database metadata so that the
2087  * thread ID can be looked up if the message is added to the database
2088  * later.
2089  */
2090 static notmuch_status_t
2091 _resolve_message_id_to_thread_id (notmuch_database_t *notmuch,
2092                                   void *ctx,
2093                                   const char *message_id,
2094                                   const char **thread_id_ret)
2095 {
2096     notmuch_private_status_t status;
2097     notmuch_message_t *message;
2098
2099     if (! (notmuch->features & NOTMUCH_FEATURE_GHOSTS))
2100         return _resolve_message_id_to_thread_id_old (notmuch, ctx, message_id,
2101                                                      thread_id_ret);
2102
2103     /* Look for this message (regular or ghost) */
2104     message = _notmuch_message_create_for_message_id (
2105         notmuch, message_id, &status);
2106     if (status == NOTMUCH_PRIVATE_STATUS_SUCCESS) {
2107         /* Message exists */
2108         *thread_id_ret = talloc_steal (
2109             ctx, notmuch_message_get_thread_id (message));
2110     } else if (status == NOTMUCH_PRIVATE_STATUS_NO_DOCUMENT_FOUND) {
2111         /* Message did not exist.  Give it a fresh thread ID and
2112          * populate this message as a ghost message. */
2113         *thread_id_ret = talloc_strdup (
2114             ctx, _notmuch_database_generate_thread_id (notmuch));
2115         if (! *thread_id_ret) {
2116             status = NOTMUCH_PRIVATE_STATUS_OUT_OF_MEMORY;
2117         } else {
2118             status = _notmuch_message_initialize_ghost (message, *thread_id_ret);
2119             if (status == 0)
2120                 /* Commit the new ghost message */
2121                 _notmuch_message_sync (message);
2122         }
2123     } else {
2124         /* Create failed. Fall through. */
2125     }
2126
2127     notmuch_message_destroy (message);
2128
2129     return COERCE_STATUS (status, "Error creating ghost message");
2130 }
2131
2132 /* Pre-ghost messages _resolve_message_id_to_thread_id */
2133 static notmuch_status_t
2134 _resolve_message_id_to_thread_id_old (notmuch_database_t *notmuch,
2135                                       void *ctx,
2136                                       const char *message_id,
2137                                       const char **thread_id_ret)
2138 {
2139     notmuch_status_t status;
2140     notmuch_message_t *message;
2141     string thread_id_string;
2142     char *metadata_key;
2143     Xapian::WritableDatabase *db;
2144
2145     status = notmuch_database_find_message (notmuch, message_id, &message);
2146
2147     if (status)
2148         return status;
2149
2150     if (message) {
2151         *thread_id_ret = talloc_steal (ctx,
2152                                        notmuch_message_get_thread_id (message));
2153
2154         notmuch_message_destroy (message);
2155
2156         return NOTMUCH_STATUS_SUCCESS;
2157     }
2158
2159     /* Message has not been seen yet.
2160      *
2161      * We may have seen a reference to it already, in which case, we
2162      * can return the thread ID stored in the metadata. Otherwise, we
2163      * generate a new thread ID and store it there.
2164      */
2165     db = static_cast <Xapian::WritableDatabase *> (notmuch->xapian_db);
2166     metadata_key = _get_metadata_thread_id_key (ctx, message_id);
2167     thread_id_string = notmuch->xapian_db->get_metadata (metadata_key);
2168
2169     if (thread_id_string.empty()) {
2170         *thread_id_ret = talloc_strdup (ctx,
2171                                         _notmuch_database_generate_thread_id (notmuch));
2172         db->set_metadata (metadata_key, *thread_id_ret);
2173     } else {
2174         *thread_id_ret = talloc_strdup (ctx, thread_id_string.c_str());
2175     }
2176
2177     talloc_free (metadata_key);
2178
2179     return NOTMUCH_STATUS_SUCCESS;
2180 }
2181
2182 static notmuch_status_t
2183 _merge_threads (notmuch_database_t *notmuch,
2184                 const char *winner_thread_id,
2185                 const char *loser_thread_id)
2186 {
2187     Xapian::PostingIterator loser, loser_end;
2188     notmuch_message_t *message = NULL;
2189     notmuch_private_status_t private_status;
2190     notmuch_status_t ret = NOTMUCH_STATUS_SUCCESS;
2191
2192     find_doc_ids (notmuch, "thread", loser_thread_id, &loser, &loser_end);
2193
2194     for ( ; loser != loser_end; loser++) {
2195         message = _notmuch_message_create (notmuch, notmuch,
2196                                            *loser, &private_status);
2197         if (message == NULL) {
2198             ret = COERCE_STATUS (private_status,
2199                                  "Cannot find document for doc_id from query");
2200             goto DONE;
2201         }
2202
2203         _notmuch_message_remove_term (message, "thread", loser_thread_id);
2204         _notmuch_message_add_term (message, "thread", winner_thread_id);
2205         _notmuch_message_sync (message);
2206
2207         notmuch_message_destroy (message);
2208         message = NULL;
2209     }
2210
2211   DONE:
2212     if (message)
2213         notmuch_message_destroy (message);
2214
2215     return ret;
2216 }
2217
2218 static void
2219 _my_talloc_free_for_g_hash (void *ptr)
2220 {
2221     talloc_free (ptr);
2222 }
2223
2224 static notmuch_status_t
2225 _notmuch_database_link_message_to_parents (notmuch_database_t *notmuch,
2226                                            notmuch_message_t *message,
2227                                            notmuch_message_file_t *message_file,
2228                                            const char **thread_id)
2229 {
2230     GHashTable *parents = NULL;
2231     const char *refs, *in_reply_to, *in_reply_to_message_id;
2232     const char *last_ref_message_id, *this_message_id;
2233     GList *l, *keys = NULL;
2234     notmuch_status_t ret = NOTMUCH_STATUS_SUCCESS;
2235
2236     parents = g_hash_table_new_full (g_str_hash, g_str_equal,
2237                                      _my_talloc_free_for_g_hash, NULL);
2238     this_message_id = notmuch_message_get_message_id (message);
2239
2240     refs = _notmuch_message_file_get_header (message_file, "references");
2241     last_ref_message_id = parse_references (message,
2242                                             this_message_id,
2243                                             parents, refs);
2244
2245     in_reply_to = _notmuch_message_file_get_header (message_file, "in-reply-to");
2246     in_reply_to_message_id = parse_references (message,
2247                                                this_message_id,
2248                                                parents, in_reply_to);
2249
2250     /* For the parent of this message, use the last message ID of the
2251      * References header, if available.  If not, fall back to the
2252      * first message ID in the In-Reply-To header. */
2253     if (last_ref_message_id) {
2254         _notmuch_message_add_term (message, "replyto",
2255                                    last_ref_message_id);
2256     } else if (in_reply_to_message_id) {
2257         _notmuch_message_add_term (message, "replyto",
2258                              in_reply_to_message_id);
2259     }
2260
2261     keys = g_hash_table_get_keys (parents);
2262     for (l = keys; l; l = l->next) {
2263         char *parent_message_id;
2264         const char *parent_thread_id = NULL;
2265
2266         parent_message_id = (char *) l->data;
2267
2268         _notmuch_message_add_term (message, "reference",
2269                                    parent_message_id);
2270
2271         ret = _resolve_message_id_to_thread_id (notmuch,
2272                                                 message,
2273                                                 parent_message_id,
2274                                                 &parent_thread_id);
2275         if (ret)
2276             goto DONE;
2277
2278         if (*thread_id == NULL) {
2279             *thread_id = talloc_strdup (message, parent_thread_id);
2280             _notmuch_message_add_term (message, "thread", *thread_id);
2281         } else if (strcmp (*thread_id, parent_thread_id)) {
2282             ret = _merge_threads (notmuch, *thread_id, parent_thread_id);
2283             if (ret)
2284                 goto DONE;
2285         }
2286     }
2287
2288   DONE:
2289     if (keys)
2290         g_list_free (keys);
2291     if (parents)
2292         g_hash_table_unref (parents);
2293
2294     return ret;
2295 }
2296
2297 static notmuch_status_t
2298 _notmuch_database_link_message_to_children (notmuch_database_t *notmuch,
2299                                             notmuch_message_t *message,
2300                                             const char **thread_id)
2301 {
2302     const char *message_id = notmuch_message_get_message_id (message);
2303     Xapian::PostingIterator child, children_end;
2304     notmuch_message_t *child_message = NULL;
2305     const char *child_thread_id;
2306     notmuch_status_t ret = NOTMUCH_STATUS_SUCCESS;
2307     notmuch_private_status_t private_status;
2308
2309     find_doc_ids (notmuch, "reference", message_id, &child, &children_end);
2310
2311     for ( ; child != children_end; child++) {
2312
2313         child_message = _notmuch_message_create (message, notmuch,
2314                                                  *child, &private_status);
2315         if (child_message == NULL) {
2316             ret = COERCE_STATUS (private_status,
2317                                  "Cannot find document for doc_id from query");
2318             goto DONE;
2319         }
2320
2321         child_thread_id = notmuch_message_get_thread_id (child_message);
2322         if (*thread_id == NULL) {
2323             *thread_id = talloc_strdup (message, child_thread_id);
2324             _notmuch_message_add_term (message, "thread", *thread_id);
2325         } else if (strcmp (*thread_id, child_thread_id)) {
2326             _notmuch_message_remove_term (child_message, "reference",
2327                                           message_id);
2328             _notmuch_message_sync (child_message);
2329             ret = _merge_threads (notmuch, *thread_id, child_thread_id);
2330             if (ret)
2331                 goto DONE;
2332         }
2333
2334         notmuch_message_destroy (child_message);
2335         child_message = NULL;
2336     }
2337
2338   DONE:
2339     if (child_message)
2340         notmuch_message_destroy (child_message);
2341
2342     return ret;
2343 }
2344
2345 /* Fetch and clear the stored thread_id for message, or NULL if none. */
2346 static char *
2347 _consume_metadata_thread_id (void *ctx, notmuch_database_t *notmuch,
2348                              notmuch_message_t *message)
2349 {
2350     const char *message_id;
2351     string stored_id;
2352     char *metadata_key;
2353
2354     message_id = notmuch_message_get_message_id (message);
2355     metadata_key = _get_metadata_thread_id_key (ctx, message_id);
2356
2357     /* Check if we have already seen related messages to this one.
2358      * If we have then use the thread_id that we stored at that time.
2359      */
2360     stored_id = notmuch->xapian_db->get_metadata (metadata_key);
2361     if (stored_id.empty ()) {
2362         return NULL;
2363     } else {
2364         Xapian::WritableDatabase *db;
2365
2366         db = static_cast <Xapian::WritableDatabase *> (notmuch->xapian_db);
2367
2368         /* Clear the metadata for this message ID. We don't need it
2369          * anymore. */
2370         db->set_metadata (metadata_key, "");
2371
2372         return talloc_strdup (ctx, stored_id.c_str ());
2373     }
2374 }
2375
2376 /* Given a blank or ghost 'message' and its corresponding
2377  * 'message_file' link it to existing threads in the database.
2378  *
2379  * First, if is_ghost, this retrieves the thread ID already stored in
2380  * the message (which will be the case if a message was previously
2381  * added that referenced this one).  If the message is blank
2382  * (!is_ghost), it doesn't have a thread ID yet (we'll generate one
2383  * later in this function).  If the database does not support ghost
2384  * messages, this checks for a thread ID stored in database metadata
2385  * for this message ID.
2386  *
2387  * Second, we look at 'message_file' and its link-relevant headers
2388  * (References and In-Reply-To) for message IDs.
2389  *
2390  * Finally, we look in the database for existing message that
2391  * reference 'message'.
2392  *
2393  * In all cases, we assign to the current message the first thread ID
2394  * found. We will also merge any existing, distinct threads where this
2395  * message belongs to both, (which is not uncommon when messages are
2396  * processed out of order).
2397  *
2398  * Finally, if no thread ID has been found through referenced messages, we
2399  * call _notmuch_message_generate_thread_id to generate a new thread
2400  * ID. This should only happen for new, top-level messages, (no
2401  * References or In-Reply-To header in this message, and no previously
2402  * added message refers to this message).
2403  */
2404 static notmuch_status_t
2405 _notmuch_database_link_message (notmuch_database_t *notmuch,
2406                                 notmuch_message_t *message,
2407                                 notmuch_message_file_t *message_file,
2408                                 notmuch_bool_t is_ghost)
2409 {
2410     void *local = talloc_new (NULL);
2411     notmuch_status_t status;
2412     const char *thread_id = NULL;
2413
2414     /* Check if the message already had a thread ID */
2415     if (notmuch->features & NOTMUCH_FEATURE_GHOSTS) {
2416         if (is_ghost)
2417             thread_id = notmuch_message_get_thread_id (message);
2418     } else {
2419         thread_id = _consume_metadata_thread_id (local, notmuch, message);
2420         if (thread_id)
2421             _notmuch_message_add_term (message, "thread", thread_id);
2422     }
2423
2424     status = _notmuch_database_link_message_to_parents (notmuch, message,
2425                                                         message_file,
2426                                                         &thread_id);
2427     if (status)
2428         goto DONE;
2429
2430     if (! (notmuch->features & NOTMUCH_FEATURE_GHOSTS)) {
2431         /* In general, it shouldn't be necessary to link children,
2432          * since the earlier indexing of those children will have
2433          * stored a thread ID for the missing parent.  However, prior
2434          * to ghost messages, these stored thread IDs were NOT
2435          * rewritten during thread merging (and there was no
2436          * performant way to do so), so if indexed children were
2437          * pulled into a different thread ID by a merge, it was
2438          * necessary to pull them *back* into the stored thread ID of
2439          * the parent.  With ghost messages, we just rewrite the
2440          * stored thread IDs during merging, so this workaround isn't
2441          * necessary. */
2442         status = _notmuch_database_link_message_to_children (notmuch, message,
2443                                                              &thread_id);
2444         if (status)
2445             goto DONE;
2446     }
2447
2448     /* If not part of any existing thread, generate a new thread ID. */
2449     if (thread_id == NULL) {
2450         thread_id = _notmuch_database_generate_thread_id (notmuch);
2451
2452         _notmuch_message_add_term (message, "thread", thread_id);
2453     }
2454
2455  DONE:
2456     talloc_free (local);
2457
2458     return status;
2459 }
2460
2461 notmuch_status_t
2462 notmuch_database_add_message (notmuch_database_t *notmuch,
2463                               const char *filename,
2464                               notmuch_message_t **message_ret)
2465 {
2466     notmuch_message_file_t *message_file;
2467     notmuch_message_t *message = NULL;
2468     notmuch_status_t ret = NOTMUCH_STATUS_SUCCESS, ret2;
2469     notmuch_private_status_t private_status;
2470     notmuch_bool_t is_ghost = false;
2471
2472     const char *date, *header;
2473     const char *from, *to, *subject;
2474     char *message_id = NULL;
2475
2476     if (message_ret)
2477         *message_ret = NULL;
2478
2479     ret = _notmuch_database_ensure_writable (notmuch);
2480     if (ret)
2481         return ret;
2482
2483     message_file = _notmuch_message_file_open (notmuch, filename);
2484     if (message_file == NULL)
2485         return NOTMUCH_STATUS_FILE_ERROR;
2486
2487     /* Adding a message may change many documents.  Do this all
2488      * atomically. */
2489     ret = notmuch_database_begin_atomic (notmuch);
2490     if (ret)
2491         goto DONE;
2492
2493     /* Parse message up front to get better error status. */
2494     ret = _notmuch_message_file_parse (message_file);
2495     if (ret)
2496         goto DONE;
2497
2498     /* Before we do any real work, (especially before doing a
2499      * potential SHA-1 computation on the entire file's contents),
2500      * let's make sure that what we're looking at looks like an
2501      * actual email message.
2502      */
2503     from = _notmuch_message_file_get_header (message_file, "from");
2504     subject = _notmuch_message_file_get_header (message_file, "subject");
2505     to = _notmuch_message_file_get_header (message_file, "to");
2506
2507     if ((from == NULL || *from == '\0') &&
2508         (subject == NULL || *subject == '\0') &&
2509         (to == NULL || *to == '\0')) {
2510         ret = NOTMUCH_STATUS_FILE_NOT_EMAIL;
2511         goto DONE;
2512     }
2513
2514     /* Now that we're sure it's mail, the first order of business
2515      * is to find a message ID (or else create one ourselves).
2516      */
2517     header = _notmuch_message_file_get_header (message_file, "message-id");
2518     if (header && *header != '\0') {
2519         message_id = _parse_message_id (message_file, header, NULL);
2520
2521         /* So the header value isn't RFC-compliant, but it's
2522          * better than no message-id at all.
2523          */
2524         if (message_id == NULL)
2525             message_id = talloc_strdup (message_file, header);
2526     }
2527
2528     if (message_id == NULL ) {
2529         /* No message-id at all, let's generate one by taking a
2530          * hash over the file's contents.
2531          */
2532         char *sha1 = _notmuch_sha1_of_file (filename);
2533
2534         /* If that failed too, something is really wrong. Give up. */
2535         if (sha1 == NULL) {
2536             ret = NOTMUCH_STATUS_FILE_ERROR;
2537             goto DONE;
2538         }
2539
2540         message_id = talloc_asprintf (message_file, "notmuch-sha1-%s", sha1);
2541         free (sha1);
2542     }
2543
2544     try {
2545         /* Now that we have a message ID, we get a message object,
2546          * (which may or may not reference an existing document in the
2547          * database). */
2548
2549         message = _notmuch_message_create_for_message_id (notmuch,
2550                                                           message_id,
2551                                                           &private_status);
2552
2553         talloc_free (message_id);
2554
2555         if (message == NULL) {
2556             ret = COERCE_STATUS (private_status,
2557                                  "Unexpected status value from _notmuch_message_create_for_message_id");
2558             goto DONE;
2559         }
2560
2561         _notmuch_message_add_filename (message, filename);
2562
2563         /* Is this a newly created message object or a ghost
2564          * message?  We have to be slightly careful: if this is a
2565          * blank message, it's not safe to call
2566          * notmuch_message_get_flag yet. */
2567         if (private_status == NOTMUCH_PRIVATE_STATUS_NO_DOCUMENT_FOUND ||
2568             (is_ghost = notmuch_message_get_flag (
2569                 message, NOTMUCH_MESSAGE_FLAG_GHOST))) {
2570             _notmuch_message_add_term (message, "type", "mail");
2571             if (is_ghost)
2572                 /* Convert ghost message to a regular message */
2573                 _notmuch_message_remove_term (message, "type", "ghost");
2574
2575             ret = _notmuch_database_link_message (notmuch, message,
2576                                                   message_file, is_ghost);
2577             if (ret)
2578                 goto DONE;
2579
2580             date = _notmuch_message_file_get_header (message_file, "date");
2581             _notmuch_message_set_header_values (message, date, from, subject);
2582
2583             ret = _notmuch_message_index_file (message, message_file);
2584             if (ret)
2585                 goto DONE;
2586         } else {
2587             ret = NOTMUCH_STATUS_DUPLICATE_MESSAGE_ID;
2588         }
2589
2590         _notmuch_message_sync (message);
2591     } catch (const Xapian::Error &error) {
2592         _notmuch_database_log (notmuch, "A Xapian exception occurred adding message: %s.\n",
2593                  error.get_msg().c_str());
2594         notmuch->exception_reported = TRUE;
2595         ret = NOTMUCH_STATUS_XAPIAN_EXCEPTION;
2596         goto DONE;
2597     }
2598
2599   DONE:
2600     if (message) {
2601         if ((ret == NOTMUCH_STATUS_SUCCESS ||
2602              ret == NOTMUCH_STATUS_DUPLICATE_MESSAGE_ID) && message_ret)
2603             *message_ret = message;
2604         else
2605             notmuch_message_destroy (message);
2606     }
2607
2608     if (message_file)
2609         _notmuch_message_file_close (message_file);
2610
2611     ret2 = notmuch_database_end_atomic (notmuch);
2612     if ((ret == NOTMUCH_STATUS_SUCCESS ||
2613          ret == NOTMUCH_STATUS_DUPLICATE_MESSAGE_ID) &&
2614         ret2 != NOTMUCH_STATUS_SUCCESS)
2615         ret = ret2;
2616
2617     return ret;
2618 }
2619
2620 notmuch_status_t
2621 notmuch_database_remove_message (notmuch_database_t *notmuch,
2622                                  const char *filename)
2623 {
2624     notmuch_status_t status;
2625     notmuch_message_t *message;
2626
2627     status = notmuch_database_find_message_by_filename (notmuch, filename,
2628                                                         &message);
2629
2630     if (status == NOTMUCH_STATUS_SUCCESS && message) {
2631             status = _notmuch_message_remove_filename (message, filename);
2632             if (status == NOTMUCH_STATUS_SUCCESS)
2633                 _notmuch_message_delete (message);
2634             else if (status == NOTMUCH_STATUS_DUPLICATE_MESSAGE_ID)
2635                 _notmuch_message_sync (message);
2636
2637             notmuch_message_destroy (message);
2638     }
2639
2640     return status;
2641 }
2642
2643 notmuch_status_t
2644 notmuch_database_find_message_by_filename (notmuch_database_t *notmuch,
2645                                            const char *filename,
2646                                            notmuch_message_t **message_ret)
2647 {
2648     void *local;
2649     const char *prefix = _find_prefix ("file-direntry");
2650     char *direntry, *term;
2651     Xapian::PostingIterator i, end;
2652     notmuch_status_t status;
2653
2654     if (message_ret == NULL)
2655         return NOTMUCH_STATUS_NULL_POINTER;
2656
2657     if (! (notmuch->features & NOTMUCH_FEATURE_FILE_TERMS))
2658         return NOTMUCH_STATUS_UPGRADE_REQUIRED;
2659
2660     /* return NULL on any failure */
2661     *message_ret = NULL;
2662
2663     local = talloc_new (notmuch);
2664
2665     try {
2666         status = _notmuch_database_filename_to_direntry (
2667             local, notmuch, filename, NOTMUCH_FIND_LOOKUP, &direntry);
2668         if (status || !direntry)
2669             goto DONE;
2670
2671         term = talloc_asprintf (local, "%s%s", prefix, direntry);
2672
2673         find_doc_ids_for_term (notmuch, term, &i, &end);
2674
2675         if (i != end) {
2676             notmuch_private_status_t private_status;
2677
2678             *message_ret = _notmuch_message_create (notmuch, notmuch, *i,
2679                                                     &private_status);
2680             if (*message_ret == NULL)
2681                 status = NOTMUCH_STATUS_OUT_OF_MEMORY;
2682         }
2683     } catch (const Xapian::Error &error) {
2684         _notmuch_database_log (notmuch, "Error: A Xapian exception occurred finding message by filename: %s\n",
2685                  error.get_msg().c_str());
2686         notmuch->exception_reported = TRUE;
2687         status = NOTMUCH_STATUS_XAPIAN_EXCEPTION;
2688     }
2689
2690   DONE:
2691     talloc_free (local);
2692
2693     if (status && *message_ret) {
2694         notmuch_message_destroy (*message_ret);
2695         *message_ret = NULL;
2696     }
2697     return status;
2698 }
2699
2700 notmuch_string_list_t *
2701 _notmuch_database_get_terms_with_prefix (void *ctx, Xapian::TermIterator &i,
2702                                          Xapian::TermIterator &end,
2703                                          const char *prefix)
2704 {
2705     int prefix_len = strlen (prefix);
2706     notmuch_string_list_t *list;
2707
2708     list = _notmuch_string_list_create (ctx);
2709     if (unlikely (list == NULL))
2710         return NULL;
2711
2712     for (i.skip_to (prefix); i != end; i++) {
2713         /* Terminate loop at first term without desired prefix. */
2714         if (strncmp ((*i).c_str (), prefix, prefix_len))
2715             break;
2716
2717         _notmuch_string_list_append (list, (*i).c_str () + prefix_len);
2718     }
2719
2720     return list;
2721 }
2722
2723 notmuch_tags_t *
2724 notmuch_database_get_all_tags (notmuch_database_t *db)
2725 {
2726     Xapian::TermIterator i, end;
2727     notmuch_string_list_t *tags;
2728
2729     try {
2730         i = db->xapian_db->allterms_begin();
2731         end = db->xapian_db->allterms_end();
2732         tags = _notmuch_database_get_terms_with_prefix (db, i, end,
2733                                                         _find_prefix ("tag"));
2734         _notmuch_string_list_sort (tags);
2735         return _notmuch_tags_create (db, tags);
2736     } catch (const Xapian::Error &error) {
2737         _notmuch_database_log (db, "A Xapian exception occurred getting tags: %s.\n",
2738                  error.get_msg().c_str());
2739         db->exception_reported = TRUE;
2740         return NULL;
2741     }
2742 }
2743
2744 const char *
2745 notmuch_database_status_string (const notmuch_database_t *notmuch)
2746 {
2747     return notmuch->status_string;
2748 }