snprintb.c revision 1.35 1 /* $NetBSD: snprintb.c,v 1.35 2024/02/17 10:23:30 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.35 2024/02/17 10:23:30 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.35 2024/02/17 10:23:30 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 typedef struct {
63 char *const buf;
64 size_t const bufsize;
65 const char *bitfmt;
66 uint64_t const val;
67 size_t const line_max;
68
69 const char *const num_fmt;
70 unsigned const val_len;
71 unsigned total_len;
72 unsigned line_len;
73
74 const char *cur_bitfmt;
75 int restart;
76
77 const char *sep_bitfmt;
78 unsigned sep_line_len;
79 char sep;
80 } state;
81
82 static void
83 store(state *s, char c)
84 {
85 if (s->total_len < s->bufsize)
86 s->buf[s->total_len] = c;
87 s->total_len++;
88 s->line_len++;
89 }
90
91 static void
92 backup(state *s)
93 {
94 if (s->sep_line_len > 0) {
95 s->total_len -= s->line_len - s->sep_line_len;
96 s->sep_line_len = 0;
97 s->restart = 1;
98 s->bitfmt = s->sep_bitfmt;
99 }
100 store(s, '>');
101 store(s, '\0');
102 if (s->total_len < s->bufsize)
103 snprintf(s->buf + s->total_len, s->bufsize - s->total_len,
104 s->num_fmt, (uintmax_t)s->val);
105 s->total_len += s->val_len;
106 s->line_len = s->val_len;
107 }
108
109 static void
110 put_sep(state *s)
111 {
112 if (s->line_max > 0 && s->line_len >= s->line_max) {
113 backup(s);
114 store(s, '<');
115 } else {
116 if (s->line_max > 0 && s->sep != '<') {
117 s->sep_line_len = s->line_len;
118 s->sep_bitfmt = s->cur_bitfmt;
119 }
120 store(s, s->sep);
121 s->restart = 0;
122 }
123 }
124
125 static void
126 put_chr(state *s, char c)
127 {
128 if (s->line_max > 0 && s->line_len >= s->line_max - 1) {
129 backup(s);
130 if (s->restart == 0)
131 store(s, c);
132 else
133 s->sep = '<';
134 } else {
135 store(s, c);
136 s->restart = 0;
137 }
138 }
139
140 static void
141 put_bitfmt(state *s)
142 {
143 while (*s->bitfmt++ != 0) {
144 put_chr(s, s->bitfmt[-1]);
145 if (s->restart)
146 break;
147 }
148 }
149
150 static int
151 put_num(state *s, const char *fmt, uintmax_t v)
152 {
153 char *bp = s->total_len < s->bufsize ? s->buf + s->total_len : NULL;
154 size_t n = s->total_len < s->bufsize ? s->bufsize - s->total_len : 0;
155 int fmt_len = snprintf(bp, n, fmt, v);
156 if (fmt_len >= 0) {
157 s->total_len += fmt_len;
158 s->line_len += fmt_len;
159 }
160 return fmt_len;
161 }
162
163 static void
164 old_style(state *s)
165 {
166 for (uint8_t bit; (bit = *s->bitfmt) != 0;) {
167 s->cur_bitfmt = s->bitfmt++;
168 if (s->val & (1U << (bit - 1))) {
169 put_sep(s);
170 if (s->restart)
171 continue;
172 s->sep = ',';
173 for (; *s->bitfmt > ' '; ++s->bitfmt) {
174 put_chr(s, *s->bitfmt);
175 if (s->restart)
176 break;
177 }
178 } else
179 for (; *s->bitfmt > ' '; ++s->bitfmt)
180 continue;
181 }
182 }
183
184 static int
185 new_style(state *s)
186 {
187 uint64_t field = s->val;
188 int matched = 1;
189 while (*s->bitfmt != '\0') {
190 uint8_t kind = *s->bitfmt++;
191 uint8_t bit = *s->bitfmt++;
192 switch (kind) {
193 case 'b':
194 if (((s->val >> bit) & 1) == 0)
195 goto skip;
196 s->cur_bitfmt = s->bitfmt - 2;
197 put_sep(s);
198 if (s->restart)
199 break;
200 put_bitfmt(s);
201 if (s->restart == 0)
202 s->sep = ',';
203 break;
204 case 'f':
205 case 'F':
206 matched = 0;
207 s->cur_bitfmt = s->bitfmt - 2;
208 uint8_t field_width = *s->bitfmt++;
209 field = (s->val >> bit) &
210 (((uint64_t)1 << field_width) - 1);
211 put_sep(s);
212 if (s->restart == 0)
213 s->sep = ',';
214 if (kind == 'F')
215 goto skip;
216 if (s->restart == 0)
217 put_bitfmt(s);
218 if (s->restart == 0)
219 put_chr(s, '=');
220 if (s->restart == 0) {
221 if (put_num(s, s->num_fmt, field) < 0)
222 return -1;
223 if (s->line_max > 0
224 && s->line_len > s->line_max)
225 put_chr(s, '#');
226 }
227 break;
228 case '=':
229 case ':':
230 /* Here "bit" is actually a value instead. */
231 if (field != bit)
232 goto skip;
233 matched = 1;
234 if (kind == '=')
235 put_chr(s, '=');
236 put_bitfmt(s);
237 break;
238 case '*':
239 s->bitfmt--;
240 if (!matched) {
241 matched = 1;
242 if (put_num(s, s->bitfmt, field) < 0)
243 return -1;
244 }
245 /*FALLTHROUGH*/
246 default:
247 skip:
248 while (*s->bitfmt++ != '\0')
249 continue;
250 break;
251 }
252 }
253 return 0;
254 }
255
256 int
257 snprintb_m(char *buf, size_t bufsize, const char *bitfmt, uint64_t val,
258 size_t line_max)
259 {
260 #ifdef _KERNEL
261 /*
262 * For safety; no other *s*printf() do this, but in the kernel
263 * we don't usually check the return value
264 */
265 (void)memset(buf, 0, bufsize);
266 #endif /* _KERNEL */
267
268
269 int old = *bitfmt != '\177';
270 if (!old)
271 bitfmt++;
272
273 const char *num_fmt;
274 switch (*bitfmt++) {
275 case 8:
276 num_fmt = "%#jo";
277 break;
278 case 10:
279 num_fmt = "%ju";
280 break;
281 case 16:
282 num_fmt = "%#jx";
283 break;
284 default:
285 goto internal;
286 }
287
288 int val_len = snprintf(buf, bufsize, num_fmt, (uintmax_t)val);
289 if (val_len < 0)
290 goto internal;
291
292 state s = {
293 .buf = buf,
294 .bufsize = bufsize,
295 .bitfmt = bitfmt,
296 .val = val,
297 .line_max = line_max,
298
299 .num_fmt = num_fmt,
300 .val_len = val_len,
301 .total_len = val_len,
302 .line_len = val_len,
303
304 .sep = '<',
305 };
306
307 if (old)
308 old_style(&s);
309 else if (new_style(&s) < 0)
310 goto internal;
311
312 if (s.sep != '<')
313 store(&s, '>');
314 if (s.line_max > 0) {
315 store(&s, '\0');
316 if (s.bufsize >= 2 && s.total_len > s.bufsize - 2)
317 s.buf[s.bufsize - 2] = '\0';
318 }
319 store(&s, '\0');
320 if (s.bufsize >= 1 && s.total_len > s.bufsize - 1)
321 s.buf[s.bufsize - 1] = '\0';
322 return (int)(s.total_len - 1);
323 internal:
324 #ifndef _KERNEL
325 errno = EINVAL;
326 #endif
327 return -1;
328 }
329
330 int
331 snprintb(char *buf, size_t bufsize, const char *bitfmt, uint64_t val)
332 {
333 return snprintb_m(buf, bufsize, bitfmt, val, 0);
334 }
335 # endif /* ! HAVE_SNPRINTB_M */
336 #endif /* ! _STANDALONE */
337