]>
Commit | Line | Data |
---|---|---|
ccc6cda3 JA |
1 | /* pathexp.c -- The shell interface to the globbing library. */ |
2 | ||
b2613ad1 | 3 | /* Copyright (C) 1995-2023 Free Software Foundation, Inc. |
ccc6cda3 JA |
4 | |
5 | This file is part of GNU Bash, the Bourne Again SHell. | |
6 | ||
2e4498b3 CR |
7 | Bash is free software: you can redistribute it and/or modify |
8 | it under the terms of the GNU General Public License as published by | |
9 | the Free Software Foundation, either version 3 of the License, or | |
10 | (at your option) any later version. | |
ccc6cda3 | 11 | |
2e4498b3 CR |
12 | Bash is distributed in the hope that it will be useful, |
13 | but WITHOUT ANY WARRANTY; without even the implied warranty of | |
14 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | |
15 | GNU General Public License for more details. | |
ccc6cda3 | 16 | |
2e4498b3 CR |
17 | You should have received a copy of the GNU General Public License |
18 | along with Bash. If not, see <http://www.gnu.org/licenses/>. | |
19 | */ | |
ccc6cda3 JA |
20 | |
21 | #include "config.h" | |
22 | ||
23 | #include "bashtypes.h" | |
24 | #include <stdio.h> | |
25 | ||
26 | #if defined (HAVE_UNISTD_H) | |
27 | # include <unistd.h> | |
28 | #endif | |
29 | ||
d06fefb2 CR |
30 | #include "posixstat.h" |
31 | #include "stat-time.h" | |
32 | ||
ccc6cda3 JA |
33 | #include "bashansi.h" |
34 | ||
35 | #include "shell.h" | |
36 | #include "pathexp.h" | |
37 | #include "flags.h" | |
38 | ||
7117c2d2 | 39 | #include "shmbutil.h" |
547ef914 | 40 | #include "bashintl.h" |
7117c2d2 | 41 | |
d06fefb2 | 42 | |
f73dda09 | 43 | #include <glob/strmatch.h> |
b72432fd | 44 | |
a61ffa78 CR |
45 | static int glob_name_is_acceptable (const char *); |
46 | static void ignore_globbed_names (char **, sh_ignore_func_t *); | |
47 | static char *split_ignorespec (char *, int *); | |
d06fefb2 | 48 | static void sh_sortglob (char **); |
084c952b | 49 | |
7a8455e4 | 50 | #include <glob/glob.h> |
ccc6cda3 JA |
51 | |
52 | /* Control whether * matches .files in globbing. */ | |
53 | int glob_dot_filenames; | |
54 | ||
cce855bc | 55 | /* Control whether the extended globbing features are enabled. */ |
691aebcb | 56 | int extended_glob = EXTGLOB_DEFAULT; |
cce855bc | 57 | |
4ac1ff98 CR |
58 | /* Control enabling special handling of `**' */ |
59 | int glob_star = 0; | |
60 | ||
951bdaad CR |
61 | /* Return nonzero if STRING has any unquoted special globbing chars in it. |
62 | This is supposed to be called when pathname expansion is performed, so | |
63 | it implements the rules in Posix 2.13.3, specifically that an unquoted | |
64 | slash cannot appear in a bracket expression. */ | |
ccc6cda3 | 65 | int |
a61ffa78 | 66 | unquoted_glob_pattern_p (char *string) |
ccc6cda3 JA |
67 | { |
68 | register int c; | |
7117c2d2 | 69 | char *send; |
7a698806 | 70 | int open; |
ccc6cda3 | 71 | |
7117c2d2 JA |
72 | DECLARE_MBSTATE; |
73 | ||
7a698806 | 74 | open = 0; |
7117c2d2 JA |
75 | send = string + strlen (string); |
76 | ||
ccc6cda3 JA |
77 | while (c = *string++) |
78 | { | |
79 | switch (c) | |
80 | { | |
81 | case '?': | |
82 | case '*': | |
83 | return (1); | |
84 | ||
85 | case '[': | |
86 | open++; | |
87 | continue; | |
88 | ||
89 | case ']': | |
951bdaad | 90 | if (open) /* XXX - if --open == 0? */ |
ccc6cda3 JA |
91 | return (1); |
92 | continue; | |
93 | ||
951bdaad CR |
94 | case '/': |
95 | if (open) | |
96 | open = 0; | |
7a698806 | 97 | continue; |
951bdaad | 98 | |
cce855bc JA |
99 | case '+': |
100 | case '@': | |
101 | case '!': | |
7a698806 | 102 | if (extended_glob && *string == '(') /*)*/ |
cce855bc JA |
103 | return (1); |
104 | continue; | |
105 | ||
ccc6cda3 | 106 | case '\\': |
7a698806 CR |
107 | /* Even after an unquoted backslash, CTLESC either quotes the next |
108 | char or escapes a CTLESC or CTLNUL. Either way, the character | |
109 | after it is not an unquoted globbing char. */ | |
110 | if (*string == CTLESC) | |
111 | string++; | |
112 | /*FALLTHROUGH*/ | |
113 | case CTLESC: | |
ccc6cda3 JA |
114 | if (*string++ == '\0') |
115 | return (0); | |
116 | } | |
7117c2d2 JA |
117 | |
118 | /* Advance one fewer byte than an entire multibyte character to | |
119 | account for the auto-increment in the loop above. */ | |
120 | #ifdef HANDLE_MULTIBYTE | |
121 | string--; | |
122 | ADVANCE_CHAR_P (string, send - string); | |
123 | string++; | |
124 | #else | |
125 | ADVANCE_CHAR_P (string, send - string); | |
126 | #endif | |
ccc6cda3 | 127 | } |
8a9718cf | 128 | |
f7ec6b1a | 129 | return (0); |
ccc6cda3 JA |
130 | } |
131 | ||
d3ad40de CR |
132 | /* Return 1 if C is a character that is `special' in a POSIX ERE and needs to |
133 | be quoted to match itself. */ | |
134 | static inline int | |
a61ffa78 | 135 | ere_char (int c) |
d3ad40de CR |
136 | { |
137 | switch (c) | |
138 | { | |
139 | case '.': | |
140 | case '[': | |
141 | case '\\': | |
142 | case '(': | |
143 | case ')': | |
144 | case '*': | |
145 | case '+': | |
146 | case '?': | |
147 | case '{': | |
148 | case '|': | |
149 | case '^': | |
150 | case '$': | |
151 | return 1; | |
152 | default: | |
153 | return 0; | |
154 | } | |
155 | return (0); | |
156 | } | |
157 | ||
e7a56619 | 158 | /* This is only used to determine whether to backslash-quote a character. */ |
4ac1ff98 | 159 | int |
a61ffa78 | 160 | glob_char_p (const char *s) |
4ac1ff98 CR |
161 | { |
162 | switch (*s) | |
163 | { | |
7a698806 CR |
164 | #if defined (EXTENDED_GLOB) |
165 | case '+': | |
166 | case '@': | |
167 | return (s[1] == '('); /*)*/ | |
168 | case '(': | |
169 | case '|': | |
170 | case ')': | |
171 | #endif | |
ab99fdbc CR |
172 | case '!': |
173 | case '^': | |
174 | case '-': | |
175 | case '.': | |
176 | case ':': | |
177 | case '=': | |
4ac1ff98 CR |
178 | case '*': |
179 | case '[': | |
180 | case ']': | |
181 | case '?': | |
182 | case '\\': | |
183 | return 1; | |
4ac1ff98 CR |
184 | } |
185 | return 0; | |
186 | } | |
187 | ||
ccc6cda3 JA |
188 | /* PATHNAME can contain characters prefixed by CTLESC; this indicates |
189 | that the character is to be quoted. We quote it here in the style | |
cce855bc | 190 | that the glob library recognizes. If flags includes QGLOB_CVTNULL, |
ccc6cda3 JA |
191 | we change quoted null strings (pathname[0] == CTLNUL) into empty |
192 | strings (pathname[0] == 0). If this is called after quote removal | |
cce855bc | 193 | is performed, (flags & QGLOB_CVTNULL) should be 0; if called when quote |
ccc6cda3 | 194 | removal has not been done (for example, before attempting to match a |
cce855bc | 195 | pattern while executing a case statement), flags should include |
2afeb2af CR |
196 | QGLOB_CVTNULL. If flags includes QGLOB_CTLESC, we need to remove CTLESC |
197 | quoting CTLESC or CTLNUL (as if dequote_string were called). If flags | |
198 | includes QGLOB_FILENAME, appropriate quoting to match a filename should be | |
199 | performed. QGLOB_REGEXP means we're quoting for a Posix ERE (for | |
200 | [[ string =~ pat ]]) and that requires some special handling. */ | |
ccc6cda3 | 201 | char * |
a61ffa78 | 202 | quote_string_for_globbing (const char *pathname, int qflags) |
ccc6cda3 JA |
203 | { |
204 | char *temp; | |
cce855bc | 205 | register int i, j; |
2e412574 | 206 | int cclass, collsym, equiv, c, last_was_backslash; |
2171061f | 207 | int savei, savej; |
8418224f | 208 | unsigned char cc; |
ccc6cda3 | 209 | |
c61bfbfd | 210 | temp = (char *)xmalloc (2 * strlen (pathname) + 1); |
ccc6cda3 | 211 | |
cce855bc | 212 | if ((qflags & QGLOB_CVTNULL) && QUOTED_NULL (pathname)) |
ccc6cda3 JA |
213 | { |
214 | temp[0] = '\0'; | |
215 | return temp; | |
216 | } | |
217 | ||
2e412574 | 218 | cclass = collsym = equiv = last_was_backslash = 0; |
cce855bc | 219 | for (i = j = 0; pathname[i]; i++) |
ccc6cda3 | 220 | { |
4a2c75c6 CR |
221 | /* Fix for CTLESC at the end of the string? */ |
222 | if (pathname[i] == CTLESC && pathname[i+1] == '\0') | |
223 | { | |
224 | temp[j++] = pathname[i++]; | |
225 | break; | |
226 | } | |
5f0df7f9 CR |
227 | /* If we are parsing regexp, turn CTLESC CTLESC into CTLESC. It's not an |
228 | ERE special character, so we should just be able to pass it through. */ | |
6078dd9a | 229 | else if ((qflags & (QGLOB_REGEXP|QGLOB_CTLESC)) && pathname[i] == CTLESC && (pathname[i+1] == CTLESC || pathname[i+1] == CTLNUL)) |
5f0df7f9 CR |
230 | { |
231 | i++; | |
232 | temp[j++] = pathname[i]; | |
233 | continue; | |
234 | } | |
4a2c75c6 | 235 | else if (pathname[i] == CTLESC) |
28ef6c31 | 236 | { |
aa99ef52 | 237 | convert_to_backslash: |
8418224f CR |
238 | cc = pathname[i+1]; |
239 | ||
28ef6c31 JA |
240 | if ((qflags & QGLOB_FILENAME) && pathname[i+1] == '/') |
241 | continue; | |
8418224f | 242 | |
5f0df7f9 | 243 | /* What to do if preceding char is backslash? */ |
8418224f CR |
244 | |
245 | /* We don't have to backslash-quote non-special ERE characters if | |
246 | we're quoting a regexp. */ | |
247 | if (cc != CTLESC && (qflags & QGLOB_REGEXP) && ere_char (cc) == 0) | |
d3ad40de | 248 | continue; |
8418224f CR |
249 | |
250 | /* We don't have to backslash-quote non-special BRE characters if | |
251 | we're quoting a glob pattern. */ | |
252 | if (cc != CTLESC && (qflags & QGLOB_REGEXP) == 0 && glob_char_p (pathname+i+1) == 0) | |
253 | continue; | |
254 | ||
255 | /* If we're in a multibyte locale, don't bother quoting multibyte | |
256 | characters. It matters if we're going to convert NFD to NFC on | |
257 | macOS, and doesn't make a difference on other systems. */ | |
258 | if (cc != CTLESC && locale_utf8locale && UTF8_SINGLEBYTE (cc) == 0) | |
259 | continue; /* probably don't need to check for UTF-8 locale */ | |
260 | ||
cce855bc | 261 | temp[j++] = '\\'; |
7117c2d2 JA |
262 | i++; |
263 | if (pathname[i] == '\0') | |
264 | break; | |
28ef6c31 | 265 | } |
084c952b CR |
266 | else if ((qflags & QGLOB_REGEXP) && (i == 0 || pathname[i-1] != CTLESC) && pathname[i] == '[') /*]*/ |
267 | { | |
084c952b | 268 | temp[j++] = pathname[i++]; /* open bracket */ |
2171061f CR |
269 | savej = j; |
270 | savei = i; | |
084c952b | 271 | c = pathname[i++]; /* c == char after open bracket */ |
553a7d66 CR |
272 | if (c == '^') /* ignore pattern negation */ |
273 | { | |
274 | temp[j++] = c; | |
275 | c = pathname[i++]; | |
276 | } | |
277 | if (c == ']') /* ignore right bracket if first char */ | |
278 | { | |
279 | temp[j++] = c; | |
280 | c = pathname[i++]; | |
281 | } | |
084c952b CR |
282 | do |
283 | { | |
284 | if (c == 0) | |
285 | goto endpat; | |
286 | else if (c == CTLESC) | |
287 | { | |
288 | /* skip c, check for EOS, let assignment at end of loop */ | |
289 | /* pathname[i] == backslash-escaped character */ | |
290 | if (pathname[i] == 0) | |
291 | goto endpat; | |
292 | temp[j++] = pathname[i++]; | |
293 | } | |
294 | else if (c == '[' && pathname[i] == ':') | |
295 | { | |
296 | temp[j++] = c; | |
297 | temp[j++] = pathname[i++]; | |
298 | cclass = 1; | |
299 | } | |
300 | else if (cclass && c == ':' && pathname[i] == ']') | |
301 | { | |
302 | temp[j++] = c; | |
303 | temp[j++] = pathname[i++]; | |
304 | cclass = 0; | |
305 | } | |
306 | else if (c == '[' && pathname[i] == '=') | |
307 | { | |
308 | temp[j++] = c; | |
309 | temp[j++] = pathname[i++]; | |
310 | if (pathname[i] == ']') | |
311 | temp[j++] = pathname[i++]; /* right brack can be in equiv */ | |
312 | equiv = 1; | |
313 | } | |
314 | else if (equiv && c == '=' && pathname[i] == ']') | |
315 | { | |
316 | temp[j++] = c; | |
317 | temp[j++] = pathname[i++]; | |
318 | equiv = 0; | |
319 | } | |
320 | else if (c == '[' && pathname[i] == '.') | |
321 | { | |
322 | temp[j++] = c; | |
323 | temp[j++] = pathname[i++]; | |
324 | if (pathname[i] == ']') | |
325 | temp[j++] = pathname[i++]; /* right brack can be in collsym */ | |
326 | collsym = 1; | |
327 | } | |
328 | else if (collsym && c == '.' && pathname[i] == ']') | |
329 | { | |
330 | temp[j++] = c; | |
331 | temp[j++] = pathname[i++]; | |
332 | collsym = 0; | |
333 | } | |
334 | else | |
335 | temp[j++] = c; | |
336 | } | |
2171061f CR |
337 | while (((c = pathname[i++]) != ']') && c != 0); |
338 | ||
339 | /* If we don't find the closing bracket before we hit the end of | |
340 | the string, rescan string without treating it as a bracket | |
341 | expression (has implications for backslash and special ERE | |
342 | chars) */ | |
343 | if (c == 0) | |
344 | { | |
345 | i = savei - 1; /* -1 for autoincrement above */ | |
346 | j = savej; | |
347 | continue; | |
348 | } | |
349 | ||
084c952b CR |
350 | temp[j++] = c; /* closing right bracket */ |
351 | i--; /* increment will happen above in loop */ | |
352 | continue; /* skip double assignment below */ | |
353 | } | |
5f0df7f9 | 354 | else if (pathname[i] == '\\' && (qflags & QGLOB_REGEXP) == 0) |
6a2e7e1f | 355 | { |
4a2c75c6 | 356 | /* XXX - if not quoting regexp, use backslash as quote char. Should |
f7ec6b1a | 357 | We just pass it through without treating it as special? That is |
4a2c75c6 | 358 | what ksh93 seems to do. */ |
5f0df7f9 CR |
359 | |
360 | /* If we want to pass through backslash unaltered, comment out these | |
361 | lines. */ | |
362 | temp[j++] = '\\'; | |
363 | ||
364 | i++; | |
365 | if (pathname[i] == '\0') | |
366 | break; | |
1a5fa30b CR |
367 | /* If we are turning CTLESC CTLESC into CTLESC, we need to do that |
368 | even when the first CTLESC is preceded by a backslash. */ | |
6078dd9a CR |
369 | if ((qflags & QGLOB_CTLESC) && pathname[i] == CTLESC && (pathname[i+1] == CTLESC || pathname[i+1] == CTLNUL)) |
370 | i++; /* skip over the CTLESC */ | |
aa99ef52 CR |
371 | else if ((qflags & QGLOB_CTLESC) && pathname[i] == CTLESC) |
372 | /* A little more general: if there is an unquoted backslash in the | |
373 | pattern and we are handling quoted characters in the pattern, | |
374 | convert the CTLESC to backslash and add the next character on | |
375 | the theory that the backslash will quote the next character | |
376 | but it would be inconsistent not to replace the CTLESC with | |
377 | another backslash here. We can't tell at this point whether the | |
378 | CTLESC comes from a backslash or other form of quoting in the | |
379 | original pattern. */ | |
380 | goto convert_to_backslash; | |
6a2e7e1f | 381 | } |
5f0df7f9 CR |
382 | else if (pathname[i] == '\\' && (qflags & QGLOB_REGEXP)) |
383 | last_was_backslash = 1; | |
7117c2d2 | 384 | temp[j++] = pathname[i]; |
ccc6cda3 | 385 | } |
084c952b | 386 | endpat: |
cce855bc | 387 | temp[j] = '\0'; |
ccc6cda3 JA |
388 | |
389 | return (temp); | |
390 | } | |
391 | ||
392 | char * | |
a61ffa78 | 393 | quote_globbing_chars (const char *string) |
ccc6cda3 | 394 | { |
7117c2d2 | 395 | size_t slen; |
8f50a023 CR |
396 | char *temp, *t; |
397 | const char *s, *send; | |
7117c2d2 JA |
398 | DECLARE_MBSTATE; |
399 | ||
400 | slen = strlen (string); | |
401 | send = string + slen; | |
ccc6cda3 | 402 | |
7117c2d2 | 403 | temp = (char *)xmalloc (slen * 2 + 1); |
ccc6cda3 JA |
404 | for (t = temp, s = string; *s; ) |
405 | { | |
4ac1ff98 CR |
406 | if (glob_char_p (s)) |
407 | *t++ = '\\'; | |
7117c2d2 JA |
408 | |
409 | /* Copy a single (possibly multibyte) character from s to t, | |
084c952b | 410 | incrementing both. */ |
7117c2d2 | 411 | COPY_CHAR_P (t, s, send); |
ccc6cda3 JA |
412 | } |
413 | *t = '\0'; | |
414 | return temp; | |
415 | } | |
416 | ||
d06fefb2 CR |
417 | /* Call the glob library to do globbing on PATHNAME, honoring all the shell |
418 | variables that control globbing. */ | |
ccc6cda3 | 419 | char ** |
a61ffa78 | 420 | shell_glob_filename (const char *pathname, int qflags) |
ccc6cda3 | 421 | { |
ccc6cda3 | 422 | char *temp, **results; |
48492ffa | 423 | int gflags, quoted_pattern; |
ccc6cda3 JA |
424 | |
425 | noglob_dot_filenames = glob_dot_filenames == 0; | |
426 | ||
f7ec6b1a | 427 | temp = quote_string_for_globbing (pathname, QGLOB_FILENAME|qflags); |
5af34ee8 CR |
428 | gflags = glob_star ? GX_GLOBSTAR : 0; |
429 | results = glob_filename (temp, gflags); | |
ccc6cda3 JA |
430 | free (temp); |
431 | ||
432 | if (results && ((GLOB_FAILED (results)) == 0)) | |
433 | { | |
434 | if (should_ignore_glob_matches ()) | |
435 | ignore_glob_matches (results); | |
436 | if (results && results[0]) | |
d06fefb2 | 437 | sh_sortglob (results); |
ccc6cda3 JA |
438 | else |
439 | { | |
440 | FREE (results); | |
441 | results = (char **)&glob_error_return; | |
442 | } | |
443 | } | |
444 | ||
445 | return (results); | |
ccc6cda3 JA |
446 | } |
447 | ||
d06fefb2 CR |
448 | #if defined (READLINE) && defined (PROGRAMMABLE_COMPLETION) |
449 | char ** | |
450 | noquote_glob_filename (char *pathname) | |
451 | { | |
452 | char **results; | |
453 | int gflags; | |
454 | ||
455 | noglob_dot_filenames = glob_dot_filenames == 0; | |
456 | gflags = glob_star ? GX_GLOBSTAR : 0; | |
457 | ||
458 | results = glob_filename (pathname, gflags); | |
459 | ||
460 | if (results && GLOB_FAILED (results)) | |
461 | results = (char **)NULL; | |
462 | ||
463 | if (results && results[0]) | |
464 | sh_sortglob (results); | |
465 | ||
466 | return (results); | |
467 | } | |
468 | #endif | |
469 | ||
ccc6cda3 JA |
470 | /* Stuff for GLOBIGNORE. */ |
471 | ||
472 | static struct ignorevar globignore = | |
473 | { | |
474 | "GLOBIGNORE", | |
475 | (struct ign *)0, | |
476 | 0, | |
477 | (char *)0, | |
f73dda09 | 478 | (sh_iv_item_func_t *)0, |
ccc6cda3 JA |
479 | }; |
480 | ||
481 | /* Set up to ignore some glob matches because the value of GLOBIGNORE | |
482 | has changed. If GLOBIGNORE is being unset, we also need to disable | |
483 | the globbing of filenames beginning with a `.'. */ | |
484 | void | |
b2613ad1 | 485 | setup_glob_ignore (const char *name) |
ccc6cda3 JA |
486 | { |
487 | char *v; | |
488 | ||
489 | v = get_string_value (name); | |
490 | setup_ignore_patterns (&globignore); | |
491 | ||
492 | if (globignore.num_ignores) | |
493 | glob_dot_filenames = 1; | |
494 | else if (v == 0) | |
495 | glob_dot_filenames = 0; | |
496 | } | |
497 | ||
498 | int | |
a61ffa78 | 499 | should_ignore_glob_matches (void) |
ccc6cda3 JA |
500 | { |
501 | return globignore.num_ignores; | |
502 | } | |
503 | ||
504 | /* Return 0 if NAME matches a pattern in the globignore.ignores list. */ | |
505 | static int | |
a61ffa78 | 506 | glob_name_is_acceptable (const char *name) |
ccc6cda3 JA |
507 | { |
508 | struct ign *p; | |
d37a4722 | 509 | char *n; |
cce855bc | 510 | int flags; |
ccc6cda3 | 511 | |
d37a4722 CR |
512 | /* . and .. are never matched. We extend this to the terminal component of a |
513 | pathname. */ | |
514 | n = strrchr (name, '/'); | |
515 | if (n == 0 || n[1] == 0) | |
516 | n = (char *)name; | |
517 | else | |
518 | n++; | |
519 | ||
520 | if (n[0] == '.' && (n[1] == '\0' || (n[1] == '.' && n[2] == '\0'))) | |
ccc6cda3 JA |
521 | return (0); |
522 | ||
0a233f3e | 523 | flags = FNM_PATHNAME | FNMATCH_EXTFLAG | FNMATCH_NOCASEGLOB; |
ccc6cda3 JA |
524 | for (p = globignore.ignores; p->val; p++) |
525 | { | |
f73dda09 | 526 | if (strmatch (p->val, (char *)name, flags) != FNM_NOMATCH) |
28ef6c31 | 527 | return (0); |
ccc6cda3 JA |
528 | } |
529 | return (1); | |
530 | } | |
531 | ||
532 | /* Internal function to test whether filenames in NAMES should be | |
533 | ignored. NAME_FUNC is a pointer to a function to call with each | |
534 | name. It returns non-zero if the name is acceptable to the particular | |
535 | ignore function which called _ignore_names; zero if the name should | |
536 | be removed from NAMES. */ | |
537 | ||
538 | static void | |
a61ffa78 | 539 | ignore_globbed_names (char **names, sh_ignore_func_t *name_func) |
ccc6cda3 JA |
540 | { |
541 | char **newnames; | |
2e725f73 | 542 | size_t n, i; |
ccc6cda3 JA |
543 | |
544 | for (i = 0; names[i]; i++) | |
545 | ; | |
7117c2d2 | 546 | newnames = strvec_create (i + 1); |
ccc6cda3 JA |
547 | |
548 | for (n = i = 0; names[i]; i++) | |
549 | { | |
550 | if ((*name_func) (names[i])) | |
28ef6c31 | 551 | newnames[n++] = names[i]; |
ccc6cda3 JA |
552 | else |
553 | free (names[i]); | |
554 | } | |
555 | ||
556 | newnames[n] = (char *)NULL; | |
557 | ||
558 | if (n == 0) | |
559 | { | |
560 | names[0] = (char *)NULL; | |
561 | free (newnames); | |
562 | return; | |
563 | } | |
564 | ||
565 | /* Copy the acceptable names from NEWNAMES back to NAMES and set the | |
566 | new array end. */ | |
567 | for (n = 0; newnames[n]; n++) | |
568 | names[n] = newnames[n]; | |
569 | names[n] = (char *)NULL; | |
d166f048 | 570 | free (newnames); |
ccc6cda3 JA |
571 | } |
572 | ||
573 | void | |
a61ffa78 | 574 | ignore_glob_matches (char **names) |
ccc6cda3 JA |
575 | { |
576 | if (globignore.num_ignores == 0) | |
577 | return; | |
578 | ||
579 | ignore_globbed_names (names, glob_name_is_acceptable); | |
580 | } | |
581 | ||
5f8cde23 | 582 | static char * |
a61ffa78 | 583 | split_ignorespec (char *s, int *ip) |
5f8cde23 CR |
584 | { |
585 | char *t; | |
586 | int n, i; | |
587 | ||
588 | if (s == 0) | |
589 | return 0; | |
590 | ||
591 | i = *ip; | |
592 | if (s[i] == 0) | |
593 | return 0; | |
594 | ||
fbbc416f | 595 | n = skip_to_delim (s, i, ":", SD_NOJMP|SD_EXTGLOB|SD_GLOB); |
5f8cde23 CR |
596 | t = substring (s, i, n); |
597 | ||
598 | if (s[n] == ':') | |
599 | n++; | |
600 | *ip = n; | |
601 | return t; | |
602 | } | |
603 | ||
ccc6cda3 | 604 | void |
a61ffa78 | 605 | setup_ignore_patterns (struct ignorevar *ivp) |
ccc6cda3 JA |
606 | { |
607 | int numitems, maxitems, ptr; | |
608 | char *colon_bit, *this_ignoreval; | |
609 | struct ign *p; | |
610 | ||
611 | this_ignoreval = get_string_value (ivp->varname); | |
612 | ||
613 | /* If nothing has changed then just exit now. */ | |
614 | if ((this_ignoreval && ivp->last_ignoreval && STREQ (this_ignoreval, ivp->last_ignoreval)) || | |
615 | (!this_ignoreval && !ivp->last_ignoreval)) | |
616 | return; | |
617 | ||
618 | /* Oops. The ignore variable has changed. Re-parse it. */ | |
619 | ivp->num_ignores = 0; | |
620 | ||
621 | if (ivp->ignores) | |
622 | { | |
623 | for (p = ivp->ignores; p->val; p++) | |
624 | free(p->val); | |
625 | free (ivp->ignores); | |
626 | ivp->ignores = (struct ign *)NULL; | |
627 | } | |
628 | ||
629 | if (ivp->last_ignoreval) | |
630 | { | |
631 | free (ivp->last_ignoreval); | |
632 | ivp->last_ignoreval = (char *)NULL; | |
633 | } | |
634 | ||
635 | if (this_ignoreval == 0 || *this_ignoreval == '\0') | |
636 | return; | |
637 | ||
638 | ivp->last_ignoreval = savestring (this_ignoreval); | |
639 | ||
640 | numitems = maxitems = ptr = 0; | |
641 | ||
5f8cde23 | 642 | while (colon_bit = split_ignorespec (this_ignoreval, &ptr)) |
ccc6cda3 JA |
643 | { |
644 | if (numitems + 1 >= maxitems) | |
645 | { | |
646 | maxitems += 10; | |
647 | ivp->ignores = (struct ign *)xrealloc (ivp->ignores, maxitems * sizeof (struct ign)); | |
648 | } | |
649 | ivp->ignores[numitems].val = colon_bit; | |
650 | ivp->ignores[numitems].len = strlen (colon_bit); | |
651 | ivp->ignores[numitems].flags = 0; | |
652 | if (ivp->item_func) | |
28ef6c31 | 653 | (*ivp->item_func) (&ivp->ignores[numitems]); |
ccc6cda3 JA |
654 | numitems++; |
655 | } | |
656 | ivp->ignores[numitems].val = (char *)NULL; | |
657 | ivp->num_ignores = numitems; | |
658 | } | |
d06fefb2 CR |
659 | |
660 | /* Functions to handle sorting glob results in different ways depending on | |
661 | the value of the GLOBSORT variable. */ | |
662 | ||
23935dbe | 663 | static int glob_sorttype = SORT_NONE; |
d06fefb2 CR |
664 | |
665 | static STRING_INT_ALIST sorttypes[] = { | |
23935dbe CR |
666 | { "name", SORT_NAME }, |
667 | { "size", SORT_SIZE }, | |
668 | { "mtime", SORT_MTIME }, | |
669 | { "atime", SORT_ATIME }, | |
670 | { "ctime", SORT_CTIME }, | |
671 | { "blocks", SORT_BLOCKS }, | |
672 | { "nosort", SORT_NOSORT }, | |
d06fefb2 CR |
673 | { (char *)NULL, -1 } |
674 | }; | |
675 | ||
676 | /* A subset of the fields in the posix stat struct -- the ones we need -- | |
677 | normalized to using struct timespec. */ | |
678 | struct globstat { | |
679 | off_t size; | |
680 | struct timespec mtime; | |
681 | struct timespec atime; | |
682 | struct timespec ctime; | |
683 | int blocks; | |
684 | }; | |
685 | ||
686 | struct globsort_t { | |
687 | char *name; | |
688 | struct globstat st; | |
689 | }; | |
690 | ||
691 | static struct globstat glob_nullstat = { -1, { -1, -1 }, { -1, -1 }, { -1, -1 }, -1 }; | |
692 | ||
693 | static inline int | |
694 | glob_findtype (char *t) | |
695 | { | |
696 | int type; | |
697 | ||
698 | type = find_string_in_alist (t, sorttypes, 0); | |
23935dbe | 699 | return (type == -1 ? SORT_NONE : type); |
d06fefb2 CR |
700 | } |
701 | ||
702 | void | |
703 | setup_globsort (const char *varname) | |
704 | { | |
705 | char *val; | |
706 | int r, t; | |
707 | ||
23935dbe | 708 | glob_sorttype = SORT_NONE; |
d06fefb2 CR |
709 | val = get_string_value (varname); |
710 | if (val == 0 || *val == 0) | |
711 | return; | |
712 | ||
713 | t = r = 0; | |
714 | while (*val && whitespace (*val)) | |
715 | val++; /* why not? */ | |
716 | if (*val == '+') | |
717 | val++; /* allow leading `+' but ignore it */ | |
718 | else if (*val == '-') | |
719 | { | |
23935dbe | 720 | r = SORT_REVERSE; /* leading `-' reverses sort order */ |
d06fefb2 CR |
721 | val++; |
722 | } | |
723 | ||
724 | if (*val == 0) | |
725 | { | |
726 | /* A bare `+' means the default sort by name in ascending order; a bare | |
727 | `-' means to sort by name in descending order. */ | |
23935dbe | 728 | glob_sorttype = SORT_NAME | r; |
d06fefb2 CR |
729 | return; |
730 | } | |
731 | ||
732 | t = glob_findtype (val); | |
733 | /* any other value is equivalent to the historical behavior */ | |
23935dbe | 734 | glob_sorttype = (t == SORT_NONE) ? t : t | r; |
d06fefb2 CR |
735 | } |
736 | ||
737 | static int | |
738 | globsort_namecmp (char **s1, char **s2) | |
739 | { | |
23935dbe | 740 | return ((glob_sorttype < SORT_REVERSE) ? strvec_posixcmp (s1, s2) : strvec_posixcmp (s2, s1)); |
d06fefb2 CR |
741 | } |
742 | ||
743 | static int | |
744 | globsort_sizecmp (struct globsort_t *g1, struct globsort_t *g2) | |
745 | { | |
23935dbe | 746 | return ((glob_sorttype < SORT_REVERSE) ? g1->st.size - g2->st.size : g2->st.size - g1->st.size); |
d06fefb2 CR |
747 | } |
748 | ||
749 | static int | |
750 | globsort_timecmp (struct globsort_t *g1, struct globsort_t *g2) | |
751 | { | |
752 | int t; | |
753 | struct timespec t1, t2; | |
754 | ||
23935dbe CR |
755 | t = (glob_sorttype < SORT_REVERSE) ? glob_sorttype : glob_sorttype - SORT_REVERSE; |
756 | if (t == SORT_MTIME) | |
d06fefb2 CR |
757 | { |
758 | t1 = g1->st.mtime; | |
759 | t2 = g2->st.mtime; | |
760 | } | |
23935dbe | 761 | else if (t == SORT_ATIME) |
d06fefb2 CR |
762 | { |
763 | t1 = g1->st.atime; | |
764 | t2 = g2->st.atime; | |
765 | } | |
766 | else | |
767 | { | |
768 | t1 = g1->st.ctime; | |
769 | t2 = g2->st.ctime; | |
770 | } | |
771 | ||
23935dbe | 772 | return ((glob_sorttype < SORT_REVERSE) ? timespec_cmp (t1, t2) : timespec_cmp (t2, t1)); |
d06fefb2 CR |
773 | } |
774 | ||
775 | static int | |
776 | globsort_blockscmp (struct globsort_t *g1, struct globsort_t *g2) | |
777 | { | |
23935dbe | 778 | return ((glob_sorttype < SORT_REVERSE) ? g1->st.blocks - g2->st.blocks : g2->st.blocks - g1->st.blocks); |
d06fefb2 CR |
779 | } |
780 | ||
781 | static struct globsort_t * | |
782 | globsort_buildarray (char **array, size_t len) | |
783 | { | |
784 | struct globsort_t *ret; | |
785 | int i; | |
786 | struct stat st; | |
787 | ||
788 | ret = (struct globsort_t *)xmalloc (len * sizeof (struct globsort_t)); | |
789 | ||
790 | for (i = 0; i < len; i++) | |
791 | { | |
792 | ret[i].name = array[i]; | |
793 | if (stat (array[i], &st) != 0) | |
794 | ret[i].st = glob_nullstat; | |
795 | else | |
796 | { | |
797 | ret[i].st.size = st.st_size; | |
798 | ret[i].st.mtime = get_stat_mtime (&st); | |
799 | ret[i].st.atime = get_stat_atime (&st); | |
800 | ret[i].st.ctime = get_stat_ctime (&st); | |
801 | ret[i].st.blocks = st.st_blocks; | |
802 | } | |
803 | } | |
804 | ||
805 | return ret; | |
806 | } | |
807 | ||
808 | static inline void | |
809 | globsort_sortbyname (char **results) | |
810 | { | |
811 | qsort (results, strvec_len (results), sizeof (char *), (QSFUNC *)globsort_namecmp); | |
812 | } | |
813 | ||
814 | static void | |
815 | globsort_sortarray (struct globsort_t *garray, size_t len) | |
816 | { | |
817 | int t; | |
818 | QSFUNC *sortfunc; | |
819 | ||
23935dbe | 820 | t = (glob_sorttype < SORT_REVERSE) ? glob_sorttype : glob_sorttype - SORT_REVERSE; |
d06fefb2 CR |
821 | |
822 | switch (t) | |
823 | { | |
23935dbe | 824 | case SORT_SIZE: |
d06fefb2 CR |
825 | sortfunc = (QSFUNC *)globsort_sizecmp; |
826 | break; | |
23935dbe CR |
827 | case SORT_ATIME: |
828 | case SORT_MTIME: | |
829 | case SORT_CTIME: | |
d06fefb2 CR |
830 | sortfunc = (QSFUNC *)globsort_timecmp; |
831 | break; | |
23935dbe | 832 | case SORT_BLOCKS: |
d06fefb2 CR |
833 | sortfunc = (QSFUNC *)globsort_blockscmp; |
834 | break; | |
835 | default: | |
836 | internal_error (_("invalid glob sort type")); | |
837 | break; | |
838 | } | |
839 | ||
840 | qsort (garray, len, sizeof (struct globsort_t), sortfunc); | |
841 | } | |
842 | ||
843 | static void | |
844 | sh_sortglob (char **results) | |
845 | { | |
846 | size_t rlen; | |
847 | struct globsort_t *garray; | |
848 | ||
23935dbe CR |
849 | if (glob_sorttype == SORT_NOSORT || glob_sorttype == (SORT_NOSORT|SORT_REVERSE)) |
850 | return; | |
851 | ||
852 | if (glob_sorttype == SORT_NONE || glob_sorttype == SORT_NAME) | |
d06fefb2 | 853 | globsort_sortbyname (results); /* posix sort */ |
23935dbe | 854 | else if (glob_sorttype == (SORT_NAME|SORT_REVERSE)) |
d06fefb2 CR |
855 | globsort_sortbyname (results); /* posix sort reverse order */ |
856 | else | |
857 | { | |
858 | int i; | |
859 | ||
860 | rlen = strvec_len (results); | |
861 | /* populate an array of name/statinfo, sort it appropriately, copy the | |
862 | names from the sorted array back to RESULTS, and free the array */ | |
863 | garray = globsort_buildarray (results, rlen); | |
864 | globsort_sortarray (garray, rlen); | |
865 | for (i = 0; i < rlen; i++) | |
866 | results[i] = garray[i].name; | |
867 | free (garray); | |
868 | } | |
869 | } |