+static int
+dirent_sort_inode (const struct dirent **a, const struct dirent **b)
+{
+ return ((*a)->d_ino < (*b)->d_ino) ? -1 : 1;
+}
+
+static int
+dirent_sort_strcmp_name (const struct dirent **a, const struct dirent **b)
+{
+ return strcmp ((*a)->d_name, (*b)->d_name);
+}
+
+/* Return the type of a directory entry relative to path as a stat(2)
+ * mode. Like stat, this follows symlinks. Returns -1 and sets errno
+ * if the file's type cannot be determined (which includes dangling
+ * symlinks).
+ */
+static int
+dirent_type (const char *path, const struct dirent *entry)
+{
+ struct stat statbuf;
+ char *abspath;
+ int err, saved_errno;
+
+#if HAVE_D_TYPE
+ /* Mapping from d_type to stat mode_t. We omit DT_LNK so that
+ * we'll fall through to stat and get the real file type. */
+ static const mode_t modes[] = {
+ [DT_BLK] = S_IFBLK,
+ [DT_CHR] = S_IFCHR,
+ [DT_DIR] = S_IFDIR,
+ [DT_FIFO] = S_IFIFO,
+ [DT_REG] = S_IFREG,
+ [DT_SOCK] = S_IFSOCK
+ };
+ if (entry->d_type < ARRAY_SIZE(modes) && modes[entry->d_type])
+ return modes[entry->d_type];
+#endif
+
+ abspath = talloc_asprintf (NULL, "%s/%s", path, entry->d_name);
+ if (!abspath) {
+ errno = ENOMEM;
+ return -1;
+ }
+ err = stat(abspath, &statbuf);
+ saved_errno = errno;
+ talloc_free (abspath);
+ if (err < 0) {
+ errno = saved_errno;
+ return -1;
+ }
+ return statbuf.st_mode & S_IFMT;
+}
+
+/* Test if the directory looks like a Maildir directory.
+ *
+ * Search through the array of directory entries to see if we can find all
+ * three subdirectories typical for Maildir, that is "new", "cur", and "tmp".
+ *
+ * Return 1 if the directory looks like a Maildir and 0 otherwise.
+ */
+static int
+_entries_resemble_maildir (const char *path, struct dirent **entries, int count)
+{
+ int i, found = 0;
+
+ for (i = 0; i < count; i++) {
+ if (dirent_type (path, entries[i]) != S_IFDIR)
+ continue;
+
+ if (strcmp(entries[i]->d_name, "new") == 0 ||
+ strcmp(entries[i]->d_name, "cur") == 0 ||
+ strcmp(entries[i]->d_name, "tmp") == 0)
+ {
+ found++;
+ if (found == 3)
+ return 1;
+ }
+ }
+
+ return 0;
+}
+
+static bool
+_special_directory (const char *entry)
+{
+ return strcmp (entry, ".") == 0 || strcmp (entry, "..") == 0;
+}
+
+static bool
+_setup_ignore (notmuch_config_t *config, add_files_state_t *state)
+{
+ const char **ignore_list, **ignore;
+ int nregex = 0, nverbatim = 0;
+ const char **verbatim = NULL;
+ regex_t *regex = NULL;
+
+ ignore_list = notmuch_config_get_new_ignore (config, NULL);
+ if (! ignore_list)
+ return true;
+
+ for (ignore = ignore_list; *ignore; ignore++) {
+ const char *s = *ignore;
+ size_t len = strlen (s);
+
+ if (len == 0) {
+ fprintf (stderr, "Error: Empty string in new.ignore list\n");
+ return false;
+ }
+
+ if (s[0] == '/') {
+ regex_t *preg;
+ char *r;
+ int rerr;
+
+ if (len < 3 || s[len - 1] != '/') {
+ fprintf (stderr, "Error: Malformed pattern '%s' in new.ignore\n",
+ s);
+ return false;
+ }
+
+ r = talloc_strndup (config, s + 1, len - 2);
+ regex = talloc_realloc (config, regex, regex_t, nregex + 1);
+ preg = ®ex[nregex];
+
+ rerr = regcomp (preg, r, REG_EXTENDED | REG_NOSUB);
+ if (rerr) {
+ size_t error_size = regerror (rerr, preg, NULL, 0);
+ char *error = talloc_size (r, error_size);
+
+ regerror (rerr, preg, error, error_size);
+
+ fprintf (stderr, "Error: Invalid regex '%s' in new.ignore: %s\n",
+ r, error);
+ return false;
+ }
+ nregex++;
+
+ talloc_free (r);
+ } else {
+ verbatim = talloc_realloc (config, verbatim, const char *,
+ nverbatim + 1);
+ verbatim[nverbatim++] = s;
+ }
+ }
+
+ state->ignore_regex = regex;
+ state->ignore_regex_length = nregex;
+ state->ignore_verbatim = verbatim;
+ state->ignore_verbatim_length = nverbatim;
+
+ return true;
+}
+
+static char *
+_get_relative_path (const char *db_path, const char *dirpath, const char *entry)
+{
+ size_t db_path_len = strlen (db_path);
+
+ /* paranoia? */
+ if (strncmp (dirpath, db_path, db_path_len) != 0) {
+ fprintf (stderr, "Warning: '%s' is not a subdirectory of '%s'\n",
+ dirpath, db_path);
+ return NULL;
+ }
+
+ dirpath += db_path_len;
+ while (*dirpath == '/')
+ dirpath++;
+
+ if (*dirpath)
+ return talloc_asprintf (NULL, "%s/%s", dirpath, entry);
+ else
+ return talloc_strdup (NULL, entry);
+}
+
+/* Test if the file/directory is to be ignored.
+ */
+static bool
+_entry_in_ignore_list (add_files_state_t *state, const char *dirpath,
+ const char *entry)
+{
+ bool ret = false;
+ size_t i;
+ char *path;
+
+ for (i = 0; i < state->ignore_verbatim_length; i++) {
+ if (strcmp (entry, state->ignore_verbatim[i]) == 0)
+ return true;
+ }
+
+ if (state->ignore_regex_length == 0)
+ return false;
+
+ path = _get_relative_path (state->db_path, dirpath, entry);
+ if (! path)
+ return false;
+
+ for (i = 0; i < state->ignore_regex_length; i++) {
+ if (regexec (&state->ignore_regex[i], path, 0, NULL, 0) == 0) {
+ ret = true;
+ break;
+ }
+ }
+
+ talloc_free (path);
+
+ return ret;
+}
+
+/* Add a single file to the database. */
+static notmuch_status_t
+add_file (notmuch_database_t *notmuch, const char *filename,
+ add_files_state_t *state)