4 * Wildcard matching functions.
8 * Copyright (C) 2013 Eric Biggers
10 * This file is part of wimlib, a library for working with WIM files.
12 * wimlib is free software; you can redistribute it and/or modify it under the
13 * terms of the GNU General Public License as published by the Free
14 * Software Foundation; either version 3 of the License, or (at your option)
17 * wimlib is distributed in the hope that it will be useful, but WITHOUT ANY
18 * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
19 * A PARTICULAR PURPOSE. See the GNU General Public License for more
22 * You should have received a copy of the GNU General Public License
23 * along with wimlib; if not, see http://www.gnu.org/licenses/.
31 #include "wimlib/dentry.h"
32 #include "wimlib/encoding.h"
33 #include "wimlib/error.h"
34 #include "wimlib/metadata.h"
35 #include "wimlib/wildcard.h"
37 struct match_dentry_ctx {
38 int (*consume_dentry)(struct wim_dentry *, void *);
39 void *consume_dentry_ctx;
40 size_t consume_dentry_count;
42 size_t cur_component_offset;
43 size_t cur_component_len;
44 bool case_insensitive;
47 #define PLATFORM_SUPPORTS_FNMATCH
50 /* PathMatchSpec() could provide a fnmatch() alternative, but it isn't
51 * documented properly, nor does it work properly. For example, it returns that
52 * any name matches *.* even if that name doesn't actually contain a period. */
53 # undef PLATFORM_SUPPORTS_FNMATCH
56 #ifndef PLATFORM_SUPPORTS_FNMATCH
58 do_match_wildcard(const tchar *string, size_t string_len,
59 const tchar *wildcard, size_t wildcard_len,
63 if (string_len == 0) {
64 while (wildcard_len != 0 && *wildcard == T('*')) {
68 return (wildcard_len == 0);
69 } else if (wildcard_len == 0) {
71 } else if (*string == *wildcard || *wildcard == T('?') ||
72 (ignore_case && totlower(*string) == totlower(*wildcard)))
79 } else if (*wildcard == T('*')) {
80 return do_match_wildcard(string, string_len,
81 wildcard + 1, wildcard_len - 1,
83 do_match_wildcard(string + 1, string_len - 1,
84 wildcard, wildcard_len,
91 #endif /* ! PLATFORM_SUPPORTS_FNMATCH */
94 match_wildcard(const tchar *string, tchar *wildcard,
95 size_t wildcard_len, bool ignore_case)
97 #ifdef PLATFORM_SUPPORTS_FNMATCH
100 int flags = FNM_NOESCAPE;
102 flags |= FNM_CASEFOLD;
104 orig = wildcard[wildcard_len];
105 wildcard[wildcard_len] = T('\0');
107 ret = fnmatch(wildcard, string, flags);
109 wildcard[wildcard_len] = orig;
112 return do_match_wildcard(string, tstrlen(string),
113 wildcard, wildcard_len, ignore_case);
118 expand_wildcard_recursive(struct wim_dentry *cur_dentry,
119 struct match_dentry_ctx *ctx);
122 WILDCARD_STATUS_DONE_FULLY,
123 WILDCARD_STATUS_DONE_TRAILING_SLASHES,
124 WILDCARD_STATUS_NOT_DONE,
128 wildcard_status(const tchar *wildcard)
130 if (*wildcard == T('\0'))
131 return WILDCARD_STATUS_DONE_FULLY;
132 while (*wildcard == WIM_PATH_SEPARATOR)
134 if (*wildcard == T('\0'))
135 return WILDCARD_STATUS_DONE_TRAILING_SLASHES;
137 return WILDCARD_STATUS_NOT_DONE;
141 match_dentry(struct wim_dentry *cur_dentry, void *_ctx)
143 struct match_dentry_ctx *ctx = _ctx;
148 if (cur_dentry->file_name_nbytes == 0)
152 name = cur_dentry->file_name;
153 name_len = cur_dentry->file_name_nbytes;
155 ret = utf16le_to_tstr(cur_dentry->file_name,
156 cur_dentry->file_name_nbytes,
161 name_len /= sizeof(tchar);
163 if (match_wildcard(name,
164 &ctx->wildcard_path[ctx->cur_component_offset],
165 ctx->cur_component_len,
166 ctx->case_insensitive))
168 switch (wildcard_status(&ctx->wildcard_path[
169 ctx->cur_component_offset +
170 ctx->cur_component_len]))
172 case WILDCARD_STATUS_DONE_TRAILING_SLASHES:
173 if (!dentry_is_directory(cur_dentry)) {
178 case WILDCARD_STATUS_DONE_FULLY:
179 ret = (*ctx->consume_dentry)(cur_dentry,
180 ctx->consume_dentry_ctx);
181 ctx->consume_dentry_count++;
183 case WILDCARD_STATUS_NOT_DONE:
184 ret = expand_wildcard_recursive(cur_dentry, ctx);
191 #if !TCHAR_IS_UTF16LE
198 expand_wildcard_recursive(struct wim_dentry *cur_dentry,
199 struct match_dentry_ctx *ctx)
209 w = ctx->wildcard_path;
211 begin = ctx->cur_component_offset + ctx->cur_component_len;
212 while (w[begin] == WIM_PATH_SEPARATOR)
217 while (w[end] != T('\0') && w[end] != WIM_PATH_SEPARATOR)
225 offset_save = ctx->cur_component_offset;
226 len_save = ctx->cur_component_len;
228 ctx->cur_component_offset = begin;
229 ctx->cur_component_len = len;
231 ret = for_dentry_child(cur_dentry, match_dentry, ctx);
233 ctx->cur_component_len = len_save;
234 ctx->cur_component_offset = offset_save;
239 /* Expand a wildcard relative to the current WIM image.
242 * WIMStruct whose currently selected image is searched to expand the
245 * Wildcard path to expand, which may contain the '?' and '*' characters.
246 * Path separators must be WIM_PATH_SEPARATOR. Leading path separators are
247 * ignored, whereas one or more trailing path separators indicate that the
248 * wildcard path can only match directories (and not reparse points).
250 * Callback function which will receive each directory entry matched by the
252 * @consume_dentry_ctx
253 * Argument to pass to @consume_dentry.
255 * Zero or more of the following flags:
257 * WILDCARD_FLAG_WARN_IF_NO_MATCH:
258 * Issue a warning if the wildcard does not match any dentries.
260 * WILDCARD_FLAG_ERROR_IF_NO_MATCH:
261 * Issue an error and return WIMLIB_ERR_PATH_DOES_NOT_EXIST if the
262 * wildcard does not match any dentries.
264 * WILDCARD_FLAG_CASE_INSENSITIVE:
265 * Perform the matching case insensitively. Note that this may
266 * cause @wildcard to match multiple dentries, even if it does not
267 * contain wildcard characters.
269 * @return 0 on success; a positive error code on error; or the first nonzero
270 * value returned by @consume_dentry.
272 * Note: this function uses the @tmp_list field of dentries it attempts to
276 expand_wildcard(WIMStruct *wim,
277 const tchar *wildcard_path,
278 int (*consume_dentry)(struct wim_dentry *, void *),
279 void *consume_dentry_ctx,
282 struct wim_dentry *root;
285 root = wim_root_dentry(wim);
289 struct match_dentry_ctx ctx = {
290 .consume_dentry = consume_dentry,
291 .consume_dentry_ctx = consume_dentry_ctx,
292 .consume_dentry_count = 0,
293 .wildcard_path = TSTRDUP(wildcard_path),
294 .cur_component_offset = 0,
295 .cur_component_len = 0,
296 .case_insensitive = ((flags & WILDCARD_FLAG_CASE_INSENSITIVE) != 0),
299 if (ctx.wildcard_path == NULL)
300 return WIMLIB_ERR_NOMEM;
302 ret = expand_wildcard_recursive(root, &ctx);
303 FREE(ctx.wildcard_path);
304 if (ret == 0 && ctx.consume_dentry_count == 0)
310 if (flags & WILDCARD_FLAG_WARN_IF_NO_MATCH)
311 WARNING("No matches for wildcard path \"%"TS"\"", wildcard_path);
313 if (flags & WILDCARD_FLAG_ERROR_IF_NO_MATCH) {
314 ERROR("No matches for wildcard path \"%"TS"\"", wildcard_path);
315 ret = WIMLIB_ERR_PATH_DOES_NOT_EXIST;