From f720781671c9f5421fb8101dd3bcf7b56efca131 Mon Sep 17 00:00:00 2001 From: Karel Zak Date: Mon, 20 Aug 2018 14:56:08 +0200 Subject: [PATCH 178/178] sulogin: backport RHEL-8 version The new version is more robust and it does not use mmap()-ed shared memory between sulogins instances. It also provides some fallbacks for s390 machines. Addresses: https://bugzilla.redhat.com/show_bug.cgi?id=1616264 Signed-off-by: Karel Zak --- configure.ac | 1 + include/c.h | 4 + login-utils/sulogin-consoles.c | 116 +++++++++++++------ login-utils/sulogin.c | 253 +++++++++++++++++++++++++++-------------- 4 files changed, 252 insertions(+), 122 deletions(-) diff --git a/configure.ac b/configure.ac index d561e01d0..3f07df495 100644 --- a/configure.ac +++ b/configure.ac @@ -218,6 +218,7 @@ AC_CHECK_HEADERS([ \ sys/prctl.h \ sys/queue.h \ sys/resource.h \ + sys/sysmacros.h \ sys/socket.h \ sys/sockio.h \ sys/stat.h \ diff --git a/include/c.h b/include/c.h index 124035ea5..51d439297 100644 --- a/include/c.h +++ b/include/c.h @@ -19,6 +19,10 @@ # include #endif +#ifdef HAVE_SYS_SYSMACROS_H +# include /* for major, minor */ +#endif + #ifndef HAVE_USLEEP # include #endif diff --git a/login-utils/sulogin-consoles.c b/login-utils/sulogin-consoles.c index 07af33a6d..2c0eed3a4 100644 --- a/login-utils/sulogin-consoles.c +++ b/login-utils/sulogin-consoles.c @@ -36,8 +36,9 @@ # include # include #endif -#include #include +#include +#include #include #ifdef USE_SULOGIN_EMERGENCY_MOUNT @@ -74,7 +75,7 @@ static int consoles_debug; } while (0) static inline void __attribute__ ((__format__ (__printf__, 1, 2))) -dbgprint(const char *mesg, ...) +dbgprint(const char * const mesg, ...) { va_list ap; va_start(ap, mesg); @@ -112,7 +113,7 @@ void emergency_do_mounts(void) } if (stat("/", &rt) != 0) { - warn("can not get file status of root file system\n"); + warn("cannot get file status of root file system\n"); return; } @@ -150,17 +151,19 @@ void emergency_do_mounts(void) { } * the caller has to free the result */ static __attribute__((__nonnull__)) -char *oneline(const char *file) +char *oneline(const char * const file) { FILE *fp; char *ret = NULL; - size_t len = 0; + size_t dummy = 0; + ssize_t len; DBG(dbgprint("reading %s", file)); - if (!(fp = fopen(file, "re"))) + if (!(fp = fopen(file, "r" UL_CLOEXECSTR))) return NULL; - if (getline(&ret, &len, fp) >= 0) { + len = getline(&ret, &dummy, fp); + if (len >= 0) { char *nl; if (len) @@ -179,7 +182,7 @@ char *oneline(const char *file) * /sys/class/tty, the caller has to free the result. */ static __attribute__((__malloc__)) -char *actattr(const char *tty) +char *actattr(const char * const tty) { char *ret, *path; @@ -198,7 +201,7 @@ char *actattr(const char *tty) * /sys/class/tty. */ static -dev_t devattr(const char *tty) +dev_t devattr(const char * const tty) { dev_t dev = 0; char *path, *value; @@ -223,42 +226,77 @@ dev_t devattr(const char *tty) #endif /* __linux__ */ /* - * Search below /dev for the characer device in `dev_t comparedev' variable. + * Search below /dev for the character device in `dev_t comparedev' variable. + * Note that realpath(3) is used here to avoid not existent devices due the + * strdup(3) used in our canonicalize_path()! */ static #ifdef __GNUC__ __attribute__((__nonnull__,__malloc__,__hot__)) #endif -char* scandev(DIR *dir, dev_t comparedev) +char* scandev(DIR *dir, const dev_t comparedev) { + char path[PATH_MAX]; char *name = NULL; - struct dirent *dent; - int fd; + const struct dirent *dent; + int len, fd; DBG(dbgprint("scanning /dev for %u:%u", major(comparedev), minor(comparedev))); + /* + * Try udev links on character devices first. + */ + if ((len = snprintf(path, sizeof(path), + "/dev/char/%u:%u", major(comparedev), minor(comparedev))) > 0 && + (size_t)len < sizeof(path)) { + + name = realpath(path, NULL); + if (name) + goto out; + } + fd = dirfd(dir); rewinddir(dir); while ((dent = readdir(dir))) { - char path[PATH_MAX]; struct stat st; + +#ifdef _DIRENT_HAVE_D_TYPE + if (dent->d_type != DT_UNKNOWN && dent->d_type != DT_CHR) + continue; +#endif if (fstatat(fd, dent->d_name, &st, 0) < 0) continue; if (!S_ISCHR(st.st_mode)) continue; if (comparedev != st.st_rdev) continue; - if ((size_t)snprintf(path, sizeof(path), "/dev/%s", dent->d_name) >= sizeof(path)) + if ((len = snprintf(path, sizeof(path), "/dev/%s", dent->d_name)) < 0 || + (size_t)len >= sizeof(path)) continue; -#ifdef USE_SULOGIN_EMERGENCY_MOUNT - if (emergency_flags & MNT_DEVTMPFS) - mknod(path, S_IFCHR|S_IRUSR|S_IWUSR, comparedev); -#endif - name = canonicalize_path(path); - break; + name = realpath(path, NULL); + if (name) + goto out; } +#ifdef USE_SULOGIN_EMERGENCY_MOUNT + /* + * There was no /dev mounted hence and no device was found hence we create our own. + */ + if (!name && (emergency_flags & MNT_DEVTMPFS)) { + + if ((len = snprintf(path, sizeof(path), + "/dev/tmp-%u:%u", major(comparedev), minor(comparedev))) < 0 || + (size_t)len >= sizeof(path)) + goto out; + + if (mknod(path, S_IFCHR|S_IRUSR|S_IWUSR, comparedev) < 0 && errno != EEXIST) + goto out; + + name = realpath(path, NULL); + } +#endif +out: return name; } @@ -273,12 +311,12 @@ char* scandev(DIR *dir, dev_t comparedev) */ static #ifdef __GNUC__ -__attribute__((__nonnull__,__hot__)) +__attribute__((__hot__)) #endif -int append_console(struct list_head *consoles, const char *name) +int append_console(struct list_head *consoles, const char * const name) { struct console *restrict tail; - struct console *last = NULL; + const struct console *last = NULL; DBG(dbgprint("appenging %s", name)); @@ -300,7 +338,7 @@ int append_console(struct list_head *consoles, const char *name) tail->flags = 0; tail->fd = -1; tail->id = last ? last->id + 1 : 0; - tail->pid = 0; + tail->pid = -1; memset(&tail->tio, 0, sizeof(tail->tio)); return 0; @@ -319,11 +357,11 @@ static int detect_consoles_from_proc(struct list_head *consoles) char fbuf[16 + 1]; DIR *dir = NULL; FILE *fc = NULL; - int maj, min, rc = 1; + int maj, min, rc = 1, matches; DBG(dbgprint("trying /proc")); - fc = fopen("/proc/consoles", "re"); + fc = fopen("/proc/consoles", "r" UL_CLOEXECSTR); if (!fc) { rc = 2; goto done; @@ -332,10 +370,12 @@ static int detect_consoles_from_proc(struct list_head *consoles) if (!dir) goto done; - while (fscanf(fc, "%*s %*s (%16[^)]) %d:%d", fbuf, &maj, &min) == 3) { + while ((matches = fscanf(fc, "%*s %*s (%16[^)]) %d:%d", fbuf, &maj, &min)) >= 1) { char *name; dev_t comparedev; + if (matches != 3) + continue; if (!strchr(fbuf, 'E')) continue; comparedev = makedev(maj, min); @@ -509,7 +549,7 @@ done: #ifdef TIOCGDEV static int detect_consoles_from_tiocgdev(struct list_head *consoles, - int fallback, + const int fallback, const char *device) { unsigned int devnum; @@ -579,7 +619,7 @@ done: * Returns 1 if stdout and stderr should be reconnected and 0 * otherwise or less than zero on error. */ -int detect_consoles(const char *device, int fallback, struct list_head *consoles) +int detect_consoles(const char *device, const int fallback, struct list_head *consoles) { int fd, reconnect = 0, rc; dev_t comparedev = 0; @@ -587,7 +627,7 @@ int detect_consoles(const char *device, int fallback, struct list_head *consoles consoles_debug = getenv("CONSOLES_DEBUG") ? 1 : 0; if (!device || !*device) - fd = dup(fallback); + fd = fallback >= 0 ? dup(fallback) : - 1; else { fd = open(device, O_RDWR|O_NONBLOCK|O_NOCTTY|O_CLOEXEC); reconnect = 1; @@ -602,6 +642,14 @@ int detect_consoles(const char *device, int fallback, struct list_head *consoles struct stat st; #ifdef TIOCGDEV unsigned int devnum; +#endif +#ifdef __GNU__ + /* + * The Hurd always gives st_rdev as 0, which causes this + * method to select the first terminal it finds. + */ + close(fd); + goto fallback; #endif DBG(dbgprint("trying device/fallback file descriptor")); @@ -670,7 +718,7 @@ int detect_consoles(const char *device, int fallback, struct list_head *consoles #ifdef __linux__ console: /* - * Detection of devices used for Linux system consolei using + * Detection of devices used for Linux system console using * the /proc/consoles API with kernel 2.6.38 and higher. */ rc = detect_consoles_from_proc(consoles); @@ -754,8 +802,7 @@ int main(int argc, char *argv[]) { char *name = NULL; int fd, re; - LIST_HEAD(consoles); - struct list_head *p; + struct list_head *p, consoles; if (argc == 2) { name = argv[1]; @@ -768,6 +815,7 @@ int main(int argc, char *argv[]) if (!name) errx(EXIT_FAILURE, "usage: %s []\n", program_invocation_short_name); + INIT_LIST_HEAD(&consoles); re = detect_consoles(name, fd, &consoles); list_for_each(p, &consoles) { diff --git a/login-utils/sulogin.c b/login-utils/sulogin.c index dd73a1c50..4620ed2da 100644 --- a/login-utils/sulogin.c +++ b/login-utils/sulogin.c @@ -56,8 +56,12 @@ #include "c.h" #include "closestream.h" +#include "env.h" #include "nls.h" #include "pathnames.h" +#ifdef USE_PLYMOUTH_SUPPORT +# include "plymouth-ctrl.h" +#endif #include "strutils.h" #include "ttyutils.h" #include "sulogin-consoles.h" @@ -66,13 +70,12 @@ static unsigned int timeout; static int profile; static volatile uint32_t openfd; /* Remember higher file descriptors */ -static volatile uint32_t *usemask; -struct sigaction saved_sigint; -struct sigaction saved_sigtstp; -struct sigaction saved_sigquit; -struct sigaction saved_sighup; -struct sigaction saved_sigchld; +static struct sigaction saved_sigint; +static struct sigaction saved_sigtstp; +static struct sigaction saved_sigquit; +static struct sigaction saved_sighup; +static struct sigaction saved_sigchld; static volatile sig_atomic_t alarm_rised; static volatile sig_atomic_t sigchild; @@ -81,7 +84,12 @@ static volatile sig_atomic_t sigchild; # define IUCLC 0 #endif -static int locked_account_password(const char *passwd) +#ifndef WEXITED +# warning "WEXITED is missing, sulogin may not work as expected" +# define WEXITED 0 +#endif + +static int locked_account_password(const char * const passwd) { if (passwd && (*passwd == '*' || *passwd == '!')) return 1; @@ -93,10 +101,29 @@ static int locked_account_password(const char *passwd) */ static void tcinit(struct console *con) { - int mode = 0, flags = 0; + int flags = 0, mode = 0; struct termios *tio = &con->tio; - int fd = con->fd; - + const int fd = con->fd; +#ifdef USE_PLYMOUTH_SUPPORT + struct termios lock; + int i = (plymouth_command(MAGIC_PING)) ? PLYMOUTH_TERMIOS_FLAGS_DELAY : 0; + if (i) + plymouth_command(MAGIC_QUIT); + while (i-- > 0) { + /* + * With plymouth the termios flags become changed after this + * function had changed the termios. + */ + memset(&lock, 0, sizeof(struct termios)); + if (ioctl(fd, TIOCGLCKTRMIOS, &lock) < 0) + break; + if (!lock.c_iflag && !lock.c_oflag && !lock.c_cflag && !lock.c_lflag) + break; + sleep(1); + } + memset(&lock, 0, sizeof(struct termios)); + ioctl(fd, TIOCSLCKTRMIOS, &lock); +#endif errno = 0; if (tcgetattr(fd, tio) < 0) { @@ -189,20 +216,23 @@ setattr: */ static void tcfinal(struct console *con) { - struct termios *tio; - int fd; + struct termios *tio = &con->tio; + const int fd = con->fd; if ((con->flags & CON_SERIAL) == 0) { - setenv("TERM", "linux", 1); + xsetenv("TERM", "linux", 1); return; } - if (con->flags & CON_NOTTY) + if (con->flags & CON_NOTTY) { + xsetenv("TERM", "dumb", 1); return; + } - setenv("TERM", "vt102", 1); - tio = &con->tio; - fd = con->fd; - +#if defined (__s390__) || defined (__s390x__) + xsetenv("TERM", "dumb", 1); +#else + xsetenv("TERM", "vt102", 1); +#endif tio->c_iflag |= (IXON | IXOFF); tio->c_lflag |= (ICANON | ISIG | ECHO|ECHOE|ECHOK|ECHOKE); tio->c_oflag |= OPOST; @@ -237,11 +267,11 @@ static void tcfinal(struct console *con) break; case 1: /* odd parity */ tio->c_cflag |= PARODD; - /* fall through */ + /* fallthrough */ case 2: /* even parity */ tio->c_cflag |= PARENB; tio->c_iflag |= (INPCK | ISTRIP); - /* fall through */ + /* fallthrough */ case (1 | 2): /* no parity bit */ tio->c_cflag &= ~CSIZE; tio->c_cflag |= CS7; @@ -466,7 +496,7 @@ static struct passwd *getrootpwent(int try_manually) warnx(_("%s: no entry for root"), _PATH_SHADOW_PASSWD); *pwd.pw_passwd = '\0'; } - /* locked accont passwords are valid too */ + /* locked account passwords are valid too */ if (!locked_account_password(pwd.pw_passwd) && !valid(pwd.pw_passwd)) { warnx(_("%s: root password garbled"), _PATH_SHADOW_PASSWD); *pwd.pw_passwd = '\0'; @@ -524,22 +554,17 @@ err: */ static void setup(struct console *con) { - pid_t pid, pgrp, ppgrp, ttypgrp; - int fd; + int fd = con->fd; + const pid_t pid = getpid(), pgrp = getpgid(0), ppgrp = + getpgid(getppid()), ttypgrp = tcgetpgrp(fd); if (con->flags & CON_NOTTY) return; - fd = con->fd; /* * Only go through this trouble if the new * tty doesn't fall in this process group. */ - pid = getpid(); - pgrp = getpgid(0); - ppgrp = getpgid(getppid()); - ttypgrp = tcgetpgrp(fd); - if (pgrp != ttypgrp && ppgrp != ttypgrp) { if (pid != getsid(0)) { if (pid == getpgid(0)) @@ -575,21 +600,20 @@ static void setup(struct console *con) * Ask for the password. Note that there is no default timeout as we normally * skip this during boot. */ -static char *getpasswd(struct console *con) +static const char *getpasswd(struct console *con) { struct sigaction sa; struct termios tty; static char pass[128], *ptr; struct chardata *cp; - char *ret = pass; + const char *ret = pass; unsigned char tc; char c, ascval; int eightbit; - int fd; + const int fd = con->fd; if (con->flags & CON_NOTTY) goto out; - fd = con->fd; cp = &con->cp; tty = con->tio; @@ -597,6 +621,7 @@ static char *getpasswd(struct console *con) tty.c_lflag &= ~(ECHO|ECHOE|ECHOK|ECHONL|TOSTOP|ISIG); tc = (tcsetattr(fd, TCSAFLUSH, &tty) == 0); + sigemptyset(&sa.sa_mask); sa.sa_handler = alrm_handler; sa.sa_flags = 0; sigaction(SIGALRM, &sa, NULL); @@ -615,7 +640,7 @@ static char *getpasswd(struct console *con) ret = NULL; goto quit; } - usleep(1000); + usleep(250000); continue; } ret = (char*)0; @@ -627,7 +652,7 @@ static char *getpasswd(struct console *con) case ENOENT: break; default: - warn(_("%s: read failed"), con->tty); + warn(_("cannot read %s"), con->tty); break; } goto quit; @@ -694,8 +719,8 @@ static void sushell(struct passwd *pwd) { char shell[PATH_MAX]; char home[PATH_MAX]; - char *p; - char *su_shell; + char const *p; + char const *su_shell; /* * Set directory and shell. @@ -731,16 +756,16 @@ static void sushell(struct passwd *pwd) if (getcwd(home, sizeof(home)) == NULL) strcpy(home, "/"); - setenv("HOME", home, 1); - setenv("LOGNAME", "root", 1); - setenv("USER", "root", 1); + xsetenv("HOME", home, 1); + xsetenv("LOGNAME", "root", 1); + xsetenv("USER", "root", 1); if (!profile) - setenv("SHLVL","0",1); + xsetenv("SHLVL","0",1); /* * Try to execute a shell. */ - setenv("SHELL", su_shell, 1); + xsetenv("SHELL", su_shell, 1); unmask_signal(SIGINT, &saved_sigint); unmask_signal(SIGTSTP, &saved_sigtstp); unmask_signal(SIGQUIT, &saved_sigquit); @@ -765,17 +790,21 @@ static void sushell(struct passwd *pwd) execl(su_shell, shell, NULL); warn(_("failed to execute %s"), su_shell); - setenv("SHELL", "/bin/sh", 1); + xsetenv("SHELL", "/bin/sh", 1); execl("/bin/sh", profile ? "-sh" : "sh", NULL); warn(_("failed to execute %s"), "/bin/sh"); } -static void usage(FILE *out) +static void usage(void) { + FILE *out = stdout; fputs(USAGE_HEADER, out); fprintf(out, _( " %s [options] [tty device]\n"), program_invocation_short_name); + fputs(USAGE_SEPARATOR, out); + fputs(_("Single-user login.\n"), out); + fputs(USAGE_OPTIONS, out); fputs(_(" -p, --login-shell start a login shell\n" " -t, --timeout max time to wait for a password (default: no limit)\n" @@ -783,32 +812,35 @@ static void usage(FILE *out) out); fputs(USAGE_SEPARATOR, out); - fputs(USAGE_HELP, out); - fputs(USAGE_VERSION, out); - fprintf(out, USAGE_MAN_TAIL("sulogin(8)")); + printf(USAGE_HELP_OPTIONS(26)); + printf(USAGE_MAN_TAIL("sulogin(8)")); } int main(int argc, char **argv) { - LIST_HEAD(consoles); - struct list_head *ptr; + struct list_head *ptr, consoles; struct console *con; char *tty = NULL; struct passwd *pwd; - int c, status = 0; - int reconnect = 0; + const struct timespec sigwait = { .tv_sec = 0, .tv_nsec = 50000000 }; + siginfo_t status = { 0 }; + sigset_t set; + int c, reconnect = 0; int opt_e = 0; + int wait = 0; pid_t pid; static const struct option longopts[] = { - { "login-shell", 0, 0, 'p' }, - { "timeout", 1, 0, 't' }, - { "force", 0, 0, 'e' }, - { "help", 0, 0, 'h' }, - { "version", 0, 0, 'V' }, - { NULL, 0, 0, 0 } + { "login-shell", no_argument, NULL, 'p' }, + { "timeout", required_argument, NULL, 't' }, + { "force", no_argument, NULL, 'e' }, + { "help", no_argument, NULL, 'h' }, + { "version", no_argument, NULL, 'V' }, + { NULL, 0, NULL, 0 } }; + INIT_LIST_HEAD(&consoles); + /* * If we are init we need to set up a own session. */ @@ -840,17 +872,16 @@ int main(int argc, char **argv) printf(UTIL_LINUX_VERSION); return EXIT_SUCCESS; case 'h': - usage(stdout); + usage(); return EXIT_SUCCESS; default: - usage(stderr); - /* Do not exit! */ + /* Do not exit! getopt prints a warning. */ break; } } if (geteuid() != 0) - errx(EXIT_FAILURE, _("only root can run this program.")); + errx(EXIT_FAILURE, _("only superuser can run this program")); mask_signal(SIGQUIT, SIG_IGN, &saved_sigquit); mask_signal(SIGTSTP, SIG_IGN, &saved_sigtstp); @@ -877,7 +908,7 @@ int main(int argc, char **argv) reconnect = detect_consoles(tty, STDIN_FILENO, &consoles); /* - * If previous stdin was not the speified tty and therefore reconnected + * If previous stdin was not the specified tty and therefore reconnected * to the specified tty also reconnect stdout and stderr. */ if (reconnect) { @@ -900,7 +931,7 @@ int main(int argc, char **argv) * Get the root password. */ if ((pwd = getrootpwent(opt_e)) == NULL) { - warnx(_("cannot open password database.")); + warnx(_("cannot open password database")); sleep(2); return EXIT_FAILURE; } @@ -923,9 +954,6 @@ int main(int argc, char **argv) tcinit(con); } ptr = (&consoles)->next; - usemask = (uint32_t*) mmap(NULL, sizeof(uint32_t), - PROT_READ|PROT_WRITE, - MAP_ANONYMOUS|MAP_SHARED, -1, 0); if (ptr->next == &consoles) { con = list_entry(ptr, struct console, entry); @@ -942,7 +970,6 @@ int main(int argc, char **argv) switch ((con->pid = fork())) { case 0: mask_signal(SIGCHLD, SIG_DFL, NULL); - /* fall through */ nofork: setup(con); while (1) { @@ -971,9 +998,7 @@ int main(int argc, char **argv) } if (doshell) { - *usemask |= (1<id); sushell(pwd); - *usemask &= ~(1<id); failed++; } @@ -982,7 +1007,7 @@ int main(int argc, char **argv) mask_signal(SIGINT, SIG_IGN, &saved_sigint); if (failed) { - fprintf(stderr, _("Can not execute su shell\n\n")); + fprintf(stderr, _("cannot execute su shell\n\n")); break; } fprintf(stderr, _("Login incorrect\n\n")); @@ -997,7 +1022,7 @@ int main(int argc, char **argv) exit(0); case -1: warn(_("fork failed")); - /* fall through */ + /* fallthrough */ default: break; } @@ -1006,28 +1031,80 @@ int main(int argc, char **argv) } while (ptr != &consoles); - while ((pid = wait(&status))) { - if (errno == ECHILD) + do { + int ret; + + status.si_pid = 0; + ret = waitid(P_ALL, 0, &status, WEXITED); + + if (ret == 0) break; - if (pid < 0) - continue; - list_for_each(ptr, &consoles) { - con = list_entry(ptr, struct console, entry); - if (con->pid == pid) { - *usemask &= ~(1<id); + if (ret < 0) { + if (errno == ECHILD) + break; + if (errno == EINTR) continue; - } - if (kill(con->pid, 0) < 0) { - *usemask &= ~(1<id); + } + + errx(EXIT_FAILURE, _("cannot wait on su shell\n\n")); + + } while (1); + + list_for_each(ptr, &consoles) { + con = list_entry(ptr, struct console, entry); + + if (con->fd < 0) + continue; + if (con->pid < 0) + continue; + if (con->pid == status.si_pid) + con->pid = -1; + else { + kill(con->pid, SIGTERM); + wait++; + } + } + + sigemptyset(&set); + sigaddset(&set, SIGCHLD); + + do { + int signum, ret; + + if (!wait) + break; + + status.si_pid = 0; + ret = waitid(P_ALL, 0, &status, WEXITED|WNOHANG); + + if (ret < 0) { + if (errno == ECHILD) + break; + if (errno == EINTR) continue; + } + + if (!ret && status.si_pid > 0) { + list_for_each(ptr, &consoles) { + con = list_entry(ptr, struct console, entry); + + if (con->fd < 0) + continue; + if (con->pid < 0) + continue; + if (con->pid == status.si_pid) { + con->pid = -1; + wait--; + } } - if (*usemask & (1<id)) - continue; - kill(con->pid, SIGHUP); - usleep(5000); - kill(con->pid, SIGKILL); + continue; } - } + + signum = sigtimedwait(&set, NULL, &sigwait); + if (signum != SIGCHLD && signum < 0 && errno == EAGAIN) + break; + + } while (1); mask_signal(SIGCHLD, SIG_DFL, NULL); return EXIT_SUCCESS; -- 2.14.4