summary refs log tree commit diff
path: root/src/bltin
diff options
context:
space:
mode:
authorHerbert Xu <herbert@gondor.apana.org.au>2014-10-27 12:19:25 +0800
committerHerbert Xu <herbert@gondor.apana.org.au>2014-10-27 12:19:25 +0800
commitd6c0e1e2ffbf7913ab69d51cc794d48d41c8fcb1 (patch)
tree557d3818ec91ef4e281b49fd43f208a7e8383ac7 /src/bltin
parent[BUILTIN] Add printf support for format string a, A, and F (diff)
downloaddash-d6c0e1e2ffbf7913ab69d51cc794d48d41c8fcb1.tar.gz
dash-d6c0e1e2ffbf7913ab69d51cc794d48d41c8fcb1.zip
[BUILTIN] Handle embedded NULs correctly in printf
https://bugs.debian.org/cgi-bin/bugreport.cgi?bug=379227

On Sat, Jul 22, 2006 at 12:48:38PM +0200, A Mennucc wrote:
> Package: dash
> Version: 0.5.3-3
> Severity: normal
> 
> hi
> 
> here are the examples
> 
> $ bash -c 'echo -n -e "A\0102C\00D\0E" | hexdump -c'
> 0000000   A   B   C  \0   D  \0   E
> 0000007
> 
> $ /bin/echo -n -e "A\0102C\00D\0E" | hexdump -c
> 0000000   A   B   C  \0   D  \0   E
> 0000007
> 
> $ zsh -c 'echo -n -e "A\0102C\00D\0E" | hexdump -c'
> 0000000   A   B   C  \0   D  \0   E
> 0000007
> 
> $ dash -c 'echo -n  "A\0102C\00D\0E" | hexdump -c'
> 0000000   A   B   C
> 0000003
> 
> and also
> 
> $ dash -c 'echo -n  "ABC\0DEFGH" | hexdump -c'
> 0000000   A   B   C
> 0000003
> 
> As you see, dash 's builtin echo truncates the output at the first \0
> 
> a.
> 
> -- System Information:
> Debian Release: testing/unstable
>   APT prefers unstable
>   APT policy: (500, 'unstable'), (500, 'testing')
> Architecture: i386 (i686)
> Shell:  /bin/sh linked to /bin/bash
> Kernel: Linux 2.6.16-1-k7
> Locale: LANG=it_IT.UTF-8, LC_CTYPE=it_IT.UTF-8 (charmap=UTF-8)
> 
> Versions of packages dash depends on:
> ii  libc6                         2.3.6-15   GNU C Library: Shared libraries
> 
> dash recommends no packages.
> 
> -- debconf information:
> * dash/sh: false
> 
> -- 
> Andrea Mennucc
>  "E' un mondo difficile. Che vita intensa!" (Tonino Carotone)

This patch fixes handling of embedded NULs using an approach similar
to the one taken by NetBSD.  In particular, we first determine the
length of the output string, and then use a sequence of Xs of the
same length as input to the underlying C printf to determine the
amount of leading and trailing padding.  Finally we replace the
Xs with the actual string before writing it out.

In order to print out the temporary string containing Xs and padding,
a new helper xasprintf is added.  Unlike asprintf though, our
xasprintf prints to the ash stack rather than using straight malloc
memory.

Signed-off-by: Herbert Xu <herbert@gondor.apana.org.au>
Diffstat (limited to '')
-rw-r--r--src/bltin/printf.c80
1 files changed, 62 insertions, 18 deletions
diff --git a/src/bltin/printf.c b/src/bltin/printf.c
index e0ef912..213025f 100644
--- a/src/bltin/printf.c
+++ b/src/bltin/printf.c
@@ -40,7 +40,7 @@
 #include <string.h>
 #include <unistd.h>
 
-static int	 conv_escape_str(char *);
+static int	 conv_escape_str(char *, char **);
 static char	*conv_escape(char *, int *);
 static int	 getchr(void);
 static double	 getdouble(void);
@@ -73,6 +73,53 @@ static char  **gargv;
 	} \
 }
 
+#define ASPF(sp, f, func) ({ \
+	int ret; \
+	switch ((char *)param - (char *)array) { \
+	default: \
+		ret = xasprintf(sp, f, array[0], array[1], func); \
+		break; \
+	case sizeof(*param): \
+		ret = xasprintf(sp, f, array[0], func); \
+		break; \
+	case 0: \
+		ret = xasprintf(sp, f, func); \
+		break; \
+	} \
+	ret; \
+})
+
+
+static int print_escape_str(const char *f, int *param, int *array, char *s)
+{
+	struct stackmark smark;
+	char *p, *q;
+	int done;
+	int len;
+	int total;
+
+	setstackmark(&smark);
+	done = conv_escape_str(s, &p);
+	q = stackblock();
+	len = p - q;
+
+	p = makestrspace(len, p);
+	memset(p, 'X', len - 1);
+	p[len - 1] = 0;
+
+	q = stackblock();
+	total = ASPF(&p, f, p);
+
+	len = strchrnul(p, 'X') - p;
+	memcpy(p + len, q, strchrnul(p + len, ' ') - (p + len));
+
+	out1mem(p, total);
+
+	popstackmark(&smark);
+	return done;
+}
+
+
 int printfcmd(int argc, char *argv[])
 {
 	char *fmt;
@@ -154,17 +201,14 @@ pc:
 			fmt[1] = 0;
 			switch (ch) {
 
-			case 'b': {
-				int done = conv_escape_str(getstr());
-				char *p = stackblock();
+			case 'b':
 				*fmt = 's';
-				PF(start, p);
 				/* escape if a \c was encountered */
-				if (done)
+				if (print_escape_str(start, param, array,
+						     getstr()))
 					goto out;
 				*fmt = 'b';
 				break;
-			}
 			case 'c': {
 				int p = getchr();
 				PF(start, p);
@@ -223,8 +267,9 @@ err:
  *	Halts processing string if a \c escape is encountered.
  */
 static int
-conv_escape_str(char *str)
+conv_escape_str(char *str, char **sp)
 {
+	int c;
 	int ch;
 	char *cp;
 
@@ -232,16 +277,14 @@ conv_escape_str(char *str)
 	STARTSTACKSTR(cp);
 
 	do {
-		int c;
-
-		ch = *str++;
+		c = ch = *str++;
 		if (ch != '\\')
 			continue;
 
 		ch = *str++;
 		if (ch == 'c') {
 			/* \c as in SYSV echo - abort all processing.... */
-			ch = 0x100;
+			c = ch = 0x100;
 			continue;
 		}
 
@@ -253,14 +296,14 @@ conv_escape_str(char *str)
 		if (ch == '0') {
 			unsigned char i;
 			i = 3;
-			ch = 0;
+			c = 0;
 			do {
 				unsigned k = octtobin(*str);
 				if (k > 7)
 					break;
 				str++;
-				ch <<= 3;
-				ch += k;
+				c <<= 3;
+				c += k;
 			} while (--i);
 			continue;
 		}
@@ -268,7 +311,9 @@ conv_escape_str(char *str)
 		/* Finally test for sequences valid in the format string */
 		str = conv_escape(str - 1, &c);
 		ch = c;
-	} while (STPUTC(ch, cp), (char)ch);
+	} while (STPUTC(c, cp), (char)ch);
+
+	*sp = cp;
 
 	return ch;
 }
@@ -456,8 +501,7 @@ echocmd(int argc, char **argv)
 	do {
 		int c;
 
-		nonl += conv_escape_str(*argv);
-		outstr(stackblock(), outs);
+		nonl += print_escape_str("%s", NULL, NULL, *argv);
 		if (nonl > 0)
 			break;