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