From 2e2fd2446239e9e249ae0d92adcd2efb7ad332e1 Mon Sep 17 00:00:00 2001 From: Bron Gondwana Date: Wed, 24 Dec 2008 23:28:48 +1100 Subject: [PATCH] Clean Shutdown The instructions for Cyrus say to send a signal to master to shut the system down. This generally means a SIGTERM. When master gets a SIGTERM is sends SIGTERM to all children, who don't trap it and exit instantly. Fine if you're never in a "critical section" Unfortunately both replication and fast rename create critical sections. Fast rename thanks to split meta (two renames) and replication because if you have successfully committed a message to the index file but not yet logged the replication event, then it could fail to be copied. In a system as big as fastmail.fm's, this means almost every shutdown causes a couple of messages to fail to sync. Then we bring up the replica, get two messages with the same UID, hilarity ensues. This patch adds a SIGQUIT handler to master, which sends SIGQUIT to children then doesn't exit until all children have. The children already had a SIGQUIT handler which waited for signals_poll() to be checked. The patch also adds a few more signals_poll() in likely places. Some documentation has been added on using this, though I feel it could be made a bit more prominent since clean shutdown is much safer for data integrity. In particular, distribution maintainers should be making their system init scripts use it. --- doc/install-configure.html | 36 +++++++++++++ imap/Makefile.in | 2 +- imap/sync_client.c | 2 + lib/Makefile.in | 8 ++-- lib/prot.c | 15 +++--- lib/signals.c | 120 ++++++++++++++++++++++++++++++++++++++++++++ lib/signals.h | 54 ++++++++++++++++++++ master/master.c | 120 +++++++++++++++++++++++++++++-------------- master/service-thread.c | 4 +- master/service.c | 5 +- 10 files changed, 312 insertions(+), 54 deletions(-) create mode 100644 lib/signals.c create mode 100644 lib/signals.h diff --git a/doc/install-configure.html b/doc/install-configure.html index 2b4b567..62e1bed 100644 --- a/doc/install-configure.html +++ b/doc/install-configure.html @@ -242,6 +242,42 @@ process by hand:

  • Monitor the progress of the master process by examining the imapd.log file. It should never exit by itself, but you can shut down the mail system by sending it a signal with kill. + +

  • Clean Shutdown - you can shut the master process down +cleanly by sending it a SIGQUIT rather than SIGTERM signal. This +will cause the master process to send SIGQUIT to all its children and +then wait for them to finish cleanly. This avoids issues like a +message being appended by lmtpd but the sync_log record never being +written. + +

    Since a clean shutdown may never finish if a child process is stuck +for some reason the recommended approach is to send a SIGQUIT then loop +on the master process sending a signal 0 every second until either the +master process has gone away or a suitable time has expired (maybe 10 +seconds). You can then send a SIGTERM if the process still exists. + +

    At FastMail the following snippet of perl is used (warning: Linux +specific signal numbers - check your own system before using this): + +

    +    my $pid = `cat $PIDFILE`;
    +    chomp($pid);
    +    print "Trying nice shutdown - killing $pid with SIGQUIT\n";
    +    kill 3, $pid;
    +    foreach my $num (1..10) {
    +      if (kill 0, $pid) {
    +        print "Not dead yet after $num seconds\n";
    +        sleep 1;
    +      }
    +      else {
    +        last;
    +      }
    +    }
    +    if (kill 0, $pid) {
    +      print "No more Mr. Nice Guy - killing $pid with SIGTERM\n";
    +      kill 15, $pid;
    +    }
    +

    Configuring the Mail Transfer Agent

    diff --git a/imap/Makefile.in b/imap/Makefile.in index 11fc4b5..69fd3db 100644 --- a/imap/Makefile.in +++ b/imap/Makefile.in @@ -98,7 +98,7 @@ service_path = @service_path@ LOBJS= append.o mailbox.o mboxlist.o mupdate-client.o mboxname.o message.o \ global.o imap_err.o mupdate_err.o proc.o setproctitle.o \ - convert_code.o duplicate.o saslclient.o saslserver.o signals.o \ + convert_code.o duplicate.o saslclient.o saslserver.o ../lib/signals.o \ annotate.o search_engines.o squat.o squat_internal.o mbdump.o \ imapparse.o telemetry.o user.o notify.o idle.o quota_db.o \ sync_log.o $(SEEN) mboxkey.o backend.o tls.o message_guid.o \ diff --git a/imap/sync_client.c b/imap/sync_client.c index a2129e4..2877e26 100644 --- a/imap/sync_client.c +++ b/imap/sync_client.c @@ -3301,6 +3301,8 @@ int do_daemon_work(const char *sync_log_file, const char *sync_shutdown_file, while (1) { single_start = time(NULL); + signals_poll(); + /* Check for shutdown file */ if (sync_shutdown_file && !stat(sync_shutdown_file, &sbuf)) { unlink(sync_shutdown_file); diff --git a/lib/Makefile.in b/lib/Makefile.in index 34698fd..07f8e59 100644 --- a/lib/Makefile.in +++ b/lib/Makefile.in @@ -83,7 +83,7 @@ LIBCYR_HDRS = $(srcdir)/acl.h $(srcdir)/assert.h $(srcdir)/auth.h \ $(srcdir)/lock.h $(srcdir)/map.h $(srcdir)/mkgmtime.h \ $(srcdir)/nonblock.h $(srcdir)/parseaddr.h $(srcdir)/prot.h \ $(srcdir)/retry.h $(srcdir)/sysexits.h $(srcdir)/strhash.h \ - $(srcdir)/lsort.h $(srcdir)/stristr.h \ + $(srcdir)/lsort.h $(srcdir)/stristr.h $(srcdir)/signals.h \ $(srcdir)/util.h $(srcdir)/xstrlcpy.h $(srcdir)/xstrlcat.h \ $(srcdir)/xmalloc.h $(srcdir)/imapurl.h \ $(srcdir)/cyrusdb.h $(srcdir)/iptostring.h $(srcdir)/rfc822date.h \ @@ -92,7 +92,7 @@ LIBCYR_HDRS = $(srcdir)/acl.h $(srcdir)/assert.h $(srcdir)/auth.h \ LIBCYR_OBJS = acl.o bsearch.o charset.o glob.o retry.o util.o \ libcyr_cfg.o mkgmtime.o prot.o parseaddr.o imclient.o imparse.o \ - lsort.o stristr.o rfc822date.o cyrusdb.o strhash.o \ + lsort.o stristr.o rfc822date.o signals.o cyrusdb.o strhash.o \ chartable.o imapurl.o nonblock_@WITH_NONBLOCK@.o lock_@WITH_LOCK@.o \ gmtoff_@WITH_GMTOFF@.o map_@WITH_MAP@.o $(ACL) $(AUTH) \ @LIBOBJS@ @CYRUSDB_OBJS@ @MD5OBJ@ \ @@ -102,9 +102,9 @@ LIBCYR_OBJS = acl.o bsearch.o charset.o glob.o retry.o util.o \ LIBCYRM_HDRS = $(srcdir)/hash.h $(srcdir)/mpool.h $(srcdir)/xmalloc.h \ $(srcdir)/xstrlcat.h $(srcdir)/xstrlcpy.h $(srcdir)/util.h \ $(srcdir)/strhash.h $(srcdir)/libconfig.h $(srcdir)/assert.h \ - imapopts.h + imapopts.h signals.h LIBCYRM_OBJS = libconfig.o imapopts.o hash.o mpool.o xmalloc.o strhash.o \ - xstrlcat.o xstrlcpy.o assert.o util.o @IPV6_OBJS@ + xstrlcat.o xstrlcpy.o assert.o util.o signals.o @IPV6_OBJS@ all: $(BUILTSOURCES) libcyrus_min.a libcyrus.a diff --git a/lib/prot.c b/lib/prot.c index c31c725..63a0098 100644 --- a/lib/prot.c +++ b/lib/prot.c @@ -69,6 +69,7 @@ #include "map.h" #include "nonblock.h" #include "prot.h" +#include "signals.h" #include "util.h" #include "xmalloc.h" @@ -586,11 +587,11 @@ int prot_fill(struct protstream *s) r = select(s->fd + 1, &rfds, (fd_set *)0, (fd_set *)0, &timeout); now = time(NULL); - } while ((r == 0 || (r == -1 && errno == EINTR)) && + } while ((r == 0 || (r == -1 && errno == EINTR && !signals_poll())) && (now < read_timeout)); if ((r == 0) || /* ignore EINTR if we've timed out */ - (r == -1 && errno == EINTR && now >= read_timeout)) { + (r == -1 && errno == EINTR && !signals_poll() && now >= read_timeout)) { if (!s->dontblock) { s->error = xstrdup("idle for too long"); return EOF; @@ -620,7 +621,7 @@ int prot_fill(struct protstream *s) #else /* HAVE_SSL */ n = read(s->fd, s->buf, PROT_BUFSIZE); #endif /* HAVE_SSL */ - } while (n == -1 && errno == EINTR); + } while (n == -1 && errno == EINTR && !signals_poll()); if (n <= 0) { if (n) s->error = xstrdup(strerror(errno)); @@ -691,7 +692,7 @@ int prot_fill(struct protstream *s) ptr = s->ptr; do { n = write(s->logfd, ptr, left); - if (n == -1 && errno != EINTR) { + if (n == -1 && (errno != EINTR || signals_poll())) { break; } if (n > 0) { @@ -732,7 +733,7 @@ static void prot_flush_log(struct protstream *s) do { n = write(s->logfd, ptr, left); - if (n == -1 && errno != EINTR) { + if (n == -1 && (errno != EINTR || signals_poll())) { break; } if (n > 0) { @@ -838,7 +839,7 @@ static int prot_flush_writebuffer(struct protstream *s, #else /* HAVE_SSL */ n = write(s->fd, buf, len); #endif /* HAVE_SSL */ - } while (n == -1 && errno == EINTR); + } while (n == -1 && errno == EINTR && !signals_poll()); return n; } @@ -993,7 +994,7 @@ int prot_flush_internal(struct protstream *s, int force) do { n = write(s->big_buffer, ptr, left); - if (n == -1 && errno != EINTR) { + if (n == -1 && (errno != EINTR || signals_poll())) { syslog(LOG_ERR, "write to protstream buffer failed: %s", strerror(errno)); diff --git a/lib/signals.c b/lib/signals.c new file mode 100644 index 0000000..14041bf --- /dev/null +++ b/lib/signals.c @@ -0,0 +1,120 @@ +/* signals.c -- signal handling functions to allow clean shutdown + * + * Copyright (c) 1994-2008 Carnegie Mellon University. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in + * the documentation and/or other materials provided with the + * distribution. + * + * 3. The name "Carnegie Mellon University" must not be used to + * endorse or promote products derived from this software without + * prior written permission. For permission or any legal + * details, please contact + * Carnegie Mellon University + * Center for Technology Transfer and Enterprise Creation + * 4615 Forbes Avenue + * Suite 302 + * Pittsburgh, PA 15213 + * (412) 268-7393, fax: (412) 268-7395 + * innovation@andrew.cmu.edu + * + * 4. Redistributions of any form whatsoever must retain the following + * acknowledgment: + * "This product includes software developed by Computing Services + * at Carnegie Mellon University (http://www.cmu.edu/computing/)." + * + * CARNEGIE MELLON UNIVERSITY DISCLAIMS ALL WARRANTIES WITH REGARD TO + * THIS SOFTWARE, INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY + * AND FITNESS, IN NO EVENT SHALL CARNEGIE MELLON UNIVERSITY BE LIABLE + * FOR ANY SPECIAL, INDIRECT OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN + * AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING + * OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + * + * $Id: signals.c,v 1.15 2008/03/24 17:09:19 murch Exp $ + */ + +#include + +#include +#include +#include + +#include "signals.h" +#include "xmalloc.h" +#include "exitcodes.h" + +static int gotsignal = 0; + +static void sighandler(int sig) +{ + /* syslog(LOG_DEBUG, "got signal %d", sig); */ + gotsignal = sig; +} + +static const int catch[] = { SIGHUP, SIGINT, 0 }; + +void signals_add_handlers(int alarm) +{ + struct sigaction action; + int i; + + sigemptyset(&action.sa_mask); + + action.sa_flags = 0; +#ifdef SA_RESETHAND + action.sa_flags |= SA_RESETHAND; +#endif + + action.sa_handler = sighandler; + + /* SIGALRM used as a syscall timeout, so we don't set SA_RESTART */ + if (alarm && sigaction(SIGALRM, &action, NULL) < 0) { + fatal("unable to install signal handler for %d: %m", SIGALRM); + } + + /* no restartable SIGQUIT thanks */ + if (sigaction(SIGQUIT, &action, NULL) < 0) { + fatal("unable to install signal handler for %d: %m", SIGQUIT); + } + +#ifdef SA_RESTART + action.sa_flags |= SA_RESTART; +#endif + + for (i = 0; catch[i] != 0; i++) { + if (catch[i] != SIGALRM && sigaction(catch[i], &action, NULL) < 0) { + fatal("unable to install signal handler for %d: %m", catch[i]); + } + } +} + +static shutdownfn *shutdown_cb = NULL; + +void signals_set_shutdown(shutdownfn *s) +{ + shutdown_cb = s; +} + +int signals_poll(void) +{ + switch (gotsignal) { + case SIGINT: + case SIGQUIT: + if (shutdown_cb) shutdown_cb(EC_TEMPFAIL); + else exit(EC_TEMPFAIL); + break; + default: + return gotsignal; + break; + } + return 0; /* compiler warning stupidity */ +} diff --git a/lib/signals.h b/lib/signals.h new file mode 100644 index 0000000..de807c6 --- /dev/null +++ b/lib/signals.h @@ -0,0 +1,54 @@ +/* signals.h -- signal handling functions to allow clean shutdown + * + * Copyright (c) 1994-2008 Carnegie Mellon University. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in + * the documentation and/or other materials provided with the + * distribution. + * + * 3. The name "Carnegie Mellon University" must not be used to + * endorse or promote products derived from this software without + * prior written permission. For permission or any legal + * details, please contact + * Carnegie Mellon University + * Center for Technology Transfer and Enterprise Creation + * 4615 Forbes Avenue + * Suite 302 + * Pittsburgh, PA 15213 + * (412) 268-7393, fax: (412) 268-7395 + * innovation@andrew.cmu.edu + * + * 4. Redistributions of any form whatsoever must retain the following + * acknowledgment: + * "This product includes software developed by Computing Services + * at Carnegie Mellon University (http://www.cmu.edu/computing/)." + * + * CARNEGIE MELLON UNIVERSITY DISCLAIMS ALL WARRANTIES WITH REGARD TO + * THIS SOFTWARE, INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY + * AND FITNESS, IN NO EVENT SHALL CARNEGIE MELLON UNIVERSITY BE LIABLE + * FOR ANY SPECIAL, INDIRECT OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN + * AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING + * OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + * + * $Id: signals.h,v 1.3 2008/03/24 17:09:19 murch Exp $ + */ + +#ifndef INCLUDED_SIGNALS_H +#define INCLUDED_SIGNALS_H + +typedef void shutdownfn(int); + +void signals_add_handlers(int alarm); +void signals_set_shutdown(shutdownfn *s); +int signals_poll(void); + +#endif /* INCLUDED_SIGNALS_H */ diff --git a/master/master.c b/master/master.c index 78e4b13..da24f91 100644 --- a/master/master.c +++ b/master/master.c @@ -127,6 +127,8 @@ static int verbose = 0; static int listen_queue_backlog = 32; static int pidfd = -1; +static volatile int in_shutdown = 0; + const char *MASTER_CONFIG_FILENAME = DEFAULT_MASTER_CONFIG_FILENAME; #define SERVICE_NONE -1 @@ -855,8 +857,8 @@ void reap_child(void) case SERVICE_STATE_READY: s->nactive--; s->ready_workers--; - if (WIFSIGNALED(status) || - (WIFEXITED(status) && WEXITSTATUS(status))) { + if (!in_shutdown && (WIFSIGNALED(status) || + (WIFEXITED(status) && WEXITSTATUS(status)))) { syslog(LOG_WARNING, "service %s pid %d in READY state: terminated abnormally", SERVICENAME(s->name), pid); @@ -872,8 +874,8 @@ void reap_child(void) case SERVICE_STATE_BUSY: s->nactive--; - if (WIFSIGNALED(status) || - (WIFEXITED(status) && WEXITSTATUS(status))) { + if (!in_shutdown && (WIFSIGNALED(status) || + (WIFEXITED(status) && WEXITSTATUS(status)))) { syslog(LOG_DEBUG, "service %s pid %d in BUSY state: terminated abnormally", SERVICENAME(s->name), pid); @@ -985,6 +987,31 @@ void child_janitor(time_t now) } } +/* Allow a clean shutdown on SIGQUIT */ +void sigquit_handler(int sig __attribute__((unused))) +{ + struct sigaction action; + + /* Ignore SIGQUIT ourselves */ + sigemptyset(&action.sa_mask); + action.sa_flags = 0; + action.sa_handler = SIG_IGN; + if (sigaction(SIGQUIT, &action, (struct sigaction *) 0) < 0) { + syslog(LOG_ERR, "sigaction: %m"); + } + + /* send our process group a SIGQUIT */ + if (kill(0, SIGQUIT) < 0) { + syslog(LOG_ERR, "sigquit_handler: kill(0, SIGQUIT): %m"); + } + + /* Set a flag so main loop knows to shut down when + all children have exited */ + in_shutdown = 1; + + syslog(LOG_INFO, "attempting clean shutdown on SIGQUIT"); +} + static volatile sig_atomic_t gotsigchld = 0; void sigchld_handler(int sig __attribute__((unused))) @@ -1050,6 +1077,12 @@ void sighandler_setup(void) fatal("unable to install signal handler for SIGALRM: %m", 1); } + /* Allow a clean shutdown on SIGQUIT */ + action.sa_handler = sigquit_handler; + if (sigaction(SIGQUIT, &action, NULL) < 0) { + fatal("unable to install signal handler for SIGQUIT: %m", 1); + } + /* Handle SIGTERM and SIGINT the same way -- kill * off our children! */ action.sa_handler = sigterm_handler; @@ -1933,7 +1966,7 @@ int main(int argc, char **argv) now = time(NULL); for (;;) { - int r, i, maxfd; + int r, i, maxfd, total_children = 0; struct timeval tv, *tvptr; struct notify_message msg; #if defined(HAVE_UCDSNMP) || defined(HAVE_NETSNMP) @@ -1941,7 +1974,8 @@ int main(int argc, char **argv) #endif /* run any scheduled processes */ - spawn_schedule(now); + if (!in_shutdown) + spawn_schedule(now); /* reap first, that way if we need to babysit we will */ if (gotsigchld) { @@ -1952,41 +1986,49 @@ int main(int argc, char **argv) /* do we have any services undermanned? */ for (i = 0; i < nservices; i++) { - if (Services[i].exec /* enabled */ && - (Services[i].nactive < Services[i].max_workers) && - (Services[i].ready_workers < Services[i].desired_workers)) { - spawn_service(i); - } else if (Services[i].exec - && Services[i].babysit - && Services[i].nactive == 0) { - syslog(LOG_ERR, - "lost all children for service: %s. " \ - "Applying babysitter.", - Services[i].name); - spawn_service(i); - } else if (!Services[i].exec /* disabled */ && - Services[i].name /* not yet removed */ && - Services[i].nactive == 0) { - if (verbose > 2) - syslog(LOG_DEBUG, "remove: service %s pipe %d %d", - Services[i].name, - Services[i].stat[0], Services[i].stat[1]); - - /* Only free the service info on the primary */ - if (Services[i].associate == 0) { - free(Services[i].name); + total_children += Services[i].nactive; + if (!in_shutdown) { + if (Services[i].exec /* enabled */ && + (Services[i].nactive < Services[i].max_workers) && + (Services[i].ready_workers < Services[i].desired_workers)) { + spawn_service(i); + } else if (Services[i].exec + && Services[i].babysit + && Services[i].nactive == 0) { + syslog(LOG_ERR, + "lost all children for service: %s. " \ + "Applying babysitter.", + Services[i].name); + spawn_service(i); + } else if (!Services[i].exec /* disabled */ && + Services[i].name /* not yet removed */ && + Services[i].nactive == 0) { + if (verbose > 2) + syslog(LOG_DEBUG, "remove: service %s pipe %d %d", + Services[i].name, + Services[i].stat[0], Services[i].stat[1]); + + /* Only free the service info on the primary */ + if (Services[i].associate == 0) { + free(Services[i].name); + } + Services[i].name = NULL; + Services[i].nforks = 0; + Services[i].nactive = 0; + Services[i].nconnections = 0; + Services[i].associate = 0; + + if (Services[i].stat[0] > 0) close(Services[i].stat[0]); + if (Services[i].stat[1] > 0) close(Services[i].stat[1]); + memset(Services[i].stat, 0, sizeof(Services[i].stat)); } - Services[i].name = NULL; - Services[i].nforks = 0; - Services[i].nactive = 0; - Services[i].nconnections = 0; - Services[i].associate = 0; - - if (Services[i].stat[0] > 0) close(Services[i].stat[0]); - if (Services[i].stat[1] > 0) close(Services[i].stat[1]); - memset(Services[i].stat, 0, sizeof(Services[i].stat)); } } + + if (in_shutdown && total_children == 0) { + syslog(LOG_NOTICE, "All children have exited, closing down"); + exit(0); + } if (gotsighup) { syslog(LOG_NOTICE, "got SIGHUP"); @@ -2071,7 +2113,7 @@ int main(int argc, char **argv) process_msg(i, &msg); } - if (Services[i].exec && + if (!in_shutdown && Services[i].exec && Services[i].nactive < Services[i].max_workers) { /* bring us up to desired_workers */ for (j = Services[i].ready_workers; diff --git a/master/service-thread.c b/master/service-thread.c index 3f96ccb..636bbd9 100644 --- a/master/service-thread.c +++ b/master/service-thread.c @@ -70,6 +70,7 @@ #include "service.h" #include "libconfig.h" #include "xmalloc.h" +#include "signals.h" extern int optind; extern char *optarg; @@ -265,6 +266,8 @@ int main(int argc, char **argv, char **envp) fd = accept(LISTEN_FD, NULL, NULL); if (fd < 0) { switch (errno) { + case EINTR: + signals_poll(); case ENETDOWN: #ifdef EPROTO case EPROTO: @@ -278,7 +281,6 @@ int main(int argc, char **argv, char **envp) case EOPNOTSUPP: case ENETUNREACH: case EAGAIN: - case EINTR: case ECONNABORTED: break; default: diff --git a/master/service.c b/master/service.c index ab1409b..3c343d3 100644 --- a/master/service.c +++ b/master/service.c @@ -223,7 +223,7 @@ static int unlockaccept(void) if (lockfd != -1) { alockinfo.l_type = F_UNLCK; while ((rc = fcntl(lockfd, F_SETLKW, &alockinfo)) < 0 && - errno == EINTR) + errno == EINTR && !signals_poll()) /* noop */; if (rc < 0) { @@ -436,6 +436,8 @@ int main(int argc, char **argv, char **envp) fd = accept(LISTEN_FD, NULL, NULL); if (fd < 0) { switch (errno) { + case EINTR: + signals_poll(); case ENETDOWN: #ifdef EPROTO case EPROTO: @@ -449,7 +451,6 @@ int main(int argc, char **argv, char **envp) case EOPNOTSUPP: case ENETUNREACH: case EAGAIN: - case EINTR: break; case EINVAL: -- 1.5.6.5