snprintb.c revision 1.33 1 /* $NetBSD: snprintb.c,v 1.33 2024/02/16 19:53:40 rillig 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.33 2024/02/16 19:53:40 rillig Exp $");
45 # endif
46
47 # include <sys/types.h>
48 # include <inttypes.h>
49 # include <stdio.h>
50 # include <util.h>
51 # include <errno.h>
52 # else /* ! _KERNEL */
53 # include <sys/cdefs.h>
54 __KERNEL_RCSID(0, "$NetBSD: snprintb.c,v 1.33 2024/02/16 19:53:40 rillig Exp $");
55 # include <sys/param.h>
56 # include <sys/inttypes.h>
57 # include <sys/systm.h>
58 # include <lib/libkern/libkern.h>
59 # endif /* ! _KERNEL */
60
61 # ifndef HAVE_SNPRINTB_M
62 int
63 snprintb_m(char *buf, size_t bufsize, const char *bitfmt, uint64_t val,
64 size_t line_max)
65 {
66 const char *num_fmt, *cur_bitfmt, *sep_bitfmt = NULL;
67 char sep;
68 int restart = 0;
69
70 #ifdef _KERNEL
71 /*
72 * For safety; no other *s*printf() do this, but in the kernel
73 * we don't usually check the return value
74 */
75 (void)memset(buf, 0, bufsize);
76 #endif /* _KERNEL */
77
78 int old_style = *bitfmt != '\177';
79 if (!old_style)
80 bitfmt++;
81 switch (*bitfmt++) {
82 case 8:
83 num_fmt = "%#jo";
84 break;
85 case 10:
86 num_fmt = "%ju";
87 break;
88 case 16:
89 num_fmt = "%#jx";
90 break;
91 default:
92 goto internal;
93 }
94
95 /* Reserve space for trailing blank line if needed */
96 if (line_max > 0)
97 bufsize--;
98
99 int val_len = snprintf(buf, bufsize, num_fmt, (uintmax_t)val);
100 if (val_len < 0)
101 goto internal;
102
103 size_t total_len = val_len, line_len = val_len, sep_line_len = 0;
104
105 #define STORE(c) do { \
106 if (total_len < bufsize) \
107 buf[total_len] = (c); \
108 total_len++; \
109 line_len++; \
110 } while (0)
111
112 #define BACKUP() do { \
113 if (sep_line_len > 0) { \
114 total_len -= line_len - sep_line_len; \
115 sep_line_len = 0; \
116 restart = 1; \
117 bitfmt = sep_bitfmt; \
118 } \
119 STORE('>'); \
120 STORE('\0'); \
121 if (total_len < bufsize) \
122 snprintf(buf + total_len, bufsize - total_len, \
123 num_fmt, (uintmax_t)val); \
124 total_len += val_len; \
125 line_len = val_len; \
126 } while (0)
127
128 #define PUTSEP() do { \
129 if (line_max > 0 && line_len >= line_max) { \
130 BACKUP(); \
131 STORE('<'); \
132 } else { \
133 if (line_max > 0 && sep != '<') { \
134 sep_line_len = line_len; \
135 sep_bitfmt = cur_bitfmt; \
136 } \
137 STORE(sep); \
138 restart = 0; \
139 } \
140 } while (0)
141
142 #define PUTCHR(c) do { \
143 if (line_max > 0 && line_len >= line_max - 1) { \
144 BACKUP(); \
145 if (restart == 0) \
146 STORE(c); \
147 else \
148 sep = '<'; \
149 } else { \
150 STORE(c); \
151 restart = 0; \
152 } \
153 } while (0)
154
155 #define PUTS(s) do { \
156 while ((*(s)++) != 0) { \
157 PUTCHR((s)[-1]); \
158 if (restart) \
159 break; \
160 } \
161 } while (0)
162
163 #define FMTSTR(sb, f) do { \
164 char *bp = total_len < bufsize ? buf + total_len : NULL; \
165 size_t n = total_len < bufsize ? bufsize - total_len : 0; \
166 int fmt_len = snprintf(bp, n, sb, (uintmax_t)f); \
167 if (fmt_len < 0) \
168 goto internal; \
169 total_len += fmt_len; \
170 line_len += fmt_len; \
171 } while (0)
172
173 sep = '<';
174 if (old_style) {
175 /* old-style format, 32-bit, 1-origin. */
176 for (uint8_t bit; (bit = *bitfmt) != 0;) {
177 cur_bitfmt = bitfmt++;
178 if (val & (1U << (bit - 1))) {
179 PUTSEP();
180 if (restart)
181 continue;
182 sep = ',';
183 for (; *bitfmt > ' '; ++bitfmt) {
184 PUTCHR(*bitfmt);
185 if (restart)
186 break;
187 }
188 } else
189 for (; *bitfmt > ' '; ++bitfmt)
190 continue;
191 }
192 } else {
193 /* new-style format, 64-bit, 0-origin; also does fields. */
194 uint64_t field = val;
195 int matched = 1;
196 while (*bitfmt != '\0') {
197 uint8_t kind = *bitfmt++;
198 uint8_t bit = *bitfmt++;
199 switch (kind) {
200 case 'b':
201 if (((val >> bit) & 1) == 0)
202 goto skip;
203 cur_bitfmt = bitfmt - 2;
204 PUTSEP();
205 if (restart)
206 break;
207 PUTS(bitfmt);
208 if (restart == 0)
209 sep = ',';
210 break;
211 case 'f':
212 case 'F':
213 matched = 0;
214 cur_bitfmt = bitfmt - 2;
215 uint8_t field_width = *bitfmt++;
216 field = (val >> bit) &
217 (((uint64_t)1 << field_width) - 1);
218 PUTSEP();
219 if (restart == 0)
220 sep = ',';
221 if (kind == 'F') { /* just extract */
222 /* duplicate PUTS() effect on bitfmt */
223 while (*bitfmt++ != '\0')
224 continue;
225 break;
226 }
227 if (restart == 0)
228 PUTS(bitfmt);
229 if (restart == 0)
230 PUTCHR('=');
231 if (restart == 0) {
232 FMTSTR(num_fmt, field);
233 if (line_max > 0
234 && line_len > line_max)
235 PUTCHR('#');
236 }
237 break;
238 case '=':
239 case ':':
240 /*
241 * Here "bit" is actually a value instead,
242 * to be compared against the last field.
243 * This only works for values in [0..255],
244 * of course.
245 */
246 if (field != bit)
247 goto skip;
248 matched = 1;
249 if (kind == '=')
250 PUTCHR('=');
251 PUTS(bitfmt);
252 break;
253 case '*':
254 bitfmt--;
255 if (!matched) {
256 matched = 1;
257 FMTSTR(bitfmt, field);
258 }
259 /*FALLTHROUGH*/
260 default:
261 skip:
262 while (*bitfmt++ != '\0')
263 continue;
264 break;
265 }
266 }
267 }
268 if (sep != '<')
269 STORE('>');
270 if (line_max > 0) {
271 bufsize++;
272 STORE('\0');
273 if (total_len >= bufsize && bufsize > 1)
274 buf[bufsize - 2] = '\0';
275 }
276 STORE('\0');
277 if (total_len >= bufsize && bufsize > 0)
278 buf[bufsize - 1] = '\0';
279 return (int)(total_len - 1);
280 internal:
281 #ifndef _KERNEL
282 errno = EINVAL;
283 #endif
284 return -1;
285 }
286
287 int
288 snprintb(char *buf, size_t bufsize, const char *bitfmt, uint64_t val)
289 {
290 return snprintb_m(buf, bufsize, bitfmt, val, 0);
291 }
292 # endif /* ! HAVE_SNPRINTB_M */
293 #endif /* ! _STANDALONE */
294