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
x_init()40 x_init()
41 {
42 /* set to -2 to force initial binding */
43 edchars.erase = edchars.kill = edchars.intr = edchars.quit
44 = edchars.eof = -2;
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
x_sigwinch(sig)81 x_sigwinch(sig)
82 int sig;
83 {
84 got_sigwinch = 1;
85 return RETSIGVAL;
86 }
87
88 static void
check_sigwinch(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
x_read(buf,len)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
x_getc()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
x_flush()175 x_flush()
176 {
177 shf_flush(shl_out);
178 }
179
180 void
x_putc(c)181 x_putc(c)
182 int c;
183 {
184 shf_putc(c, shl_out);
185 }
186
187 void
x_puts(s)188 x_puts(s)
189 const char *s;
190 {
191 while (*s != 0)
192 shf_putc(*s++, shl_out);
193 }
194
195 bool_t
x_mode(onoff)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 #ifdef __CYGWIN__
288 if (edchars.eof == '\0')
289 edchars.eof = '\4';
290 #endif /* __CYGWIN__ */
291
292 /* Convert unset values to internal `unset' value */
293 if (edchars.erase == vdisable_c)
294 edchars.erase = -1;
295 if (edchars.kill == vdisable_c)
296 edchars.kill = -1;
297 if (edchars.intr == vdisable_c)
298 edchars.intr = -1;
299 if (edchars.quit == vdisable_c)
300 edchars.quit = -1;
301 if (edchars.eof == vdisable_c)
302 edchars.eof = -1;
303 if (edchars.werase == vdisable_c)
304 edchars.werase = -1;
305 if (memcmp(&edchars, &oldchars, sizeof(edchars)) != 0) {
306 #ifdef EMACS
307 x_emacs_keys(&edchars);
308 #endif
309 }
310 } else {
311 /* TF_WAIT doesn't seem to be necessary when leaving xmode */
312 set_tty(tty_fd, &tty_state, TF_NONE);
313 }
314
315 return prev;
316 }
317
318 /* NAME:
319 * promptlen - calculate the length of PS1 etc.
320 *
321 * DESCRIPTION:
322 * This function is based on a fix from guy@demon.co.uk
323 * It fixes a bug in that if PS1 contains '!', the length
324 * given by strlen() is probably wrong.
325 *
326 * RETURN VALUE:
327 * length
328 */
329 int
promptlen(cp,spp)330 promptlen(cp, spp)
331 const char *cp;
332 const char **spp;
333 {
334 int count = 0;
335 const char *sp = cp;
336 char delimiter = 0;
337 int indelimit = 0;
338
339 /* Undocumented AT&T ksh feature:
340 * If the second char in the prompt string is \r then the first char
341 * is taken to be a non-printing delimiter and any chars between two
342 * instances of the delimiter are not considered to be part of the
343 * prompt length
344 */
345 if (*cp && cp[1] == '\r') {
346 delimiter = *cp;
347 indelimit = !indelimit;
348 cp += 2;
349 }
350
351 for (; *cp; cp++) {
352 if (indelimit && *cp != delimiter)
353 ;
354 else if (*cp == '\n' || *cp == '\r') {
355 count = 0;
356 sp = cp + 1;
357 } else if (*cp == '\t') {
358 count = (count | 7) + 1;
359 } else if (*cp == '\b') {
360 if (count > 0)
361 count--;
362 } else if (*cp == delimiter)
363 indelimit = !indelimit;
364 else
365 count++;
366 }
367 if (spp)
368 *spp = sp;
369 return count;
370 }
371
372 void
set_editmode(ed)373 set_editmode(ed)
374 const char *ed;
375 {
376 static const enum sh_flag edit_flags[] = {
377 #ifdef EMACS
378 FEMACS, FGMACS,
379 #endif
380 #ifdef VI
381 FVI,
382 #endif
383 };
384 char *rcp;
385 int i;
386
387 if ((rcp = ksh_strrchr_dirsep(ed)))
388 ed = ++rcp;
389 for (i = 0; i < NELEM(edit_flags); i++)
390 if (strstr(ed, options[(int) edit_flags[i]].name)) {
391 change_flag(edit_flags[i], OF_SPECIAL, 1);
392 return;
393 }
394 }
395
396 /* ------------------------------------------------------------------------- */
397 /* Misc common code for vi/emacs */
398
399 /* Handle the commenting/uncommenting of a line.
400 * Returns:
401 * 1 if a carriage return is indicated (comment added)
402 * 0 if no return (comment removed)
403 * -1 if there is an error (not enough room for comment chars)
404 * If successful, *lenp contains the new length. Note: cursor should be
405 * moved to the start of the line after (un)commenting.
406 */
407 int
x_do_comment(buf,bsize,lenp)408 x_do_comment(buf, bsize, lenp)
409 char *buf;
410 int bsize;
411 int *lenp;
412 {
413 int i, j;
414 int len = *lenp;
415
416 if (len == 0)
417 return 1; /* somewhat arbitrary - it's what at&t ksh does */
418
419 /* Already commented? */
420 if (buf[0] == '#') {
421 int saw_nl = 0;
422
423 for (j = 0, i = 1; i < len; i++) {
424 if (!saw_nl || buf[i] != '#')
425 buf[j++] = buf[i];
426 saw_nl = buf[i] == '\n';
427 }
428 *lenp = j;
429 return 0;
430 } else {
431 int n = 1;
432
433 /* See if there's room for the #'s - 1 per \n */
434 for (i = 0; i < len; i++)
435 if (buf[i] == '\n')
436 n++;
437 if (len + n >= bsize)
438 return -1;
439 /* Now add them... */
440 for (i = len, j = len + n; --i >= 0; ) {
441 if (buf[i] == '\n')
442 buf[--j] = '#';
443 buf[--j] = buf[i];
444 }
445 buf[0] = '#';
446 *lenp += n;
447 return 1;
448 }
449 }
450
451 /* ------------------------------------------------------------------------- */
452 /* Common file/command completion code for vi/emacs */
453
454
455 static char *add_glob ARGS((const char *str, int slen));
456 static void glob_table ARGS((const char *pat, XPtrV *wp, struct table *tp));
457 static void glob_path ARGS((int flags, const char *pat, XPtrV *wp,
458 const char *path));
459
460 #if 0 /* not used... */
461 int x_complete_word ARGS((const char *str, int slen, int is_command,
462 int *multiple, char **ret));
463 int
464 x_complete_word(str, slen, is_command, nwordsp, ret)
465 const char *str;
466 int slen;
467 int is_command;
468 int *nwordsp;
469 char **ret;
470 {
471 int nwords;
472 int prefix_len;
473 char **words;
474
475 nwords = (is_command ? x_command_glob : x_file_glob)(XCF_FULLPATH,
476 str, slen, &words);
477 *nwordsp = nwords;
478 if (nwords == 0) {
479 *ret = (char *) 0;
480 return -1;
481 }
482
483 prefix_len = x_longest_prefix(nwords, words);
484 *ret = str_nsave(words[0], prefix_len, ATEMP);
485 x_free_words(nwords, words);
486 return prefix_len;
487 }
488 #endif /* 0 */
489
490 void
x_print_expansions(nwords,words,is_command)491 x_print_expansions(nwords, words, is_command)
492 int nwords;
493 char *const *words;
494 int is_command;
495 {
496 int use_copy = 0;
497 int prefix_len;
498 XPtrV l;
499
500 /* Check if all matches are in the same directory (in this
501 * case, we want to omitt the directory name)
502 */
503 if (!is_command
504 && (prefix_len = x_longest_prefix(nwords, words)) > 0)
505 {
506 int i;
507
508 /* Special case for 1 match (prefix is whole word) */
509 if (nwords == 1)
510 prefix_len = x_basename(words[0], (char *) 0);
511 /* Any (non-trailing) slashes in non-common word suffixes? */
512 for (i = 0; i < nwords; i++)
513 if (x_basename(words[i] + prefix_len, (char *) 0)
514 > prefix_len)
515 break;
516 /* All in same directory? */
517 if (i == nwords) {
518 while (prefix_len > 0
519 && !ISDIRSEP(words[0][prefix_len - 1]))
520 prefix_len--;
521 use_copy = 1;
522 XPinit(l, nwords + 1);
523 for (i = 0; i < nwords; i++)
524 XPput(l, words[i] + prefix_len);
525 XPput(l, (char *) 0);
526 }
527 }
528
529 /*
530 * Enumerate expansions
531 */
532 x_putc('\r');
533 x_putc('\n');
534 pr_menu(use_copy ? (char **) XPptrv(l) : words);
535
536 if (use_copy)
537 XPfree(l); /* not x_free_words() */
538 }
539
540 /*
541 * Do file globbing:
542 * - appends * to (copy of) str if no globbing chars found
543 * - does expansion, checks for no match, etc.
544 * - sets *wordsp to array of matching strings
545 * - returns number of matching strings
546 */
547 static int
x_file_glob(flags,str,slen,wordsp)548 x_file_glob(flags, str, slen, wordsp)
549 int flags;
550 const char *str;
551 int slen;
552 char ***wordsp;
553 {
554 char *toglob;
555 char **words;
556 int nwords;
557 XPtrV w;
558 struct source *s, *sold;
559
560 if (slen < 0)
561 return 0;
562
563 toglob = add_glob(str, slen);
564
565 /*
566 * Convert "foo*" (toglob) to an array of strings (words)
567 */
568 sold = source;
569 s = pushs(SWSTR, ATEMP);
570 s->start = s->str = toglob;
571 source = s;
572 if (yylex(ONEWORD) != LWORD) {
573 source = sold;
574 internal_errorf(0, "fileglob: substitute error");
575 return 0;
576 }
577 source = sold;
578 XPinit(w, 32);
579 expand(yylval.cp, &w, DOGLOB|DOTILDE|DOMARKDIRS);
580 XPput(w, NULL);
581 words = (char **) XPclose(w);
582
583 for (nwords = 0; words[nwords]; nwords++)
584 ;
585 if (nwords == 1) {
586 struct stat statb;
587
588 /* Check if globbing failed (returned glob pattern),
589 * but be careful (E.g. toglob == "ab*" when the file
590 * "ab*" exists is not an error).
591 * Also, check for empty result - happens if we tried
592 * to glob something which evaluated to an empty
593 * string (e.g., "$FOO" when there is no FOO, etc).
594 */
595 if ((strcmp(words[0], toglob) == 0
596 && stat(words[0], &statb) < 0)
597 || words[0][0] == '\0')
598 {
599 x_free_words(nwords, words);
600 nwords = 0;
601 }
602 }
603 afree(toglob, ATEMP);
604
605 *wordsp = nwords ? words : (char **) 0;
606
607 return nwords;
608 }
609
610 /* Data structure used in x_command_glob() */
611 struct path_order_info {
612 char *word;
613 int base;
614 int path_order;
615 };
616
617 /* Compare routine used in x_command_glob() */
618 static int
path_order_cmp(aa,bb)619 path_order_cmp(aa, bb)
620 const void *aa;
621 const void *bb;
622 {
623 const struct path_order_info *a = (const struct path_order_info *) aa;
624 const struct path_order_info *b = (const struct path_order_info *) bb;
625 int t;
626
627 t = FILECMP(a->word + a->base, b->word + b->base);
628 return t ? t : a->path_order - b->path_order;
629 }
630
631 static int
x_command_glob(flags,str,slen,wordsp)632 x_command_glob(flags, str, slen, wordsp)
633 int flags;
634 const char *str;
635 int slen;
636 char ***wordsp;
637 {
638 char *toglob;
639 char *pat;
640 char *fpath;
641 int nwords;
642 XPtrV w;
643 struct block *l;
644
645 if (slen < 0)
646 return 0;
647
648 toglob = add_glob(str, slen);
649
650 /* Convert "foo*" (toglob) to a pattern for future use */
651 pat = evalstr(toglob, DOPAT|DOTILDE);
652 afree(toglob, ATEMP);
653
654 XPinit(w, 32);
655
656 glob_table(pat, &w, &keywords);
657 glob_table(pat, &w, &aliases);
658 glob_table(pat, &w, &builtins);
659 for (l = e->loc; l; l = l->next)
660 glob_table(pat, &w, &l->funs);
661
662 glob_path(flags, pat, &w, path);
663 if ((fpath = str_val(global("FPATH"))) != null)
664 glob_path(flags, pat, &w, fpath);
665
666 nwords = XPsize(w);
667
668 if (!nwords) {
669 *wordsp = (char **) 0;
670 XPfree(w);
671 return 0;
672 }
673
674 /* Sort entries */
675 if (flags & XCF_FULLPATH) {
676 /* Sort by basename, then path order */
677 struct path_order_info *info;
678 struct path_order_info *last_info = 0;
679 char **words = (char **) XPptrv(w);
680 int path_order = 0;
681 int i;
682
683 info = (struct path_order_info *)
684 alloc(sizeof(struct path_order_info) * nwords, ATEMP);
685 for (i = 0; i < nwords; i++) {
686 info[i].word = words[i];
687 info[i].base = x_basename(words[i], (char *) 0);
688 if (!last_info || info[i].base != last_info->base
689 || FILENCMP(words[i],
690 last_info->word, info[i].base) != 0)
691 {
692 last_info = &info[i];
693 path_order++;
694 }
695 info[i].path_order = path_order;
696 }
697 qsort(info, nwords, sizeof(struct path_order_info),
698 path_order_cmp);
699 for (i = 0; i < nwords; i++)
700 words[i] = info[i].word;
701 afree((void *) info, ATEMP);
702 } else {
703 /* Sort and remove duplicate entries */
704 char **words = (char **) XPptrv(w);
705 int i, j;
706
707 qsortp(XPptrv(w), (size_t) nwords, xstrcmp);
708
709 for (i = j = 0; i < nwords - 1; i++) {
710 if (strcmp(words[i], words[i + 1]))
711 words[j++] = words[i];
712 else
713 afree(words[i], ATEMP);
714 }
715 words[j++] = words[i];
716 nwords = j;
717 w.cur = (void **) &words[j];
718 }
719
720 XPput(w, NULL);
721 *wordsp = (char **) XPclose(w);
722
723 return nwords;
724 }
725
726 #define IS_WORDC(c) !(ctype(c, C_LEX1) || (c) == '\'' || (c) == '"')
727
728 static int
x_locate_word(buf,buflen,pos,startp,is_commandp)729 x_locate_word(buf, buflen, pos, startp, is_commandp)
730 const char *buf;
731 int buflen;
732 int pos;
733 int *startp;
734 int *is_commandp;
735 {
736 int p;
737 int start, end;
738
739 /* Bad call? Probably should report error */
740 if (pos < 0 || pos > buflen) {
741 *startp = pos;
742 *is_commandp = 0;
743 return 0;
744 }
745 /* The case where pos == buflen happens to take care of itself... */
746
747 start = pos;
748 /* Keep going backwards to start of word (has effect of allowing
749 * one blank after the end of a word)
750 */
751 for (; start > 0 && IS_WORDC(buf[start - 1]); start--)
752 ;
753 /* Go forwards to end of word */
754 for (end = start; end < buflen && IS_WORDC(buf[end]); end++)
755 ;
756
757 if (is_commandp) {
758 int iscmd;
759
760 /* Figure out if this is a command */
761 for (p = start - 1; p >= 0 && isspace(buf[p]); p--)
762 ;
763 iscmd = p < 0 || strchr(";|&()", buf[p]);
764 if (iscmd) {
765 /* If command has a /, path, etc. is not searched;
766 * only current directory is searched, which is just
767 * like file globbing.
768 */
769 for (p = start; p < end; p++)
770 if (ISDIRSEP(buf[p]))
771 break;
772 iscmd = p == end;
773 }
774 *is_commandp = iscmd;
775 }
776
777 *startp = start;
778
779 return end - start;
780 }
781
782 int
x_cf_glob(flags,buf,buflen,pos,startp,endp,wordsp,is_commandp)783 x_cf_glob(flags, buf, buflen, pos, startp, endp, wordsp, is_commandp)
784 int flags;
785 const char *buf;
786 int buflen;
787 int pos;
788 int *startp;
789 int *endp;
790 char ***wordsp;
791 int *is_commandp;
792 {
793 int len;
794 int nwords;
795 char **words;
796 int is_command;
797
798 len = x_locate_word(buf, buflen, pos, startp, &is_command);
799 if (!(flags & XCF_COMMAND))
800 is_command = 0;
801 /* Don't do command globing on zero length strings - it takes too
802 * long and isn't very useful. File globs are more likely to be
803 * useful, so allow these.
804 */
805 if (len == 0 && is_command)
806 return 0;
807
808 nwords = (is_command ? x_command_glob : x_file_glob)(flags,
809 buf + *startp, len, &words);
810 if (nwords == 0) {
811 *wordsp = (char **) 0;
812 return 0;
813 }
814
815 if (is_commandp)
816 *is_commandp = is_command;
817 *wordsp = words;
818 *endp = *startp + len;
819
820 return nwords;
821 }
822
823 /* Given a string, copy it and possibly add a '*' to the end. The
824 * new string is returned.
825 */
826 static char *
add_glob(str,slen)827 add_glob(str, slen)
828 const char *str;
829 int slen;
830 {
831 char *toglob;
832 char *s;
833 bool_t saw_slash = FALSE;
834
835 if (slen < 0)
836 return (char *) 0;
837
838 toglob = str_nsave(str, slen + 1, ATEMP); /* + 1 for "*" */
839 toglob[slen] = '\0';
840
841 /*
842 * If the pathname contains a wildcard (an unquoted '*',
843 * '?', or '[') or parameter expansion ('$'), or a ~username
844 * with no trailing slash, then it is globbed based on that
845 * value (i.e., without the appended '*').
846 */
847 for (s = toglob; *s; s++) {
848 if (*s == '\\' && s[1])
849 s++;
850 else if (*s == '*' || *s == '[' || *s == '?'
851 || (s[1] == '(' /*)*/ && strchr("*+?@!", *s)))
852 break;
853 else if (ISDIRSEP(*s))
854 saw_slash = TRUE;
855 }
856 if (!*s && (*toglob != '~' || saw_slash)) {
857 toglob[slen] = '*';
858 toglob[slen + 1] = '\0';
859 }
860
861 return toglob;
862 }
863
864 /*
865 * Find longest common prefix
866 */
867 int
x_longest_prefix(nwords,words)868 x_longest_prefix(nwords, words)
869 int nwords;
870 char *const *words;
871 {
872 int i, j;
873 int prefix_len;
874 char *p;
875
876 if (nwords <= 0)
877 return 0;
878
879 prefix_len = strlen(words[0]);
880 for (i = 1; i < nwords; i++)
881 for (j = 0, p = words[i]; j < prefix_len; j++)
882 if (FILECHCONV(p[j]) != FILECHCONV(words[0][j])) {
883 prefix_len = j;
884 break;
885 }
886 return prefix_len;
887 }
888
889 void
x_free_words(nwords,words)890 x_free_words(nwords, words)
891 int nwords;
892 char **words;
893 {
894 int i;
895
896 for (i = 0; i < nwords; i++)
897 if (words[i])
898 afree(words[i], ATEMP);
899 afree(words, ATEMP);
900 }
901
902 /* Return the offset of the basename of string s (which ends at se - need not
903 * be null terminated). Trailing slashes are ignored. If s is just a slash,
904 * then the offset is 0 (actually, length - 1).
905 * s Return
906 * /etc 1
907 * /etc/ 1
908 * /etc// 1
909 * /etc/fo 5
910 * foo 0
911 * /// 2
912 * 0
913 */
914 int
x_basename(s,se)915 x_basename(s, se)
916 const char *s;
917 const char *se;
918 {
919 const char *p;
920
921 if (se == (char *) 0)
922 se = s + strlen(s);
923 if (s == se)
924 return 0;
925
926 /* Skip trailing slashes */
927 for (p = se - 1; p > s && ISDIRSEP(*p); p--)
928 ;
929 for (; p > s && !ISDIRSEP(*p); p--)
930 ;
931 if (ISDIRSEP(*p) && p + 1 < se)
932 p++;
933
934 return p - s;
935 }
936
937 /*
938 * Apply pattern matching to a table: all table entries that match a pattern
939 * are added to wp.
940 */
941 static void
glob_table(pat,wp,tp)942 glob_table(pat, wp, tp)
943 const char *pat;
944 XPtrV *wp;
945 struct table *tp;
946 {
947 struct tstate ts;
948 struct tbl *te;
949
950 for (twalk(&ts, tp); (te = tnext(&ts)); ) {
951 if (gmatch(te->name, pat, FALSE))
952 XPput(*wp, str_save(te->name, ATEMP));
953 }
954 }
955
956 static void
glob_path(flags,pat,wp,path)957 glob_path(flags, pat, wp, path)
958 int flags;
959 const char *pat;
960 XPtrV *wp;
961 const char *path;
962 {
963 const char *sp, *p;
964 char *xp;
965 int pathlen;
966 int patlen;
967 int oldsize, newsize, i, j;
968 char **words;
969 XString xs;
970
971 patlen = strlen(pat) + 1;
972 sp = path;
973 Xinit(xs, xp, patlen + 128, ATEMP);
974 while (sp) {
975 xp = Xstring(xs, xp);
976 if (!(p = strchr(sp, PATHSEP)))
977 p = sp + strlen(sp);
978 pathlen = p - sp;
979 if (pathlen) {
980 /* Copy sp into xp, stuffing any MAGIC characters
981 * on the way
982 */
983 const char *s = sp;
984
985 XcheckN(xs, xp, pathlen * 2);
986 while (s < p) {
987 if (ISMAGIC(*s))
988 *xp++ = MAGIC;
989 *xp++ = *s++;
990 }
991 *xp++ = DIRSEP;
992 pathlen++;
993 }
994 sp = p;
995 XcheckN(xs, xp, patlen);
996 memcpy(xp, pat, patlen);
997
998 oldsize = XPsize(*wp);
999 glob_str(Xstring(xs, xp), wp, 0);
1000 newsize = XPsize(*wp);
1001
1002 /* Check that each match is executable... */
1003 words = (char **) XPptrv(*wp);
1004 for (i = j = oldsize; i < newsize; i++) {
1005 if (search_access(words[i], X_OK, (int *) 0) >= 0) {
1006 words[j] = words[i];
1007 if (!(flags & XCF_FULLPATH))
1008 memmove(words[j], words[j] + pathlen,
1009 strlen(words[j] + pathlen) + 1);
1010 j++;
1011 } else
1012 afree(words[i], ATEMP);
1013 }
1014 wp->cur = (void **) &words[j];
1015
1016 if (!*sp++)
1017 break;
1018 }
1019 Xfree(xs, xp);
1020 }
1021
1022 #endif /* EDIT */
1023