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