File Index
Files and directories
Sign in
FILE

vc_screen.c

File preview with syntax highlighting.

linux-6.19.14/drivers/tty/vt/vc_screen.c C
  1// SPDX-License-Identifier: GPL-2.0
  2/*
  3 * Provide access to virtual console memory.
  4 * /dev/vcs: the screen as it is being viewed right now (possibly scrolled)
  5 * /dev/vcsN: the screen of /dev/ttyN (1 <= N <= 63)
  6 *            [minor: N]
  7 *
  8 * /dev/vcsaN: idem, but including attributes, and prefixed with
  9 *	the 4 bytes lines,columns,x,y (as screendump used to give).
 10 *	Attribute/character pair is in native endianity.
 11 *            [minor: N+128]
 12 *
 13 * /dev/vcsuN: similar to /dev/vcsaN but using 4-byte unicode values
 14 *	instead of 1-byte screen glyph values.
 15 *            [minor: N+64]
 16 *
 17 * /dev/vcsuaN: same idea as /dev/vcsaN for unicode (not yet implemented).
 18 *
 19 * This replaces screendump and part of selection, so that the system
 20 * administrator can control access using file system permissions.
 21 *
 22 * aeb@cwi.nl - efter Friedas begravelse - 950211
 23 *
 24 * machek@k332.feld.cvut.cz - modified not to send characters to wrong console
 25 *	 - fixed some fatal off-by-one bugs (0-- no longer == -1 -> looping and looping and looping...)
 26 *	 - making it shorter - scr_readw are macros which expand in PRETTY long code
 27 */
 28
 29#include <linux/kernel.h>
 30#include <linux/major.h>
 31#include <linux/errno.h>
 32#include <linux/export.h>
 33#include <linux/tty.h>
 34#include <linux/interrupt.h>
 35#include <linux/mm.h>
 36#include <linux/init.h>
 37#include <linux/vt_kern.h>
 38#include <linux/selection.h>
 39#include <linux/kbd_kern.h>
 40#include <linux/console.h>
 41#include <linux/device.h>
 42#include <linux/sched.h>
 43#include <linux/fs.h>
 44#include <linux/poll.h>
 45#include <linux/signal.h>
 46#include <linux/slab.h>
 47#include <linux/notifier.h>
 48
 49#include <linux/uaccess.h>
 50#include <asm/byteorder.h>
 51#include <linux/unaligned.h>
 52
 53#define HEADER_SIZE	4u
 54#define CON_BUF_SIZE (IS_ENABLED(CONFIG_BASE_SMALL) ? 256 : PAGE_SIZE)
 55
 56DEFINE_FREE(free_page_ptr, void *, if (_T) free_page((unsigned long)_T));
 57
 58/*
 59 * Our minor space:
 60 *
 61 *   0 ... 63	glyph mode without attributes
 62 *  64 ... 127	unicode mode without attributes
 63 * 128 ... 191	glyph mode with attributes
 64 * 192 ... 255	unused (reserved for unicode with attributes)
 65 *
 66 * This relies on MAX_NR_CONSOLES being  <= 63, meaning 63 actual consoles
 67 * with minors 0, 64, 128 and 192 being proxies for the foreground console.
 68 */
 69#if MAX_NR_CONSOLES > 63
 70#warning "/dev/vcs* devices may not accommodate more than 63 consoles"
 71#endif
 72
 73#define console(inode)		(iminor(inode) & 63)
 74#define use_unicode(inode)	(iminor(inode) & 64)
 75#define use_attributes(inode)	(iminor(inode) & 128)
 76
 77struct vcs_poll_data {
 78	struct notifier_block notifier;
 79	unsigned int cons_num;
 80	int event;
 81	wait_queue_head_t waitq;
 82	struct fasync_struct *fasync;
 83};
 84
 85static int
 86vcs_notifier(struct notifier_block *nb, unsigned long code, void *_param)
 87{
 88	struct vt_notifier_param *param = _param;
 89	struct vc_data *vc = param->vc;
 90	struct vcs_poll_data *poll =
 91		container_of(nb, struct vcs_poll_data, notifier);
 92	int currcons = poll->cons_num;
 93	int fa_band;
 94
 95	switch (code) {
 96	case VT_UPDATE:
 97		fa_band = POLL_PRI;
 98		break;
 99	case VT_DEALLOCATE:
100		fa_band = POLL_HUP;
101		break;
102	default:
103		return NOTIFY_DONE;
104	}
105
106	if (currcons == 0)
107		currcons = fg_console;
108	else
109		currcons--;
110	if (currcons != vc->vc_num)
111		return NOTIFY_DONE;
112
113	poll->event = code;
114	wake_up_interruptible(&poll->waitq);
115	kill_fasync(&poll->fasync, SIGIO, fa_band);
116	return NOTIFY_OK;
117}
118
119static void
120vcs_poll_data_free(struct vcs_poll_data *poll)
121{
122	unregister_vt_notifier(&poll->notifier);
123	kfree(poll);
124}
125
126static struct vcs_poll_data *
127vcs_poll_data_get(struct file *file)
128{
129	struct vcs_poll_data *poll = file->private_data, *kill = NULL;
130
131	if (poll)
132		return poll;
133
134	poll = kzalloc(sizeof(*poll), GFP_KERNEL);
135	if (!poll)
136		return NULL;
137	poll->cons_num = console(file_inode(file));
138	init_waitqueue_head(&poll->waitq);
139	poll->notifier.notifier_call = vcs_notifier;
140	/*
141	 * In order not to lose any update event, we must pretend one might
142	 * have occurred before we have a chance to register our notifier.
143	 * This is also how user space has come to detect which kernels
144	 * support POLLPRI on /dev/vcs* devices i.e. using poll() with
145	 * POLLPRI and a zero timeout.
146	 */
147	poll->event = VT_UPDATE;
148
149	if (register_vt_notifier(&poll->notifier) != 0) {
150		kfree(poll);
151		return NULL;
152	}
153
154	/*
155	 * This code may be called either through ->poll() or ->fasync().
156	 * If we have two threads using the same file descriptor, they could
157	 * both enter this function, both notice that the structure hasn't
158	 * been allocated yet and go ahead allocating it in parallel, but
159	 * only one of them must survive and be shared otherwise we'd leak
160	 * memory with a dangling notifier callback.
161	 */
162	spin_lock(&file->f_lock);
163	if (!file->private_data) {
164		file->private_data = poll;
165	} else {
166		/* someone else raced ahead of us */
167		kill = poll;
168		poll = file->private_data;
169	}
170	spin_unlock(&file->f_lock);
171	if (kill)
172		vcs_poll_data_free(kill);
173
174	return poll;
175}
176
177/**
178 * vcs_vc - return VC for @inode
179 * @inode: inode for which to return a VC
180 * @viewed: returns whether this console is currently foreground (viewed)
181 *
182 * Must be called with console_lock.
183 */
184static struct vc_data *vcs_vc(struct inode *inode, bool *viewed)
185{
186	unsigned int currcons = console(inode);
187
188	WARN_CONSOLE_UNLOCKED();
189
190	if (currcons == 0) {
191		currcons = fg_console;
192		if (viewed)
193			*viewed = true;
194	} else {
195		currcons--;
196		if (viewed)
197			*viewed = false;
198	}
199	return vc_cons[currcons].d;
200}
201
202/**
203 * vcs_size - return size for a VC in @vc
204 * @vc: which VC
205 * @attr: does it use attributes?
206 * @unicode: is it unicode?
207 *
208 * Must be called with console_lock.
209 */
210static int vcs_size(const struct vc_data *vc, bool attr, bool unicode)
211{
212	int size;
213
214	WARN_CONSOLE_UNLOCKED();
215
216	size = vc->vc_rows * vc->vc_cols;
217
218	if (attr) {
219		if (unicode)
220			return -EOPNOTSUPP;
221
222		size = 2 * size + HEADER_SIZE;
223	} else if (unicode)
224		size *= 4;
225
226	return size;
227}
228
229static loff_t vcs_lseek(struct file *file, loff_t offset, int orig)
230{
231	struct inode *inode = file_inode(file);
232	struct vc_data *vc;
233	int size;
234
235	scoped_guard(console_lock) {
236		vc = vcs_vc(inode, NULL);
237		if (!vc)
238			return -ENXIO;
239
240		size = vcs_size(vc, use_attributes(inode), use_unicode(inode));
241	}
242	if (size < 0)
243		return size;
244	return fixed_size_llseek(file, offset, orig, size);
245}
246
247static int vcs_read_buf_uni(struct vc_data *vc, char *con_buf,
248		unsigned int pos, unsigned int count, bool viewed)
249{
250	unsigned int nr, row, col, maxcol = vc->vc_cols;
251	int ret;
252
253	ret = vc_uniscr_check(vc);
254	if (ret)
255		return ret;
256
257	pos /= 4;
258	row = pos / maxcol;
259	col = pos % maxcol;
260	nr = maxcol - col;
261	do {
262		if (nr > count / 4)
263			nr = count / 4;
264		vc_uniscr_copy_line(vc, con_buf, viewed, row, col, nr);
265		con_buf += nr * 4;
266		count -= nr * 4;
267		row++;
268		col = 0;
269		nr = maxcol;
270	} while (count);
271
272	return 0;
273}
274
275static void vcs_read_buf_noattr(const struct vc_data *vc, char *con_buf,
276		unsigned int pos, unsigned int count, bool viewed)
277{
278	u16 *org;
279	unsigned int col, maxcol = vc->vc_cols;
280
281	org = screen_pos(vc, pos, viewed);
282	col = pos % maxcol;
283	pos += maxcol - col;
284
285	while (count-- > 0) {
286		*con_buf++ = (vcs_scr_readw(vc, org++) & 0xff);
287		if (++col == maxcol) {
288			org = screen_pos(vc, pos, viewed);
289			col = 0;
290			pos += maxcol;
291		}
292	}
293}
294
295static unsigned int vcs_read_buf(const struct vc_data *vc, char *con_buf,
296		unsigned int pos, unsigned int count, bool viewed,
297		unsigned int *skip)
298{
299	u16 *org, *con_buf16;
300	unsigned int col, maxcol = vc->vc_cols;
301	unsigned int filled = count;
302
303	if (pos < HEADER_SIZE) {
304		/* clamp header values if they don't fit */
305		con_buf[0] = min(vc->vc_rows, 0xFFu);
306		con_buf[1] = min(vc->vc_cols, 0xFFu);
307		getconsxy(vc, con_buf + 2);
308
309		*skip += pos;
310		count += pos;
311		if (count > CON_BUF_SIZE) {
312			count = CON_BUF_SIZE;
313			filled = count - pos;
314		}
315
316		/* Advance state pointers and move on. */
317		count -= min(HEADER_SIZE, count);
318		pos = HEADER_SIZE;
319		con_buf += HEADER_SIZE;
320		/* If count >= 0, then pos is even... */
321	} else if (pos & 1) {
322		/*
323		 * Skip first byte for output if start address is odd. Update
324		 * region sizes up/down depending on free space in buffer.
325		 */
326		(*skip)++;
327		if (count < CON_BUF_SIZE)
328			count++;
329		else
330			filled--;
331	}
332
333	if (!count)
334		return filled;
335
336	pos -= HEADER_SIZE;
337	pos /= 2;
338	col = pos % maxcol;
339
340	org = screen_pos(vc, pos, viewed);
341	pos += maxcol - col;
342
343	/*
344	 * Buffer has even length, so we can always copy character + attribute.
345	 * We do not copy last byte to userspace if count is odd.
346	 */
347	count = (count + 1) / 2;
348	con_buf16 = (u16 *)con_buf;
349
350	while (count) {
351		*con_buf16++ = vcs_scr_readw(vc, org++);
352		count--;
353		if (++col == maxcol) {
354			org = screen_pos(vc, pos, viewed);
355			col = 0;
356			pos += maxcol;
357		}
358	}
359
360	return filled;
361}
362
363static ssize_t
364vcs_read(struct file *file, char __user *buf, size_t count, loff_t *ppos)
365{
366	struct inode *inode = file_inode(file);
367	struct vc_data *vc;
368	struct vcs_poll_data *poll;
369	unsigned int read;
370	ssize_t ret;
371	loff_t pos;
372	bool viewed, attr, uni_mode;
373
374	char *con_buf __free(free_page_ptr) = (char *)__get_free_page(GFP_KERNEL);
375	if (!con_buf)
376		return -ENOMEM;
377
378	pos = *ppos;
379
380	/* Select the proper current console and verify
381	 * sanity of the situation under the console lock.
382	 */
383	guard(console_lock)();
384
385	uni_mode = use_unicode(inode);
386	attr = use_attributes(inode);
387
388	if (pos < 0)
389		return -EINVAL;
390	/* we enforce 32-bit alignment for pos and count in unicode mode */
391	if (uni_mode && (pos | count) & 3)
392		return -EINVAL;
393
394	poll = file->private_data;
395	if (count && poll)
396		poll->event = 0;
397	read = 0;
398	ret = 0;
399	while (count) {
400		unsigned int this_round, skip = 0;
401		int size;
402
403		vc = vcs_vc(inode, &viewed);
404		if (!vc) {
405			ret = -ENXIO;
406			break;
407		}
408
409		/* Check whether we are above size each round,
410		 * as copy_to_user at the end of this loop
411		 * could sleep.
412		 */
413		size = vcs_size(vc, attr, uni_mode);
414		if (size < 0) {
415			ret = size;
416			break;
417		}
418		if (pos >= size)
419			break;
420		if (count > size - pos)
421			count = size - pos;
422
423		this_round = count;
424		if (this_round > CON_BUF_SIZE)
425			this_round = CON_BUF_SIZE;
426
427		/* Perform the whole read into the local con_buf.
428		 * Then we can drop the console spinlock and safely
429		 * attempt to move it to userspace.
430		 */
431
432		if (uni_mode) {
433			ret = vcs_read_buf_uni(vc, con_buf, pos, this_round,
434					viewed);
435			if (ret)
436				break;
437		} else if (!attr) {
438			vcs_read_buf_noattr(vc, con_buf, pos, this_round,
439					viewed);
440		} else {
441			this_round = vcs_read_buf(vc, con_buf, pos, this_round,
442					viewed, &skip);
443		}
444
445		/* Finally, release the console semaphore while we push
446		 * all the data to userspace from our temporary buffer.
447		 *
448		 * AKPM: Even though it's a semaphore, we should drop it because
449		 * the pagefault handling code may want to call printk().
450		 */
451
452		console_unlock();
453		ret = copy_to_user(buf, con_buf + skip, this_round);
454		console_lock();
455
456		if (ret) {
457			read += this_round - ret;
458			ret = -EFAULT;
459			break;
460		}
461		buf += this_round;
462		pos += this_round;
463		read += this_round;
464		count -= this_round;
465	}
466	*ppos += read;
467	if (read)
468		return read;
469
470	return ret;
471}
472
473static u16 *vcs_write_buf_noattr(struct vc_data *vc, const char *con_buf,
474		unsigned int pos, unsigned int count, bool viewed, u16 **org0)
475{
476	u16 *org;
477	unsigned int col, maxcol = vc->vc_cols;
478
479	*org0 = org = screen_pos(vc, pos, viewed);
480	col = pos % maxcol;
481	pos += maxcol - col;
482
483	while (count > 0) {
484		unsigned char c = *con_buf++;
485
486		count--;
487		vcs_scr_writew(vc,
488			       (vcs_scr_readw(vc, org) & 0xff00) | c, org);
489		org++;
490		if (++col == maxcol) {
491			org = screen_pos(vc, pos, viewed);
492			col = 0;
493			pos += maxcol;
494		}
495	}
496
497	return org;
498}
499
500/*
501 * Compilers (gcc 10) are unable to optimize the swap in cpu_to_le16. So do it
502 * the poor man way.
503 */
504static inline u16 vc_compile_le16(u8 hi, u8 lo)
505{
506#ifdef __BIG_ENDIAN
507	return (lo << 8u) | hi;
508#else
509	return (hi << 8u) | lo;
510#endif
511}
512
513static u16 *vcs_write_buf(struct vc_data *vc, const char *con_buf,
514		unsigned int pos, unsigned int count, bool viewed, u16 **org0)
515{
516	u16 *org;
517	unsigned int col, maxcol = vc->vc_cols;
518	unsigned char c;
519
520	/* header */
521	if (pos < HEADER_SIZE) {
522		char header[HEADER_SIZE];
523
524		getconsxy(vc, header + 2);
525		while (pos < HEADER_SIZE && count > 0) {
526			count--;
527			header[pos++] = *con_buf++;
528		}
529		if (!viewed)
530			putconsxy(vc, header + 2);
531	}
532
533	if (!count)
534		return NULL;
535
536	pos -= HEADER_SIZE;
537	col = (pos/2) % maxcol;
538
539	*org0 = org = screen_pos(vc, pos/2, viewed);
540
541	/* odd pos -- the first single character */
542	if (pos & 1) {
543		count--;
544		c = *con_buf++;
545		vcs_scr_writew(vc, vc_compile_le16(c, vcs_scr_readw(vc, org)),
546				org);
547		org++;
548		pos++;
549		if (++col == maxcol) {
550			org = screen_pos(vc, pos/2, viewed);
551			col = 0;
552		}
553	}
554
555	pos /= 2;
556	pos += maxcol - col;
557
558	/* even pos -- handle attr+character pairs */
559	while (count > 1) {
560		unsigned short w;
561
562		w = get_unaligned(((unsigned short *)con_buf));
563		vcs_scr_writew(vc, w, org++);
564		con_buf += 2;
565		count -= 2;
566		if (++col == maxcol) {
567			org = screen_pos(vc, pos, viewed);
568			col = 0;
569			pos += maxcol;
570		}
571	}
572
573	if (!count)
574		return org;
575
576	/* odd pos -- the remaining character */
577	c = *con_buf++;
578	vcs_scr_writew(vc, vc_compile_le16(vcs_scr_readw(vc, org) >> 8, c),
579				org);
580
581	return org;
582}
583
584static ssize_t
585vcs_write(struct file *file, const char __user *buf, size_t count, loff_t *ppos)
586{
587	struct inode *inode = file_inode(file);
588	struct vc_data *vc;
589	u16 *org0, *org;
590	unsigned int written;
591	int size;
592	ssize_t ret;
593	loff_t pos;
594	bool viewed, attr;
595
596	if (use_unicode(inode))
597		return -EOPNOTSUPP;
598
599	char *con_buf __free(free_page_ptr) = (char *)__get_free_page(GFP_KERNEL);
600	if (!con_buf)
601		return -ENOMEM;
602
603	pos = *ppos;
604
605	/* Select the proper current console and verify
606	 * sanity of the situation under the console lock.
607	 */
608	guard(console_lock)();
609
610	attr = use_attributes(inode);
611	vc = vcs_vc(inode, &viewed);
612	if (!vc)
613		return -ENXIO;
614
615	size = vcs_size(vc, attr, false);
616	if (size < 0)
617		return size;
618	if (pos < 0 || pos > size)
619		return -EINVAL;
620	if (count > size - pos)
621		count = size - pos;
622	written = 0;
623	while (count) {
624		unsigned int this_round = count;
625
626		if (this_round > CON_BUF_SIZE)
627			this_round = CON_BUF_SIZE;
628
629		/* Temporarily drop the console lock so that we can read
630		 * in the write data from userspace safely.
631		 */
632		console_unlock();
633		ret = copy_from_user(con_buf, buf, this_round);
634		console_lock();
635
636		if (ret) {
637			this_round -= ret;
638			if (!this_round) {
639				/* Abort loop if no data were copied. Otherwise
640				 * fail with -EFAULT.
641				 */
642				if (written)
643					break;
644				return -EFAULT;
645			}
646		}
647
648		/* The vc might have been freed or vcs_size might have changed
649		 * while we slept to grab the user buffer, so recheck.
650		 * Return data written up to now on failure.
651		 */
652		vc = vcs_vc(inode, &viewed);
653		if (!vc) {
654			if (written)
655				break;
656			return -ENXIO;
657		}
658		size = vcs_size(vc, attr, false);
659		if (size < 0) {
660			if (written)
661				break;
662			return size;
663		}
664		if (pos >= size)
665			break;
666		if (this_round > size - pos)
667			this_round = size - pos;
668
669		/* OK, now actually push the write to the console
670		 * under the lock using the local kernel buffer.
671		 */
672
673		if (attr)
674			org = vcs_write_buf(vc, con_buf, pos, this_round,
675					viewed, &org0);
676		else
677			org = vcs_write_buf_noattr(vc, con_buf, pos, this_round,
678					viewed, &org0);
679
680		count -= this_round;
681		written += this_round;
682		buf += this_round;
683		pos += this_round;
684		if (org)
685			update_region(vc, (unsigned long)(org0), org - org0);
686	}
687	*ppos += written;
688	ret = written;
689	if (written)
690		vcs_scr_updated(vc);
691
692	return ret;
693}
694
695static __poll_t
696vcs_poll(struct file *file, poll_table *wait)
697{
698	struct vcs_poll_data *poll = vcs_poll_data_get(file);
699	__poll_t ret = DEFAULT_POLLMASK|EPOLLERR;
700
701	if (poll) {
702		poll_wait(file, &poll->waitq, wait);
703		switch (poll->event) {
704		case VT_UPDATE:
705			ret = DEFAULT_POLLMASK|EPOLLPRI;
706			break;
707		case VT_DEALLOCATE:
708			ret = DEFAULT_POLLMASK|EPOLLHUP|EPOLLERR;
709			break;
710		case 0:
711			ret = DEFAULT_POLLMASK;
712			break;
713		}
714	}
715	return ret;
716}
717
718static int
719vcs_fasync(int fd, struct file *file, int on)
720{
721	struct vcs_poll_data *poll = file->private_data;
722
723	if (!poll) {
724		/* don't allocate anything if all we want is disable fasync */
725		if (!on)
726			return 0;
727		poll = vcs_poll_data_get(file);
728		if (!poll)
729			return -ENOMEM;
730	}
731
732	return fasync_helper(fd, file, on, &poll->fasync);
733}
734
735static int
736vcs_open(struct inode *inode, struct file *filp)
737{
738	unsigned int currcons = console(inode);
739	bool attr = use_attributes(inode);
740	bool uni_mode = use_unicode(inode);
741
742	/* we currently don't support attributes in unicode mode */
743	if (attr && uni_mode)
744		return -EOPNOTSUPP;
745
746	guard(console_lock)();
747
748	if (currcons && !vc_cons_allocated(currcons - 1))
749		return -ENXIO;
750
751	return 0;
752}
753
754static int vcs_release(struct inode *inode, struct file *file)
755{
756	struct vcs_poll_data *poll = file->private_data;
757
758	if (poll)
759		vcs_poll_data_free(poll);
760	return 0;
761}
762
763static const struct file_operations vcs_fops = {
764	.llseek		= vcs_lseek,
765	.read		= vcs_read,
766	.write		= vcs_write,
767	.poll		= vcs_poll,
768	.fasync		= vcs_fasync,
769	.open		= vcs_open,
770	.release	= vcs_release,
771};
772
773static const struct class vc_class = {
774	.name = "vc",
775};
776
777void vcs_make_sysfs(int index)
778{
779	device_create(&vc_class, NULL, MKDEV(VCS_MAJOR, index + 1), NULL, "vcs%u", index + 1);
780	device_create(&vc_class, NULL, MKDEV(VCS_MAJOR, index + 65), NULL, "vcsu%u", index + 1);
781	device_create(&vc_class, NULL, MKDEV(VCS_MAJOR, index + 129), NULL, "vcsa%u", index + 1);
782}
783
784void vcs_remove_sysfs(int index)
785{
786	device_destroy(&vc_class, MKDEV(VCS_MAJOR, index + 1));
787	device_destroy(&vc_class, MKDEV(VCS_MAJOR, index + 65));
788	device_destroy(&vc_class, MKDEV(VCS_MAJOR, index + 129));
789}
790
791int __init vcs_init(void)
792{
793	unsigned int i;
794
795	if (register_chrdev(VCS_MAJOR, "vcs", &vcs_fops))
796		panic("unable to get major %d for vcs device", VCS_MAJOR);
797	if (class_register(&vc_class))
798		panic("unable to create vc_class");
799
800	device_create(&vc_class, NULL, MKDEV(VCS_MAJOR, 0), NULL, "vcs");
801	device_create(&vc_class, NULL, MKDEV(VCS_MAJOR, 64), NULL, "vcsu");
802	device_create(&vc_class, NULL, MKDEV(VCS_MAJOR, 128), NULL, "vcsa");
803	for (i = 0; i < MIN_NR_CONSOLES; i++)
804		vcs_make_sysfs(i);
805	return 0;
806}
~/ Read-only file preview.