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