diff options
author | Yang Tse <yangsita@gmail.com> | 2009-12-02 15:02:30 +0000 |
---|---|---|
committer | Yang Tse <yangsita@gmail.com> | 2009-12-02 15:02:30 +0000 |
commit | 2286f566d03fdab4e3ab3d73998a42ffa95e801d (patch) | |
tree | 80ac536613a106dc7a8e5c952d4a7bf08e93bfac | |
parent | ed2aa87e63fa29df0c60b609fd80645aa99d7c59 (diff) |
signal handling to cleanup on SIGINT and SIGTERM
-rw-r--r-- | tests/server/tftpd.c | 495 |
1 files changed, 355 insertions, 140 deletions
diff --git a/tests/server/tftpd.c b/tests/server/tftpd.c index 1f6418f59..9106836f2 100644 --- a/tests/server/tftpd.c +++ b/tests/server/tftpd.c @@ -99,11 +99,11 @@ /* include memdebug.h last */ #include "memdebug.h" -#ifdef ENABLE_IPV6 -static bool use_ipv6 = FALSE; -#endif -static const char *ipv_inuse = "IPv4"; -static int serverlogslocked = 0; +/***************************************************************************** +* STRUCT DECLARATIONS AND DEFINES * +*****************************************************************************/ + +#define PKTSIZE (SEGSIZE + 4) /* SEGSIZE defined in arpa/tftp.h */ struct testcase { char *buffer; /* holds the file data to send to the client */ @@ -114,12 +114,28 @@ struct testcase { int ofile; /* file descriptor for output file when uploading to us */ }; -static int synchnet(curl_socket_t); -static struct tftphdr *r_init(void); -static struct tftphdr *w_init(void); -static int readit(struct testcase *test, struct tftphdr **dpp, int convert); -static int writeit(struct testcase *test, struct tftphdr **dpp, int ct, - int convert); +struct formats { + const char *f_mode; + int f_convert; +}; + +struct errmsg { + int e_code; + const char *e_msg; +}; + +/* + * bf.counter values in range [-1 .. SEGSIZE] represents size of data in the + * bf.buf buffer. Additionally it can also hold flags BF_ALLOC or BF_FREE. + */ + +struct bf { + int counter; /* size of data in buffer, or flag */ + char buf[PKTSIZE]; /* room for data packet */ +}; + +#define BF_ALLOC -3 /* alloc'd but not yet filled */ +#define BF_FREE -2 /* free */ #define opcode_RRQ 1 #define opcode_WRQ 2 @@ -127,63 +143,290 @@ static int writeit(struct testcase *test, struct tftphdr **dpp, int ct, #define opcode_ACK 4 #define opcode_ERROR 5 -#define TIMEOUT 5 +#define TIMEOUT 5 -#define PKTSIZE SEGSIZE+4 +#undef MIN +#define MIN(x,y) ((x)<(y)?(x):(y)) -struct formats { - const char *f_mode; - int f_convert; +#ifndef DEFAULT_LOGFILE +#define DEFAULT_LOGFILE "log/tftpd.log" +#endif + +#define REQUEST_DUMP "log/server.input" + +#define DEFAULT_PORT 8999 /* UDP */ + +/***************************************************************************** +* GLOBAL VARIABLES * +*****************************************************************************/ + +static struct errmsg errmsgs[] = { + { EUNDEF, "Undefined error code" }, + { ENOTFOUND, "File not found" }, + { EACCESS, "Access violation" }, + { ENOSPACE, "Disk full or allocation exceeded" }, + { EBADOP, "Illegal TFTP operation" }, + { EBADID, "Unknown transfer ID" }, + { EEXISTS, "File already exists" }, + { ENOUSER, "No such user" }, + { -1, 0 } }; + static struct formats formata[] = { { "netascii", 1 }, { "octet", 0 }, { NULL, 0 } }; -static int tftp(struct testcase *test, struct tftphdr *tp, ssize_t size); -static void nak(int error); -static void sendtftp(struct testcase *test, struct formats *pf); -static void recvtftp(struct testcase *test, struct formats *pf); -static int validate_access(struct testcase *test, const char *, int); +static struct bf bfs[2]; + +static int nextone; /* index of next buffer to use */ +static int current; /* index of buffer in use */ -static curl_socket_t peer; -static int maxtimeout = 5*TIMEOUT; + /* control flags for crlf conversions */ +static int newline = 0; /* fillbuf: in middle of newline expansion */ +static int prevchar = -1; /* putbuf: previous char (cr check) */ static char buf[PKTSIZE]; static char ackbuf[PKTSIZE]; + static struct sockaddr_in from; static curl_socklen_t fromlen; -struct bf { - int counter; /* size of data in buffer, or flag */ - char buf[PKTSIZE]; /* room for data packet */ -}; -static struct bf bfs[2]; +static curl_socket_t peer = CURL_SOCKET_BAD; - /* Values for bf.counter */ -#define BF_ALLOC -3 /* alloc'd but not yet filled */ -#define BF_FREE -2 /* free */ -/* [-1 .. SEGSIZE] = size of data in the data buffer */ +static int timeout; +static int maxtimeout = 5 * TIMEOUT; -static int nextone; /* index of next buffer to use */ -static int current; /* index of buffer in use */ +static unsigned short sendblock; /* block count used by sendtftp() */ +static struct tftphdr *sdp; /* data buffer used by sendtftp() */ +static struct tftphdr *sap; /* ack buffer used by sendtftp() */ - /* control flags for crlf conversions */ -static int newline = 0; /* fillbuf: in middle of newline expansion */ -static int prevchar = -1; /* putbuf: previous char (cr check) */ +static unsigned short recvblock; /* block count used by recvtftp() */ +static struct tftphdr *rdp; /* data buffer used by recvtftp() */ +static struct tftphdr *rap; /* ack buffer used by recvtftp() */ + +#ifdef ENABLE_IPV6 +static bool use_ipv6 = FALSE; +#endif +static const char *ipv_inuse = "IPv4"; + +const char *serverlogfile = DEFAULT_LOGFILE; +static char *pidname= (char *)".tftpd.pid"; +static int serverlogslocked = 0; +static int wrotepidfile = 0; + +#ifdef HAVE_SIGSETJMP +static sigjmp_buf timeoutbuf; +#endif + +#if defined(HAVE_ALARM) && defined(SIGALRM) +static int rexmtval = TIMEOUT; +#endif + +/* do-nothing macro replacement for systems which lack siginterrupt() */ + +#ifndef HAVE_SIGINTERRUPT +#define siginterrupt(x,y) do {} while(0) +#endif + +/* vars used to keep around previous signal handlers */ + +typedef RETSIGTYPE (*SIGHANDLER_T)(int); + +#ifdef SIGHUP +static SIGHANDLER_T old_sighup_handler = SIG_ERR; +#endif + +#ifdef SIGPIPE +static SIGHANDLER_T old_sigpipe_handler = SIG_ERR; +#endif + +#ifdef SIGINT +static SIGHANDLER_T old_sigint_handler = SIG_ERR; +#endif + +#ifdef SIGTERM +static SIGHANDLER_T old_sigterm_handler = SIG_ERR; +#endif + +/* var which if set indicates that the program should finish execution */ + +SIG_ATOMIC_T got_exit_signal = 0; + +/* if next is set indicates the first signal handled in exit_signal_handler */ + +static volatile int exit_signal = 0; + +/***************************************************************************** +* FUNCTION PROTOTYPES * +*****************************************************************************/ -static void read_ahead(struct testcase *test, - int convert /* if true, convert to ascii */); -static ssize_t write_behind(struct testcase *test, int convert); static struct tftphdr *rw_init(int); -static struct tftphdr *w_init(void) { return rw_init(0); } /* write-behind */ -static struct tftphdr *r_init(void) { return rw_init(1); } /* read-ahead */ -static struct tftphdr * -rw_init(int x) /* init for either read-ahead or write-behind */ -{ /* zero for write-behind, one for read-head */ - newline = 0; /* init crlf flag */ +static struct tftphdr *w_init(void); + +static struct tftphdr *r_init(void); + +static int readit(struct testcase *test, + struct tftphdr **dpp, + int convert); + +static int writeit(struct testcase *test, + struct tftphdr **dpp, + int ct, + int convert); + +static void read_ahead(struct testcase *test, int convert); + +static ssize_t write_behind(struct testcase *test, int convert); + +static int synchnet(curl_socket_t); + +static int do_tftp(struct testcase *test, struct tftphdr *tp, ssize_t size); + +static int validate_access(struct testcase *test, const char *fname, int mode); + +static void sendtftp(struct testcase *test, struct formats *pf); + +static void recvtftp(struct testcase *test, struct formats *pf); + +static void nak(int error); + +#if defined(HAVE_ALARM) && defined(SIGALRM) + +static void mysignal(int sig, void (*handler)(int)); + +static void timer(int signum); + +static void justtimeout(int signum); + +#endif /* HAVE_ALARM && SIGALRM */ + +static RETSIGTYPE exit_signal_handler(int signum); + +static void install_signal_handlers(void); + +static void restore_signal_handlers(void); + +/***************************************************************************** +* FUNCTION IMPLEMENTATIONS * +*****************************************************************************/ + +#if defined(HAVE_ALARM) && defined(SIGALRM) + +/* + * Like signal(), but with well-defined semantics. + */ +static void mysignal(int sig, void (*handler)(int)) +{ + struct sigaction sa; + memset(&sa, 0, sizeof(sa)); + sa.sa_handler = handler; + sigaction(sig, &sa, NULL); +} + +static void timer(int signum) +{ + (void)signum; + + logmsg("alarm!"); + + timeout += rexmtval; + if(timeout >= maxtimeout) { + if(wrotepidfile) { + wrotepidfile = 0; + unlink(pidname); + } + if(serverlogslocked) { + serverlogslocked = 0; + clear_advisor_read_lock(SERVERLOGS_LOCK); + } + exit(1); + } +#ifdef HAVE_SIGSETJMP + siglongjmp(timeoutbuf, 1); +#endif +} + +static void justtimeout(int signum) +{ + (void)signum; +} + +#endif /* HAVE_ALARM && SIGALRM */ + +/* signal handler that will be triggered to indicate that the program + should finish its execution in a controlled manner as soon as possible. + The first time this is called it will set got_exit_signal to one and + store in exit_signal the signal that triggered its execution. */ + +static RETSIGTYPE exit_signal_handler(int signum) +{ + int old_errno = ERRNO; + if(got_exit_signal == 0) { + got_exit_signal = 1; + exit_signal = signum; + } + (void)signal(signum, exit_signal_handler); + SET_ERRNO(old_errno); +} + +static void install_signal_handlers(void) +{ +#ifdef SIGHUP + /* ignore SIGHUP signal */ + if((old_sighup_handler = signal(SIGHUP, SIG_IGN)) == SIG_ERR) + logmsg("cannot install SIGHUP handler: %s", strerror(ERRNO)); +#endif +#ifdef SIGPIPE + /* ignore SIGPIPE signal */ + if((old_sigpipe_handler = signal(SIGPIPE, SIG_IGN)) == SIG_ERR) + logmsg("cannot install SIGPIPE handler: %s", strerror(ERRNO)); +#endif +#ifdef SIGINT + /* handle SIGINT signal with our exit_signal_handler */ + if((old_sigint_handler = signal(SIGINT, exit_signal_handler)) == SIG_ERR) + logmsg("cannot install SIGINT handler: %s", strerror(ERRNO)); + else + siginterrupt(SIGINT, 1); +#endif +#ifdef SIGTERM + /* handle SIGTERM signal with our exit_signal_handler */ + if((old_sigterm_handler = signal(SIGTERM, exit_signal_handler)) == SIG_ERR) + logmsg("cannot install SIGTERM handler: %s", strerror(ERRNO)); + else + siginterrupt(SIGTERM, 1); +#endif +} + +static void restore_signal_handlers(void) +{ +#ifdef SIGHUP + if(SIG_ERR != old_sighup_handler) + (void)signal(SIGHUP, old_sighup_handler); +#endif +#ifdef SIGPIPE + if(SIG_ERR != old_sigpipe_handler) + (void)signal(SIGPIPE, old_sigpipe_handler); +#endif +#ifdef SIGINT + if(SIG_ERR != old_sigint_handler) + (void)signal(SIGINT, old_sigint_handler); +#endif +#ifdef SIGTERM + if(SIG_ERR != old_sigterm_handler) + (void)signal(SIGTERM, old_sigterm_handler); +#endif +} + +/* + * init for either read-ahead or write-behind. + * zero for write-behind, one for read-head. + */ +static struct tftphdr *rw_init(int x) +{ + newline = 0; /* init crlf flag */ prevchar = -1; bfs[0].counter = BF_ALLOC; /* pass out the first buffer */ current = 0; @@ -192,6 +435,15 @@ rw_init(int x) /* init for either read-ahead or write-behind */ return (struct tftphdr *)bfs[0].buf; } +static struct tftphdr *w_init(void) +{ + return rw_init(0); /* write-behind */ +} + +static struct tftphdr *r_init(void) +{ + return rw_init(1); /* read-ahead */ +} /* Have emptied current buffer by sending to net and getting ack. Free it and return next buffer filled with data. @@ -212,9 +464,6 @@ static int readit(struct testcase *test, struct tftphdr **dpp, return b->counter; } -#undef MIN /* some systems have this defined already, some don't */ -#define MIN(x,y) ((x)<(y)?(x):(y)); - /* * fill the input buffer, doing ascii conversions if requested * conversions are lf -> cr,lf and cr -> cr, nul @@ -354,7 +603,6 @@ static ssize_t write_behind(struct testcase *test, int convert) return count; } - /* When an error has occurred, it is possible that the two sides are out of * synch. Ie: that what I think is the other side's response to packet N is * really their response to packet N-1. @@ -397,29 +645,6 @@ static int synchnet(curl_socket_t f /* socket to flush */) return j; } -#if defined(HAVE_ALARM) && defined(SIGALRM) -/* - * Like signal(), but with well-defined semantics. - */ -static void mysignal(int sig, void (*handler)(int)) -{ - struct sigaction sa; - memset(&sa, 0, sizeof(sa)); - sa.sa_handler = handler; - sigaction(sig, &sa, NULL); -} -#endif - -#ifndef DEFAULT_LOGFILE -#define DEFAULT_LOGFILE "log/tftpd.log" -#endif - -#define DEFAULT_PORT 8999 /* UDP */ -const char *serverlogfile = DEFAULT_LOGFILE; - -#define REQUEST_DUMP "log/server.input" - - int main(int argc, char **argv) { struct sockaddr_in me; @@ -430,12 +655,12 @@ int main(int argc, char **argv) struct tftphdr *tp; ssize_t n = 0; int arg = 1; - char *pidname= (char *)".tftpd.pid"; unsigned short port = DEFAULT_PORT; curl_socket_t sock = CURL_SOCKET_BAD; int flag; int rc; int error; + long pid; struct testcase test; int result = 0; @@ -477,6 +702,10 @@ int main(int argc, char **argv) atexit(win32_cleanup); #endif + install_signal_handlers(); + + pid = (long)getpid(); + #ifdef ENABLE_IPV6 if(!use_ipv6) #endif @@ -490,7 +719,8 @@ int main(int argc, char **argv) error = SOCKERRNO; logmsg("Error creating socket: (%d) %s", error, strerror(error)); - return 1; + result = 1; + goto tftpd_cleanup; } flag = 1; @@ -499,8 +729,8 @@ int main(int argc, char **argv) error = SOCKERRNO; logmsg("setsockopt(SO_REUSEADDR) failed with error: (%d) %s", error, strerror(error)); - sclose(sock); - return 1; + result = 1; + goto tftpd_cleanup; } #ifdef ENABLE_IPV6 @@ -525,13 +755,14 @@ int main(int argc, char **argv) error = SOCKERRNO; logmsg("Error binding socket on port %hu: (%d) %s", port, error, strerror(error)); - sclose(sock); - return 1; + result = 1; + goto tftpd_cleanup; } - if(!write_pidfile(pidname)) { - sclose(sock); - return 1; + wrotepidfile = write_pidfile(pidname); + if(!wrotepidfile) { + result = 1; + goto tftpd_cleanup; } logmsg("Running %s version on port UDP/%d", ipv_inuse, (int)port); @@ -540,6 +771,8 @@ int main(int argc, char **argv) fromlen = sizeof(from); n = (ssize_t)recvfrom(sock, buf, sizeof(buf), 0, (struct sockaddr *)&from, &fromlen); + if(got_exit_signal) + break; if (n < 0) { logmsg("recvfrom"); result = 3; @@ -569,12 +802,16 @@ int main(int argc, char **argv) tp->th_opcode = ntohs(tp->th_opcode); if (tp->th_opcode == opcode_RRQ || tp->th_opcode == opcode_WRQ) { memset(&test, 0, sizeof(test)); - if (tftp(&test, tp, n) < 0) + if (do_tftp(&test, tp, n) < 0) break; if(test.buffer) free(test.buffer); } sclose(peer); + peer = CURL_SOCKET_BAD; + + if(got_exit_signal) + break; if(serverlogslocked) { serverlogslocked = 0; @@ -585,18 +822,46 @@ int main(int argc, char **argv) } +tftpd_cleanup: + + if((peer != sock) && (peer != CURL_SOCKET_BAD)) + sclose(peer); + + if(sock != CURL_SOCKET_BAD) + sclose(sock); + + if(got_exit_signal) + logmsg("signalled to die"); + + if(wrotepidfile) + unlink(pidname); + if(serverlogslocked) { serverlogslocked = 0; clear_advisor_read_lock(SERVERLOGS_LOCK); } + restore_signal_handlers(); + + if(got_exit_signal) { + logmsg("========> %s tftpd (port: %d pid: %ld) exits with signal (%d)", + ipv_inuse, (int)port, pid, exit_signal); + /* + * To properly set the return status of the process we + * must raise the same signal SIGINT or SIGTERM that we + * caught and let the old handler take care of it. + */ + raise(exit_signal); + } + + logmsg("========> tftpd quits"); return result; } /* * Handle initial connection protocol. */ -static int tftp(struct testcase *test, struct tftphdr *tp, ssize_t size) +static int do_tftp(struct testcase *test, struct tftphdr *tp, ssize_t size) { char *cp; int first = 1, ecode; @@ -758,42 +1023,6 @@ static int validate_access(struct testcase *test, return 0; } -static int timeout; -#ifdef HAVE_SIGSETJMP -static sigjmp_buf timeoutbuf; -#endif - -#if defined(HAVE_ALARM) && defined(SIGALRM) -static int rexmtval = TIMEOUT; - -static void timer(int signum) -{ - (void)signum; - - logmsg("alarm!"); - - timeout += rexmtval; - if(timeout >= maxtimeout) { - if(serverlogslocked) { - serverlogslocked = 0; - clear_advisor_read_lock(SERVERLOGS_LOCK); - } - exit(1); - } -#ifdef HAVE_SIGSETJMP - siglongjmp(timeoutbuf, 1); -#endif -} - -static void justtimeout(int signum) -{ - (void)signum; -} -#endif /* HAVE_ALARM && SIGALRM */ - -static unsigned short sendblock; -static struct tftphdr *sdp; -static struct tftphdr *sap; /* ack packet */ /* * Send the requested file. */ @@ -833,6 +1062,8 @@ static void sendtftp(struct testcase *test, struct formats *pf) #ifdef HAVE_ALARM alarm(0); #endif + if(got_exit_signal) + return; if (n < 0) { logmsg("read: fail"); return; @@ -861,10 +1092,6 @@ static void sendtftp(struct testcase *test, struct formats *pf) } while (size == SEGSIZE); } - -static unsigned short recvblock; -static struct tftphdr *rdp; -static struct tftphdr *rap; /* ack buffer */ /* * Receive a file. */ @@ -899,6 +1126,8 @@ send_ack: #ifdef HAVE_ALARM alarm(0); #endif + if(got_exit_signal) + goto abort; if (n < 0) { /* really? */ logmsg("read: fail\n"); goto abort; @@ -940,6 +1169,8 @@ send_ack: #ifdef HAVE_ALARM alarm(0); #endif + if(got_exit_signal) + goto abort; if (n >= 4 && /* if read some data */ rdp->th_opcode == opcode_DATA && /* and got a data block */ recvblock == rdp->th_block) { /* then my last ack was lost */ @@ -949,22 +1180,6 @@ abort: return; } -struct errmsg { - int e_code; - const char *e_msg; -}; -static struct errmsg errmsgs[] = { - { EUNDEF, "Undefined error code" }, - { ENOTFOUND, "File not found" }, - { EACCESS, "Access violation" }, - { ENOSPACE, "Disk full or allocation exceeded" }, - { EBADOP, "Illegal TFTP operation" }, - { EBADID, "Unknown transfer ID" }, - { EEXISTS, "File already exists" }, - { ENOUSER, "No such user" }, - { -1, 0 } -}; - /* * Send a nak packet (error message). Error code passed in is one of the * standard TFTP codes, or a UNIX errno offset by 100. |