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