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