]> git.ipfire.org Git - thirdparty/bash.git/blame - pathexp.c
changes to quoting for some globbing characters; regularize error behavior of builtin...
[thirdparty/bash.git] / pathexp.c
CommitLineData
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
45static int glob_name_is_acceptable (const char *);
46static void ignore_globbed_names (char **, sh_ignore_func_t *);
47static char *split_ignorespec (char *, int *);
d06fefb2 48static void sh_sortglob (char **);
084c952b 49
7a8455e4 50#include <glob/glob.h>
ccc6cda3
JA
51
52/* Control whether * matches .files in globbing. */
53int glob_dot_filenames;
54
cce855bc 55/* Control whether the extended globbing features are enabled. */
691aebcb 56int extended_glob = EXTGLOB_DEFAULT;
cce855bc 57
4ac1ff98
CR
58/* Control enabling special handling of `**' */
59int 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 65int
a61ffa78 66unquoted_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. */
134static inline int
a61ffa78 135ere_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 159int
a61ffa78 160glob_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 201char *
a61ffa78 202quote_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 237convert_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 386endpat:
cce855bc 387 temp[j] = '\0';
ccc6cda3
JA
388
389 return (temp);
390}
391
392char *
a61ffa78 393quote_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 419char **
a61ffa78 420shell_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)
449char **
450noquote_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
472static 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 `.'. */
484void
b2613ad1 485setup_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
498int
a61ffa78 499should_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. */
505static int
a61ffa78 506glob_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
538static void
a61ffa78 539ignore_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
573void
a61ffa78 574ignore_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 582static char *
a61ffa78 583split_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 604void
a61ffa78 605setup_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 663static int glob_sorttype = SORT_NONE;
d06fefb2
CR
664
665static 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. */
678struct globstat {
679 off_t size;
680 struct timespec mtime;
681 struct timespec atime;
682 struct timespec ctime;
683 int blocks;
684};
685
686struct globsort_t {
687 char *name;
688 struct globstat st;
689};
690
691static struct globstat glob_nullstat = { -1, { -1, -1 }, { -1, -1 }, { -1, -1 }, -1 };
692
693static inline int
694glob_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
702void
703setup_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
737static int
738globsort_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
743static int
744globsort_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
749static int
750globsort_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
775static int
776globsort_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
781static struct globsort_t *
782globsort_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
808static inline void
809globsort_sortbyname (char **results)
810{
811 qsort (results, strvec_len (results), sizeof (char *), (QSFUNC *)globsort_namecmp);
812}
813
814static void
815globsort_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
843static void
844sh_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}