xref: /illumos-gate/usr/src/common/util/string.c (revision 406fc5100dac8d225a315a6def6be8d628f34e24)
1 /*
2  * CDDL HEADER START
3  *
4  * The contents of this file are subject to the terms of the
5  * Common Development and Distribution License (the "License").
6  * You may not use this file except in compliance with the License.
7  *
8  * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
9  * or http://www.opensolaris.org/os/licensing.
10  * See the License for the specific language governing permissions
11  * and limitations under the License.
12  *
13  * When distributing Covered Code, include this CDDL HEADER in each
14  * file and include the License file at usr/src/OPENSOLARIS.LICENSE.
15  * If applicable, add the following below this CDDL HEADER, with the
16  * fields enclosed by brackets "[]" replaced with your own identifying
17  * information: Portions Copyright [yyyy] [name of copyright owner]
18  *
19  * CDDL HEADER END
20  */
21 /*
22  * Copyright 2008 Sun Microsystems, Inc.  All rights reserved.
23  * Use is subject to license terms.
24  * Copyright 2014 Joyent, Inc.  All rights reserved.
25  */
26 
27 /*
28  * Implementations of the functions described in vsnprintf(3C) and string(3C),
29  * for use by the kernel, the standalone, and kmdb.  Unless otherwise specified,
30  * these functions match the section 3C manpages.
31  */
32 
33 #include <sys/types.h>
34 #include <sys/null.h>
35 #include <sys/varargs.h>
36 
37 #if defined(_KERNEL)
38 #include <sys/systm.h>
39 #include <sys/debug.h>
40 #elif !defined(_BOOT)
41 #include <string.h>
42 #endif
43 
44 #include "memcpy.h"
45 #include "string.h"
46 
47 /*
48  * We don't need these for x86 boot or kmdb.
49  */
50 #if !defined(_KMDB) && (!defined(_BOOT) || defined(__sparc))
51 
52 #define	ADDCHAR(c)	if (bufp++ - buf < buflen) bufp[-1] = (c)
53 
54 /*
55  * Given a buffer 'buf' of size 'buflen', render as much of the string
56  * described by <fmt, args> as possible.  The string will always be
57  * null-terminated, so the maximum string length is 'buflen - 1'.
58  * Returns the number of bytes that would be necessary to render the
59  * entire string, not including null terminator (just like vsnprintf(3S)).
60  * To determine buffer size in advance, use vsnprintf(NULL, 0, fmt, args) + 1.
61  *
62  * There is no support for floating point, and the C locale is assumed.
63  */
64 size_t
65 vsnprintf(char *buf, size_t buflen, const char *fmt, va_list aargs)
66 {
67 	uint64_t ul, tmp;
68 	char *bufp = buf;	/* current buffer pointer */
69 	int pad, width, base, sign, c, num;
70 	int prec, h_count, l_count, dot_count;
71 	int pad_count, transfer_count, left_align;
72 	char *digits, *sp, *bs;
73 	char numbuf[65];	/* sufficient for a 64-bit binary value */
74 	va_list args;
75 
76 	/*
77 	 * Make a copy so that all our callers don't have to make a copy
78 	 */
79 	va_copy(args, aargs);
80 
81 	if ((ssize_t)buflen < 0)
82 		buflen = 0;
83 
84 	while ((c = *fmt++) != '\0') {
85 		if (c != '%') {
86 			ADDCHAR(c);
87 			continue;
88 		}
89 
90 		width = prec = 0;
91 		left_align = base = sign = 0;
92 		h_count = l_count = dot_count = 0;
93 		pad = ' ';
94 		digits = "0123456789abcdef";
95 next_fmt:
96 		if ((c = *fmt++) == '\0')
97 			break;
98 
99 		if (c >= 'A' && c <= 'Z') {
100 			c += 'a' - 'A';
101 			digits = "0123456789ABCDEF";
102 		}
103 
104 		switch (c) {
105 		case '-':
106 			left_align++;
107 			goto next_fmt;
108 		case '0':
109 			if (dot_count == 0)
110 				pad = '0';
111 			/*FALLTHROUGH*/
112 		case '1':
113 		case '2':
114 		case '3':
115 		case '4':
116 		case '5':
117 		case '6':
118 		case '7':
119 		case '8':
120 		case '9':
121 			num = 0;
122 			for (;;) {
123 				num = 10 * num + c - '0';
124 				c = *fmt;
125 				if (c < '0' || c > '9')
126 					break;
127 				else
128 					fmt++;
129 			}
130 			if (dot_count > 0)
131 				prec = num;
132 			else
133 				width = num;
134 
135 			goto next_fmt;
136 		case '.':
137 			dot_count++;
138 			goto next_fmt;
139 		case '*':
140 			if (dot_count > 0)
141 				prec = (int)va_arg(args, int);
142 			else
143 				width = (int)va_arg(args, int);
144 			goto next_fmt;
145 		case 'l':
146 			l_count++;
147 			goto next_fmt;
148 		case 'h':
149 			h_count++;
150 			goto next_fmt;
151 		case 'd':
152 			sign = 1;
153 			/*FALLTHROUGH*/
154 		case 'u':
155 			base = 10;
156 			break;
157 		case 'p':
158 			l_count = 1;
159 			/*FALLTHROUGH*/
160 		case 'x':
161 			base = 16;
162 			break;
163 		case 'o':
164 			base = 8;
165 			break;
166 		case 'b':
167 			l_count = 0;
168 			base = 1;
169 			break;
170 		case 'c':
171 			c = (char)va_arg(args, int);
172 			ADDCHAR(c);
173 			break;
174 		case 's':
175 			sp = va_arg(args, char *);
176 			if (sp == NULL) {
177 				sp = "<null string>";
178 				/* avoid truncation */
179 				prec = strlen(sp);
180 			}
181 			/*
182 			 * Handle simple case specially to avoid
183 			 * performance hit of strlen()
184 			 */
185 			if (prec == 0 && width == 0) {
186 				while ((c = *sp++) != 0)
187 					ADDCHAR(c);
188 				break;
189 			}
190 			if (prec > 0) {
191 				transfer_count = strnlen(sp, prec);
192 				/* widen field if too narrow */
193 				if (prec > width)
194 					width = prec;
195 			} else
196 				transfer_count = strlen(sp);
197 			if (width > transfer_count)
198 				pad_count = width - transfer_count;
199 			else
200 				pad_count = 0;
201 			while ((!left_align) && (pad_count-- > 0))
202 				ADDCHAR(' ');
203 			/* ADDCHAR() evaluates arg at most once */
204 			while (transfer_count-- > 0)
205 				ADDCHAR(*sp++);
206 			while ((left_align) && (pad_count-- > 0))
207 				ADDCHAR(' ');
208 			break;
209 		case '%':
210 			ADDCHAR('%');
211 			break;
212 		}
213 
214 		if (base == 0)
215 			continue;
216 
217 		if (h_count == 0 && l_count == 0)
218 			if (sign)
219 				ul = (int64_t)va_arg(args, int);
220 			else
221 				ul = (int64_t)va_arg(args, unsigned int);
222 		else if (l_count > 1)
223 			if (sign)
224 				ul = (int64_t)va_arg(args, int64_t);
225 			else
226 				ul = (int64_t)va_arg(args, uint64_t);
227 		else if (l_count > 0)
228 			if (sign)
229 				ul = (int64_t)va_arg(args, long);
230 			else
231 				ul = (int64_t)va_arg(args, unsigned long);
232 		else if (h_count > 1)
233 			if (sign)
234 				ul = (int64_t)((char)va_arg(args, int));
235 			else
236 				ul = (int64_t)((unsigned char)va_arg(args,
237 				    int));
238 		else if (h_count > 0)
239 			if (sign)
240 				ul = (int64_t)((short)va_arg(args, int));
241 			else
242 				ul = (int64_t)((unsigned short)va_arg(args,
243 				    int));
244 
245 		if (sign && (int64_t)ul < 0)
246 			ul = -ul;
247 		else
248 			sign = 0;
249 
250 		if (c == 'b') {
251 			bs = va_arg(args, char *);
252 			base = *bs++;
253 		}
254 
255 		/* avoid repeated division if width is 0 */
256 		if (width > 0) {
257 			tmp = ul;
258 			do {
259 				width--;
260 			} while ((tmp /= base) != 0);
261 		}
262 
263 		if (sign && pad == '0')
264 			ADDCHAR('-');
265 		while ((!left_align) && (width-- > sign))
266 			ADDCHAR(pad);
267 		if (sign && pad == ' ')
268 			ADDCHAR('-');
269 
270 		sp = numbuf;
271 		tmp = ul;
272 		do {
273 			*sp++ = digits[tmp % base];
274 		} while ((tmp /= base) != 0);
275 
276 		while (sp > numbuf) {
277 			sp--;
278 			ADDCHAR(*sp);
279 		}
280 
281 		/* add left-alignment padding */
282 		while (width-- > sign)
283 			ADDCHAR(' ');
284 
285 		if (c == 'b' && ul != 0) {
286 			int any = 0;
287 			c = *bs++;
288 			while (c != 0) {
289 				if (ul & (1 << (c - 1))) {
290 					if (any++ == 0)
291 						ADDCHAR('<');
292 					while ((c = *bs++) >= 32)
293 						ADDCHAR(c);
294 					ADDCHAR(',');
295 				} else {
296 					while ((c = *bs++) >= 32)
297 						continue;
298 				}
299 			}
300 			if (any) {
301 				bufp--;
302 				ADDCHAR('>');
303 			}
304 		}
305 	}
306 	if (bufp - buf < buflen)
307 		bufp[0] = c;
308 	else if (buflen != 0)
309 		buf[buflen - 1] = c;
310 
311 	va_end(args);
312 
313 	return (bufp - buf);
314 }
315 
316 /*PRINTFLIKE1*/
317 size_t
318 snprintf(char *buf, size_t buflen, const char *fmt, ...)
319 {
320 	va_list args;
321 
322 	va_start(args, fmt);
323 	buflen = vsnprintf(buf, buflen, fmt, args);
324 	va_end(args);
325 
326 	return (buflen);
327 }
328 
329 #if defined(_BOOT) && defined(__sparc)
330 /*
331  * The sprintf() and vsprintf() routines aren't shared with the kernel because
332  * the DDI mandates that they return the buffer rather than its length.
333  */
334 /*PRINTFLIKE2*/
335 int
336 sprintf(char *buf, const char *fmt, ...)
337 {
338 	va_list args;
339 
340 	va_start(args, fmt);
341 	(void) vsnprintf(buf, INT_MAX, fmt, args);
342 	va_end(args);
343 
344 	return (strlen(buf));
345 }
346 
347 int
348 vsprintf(char *buf, const char *fmt, va_list args)
349 {
350 	(void) vsnprintf(buf, INT_MAX, fmt, args);
351 	return (strlen(buf));
352 }
353 #endif /* _BOOT && __sparc */
354 
355 #endif /* !_KMDB && (!_BOOT || __sparc) */
356 
357 char *
358 strcat(char *s1, const char *s2)
359 {
360 	char *os1 = s1;
361 
362 	while (*s1++ != '\0')
363 		;
364 	s1--;
365 	while ((*s1++ = *s2++) != '\0')
366 		;
367 	return (os1);
368 }
369 
370 char *
371 strchr(const char *sp, int c)
372 {
373 	do {
374 		if (*sp == (char)c)
375 			return ((char *)sp);
376 	} while (*sp++);
377 	return (NULL);
378 }
379 
380 int
381 strcmp(const char *s1, const char *s2)
382 {
383 	while (*s1 == *s2++)
384 		if (*s1++ == '\0')
385 			return (0);
386 	return (*(unsigned char *)s1 - *(unsigned char *)--s2);
387 }
388 
389 int
390 strncmp(const char *s1, const char *s2, size_t n)
391 {
392 	if (s1 == s2)
393 		return (0);
394 	n++;
395 	while (--n != 0 && *s1 == *s2++)
396 		if (*s1++ == '\0')
397 			return (0);
398 	return ((n == 0) ? 0 : *(unsigned char *)s1 - *(unsigned char *)--s2);
399 }
400 
401 static const char charmap[] = {
402 	'\000', '\001', '\002', '\003', '\004', '\005', '\006', '\007',
403 	'\010', '\011', '\012', '\013', '\014', '\015', '\016', '\017',
404 	'\020', '\021', '\022', '\023', '\024', '\025', '\026', '\027',
405 	'\030', '\031', '\032', '\033', '\034', '\035', '\036', '\037',
406 	'\040', '\041', '\042', '\043', '\044', '\045', '\046', '\047',
407 	'\050', '\051', '\052', '\053', '\054', '\055', '\056', '\057',
408 	'\060', '\061', '\062', '\063', '\064', '\065', '\066', '\067',
409 	'\070', '\071', '\072', '\073', '\074', '\075', '\076', '\077',
410 	'\100', '\141', '\142', '\143', '\144', '\145', '\146', '\147',
411 	'\150', '\151', '\152', '\153', '\154', '\155', '\156', '\157',
412 	'\160', '\161', '\162', '\163', '\164', '\165', '\166', '\167',
413 	'\170', '\171', '\172', '\133', '\134', '\135', '\136', '\137',
414 	'\140', '\141', '\142', '\143', '\144', '\145', '\146', '\147',
415 	'\150', '\151', '\152', '\153', '\154', '\155', '\156', '\157',
416 	'\160', '\161', '\162', '\163', '\164', '\165', '\166', '\167',
417 	'\170', '\171', '\172', '\173', '\174', '\175', '\176', '\177',
418 	'\200', '\201', '\202', '\203', '\204', '\205', '\206', '\207',
419 	'\210', '\211', '\212', '\213', '\214', '\215', '\216', '\217',
420 	'\220', '\221', '\222', '\223', '\224', '\225', '\226', '\227',
421 	'\230', '\231', '\232', '\233', '\234', '\235', '\236', '\237',
422 	'\240', '\241', '\242', '\243', '\244', '\245', '\246', '\247',
423 	'\250', '\251', '\252', '\253', '\254', '\255', '\256', '\257',
424 	'\260', '\261', '\262', '\263', '\264', '\265', '\266', '\267',
425 	'\270', '\271', '\272', '\273', '\274', '\275', '\276', '\277',
426 	'\300', '\301', '\302', '\303', '\304', '\305', '\306', '\307',
427 	'\310', '\311', '\312', '\313', '\314', '\315', '\316', '\317',
428 	'\320', '\321', '\322', '\323', '\324', '\325', '\326', '\327',
429 	'\330', '\331', '\332', '\333', '\334', '\335', '\336', '\337',
430 	'\340', '\341', '\342', '\343', '\344', '\345', '\346', '\347',
431 	'\350', '\351', '\352', '\353', '\354', '\355', '\356', '\357',
432 	'\360', '\361', '\362', '\363', '\364', '\365', '\366', '\367',
433 	'\370', '\371', '\372', '\373', '\374', '\375', '\376', '\377',
434 };
435 
436 int
437 strcasecmp(const char *s1, const char *s2)
438 {
439 	const unsigned char *cm = (const unsigned char *)charmap;
440 	const unsigned char *us1 = (const unsigned char *)s1;
441 	const unsigned char *us2 = (const unsigned char *)s2;
442 
443 	while (cm[*us1] == cm[*us2++])
444 		if (*us1++ == '\0')
445 			return (0);
446 	return (cm[*us1] - cm[*(us2 - 1)]);
447 }
448 
449 int
450 strncasecmp(const char *s1, const char *s2, size_t n)
451 {
452 	const unsigned char *cm = (const unsigned char *)charmap;
453 	const unsigned char *us1 = (const unsigned char *)s1;
454 	const unsigned char *us2 = (const unsigned char *)s2;
455 
456 	while (n != 0 && cm[*us1] == cm[*us2++]) {
457 		if (*us1++ == '\0')
458 			return (0);
459 		n--;
460 	}
461 	return (n == 0 ? 0 : cm[*us1] - cm[*(us2 - 1)]);
462 }
463 
464 char *
465 strcpy(char *s1, const char *s2)
466 {
467 	char *os1 = s1;
468 
469 	while ((*s1++ = *s2++) != '\0')
470 		;
471 	return (os1);
472 }
473 
474 char *
475 strncpy(char *s1, const char *s2, size_t n)
476 {
477 	char *os1 = s1;
478 
479 	n++;
480 	while (--n != 0 && (*s1++ = *s2++) != '\0')
481 		;
482 	if (n != 0)
483 		while (--n != 0)
484 			*s1++ = '\0';
485 	return (os1);
486 }
487 
488 char *
489 strrchr(const char *sp, int c)
490 {
491 	char *r = NULL;
492 
493 	do {
494 		if (*sp == (char)c)
495 			r = (char *)sp;
496 	} while (*sp++);
497 
498 	return (r);
499 }
500 
501 char *
502 strstr(const char *as1, const char *as2)
503 {
504 	const char *s1, *s2;
505 	const char *tptr;
506 	char c;
507 
508 	s1 = as1;
509 	s2 = as2;
510 
511 	if (s2 == NULL || *s2 == '\0')
512 		return ((char *)s1);
513 	c = *s2;
514 
515 	while (*s1)
516 		if (*s1++ == c) {
517 			tptr = s1;
518 			while ((c = *++s2) == *s1++ && c)
519 				;
520 			if (c == 0)
521 				return ((char *)tptr - 1);
522 			s1 = tptr;
523 			s2 = as2;
524 			c = *s2;
525 		}
526 
527 	return (NULL);
528 }
529 
530 char *
531 strpbrk(const char *string, const char *brkset)
532 {
533 	const char *p;
534 
535 	do {
536 		for (p = brkset; *p != '\0' && *p != *string; ++p)
537 			;
538 		if (*p != '\0')
539 			return ((char *)string);
540 	} while (*string++);
541 
542 	return (NULL);
543 }
544 
545 char *
546 strncat(char *s1, const char *s2, size_t n)
547 {
548 	char *os1 = s1;
549 
550 	n++;
551 	while (*s1++ != '\0')
552 		;
553 	--s1;
554 	while ((*s1++ = *s2++) != '\0') {
555 		if (--n == 0) {
556 			s1[-1] = '\0';
557 			break;
558 		}
559 	}
560 	return (os1);
561 }
562 
563 #if defined(_BOOT) || defined(_KMDB)
564 #define	bcopy(src, dst, n)	(void) memcpy((dst), (src), (n))
565 #endif
566 
567 size_t
568 strlcat(char *dst, const char *src, size_t dstsize)
569 {
570 	char *df = dst;
571 	size_t left = dstsize;
572 	size_t l1;
573 	size_t l2 = strlen(src);
574 	size_t copied;
575 
576 	while (left-- != 0 && *df != '\0')
577 		df++;
578 	/*LINTED: possible ptrdiff_t overflow*/
579 	l1 = (size_t)(df - dst);
580 	if (dstsize == l1)
581 		return (l1 + l2);
582 
583 	copied = l1 + l2 >= dstsize ? dstsize - l1 - 1 : l2;
584 	bcopy(src, dst + l1, copied);
585 	dst[l1+copied] = '\0';
586 	return (l1 + l2);
587 }
588 
589 size_t
590 strlcpy(char *dst, const char *src, size_t len)
591 {
592 	size_t slen = strlen(src);
593 	size_t copied;
594 
595 	if (len == 0)
596 		return (slen);
597 
598 	if (slen >= len)
599 		copied = len - 1;
600 	else
601 		copied = slen;
602 	bcopy(src, dst, copied);
603 	dst[copied] = '\0';
604 	return (slen);
605 }
606 
607 size_t
608 strspn(const char *string, const char *charset)
609 {
610 	const char *p, *q;
611 
612 	for (q = string; *q != '\0'; ++q) {
613 		for (p = charset; *p != '\0' && *p != *q; ++p)
614 			;
615 		if (*p == '\0')
616 			break;
617 	}
618 
619 	/*LINTED: possible ptrdiff_t overflow*/
620 	return ((size_t)(q - string));
621 }
622 
623 size_t
624 strcspn(const char *string, const char *charset)
625 {
626 	const char *p, *q;
627 
628 	for (q = string; *q != '\0'; ++q) {
629 		for (p = charset; *p != '\0' && *p != *q; ++p)
630 			;
631 		if (*p != '\0')
632 			break;
633 	}
634 
635 	/*LINTED E_PTRDIFF_OVERFLOW*/
636 	return ((size_t)(q - string));
637 }
638 
639 /*
640  * strsep
641  *
642  * The strsep() function locates, in the string referenced by *stringp, the
643  * first occurrence of any character in the string delim (or the terminating
644  * `\0' character) and replaces it with a `\0'.  The location of the next
645  * character after the delimiter character (or NULL, if the end of the
646  * string was reached) is stored in *stringp.  The original value of
647  * *stringp is returned.
648  *
649  * If *stringp is initially NULL, strsep() returns NULL.
650  *
651  * NOTE: This instance is left for in-kernel use. Libraries and programs
652  *       should use strsep from libc.
653  */
654 char *
655 strsep(char **stringp, const char *delim)
656 {
657 	char *s;
658 	const char *spanp;
659 	int c, sc;
660 	char *tok;
661 
662 	if ((s = *stringp) == NULL)
663 		return (NULL);
664 
665 	for (tok = s; ; ) {
666 		c = *s++;
667 		spanp = delim;
668 		do {
669 			if ((sc = *spanp++) == c) {
670 				if (c == 0)
671 					s = NULL;
672 				else
673 					s[-1] = 0;
674 				*stringp = s;
675 				return (tok);
676 			}
677 		} while (sc != 0);
678 	}
679 	/* NOTREACHED */
680 }
681 
682 /*
683  * Unless mentioned otherwise, all of the routines below should be added to
684  * the Solaris DDI as necessary.  For now, only provide them to standalone.
685  */
686 #if defined(_BOOT) || defined(_KMDB)
687 char *
688 strtok(char *string, const char *sepset)
689 {
690 	char		*p, *q, *r;
691 	static char	*savept;
692 
693 	/*
694 	 * Set `p' to our current location in the string.
695 	 */
696 	p = (string == NULL) ? savept : string;
697 	if (p == NULL)
698 		return (NULL);
699 
700 	/*
701 	 * Skip leading separators; bail if no tokens remain.
702 	 */
703 	q = p + strspn(p, sepset);
704 	if (*q == '\0')
705 		return (NULL);
706 
707 	/*
708 	 * Mark the end of the token and set `savept' for the next iteration.
709 	 */
710 	if ((r = strpbrk(q, sepset)) == NULL)
711 		savept = NULL;
712 	else {
713 		*r = '\0';
714 		savept = ++r;
715 	}
716 
717 	return (q);
718 }
719 
720 /*
721  * The strlen() routine isn't shared with the kernel because it has its own
722  * hand-tuned assembly version.
723  */
724 size_t
725 strlen(const char *s)
726 {
727 	size_t n = 0;
728 
729 	while (*s++)
730 		n++;
731 	return (n);
732 }
733 
734 #endif /* _BOOT || _KMDB */
735 
736 /*
737  * Returns the number of non-NULL bytes in string argument,
738  * but not more than maxlen.  Does not look past str + maxlen.
739  */
740 size_t
741 strnlen(const char *s, size_t maxlen)
742 {
743 	size_t n = 0;
744 
745 	while (maxlen != 0 && *s != 0) {
746 		s++;
747 		maxlen--;
748 		n++;
749 	}
750 
751 	return (n);
752 }
753 
754 
755 #ifdef _KERNEL
756 /*
757  * Check for a valid C identifier:
758  *	a letter or underscore, followed by
759  *	zero or more letters, digits and underscores.
760  */
761 
762 #define	IS_DIGIT(c)	((c) >= '0' && (c) <= '9')
763 
764 #define	IS_ALPHA(c)	\
765 	(((c) >= 'a' && (c) <= 'z') || ((c) >= 'A' && (c) <= 'Z'))
766 
767 int
768 strident_valid(const char *id)
769 {
770 	int c = *id++;
771 
772 	if (!IS_ALPHA(c) && c != '_')
773 		return (0);
774 	while ((c = *id++) != 0) {
775 		if (!IS_ALPHA(c) && !IS_DIGIT(c) && c != '_')
776 			return (0);
777 	}
778 	return (1);
779 }
780 
781 /*
782  * Convert a string into a valid C identifier by replacing invalid
783  * characters with '_'.  Also makes sure the string is nul-terminated
784  * and takes up at most n bytes.
785  */
786 void
787 strident_canon(char *s, size_t n)
788 {
789 	char c;
790 	char *end = s + n - 1;
791 
792 	ASSERT(n > 0);
793 
794 	if ((c = *s) == 0)
795 		return;
796 
797 	if (!IS_ALPHA(c) && c != '_')
798 		*s = '_';
799 
800 	while (s < end && ((c = *(++s)) != 0)) {
801 		if (!IS_ALPHA(c) && !IS_DIGIT(c) && c != '_')
802 			*s = '_';
803 	}
804 	*s = 0;
805 }
806 
807 #endif	/* _KERNEL */
808