/* * Copyright (c) 1995, 1996 Gunther Schadow. All rights reserved. * * 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., 675 Mass Ave, Cambridge, MA 02139, USA. */ #include "pg_config.h" IDENT("@(#) llptest.c (Gunther Schadow) 12/19/96"); #include "socket.h" #include "trap.h" #include "intern.h" #include #include #include #include #include #include #include #include #include #include /* * Define last resort defaults. * These should have been supplied by the Makefile -- do not edit here! */ #ifndef LOG_IDENT # define LOG_IDENT "llptest" #endif #ifndef LOG_FACILITY # define LOG_FACILITY LOG_USER #endif #ifndef LOG_FILE # define LOG_FILE "/tmp/llptest.log" #endif #ifndef FROM_AF # define FROM_AF AF_INET #endif #ifndef FROM_ADDR # define FROM_ADDR "test1%localhost" #endif #ifndef TO_FILE # define TO_FILE "/tmp/llptest.file" #endif /* * End of last resort defaults. */ #ifndef BUF_SIZE # define BUF_SIZE 1024 #endif char *program; char *from_addr = FROM_ADDR; /* from address */ int from_af = FROM_AF; /* from address family */ char *to_file = TO_FILE; char append = FALSE; bool dont_fork = FALSE; #define HOSTNAME_SIZE 128 char peer[HOSTNAME_SIZE]; static void reaper(int); static void finish(); static void cleanup(); static result transact(int); static result receive(int, int, off_t); static result reply(int, int, off_t); static result read_peer(int, char *, size_t); void usage() { fprintf(stderr, "usage: %s " "[-ay] [-x level] [-l logfile] [[-i|u] address] [-f file]\n", program); exit(1); } int main(int argc, char *argv[]) { int dlevel = LOG_WARNING; int sfd; #ifndef HAVE_SYSLOG logfile = LOG_FILE; #endif /* * Parse command line arguments */ { char c; program = argv[0]; while ( ( c = getopt(argc, argv, "af:i:u:l:x:y") ) != FAIL ) { switch(c) { case 'i': from_af = AF_INET; from_addr = optarg; break; case 'u': from_af = AF_UNIX; from_addr = optarg; break; case 'f': to_file = optarg; break; case 'a': append = TRUE; break; #ifdef DEBUG case 'y': dont_fork = TRUE; break; case 'x': dlevel = atoi(optarg); if (dlevel > LOG_DEBUG) dlevel = LOG_DEBUG; if (dlevel < LOG_WARNING) dlevel = LOG_WARNING; break; #endif #ifndef HAVE_SYSLOG case 'l': logfile = optarg; break; #endif default: usage(); } } } /* * Open syslog */ openlog(LOG_IDENT, LOG_NDELAY | LOG_PID, LOG_FACILITY); setlogmask(LOG_UPTO(dlevel)); /* * Bind socket */ switch(from_af) { case AF_UNIX: sfd = bind_unix(from_addr); break; case AF_INET: sfd = bind_inet(from_addr); break; } if(sfd == FAIL) { fprintf(stderr, "bind %s: %s\n", from_addr, strerror(errno)); exit(1); } if(!dont_fork) { /* * Fork and disassociate from terminal */ int i = fork(); if(i < 0) { fprintf(stderr, "%s:fork: %s", program, strerror(errno)); cleanup(); exit(1); } if(i > 0) exit(0); /* parent exits here */ for(i = 0; i < 3; i++) close(i); if(setsid() == FAIL) { fprintf(stderr,"%s:setsid: %s", program, strerror(errno)); cleanup(); exit(1); } } { /* * Log start of process */ char afopt; switch(from_af) { case AF_INET: afopt = 'i'; break; case AF_UNIX: afopt = 'u'; break; } syslog(LOG_INFO, "%s -%c %s -f %s", program, afopt, from_addr, to_file); } ssignal(SIGINT, finish, T_SINTR); ssignal(SIGTERM, finish, T_SINTR); ssignal(SIGHUP, finish, T_SINTR); ssignal(SIGCHLD, reaper, 0); listen(sfd, 5); do { int cfd; switch(from_af) { case AF_UNIX: cfd = accept_unix(sfd); break; case AF_INET: cfd = accept_inet(sfd); break; } if (cfd == FAIL) { syslog(LOG_WARNING,"accept failed"); continue; } syslog(LOG_INFO, "connection established"); if(dont_fork) DBG(fprintf(stderr, "connection established (%d)\n", BUF_SIZE)); #ifdef OOB_INLINE { int on = 1; if(setsockopt(cfd, SOL_SOCKET, SO_OOBINLINE, &on, sizeof(on)) == FAIL) { syslog(LOG_ERR, "SO_OOBINLINE: %m"); close(cfd); continue; } } #endif if(read_peer(cfd, peer, sizeof(peer)) == FAIL) { close(cfd); continue; } syslog(LOG_INFO, "peer is: `%s'", peer); if(!dont_fork) { if(fork() == 0) /* I am the child */ { result res; close(sfd); /* I don't need my Papi's socket */ /* I gonna play with my own */ res = transact(cfd); close(cfd); exit( res == SUCCESS ? 0 : 1 ); } else /* I am the Father */ close(cfd); /* I leave my son alone with his new socket */ } else /* dont_fork */ { result r; r = transact(cfd); syslog(LOG_DEBUG, "transaction finished with %d", r); close(cfd); } } while(1); } static void reaper(int sig) { int status; pid_t pid; DBG(syslog(LOG_DEBUG, "reaper called by signal %d `%s'", sig, sys_siglist[sig])); while((pid = wait3(&status, WNOHANG, 0)) > 0) { if(WIFEXITED(status)) { DBG(syslog(LOG_DEBUG, "process %d exited with %d", pid, WEXITSTATUS(status))); } else if(WIFSIGNALED(status)) { int tsig = WTERMSIG(status); DBG(syslog(LOG_DEBUG, "process %d received signal %d `%s'", pid, tsig, sys_siglist[tsig])); } else if(WIFSTOPPED(status)) { int ssig = WSTOPSIG(status); DBG(syslog(LOG_DEBUG, "process %d stopped by signal %d `s'", pid, ssig, sys_siglist[ssig])); kill(pid, SIGCONT); } } } static void finish() { sigset_t set; DBG(syslog(LOG_DEBUG, "shut down server")); if(dont_fork) goto end_kill; /* Block SIGHUP and SIGTERM, which are to be sent to the process * group: */ sigemptyset(&set); sigaddset(&set, SIGHUP); sigaddset(&set, SIGTERM); sigprocmask(SIG_BLOCK, &set, NULL); /* Gently terminate server processes: * * 1.) please them */ #define WAIT_TIME 5 syslog(LOG_INFO, "sending HUP signal to server processes"); if(kill(0, SIGHUP) == FAIL) goto end_kill; syslog(LOG_INFO, "waiting %d seconds for server processes to exit", WAIT_TIME); sleep(WAIT_TIME); /* 2.) urge them */ syslog(LOG_INFO, "sending TERM signal to server processes"); if(kill(0, SIGTERM) == FAIL) goto end_kill; syslog(LOG_INFO, "waiting %d seconds for server processes to exit", WAIT_TIME); sleep(WAIT_TIME); end_kill: /* If socket was UNIX remove it */ cleanup(); DBG(syslog(LOG_DEBUG, "exiting.")); exit(0); } static void cleanup() { /* If socket was UNIX remove it */ if(from_af == AF_UNIX) { syslog(LOG_INFO, "removing unix domain socket `%s'", from_addr); unlink(from_addr); } } result transact(int cfd) { int fd; off_t offset; atsignal(SIGHUP, 0) exit(2); atsignal(SIGTERM, 0) exit(2); fd = open(to_file, O_RDWR|O_CREAT|(append ? O_APPEND : 0), S_IRUSR | S_IWUSR); if(fd == FAIL) { syslog(LOG_ERR, "open %s: %m", to_file); goto fail; } { char buf[129]; u_int len; strncpy(buf, peer, 128); len = strlen(buf); buf[len] = A_STX; if(write(fd, buf, len + 1) < len + 1) { syslog(LOG_ERR, "write peer: %m"); goto fail; } } if((offset = lseek(fd, 0, ( append ? SEEK_END : SEEK_CUR ) )) == FAIL) { syslog(LOG_ERR, "lseek: %m"); goto fail; } if(ftruncate(fd, offset) == FAIL) { syslog(LOG_ERR, "ftruncate: %m"); goto fail; } if(receive(cfd, fd, offset) == FAIL) { if(ftruncate(fd, offset) == FAIL) syslog(LOG_ERR, "ftruncate: %m"); goto fail; } if(reply(cfd, fd, offset) == FAIL) goto fail; close(fd); return SUCCESS; fail: close(fd); return FAIL; } static int receive(int cfd, int fd, off_t offset) { int total = 0; #ifdef hpux int minuspid = -getpid(); #endif DBG(syslog(LOG_DEBUG, "receiving")); #ifdef hpux if(ioctl(cfd, SIOCSPGRP, &minuspid) == FAIL) #else if(fcntl(cfd, F_SETOWN, getpid()) == FAIL) #endif { syslog(LOG_ERR, "F_SETOWN: %m"); return FAIL; } atsignal(SIGURG, 0) { char c; bool atmark; #ifdef DONT_NEED_READ_1 char buf[BUF_SIZE]; #endif DBG(syslog(LOG_DEBUG, "SIGURG trapped")); if(dont_fork) DBG(fprintf(stderr, "SIGURG trapped\n")); do { if(ioctl(cfd, SIOCATMARK, &atmark) == FAIL) { syslog(LOG_ERR, "SIOCATMARK: %m"); return FAIL; } if(atmark) break; #ifndef DONT_NEED_READ_1 if(read(cfd, &c, 1) < 1) #else if(read(cfd, buf, BUF_SIZE) < 1) #endif { syslog(LOG_ERR, "read: %m"); return FAIL; } } while(TRUE); #ifdef OOB_INLINE if(recv(cfd, &c, 1, 0) < 1) #else if(recv(cfd, &c, 1, MSG_OOB) < 1) #endif { syslog(LOG_ERR, "recv: %m"); return FAIL; } switch(c) { case A_STX: syslog(LOG_NOTICE, "block restarted"); if(lseek(fd, offset, SEEK_SET) == FAIL) { syslog(LOG_ERR, "lseek: %m"); return FAIL; } if(ftruncate(fd, offset) == FAIL) { syslog(LOG_ERR, "ftruncate: %m"); return FAIL; } total = 0; break; case A_CAN: syslog(LOG_WARNING, "transaction cancelled by client"); return FAIL; default: syslog(LOG_ERR, "undefined out of band signal %d", c); return FAIL; } } do { char buf[BUF_SIZE+1]; u_int cnt; cnt = read(cfd, buf, BUF_SIZE); if(cnt == FAIL) /* read error */ { syslog(LOG_ERR, "read: %m"); return FAIL; } else if(cnt == 0) /* connection closed: end of block */ { char c = 0; DBG(syslog(LOG_DEBUG, "end of block")); if(write(fd, &c, 1) < 1) { syslog(LOG_ERR, "write: %m"); return FAIL; } return total + 1; } else /* data */ { u_int n, start = 0; buf[cnt] = 0; DBG(syslog(LOG_DEBUG, "data: `%s'", buf)); do { n = write(fd, &buf[start], cnt); if(n == FAIL) { syslog(LOG_ERR, "write: %m"); return FAIL; } cnt -= n; start += n; total += n; } while(cnt > 0); } } while(TRUE); } static result reply(int cfd, int fd, off_t offset) { char buf[BUF_SIZE + 1]; u_int cnt, len, start; bool end_flag = FALSE; DBG(syslog(LOG_DEBUG, "replying")); if(lseek(fd, offset, SEEK_SET) == FAIL) { syslog(LOG_ERR, "lseek: %m"); return FAIL; } do { cnt = read(fd, buf, BUF_SIZE); if(cnt == FAIL) { syslog(LOG_ERR, "read %s: %m", to_file); return FAIL; } len = strlen(buf); if(len < cnt || buf[cnt - 1] == 0) { cnt = len; end_flag = TRUE; } buf[cnt] = 0; DBG(syslog(LOG_DEBUG, "data: `%s'", buf)); start = 0; do { u_int n; n = write(cfd, &buf[start], cnt); if(n == FAIL) { syslog(LOG_ERR, "write: %m"); return FAIL; } cnt -= n; start += n; } while(cnt > 0); } while(!end_flag); shutdown(cfd, 1); return SUCCESS; } result read_peer(int sd, char *str, size_t len) { char c; u_int i = 0; DBG(syslog(LOG_DEBUG, "getting peername")); do { if(read(sd, &c, 1) < 1) { syslog(LOG_ERR, "read: %m"); return FAIL; } DBG(syslog(LOG_DEBUG, "got: %d `%c'", c, c)); str[i++] = c; } while(c != A_STX && i < len); str[i-1] = 0; DBG(syslog(LOG_DEBUG, "peername is: %s", str)); return SUCCESS; }