xref: /openbmc/u-boot/common/cli_readline.c (revision 78a88f79)
1 // SPDX-License-Identifier: GPL-2.0+
2 /*
3  * (C) Copyright 2000
4  * Wolfgang Denk, DENX Software Engineering, wd@denx.de.
5  *
6  * Add to readline cmdline-editing by
7  * (C) Copyright 2005
8  * JinHua Luo, GuangDong Linux Center, <luo.jinhua@gd-linux.com>
9  */
10 
11 #include <common.h>
12 #include <bootretry.h>
13 #include <cli.h>
14 #include <watchdog.h>
15 
16 DECLARE_GLOBAL_DATA_PTR;
17 
18 static const char erase_seq[] = "\b \b";	/* erase sequence */
19 static const char   tab_seq[] = "        ";	/* used to expand TABs */
20 
21 char console_buffer[CONFIG_SYS_CBSIZE + 1];	/* console I/O buffer	*/
22 
23 static char *delete_char (char *buffer, char *p, int *colp, int *np, int plen)
24 {
25 	char *s;
26 
27 	if (*np == 0)
28 		return p;
29 
30 	if (*(--p) == '\t') {		/* will retype the whole line */
31 		while (*colp > plen) {
32 			puts(erase_seq);
33 			(*colp)--;
34 		}
35 		for (s = buffer; s < p; ++s) {
36 			if (*s == '\t') {
37 				puts(tab_seq + ((*colp) & 07));
38 				*colp += 8 - ((*colp) & 07);
39 			} else {
40 				++(*colp);
41 				putc(*s);
42 			}
43 		}
44 	} else {
45 		puts(erase_seq);
46 		(*colp)--;
47 	}
48 	(*np)--;
49 
50 	return p;
51 }
52 
53 #ifdef CONFIG_CMDLINE_EDITING
54 
55 /*
56  * cmdline-editing related codes from vivi.
57  * Author: Janghoon Lyu <nandy@mizi.com>
58  */
59 
60 #define putnstr(str, n)	printf("%.*s", (int)n, str)
61 
62 #define CTL_CH(c)		((c) - 'a' + 1)
63 #define CTL_BACKSPACE		('\b')
64 #define DEL			((char)255)
65 #define DEL7			((char)127)
66 #define CREAD_HIST_CHAR		('!')
67 
68 #define getcmd_putch(ch)	putc(ch)
69 #define getcmd_getch()		getc()
70 #define getcmd_cbeep()		getcmd_putch('\a')
71 
72 #define HIST_MAX		20
73 #define HIST_SIZE		CONFIG_SYS_CBSIZE
74 
75 static int hist_max;
76 static int hist_add_idx;
77 static int hist_cur = -1;
78 static unsigned hist_num;
79 
80 static char *hist_list[HIST_MAX];
81 static char hist_lines[HIST_MAX][HIST_SIZE + 1];	/* Save room for NULL */
82 
83 #define add_idx_minus_one() ((hist_add_idx == 0) ? hist_max : hist_add_idx-1)
84 
85 static void hist_init(void)
86 {
87 	int i;
88 
89 	hist_max = 0;
90 	hist_add_idx = 0;
91 	hist_cur = -1;
92 	hist_num = 0;
93 
94 	for (i = 0; i < HIST_MAX; i++) {
95 		hist_list[i] = hist_lines[i];
96 		hist_list[i][0] = '\0';
97 	}
98 }
99 
100 static void cread_add_to_hist(char *line)
101 {
102 	strcpy(hist_list[hist_add_idx], line);
103 
104 	if (++hist_add_idx >= HIST_MAX)
105 		hist_add_idx = 0;
106 
107 	if (hist_add_idx > hist_max)
108 		hist_max = hist_add_idx;
109 
110 	hist_num++;
111 }
112 
113 static char *hist_prev(void)
114 {
115 	char *ret;
116 	int old_cur;
117 
118 	if (hist_cur < 0)
119 		return NULL;
120 
121 	old_cur = hist_cur;
122 	if (--hist_cur < 0)
123 		hist_cur = hist_max;
124 
125 	if (hist_cur == hist_add_idx) {
126 		hist_cur = old_cur;
127 		ret = NULL;
128 	} else {
129 		ret = hist_list[hist_cur];
130 	}
131 
132 	return ret;
133 }
134 
135 static char *hist_next(void)
136 {
137 	char *ret;
138 
139 	if (hist_cur < 0)
140 		return NULL;
141 
142 	if (hist_cur == hist_add_idx)
143 		return NULL;
144 
145 	if (++hist_cur > hist_max)
146 		hist_cur = 0;
147 
148 	if (hist_cur == hist_add_idx)
149 		ret = "";
150 	else
151 		ret = hist_list[hist_cur];
152 
153 	return ret;
154 }
155 
156 #ifndef CONFIG_CMDLINE_EDITING
157 static void cread_print_hist_list(void)
158 {
159 	int i;
160 	unsigned long n;
161 
162 	n = hist_num - hist_max;
163 
164 	i = hist_add_idx + 1;
165 	while (1) {
166 		if (i > hist_max)
167 			i = 0;
168 		if (i == hist_add_idx)
169 			break;
170 		printf("%s\n", hist_list[i]);
171 		n++;
172 		i++;
173 	}
174 }
175 #endif /* CONFIG_CMDLINE_EDITING */
176 
177 #define BEGINNING_OF_LINE() {			\
178 	while (num) {				\
179 		getcmd_putch(CTL_BACKSPACE);	\
180 		num--;				\
181 	}					\
182 }
183 
184 #define ERASE_TO_EOL() {				\
185 	if (num < eol_num) {				\
186 		printf("%*s", (int)(eol_num - num), ""); \
187 		do {					\
188 			getcmd_putch(CTL_BACKSPACE);	\
189 		} while (--eol_num > num);		\
190 	}						\
191 }
192 
193 #define REFRESH_TO_EOL() {			\
194 	if (num < eol_num) {			\
195 		wlen = eol_num - num;		\
196 		putnstr(buf + num, wlen);	\
197 		num = eol_num;			\
198 	}					\
199 }
200 
201 static void cread_add_char(char ichar, int insert, unsigned long *num,
202 	       unsigned long *eol_num, char *buf, unsigned long len)
203 {
204 	unsigned long wlen;
205 
206 	/* room ??? */
207 	if (insert || *num == *eol_num) {
208 		if (*eol_num > len - 1) {
209 			getcmd_cbeep();
210 			return;
211 		}
212 		(*eol_num)++;
213 	}
214 
215 	if (insert) {
216 		wlen = *eol_num - *num;
217 		if (wlen > 1)
218 			memmove(&buf[*num+1], &buf[*num], wlen-1);
219 
220 		buf[*num] = ichar;
221 		putnstr(buf + *num, wlen);
222 		(*num)++;
223 		while (--wlen)
224 			getcmd_putch(CTL_BACKSPACE);
225 	} else {
226 		/* echo the character */
227 		wlen = 1;
228 		buf[*num] = ichar;
229 		putnstr(buf + *num, wlen);
230 		(*num)++;
231 	}
232 }
233 
234 static void cread_add_str(char *str, int strsize, int insert,
235 			  unsigned long *num, unsigned long *eol_num,
236 			  char *buf, unsigned long len)
237 {
238 	while (strsize--) {
239 		cread_add_char(*str, insert, num, eol_num, buf, len);
240 		str++;
241 	}
242 }
243 
244 static int cread_line(const char *const prompt, char *buf, unsigned int *len,
245 		int timeout)
246 {
247 	unsigned long num = 0;
248 	unsigned long eol_num = 0;
249 	unsigned long wlen;
250 	char ichar;
251 	int insert = 1;
252 	int esc_len = 0;
253 	char esc_save[8];
254 	int init_len = strlen(buf);
255 	int first = 1;
256 
257 	if (init_len)
258 		cread_add_str(buf, init_len, 1, &num, &eol_num, buf, *len);
259 
260 	while (1) {
261 		if (bootretry_tstc_timeout())
262 			return -2;	/* timed out */
263 		if (first && timeout) {
264 			uint64_t etime = endtick(timeout);
265 
266 			while (!tstc()) {	/* while no incoming data */
267 				if (get_ticks() >= etime)
268 					return -2;	/* timed out */
269 				WATCHDOG_RESET();
270 			}
271 			first = 0;
272 		}
273 
274 		ichar = getcmd_getch();
275 
276 		if ((ichar == '\n') || (ichar == '\r')) {
277 			putc('\n');
278 			break;
279 		}
280 
281 		/*
282 		 * handle standard linux xterm esc sequences for arrow key, etc.
283 		 */
284 		if (esc_len != 0) {
285 			enum { ESC_REJECT, ESC_SAVE, ESC_CONVERTED } act = ESC_REJECT;
286 
287 			if (esc_len == 1) {
288 				if (ichar == '[' || ichar == 'O')
289 					act = ESC_SAVE;
290 			} else if (esc_len == 2) {
291 				switch (ichar) {
292 				case 'D':	/* <- key */
293 					ichar = CTL_CH('b');
294 					act = ESC_CONVERTED;
295 					break;	/* pass off to ^B handler */
296 				case 'C':	/* -> key */
297 					ichar = CTL_CH('f');
298 					act = ESC_CONVERTED;
299 					break;	/* pass off to ^F handler */
300 				case 'H':	/* Home key */
301 					ichar = CTL_CH('a');
302 					act = ESC_CONVERTED;
303 					break;	/* pass off to ^A handler */
304 				case 'F':	/* End key */
305 					ichar = CTL_CH('e');
306 					act = ESC_CONVERTED;
307 					break;	/* pass off to ^E handler */
308 				case 'A':	/* up arrow */
309 					ichar = CTL_CH('p');
310 					act = ESC_CONVERTED;
311 					break;	/* pass off to ^P handler */
312 				case 'B':	/* down arrow */
313 					ichar = CTL_CH('n');
314 					act = ESC_CONVERTED;
315 					break;	/* pass off to ^N handler */
316 				case '1':
317 				case '3':
318 				case '4':
319 				case '7':
320 				case '8':
321 					if (esc_save[1] == '[') {
322 						/* see if next character is ~ */
323 						act = ESC_SAVE;
324 					}
325 					break;
326 				}
327 			} else if (esc_len == 3) {
328 				if (ichar == '~') {
329 					switch (esc_save[2]) {
330 					case '3':	/* Delete key */
331 						ichar = CTL_CH('d');
332 						act = ESC_CONVERTED;
333 						break;	/* pass to ^D handler */
334 					case '1':	/* Home key */
335 					case '7':
336 						ichar = CTL_CH('a');
337 						act = ESC_CONVERTED;
338 						break;	/* pass to ^A handler */
339 					case '4':	/* End key */
340 					case '8':
341 						ichar = CTL_CH('e');
342 						act = ESC_CONVERTED;
343 						break;	/* pass to ^E handler */
344 					}
345 				}
346 			}
347 
348 			switch (act) {
349 			case ESC_SAVE:
350 				esc_save[esc_len++] = ichar;
351 				continue;
352 			case ESC_REJECT:
353 				esc_save[esc_len++] = ichar;
354 				cread_add_str(esc_save, esc_len, insert,
355 					      &num, &eol_num, buf, *len);
356 				esc_len = 0;
357 				continue;
358 			case ESC_CONVERTED:
359 				esc_len = 0;
360 				break;
361 			}
362 		}
363 
364 		switch (ichar) {
365 		case 0x1b:
366 			if (esc_len == 0) {
367 				esc_save[esc_len] = ichar;
368 				esc_len = 1;
369 			} else {
370 				puts("impossible condition #876\n");
371 				esc_len = 0;
372 			}
373 			break;
374 
375 		case CTL_CH('a'):
376 			BEGINNING_OF_LINE();
377 			break;
378 		case CTL_CH('c'):	/* ^C - break */
379 			*buf = '\0';	/* discard input */
380 			return -1;
381 		case CTL_CH('f'):
382 			if (num < eol_num) {
383 				getcmd_putch(buf[num]);
384 				num++;
385 			}
386 			break;
387 		case CTL_CH('b'):
388 			if (num) {
389 				getcmd_putch(CTL_BACKSPACE);
390 				num--;
391 			}
392 			break;
393 		case CTL_CH('d'):
394 			if (num < eol_num) {
395 				wlen = eol_num - num - 1;
396 				if (wlen) {
397 					memmove(&buf[num], &buf[num+1], wlen);
398 					putnstr(buf + num, wlen);
399 				}
400 
401 				getcmd_putch(' ');
402 				do {
403 					getcmd_putch(CTL_BACKSPACE);
404 				} while (wlen--);
405 				eol_num--;
406 			}
407 			break;
408 		case CTL_CH('k'):
409 			ERASE_TO_EOL();
410 			break;
411 		case CTL_CH('e'):
412 			REFRESH_TO_EOL();
413 			break;
414 		case CTL_CH('o'):
415 			insert = !insert;
416 			break;
417 		case CTL_CH('x'):
418 		case CTL_CH('u'):
419 			BEGINNING_OF_LINE();
420 			ERASE_TO_EOL();
421 			break;
422 		case DEL:
423 		case DEL7:
424 		case 8:
425 			if (num) {
426 				wlen = eol_num - num;
427 				num--;
428 				memmove(&buf[num], &buf[num+1], wlen);
429 				getcmd_putch(CTL_BACKSPACE);
430 				putnstr(buf + num, wlen);
431 				getcmd_putch(' ');
432 				do {
433 					getcmd_putch(CTL_BACKSPACE);
434 				} while (wlen--);
435 				eol_num--;
436 			}
437 			break;
438 		case CTL_CH('p'):
439 		case CTL_CH('n'):
440 		{
441 			char *hline;
442 
443 			esc_len = 0;
444 
445 			if (ichar == CTL_CH('p'))
446 				hline = hist_prev();
447 			else
448 				hline = hist_next();
449 
450 			if (!hline) {
451 				getcmd_cbeep();
452 				continue;
453 			}
454 
455 			/* nuke the current line */
456 			/* first, go home */
457 			BEGINNING_OF_LINE();
458 
459 			/* erase to end of line */
460 			ERASE_TO_EOL();
461 
462 			/* copy new line into place and display */
463 			strcpy(buf, hline);
464 			eol_num = strlen(buf);
465 			REFRESH_TO_EOL();
466 			continue;
467 		}
468 #ifdef CONFIG_AUTO_COMPLETE
469 		case '\t': {
470 			int num2, col;
471 
472 			/* do not autocomplete when in the middle */
473 			if (num < eol_num) {
474 				getcmd_cbeep();
475 				break;
476 			}
477 
478 			buf[num] = '\0';
479 			col = strlen(prompt) + eol_num;
480 			num2 = num;
481 			if (cmd_auto_complete(prompt, buf, &num2, &col)) {
482 				col = num2 - num;
483 				num += col;
484 				eol_num += col;
485 			}
486 			break;
487 		}
488 #endif
489 		default:
490 			cread_add_char(ichar, insert, &num, &eol_num, buf,
491 				       *len);
492 			break;
493 		}
494 	}
495 	*len = eol_num;
496 	buf[eol_num] = '\0';	/* lose the newline */
497 
498 	if (buf[0] && buf[0] != CREAD_HIST_CHAR)
499 		cread_add_to_hist(buf);
500 	hist_cur = hist_add_idx;
501 
502 	return 0;
503 }
504 
505 #endif /* CONFIG_CMDLINE_EDITING */
506 
507 /****************************************************************************/
508 
509 int cli_readline(const char *const prompt)
510 {
511 	/*
512 	 * If console_buffer isn't 0-length the user will be prompted to modify
513 	 * it instead of entering it from scratch as desired.
514 	 */
515 	console_buffer[0] = '\0';
516 
517 	return cli_readline_into_buffer(prompt, console_buffer, 0);
518 }
519 
520 
521 int cli_readline_into_buffer(const char *const prompt, char *buffer,
522 			     int timeout)
523 {
524 	char *p = buffer;
525 #ifdef CONFIG_CMDLINE_EDITING
526 	unsigned int len = CONFIG_SYS_CBSIZE;
527 	int rc;
528 	static int initted;
529 
530 	/*
531 	 * History uses a global array which is not
532 	 * writable until after relocation to RAM.
533 	 * Revert to non-history version if still
534 	 * running from flash.
535 	 */
536 	if (gd->flags & GD_FLG_RELOC) {
537 		if (!initted) {
538 			hist_init();
539 			initted = 1;
540 		}
541 
542 		if (prompt)
543 			puts(prompt);
544 
545 		rc = cread_line(prompt, p, &len, timeout);
546 		return rc < 0 ? rc : len;
547 
548 	} else {
549 #endif	/* CONFIG_CMDLINE_EDITING */
550 	char *p_buf = p;
551 	int	n = 0;				/* buffer index		*/
552 	int	plen = 0;			/* prompt length	*/
553 	int	col;				/* output column cnt	*/
554 	char	c;
555 
556 	/* print prompt */
557 	if (prompt) {
558 		plen = strlen(prompt);
559 		puts(prompt);
560 	}
561 	col = plen;
562 
563 	for (;;) {
564 		if (bootretry_tstc_timeout())
565 			return -2;	/* timed out */
566 		WATCHDOG_RESET();	/* Trigger watchdog, if needed */
567 
568 #ifdef CONFIG_SHOW_ACTIVITY
569 		while (!tstc()) {
570 			show_activity(0);
571 			WATCHDOG_RESET();
572 		}
573 #endif
574 		c = getc();
575 
576 		/*
577 		 * Special character handling
578 		 */
579 		switch (c) {
580 		case '\r':			/* Enter		*/
581 		case '\n':
582 			*p = '\0';
583 			puts("\r\n");
584 			return p - p_buf;
585 
586 		case '\0':			/* nul			*/
587 			continue;
588 
589 		case 0x03:			/* ^C - break		*/
590 			p_buf[0] = '\0';	/* discard input */
591 			return -1;
592 
593 		case 0x15:			/* ^U - erase line	*/
594 			while (col > plen) {
595 				puts(erase_seq);
596 				--col;
597 			}
598 			p = p_buf;
599 			n = 0;
600 			continue;
601 
602 		case 0x17:			/* ^W - erase word	*/
603 			p = delete_char(p_buf, p, &col, &n, plen);
604 			while ((n > 0) && (*p != ' '))
605 				p = delete_char(p_buf, p, &col, &n, plen);
606 			continue;
607 
608 		case 0x08:			/* ^H  - backspace	*/
609 		case 0x7F:			/* DEL - backspace	*/
610 			p = delete_char(p_buf, p, &col, &n, plen);
611 			continue;
612 
613 		default:
614 			/*
615 			 * Must be a normal character then
616 			 */
617 			if (n < CONFIG_SYS_CBSIZE-2) {
618 				if (c == '\t') {	/* expand TABs */
619 #ifdef CONFIG_AUTO_COMPLETE
620 					/*
621 					 * if auto completion triggered just
622 					 * continue
623 					 */
624 					*p = '\0';
625 					if (cmd_auto_complete(prompt,
626 							      console_buffer,
627 							      &n, &col)) {
628 						p = p_buf + n;	/* reset */
629 						continue;
630 					}
631 #endif
632 					puts(tab_seq + (col & 07));
633 					col += 8 - (col & 07);
634 				} else {
635 					char __maybe_unused buf[2];
636 
637 					/*
638 					 * Echo input using puts() to force an
639 					 * LCD flush if we are using an LCD
640 					 */
641 					++col;
642 					buf[0] = c;
643 					buf[1] = '\0';
644 					puts(buf);
645 				}
646 				*p++ = c;
647 				++n;
648 			} else {			/* Buffer full */
649 				putc('\a');
650 			}
651 		}
652 	}
653 #ifdef CONFIG_CMDLINE_EDITING
654 	}
655 #endif
656 }
657