xref: /dpdk/lib/cmdline/cmdline_rdline.c (revision daa02b5cddbb8e11b31d41e2bf7bb1ae64dcae2f)
1 /* SPDX-License-Identifier: BSD-3-Clause
2  * Copyright(c) 2010-2014 Intel Corporation.
3  * Copyright (c) 2009, Olivier MATZ <zer0@droids-corp.org>
4  * All rights reserved.
5  */
6 
7 #include <stdlib.h>
8 #include <stdio.h>
9 #include <stdint.h>
10 #include <string.h>
11 #include <stdarg.h>
12 #include <errno.h>
13 #include <ctype.h>
14 
15 #include "cmdline_cirbuf.h"
16 #include "cmdline_private.h"
17 #include "cmdline_rdline.h"
18 
19 static void rdline_puts(struct rdline *rdl, const char *buf);
20 static void rdline_miniprintf(struct rdline *rdl,
21 			      const char *buf, unsigned int val);
22 
23 static void rdline_remove_old_history_item(struct rdline *rdl);
24 static void rdline_remove_first_history_item(struct rdline *rdl);
25 static unsigned int rdline_get_history_size(struct rdline *rdl);
26 
27 
28 /* isblank() needs _XOPEN_SOURCE >= 600 || _ISOC99_SOURCE, so use our
29  * own. */
30 static int
31 isblank2(char c)
32 {
33 	if (c == ' ' ||
34 	    c == '\t' )
35 		return 1;
36 	return 0;
37 }
38 
39 int
40 rdline_init(struct rdline *rdl,
41 	    rdline_write_char_t *write_char,
42 	    rdline_validate_t *validate,
43 	    rdline_complete_t *complete,
44 	    void *opaque)
45 {
46 	if (!rdl || !write_char || !validate || !complete)
47 		return -EINVAL;
48 	memset(rdl, 0, sizeof(*rdl));
49 	rdl->validate = validate;
50 	rdl->complete = complete;
51 	rdl->write_char = write_char;
52 	rdl->opaque = opaque;
53 	rdl->status = RDLINE_INIT;
54 	return cirbuf_init(&rdl->history, rdl->history_buf, 0, RDLINE_HISTORY_BUF_SIZE);
55 }
56 
57 struct rdline *
58 rdline_new(rdline_write_char_t *write_char,
59 	   rdline_validate_t *validate,
60 	   rdline_complete_t *complete,
61 	   void *opaque)
62 {
63 	struct rdline *rdl;
64 
65 	rdl = malloc(sizeof(*rdl));
66 	if (rdline_init(rdl, write_char, validate, complete, opaque) < 0) {
67 		free(rdl);
68 		rdl = NULL;
69 	}
70 	return rdl;
71 }
72 
73 void
74 rdline_free(struct rdline *rdl)
75 {
76 	free(rdl);
77 }
78 
79 void
80 rdline_newline(struct rdline *rdl, const char *prompt)
81 {
82 	unsigned int i;
83 
84 	if (!rdl || !prompt)
85 		return;
86 
87 	vt100_init(&rdl->vt100);
88 	cirbuf_init(&rdl->left, rdl->left_buf, 0, RDLINE_BUF_SIZE);
89 	cirbuf_init(&rdl->right, rdl->right_buf, 0, RDLINE_BUF_SIZE);
90 
91 	rdl->prompt_size = strnlen(prompt, RDLINE_PROMPT_SIZE-1);
92 	if (prompt != rdl->prompt)
93 		memcpy(rdl->prompt, prompt, rdl->prompt_size);
94 	rdl->prompt[RDLINE_PROMPT_SIZE-1] = '\0';
95 
96 	for (i=0 ; i<rdl->prompt_size ; i++)
97 		rdl->write_char(rdl, rdl->prompt[i]);
98 	rdl->status = RDLINE_RUNNING;
99 
100 	rdl->history_cur_line = -1;
101 }
102 
103 void
104 rdline_stop(struct rdline *rdl)
105 {
106 	if (!rdl)
107 		return;
108 	rdl->status = RDLINE_INIT;
109 }
110 
111 void
112 rdline_quit(struct rdline *rdl)
113 {
114 	if (!rdl)
115 		return;
116 	rdl->status = RDLINE_EXITED;
117 }
118 
119 void
120 rdline_restart(struct rdline *rdl)
121 {
122 	if (!rdl)
123 		return;
124 	rdl->status = RDLINE_RUNNING;
125 }
126 
127 void
128 rdline_reset(struct rdline *rdl)
129 {
130 	if (!rdl)
131 		return;
132 	vt100_init(&rdl->vt100);
133 	cirbuf_init(&rdl->left, rdl->left_buf, 0, RDLINE_BUF_SIZE);
134 	cirbuf_init(&rdl->right, rdl->right_buf, 0, RDLINE_BUF_SIZE);
135 
136 	rdl->status = RDLINE_RUNNING;
137 
138 	rdl->history_cur_line = -1;
139 }
140 
141 const char *
142 rdline_get_buffer(struct rdline *rdl)
143 {
144 	if (!rdl)
145 		return NULL;
146 	unsigned int len_l, len_r;
147 	cirbuf_align_left(&rdl->left);
148 	cirbuf_align_left(&rdl->right);
149 
150 	len_l = CIRBUF_GET_LEN(&rdl->left);
151 	len_r = CIRBUF_GET_LEN(&rdl->right);
152 	memcpy(rdl->left_buf+len_l, rdl->right_buf, len_r);
153 
154 	rdl->left_buf[len_l + len_r] = '\n';
155 	rdl->left_buf[len_l + len_r + 1] = '\0';
156 	return rdl->left_buf;
157 }
158 
159 static void
160 display_right_buffer(struct rdline *rdl, int force)
161 {
162 	unsigned int i;
163 	char tmp;
164 
165 	if (!force && CIRBUF_IS_EMPTY(&rdl->right))
166 		return;
167 
168 	rdline_puts(rdl, vt100_clear_right);
169 	CIRBUF_FOREACH(&rdl->right, i, tmp) {
170 		rdl->write_char(rdl, tmp);
171 	}
172 	if (!CIRBUF_IS_EMPTY(&rdl->right))
173 		rdline_miniprintf(rdl, vt100_multi_left,
174 				  CIRBUF_GET_LEN(&rdl->right));
175 }
176 
177 void
178 rdline_redisplay(struct rdline *rdl)
179 {
180 	unsigned int i;
181 	char tmp;
182 
183 	if (!rdl)
184 		return;
185 
186 	rdline_puts(rdl, vt100_home);
187 	for (i=0 ; i<rdl->prompt_size ; i++)
188 		rdl->write_char(rdl, rdl->prompt[i]);
189 	CIRBUF_FOREACH(&rdl->left, i, tmp) {
190 		rdl->write_char(rdl, tmp);
191 	}
192 	display_right_buffer(rdl, 1);
193 }
194 
195 int
196 rdline_char_in(struct rdline *rdl, char c)
197 {
198 	unsigned int i;
199 	int cmd;
200 	char tmp;
201 	char *buf;
202 
203 	if (!rdl)
204 		return -EINVAL;
205 
206 	if (rdl->status == RDLINE_EXITED)
207 		return RDLINE_RES_EXITED;
208 	if (rdl->status != RDLINE_RUNNING)
209 		return RDLINE_RES_NOT_RUNNING;
210 
211 	cmd = vt100_parser(&rdl->vt100, c);
212 	if (cmd == -2)
213 		return RDLINE_RES_SUCCESS;
214 
215 	if (cmd >= 0) {
216 		switch (cmd) {
217 		/* move caret 1 char to the left */
218 		case CMDLINE_KEY_CTRL_B:
219 		case CMDLINE_KEY_LEFT_ARR:
220 			if (CIRBUF_IS_EMPTY(&rdl->left))
221 				break;
222 			tmp = cirbuf_get_tail(&rdl->left);
223 			cirbuf_del_tail(&rdl->left);
224 			cirbuf_add_head(&rdl->right, tmp);
225 			rdline_puts(rdl, vt100_left_arr);
226 			break;
227 
228 		/* move caret 1 char to the right */
229 		case CMDLINE_KEY_CTRL_F:
230 		case CMDLINE_KEY_RIGHT_ARR:
231 			if (CIRBUF_IS_EMPTY(&rdl->right))
232 				break;
233 			tmp = cirbuf_get_head(&rdl->right);
234 			cirbuf_del_head(&rdl->right);
235 			cirbuf_add_tail(&rdl->left, tmp);
236 			rdline_puts(rdl, vt100_right_arr);
237 			break;
238 
239 		/* move caret 1 word to the left */
240 		/* keyboard equivalent: Alt+B */
241 		case CMDLINE_KEY_WLEFT:
242 			while (! CIRBUF_IS_EMPTY(&rdl->left) &&
243 			       (tmp = cirbuf_get_tail(&rdl->left)) &&
244 			       isblank2(tmp)) {
245 				rdline_puts(rdl, vt100_left_arr);
246 				cirbuf_del_tail(&rdl->left);
247 				cirbuf_add_head(&rdl->right, tmp);
248 			}
249 			while (! CIRBUF_IS_EMPTY(&rdl->left) &&
250 			       (tmp = cirbuf_get_tail(&rdl->left)) &&
251 			       !isblank2(tmp)) {
252 				rdline_puts(rdl, vt100_left_arr);
253 				cirbuf_del_tail(&rdl->left);
254 				cirbuf_add_head(&rdl->right, tmp);
255 			}
256 			break;
257 
258 		/* move caret 1 word to the right */
259 		/* keyboard equivalent: Alt+F */
260 		case CMDLINE_KEY_WRIGHT:
261 			while (! CIRBUF_IS_EMPTY(&rdl->right) &&
262 			       (tmp = cirbuf_get_head(&rdl->right)) &&
263 			       isblank2(tmp)) {
264 				rdline_puts(rdl, vt100_right_arr);
265 				cirbuf_del_head(&rdl->right);
266 				cirbuf_add_tail(&rdl->left, tmp);
267 			}
268 			while (! CIRBUF_IS_EMPTY(&rdl->right) &&
269 			       (tmp = cirbuf_get_head(&rdl->right)) &&
270 			       !isblank2(tmp)) {
271 				rdline_puts(rdl, vt100_right_arr);
272 				cirbuf_del_head(&rdl->right);
273 				cirbuf_add_tail(&rdl->left, tmp);
274 			}
275 			break;
276 
277 		/* move caret to the left */
278 		case CMDLINE_KEY_CTRL_A:
279 			if (CIRBUF_IS_EMPTY(&rdl->left))
280 				break;
281 			rdline_miniprintf(rdl, vt100_multi_left,
282 						CIRBUF_GET_LEN(&rdl->left));
283 			while (! CIRBUF_IS_EMPTY(&rdl->left)) {
284 				tmp = cirbuf_get_tail(&rdl->left);
285 				cirbuf_del_tail(&rdl->left);
286 				cirbuf_add_head(&rdl->right, tmp);
287 			}
288 			break;
289 
290 		/* move caret to the right */
291 		case CMDLINE_KEY_CTRL_E:
292 			if (CIRBUF_IS_EMPTY(&rdl->right))
293 				break;
294 			rdline_miniprintf(rdl, vt100_multi_right,
295 						CIRBUF_GET_LEN(&rdl->right));
296 			while (! CIRBUF_IS_EMPTY(&rdl->right)) {
297 				tmp = cirbuf_get_head(&rdl->right);
298 				cirbuf_del_head(&rdl->right);
299 				cirbuf_add_tail(&rdl->left, tmp);
300 			}
301 			break;
302 
303 		/* delete 1 char from the left */
304 		case CMDLINE_KEY_BKSPACE:
305 		case CMDLINE_KEY_BKSPACE2:
306 			if(!cirbuf_del_tail_safe(&rdl->left)) {
307 				rdline_puts(rdl, vt100_bs);
308 				display_right_buffer(rdl, 1);
309 			}
310 			break;
311 
312 		/* delete 1 char from the right */
313 		case CMDLINE_KEY_SUPPR:
314 		case CMDLINE_KEY_CTRL_D:
315 			if (cmd == CMDLINE_KEY_CTRL_D &&
316 			    CIRBUF_IS_EMPTY(&rdl->left) &&
317 			    CIRBUF_IS_EMPTY(&rdl->right)) {
318 				return RDLINE_RES_EOF;
319 			}
320 			if (!cirbuf_del_head_safe(&rdl->right)) {
321 				display_right_buffer(rdl, 1);
322 			}
323 			break;
324 
325 		/* delete 1 word from the left */
326 		case CMDLINE_KEY_META_BKSPACE:
327 		case CMDLINE_KEY_CTRL_W:
328 			while (! CIRBUF_IS_EMPTY(&rdl->left) && isblank2(cirbuf_get_tail(&rdl->left))) {
329 				rdline_puts(rdl, vt100_bs);
330 				cirbuf_del_tail(&rdl->left);
331 			}
332 			while (! CIRBUF_IS_EMPTY(&rdl->left) && !isblank2(cirbuf_get_tail(&rdl->left))) {
333 				rdline_puts(rdl, vt100_bs);
334 				cirbuf_del_tail(&rdl->left);
335 			}
336 			display_right_buffer(rdl, 1);
337 			break;
338 
339 		/* delete 1 word from the right */
340 		case CMDLINE_KEY_META_D:
341 			while (! CIRBUF_IS_EMPTY(&rdl->right) && isblank2(cirbuf_get_head(&rdl->right)))
342 				cirbuf_del_head(&rdl->right);
343 			while (! CIRBUF_IS_EMPTY(&rdl->right) && !isblank2(cirbuf_get_head(&rdl->right)))
344 				cirbuf_del_head(&rdl->right);
345 			display_right_buffer(rdl, 1);
346 			break;
347 
348 		/* set kill buffer to contents on the right side of caret */
349 		case CMDLINE_KEY_CTRL_K:
350 			cirbuf_get_buf_head(&rdl->right, rdl->kill_buf, RDLINE_BUF_SIZE);
351 			rdl->kill_size = CIRBUF_GET_LEN(&rdl->right);
352 			cirbuf_del_buf_head(&rdl->right, rdl->kill_size);
353 			rdline_puts(rdl, vt100_clear_right);
354 			break;
355 
356 		/* paste contents of kill buffer to the left side of caret */
357 		case CMDLINE_KEY_CTRL_Y:
358 			i=0;
359 			while(CIRBUF_GET_LEN(&rdl->right) + CIRBUF_GET_LEN(&rdl->left) <
360 			      RDLINE_BUF_SIZE &&
361 			      i < rdl->kill_size) {
362 				cirbuf_add_tail(&rdl->left, rdl->kill_buf[i]);
363 				rdl->write_char(rdl, rdl->kill_buf[i]);
364 				i++;
365 			}
366 			display_right_buffer(rdl, 0);
367 			break;
368 
369 		/* clear and newline */
370 		case CMDLINE_KEY_CTRL_C:
371 			rdline_puts(rdl, "\r\n");
372 			rdline_newline(rdl, rdl->prompt);
373 			break;
374 
375 		/* redisplay (helps when prompt is lost in other output) */
376 		case CMDLINE_KEY_CTRL_L:
377 			rdline_redisplay(rdl);
378 			break;
379 
380 		/* autocomplete */
381 		case CMDLINE_KEY_TAB:
382 		case CMDLINE_KEY_HELP:
383 			cirbuf_align_left(&rdl->left);
384 			rdl->left_buf[CIRBUF_GET_LEN(&rdl->left)] = '\0';
385 			if (rdl->complete) {
386 				char tmp_buf[BUFSIZ];
387 				int complete_state;
388 				int ret;
389 				unsigned int tmp_size;
390 
391 				if (cmd == CMDLINE_KEY_TAB)
392 					complete_state = 0;
393 				else
394 					complete_state = -1;
395 
396 				/* see in parse.h for help on complete() */
397 				ret = rdl->complete(rdl, rdl->left_buf,
398 						    tmp_buf, sizeof(tmp_buf),
399 						    &complete_state);
400 				/* no completion or error */
401 				if (ret <= 0) {
402 					return RDLINE_RES_COMPLETE;
403 				}
404 
405 				tmp_size = strnlen(tmp_buf, sizeof(tmp_buf));
406 				/* add chars */
407 				if (ret == RDLINE_RES_COMPLETE) {
408 					i=0;
409 					while(CIRBUF_GET_LEN(&rdl->right) + CIRBUF_GET_LEN(&rdl->left) <
410 					      RDLINE_BUF_SIZE &&
411 					      i < tmp_size) {
412 						cirbuf_add_tail(&rdl->left, tmp_buf[i]);
413 						rdl->write_char(rdl, tmp_buf[i]);
414 						i++;
415 					}
416 					display_right_buffer(rdl, 1);
417 					return RDLINE_RES_COMPLETE; /* ?? */
418 				}
419 
420 				/* choice */
421 				rdline_puts(rdl, "\r\n");
422 				while (ret) {
423 					rdl->write_char(rdl, ' ');
424 					for (i=0 ; tmp_buf[i] ; i++)
425 						rdl->write_char(rdl, tmp_buf[i]);
426 					rdline_puts(rdl, "\r\n");
427 					ret = rdl->complete(rdl, rdl->left_buf,
428 							    tmp_buf, sizeof(tmp_buf),
429 							    &complete_state);
430 				}
431 
432 				rdline_redisplay(rdl);
433 			}
434 			return RDLINE_RES_COMPLETE;
435 
436 		/* complete buffer */
437 		case CMDLINE_KEY_RETURN:
438 		case CMDLINE_KEY_RETURN2:
439 			rdline_get_buffer(rdl);
440 			rdl->status = RDLINE_INIT;
441 			rdline_puts(rdl, "\r\n");
442 			if (rdl->history_cur_line != -1)
443 				rdline_remove_first_history_item(rdl);
444 
445 			if (rdl->validate)
446 				rdl->validate(rdl, rdl->left_buf, CIRBUF_GET_LEN(&rdl->left)+2);
447 			/* user may have stopped rdline */
448 			if (rdl->status == RDLINE_EXITED)
449 				return RDLINE_RES_EXITED;
450 			return RDLINE_RES_VALIDATED;
451 
452 		/* previous element in history */
453 		case CMDLINE_KEY_UP_ARR:
454 		case CMDLINE_KEY_CTRL_P:
455 			if (rdl->history_cur_line == 0) {
456 				rdline_remove_first_history_item(rdl);
457 			}
458 			if (rdl->history_cur_line <= 0) {
459 				rdline_add_history(rdl, rdline_get_buffer(rdl));
460 				rdl->history_cur_line = 0;
461 			}
462 
463 			buf = rdline_get_history_item(rdl, rdl->history_cur_line + 1);
464 			if (!buf)
465 				break;
466 
467 			rdl->history_cur_line ++;
468 			vt100_init(&rdl->vt100);
469 			cirbuf_init(&rdl->left, rdl->left_buf, 0, RDLINE_BUF_SIZE);
470 			cirbuf_init(&rdl->right, rdl->right_buf, 0, RDLINE_BUF_SIZE);
471 			cirbuf_add_buf_tail(&rdl->left, buf, strnlen(buf, RDLINE_BUF_SIZE));
472 			rdline_redisplay(rdl);
473 			break;
474 
475 		/* next element in history */
476 		case CMDLINE_KEY_DOWN_ARR:
477 		case CMDLINE_KEY_CTRL_N:
478 			if (rdl->history_cur_line - 1 < 0)
479 				break;
480 
481 			rdl->history_cur_line --;
482 			buf = rdline_get_history_item(rdl, rdl->history_cur_line);
483 			if (!buf)
484 				break;
485 			vt100_init(&rdl->vt100);
486 			cirbuf_init(&rdl->left, rdl->left_buf, 0, RDLINE_BUF_SIZE);
487 			cirbuf_init(&rdl->right, rdl->right_buf, 0, RDLINE_BUF_SIZE);
488 			cirbuf_add_buf_tail(&rdl->left, buf, strnlen(buf, RDLINE_BUF_SIZE));
489 			rdline_redisplay(rdl);
490 
491 			break;
492 
493 
494 		default:
495 			break;
496 		}
497 
498 		return RDLINE_RES_SUCCESS;
499 	}
500 
501 	if (!isprint((int)c))
502 		return RDLINE_RES_SUCCESS;
503 
504 	/* standard chars */
505 	if (CIRBUF_GET_LEN(&rdl->left) + CIRBUF_GET_LEN(&rdl->right) >= RDLINE_BUF_SIZE)
506 		return RDLINE_RES_SUCCESS;
507 
508 	if (cirbuf_add_tail_safe(&rdl->left, c))
509 		return RDLINE_RES_SUCCESS;
510 
511 	rdl->write_char(rdl, c);
512 	display_right_buffer(rdl, 0);
513 
514 	return RDLINE_RES_SUCCESS;
515 }
516 
517 
518 /* HISTORY */
519 
520 static void
521 rdline_remove_old_history_item(struct rdline * rdl)
522 {
523 	char tmp;
524 
525 	while (! CIRBUF_IS_EMPTY(&rdl->history) ) {
526 		tmp = cirbuf_get_head(&rdl->history);
527 		cirbuf_del_head(&rdl->history);
528 		if (!tmp)
529 			break;
530 	}
531 }
532 
533 static void
534 rdline_remove_first_history_item(struct rdline * rdl)
535 {
536 	char tmp;
537 
538 	if ( CIRBUF_IS_EMPTY(&rdl->history) ) {
539 		return;
540 	}
541 	else {
542 		cirbuf_del_tail(&rdl->history);
543 	}
544 
545 	while (! CIRBUF_IS_EMPTY(&rdl->history) ) {
546 		tmp = cirbuf_get_tail(&rdl->history);
547 		if (!tmp)
548 			break;
549 		cirbuf_del_tail(&rdl->history);
550 	}
551 }
552 
553 static unsigned int
554 rdline_get_history_size(struct rdline * rdl)
555 {
556 	unsigned int i, tmp, ret=0;
557 
558 	CIRBUF_FOREACH(&rdl->history, i, tmp) {
559 		if (tmp == 0)
560 			ret ++;
561 	}
562 
563 	return ret;
564 }
565 
566 char *
567 rdline_get_history_item(struct rdline * rdl, unsigned int idx)
568 {
569 	unsigned int len, i, tmp;
570 
571 	if (!rdl)
572 		return NULL;
573 
574 	len = rdline_get_history_size(rdl);
575 	if ( idx >= len ) {
576 		return NULL;
577 	}
578 
579 	cirbuf_align_left(&rdl->history);
580 
581 	CIRBUF_FOREACH(&rdl->history, i, tmp) {
582 		if ( idx == len - 1) {
583 			return rdl->history_buf + i;
584 		}
585 		if (tmp == 0)
586 			len --;
587 	}
588 
589 	return NULL;
590 }
591 
592 size_t
593 rdline_get_history_buffer_size(struct rdline *rdl)
594 {
595 	return sizeof(rdl->history_buf);
596 }
597 
598 void *
599 rdline_get_opaque(struct rdline *rdl)
600 {
601 	return rdl != NULL ? rdl->opaque : NULL;
602 }
603 
604 int
605 rdline_add_history(struct rdline * rdl, const char * buf)
606 {
607 	unsigned int len, i;
608 
609 	if (!rdl || !buf)
610 		return -EINVAL;
611 
612 	len = strnlen(buf, RDLINE_BUF_SIZE);
613 	for (i=0; i<len ; i++) {
614 		if (buf[i] == '\n') {
615 			len = i;
616 			break;
617 		}
618 	}
619 
620 	if ( len >= RDLINE_HISTORY_BUF_SIZE )
621 		return -1;
622 
623 	while ( len >= CIRBUF_GET_FREELEN(&rdl->history) ) {
624 		rdline_remove_old_history_item(rdl);
625 	}
626 
627 	cirbuf_add_buf_tail(&rdl->history, buf, len);
628 	cirbuf_add_tail(&rdl->history, 0);
629 
630 	return 0;
631 }
632 
633 void
634 rdline_clear_history(struct rdline * rdl)
635 {
636 	if (!rdl)
637 		return;
638 	cirbuf_init(&rdl->history, rdl->history_buf, 0, RDLINE_HISTORY_BUF_SIZE);
639 }
640 
641 
642 /* STATIC USEFUL FUNCS */
643 
644 static void
645 rdline_puts(struct rdline * rdl, const char * buf)
646 {
647 	char c;
648 	while ( (c = *(buf++)) != '\0' ) {
649 		rdl->write_char(rdl, c);
650 	}
651 }
652 
653 /* a very very basic printf with one arg and one format 'u' */
654 static void
655 rdline_miniprintf(struct rdline *rdl, const char * buf, unsigned int val)
656 {
657 	char c, started=0, div=100;
658 
659 	while ( (c=*(buf++)) ) {
660 		if (c != '%') {
661 			rdl->write_char(rdl, c);
662 			continue;
663 		}
664 		c = *(buf++);
665 		if (c != 'u') {
666 			rdl->write_char(rdl, '%');
667 			rdl->write_char(rdl, c);
668 			continue;
669 		}
670 		/* val is never more than 255 */
671 		while (div) {
672 			c = (char)(val / div);
673 			if (c || started) {
674 				rdl->write_char(rdl, (char)(c+'0'));
675 				started = 1;
676 			}
677 			val %= div;
678 			div /= 10;
679 		}
680 	}
681 }
682