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