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