]> wimlib.net Git - wimlib/blob - src/wildcard.c
test-imagex-ntfs: Try to work around yet another race condition
[wimlib] / src / wildcard.c
1 /*
2  * wildcard.c
3  *
4  * Wildcard matching functions.
5  */
6
7 /*
8  * Copyright (C) 2013 Eric Biggers
9  *
10  * This file is part of wimlib, a library for working with WIM files.
11  *
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)
15  * any later version.
16  *
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
20  * details.
21  *
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/.
24  */
25
26 #ifdef HAVE_CONFIG_H
27 #  include "config.h"
28 #endif
29
30 #include "wimlib/dentry.h"
31 #include "wimlib/encoding.h"
32 #include "wimlib/error.h"
33 #include "wimlib/metadata.h"
34 #include "wimlib/wildcard.h"
35
36 struct match_dentry_ctx {
37         int (*consume_dentry)(struct wim_dentry *, void *);
38         void *consume_dentry_ctx;
39         size_t consume_dentry_count;
40         tchar *wildcard_path;
41         size_t cur_component_offset;
42         size_t cur_component_len;
43         bool case_insensitive;
44 };
45
46 #ifdef __WIN32__
47 static bool
48 match_wildcard_case_sensitive(const tchar *string, size_t string_len,
49                               const tchar *wildcard, size_t wildcard_len)
50 {
51         for (;;) {
52                 if (string_len == 0) {
53                         while (wildcard_len != 0 && *wildcard == T('*')) {
54                                 wildcard++;
55                                 wildcard_len--;
56                         }
57                         return (wildcard_len == 0);
58                 } else if (wildcard_len == 0) {
59                         return false;
60                 } else if (*string == *wildcard || *wildcard == T('?')) {
61                         string++;
62                         string_len--;
63                         wildcard_len--;
64                         wildcard++;
65                         continue;
66                 } else if (*wildcard == T('*')) {
67                         return match_wildcard_case_sensitive(
68                                               string, string_len,
69                                               wildcard + 1, wildcard_len - 1) ||
70                                match_wildcard_case_sensitive(
71                                               string + 1, string_len - 1,
72                                               wildcard, wildcard_len);
73                 } else {
74                         return false;
75                 }
76         }
77 }
78 #endif /* __WIN32__ */
79
80 static bool
81 match_wildcard(const tchar *string, tchar *wildcard,
82                size_t wildcard_len, bool case_insensitive)
83 {
84         /* Note: in Windows builds fnmatch() calls a replacement function.  It
85          * does not support case-sensitive globbing.  */
86 #ifdef __WIN32__
87         if (case_insensitive)
88 #endif
89         {
90                 char orig;
91                 int ret;
92                 int flags = FNM_NOESCAPE;
93                 if (case_insensitive)
94                         flags |= FNM_CASEFOLD;
95
96                 orig = wildcard[wildcard_len];
97                 wildcard[wildcard_len] = T('\0');
98
99                 ret = fnmatch(wildcard, string, flags);
100
101                 wildcard[wildcard_len] = orig;
102                 return (ret == 0);
103         }
104 #ifdef __WIN32__
105         else
106         {
107                 return match_wildcard_case_sensitive(string,
108                                                      tstrlen(string),
109                                                      wildcard,
110                                                      wildcard_len);
111         }
112 #endif
113 }
114
115 static int
116 expand_wildcard_recursive(struct wim_dentry *cur_dentry,
117                           struct match_dentry_ctx *ctx);
118
119 enum {
120         WILDCARD_STATUS_DONE_FULLY,
121         WILDCARD_STATUS_DONE_TRAILING_SLASHES,
122         WILDCARD_STATUS_NOT_DONE,
123 };
124
125 static int
126 wildcard_status(const tchar *wildcard)
127 {
128         if (*wildcard == T('\0'))
129                 return WILDCARD_STATUS_DONE_FULLY;
130         while (is_any_path_separator(*wildcard))
131                 wildcard++;
132         if (*wildcard == T('\0'))
133                 return WILDCARD_STATUS_DONE_TRAILING_SLASHES;
134
135         return WILDCARD_STATUS_NOT_DONE;
136 }
137
138 static int
139 match_dentry(struct wim_dentry *cur_dentry, void *_ctx)
140 {
141         struct match_dentry_ctx *ctx = _ctx;
142         tchar *name;
143         size_t name_len;
144         int ret;
145
146         if (cur_dentry->file_name_nbytes == 0)
147                 return 0;
148
149 #if TCHAR_IS_UTF16LE
150         name = cur_dentry->file_name;
151         name_len = cur_dentry->file_name_nbytes;
152 #else
153         ret = utf16le_to_tstr(cur_dentry->file_name,
154                               cur_dentry->file_name_nbytes,
155                               &name, &name_len);
156         if (ret)
157                 return ret;
158 #endif
159         name_len /= sizeof(tchar);
160
161         if (match_wildcard(name,
162                            &ctx->wildcard_path[ctx->cur_component_offset],
163                            ctx->cur_component_len,
164                            ctx->case_insensitive))
165         {
166                 switch (wildcard_status(&ctx->wildcard_path[
167                                 ctx->cur_component_offset +
168                                 ctx->cur_component_len]))
169                 {
170                 case WILDCARD_STATUS_DONE_TRAILING_SLASHES:
171                         if (!dentry_is_directory(cur_dentry)) {
172                                 ret = 0;
173                                 break;
174                         }
175                         /* Fall through  */
176                 case WILDCARD_STATUS_DONE_FULLY:
177                         ret = (*ctx->consume_dentry)(cur_dentry,
178                                                      ctx->consume_dentry_ctx);
179                         ctx->consume_dentry_count++;
180                         break;
181                 case WILDCARD_STATUS_NOT_DONE:
182                         ret = expand_wildcard_recursive(cur_dentry, ctx);
183                         break;
184                 }
185         } else {
186                 ret = 0;
187         }
188
189 #if !TCHAR_IS_UTF16LE
190         FREE(name);
191 #endif
192         return ret;
193 }
194
195 static int
196 expand_wildcard_recursive(struct wim_dentry *cur_dentry,
197                           struct match_dentry_ctx *ctx)
198 {
199         tchar *w;
200         size_t begin;
201         size_t end;
202         size_t len;
203         size_t offset_save;
204         size_t len_save;
205         int ret;
206
207         w = ctx->wildcard_path;
208
209         begin = ctx->cur_component_offset + ctx->cur_component_len;
210         while (is_any_path_separator(w[begin]))
211                 begin++;
212
213         end = begin;
214
215         while (w[end] != T('\0') && !is_any_path_separator(w[end]))
216                 end++;
217
218         len = end - begin;
219
220         if (len == 0)
221                 return 0;
222
223         offset_save = ctx->cur_component_offset;
224         len_save = ctx->cur_component_len;
225
226         ctx->cur_component_offset = begin;
227         ctx->cur_component_len = len;
228
229         ret = for_dentry_child(cur_dentry, match_dentry, ctx);
230
231         ctx->cur_component_len = len_save;
232         ctx->cur_component_offset = offset_save;
233
234         return ret;
235 }
236
237 /* Expand a wildcard relative to the current WIM image.
238  *
239  * @wim
240  *      WIMStruct whose currently selected image is searched to expand the
241  *      wildcard.
242  * @wildcard_path
243  *      Wildcard path to expand, which may contain the '?' and '*' characters.
244  *      Path separators may be either forward slashes, and leading path
245  *      separators are ignored.  Trailing path separators indicate that the
246  *      wildcard can only match directories.
247  * @consume_dentry
248  *      Callback function which will receive each directory entry matched by the
249  *      wildcard.
250  * @consume_dentry_ctx
251  *      Argument to pass to @consume_dentry.
252  * @flags
253  *      Zero or more of the following flags:
254  *
255  *      WILDCARD_FLAG_WARN_IF_NO_MATCH:
256  *              Issue a warning if the wildcard does not match any dentries.
257  *
258  *      WILDCARD_FLAG_ERROR_IF_NO_MATCH:
259  *              Issue an error and return WIMLIB_ERR_PATH_DOES_NOT_EXIST if the
260  *              wildcard does not match any dentries.
261  *
262  *      WILDCARD_FLAG_CASE_INSENSITIVE:
263  *              Perform the matching case insensitively.  Note that this may
264  *              cause @wildcard to match multiple dentries, even if it does not
265  *              contain wildcard characters.
266  *
267  * @return 0 on success; a positive error code on error; or the first nonzero
268  * value returned by @consume_dentry.
269  *
270  * Note: this function uses the @tmp_list field of dentries it attempts to
271  * match.
272  */
273 int
274 expand_wildcard(WIMStruct *wim,
275                 const tchar *wildcard_path,
276                 int (*consume_dentry)(struct wim_dentry *, void *),
277                 void *consume_dentry_ctx,
278                 u32 flags)
279 {
280         struct wim_dentry *root;
281         int ret;
282
283         root = wim_root_dentry(wim);
284         if (root == NULL)
285                 goto no_match;
286
287         struct match_dentry_ctx ctx = {
288                 .consume_dentry = consume_dentry,
289                 .consume_dentry_ctx = consume_dentry_ctx,
290                 .consume_dentry_count = 0,
291                 .wildcard_path = TSTRDUP(wildcard_path),
292                 .cur_component_offset = 0,
293                 .cur_component_len = 0,
294                 .case_insensitive = ((flags & WILDCARD_FLAG_CASE_INSENSITIVE) != 0),
295         };
296
297         if (ctx.wildcard_path == NULL)
298                 return WIMLIB_ERR_NOMEM;
299
300         ret = expand_wildcard_recursive(root, &ctx);
301         FREE(ctx.wildcard_path);
302         if (ret == 0 && ctx.consume_dentry_count == 0)
303                 goto no_match;
304         return ret;
305
306 no_match:
307         ret = 0;
308         if (flags & WILDCARD_FLAG_WARN_IF_NO_MATCH)
309                 WARNING("No matches for wildcard path \"%"TS"\"", wildcard_path);
310
311         if (flags & WILDCARD_FLAG_ERROR_IF_NO_MATCH) {
312                 ERROR("No matches for wildcard path \"%"TS"\"", wildcard_path);
313                 ret = WIMLIB_ERR_PATH_DOES_NOT_EXIST;
314         }
315         return ret;
316 }