Linux Programming Hints

 in
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.
Don't re-invent the wheel

Unless you want to, of course. By the time you read this, I will probably have upgraded vlock several times. The newest version of vlock will always be available from the ftp site tsx-11.mit.edu in the directory /pub/linux/sources/usr.bin in a file called vlock-m.n.tar.gz, where m and n are the major and minor version numbers of the release, respectively.

As of this writing, the current version of vlock is 0.6. If you cannot use ftp, but do have Internet e-mail, you may send e-mail to johnsonm@redhat.com and request a copy, and I can send you a uuencoded gzipped tar file containing both sources and a binary for vlock. Also, the Debian distribution of Linux includes vlock.

The Flaw...

Near the beginning of this article I said I would explain the fundamental flaw with trying to lock the virtual consoles by simply capturing all the keys. The problem is that someone could easily log in from the network or a modem or serial terminal and run a program (they would probably have to write it first) which would issue a request to change the virtual console. This program would be a little trickier than it sounds at first, but it is possible to write it. The kernel would honor the ioctl() requesting the change, and the screen-locking program would be defeated.

Loose ends

I am finding out that many Unix programmers are a little confused about signals. This is understandable, because there are at least three standards for using signals. [Purists, please don't tell me that there are actually more; I'm trying to keep things relatively simple here. A much more detailed explanation, which is historically correct to the best of my knowledge, can be found in Advanced Programming in the Unix Environment, by W. Richard Stevens, in chapter 10, Signals.] Though I have mentioned the differences in signals before, in issue one, I will explain more explicitly here.

The original signals were unreliable. The signal() function was used to install a signal handler that was good for one invocation of the signal, and once the signal handler had been invoked once, the signal handler would uninstall. So you would install your signal handler like this:

signal(SIGUSR1, signal_handling_function);

and then you would implement your signal handling function like this:

void signal_handling_function(int signo) {
  signal(SIGUSR1, signal_handling_function);
  /* Do whatever the signal handling
     function is supposed to do... */
}

The problem with taking this approach is that occasionally a second signal would arrive in between the time that the kernel uninstalled the signal handler and the time that the signal handler re-installed itself.

The problem with not taking this approach is that signal handlers need to be reentrant.

Unfortunately, as reliable signals were introduced, BSD revised signal() to not get uninstalled when it was called, while SYSV left signal() the way it was. There is more to the story, but it only gets more confusing.

Fortunately, there is absolutely no need to be confused. There is no need to use signal() at all. Don't use it: to do so (without knowing all the details about the signal() function on all different versions of Unix) is to write non-portable code.

POSIX defines an alternate interface that is the same on all POSIX-compliant platforms. This interface is called sigaction, and is more powerful and flexible than either version of signal(). [sigaction is derived from the first BSD implementation of reliable signals, so code which uses sigaction will not only be portable to all POSIX platforms, but to pre-POSIX BSD systems as well.] Unfortunately, it is a little more complex, but you can write your own signal management wrapper functions to get exactly the kind of signals you need. Here is an example:

typedef void signal_handler(int);
signal_handler *
my_signal(int signo, signal_handler *func, int oneshot) {
 struct sigaction sact, osact;
  sigemptyset(&sact.sa_mask);
  sact.sa_handler = func;
  if (oneshot) {
    sact.sa_flags = SA_ONESHOT;
  } else {
    sact.sa_flags = 0;
  }
  if (sigaction(signo, &sact, &osact) < 0) {
    return (SIG_ERR);
  } else {
    return (oact.sa_handler);
  }
}

This is not perfect, but it creates an interface to sigaction that is as convenient as signal() but will have the same semantics no matter what system it is compiled on, unlike signal().

It works like signal(), except that it takes a third argument. That third argument determines whether the signal handler remains installed when it is called or if it is uninstalled as soon as it is called.

There are two normal reasons to have a signal handler automatically uninstalled. The first is if the signal handler is not reentrant—if it is not safe to run the signal handler again until while it is already being run. The second is for those times when you really only want to catch one instance of a signal, for example SIGALRM.

You may have noticed the call to sigemptyset() in the code above. It is important for it to be there, but I have not yet mentioned it. It turns out that it is possible for a sigaction signal handler to mask out certain signals while it is being run. Perhaps the most common occurrence of this is in signal handlers that are not reentrant. These signal handlers can set their sa_mask to keep from being called again while they are being invoked, by using code like this:

sigemptyset(&sact.sa_mask);
sigaddset(&sact.sa_mask, SIGFOO);
sact.sa_handler = signal_handler;
sact.sa_flags = 0;
if (sigaction(SIGFOO, &sact, &osact) < 0) {
  do_signal_error(SIGFOO);
}

This will allow you to use a non-reentrant signal handler for SIGFOO. Of course, this code will have to be altered slightly to fit into your application. You will at least have to use a real signal name instead of SIGFOO...

If you are interested in doing more with signals, look up the sigaction() function in a modern Unix programming book or manual, and also read up on “signal sets”, which may be found under the following functions; sigemptyset(), sigfillset(), sigaddset(), sigdelset(), sigismember(), sigprocmask(), sigpending(), sigsetjmp(), siglongjmp() and sigsuspend(). These provide very fine-tuneable support for all sorts of fancy signal work, which I will not try to cover this month.

Please send e-mail to johnsonm@redhat.com or send paper mail to Programming Tips, Linux Journal, P.O. Box 85867, Seattle, WA 98145-1867, if you have any suggestions or comments about this column. I'd like to know what you have found useful so far.

If there are any undocumented Linux features that you would like to see covered, I'll look at them. I may write a column, if there is enough interest. I'd also like to have guest columnists write for Linux Programming Hints.

______________________

Webinar
One Click, Universal Protection: Implementing Centralized Security Policies on Linux Systems

As Linux continues to play an ever increasing role in corporate data centers and institutions, ensuring the integrity and protection of these systems must be a priority. With 60% of the world's websites and an increasing share of organization's mission-critical workloads running on Linux, failing to stop malware and other advanced threats on Linux can increasingly impact an organization's reputation and bottom line.

Learn More

Sponsored by Bit9

Webinar
Linux Backup and Recovery Webinar

Most companies incorporate backup procedures for critical data, which can be restored quickly if a loss occurs. However, fewer companies are prepared for catastrophic system failures, in which they lose all data, the entire operating system, applications, settings, patches and more, reducing their system(s) to “bare metal.” After all, before data can be restored to a system, there must be a system to restore it to.

In this one hour webinar, learn how to enhance your existing backup strategies for better disaster recovery preparedness using Storix System Backup Administrator (SBAdmin), a highly flexible bare-metal recovery solution for UNIX and Linux systems.

Learn More

Sponsored by Storix