aboutsummaryrefslogtreecommitdiffstatshomepage
path: root/tools/testing/selftests/powerpc/signal/sig_sc_double_restart.c
blob: e3972264615b478cff672b5ca99f918309d123d1 (plain) (blame)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
// SPDX-License-Identifier: GPL-2.0-or-later
/*
 * Test that a syscall does not get restarted twice, handled by trap_norestart()
 *
 * Based on Al's description, and a test for the bug fixed in this commit:
 *
 * commit 9a81c16b527528ad307843be5571111aa8d35a80
 * Author: Al Viro <viro@zeniv.linux.org.uk>
 * Date:   Mon Sep 20 21:48:57 2010 +0100
 *
 *  powerpc: fix double syscall restarts
 *
 *  Make sigreturn zero regs->trap, make do_signal() do the same on all
 *  paths.  As it is, signal interrupting e.g. read() from fd 512 (==
 *  ERESTARTSYS) with another signal getting unblocked when the first
 *  handler finishes will lead to restart one insn earlier than it ought
 *  to.  Same for multiple signals with in-kernel handlers interrupting
 *  that sucker at the same time.  Same for multiple signals of any kind
 *  interrupting that sucker on 64bit...
 */
#define _GNU_SOURCE
#include <sys/types.h>
#include <sys/wait.h>
#include <sys/syscall.h>
#include <unistd.h>
#include <signal.h>
#include <errno.h>
#include <stdlib.h>
#include <stdio.h>
#include <string.h>

#include "utils.h"

static void SIGUSR1_handler(int sig)
{
	kill(getpid(), SIGUSR2);
	/*
	 * SIGUSR2 is blocked until the handler exits, at which point it will
	 * be raised again and think there is a restart to be done because the
	 * pending restarted syscall has 512 (ERESTARTSYS) in r3. The second
	 * restart will retreat NIP another 4 bytes to fail case branch.
	 */
}

static void SIGUSR2_handler(int sig)
{
}

static ssize_t raw_read(int fd, void *buf, size_t count)
{
	register long nr asm("r0") = __NR_read;
	register long _fd asm("r3") = fd;
	register void *_buf asm("r4") = buf;
	register size_t _count asm("r5") = count;

	asm volatile(
"		b	0f		\n"
"		b	1f		\n"
"	0:	sc	0		\n"
"		bns	2f		\n"
"		neg	%0,%0		\n"
"		b	2f		\n"
"	1:				\n"
"		li	%0,%4		\n"
"	2:				\n"
		: "+r"(_fd), "+r"(nr), "+r"(_buf), "+r"(_count)
		: "i"(-ENOANO)
		: "memory", "r6", "r7", "r8", "r9", "r10", "r11", "r12", "ctr", "cr0");

	if (_fd < 0) {
		errno = -_fd;
		_fd = -1;
	}

	return _fd;
}

#define DATA "test 123"
#define DLEN (strlen(DATA)+1)

int test_restart(void)
{
	int pipefd[2];
	pid_t pid;
	char buf[512];

	if (pipe(pipefd) == -1) {
		perror("pipe");
		exit(EXIT_FAILURE);
	}

	pid = fork();
	if (pid == -1) {
		perror("fork");
		exit(EXIT_FAILURE);
	}

	if (pid == 0) { /* Child reads from pipe */
		struct sigaction act;
		int fd;

		memset(&act, 0, sizeof(act));
		sigaddset(&act.sa_mask, SIGUSR2);
		act.sa_handler = SIGUSR1_handler;
		act.sa_flags = SA_RESTART;
		if (sigaction(SIGUSR1, &act, NULL) == -1) {
			perror("sigaction");
			exit(EXIT_FAILURE);
		}

		memset(&act, 0, sizeof(act));
		act.sa_handler = SIGUSR2_handler;
		act.sa_flags = SA_RESTART;
		if (sigaction(SIGUSR2, &act, NULL) == -1) {
			perror("sigaction");
			exit(EXIT_FAILURE);
		}

		/* Let's get ERESTARTSYS into r3 */
		while ((fd = dup(pipefd[0])) != 512) {
			if (fd == -1) {
				perror("dup");
				exit(EXIT_FAILURE);
			}
		}

		if (raw_read(fd, buf, 512) == -1) {
			if (errno == ENOANO) {
				fprintf(stderr, "Double restart moved restart before sc instruction.\n");
				_exit(EXIT_FAILURE);
			}
			perror("read");
			exit(EXIT_FAILURE);
		}

		if (strncmp(buf, DATA, DLEN)) {
			fprintf(stderr, "bad test string %s\n", buf);
			exit(EXIT_FAILURE);
		}

		return 0;

	} else {
		int wstatus;

		usleep(100000);		/* Hack to get reader waiting */
		kill(pid, SIGUSR1);
		usleep(100000);
		if (write(pipefd[1], DATA, DLEN) != DLEN) {
			perror("write");
			exit(EXIT_FAILURE);
		}
		close(pipefd[0]);
		close(pipefd[1]);
		if (wait(&wstatus) == -1) {
			perror("wait");
			exit(EXIT_FAILURE);
		}
		if (!WIFEXITED(wstatus)) {
			fprintf(stderr, "child exited abnormally\n");
			exit(EXIT_FAILURE);
		}

		FAIL_IF(WEXITSTATUS(wstatus) != EXIT_SUCCESS);

		return 0;
	}
}

int main(void)
{
	test_harness_set_timeout(10);
	return test_harness(test_restart, "sig sys restart");
}