]> git.ipfire.org Git - thirdparty/bash.git/blob - pathexp.c
changes to quoting for some globbing characters; regularize error behavior of builtin...
[thirdparty/bash.git] / pathexp.c
1 /* pathexp.c -- The shell interface to the globbing library. */
2
3 /* Copyright (C) 1995-2023 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 /* 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:
114 if (*string++ == '\0')
115 return (0);
116 }
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
127 }
128
129 return (0);
130 }
131
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
135 ere_char (int c)
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
158 /* This is only used to determine whether to backslash-quote a character. */
159 int
160 glob_char_p (const char *s)
161 {
162 switch (*s)
163 {
164 #if defined (EXTENDED_GLOB)
165 case '+':
166 case '@':
167 return (s[1] == '('); /*)*/
168 case '(':
169 case '|':
170 case ')':
171 #endif
172 case '!':
173 case '^':
174 case '-':
175 case '.':
176 case ':':
177 case '=':
178 case '*':
179 case '[':
180 case ']':
181 case '?':
182 case '\\':
183 return 1;
184 }
185 return 0;
186 }
187
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
190 that the glob library recognizes. If flags includes QGLOB_CVTNULL,
191 we change quoted null strings (pathname[0] == CTLNUL) into empty
192 strings (pathname[0] == 0). If this is called after quote removal
193 is performed, (flags & QGLOB_CVTNULL) should be 0; if called when quote
194 removal has not been done (for example, before attempting to match a
195 pattern while executing a case statement), flags should include
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. */
201 char *
202 quote_string_for_globbing (const char *pathname, int qflags)
203 {
204 char *temp;
205 register int i, j;
206 int cclass, collsym, equiv, c, last_was_backslash;
207 int savei, savej;
208 unsigned char cc;
209
210 temp = (char *)xmalloc (2 * strlen (pathname) + 1);
211
212 if ((qflags & QGLOB_CVTNULL) && QUOTED_NULL (pathname))
213 {
214 temp[0] = '\0';
215 return temp;
216 }
217
218 cclass = collsym = equiv = last_was_backslash = 0;
219 for (i = j = 0; pathname[i]; i++)
220 {
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 }
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. */
229 else if ((qflags & (QGLOB_REGEXP|QGLOB_CTLESC)) && pathname[i] == CTLESC && (pathname[i+1] == CTLESC || pathname[i+1] == CTLNUL))
230 {
231 i++;
232 temp[j++] = pathname[i];
233 continue;
234 }
235 else if (pathname[i] == CTLESC)
236 {
237 convert_to_backslash:
238 cc = pathname[i+1];
239
240 if ((qflags & QGLOB_FILENAME) && pathname[i+1] == '/')
241 continue;
242
243 /* What to do if preceding char is backslash? */
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)
248 continue;
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
261 temp[j++] = '\\';
262 i++;
263 if (pathname[i] == '\0')
264 break;
265 }
266 else if ((qflags & QGLOB_REGEXP) && (i == 0 || pathname[i-1] != CTLESC) && pathname[i] == '[') /*]*/
267 {
268 temp[j++] = pathname[i++]; /* open bracket */
269 savej = j;
270 savei = i;
271 c = pathname[i++]; /* c == char after open bracket */
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 }
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 }
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
350 temp[j++] = c; /* closing right bracket */
351 i--; /* increment will happen above in loop */
352 continue; /* skip double assignment below */
353 }
354 else if (pathname[i] == '\\' && (qflags & QGLOB_REGEXP) == 0)
355 {
356 /* XXX - if not quoting regexp, use backslash as quote char. Should
357 We just pass it through without treating it as special? That is
358 what ksh93 seems to do. */
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;
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. */
369 if ((qflags & QGLOB_CTLESC) && pathname[i] == CTLESC && (pathname[i+1] == CTLESC || pathname[i+1] == CTLNUL))
370 i++; /* skip over the CTLESC */
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;
381 }
382 else if (pathname[i] == '\\' && (qflags & QGLOB_REGEXP))
383 last_was_backslash = 1;
384 temp[j++] = pathname[i];
385 }
386 endpat:
387 temp[j] = '\0';
388
389 return (temp);
390 }
391
392 char *
393 quote_globbing_chars (const char *string)
394 {
395 size_t slen;
396 char *temp, *t;
397 const char *s, *send;
398 DECLARE_MBSTATE;
399
400 slen = strlen (string);
401 send = string + slen;
402
403 temp = (char *)xmalloc (slen * 2 + 1);
404 for (t = temp, s = string; *s; )
405 {
406 if (glob_char_p (s))
407 *t++ = '\\';
408
409 /* Copy a single (possibly multibyte) character from s to t,
410 incrementing both. */
411 COPY_CHAR_P (t, s, send);
412 }
413 *t = '\0';
414 return temp;
415 }
416
417 /* Call the glob library to do globbing on PATHNAME, honoring all the shell
418 variables that control globbing. */
419 char **
420 shell_glob_filename (const char *pathname, int qflags)
421 {
422 char *temp, **results;
423 int gflags, quoted_pattern;
424
425 noglob_dot_filenames = glob_dot_filenames == 0;
426
427 temp = quote_string_for_globbing (pathname, QGLOB_FILENAME|qflags);
428 gflags = glob_star ? GX_GLOBSTAR : 0;
429 results = glob_filename (temp, gflags);
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])
437 sh_sortglob (results);
438 else
439 {
440 FREE (results);
441 results = (char **)&glob_error_return;
442 }
443 }
444
445 return (results);
446 }
447
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
470 /* Stuff for GLOBIGNORE. */
471
472 static struct ignorevar globignore =
473 {
474 "GLOBIGNORE",
475 (struct ign *)0,
476 0,
477 (char *)0,
478 (sh_iv_item_func_t *)0,
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
485 setup_glob_ignore (const char *name)
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
499 should_ignore_glob_matches (void)
500 {
501 return globignore.num_ignores;
502 }
503
504 /* Return 0 if NAME matches a pattern in the globignore.ignores list. */
505 static int
506 glob_name_is_acceptable (const char *name)
507 {
508 struct ign *p;
509 char *n;
510 int flags;
511
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')))
521 return (0);
522
523 flags = FNM_PATHNAME | FNMATCH_EXTFLAG | FNMATCH_NOCASEGLOB;
524 for (p = globignore.ignores; p->val; p++)
525 {
526 if (strmatch (p->val, (char *)name, flags) != FNM_NOMATCH)
527 return (0);
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
539 ignore_globbed_names (char **names, sh_ignore_func_t *name_func)
540 {
541 char **newnames;
542 size_t n, i;
543
544 for (i = 0; names[i]; i++)
545 ;
546 newnames = strvec_create (i + 1);
547
548 for (n = i = 0; names[i]; i++)
549 {
550 if ((*name_func) (names[i]))
551 newnames[n++] = names[i];
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;
570 free (newnames);
571 }
572
573 void
574 ignore_glob_matches (char **names)
575 {
576 if (globignore.num_ignores == 0)
577 return;
578
579 ignore_globbed_names (names, glob_name_is_acceptable);
580 }
581
582 static char *
583 split_ignorespec (char *s, int *ip)
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
595 n = skip_to_delim (s, i, ":", SD_NOJMP|SD_EXTGLOB|SD_GLOB);
596 t = substring (s, i, n);
597
598 if (s[n] == ':')
599 n++;
600 *ip = n;
601 return t;
602 }
603
604 void
605 setup_ignore_patterns (struct ignorevar *ivp)
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
642 while (colon_bit = split_ignorespec (this_ignoreval, &ptr))
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)
653 (*ivp->item_func) (&ivp->ignores[numitems]);
654 numitems++;
655 }
656 ivp->ignores[numitems].val = (char *)NULL;
657 ivp->num_ignores = numitems;
658 }
659
660 /* Functions to handle sorting glob results in different ways depending on
661 the value of the GLOBSORT variable. */
662
663 static int glob_sorttype = SORT_NONE;
664
665 static STRING_INT_ALIST sorttypes[] = {
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 },
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);
699 return (type == -1 ? SORT_NONE : type);
700 }
701
702 void
703 setup_globsort (const char *varname)
704 {
705 char *val;
706 int r, t;
707
708 glob_sorttype = SORT_NONE;
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 {
720 r = SORT_REVERSE; /* leading `-' reverses sort order */
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. */
728 glob_sorttype = SORT_NAME | r;
729 return;
730 }
731
732 t = glob_findtype (val);
733 /* any other value is equivalent to the historical behavior */
734 glob_sorttype = (t == SORT_NONE) ? t : t | r;
735 }
736
737 static int
738 globsort_namecmp (char **s1, char **s2)
739 {
740 return ((glob_sorttype < SORT_REVERSE) ? strvec_posixcmp (s1, s2) : strvec_posixcmp (s2, s1));
741 }
742
743 static int
744 globsort_sizecmp (struct globsort_t *g1, struct globsort_t *g2)
745 {
746 return ((glob_sorttype < SORT_REVERSE) ? g1->st.size - g2->st.size : g2->st.size - g1->st.size);
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
755 t = (glob_sorttype < SORT_REVERSE) ? glob_sorttype : glob_sorttype - SORT_REVERSE;
756 if (t == SORT_MTIME)
757 {
758 t1 = g1->st.mtime;
759 t2 = g2->st.mtime;
760 }
761 else if (t == SORT_ATIME)
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
772 return ((glob_sorttype < SORT_REVERSE) ? timespec_cmp (t1, t2) : timespec_cmp (t2, t1));
773 }
774
775 static int
776 globsort_blockscmp (struct globsort_t *g1, struct globsort_t *g2)
777 {
778 return ((glob_sorttype < SORT_REVERSE) ? g1->st.blocks - g2->st.blocks : g2->st.blocks - g1->st.blocks);
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
820 t = (glob_sorttype < SORT_REVERSE) ? glob_sorttype : glob_sorttype - SORT_REVERSE;
821
822 switch (t)
823 {
824 case SORT_SIZE:
825 sortfunc = (QSFUNC *)globsort_sizecmp;
826 break;
827 case SORT_ATIME:
828 case SORT_MTIME:
829 case SORT_CTIME:
830 sortfunc = (QSFUNC *)globsort_timecmp;
831 break;
832 case SORT_BLOCKS:
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
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)
853 globsort_sortbyname (results); /* posix sort */
854 else if (glob_sorttype == (SORT_NAME|SORT_REVERSE))
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 }