1 // SPDX-License-Identifier: GPL-2.0+
2 /*
3  * (C) Copyright 2000
4  * Wolfgang Denk, DENX Software Engineering, wd@denx.de.
5  *
6  * Copyright 2022 Google LLC
7  */
8 
9 #include <cli.h>
10 #include <stdio.h>
11 #include <string.h>
12 #include <linux/errno.h>
13 
14 /**
15  * enum cli_esc_state_t - indicates what to do with an escape character
16  *
17  * @ESC_REJECT: Invalid escape sequence, so the esc_save[] characters are
18  *	returned from each subsequent call to cli_ch_esc()
19  * @ESC_SAVE: Character should be saved in esc_save until we have another one
20  * @ESC_CONVERTED: Escape sequence has been completed and the resulting
21  *	character is available
22  */
23 enum cli_esc_state_t {
24 	ESC_REJECT,
25 	ESC_SAVE,
26 	ESC_CONVERTED
27 };
28 
cli_ch_init(struct cli_ch_state * cch)29 void cli_ch_init(struct cli_ch_state *cch)
30 {
31 	memset(cch, '\0', sizeof(*cch));
32 }
33 
34 /**
35  * cli_ch_esc() - Process a character in an ongoing escape sequence
36  *
37  * @cch: State information
38  * @ichar: Character to process
39  * @actp: Returns the action to take
40  * Returns: Output character if *actp is ESC_CONVERTED, else 0
41  */
cli_ch_esc(struct cli_ch_state * cch,int ichar,enum cli_esc_state_t * actp)42 static int cli_ch_esc(struct cli_ch_state *cch, int ichar,
43 		      enum cli_esc_state_t *actp)
44 {
45 	enum cli_esc_state_t act = ESC_REJECT;
46 
47 	switch (cch->esc_len) {
48 	case 1:
49 		if (ichar == '[' || ichar == 'O')
50 			act = ESC_SAVE;
51 		else
52 			act = ESC_CONVERTED;
53 		break;
54 	case 2:
55 		switch (ichar) {
56 		case 'D':	/* <- key */
57 			ichar = CTL_CH('b');
58 			act = ESC_CONVERTED;
59 			break;	/* pass off to ^B handler */
60 		case 'C':	/* -> key */
61 			ichar = CTL_CH('f');
62 			act = ESC_CONVERTED;
63 			break;	/* pass off to ^F handler */
64 		case 'H':	/* Home key */
65 			ichar = CTL_CH('a');
66 			act = ESC_CONVERTED;
67 			break;	/* pass off to ^A handler */
68 		case 'F':	/* End key */
69 			ichar = CTL_CH('e');
70 			act = ESC_CONVERTED;
71 			break;	/* pass off to ^E handler */
72 		case 'A':	/* up arrow */
73 			ichar = CTL_CH('p');
74 			act = ESC_CONVERTED;
75 			break;	/* pass off to ^P handler */
76 		case 'B':	/* down arrow */
77 			ichar = CTL_CH('n');
78 			act = ESC_CONVERTED;
79 			break;	/* pass off to ^N handler */
80 		case '1':
81 		case '2':
82 		case '3':
83 		case '4':
84 		case '7':
85 		case '8':
86 			if (cch->esc_save[1] == '[') {
87 				/* see if next character is ~ */
88 				act = ESC_SAVE;
89 			}
90 			break;
91 		}
92 		break;
93 	case 3:
94 		switch (ichar) {
95 		case '~':
96 			switch (cch->esc_save[2]) {
97 			case '3':	/* Delete key */
98 				ichar = CTL_CH('d');
99 				act = ESC_CONVERTED;
100 				break;	/* pass to ^D handler */
101 			case '1':	/* Home key */
102 			case '7':
103 				ichar = CTL_CH('a');
104 				act = ESC_CONVERTED;
105 				break;	/* pass to ^A handler */
106 			case '4':	/* End key */
107 			case '8':
108 				ichar = CTL_CH('e');
109 				act = ESC_CONVERTED;
110 				break;	/* pass to ^E handler */
111 			}
112 			break;
113 		case '0':
114 			if (cch->esc_save[2] == '2')
115 				act = ESC_SAVE;
116 			break;
117 		}
118 		break;
119 	case 4:
120 		switch (ichar) {
121 		case '0':
122 		case '1':
123 			act = ESC_SAVE;
124 			break;		/* bracketed paste */
125 		}
126 		break;
127 	case 5:
128 		if (ichar == '~') {	/* bracketed paste */
129 			ichar = 0;
130 			act = ESC_CONVERTED;
131 		}
132 	}
133 
134 	*actp = act;
135 
136 	return ichar;
137 }
138 
cli_ch_process(struct cli_ch_state * cch,int ichar)139 int cli_ch_process(struct cli_ch_state *cch, int ichar)
140 {
141 	/*
142 	 * ichar=0x0 when error occurs in U-Boot getchar() or when the caller
143 	 * wants to check if there are more characters saved in the escape
144 	 * sequence
145 	 */
146 	if (!ichar) {
147 		if (cch->emitting) {
148 			if (cch->emit_upto < cch->esc_len)
149 				return cch->esc_save[cch->emit_upto++];
150 			cch->emit_upto = 0;
151 			cch->emitting = false;
152 			cch->esc_len = 0;
153 		}
154 		return 0;
155 	} else if (ichar == -ETIMEDOUT) {
156 		/*
157 		 * If we are in an escape sequence but nothing has followed the
158 		 * Escape character, then the user probably just pressed the
159 		 * Escape key. Return it and clear the sequence.
160 		 */
161 		if (cch->esc_len) {
162 			cch->esc_len = 0;
163 			return '\e';
164 		}
165 
166 		/* Otherwise there is nothing to return */
167 		return 0;
168 	}
169 
170 	if (ichar == '\n' || ichar == '\r')
171 		return '\n';
172 
173 	/* handle standard linux xterm esc sequences for arrow key, etc. */
174 	if (cch->esc_len != 0) {
175 		enum cli_esc_state_t act;
176 
177 		ichar = cli_ch_esc(cch, ichar, &act);
178 
179 		switch (act) {
180 		case ESC_SAVE:
181 			/* save this character and return nothing */
182 			cch->esc_save[cch->esc_len++] = ichar;
183 			ichar = 0;
184 			break;
185 		case ESC_REJECT:
186 			/*
187 			 * invalid escape sequence, start returning the
188 			 * characters in it
189 			 */
190 			cch->esc_save[cch->esc_len++] = ichar;
191 			ichar = cch->esc_save[cch->emit_upto++];
192 			cch->emitting = true;
193 			return ichar;
194 		case ESC_CONVERTED:
195 			/* valid escape sequence, return the resulting char */
196 			cch->esc_len = 0;
197 			break;
198 		}
199 	}
200 
201 	if (ichar == '\e') {
202 		if (!cch->esc_len) {
203 			cch->esc_save[cch->esc_len] = ichar;
204 			cch->esc_len = 1;
205 		} else {
206 			puts("impossible condition #876\n");
207 			cch->esc_len = 0;
208 		}
209 		return 0;
210 	}
211 
212 	return ichar;
213 }
214