1 /* $NetBSD: snprintb.c,v 1.22 2019/12/06 19:36:22 christos 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
36 #include <sys/types.h>
37 #include <errno.h>
38 #include <inttypes.h>
39 #include <libutil.h>
40 #include <stdio.h>
41
42 int
snprintb_m(char * buf,size_t buflen,const char * bitfmt,uint64_t val,size_t l_max)43 snprintb_m(char *buf, size_t buflen, const char *bitfmt, uint64_t val,
44 size_t l_max)
45 {
46 char *bp = buf, *s_bp = NULL;
47 const char *c_fmt, *s_fmt = NULL, *cur_fmt;
48 const char *sbase;
49 int bit, ch, t_len, s_len = 0, l_len, f_len, v_len, sep;
50 int restart = 0, matched = 1;
51 uint64_t field;
52
53 ch = *bitfmt++;
54 switch (ch != '\177' ? ch : *bitfmt++) {
55 case 8:
56 sbase = "%#jo";
57 break;
58 case 10:
59 sbase = "%ju";
60 break;
61 case 16:
62 sbase = "%#jx";
63 break;
64 default:
65 goto internal;
66 }
67
68 /* Reserve space for trailing blank line if needed */
69 if (l_max > 0)
70 buflen--;
71
72 t_len = snprintf(bp, buflen, sbase, (uintmax_t)val);
73 if (t_len < 0)
74 goto internal;
75
76 v_len = l_len = t_len;
77
78 if ((size_t)t_len < buflen)
79 bp += t_len;
80 else
81 bp += buflen - 1;
82
83 /*
84 * If the value we printed was 0 and we're using the old-style format,
85 * we're done.
86 */
87 if ((val == 0) && (ch != '\177'))
88 goto terminate;
89
90 #define STORE(c) do { l_len++; \
91 if ((size_t)(++t_len) < buflen) \
92 *bp++ = (c); \
93 } while ( /* CONSTCOND */ 0)
94
95 #define BACKUP do { if (s_bp != NULL) { \
96 bp = s_bp; s_bp = NULL; \
97 t_len -= l_len - s_len; \
98 restart = 1; \
99 bitfmt = s_fmt; \
100 } \
101 STORE('>'); STORE('\0'); \
102 if ((size_t)t_len < buflen) \
103 snprintf(bp, buflen - t_len, sbase, (uintmax_t)val);\
104 t_len += v_len; l_len = v_len; bp += v_len; \
105 } while ( /* CONSTCOND */ 0)
106
107 #define PUTSEP do { \
108 if (l_max > 0 && (size_t)l_len >= l_max) { \
109 BACKUP; \
110 STORE('<'); \
111 } else { \
112 /* Remember separator location */ \
113 if (l_max > 0 && sep != '<') { \
114 s_len = l_len; \
115 s_bp = bp; \
116 s_fmt = cur_fmt; \
117 } \
118 STORE(sep); \
119 restart = 0; \
120 } \
121 } while ( /* CONSTCOND */ 0)
122
123 #define PUTCHR(c) do { \
124 if (l_max > 0 && (size_t)l_len >= (l_max - 1)) {\
125 BACKUP; \
126 if (restart == 0) \
127 STORE(c); \
128 else \
129 sep = '<'; \
130 } else { \
131 STORE(c); \
132 restart = 0; \
133 } \
134 } while ( /* CONSTCOND */ 0)
135
136 #define PUTS(s) while ((ch = *(s)++) != 0) { \
137 PUTCHR(ch); \
138 if (restart) \
139 break; \
140 }
141 #define FMTSTR(sb, f) \
142 do { \
143 f_len = snprintf(bp, buflen - t_len, sb, (uintmax_t)f); \
144 if (f_len < 0) \
145 goto internal; \
146 t_len += f_len; \
147 l_len += f_len; \
148 if ((size_t)t_len < buflen) \
149 bp += f_len; \
150 } while (/*CONSTCOND*/0)
151
152 /*
153 * Chris Torek's new bitmask format is identified by a leading \177
154 */
155 sep = '<';
156 if (ch != '\177') {
157 /* old (standard) format. */
158 for (;(bit = *bitfmt) != 0;) {
159 cur_fmt = bitfmt++;
160 if (val & (1U << (bit - 1))) {
161 PUTSEP;
162 if (restart)
163 continue;
164 sep = ',';
165 for (; (ch = *bitfmt) > ' '; ++bitfmt) {
166 PUTCHR(ch);
167 if (restart)
168 break;
169 }
170 } else
171 for (; *bitfmt > ' '; ++bitfmt)
172 continue;
173 }
174 } else {
175 /* new quad-capable format; also does fields. */
176 field = val;
177 while (c_fmt = bitfmt, (ch = *bitfmt++) != '\0') {
178 bit = *bitfmt++; /* now 0-origin */
179 switch (ch) {
180 case 'b':
181 if (((unsigned int)(val >> bit) & 1) == 0)
182 goto skip;
183 cur_fmt = c_fmt;
184 PUTSEP;
185 if (restart)
186 break;
187 PUTS(bitfmt);
188 if (restart == 0)
189 sep = ',';
190 break;
191 case 'f':
192 case 'F':
193 matched = 0;
194 cur_fmt = c_fmt;
195 f_len = *bitfmt++; /* field length */
196 field = (val >> bit) &
197 (((uint64_t)1 << f_len) - 1);
198 PUTSEP;
199 if (restart == 0)
200 sep = ',';
201 if (ch == 'F') { /* just extract */
202 /* duplicate PUTS() effect on bitfmt */
203 while (*bitfmt++ != '\0')
204 continue;
205 break;
206 }
207 if (restart == 0)
208 PUTS(bitfmt);
209 if (restart == 0)
210 PUTCHR('=');
211 if (restart == 0) {
212 FMTSTR(sbase, field);
213 if (l_max > 0 && (size_t)l_len > l_max)
214 PUTCHR('#');
215 }
216 break;
217 case '=':
218 case ':':
219 /*
220 * Here "bit" is actually a value instead,
221 * to be compared against the last field.
222 * This only works for values in [0..255],
223 * of course.
224 */
225 if ((int)field != bit)
226 goto skip;
227 matched = 1;
228 if (ch == '=')
229 PUTCHR('=');
230 PUTS(bitfmt);
231 break;
232 case '*':
233 bitfmt--;
234 if (!matched) {
235 matched = 1;
236 FMTSTR(bitfmt, field);
237 }
238 /*FALLTHROUGH*/
239 default:
240 skip:
241 while (*bitfmt++ != '\0')
242 continue;
243 break;
244 }
245 }
246 }
247 l_len++;
248 if (sep != '<' && (size_t)(++t_len) < buflen)
249 *bp++ = '>';
250 terminate:
251 *bp++ = '\0';
252 if (l_max != 0) {
253 t_len++;
254 *bp = '\0';
255 }
256 return t_len;
257 internal:
258 errno = EINVAL;
259 return -1;
260 }
261
262 int
snprintb(char * buf,size_t buflen,const char * bitfmt,uint64_t val)263 snprintb(char *buf, size_t buflen, const char *bitfmt, uint64_t val)
264 {
265 return snprintb_m(buf, buflen, bitfmt, val, 0);
266 }
267