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