edit.c revision 1.1.1.2 1 /*
2 * Command line editing - common code
3 *
4 */
5
6 #include "config.h"
7 #ifdef EDIT
8
9 #include "sh.h"
10 #include "tty.h"
11 #define EXTERN
12 #include "edit.h"
13 #undef EXTERN
14 #ifdef OS_SCO /* SCO Unix 3.2v4.1 */
15 # include <sys/stream.h> /* needed for <sys/ptem.h> */
16 # include <sys/ptem.h> /* needed for struct winsize */
17 #endif /* OS_SCO */
18 #include <ctype.h>
19 #include "ksh_stat.h"
20
21
22 #if defined(TIOCGWINSZ)
23 static RETSIGTYPE x_sigwinch ARGS((int sig));
24 static int got_sigwinch;
25 static void check_sigwinch ARGS((void));
26 #endif /* TIOCGWINSZ */
27
28 static int x_file_glob ARGS((int flags, const char *str, int slen,
29 char ***wordsp));
30 static int x_command_glob ARGS((int flags, const char *str, int slen,
31 char ***wordsp));
32 static int x_locate_word ARGS((const char *buf, int buflen, int pos,
33 int *startp, int *is_command));
34
35 static char vdisable_c;
36
37
38 /* Called from main */
39 void
40 x_init()
41 {
42 /* set to -1 to force initial binding */
43 edchars.erase = edchars.kill = edchars.intr = edchars.quit
44 = edchars.eof = -1;
45 /* default value for deficient systems */
46 edchars.werase = 027; /* ^W */
47
48 #ifdef TIOCGWINSZ
49 # ifdef SIGWINCH
50 if (setsig(&sigtraps[SIGWINCH], x_sigwinch, SS_RESTORE_ORIG|SS_SHTRAP))
51 sigtraps[SIGWINCH].flags |= TF_SHELL_USES;
52 # endif /* SIGWINCH */
53 got_sigwinch = 1; /* force initial check */
54 check_sigwinch();
55 #endif /* TIOCGWINSZ */
56
57 #ifdef EMACS
58 x_init_emacs();
59 #endif /* EMACS */
60
61 /* Bizarreness to figure out how to disable
62 * a struct termios.c_cc[] char
63 */
64 #ifdef _POSIX_VDISABLE
65 if (_POSIX_VDISABLE >= 0)
66 vdisable_c = (char) _POSIX_VDISABLE;
67 else
68 /* `feature not available' */
69 vdisable_c = (char) 0377;
70 #else
71 # if defined(HAVE_PATHCONF) && defined(_PC_VDISABLE)
72 vdisable_c = fpathconf(tty_fd, _PC_VDISABLE);
73 # else
74 vdisable_c = (char) 0377; /* default to old BSD value */
75 # endif
76 #endif /* _POSIX_VDISABLE */
77 }
78
79 #if defined(TIOCGWINSZ)
80 static RETSIGTYPE
81 x_sigwinch(sig)
82 int sig;
83 {
84 got_sigwinch = 1;
85 return RETSIGVAL;
86 }
87
88 static void
89 check_sigwinch ARGS((void))
90 {
91 if (got_sigwinch) {
92 struct winsize ws;
93
94 got_sigwinch = 0;
95 if (procpid == kshpid && ioctl(tty_fd, TIOCGWINSZ, &ws) >= 0) {
96 struct tbl *vp;
97
98 /* Do NOT export COLUMNS/LINES. Many applications
99 * check COLUMNS/LINES before checking ws.ws_col/row,
100 * so if the app is started with C/L in the environ
101 * and the window is then resized, the app won't
102 * see the change cause the environ doesn't change.
103 */
104 if (ws.ws_col) {
105 x_cols = ws.ws_col < MIN_COLS ? MIN_COLS
106 : ws.ws_col;
107
108 if ((vp = typeset("COLUMNS", 0, 0, 0, 0)))
109 setint(vp, (long) ws.ws_col);
110 }
111 if (ws.ws_row
112 && (vp = typeset("LINES", 0, 0, 0, 0)))
113 setint(vp, (long) ws.ws_row);
114 }
115 }
116 }
117 #endif /* TIOCGWINSZ */
118
119 /*
120 * read an edited command line
121 */
122 int
123 x_read(buf, len)
124 char *buf;
125 size_t len;
126 {
127 int i;
128
129 #if defined(TIOCGWINSZ)
130 if (got_sigwinch)
131 check_sigwinch();
132 #endif /* TIOCGWINSZ */
133
134 x_mode(TRUE);
135 #ifdef EMACS
136 if (Flag(FEMACS) || Flag(FGMACS))
137 i = x_emacs(buf, len);
138 else
139 #endif
140 #ifdef VI
141 if (Flag(FVI))
142 i = x_vi(buf, len);
143 else
144 #endif
145 i = -1; /* internal error */
146 x_mode(FALSE);
147 return i;
148 }
149
150 /* tty I/O */
151
152 int
153 x_getc()
154 {
155 #ifdef OS2
156 unsigned char c = _read_kbd(0, 1, 0);
157 return c == 0 ? 0xE0 : c;
158 #else /* OS2 */
159 char c;
160 int n;
161
162 while ((n = blocking_read(0, &c, 1)) < 0 && errno == EINTR)
163 if (trap) {
164 x_mode(FALSE);
165 runtraps(0);
166 x_mode(TRUE);
167 }
168 if (n != 1)
169 return -1;
170 return (int) (unsigned char) c;
171 #endif /* OS2 */
172 }
173
174 void
175 x_flush()
176 {
177 shf_flush(shl_out);
178 }
179
180 void
181 x_putc(c)
182 int c;
183 {
184 shf_putc(c, shl_out);
185 }
186
187 void
188 x_puts(s)
189 const char *s;
190 {
191 while (*s != 0)
192 shf_putc(*s++, shl_out);
193 }
194
195 bool_t
196 x_mode(onoff)
197 bool_t onoff;
198 {
199 static bool_t x_cur_mode;
200 bool_t prev;
201
202 if (x_cur_mode == onoff)
203 return x_cur_mode;
204 prev = x_cur_mode;
205 x_cur_mode = onoff;
206
207 if (onoff) {
208 TTY_state cb;
209 X_chars oldchars;
210
211 oldchars = edchars;
212 cb = tty_state;
213
214 #if defined(HAVE_TERMIOS_H) || defined(HAVE_TERMIO_H)
215 edchars.erase = cb.c_cc[VERASE];
216 edchars.kill = cb.c_cc[VKILL];
217 edchars.intr = cb.c_cc[VINTR];
218 edchars.quit = cb.c_cc[VQUIT];
219 edchars.eof = cb.c_cc[VEOF];
220 # ifdef VWERASE
221 edchars.werase = cb.c_cc[VWERASE];
222 # endif
223 # ifdef _CRAY2 /* brain-damaged terminal handler */
224 cb.c_lflag &= ~(ICANON|ECHO);
225 /* rely on print routine to map '\n' to CR,LF */
226 # else
227 cb.c_iflag &= ~(INLCR|ICRNL);
228 # ifdef _BSD_SYSV /* need to force CBREAK instead of RAW (need CRMOD on output) */
229 cb.c_lflag &= ~(ICANON|ECHO);
230 # else
231 # ifdef SWTCH /* need CBREAK to handle swtch char */
232 cb.c_lflag &= ~(ICANON|ECHO);
233 cb.c_lflag |= ISIG;
234 cb.c_cc[VINTR] = vdisable_c;
235 cb.c_cc[VQUIT] = vdisable_c;
236 # else
237 cb.c_lflag &= ~(ISIG|ICANON|ECHO);
238 # endif
239 # endif
240 # ifdef VLNEXT
241 /* osf/1 processes lnext when ~icanon */
242 cb.c_cc[VLNEXT] = vdisable_c;
243 # endif /* VLNEXT */
244 # ifdef VDISCARD
245 /* sunos 4.1.x & osf/1 processes discard(flush) when ~icanon */
246 cb.c_cc[VDISCARD] = vdisable_c;
247 # endif /* VDISCARD */
248 cb.c_cc[VTIME] = 0;
249 cb.c_cc[VMIN] = 1;
250 # endif /* _CRAY2 */
251 #else
252 /* Assume BSD tty stuff. */
253 edchars.erase = cb.sgttyb.sg_erase;
254 edchars.kill = cb.sgttyb.sg_kill;
255 cb.sgttyb.sg_flags &= ~ECHO;
256 cb.sgttyb.sg_flags |= CBREAK;
257 # ifdef TIOCGATC
258 edchars.intr = cb.lchars.tc_intrc;
259 edchars.quit = cb.lchars.tc_quitc;
260 edchars.eof = cb.lchars.tc_eofc;
261 edchars.werase = cb.lchars.tc_werasc;
262 cb.lchars.tc_suspc = -1;
263 cb.lchars.tc_dsuspc = -1;
264 cb.lchars.tc_lnextc = -1;
265 cb.lchars.tc_statc = -1;
266 cb.lchars.tc_intrc = -1;
267 cb.lchars.tc_quitc = -1;
268 cb.lchars.tc_rprntc = -1;
269 # else
270 edchars.intr = cb.tchars.t_intrc;
271 edchars.quit = cb.tchars.t_quitc;
272 edchars.eof = cb.tchars.t_eofc;
273 cb.tchars.t_intrc = -1;
274 cb.tchars.t_quitc = -1;
275 # ifdef TIOCGLTC
276 edchars.werase = cb.ltchars.t_werasc;
277 cb.ltchars.t_suspc = -1;
278 cb.ltchars.t_dsuspc = -1;
279 cb.ltchars.t_lnextc = -1;
280 cb.ltchars.t_rprntc = -1;
281 # endif
282 # endif /* TIOCGATC */
283 #endif /* HAVE_TERMIOS_H || HAVE_TERMIO_H */
284
285 set_tty(tty_fd, &cb, TF_WAIT);
286
287 if (memcmp(&edchars, &oldchars, sizeof(edchars)) != 0) {
288 #ifdef EMACS
289 x_emacs_keys(&edchars);
290 #endif
291 }
292 } else
293 /* TF_WAIT doesn't seem to be necessary when leaving xmode */
294 set_tty(tty_fd, &tty_state, TF_NONE);
295
296 return prev;
297 }
298
299 /* NAME:
300 * promptlen - calculate the length of PS1 etc.
301 *
302 * DESCRIPTION:
303 * This function is based on a fix from guy (at) demon.co.uk
304 * It fixes a bug in that if PS1 contains '!', the length
305 * given by strlen() is probably wrong.
306 *
307 * RETURN VALUE:
308 * length
309 */
310 int
311 promptlen(cp, spp)
312 const char *cp;
313 const char **spp;
314 {
315 int count = 0;
316 const char *sp = cp;
317 char delimiter = 0;
318 int indelimit = 0;
319
320 /* Undocumented AT&T ksh feature:
321 * If the second char in the prompt string is \r then the first char
322 * is taken to be a non-printing delimiter and any chars between two
323 * instances of the delimiter are not considered to be part of the
324 * prompt length
325 */
326 if (*cp && cp[1] == '\r') {
327 delimiter = *cp;
328 cp += 2;
329 }
330
331 for (; *cp; cp++) {
332 if (indelimit && *cp != delimiter)
333 ;
334 else if (*cp == '\n' || *cp == '\r') {
335 count = 0;
336 sp = cp + 1;
337 } else if (*cp == '\t') {
338 count = (count | 7) + 1;
339 } else if (*cp == '\b') {
340 if (count > 0)
341 count--;
342 } else if (*cp == delimiter)
343 indelimit = !indelimit;
344 else
345 count++;
346 }
347 if (spp)
348 *spp = sp;
349 return count;
350 }
351
352 void
353 set_editmode(ed)
354 const char *ed;
355 {
356 static const enum sh_flag edit_flags[] = {
357 #ifdef EMACS
358 FEMACS, FGMACS,
359 #endif
360 #ifdef VI
361 FVI,
362 #endif
363 };
364 char *rcp;
365 int i;
366
367 if ((rcp = ksh_strrchr_dirsep(ed)))
368 ed = ++rcp;
369 for (i = 0; i < NELEM(edit_flags); i++)
370 if (strstr(ed, options[(int) edit_flags[i]].name)) {
371 change_flag(edit_flags[i], OF_SPECIAL, 1);
372 return;
373 }
374 }
375
376 /* ------------------------------------------------------------------------- */
377 /* Misc common code for vi/emacs */
378
379 /* Handle the commenting/uncommenting of a line.
380 * Returns:
381 * 1 if a carriage return is indicated (comment added)
382 * 0 if no return (comment removed)
383 * -1 if there is an error (not enough room for comment chars)
384 * If successful, *lenp contains the new length. Note: cursor should be
385 * moved to the start of the line after (un)commenting.
386 */
387 int
388 x_do_comment(buf, bsize, lenp)
389 char *buf;
390 int bsize;
391 int *lenp;
392 {
393 int i, j;
394 int len = *lenp;
395
396 if (len == 0)
397 return 1; /* somewhat arbitrary - it's what at&t ksh does */
398
399 /* Already commented? */
400 if (buf[0] == '#') {
401 int saw_nl = 0;
402
403 for (j = 0, i = 1; i < len; i++) {
404 if (!saw_nl || buf[i] != '#')
405 buf[j++] = buf[i];
406 saw_nl = buf[i] == '\n';
407 }
408 *lenp = j;
409 return 0;
410 } else {
411 int n = 1;
412
413 /* See if there's room for the #'s - 1 per \n */
414 for (i = 0; i < len; i++)
415 if (buf[i] == '\n')
416 n++;
417 if (len + n >= bsize)
418 return -1;
419 /* Now add them... */
420 for (i = len, j = len + n; --i >= 0; ) {
421 if (buf[i] == '\n')
422 buf[--j] = '#';
423 buf[--j] = buf[i];
424 }
425 buf[0] = '#';
426 *lenp += n;
427 return 1;
428 }
429 }
430
431 /* ------------------------------------------------------------------------- */
432 /* Common file/command completion code for vi/emacs */
433
434
435 static char *add_glob ARGS((const char *str, int slen));
436 static void glob_table ARGS((const char *pat, XPtrV *wp, struct table *tp));
437 static void glob_path ARGS((int flags, const char *pat, XPtrV *wp,
438 const char *path));
439
440 #if 0 /* not used... */
441 int x_complete_word ARGS((const char *str, int slen, int is_command,
442 int *multiple, char **ret));
443 int
444 x_complete_word(str, slen, is_command, nwordsp, ret)
445 const char *str;
446 int slen;
447 int is_command;
448 int *nwordsp;
449 char **ret;
450 {
451 int nwords;
452 int prefix_len;
453 char **words;
454
455 nwords = (is_command ? x_command_glob : x_file_glob)(XCF_FULLPATH,
456 str, slen, &words);
457 *nwordsp = nwords;
458 if (nwords == 0) {
459 *ret = (char *) 0;
460 return -1;
461 }
462
463 prefix_len = x_longest_prefix(nwords, words);
464 *ret = str_nsave(words[0], prefix_len, ATEMP);
465 x_free_words(nwords, words);
466 return prefix_len;
467 }
468 #endif /* 0 */
469
470 void
471 x_print_expansions(nwords, words, is_command)
472 int nwords;
473 char *const *words;
474 int is_command;
475 {
476 int use_copy = 0;
477 int prefix_len;
478 XPtrV l;
479
480 /* Check if all matches are in the same directory (in this
481 * case, we want to omitt the directory name)
482 */
483 if (!is_command
484 && (prefix_len = x_longest_prefix(nwords, words)) > 0)
485 {
486 int i;
487
488 /* Special case for 1 match (prefix is whole word) */
489 if (nwords == 1)
490 prefix_len = x_basename(words[0], (char *) 0);
491 /* Any (non-trailing) slashes in non-common word suffixes? */
492 for (i = 0; i < nwords; i++)
493 if (x_basename(words[i] + prefix_len, (char *) 0)
494 > prefix_len)
495 break;
496 /* All in same directory? */
497 if (i == nwords) {
498 while (prefix_len > 0
499 && !ISDIRSEP(words[0][prefix_len - 1]))
500 prefix_len--;
501 use_copy = 1;
502 XPinit(l, nwords + 1);
503 for (i = 0; i < nwords; i++)
504 XPput(l, words[i] + prefix_len);
505 XPput(l, (char *) 0);
506 }
507 }
508
509 /*
510 * Enumerate expansions
511 */
512 x_putc('\r');
513 x_putc('\n');
514 pr_menu(use_copy ? (char **) XPptrv(l) : words);
515
516 if (use_copy)
517 XPfree(l); /* not x_free_words() */
518 }
519
520 /*
521 * Do file globbing:
522 * - appends * to (copy of) str if no globbing chars found
523 * - does expansion, checks for no match, etc.
524 * - sets *wordsp to array of matching strings
525 * - returns number of matching strings
526 */
527 static int
528 x_file_glob(flags, str, slen, wordsp)
529 int flags;
530 const char *str;
531 int slen;
532 char ***wordsp;
533 {
534 char *toglob;
535 char **words;
536 int nwords;
537 XPtrV w;
538 struct source *s, *sold;
539
540 if (slen < 0)
541 return 0;
542
543 toglob = add_glob(str, slen);
544
545 /*
546 * Convert "foo*" (toglob) to an array of strings (words)
547 */
548 sold = source;
549 s = pushs(SWSTR, ATEMP);
550 s->start = s->str = toglob;
551 source = s;
552 if (yylex(ONEWORD) != LWORD) {
553 source = sold;
554 internal_errorf(0, "fileglob: substitute error");
555 return 0;
556 }
557 source = sold;
558 XPinit(w, 32);
559 expand(yylval.cp, &w, DOGLOB|DOTILDE|DOMARKDIRS);
560 XPput(w, NULL);
561 words = (char **) XPclose(w);
562
563 for (nwords = 0; words[nwords]; nwords++)
564 ;
565 if (nwords == 1) {
566 struct stat statb;
567
568 /* Check if globbing failed (returned glob pattern),
569 * but be careful (E.g. toglob == "ab*" when the file
570 * "ab*" exists is not an error).
571 * Also, check for empty result - happens if we tried
572 * to glob something which evaluated to an empty
573 * string (e.g., "$FOO" when there is no FOO, etc).
574 */
575 if ((strcmp(words[0], toglob) == 0
576 && stat(words[0], &statb) < 0)
577 || words[0][0] == '\0')
578 {
579 x_free_words(nwords, words);
580 nwords = 0;
581 }
582 }
583 afree(toglob, ATEMP);
584
585 *wordsp = nwords ? words : (char **) 0;
586
587 return nwords;
588 }
589
590 /* Data structure used in x_command_glob() */
591 struct path_order_info {
592 char *word;
593 int base;
594 int path_order;
595 };
596
597 /* Compare routine used in x_command_glob() */
598 static int
599 path_order_cmp(aa, bb)
600 const void *aa;
601 const void *bb;
602 {
603 const struct path_order_info *a = (const struct path_order_info *) aa;
604 const struct path_order_info *b = (const struct path_order_info *) bb;
605 int t;
606
607 t = FILECMP(a->word + a->base, b->word + b->base);
608 return t ? t : a->path_order - b->path_order;
609 }
610
611 static int
612 x_command_glob(flags, str, slen, wordsp)
613 int flags;
614 const char *str;
615 int slen;
616 char ***wordsp;
617 {
618 char *toglob;
619 char *pat;
620 char *fpath;
621 int nwords;
622 XPtrV w;
623 struct block *l;
624
625 if (slen < 0)
626 return 0;
627
628 toglob = add_glob(str, slen);
629
630 /* Convert "foo*" (toglob) to a pattern for future use */
631 pat = evalstr(toglob, DOPAT|DOTILDE);
632 afree(toglob, ATEMP);
633
634 XPinit(w, 32);
635
636 glob_table(pat, &w, &keywords);
637 glob_table(pat, &w, &aliases);
638 glob_table(pat, &w, &builtins);
639 for (l = e->loc; l; l = l->next)
640 glob_table(pat, &w, &l->funs);
641
642 glob_path(flags, pat, &w, path);
643 if ((fpath = str_val(global("FPATH"))) != null)
644 glob_path(flags, pat, &w, fpath);
645
646 nwords = XPsize(w);
647
648 if (!nwords) {
649 *wordsp = (char **) 0;
650 XPfree(w);
651 return 0;
652 }
653
654 /* Sort entries */
655 if (flags & XCF_FULLPATH) {
656 /* Sort by basename, then path order */
657 struct path_order_info *info;
658 struct path_order_info *last_info = 0;
659 char **words = (char **) XPptrv(w);
660 int path_order = 0;
661 int i;
662
663 info = (struct path_order_info *)
664 alloc(sizeof(struct path_order_info) * nwords, ATEMP);
665 for (i = 0; i < nwords; i++) {
666 info[i].word = words[i];
667 info[i].base = x_basename(words[i], (char *) 0);
668 if (!last_info || info[i].base != last_info->base
669 || FILENCMP(words[i],
670 last_info->word, info[i].base) != 0)
671 {
672 last_info = &info[i];
673 path_order++;
674 }
675 info[i].path_order = path_order;
676 }
677 qsort(info, nwords, sizeof(struct path_order_info),
678 path_order_cmp);
679 for (i = 0; i < nwords; i++)
680 words[i] = info[i].word;
681 afree((void *) info, ATEMP);
682 } else {
683 /* Sort and remove duplicate entries */
684 char **words = (char **) XPptrv(w);
685 int i, j;
686
687 qsortp(XPptrv(w), (size_t) nwords, xstrcmp);
688
689 for (i = j = 0; i < nwords - 1; i++) {
690 if (strcmp(words[i], words[i + 1]))
691 words[j++] = words[i];
692 else
693 afree(words[i], ATEMP);
694 }
695 words[j++] = words[i];
696 nwords = j;
697 w.cur = (void **) &words[j];
698 }
699
700 XPput(w, NULL);
701 *wordsp = (char **) XPclose(w);
702
703 return nwords;
704 }
705
706 #define IS_WORDC(c) !isspace(c)
707
708 static int
709 x_locate_word(buf, buflen, pos, startp, is_commandp)
710 const char *buf;
711 int buflen;
712 int pos;
713 int *startp;
714 int *is_commandp;
715 {
716 int p;
717 int start, end;
718
719 /* Bad call? Probably should report error */
720 if (pos < 0 || pos > buflen) {
721 *startp = pos;
722 *is_commandp = 0;
723 return 0;
724 }
725
726 if (pos == buflen) {
727 if (pos == 0) { /* empty buffer? */
728 *startp = pos;
729 *is_commandp = 1;
730 return 0;
731 }
732 pos--;
733 }
734
735 start = pos;
736 /* Keep going backwards to start of word (has effect of allowing
737 * one blank after the end of a word)
738 */
739 for (; start > 0 && IS_WORDC(buf[start - 1]); start--)
740 ;
741 /* Go forwards to end of word */
742 for (end = start; end < buflen && IS_WORDC(buf[end]); end++)
743 ;
744
745 if (is_commandp) {
746 int iscmd;
747
748 /* Figure out if this is a command */
749 for (p = start - 1; p >= 0 && isspace(buf[p]); p--)
750 ;
751 iscmd = p < 0 || strchr(";|&()", buf[p]);
752 if (iscmd) {
753 /* If command has a /, path, etc. is not searched;
754 * only current directory is searched, which is just
755 * like file globbing.
756 */
757 for (p = start; p < end; p++)
758 if (ISDIRSEP(buf[p]))
759 break;
760 iscmd = p == end;
761 }
762 *is_commandp = iscmd;
763 }
764
765 *startp = start;
766
767 return end - start;
768 }
769
770 int
771 x_cf_glob(flags, buf, buflen, pos, startp, endp, wordsp, is_commandp)
772 int flags;
773 const char *buf;
774 int buflen;
775 int pos;
776 int *startp;
777 int *endp;
778 char ***wordsp;
779 int *is_commandp;
780 {
781 int len;
782 int nwords;
783 char **words;
784 int is_command;
785
786 len = x_locate_word(buf, buflen, pos, startp, &is_command);
787 if (!(flags & XCF_COMMAND))
788 is_command = 0;
789 /* Don't do command globing on zero length strings - it takes too
790 * long and isn't very useful. File globs are more likely to be
791 * useful, so allow these.
792 */
793 if (len == 0 && is_command)
794 return 0;
795
796 nwords = (is_command ? x_command_glob : x_file_glob)(flags,
797 buf + *startp, len, &words);
798 if (nwords == 0) {
799 *wordsp = (char **) 0;
800 return 0;
801 }
802
803 if (is_commandp)
804 *is_commandp = is_command;
805 *wordsp = words;
806 *endp = *startp + len;
807
808 return nwords;
809 }
810
811 /* Given a string, copy it and possibly add a '*' to the end. The
812 * new string is returned.
813 */
814 static char *
815 add_glob(str, slen)
816 const char *str;
817 int slen;
818 {
819 char *toglob;
820 char *s;
821 bool_t saw_slash = FALSE;
822
823 if (slen < 0)
824 return (char *) 0;
825
826 toglob = str_nsave(str, slen + 1, ATEMP); /* + 1 for "*" */
827 toglob[slen] = '\0';
828
829 /*
830 * If the pathname contains a wildcard (an unquoted '*',
831 * '?', or '[') or parameter expansion ('$'), or a ~username
832 * with no trailing slash, then it is globbed based on that
833 * value (i.e., without the appended '*').
834 */
835 for (s = toglob; *s; s++) {
836 if (*s == '\\' && s[1])
837 s++;
838 else if (*s == '*' || *s == '[' || *s == '?' || *s == '$'
839 || (s[1] == '(' /*)*/ && strchr("*+?@!", *s)))
840 break;
841 else if (ISDIRSEP(*s))
842 saw_slash = TRUE;
843 }
844 if (!*s && (*toglob != '~' || saw_slash)) {
845 toglob[slen] = '*';
846 toglob[slen + 1] = '\0';
847 }
848
849 return toglob;
850 }
851
852 /*
853 * Find longest common prefix
854 */
855 int
856 x_longest_prefix(nwords, words)
857 int nwords;
858 char *const *words;
859 {
860 int i, j;
861 int prefix_len;
862 char *p;
863
864 if (nwords <= 0)
865 return 0;
866
867 prefix_len = strlen(words[0]);
868 for (i = 1; i < nwords; i++)
869 for (j = 0, p = words[i]; j < prefix_len; j++)
870 if (FILECHCONV(p[j]) != FILECHCONV(words[0][j])) {
871 prefix_len = j;
872 break;
873 }
874 return prefix_len;
875 }
876
877 void
878 x_free_words(nwords, words)
879 int nwords;
880 char **words;
881 {
882 int i;
883
884 for (i = 0; i < nwords; i++)
885 if (words[i])
886 afree(words[i], ATEMP);
887 afree(words, ATEMP);
888 }
889
890 /* Return the offset of the basename of string s (which ends at se - need not
891 * be null terminated). Trailing slashes are ignored. If s is just a slash,
892 * then the offset is 0 (actually, length - 1).
893 * s Return
894 * /etc 1
895 * /etc/ 1
896 * /etc// 1
897 * /etc/fo 5
898 * foo 0
899 * /// 2
900 * 0
901 */
902 int
903 x_basename(s, se)
904 const char *s;
905 const char *se;
906 {
907 const char *p;
908
909 if (se == (char *) 0)
910 se = s + strlen(s);
911 if (s == se)
912 return 0;
913
914 /* Skip trailing slashes */
915 for (p = se - 1; p > s && ISDIRSEP(*p); p--)
916 ;
917 for (; p > s && !ISDIRSEP(*p); p--)
918 ;
919 if (ISDIRSEP(*p) && p + 1 < se)
920 p++;
921
922 return p - s;
923 }
924
925 /*
926 * Apply pattern matching to a table: all table entries that match a pattern
927 * are added to wp.
928 */
929 static void
930 glob_table(pat, wp, tp)
931 const char *pat;
932 XPtrV *wp;
933 struct table *tp;
934 {
935 struct tstate ts;
936 struct tbl *te;
937
938 for (twalk(&ts, tp); (te = tnext(&ts)); ) {
939 if (gmatch(te->name, pat, FALSE))
940 XPput(*wp, str_save(te->name, ATEMP));
941 }
942 }
943
944 static void
945 glob_path(flags, pat, wp, path)
946 int flags;
947 const char *pat;
948 XPtrV *wp;
949 const char *path;
950 {
951 const char *sp, *p;
952 char *xp;
953 int pathlen;
954 int patlen;
955 int oldsize, newsize, i, j;
956 char **words;
957 XString xs;
958
959 patlen = strlen(pat) + 1;
960 sp = path;
961 Xinit(xs, xp, patlen + 128, ATEMP);
962 while (sp) {
963 xp = Xstring(xs, xp);
964 if (!(p = strchr(sp, PATHSEP)))
965 p = sp + strlen(sp);
966 pathlen = p - sp;
967 if (pathlen) {
968 /* Copy sp into xp, stuffing any MAGIC characters
969 * on the way
970 */
971 const char *s = sp;
972
973 XcheckN(xs, xp, pathlen * 2);
974 while (s < p) {
975 if (ISMAGIC(*s))
976 *xp++ = MAGIC;
977 *xp++ = *s++;
978 }
979 *xp++ = DIRSEP;
980 pathlen++;
981 }
982 sp = p;
983 XcheckN(xs, xp, patlen);
984 memcpy(xp, pat, patlen);
985
986 oldsize = XPsize(*wp);
987 glob_str(Xstring(xs, xp), wp, 0);
988 newsize = XPsize(*wp);
989
990 /* Check that each match is executable... */
991 words = (char **) XPptrv(*wp);
992 for (i = j = oldsize; i < newsize; i++) {
993 if (search_access(words[i], X_OK, (int *) 0) >= 0) {
994 words[j] = words[i];
995 if (!(flags & XCF_FULLPATH))
996 memmove(words[j], words[j] + pathlen,
997 strlen(words[j] + pathlen) + 1);
998 j++;
999 } else
1000 afree(words[i], ATEMP);
1001 }
1002 wp->cur = (void **) &words[j];
1003
1004 if (!*sp++)
1005 break;
1006 }
1007 Xfree(xs, xp);
1008 }
1009
1010 #endif /* EDIT */
1011