Index: uspace/lib/c/Makefile
===================================================================
--- uspace/lib/c/Makefile	(revision ec397203e399886ac45d617da8098ea6ae81f914)
+++ uspace/lib/c/Makefile	(revision ed18e148046bfdc424427e6fcc350b1718b2628b)
@@ -131,4 +131,5 @@
 	generic/double_to_str.c \
 	generic/malloc.c \
+	generic/stdio/scanf.c \
 	generic/sysinfo.c \
 	generic/ipc.c \
@@ -185,4 +186,5 @@
 	test/main.c \
 	test/io/table.c \
+	test/stdio/scanf.c \
 	test/odict.c \
 	test/qsort.c \
Index: uspace/lib/c/generic/stdio/scanf.c
===================================================================
--- uspace/lib/c/generic/stdio/scanf.c	(revision ed18e148046bfdc424427e6fcc350b1718b2628b)
+++ uspace/lib/c/generic/stdio/scanf.c	(revision ed18e148046bfdc424427e6fcc350b1718b2628b)
@@ -0,0 +1,1366 @@
+/*
+ * Copyright (c) 2018 Jiri Svoboda
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ *
+ * - Redistributions of source code must retain the above copyright
+ *   notice, this list of conditions and the following disclaimer.
+ * - Redistributions in binary form must reproduce the above copyright
+ *   notice, this list of conditions and the following disclaimer in the
+ *   documentation and/or other materials provided with the distribution.
+ * - The name of the author may not be used to endorse or promote products
+ *   derived from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
+ * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
+ * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
+ * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
+ * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
+ * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
+ * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+/** @addtogroup libc
+ * @{
+ */
+/**
+ * @file
+ * @brief Formatted input (scanf family)
+ */
+
+#include <assert.h>
+#include <_bits/ssize_t.h>
+#include <ctype.h>
+#include <errno.h>
+#include <limits.h>
+#include <stdarg.h>
+#include <stdbool.h>
+#include <stddef.h>
+#include <stdint.h>
+#include <stdio.h>
+#include <stdlib.h>
+
+typedef enum {
+	/** No length modifier */
+	lm_none,
+	/** 'hh' */
+	lm_hh,
+	/** 'h' */
+	lm_h,
+	/** 'l' */
+	lm_l,
+	/** 'll' */
+	lm_ll,
+	/** 'j' */
+	lm_j,
+	/** 'z' */
+	lm_z,
+	/** 't' */
+	lm_t,
+	/** 'L' */
+	lm_L
+} lenmod_t;
+
+typedef enum {
+	/** Unknown */
+	cs_unknown,
+	/** 'd' */
+	cs_decimal,
+	/** 'i' */
+	cs_int,
+	/** 'o' */
+	cs_octal,
+	/** 'u' */
+	cs_udecimal,
+	/** 'x', 'X' */
+	cs_hex,
+	/** 'a', 'e', 'f', 'g', 'A', 'E', 'F', 'G' */
+	cs_float,
+	/** 'c' */
+	cs_char,
+	/** 's' */
+	cs_str,
+	/* [...] */
+	cs_set,
+	/* 'p' */
+	cs_ptr,
+	/* 'n' */
+	cs_numchar,
+	/* '%' */
+	cs_percent
+} cvtspcr_t;
+
+/** Conversion specification */
+typedef struct {
+	/** Suppress assignment */
+	bool noassign;
+	/** Allocate memory for string (GNU extension) */
+	bool memalloc;
+	/** @c true if width field is valid */
+	bool have_width;
+	/** Width */
+	size_t width;
+	/** Length modifier */
+	lenmod_t lenmod;
+	/** Conversion specifier */
+	cvtspcr_t spcr;
+	/** Scan set (if spc == cs_set) */
+	const char *scanset;
+} cvtspec_t;
+
+/** Buffer for writing strings */
+typedef struct {
+	/** Buffer */
+	char *buf;
+	/** Place where to store pointer to the buffer */
+	char **pptr;
+	/** Allocating memory for caller */
+	bool memalloc;
+	/** Current size of allocated buffer */
+	size_t size;
+} strbuf_t;
+
+static int digit_value(char digit)
+{
+	switch (digit) {
+	case '0':
+		return 0;
+	case '1':
+		return 1;
+	case '2':
+		return 2;
+	case '3':
+		return 3;
+	case '4':
+		return 4;
+	case '5':
+		return 5;
+	case '6':
+		return 6;
+	case '7':
+		return 7;
+	case '8':
+		return 8;
+	case '9':
+		return 9;
+	case 'a':
+	case 'A':
+		return 10;
+	case 'b':
+	case 'B':
+		return 11;
+	case 'c':
+	case 'C':
+		return 12;
+	case 'd':
+	case 'D':
+		return 13;
+	case 'e':
+	case 'E':
+		return 14;
+	case 'f':
+	case 'F':
+		return 15;
+	default:
+		assert(false);
+	}
+}
+
+static void cvtspec_parse(const char **fmt, cvtspec_t *spec)
+{
+	spec->scanset = NULL;
+	spec->width = 0;
+
+	/* Assignment suppresion */
+
+	if (**fmt == '*') {
+		spec->noassign = true;
+		++(*fmt);
+	} else {
+		spec->noassign = false;
+	}
+
+	/* Memory allocation */
+
+	if (**fmt == 'm') {
+		spec->memalloc = true;
+		++(*fmt);
+	} else {
+		spec->memalloc = false;
+	}
+
+	/* Width specifier */
+
+	if (isdigit(**fmt)) {
+		spec->have_width = true;
+		assert(**fmt != '0');
+		spec->width = 0;
+		while (isdigit(**fmt)) {
+			spec->width *= 10;
+			spec->width += digit_value(**fmt);
+			++(*fmt);
+		}
+	} else {
+		spec->have_width = false;
+	}
+
+	/* Length modifier */
+
+	switch (**fmt) {
+	case 'h':
+		++(*fmt);
+		if (**fmt == 'h') {
+			spec->lenmod = lm_hh;
+			++(*fmt);
+		} else {
+			spec->lenmod = lm_h;
+		}
+		break;
+	case 'l':
+		++(*fmt);
+		if (**fmt == 'l') {
+			spec->lenmod = lm_ll;
+			++(*fmt);
+		} else {
+			spec->lenmod = lm_l;
+		}
+		break;
+	case 'j':
+		++(*fmt);
+		spec->lenmod = lm_j;
+		break;
+	case 'z':
+		++(*fmt);
+		spec->lenmod = lm_z;
+		break;
+	case 't':
+		++(*fmt);
+		spec->lenmod = lm_t;
+		break;
+	case 'L':
+		++(*fmt);
+		spec->lenmod = lm_L;
+		break;
+	default:
+		spec->lenmod = lm_none;
+		break;
+	}
+
+	/* Conversion specifier */
+
+	switch (**fmt) {
+	case 'd':
+		++(*fmt);
+		spec->spcr = cs_decimal;
+		break;
+	case 'i':
+		++(*fmt);
+		spec->spcr = cs_int;
+		break;
+	case 'o':
+		++(*fmt);
+		spec->spcr = cs_octal;
+		break;
+	case 'u':
+		++(*fmt);
+		spec->spcr = cs_udecimal;
+		break;
+	case 'x':
+	case 'X':
+		++(*fmt);
+		spec->spcr = cs_hex;
+		break;
+	case 'a':
+	case 'e':
+	case 'f':
+	case 'g':
+	case 'A':
+	case 'E':
+	case 'F':
+	case 'G':
+		++(*fmt);
+		spec->spcr = cs_float;
+		break;
+	case 'c':
+		++(*fmt);
+		spec->spcr = cs_char;
+		break;
+	case 's':
+		++(*fmt);
+		spec->spcr = cs_str;
+		break;
+	case '[':
+		++(*fmt);
+		spec->spcr = cs_set;
+		spec->scanset = *fmt;
+		while (**fmt != ']' && **fmt != '\0')
+			++(*fmt);
+		if (**fmt == ']')
+			++(*fmt);
+		break;
+	case 'p':
+		++(*fmt);
+		spec->spcr = cs_ptr;
+		break;
+	case 'n':
+		++(*fmt);
+		spec->spcr = cs_numchar;
+		break;
+	case '%':
+		++(*fmt);
+		spec->spcr = cs_percent;
+		break;
+	default:
+		assert(false);
+		spec->spcr = cs_unknown;
+		break;
+	}
+}
+
+/** Initialize string buffer.
+ *
+ * String buffer is used to write characters from a string conversion.
+ * The buffer can be provided by caller or dynamically allocated
+ * (and grown).
+ *
+ * Initialize the string buffer @a strbuf. If @a spec->noassign,is true,
+ * set the buffer pointer to NULL. Otherwise, if @a spec->memalloc is true,
+ * allocate a buffer and read an argument of type char ** designating
+ * a place where the pointer should be stored. If @a spec->memalloc is false,
+ * read an argument of type char * and use it as a destination to write
+ * the characters to.
+ *
+ * @param strbuf String buffer to initialize
+ * @param cvtspec Conversion specification (noassign, memalloc)
+ * @param ap Argument list to read pointer from
+ *
+ * @return EOK on success, ENOMEM if out of memory
+ */
+static int strbuf_init(strbuf_t *strbuf, cvtspec_t *spec, va_list ap)
+{
+	if (spec->noassign) {
+		strbuf->memalloc = false;
+		strbuf->buf = NULL;
+		strbuf->pptr = NULL;
+		strbuf->size = 0;
+		return EOK;
+	}
+
+	if (spec->memalloc) {
+		/* Allocate memory for caller */
+		strbuf->memalloc = true;
+		strbuf->size = 1;
+		strbuf->buf = malloc(strbuf->size);
+		if (strbuf->buf == NULL)
+			return ENOMEM;
+
+		/*
+		 * Save pointer to allocated buffer to caller-provided
+		 * location
+		 */
+		strbuf->pptr = va_arg(ap, char **);
+		*strbuf->pptr = strbuf->buf;
+	} else {
+		/* Caller-provided buffer */
+		strbuf->memalloc = false;
+		strbuf->size = 0;
+		strbuf->buf = va_arg(ap, char *);
+		strbuf->pptr = NULL;
+	}
+
+	return EOK;
+}
+
+/** Write character at the specified position in a string buffer.
+ *
+ * The buffer is enlarged if necessary.
+ *
+ * @param strbuf String buffer
+ * @param idx Character position (starting from 0)
+ * @param c Character to write
+ *
+ * @return EOK on sucess, ENOMEM if out of memory
+ */
+static int strbuf_write(strbuf_t *strbuf, size_t idx, char c)
+{
+	if (strbuf->memalloc && idx >= strbuf->size) {
+		/* Enlarge buffer */
+		strbuf->size = strbuf->size * 2;
+		strbuf->buf = realloc(strbuf->buf, strbuf->size);
+		*strbuf->pptr = strbuf->buf;
+		if (strbuf->buf == NULL)
+			return ENOMEM;
+	}
+
+	if (strbuf->buf != NULL)
+		strbuf->buf[idx] = c;
+
+	return EOK;
+}
+
+/** Get character from stream, keeping count of number of characters read.
+ *
+ * @param f Stream
+ * @param numchar Pointer to counter of characters read
+ * @return Character on success, EOF on error
+ */
+static int __fgetc(FILE *f, int *numchar)
+{
+	int c;
+
+	c = fgetc(f);
+	if (c == EOF)
+		return EOF;
+
+	++(*numchar);
+	return c;
+}
+
+/** Unget character to stream, keeping count of number of characters read.
+ *
+ * @param c Character
+ * @param f Stream
+ * @param numchar Pointer to counter of characters read
+ *
+ * @return @a c on success, EOF on failure
+ */
+static int __ungetc(int c, FILE *f, int *numchar)
+{
+	int rc;
+
+	rc = ungetc(c, f);
+	if (rc == EOF)
+		return EOF;
+
+	--(*numchar);
+	return rc;
+}
+
+/* Skip whitespace in input stream */
+static int vfscanf_skip_ws(FILE *f, int *numchar)
+{
+	int c;
+
+	c = __fgetc(f, numchar);
+	if (c == EOF)
+		return EIO;
+
+	while (isspace(c)) {
+		c = __fgetc(f, numchar);
+		if (c == EOF)
+			return EIO;
+	}
+
+	if (c == EOF)
+		return EIO;
+
+	__ungetc(c, f, numchar);
+	return EOK;
+}
+
+/* Match whitespace. */
+static int vfscanf_match_ws(FILE *f, int *numchar, const char **fmt)
+{
+	int rc;
+
+	rc = vfscanf_skip_ws(f, numchar);
+	if (rc == EOF)
+		return EIO;
+
+	++(*fmt);
+	return EOK;
+}
+
+/** Read intmax_t integer from file.
+ *
+ * @param f Input file
+ * @param numchar Pointer to counter of characters read
+ * @param base Numeric base (0 means detect using prefix)
+ * @param width Maximum field with in characters
+ * @param dest Place to store result
+ * @return EOK on success, EIO on I/O error, EINVAL if input is not valid
+ */
+static int __fstrtoimax(FILE *f, int *numchar, int base, size_t width,
+    intmax_t *dest)
+{
+	int c;
+	intmax_t v;
+	int digit;
+	int sign;
+
+	c = __fgetc(f, numchar);
+	if (c == EOF)
+		return EIO;
+
+	if (c == '+' || c == '-') {
+		/* Sign */
+		sign = (c == '-') ? -1 : +1;
+		c = __fgetc(f, numchar);
+		if (c == EOF)
+			return EIO;
+		--width;
+	} else {
+		sign = 1;
+	}
+
+	if (!isdigit(c) || width < 1) {
+		__ungetc(c, f, numchar);
+		return EINVAL;
+	}
+
+	if (base == 0) {
+		/* Base prefix */
+		if (c == '0') {
+			c = __fgetc(f, numchar);
+			if (c == EOF)
+				return EIO;
+			--width;
+
+			if (width > 0 && (c == 'x' || c == 'X')) {
+				--width;
+				c = __fgetc(f, numchar);
+				if (c == EOF)
+					return EIO;
+				if (width > 0 && isxdigit(c)) {
+					base = 16;
+				} else {
+					*dest = 0;
+					return EOK;
+				}
+			} else {
+				base = 8;
+				if (width == 0) {
+					*dest = 0;
+					return EOK;
+				}
+			}
+		} else {
+			base = 10;
+		}
+	}
+
+	/* Value */
+	v = 0;
+	do {
+		digit = digit_value(c);
+		if (digit >= base)
+			break;
+
+		v = v * base + digit;
+		c = __fgetc(f, numchar);
+		--width;
+	} while (width > 0 && isxdigit(c));
+
+	if (c != EOF)
+		__ungetc(c, f, numchar);
+
+	*dest = sign * v;
+	return EOK;
+}
+
+/** Read uintmax_t unsigned integer from file.
+ *
+ * @param f Input file
+ * @param numchar Pointer to counter of characters read
+ * @param base Numeric base (0 means detect using prefix)
+ * @param width Maximum field with in characters
+ * @param dest Place to store result
+ * @return EOK on success, EIO on I/O error, EINVAL if input is not valid
+ */
+static int __fstrtoumax(FILE *f, int *numchar, int base, size_t width,
+    uintmax_t *dest)
+{
+	int c;
+	uintmax_t v;
+	int digit;
+
+	c = __fgetc(f, numchar);
+	if (c == EOF)
+		return EIO;
+
+	if (!isdigit(c) || width < 1) {
+		__ungetc(c, f, numchar);
+		return EINVAL;
+	}
+
+	if (base == 0) {
+		/* Base prefix */
+		if (c == '0') {
+			c = __fgetc(f, numchar);
+			if (c == EOF)
+				return EIO;
+			--width;
+
+			if (width > 0 && (c == 'x' || c == 'X')) {
+				--width;
+				c = __fgetc(f, numchar);
+				if (c == EOF)
+					return EIO;
+				if (width > 0 && isxdigit(c)) {
+					base = 16;
+				} else {
+					*dest = 0;
+					return EOK;
+				}
+			} else {
+				base = 8;
+				if (width == 0) {
+					*dest = 0;
+					return EOK;
+				}
+			}
+		} else {
+			base = 10;
+		}
+	}
+
+	/* Value */
+	v = 0;
+	do {
+		digit = digit_value(c); // XXX
+		if (digit >= base)
+			break;
+
+		v = v * base + digit;
+		c = __fgetc(f, numchar);
+		--width;
+	} while (width > 0 && isxdigit(c));
+
+	if (c != EOF)
+		__ungetc(c, f, numchar);
+
+	*dest = v;
+	return EOK;
+}
+
+/** Read long double from file.
+ *
+ * @param f Input file
+ * @param numchar Pointer to counter of characters read
+ * @param width Maximum field with in characters
+ * @param dest Place to store result
+ * @return EOK on success, EIO on I/O error, EINVAL if input is not valid
+ */
+static int __fstrtold(FILE *f, int *numchar, size_t width, long double *dest)
+{
+	int c;
+	long double v;
+	int digit;
+	int sign;
+	int base;
+	int efactor;
+	int eadd;
+	int eadj;
+	int exp;
+	int expsign;
+
+	c = __fgetc(f, numchar);
+	if (c == EOF)
+		return EIO;
+
+	if (c == '+' || c == '-') {
+		/* Sign */
+		sign = (c == '-') ? -1 : +1;
+		c = __fgetc(f, numchar);
+		if (c == EOF)
+			return EIO;
+		--width;
+	} else {
+		sign = 1;
+	}
+
+	if (!isdigit(c) || width < 1) {
+		__ungetc(c, f, numchar);
+		return EINVAL;
+	}
+
+	/*
+	 * Default is base 10
+	 */
+
+	/* Significand is in base 10 */
+	base = 10;
+	/* e+1 multiplies number by ten */
+	efactor = 10;
+	/* Adjust exp. by one for each fractional digit */
+	eadd = 1;
+
+	/* Base prefix */
+	if (c == '0') {
+		c = __fgetc(f, numchar);
+		if (c == EOF)
+			return EIO;
+		--width;
+
+		if (width > 0 && (c == 'x' || c == 'X')) {
+			--width;
+			c = __fgetc(f, numchar);
+
+			if (width > 0 && isxdigit(c)) {
+				/* Significand is in base 16 */
+				base = 16;
+				/* p+1 multiplies number by two */
+				efactor = 2;
+				/*
+				 * Adjust exponent by 4 for each
+				 * fractional digit
+				 */
+				eadd = 4;
+			} else {
+				*dest = 0;
+				return EOK;
+			}
+		}
+	}
+
+	/* Value */
+	v = 0;
+	do {
+		digit = digit_value(c);
+		if (digit >= base)
+			break;
+
+		v = v * base + digit;
+		c = __fgetc(f, numchar);
+		--width;
+	} while (width > 0 && isxdigit(c));
+
+	/* Decimal-point */
+	eadj = 0;
+
+	if (c == '.' && width > 1) {
+		c = __fgetc(f, numchar);
+		if (c == EOF)
+			return EIO;
+
+		--width;
+
+		/* Fractional part */
+		while (width > 0 && isxdigit(c)) {
+			digit = digit_value(c);
+			if (digit >= base)
+				break;
+
+			v = v * base + digit;
+			c = __fgetc(f, numchar);
+			--width;
+			eadj -= eadd;
+		}
+	}
+
+	exp = 0;
+
+	/* Exponent */
+	if ((width > 1 && base == 10 && (c == 'e' || c == 'E')) ||
+	    (width > 1 && base == 16 && (c == 'p' || c == 'P'))) {
+		c = __fgetc(f, numchar);
+		if (c == EOF)
+			return EIO;
+
+		--width;
+
+		if (width > 1 && (c == '+' || c == '-')) {
+			/* Exponent sign */
+			if (c == '+') {
+				expsign = 1;
+			} else {
+				expsign = -1;
+			}
+
+			c = __fgetc(f, numchar);
+			if (c == EOF)
+				return EIO;
+
+			--width;
+		} else {
+			expsign = 1;
+		}
+
+		while (width > 0 && isdigit(c)) {
+			digit = digit_value(c);
+			if (digit >= 10)
+				break;
+
+			exp = exp * 10 + digit;
+			c = __fgetc(f, numchar);
+			--width;
+		}
+
+		exp = exp * expsign;
+	}
+
+	exp += eadj;
+
+	/* Adjust v for value of exponent */
+
+	while (exp > 0) {
+		v = v * efactor;
+		--exp;
+	}
+
+	while (exp < 0) {
+		v = v / efactor;
+		++exp;
+	}
+
+	if (c != EOF)
+		__ungetc(c, f, numchar);
+
+	*dest = sign * v;
+	return EOK;
+}
+
+/* Read characters from stream */
+static int __fgetchars(FILE *f, int *numchar, size_t width, strbuf_t *strbuf,
+    size_t *nread)
+{
+	size_t cnt;
+	int c;
+	int rc;
+
+	for (cnt = 0; cnt < width; cnt++) {
+		c = __fgetc(f, numchar);
+		if (c == EOF) {
+			*nread = cnt;
+			return EIO;
+		}
+
+		rc = strbuf_write(strbuf, cnt, c);
+		if (rc != EOK) {
+			*nread = cnt;
+			return rc;
+		}
+	}
+
+	*nread = cnt;
+	return EOK;
+}
+
+/* Read non-whitespace string from stream */
+static int __fgetstr(FILE *f, int *numchar, size_t width, strbuf_t *strbuf,
+    size_t *nread)
+{
+	size_t cnt;
+	int c;
+	int rc;
+	int rc2;
+
+	rc = EOK;
+
+	for (cnt = 0; cnt < width; cnt++) {
+		c = __fgetc(f, numchar);
+		if (c == EOF) {
+			rc = EIO;
+			break;
+		}
+
+		if (isspace(c)) {
+			__ungetc(c, f, numchar);
+			break;
+		}
+
+		rc = strbuf_write(strbuf, cnt, c);
+		if (rc != EOK) {
+			*nread = cnt;
+			return rc;
+		}
+	}
+
+	/* Null-terminate */
+	rc2 = strbuf_write(strbuf, cnt, '\0');
+	if (rc2 != EOK) {
+		*nread = cnt;
+		return rc2;
+	}
+
+	*nread = cnt;
+	return rc;
+}
+
+/** Determine if character is in scanset.
+ *
+ * @param c Character
+ * @param scanset Pointer to scanset
+ * @return @c true iff @a c is in scanset @a scanset.
+ */
+static bool is_in_scanset(char c, const char *scanset)
+{
+	const char *p = scanset;
+	bool inverted = false;
+
+	/* Inverted scanset */
+	if (*p == '^') {
+		inverted = true;
+		++p;
+	}
+
+	/* ']' character in scanset */
+	if (*p == ']') {
+		if (c == ']')
+			return !inverted;
+		++p;
+	}
+
+	/* Remaining characters */
+	while (*p != '\0' && *p != ']') {
+		if (*p == c)
+			return !inverted;
+		++p;
+	}
+
+	return inverted;
+}
+
+/* Read string of characters from scanset from stream */
+static int __fgetscanstr(FILE *f, int *numchar, size_t width,
+    const char *scanset, strbuf_t *strbuf, size_t *nread)
+{
+	size_t cnt;
+	int c;
+	int rc;
+	int rc2;
+
+	rc = EOK;
+
+	for (cnt = 0; cnt < width; cnt++) {
+		c = __fgetc(f, numchar);
+		if (c == EOF) {
+			rc = EIO;
+			break;
+		}
+
+		if (!is_in_scanset(c, scanset)) {
+			__ungetc(c, f, numchar);
+			break;
+		}
+
+		rc = strbuf_write(strbuf, cnt, c);
+		if (rc != EOK) {
+			*nread = cnt;
+			break;
+		}
+	}
+
+	/* Null-terminate */
+	rc2 = strbuf_write(strbuf, cnt, '\0');
+	if (rc2 != EOK) {
+		*nread = cnt;
+		return rc2;
+	}
+
+	*nread = cnt;
+	return rc;
+}
+
+/** Perform a single conversion. */
+static int vfscanf_cvt(FILE *f, const char **fmt, va_list ap, int *numchar,
+    unsigned *ncvt)
+{
+	int rc;
+	int c;
+	intmax_t ival;
+	uintmax_t uval;
+	long double fval = 0.0;
+	int *iptr;
+	short *sptr;
+	signed char *scptr;
+	long *lptr;
+	long long *llptr;
+	ssize_t *ssptr;
+	ptrdiff_t *pdptr;
+	intmax_t *imptr;
+	unsigned *uptr;
+	unsigned short *usptr;
+	unsigned char *ucptr;
+	unsigned long *ulptr;
+	unsigned long long *ullptr;
+	float *fptr;
+	double *dptr;
+	long double *ldptr;
+	size_t *szptr;
+	ptrdiff_t *updptr;
+	uintmax_t *umptr;
+	void **pptr;
+	size_t width;
+	cvtspec_t cvtspec;
+	strbuf_t strbuf;
+	size_t nread;
+
+	++(*fmt);
+
+	cvtspec_parse(fmt, &cvtspec);
+
+	if (cvtspec.spcr != cs_set && cvtspec.spcr != cs_char &&
+	    cvtspec.spcr != cs_numchar) {
+		/* Skip whitespace */
+		rc = vfscanf_skip_ws(f, numchar);
+		if (rc == EIO)
+			return EIO;
+
+		assert(rc == EOK);
+	}
+
+	width = cvtspec.have_width ? cvtspec.width : SIZE_MAX;
+
+	switch (cvtspec.spcr) {
+	case cs_percent:
+		/* Match % character */
+		c = __fgetc(f, numchar);
+		if (c == EOF)
+			return EIO;
+		if (c != '%') {
+			__ungetc(c, f, numchar);
+			return EINVAL;
+		}
+		break;
+	case cs_decimal:
+		/* Decimal integer */
+		rc = __fstrtoimax(f, numchar, 10, width, &ival);
+		if (rc != EOK)
+			return rc;
+		break;
+	case cs_udecimal:
+		/* Decimal unsigned integer */
+		rc = __fstrtoumax(f, numchar, 10, width, &uval);
+		if (rc != EOK)
+			return rc;
+		break;
+	case cs_octal:
+		/* Octal unsigned integer */
+		rc = __fstrtoumax(f, numchar, 8, width, &uval);
+		if (rc != EOK)
+			return rc;
+		break;
+	case cs_hex:
+		/* Hexadecimal unsigned integer */
+		rc = __fstrtoumax(f, numchar, 16, width, &uval);
+		if (rc != EOK)
+			return rc;
+		break;
+	case cs_float:
+		/* Floating-point value */
+		rc = __fstrtold(f, numchar, width, &fval);
+		if (rc != EOK)
+			return rc;
+		break;
+	case cs_int:
+		/* Signed integer with base detection */
+		rc = __fstrtoimax(f, numchar, 0, width, &ival);
+		if (rc != EOK)
+			return rc;
+		break;
+	case cs_ptr:
+		/* Unsigned integer with base detection (need 0xXXXXX) */
+		rc = __fstrtoumax(f, numchar, 0, width, &uval);
+		if (rc != EOK)
+			return rc;
+		break;
+	case cs_char:
+		/* Characters */
+		rc = strbuf_init(&strbuf, &cvtspec, ap);
+		if (rc != EOK)
+			return rc;
+
+		width = cvtspec.have_width ? cvtspec.width : 1;
+		rc = __fgetchars(f, numchar, width, &strbuf, &nread);
+		if (rc != EOK) {
+			if (rc == ENOMEM)
+				return EIO;
+
+			assert(rc == EIO);
+			/* If we have at least one char, we succeeded */
+			if (nread > 0)
+				++(*ncvt);
+			return EIO;
+		}
+		break;
+	case cs_str:
+		/* Non-whitespace string */
+		rc = strbuf_init(&strbuf, &cvtspec, ap);
+		if (rc != EOK)
+			return rc;
+
+		width = cvtspec.have_width ? cvtspec.width : SIZE_MAX;
+		rc = __fgetstr(f, numchar, width, &strbuf, &nread);
+		if (rc != EOK) {
+			if (rc == ENOMEM)
+				return EIO;
+
+			assert(rc == EIO);
+			/* If we have at least one char, we succeeded */
+			if (nread > 0)
+				++(*ncvt);
+			return EIO;
+		}
+		break;
+	case cs_set:
+		/* String of characters from scanset */
+		rc = strbuf_init(&strbuf, &cvtspec, ap);
+		if (rc != EOK)
+			return rc;
+
+		width = cvtspec.have_width ? cvtspec.width : SIZE_MAX;
+		rc = __fgetscanstr(f, numchar, width, cvtspec.scanset,
+		    &strbuf, &nread);
+		if (rc != EOK) {
+			if (rc == ENOMEM)
+				return EIO;
+
+			assert(rc == EIO);
+			/* If we have at least one char, we succeeded */
+			if (nread > 0)
+				++(*ncvt);
+			return EIO;
+		}
+		break;
+	case cs_numchar:
+		break;
+	default:
+		return EINVAL;
+	}
+
+	/* Assignment */
+
+	if (cvtspec.noassign)
+		goto skip_assign;
+
+	switch (cvtspec.spcr) {
+	case cs_percent:
+		break;
+	case cs_decimal:
+	case cs_int:
+		switch (cvtspec.lenmod) {
+		case lm_none:
+			iptr = va_arg(ap, int *);
+			*iptr = ival;
+			break;
+		case lm_hh:
+			scptr = va_arg(ap, signed char *);
+			*scptr = ival;
+			break;
+		case lm_h:
+			sptr = va_arg(ap, short *);
+			*sptr = ival;
+			break;
+		case lm_l:
+			lptr = va_arg(ap, long *);
+			*lptr = ival;
+			break;
+		case lm_ll:
+			llptr = va_arg(ap, long long *);
+			*llptr = ival;
+			break;
+		case lm_j:
+			imptr = va_arg(ap, intmax_t *);
+			*imptr = ival;
+			break;
+		case lm_z:
+			ssptr = va_arg(ap, ssize_t *);
+			*ssptr = ival;
+			break;
+		case lm_t:
+			pdptr = va_arg(ap, ptrdiff_t *);
+			*pdptr = ival;
+			break;
+		default:
+			assert(false);
+		}
+
+		++(*ncvt);
+		break;
+	case cs_udecimal:
+	case cs_octal:
+	case cs_hex:
+		switch (cvtspec.lenmod) {
+		case lm_none:
+			uptr = va_arg(ap, unsigned *);
+			*uptr = uval;
+			break;
+		case lm_hh:
+			ucptr = va_arg(ap, unsigned char *);
+			*ucptr = uval;
+			break;
+		case lm_h:
+			usptr = va_arg(ap, unsigned short *);
+			*usptr = uval;
+			break;
+		case lm_l:
+			ulptr = va_arg(ap, unsigned long *);
+			*ulptr = uval;
+			break;
+		case lm_ll:
+			ullptr = va_arg(ap, unsigned long long *);
+			*ullptr = uval;
+			break;
+		case lm_j:
+			umptr = va_arg(ap, uintmax_t *);
+			*umptr = uval;
+			break;
+		case lm_z:
+			szptr = va_arg(ap, size_t *);
+			*szptr = uval;
+			break;
+		case lm_t:
+			updptr = va_arg(ap, ptrdiff_t *);
+			*updptr = uval;
+			break;
+		default:
+			assert(false);
+		}
+
+		++(*ncvt);
+		break;
+	case cs_float:
+		switch (cvtspec.lenmod) {
+		case lm_none:
+			fptr = va_arg(ap, float *);
+			*fptr = fval;
+			break;
+		case lm_l:
+			dptr = va_arg(ap, double *);
+			*dptr = fval;
+			break;
+		case lm_L:
+			ldptr = va_arg(ap, long double *);
+			*ldptr = fval;
+			break;
+		default:
+			assert(false);
+		}
+
+		++(*ncvt);
+		break;
+	case cs_ptr:
+		pptr = va_arg(ap, void *);
+		*pptr = (void *)(uintptr_t)uval;
+		++(*ncvt);
+		break;
+	case cs_char:
+		++(*ncvt);
+		break;
+	case cs_str:
+		++(*ncvt);
+		break;
+	case cs_set:
+		++(*ncvt);
+		break;
+	case cs_numchar:
+		/* Store number of characters read so far. */
+		iptr = va_arg(ap, int *);
+		*iptr = *numchar;
+		/* No incrementing of ncvt */
+		break;
+	default:
+		return EINVAL;
+	}
+
+skip_assign:
+
+	return EOK;
+}
+
+static int xxvfscanf(FILE *f, const char *fmt, va_list ap)
+{
+	const char *cp;
+	int c;
+	unsigned ncvt;
+	int numchar;
+	bool input_error = false;
+	int rc;
+
+	ncvt = 0;
+	numchar = 0;
+	cp = fmt;
+	while (*cp != '\0') {
+		if (isspace(*cp)) {
+			/* Whitespace */
+			rc = vfscanf_match_ws(f, &numchar, &cp);
+			if (rc == EIO) {
+				input_error = true;
+				break;
+			}
+
+			assert(rc == EOK);
+		} else if (*cp == '%') {
+			/* Conversion specification */
+			rc = vfscanf_cvt(f, &cp, ap, &numchar, &ncvt);
+			if (rc == EIO) {
+				/* Input error */
+				input_error = true;
+				break;
+			}
+
+			/* Other error */
+			if (rc != EOK)
+				break;
+		} else {
+			/* Match specific character */
+			c = __fgetc(f, &numchar);
+			if (c == EOF) {
+				input_error = true;
+				break;
+			}
+
+			if (c != *cp) {
+				__ungetc(c, f, &numchar);
+				break;
+			}
+
+			++cp;
+		}
+	}
+
+	if (input_error && ncvt == 0)
+		return EOF;
+
+	return ncvt;
+}
+
+int xxfscanf(FILE *f, const char *fmt, ...)
+{
+	va_list args;
+	int rc;
+
+	va_start(args, fmt);
+	rc = xxvfscanf(f, fmt, args);
+	va_end(args);
+
+	return rc;
+}
+
+int xxsscanf(const char *s, const char *fmt, ...)
+{
+	va_list args;
+	FILE *f;
+	int rc;
+
+	f = fopen("/tmp/test.tmp", "wt");
+	if (f == NULL)
+		return EOF;
+
+	if (fputs(s, f) == EOF)
+		return EOF;
+
+	if (fclose(f) == EOF)
+		return EOF;
+
+	f = fopen("/tmp/test.tmp", "rt");
+	if (f == NULL) {
+		printf("failed to open for reading\n");
+		return EOF;
+	}
+
+	va_start(args, fmt);
+	rc = xxvfscanf(f, fmt, args);
+	va_end(args);
+
+	fclose(f);
+
+	return rc;
+}
Index: uspace/lib/c/include/stdio.h
===================================================================
--- uspace/lib/c/include/stdio.h	(revision ec397203e399886ac45d617da8098ea6ae81f914)
+++ uspace/lib/c/include/stdio.h	(revision ed18e148046bfdc424427e6fcc350b1718b2628b)
@@ -101,4 +101,7 @@
 extern int vsnprintf(char *, size_t, const char *, va_list);
 
+extern int xxfscanf(FILE *, const char *, ...);
+extern int xxsscanf(const char *, const char *, ...);
+
 /* File stream functions */
 extern FILE *fopen(const char *, const char *);
Index: uspace/lib/c/test/main.c
===================================================================
--- uspace/lib/c/test/main.c	(revision ec397203e399886ac45d617da8098ea6ae81f914)
+++ uspace/lib/c/test/main.c	(revision ed18e148046bfdc424427e6fcc350b1718b2628b)
@@ -36,4 +36,5 @@
 PCUT_IMPORT(odict);
 PCUT_IMPORT(qsort);
+PCUT_IMPORT(scanf);
 PCUT_IMPORT(sprintf);
 PCUT_IMPORT(str);
Index: uspace/lib/c/test/stdio/scanf.c
===================================================================
--- uspace/lib/c/test/stdio/scanf.c	(revision ed18e148046bfdc424427e6fcc350b1718b2628b)
+++ uspace/lib/c/test/stdio/scanf.c	(revision ed18e148046bfdc424427e6fcc350b1718b2628b)
@@ -0,0 +1,1192 @@
+/*
+ * Copyright (c) 2018 Jiri Svoboda
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ *
+ * - Redistributions of source code must retain the above copyright
+ *   notice, this list of conditions and the following disclaimer.
+ * - Redistributions in binary form must reproduce the above copyright
+ *   notice, this list of conditions and the following disclaimer in the
+ *   documentation and/or other materials provided with the distribution.
+ * - The name of the author may not be used to endorse or promote products
+ *   derived from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
+ * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
+ * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
+ * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
+ * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
+ * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
+ * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+/** @addtogroup libc
+ * @{
+ */
+/**
+ * @file
+ * @brief Test formatted input (scanf family)
+ */
+
+#include <mem.h>
+#include <pcut/pcut.h>
+#include <stddef.h>
+#include <stdint.h>
+#include <stdio.h>
+#include <stdlib.h>
+
+PCUT_INIT;
+
+PCUT_TEST_SUITE(scanf);
+
+enum {
+	chars_size = 10
+};
+
+PCUT_TEST(empty_fmt)
+{
+	int rc;
+
+	/* Empty format string */
+	rc = xxsscanf("42", "");
+	PCUT_ASSERT_INT_EQUALS(0, rc);
+}
+
+PCUT_TEST(dec_int)
+{
+	int rc;
+	int i;
+
+	/* Decimal integer */
+	rc = xxsscanf("42", "%d", &i);
+	PCUT_ASSERT_INT_EQUALS(1, rc);
+	PCUT_ASSERT_TRUE(i == 42);
+}
+
+PCUT_TEST(int_int)
+{
+	int rc;
+	int i, j;
+
+	/* Two integers */
+	rc = xxsscanf("42 43", "%d%d", &i, &j);
+	PCUT_ASSERT_INT_EQUALS(2, rc);
+	PCUT_ASSERT_TRUE(i == 42);
+	PCUT_ASSERT_TRUE(j == 43);
+}
+
+PCUT_TEST(dec_sign_char)
+{
+	int rc;
+	signed char sc;
+
+	/* Decimal signed char */
+	rc = xxsscanf("42", "%hhd", &sc);
+	PCUT_ASSERT_INT_EQUALS(1, rc);
+	PCUT_ASSERT_TRUE(sc == 42);
+}
+
+PCUT_TEST(dec_short)
+{
+	int rc;
+	short si;
+
+	/* Decimal short */
+	rc = xxsscanf("42", "%hd", &si);
+	PCUT_ASSERT_INT_EQUALS(1, rc);
+	PCUT_ASSERT_TRUE(si == 42);
+}
+
+PCUT_TEST(dec_long)
+{
+	int rc;
+	long li;
+
+	/* Decimal long */
+	rc = xxsscanf("42", "%ld", &li);
+	PCUT_ASSERT_INT_EQUALS(1, rc);
+	PCUT_ASSERT_TRUE(li == 42);
+}
+
+PCUT_TEST(dec_long_long)
+{
+	int rc;
+	long long lli;
+
+	/* Decimal long long */
+	rc = xxsscanf("42", "%lld", &lli);
+	PCUT_ASSERT_INT_EQUALS(1, rc);
+	PCUT_ASSERT_TRUE(lli == 42);
+}
+
+PCUT_TEST(dec_intmax)
+{
+	int rc;
+	intmax_t imax;
+
+	/* Decimal intmax_t */
+	rc = xxsscanf("42", "%jd", &imax);
+	PCUT_ASSERT_INT_EQUALS(1, rc);
+	PCUT_ASSERT_TRUE(imax == 42);
+}
+
+PCUT_TEST(dec_size_t_size)
+{
+	int rc;
+	size_t szi;
+
+	/* Decimal size_t-sized */
+	rc = xxsscanf("42", "%zd", &szi);
+	PCUT_ASSERT_INT_EQUALS(1, rc);
+	PCUT_ASSERT_TRUE(szi == 42);
+}
+
+PCUT_TEST(dec_ptrdiff_t_size)
+{
+	int rc;
+	ptrdiff_t pdi;
+
+	/* Decimal ptrdiff_t-sized */
+	rc = xxsscanf("42", "%td", &pdi);
+	PCUT_ASSERT_INT_EQUALS(1, rc);
+	PCUT_ASSERT_TRUE(pdi == 42);
+}
+
+PCUT_TEST(dec_int_hexdigit)
+{
+	int rc;
+	int i;
+
+	/* Decimal integer followed by hexadecimal digit */
+	rc = xxsscanf("42a", "%d", &i);
+	PCUT_ASSERT_INT_EQUALS(1, rc);
+	PCUT_ASSERT_TRUE(i == 42);
+}
+
+PCUT_TEST(int_noprefix)
+{
+	int rc;
+	int i;
+
+	/* Decimal integer - detect no prefix */
+	rc = xxsscanf("42", "%i", &i);
+	PCUT_ASSERT_INT_EQUALS(1, rc);
+	PCUT_ASSERT_TRUE(i == 42);
+}
+
+PCUT_TEST(octal_decimal_digit)
+{
+	int rc;
+	int i;
+
+	/* Prefixed octal integer followed by decimal digit */
+	rc = xxsscanf("019", "%i", &i);
+	PCUT_ASSERT_INT_EQUALS(1, rc);
+	PCUT_ASSERT_TRUE(i == 1);
+}
+
+PCUT_TEST(hex_other_char)
+{
+	int rc;
+	int i;
+
+	/* Prefixed hexadecimal integer followed by other character */
+	rc = xxsscanf("0xag", "%i", &i);
+	PCUT_ASSERT_INT_EQUALS(1, rc);
+	PCUT_ASSERT_TRUE(i == 10);
+}
+
+PCUT_TEST(positive_dec)
+{
+	int rc;
+	int i;
+
+	/* Decimal integer with '+' sign */
+	rc = xxsscanf("+42", "%d", &i);
+	PCUT_ASSERT_INT_EQUALS(1, rc);
+	PCUT_ASSERT_TRUE(i == 42);
+}
+
+PCUT_TEST(negative_dec)
+{
+	int rc;
+	int i;
+
+	/* Decimal integer with '-' sign */
+	rc = xxsscanf("-42", "%d", &i);
+	PCUT_ASSERT_INT_EQUALS(1, rc);
+	PCUT_ASSERT_TRUE(i == -42);
+}
+
+PCUT_TEST(negative_hex)
+{
+	int rc;
+	int i;
+
+	/* Hexadecimal integer with prefix and '-' sign */
+	rc = xxsscanf("-0xa", "%i", &i);
+	PCUT_ASSERT_INT_EQUALS(1, rc);
+	PCUT_ASSERT_TRUE(i == -10);
+}
+
+PCUT_TEST(dec_unsigned)
+{
+	int rc;
+	unsigned u;
+
+	/* Decimal unsigned integer */
+	rc = xxsscanf("42", "%u", &u);
+	PCUT_ASSERT_INT_EQUALS(1, rc);
+	PCUT_ASSERT_TRUE(u == 42);
+}
+
+PCUT_TEST(dec_unsigned_char)
+{
+	int rc;
+	unsigned char uc;
+
+	/* Decimal unsigned char */
+	rc = xxsscanf("42", "%hhu", &uc);
+	PCUT_ASSERT_INT_EQUALS(1, rc);
+	PCUT_ASSERT_TRUE(uc == 42);
+}
+
+PCUT_TEST(dec_unsigned_short)
+{
+	int rc;
+	unsigned short su;
+
+	/* Decimal unsigned short */
+	rc = xxsscanf("42", "%hu", &su);
+	PCUT_ASSERT_INT_EQUALS(1, rc);
+	PCUT_ASSERT_TRUE(su == 42);
+}
+
+PCUT_TEST(dec_unsigned_long)
+{
+	int rc;
+	unsigned long lu;
+
+	/* Decimal unsigned long */
+	rc = xxsscanf("42", "%lu", &lu);
+	PCUT_ASSERT_INT_EQUALS(1, rc);
+	PCUT_ASSERT_TRUE(lu == 42);
+}
+
+PCUT_TEST(dec_unsigned_long_long)
+{
+	int rc;
+	unsigned long long llu;
+
+	/* Decimal unsigned long long */
+	rc = xxsscanf("42", "%llu", &llu);
+	PCUT_ASSERT_INT_EQUALS(1, rc);
+	PCUT_ASSERT_TRUE(llu == 42);
+}
+
+PCUT_TEST(dec_unitmax)
+{
+	int rc;
+	uintmax_t umax;
+
+	/* Decimal uintmax_t */
+	rc = xxsscanf("42", "%ju", &umax);
+	PCUT_ASSERT_INT_EQUALS(1, rc);
+	PCUT_ASSERT_TRUE(umax == 42);
+}
+
+PCUT_TEST(dec_unsigned_size)
+{
+	int rc;
+	size_t szu;
+
+	/* Decimal size_t */
+	rc = xxsscanf("42", "%zu", &szu);
+	PCUT_ASSERT_INT_EQUALS(1, rc);
+	PCUT_ASSERT_TRUE(szu == 42);
+}
+
+PCUT_TEST(dec_unsigned_ptrdiff)
+{
+	int rc;
+	ptrdiff_t pdu;
+
+	/* Decimal ptrdiff_t-sized unsigned int*/
+	rc = xxsscanf("42", "%tu", &pdu);
+	PCUT_ASSERT_INT_EQUALS(1, rc);
+	PCUT_ASSERT_TRUE(pdu == 42);
+}
+
+PCUT_TEST(octal_unsigned)
+{
+	int rc;
+	unsigned u;
+
+	/* Octal unsigned integer */
+	rc = xxsscanf("52", "%o", &u);
+	PCUT_ASSERT_INT_EQUALS(1, rc);
+	PCUT_ASSERT_TRUE(u == 052);
+}
+
+PCUT_TEST(hex_unsigned)
+{
+	int rc;
+	unsigned u;
+
+	/* Hexadecimal unsigned integer */
+	rc = xxsscanf("2a", "%x", &u);
+	PCUT_ASSERT_INT_EQUALS(1, rc);
+	PCUT_ASSERT_TRUE(u == 0x2a);
+}
+
+PCUT_TEST(hex_unsigned_cap_x)
+{
+	int rc;
+	unsigned u;
+
+	/* Hexadecimal unsigned integer unsing alternate specifier */
+	rc = xxsscanf("2a", "%X", &u);
+	PCUT_ASSERT_INT_EQUALS(1, rc);
+	PCUT_ASSERT_TRUE(u == 0x2a);
+}
+
+PCUT_TEST(uppercase_hex_unsigned)
+{
+	int rc;
+	unsigned u;
+
+	/* Uppercase hexadecimal unsigned integer */
+	rc = xxsscanf("2A", "%x", &u);
+	PCUT_ASSERT_INT_EQUALS(1, rc);
+	PCUT_ASSERT_TRUE(u == 0x2a);
+}
+
+PCUT_TEST(skipws)
+{
+	int rc;
+	int i;
+
+	/* Skipping whitespace */
+	rc = xxsscanf(" \t\n42", "%d", &i);
+	PCUT_ASSERT_INT_EQUALS(1, rc);
+	PCUT_ASSERT_TRUE(i == 42);
+}
+
+PCUT_TEST(percentile)
+{
+	int rc;
+	int i;
+
+	/* Percentile conversion */
+	rc = xxsscanf(" \t\n%42", "%%%d", &i);
+	PCUT_ASSERT_INT_EQUALS(1, rc);
+	PCUT_ASSERT_TRUE(i == 42);
+}
+
+PCUT_TEST(match_spec_char)
+{
+	int rc;
+	int i;
+
+	/* Matching specific character */
+	rc = xxsscanf("x42", "x%d", &i);
+	PCUT_ASSERT_INT_EQUALS(1, rc);
+	PCUT_ASSERT_TRUE(i == 42);
+}
+
+PCUT_TEST(match_char_noskipws)
+{
+	int rc;
+	int i;
+
+	/* Matching specific character should not skip whitespace */
+	rc = xxsscanf(" x42", "x%d", &i);
+	PCUT_ASSERT_INT_EQUALS(0, rc);
+}
+
+PCUT_TEST(skipws_match_char)
+{
+	int rc;
+	int i;
+
+	/* Skipping whitespace + match specific character */
+	rc = xxsscanf(" x42", "\t\nx%d", &i);
+	PCUT_ASSERT_INT_EQUALS(1, rc);
+	PCUT_ASSERT_TRUE(i == 42);
+}
+
+PCUT_TEST(dec_sufficient_lim_width)
+{
+	int rc;
+	int i;
+
+	/* Decimal with limited, but sufficient width */
+	rc = xxsscanf("42", "%2d", &i);
+	PCUT_ASSERT_INT_EQUALS(1, rc);
+	PCUT_ASSERT_TRUE(i == 42);
+}
+
+PCUT_TEST(dec_smaller_width)
+{
+	int rc;
+	int i;
+
+	/* Decimal with limited, smaller width */
+	rc = xxsscanf("42", "%1d", &i);
+	PCUT_ASSERT_INT_EQUALS(1, rc);
+	PCUT_ASSERT_TRUE(i == 4);
+}
+
+PCUT_TEST(int_hex_limited_width)
+{
+	int rc;
+	int i;
+
+	/* Integer with hex prefix, format with limited, sufficient width */
+	rc = xxsscanf("0x1", "%3i", &i);
+	PCUT_ASSERT_INT_EQUALS(1, rc);
+	PCUT_ASSERT_TRUE(i == 1);
+}
+
+PCUT_TEST(int_hex_small_width)
+{
+	int rc;
+	int i;
+
+	/* Integer with hex prefix, format with limited, smaller width */
+	rc = xxsscanf("0x1", "%2i", &i);
+	PCUT_ASSERT_INT_EQUALS(1, rc);
+	PCUT_ASSERT_TRUE(i == 0);
+}
+
+PCUT_TEST(int_oct_limited_width)
+{
+	int rc;
+	int i;
+
+	/* Integer with octal prefix, format with limited, sufficient width */
+	rc = xxsscanf("012", "%3i", &i);
+	PCUT_ASSERT_INT_EQUALS(1, rc);
+	PCUT_ASSERT_TRUE(i == 012);
+}
+
+PCUT_TEST(int_oct_smaller_width)
+{
+	int rc;
+	int i;
+
+	/* Integer with octal prefix, format with limited, smaller width */
+	rc = xxsscanf("012", "%2i", &i);
+	PCUT_ASSERT_INT_EQUALS(1, rc);
+	PCUT_ASSERT_TRUE(i == 01);
+}
+
+PCUT_TEST(int_oct_tiny_width)
+{
+	int rc;
+	int i;
+
+	/* Integer with octal prefix, format with width allowing just for 0 */
+	rc = xxsscanf("012", "%1i", &i);
+	PCUT_ASSERT_INT_EQUALS(1, rc);
+	PCUT_ASSERT_TRUE(i == 0);
+}
+
+PCUT_TEST(pointer)
+{
+	int rc;
+	void *ptr;
+
+	/* Pointer */
+	rc = xxsscanf("0x12341234", "%p", &ptr);
+	PCUT_ASSERT_INT_EQUALS(1, rc);
+	PCUT_ASSERT_TRUE(ptr == (void *)0x12341234);
+}
+
+PCUT_TEST(single_char)
+{
+	int rc;
+	char c;
+
+	/* Single character */
+	rc = xxsscanf("x", "%c", &c);
+	PCUT_ASSERT_INT_EQUALS(1, rc);
+	PCUT_ASSERT_TRUE(c == 'x');
+}
+
+PCUT_TEST(single_ws_char)
+{
+	int rc;
+	char c;
+
+	/* Single whitespace character */
+	rc = xxsscanf("\t", "%c", &c);
+	PCUT_ASSERT_INT_EQUALS(1, rc);
+	PCUT_ASSERT_TRUE(c == '\t');
+}
+
+PCUT_TEST(chars)
+{
+	int rc;
+	char chars[chars_size];
+
+	/* Multiple characters */
+	memset(chars, 'X', chars_size);
+	rc = xxsscanf("abc", "%3c", chars);
+	PCUT_ASSERT_INT_EQUALS(1, rc);
+	PCUT_ASSERT_TRUE(chars[0] == 'a');
+	PCUT_ASSERT_TRUE(chars[1] == 'b');
+	PCUT_ASSERT_TRUE(chars[2] == 'c');
+	PCUT_ASSERT_TRUE(chars[3] == 'X');
+}
+
+PCUT_TEST(fewer_chars)
+{
+	int rc;
+	char chars[chars_size];
+
+	/* Fewer characters than requested */
+	memset(chars, 'X', chars_size);
+	rc = xxsscanf("abc", "%5c", chars);
+	PCUT_ASSERT_INT_EQUALS(1, rc);
+	PCUT_ASSERT_TRUE(chars[0] == 'a');
+	PCUT_ASSERT_TRUE(chars[1] == 'b');
+	PCUT_ASSERT_TRUE(chars[2] == 'c');
+	PCUT_ASSERT_TRUE(chars[3] == 'X');
+}
+
+PCUT_TEST(chars_not_found)
+{
+	int rc;
+	char chars[chars_size];
+
+	/* Reading characters but no found */
+	memset(chars, 'X', chars_size);
+	rc = xxsscanf("", "%5c", chars);
+	PCUT_ASSERT_INT_EQUALS(EOF, rc);
+	PCUT_ASSERT_TRUE(chars[0] == 'X');
+}
+
+PCUT_TEST(chars_noassign)
+{
+	int rc;
+	int n;
+
+	/* Multiple characters with suppressed assignment */
+	rc = xxsscanf("abc", "%*3c%n", &n);
+	PCUT_ASSERT_INT_EQUALS(0, rc);
+	PCUT_ASSERT_INT_EQUALS(3, n);
+}
+
+PCUT_TEST(chars_malloc)
+{
+	int rc;
+	char *cp;
+
+	/* Multiple characters with memory allocation */
+	cp = NULL;
+	rc = xxsscanf("abc", "%m3c", &cp);
+	PCUT_ASSERT_INT_EQUALS(1, rc);
+	PCUT_ASSERT_NOT_NULL(cp);
+	PCUT_ASSERT_TRUE(cp[0] == 'a');
+	PCUT_ASSERT_TRUE(cp[1] == 'b');
+	PCUT_ASSERT_TRUE(cp[2] == 'c');
+	free(cp);
+}
+
+PCUT_TEST(str)
+{
+	int rc;
+	char chars[chars_size];
+
+	/* String of non-whitespace characters, unlimited width */
+	memset(chars, 'X', chars_size);
+	rc = xxsscanf(" abc d", "%s", chars);
+	PCUT_ASSERT_INT_EQUALS(1, rc);
+	PCUT_ASSERT_TRUE(chars[0] == 'a');
+	PCUT_ASSERT_TRUE(chars[1] == 'b');
+	PCUT_ASSERT_TRUE(chars[2] == 'c');
+	PCUT_ASSERT_TRUE(chars[3] == '\0');
+	PCUT_ASSERT_TRUE(chars[4] == 'X');
+}
+
+PCUT_TEST(str_till_end)
+{
+	int rc;
+	char chars[chars_size];
+
+	/* String of non-whitespace characters, until the end */
+	memset(chars, 'X', chars_size);
+	rc = xxsscanf(" abc", "%s", chars);
+	PCUT_ASSERT_INT_EQUALS(1, rc);
+	PCUT_ASSERT_TRUE(chars[0] == 'a');
+	PCUT_ASSERT_TRUE(chars[1] == 'b');
+	PCUT_ASSERT_TRUE(chars[2] == 'c');
+	PCUT_ASSERT_TRUE(chars[3] == '\0');
+	PCUT_ASSERT_TRUE(chars[4] == 'X');
+}
+
+PCUT_TEST(str_large_width)
+{
+	int rc;
+	char chars[chars_size];
+
+	/* String of non-whitespace characters, large enough width */
+	memset(chars, 'X', chars_size);
+	rc = xxsscanf(" abc d", "%5s", chars);
+	PCUT_ASSERT_INT_EQUALS(1, rc);
+	PCUT_ASSERT_TRUE(chars[0] == 'a');
+	PCUT_ASSERT_TRUE(chars[1] == 'b');
+	PCUT_ASSERT_TRUE(chars[2] == 'c');
+	PCUT_ASSERT_TRUE(chars[3] == '\0');
+	PCUT_ASSERT_TRUE(chars[4] == 'X');
+}
+
+PCUT_TEST(str_not_found)
+{
+	int rc;
+	char chars[chars_size];
+
+	/* Want string of non-whitespace, but got only whitespace */
+	memset(chars, 'X', chars_size);
+	rc = xxsscanf(" ", "%s", chars);
+	PCUT_ASSERT_INT_EQUALS(EOF, rc);
+	PCUT_ASSERT_TRUE(chars[0] == 'X');
+}
+
+PCUT_TEST(str_small_width)
+{
+	int rc;
+	char chars[chars_size];
+
+	/* String of non-whitespace characters, small width */
+	memset(chars, 'X', chars_size);
+	rc = xxsscanf(" abc", "%2s", chars);
+	PCUT_ASSERT_INT_EQUALS(1, rc);
+	PCUT_ASSERT_TRUE(chars[0] == 'a');
+	PCUT_ASSERT_TRUE(chars[1] == 'b');
+	PCUT_ASSERT_TRUE(chars[2] == '\0');
+	PCUT_ASSERT_TRUE(chars[3] == 'X');
+}
+
+PCUT_TEST(str_noassign)
+{
+	int rc;
+	int n;
+
+	/* String of non-whitespace characters, assignment suppression */
+	rc = xxsscanf(" abc d", "%*s%n", &n);
+	PCUT_ASSERT_INT_EQUALS(0, rc);
+	PCUT_ASSERT_INT_EQUALS(4, n);
+}
+
+PCUT_TEST(str_malloc)
+{
+	int rc;
+	char *cp;
+
+	/* String of non-whitespace characters, memory allocation */
+	rc = xxsscanf(" abc d", "%ms", &cp);
+	PCUT_ASSERT_INT_EQUALS(1, rc);
+	PCUT_ASSERT_NOT_NULL(cp);
+	PCUT_ASSERT_TRUE(cp[0] == 'a');
+	PCUT_ASSERT_TRUE(cp[1] == 'b');
+	PCUT_ASSERT_TRUE(cp[2] == 'c');
+	PCUT_ASSERT_TRUE(cp[3] == '\0');
+	free(cp);
+}
+
+PCUT_TEST(set_convert)
+{
+	int rc;
+	char chars[chars_size];
+	int i;
+
+	/* Set conversion without width specified terminating before the end  */
+	memset(chars, 'X', chars_size);
+	rc = xxsscanf("abcd42", "%[abc]d%d", chars, &i);
+	PCUT_ASSERT_INT_EQUALS(2, rc);
+	PCUT_ASSERT_TRUE(chars[0] == 'a');
+	PCUT_ASSERT_TRUE(chars[1] == 'b');
+	PCUT_ASSERT_TRUE(chars[2] == 'c');
+	PCUT_ASSERT_TRUE(chars[3] == '\0');
+	PCUT_ASSERT_TRUE(chars[4] == 'X');
+	PCUT_ASSERT_TRUE(i == 42);
+}
+
+PCUT_TEST(set_till_end)
+{
+	int rc;
+	char chars[chars_size];
+
+	/* Set conversion without width specified, until the end */
+	memset(chars, 'X', chars_size);
+	rc = xxsscanf("abc", "%[abc]", chars);
+	PCUT_ASSERT_INT_EQUALS(1, rc);
+	PCUT_ASSERT_TRUE(chars[0] == 'a');
+	PCUT_ASSERT_TRUE(chars[1] == 'b');
+	PCUT_ASSERT_TRUE(chars[2] == 'c');
+	PCUT_ASSERT_TRUE(chars[3] == '\0');
+	PCUT_ASSERT_TRUE(chars[4] == 'X');
+}
+
+PCUT_TEST(set_large_width)
+{
+	int rc;
+	char chars[chars_size];
+
+	/* Set conversion with larger width */
+	memset(chars, 'X', chars_size);
+	rc = xxsscanf("abcd", "%5[abc]", chars);
+	PCUT_ASSERT_INT_EQUALS(1, rc);
+	PCUT_ASSERT_TRUE(chars[0] == 'a');
+	PCUT_ASSERT_TRUE(chars[1] == 'b');
+	PCUT_ASSERT_TRUE(chars[2] == 'c');
+	PCUT_ASSERT_TRUE(chars[3] == '\0');
+	PCUT_ASSERT_TRUE(chars[4] == 'X');
+}
+
+PCUT_TEST(set_small_width)
+{
+	int rc;
+	char chars[chars_size];
+
+	/* Set conversion with smaller width */
+	memset(chars, 'X', chars_size);
+	rc = xxsscanf("abcd", "%3[abcd]", chars);
+	PCUT_ASSERT_INT_EQUALS(1, rc);
+	PCUT_ASSERT_TRUE(chars[0] == 'a');
+	PCUT_ASSERT_TRUE(chars[1] == 'b');
+	PCUT_ASSERT_TRUE(chars[2] == 'c');
+	PCUT_ASSERT_TRUE(chars[3] == '\0');
+	PCUT_ASSERT_TRUE(chars[4] == 'X');
+}
+
+PCUT_TEST(set_negated)
+{
+	int rc;
+	char chars[chars_size];
+
+	/* Set conversion with negated scanset */
+	memset(chars, 'X', chars_size);
+	rc = xxsscanf("abcd", "%[^d]", chars);
+	PCUT_ASSERT_INT_EQUALS(1, rc);
+	PCUT_ASSERT_TRUE(chars[0] == 'a');
+	PCUT_ASSERT_TRUE(chars[1] == 'b');
+	PCUT_ASSERT_TRUE(chars[2] == 'c');
+	PCUT_ASSERT_TRUE(chars[3] == '\0');
+	PCUT_ASSERT_TRUE(chars[4] == 'X');
+}
+
+PCUT_TEST(set_with_rbr)
+{
+	int rc;
+	char chars[chars_size];
+
+	/* Set conversion with ']' in scanset */
+	memset(chars, 'X', chars_size);
+	rc = xxsscanf("]bcd", "%[]bc]", chars);
+	PCUT_ASSERT_INT_EQUALS(1, rc);
+	PCUT_ASSERT_TRUE(chars[0] == ']');
+	PCUT_ASSERT_TRUE(chars[1] == 'b');
+	PCUT_ASSERT_TRUE(chars[2] == 'c');
+	PCUT_ASSERT_TRUE(chars[3] == '\0');
+	PCUT_ASSERT_TRUE(chars[4] == 'X');
+}
+
+PCUT_TEST(set_inverted_with_rbr)
+{
+	int rc;
+	char chars[chars_size];
+
+	/* Set conversion with ']' in inverted scanset */
+	memset(chars, 'X', chars_size);
+	rc = xxsscanf("abc]", "%[^]def]", chars);
+	PCUT_ASSERT_INT_EQUALS(1, rc);
+	PCUT_ASSERT_TRUE(chars[0] == 'a');
+	PCUT_ASSERT_TRUE(chars[1] == 'b');
+	PCUT_ASSERT_TRUE(chars[2] == 'c');
+	PCUT_ASSERT_TRUE(chars[3] == '\0');
+	PCUT_ASSERT_TRUE(chars[4] == 'X');
+}
+
+PCUT_TEST(set_noassign)
+{
+	int rc;
+	int n;
+
+	/* Set conversion with assignment suppression */
+	rc = xxsscanf("abcd42", "%*[abc]%n", &n);
+	PCUT_ASSERT_INT_EQUALS(0, rc);
+	PCUT_ASSERT_INT_EQUALS(3, n);
+}
+
+PCUT_TEST(set_malloc)
+{
+	int rc;
+	char *cp;
+
+	/* Set conversion with memory allocation */
+	cp = NULL;
+	rc = xxsscanf("abcd42", "%m[abcd]", &cp);
+	PCUT_ASSERT_INT_EQUALS(1, rc);
+	PCUT_ASSERT_NOT_NULL(cp);
+	PCUT_ASSERT_TRUE(cp[0] == 'a');
+	PCUT_ASSERT_TRUE(cp[1] == 'b');
+	PCUT_ASSERT_TRUE(cp[2] == 'c');
+	PCUT_ASSERT_TRUE(cp[3] == 'd');
+	PCUT_ASSERT_TRUE(cp[4] == '\0');
+	free(cp);
+}
+
+PCUT_TEST(dec_int_noassign)
+{
+	int rc;
+	int n;
+
+	/* Decimal integer with suppressed assignment */
+	rc = xxsscanf("42", "%*d%n", &n);
+	PCUT_ASSERT_INT_EQUALS(0, rc);
+	PCUT_ASSERT_INT_EQUALS(2, n);
+}
+
+PCUT_TEST(count_chars)
+{
+	int rc;
+	char chars[chars_size];
+	int n;
+
+	/* Count of characters read */
+	memset(chars, 'X', chars_size);
+	rc = xxsscanf("abcd", "%3c%n", chars, &n);
+	PCUT_ASSERT_INT_EQUALS(1, rc);
+	PCUT_ASSERT_TRUE(chars[0] == 'a');
+	PCUT_ASSERT_TRUE(chars[1] == 'b');
+	PCUT_ASSERT_TRUE(chars[2] == 'c');
+	PCUT_ASSERT_TRUE(chars[3] == 'X');
+	PCUT_ASSERT_INT_EQUALS(3, n);
+}
+
+PCUT_TEST(float_intpart_only)
+{
+	int rc;
+	float f;
+
+	/* Float with just integer part */
+	rc = xxsscanf("42", "%f", &f);
+	PCUT_ASSERT_INT_EQUALS(1, rc);
+	PCUT_ASSERT_TRUE(f == 42.0);
+}
+
+PCUT_TEST(double_intpart_only)
+{
+	int rc;
+	double d;
+
+	/* Double with just integer part */
+	rc = xxsscanf("42", "%lf", &d);
+	PCUT_ASSERT_INT_EQUALS(1, rc);
+	PCUT_ASSERT_TRUE(d == 42.0);
+}
+
+PCUT_TEST(ldouble_intpart_only)
+{
+	int rc;
+	long double ld;
+
+	/* Long double with just integer part */
+	rc = xxsscanf("42", "%Lf", &ld);
+	PCUT_ASSERT_INT_EQUALS(1, rc);
+	PCUT_ASSERT_TRUE(ld == 42.0);
+}
+
+PCUT_TEST(float_hex_intpart_only)
+{
+	int rc;
+	float f;
+
+	/* Float with just hexadecimal integer part */
+	rc = xxsscanf("0x2a", "%f", &f);
+	PCUT_ASSERT_INT_EQUALS(1, rc);
+	PCUT_ASSERT_TRUE(f == 0x2a.0p0);
+}
+
+PCUT_TEST(float_sign_intpart)
+{
+	int rc;
+	float f;
+
+	/* Float with sign and integer part */
+	rc = xxsscanf("-42", "%f", &f);
+	PCUT_ASSERT_INT_EQUALS(1, rc);
+	PCUT_ASSERT_TRUE(f == -42.0);
+}
+
+PCUT_TEST(float_intpart_fract)
+{
+	int rc;
+	float f;
+
+	/* Float with integer and fractional part */
+	rc = xxsscanf("4.2", "%f", &f);
+	PCUT_ASSERT_INT_EQUALS(1, rc);
+	/* 1/10 is not exactly representable in binary floating point */
+	PCUT_ASSERT_TRUE(f > 4.199);
+	PCUT_ASSERT_TRUE(f < 4.201);
+}
+
+PCUT_TEST(float_intpart_exp)
+{
+	int rc;
+	float f;
+
+	/* Float with integer part and unsigned exponent */
+	rc = xxsscanf("42e1", "%f", &f);
+	PCUT_ASSERT_INT_EQUALS(1, rc);
+	PCUT_ASSERT_TRUE(f == 420.0);
+}
+
+PCUT_TEST(float_intpart_posexp)
+{
+	int rc;
+	float f;
+
+	/* Float with integer part and positive exponent */
+	rc = xxsscanf("42e+1", "%f", &f);
+	PCUT_ASSERT_INT_EQUALS(1, rc);
+	PCUT_ASSERT_TRUE(f == 420.0);
+}
+
+PCUT_TEST(float_intpart_negexp)
+{
+	int rc;
+	float f;
+
+	/* Float with integer part and negative exponent */
+	rc = xxsscanf("42e-1", "%f", &f);
+	PCUT_ASSERT_INT_EQUALS(1, rc);
+	/* 1/10 is not exactly representable in binary floating point */
+	PCUT_ASSERT_TRUE(f > 4.199);
+	PCUT_ASSERT_TRUE(f < 4.201);
+}
+
+PCUT_TEST(float_intpart_fract_exp)
+{
+	int rc;
+	float f;
+
+	/* Float with integer, fractional parts and unsigned exponent */
+	rc = xxsscanf("4.2e1", "%f", &f);
+	PCUT_ASSERT_INT_EQUALS(1, rc);
+	PCUT_ASSERT_TRUE(f == 42.0);
+}
+
+PCUT_TEST(hexfloat_intpart_fract)
+{
+	int rc;
+	float f;
+
+	/* Hexadecimal float with integer and fractional part */
+	rc = xxsscanf("0x2.a", "%f", &f);
+	PCUT_ASSERT_INT_EQUALS(1, rc);
+	PCUT_ASSERT_TRUE(f == 0x2.ap0);
+}
+
+PCUT_TEST(hexfloat_intpart_exp)
+{
+	int rc;
+	float f;
+
+	/*
+	 * Hexadecimal float with integer part and unsigned exponent
+	 */
+	rc = xxsscanf("0x2ap1", "%f", &f);
+	PCUT_ASSERT_INT_EQUALS(1, rc);
+	PCUT_ASSERT_TRUE(f == 0x2ap1);
+}
+
+PCUT_TEST(hexfloat_intpart_negexp)
+{
+	int rc;
+	float f;
+
+	/*
+	 * Hexadecimal float with integer part and negative exponent
+	 */
+	rc = xxsscanf("0x2ap-1", "%f", &f);
+	PCUT_ASSERT_INT_EQUALS(1, rc);
+	PCUT_ASSERT_TRUE(f == 0x2ap-1);
+}
+
+PCUT_TEST(hexfloat_intpart_fract_exp)
+{
+	int rc;
+	float f;
+
+	/*
+	 * Hexadecimal float with integer, fractional parts and unsigned
+	 * exponent
+	 */
+	rc = xxsscanf("0x2.ap4", "%f", &f);
+	PCUT_ASSERT_INT_EQUALS(1, rc);
+	PCUT_ASSERT_TRUE(f == 0x2.ap4);
+}
+
+PCUT_TEST(float_intpart_limwidth)
+{
+	int rc;
+	float f;
+
+	/* Float with just integer part and limited width */
+	rc = xxsscanf("1234", "%3f", &f);
+	PCUT_ASSERT_INT_EQUALS(1, rc);
+	PCUT_ASSERT_TRUE(f == 123.0);
+}
+
+PCUT_TEST(float_intpart_fract_limwidth)
+{
+	int rc;
+	float f;
+
+	/* Float with integer, fractional part and limited width */
+	rc = xxsscanf("12.34", "%4f", &f);
+	PCUT_ASSERT_INT_EQUALS(1, rc);
+	/* 1/10 is not exactly representable in binary floating point */
+	PCUT_ASSERT_TRUE(f > 12.29);
+	PCUT_ASSERT_TRUE(f < 12.31);
+}
+
+PCUT_TEST(float_width_for_only_intpart)
+{
+	int rc;
+	float f;
+
+	/* Float with width only enough to cover an integral part */
+	rc = xxsscanf("12.34", "%3f", &f);
+	PCUT_ASSERT_INT_EQUALS(1, rc);
+	PCUT_ASSERT_TRUE(f == 12.0);
+}
+
+PCUT_TEST(float_width_small_for_expnum)
+{
+	int rc;
+	float f;
+
+	/* Float with width too small to cover the exponent number */
+	rc = xxsscanf("12.34e+2", "%7f", &f);
+	PCUT_ASSERT_INT_EQUALS(1, rc);
+	/* 1/10 is not exactly representable in binary floating point */
+	PCUT_ASSERT_TRUE(f > 12.339);
+	PCUT_ASSERT_TRUE(f < 12.341);
+}
+
+PCUT_TEST(float_width_small_for_expsignum)
+{
+	int rc;
+	float f;
+
+	/* Float with width too small to cover the exponent sign and number */
+	rc = xxsscanf("12.34e+2", "%6f", &f);
+	PCUT_ASSERT_INT_EQUALS(1, rc);
+	/* 1/10 is not exactly representable in binary floating point */
+	PCUT_ASSERT_TRUE(f > 12.339);
+	PCUT_ASSERT_TRUE(f < 12.341);
+}
+
+PCUT_TEST(float_width_small_for_exp)
+{
+	int rc;
+	float f;
+
+	/* Float with width too small to cover the exponent part */
+	rc = xxsscanf("12.34e+2", "%5f", &f);
+	PCUT_ASSERT_INT_EQUALS(1, rc);
+	/* 1/10 is not exactly representable in binary floating point */
+	PCUT_ASSERT_TRUE(f > 12.339);
+	PCUT_ASSERT_TRUE(f < 12.341);
+}
+
+PCUT_TEST(float_cap_f)
+{
+	int rc;
+	float f;
+
+	/* Float using alternate form 'F' */
+	rc = xxsscanf("42e1", "%F", &f);
+	PCUT_ASSERT_INT_EQUALS(1, rc);
+	PCUT_ASSERT_TRUE(f == 420.0);
+}
+
+PCUT_TEST(float_a)
+{
+	int rc;
+	float f;
+
+	/* Float using alternate form 'a' */
+	rc = xxsscanf("42e1", "%a", &f);
+	PCUT_ASSERT_INT_EQUALS(1, rc);
+	PCUT_ASSERT_TRUE(f == 420.0);
+}
+
+PCUT_TEST(float_e)
+{
+	int rc;
+	float f;
+
+	/* Float using alternate form 'e' */
+	rc = xxsscanf("42e1", "%e", &f);
+	PCUT_ASSERT_INT_EQUALS(1, rc);
+	PCUT_ASSERT_TRUE(f == 420.0);
+}
+
+PCUT_TEST(float_g)
+{
+	int rc;
+	float f;
+
+	/* Float using alternate form 'g' */
+	rc = xxsscanf("42e1", "%g", &f);
+	PCUT_ASSERT_INT_EQUALS(1, rc);
+	PCUT_ASSERT_TRUE(f == 420.0);
+}
+
+PCUT_TEST(float_cap_a)
+{
+	int rc;
+	float f;
+
+	/* Float using alternate form 'A' */
+	rc = xxsscanf("42e1", "%A", &f);
+	PCUT_ASSERT_INT_EQUALS(1, rc);
+	PCUT_ASSERT_TRUE(f == 420.0);
+}
+
+PCUT_TEST(float_cap_e)
+{
+	int rc;
+	float f;
+
+	/* Float using alternate form 'E' */
+	rc = xxsscanf("42e1", "%E", &f);
+	PCUT_ASSERT_INT_EQUALS(1, rc);
+	PCUT_ASSERT_TRUE(f == 420.0);
+}
+
+PCUT_TEST(float_cap_g)
+{
+	int rc;
+	float f;
+
+	/* Float using alternate form 'G' */
+	rc = xxsscanf("42e1", "%G", &f);
+	PCUT_ASSERT_INT_EQUALS(1, rc);
+	PCUT_ASSERT_TRUE(f == 420.0);
+}
+
+PCUT_EXPORT(scanf);
