xref: /netbsd-src/sys/kern/subr_userconf.c (revision df0caa2637da0538ecdf6b878c4d08e684b43d8f)
1 /*	$NetBSD: subr_userconf.c,v 1.15 2005/06/23 18:44:44 thorpej Exp $	*/
2 
3 /*
4  * Copyright (c) 1996 Mats O Jansson <moj@stacken.kth.se>
5  * All rights reserved.
6  *
7  * Redistribution and use in source and binary forms, with or without
8  * modification, are permitted provided that the following conditions
9  * are met:
10  * 1. Redistributions of source code must retain the above copyright
11  *    notice, this list of conditions and the following disclaimer.
12  * 2. Redistributions in binary form must reproduce the above copyright
13  *    notice, this list of conditions and the following disclaimer in the
14  *    documentation and/or other materials provided with the distribution.
15  * 3. All advertising materials mentioning features or use of this software
16  *    must display the following acknowledgement:
17  *	This product includes software developed by Mats O Jansson.
18  * 4. The name of the author may not be used to endorse or promote
19  *    products derived from this software without specific prior written
20  *    permission.
21  *
22  * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS
23  * OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
24  * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
25  * ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY
26  * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
27  * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
28  * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
29  * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
30  * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
31  * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
32  * SUCH DAMAGE.
33  *
34  *	OpenBSD: subr_userconf.c,v 1.19 2000/01/08 23:23:37 d Exp
35  */
36 
37 #include <sys/cdefs.h>
38 __KERNEL_RCSID(0, "$NetBSD: subr_userconf.c,v 1.15 2005/06/23 18:44:44 thorpej Exp $");
39 
40 #include "opt_userconf.h"
41 
42 #include <sys/param.h>
43 #include <sys/systm.h>
44 #include <sys/device.h>
45 #include <sys/malloc.h>
46 #include <sys/time.h>
47 
48 #include <dev/cons.h>
49 
50 extern struct cfdata cfdata[];
51 
52 static int userconf_base = 16;			/* Base for "large" numbers */
53 static int userconf_maxdev = -1;		/* # of used device slots   */
54 static int userconf_totdev = -1;		/* # of device slots        */
55 #if 0
56 static int userconf_maxlocnames = -1;		/* # of locnames            */
57 #endif
58 static int userconf_cnt = -1;			/* Line counter for ...     */
59 static int userconf_lines = 12;			/* ... # of lines per page  */
60 static int userconf_histlen = 0;
61 static int userconf_histcur = 0;
62 static char userconf_history[1024];
63 static int userconf_histsz = sizeof(userconf_history);
64 static char userconf_argbuf[40];		/* Additional input         */
65 static char userconf_cmdbuf[40];		/* Command line             */
66 static char userconf_histbuf[40];
67 
68 static int getsn(char *, int);
69 
70 #define UC_CHANGE 'c'
71 #define UC_DISABLE 'd'
72 #define UC_ENABLE 'e'
73 #define UC_FIND 'f'
74 #define UC_SHOW 's'
75 
76 static const char *userconf_cmds[] = {
77 	"base",		"b",
78 	"change",	"c",
79 	"disable",	"d",
80 	"enable",	"e",
81 	"exit",		"q",
82 	"find",		"f",
83 	"help",		"h",
84 	"list",		"l",
85 	"lines",	"L",
86 	"quit",		"q",
87 	"?",		"h",
88 	"",		 "",
89 };
90 
91 static void
92 userconf_init(void)
93 {
94 	int i;
95 	struct cfdata *cf;
96 
97 	i = 0;
98 	for (cf = cfdata; cf->cf_name; cf++)
99 		i++;
100 
101 	userconf_maxdev = i - 1;
102 	userconf_totdev = i - 1;
103 }
104 
105 static int
106 userconf_more(void)
107 {
108 	int quit = 0;
109 	char c = '\0';
110 
111 	if (userconf_cnt != -1) {
112 		if (userconf_cnt == userconf_lines) {
113 			printf("-- more --");
114 			c = cngetc();
115 			userconf_cnt = 0;
116 			printf("\r            \r");
117 		}
118 		userconf_cnt++;
119 		if (c == 'q' || c == 'Q')
120 			quit = 1;
121 	}
122 	return (quit);
123 }
124 
125 static void
126 userconf_hist_cmd(char cmd)
127 {
128 	userconf_histcur = userconf_histlen;
129 	if (userconf_histcur < userconf_histsz) {
130 		userconf_history[userconf_histcur] = cmd;
131 		userconf_histcur++;
132 	}
133 }
134 
135 static void
136 userconf_hist_int(int val)
137 {
138 	snprintf(userconf_histbuf, sizeof(userconf_histbuf), " %d", val);
139 	if ((userconf_histcur + strlen(userconf_histbuf)) < userconf_histsz) {
140 		memcpy(&userconf_history[userconf_histcur],
141 		      userconf_histbuf,
142 		      strlen(userconf_histbuf));
143 		userconf_histcur = userconf_histcur + strlen(userconf_histbuf);
144 	}
145 }
146 
147 static void
148 userconf_hist_eoc(void)
149 {
150 	if (userconf_histcur < userconf_histsz) {
151 		userconf_history[userconf_histcur] = '\n';
152 		userconf_histcur++;
153 		userconf_histlen = userconf_histcur;
154 	}
155 }
156 
157 static void
158 userconf_pnum(int val)
159 {
160 	if (val > -2 && val < 16) {
161 		printf("%d",val);
162 	} else {
163 		switch (userconf_base) {
164 		case 8:
165 			printf("0%o",val);
166 			break;
167 		case 10:
168 			printf("%d",val);
169 			break;
170 		case 16:
171 		default:
172 			printf("0x%x",val);
173 			break;
174 		}
175 	}
176 }
177 
178 static void
179 userconf_pdevnam(short dev)
180 {
181 	struct cfdata *cd;
182 
183 	cd = &cfdata[dev];
184 	printf("%s", cd->cf_name);
185 	switch (cd->cf_fstate) {
186 	case FSTATE_NOTFOUND:
187 	case FSTATE_DNOTFOUND:
188 		printf("%d", cd->cf_unit);
189 		break;
190 	case FSTATE_FOUND:
191 		printf("*FOUND*");
192 		break;
193 	case FSTATE_STAR:
194 	case FSTATE_DSTAR:
195 		printf("*");
196 		break;
197 	default:
198 		printf("*UNKNOWN*");
199 		break;
200 	}
201 }
202 
203 static void
204 userconf_pdev(short devno)
205 {
206 	struct cfdata *cd;
207 	const struct cfparent *cfp;
208 	int   *l;
209 	const char * const *ln;
210 
211 	if (devno > userconf_maxdev) {
212 		printf("Unknown devno (max is %d)\n", userconf_maxdev);
213 		return;
214 	}
215 
216 	cd = &cfdata[devno];
217 
218 	printf("[%3d] ", devno);
219 	userconf_pdevnam(devno);
220 	printf(" at");
221 	cfp = cd->cf_pspec;
222 	if (cfp == NULL)
223 		printf(" root");
224 	else if (cfp->cfp_parent != NULL && cfp->cfp_unit != -1)
225 		printf(" %s%d", cfp->cfp_parent, cfp->cfp_unit);
226 	else
227 		printf(" %s?", cfp->cfp_parent != NULL ? cfp->cfp_parent
228 						       : cfp->cfp_iattr);
229 	switch (cd->cf_fstate) {
230 	case FSTATE_NOTFOUND:
231 	case FSTATE_FOUND:
232 	case FSTATE_STAR:
233 		break;
234 	case FSTATE_DNOTFOUND:
235 	case FSTATE_DSTAR:
236 		printf(" disable");
237 		break;
238 	default:
239 		printf(" ???");
240 		break;
241 	}
242 	l = cd->cf_loc;
243 	ln = cd->cf_locnames;
244 	while (ln && *ln) {
245 		printf(" %s ", *ln++);
246 		userconf_pnum(*l++);
247 	}
248 	printf("\n");
249 }
250 
251 static int
252 userconf_number(char *c, int *val)
253 {
254 	u_int num = 0;
255 	int neg = 0;
256 	int base = 10;
257 
258 	if (*c == '-') {
259 		neg = 1;
260 		c++;
261 	}
262 	if (*c == '0') {
263 		base = 8;
264 		c++;
265 		if (*c == 'x' || *c == 'X') {
266 			base = 16;
267 			c++;
268 		}
269 	}
270 	while (*c != '\n' && *c != '\t' && *c != ' ' && *c != '\0') {
271 		u_char cc = *c;
272 
273 		if (cc >= '0' && cc <= '9')
274 			cc = cc - '0';
275 		else if (cc >= 'a' && cc <= 'f')
276 			cc = cc - 'a' + 10;
277 		else if (cc >= 'A' && cc <= 'F')
278 			cc = cc - 'A' + 10;
279 		else
280 			return (-1);
281 
282 		if (cc > base)
283 			return (-1);
284 		num = num * base + cc;
285 		c++;
286 	}
287 
288 	if (neg && num > INT_MAX)	/* overflow */
289 		return (1);
290 	*val = neg ? - num : num;
291 	return (0);
292 }
293 
294 static int
295 userconf_device(char *cmd, int *len, short *unit, short *state)
296 {
297 	short u = 0, s = FSTATE_FOUND;
298 	int l = 0;
299 	char *c;
300 
301 	c = cmd;
302 	while (*c >= 'a' && *c <= 'z') {
303 		l++;
304 		c++;
305 	}
306 	if (*c == '*') {
307 		s = FSTATE_STAR;
308 		c++;
309 	} else {
310 		while (*c >= '0' && *c <= '9') {
311 			s = FSTATE_NOTFOUND;
312 			u = u*10 + *c - '0';
313 			c++;
314 		}
315 	}
316 	while (*c == ' ' || *c == '\t' || *c == '\n')
317 		c++;
318 
319 	if (*c == '\0') {
320 		*len = l;
321 		*unit = u;
322 		*state = s;
323 		return(0);
324 	}
325 
326 	return(-1);
327 }
328 
329 static void
330 userconf_modify(const char *item, int *val)
331 {
332 	int ok = 0;
333 	int a;
334 	char *c;
335 
336 	while (!ok) {
337 		printf("%s [", item);
338 		userconf_pnum(*val);
339 		printf("] ? ");
340 
341 		getsn(userconf_argbuf, sizeof(userconf_argbuf));
342 
343 		c = userconf_argbuf;
344 		while (*c == ' ' || *c == '\t' || *c == '\n') c++;
345 
346 		if (*c != '\0') {
347 			if (userconf_number(c, &a) == 0) {
348 				*val = a;
349 				ok = 1;
350 			} else {
351 				printf("Unknown argument\n");
352 			}
353 		} else {
354 			ok = 1;
355 		}
356 	}
357 }
358 
359 static void
360 userconf_change(int devno)
361 {
362 	struct cfdata *cd;
363 	char c = '\0';
364 	int   *l;
365 	int   ln;
366 	const char * const *locnames;
367 
368 	if (devno <=  userconf_maxdev) {
369 
370 		userconf_pdev(devno);
371 
372 		while (c != 'y' && c != 'Y' && c != 'n' && c != 'N') {
373 			printf("change (y/n) ?");
374 			c = cngetc();
375 			printf("\n");
376 		}
377 
378 		if (c == 'y' || c == 'Y') {
379 
380 			/* XXX add cmd 'c' <devno> */
381 			userconf_hist_cmd('c');
382 			userconf_hist_int(devno);
383 
384 			cd = &cfdata[devno];
385 			l = cd->cf_loc;
386 			locnames = cd->cf_locnames;
387 			ln = 0;
388 
389 			while (locnames[ln])
390 			{
391 				userconf_modify(locnames[ln], l);
392 
393 				/* XXX add *l */
394 				userconf_hist_int(*l);
395 
396 				ln++;
397 				l++;
398 			}
399 
400 			printf("[%3d] ", devno);
401 			userconf_pdevnam(devno);
402 			printf(" changed\n");
403 			userconf_pdev(devno);
404 
405 			/* XXX add eoc */
406 			userconf_hist_eoc();
407 
408 		}
409 	} else {
410 		printf("Unknown devno (max is %d)\n", userconf_maxdev);
411 	}
412 }
413 
414 static void
415 userconf_disable(int devno)
416 {
417 	int done = 0;
418 
419 	if (devno <= userconf_maxdev) {
420 		switch (cfdata[devno].cf_fstate) {
421 		case FSTATE_NOTFOUND:
422 			cfdata[devno].cf_fstate = FSTATE_DNOTFOUND;
423 			break;
424 		case FSTATE_STAR:
425 			cfdata[devno].cf_fstate = FSTATE_DSTAR;
426 			break;
427 		case FSTATE_DNOTFOUND:
428 		case FSTATE_DSTAR:
429 			done = 1;
430 			break;
431 		default:
432 			printf("Error unknown state\n");
433 			break;
434 		}
435 
436 		printf("[%3d] ", devno);
437 		userconf_pdevnam(devno);
438 		if (done) {
439 			printf(" already");
440 		} else {
441 			/* XXX add cmd 'd' <devno> eoc */
442 			userconf_hist_cmd('d');
443 			userconf_hist_int(devno);
444 			userconf_hist_eoc();
445 		}
446 		printf(" disabled\n");
447 	} else {
448 		printf("Unknown devno (max is %d)\n", userconf_maxdev);
449 	}
450 }
451 
452 static void
453 userconf_enable(int devno)
454 {
455 	int done = 0;
456 
457 	if (devno <= userconf_maxdev) {
458 		switch (cfdata[devno].cf_fstate) {
459 		case FSTATE_DNOTFOUND:
460 			cfdata[devno].cf_fstate = FSTATE_NOTFOUND;
461 			break;
462 		case FSTATE_DSTAR:
463 			cfdata[devno].cf_fstate = FSTATE_STAR;
464 			break;
465 		case FSTATE_NOTFOUND:
466 		case FSTATE_STAR:
467 			done = 1;
468 			break;
469 		default:
470 			printf("Error unknown state\n");
471 			break;
472 		}
473 
474 		printf("[%3d] ", devno);
475 		userconf_pdevnam(devno);
476 		if (done) {
477 			printf(" already");
478 		} else {
479 			/* XXX add cmd 'e' <devno> eoc */
480 			userconf_hist_cmd('d');
481 			userconf_hist_int(devno);
482 			userconf_hist_eoc();
483 		}
484 		printf(" enabled\n");
485 	} else {
486 		printf("Unknown devno (max is %d)\n", userconf_maxdev);
487 	}
488 }
489 
490 static void
491 userconf_help(void)
492 {
493 	int j = 0, k;
494 
495 	printf("command   args                description\n");
496 	while (*userconf_cmds[j] != '\0') {
497 		printf(userconf_cmds[j]);
498 		k = strlen(userconf_cmds[j]);
499 		while (k < 10) {
500 			printf(" ");
501 			k++;
502 		}
503 		switch (*userconf_cmds[j+1]) {
504 		case 'L':
505 			printf("[count]             number of lines before more");
506 			break;
507 		case 'b':
508 			printf("8|10|16             base on large numbers");
509 			break;
510 		case 'c':
511 			printf("devno|dev           change devices");
512 			break;
513 		case 'd':
514 			printf("devno|dev           disable devices");
515 			break;
516 		case 'e':
517 			printf("devno|dev           enable devices");
518 			break;
519 		case 'f':
520 			printf("devno|dev           find devices");
521 			break;
522 		case 'h':
523 			printf("                    this message");
524 			break;
525 		case 'l':
526 			printf("                    list configuration");
527 			break;
528 		case 'q':
529 			printf("                    leave userconf");
530 			break;
531 		default:
532 			printf("                    don't know");
533 			break;
534 		}
535 		printf("\n");
536 		j += 2;
537 	}
538 }
539 
540 static void
541 userconf_list(void)
542 {
543 	int i = 0;
544 
545 	userconf_cnt = 0;
546 
547 	while (cfdata[i].cf_name != NULL) {
548 		if (userconf_more())
549 			break;
550 		userconf_pdev(i++);
551 	}
552 
553 	userconf_cnt = -1;
554 }
555 
556 static void
557 userconf_common_dev(char *dev, int len, short unit, short state, char routine)
558 {
559 	int i = 0;
560 
561 	switch (routine) {
562 	case UC_CHANGE:
563 		break;
564 	default:
565 		userconf_cnt = 0;
566 		break;
567 	}
568 
569 	while (cfdata[i].cf_name != NULL) {
570 		if (strlen(cfdata[i].cf_name) == len) {
571 
572 			/*
573 			 * Ok, if device name is correct
574 			 *  If state == FSTATE_FOUND, look for "dev"
575 			 *  If state == FSTATE_STAR, look for "dev*"
576 			 *  If state == FSTATE_NOTFOUND, look for "dev0"
577 			 */
578 			if (strncasecmp(dev, cfdata[i].cf_name,
579 					len) == 0 &&
580 			    (state == FSTATE_FOUND ||
581 			     (state == FSTATE_STAR &&
582 			      (cfdata[i].cf_fstate == FSTATE_STAR ||
583 			       cfdata[i].cf_fstate == FSTATE_DSTAR)) ||
584 			     (state == FSTATE_NOTFOUND &&
585 			      cfdata[i].cf_unit == unit &&
586 			      (cfdata[i].cf_fstate == FSTATE_NOTFOUND ||
587 			       cfdata[i].cf_fstate == FSTATE_DNOTFOUND)))) {
588 				if (userconf_more())
589 					break;
590 				switch (routine) {
591 				case UC_CHANGE:
592 					userconf_change(i);
593 					break;
594 				case UC_ENABLE:
595 					userconf_enable(i);
596 					break;
597 				case UC_DISABLE:
598 					userconf_disable(i);
599 					break;
600 				case UC_FIND:
601 					userconf_pdev(i);
602 					break;
603 				default:
604 					printf("Unknown routine /%c/\n",
605 					    routine);
606 					break;
607 				}
608 			}
609 		}
610 		i++;
611 	}
612 
613 	switch (routine) {
614 	case UC_CHANGE:
615 		break;
616 	default:
617 		userconf_cnt = -1;
618 		break;
619 	}
620 }
621 
622 #if 0
623 static void
624 userconf_add_read(char *prompt, char field, char *dev, int len, int *val)
625 {
626 	int ok = 0;
627 	int a;
628 	char *c;
629 
630 	*val = -1;
631 
632 	while (!ok) {
633 		printf("%s ? ", prompt);
634 
635 		getsn(userconf_argbuf, sizeof(userconf_argbuf));
636 
637 		c = userconf_argbuf;
638 		while (*c == ' ' || *c == '\t' || *c == '\n') c++;
639 
640 		if (*c != '\0') {
641 			if (userconf_number(c, &a) == 0) {
642 				if (a > userconf_maxdev) {
643 					printf("Unknown devno (max is %d)\n",
644 					    userconf_maxdev);
645 				} else if (strncasecmp(dev,
646 				    cfdata[a].cf_name, len) != 0 &&
647 					field == 'a') {
648 					printf("Not same device type\n");
649 				} else {
650 					*val = a;
651 					ok = 1;
652 				}
653 			} else if (*c == '?') {
654 				userconf_common_dev(dev, len, 0,
655 				    FSTATE_FOUND, UC_FIND);
656 			} else if (*c == 'q' || *c == 'Q') {
657 				ok = 1;
658 			} else {
659 				printf("Unknown argument\n");
660 			}
661 		} else {
662 			ok = 1;
663 		}
664 	}
665 }
666 #endif /* 0 */
667 
668 static int
669 userconf_parse(char *cmd)
670 {
671 	char *c, *v;
672 	int i = 0, j = 0, k, a;
673 	short unit, state;
674 
675 	c = cmd;
676 	while (*c == ' ' || *c == '\t')
677 		c++;
678 	v = c;
679 	while (*c != ' ' && *c != '\t' && *c != '\n' && *c != '\0') {
680 		c++;
681 		i++;
682 	}
683 
684 	k = -1;
685 	while (*userconf_cmds[j] != '\0') {
686 		if (strlen(userconf_cmds[j]) == i) {
687 			if (strncasecmp(v, userconf_cmds[j], i) == 0)
688 				k = j;
689 		}
690 		j += 2;
691 	}
692 
693 	while (*c == ' ' || *c == '\t' || *c == '\n')
694 		c++;
695 
696 	if (k == -1) {
697 		if (*v != '\n')
698 			printf("Unknown command, try help\n");
699 	} else {
700 		switch (*userconf_cmds[k+1]) {
701 		case 'L':
702 			if (*c == '\0')
703 				printf("Argument expected\n");
704 			else if (userconf_number(c, &a) == 0)
705 				userconf_lines = a;
706 			else
707 				printf("Unknown argument\n");
708 			break;
709 		case 'b':
710 			if (*c == '\0')
711 				printf("8|10|16 expected\n");
712 			else if (userconf_number(c, &a) == 0) {
713 				if (a == 8 || a == 10 || a == 16) {
714 					userconf_base = a;
715 				} else {
716 					printf("8|10|16 expected\n");
717 				}
718 			} else
719 				printf("Unknown argument\n");
720 			break;
721 		case 'c':
722 			if (*c == '\0')
723 				printf("DevNo or Dev expected\n");
724 			else if (userconf_number(c, &a) == 0)
725 				userconf_change(a);
726 			else if (userconf_device(c, &a, &unit, &state) == 0)
727 				userconf_common_dev(c, a, unit, state, UC_CHANGE);
728 			else
729 				printf("Unknown argument\n");
730 			break;
731 		case 'd':
732 			if (*c == '\0')
733 				printf("Attr, DevNo or Dev expected\n");
734 			else if (userconf_number(c, &a) == 0)
735 				userconf_disable(a);
736 			else if (userconf_device(c, &a, &unit, &state) == 0)
737 				userconf_common_dev(c, a, unit, state, UC_DISABLE);
738 			else
739 				printf("Unknown argument\n");
740 			break;
741 		case 'e':
742 			if (*c == '\0')
743 				printf("Attr, DevNo or Dev expected\n");
744 			else if (userconf_number(c, &a) == 0)
745 				userconf_enable(a);
746 			else if (userconf_device(c, &a, &unit, &state) == 0)
747 				userconf_common_dev(c, a, unit, state, UC_ENABLE);
748 			else
749 				printf("Unknown argument\n");
750 			break;
751 		case 'f':
752 			if (*c == '\0')
753 				printf("DevNo or Dev expected\n");
754 			else if (userconf_number(c, &a) == 0)
755 				userconf_pdev(a);
756 			else if (userconf_device(c, &a, &unit, &state) == 0)
757 				userconf_common_dev(c, a, unit, state, UC_FIND);
758 			else
759 				printf("Unknown argument\n");
760 			break;
761 		case 'h':
762 			userconf_help();
763 			break;
764 		case 'l':
765 			if (*c == '\0')
766 				userconf_list();
767 			else
768 				printf("Unknown argument\n");
769 			break;
770 		case 'q':
771 			/* XXX add cmd 'q' eoc */
772 			userconf_hist_cmd('q');
773 			userconf_hist_eoc();
774 			return(-1);
775 		case 's':
776 		default:
777 			printf("Unknown command\n");
778 			break;
779 		}
780 	}
781 	return(0);
782 }
783 
784 extern void user_config(void);
785 
786 void
787 user_config(void)
788 {
789 	char prompt[] = "uc> ";
790 
791 	userconf_init();
792 	printf("userconf: configure system autoconfiguration:\n");
793 
794 	while (1) {
795 		printf(prompt);
796 		if (getsn(userconf_cmdbuf, sizeof(userconf_cmdbuf)) > 0 &&
797 		    userconf_parse(userconf_cmdbuf))
798 			break;
799 	}
800 	printf("Continuing...\n");
801 }
802 
803 /*
804  * XXX shouldn't this be a common function?
805  */
806 static int
807 getsn(char *cp, int size)
808 {
809 	char *lp;
810 	int c, len;
811 
812 	cnpollc(1);
813 
814 	lp = cp;
815 	len = 0;
816 	for (;;) {
817 		c = cngetc();
818 		switch (c) {
819 		case '\n':
820 		case '\r':
821 			printf("\n");
822 			*lp++ = '\0';
823 			cnpollc(0);
824 			return (len);
825 		case '\b':
826 		case '\177':
827 		case '#':
828 			if (len) {
829 				--len;
830 				--lp;
831 				printf("\b \b");
832 			}
833 			continue;
834 		case '@':
835 		case 'u'&037:
836 			len = 0;
837 			lp = cp;
838 			printf("\n");
839 			continue;
840 		default:
841 			if (len + 1 >= size || c < ' ') {
842 				printf("\007");
843 				continue;
844 			}
845 			printf("%c", c);
846 			++len;
847 			*lp++ = c;
848 		}
849 	}
850 }
851