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