[PATCH v2] _scanf.c: Implement 'm' modifier for 'c' and '[' conversions.

Max Filippov <[email protected]>
Newsgroups gmane.comp.lib.uclibc.general
Message-ID <[email protected]>
From: Will Newton <[email protected]>

The current code implements the 'm' modifier only for 's'
conversions and would cause a segfault if it was used for 'c'
or '[' conversions. This patch extends the code to cover these
cases too.

The original version could write scanned data outside the passed buffer
because index i used in the '[' conversion handling block was clobbered.

Signed-off-by: Will Newton <[email protected]>
Signed-off-by: Max Filippov <[email protected]>
---
Changes v1->v2:
- add testcase for %[...]/%c/%m[...]/%mc.

 libc/stdio/_scanf.c  | 51 +++++++++++++++++++++++++++++++++------------------
 test/stdio/scanf_m.c | 24 ++++++++++++++++++++++++
 2 files changed, 57 insertions(+), 18 deletions(-)
 create mode 100644 test/stdio/scanf_m.c

diff --git a/libc/stdio/_scanf.c b/libc/stdio/_scanf.c
index 6ecb3cb..a5828c3 100644
--- a/libc/stdio/_scanf.c
+++ b/libc/stdio/_scanf.c
@@ -1352,7 +1352,20 @@ int VFSCANF (FILE *__restrict fp, const Wchar *__restrict format, va_list arg)
 				(psfs.conv_num >= CONV_c)
 #endif /* __UCLIBC_HAS_WCHAR__ */
 			{
+				/* We might have to handle the allocation ourselves */
+				int len;
+				unsigned char **ptr;
+
 				b = (psfs.store ? ((unsigned char *) psfs.cur_ptr) : buf);
+				/* With 'm', we actually got a pointer to a pointer */
+				ptr = (void *)b;
+
+				if (psfs.flags & FLAG_MALLOC) {
+					len = 0;
+					b = NULL;
+				} else
+					len = -1;
+
 				fail = 1;
 
 				if (psfs.conv_num == CONV_c) {
@@ -1360,32 +1373,28 @@ int VFSCANF (FILE *__restrict fp, const Wchar *__restrict format, va_list arg)
 						sc.width = 1;
 					}
 
+					if (psfs.flags & FLAG_MALLOC)
+						b = malloc(sc.width);
+
+					i = 0;
 					while (__scan_getc(&sc) >= 0) {
 						zero_conversions = 0;
-						*b = sc.cc;
-						b += psfs.store;
+						b[i] = sc.cc;
+						i += psfs.store;
 					}
 					__scan_ungetc(&sc);
 					if (sc.width > 0) {	/* Failed to read all required. */
 						goto DONE;
 					}
+					if (psfs.flags & FLAG_MALLOC)
+						*ptr = b;
 					psfs.cnt += psfs.store;
 					goto NEXT_FMT;
 				}
 
 				if (psfs.conv_num == CONV_s) {
-					/* We might have to handle the allocation ourselves */
-					int len;
-					/* With 'm', we actually got a pointer to a pointer */
-					unsigned char **ptr = (void *)b;
 
 					i = 0;
-					if (psfs.flags & FLAG_MALLOC) {
-						len = 0;
-						b = NULL;
-					} else
-						len = -1;
-
 					/* Yes, believe it or not, a %s conversion can store nuls. */
 					while ((__scan_getc(&sc) >= 0) && !isspace(sc.cc)) {
 						zero_conversions = 0;
@@ -1400,10 +1409,6 @@ int VFSCANF (FILE *__restrict fp, const Wchar *__restrict format, va_list arg)
 						fail = 0;
 					}
 
-					if (psfs.flags & FLAG_MALLOC)
-						*ptr = b;
-					/* The code below takes care of terminating NUL */
-					b += i;
 				} else {
 #ifdef __UCLIBC_HAS_WCHAR__
 					assert((psfs.conv_num == CONV_LEFTBRACKET) || \
@@ -1454,13 +1459,20 @@ int VFSCANF (FILE *__restrict fp, const Wchar *__restrict format, va_list arg)
 #endif /* __UCLIBC_HAS_WCHAR__ */
 
 
+					i = 0;
 					while (__scan_getc(&sc) >= 0) {
 						zero_conversions = 0;
 						if (!scanset[sc.cc]) {
 							break;
 						}
-						*b = sc.cc;
-						b += psfs.store;
+						if (i == len) {
+							/* Pick a size that won't trigger a lot of
+							 * mallocs early on ... */
+							len += 256;
+							b = realloc(b, len + 1);
+						}
+						b[i] = sc.cc;
+						i += psfs.store;
 						fail = 0;
 					}
 				}
@@ -1470,6 +1482,9 @@ int VFSCANF (FILE *__restrict fp, const Wchar *__restrict format, va_list arg)
 				if (fail) {	/* nothing stored! */
 					goto DONE;
 				}
+				if (psfs.flags & FLAG_MALLOC)
+					*ptr = b;
+				b += i;
 				*b = 0;		/* Nul-terminate string. */
 				psfs.cnt += psfs.store;
 				goto NEXT_FMT;
diff --git a/test/stdio/scanf_m.c b/test/stdio/scanf_m.c
new file mode 100644
index 0000000..0ce78b6
--- /dev/null
+++ b/test/stdio/scanf_m.c
@@ -0,0 +1,24 @@
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+int main(void)
+{
+	const char *buf = "hello world";
+	char *ps = NULL, *pc = NULL;
+	char s[6], c;
+
+	/* Check that %[...]/%c work. */
+	sscanf(buf, "%[a-z] %c", s, &c);
+	/* Check that %m[...]/%mc work. */
+	sscanf(buf, "%m[a-z] %mc", &ps, &pc);
+
+	if (strcmp(ps, "hello") != 0 || *pc != 'w' ||
+	    strcmp(s, "hello") != 0 || c != 'w')
+		return 1;
+
+	free(ps);
+	free(pc);
+
+	return 0;
+}
-- 
1.8.1.4
lmpx.com only provides a reader for public news (NNTP) servers. It is not affiliated with the servers or forums shown here and is not responsible for the content of articles, which is written by their respective authors.