1/*	$NetBSD: snprintb.c,v 1.7 2012/01/23 03:22:41 christos Exp $	*/
2
3/*-
4 * Copyright (c) 2002 The NetBSD Foundation, Inc.
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 *
16 * THIS SOFTWARE IS PROVIDED BY THE NETBSD FOUNDATION, INC. AND CONTRIBUTORS
17 * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
18 * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
19 * PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL THE FOUNDATION OR CONTRIBUTORS
20 * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
21 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
22 * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
23 * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
24 * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
25 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
26 * POSSIBILITY OF SUCH DAMAGE.
27 */
28
29/*
30 * snprintb: print an interpreted bitmask to a buffer
31 *
32 * => returns the length of the buffer that would be required to print the
33 *    string minus the terminating NUL.
34 */
35#ifndef _STANDALONE
36# ifndef _KERNEL
37
38#  if HAVE_NBTOOL_CONFIG_H
39#   include "nbtool_config.h"
40#  endif
41
42#  include <sys/cdefs.h>
43#  if defined(LIBC_SCCS) && !defined(lint)
44__RCSID("$NetBSD: snprintb.c,v 1.7 2012/01/23 03:22:41 christos Exp $");
45#  endif
46
47#  include <sys/types.h>
48#  include <sys/inttypes.h>
49#  include <stdio.h>
50#  include <util.h>
51#  include <errno.h>
52# else
53#  include <sys/cdefs.h>
54__KERNEL_RCSID(0, "$NetBSD: snprintb.c,v 1.7 2012/01/23 03:22:41 christos Exp $");
55#  include <sys/param.h>
56#  include <sys/inttypes.h>
57#  include <sys/systm.h>
58#  include <lib/libkern/libkern.h>
59# endif
60
61int
62snprintb_m(char *buf, size_t buflen, const char *bitfmt, uint64_t val,
63	   size_t l_max)
64{
65	char *bp = buf, *s_bp = NULL;
66	const char *c_fmt, *s_fmt = NULL, *cur_fmt;
67	const char *sbase;
68	int bit, ch, t_len, s_len = 0, l_len, f_len, v_len, sep;
69	int restart = 0;
70	uint64_t field;
71
72#ifdef _KERNEL
73	/*
74	 * For safety; no other *s*printf() do this, but in the kernel
75	 * we don't usually check the return value
76	 */
77	(void)memset(buf, 0, buflen);
78#endif /* _KERNEL */
79
80	ch = *bitfmt++;
81	switch (ch != '\177' ? ch : *bitfmt++) {
82	case 8:
83		sbase = "0%" PRIo64;
84		break;
85	case 10:
86		sbase = "%" PRId64;
87		break;
88	case 16:
89		sbase = "0x%" PRIx64;
90		break;
91	default:
92		goto internal;
93	}
94
95	/* Reserve space for trailing blank line if needed */
96	if (l_max > 0)
97		buflen--;
98
99	t_len = snprintf(bp, buflen, sbase, val);
100	if (t_len < 0)
101		goto internal;
102
103	v_len = l_len = t_len;
104
105	if ((size_t)t_len < buflen)
106		bp += t_len;
107	else
108		bp += buflen - 1;
109
110	/*
111	 * If the value we printed was 0 and we're using the old-style format,
112	 * we're done.
113	 */
114	if ((val == 0) && (ch != '\177'))
115		goto terminate;
116
117#define STORE(c) { l_len++;						\
118		   if ((size_t)(++t_len) < buflen)			\
119		   	*bp++ = (c);					\
120		 } while ( /* CONSTCOND */ 0)
121
122#define	BACKUP	{ if (s_bp != NULL) {					\
123			bp = s_bp; s_bp = NULL;				\
124			t_len -= l_len - s_len;				\
125			restart = 1;					\
126			bitfmt = s_fmt;					\
127		  }							\
128		  STORE('>'); STORE('\0');				\
129		  if ((size_t)t_len < buflen)				\
130			snprintf(bp, buflen - t_len, sbase, val);	\
131		  t_len += v_len; l_len = v_len; bp += v_len;		\
132		} while ( /* CONSTCOND */ 0)
133
134#define	PUTSEP								\
135		if (l_max > 0 && (size_t)l_len >= l_max) {		\
136			BACKUP;						\
137			STORE('<');					\
138		} else {						\
139			/* Remember separator location */		\
140			if (l_max > 0 && sep != '<') {			\
141				s_len = l_len;				\
142				s_bp  = bp;				\
143				s_fmt = cur_fmt;			\
144			}						\
145			STORE(sep);					\
146			restart = 0;					\
147		}							\
148
149#define	PUTCHR(c)							\
150		if (l_max > 0 && (size_t)l_len >= (l_max - 1)) {	\
151			BACKUP;						\
152			if (restart == 0) {				\
153				STORE(c);				\
154			} else						\
155				sep = '<';				\
156		} else {						\
157			STORE(c);					\
158			restart = 0;					\
159		}							\
160
161#define PUTS(s) while ((ch = *(s)++) != 0) {				\
162			PUTCHR(ch);					\
163			if (restart)					\
164				break;					\
165		}
166
167	/*
168	 * Chris Torek's new bitmask format is identified by a leading \177
169	 */
170	sep = '<';
171	if (ch != '\177') {
172		/* old (standard) format. */
173		for (;(bit = *bitfmt) != 0;) {
174			cur_fmt = bitfmt++;
175			if (val & (1 << (bit - 1))) {
176				PUTSEP;
177				if (restart)
178					continue;
179				sep = ',';
180				for (; (ch = *bitfmt) > ' '; ++bitfmt) {
181					PUTCHR(ch);
182					if (restart)
183						break;
184				}
185			} else
186				for (; *bitfmt > ' '; ++bitfmt)
187					continue;
188		}
189	} else {
190		/* new quad-capable format; also does fields. */
191		field = val;
192		while (c_fmt = bitfmt, (ch = *bitfmt++) != '\0') {
193			bit = *bitfmt++;	/* now 0-origin */
194			switch (ch) {
195			case 'b':
196				if (((u_int)(val >> bit) & 1) == 0)
197					goto skip;
198				cur_fmt = c_fmt;
199				PUTSEP;
200				if (restart)
201					break;
202				PUTS(bitfmt);
203				if (restart == 0)
204					sep = ',';
205				break;
206			case 'f':
207			case 'F':
208				cur_fmt = c_fmt;
209				f_len = *bitfmt++;	/* field length */
210				field = (val >> bit) &
211					    (((uint64_t)1 << f_len) - 1);
212				PUTSEP;
213				if (restart == 0)
214					sep = ',';
215				if (ch == 'F')	/* just extract */
216					break;
217				if (restart == 0) {
218					PUTS(bitfmt);
219					PUTCHR('=');
220				}
221				if (restart == 0) {
222					f_len = snprintf(bp, buflen - t_len,
223							 sbase, field);
224					if (f_len < 0)
225						goto internal;
226					t_len += f_len;
227					l_len += f_len;
228					if ((size_t)t_len < buflen)
229						bp += f_len;
230					if (l_max > 0 &&
231					    (size_t)l_len > l_max) {
232						PUTCHR('#');
233					}
234				}
235				break;
236			case '=':
237			case ':':
238				/*
239				 * Here "bit" is actually a value instead,
240				 * to be compared against the last field.
241				 * This only works for values in [0..255],
242				 * of course.
243				 */
244				if ((int)field != bit)
245					goto skip;
246				if (ch == '=') {
247					PUTCHR('=');
248				}
249				PUTS(bitfmt);
250				break;
251			default:
252			skip:
253				while (*bitfmt++ != '\0')
254					continue;
255				break;
256			}
257		}
258	}
259	l_len++;
260	if ((size_t)(++t_len) < buflen)
261		*bp++ = '>';
262terminate:
263	*bp++ = '\0';
264	if (l_max != 0) {
265		t_len++;
266		*bp = '\0';
267	}
268	return t_len;
269internal:
270#ifndef _KERNEL
271	errno = EINVAL;
272#endif
273	return -1;
274}
275
276int
277snprintb(char *buf, size_t buflen, const char *bitfmt, uint64_t val)
278{
279	return snprintb_m(buf, buflen, bitfmt, val, 0);
280}
281#endif
282