Public feed #600782

Miscellany

public text v1 · immutable
Raw Edit
·published 2007-07-03 02:41 UTC
rendered paste body
/* upstart
 *
 * Copyright © 2007 Canonical Ltd.
 * Author: Scott James Remnant <scott@ubuntu.com>.
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 2 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301 USA
 */

#ifdef HAVE_CONFIG_H
# include <config.h>
#endif /* HAVE_CONFIG_H */


#include <sys/types.h>
#include <sys/time.h>
#include <sys/wait.h>
#include <sys/ioctl.h>
#include <sys/reboot.h>
#include <sys/resource.h>

#include <errno.h>
#include <stdio.h>
#include <signal.h>
#include <stdlib.h>
#include <string.h>
#include <syslog.h>
#include <unistd.h>
#include <termios.h>

#include <linux/kd.h>

#include <nih/macros.h>
#include <nih/alloc.h>
#include <nih/list.h>
#include <nih/timer.h>
#include <nih/signal.h>
#include <nih/child.h>
#include <nih/option.h>
#include <nih/main.h>
#include <nih/error.h>
#include <nih/logging.h>

#include "process.h"
#include "job.h"
#include "event.h"
#include "control.h"
#include "cfgfile.h"
#include "paths.h"

#include <sys/mman.h>
#include <selinux/selinux.h>
#include <sepol/sepol.h>
#include <sys/mount.h>
#include <ctype.h>


/* Prototypes for static functions */
static void reset_console   (void);
static void crash_handler   (int signum);
static void cad_handler     (void *data, NihSignal *signal);
static void kbd_handler     (void *data, NihSignal *signal);
static void pwr_handler     (void *data, NihSignal *signal);
static void stop_handler    (void *data, NihSignal *signal);
static void term_handler    (void *data, NihSignal *signal);


/**
 * argv0:
 *
 * Path to program executed, used for re-executing the init binary from the
 * same location we were executed from.
 **/
static const char *argv0 = NULL;

/**
 * restart:
 *
 * This is set to TRUE if we're being re-exec'd by an existing init
 * process.
 **/
static int restart = FALSE;

/**
 * rescue:
 *
 * This is set to TRUE if we're being re-exec'd by an existing init process
 * that has crashed.
 **/
static int rescue = FALSE;


/**
 * options:
 *
 * Command-line options we accept.
 **/
static NihOption options[] = {
	{ 0, "restart", NULL, NULL, NULL, &restart, NULL },
	{ 0, "rescue", NULL, NULL, NULL, &rescue, NULL },

	/* Ignore invalid options */
	{ '-', "--", NULL, NULL, NULL, NULL, NULL },

	NIH_OPTION_LAST
};

/*
 * SELinux block, note-to-self: use nih signal handler
 */
#define SELINUXMNT "/selinux/"									// define mountpoint for selinuxfs
#define BUF_CHARS (size_t)4096
int enforcing = -1;												// SELinux enforcing mode parsed from the /proc/cmdline

static int load_policy(int *enforce) {							// Load policy funciton
		int fd=-1, ret=-1;										// return -1 by default, and file-descriptor -1?
		int rc=0, orig_enforce;									//?rc0?? and default enforcing mode
		struct stat sb;											// file attributes, sb
		void *map;												// null pointer
		char policy_file[PATH_MAX];								//?/etc/selinux/config?
		int policy_version=0;									// default policy version, current =21
		extern char *selinux_mnt;								// pointer to value "/selinux" from some header
		FILE *cfg;												// /proc/cmdline
		char *buf;											//!holds the kernel parameter string!OBS:conform to NIH
		
		buf = malloc(BUF_CHARS * sizeof(char));
		if (!buf)
			return -1;
		
		int seconfig = -2;										// default return for selinuxenforced
		selinux_getenforcemode(&seconfig);						// gets the boot enforcemode from /etc/selinux/config
		
		/*!OBS! conform to standard c++ way of opening files */
		mount("none", "/proc", "proc", 0, 0);					// mount proc fs
		cfg = fopen("/proc/cmdline","r");						// open the file to check arguments given
		if (cfg) {												
			char *tmp;								
			if (fgets(buf, BUF_CHARS, cfg) && (tmp = strstr(buf, "enforcing="))) {	// parse the arguments given to /proc/cmdline
				if (tmp == buf || isspace(*(tmp-1))) {			// if it points to the beginning, or prse to space character
					enforcing=atoi(tmp+10);						// get the selinux=1 as an int to enforcing
				}
			}
			fclose(cfg);										// close the file
		}
		
		#define MNT_DETACH 2									//?
		umount2("/proc",MNT_DETACH);

		if (enforcing >=0)										// if enforce enabled at /proc/cmdline
			*enforce = enforcing;								// enforce success
		else if (seconfig == 1)									// or if enforce enabled in /etc/selinux/config
			*enforce = 1;										// enforce success
		
		if (mount("none", SELINUXMNT, "selinuxfs", 0, 0) < 0) {		// if /selinux SELINUXFS is not mounted
			if (errno == ENODEV) {								// if kernel omitted 'no dev' (selinuxfs)
				printf("SELinux not supported by kernel: %s\n",strerror(errno));
				*enforce = 0;									// enforcing failure
				snprintf(buf, BUF_CHARS, "Warning: SELinux not supported by kernel: %s\n",strerror(errno));
				//print(buf);
				//free(buf);
				return ret;
			} else {
				snprintf(buf, BUF_CHARS, "Failed to mount %s: %s\n",SELINUXMNT,strerror(errno));
				//print(buf);
				//free(buf);
				return -1;
			}
		}
		
		free(buf);
		
		selinux_mnt = SELINUXMNT;								// set manually

		policy_version=security_policyvers();					// policyversion from /selinux/policyver
		if (policy_version < 0) {								// if policyversion is wrong or it failed to load
			printf( "Can't get policy version: %s\n", strerror(errno));
			goto UMOUNT;
		}
		
		orig_enforce = rc = security_getenforce();				//?orig_enforce = 1 or 0 depending on if it is enforced
		if (rc < 0) {											// if enforcing is -X
			printf( "Can't get SELinux enforcement flag: %s\n", strerror(errno));  //?wtf?? fix
			goto UMOUNT;
		}
		if (enforcing >= 0) {									// if enforcing is enabled, or disabled
			*enforce = enforcing;								// enable enforcing in the selinux server
		} else if (seconfig == -1) {							// if selinux disabled in /etc/selinux/conf
			*enforce = 0;										// disable enforcing in the selinux server
			rc = security_disable();							//?security_disable()??
			if (rc == 0) umount(SELINUXMNT);
			if (rc < 0) {
				rc = security_setenforce(0);										//
				if (rc < 0) {														//
					printf("Can't disable SELinux: %s\n", strerror(errno));			// note-to-self:
					goto UMOUNT;													// what is rc?? helper variable?
				}																	//
			}																		//
			ret = 0;
			goto UMOUNT;
		} else if (seconfig >= 0) {								// if selinux disabled in seconfig
			*enforce = seconfig;								//!fix (sets enforcing mode 0 in the seliniux server)
			if (orig_enforce != *enforce) {						// if enforcingmode in /selinux/enforcing is not equal to
				rc = security_setenforce(seconfig);				// rc is 0 or 1 based on enforcing mode in the config
				if (rc < 0) {									//?if enforcing disabled in the config
					printf("Can't set SELinux enforcement flag: %s\n", strerror(errno)); //?...
					goto UMOUNT;
				}
			}
		}
		
		snprintf(policy_file,sizeof(policy_file),"%s.%d",selinux_binary_policy_path(),policy_version);
		fd = open(policy_file, O_RDONLY);						// file descriptor = policyfile(readonly)
		if (fd < 0) {											// if policyfile does not exist
			/* Check previous version to see if old policy is available */
			snprintf(policy_file,sizeof(policy_file),"%s.%d",selinux_binary_policy_path(),policy_version-1); // version -1
			fd = open(policy_file, O_RDONLY);
			if (fd < 0) {
				printf( "Can't open '%s.%d':  %s\n", selinux_binary_policy_path(),policy_version,strerror(errno));
				goto UMOUNT;
			}
		}
		
		if (fstat(fd, &sb) < 0) {								// if it could not get the file attributes
			printf("Can't stat '%s':  %s\n",
					policy_file, strerror(errno));
			goto UMOUNT;
		}

		map = mmap(NULL, sb.st_size, PROT_READ | PROT_WRITE, MAP_PRIVATE, fd, 0);	// map file descriptor to memory
		if (map == MAP_FAILED) {								// if it failed..
			printf( "Can't map '%s':  %s\n",
					policy_file, strerror(errno));
			goto UMOUNT;
		}
		
		/* Set booleans based on a booleans configuration file. */
		ret = sepol_genbools(map, sb.st_size, selinux_booleans_path());				// map booleansfile and return retcode
		if (ret < 0) {											// if it failed..
			if (errno == ENOENT || errno == EINVAL) {
				/* No booleans file or stale booleans in the file; non-fatal. */
				printf("Warning!  Error while setting booleans:  %s\n", strerror(errno));
			} else {
				printf("Error while setting booleans:  %s\n", strerror(errno));
				goto UMOUNT;
			}
		}
		
		printf("Loading security policy\n");
		ret=security_load_policy(map, sb.st_size);				//?what was sb??
		if (ret < 0) {
			printf("security_load_policy failed\n");
		}

		UMOUNT:
		/*umount(SELINUXMNT); */
		if ( fd >= 0) {
			close(fd);
		}
		return(ret);
	}		
}
#undef BUF_CHARS

/*
 * END OF SELinux BLOCK 
 */

int
main (int   argc,
      char *argv[])
{
	char **args;
	int    ret;
	
	/*
	 * SELINUX INITIALIZATION BLOCK
	 */
	int enforce = 0;

	if (getenv("SELINUX_INIT") == NULL) {
		putenv("SELINUX_INIT=YES");
		if (load_policy(&enforce) == 0 ) {
			execv(myname, argv);
		} else {
			if (enforce > 0) {
				/* SELinux in enforcing mode but load_policy failed */
				/* At this point, we probably can't open /dev/console, so log() won't work */
				fprintf(stderr,"Enforcing mode requested but no policy loaded. Halting now.\n");
				exit(1);
			}
		}
	}
	/*
	 * SELINUXBLOCKEND
	 */
	
	argv0 = argv[0];
	nih_main_init (argv0);																		//## parse character array?

	nih_option_set_synopsis (_("Process management daemon."));
	nih_option_set_help (
		_("This daemon is normally executed by the kernel and given "
		  "process id 1 to denote its special status.  When executed "
		  "by a user process, it will actually run /sbin/telinit."));

	args = nih_option_parser (NULL, argc, argv, options, FALSE);								// PARSE
	if (! args)
		exit (1);

	/* Check we're root */
	if (getuid ()) {
		nih_fatal (_("Need to be root"));
		exit (1);
	}

	/* Check we're process #1 */
	if (getpid () > 1) {
		execv (TELINIT, argv);
		/* Ignore failure, probably just that telinit doesn't exist */

		nih_fatal (_("Not being executed as init"));
		exit (1);
	}

	/* Open control socket */
	if (! control_open ()) {
		NihError *err;

		err = nih_error_get ();
		nih_fatal ("%s: %s", _("Unable to open control socket"),
			   err->message);
		nih_free (err);

		exit (1);
	}

	/* Read configuration */
	if (cfg_watch_dir (CFG_DIR, NULL) == (void *)-1) {
		NihError *err;

		err = nih_error_get ();
		nih_fatal ("%s: %s", _("Error parsing configuration"),
			   err->message);
		nih_free (err);

		exit (1);
	}


	/* Clear our arguments from the command-line, so that we show up in
	 * ps or top output as /sbin/init, with no extra flags.
	 *
	 * This is a very Linux-specific trick; by deleting the NULL
	 * terminator at the end of the last argument, we fool the kernel
	 * into believing we used a setproctitle()-a-like to extend the
	 * argument space into the environment space, and thus make it use
	 * strlen() instead of its own assumed length.  In fact, we've done
	 * the exact opposite, and shrunk the command line length to just that
	 * of whatever is in argv[0].
	 *
	 * If we don't do this, and just write \0 over the rest of argv, for
	 * example; the command-line length still includes those \0s, and ps
	 * will show whitespace in their place.
	 */
	if (argc > 1) {
		char *arg_end;

		arg_end = argv[argc-1] + strlen (argv[argc-1]);
		*arg_end = ' ';
	}

	/* Become session and process group leader (should be already,
	 * but you never know what initramfs did
	 */
	setsid ();

	/* Send all logging output to syslog */
	openlog (program_name, LOG_CONS, LOG_DAEMON);
	nih_log_set_logger (nih_logger_syslog);

	/* Close any file descriptors we inherited, and open the console
	 * device instead.  Normally we reset the console, unless we're
	 * inheriting one from another init process.
	 */
	for (int i = 0; i < 3; i++)
		close (i);

	if (process_setup_console (NULL, CONSOLE_OUTPUT) < 0)
		nih_free (nih_error_get ());
	if (! (restart || rescue))
		reset_console ();

	/* Set the PATH environment variable */
	setenv ("PATH", PATH, TRUE);


	/* Reset the signal state and install the signal handler for those
	 * signals we actually want to catch; this also sets those that
	 * can be sent to us, because we're special
	 */
	if (! (restart || rescue))
		nih_signal_reset ();

	nih_signal_set_handler (SIGCHLD,  nih_signal_handler);
	nih_signal_set_handler (SIGALRM,  nih_signal_handler);
	nih_signal_set_handler (SIGHUP,   nih_signal_handler);
	nih_signal_set_handler (SIGTSTP,  nih_signal_handler);
	nih_signal_set_handler (SIGCONT,  nih_signal_handler);
	nih_signal_set_handler (SIGTERM,  nih_signal_handler);
	nih_signal_set_handler (SIGINT,   nih_signal_handler);
	nih_signal_set_handler (SIGWINCH, nih_signal_handler);
	nih_signal_set_handler (SIGPWR,   nih_signal_handler);
	nih_signal_set_handler (SIGSEGV,  crash_handler);
	nih_signal_set_handler (SIGABRT,  crash_handler);

	/* Ensure that we don't process events while paused */
	NIH_MUST (nih_signal_add_handler (NULL, SIGTSTP, stop_handler, NULL));
	NIH_MUST (nih_signal_add_handler (NULL, SIGCONT, stop_handler, NULL));

	/* Ask the kernel to send us SIGINT when control-alt-delete is
	 * pressed; generate an event with the same name.
	 */
	reboot (RB_DISABLE_CAD);
	NIH_MUST (nih_signal_add_handler (NULL, SIGINT, cad_handler, NULL));

	/* Ask the kernel to send us SIGWINCH when alt-uparrow is pressed;
	 * generate a kbdrequest event.
	 */
	if (ioctl (0, KDSIGACCEPT, SIGWINCH) == 0)
		NIH_MUST (nih_signal_add_handler (NULL, SIGWINCH,
						  kbd_handler, NULL));

	/* powstatd sends us SIGPWR when it changes /etc/powerstatus */
	NIH_MUST (nih_signal_add_handler (NULL, SIGPWR, pwr_handler, NULL));

	/* SIGTERM instructs us to re-exec ourselves */
	NIH_MUST (nih_signal_add_handler (NULL, SIGTERM,
					  (NihSignalHandler)term_handler,
					  NULL));

	/* Reap all children that die */
	NIH_MUST (nih_child_add_watch (NULL, -1, job_child_reaper, NULL));

	/* Process the event queue and check the jobs to make sure we
	 * haven't stalled, every time through the main loop */
	NIH_MUST (nih_main_loop_add_func (NULL, (NihMainLoopCb)event_poll,
					  NULL));
	NIH_MUST (nih_main_loop_add_func (NULL, (NihMainLoopCb)job_detect_stalled,
					  NULL));
	NIH_MUST (nih_main_loop_add_func (NULL, (NihMainLoopCb)job_free_deleted,
					  NULL));

	
	
	/* Generate and run the startup event or read the state from the
	 * init daemon that exec'd us
	 */
	if (!(restart || rescue)) {
		event_emit (STARTUP_EVENT, NULL, NULL);
	} else {
		sigset_t mask;

		/* We're ok to receive signals again */
		sigemptyset (&mask);
		sigprocmask (SIG_SETMASK, &mask, NULL);
	}

	/* Run through the loop at least once */
	nih_main_loop_interrupt ();
	ret = nih_main_loop ();

	return ret;
}


/**
 * reset_console:
 *
 * Set up the console flags to something sensible.  Cribbed from sysvinit,
 * initng, etc.
 **/
static void
reset_console (void)
{
	struct termios tty;

	tcgetattr (0, &tty);

	tty.c_cflag &= (CBAUD | CBAUDEX | CSIZE | CSTOPB | PARENB | PARODD);
	tty.c_cflag |= (HUPCL | CLOCAL | CREAD);

	/* Set up usual keys */
	tty.c_cc[VINTR]  = 3;   /* ^C */
	tty.c_cc[VQUIT]  = 28;  /* ^\ */
	tty.c_cc[VERASE] = 127;
	tty.c_cc[VKILL]  = 24;  /* ^X */
	tty.c_cc[VEOF]   = 4;   /* ^D */
	tty.c_cc[VTIME]  = 0;
	tty.c_cc[VMIN]   = 1;
	tty.c_cc[VSTART] = 17;  /* ^Q */
	tty.c_cc[VSTOP]  = 19;  /* ^S */
	tty.c_cc[VSUSP]  = 26;  /* ^Z */

	/* Pre and post processing */
	tty.c_iflag = (IGNPAR | ICRNL | IXON | IXANY);
	tty.c_oflag = (OPOST | ONLCR);
	tty.c_lflag = (ISIG | ICANON | ECHO | ECHOCTL | ECHOPRT | ECHOKE);

	/* Set the terminal line and flush it */
	tcsetattr (0, TCSANOW, &tty);
	tcflush (0, TCIOFLUSH);
}


/**
 * crash_handler:
 * @signum: signal number received.
 *
 * Handle receiving the SEGV or ABRT signal, usually caused by one of
 * our own mistakes.  We deal with it by dumping core in a child process
 * and just carrying on in the parent.
 *
 * This may or may not work, but the only alternative would be sigjmp()ing
 * to somewhere "safe" leaving inconsistent state everywhere (like dangling
 * lists pointers) or exec'ing another process (which we couldn't transfer
 * our state to anyway).  This just hopes that the kernel resumes on the
 * next instruction.
 **/
static void
crash_handler (int signum)
{
	NihError   *err;
	const char *loglevel = NULL;
	sigset_t    mask, oldmask;
	pid_t       pid;

	nih_assert (argv0 != NULL);

	pid = fork ();
	if (pid == 0) {
		struct sigaction act;
		struct rlimit    limit;
		sigset_t         mask;

		/* Mask out all signals */
		sigfillset (&mask);
		sigprocmask (SIG_SETMASK, &mask, NULL);

		/* Set the handler to the default so core is dumped */
		act.sa_handler = SIG_DFL;
		act.sa_flags = 0;
		sigemptyset (&act.sa_mask);
		sigaction (signum, &act, NULL);

		/* Dump in the root directory */
		chdir ("/");

		/* Don't limit the core dump size */
		limit.rlim_cur = RLIM_INFINITY;
		limit.rlim_max = RLIM_INFINITY;
		setrlimit (RLIMIT_CORE, &limit);

		/* Raise the signal again */
		raise (signum);

		/* Unmask so that we receive it */
		sigdelset (&mask, signum);
		sigprocmask (SIG_SETMASK, &mask, NULL);

		/* Wait for death */
		pause ();
		exit (0);
	} else if (pid > 0) {
		/* Wait for the core to be generated */
		waitpid (pid, NULL, 0);

		nih_fatal (_("Caught %s, core dumped"),
			   (signum == SIGSEGV
			    ? "segmentation fault" : "abort"));
	} else {
		nih_fatal (_("Caught %s, unable to dump core"),
			   (signum == SIGSEGV
			    ? "segmentation fault" : "abort"));
	}

	/* There's no point carrying on from here; our state is almost
	 * likely in tatters, so we'd just end up core-dumping again and
	 * writing over the one that contains the real bug.  We can't
	 * even re-exec properly, since we'd probably core dump while
	 * trying to transfer the state.
	 *
	 * So we just do the only thing we can, block out all signals
	 * and try and start again from scratch.
	 */
	sigfillset (&mask);
	sigprocmask (SIG_BLOCK, &mask, &oldmask);

	/* Argument list */
	if (nih_log_priority <= NIH_LOG_DEBUG) {
		loglevel = "--debug";
	} else if (nih_log_priority <= NIH_LOG_INFO) {
		loglevel = "--verbose";
	} else if (nih_log_priority >= NIH_LOG_ERROR) {
		loglevel = "--error";
	} else {
		loglevel = NULL;
	}
	execl (argv0, argv0, "--rescue", loglevel, NULL);
	nih_error_raise_system ();

	err = nih_error_get ();
	nih_fatal (_("Failed to re-execute %s: %s"),
		   "/sbin/init", err->message);
	nih_free (err);

	sigprocmask (SIG_SETMASK, &oldmask, NULL);

	/* Oh Bugger */
	exit (1);
}

/**
 * cad_handler:
 * @data: unused,
 * @signal: signal that called this handler.
 *
 * Handle having recieved the SIGINT signal, sent to us when somebody
 * presses Ctrl-Alt-Delete on the console.  We just generate a
 * ctrlaltdel event.
 **/
static void
cad_handler (void      *data,
	     NihSignal *signal)
{
	event_emit (CTRLALTDEL_EVENT, NULL, NULL);
}

/**
 * kbd_handler:
 * @data: unused,
 * @signal: signal that called this handler.
 *
 * Handle having recieved the SIGWINCH signal, sent to us when somebody
 * presses Alt-UpArrow on the console.  We just generate a
 * kbdrequest event.
 **/
static void
kbd_handler (void      *data,
	     NihSignal *signal)
{
	event_emit (KBDREQUEST_EVENT, NULL, NULL);
}

/**
 * pwr_handler:
 * @data: unused,
 * @signal: signal that called this handler.
 *
 * Handle having recieved the SIGPWR signal, sent to us when powstatd
 * changes the /etc/powerstatus file.  We just generate a
 * power-status-changed event and jobs read the file.
 **/
static void
pwr_handler (void      *data,
	     NihSignal *signal)
{
	event_emit (PWRSTATUS_EVENT, NULL, NULL);
}

/**
 * stop_handler:
 * @data: unused,
 * @signal: signal caught.
 *
 * This is called when we receive the STOP, TSTP or CONT signals; we
 * adjust the paused variable appropriately so that the event queue and
 * job stalled detection is not run.
 **/
static void
stop_handler (void      *data,
	      NihSignal *signal)
{
	nih_assert (signal != NULL);

	if (signal->signum == SIGCONT) {
		nih_info (_("Event queue resumed"));
		paused = FALSE;
	} else {
		nih_info (_("Event queue paused"));
		paused = TRUE;
	}
}


/**
 * term_handler:
 * @data: unused,
 * @signal: signal caught.
 *
 * This is called when we receive the TERM signal, which instructs us
 * to reexec ourselves.
 **/
static void
term_handler (void      *data,
	      NihSignal *signal)
{
	NihError   *err;
	const char *loglevel;
	sigset_t    mask, oldmask;

	nih_assert (argv0 != NULL);
	nih_assert (signal != NULL);

	nih_warn (_("Re-executing %s"), argv0);

	/* Block signals while we work.  We're the last signal handler
	 * installed so this should mean that they're all handled now.
	 *
	 * The child must make sure that it unblocks these again when
	 * it's ready.
	 */
	sigfillset (&mask);
	sigprocmask (SIG_BLOCK, &mask, &oldmask);

	/* Argument list */
	if (nih_log_priority <= NIH_LOG_DEBUG) {
		loglevel = "--debug";
	} else if (nih_log_priority <= NIH_LOG_INFO) {
		loglevel = "--verbose";
	} else if (nih_log_priority >= NIH_LOG_ERROR) {
		loglevel = "--error";
	} else {
		loglevel = NULL;
	}
	execl (argv0, argv0, "--restart", loglevel, NULL);
	nih_error_raise_system ();

	err = nih_error_get ();
	nih_error (_("Failed to re-execute %s: %s"), argv0, err->message);
	nih_free (err);

	sigprocmask (SIG_SETMASK, &oldmask, NULL);
}