snprintb.c revision 1.47 1 /* $NetBSD: snprintb.c,v 1.47 2024/04/07 12:05:23 rillig Exp $ */
2
3 /*-
4 * Copyright (c) 2002, 2024 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 #ifndef _STANDALONE
30 # ifndef _KERNEL
31
32 # if HAVE_NBTOOL_CONFIG_H
33 # include "nbtool_config.h"
34 # endif
35
36 # include <sys/cdefs.h>
37 # if defined(LIBC_SCCS)
38 __RCSID("$NetBSD: snprintb.c,v 1.47 2024/04/07 12:05:23 rillig Exp $");
39 # endif
40
41 # include <sys/types.h>
42 # include <inttypes.h>
43 # include <stdio.h>
44 # include <string.h>
45 # include <util.h>
46 # include <errno.h>
47 # else /* ! _KERNEL */
48 # include <sys/cdefs.h>
49 __KERNEL_RCSID(0, "$NetBSD: snprintb.c,v 1.47 2024/04/07 12:05:23 rillig Exp $");
50 # include <sys/param.h>
51 # include <sys/inttypes.h>
52 # include <sys/systm.h>
53 # include <lib/libkern/libkern.h>
54 # endif /* ! _KERNEL */
55
56 # ifndef HAVE_SNPRINTB_M
57 typedef struct {
58 char *const buf;
59 size_t const bufsize;
60 const char *bitfmt;
61 uint64_t const val;
62 size_t const line_max;
63
64 char num_fmt[5];
65 size_t total_len;
66 size_t line_pos;
67 size_t comma_pos;
68 int in_angle_brackets;
69 } state;
70
71 static void
72 store(state *s, char c)
73 {
74 if (s->total_len < s->bufsize)
75 s->buf[s->total_len] = c;
76 s->total_len++;
77 }
78
79 static int
80 store_num(state *s, const char *fmt, uintmax_t num)
81 {
82 int num_len = s->total_len < s->bufsize
83 ? snprintf(s->buf + s->total_len, s->bufsize - s->total_len,
84 fmt, num)
85 : snprintf(NULL, 0, fmt, num);
86 if (num_len > 0)
87 s->total_len += num_len;
88 return num_len;
89 }
90
91 static void
92 store_eol(state *s)
93 {
94 if (s->total_len - s->line_pos > s->line_max) {
95 s->total_len = s->line_pos + s->line_max - 1;
96 store(s, '#');
97 }
98 store(s, '\0');
99 s->line_pos = s->total_len;
100 s->comma_pos = 0;
101 s->in_angle_brackets = 0;
102 }
103
104 static void
105 store_delimiter(state *s)
106 {
107 if (s->in_angle_brackets) {
108 s->comma_pos = s->total_len;
109 store(s, ',');
110 } else {
111 store(s, '<');
112 s->in_angle_brackets = 1;
113 }
114 }
115
116 static void
117 maybe_wrap_line(state *s, const char *bitfmt)
118 {
119 if (s->line_max > 0
120 && s->comma_pos > 0
121 && s->total_len - s->line_pos >= s->line_max) {
122 s->total_len = s->comma_pos;
123 store(s, '>');
124 store_eol(s);
125 store_num(s, s->num_fmt, s->val);
126 s->bitfmt = bitfmt;
127 }
128 }
129
130 static int
131 old_style(state *s)
132 {
133 while (*s->bitfmt != '\0') {
134 const char *cur_bitfmt = s->bitfmt;
135 uint8_t bit = *s->bitfmt;
136 if (bit > ' ')
137 return -1;
138 if (s->val & (1U << (bit - 1))) {
139 store_delimiter(s);
140 while ((uint8_t)*++s->bitfmt > ' ')
141 store(s, *s->bitfmt);
142 maybe_wrap_line(s, cur_bitfmt);
143 } else
144 while ((uint8_t)*++s->bitfmt > ' ')
145 continue;
146 }
147 return 0;
148 }
149
150 static int
151 new_style(state *s)
152 {
153 uint8_t field_kind = 0; // 0 or 'f' or 'F'
154 uint64_t field = 0; // valid if field_kind != '\0'
155 int matched = 1;
156 const char *prev_bitfmt = s->bitfmt;
157 while (*s->bitfmt != '\0') {
158 const char *cur_bitfmt = s->bitfmt;
159 uint8_t kind = cur_bitfmt[0];
160 switch (kind) {
161 case 'b':
162 field_kind = 0;
163 prev_bitfmt = cur_bitfmt;
164 uint8_t b_bit = cur_bitfmt[1];
165 if (b_bit >= 64)
166 return -1;
167 if (cur_bitfmt[2] == '\0')
168 return -1;
169 s->bitfmt += 2;
170 if (((s->val >> b_bit) & 1) == 0)
171 goto skip_description;
172 store_delimiter(s);
173 while (*s->bitfmt++ != '\0')
174 store(s, s->bitfmt[-1]);
175 maybe_wrap_line(s, cur_bitfmt);
176 break;
177 case 'f':
178 case 'F':
179 field_kind = kind;
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 if (kind == 'f' && cur_bitfmt[3] == '\0')
189 return -1;
190 field = s->val >> f_lsb;
191 if (f_width < 64)
192 field &= ((uint64_t) 1 << f_width) - 1;
193 s->bitfmt += 3;
194 store_delimiter(s);
195 if (kind == 'F')
196 goto skip_description;
197 while (*s->bitfmt++ != '\0')
198 store(s, s->bitfmt[-1]);
199 store(s, '=');
200 store_num(s, s->num_fmt, field);
201 maybe_wrap_line(s, cur_bitfmt);
202 break;
203 case '=':
204 case ':':
205 s->bitfmt += 2;
206 if (kind == '=' && field_kind != 'f')
207 return -1;
208 if (kind == ':' && field_kind != 'F')
209 return -1;
210 uint8_t cmp = cur_bitfmt[1];
211 if (cur_bitfmt[2] == '\0')
212 return -1;
213 if (field != cmp)
214 goto skip_description;
215 matched = 1;
216 if (kind == '=')
217 store(s, '=');
218 while (*s->bitfmt++ != '\0')
219 store(s, s->bitfmt[-1]);
220 maybe_wrap_line(s, prev_bitfmt);
221 break;
222 case '*':
223 if (field_kind == 0)
224 return -1;
225 if (cur_bitfmt[1] == '\0')
226 return -1;
227 s->bitfmt++;
228 if (matched)
229 goto skip_description;
230 matched = 1;
231 if (store_num(s, s->bitfmt, field) < 0)
232 return -1;
233 maybe_wrap_line(s, prev_bitfmt);
234 goto skip_description;
235 default:
236 return -1;
237 skip_description:
238 while (*s->bitfmt++ != '\0')
239 continue;
240 break;
241 }
242 }
243 return 0;
244 }
245
246 static void
247 finish_buffer(state *s)
248 {
249 if (s->line_max > 0) {
250 store_eol(s);
251 store(s, '\0');
252 if (s->bufsize >= 3 && s->total_len > s->bufsize)
253 s->buf[s->bufsize - 3] = '#';
254 if (s->bufsize >= 2 && s->total_len > s->bufsize)
255 s->buf[s->bufsize - 2] = '\0';
256 if (s->bufsize >= 1 && s->total_len > s->bufsize)
257 s->buf[s->bufsize - 1] = '\0';
258 } else {
259 store(s, '\0');
260 if (s->bufsize >= 2 && s->total_len > s->bufsize)
261 s->buf[s->bufsize - 2] = '#';
262 if (s->bufsize >= 1 && s->total_len > s->bufsize)
263 s->buf[s->bufsize - 1] = '\0';
264 }
265 }
266
267 int
268 snprintb_m(char *buf, size_t bufsize, const char *bitfmt, uint64_t val,
269 size_t line_max)
270 {
271 int old = *bitfmt != '\177';
272 if (!old)
273 bitfmt++;
274
275 state s = {
276 .buf = buf,
277 .bufsize = bufsize,
278 .bitfmt = bitfmt,
279 .val = val,
280 .line_max = line_max,
281 };
282 int had_error = 0;
283
284 switch (*s.bitfmt++) {
285 case 8:
286 memcpy(s.num_fmt, "%#jo", 4);
287 break;
288 case 10:
289 memcpy(s.num_fmt, "%ju", 4);
290 break;
291 case 16:
292 memcpy(s.num_fmt, "%#jx", 4);
293 break;
294 default:
295 goto had_error;
296 }
297
298 store_num(&s, s.num_fmt, val);
299
300 if ((old ? old_style(&s) : new_style(&s)) < 0) {
301 had_error:
302 #ifndef _KERNEL
303 errno = EINVAL;
304 #endif
305 had_error = 1;
306 store(&s, '#');
307 } else if (s.in_angle_brackets)
308 store(&s, '>');
309 finish_buffer(&s);
310 return had_error ? -1 : (int)(s.total_len - 1);
311 }
312
313 int
314 snprintb(char *buf, size_t bufsize, const char *bitfmt, uint64_t val)
315 {
316 return snprintb_m(buf, bufsize, bitfmt, val, 0);
317 }
318 # endif /* ! HAVE_SNPRINTB_M */
319 #endif /* ! _STANDALONE */
320