tests: add poll.test

* tests/poll.c: New file.
* tests/poll.test: New test.
* tests/.gitignore: Add poll.
* tests/Makefile.am (check_PROGRAMS): Likewise.
(TESTS): Add poll.test.
diff --git a/tests/.gitignore b/tests/.gitignore
index 1cee1f8..5671978 100644
--- a/tests/.gitignore
+++ b/tests/.gitignore
@@ -72,6 +72,7 @@
 pc
 personality
 pipe
+poll
 ppoll
 pselect6
 readdir
diff --git a/tests/Makefile.am b/tests/Makefile.am
index b36362a..2e77934 100644
--- a/tests/Makefile.am
+++ b/tests/Makefile.am
@@ -120,6 +120,7 @@
 	pc \
 	personality \
 	pipe \
+	poll \
 	ppoll \
 	pselect6 \
 	readdir \
@@ -270,6 +271,7 @@
 	pc.test \
 	personality.test \
 	pipe.test \
+	poll.test \
 	ppoll.test \
 	pselect6.test \
 	readdir.test \
diff --git a/tests/poll.c b/tests/poll.c
new file mode 100644
index 0000000..045aca0
--- /dev/null
+++ b/tests/poll.c
@@ -0,0 +1,260 @@
+/*
+ * This file is part of poll strace test.
+ *
+ * Copyright (c) 2016 Dmitry V. Levin <ldv@altlinux.org>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ * 2. 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.
+ * 3. 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.
+ */
+
+#include "tests.h"
+#include <sys/syscall.h>
+
+#ifdef __NR_poll
+
+# include <assert.h>
+# include <errno.h>
+# include <poll.h>
+# include <stdio.h>
+# include <stdlib.h>
+# include <unistd.h>
+
+#define PRINT_EVENT(flag, member) \
+	if (member & flag) { \
+		if (member != pfd->member) \
+			tprintf("|"); \
+		tprintf(#flag); \
+		member &= ~flag; \
+	}
+
+static void
+print_pollfd_entering(const struct pollfd *const pfd)
+{
+	tprintf("{fd=%d", pfd->fd);
+	if (pfd->fd >= 0) {
+		tprintf(", events=");
+		short events = pfd->events;
+
+		if (pfd->events) {
+			PRINT_EVENT(POLLIN, events)
+			PRINT_EVENT(POLLPRI, events)
+			PRINT_EVENT(POLLOUT, events)
+#ifdef POLLRDNORM
+			PRINT_EVENT(POLLRDNORM, events)
+#endif
+#ifdef POLLWRNORM
+			PRINT_EVENT(POLLWRNORM, events)
+#endif
+#ifdef POLLRDBAND
+			PRINT_EVENT(POLLRDBAND, events)
+#endif
+#ifdef POLLWRBAND
+			PRINT_EVENT(POLLWRBAND, events)
+#endif
+			PRINT_EVENT(POLLERR, events)
+			PRINT_EVENT(POLLHUP, events)
+			PRINT_EVENT(POLLNVAL, events)
+		} else
+			tprintf("0");
+	}
+	tprintf("}");
+}
+
+static void
+print_pollfd_array_entering(const struct pollfd *const pfd,
+			    const unsigned int size,
+			    const unsigned int valid,
+			    const unsigned int abbrev)
+{
+	tprintf("[");
+	unsigned int i;
+	for (i = 0; i < size; ++i) {
+		if (i)
+			tprintf(", ");
+		if (i >= abbrev) {
+			tprintf("...");
+			break;
+		}
+		if (i < valid)
+			print_pollfd_entering(&pfd[i]);
+		else {
+			tprintf("%p", &pfd[i]);
+			break;
+		}
+	}
+	tprintf("]");
+}
+
+static void
+print_pollfd_exiting(const struct pollfd *const pfd,
+		     unsigned int *const seen,
+		     const unsigned int abbrev)
+{
+	if (!pfd->revents || pfd->fd < 0 || *seen > abbrev)
+		return;
+
+	if (*seen)
+		tprintf(", ");
+	++(*seen);
+
+	if (*seen > abbrev) {
+		tprintf("...");
+		return;
+	}
+	tprintf("{fd=%d, revents=", pfd->fd);
+	short revents = pfd->revents;
+
+	PRINT_EVENT(POLLIN, revents)
+	PRINT_EVENT(POLLPRI, revents)
+	PRINT_EVENT(POLLOUT, revents)
+#ifdef POLLRDNORM
+	PRINT_EVENT(POLLRDNORM, revents)
+#endif
+#ifdef POLLWRNORM
+	PRINT_EVENT(POLLWRNORM, revents)
+#endif
+#ifdef POLLRDBAND
+	PRINT_EVENT(POLLRDBAND, revents)
+#endif
+#ifdef POLLWRBAND
+	PRINT_EVENT(POLLWRBAND, revents)
+#endif
+	PRINT_EVENT(POLLERR, revents)
+	PRINT_EVENT(POLLHUP, revents)
+	PRINT_EVENT(POLLNVAL, revents)
+	tprintf("}");
+}
+
+static void
+print_pollfd_array_exiting(const struct pollfd *const pfd,
+			   const unsigned int size,
+			   const unsigned int abbrev)
+{
+	tprintf("[");
+	unsigned int seen = 0;
+	unsigned int i;
+	for (i = 0; i < size; ++i)
+		print_pollfd_exiting(&pfd[i], &seen, abbrev);
+	tprintf("]");
+}
+
+int
+main(int ac, char **av)
+{
+	tprintf("%s", "");
+
+	assert(syscall(__NR_poll, NULL, 42, 0) == -1);
+	if (ENOSYS == errno)
+		perror_msg_and_skip("poll");
+	tprintf("poll(NULL, 42, 0) = -1 EFAULT (%m)\n");
+
+	int fds[2];
+	if (pipe(fds) || pipe(fds))
+		perror_msg_and_fail("pipe");
+
+	const unsigned int abbrev = (ac > 1) ? atoi(av[1]) : -1;
+	const struct pollfd pfds0[] = {
+		{ .fd = 0, .events = POLLIN | POLLPRI | POLLRDNORM | POLLRDBAND },
+		{ .fd = 1, .events = POLLOUT | POLLWRNORM | POLLWRBAND },
+		{ .fd = fds[0], .events = POLLIN | POLLPRI },
+		{ .fd = fds[1], .events = POLLOUT },
+		{ .fd = 2, .events = POLLOUT | POLLWRBAND }
+	};
+	struct pollfd *const tail_fds0 = tail_memdup(pfds0, sizeof(pfds0));
+	const int timeout = 42;
+	int rc = syscall(__NR_poll, tail_fds0, 0, timeout);
+	assert(rc == 0);
+
+	tprintf("poll(%p, 0, %d) = %d (Timeout)\n", tail_fds0, timeout, rc);
+
+	rc = syscall(__NR_poll, tail_fds0, ARRAY_SIZE(pfds0), timeout);
+	assert(rc == 3);
+
+	tprintf("poll(");
+	print_pollfd_array_entering(tail_fds0, ARRAY_SIZE(pfds0),
+				    ARRAY_SIZE(pfds0), abbrev);
+	tprintf(", %u, %d) = %d (", ARRAY_SIZE(pfds0), timeout, rc);
+	print_pollfd_array_exiting(tail_fds0, ARRAY_SIZE(pfds0), abbrev);
+	tprintf(")\n");
+
+	tail_fds0[0].fd = -1;
+	tail_fds0[2].fd = -3;
+	tail_fds0[4].events = 0;
+	rc = syscall(__NR_poll, tail_fds0, ARRAY_SIZE(pfds0), timeout);
+	assert(rc == 2);
+
+	tprintf("poll(");
+	print_pollfd_array_entering(tail_fds0, ARRAY_SIZE(pfds0),
+				    ARRAY_SIZE(pfds0), abbrev);
+	tprintf(", %u, %d) = %d (", ARRAY_SIZE(pfds0), timeout, rc);
+	print_pollfd_array_exiting(tail_fds0, ARRAY_SIZE(pfds0), abbrev);
+	tprintf(")\n");
+
+	tail_fds0[1].fd = -2;
+	tail_fds0[4].fd = -5;
+	rc = syscall(__NR_poll, tail_fds0, ARRAY_SIZE(pfds0), timeout);
+	assert(rc == 1);
+
+	tprintf("poll(");
+	print_pollfd_array_entering(tail_fds0, ARRAY_SIZE(pfds0),
+				    ARRAY_SIZE(pfds0), abbrev);
+	tprintf(", %u, %d) = %d (", ARRAY_SIZE(pfds0), timeout, rc);
+	print_pollfd_array_exiting(tail_fds0, ARRAY_SIZE(pfds0), abbrev);
+	tprintf(")\n");
+
+	struct pollfd pfds1[] = {
+		{ .fd = 1, .events = POLLIN | POLLPRI | POLLRDNORM | POLLRDBAND },
+		{ .fd = 0, .events = POLLOUT | POLLWRNORM | POLLWRBAND }
+	};
+	struct pollfd *const tail_fds1 = tail_memdup(pfds1, sizeof(pfds1));
+	rc = syscall(__NR_poll, tail_fds1, ARRAY_SIZE(pfds1), timeout);
+	assert(rc == 0);
+
+	tprintf("poll(");
+	print_pollfd_array_entering(tail_fds1, ARRAY_SIZE(pfds1),
+				    ARRAY_SIZE(pfds1), abbrev);
+	tprintf(", %u, %d) = %d (Timeout)\n", ARRAY_SIZE(pfds1), timeout, rc);
+
+	const void *const efault = tail_fds0 + ARRAY_SIZE(pfds0);
+	rc = syscall(__NR_poll, efault, 1, 0);
+	assert(rc == -1);
+	tprintf("poll(%p, 1, 0) = -1 EFAULT (%m)\n", efault);
+
+	const unsigned int valid = 1;
+	const void *const epfds = tail_fds0 + ARRAY_SIZE(pfds0) - valid;
+	rc = syscall(__NR_poll, epfds, valid + 1, 0);
+	assert(rc == -1);
+	tprintf("poll(");
+	print_pollfd_array_entering(epfds, valid + 1, valid, abbrev);
+	errno = EFAULT;
+	tprintf(", %u, 0) = -1 EFAULT (%m)\n", valid + 1);
+
+	tprintf("+++ exited with 0 +++\n");
+	return 0;
+}
+
+#else
+
+SKIP_MAIN_UNDEFINED("__NR_poll")
+
+#endif
diff --git a/tests/poll.test b/tests/poll.test
new file mode 100755
index 0000000..869f709
--- /dev/null
+++ b/tests/poll.test
@@ -0,0 +1,18 @@
+#!/bin/sh
+
+# Check poll syscall decoding.
+
+. "${srcdir=.}/init.sh"
+
+run_prog > /dev/null
+OUT="$LOG.out"
+run_strace -a18 -vepoll $args > "$OUT"
+match_diff "$LOG" "$OUT"
+
+for abbrev in 0 1 2 3 4 5; do
+	run_prog "./${ME_%.test}" $abbrev > /dev/null
+	run_strace -a18 -epoll -s$abbrev $args > "$OUT"
+	match_diff "$LOG" "$OUT"
+done
+
+rm -f "$OUT"