From 5309d1a1556eccc17cc9f8c252671f4e63aa5978 Mon Sep 17 00:00:00 2001
From: Sascha Wildner <saw@online.de>
Date: Mon, 27 Dec 2021 18:19:23 +0100
Subject: [PATCH 2/5] libc: printf/scanf (printf passes glibc tests)

---
 lib/libc/citrus/_strtol.h     | 11 ++++++--
 lib/libc/citrus/_strtoul.h    | 11 ++++++--
 lib/libc/stdio/printf-pos.c   |  4 +++
 lib/libc/stdio/printf.3       | 31 ++++++++++++++++------
 lib/libc/stdio/printfcommon.h | 14 ++++++++++
 lib/libc/stdio/scanf.3        | 24 +++++++++++------
 lib/libc/stdio/vfprintf.c     | 35 ++++++++++++++----------
 lib/libc/stdio/vfscanf.c      | 50 +++++++++++++++++++++++++++--------
 lib/libc/stdio/vfwprintf.c    | 34 ++++++++++++++----------
 lib/libc/stdio/vfwscanf.c     | 50 +++++++++++++++++++++++++++--------
 lib/libc/stdio/wprintf.3      | 31 ++++++++++++++++------
 lib/libc/stdio/wscanf.3       | 24 +++++++++++------
 lib/libc/stdio/xprintf_int.c  | 28 +++++++++++++++++++-
 lib/libc/stdlib/_strtol.h     | 11 ++++++--
 lib/libc/stdlib/_strtoul.h    | 11 ++++++--
 lib/libc/stdlib/strtol.3      | 14 ++++++++--
 lib/libc/stdlib/strtoul.3     | 14 ++++++++--
 17 files changed, 302 insertions(+), 95 deletions(-)

diff --git a/lib/libc/citrus/_strtol.h b/lib/libc/citrus/_strtol.h
index f8213d79c3d..cd2e0e3f5db 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') &&
diff --git a/lib/libc/citrus/_strtoul.h b/lib/libc/citrus/_strtoul.h
index a695791f337..ee83e70df58 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') &&
diff --git a/lib/libc/stdio/printf-pos.c b/lib/libc/stdio/printf-pos.c
index 08a16af73b6..2adbf6cb473 100644
--- a/lib/libc/stdio/printf-pos.c
+++ b/lib/libc/stdio/printf-pos.c
@@ -408,6 +408,8 @@ reswitch:	switch (ch) {
 		case 'u':
 		case 'X':
 		case 'x':
+		case 'B':
+		case 'b':
 			if ((error = adduarg(&types, flags)))
 				goto error;
 			break;
@@ -596,6 +598,8 @@ reswitch:	switch (ch) {
 		case 'u':
 		case 'X':
 		case 'x':
+		case 'B':
+		case 'b':
 			if ((error = adduarg(&types, flags)))
 				goto error;
 			break;
diff --git a/lib/libc/stdio/printf.3 b/lib/libc/stdio/printf.3
index a19df72baca..ac4bb988c0b 100644
--- a/lib/libc/stdio/printf.3
+++ b/lib/libc/stdio/printf.3
@@ -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 (non-standard)" ".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 *"
@@ -342,7 +353,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
@@ -407,11 +418,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 9dc074a0996..462a6a0a457 100644
--- a/lib/libc/stdio/scanf.3
+++ b/lib/libc/stdio/scanf.3
@@ -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
@@ -218,7 +218,7 @@ and the next pointer is a pointer to a
 (non-standard).
 .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
@@ -227,7 +227,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
@@ -236,7 +236,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
@@ -246,7 +246,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
@@ -290,6 +290,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
@@ -303,6 +307,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..e04873cd4ea 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
@@ -318,11 +318,12 @@ __vfprintf(FILE *fp, locale_t locale, const char *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
@@ -343,17 +344,18 @@ __vfprintf(FILE *fp, locale_t locale, const char *fmt0, va_list ap)
 	char 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 */
 	char buf[BUF];		/* buffer with space for digits of uintmax_t */
-	char ox[2];		/* space for 0x; ox[1] is either x, X, or \0 */
+	char ox[2];		/* space for 0x or 0b; ox[1] is either b, B, */
+				/* x, X, or \0 */
 	union arg *argtable;    /* args, built due to positional arg */
 	union arg statargtable [STATIC_ARG_TBL_SIZE];
 	int nextarg;            /* 1-based argument index */
@@ -846,18 +848,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 +873,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
 			 */
@@ -918,7 +925,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.
 		 *
@@ -946,7 +953,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/vfscanf.c b/lib/libc/stdio/vfscanf.c
index a762356eb13..c1d5fef80fa 100644
--- a/lib/libc/stdio/vfscanf.c
+++ b/lib/libc/stdio/vfscanf.c
@@ -81,7 +81,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:
@@ -89,7 +89,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 */
 
@@ -99,7 +99,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 *);
@@ -354,10 +354,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;
 
@@ -369,10 +377,23 @@ 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;
+				goto ok;
+			}
+			/* FALLTHROUGH */
+
+		/* 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)
@@ -420,8 +441,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)
@@ -429,7 +450,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);
 	}
@@ -558,6 +579,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..bd30ed54407 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,17 +421,17 @@ __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 */
+	wchar_t ox[2];		/* space for 0x hex- or 0b binary-prefix */
 	union arg *argtable;	/* args, built due to positional arg */
 	union arg statargtable [STATIC_ARG_TBL_SIZE];
 	int nextarg;		/* 1-based argument index */
@@ -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 acad6570c37..2284ed36210 100644
--- a/lib/libc/stdio/vfwscanf.c
+++ b/lib/libc/stdio/vfwscanf.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 */
 
 #ifndef NO_FLOATING_POINT
@@ -338,10 +338,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;
 
@@ -353,10 +361,23 @@ 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;
+				goto ok;
+			}
+			/* FALLTHROUGH */
+
+		/* 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)
@@ -402,8 +423,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)
@@ -411,7 +432,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);
 	}
@@ -538,6 +559,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 908bcd9af28..8b234b04683 100644
--- a/lib/libc/stdio/wprintf.3
+++ b/lib/libc/stdio/wprintf.3
@@ -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 (non-standard)" ".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 *"
@@ -270,7 +281,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
@@ -333,11 +344,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 8dde355a9d8..dc2329becaa 100644
--- a/lib/libc/stdio/wscanf.3
+++ b/lib/libc/stdio/wscanf.3
@@ -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
@@ -219,7 +219,7 @@ and the next pointer is a pointer to a
 (non-standard).
 .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
@@ -228,7 +228,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
@@ -237,7 +237,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
@@ -247,7 +247,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
@@ -284,6 +284,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
@@ -297,6 +301,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 aa66417d136..15f67215a8d 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') &&
diff --git a/lib/libc/stdlib/_strtoul.h b/lib/libc/stdlib/_strtoul.h
index b8509b67841..4a5a22fa671 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') &&
diff --git a/lib/libc/stdlib/strtol.3 b/lib/libc/stdlib/strtol.3
index 0191d756ee8..06ba340bc26 100644
--- a/lib/libc/stdlib/strtol.3
+++ b/lib/libc/stdlib/strtol.3
@@ -32,7 +32,7 @@
 .\"     @(#)strtol.3	8.1 (Berkeley) 6/4/93
 .\" $FreeBSD: src/lib/libc/stdlib/strtol.3,v 1.23 2007/04/10 11:17:00 ru Exp $
 .\"
-.Dd December 25, 2013
+.Dd December 27, 2021
 .Dt STRTOL 3
 .Os
 .Sh NAME
@@ -126,8 +126,18 @@ If
 is zero or 16,
 the string may then include a
 .Dq Li 0x
+or
+.Dq Li 0X
+prefix,
+and the number will be read in base 16; if
+.Fa base
+is zero or 2,
+the string may then include a
+.Dq Li 0b
+or
+.Dq Li 0B
 prefix,
-and the number will be read in base 16; otherwise, a zero
+and the number will be read in base 2; otherwise, a zero
 .Fa base
 is taken as 10 (decimal) unless the next character is
 .Ql 0 ,
diff --git a/lib/libc/stdlib/strtoul.3 b/lib/libc/stdlib/strtoul.3
index fe814af642d..d7e235d3456 100644
--- a/lib/libc/stdlib/strtoul.3
+++ b/lib/libc/stdlib/strtoul.3
@@ -32,7 +32,7 @@
 .\"     @(#)strtoul.3	8.1 (Berkeley) 6/4/93
 .\" $FreeBSD: src/lib/libc/stdlib/strtoul.3,v 1.23 2007/01/09 00:28:10 imp Exp $
 .\"
-.Dd December 25, 2013
+.Dd December 27, 2021
 .Dt STRTOUL 3
 .Os
 .Sh NAME
@@ -126,8 +126,18 @@ If
 is zero or 16,
 the string may then include a
 .Dq Li 0x
+or
+.Dq Li 0X
+prefix,
+and the number will be read in base 16; if
+.Fa base
+is zero or 2,
+the string may then include a
+.Dq Li 0b
+or
+.Dq Li 0B
 prefix,
-and the number will be read in base 16; otherwise, a zero
+and the number will be read in base 2; otherwise, a zero
 .Fa base
 is taken as 10 (decimal) unless the next character is
 .Ql 0 ,
-- 
2.42.0

