diff --git a/.gitignore b/.gitignore
index a279991..9c247c8 100644
--- a/.gitignore
+++ b/.gitignore
@@ -29,6 +29,8 @@
/tests/example_error.o
/tests/example_exec
/tests/example_exec.o
+/tests/example_stdio
+/tests/example_stdio.o
/tests/example_vfork
/tests/example_vfork.o
/tests/test_environment.sh.log
@@ -47,6 +49,8 @@
/tests/test_redirects.sh.trs
/tests/test_simple.sh.log
/tests/test_simple.sh.trs
+/tests/test_stdio.sh.log
+/tests/test_stdio.sh.trs
/tests/test_symbols.sh.log
/tests/test_symbols.sh.trs
/tests/test_vfork.sh.log
diff --git a/README b/README
index 79a4084..e298a04 100644
--- a/README
+++ b/README
@@ -137,6 +137,14 @@ doesn't exist it's created. An existing file isn't overwritten, but the
warnings are appended at the end.
+KNOWN ISSUES
+------------
+
+- `{fputc,putc,putchar}_unlocked()` are not hooked when writing to stdout
+ (which might be redirected to stderr). Can't be fixed as the compiler
+ inlines the code into the program without calling any function.
+
+
BUGS
----
diff --git a/tests/Makefile.am b/tests/Makefile.am
index 24f94e0..2863950 100644
--- a/tests/Makefile.am
+++ b/tests/Makefile.am
@@ -6,8 +6,9 @@ TESTS = test_environment.sh \
test_exec.sh \
test_noforce.sh \
test_redirects.sh \
- test_simple.sh
-check_PROGRAMS = example example_exec
+ test_simple.sh \
+ test_stdio.sh
+check_PROGRAMS = example example_exec example_stdio
if HAVE_ERR_H
TESTS += test_err.sh
@@ -36,6 +37,7 @@ dist_check_DATA = example.h \
example_redirects.sh.expected \
example_simple.sh \
example_simple.sh.expected \
+ example_stdio.expected \
example_vfork.expected
# Used by lib.sh. Can't use "export EGREP" because it doesn't work with BSD's
diff --git a/tests/example_stdio.c b/tests/example_stdio.c
new file mode 100644
index 0000000..028b3f7
--- /dev/null
+++ b/tests/example_stdio.c
@@ -0,0 +1,140 @@
+/*
+ * Test all hooked stdio.h functions.
+ *
+ * Copyright (C) 2013 Simon Ruderich
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see .
+ */
+
+#include
+
+/* For {fwrite,fputs,fputc}_unlocked(), if available. */
+#define _GNU_SOURCE
+#include
+#include
+#include
+#include
+
+#include "example.h"
+#include "../src/compiler.h"
+
+
+/* These are not in POSIX. */
+#ifndef HAVE_FWRITE_UNLOCKED
+static size_t fwrite_unlocked(void const *ptr, size_t size, size_t n, FILE *stream) {
+ return fwrite(ptr, size, n, stream);
+}
+#endif
+#ifndef HAVE_FPUTS_UNLOCKED
+static int fputs_unlocked(char const *s, FILE *stream) {
+ return fputs(s, stream);
+}
+#endif
+#ifndef HAVE_FPUTC_UNLOCKED
+static int fputc_unlocked(int c, FILE *stream) {
+ return fputc(c, stream);
+}
+#endif
+
+
+static int out;
+
+static void test_vprintf(char const *format, ...) noinline;
+static void test_vfprintf(FILE *stream, char const *format, ...) noinline;
+static void NEWLINE(void) noinline;
+
+static void test_vprintf(char const *format, ...) {
+ va_list ap;
+
+ va_start(ap, format);
+ vprintf(format, ap);
+ va_end(ap);
+}
+static void test_vfprintf(FILE *stream, char const *format, ...) {
+ va_list ap;
+
+ va_start(ap, format);
+ vfprintf(stream, format, ap);
+ va_end(ap);
+}
+
+static void NEWLINE(void) {
+ fflush(stdout);
+ fflush(stderr);
+ xwrite(out, "\n", 1);
+}
+
+
+int main(int argc, char **argv unused) {
+ /* stdout */
+
+ out = dup(STDOUT_FILENO);
+ if (out == -1) {
+ perror("dup");
+ return EXIT_FAILURE;
+ }
+
+ /* Redirect stdout to stderr so we can test all functions, including those
+ * not writing to stderr. */
+ xdup2(STDERR_FILENO, STDOUT_FILENO);
+
+ /* stdout redirected to stderr. */
+
+ xwrite(STDOUT_FILENO, "write()", 7); NEWLINE();
+ fwrite("fwrite()", 8, 1, stdout); NEWLINE();
+
+ /* puts(3) */
+ fputs("fputs()", stdout); NEWLINE();
+ fputc('a', stdout); NEWLINE();
+ putc('b', stdout); NEWLINE();
+ putchar('c'); NEWLINE();
+ puts("puts()"); NEWLINE();
+
+ /* printf(3) */
+ printf("%s [%d]", "printf()", argc); NEWLINE();
+ fprintf(stdout, "%s [%d]", "fprintf()", argc); NEWLINE();
+ test_vprintf("%s [%d]", "vprintf()", argc); NEWLINE();
+ test_vfprintf(stdout, "%s [%d]", "vfprintf()", argc); NEWLINE();
+
+ /* unlocked_stdio(3) */
+ fwrite_unlocked("fwrite_unlocked()", 17, 1, stdout); NEWLINE();
+ fputs_unlocked("fputs_unlocked()", stdout); NEWLINE();
+ /* FIXME: 'x', 'y' and 'z' are not colored */
+ fputc_unlocked('x', stdout); NEWLINE();
+ putc_unlocked('y', stdout); NEWLINE();
+ putchar_unlocked('z'); NEWLINE();
+
+
+ /* stderr */
+
+ xwrite(STDERR_FILENO, "write()", 7); NEWLINE();
+ fwrite("fwrite()", 8, 1, stderr); NEWLINE();
+
+ /* puts(3) */
+ fputs("fputs()", stderr); NEWLINE();
+ fputc('a', stderr); NEWLINE();
+ putc('b', stderr); NEWLINE();
+
+ /* printf(3) */
+ fprintf(stderr, "%s [%d]", "fprintf()", argc); NEWLINE();
+ test_vfprintf(stderr, "%s [%d]", "vfprintf()", argc); NEWLINE();
+
+ /* unlocked_stdio(3) */
+ fwrite_unlocked("fwrite_unlocked()", 17, 1, stderr); NEWLINE();
+ fputs_unlocked("fputs_unlocked()", stderr); NEWLINE();
+ fputc_unlocked('x', stderr); NEWLINE();
+ putc_unlocked('y', stderr); NEWLINE();
+
+ return EXIT_SUCCESS;
+}
diff --git a/tests/example_stdio.expected b/tests/example_stdio.expected
new file mode 100644
index 0000000..0ab9883
--- /dev/null
+++ b/tests/example_stdio.expected
@@ -0,0 +1,28 @@
+>STDERR>write()STDERR>fwrite()STDERR>fputs()STDERR>aSTDERR>bSTDERR>cSTDERR>puts()
+STDERR>printf() [1]STDERR>fprintf() [1]STDERR>vprintf() [1]STDERR>vfprintf() [1]STDERR>fwrite_unlocked()STDERR>fputs_unlocked()STDERR>write()STDERR>fwrite()STDERR>fputs()STDERR>aSTDERR>bSTDERR>fprintf() [1]STDERR>vfprintf() [1]STDERR>fwrite_unlocked()STDERR>fputs_unlocked()STDERR>xSTDERR>y.
+
+
+test "x$srcdir" = x && srcdir=.
+. "$srcdir/lib.sh"
+
+test_program example_stdio
+test_program_subshell example_stdio