Linux Programming Hints

In this month's column, I said that I would give a simple screen-locking example that uses the VT, or Virtual Terminal, ioctl()'s that I documented in that column.

In case you can't remember or didn't read last month's column, the VT ioctl()'s allow you to specify from a user program what the kernel should do about the virtual terminals, or virtual consoles. (These are essentially the same. For the rest of the column, I will refer to them as virtual consoles, not virtual terminals, for no particular reason).

A program can request that the kernel give it raw scan codes instead of full keystrokes, can tell the kernel that you are going into graphics mode on that terminal, and do many other low-level things. XFree86 uses these ioctl()'s heavily, as does svgalib. The Linux DOS emulator (which is really a BIOS emulator) uses them, and the loadable keymaps program (kbd) uses them.

If you didn't read last month's column, the main content of the column will be included in future man pages to be released by the Linux man page project.

I have written a program called vlock, which is a screen locker which can lock virtual consoles. I don't have space here to reproduce the entire source code, but I will give enough details for you to easily construct your own similar program. Instructions for obtaining a copy via anonymous ftp on the Internet follow the code in this column.


My original purpose in writing vlock was to demonstrate a use for the VT ioctl()'s that they had really not been designed for, to show their flexibility.

If you are like many Linux users, you may have one or two sessions of X running, and a few console logins active at the save time, and be switching back and forth between them. Perhaps you have been editing a program you have been working on, and don't want your roommates or children to start playing with your files while you go away from your computer for one reason or another, but you really don't feel like logging out and restarting all your sessions.

xlock could solve your problem if you only have an X session that you want to lock, but anyone can still switch to the console even when xlock is running. You need a program that can lock all the sessions at once. Well, maybe you need a program that can lock all the sessions at once...


My first idea for locking the console was to read raw scancodes from the keyboard instead of reading normal characters, and to ignore anything but the scancodes for alphanumeric keys, the shift key, the caps lock key, and the control key, and write a state machine to get keys from that. This would automatically ignore the ALT-Fn keys that are normally used to switch from virtual console to virtual console, so those keypresses would not make the VC switch. Of course, it would be possible that there would be some problems with some national keyboards, but it would mostly work for mostly anyone.

However, that would involve a lot of work, and a lot of testing, and I'm too lazy to do that much work if there is an easy way to do it. (I later realized that there was a serious security problem with this approach as well. I'll let you try to figure out what the flaw is, and I'll explain at the end of this column.)

I then noticed that there are ioctl()'s specifically for telling the kernel to ask first before switching virtual consoles. It is possible for a program to explicitly refuse to let the kernel switch virtual consoles. These ioctl()'s only work on virtual consoles, so first we need to open one of the virtual consoles to perform the ioctl()'s on. The easiest thing to do is this:

if (vfd = open("/dev/console", O_RDWR) < 0) {
  perror("vlock: could not open /dev/console");
  exit (1);

/dev/console stands for the current screen. The assumption is that when vlock is run, it will be run on the current virtual console. (It turns out that this assumption does not create a security hole, although it might look to you like it ought to.)

It would also be possible to switch to an unallocated virtual terminal, like X does, which might be preferable in some circumstances. To do this, we could have used the ioctl VT_OPENQRY to find the number of the first available virtual console, opened the appropriate device (/dev/ttynn, where nn is the number returned by VT_OPENQRY), and used VT_ACTIVATE to switch to that virtual console.

It is a lot easier to just open /dev/console.

c = ioctl(vfd, VT_GETMODE, &vtm);
if (c < 0) {
        "This tty is not a virtual console.\n");
  is_vt = 0;
} else {
  is_vt = 1;

We will treat the VT_GETMODE and VT_SETMODE ioctl()'s like the termios interface: first we get the current settings, then we change the local copy, then we set the kernel's copy to look like the changed local copy.

VT_GETMODE fills a vt_mode structure with the current VT settings. If it returns an error, the program must not be running on a virtual console. vlock does not exit on this error, but it does set the is_vt variable to 0, and it does not try to use any more VT ioctl()'s if the is_vt variable is set to 0.

/* we set SIGUSR{1,2} to point to *_vt() */
sa.sa_flags = 0;
sa.sa_handler = release_vt;
sigaction(SIGUSR1, &sa, NULL);
sa.sa_handler = acquire_vt;
sigaction(SIGUSR2, &sa, NULL);

We will arrange in a moment for SIGUSR1 to be sent to the process whenever the kernel is requested to change away from the virtual console the program is running on, and for SIGUSR2 to be sent to the process whenever the kernel is requested to change to the virtual console the program is running on. These requests can be caused by the user pressing ALT-Fn keys or by other programs issuing a VT_ACTIVATE ioctl.

When SIGUSR1 is received, release_vt() is called:

void release_vt(int signo) {
  if (!o_lock_all)
    /* kernel is allowed to switch */
    ioctl(vfd, VT_RELDISP, 1);
    /* kernel is not allowed to switch */
    ioctl(vfd, VT_RELDISP, 0);

The variable o_lock_all is set if the user wants to lock all virtual consoles at once. It is not set if the user only wants to lock the current virtual console. VT_RELDISP is used to tell the kernel that the program acknowledges that it has received the signal asking it to relinquish the virtual console, and tells the kernel whether or not it agrees to do so. The third argument is set to 1 to allow the kernel to switch to another virtual console, or set to 0 to prevent the kernel from switching to another virtual console.

When SIGUSR2 is received, acquire_vt() is called:

void acquire_vt(int signo) {
  /* This call is not currently required under Linux,
     but it won't hurt, either... */
  ioctl(vfd, VT_RELDISP, VT_ACKACQ);

Linux does not actually require that this be done; it is included for compatibility with SYSV, which does require that it is called. I included it in vlock mainly so that if someone wanted to port vlock to some version of SYSV, there would be one less stumbling block for him or her.

Now that we have set up these signal handlers, we will tell the virtual console manager about them.

We did not want to tell the virtual console manager to route requests to change virtual consoles through these signals until the signals' handlers had been installed, because to do otherwise could cause a small possibility of a bug on very slow machines which are running too many processes at once.

if (is_vt) {
/* Keep a copy around to restore
     at appropriate times */
  ovtm = vtm;
  vtm.mode = VT_PROCESS;
  /* handled by release_vt(): */
  vtm.relsig = SIGUSR1;
  /* handled by acquire_vt(): */
vtm.acqsig = SIGUSR2;
  ioctl(vfd, VT_SETMODE, &vtm);

ovtm is another vt_mode structure, like vtm. Setting vtm.mode to VT_PROCESS causes the kernel to ask permission to change virtual consoles. Setting vtm.relsig to SIGUSR1 and vtm.acqsig to SIGUSR2 tells the kernel how to ask permission.

At this point, all that needs to be done is to handle all reasonable signals, so that people can't break in by typing control-c or control-\ or control-break, and to then ask for the user to type in a password and check it against the real password. There is a library function, getpass(), which gets a password from the user without echoing it to the screen.

Unfortunately, this function is broken under at least one shadow password implementation, because signal handlers are not installed correctly, so to make a screen locking program that works with shadow passwords, you either have to fix the shadow password library or write your own version of getpass(). With vlock, I chose to tell people that vlock doesn't work right with shadow passwords without fixing their shadow password library, rather than writing my own version of the function.

Once a correct password has been entered, the program can just exit. This is acceptable under Linux, at least. However, in case this doesn't work with some other SYSV implementations of the VT ioctl()'s, I have included code in vlock to restore everything, including the VT state, to the original settings. That's why I made the copy of vtm called ovtm a few code fragments ago.