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