snprintb.c revision 1.34 1 /* $NetBSD: snprintb.c,v 1.34 2024/02/16 21:25:46 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.34 2024/02/16 21:25:46 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.34 2024/02/16 21:25:46 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 int val_len = snprintf(buf, bufsize, num_fmt, (uintmax_t)val);
96 if (val_len < 0)
97 goto internal;
98
99 size_t total_len = val_len, line_len = val_len, sep_line_len = 0;
100
101 #define STORE(c) do { \
102 if (total_len < bufsize) \
103 buf[total_len] = (c); \
104 total_len++; \
105 line_len++; \
106 } while (0)
107
108 #define BACKUP() do { \
109 if (sep_line_len > 0) { \
110 total_len -= line_len - sep_line_len; \
111 sep_line_len = 0; \
112 restart = 1; \
113 bitfmt = sep_bitfmt; \
114 } \
115 STORE('>'); \
116 STORE('\0'); \
117 if (total_len < bufsize) \
118 snprintf(buf + total_len, bufsize - total_len, \
119 num_fmt, (uintmax_t)val); \
120 total_len += val_len; \
121 line_len = val_len; \
122 } while (0)
123
124 #define PUTSEP() do { \
125 if (line_max > 0 && line_len >= line_max) { \
126 BACKUP(); \
127 STORE('<'); \
128 } else { \
129 if (line_max > 0 && sep != '<') { \
130 sep_line_len = line_len; \
131 sep_bitfmt = cur_bitfmt; \
132 } \
133 STORE(sep); \
134 restart = 0; \
135 } \
136 } while (0)
137
138 #define PUTCHR(c) do { \
139 if (line_max > 0 && line_len >= line_max - 1) { \
140 BACKUP(); \
141 if (restart == 0) \
142 STORE(c); \
143 else \
144 sep = '<'; \
145 } else { \
146 STORE(c); \
147 restart = 0; \
148 } \
149 } while (0)
150
151 #define PUTS(s) do { \
152 while ((*(s)++) != 0) { \
153 PUTCHR((s)[-1]); \
154 if (restart) \
155 break; \
156 } \
157 } while (0)
158
159 #define FMTSTR(sb, f) do { \
160 char *bp = total_len < bufsize ? buf + total_len : NULL; \
161 size_t n = total_len < bufsize ? bufsize - total_len : 0; \
162 int fmt_len = snprintf(bp, n, sb, (uintmax_t)f); \
163 if (fmt_len < 0) \
164 goto internal; \
165 total_len += fmt_len; \
166 line_len += fmt_len; \
167 } while (0)
168
169 sep = '<';
170 if (old_style) {
171 /* old-style format, 32-bit, 1-origin. */
172 for (uint8_t bit; (bit = *bitfmt) != 0;) {
173 cur_bitfmt = bitfmt++;
174 if (val & (1U << (bit - 1))) {
175 PUTSEP();
176 if (restart)
177 continue;
178 sep = ',';
179 for (; *bitfmt > ' '; ++bitfmt) {
180 PUTCHR(*bitfmt);
181 if (restart)
182 break;
183 }
184 } else
185 for (; *bitfmt > ' '; ++bitfmt)
186 continue;
187 }
188 } else {
189 /* new-style format, 64-bit, 0-origin; also does fields. */
190 uint64_t field = val;
191 int matched = 1;
192 while (*bitfmt != '\0') {
193 uint8_t kind = *bitfmt++;
194 uint8_t bit = *bitfmt++;
195 switch (kind) {
196 case 'b':
197 if (((val >> bit) & 1) == 0)
198 goto skip;
199 cur_bitfmt = bitfmt - 2;
200 PUTSEP();
201 if (restart)
202 break;
203 PUTS(bitfmt);
204 if (restart == 0)
205 sep = ',';
206 break;
207 case 'f':
208 case 'F':
209 matched = 0;
210 cur_bitfmt = bitfmt - 2;
211 uint8_t field_width = *bitfmt++;
212 field = (val >> bit) &
213 (((uint64_t)1 << field_width) - 1);
214 PUTSEP();
215 if (restart == 0)
216 sep = ',';
217 if (kind == 'F') { /* just extract */
218 /* duplicate PUTS() effect on bitfmt */
219 while (*bitfmt++ != '\0')
220 continue;
221 break;
222 }
223 if (restart == 0)
224 PUTS(bitfmt);
225 if (restart == 0)
226 PUTCHR('=');
227 if (restart == 0) {
228 FMTSTR(num_fmt, field);
229 if (line_max > 0
230 && line_len > line_max)
231 PUTCHR('#');
232 }
233 break;
234 case '=':
235 case ':':
236 /*
237 * Here "bit" is actually a value instead,
238 * to be compared against the last field.
239 * This only works for values in [0..255],
240 * of course.
241 */
242 if (field != bit)
243 goto skip;
244 matched = 1;
245 if (kind == '=')
246 PUTCHR('=');
247 PUTS(bitfmt);
248 break;
249 case '*':
250 bitfmt--;
251 if (!matched) {
252 matched = 1;
253 FMTSTR(bitfmt, field);
254 }
255 /*FALLTHROUGH*/
256 default:
257 skip:
258 while (*bitfmt++ != '\0')
259 continue;
260 break;
261 }
262 }
263 }
264 if (sep != '<')
265 STORE('>');
266 if (line_max > 0) {
267 STORE('\0');
268 if (bufsize >= 2 && total_len > bufsize - 2)
269 buf[bufsize - 2] = '\0';
270 }
271 STORE('\0');
272 if (bufsize >= 1 && total_len > bufsize - 1)
273 buf[bufsize - 1] = '\0';
274 return (int)(total_len - 1);
275 internal:
276 #ifndef _KERNEL
277 errno = EINVAL;
278 #endif
279 return -1;
280 }
281
282 int
283 snprintb(char *buf, size_t bufsize, const char *bitfmt, uint64_t val)
284 {
285 return snprintb_m(buf, bufsize, bitfmt, val, 0);
286 }
287 # endif /* ! HAVE_SNPRINTB_M */
288 #endif /* ! _STANDALONE */
289