Upgrade to upstream tip of tree strace.
Change-Id: I56ccbfbb64885c61f160145f181e42dab78adefb
diff --git a/tests/.gitignore b/tests/.gitignore
new file mode 100644
index 0000000..c400a79
--- /dev/null
+++ b/tests/.gitignore
@@ -0,0 +1,7 @@
+net-accept-connect
+set_ptracer_any
+sigaction
+*.log
+*.log.*
+*.o
+*.trs
diff --git a/tests/Makefile.am b/tests/Makefile.am
new file mode 100644
index 0000000..82226df
--- /dev/null
+++ b/tests/Makefile.am
@@ -0,0 +1,25 @@
+# Automake input for strace tests.
+
+AM_CFLAGS = $(WARN_CFLAGS)
+
+check_PROGRAMS = net-accept-connect set_ptracer_any sigaction
+
+TESTS = \
+ ptrace_setoptions.test \
+ strace-f.test \
+ qual_syscall.test \
+ sigaction.test \
+ stat.test \
+ net.test \
+ net-fd.test \
+ detach-sleeping.test \
+ detach-stopped.test \
+ detach-running.test
+
+net-fd.log: net.log
+
+TEST_LOG_COMPILER = $(srcdir)/run.sh
+
+EXTRA_DIST = init.sh run.sh sigaction.awk $(TESTS)
+
+CLEANFILES = $(TESTS:=.tmp)
diff --git a/tests/detach-running.test b/tests/detach-running.test
new file mode 100755
index 0000000..16f552b
--- /dev/null
+++ b/tests/detach-running.test
@@ -0,0 +1,57 @@
+#!/bin/sh
+
+# Ensure that strace can detach from running processes.
+
+. "${srcdir=.}/init.sh"
+
+check_prog sleep
+check_prog grep
+
+set -e
+
+./set_ptracer_any sh -c "echo > $LOG; while :; do :; done" > /dev/null &
+
+while ! [ -s $LOG ]; do
+ kill -0 $! 2> /dev/null ||
+ fail_ 'set_ptracer_any sh failed'
+ $SLEEP_A_BIT
+done
+
+tracee_pid=$!
+
+cleanup()
+{
+ set +e
+ kill $tracee_pid
+ wait $tracee_pid 2> /dev/null
+}
+
+rm -f $LOG
+$STRACE -p $tracee_pid 2> $LOG &
+
+while ! grep -F "Process $tracee_pid attached" $LOG > /dev/null; do
+ kill -0 $! 2> /dev/null ||
+ { cat $LOG; cleanup; fail_ 'strace -p does not work'; }
+ $SLEEP_A_BIT
+done
+
+kill -INT $!
+wait $!
+
+grep -F "Process $tracee_pid detached" $LOG > /dev/null ||
+ { cat $LOG; cleanup; fail_ 'strace -p failed to detach'; }
+
+if [ -f /proc/self/status ]; then
+ $SLEEP_A_BIT
+ test -d /proc/$tracee_pid ||
+ { cat $LOG; cleanup; fail_ 'tracee died after detach'; }
+ grep '^State:.*R (running)' < /proc/$tracee_pid/status > /dev/null || {
+ cat $LOG
+ grep '^State:' < /proc/$tracee_pid/status
+ cleanup
+ fail_ 'tracee is not running after detach'
+ }
+fi
+
+cleanup
+exit 0
diff --git a/tests/detach-sleeping.test b/tests/detach-sleeping.test
new file mode 100755
index 0000000..92138b5
--- /dev/null
+++ b/tests/detach-sleeping.test
@@ -0,0 +1,58 @@
+#!/bin/sh
+
+# Ensure that strace can detach from sleeping processes.
+
+. "${srcdir=.}/init.sh"
+
+check_prog sleep
+check_prog grep
+
+set -e
+
+rm -f $LOG
+./set_ptracer_any sleep $((2*$TIMEOUT_DURATION)) > $LOG &
+
+while ! [ -s $LOG ]; do
+ kill -0 $! 2> /dev/null ||
+ fail_ 'set_ptracer_any sleep failed'
+ $SLEEP_A_BIT
+done
+
+tracee_pid=$!
+
+cleanup()
+{
+ set +e
+ kill $tracee_pid
+ wait $tracee_pid 2> /dev/null
+}
+
+rm -f $LOG
+$STRACE -p $tracee_pid 2> $LOG &
+
+while ! grep -F "Process $tracee_pid attached" $LOG > /dev/null; do
+ kill -0 $! 2> /dev/null ||
+ { cat $LOG; cleanup; fail_ 'strace -p does not work'; }
+ $SLEEP_A_BIT
+done
+
+kill -INT $!
+wait $!
+
+grep -F "Process $tracee_pid detached" $LOG > /dev/null ||
+ { cat $LOG; cleanup; fail_ 'strace -p failed to detach'; }
+
+if [ -f /proc/self/status ]; then
+ $SLEEP_A_BIT
+ test -d /proc/$tracee_pid ||
+ { cat $LOG; cleanup; fail_ 'tracee died after detach'; }
+ grep '^State:.*S (sleeping)' < /proc/$tracee_pid/status > /dev/null || {
+ cat $LOG
+ grep '^State:' < /proc/$tracee_pid/status
+ cleanup
+ fail_ 'tracee is not sleeping after detach'
+ }
+fi
+
+cleanup
+exit 0
diff --git a/tests/detach-stopped.test b/tests/detach-stopped.test
new file mode 100755
index 0000000..81fd303
--- /dev/null
+++ b/tests/detach-stopped.test
@@ -0,0 +1,66 @@
+#!/bin/sh
+
+# Ensure that strace can detach from stopped processes.
+
+. "${srcdir=.}/init.sh"
+
+check_prog sleep
+check_prog grep
+
+set -e
+
+rm -f $LOG
+./set_ptracer_any sleep $((2*$TIMEOUT_DURATION)) > $LOG &
+
+while ! [ -s $LOG ]; do
+ kill -0 $! 2> /dev/null ||
+ fail_ 'set_ptracer_any sleep failed'
+ $SLEEP_A_BIT
+done
+
+tracee_pid=$!
+kill -STOP $tracee_pid
+
+cleanup()
+{
+ set +e
+ kill $tracee_pid
+ kill -CONT $tracee_pid
+ wait $tracee_pid 2> /dev/null
+}
+
+rm -f $LOG
+$STRACE -p $tracee_pid 2> $LOG &
+
+while ! grep -F "Process $tracee_pid attached" $LOG > /dev/null; do
+ kill -0 $! 2> /dev/null ||
+ { cat $LOG; cleanup; fail_ 'strace -p does not work'; }
+ $SLEEP_A_BIT
+done
+
+while ! grep -F -e '--- stopped by ' $LOG > /dev/null; do
+ kill -0 $! 2> /dev/null ||
+ { cat $LOG; cleanup; fail_ 'strace -p does not work'; }
+ $SLEEP_A_BIT
+done
+
+kill -INT $!
+wait $!
+
+grep -F "Process $tracee_pid detached" $LOG > /dev/null ||
+ { cat $LOG; cleanup; fail_ 'strace -p failed to detach'; }
+
+if [ -f /proc/self/status ]; then
+ $SLEEP_A_BIT
+ test -d /proc/$tracee_pid ||
+ { cat $LOG; cleanup; fail_ 'tracee died after detach'; }
+ grep '^State:.*T (stopped)' < /proc/$tracee_pid/status > /dev/null || {
+ cat $LOG
+ grep '^State:' < /proc/$tracee_pid/status
+ cleanup
+ fail_ 'tracee is not group-stopped after detach'
+ }
+fi
+
+cleanup
+exit 0
diff --git a/tests/init.sh b/tests/init.sh
new file mode 100644
index 0000000..3976a1e
--- /dev/null
+++ b/tests/init.sh
@@ -0,0 +1,22 @@
+#!/bin/sh
+
+ME_="${0##*/}"
+
+LOG="$ME_.tmp"
+rm -f "$LOG"
+
+warn_() { printf >&2 '%s\n' "$*"; }
+fail_() { warn_ "$ME_: failed test: $*"; exit 1; }
+skip_() { warn_ "$ME_: skipped test: $*"; exit 77; }
+framework_failure_() { warn_ "$ME_: framework failure: $*"; exit 99; }
+framework_skip_() { warn_ "$ME_: framework skip: $*"; exit 77; }
+
+check_prog()
+{
+ type "$@" > /dev/null 2>&1 ||
+ framework_skip_ "$* is not available"
+}
+
+: "${STRACE:=../strace}"
+: "${TIMEOUT_DURATION:=60}"
+: "${SLEEP_A_BIT:=sleep 1}"
diff --git a/tests/net-accept-connect.c b/tests/net-accept-connect.c
new file mode 100644
index 0000000..5af7d81
--- /dev/null
+++ b/tests/net-accept-connect.c
@@ -0,0 +1,49 @@
+#include <assert.h>
+#include <stddef.h>
+#include <string.h>
+#include <unistd.h>
+#include <sys/wait.h>
+#include <sys/socket.h>
+#include <sys/un.h>
+
+#define SUN_PATH "local-stream"
+int main(void)
+{
+ struct sockaddr_un addr = {
+ .sun_family = AF_UNIX,
+ .sun_path = SUN_PATH
+ };
+ socklen_t len = offsetof(struct sockaddr_un, sun_path) + sizeof SUN_PATH;
+
+ unlink(SUN_PATH);
+ close(0);
+ close(1);
+
+ assert(socket(PF_LOCAL, SOCK_STREAM, 0) == 0);
+ assert(bind(0, (struct sockaddr *) &addr, len) == 0);
+ assert(listen(0, 5) == 0);
+
+ memset(&addr, 0, sizeof addr);
+ assert(getsockname(0, (struct sockaddr *) &addr, &len) == 0);
+
+ pid_t pid = fork();
+ assert(pid >= 0);
+
+ if (pid) {
+ assert(accept(0, (struct sockaddr *) &addr, &len) == 1);
+ assert(close(0) == 0);
+ int status;
+ assert(waitpid(pid, &status, 0) == pid);
+ assert(status == 0);
+ assert(close(1) == 0);
+ } else {
+ assert(socket(PF_LOCAL, SOCK_STREAM, 0) == 1);
+ assert(close(0) == 0);
+ assert(connect(1, (struct sockaddr *) &addr, len) == 0);
+ assert(close(1) == 0);
+ return 0;
+ }
+
+ unlink(SUN_PATH);
+ return 0;
+}
diff --git a/tests/net-fd.test b/tests/net-fd.test
new file mode 100755
index 0000000..0f6b4e2
--- /dev/null
+++ b/tests/net-fd.test
@@ -0,0 +1,47 @@
+#!/bin/sh
+
+# Check how network syscalls are traced when decoding socket descriptors
+
+. "${srcdir=.}/init.sh"
+
+# strace -y is implemented using /proc/self/fd
+[ -d /proc/self/fd/ ] ||
+ framework_skip_ '/proc/self/fd/ is not available'
+
+check_prog grep
+check_prog rm
+
+rm -f $LOG.*
+
+./net-accept-connect ||
+ fail_ 'net-accept-connect failed'
+
+# using -y to test socket descriptors 'paths' decoding
+args="-tt -ff -y -o $LOG -enetwork ./net-accept-connect"
+$STRACE $args ||
+ fail_ "strace $args failed"
+
+"$srcdir"/../strace-log-merge $LOG > $LOG || {
+ cat $LOG
+ fail_ 'strace-log-merge failed'
+}
+
+rm -f $LOG.*
+
+grep_log()
+{
+ local syscall="$1"; shift
+ local prefix='[1-9][0-9]* +[0-9]+:[0-9]+:[0-9]+\.[0-9]+ +'
+
+ LC_ALL=C grep -E -x "$prefix$syscall$@" $LOG > /dev/null || {
+ cat $LOG
+ fail_ "strace -enetwork failed to trace \"$syscall\" properly"
+ }
+}
+grep_log bind '\(0<socket:\[[0-9]+\]>, \{sa_family=AF_(LOCAL|UNIX|FILE), sun_path="local-stream"\}, 15\) += 0'
+grep_log listen '\(0<socket:\[[0-9]+\]>, 5\) += 0'
+grep_log getsockname '\(0<socket:\[[0-9]+\]>, \{sa_family=AF_(LOCAL|UNIX|FILE), sun_path="local-stream"\}, \[15\]\) += 0'
+grep_log accept '\(0<socket:\[[0-9]+\]>, \{sa_family=AF_(LOCAL|UNIX|FILE), NULL\}, \[2\]\) += 1'
+grep_log connect '\(1<socket:\[[0-9]+\]>, \{sa_family=AF_(LOCAL|UNIX|FILE), sun_path="local-stream"\}, 15\) += 0'
+
+exit 0
diff --git a/tests/net.test b/tests/net.test
new file mode 100755
index 0000000..de1dae1
--- /dev/null
+++ b/tests/net.test
@@ -0,0 +1,45 @@
+#!/bin/sh
+
+# Check how network syscalls are traced.
+
+. "${srcdir=.}/init.sh"
+
+check_prog grep
+check_prog rm
+
+rm -f $LOG.*
+
+./net-accept-connect ||
+ fail_ 'net-accept-connect failed'
+
+args="-tt -ff -o $LOG -enetwork ./net-accept-connect"
+$STRACE $args ||
+ fail_ "strace $args failed"
+
+"$srcdir"/../strace-log-merge $LOG > $LOG || {
+ cat $LOG
+ fail_ 'strace-log-merge failed'
+}
+
+rm -f $LOG.*
+
+grep_log()
+{
+ local syscall="$1"; shift
+ local prefix='[1-9][0-9]* +[0-9]+:[0-9]+:[0-9]+\.[0-9]+ +'
+
+ LC_ALL=C grep -E -x "$prefix$syscall$@" $LOG > /dev/null || {
+ cat $LOG
+ fail_ "strace -enetwork failed to trace \"$syscall\" properly"
+ }
+}
+
+grep_log socket '\(PF_(LOCAL|UNIX|FILE), SOCK_STREAM, 0\) += 0'
+grep_log socket '\(PF_(LOCAL|UNIX|FILE), SOCK_STREAM, 0\) += 1'
+grep_log bind '\(0, \{sa_family=AF_(LOCAL|UNIX|FILE), sun_path="local-stream"\}, 15\) += 0'
+grep_log listen '\(0, 5\) += 0'
+grep_log getsockname '\(0, \{sa_family=AF_(LOCAL|UNIX|FILE), sun_path="local-stream"\}, \[15\]\) += 0'
+grep_log accept '\(0, \{sa_family=AF_(LOCAL|UNIX|FILE), NULL\}, \[2\]\) += 1'
+grep_log connect '\(1, \{sa_family=AF_(LOCAL|UNIX|FILE), sun_path="local-stream"\}, 15\) += 0'
+
+exit 0
diff --git a/tests/ptrace_setoptions.test b/tests/ptrace_setoptions.test
new file mode 100755
index 0000000..e574e24
--- /dev/null
+++ b/tests/ptrace_setoptions.test
@@ -0,0 +1,30 @@
+#!/bin/sh
+
+# Ensure that strace tests kernel PTRACE_O_TRACECLONE
+# and PTRACE_O_TRACESYSGOOD support properly.
+
+. "${srcdir=.}/init.sh"
+
+[ "$(uname -s)" = Linux ] ||
+ skip_ 'The kernel is not a Linux kernel'
+case "$(uname -r)" in
+ 2.[6-9]*|2.[1-5][0-9]*|[3-9].*|[12][0-9]*) ;;
+ *) skip_ 'The kernel is not Linux 2.6.* or newer' ;;
+esac
+
+$STRACE -df -enone / > /dev/null 2> $LOG
+grep -F -x 'ptrace_setoptions = 0xe' $LOG > /dev/null || {
+ cat $LOG
+ fail_ 'strace -f failed to recognize proper kernel PTRACE_O_TRACECLONE support'
+}
+
+grep -F -x 'ptrace_setoptions = 0x1f' $LOG > /dev/null || {
+ cat $LOG
+ fail_ 'strace -f failed to recognize proper kernel PTRACE_O_TRACESYSGOOD support'
+}
+
+$STRACE -d -enone / > /dev/null 2> $LOG
+grep -F -x 'ptrace_setoptions = 0x11' $LOG > /dev/null || {
+ cat $LOG
+ fail_ 'strace failed to recognize proper kernel PTRACE_O_TRACESYSGOOD support'
+}
diff --git a/tests/qual_syscall.test b/tests/qual_syscall.test
new file mode 100755
index 0000000..652fcdb
--- /dev/null
+++ b/tests/qual_syscall.test
@@ -0,0 +1,25 @@
+#!/bin/sh
+
+# Ensure that strace -e trace=set works.
+
+. "${srcdir=.}/init.sh"
+
+check_prog ls
+check_prog grep
+
+$STRACE -e execve ls > /dev/null 2> $LOG &&
+grep '^execve(' $LOG > /dev/null ||
+ { cat $LOG; fail_ 'strace -e execve does not work'; }
+
+grep -v '^execve(' $LOG |
+LC_ALL=C grep '^[[:alnum:]_]*(' > /dev/null &&
+ { cat $LOG; fail_ 'strace -e execve does not work properly'; }
+
+$STRACE -e trace=process ls > /dev/null 2> $LOG &&
+grep '^execve(' $LOG > /dev/null ||
+ { cat $LOG; fail_ 'strace -e trace=process does not work'; }
+
+grep '^open' $LOG > /dev/null &&
+ { cat $LOG; fail_ 'strace -e trace=process does not work properly'; }
+
+exit 0
diff --git a/tests/run.sh b/tests/run.sh
new file mode 100755
index 0000000..02d9912
--- /dev/null
+++ b/tests/run.sh
@@ -0,0 +1,12 @@
+#!/bin/sh
+
+. "${srcdir=.}/init.sh"
+
+$STRACE -V > /dev/null ||
+ framework_failure_ "$STRACE is not available"
+
+TIMEOUT="timeout -s 9 $TIMEOUT_DURATION"
+$TIMEOUT true > /dev/null 2>&1 ||
+ TIMEOUT=
+
+exec $TIMEOUT "$@"
diff --git a/tests/set_ptracer_any.c b/tests/set_ptracer_any.c
new file mode 100644
index 0000000..7254a07
--- /dev/null
+++ b/tests/set_ptracer_any.c
@@ -0,0 +1,24 @@
+#ifdef HAVE_CONFIG_H
+# include "config.h"
+#endif
+#include <stdio.h>
+#include <unistd.h>
+#ifdef HAVE_PRCTL
+# include <sys/prctl.h>
+#endif
+
+int main(int argc, char **argv)
+{
+ if (argc < 2)
+ return 99;
+#if defined HAVE_PRCTL && defined PR_SET_PTRACER && defined PR_SET_PTRACER_ANY
+ (void) prctl(PR_SET_PTRACER, PR_SET_PTRACER_ANY);
+#endif
+ if (write(1, "\n", 1) != 1) {
+ perror("write");
+ return 99;
+ }
+ (void) execvp(argv[1], argv + 1);
+ perror(argv[1]);
+ return 99;
+}
diff --git a/tests/sigaction.awk b/tests/sigaction.awk
new file mode 100644
index 0000000..2c4eab6
--- /dev/null
+++ b/tests/sigaction.awk
@@ -0,0 +1,36 @@
+# rt_sigaction on ALPHA has 5 args: sig, act, oact, sigsetsize, restorer.
+# rt_sigaction on SPARC has 5 args: sig, act, oact, restorer, sigsetsize.
+# rt_sigaction on other architectures has 4 args: sig, act, oact, sigsetsize.
+# Some architectures have SA_RESTORER, some don't;
+# in particular, SPARC has and ALPHA hasn't.
+#
+# There are two regexps for each test:
+# the 1st is for any architecture with SA_RESTORER, including SPARC;
+# the 2nd is for any architecture without SA_RESTORER, including ALPHA.
+
+# Test 1.
+NR == 1 && /^rt_sigaction\(SIGUSR2, {SIG_IGN, \[HUP INT\], SA_RESTORER\|SA_RESTART, 0x[0-9a-f]+}, {SIG_DFL, \[\], 0}, (0x[0-9a-f]+, )?(4|8|16)\) = 0$/ {next}
+NR == 1 && /^rt_sigaction\(SIGUSR2, {SIG_IGN, \[HUP INT\], SA_RESTART}, {SIG_DFL, \[\], 0}, (4|8|16)(, 0x[0-9a-f]+)?\) = 0$/ {next}
+
+# Test 2.
+NR == 2 && /^rt_sigaction\(SIGUSR2, {0x[0-9a-f]+, \[QUIT TERM\], SA_RESTORER\|SA_SIGINFO, 0x[0-9a-f]+}, {SIG_IGN, \[HUP INT\], SA_RESTORER\|SA_RESTART, 0x[0-9a-f]+}, (0x[0-9a-f]+, )?(4|8|16)\) = 0$/ {next}
+NR == 2 && /^rt_sigaction\(SIGUSR2, {0x[0-9a-f]+, \[QUIT TERM\], SA_SIGINFO}, {SIG_IGN, \[HUP INT\], SA_RESTART}, (4|8|16)(, 0x[0-9a-f]+)?\) = 0$/ {next}
+
+# Test 3.
+NR == 3 && /^rt_sigaction\(SIGUSR2, {SIG_DFL, \[\], SA_RESTORER, 0x[0-9a-f]+}, {0x[0-9a-f]+, \[QUIT TERM\], SA_RESTORER\|SA_SIGINFO, 0x[0-9a-f]+}, (0x[0-9a-f]+, )?(4|8|16)\) = 0$/ {next}
+NR == 3 && /^rt_sigaction\(SIGUSR2, {SIG_DFL, \[\], 0}, {0x[0-9a-f]+, \[QUIT TERM\], SA_SIGINFO}, (4|8|16)(, 0x[0-9a-f]+)?\) = 0$/ {next}
+
+# The last line.
+NR == 4 && /^\+\+\+ exited with 0 \+\+\+$/ {next}
+
+{
+ print "Line " NR " does not match: " $0
+ exit 1
+}
+
+END {
+ if (NR != 4) {
+ print "Expected 4 lines, found " NR " line(s)."
+ exit 1
+ }
+}
diff --git a/tests/sigaction.c b/tests/sigaction.c
new file mode 100644
index 0000000..82666f9
--- /dev/null
+++ b/tests/sigaction.c
@@ -0,0 +1,36 @@
+#include <assert.h>
+#include <stdlib.h>
+#include <unistd.h>
+#include <signal.h>
+
+static void handle_signal(int no)
+{
+ _exit(128 + no);
+}
+
+int
+main(void)
+{
+ struct sigaction sa, sa1, sa2, sa3;
+
+ sa.sa_handler = SIG_IGN;
+ sigemptyset(&sa.sa_mask);
+ sigaddset(&sa.sa_mask, SIGHUP);
+ sigaddset(&sa.sa_mask, SIGINT);
+ sa.sa_flags = SA_RESTART;
+ assert(!sigaction(SIGUSR2, &sa, &sa1));
+
+ sa.sa_handler = handle_signal;
+ sigemptyset(&sa.sa_mask);
+ sigaddset(&sa.sa_mask, SIGQUIT);
+ sigaddset(&sa.sa_mask, SIGTERM);
+ sa.sa_flags = SA_SIGINFO;
+ assert(!sigaction(SIGUSR2, &sa, &sa2));
+
+ sa.sa_handler = SIG_DFL;
+ sigemptyset(&sa.sa_mask);
+ sa.sa_flags = 0;
+ assert(!sigaction(SIGUSR2, &sa, &sa3));
+
+ return 0;
+}
diff --git a/tests/sigaction.test b/tests/sigaction.test
new file mode 100755
index 0000000..33732e0
--- /dev/null
+++ b/tests/sigaction.test
@@ -0,0 +1,19 @@
+#!/bin/sh
+
+# Check rt_sigaction decoding.
+
+. "${srcdir=.}/init.sh"
+
+check_prog awk
+
+./sigaction ||
+ fail_ 'sigaction failed'
+
+args="-o $LOG -ert_sigaction ./sigaction"
+$STRACE $args ||
+ fail_ "strace $args failed"
+
+awk -f "$srcdir"/sigaction.awk $LOG ||
+ { cat $LOG; fail_ 'unexpected output'; }
+
+exit 0
diff --git a/tests/stat.test b/tests/stat.test
new file mode 100755
index 0000000..4176df0
--- /dev/null
+++ b/tests/stat.test
@@ -0,0 +1,35 @@
+#!/bin/sh
+
+# Check how ftruncate, lseek and stat family syscalls are traced.
+
+. "${srcdir=.}/init.sh"
+
+check_prog dd
+check_prog find
+check_prog grep
+check_prog rm
+
+umask 022
+truncate_cmd='dd seek=46118400000 obs=1 count=0 if=/dev/null of=sample'
+$truncate_cmd > $LOG 2>&1 ||
+ { cat $LOG; framework_skip_ 'failed to create a large sparse file'; }
+rm -f sample
+
+$STRACE -edesc $truncate_cmd 2>&1 > /dev/null 2> $LOG &&
+LC_ALL=C grep -E -x 'ftruncate(64)?\(1, 46118400000\) += 0' $LOG > /dev/null ||
+ { cat $LOG; fail_ 'strace -edesc failed to trace ftruncate/ftruncate64 properly'; }
+
+LC_ALL=C grep -E -x 'lseek\(1, 46118400000, SEEK_CUR\) += 46118400000|_llseek\(1, 46118400000, \[46118400000\], SEEK_CUR\) += 0' $LOG > /dev/null ||
+ { cat $LOG; fail_ 'strace -edesc failed to trace lseek/_llseek properly'; }
+
+$STRACE -efile find -L sample > /dev/null 2> $LOG &&
+LC_ALL=C grep -E -x 'stat(64)?\("sample", \{st_mode=S_IFREG\|0644, st_size=46118400000, \.\.\.\}\) += 0|(new)?fstatat(64)?\(AT_FDCWD, "sample", \{st_mode=S_IFREG\|0644, st_size=46118400000, \.\.\.\}, 0\) += 0' $LOG > /dev/null ||
+ { cat $LOG; fail_ 'strace -efile failed to trace stat/stat64 properly'; }
+
+$STRACE -efile find sample > /dev/null 2> $LOG &&
+LC_ALL=C grep -E -x 'lstat(64)?\("sample", \{st_mode=S_IFREG\|0644, st_size=46118400000, \.\.\.\}\) += 0|(new)?fstatat(64)?\(AT_FDCWD, "sample", \{st_mode=S_IFREG\|0644, st_size=46118400000, \.\.\.\}, AT_SYMLINK_NOFOLLOW\) += 0' $LOG > /dev/null ||
+ { cat $LOG; fail_ 'strace -efile failed to trace fstatat/fstatat64 properly'; }
+
+rm -f sample
+
+exit 0
diff --git a/tests/strace-f.test b/tests/strace-f.test
new file mode 100755
index 0000000..0c4622c
--- /dev/null
+++ b/tests/strace-f.test
@@ -0,0 +1,11 @@
+#!/bin/sh
+
+# Ensure that strace -f works.
+
+. "${srcdir=.}/init.sh"
+
+time=/usr/bin/time
+check_prog $time
+
+$STRACE -f $time /bin/ls > $LOG 2>&1 ||
+ { cat $LOG; fail_ 'strace -f does not work'; }