return (*dirfd < 0) ? -1 : 0;
}
}
+
+#ifdef HAVE_DIRENT_H
+/*
+ * Filter filenames when reading a directory.
+ */
+static int fr_globdir_dir_next(char const **filename, fr_globdir_iter_t *iter)
+{
+ struct dirent *dp;
+ char const *p;
+ size_t len;
+
+ fr_assert(iter->dir != NULL);
+
+ /*
+ * Reading all of the directory entries will result in
+ * reading ones we don't want, so we filter them.
+ */
+ while (true) {
+ errno = 0;
+ dp = readdir(iter->dir);
+ if (!dp) {
+ if (errno != 0) return -1;
+
+ *filename = NULL;
+ return 0;
+ }
+
+ /*
+ * Filter the filenames.
+ */
+ if (dp->d_name[0] == '.') continue;
+
+ /*
+ * Check for valid characters
+ */
+ for (p = dp->d_name; *p != '\0'; p++) {
+ if (isalpha((uint8_t)*p) ||
+ isdigit((uint8_t)*p) ||
+ (*p == '-') ||
+ (*p == '_') ||
+ (*p == '.')) continue;
+ break;
+ }
+
+ /*
+ * Invalid character in filename. Skip it.
+ */
+ if (*p != '\0') continue;
+
+ /*
+ * Ignore config files generated by deb / rpm packaging updates.
+ */
+ len = strlen(dp->d_name);
+ if ((len > 10) && (strncmp(&dp->d_name[len - 10], ".dpkg-dist", 10) == 0)) continue;
+ if ((len > 9) && (strncmp(&dp->d_name[len - 9], ".dpkg-old", 9) == 0)) continue;
+ if ((len > 7) && (strncmp(&dp->d_name[len - 7], ".rpmnew", 9) == 0)) continue;
+ if ((len > 8) && (strncmp(&dp->d_name[len - 8], ".rpmsave", 10) == 0)) continue;
+ }
+
+ *filename = dp->d_name;
+ return 0;
+}
+#endif
+
+/** Create a full path from dir + pattern.
+ *
+ */
+static int fr_globdir_get_path(char const *dir, char const *pattern, fr_globdir_iter_t *iter)
+{
+ char const *p;
+ char *q;
+ bool slash;
+ size_t size = strlen(dir) + 1 + strlen(pattern) + 1;
+
+ /*
+ * Bootstrap the full path.
+ */
+ iter->path = malloc(size);
+ if (!iter->path) {
+ errno = ENOMEM;
+ return -1;
+ }
+
+ /*
+ * Sanity check the directory name.
+ *
+ * We assume that the directory name is otherwise safe.
+ */
+ p = dir;
+ q = iter->path;
+ while (*p) {
+ /*
+ * Suppress duplicate '/', and trailing '/'.
+ */
+ if (*p == '/') {
+ *(q++) = *(p++);
+
+ while (*p == '/') p++;
+
+ if (!p[1]) break;
+ continue;
+ }
+
+ if ((size_t) (q - iter->path) >= size) {
+ free(iter->path);
+ errno = ENOMEM;
+ return -1;
+ }
+
+ *(q++) = *(p++);
+ }
+
+ /*
+ * Add in a trailing '/' to the directory.
+ */
+ *(q++) = '/';
+ slash = true;
+ iter->filename = q;
+
+ /*
+ * Copy over the pattern name, but sanity check it.
+ */
+ p = pattern;
+ while (*p) {
+ if ((size_t) (q - iter->path) >= size) {
+ free(iter->path);
+ errno = ENOMEM;
+ return -1;
+ }
+
+ /*
+ * @todo - if it's a pattern, then handle [./] as a special case.
+ */
+
+ /*
+ * Handle the case of bad things after the directory name, too.
+ *
+ * But otherwise copy over any '/' which we see in the pattern.
+ */
+ if (!slash) {
+ slash = (*p == '/');
+ *(q++) = *(p++);
+
+ if (!slash) continue;
+ }
+
+ more_slash:
+ /*
+ * ///// --> /
+ */
+ while (*p == '/') p++;
+
+ /*
+ * foo/. may be special
+ */
+ if (*p != '.') {
+ slash = false;
+ continue;
+ }
+
+ /*
+ * foo/./ --> foo/
+ */
+ if (p[1] == '/') {
+ p += 2;
+ goto more_slash;
+ }
+
+ /*
+ * foo/../ --> error
+ */
+ if ((p[1] == '.') && (p[2] == '/')) {
+ free(iter->path);
+ errno = ENOENT;
+ return -1;
+ }
+
+ /*
+ * foo/.bar is OK
+ */
+ slash = false;
+ }
+ *q = '\0';
+
+ return 0;
+}
+
+/** Initialize an iterator over filenames.
+ *
+ * @param[out] filename the _relative_ filename which should be opened
+ * @param[in] dir the directory to read
+ * @param[in] pattern the filename or pattern to read. Cannot be an absolute path.
+ * @param[in,out] iter the iteration structure.
+ * @return
+ * - <0 on error
+ * - 0 on success, but no filename
+ * - 1 for "have file".
+ */
+int fr_globdir_iter_init(char const **filename, char const *dir, char const *pattern, fr_globdir_iter_t *iter)
+{
+ char const *p;
+ char const *to_open;
+
+ /*
+ * Default to files, which is the most common case.
+ */
+ *iter = (fr_globdir_iter_t) {
+ .type= FR_GLOBDIR_FILE,
+ };
+
+ /*
+ * Figure out what kind of thing we're opening.
+ */
+ for (p = pattern; *p != '\0'; p++) {
+ /*
+ * foo/ - read the entire directory.
+ */
+ if (*p == '/') {
+ if (p[1]) continue;
+
+#ifdef HAVE_DIRENT_H
+ iter->type = FR_GLOBDIR_DIR;
+ break;
+#else
+ errno = ENOENT;
+ return -1;
+#endif
+ }
+
+ /*
+ * foo*.txt
+ * foo?.txt
+ * foo.[ch]
+ *
+ * - file globbing.
+ *
+ * File globbing is either full path, or a path relative to CWD. It is most notably NOT
+ * relative to the input "dir". So if there are globs, we need a full path.
+ */
+ if ((*p == '*') || (*p == '?') || (*p == '[')) {
+#ifdef HAVE_GLOB_H
+ if ((pattern[0] != '/') && (dir[0] != '/')) {
+ errno = ENOENT;
+ return -1;
+ }
+
+ iter->type = FR_GLOBDIR_GLOB;
+ break;
+#else
+ errno = ENOENT;
+ return -1;
+#endif
+ }
+ }
+
+ /*
+ * The pattern is an absolute path, we just use that as-is.
+ */
+ if (pattern[0] == '/') {
+ to_open = pattern;
+
+ } else if (iter->type == FR_GLOBDIR_FILE) {
+ /*
+ * Short-circuit the common case for files. We're just opening a file, and the file is
+ * relative to the directory which was passed in.
+ */
+ *filename = pattern;
+ return 1;
+
+ } else {
+ /*
+ * Either dir is absolute and pattern is relative, or they're both relative. Merge dir +
+ * pattern into a path.
+ *
+ * Note that globs are relative to CWD, so relative globs must be passed in correctly
+ * (that's an @todo), otherwise they won't work.
+ */
+ if (fr_globdir_get_path(dir, pattern, iter) < 0) {
+ return -1;
+ }
+
+ to_open = iter->path;
+ }
+
+ /*
+ * Now that we know what type of thing it is, go do the
+ * right thing.
+ */
+ switch (iter->type) {
+ case FR_GLOBDIR_INVALID:
+ (void) fr_globdir_iter_free(iter);
+ errno = ENOENT;
+ return -1;
+
+ case FR_GLOBDIR_FILE:
+ *filename = iter->path;
+ break;
+
+#ifdef HAVE_DIRENT_H
+ case FR_GLOBDIR_DIR:
+ /*
+ * No directory means no file. The caller then decides if the file is required.
+ */
+ iter->dir = opendir(to_open);
+ if (!iter->dir) {
+ (void) fr_globdir_iter_free(iter);
+ return 0;
+ }
+
+ if (fr_globdir_dir_next(filename, iter) < 0) {
+ (void) fr_globdir_iter_free(iter);
+ return -1;
+ }
+ break;
+#endif
+
+#ifdef HAVE_GLOB_H
+ case FR_GLOBDIR_GLOB:
+ if (glob(to_open, GLOB_NOESCAPE | GLOB_ERR, NULL, &iter->glob) < 0) {
+ (void) fr_globdir_iter_free(iter);
+ return -1;
+ }
+
+ if (iter->glob.gl_pathc == 0) {
+ *filename = NULL;
+ } else {
+ iter->gl_current = 0;
+ *filename = iter->glob.gl_pathv[iter->gl_current];
+ }
+ break;
+#endif
+ }
+
+ return 0 + (*filename != NULL);
+}
+
+/** Get the next filename.
+ *
+ * fr_globdir_iter_init()
+ * do {
+ * ... use filename
+ * } while (fr_globdir_iter_next() == 1);
+ * fr_globdir_iter_free()
+ *
+ * @return
+ * - <0 for error
+ * - 0 for done
+ * - 1 for "have filename"
+ */
+int fr_globdir_iter_next(char const **filename, fr_globdir_iter_t *iter)
+{
+ switch (iter->type) {
+ case FR_GLOBDIR_INVALID:
+ break;
+
+ case FR_GLOBDIR_FILE:
+ *filename = NULL;
+ return 0;
+
+#ifdef HAVE_DIRENT_H
+ case FR_GLOBDIR_DIR:
+ if (fr_globdir_dir_next(filename, iter) < 0) return -1;
+
+ return 0 + (*filename != NULL);
+#endif
+
+#ifdef HAVE_GLOB_H
+ case FR_GLOBDIR_GLOB:
+ iter->gl_current++;
+ if (iter->gl_current >= iter->glob.gl_pathc) {
+ return 0;
+ }
+
+ *filename = iter->glob.gl_pathv[iter->gl_current];
+ fr_assert(*filename != NULL);
+
+ return 1;
+#endif
+ }
+
+ return -1;
+}
+
+int fr_globdir_iter_free(fr_globdir_iter_t *iter)
+{
+ if (iter->path) {
+ free(iter->path);
+ iter->path = NULL;
+ }
+
+ switch (iter->type) {
+ case FR_GLOBDIR_INVALID:
+ return -1;
+
+ case FR_GLOBDIR_FILE:
+ break;
+
+#ifdef HAVE_DIRENT_H
+ case FR_GLOBDIR_DIR:
+ if (!iter->dir) return 0;
+
+ return closedir(iter->dir);
+#endif
+
+#ifdef HAVE_GLOB_H
+ case FR_GLOBDIR_GLOB:
+ globfree(&iter->glob);
+ break;
+#endif
+ }
+
+ return 0;
+}