xref: /dragonfly/lib/libutil/snprintb.c (revision 1d3001ef599cad179a2b2ad4b24a0f002ecdf498)
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