Thursday, November 12, 2009

8.3 Manipulating Signal Masks and Signal Sets



[ Team LiB ]






8.3 Manipulating Signal Masks and Signal Sets


A process can temporarily prevent a signal from being delivered by blocking it. Blocked signals do not affect the behavior of the process until they are delivered. The process signal mask gives the set of signals that are currently blocked. The signal mask is of type sigset_t.


Blocking a signal is different from ignoring a signal. When a process blocks a signal, the operating system does not deliver the signal until the process unblocks the signal. A process blocks a signal by modifying its signal mask with sigprocmask. When a process ignores a signal, the signal is delivered and the process handles it by throwing it away. The process sets a signal to be ignored by calling sigaction with a handler of SIG_IGN, as described in Section 8.4.


Specify operations (such as blocking or unblocking) on groups of signals by using signal sets of type sigset_t. Signal sets are manipulated by the five functions listed in the following synopsis box. The first parameter for each function is a pointer to a sigset_t. The sigaddset adds signo to the signal set, and the sigdelset removes signo from the signal set. The sigemptyset function initializes a sigset_t to contain no signals; sigfillset initializes a sigset_t to contain all signals. Initialize a signal set by calling either sigemptyset or sigfillset before using it. The sigismember reports whether signo is in a sigset_t.



SYNOPSIS

#include <signal.h>

int sigaddset(sigset_t *set, int signo);
int sigdelset(sigset_t *set, int signo);
int sigemptyset(sigset_t *set);
int sigfillset(sigset_t *set);
int sigismember(const sigset_t *set, int signo);
POSIX:CX

The sigismember function returns 1 if signo is in *set and 0 if signo is not in *set. If successful, the other functions return 0. If unsuccessful, these other functions return �1 and set errno. POSIX does not define any mandatory errors for these functions.



Example 8.9

The following code segment initializes signal set twosigs to contain exactly the two signals SIGINT and SIGQUIT.



if ((sigemptyset(&twosigs) == -1) ||
(sigaddset(&twosigs, SIGINT) == -1) ||
(sigaddset(&twosigs, SIGQUIT) == -1))
perror("Failed to set up signal mask");


A process can examine or modify its process signal mask with the sigprocmask function. The how parameter is an integer specifying the manner in which the signal mask is to be modified. The set parameter is a pointer to a signal set to be used in the modification. If set is NULL, no modification is made. If oset is not NULL, the sigprocmask returns in *oset the signal set before the modification.



SYNOPSIS

#include <signal.h>

int sigprocmask(int how, const sigset_t *restrict set,
sigset_t *restrict oset);
POSIX:CX

If successful, sigprocmask returns 0. If unsuccessful, sigprocmask returns �1 and sets errno. The sigprocmask function sets errno to EINVAL if how is invalid. The sigprocmask function should only be used by a process with a single thread. When multiple threads exist, the pthread_sigmask function (page 474) should be used.


The how parameter, which specifies the manner in which the signal mask is to be modified, can take on one of the following three values.


SIG_BLOCK:

add a collection of signals to those currently blocked

SIG_UNBLOCK:

delete a collection of signals from those currently blocked

SIG_SETMASK:

set the collection of signals being blocked to the specified set


Keep in mind that some signals, such as SIGSTOP and SIGKILL, cannot be blocked. If an attempt is made to block these signals, the system ignores the request without reporting an error.



Example 8.10

The following code segment adds SIGINT to the set of signals that the process has blocked.



sigset_t newsigset;

if ((sigemptyset(&newsigset) == -1) ||
(sigaddset(&newsigset, SIGINT) == -1))
perror("Failed to initialize the signal set");
else if (sigprocmask(SIG_BLOCK, &newsigset, NULL) == -1)
perror("Failed to block SIGINT");

If SIGINT is already blocked, the call to sigprocmask has no effect.



Program 8.1 displays a message, blocks the SIGINT signal while doing some useless work, unblocks the signal, and does more useless work. The program repeats this sequence continually in a loop.


If a user enters Ctrl-C while SIGINT is blocked, Program 8.1 finishes the calculation and prints a message before terminating. If a user types Ctrl-C while SIGINT is unblocked, the program terminates immediately.



Program 8.1 blocktest.c

A program that blocks and unblocks SIGINT.



#include <math.h>
#include <signal.h>
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>

int main(int argc, char *argv[]) {
int i;
sigset_t intmask;
int repeatfactor;
double y = 0.0;

if (argc != 2) {
fprintf(stderr, "Usage: %s repeatfactor\n", argv[0]);
return 1;
}
repeatfactor = atoi(argv[1]);
if ((sigemptyset(&intmask) == -1) || (sigaddset(&intmask, SIGINT) == -1)){
perror("Failed to initialize the signal mask");
return 1;
}
for ( ; ; ) {
if (sigprocmask(SIG_BLOCK, &intmask, NULL) == -1)
break;
fprintf(stderr, "SIGINT signal blocked\n");
for (i = 0; i < repeatfactor; i++)
y += sin((double)i);
fprintf(stderr, "Blocked calculation is finished, y = %f\n", y);
if (sigprocmask(SIG_UNBLOCK, &intmask, NULL) == -1)
break;
fprintf(stderr, "SIGINT signal unblocked\n");
for (i = 0; i < repeatfactor; i++)
y += sin((double)i);
fprintf(stderr, "Unblocked calculation is finished, y=%f\n", y);
}
perror("Failed to change signal mask");
return 1;
}


The function makepair of Program 8.2 takes two pathnames as parameters and creates two named pipes with these names. If successful, makepair returns 0. If unsuccessful, makepair returns �1 and sets errno. The function blocks all signals during the creation of the two pipes to be sure that it can deallocate both pipes if there is an error. The function restores the original signal mask before the return. The if statement relies on the conditional left-to-right evaluation of && and ||.



Exercise 8.11

Is it possible that after a call to makepair, pipe1 exists but pipe2 does not?


Answer:


Yes. This could happen if pipe1 already exists but pipe2 does not and the user does not have write permission to the directory. It could also happen if the SIGKILL signal is delivered between the two calls to mkfifo.




Program 8.2 makepair.c

A function that blocks signals while creating two pipes. (See Exercise 8.11 and Exercise 8.12 for a discussion of some flaws.)



#include <errno.h>
#include <signal.h>
#include <unistd.h>
#include <sys/stat.h>
#define R_MODE (S_IRUSR | S_IRGRP | S_IROTH)
#define W_MODE (S_IWUSR | S_IWGRP | S_IWOTH)
#define RW_MODE (R_MODE | W_MODE)

int makepair(char *pipe1, char *pipe2) {
sigset_t blockmask;
sigset_t oldmask;
int returncode = 0;

if (sigfillset(&blockmask) == -1)
return -1;
if (sigprocmask(SIG_SETMASK, &blockmask, &oldmask) == -1)
return -1;
if (((mkfifo(pipe1, RW_MODE) == -1) && (errno != EEXIST)) ||
((mkfifo(pipe2, RW_MODE) == -1) && (errno != EEXIST))) {
returncode = errno;
unlink(pipe1);
unlink(pipe2);
}
if ((sigprocmask(SIG_SETMASK, &oldmask, NULL) == -1) && !returncode)
returncode = errno;
if (returncode) {
errno = returncode;
return -1;
}
return 0;
}



Exercise 8.12

Does a makepair return value of 0 guarantee that FIFOs corresponding to pipe1 and pipe2 are available on return?


Answer:


If one of the files already exists, mkfifo returns �1 and sets errno to EEXIST. The makepair function assumes that the FIFO exists without checking whether the file was a FIFO or an ordinary file. Thus, it is possible for makepair to indicate success even if this previously existing file is not a FIFO.



In Program 8.3, the parent blocks all signals before forking a child process to execute an ls command. Processes inherit the signal mask after both fork and exec, so the ls command executes with signals blocked. The child created by fork in Program 8.3 has a copy of the original signal mask saved in oldmask. An exec command overwrites all program variables, so an executed process cannot restore the original mask once exec takes place. The parent restores the original signal mask and then waits for the child.



Program 8.3 blockchild.c

A program that blocks signals before calling fork and execl.



#include <errno.h>
#include <stdio.h>
#include <signal.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/wait.h>
#include "restart.h"

int main(void) {
pid_t child;
sigset_t mask, omask;

if ((sigfillset(&mask) == -1) ||
(sigprocmask(SIG_SETMASK, &mask, &omask) == -1)) {
perror("Failed to block the signals");
return 1;
}
if ((child = fork()) == -1) {
perror("Failed to fork child");
return 1;
}
if (child == 0) { /* child code */
execl("/bin/ls", "ls", "-l", NULL);
perror("Child failed to exec");
return 1;
}
if (sigprocmask(SIG_SETMASK, &omask, NULL) == -1){ /* parent code */
perror("Parent failed to restore signal mask");
return 1;
}
if (r_wait(NULL) == -1) {
perror("Parent failed to wait for child");
return 1;
}
return 0;
}



Exercise 8.13

Run Program 8.3 from a working directory with a large number of files. Experiment with entering Ctrl-C at various points during the execution and explain what happens.


Answer:


The main program can be interrupted while the listing is being displayed, and the prompt will appear in the middle of the listing. The execution of ls will not be interrupted by the signal.




Program 8.4 password.c

A function that retrieves a user password.



#include <errno.h>
#include <fcntl.h>
#include <signal.h>
#include <stdio.h>
#include <string.h>
#include <termios.h>
#include <unistd.h>
#include "restart.h"

int setecho(int fd, int onflag);

int password(const char *prompt, char *passbuf, int passmax) {
int fd;
int firsterrno = 0;
sigset_t signew, sigold;
char termbuf[L_ctermid];

if (ctermid(termbuf) == NULL) { /* find the terminal name */
errno = ENODEV;
return -1;
}
if ((fd = open(termbuf, O_RDONLY)) == -1) /* open descriptor to terminal */
return -1;
if ((sigemptyset(&signew) == -1) || /* block SIGINT, SIGQUIT and SIGTSTP */
(sigaddset(&signew, SIGINT) == -1) ||
(sigaddset(&signew, SIGQUIT) == -1) ||
(sigaddset(&signew, SIGTSTP) == -1) ||
(sigprocmask(SIG_BLOCK, &signew, &sigold) == -1) ||
(setecho(fd, 0) == -1)) { /* set terminal echo off */
firsterrno = errno;
sigprocmask(SIG_SETMASK, &sigold, NULL);
r_close(fd);
errno = firsterrno;
return -1;
}
if ((r_write(STDOUT_FILENO, (char *)prompt, strlen(prompt)) == -1) ||
(readline(fd, passbuf, passmax) == -1)) /* read password */
firsterrno = errno;
else
passbuf[strlen(passbuf) - 1] = 0; /* remove newline */
if ((setecho(fd, 1) == -1) && !firsterrno) /* turn echo back on */
firsterrno = errno;
if ((sigprocmask(SIG_SETMASK, &sigold, NULL) == -1) && !firsterrno )
firsterrno = errno;
if ((r_close(fd) == -1) && !firsterrno) /* close descriptor to terminal */
firsterrno = errno;
return firsterrno ? errno = firsterrno, -1: 0;
}


Program 8.4 shows an improvement on the passwordnosigs function of Program 6.13 on page 208. The password function blocks SIGINT, SIGQUIT and SIGTSTP while terminal echo is set off, preventing the terminal from being placed in an unusable state if one of these signals is delivered to the process while this function is executing.






    [ Team LiB ]



    No comments: