xref: /openbsd-src/bin/ksh/edit.c (revision 195d2f38bcccf58b1ed096bdcb09ec4e2cd3e582)
1 /*	$OpenBSD: edit.c,v 1.55 2016/09/04 17:21:44 nicm 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, 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, 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, *last;
587 	int cmdlen, n;
588 	char *name, *s;
589 	size_t slen;
590 	struct tbl *v, *vp;
591 
592 	*nwords = 0;
593 	*words = NULL;
594 
595 	/* Walk back to find start of command. */
596 	if (want == buf)
597 		return 0;
598 	for (cmd = want; cmd > buf; cmd--) {
599 		if (strchr(";|&()`", cmd[-1]) != NULL)
600 			break;
601 	}
602 	while (cmd < want && isspace((u_char)*cmd))
603 		cmd++;
604 	cmdlen = 0;
605 	while (cmd + cmdlen < want && !isspace((u_char)cmd[cmdlen]))
606 		cmdlen++;
607 
608 	/* Take a stab at argument count from here. */
609 	n = 1;
610 	for (cp = cmd + cmdlen + 1; cp < want; cp++) {
611 		if (!isspace((u_char)cp[-1]) && isspace((u_char)*cp))
612 			n++;
613 	}
614 
615 	/* Try to find the array. */
616 	if (asprintf(&name, "complete_%.*s_%d", cmdlen, cmd, n) < 0)
617 		internal_errorf(1, "unable to allocate memory");
618 	v = global(name);
619 	free(name);
620 	if (~v->flag & (ISSET|ARRAY)) {
621 		if (asprintf(&name, "complete_%.*s", cmdlen, cmd) < 0)
622 			internal_errorf(1, "unable to allocate memory");
623 		v = global(name);
624 		free(name);
625 		if (~v->flag & (ISSET|ARRAY))
626 			return 0;
627 	}
628 
629 	/* Walk the array and build words list. */
630 	for (vp = v; vp; vp = vp->u.array) {
631 		if (~vp->flag & ISSET)
632 			continue;
633 
634 		s = str_val(vp);
635 		slen = strlen(s);
636 
637 		if (slen < wantlen)
638 			continue;
639 		if (slen > wantlen)
640 			slen = wantlen;
641 		if (slen != 0 && strncmp(s, want, slen) != 0)
642 			continue;
643 
644 		*words = areallocarray(*words, (*nwords) + 2, sizeof **words,
645 		    ATEMP);
646 		(*words)[(*nwords)++] = str_save(s, ATEMP);
647 	}
648 	if (*nwords != 0)
649 		(*words)[*nwords] = NULL;
650 
651 	return *nwords != 0;
652 }
653 
654 int
655 x_cf_glob(int flags, const char *buf, int buflen, int pos, int *startp,
656     int *endp, char ***wordsp, int *is_commandp)
657 {
658 	int len;
659 	int nwords;
660 	char **words = NULL;
661 	int is_command;
662 
663 	len = x_locate_word(buf, buflen, pos, startp, &is_command);
664 	if (!(flags & XCF_COMMAND))
665 		is_command = 0;
666 	/* Don't do command globing on zero length strings - it takes too
667 	 * long and isn't very useful.  File globs are more likely to be
668 	 * useful, so allow these.
669 	 */
670 	if (len == 0 && is_command)
671 		return 0;
672 
673 	if (is_command)
674 		nwords = x_command_glob(flags, buf + *startp, len, &words);
675 	else if (!x_try_array(buf, buflen, buf + *startp, len, &nwords, &words))
676 		nwords = x_file_glob(flags, buf + *startp, len, &words);
677 	if (nwords == 0) {
678 		*wordsp = NULL;
679 		return 0;
680 	}
681 
682 	if (is_commandp)
683 		*is_commandp = is_command;
684 	*wordsp = words;
685 	*endp = *startp + len;
686 
687 	return nwords;
688 }
689 
690 /* Given a string, copy it and possibly add a '*' to the end.  The
691  * new string is returned.
692  */
693 static char *
694 add_glob(const char *str, int slen)
695 {
696 	char *toglob;
697 	char *s;
698 	bool saw_slash = false;
699 
700 	if (slen < 0)
701 		return NULL;
702 
703 	toglob = str_nsave(str, slen + 1, ATEMP); /* + 1 for "*" */
704 	toglob[slen] = '\0';
705 
706 	/*
707 	 * If the pathname contains a wildcard (an unquoted '*',
708 	 * '?', or '[') or parameter expansion ('$'), or a ~username
709 	 * with no trailing slash, then it is globbed based on that
710 	 * value (i.e., without the appended '*').
711 	 */
712 	for (s = toglob; *s; s++) {
713 		if (*s == '\\' && s[1])
714 			s++;
715 		else if (*s == '*' || *s == '[' || *s == '?' || *s == '$' ||
716 		    (s[1] == '(' /*)*/ && strchr("+@!", *s)))
717 			break;
718 		else if (*s == '/')
719 			saw_slash = true;
720 	}
721 	if (!*s && (*toglob != '~' || saw_slash)) {
722 		toglob[slen] = '*';
723 		toglob[slen + 1] = '\0';
724 	}
725 
726 	return toglob;
727 }
728 
729 /*
730  * Find longest common prefix
731  */
732 int
733 x_longest_prefix(int nwords, char *const *words)
734 {
735 	int i, j;
736 	int prefix_len;
737 	char *p;
738 
739 	if (nwords <= 0)
740 		return 0;
741 
742 	prefix_len = strlen(words[0]);
743 	for (i = 1; i < nwords; i++)
744 		for (j = 0, p = words[i]; j < prefix_len; j++)
745 			if (p[j] != words[0][j]) {
746 				prefix_len = j;
747 				break;
748 			}
749 	return prefix_len;
750 }
751 
752 void
753 x_free_words(int nwords, char **words)
754 {
755 	int i;
756 
757 	for (i = 0; i < nwords; i++)
758 		afree(words[i], ATEMP);
759 	afree(words, ATEMP);
760 }
761 
762 /* Return the offset of the basename of string s (which ends at se - need not
763  * be null terminated).  Trailing slashes are ignored.  If s is just a slash,
764  * then the offset is 0 (actually, length - 1).
765  *	s		Return
766  *	/etc		1
767  *	/etc/		1
768  *	/etc//		1
769  *	/etc/fo		5
770  *	foo		0
771  *	///		2
772  *			0
773  */
774 int
775 x_basename(const char *s, const char *se)
776 {
777 	const char *p;
778 
779 	if (se == NULL)
780 		se = s + strlen(s);
781 	if (s == se)
782 		return 0;
783 
784 	/* Skip trailing slashes */
785 	for (p = se - 1; p > s && *p == '/'; p--)
786 		;
787 	for (; p > s && *p != '/'; p--)
788 		;
789 	if (*p == '/' && p + 1 < se)
790 		p++;
791 
792 	return p - s;
793 }
794 
795 /*
796  *  Apply pattern matching to a table: all table entries that match a pattern
797  * are added to wp.
798  */
799 static void
800 glob_table(const char *pat, XPtrV *wp, struct table *tp)
801 {
802 	struct tstate ts;
803 	struct tbl *te;
804 
805 	for (ktwalk(&ts, tp); (te = ktnext(&ts)); ) {
806 		if (gmatch(te->name, pat, false))
807 			XPput(*wp, str_save(te->name, ATEMP));
808 	}
809 }
810 
811 static void
812 glob_path(int flags, const char *pat, XPtrV *wp, const char *path)
813 {
814 	const char *sp, *p;
815 	char *xp;
816 	int staterr;
817 	int pathlen;
818 	int patlen;
819 	int oldsize, newsize, i, j;
820 	char **words;
821 	XString xs;
822 
823 	patlen = strlen(pat) + 1;
824 	sp = path;
825 	Xinit(xs, xp, patlen + 128, ATEMP);
826 	while (sp) {
827 		xp = Xstring(xs, xp);
828 		if (!(p = strchr(sp, ':')))
829 			p = sp + strlen(sp);
830 		pathlen = p - sp;
831 		if (pathlen) {
832 			/* Copy sp into xp, stuffing any MAGIC characters
833 			 * on the way
834 			 */
835 			const char *s = sp;
836 
837 			XcheckN(xs, xp, pathlen * 2);
838 			while (s < p) {
839 				if (ISMAGIC(*s))
840 					*xp++ = MAGIC;
841 				*xp++ = *s++;
842 			}
843 			*xp++ = '/';
844 			pathlen++;
845 		}
846 		sp = p;
847 		XcheckN(xs, xp, patlen);
848 		memcpy(xp, pat, patlen);
849 
850 		oldsize = XPsize(*wp);
851 		glob_str(Xstring(xs, xp), wp, 1); /* mark dirs */
852 		newsize = XPsize(*wp);
853 
854 		/* Check that each match is executable... */
855 		words = (char **) XPptrv(*wp);
856 		for (i = j = oldsize; i < newsize; i++) {
857 			staterr = 0;
858 			if ((search_access(words[i], X_OK, &staterr) >= 0) ||
859 			    (staterr == EISDIR)) {
860 				words[j] = words[i];
861 				if (!(flags & XCF_FULLPATH))
862 					memmove(words[j], words[j] + pathlen,
863 					    strlen(words[j] + pathlen) + 1);
864 				j++;
865 			} else
866 				afree(words[i], ATEMP);
867 		}
868 		wp->cur = (void **) &words[j];
869 
870 		if (!*sp++)
871 			break;
872 	}
873 	Xfree(xs, xp);
874 }
875 
876 /*
877  * if argument string contains any special characters, they will
878  * be escaped and the result will be put into edit buffer by
879  * keybinding-specific function
880  */
881 int
882 x_escape(const char *s, size_t len, int (*putbuf_func) (const char *, size_t))
883 {
884 	size_t add, wlen;
885 	const char *ifs = str_val(local("IFS", 0));
886 	int rval = 0;
887 
888 	for (add = 0, wlen = len; wlen - add > 0; add++) {
889 		if (strchr("!\"#$&'()*:;<=>?[\\]`{|}", s[add]) ||
890 		    strchr(ifs, s[add])) {
891 			if (putbuf_func(s, add) != 0) {
892 				rval = -1;
893 				break;
894 			}
895 
896 			putbuf_func("\\", 1);
897 			putbuf_func(&s[add], 1);
898 
899 			add++;
900 			wlen -= add;
901 			s += add;
902 			add = -1; /* after the increment it will go to 0 */
903 		}
904 	}
905 	if (wlen > 0 && rval == 0)
906 		rval = putbuf_func(s, wlen);
907 
908 	return (rval);
909 }
910 #endif /* EDIT */
911