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;
+
+#ifdef _DIRENT_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
* Return 1 if the directory looks like a Maildir and 0 otherwise.
*/
static int
-_entries_resemble_maildir (struct dirent **entries, int count)
+_entries_resemble_maildir (const char *path, struct dirent **entries, int count)
{
int i, found = 0;
for (i = 0; i < count; i++) {
- if (entries[i]->d_type != DT_DIR && entries[i]->d_type != DT_UNKNOWN)
+ if (dirent_type (path, entries[i]) != S_IFDIR)
continue;
if (strcmp(entries[i]->d_name, "new") == 0 ||
* if fs_mtime isn't the current wall-clock time.
*/
static notmuch_status_t
-add_files_recursive (notmuch_database_t *notmuch,
- const char *path,
- add_files_state_t *state)
+add_files (notmuch_database_t *notmuch,
+ const char *path,
+ add_files_state_t *state)
{
DIR *dir = NULL;
struct dirent *entry = NULL;
notmuch_status_t status, ret = NOTMUCH_STATUS_SUCCESS;
notmuch_message_t *message = NULL;
struct dirent **fs_entries = NULL;
- int i, num_fs_entries;
+ int i, num_fs_entries = 0, entry_type;
notmuch_directory_t *directory;
notmuch_filenames_t *db_files = NULL;
notmuch_filenames_t *db_subdirs = NULL;
time_t stat_time;
struct stat st;
- notmuch_bool_t is_maildir, new_directory;
+ notmuch_bool_t is_maildir;
const char **tag;
if (stat (path, &st)) {
}
stat_time = time (NULL);
- /* This is not an error since we may have recursed based on a
- * symlink to a regular file, not a directory, and we don't know
- * that until this stat. */
- if (! S_ISDIR (st.st_mode))
- return NOTMUCH_STATUS_SUCCESS;
+ if (! S_ISDIR (st.st_mode)) {
+ fprintf (stderr, "Error: %s is not a directory.\n", path);
+ return NOTMUCH_STATUS_FILE_ERROR;
+ }
fs_mtime = st.st_mtime;
- directory = notmuch_database_get_directory (notmuch, path);
- db_mtime = notmuch_directory_get_mtime (directory);
-
- new_directory = db_mtime ? FALSE : TRUE;
-
- /* XXX This is a temporary workaround. If we don't update the
- * database mtime until after processing messages in this
- * directory, then a 0 mtime is *not* sufficient to indicate that
- * this directory has no messages or subdirs in the database (for
- * example, if an earlier run skipped the mtime update because
- * fs_mtime == stat_time, or was interrupted before updating the
- * mtime at the end). To address this, we record a (bogus)
- * non-zero value before processing any child messages so that a
- * later run won't mistake this for a new directory (and, for
- * example, fail to detect removed files and subdirs).
- *
- * A better solution would be for notmuch_database_get_directory
- * to indicate if it really created a new directory or not, either
- * by a new out-argument, or by recording this information and
- * providing an accessor.
- */
- if (new_directory)
- notmuch_directory_set_mtime (directory, -1);
+ status = notmuch_database_get_directory (notmuch, path, &directory);
+ if (status) {
+ ret = status;
+ goto DONE;
+ }
+ db_mtime = directory ? notmuch_directory_get_mtime (directory) : 0;
/* If the database knows about this directory, then we sort based
* on strcmp to match the database sorting. Otherwise, we can do
* inode-based sorting for faster filesystem operation. */
num_fs_entries = scandir (path, &fs_entries, 0,
- new_directory ?
- dirent_sort_inode : dirent_sort_strcmp_name);
+ directory ?
+ dirent_sort_strcmp_name : dirent_sort_inode);
if (num_fs_entries == -1) {
fprintf (stderr, "Error opening directory %s: %s\n",
}
/* Pass 1: Recurse into all sub-directories. */
- is_maildir = _entries_resemble_maildir (fs_entries, num_fs_entries);
+ is_maildir = _entries_resemble_maildir (path, fs_entries, num_fs_entries);
for (i = 0; i < num_fs_entries; i++) {
if (interrupted)
entry = fs_entries[i];
- /* We only want to descend into directories.
- * But symlinks can be to directories too, of course.
- *
- * And if the filesystem doesn't tell us the file type in the
- * scandir results, then it might be a directory (and if not,
- * then we'll stat and return immediately in the next level of
- * recursion). */
- if (entry->d_type != DT_DIR &&
- entry->d_type != DT_LNK &&
- entry->d_type != DT_UNKNOWN)
- {
+ /* We only want to descend into directories (and symlinks to
+ * directories). */
+ entry_type = dirent_type (path, entry);
+ if (entry_type == -1) {
+ /* Be pessimistic, e.g. so we don't lose lots of mail just
+ * because a user broke a symlink. */
+ fprintf (stderr, "Error reading file %s/%s: %s\n",
+ path, entry->d_name, strerror (errno));
+ return NOTMUCH_STATUS_FILE_ERROR;
+ } else if (entry_type != S_IFDIR) {
continue;
}
}
next = talloc_asprintf (notmuch, "%s/%s", path, entry->d_name);
- status = add_files_recursive (notmuch, next, state);
+ status = add_files (notmuch, next, state);
if (status) {
ret = status;
goto DONE;
* being discovered until the clock catches up and the directory
* is modified again).
*/
- if (fs_mtime == db_mtime)
+ if (directory && fs_mtime == db_mtime)
goto DONE;
- /* new_directory means a directory that the database has never
- * seen before. In that case, we can simply leave db_files and
- * db_subdirs NULL. */
- if (!new_directory) {
+ /* If the database has never seen this directory before, we can
+ * simply leave db_files and db_subdirs NULL. */
+ if (directory) {
db_files = notmuch_directory_get_child_files (directory);
db_subdirs = notmuch_directory_get_child_directories (directory);
}
notmuch_filenames_move_to_next (db_subdirs);
}
- /* If we're looking at a symlink, we only want to add it if it
- * links to a regular file, (and not to a directory, say).
- *
- * Similarly, if the file is of unknown type (due to filesystem
- * limitations), then we also need to look closer.
- *
- * In either case, a stat does the trick.
- */
- if (entry->d_type == DT_LNK || entry->d_type == DT_UNKNOWN) {
- int err;
-
- next = talloc_asprintf (notmuch, "%s/%s", path, entry->d_name);
- err = stat (next, &st);
- talloc_free (next);
- next = NULL;
-
- /* Don't emit an error for a link pointing nowhere, since
- * the directory-traversal pass will have already done
- * that. */
- if (err)
- continue;
-
- if (! S_ISREG (st.st_mode))
- continue;
- } else if (entry->d_type != DT_REG) {
+ /* Only add regular files (and symlinks to regular files). */
+ entry_type = dirent_type (path, entry);
+ if (entry_type == -1) {
+ fprintf (stderr, "Error reading file %s/%s: %s\n",
+ path, entry->d_name, strerror (errno));
+ return NOTMUCH_STATUS_FILE_ERROR;
+ } else if (entry_type != S_IFREG) {
continue;
}
}
-/* This is the top-level entry point for add_files. It does a couple
- * of error checks and then calls into the recursive function. */
-static notmuch_status_t
-add_files (notmuch_database_t *notmuch,
- const char *path,
- add_files_state_t *state)
-{
- notmuch_status_t status;
- struct stat st;
-
- if (stat (path, &st)) {
- fprintf (stderr, "Error reading directory %s: %s\n",
- path, strerror (errno));
- return NOTMUCH_STATUS_FILE_ERROR;
- }
-
- if (! S_ISDIR (st.st_mode)) {
- fprintf (stderr, "Error: %s is not a directory.\n", path);
- return NOTMUCH_STATUS_FILE_ERROR;
- }
-
- status = add_files_recursive (notmuch, path, state);
-
- return status;
-}
-
/* XXX: This should be merged with the add_files function since it
* shares a lot of logic with it. */
/* Recursively count all regular files in path and all sub-directories
notmuch_filenames_t *files, *subdirs;
char *absolute;
- directory = notmuch_database_get_directory (notmuch, path);
+ status = notmuch_database_get_directory (notmuch, path, &directory);
+ if (status || !directory)
+ return status;
for (files = notmuch_directory_get_child_files (directory);
notmuch_filenames_valid (files);
}
for (f = add_files_state.directory_mtimes->head; f && !interrupted; f = f->next) {
+ notmuch_status_t status;
notmuch_directory_t *directory;
- directory = notmuch_database_get_directory (notmuch, f->filename);
- if (directory) {
+ status = notmuch_database_get_directory (notmuch, f->filename, &directory);
+ if (status == NOTMUCH_STATUS_SUCCESS && directory) {
notmuch_directory_set_mtime (directory, f->mtime);
notmuch_directory_destroy (directory);
}