xref: /openbmc/u-boot/common/cli_readline.c (revision 5396b2a9)
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 		/* ichar=0x0 when error occurs in U-Boot getc */
277 		if (!ichar)
278 			continue;
279 
280 		if ((ichar == '\n') || (ichar == '\r')) {
281 			putc('\n');
282 			break;
283 		}
284 
285 		/*
286 		 * handle standard linux xterm esc sequences for arrow key, etc.
287 		 */
288 		if (esc_len != 0) {
289 			enum { ESC_REJECT, ESC_SAVE, ESC_CONVERTED } act = ESC_REJECT;
290 
291 			if (esc_len == 1) {
292 				if (ichar == '[' || ichar == 'O')
293 					act = ESC_SAVE;
294 			} else if (esc_len == 2) {
295 				switch (ichar) {
296 				case 'D':	/* <- key */
297 					ichar = CTL_CH('b');
298 					act = ESC_CONVERTED;
299 					break;	/* pass off to ^B handler */
300 				case 'C':	/* -> key */
301 					ichar = CTL_CH('f');
302 					act = ESC_CONVERTED;
303 					break;	/* pass off to ^F handler */
304 				case 'H':	/* Home key */
305 					ichar = CTL_CH('a');
306 					act = ESC_CONVERTED;
307 					break;	/* pass off to ^A handler */
308 				case 'F':	/* End key */
309 					ichar = CTL_CH('e');
310 					act = ESC_CONVERTED;
311 					break;	/* pass off to ^E handler */
312 				case 'A':	/* up arrow */
313 					ichar = CTL_CH('p');
314 					act = ESC_CONVERTED;
315 					break;	/* pass off to ^P handler */
316 				case 'B':	/* down arrow */
317 					ichar = CTL_CH('n');
318 					act = ESC_CONVERTED;
319 					break;	/* pass off to ^N handler */
320 				case '1':
321 				case '3':
322 				case '4':
323 				case '7':
324 				case '8':
325 					if (esc_save[1] == '[') {
326 						/* see if next character is ~ */
327 						act = ESC_SAVE;
328 					}
329 					break;
330 				}
331 			} else if (esc_len == 3) {
332 				if (ichar == '~') {
333 					switch (esc_save[2]) {
334 					case '3':	/* Delete key */
335 						ichar = CTL_CH('d');
336 						act = ESC_CONVERTED;
337 						break;	/* pass to ^D handler */
338 					case '1':	/* Home key */
339 					case '7':
340 						ichar = CTL_CH('a');
341 						act = ESC_CONVERTED;
342 						break;	/* pass to ^A handler */
343 					case '4':	/* End key */
344 					case '8':
345 						ichar = CTL_CH('e');
346 						act = ESC_CONVERTED;
347 						break;	/* pass to ^E handler */
348 					}
349 				}
350 			}
351 
352 			switch (act) {
353 			case ESC_SAVE:
354 				esc_save[esc_len++] = ichar;
355 				continue;
356 			case ESC_REJECT:
357 				esc_save[esc_len++] = ichar;
358 				cread_add_str(esc_save, esc_len, insert,
359 					      &num, &eol_num, buf, *len);
360 				esc_len = 0;
361 				continue;
362 			case ESC_CONVERTED:
363 				esc_len = 0;
364 				break;
365 			}
366 		}
367 
368 		switch (ichar) {
369 		case 0x1b:
370 			if (esc_len == 0) {
371 				esc_save[esc_len] = ichar;
372 				esc_len = 1;
373 			} else {
374 				puts("impossible condition #876\n");
375 				esc_len = 0;
376 			}
377 			break;
378 
379 		case CTL_CH('a'):
380 			BEGINNING_OF_LINE();
381 			break;
382 		case CTL_CH('c'):	/* ^C - break */
383 			*buf = '\0';	/* discard input */
384 			return -1;
385 		case CTL_CH('f'):
386 			if (num < eol_num) {
387 				getcmd_putch(buf[num]);
388 				num++;
389 			}
390 			break;
391 		case CTL_CH('b'):
392 			if (num) {
393 				getcmd_putch(CTL_BACKSPACE);
394 				num--;
395 			}
396 			break;
397 		case CTL_CH('d'):
398 			if (num < eol_num) {
399 				wlen = eol_num - num - 1;
400 				if (wlen) {
401 					memmove(&buf[num], &buf[num+1], wlen);
402 					putnstr(buf + num, wlen);
403 				}
404 
405 				getcmd_putch(' ');
406 				do {
407 					getcmd_putch(CTL_BACKSPACE);
408 				} while (wlen--);
409 				eol_num--;
410 			}
411 			break;
412 		case CTL_CH('k'):
413 			ERASE_TO_EOL();
414 			break;
415 		case CTL_CH('e'):
416 			REFRESH_TO_EOL();
417 			break;
418 		case CTL_CH('o'):
419 			insert = !insert;
420 			break;
421 		case CTL_CH('x'):
422 		case CTL_CH('u'):
423 			BEGINNING_OF_LINE();
424 			ERASE_TO_EOL();
425 			break;
426 		case DEL:
427 		case DEL7:
428 		case 8:
429 			if (num) {
430 				wlen = eol_num - num;
431 				num--;
432 				memmove(&buf[num], &buf[num+1], wlen);
433 				getcmd_putch(CTL_BACKSPACE);
434 				putnstr(buf + num, wlen);
435 				getcmd_putch(' ');
436 				do {
437 					getcmd_putch(CTL_BACKSPACE);
438 				} while (wlen--);
439 				eol_num--;
440 			}
441 			break;
442 		case CTL_CH('p'):
443 		case CTL_CH('n'):
444 		{
445 			char *hline;
446 
447 			esc_len = 0;
448 
449 			if (ichar == CTL_CH('p'))
450 				hline = hist_prev();
451 			else
452 				hline = hist_next();
453 
454 			if (!hline) {
455 				getcmd_cbeep();
456 				continue;
457 			}
458 
459 			/* nuke the current line */
460 			/* first, go home */
461 			BEGINNING_OF_LINE();
462 
463 			/* erase to end of line */
464 			ERASE_TO_EOL();
465 
466 			/* copy new line into place and display */
467 			strcpy(buf, hline);
468 			eol_num = strlen(buf);
469 			REFRESH_TO_EOL();
470 			continue;
471 		}
472 #ifdef CONFIG_AUTO_COMPLETE
473 		case '\t': {
474 			int num2, col;
475 
476 			/* do not autocomplete when in the middle */
477 			if (num < eol_num) {
478 				getcmd_cbeep();
479 				break;
480 			}
481 
482 			buf[num] = '\0';
483 			col = strlen(prompt) + eol_num;
484 			num2 = num;
485 			if (cmd_auto_complete(prompt, buf, &num2, &col)) {
486 				col = num2 - num;
487 				num += col;
488 				eol_num += col;
489 			}
490 			break;
491 		}
492 #endif
493 		default:
494 			cread_add_char(ichar, insert, &num, &eol_num, buf,
495 				       *len);
496 			break;
497 		}
498 	}
499 	*len = eol_num;
500 	buf[eol_num] = '\0';	/* lose the newline */
501 
502 	if (buf[0] && buf[0] != CREAD_HIST_CHAR)
503 		cread_add_to_hist(buf);
504 	hist_cur = hist_add_idx;
505 
506 	return 0;
507 }
508 
509 #endif /* CONFIG_CMDLINE_EDITING */
510 
511 /****************************************************************************/
512 
513 int cli_readline(const char *const prompt)
514 {
515 	/*
516 	 * If console_buffer isn't 0-length the user will be prompted to modify
517 	 * it instead of entering it from scratch as desired.
518 	 */
519 	console_buffer[0] = '\0';
520 
521 	return cli_readline_into_buffer(prompt, console_buffer, 0);
522 }
523 
524 
525 int cli_readline_into_buffer(const char *const prompt, char *buffer,
526 			     int timeout)
527 {
528 	char *p = buffer;
529 #ifdef CONFIG_CMDLINE_EDITING
530 	unsigned int len = CONFIG_SYS_CBSIZE;
531 	int rc;
532 	static int initted;
533 
534 	/*
535 	 * History uses a global array which is not
536 	 * writable until after relocation to RAM.
537 	 * Revert to non-history version if still
538 	 * running from flash.
539 	 */
540 	if (gd->flags & GD_FLG_RELOC) {
541 		if (!initted) {
542 			hist_init();
543 			initted = 1;
544 		}
545 
546 		if (prompt)
547 			puts(prompt);
548 
549 		rc = cread_line(prompt, p, &len, timeout);
550 		return rc < 0 ? rc : len;
551 
552 	} else {
553 #endif	/* CONFIG_CMDLINE_EDITING */
554 	char *p_buf = p;
555 	int	n = 0;				/* buffer index		*/
556 	int	plen = 0;			/* prompt length	*/
557 	int	col;				/* output column cnt	*/
558 	char	c;
559 
560 	/* print prompt */
561 	if (prompt) {
562 		plen = strlen(prompt);
563 		puts(prompt);
564 	}
565 	col = plen;
566 
567 	for (;;) {
568 		if (bootretry_tstc_timeout())
569 			return -2;	/* timed out */
570 		WATCHDOG_RESET();	/* Trigger watchdog, if needed */
571 
572 #ifdef CONFIG_SHOW_ACTIVITY
573 		while (!tstc()) {
574 			show_activity(0);
575 			WATCHDOG_RESET();
576 		}
577 #endif
578 		c = getc();
579 
580 		/*
581 		 * Special character handling
582 		 */
583 		switch (c) {
584 		case '\r':			/* Enter		*/
585 		case '\n':
586 			*p = '\0';
587 			puts("\r\n");
588 			return p - p_buf;
589 
590 		case '\0':			/* nul			*/
591 			continue;
592 
593 		case 0x03:			/* ^C - break		*/
594 			p_buf[0] = '\0';	/* discard input */
595 			return -1;
596 
597 		case 0x15:			/* ^U - erase line	*/
598 			while (col > plen) {
599 				puts(erase_seq);
600 				--col;
601 			}
602 			p = p_buf;
603 			n = 0;
604 			continue;
605 
606 		case 0x17:			/* ^W - erase word	*/
607 			p = delete_char(p_buf, p, &col, &n, plen);
608 			while ((n > 0) && (*p != ' '))
609 				p = delete_char(p_buf, p, &col, &n, plen);
610 			continue;
611 
612 		case 0x08:			/* ^H  - backspace	*/
613 		case 0x7F:			/* DEL - backspace	*/
614 			p = delete_char(p_buf, p, &col, &n, plen);
615 			continue;
616 
617 		default:
618 			/*
619 			 * Must be a normal character then
620 			 */
621 			if (n < CONFIG_SYS_CBSIZE-2) {
622 				if (c == '\t') {	/* expand TABs */
623 #ifdef CONFIG_AUTO_COMPLETE
624 					/*
625 					 * if auto completion triggered just
626 					 * continue
627 					 */
628 					*p = '\0';
629 					if (cmd_auto_complete(prompt,
630 							      console_buffer,
631 							      &n, &col)) {
632 						p = p_buf + n;	/* reset */
633 						continue;
634 					}
635 #endif
636 					puts(tab_seq + (col & 07));
637 					col += 8 - (col & 07);
638 				} else {
639 					char __maybe_unused buf[2];
640 
641 					/*
642 					 * Echo input using puts() to force an
643 					 * LCD flush if we are using an LCD
644 					 */
645 					++col;
646 					buf[0] = c;
647 					buf[1] = '\0';
648 					puts(buf);
649 				}
650 				*p++ = c;
651 				++n;
652 			} else {			/* Buffer full */
653 				putc('\a');
654 			}
655 		}
656 	}
657 #ifdef CONFIG_CMDLINE_EDITING
658 	}
659 #endif
660 }
661