xref: /netbsd-src/common/lib/libutil/snprintb.c (revision b5677b36047b601b9addaaa494a58ceae82c2a6c)
1 /*	$NetBSD: snprintb.c,v 1.4 2009/01/18 12:05:49 lukem 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.4 2009/01/18 12:05:49 lukem 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.4 2009/01/18 12:05:49 lukem 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 
61 int
62 snprintb(char *buf, size_t buflen, const char *bitfmt, uint64_t val)
63 {
64 	char *bp = buf;
65 	const char *sbase;
66 	int bit, ch, len, sep, flen;
67 	uint64_t field;
68 
69 #ifdef _KERNEL
70 	/*
71 	 * For safety; no other *s*printf() do this, but in the kernel
72 	 * we don't usually check the return value
73 	 */
74 	(void)memset(buf, 0, buflen);
75 #endif /* _KERNEL */
76 
77 	ch = *bitfmt++;
78 	switch (ch != '\177' ? ch : *bitfmt++) {
79 	case 8:
80 		sbase = "0%" PRIo64;
81 		break;
82 	case 10:
83 		sbase = "%" PRId64;
84 		break;
85 	case 16:
86 		sbase = "0x%" PRIx64;
87 		break;
88 	default:
89 		goto internal;
90 	}
91 
92 	len = snprintf(bp, buflen, sbase, val);
93 	if (len < 0)
94 		goto internal;
95 
96 	if ((size_t)len < buflen)
97 		bp += len;
98 	else
99 		bp += buflen - 1;
100 
101 	/*
102 	 * If the value we printed was 0 and we're using the old-style format,
103 	 * we're done.
104 	 */
105 	if ((val == 0) && (ch != '\177'))
106 		goto terminate;
107 
108 #define PUTC(c) if ((size_t)(++len) < buflen) *bp++ = (c)
109 #define PUTS(s) while ((ch = *(s)++) != 0) PUTC(ch)
110 
111 	/*
112 	 * Chris Torek's new bitmask format is identified by a leading \177
113 	 */
114 	sep = '<';
115 	if (ch != '\177') {
116 		/* old (standard) format. */
117 		for (;(bit = *bitfmt++) != 0;) {
118 			if (val & (1 << (bit - 1))) {
119 				PUTC(sep);
120 				for (; (ch = *bitfmt) > ' '; ++bitfmt)
121 					PUTC(ch);
122 				sep = ',';
123 			} else
124 				for (; *bitfmt > ' '; ++bitfmt)
125 					continue;
126 		}
127 	} else {
128 		/* new quad-capable format; also does fields. */
129 		field = val;
130 		while ((ch = *bitfmt++) != '\0') {
131 			bit = *bitfmt++;	/* now 0-origin */
132 			switch (ch) {
133 			case 'b':
134 				if (((u_int)(val >> bit) & 1) == 0)
135 					goto skip;
136 				PUTC(sep);
137 				PUTS(bitfmt);
138 				sep = ',';
139 				break;
140 			case 'f':
141 			case 'F':
142 				flen = *bitfmt++;	/* field length */
143 				field = (val >> bit) &
144 					    (((uint64_t)1 << flen) - 1);
145 				if (ch == 'F')	/* just extract */
146 					break;
147 				PUTC(sep);
148 				sep = ',';
149 				PUTS(bitfmt);
150 				PUTC('=');
151 				flen = snprintf(bp, buflen - len, sbase, field);
152 				if (flen < 0)
153 					goto internal;
154 				len += flen;
155 				if ((size_t)len < buflen)
156 					bp += flen;
157 				break;
158 			case '=':
159 			case ':':
160 				/*
161 				 * Here "bit" is actually a value instead,
162 				 * to be compared against the last field.
163 				 * This only works for values in [0..255],
164 				 * of course.
165 				 */
166 				if ((int)field != bit)
167 					goto skip;
168 				if (ch == '=') {
169 					PUTC('=');
170 				}
171 				PUTS(bitfmt);
172 				break;
173 			default:
174 			skip:
175 				while (*bitfmt++ != '\0')
176 					continue;
177 				break;
178 			}
179 		}
180 	}
181 	if (sep != '<') {
182 		PUTC('>');
183 	}
184 terminate:
185 	*bp = '\0';
186 	return len;
187 internal:
188 #ifndef _KERNEL
189 	errno = EINVAL;
190 #endif
191 	return -1;
192 }
193 #endif
194