There is POSIX calls to walk through direcotry tree, nftw(3), but there is no way to allow one to pass user data to the callback: int nftw(const char *dirpath, int (*fn) (const char *fpath, const struct stat *sb, int typeflag, struct FTW *ftwbuf), int nopenfd, int flags); Assuming a simple requirement: one wants to simply find out all the files which have name "vendor" and it has a specific value, under a directory, It's not possible to achieve with nftw(3). To solve the requirements like this, this new util function comes. int virTraverseDirectory(const char *dirpath, int depth, unsigned int flags, virTraverseDirectoryCallback cb, virTraverseDirectorySkipCallback skipcb, void *opaque, char ***filepaths); * Which allow to traverse a directory limited to specified @depth (relative to the @dirpath) * Two callbacks are provided, callback @cb is to handle the file path passed to it, with the user data @opaque; callback @skipcb is to allow one to make the rules to skip the passed file path. For example, one can define regex patterns in @skipcb to skip all the file path which matches it. * @cb should return 0 to indicate the file path is successfully handled, otherwise -1 should be returned. * Like @cb, @skipcb also should return 0 to indiate the file path will be skipped to handle, otherwise -1 should be returned. * If passed @filepath is not NULL, it will be filled with the file paths which are successfully handled by @cb. * @flags can be used to control the general behaviour of the traversing. E.g. Don't follow symbol links. A simple example: /* * utiltest.c: Prints all the file paths whose basename is * "vendor" under directory "./testdir". */ static int traverseCallback(const char *path, void *opaque) { const char *name = opaque; char *p = NULL; if ((p = strrchr(path, '/'))) p++; if (STRNEQ(p, name)) return -1; return 0; } static int findVendor(void) { char **filepaths = NULL; unsigned int flags = 0; const char *name = "vendor"; int n; int i; flags |= VIR_TRAVERSE_DIRECTORY_FALL_THROUGH | VIR_TRAVERSE_DIRECTORY_IGNORE_HIDDEN_FILES ; if ((n = virTraverseDirectory("./testdir", 2, flags, traverseCallback, NULL, (void *)name, &filepaths)) < 0) return -1; for (i = 0; i < n; i++) printf("Match%d: %s\n", i, filepaths[i]); for (i = 0; i < n; i++) VIR_FREE(filepaths[i]); VIR_FREE(filepaths); return 0; } Tree view of "./testdir": % tree -a ./testdir/ ./testdir/ |-- device |-- dir1 | |-- device | |-- dir2 | | |-- device | | |-- dir3 | | | `-- vendor | | `-- vendor | |-- .dir7 | | `-- vendor | |-- dir8 -> ../dir4 | `-- vendor |-- dir4 | |-- device | |-- dir5 | | |-- device | | `-- vendor | `-- vendor `-- vendor 7 directories, 12 files % ./utiltest Match0: ./testdir/dir1/dir2/vendor Match1: ./testdir/dir4/dir5/vendor Only "vendor" in second depth are returned (flag *_FALL_THROUGH took effect), and the symbol link is not followed. "virTraverseDirectory" is implemented using recursion method, which is generally not good for performance and memory use, and even may eats up the file descriptors. But since we allow to specify the tree "depth", and I don't think libvirt has use cases which need to traverse a very deep directory tree. And on the other hand, Implementing it using either iteration or stack will be much less readable. Later patches will take use of virTraverseDirectory, and tests will be added. --- src/libvirt_private.syms | 1 + src/util/virutil.c | 151 ++++++++++++++++++++++++++++++++++++++++++++++- src/util/virutil.h | 52 ++++++++++++++++ 3 files changed, 203 insertions(+), 1 deletion(-) diff --git a/src/libvirt_private.syms b/src/libvirt_private.syms index 1ea7467..fe182e8 100644 --- a/src/libvirt_private.syms +++ b/src/libvirt_private.syms @@ -1965,6 +1965,7 @@ virSetNonBlock; virSetUIDGID; virSetUIDGIDWithCaps; virStrIsPrint; +virTraverseDirectory; virValidateWWN; diff --git a/src/util/virutil.c b/src/util/virutil.c index c5246bc..c1938f9 100644 --- a/src/util/virutil.c +++ b/src/util/virutil.c @@ -2048,7 +2048,6 @@ virFindFCHostCapableVport(const char *sysfs_prefix ATTRIBUTE_UNUSED) virReportSystemError(ENOSYS, "%s", _("Not supported on this platform")); return NULL; } - #endif /* __linux__ */ /** @@ -2070,3 +2069,153 @@ virCompareLimitUlong(unsigned long long a, unsigned long b) return -1; } + +/* + * virTraverseDirectory: + * @dirpath: Directory path, (relative or absolute) + * @depth: The max directory tree depth for traversing + * @cb: Callback to handle file (not directory) during traversing + * @skibcb: Callback to define rules to skip entry + * @opaque: User data to pass to @cb + * @filepaths: Pointer to array to store the file paths, the file + * path passed to @cb is stored into @filepaths as long + * as @cb returns 0. + * + * Traverse a directory tree into specified @depth, handing the file + * with callback in the process. Caller must free @filepaths and + * its elements after use. + * + * Returns the number of file paths successfully handled by @cb on + * success, with @fillpaths (if passed @fillpath is not NULL) filled, + * or -1 on failure. + */ +int +virTraverseDirectory(const char *dirpath, + int depth, + unsigned int flags, + virTraverseDirectoryCallback cb, + virTraverseDirectorySkipCallback skipcb, + void *opaque, + char ***filepaths) +{ + struct dirent *entry = NULL; + DIR *dir = NULL; + char *fpath = NULL; + struct stat st; + int nfilepaths = 0; + int ret = -1; + int i; + + if (depth < 0) + return 0; + + if (filepaths) + *filepaths = NULL; + + if (!(dir = opendir(dirpath))) { + virReportSystemError(errno, + _("Failed to open directory '%s'"), + dirpath); + return -1; + } + + while ((entry = readdir(dir))) { + /* Always Ignore "." and ".." */ + if (STREQ(entry->d_name, ".") || STREQ(entry->d_name, "..")) + continue; + + /* Ignore hidden files if it's required */ + if (flags & VIR_TRAVERSE_DIRECTORY_IGNORE_HIDDEN_FILES && + entry->d_name[0] == '.') + continue; + + if (virAsprintf(&fpath, "%s/%s", dirpath, entry->d_name) < 0) { + virReportOOMError(); + goto error; + } + + /* Skip to handle the entry as long as @skipcb returns 0 for it */ + if (skipcb && skipcb(fpath, opaque) == 0) { + VIR_FREE(fpath); + continue; + } + + if (lstat(fpath, &st) < 0) { + virReportSystemError(errno, + _("Failed to stat '%s'"), + fpath); + goto error; + } + + /* Don't follow symbol link unless it's explicitly required */ + if (S_ISLNK(st.st_mode) && + !(flags & VIR_TRAVERSE_DIRECTORY_FOLLOW_LINK)) { + VIR_FREE(fpath); + continue; + } + + if (S_ISDIR(st.st_mode)) { + char **tmp = NULL; + int rc; + + if ((rc = virTraverseDirectory(fpath, depth - 1, flags, cb, skipcb, + opaque, filepaths ? &tmp : NULL)) < 0) + goto error; + + if (rc > 0) { + /* Copy the filepaths returned by recursive call */ + if (filepaths) { + if (VIR_REALLOC_N((*filepaths), nfilepaths + rc) < 0) { + for (i = 0; i < rc; i++) + VIR_FREE(tmp[i]); + VIR_FREE(tmp); + virReportOOMError(); + goto error; + } + + for (i = 0; i < rc; i++) + (*filepaths)[nfilepaths + i] = tmp[i]; + } + nfilepaths += rc; + } + } else { + /* Don't handle the file unless it's the last depth */ + if ((flags & VIR_TRAVERSE_DIRECTORY_FALL_THROUGH) && + depth != 0) { + VIR_FREE(fpath); + continue; + } + + if (cb(fpath, opaque) == 0) { + if (filepaths) { + if (VIR_REALLOC_N((*filepaths), nfilepaths + 1) < 0) { + virReportOOMError(); + goto error; + } + + if (VIR_STRDUP((*filepaths)[nfilepaths], fpath) < 0) + goto error; + + } + nfilepaths++; + } + } + + VIR_FREE(fpath); + } + + ret = nfilepaths; + +cleanup: + closedir(dir); + return ret; + +error: + VIR_FREE(fpath); + if (filepaths) { + for (i = 0; i < nfilepaths; i++) + VIR_FREE((*filepaths)[i]); + VIR_FREE(*filepaths); + } + goto cleanup; +} diff --git a/src/util/virutil.h b/src/util/virutil.h index 280a18d..6c46f23 100644 --- a/src/util/virutil.h +++ b/src/util/virutil.h @@ -166,4 +166,56 @@ char *virFindFCHostCapableVport(const char *sysfs_prefix); int virCompareLimitUlong(unsigned long long a, unsigned long b); +/** + * virTraverseDirectoryCallback: + * @fpath: file path to handle + * @opaque: User data to pass to the callback + * + * Callback for "virTraverseDirectory". + * + * Returns 0 on success, -1 on failure. + */ +typedef int (*virTraverseDirectoryCallback)(const char *fpath, + void *opaque); + +/** + * virTraverseDirectorySkipCallback: + * @fpath: file path to handle + * @opaque: User data to pass to the callback + * + * Callback to control whether virTraverseDirectory should ship + * @fpath E.g. Skipping files whose name match a regex pattern. + * + * Return 0 to let "virTraverseDirectory" skip handling the @fpath, + * -1 to not skip. + */ +typedef int (*virTraverseDirectorySkipCallback)(const char *fpath, + void *opaque); + +/** + * virTraverseDirectoryFlags: + * + * Except virTraverseDirectorySkipCallback, one can use these flags to + * control the general behaviour of virTraverseDirectory + */ +enum { + /* Follow symbol links */ + VIR_TRAVERSE_DIRECTORY_FOLLOW_LINK = 1 << 0, + + /* Ignore hidden files or directory */ + VIR_TRAVERSE_DIRECTORY_IGNORE_HIDDEN_FILES = 1 << 1, + + /* Don't handle files unless it's in the last depth */ + VIR_TRAVERSE_DIRECTORY_FALL_THROUGH = 1 << 2, +} virTraverseDirectoryFlags; + +int virTraverseDirectory(const char *dirpath, + int depth, + unsigned int flags, + virTraverseDirectoryCallback cb, + virTraverseDirectorySkipCallback skipcb, + void *opaque, + char ***filepaths); + ATTRIBUTE_NONNULL(1) ATTRIBUTE_NONNULL(3) + #endif /* __VIR_UTIL_H__ */ -- 1.8.1.4 -- libvir-list mailing list libvir-list@xxxxxxxxxx https://www.redhat.com/mailman/listinfo/libvir-list