xref: /linux/arch/um/drivers/chan_user.c (revision 91f0a0c5cc5bc863888a936fbd05394c6e284466)
1 // SPDX-License-Identifier: GPL-2.0
2 /*
3  * Copyright (C) 2000 - 2007 Jeff Dike (jdike@{linux.intel,addtoit}.com)
4  */
5 
6 #include <stdlib.h>
7 #include <unistd.h>
8 #include <errno.h>
9 #include <sched.h>
10 #include <signal.h>
11 #include <termios.h>
12 #include <sys/ioctl.h>
13 #include <sys/prctl.h>
14 #include "chan_user.h"
15 #include <os.h>
16 #include <um_malloc.h>
17 
18 void generic_close(int fd, void *unused)
19 {
20 	close(fd);
21 }
22 
23 int generic_read(int fd, __u8 *c_out, void *unused)
24 {
25 	int n;
26 
27 	CATCH_EINTR(n = read(fd, c_out, sizeof(*c_out)));
28 	if (n > 0)
29 		return n;
30 	else if (n == 0)
31 		return -EIO;
32 	else if (errno == EAGAIN)
33 		return 0;
34 	return -errno;
35 }
36 
37 /* XXX Trivial wrapper around write */
38 
39 int generic_write(int fd, const __u8 *buf, size_t n, void *unused)
40 {
41 	int written = 0;
42 	int err;
43 
44 	/* The FD may be in blocking mode, as such, need to retry short writes,
45 	 * they may have been interrupted by a signal.
46 	 */
47 	do {
48 		errno = 0;
49 		err = write(fd, buf + written, n - written);
50 		if (err > 0) {
51 			written += err;
52 			continue;
53 		}
54 	} while (err < 0 && errno == EINTR);
55 
56 	if (written > 0)
57 		return written;
58 	else if (errno == EAGAIN)
59 		return 0;
60 	else if (err == 0)
61 		return -EIO;
62 	return -errno;
63 }
64 
65 int generic_window_size(int fd, void *unused, unsigned short *rows_out,
66 			unsigned short *cols_out)
67 {
68 	struct winsize size;
69 	int ret;
70 
71 	if (ioctl(fd, TIOCGWINSZ, &size) < 0)
72 		return -errno;
73 
74 	ret = ((*rows_out != size.ws_row) || (*cols_out != size.ws_col));
75 
76 	*rows_out = size.ws_row;
77 	*cols_out = size.ws_col;
78 
79 	return ret;
80 }
81 
82 void generic_free(void *data)
83 {
84 	kfree(data);
85 }
86 
87 int generic_console_write(int fd, const char *buf, int n)
88 {
89 	sigset_t old, no_sigio;
90 	struct termios save, new;
91 	int err;
92 
93 	if (isatty(fd)) {
94 		sigemptyset(&no_sigio);
95 		sigaddset(&no_sigio, SIGIO);
96 		if (sigprocmask(SIG_BLOCK, &no_sigio, &old))
97 			goto error;
98 
99 		CATCH_EINTR(err = tcgetattr(fd, &save));
100 		if (err)
101 			goto error;
102 		new = save;
103 		/*
104 		 * The terminal becomes a bit less raw, to handle \n also as
105 		 * "Carriage Return", not only as "New Line". Otherwise, the new
106 		 * line won't start at the first column.
107 		 */
108 		new.c_oflag |= OPOST;
109 		CATCH_EINTR(err = tcsetattr(fd, TCSAFLUSH, &new));
110 		if (err)
111 			goto error;
112 	}
113 	err = generic_write(fd, buf, n, NULL);
114 	/*
115 	 * Restore raw mode, in any case; we *must* ignore any error apart
116 	 * EINTR, except for debug.
117 	 */
118 	if (isatty(fd)) {
119 		CATCH_EINTR(tcsetattr(fd, TCSAFLUSH, &save));
120 		sigprocmask(SIG_SETMASK, &old, NULL);
121 	}
122 
123 	return err;
124 error:
125 	return -errno;
126 }
127 
128 /*
129  * UML SIGWINCH handling
130  *
131  * The point of this is to handle SIGWINCH on consoles which have host
132  * ttys and relay them inside UML to whatever might be running on the
133  * console and cares about the window size (since SIGWINCH notifies
134  * about terminal size changes).
135  *
136  * So, we have a separate thread for each host tty attached to a UML
137  * device (side-issue - I'm annoyed that one thread can't have
138  * multiple controlling ttys for the purpose of handling SIGWINCH, but
139  * I imagine there are other reasons that doesn't make any sense).
140  *
141  * SIGWINCH can't be received synchronously, so you have to set up to
142  * receive it as a signal.  That being the case, if you are going to
143  * wait for it, it is convenient to sit in sigsuspend() and wait for
144  * the signal to bounce you out of it (see below for how we make sure
145  * to exit only on SIGWINCH).
146  */
147 
148 static void winch_handler(int sig)
149 {
150 }
151 
152 struct winch_data {
153 	int pty_fd;
154 	int pipe_fd;
155 };
156 
157 static __noreturn int winch_thread(void *arg)
158 {
159 	struct winch_data *data = arg;
160 	sigset_t sigs;
161 	int pty_fd, pipe_fd;
162 	int count;
163 	char c = 1;
164 
165 	prctl(PR_SET_PDEATHSIG, SIGKILL);
166 
167 	pty_fd = data->pty_fd;
168 	pipe_fd = data->pipe_fd;
169 	count = write(pipe_fd, &c, sizeof(c));
170 	if (count != sizeof(c))
171 		os_info("winch_thread : failed to write synchronization byte, err = %d\n",
172 			-count);
173 
174 	/*
175 	 * We are not using SIG_IGN on purpose, so don't fix it as I thought to
176 	 * do! If using SIG_IGN, the sigsuspend() call below would not stop on
177 	 * SIGWINCH.
178 	 */
179 
180 	signal(SIGWINCH, winch_handler);
181 	sigfillset(&sigs);
182 	/* Block all signals possible. */
183 	if (sigprocmask(SIG_SETMASK, &sigs, NULL) < 0) {
184 		os_info("winch_thread : sigprocmask failed, errno = %d\n",
185 			errno);
186 		goto wait_kill;
187 	}
188 	/* In sigsuspend(), block anything else than SIGWINCH. */
189 	sigdelset(&sigs, SIGWINCH);
190 
191 	if (setsid() < 0) {
192 		os_info("winch_thread : setsid failed, errno = %d\n",
193 		       errno);
194 		goto wait_kill;
195 	}
196 
197 	if (ioctl(pty_fd, TIOCSCTTY, 0) < 0) {
198 		os_info("winch_thread : TIOCSCTTY failed on "
199 			"fd %d err = %d\n", pty_fd, errno);
200 		goto wait_kill;
201 	}
202 
203 	if (tcsetpgrp(pty_fd, os_getpid()) < 0) {
204 		os_info("winch_thread : tcsetpgrp failed on fd %d err = %d\n",
205 			pty_fd, errno);
206 		goto wait_kill;
207 	}
208 
209 	/*
210 	 * These are synchronization calls between various UML threads on the
211 	 * host - since they are not different kernel threads, we cannot use
212 	 * kernel semaphores. We don't use SysV semaphores because they are
213 	 * persistent.
214 	 */
215 	count = read(pipe_fd, &c, sizeof(c));
216 	if (count != sizeof(c))
217 		os_info("winch_thread : failed to read synchronization byte, err = %d\n",
218 			errno);
219 
220 	while(1) {
221 		/*
222 		 * This will be interrupted by SIGWINCH only, since
223 		 * other signals are blocked.
224 		 */
225 		sigsuspend(&sigs);
226 
227 		count = write(pipe_fd, &c, sizeof(c));
228 		if (count != sizeof(c))
229 			os_info("winch_thread : write failed, err = %d\n",
230 				errno);
231 	}
232 
233 wait_kill:
234 	c = 2;
235 	count = write(pipe_fd, &c, sizeof(c));
236 	while (1)
237 		pause();
238 }
239 
240 static int winch_tramp(int fd, struct tty_port *port, int *fd_out,
241 		       unsigned long *stack_out)
242 {
243 	struct winch_data data;
244 	int fds[2], n, err, pid;
245 	char c;
246 
247 	err = os_pipe(fds, 1, 1);
248 	if (err < 0) {
249 		printk(UM_KERN_ERR "winch_tramp : os_pipe failed, err = %d\n",
250 		       -err);
251 		goto out;
252 	}
253 
254 	data = ((struct winch_data) { .pty_fd 		= fd,
255 				      .pipe_fd 		= fds[1] } );
256 	/*
257 	 * CLONE_FILES so this thread doesn't hold open files which are open
258 	 * now, but later closed in a different thread.  This is a
259 	 * problem with /dev/net/tun, which if held open by this
260 	 * thread, prevents the TUN/TAP device from being reused.
261 	 */
262 	pid = run_helper_thread(winch_thread, &data, CLONE_FILES, stack_out);
263 	if (pid < 0) {
264 		err = pid;
265 		printk(UM_KERN_ERR "fork of winch_thread failed - errno = %d\n",
266 		       -err);
267 		goto out_close;
268 	}
269 
270 	*fd_out = fds[0];
271 	n = read(fds[0], &c, sizeof(c));
272 	if (n != sizeof(c)) {
273 		printk(UM_KERN_ERR "winch_tramp : failed to read "
274 		       "synchronization byte\n");
275 		printk(UM_KERN_ERR "read failed, err = %d\n", errno);
276 		printk(UM_KERN_ERR "fd %d will not support SIGWINCH\n", fd);
277 		err = -EINVAL;
278 		goto out_close;
279 	}
280 
281 	err = os_set_fd_block(*fd_out, 0);
282 	if (err) {
283 		printk(UM_KERN_ERR "winch_tramp: failed to set thread_fd "
284 		       "non-blocking.\n");
285 		goto out_close;
286 	}
287 
288 	return pid;
289 
290  out_close:
291 	close(fds[1]);
292 	close(fds[0]);
293  out:
294 	return err;
295 }
296 
297 void register_winch(int fd, struct tty_port *port)
298 {
299 	unsigned long stack;
300 	int pid, thread, count, thread_fd = -1;
301 	char c = 1;
302 
303 	if (!isatty(fd))
304 		return;
305 
306 	pid = tcgetpgrp(fd);
307 	if (is_skas_winch(pid, fd, port)) {
308 		register_winch_irq(-1, fd, -1, port, 0);
309 		return;
310 	}
311 
312 	if (pid == -1) {
313 		thread = winch_tramp(fd, port, &thread_fd, &stack);
314 		if (thread < 0)
315 			return;
316 
317 		register_winch_irq(thread_fd, fd, thread, port, stack);
318 
319 		count = write(thread_fd, &c, sizeof(c));
320 		if (count != sizeof(c))
321 			printk(UM_KERN_ERR "register_winch : failed to write "
322 			       "synchronization byte, err = %d\n", errno);
323 	}
324 }
325