You are viewing a plain text version of this content. The canonical link for it is here.
Posted to cvs@httpd.apache.org by mi...@apache.org on 2018/11/25 21:19:13 UTC

svn commit: r1847431 - /httpd/httpd/patches/2.4.x/httpd-ap_dir_fnmatch.patch

Author: minfrin
Date: Sun Nov 25 21:19:13 2018
New Revision: 1847431

URL: http://svn.apache.org/viewvc?rev=1847431&view=rev
Log:
Add httpd v2.4 backport of r1847430.

Added:
    httpd/httpd/patches/2.4.x/httpd-ap_dir_fnmatch.patch

Added: httpd/httpd/patches/2.4.x/httpd-ap_dir_fnmatch.patch
URL: http://svn.apache.org/viewvc/httpd/httpd/patches/2.4.x/httpd-ap_dir_fnmatch.patch?rev=1847431&view=auto
==============================================================================
--- httpd/httpd/patches/2.4.x/httpd-ap_dir_fnmatch.patch (added)
+++ httpd/httpd/patches/2.4.x/httpd-ap_dir_fnmatch.patch Sun Nov 25 21:19:13 2018
@@ -0,0 +1,653 @@
+Index: CHANGES
+===================================================================
+--- CHANGES	(revision 1847315)
++++ CHANGES	(working copy)
+@@ -1,6 +1,10 @@
+                                                          -*- coding: utf-8 -*-
+ Changes with Apache 2.4.38
+ 
++  *) core: Split out the ability to parse wildcard files and directories
++     from the Include/IncludeOptional directives into a generic set of
++     functions ap_dir_nofnmatch() and ap_dir_fnmatch(). [Graham Leggett]
++
+   *) mod_setenvif: We can have expressions that become true if a regex pattern
+      in the expression does NOT match. In this case val is NULL
+      and we should just set the value for the environment variable 
+Index: include/ap_mmn.h
+===================================================================
+--- include/ap_mmn.h	(revision 1847315)
++++ include/ap_mmn.h	(working copy)
+@@ -523,6 +523,7 @@
+  * 20120211.82 (2.4.35-dev) Add optional function declaration for
+  *                          ap_proxy_balancer_get_best_worker to mod_proxy.h.
+  * 20120211.83 (2.4.35-dev) Add client64 field to worker_score struct
++ * 20120211.84 (2.4.35-dev) Add ap_dir_nofnmatch() and ap_dir_fnmatch().
+  *
+  */
+ 
+Index: include/http_config.h
+===================================================================
+--- include/http_config.h	(revision 1847315)
++++ include/http_config.h	(working copy)
+@@ -928,6 +928,21 @@
+                                         ap_conf_vector_t *section_vector);
+ 
+ /**
++ * Convenience function to create a ap_dir_match_t structure from a cmd_parms.
++ *
++ * @param cmd The command.
++ * @param flags Flags to indicate whether optional or recursive.
++ * @param cb Callback for each file found that matches the wildcard. Return NULL on
++ *        success, an error string on error.
++ * @param ctx Context for the callback.
++ * @return Structure ap_dir_match_t with fields populated, allocated from the
++ *         cmd->temp_pool.
++ */
++AP_DECLARE(ap_dir_match_t *)ap_dir_cfgmatch(cmd_parms *cmd, int flags,
++        const char *(*cb)(ap_dir_match_t *w, const char *fname), void *ctx)
++        __attribute__((nonnull(1,3)));
++
++/**
+  * @defgroup ap_check_cmd_context Check command context
+  * @{
+  */
+Index: include/httpd.h
+===================================================================
+--- include/httpd.h	(revision 1847315)
++++ include/httpd.h	(working copy)
+@@ -2397,6 +2397,92 @@
+  */
+ AP_DECLARE(int) ap_cstr_casecmpn(const char *s1, const char *s2, apr_size_t n);
+ 
++/**
++ * Default flags for apr_dir_*().
++ */
++#define AP_DIR_FLAG_NONE      0
++
++/**
++ * If set, wildcards that match no files or directories will be ignored, otherwise
++ * an error is triggered.
++ */
++#define AP_DIR_FLAG_OPTIONAL  1
++
++/**
++ * If set, and the wildcard resolves to a directory, recursively find all files
++ * below that directory, otherwise return the directory.
++ */
++#define AP_DIR_FLAG_RECURSIVE 2
++
++/**
++ * Structure to provide the state of a directory match.
++ */
++typedef struct ap_dir_match_t ap_dir_match_t;
++
++/**
++ * Concrete structure to provide the state of a directory match.
++ */
++struct ap_dir_match_t {
++    /** Pool to use for allocating the result */
++    apr_pool_t *p;
++    /** Temporary pool used for directory traversal */
++    apr_pool_t *ptemp;
++    /** Prefix for log messages */
++    const char *prefix;
++    /** Callback for each file found that matches the wildcard. Return NULL on success, an error string on error. */
++    const char *(*cb)(ap_dir_match_t *w, const char *fname);
++    /** Context for the callback */
++    void *ctx;
++    /** Flags to indicate whether optional or recursive */
++    int flags;
++    /** Recursion depth safety check */
++    unsigned int depth;
++};
++
++/**
++ * Search for files given a non wildcard filename with non native separators.
++ *
++ * If the provided filename points at a file, the callback within ap_dir_match_t is
++ * triggered for that file, and this function returns the result of the callback.
++ *
++ * If the provided filename points at a directory, and recursive within ap_dir_match_t
++ * is true, the callback will be triggered for every file found recursively beneath
++ * that directory, otherwise the callback is triggered once for the directory itself.
++ * This function returns the result of the callback.
++ *
++ * If the provided path points to neither a file nor a directory, and optional within
++ * ap_dir_match_t is true, this function returns NULL. If optional within ap_dir_match_t
++ * is false, this function will return an error string indicating that the path does not
++ * exist.
++ *
++ * @param w Directory match structure containing callback and context.
++ * @param fname The name of the file or directory, with non native separators.
++ * @return NULL on success, or a string describing the error.
++ */
++AP_DECLARE(const char *)ap_dir_nofnmatch(ap_dir_match_t *w, const char *fname)
++        __attribute__((nonnull(1,2)));
++
++/**
++ * Search for files given a wildcard filename with non native separators.
++ *
++ * If the filename contains a wildcard, all files and directories that match the wildcard
++ * will be returned.
++ *
++ * ap_dir_nofnmatch() is called for each directory and file found, and the callback
++ * within ap_dir_match_t triggered as described above.
++ *
++ * Wildcards may appear in both directory and file components in the path, and
++ * wildcards may appear more than once.
++ *
++ * @param w Directory match structure containing callback and context.
++ * @param path Path prefix for search, with non native separators and no wildcards.
++ * @param fname The name of the file or directory, with non native separators and
++ * optional wildcards.
++ * @return NULL on success, or a string describing the error.
++ */
++AP_DECLARE(const char *)ap_dir_fnmatch(ap_dir_match_t *w, const char *path,
++        const char *fname) __attribute__((nonnull(1,3)));
++
+ #ifdef __cplusplus
+ }
+ #endif
+Index: server/config.c
+===================================================================
+--- server/config.c	(revision 1847315)
++++ server/config.c	(working copy)
+@@ -1793,18 +1793,6 @@
+     return NULL;
+ }
+ 
+-typedef struct {
+-    const char *fname;
+-} fnames;
+-
+-static int fname_alphasort(const void *fn1, const void *fn2)
+-{
+-    const fnames *f1 = fn1;
+-    const fnames *f2 = fn2;
+-
+-    return strcmp(f1->fname,f2->fname);
+-}
+-
+ /**
+  * Used by -D DUMP_INCLUDES to output the config file "tree".
+  */
+@@ -1897,200 +1885,15 @@
+     return NULL;
+ }
+ 
+-static const char *process_resource_config_nofnmatch(server_rec *s,
+-                                                     const char *fname,
+-                                                     ap_directive_t **conftree,
+-                                                     apr_pool_t *p,
+-                                                     apr_pool_t *ptemp,
+-                                                     unsigned depth,
+-                                                     int optional)
+-{
+-    const char *error;
+-    apr_status_t rv;
++typedef struct {
++    server_rec *s;
++    ap_directive_t **conftree;
++} configs;
+ 
+-    if (ap_is_directory(ptemp, fname)) {
+-        apr_dir_t *dirp;
+-        apr_finfo_t dirent;
+-        int current;
+-        apr_array_header_t *candidates = NULL;
+-        fnames *fnew;
+-        char *path = apr_pstrdup(ptemp, fname);
+-
+-        if (++depth > AP_MAX_INCLUDE_DIR_DEPTH) {
+-            return apr_psprintf(p, "Directory %s exceeds the maximum include "
+-                                "directory nesting level of %u. You have "
+-                                "probably a recursion somewhere.", path,
+-                                AP_MAX_INCLUDE_DIR_DEPTH);
+-        }
+-
+-        /*
+-         * first course of business is to grok all the directory
+-         * entries here and store 'em away. Recall we need full pathnames
+-         * for this.
+-         */
+-        rv = apr_dir_open(&dirp, path, ptemp);
+-        if (rv != APR_SUCCESS) {
+-            return apr_psprintf(p, "Could not open config directory %s: %pm",
+-                                path, &rv);
+-        }
+-
+-        candidates = apr_array_make(ptemp, 1, sizeof(fnames));
+-        while (apr_dir_read(&dirent, APR_FINFO_DIRENT, dirp) == APR_SUCCESS) {
+-            /* strip out '.' and '..' */
+-            if (strcmp(dirent.name, ".")
+-                && strcmp(dirent.name, "..")) {
+-                fnew = (fnames *) apr_array_push(candidates);
+-                fnew->fname = ap_make_full_path(ptemp, path, dirent.name);
+-            }
+-        }
+-
+-        apr_dir_close(dirp);
+-        if (candidates->nelts != 0) {
+-            qsort((void *) candidates->elts, candidates->nelts,
+-                  sizeof(fnames), fname_alphasort);
+-
+-            /*
+-             * Now recurse these... we handle errors and subdirectories
+-             * via the recursion, which is nice
+-             */
+-            for (current = 0; current < candidates->nelts; ++current) {
+-                fnew = &((fnames *) candidates->elts)[current];
+-                error = process_resource_config_nofnmatch(s, fnew->fname,
+-                                                          conftree, p, ptemp,
+-                                                          depth, optional);
+-                if (error) {
+-                    return error;
+-                }
+-            }
+-        }
+-
+-        return NULL;
+-    }
+-    else if (optional) {
+-        /* If the optinal flag is set (like for IncludeOptional) we can
+-         * tolerate that no file or directory is present and bail out.
+-         */
+-        apr_finfo_t finfo;
+-        if (apr_stat(&finfo, fname, APR_FINFO_TYPE, ptemp) != APR_SUCCESS
+-            || finfo.filetype == APR_NOFILE)
+-            return NULL;
+-    }
+-
+-    return ap_process_resource_config(s, fname, conftree, p, ptemp);
+-}
+-
+-static const char *process_resource_config_fnmatch(server_rec *s,
+-                                                   const char *path,
+-                                                   const char *fname,
+-                                                   ap_directive_t **conftree,
+-                                                   apr_pool_t *p,
+-                                                   apr_pool_t *ptemp,
+-                                                   unsigned depth,
+-                                                   int optional)
++static const char *process_resource_config_cb(ap_dir_match_t *w, const char *fname)
+ {
+-    const char *rest;
+-    apr_status_t rv;
+-    apr_dir_t *dirp;
+-    apr_finfo_t dirent;
+-    apr_array_header_t *candidates = NULL;
+-    fnames *fnew;
+-    int current;
+-
+-    /* find the first part of the filename */
+-    rest = ap_strchr_c(fname, '/');
+-    if (rest) {
+-        fname = apr_pstrmemdup(ptemp, fname, rest - fname);
+-        rest++;
+-    }
+-
+-    /* optimisation - if the filename isn't a wildcard, process it directly */
+-    if (!apr_fnmatch_test(fname)) {
+-        path = ap_make_full_path(ptemp, path, fname);
+-        if (!rest) {
+-            return process_resource_config_nofnmatch(s, path,
+-                                                     conftree, p,
+-                                                     ptemp, 0, optional);
+-        }
+-        else {
+-            return process_resource_config_fnmatch(s, path, rest,
+-                                                   conftree, p,
+-                                                   ptemp, 0, optional);
+-        }
+-    }
+-
+-    /*
+-     * first course of business is to grok all the directory
+-     * entries here and store 'em away. Recall we need full pathnames
+-     * for this.
+-     */
+-    rv = apr_dir_open(&dirp, path, ptemp);
+-    if (rv != APR_SUCCESS) {
+-        /* If the directory doesn't exist and the optional flag is set
+-         * there is no need to return an error.
+-         */
+-        if (rv == APR_ENOENT && optional) {
+-            return NULL;
+-        }
+-        return apr_psprintf(p, "Could not open config directory %s: %pm",
+-                            path, &rv);
+-    }
+-
+-    candidates = apr_array_make(ptemp, 1, sizeof(fnames));
+-    while (apr_dir_read(&dirent, APR_FINFO_DIRENT | APR_FINFO_TYPE, dirp) == APR_SUCCESS) {
+-        /* strip out '.' and '..' */
+-        if (strcmp(dirent.name, ".")
+-            && strcmp(dirent.name, "..")
+-            && (apr_fnmatch(fname, dirent.name,
+-                            APR_FNM_PERIOD) == APR_SUCCESS)) {
+-            const char *full_path = ap_make_full_path(ptemp, path, dirent.name);
+-            /* If matching internal to path, and we happen to match something
+-             * other than a directory, skip it
+-             */
+-            if (rest && (dirent.filetype != APR_DIR)) {
+-                continue;
+-            }
+-            fnew = (fnames *) apr_array_push(candidates);
+-            fnew->fname = full_path;
+-        }
+-    }
+-
+-    apr_dir_close(dirp);
+-    if (candidates->nelts != 0) {
+-        const char *error;
+-
+-        qsort((void *) candidates->elts, candidates->nelts,
+-              sizeof(fnames), fname_alphasort);
+-
+-        /*
+-         * Now recurse these... we handle errors and subdirectories
+-         * via the recursion, which is nice
+-         */
+-        for (current = 0; current < candidates->nelts; ++current) {
+-            fnew = &((fnames *) candidates->elts)[current];
+-            if (!rest) {
+-                error = process_resource_config_nofnmatch(s, fnew->fname,
+-                                                          conftree, p,
+-                                                          ptemp, 0, optional);
+-            }
+-            else {
+-                error = process_resource_config_fnmatch(s, fnew->fname, rest,
+-                                                        conftree, p,
+-                                                        ptemp, 0, optional);
+-            }
+-            if (error) {
+-                return error;
+-            }
+-        }
+-    }
+-    else {
+-
+-        if (!optional) {
+-            return apr_psprintf(p, "No matches for the wildcard '%s' in '%s', failing "
+-                                   "(use IncludeOptional if required)", fname, path);
+-        }
+-    }
+-
+-    return NULL;
++    configs *cfgs = w->ctx;
++    return ap_process_resource_config(cfgs->s, fname, cfgs->conftree, w->p, w->ptemp);
+ }
+ 
+ AP_DECLARE(const char *) ap_process_fnmatch_configs(server_rec *s,
+@@ -2100,9 +1903,19 @@
+                                                     apr_pool_t *ptemp,
+                                                     int optional)
+ {
+-    /* XXX: lstat() won't work on the wildcard pattern...
+-     */
++    configs cfgs;
++    ap_dir_match_t w;
+ 
++    cfgs.s = s;
++    cfgs.conftree = conftree;
++
++    w.prefix = "Include/IncludeOptional: ";
++    w.p = p;
++    w.ptemp = ptemp;
++    w.flags = (optional ? AP_DIR_FLAG_OPTIONAL : AP_DIR_FLAG_NONE) | AP_DIR_FLAG_RECURSIVE;
++    w.cb = process_resource_config_cb;
++    w.ctx = &cfgs;
++
+     /* don't require conf/httpd.conf if we have a -C or -c switch */
+     if ((ap_server_pre_read_config->nelts
+         || ap_server_post_read_config->nelts)
+@@ -2114,7 +1927,7 @@
+     }
+ 
+     if (!apr_fnmatch_test(fname)) {
+-        return process_resource_config_nofnmatch(s, fname, conftree, p, ptemp, 0, optional);
++        return ap_dir_nofnmatch(&w, fname);
+     }
+     else {
+         apr_status_t status;
+@@ -2132,8 +1945,7 @@
+         }
+ 
+         /* walk the filepath */
+-        return process_resource_config_fnmatch(s, rootpath, filepath, conftree, p, ptemp,
+-                                               0, optional);
++        return ap_dir_fnmatch(&w, rootpath, filepath);
+     }
+ }
+ 
+Index: server/util.c
+===================================================================
+--- server/util.c	(revision 1847315)
++++ server/util.c	(working copy)
+@@ -47,6 +47,7 @@
+ 
+ #include "ap_config.h"
+ #include "apr_base64.h"
++#include "apr_fnmatch.h"
+ #include "httpd.h"
+ #include "http_main.h"
+ #include "http_log.h"
+@@ -96,6 +97,11 @@
+ #undef APLOG_MODULE_INDEX
+ #define APLOG_MODULE_INDEX AP_CORE_MODULE_INDEX
+ 
++/* maximum nesting level for config directories */
++#ifndef AP_MAX_FNMATCH_DIR_DEPTH
++#define AP_MAX_FNMATCH_DIR_DEPTH (128)
++#endif
++
+ /*
+  * Examine a field value (such as a media-/content-type) string and return
+  * it sans any parameters; e.g., strip off any ';charset=foo' and the like.
+@@ -3307,3 +3313,206 @@
+     return 0;
+ }
+ 
++typedef struct {
++    const char *fname;
++} fnames;
++
++static int fname_alphasort(const void *fn1, const void *fn2)
++{
++    const fnames *f1 = fn1;
++    const fnames *f2 = fn2;
++
++    return strcmp(f1->fname,f2->fname);
++}
++
++AP_DECLARE(ap_dir_match_t *)ap_dir_cfgmatch(cmd_parms *cmd, int flags,
++        const char *(*cb)(ap_dir_match_t *w, const char *fname), void *ctx)
++{
++    ap_dir_match_t *w = apr_palloc(cmd->temp_pool, sizeof(cmd_parms));
++
++    w->prefix = apr_pstrcat(cmd->pool, cmd->cmd->name, ": ", NULL);
++    w->p = cmd->pool;
++    w->ptemp = cmd->temp_pool;
++    w->flags = flags;
++    w->cb = cb;
++    w->ctx = ctx;
++    w->depth = 0;
++
++    return w;
++}
++
++AP_DECLARE(const char *)ap_dir_nofnmatch(ap_dir_match_t *w, const char *fname)
++{
++    const char *error;
++    apr_status_t rv;
++
++    if ((w->flags & AP_DIR_FLAG_RECURSIVE) && ap_is_directory(w->ptemp, fname)) {
++        apr_dir_t *dirp;
++        apr_finfo_t dirent;
++        int current;
++        apr_array_header_t *candidates = NULL;
++        fnames *fnew;
++        char *path = apr_pstrdup(w->ptemp, fname);
++
++        if (++w->depth > AP_MAX_FNMATCH_DIR_DEPTH) {
++            return apr_psprintf(w->p, "%sDirectory '%s' exceeds the maximum include "
++                    "directory nesting level of %u. You have "
++                    "probably a recursion somewhere.", w->prefix ? w->prefix : "", path,
++                    AP_MAX_FNMATCH_DIR_DEPTH);
++        }
++
++        /*
++         * first course of business is to grok all the directory
++         * entries here and store 'em away. Recall we need full pathnames
++         * for this.
++         */
++        rv = apr_dir_open(&dirp, path, w->ptemp);
++        if (rv != APR_SUCCESS) {
++            return apr_psprintf(w->p, "%sCould not open directory %s: %pm",
++                    w->prefix ? w->prefix : "", path, &rv);
++        }
++
++        candidates = apr_array_make(w->ptemp, 1, sizeof(fnames));
++        while (apr_dir_read(&dirent, APR_FINFO_DIRENT, dirp) == APR_SUCCESS) {
++            /* strip out '.' and '..' */
++            if (strcmp(dirent.name, ".")
++                && strcmp(dirent.name, "..")) {
++                fnew = (fnames *) apr_array_push(candidates);
++                fnew->fname = ap_make_full_path(w->ptemp, path, dirent.name);
++            }
++        }
++
++        apr_dir_close(dirp);
++        if (candidates->nelts != 0) {
++            qsort((void *) candidates->elts, candidates->nelts,
++                  sizeof(fnames), fname_alphasort);
++
++            /*
++             * Now recurse these... we handle errors and subdirectories
++             * via the recursion, which is nice
++             */
++            for (current = 0; current < candidates->nelts; ++current) {
++                fnew = &((fnames *) candidates->elts)[current];
++                error = ap_dir_nofnmatch(w, fnew->fname);
++                if (error) {
++                    return error;
++                }
++            }
++        }
++
++        w->depth--;
++
++        return NULL;
++    }
++    else if (w->flags & AP_DIR_FLAG_OPTIONAL) {
++        /* If the optional flag is set (like for IncludeOptional) we can
++         * tolerate that no file or directory is present and bail out.
++         */
++        apr_finfo_t finfo;
++        if (apr_stat(&finfo, fname, APR_FINFO_TYPE, w->ptemp) != APR_SUCCESS
++            || finfo.filetype == APR_NOFILE)
++            return NULL;
++    }
++
++    return w->cb(w, fname);
++}
++
++AP_DECLARE(const char *)ap_dir_fnmatch(ap_dir_match_t *w, const char *path,
++        const char *fname)
++{
++    const char *rest;
++    apr_status_t rv;
++    apr_dir_t *dirp;
++    apr_finfo_t dirent;
++    apr_array_header_t *candidates = NULL;
++    fnames *fnew;
++    int current;
++
++    /* find the first part of the filename */
++    rest = ap_strchr_c(fname, '/');
++    if (rest) {
++        fname = apr_pstrmemdup(w->ptemp, fname, rest - fname);
++        rest++;
++    }
++
++    /* optimisation - if the filename isn't a wildcard, process it directly */
++    if (!apr_fnmatch_test(fname)) {
++        path = path ? ap_make_full_path(w->ptemp, path, fname) : fname;
++        if (!rest) {
++            return ap_dir_nofnmatch(w, path);
++        }
++        else {
++            return ap_dir_fnmatch(w, path, rest);
++        }
++    }
++
++    /*
++     * first course of business is to grok all the directory
++     * entries here and store 'em away. Recall we need full pathnames
++     * for this.
++     */
++    rv = apr_dir_open(&dirp, path, w->ptemp);
++    if (rv != APR_SUCCESS) {
++        /* If the directory doesn't exist and the optional flag is set
++         * there is no need to return an error.
++         */
++        if (rv == APR_ENOENT && (w->flags & AP_DIR_FLAG_OPTIONAL)) {
++            return NULL;
++        }
++        return apr_psprintf(w->p, "%sCould not open directory %s: %pm",
++                w->prefix ? w->prefix : "", path, &rv);
++    }
++
++    candidates = apr_array_make(w->ptemp, 1, sizeof(fnames));
++    while (apr_dir_read(&dirent, APR_FINFO_DIRENT | APR_FINFO_TYPE, dirp) == APR_SUCCESS) {
++        /* strip out '.' and '..' */
++        if (strcmp(dirent.name, ".")
++            && strcmp(dirent.name, "..")
++            && (apr_fnmatch(fname, dirent.name,
++                            APR_FNM_PERIOD) == APR_SUCCESS)) {
++            const char *full_path = ap_make_full_path(w->ptemp, path, dirent.name);
++            /* If matching internal to path, and we happen to match something
++             * other than a directory, skip it
++             */
++            if (rest && (dirent.filetype != APR_DIR)) {
++                continue;
++            }
++            fnew = (fnames *) apr_array_push(candidates);
++            fnew->fname = full_path;
++        }
++    }
++
++    apr_dir_close(dirp);
++    if (candidates->nelts != 0) {
++        const char *error;
++
++        qsort((void *) candidates->elts, candidates->nelts,
++              sizeof(fnames), fname_alphasort);
++
++        /*
++         * Now recurse these... we handle errors and subdirectories
++         * via the recursion, which is nice
++         */
++        for (current = 0; current < candidates->nelts; ++current) {
++            fnew = &((fnames *) candidates->elts)[current];
++            if (!rest) {
++                error = ap_dir_nofnmatch(w, fnew->fname);
++            }
++            else {
++                error = ap_dir_fnmatch(w, fnew->fname, rest);
++            }
++            if (error) {
++                return error;
++            }
++        }
++    }
++    else {
++
++        if (!(w->flags & AP_DIR_FLAG_OPTIONAL)) {
++            return apr_psprintf(w->p, "%sNo matches for the wildcard '%s' in '%s', failing",
++                    w->prefix ? w->prefix : "", fname, path);
++        }
++    }
++
++    return NULL;
++}