xref: /openbmc/linux/drivers/tty/vt/selection.c (revision 9256d09f)
1 // SPDX-License-Identifier: GPL-2.0
2 /*
3  * This module exports the functions:
4  *
5  *     'int set_selection_user(struct tiocl_selection __user *,
6  *			       struct tty_struct *)'
7  *     'int set_selection_kernel(struct tiocl_selection *, struct tty_struct *)'
8  *     'void clear_selection(void)'
9  *     'int paste_selection(struct tty_struct *)'
10  *     'int sel_loadlut(char __user *)'
11  *
12  * Now that /dev/vcs exists, most of this can disappear again.
13  */
14 
15 #include <linux/module.h>
16 #include <linux/tty.h>
17 #include <linux/sched.h>
18 #include <linux/mm.h>
19 #include <linux/mutex.h>
20 #include <linux/slab.h>
21 #include <linux/types.h>
22 
23 #include <linux/uaccess.h>
24 
25 #include <linux/kbd_kern.h>
26 #include <linux/vt_kern.h>
27 #include <linux/consolemap.h>
28 #include <linux/selection.h>
29 #include <linux/tiocl.h>
30 #include <linux/console.h>
31 #include <linux/tty_flip.h>
32 
33 #include <linux/sched/signal.h>
34 
35 /* Don't take this from <ctype.h>: 011-015 on the screen aren't spaces */
36 #define isspace(c)	((c) == ' ')
37 
38 extern void poke_blanked_console(void);
39 
40 /* FIXME: all this needs locking */
41 static struct vc_selection {
42 	struct mutex lock;
43 	struct vc_data *cons;			/* must not be deallocated */
44 	char *buffer;
45 	unsigned int buf_len;
46 	volatile int start;			/* cleared by clear_selection */
47 	int end;
48 } vc_sel = {
49 	.lock = __MUTEX_INITIALIZER(vc_sel.lock),
50 	.start = -1,
51 };
52 
53 /* clear_selection, highlight and highlight_pointer can be called
54    from interrupt (via scrollback/front) */
55 
56 /* set reverse video on characters s-e of console with selection. */
57 static inline void highlight(const int s, const int e)
58 {
59 	invert_screen(vc_sel.cons, s, e-s+2, 1);
60 }
61 
62 /* use complementary color to show the pointer */
63 static inline void highlight_pointer(const int where)
64 {
65 	complement_pos(vc_sel.cons, where);
66 }
67 
68 static u32
69 sel_pos(int n, bool unicode)
70 {
71 	if (unicode)
72 		return screen_glyph_unicode(vc_sel.cons, n / 2);
73 	return inverse_translate(vc_sel.cons, screen_glyph(vc_sel.cons, n), 0);
74 }
75 
76 /**
77  *	clear_selection		-	remove current selection
78  *
79  *	Remove the current selection highlight, if any from the console
80  *	holding the selection. The caller must hold the console lock.
81  */
82 void clear_selection(void)
83 {
84 	highlight_pointer(-1); /* hide the pointer */
85 	if (vc_sel.start != -1) {
86 		highlight(vc_sel.start, vc_sel.end);
87 		vc_sel.start = -1;
88 	}
89 }
90 EXPORT_SYMBOL_GPL(clear_selection);
91 
92 bool vc_is_sel(struct vc_data *vc)
93 {
94 	return vc == vc_sel.cons;
95 }
96 
97 /*
98  * User settable table: what characters are to be considered alphabetic?
99  * 128 bits. Locked by the console lock.
100  */
101 static u32 inwordLut[]={
102   0x00000000, /* control chars     */
103   0x03FFE000, /* digits and "-./"  */
104   0x87FFFFFE, /* uppercase and '_' */
105   0x07FFFFFE, /* lowercase         */
106 };
107 
108 static inline int inword(const u32 c)
109 {
110 	return c > 0x7f || (( inwordLut[c>>5] >> (c & 0x1F) ) & 1);
111 }
112 
113 /**
114  *	set loadlut		-	load the LUT table
115  *	@p: user table
116  *
117  *	Load the LUT table from user space. The caller must hold the console
118  *	lock. Make a temporary copy so a partial update doesn't make a mess.
119  */
120 int sel_loadlut(char __user *p)
121 {
122 	u32 tmplut[ARRAY_SIZE(inwordLut)];
123 	if (copy_from_user(tmplut, (u32 __user *)(p+4), sizeof(inwordLut)))
124 		return -EFAULT;
125 	memcpy(inwordLut, tmplut, sizeof(inwordLut));
126 	return 0;
127 }
128 
129 /* does screen address p correspond to character at LH/RH edge of screen? */
130 static inline int atedge(const int p, int size_row)
131 {
132 	return (!(p % size_row)	|| !((p + 2) % size_row));
133 }
134 
135 /* stores the char in UTF8 and returns the number of bytes used (1-4) */
136 static int store_utf8(u32 c, char *p)
137 {
138 	if (c < 0x80) {
139 		/*  0******* */
140 		p[0] = c;
141 		return 1;
142 	} else if (c < 0x800) {
143 		/* 110***** 10****** */
144 		p[0] = 0xc0 | (c >> 6);
145 		p[1] = 0x80 | (c & 0x3f);
146 		return 2;
147 	} else if (c < 0x10000) {
148 		/* 1110**** 10****** 10****** */
149 		p[0] = 0xe0 | (c >> 12);
150 		p[1] = 0x80 | ((c >> 6) & 0x3f);
151 		p[2] = 0x80 | (c & 0x3f);
152 		return 3;
153 	} else if (c < 0x110000) {
154 		/* 11110*** 10****** 10****** 10****** */
155 		p[0] = 0xf0 | (c >> 18);
156 		p[1] = 0x80 | ((c >> 12) & 0x3f);
157 		p[2] = 0x80 | ((c >> 6) & 0x3f);
158 		p[3] = 0x80 | (c & 0x3f);
159 		return 4;
160 	} else {
161 		/* outside Unicode, replace with U+FFFD */
162 		p[0] = 0xef;
163 		p[1] = 0xbf;
164 		p[2] = 0xbd;
165 		return 3;
166 	}
167 }
168 
169 /**
170  *	set_selection_user	-	set the current selection.
171  *	@sel: user selection info
172  *	@tty: the console tty
173  *
174  *	Invoked by the ioctl handle for the vt layer.
175  *
176  *	The entire selection process is managed under the console_lock. It's
177  *	 a lot under the lock but its hardly a performance path
178  */
179 int set_selection_user(const struct tiocl_selection __user *sel,
180 		       struct tty_struct *tty)
181 {
182 	struct tiocl_selection v;
183 
184 	if (copy_from_user(&v, sel, sizeof(*sel)))
185 		return -EFAULT;
186 
187 	return set_selection_kernel(&v, tty);
188 }
189 
190 int set_selection_kernel(struct tiocl_selection *v, struct tty_struct *tty)
191 {
192 	struct vc_data *vc = vc_cons[fg_console].d;
193 	int new_sel_start, new_sel_end, spc;
194 	char *bp, *obp;
195 	int i, ps, pe;
196 	u32 c;
197 	int ret = 0;
198 	bool unicode;
199 
200 	poke_blanked_console();
201 
202 	v->xs = min_t(u16, v->xs - 1, vc->vc_cols - 1);
203 	v->ys = min_t(u16, v->ys - 1, vc->vc_rows - 1);
204 	v->xe = min_t(u16, v->xe - 1, vc->vc_cols - 1);
205 	v->ye = min_t(u16, v->ye - 1, vc->vc_rows - 1);
206 	ps = v->ys * vc->vc_size_row + (v->xs << 1);
207 	pe = v->ye * vc->vc_size_row + (v->xe << 1);
208 
209 	if (v->sel_mode == TIOCL_SELCLEAR) {
210 		/* useful for screendump without selection highlights */
211 		clear_selection();
212 		return 0;
213 	}
214 
215 	if (mouse_reporting() && (v->sel_mode & TIOCL_SELMOUSEREPORT)) {
216 		mouse_report(tty, v->sel_mode & TIOCL_SELBUTTONMASK, v->xs,
217 			     v->ys);
218 		return 0;
219 	}
220 
221 	if (ps > pe)	/* make vc_sel.start <= vc_sel.end */
222 		swap(ps, pe);
223 
224 	mutex_lock(&vc_sel.lock);
225 	if (vc_sel.cons != vc_cons[fg_console].d) {
226 		clear_selection();
227 		vc_sel.cons = vc_cons[fg_console].d;
228 	}
229 	unicode = vt_do_kdgkbmode(fg_console) == K_UNICODE;
230 
231 	switch (v->sel_mode)
232 	{
233 		case TIOCL_SELCHAR:	/* character-by-character selection */
234 			new_sel_start = ps;
235 			new_sel_end = pe;
236 			break;
237 		case TIOCL_SELWORD:	/* word-by-word selection */
238 			spc = isspace(sel_pos(ps, unicode));
239 			for (new_sel_start = ps; ; ps -= 2)
240 			{
241 				if ((spc && !isspace(sel_pos(ps, unicode))) ||
242 				    (!spc && !inword(sel_pos(ps, unicode))))
243 					break;
244 				new_sel_start = ps;
245 				if (!(ps % vc->vc_size_row))
246 					break;
247 			}
248 			spc = isspace(sel_pos(pe, unicode));
249 			for (new_sel_end = pe; ; pe += 2)
250 			{
251 				if ((spc && !isspace(sel_pos(pe, unicode))) ||
252 				    (!spc && !inword(sel_pos(pe, unicode))))
253 					break;
254 				new_sel_end = pe;
255 				if (!((pe + 2) % vc->vc_size_row))
256 					break;
257 			}
258 			break;
259 		case TIOCL_SELLINE:	/* line-by-line selection */
260 			new_sel_start = ps - ps % vc->vc_size_row;
261 			new_sel_end = pe + vc->vc_size_row
262 				    - pe % vc->vc_size_row - 2;
263 			break;
264 		case TIOCL_SELPOINTER:
265 			highlight_pointer(pe);
266 			goto unlock;
267 		default:
268 			ret = -EINVAL;
269 			goto unlock;
270 	}
271 
272 	/* remove the pointer */
273 	highlight_pointer(-1);
274 
275 	/* select to end of line if on trailing space */
276 	if (new_sel_end > new_sel_start &&
277 		!atedge(new_sel_end, vc->vc_size_row) &&
278 		isspace(sel_pos(new_sel_end, unicode))) {
279 		for (pe = new_sel_end + 2; ; pe += 2)
280 			if (!isspace(sel_pos(pe, unicode)) ||
281 			    atedge(pe, vc->vc_size_row))
282 				break;
283 		if (isspace(sel_pos(pe, unicode)))
284 			new_sel_end = pe;
285 	}
286 	if (vc_sel.start == -1)	/* no current selection */
287 		highlight(new_sel_start, new_sel_end);
288 	else if (new_sel_start == vc_sel.start)
289 	{
290 		if (new_sel_end == vc_sel.end)	/* no action required */
291 			goto unlock;
292 		else if (new_sel_end > vc_sel.end)	/* extend to right */
293 			highlight(vc_sel.end + 2, new_sel_end);
294 		else				/* contract from right */
295 			highlight(new_sel_end + 2, vc_sel.end);
296 	}
297 	else if (new_sel_end == vc_sel.end)
298 	{
299 		if (new_sel_start < vc_sel.start) /* extend to left */
300 			highlight(new_sel_start, vc_sel.start - 2);
301 		else				/* contract from left */
302 			highlight(vc_sel.start, new_sel_start - 2);
303 	}
304 	else	/* some other case; start selection from scratch */
305 	{
306 		clear_selection();
307 		highlight(new_sel_start, new_sel_end);
308 	}
309 	vc_sel.start = new_sel_start;
310 	vc_sel.end = new_sel_end;
311 
312 	/* Allocate a new buffer before freeing the old one ... */
313 	/* chars can take up to 4 bytes with unicode */
314 	bp = kmalloc_array((vc_sel.end - vc_sel.start) / 2 + 1, unicode ? 4 : 1,
315 			   GFP_KERNEL);
316 	if (!bp) {
317 		printk(KERN_WARNING "selection: kmalloc() failed\n");
318 		clear_selection();
319 		ret = -ENOMEM;
320 		goto unlock;
321 	}
322 	kfree(vc_sel.buffer);
323 	vc_sel.buffer = bp;
324 
325 	obp = bp;
326 	for (i = vc_sel.start; i <= vc_sel.end; i += 2) {
327 		c = sel_pos(i, unicode);
328 		if (unicode)
329 			bp += store_utf8(c, bp);
330 		else
331 			*bp++ = c;
332 		if (!isspace(c))
333 			obp = bp;
334 		if (! ((i + 2) % vc->vc_size_row)) {
335 			/* strip trailing blanks from line and add newline,
336 			   unless non-space at end of line. */
337 			if (obp != bp) {
338 				bp = obp;
339 				*bp++ = '\r';
340 			}
341 			obp = bp;
342 		}
343 	}
344 	vc_sel.buf_len = bp - vc_sel.buffer;
345 unlock:
346 	mutex_unlock(&vc_sel.lock);
347 	return ret;
348 }
349 EXPORT_SYMBOL_GPL(set_selection_kernel);
350 
351 /* Insert the contents of the selection buffer into the
352  * queue of the tty associated with the current console.
353  * Invoked by ioctl().
354  *
355  * Locking: called without locks. Calls the ldisc wrongly with
356  * unsafe methods,
357  */
358 int paste_selection(struct tty_struct *tty)
359 {
360 	struct vc_data *vc = tty->driver_data;
361 	int	pasted = 0;
362 	unsigned int count;
363 	struct  tty_ldisc *ld;
364 	DECLARE_WAITQUEUE(wait, current);
365 	int ret = 0;
366 
367 	console_lock();
368 	poke_blanked_console();
369 	console_unlock();
370 
371 	ld = tty_ldisc_ref_wait(tty);
372 	if (!ld)
373 		return -EIO;	/* ldisc was hung up */
374 	tty_buffer_lock_exclusive(&vc->port);
375 
376 	add_wait_queue(&vc->paste_wait, &wait);
377 	mutex_lock(&vc_sel.lock);
378 	while (vc_sel.buffer && vc_sel.buf_len > pasted) {
379 		set_current_state(TASK_INTERRUPTIBLE);
380 		if (signal_pending(current)) {
381 			ret = -EINTR;
382 			break;
383 		}
384 		if (tty_throttled(tty)) {
385 			mutex_unlock(&vc_sel.lock);
386 			schedule();
387 			mutex_lock(&vc_sel.lock);
388 			continue;
389 		}
390 		__set_current_state(TASK_RUNNING);
391 		count = vc_sel.buf_len - pasted;
392 		count = tty_ldisc_receive_buf(ld, vc_sel.buffer + pasted, NULL,
393 					      count);
394 		pasted += count;
395 	}
396 	mutex_unlock(&vc_sel.lock);
397 	remove_wait_queue(&vc->paste_wait, &wait);
398 	__set_current_state(TASK_RUNNING);
399 
400 	tty_buffer_unlock_exclusive(&vc->port);
401 	tty_ldisc_deref(ld);
402 	return ret;
403 }
404 EXPORT_SYMBOL_GPL(paste_selection);
405