1 // SPDX-License-Identifier: GPL-2.0
2 /*
3  * Copyright (C) 2002 - 2008 Jeff Dike (jdike@{addtoit,linux.intel}.com)
4  */
5 
6 #include <unistd.h>
7 #include <errno.h>
8 #include <fcntl.h>
9 #include <poll.h>
10 #include <pty.h>
11 #include <sched.h>
12 #include <signal.h>
13 #include <string.h>
14 #include <sys/epoll.h>
15 #include <asm/unistd.h>
16 #include <kern_util.h>
17 #include <init.h>
18 #include <os.h>
19 #include <sigio.h>
20 #include <um_malloc.h>
21 
22 /*
23  * Protected by sigio_lock(), also used by sigio_cleanup, which is an
24  * exitcall.
25  */
26 static struct os_helper_thread *write_sigio_td;
27 
28 static int epollfd = -1;
29 
30 #define MAX_EPOLL_EVENTS 64
31 
32 static struct epoll_event epoll_events[MAX_EPOLL_EVENTS];
33 
34 static void *write_sigio_thread(void *unused)
35 {
36 	int pid = getpid();
37 	int r;
38 
39 	os_fix_helper_thread_signals();
40 
41 	while (1) {
42 		r = epoll_wait(epollfd, epoll_events, MAX_EPOLL_EVENTS, -1);
43 		if (r < 0) {
44 			if (errno == EINTR)
45 				continue;
46 			printk(UM_KERN_ERR "%s: epoll_wait failed, errno = %d\n",
47 			       __func__, errno);
48 		}
49 
50 		CATCH_EINTR(r = syscall(__NR_tgkill, pid, pid, SIGIO));
51 		if (r < 0)
52 			printk(UM_KERN_ERR "%s: tgkill failed, errno = %d\n",
53 			       __func__, errno);
54 	}
55 
56 	return NULL;
57 }
58 
59 int __add_sigio_fd(int fd)
60 {
61 	struct epoll_event event = {
62 		.data.fd = fd,
63 		.events = EPOLLIN | EPOLLET,
64 	};
65 	int r;
66 
67 	CATCH_EINTR(r = epoll_ctl(epollfd, EPOLL_CTL_ADD, fd, &event));
68 	return r < 0 ? -errno : 0;
69 }
70 
71 int add_sigio_fd(int fd)
72 {
73 	int err;
74 
75 	sigio_lock();
76 	err = __add_sigio_fd(fd);
77 	sigio_unlock();
78 
79 	return err;
80 }
81 
82 int __ignore_sigio_fd(int fd)
83 {
84 	struct epoll_event event;
85 	int r;
86 
87 	CATCH_EINTR(r = epoll_ctl(epollfd, EPOLL_CTL_DEL, fd, &event));
88 	return r < 0 ? -errno : 0;
89 }
90 
91 int ignore_sigio_fd(int fd)
92 {
93 	int err;
94 
95 	sigio_lock();
96 	err = __ignore_sigio_fd(fd);
97 	sigio_unlock();
98 
99 	return err;
100 }
101 
102 static void write_sigio_workaround(void)
103 {
104 	int err;
105 
106 	sigio_lock();
107 	if (write_sigio_td)
108 		goto out;
109 
110 	epollfd = epoll_create(MAX_EPOLL_EVENTS);
111 	if (epollfd < 0) {
112 		printk(UM_KERN_ERR "%s: epoll_create failed, errno = %d\n",
113 		       __func__, errno);
114 		goto out;
115 	}
116 
117 	err = os_run_helper_thread(&write_sigio_td, write_sigio_thread, NULL);
118 	if (err < 0) {
119 		printk(UM_KERN_ERR "%s: os_run_helper_thread failed, errno = %d\n",
120 		       __func__, -err);
121 		close(epollfd);
122 		epollfd = -1;
123 		goto out;
124 	}
125 
126 out:
127 	sigio_unlock();
128 }
129 
130 void sigio_broken(void)
131 {
132 	write_sigio_workaround();
133 }
134 
135 /* Changed during early boot */
136 static int pty_output_sigio;
137 
138 void maybe_sigio_broken(int fd)
139 {
140 	if (!isatty(fd))
141 		return;
142 
143 	if (pty_output_sigio)
144 		return;
145 
146 	sigio_broken();
147 }
148 
149 static void sigio_cleanup(void)
150 {
151 	if (!write_sigio_td)
152 		return;
153 
154 	os_kill_helper_thread(write_sigio_td);
155 	write_sigio_td = NULL;
156 }
157 
158 __uml_exitcall(sigio_cleanup);
159 
160 /* Used as a flag during SIGIO testing early in boot */
161 static int got_sigio;
162 
163 static void __init handler(int sig)
164 {
165 	got_sigio = 1;
166 }
167 
168 struct openpty_arg {
169 	int master;
170 	int slave;
171 	int err;
172 };
173 
174 static void openpty_cb(void *arg)
175 {
176 	struct openpty_arg *info = arg;
177 
178 	info->err = 0;
179 	if (openpty(&info->master, &info->slave, NULL, NULL, NULL))
180 		info->err = -errno;
181 }
182 
183 static int async_pty(int master, int slave)
184 {
185 	int flags;
186 
187 	flags = fcntl(master, F_GETFL);
188 	if (flags < 0)
189 		return -errno;
190 
191 	if ((fcntl(master, F_SETFL, flags | O_NONBLOCK | O_ASYNC) < 0) ||
192 	    (fcntl(master, F_SETOWN, os_getpid()) < 0))
193 		return -errno;
194 
195 	if ((fcntl(slave, F_SETFL, flags | O_NONBLOCK) < 0))
196 		return -errno;
197 
198 	return 0;
199 }
200 
201 static void __init check_one_sigio(void (*proc)(int, int))
202 {
203 	struct sigaction old, new;
204 	struct openpty_arg pty = { .master = -1, .slave = -1 };
205 	int master, slave, err;
206 
207 	initial_thread_cb(openpty_cb, &pty);
208 	if (pty.err) {
209 		printk(UM_KERN_ERR "check_one_sigio failed, errno = %d\n",
210 		       -pty.err);
211 		return;
212 	}
213 
214 	master = pty.master;
215 	slave = pty.slave;
216 
217 	if ((master == -1) || (slave == -1)) {
218 		printk(UM_KERN_ERR "check_one_sigio failed to allocate a "
219 		       "pty\n");
220 		return;
221 	}
222 
223 	/* Not now, but complain so we now where we failed. */
224 	err = raw(master);
225 	if (err < 0) {
226 		printk(UM_KERN_ERR "check_one_sigio : raw failed, errno = %d\n",
227 		      -err);
228 		return;
229 	}
230 
231 	err = async_pty(master, slave);
232 	if (err < 0) {
233 		printk(UM_KERN_ERR "check_one_sigio : sigio_async failed, "
234 		       "err = %d\n", -err);
235 		return;
236 	}
237 
238 	if (sigaction(SIGIO, NULL, &old) < 0) {
239 		printk(UM_KERN_ERR "check_one_sigio : sigaction 1 failed, "
240 		       "errno = %d\n", errno);
241 		return;
242 	}
243 
244 	new = old;
245 	new.sa_handler = handler;
246 	if (sigaction(SIGIO, &new, NULL) < 0) {
247 		printk(UM_KERN_ERR "check_one_sigio : sigaction 2 failed, "
248 		       "errno = %d\n", errno);
249 		return;
250 	}
251 
252 	got_sigio = 0;
253 	(*proc)(master, slave);
254 
255 	close(master);
256 	close(slave);
257 
258 	if (sigaction(SIGIO, &old, NULL) < 0)
259 		printk(UM_KERN_ERR "check_one_sigio : sigaction 3 failed, "
260 		       "errno = %d\n", errno);
261 }
262 
263 static void tty_output(int master, int slave)
264 {
265 	int n;
266 	char buf[512];
267 
268 	printk(UM_KERN_INFO "Checking that host ptys support output SIGIO...");
269 
270 	memset(buf, 0, sizeof(buf));
271 
272 	while (write(master, buf, sizeof(buf)) > 0) ;
273 	if (errno != EAGAIN)
274 		printk(UM_KERN_ERR "tty_output : write failed, errno = %d\n",
275 		       errno);
276 	while (((n = read(slave, buf, sizeof(buf))) > 0) &&
277 	       !({ barrier(); got_sigio; }))
278 		;
279 
280 	if (got_sigio) {
281 		printk(UM_KERN_CONT "Yes\n");
282 		pty_output_sigio = 1;
283 	} else if (n == -EAGAIN)
284 		printk(UM_KERN_CONT "No, enabling workaround\n");
285 	else
286 		printk(UM_KERN_CONT "tty_output : read failed, err = %d\n", n);
287 }
288 
289 static void __init check_sigio(void)
290 {
291 	if ((access("/dev/ptmx", R_OK) < 0) &&
292 	    (access("/dev/ptyp0", R_OK) < 0)) {
293 		printk(UM_KERN_WARNING "No pseudo-terminals available - "
294 		       "skipping pty SIGIO check\n");
295 		return;
296 	}
297 	check_one_sigio(tty_output);
298 }
299 
300 /* Here because it only does the SIGIO testing for now */
301 void __init os_check_bugs(void)
302 {
303 	check_sigio();
304 }
305