From 064e7b18eba5ea2aef665d5249eaa855c80871ff Mon Sep 17 00:00:00 2001
From: Sascha Wildner <saw@online.de>
Date: Sat, 25 Dec 2021 21:35:48 +0100
Subject: [PATCH] libc: Add %b conversion to printf()/scanf().

---
 lib/libc/citrus/_strtol.h     | 11 +++++--
 lib/libc/citrus/_strtoul.h    | 11 +++++--
 lib/libc/stdio/printf.3       | 33 +++++++++++++++------
 lib/libc/stdio/printfcommon.h | 14 +++++++++
 lib/libc/stdio/scanf.3        | 26 +++++++++++------
 lib/libc/stdio/vfprintf.c     | 13 ++++++---
 lib/libc/stdio/vfscanf.c      | 54 ++++++++++++++++++++++++++++-------
 lib/libc/stdio/vfwprintf.c    | 32 ++++++++++++---------
 lib/libc/stdio/vfwscanf.c     | 54 ++++++++++++++++++++++++++++-------
 lib/libc/stdio/wprintf.3      | 33 +++++++++++++++------
 lib/libc/stdio/wscanf.3       | 26 +++++++++++------
 lib/libc/stdio/xprintf_int.c  | 28 +++++++++++++++++-
 lib/libc/stdlib/_strtol.h     | 11 +++++--
 lib/libc/stdlib/_strtoul.h    | 11 +++++--
 14 files changed, 273 insertions(+), 84 deletions(-)

diff --git a/lib/libc/citrus/_strtol.h b/lib/libc/citrus/_strtol.h
index 02c5f71a7b8..17930237cd7 100644
--- a/lib/libc/citrus/_strtol.h
+++ b/lib/libc/citrus/_strtol.h
@@ -66,8 +66,9 @@ _FUNCNAME(const char *nptr, char **endptr, int base)
 
 	/*
 	 * Skip white space and pick up leading +/- sign if any.
-	 * If base is 0, allow 0x for hex and 0 for octal, else
-	 * assume decimal; if base is already 16, allow 0x.
+	 * If base is 0, allow 0b for binary, 0x for hex
+	 * and 0 for octal, else assume decimal; if base is
+	 * already 16, allow 0x.
 	 */
 	s = nptr;
 	do {
@@ -81,6 +82,12 @@ _FUNCNAME(const char *nptr, char **endptr, int base)
 		if (c == '+')
 			c = *s++;
 	}
+	if ((base == 0 || base == 2) &&
+	    c == '0' && (*s == 'b' || *s == 'B')) {
+		c = s[1];
+		s += 2;
+		base = 2;
+	}
 	if ((base == 0 || base == 16) &&
 	    c == '0' && (*s == 'x' || *s == 'X')) {
 		c = s[1];
diff --git a/lib/libc/citrus/_strtoul.h b/lib/libc/citrus/_strtoul.h
index f4b6dbdb902..7607d8f73bb 100644
--- a/lib/libc/citrus/_strtoul.h
+++ b/lib/libc/citrus/_strtoul.h
@@ -62,8 +62,9 @@ _FUNCNAME(const char *nptr, char **endptr, int base)
 
 	/*
 	 * Skip white space and pick up leading +/- sign if any.
-	 * If base is 0, allow 0x for hex and 0 for octal, else
-	 * assume decimal; if base is already 16, allow 0x.
+	 * If base is 0, allow 0b for binary, 0x for hex
+	 * and 0 for octal, else assume decimal; if base is
+	 * already 16, allow 0x.
 	 */
 	s = nptr;
 	do {
@@ -77,6 +78,12 @@ _FUNCNAME(const char *nptr, char **endptr, int base)
 		if (c == '+')
 			c = *s++;
 	}
+	if ((base == 0 || base == 2) &&
+	    c == '0' && (*s == 'b' || *s == 'B')) {
+		c = s[1];
+		s += 2;
+		base = 2;
+	}
 	if ((base == 0 || base == 16) &&
 	    c == '0' && (*s == 'x' || *s == 'X')) {
 		c = s[1];
diff --git a/lib/libc/stdio/printf.3 b/lib/libc/stdio/printf.3
index e25db478366..e73a9aa7f76 100644
--- a/lib/libc/stdio/printf.3
+++ b/lib/libc/stdio/printf.3
@@ -32,7 +32,7 @@
 .\"     @(#)printf.3	8.1 (Berkeley) 6/4/93
 .\" $FreeBSD: head/lib/libc/stdio/printf.3 303524 2016-07-30 01:00:16Z bapt $
 .\"
-.Dd June 30, 2021
+.Dd December 25, 2021
 .Dt PRINTF 3
 .Os
 .Sh NAME
@@ -209,6 +209,17 @@ Zero or more of the following flags:
 The value should be converted to an
 .Dq alternate form .
 For
+.Cm b
+and
+.Cm B
+conversions, a non-zero result has the string
+.Ql 0b
+(or
+.Ql 0B
+for
+.Cm B
+conversions) prepended to it.
+For
 .Cm c , d , i , n , p , s ,
 and
 .Cm u
@@ -247,7 +258,7 @@ For all conversions except
 .Cm n ,
 the converted value is padded on the left with zeros rather than blanks.
 If a precision is given with a numeric conversion
-.Cm ( d , i , o , u , i , x ,
+.Cm ( b , B , d , i , o , u , i , x ,
 and
 .Cm X ) ,
 the
@@ -303,7 +314,7 @@ followed by an
 optional digit string.
 If the digit string is omitted, the precision is taken as zero.
 This gives the minimum number of digits to appear for
-.Cm d , i , o , u , x ,
+.Cm b , B , d , i , o , u , x ,
 and
 .Cm X
 conversions, the number of digits to appear after the decimal-point for
@@ -321,12 +332,12 @@ conversions.
 .It
 An optional length modifier, that specifies the size of the argument.
 The following length modifiers are valid for the
-.Cm d , i , n , o , u , x ,
+.Cm b , B , d , i , n , o , u , x ,
 or
 .Cm X
 conversion:
 .Bl -column ".Cm q Em (deprecated)" ".Vt signed char" ".Vt unsigned long long" ".Vt long long *"
-.It Sy Modifier Ta Cm d , i Ta Cm o , u , x , X Ta Cm n
+.It Sy Modifier Ta Cm d , i Ta Cm b , B , o , u , x , X Ta Cm n
 .It Cm hh Ta Vt "signed char" Ta Vt "unsigned char" Ta Vt "signed char *"
 .It Cm h Ta Vt short Ta Vt "unsigned short" Ta Vt "short *"
 .It Cm l No (ell) Ta Vt long Ta Vt "unsigned long" Ta Vt "long *"
@@ -341,7 +352,7 @@ Note:
 the
 .Cm t
 modifier, when applied to a
-.Cm o , u , x ,
+.Cm b , B , o , u , x ,
 or
 .Cm X
 conversion, indicates that the argument is of an unsigned type
@@ -405,11 +416,15 @@ If a single format directive mixes positional
 and non-positional arguments, the results are undefined.
 .Pp
 The conversion specifiers and their meanings are:
-.Bl -tag -width ".Cm diouxX"
-.It Cm diouxX
+.Bl -tag -width ".Cm bBdiouxX"
+.It Cm bBdiouxX
 The
 .Vt int
-(or appropriate variant) argument is converted to signed decimal
+(or appropriate variant) argument is converted to unsigned binary
+.Cm ( b
+and
+.Cm B ) ,
+signed decimal
 .Cm ( d
 and
 .Cm i ) ,
diff --git a/lib/libc/stdio/printfcommon.h b/lib/libc/stdio/printfcommon.h
index 9815ad5345f..4079acf3e6a 100644
--- a/lib/libc/stdio/printfcommon.h
+++ b/lib/libc/stdio/printfcommon.h
@@ -210,6 +210,13 @@ __ultoa(u_long val, CHAR *endp, int base, int octzero, const char *xdigs)
 		} while (val);
 		break;
 
+	case 2:
+		do {
+			*--cp = to_char(val & 1);
+			val >>= 1;
+		} while (val);
+		break;
+
 	default:			/* oops */
 		abort();
 	}
@@ -256,6 +263,13 @@ __ujtoa(uintmax_t val, CHAR *endp, int base, int octzero, const char *xdigs)
 		} while (val);
 		break;
 
+	case 2:
+		do {
+			*--cp = to_char(val & 1);
+			val >>= 1;
+		} while (val);
+		break;
+
 	default:
 		abort();
 	}
diff --git a/lib/libc/stdio/scanf.3 b/lib/libc/stdio/scanf.3
index 5e4328d02be..70e5663ffcf 100644
--- a/lib/libc/stdio/scanf.3
+++ b/lib/libc/stdio/scanf.3
@@ -32,7 +32,7 @@
 .\"     @(#)scanf.3	8.2 (Berkeley) 12/11/93
 .\" $FreeBSD: head/lib/libc/stdio/scanf.3 235363 2012-05-12 20:27:13Z joel $
 .\"
-.Dd August 26, 2006
+.Dd December 25, 2021
 .Dt SCANF 3
 .Os
 .Sh NAME
@@ -142,7 +142,7 @@ The conversion that follows occurs as usual, but no pointer is used;
 the result of the conversion is simply discarded.
 .It Cm hh
 Indicates that the conversion will be one of
-.Cm dioux
+.Cm bdioux
 or
 .Cm n
 and the next pointer is a pointer to a
@@ -151,7 +151,7 @@ and the next pointer is a pointer to a
 .Vt int ) .
 .It Cm h
 Indicates that the conversion will be one of
-.Cm dioux
+.Cm bdioux
 or
 .Cm n
 and the next pointer is a pointer to a
@@ -160,7 +160,7 @@ and the next pointer is a pointer to a
 .Vt int ) .
 .It Cm l No (ell)
 Indicates that the conversion will be one of
-.Cm dioux
+.Cm bdioux
 or
 .Cm n
 and the next pointer is a pointer to a
@@ -186,7 +186,7 @@ and the next pointer is a pointer to an array of
 .Vt char ) .
 .It Cm ll No (ell ell)
 Indicates that the conversion will be one of
-.Cm dioux
+.Cm bdioux
 or
 .Cm n
 and the next pointer is a pointer to a
@@ -202,7 +202,7 @@ and the next pointer is a pointer to
 .Vt "long double" .
 .It Cm j
 Indicates that the conversion will be one of
-.Cm dioux
+.Cm bdioux
 or
 .Cm n
 and the next pointer is a pointer to a
@@ -211,7 +211,7 @@ and the next pointer is a pointer to a
 .Vt int ) .
 .It Cm t
 Indicates that the conversion will be one of
-.Cm dioux
+.Cm bdioux
 or
 .Cm n
 and the next pointer is a pointer to a
@@ -220,7 +220,7 @@ and the next pointer is a pointer to a
 .Vt int ) .
 .It Cm z
 Indicates that the conversion will be one of
-.Cm dioux
+.Cm bdioux
 or
 .Cm n
 and the next pointer is a pointer to a
@@ -230,7 +230,7 @@ and the next pointer is a pointer to a
 .It Cm q
 (deprecated.)
 Indicates that the conversion will be one of
-.Cm dioux
+.Cm bdioux
 or
 .Cm n
 and the next pointer is a pointer to a
@@ -274,6 +274,10 @@ matches a single input
 .Ql %
 character.
 No conversion is done, and assignment does not occur.
+.It Cm b , B
+Matches an optionally signed binary integer;
+the next pointer must be a pointer to
+.Vt "unsigned int" .
 .It Cm d
 Matches an optionally signed decimal integer;
 the next pointer must be a pointer to
@@ -287,6 +291,10 @@ with
 .Ql 0x
 or
 .Ql 0X ,
+in base 2 if it begins with
+.Ql 0b
+or
+.Ql 0B ,
 in base 8 if it begins with
 .Ql 0 ,
 and in base 10 otherwise.
diff --git a/lib/libc/stdio/vfprintf.c b/lib/libc/stdio/vfprintf.c
index 6215a07df6d..3fb70262a9e 100644
--- a/lib/libc/stdio/vfprintf.c
+++ b/lib/libc/stdio/vfprintf.c
@@ -291,7 +291,7 @@ vfprintf(FILE * __restrict fp, const char * __restrict fmt0, va_list ap)
  * write a uintmax_t in octal (plus one byte).
  */
 #if UINTMAX_MAX <= UINT64_MAX
-#define	BUF	32
+#define	BUF	64
 #else
 #error "BUF must be large enough to format a uintmax_t"
 #endif
@@ -846,18 +846,23 @@ reswitch:	switch (ch) {
 				ulval = UARG();
 			base = 10;
 			goto nosign;
+		case 'B':
+		case 'b':
+			base = 2;
+			goto hex_or_bin;
 		case 'X':
 			xdigs = xdigs_upper;
 			goto hex;
 		case 'x':
 			xdigs = xdigs_lower;
 hex:
+			base = 16;
+hex_or_bin:
 			if (flags & INTMAX_SIZE)
 				ujval = UJARG();
 			else
 				ulval = UARG();
-			base = 16;
-			/* leading 0x/X only if non-zero */
+			/* leading 0x/X or 0b/B only if non-zero */
 			if (flags & ALT &&
 			    (flags & INTMAX_SIZE ? ujval != 0 : ulval != 0))
 				ox[1] = ch;
@@ -866,7 +871,7 @@ reswitch:	switch (ch) {
 			/* unsigned conversions */
 nosign:			sign = '\0';
 			/*-
-			 * ``... diouXx conversions ... if a precision is
+			 * ``... BbdiouXx conversions ... if a precision is
 			 * specified, the 0 flag will be ignored.''
 			 *	-- ANSI X3J11
 			 */
diff --git a/lib/libc/stdio/vfscanf.c b/lib/libc/stdio/vfscanf.c
index 7e808ae4f06..a00911d2be8 100644
--- a/lib/libc/stdio/vfscanf.c
+++ b/lib/libc/stdio/vfscanf.c
@@ -76,7 +76,7 @@
 #define	PTRDIFFT	0x1000	/* t: ptrdiff_t */
 #define	SIZET		0x2000	/* z: size_t */
 #define	SHORTSHORT	0x4000	/* hh: char */
-#define	UNSIGNED	0x8000	/* %[oupxX] conversions */
+#define	UNSIGNED	0x8000	/* %[bBoupxX] conversions */
 
 /*
  * The following are used in integral conversions only:
@@ -84,7 +84,7 @@
  */
 #define	SIGNOK		0x40	/* +/- is (still) legal */
 #define	NDIGITS		0x80	/* no digits detected */
-#define	PFXOK		0x100	/* 0x prefix is (still) legal */
+#define	PFXOK		0x100	/* 0x or 0b prefix is (still) legal */
 #define	NZDIGITS	0x200	/* no zero digits detected */
 #define	HAVESIGN	0x10000	/* sign detected */
 
@@ -94,7 +94,7 @@
 #define	CT_CHAR		0	/* %c conversion */
 #define	CT_CCL		1	/* %[...] conversion */
 #define	CT_STRING	2	/* %s conversion */
-#define	CT_INT		3	/* %[dioupxX] conversion */
+#define	CT_INT		3	/* %[bBdioupxX] conversion */
 #define	CT_FLOAT	4	/* %[efgEFG] conversion */
 
 static const u_char *__sccl(char *, const u_char *);
@@ -349,10 +349,18 @@ parseint(FILE *fp, char * __restrict buf, int width, int base, int flags)
 				flags &= ~(SIGNOK|PFXOK|NDIGITS);
 			goto ok;
 
-		/* 1 through 7 always legal */
-		case '1': case '2': case '3':
+		/* 1 always legal */
+		case '1':
+			base = basefix[base];
+			flags &= ~(SIGNOK | PFXOK | NDIGITS);
+			goto ok;
+
+		/* 2 through 7 iff octal, decimal or hex */
+		case '2': case '3':
 		case '4': case '5': case '6': case '7':
 			base = basefix[base];
+			if (base <= 2)
+				break;	/* not legal here */
 			flags &= ~(SIGNOK | PFXOK | NDIGITS);
 			goto ok;
 
@@ -364,10 +372,27 @@ parseint(FILE *fp, char * __restrict buf, int width, int base, int flags)
 			flags &= ~(SIGNOK | PFXOK | NDIGITS);
 			goto ok;
 
-		/* letters ok iff hex */
-		case 'A': case 'B': case 'C':
+		/*
+		 * b ok iff either flag still set & 2nd char (or 3rd char if
+		 * we have a sign), or hex
+		 */
+		case 'b': case 'B':
+			if (flags & PFXOK && p ==
+			    buf + 1 + !!(flags & HAVESIGN)) {
+				base = 2;	/* if %i */
+				flags &= ~PFXOK;
+			} else {
+				/* no need to fix base here */
+				if (base <= 10)
+					break;	/* not legal here */
+				flags &= ~(SIGNOK | PFXOK | NDIGITS);
+			}
+			goto ok;
+
+		/* other letters ok iff hex */
+		case 'A': case 'C':
 		case 'D': case 'E': case 'F':
-		case 'a': case 'b': case 'c':
+		case 'a': case 'c':
 		case 'd': case 'e': case 'f':
 			/* no need to fix base here */
 			if (base <= 10)
@@ -415,8 +440,8 @@ parseint(FILE *fp, char * __restrict buf, int width, int base, int flags)
 	}
 	/*
 	 * If we had only a sign, it is no good; push back the sign.
-	 * If the number ends in `x', it was [sign] '0' 'x', so push
-	 * back the x and treat it as [sign] '0'.
+	 * If the number ends in `x' or `b', it was [sign] '0' 'x' (or 'b'),
+	 * so push back the x or b and treat it as [sign] '0'.
 	 */
 	if (flags & NDIGITS) {
 		if (p > buf)
@@ -424,7 +449,7 @@ parseint(FILE *fp, char * __restrict buf, int width, int base, int flags)
 		return (0);
 	}
 	c = ((u_char *)p)[-1];
-	if (c == 'x' || c == 'X') {
+	if (c == 'b' || c == 'B' || c == 'x' || c == 'X') {
 		--p;
 		(void) __ungetc(c, fp);
 	}
@@ -553,6 +578,13 @@ again:		c = *fmt++;
 		/*
 		 * Conversions.
 		 */
+		case 'B':
+		case 'b':
+			flags |= PFXOK;	/* enable 0b prefixing */
+			c = CT_INT;
+			flags |= UNSIGNED;
+			base = 2;
+			break;
 		case 'd':
 			c = CT_INT;
 			base = 10;
diff --git a/lib/libc/stdio/vfwprintf.c b/lib/libc/stdio/vfwprintf.c
index 112262dcfbd..0e65cbdf884 100644
--- a/lib/libc/stdio/vfwprintf.c
+++ b/lib/libc/stdio/vfwprintf.c
@@ -370,7 +370,7 @@ vfwprintf(FILE * __restrict fp, const wchar_t * __restrict fmt0, va_list ap)
  * write a uintmax_t in octal (plus one byte).
  */
 #if UINTMAX_MAX <= UINT64_MAX
-#define	BUF	32
+#define	BUF	64
 #else
 #error "BUF must be large enough to format a uintmax_t"
 #endif
@@ -396,11 +396,12 @@ __vfwprintf(FILE *fp, locale_t locale, const wchar_t *fmt0, va_list ap)
 	 * We can decompose the printed representation of floating
 	 * point numbers into several parts, some of which may be empty:
 	 *
-	 * [+|-| ] [0x|0X] MMM . NNN [e|E|p|P] [+|-] ZZ
-	 *    A       B     ---C---      D       E   F
+	 * [+|-| ] [0b|0B|0x|0X] MMM . NNN [e|E|p|P] [+|-] ZZ
+	 *    A          B        ---C---      D       E   F
 	 *
 	 * A:	'sign' holds this value if present; '\0' otherwise
-	 * B:	ox[1] holds the 'x' or 'X'; '\0' if not hexadecimal
+	 * B:	ox[1] holds the 'b', 'B', 'x' or 'X'; '\0' if not binary
+	 *      or hexadecimal
 	 * C:	cp points to the string MMMNNN.  Leading and trailing
 	 *	zeros are not in the string and must be added.
 	 * D:	expchar holds this character; '\0' if no exponent, e.g. %f
@@ -420,14 +421,14 @@ __vfwprintf(FILE *fp, locale_t locale, const wchar_t *fmt0, va_list ap)
 	wchar_t expstr[MAXEXPDIG+2];	/* buffer for exponent string: e+ZZZ */
 	char *dtoaresult;	/* buffer allocated by dtoa */
 #endif
-	u_long	ulval;		/* integer arguments %[diouxX] */
+	u_long	ulval;		/* integer arguments %[bBdiouxX] */
 	uintmax_t ujval;	/* %j, %ll, %q, %t, %z integers */
-	int base;		/* base for [diouxX] conversion */
-	int dprec;		/* a copy of prec if [diouxX], 0 otherwise */
+	int base;		/* base for [bBdiouxX] conversion */
+	int dprec;		/* a copy of prec if [bBdiouxX], 0 otherwise */
 	int realsz;		/* field size expanded by dprec, sign, etc */
 	int size;		/* size of converted field or string */
 	int prsize;             /* max size of printed field */
-	const char *xdigs;	/* digits for [xX] conversion */
+	const char *xdigs;	/* digits for [bBxX] conversion */
 	struct io_state io;	/* I/O buffering state */
 	wchar_t buf[BUF];	/* buffer with space for digits of uintmax_t */
 	wchar_t ox[2];		/* space for 0x hex-prefix */
@@ -909,18 +910,23 @@ reswitch:	switch (ch) {
 				ulval = UARG();
 			base = 10;
 			goto nosign;
+		case 'B':
+		case 'b':
+			base = 2;
+			goto hex_or_bin;
 		case 'X':
 			xdigs = xdigs_upper;
 			goto hex;
 		case 'x':
 			xdigs = xdigs_lower;
 hex:
+			base = 16;
+hex_or_bin:
 			if (flags & INTMAX_SIZE)
 				ujval = UJARG();
 			else
 				ulval = UARG();
-			base = 16;
-			/* leading 0x/X only if non-zero */
+			/* leading 0x/X or 0b/B only if non-zero */
 			if (flags & ALT &&
 			    (flags & INTMAX_SIZE ? ujval != 0 : ulval != 0))
 				ox[1] = ch;
@@ -929,7 +935,7 @@ reswitch:	switch (ch) {
 			/* unsigned conversions */
 nosign:			sign = '\0';
 			/*-
-			 * ``... diouXx conversions ... if a precision is
+			 * ``... BbdiouXx conversions ... if a precision is
 			 * specified, the 0 flag will be ignored.''
 			 *	-- ANSI X3J11
 			 */
@@ -981,7 +987,7 @@ number:			if ((dprec = prec) >= 0)
 		 * first be prefixed by any sign or other prefix; otherwise,
 		 * it should be blank padded before the prefix is emitted.
 		 * After any left-hand padding and prefixing, emit zeroes
-		 * required by a decimal [diouxX] precision, then print the
+		 * required by a decimal [bBdiouxX] precision, then print the
 		 * string proper, then emit zeroes required by any leftover
 		 * floating precision; finally, if LADJUST, pad with blanks.
 		 *
@@ -1009,7 +1015,7 @@ number:			if ((dprec = prec) >= 0)
 		if (sign)
 			PRINT(&sign, 1);
 
-		if (ox[1]) {	/* ox[1] is either x, X, or \0 */
+		if (ox[1]) {	/* ox[1] is either b, B, x, X, or \0 */
 			ox[0] = '0';
 			PRINT(ox, 2);
 		}
diff --git a/lib/libc/stdio/vfwscanf.c b/lib/libc/stdio/vfwscanf.c
index 9732cde763f..c61db3786fc 100644
--- a/lib/libc/stdio/vfwscanf.c
+++ b/lib/libc/stdio/vfwscanf.c
@@ -71,7 +71,7 @@
 #define	PTRDIFFT	0x1000	/* t: ptrdiff_t */
 #define	SIZET		0x2000	/* z: size_t */
 #define	SHORTSHORT	0x4000	/* hh: char */
-#define	UNSIGNED	0x8000	/* %[oupxX] conversions */
+#define	UNSIGNED	0x8000	/* %[bBoupxX] conversions */
 
 /*
  * The following are used in integral conversions only:
@@ -79,7 +79,7 @@
  */
 #define	SIGNOK		0x40	/* +/- is (still) legal */
 #define	NDIGITS		0x80	/* no digits detected */
-#define	PFXOK		0x100	/* 0x prefix is (still) legal */
+#define	PFXOK		0x100	/* 0x or 0b prefix is (still) legal */
 #define	NZDIGITS	0x200	/* no zero digits detected */
 #define	HAVESIGN	0x10000	/* sign detected */
 
@@ -89,7 +89,7 @@
 #define	CT_CHAR		0	/* %c conversion */
 #define	CT_CCL		1	/* %[...] conversion */
 #define	CT_STRING	2	/* %s conversion */
-#define	CT_INT		3	/* %[dioupxX] conversion */
+#define	CT_INT		3	/* %[bBdioupxX] conversion */
 #define	CT_FLOAT	4	/* %[efgEFG] conversion */
 
 #ifndef NO_FLOATING_POINT
@@ -333,10 +333,18 @@ parseint(FILE *fp, wchar_t *buf, int width, int base, int flags,
 				flags &= ~(SIGNOK|PFXOK|NDIGITS);
 			goto ok;
 
-		/* 1 through 7 always legal */
-		case '1': case '2': case '3':
+		/* 1 always legal */
+		case '1':
+			base = basefix[base];
+			flags &= ~(SIGNOK | PFXOK | NDIGITS);
+			goto ok;
+
+		/* 2 through 7 iff octal, decimal or hex */
+		case '2': case '3':
 		case '4': case '5': case '6': case '7':
 			base = basefix[base];
+			if (base <= 2)
+				break;	/* not legal here */
 			flags &= ~(SIGNOK | PFXOK | NDIGITS);
 			goto ok;
 
@@ -348,10 +356,27 @@ parseint(FILE *fp, wchar_t *buf, int width, int base, int flags,
 			flags &= ~(SIGNOK | PFXOK | NDIGITS);
 			goto ok;
 
-		/* letters ok iff hex */
-		case 'A': case 'B': case 'C':
+		/*
+		 * b ok iff either flag still set & 2nd char (or 3rd char if
+		 * we have a sign), or hex
+		 */
+		case 'b': case 'B':
+			if (flags & PFXOK && wcp ==
+			    buf + 1 + !!(flags & HAVESIGN)) {
+				base = 2;	/* if %i */
+				flags &= ~PFXOK;
+			} else {
+				/* no need to fix base here */
+				if (base <= 10)
+					break;	/* not legal here */
+				flags &= ~(SIGNOK | PFXOK | NDIGITS);
+			}
+			goto ok;
+
+		/* other letters ok iff hex */
+		case 'A': case 'C':
 		case 'D': case 'E': case 'F':
-		case 'a': case 'b': case 'c':
+		case 'a': case 'c':
 		case 'd': case 'e': case 'f':
 			/* no need to fix base here */
 			if (base <= 10)
@@ -397,8 +422,8 @@ parseint(FILE *fp, wchar_t *buf, int width, int base, int flags,
 	}
 	/*
 	 * If we had only a sign, it is no good; push back the sign.
-	 * If the number ends in `x', it was [sign] '0' 'x', so push
-	 * back the x and treat it as [sign] '0'.
+	 * If the number ends in `x' or `b', it was [sign] '0' 'x' (or 'b'),
+	 * so push back the x or b and treat it as [sign] '0'.
 	 */
 	if (flags & NDIGITS) {
 		if (wcp > buf)
@@ -406,7 +431,7 @@ parseint(FILE *fp, wchar_t *buf, int width, int base, int flags,
 		return (0);
 	}
 	c = wcp[-1];
-	if (c == 'x' || c == 'X') {
+	if (c == 'b' || c == 'B' || c == 'x' || c == 'X') {
 		--wcp;
 		__ungetwc(c, fp, locale);
 	}
@@ -533,6 +558,13 @@ again:		c = *fmt++;
 		/*
 		 * Conversions.
 		 */
+		case 'B':
+		case 'b':
+			flags |= PFXOK;	/* enable 0b prefixing */
+			c = CT_INT;
+			flags |= UNSIGNED;
+			base = 2;
+			break;
 		case 'd':
 			c = CT_INT;
 			base = 10;
diff --git a/lib/libc/stdio/wprintf.3 b/lib/libc/stdio/wprintf.3
index b2cf9feae4d..22519236bbf 100644
--- a/lib/libc/stdio/wprintf.3
+++ b/lib/libc/stdio/wprintf.3
@@ -32,7 +32,7 @@
 .\"     @(#)printf.3	8.1 (Berkeley) 6/4/93
 .\" $FreeBSD: head/lib/libc/stdio/wprintf.3 208027 2010-05-13 12:07:55Z uqs $
 .\"
-.Dd July 5, 2003
+.Dd December 25, 2021
 .Dt WPRINTF 3
 .Os
 .Sh NAME
@@ -136,6 +136,17 @@ Zero or more of the following flags:
 The value should be converted to an
 .Dq alternate form .
 For
+.Cm b
+and
+.Cm B
+conversions, a non-zero result has the string
+.Ql 0b
+(or
+.Ql 0B
+for
+.Cm B
+conversions) prepended to it.
+For
 .Cm c , d , i , n , p , s ,
 and
 .Cm u
@@ -175,7 +186,7 @@ For all conversions except
 .Cm n ,
 the converted value is padded on the left with zeros rather than blanks.
 If a precision is given with a numeric conversion
-.Cm ( d , i , o , u , i , x ,
+.Cm ( b , B , d , i , o , u , i , x ,
 and
 .Cm X ) ,
 the
@@ -231,7 +242,7 @@ followed by an
 optional digit string.
 If the digit string is omitted, the precision is taken as zero.
 This gives the minimum number of digits to appear for
-.Cm d , i , o , u , x ,
+.Cm b , B , d , i , o , u , x ,
 and
 .Cm X
 conversions, the number of digits to appear after the decimal-point for
@@ -249,12 +260,12 @@ conversions.
 .It
 An optional length modifier, that specifies the size of the argument.
 The following length modifiers are valid for the
-.Cm d , i , n , o , u , x ,
+.Cm b , B , d , i , n , o , u , x ,
 or
 .Cm X
 conversion:
 .Bl -column ".Cm q Em (deprecated)" ".Vt signed char" ".Vt unsigned long long" ".Vt long long *"
-.It Sy Modifier Ta Cm d , i Ta Cm o , u , x , X Ta Cm n
+.It Sy Modifier Ta Cm d , i Ta Cm b , B , o , u , x , X Ta Cm n
 .It Cm hh Ta Vt "signed char" Ta Vt "unsigned char" Ta Vt "signed char *"
 .It Cm h Ta Vt short Ta Vt "unsigned short" Ta Vt "short *"
 .It Cm l No (ell) Ta Vt long Ta Vt "unsigned long" Ta Vt "long *"
@@ -269,7 +280,7 @@ Note:
 the
 .Cm t
 modifier, when applied to a
-.Cm o , u , x ,
+.Cm b , B , o , u , x ,
 or
 .Cm X
 conversion, indicates that the argument is of an unsigned type
@@ -331,11 +342,15 @@ If a single format directive mixes positional
 and non-positional arguments, the results are undefined.
 .Pp
 The conversion specifiers and their meanings are:
-.Bl -tag -width ".Cm diouxX"
-.It Cm diouxX
+.Bl -tag -width ".Cm bBdiouxX"
+.It Cm bBdiouxX
 The
 .Vt int
-(or appropriate variant) argument is converted to signed decimal
+(or appropriate variant) argument is converted to unsigned binary
+.Cm ( b
+and
+.Cm B ) ,
+signed decimal
 .Cm ( d
 and
 .Cm i ) ,
diff --git a/lib/libc/stdio/wscanf.3 b/lib/libc/stdio/wscanf.3
index 422154fe7c6..a60ac182a44 100644
--- a/lib/libc/stdio/wscanf.3
+++ b/lib/libc/stdio/wscanf.3
@@ -33,7 +33,7 @@
 .\" FreeBSD: src/lib/libc/stdio/scanf.3,v 1.24 2003/06/28 09:03:25 das Exp
 .\" $FreeBSD: head/lib/libc/stdio/wscanf.3 235363 2012-05-12 20:27:13Z joel $
 .\"
-.Dd July 5, 2003
+.Dd December 25, 2021
 .Dt WSCANF 3
 .Os
 .Sh NAME
@@ -144,7 +144,7 @@ The conversion that follows occurs as usual, but no pointer is used;
 the result of the conversion is simply discarded.
 .It Cm hh
 Indicates that the conversion will be one of
-.Cm dioux
+.Cm bdioux
 or
 .Cm n
 and the next pointer is a pointer to a
@@ -153,7 +153,7 @@ and the next pointer is a pointer to a
 .Vt int ) .
 .It Cm h
 Indicates that the conversion will be one of
-.Cm dioux
+.Cm bdioux
 or
 .Cm n
 and the next pointer is a pointer to a
@@ -162,7 +162,7 @@ and the next pointer is a pointer to a
 .Vt int ) .
 .It Cm l No (ell)
 Indicates that the conversion will be one of
-.Cm dioux
+.Cm bdioux
 or
 .Cm n
 and the next pointer is a pointer to a
@@ -187,7 +187,7 @@ and the next pointer is a pointer to an array of
 .Vt char ) .
 .It Cm ll No (ell ell)
 Indicates that the conversion will be one of
-.Cm dioux
+.Cm bdioux
 or
 .Cm n
 and the next pointer is a pointer to a
@@ -203,7 +203,7 @@ and the next pointer is a pointer to
 .Vt "long double" .
 .It Cm j
 Indicates that the conversion will be one of
-.Cm dioux
+.Cm bdioux
 or
 .Cm n
 and the next pointer is a pointer to a
@@ -212,7 +212,7 @@ and the next pointer is a pointer to a
 .Vt int ) .
 .It Cm t
 Indicates that the conversion will be one of
-.Cm dioux
+.Cm bdioux
 or
 .Cm n
 and the next pointer is a pointer to a
@@ -221,7 +221,7 @@ and the next pointer is a pointer to a
 .Vt int ) .
 .It Cm z
 Indicates that the conversion will be one of
-.Cm dioux
+.Cm bdioux
 or
 .Cm n
 and the next pointer is a pointer to a
@@ -231,7 +231,7 @@ and the next pointer is a pointer to a
 .It Cm q
 (deprecated.)
 Indicates that the conversion will be one of
-.Cm dioux
+.Cm bdioux
 or
 .Cm n
 and the next pointer is a pointer to a
@@ -268,6 +268,10 @@ matches a single input
 .Ql %
 character.
 No conversion is done, and assignment does not occur.
+.It Cm b , B
+Matches an optionally signed binary integer;
+the next pointer must be a pointer to
+.Vt "unsigned int" .
 .It Cm d
 Matches an optionally signed decimal integer;
 the next pointer must be a pointer to
@@ -281,6 +285,10 @@ with
 .Ql 0x
 or
 .Ql 0X ,
+in base 2 if it begins with
+.Ql 0b
+or
+.Ql 0B ,
 in base 8 if it begins with
 .Ql 0 ,
 and in base 10 otherwise.
diff --git a/lib/libc/stdio/xprintf_int.c b/lib/libc/stdio/xprintf_int.c
index 89cba34bcb1..4fb344180e0 100644
--- a/lib/libc/stdio/xprintf_int.c
+++ b/lib/libc/stdio/xprintf_int.c
@@ -153,6 +153,13 @@ __ultoa(u_long val, char *endp, int base, const char *xdigs,
 		} while (val);
 		break;
 
+	case 2:
+		do {
+			*--cp = to_char(val & 1);
+			val >>= 1;
+		} while (val);
+		break;
+
 	default:			/* oops */
 		assert(base == 16);
 	}
@@ -220,6 +227,13 @@ __ujtoa(uintmax_t val, char *endp, int base, const char *xdigs,
 		} while (val);
 		break;
 
+	case 2:
+		do {
+			*--cp = to_char(val & 1);
+			val >>= 1;
+		} while (val);
+		break;
+
 	default:
 		abort();
 	}
@@ -307,6 +321,11 @@ __printf_render_int(struct __printf_io *io, const struct printf_info *pi,
 		rdx = 8;
 		sign = 0;
 		break;
+	case 'b':
+	case 'B':
+		rdx = 2;
+		sign = 0;
+		break;
 	default:
 		fprintf(stderr, "pi->spec = '%c'\n", pi->spec);
 		assert(1 == 0);
@@ -381,13 +400,20 @@ __printf_render_int(struct __printf_io *io, const struct printf_info *pi,
 				nalt = "0X";
 			l += 2;
 		}
+		if (rdx == 2) {
+			if (pi->spec == 'b')
+				nalt = "0b";
+			else
+				nalt = "0B";
+			l += 2;
+		}
 	}
 	l += pe - p;
 	if (ns)
 		l++;
 
 	/*-
-	 * ``... diouXx conversions ... if a precision is
+	 * ``... BbdiouXx conversions ... if a precision is
 	 * specified, the 0 flag will be ignored.''
 	 *      -- ANSI X3J11
 	 */
diff --git a/lib/libc/stdlib/_strtol.h b/lib/libc/stdlib/_strtol.h
index d170f1bc4f1..27b529d9809 100644
--- a/lib/libc/stdlib/_strtol.h
+++ b/lib/libc/stdlib/_strtol.h
@@ -65,8 +65,9 @@ _FUNCNAME_L(const char * __restrict nptr, char ** __restrict endptr, int base,
 
 	/*
 	 * Skip white space and pick up leading +/- sign if any.
-	 * If base is 0, allow 0x for hex and 0 for octal, else
-	 * assume decimal; if base is already 16, allow 0x.
+	 * If base is 0, allow 0b for binary, 0x for hex
+	 * and 0 for octal, else assume decimal; if base is
+	 * already 16, allow 0x.
 	 */
 	s = nptr;
 	do {
@@ -80,6 +81,12 @@ _FUNCNAME_L(const char * __restrict nptr, char ** __restrict endptr, int base,
 		if (c == '+')
 			c = *s++;
 	}
+	if ((base == 0 || base == 2) &&
+	    c == '0' && (*s == 'b' || *s == 'B')) {
+		c = s[1];
+		s += 2;
+		base = 2;
+	}
 	if ((base == 0 || base == 16) &&
 	    c == '0' && (*s == 'x' || *s == 'X')) {
 		c = s[1];
diff --git a/lib/libc/stdlib/_strtoul.h b/lib/libc/stdlib/_strtoul.h
index 23e74c9d0d7..10487105b02 100644
--- a/lib/libc/stdlib/_strtoul.h
+++ b/lib/libc/stdlib/_strtoul.h
@@ -65,8 +65,9 @@ _FUNCNAME_L(const char * __restrict nptr, char ** __restrict endptr, int base,
 
 	/*
 	 * Skip white space and pick up leading +/- sign if any.
-	 * If base is 0, allow 0x for hex and 0 for octal, else
-	 * assume decimal; if base is already 16, allow 0x.
+	 * If base is 0, allow 0b for binary, 0x for hex
+	 * and 0 for octal, else assume decimal; if base is
+	 * already 16, allow 0x.
 	 */
 	s = nptr;
 	do {
@@ -80,6 +81,12 @@ _FUNCNAME_L(const char * __restrict nptr, char ** __restrict endptr, int base,
 		if (c == '+')
 			c = *s++;
 	}
+	if ((base == 0 || base == 2) &&
+	    c == '0' && (*s == 'b' || *s == 'B')) {
+		c = s[1];
+		s += 2;
+		base = 2;
+	}
 	if ((base == 0 || base == 16) &&
 	    c == '0' && (*s == 'x' || *s == 'X')) {
 		c = s[1];
-- 
2.32.0

