From b4a15eb6f69df019ba074f39da3c07b08509c34e Mon Sep 17 00:00:00 2001 From: gumartinm Date: Fri, 27 Jan 2012 00:49:59 +0100 Subject: [PATCH] Many improvements in my remote TCP fork for Java I keep working on it Next steps cleaning up the code and maybe using execve instead of system. --- JavaFork/Daemon/Makefile | 8 + JavaFork/Daemon/javafork.c | 751 ++++++++++++++------- JavaFork/Daemon/javafork.h | 57 +- .../de/fork/java/LauncherProcessesDiaFork.java | 283 ++++++++ .../src/main/java/de/fork/java/RemoteForkMain.java | 36 + .../src/main/java/de/fork/java/TCPForkDaemon.java | 178 +++++ .../src/main/java/de/fork/java/XmlForkParser.java | 195 ++++++ 7 files changed, 1245 insertions(+), 263 deletions(-) create mode 100644 JavaFork/Daemon/Makefile create mode 100644 JavaFork/JavaExample/javafork-example/src/main/java/de/fork/java/LauncherProcessesDiaFork.java create mode 100644 JavaFork/JavaExample/javafork-example/src/main/java/de/fork/java/RemoteForkMain.java create mode 100644 JavaFork/JavaExample/javafork-example/src/main/java/de/fork/java/TCPForkDaemon.java create mode 100644 JavaFork/JavaExample/javafork-example/src/main/java/de/fork/java/XmlForkParser.java diff --git a/JavaFork/Daemon/Makefile b/JavaFork/Daemon/Makefile new file mode 100644 index 0000000..7c28c45 --- /dev/null +++ b/JavaFork/Daemon/Makefile @@ -0,0 +1,8 @@ +all: javafork + +javafork: javafork.c javafork.h + gcc -Wall -g -o javafork javafork.c -lpthread + +clean: + rm -f javafork + diff --git a/JavaFork/Daemon/javafork.c b/JavaFork/Daemon/javafork.c index 3ad2346..366a72b 100644 --- a/JavaFork/Daemon/javafork.c +++ b/JavaFork/Daemon/javafork.c @@ -18,358 +18,587 @@ #include #include #include +#include +#include +#include #include "javafork.h" -pid_t currentPID; + +pid_t daemonPID; int sockfd; +#define RESTARTABLE(_cmd, _result) do { \ + _result = _cmd; \ +} while((_result == -1) && (errno == EINTR)) -int main (int argc, char *argv[]) { - pid_t pid; +static int restartableClose(int fd) { + int err; + RESTARTABLE(close(fd), err); + return err; +} - if (signal(SIGTERM, sigterm_handler) == SIG_ERR) - { - perror ("\nerror signal handler"); - return -1; - } +static int closeSafely(int fd) { + return (fd == -1) ? 0 : restartableClose(fd); +} - if ((pid = fork()) == -1) { - perror("fork: "); - return -1; + +int main (int argc, char *argv[]) { + int c; /*Getopt parameter*/ + /*Default values*/ + char *avalue = IPADDRESS; /*Address: numeric value or hostname*/ + int pvalue = PORT; /*TCP port*/ + int qvalue = QUEUE; /*TCP listen queue*/ + + + /*This process is intended to be used as a daemon, it sould be launched by the INIT process, because of that*/ + /*we are not forking it (INIT needs it)*/ + if (daemonize(argv[0], LOG_SYSLOG, LOG_PID) < 0) + return 1; + + setsid(); + + if (signal(SIGPIPE,SIG_IGN) == SIG_ERR) { + syslog (LOG_ERR, "Error with SIGPIPE: %m"); + return 1; } - else { - if (pid == 0) { - /*child process*/ - currentPID = getpid(); - main_child(argc, argv); + + + opterr = 0; + while ((c = getopt (argc, argv, "a:p:q:")) != -1) { + switch (c) { + case 'a': + avalue = optarg; + break; + case 'p': + pvalue = atoi(optarg); + if ((pvalue > 65535) || (pvalue <= 0)) { + syslog (LOG_ERR, "Port value %d out of range", pvalue); + return 1; + } + break; + case 'q': + qvalue = atoi(optarg); + break; + case '?': + if ((optopt == 'a') || (optopt == 'p') || (optopt == 'q')) + syslog (LOG_ERR, "Option -%c requires an argument.", optopt); + else if (isprint (optopt)) + syslog (LOG_ERR, "Invalid option '-%c'.", optopt); + else + syslog (LOG_ERR, "Unknown option character '\\x%x'.", optopt); + return 1; + default: + abort (); } } - + /*This program does not admit options*/ + if (optind < argc) { + syslog (LOG_ERR,"This program does not admit options just argument elements with their values."); + return 1; + } + + + /*TODO: INIT process sending SIGTERM? Should I catch that signal?*/ + daemonPID = getpid(); + if (signal(SIGTERM, sigterm_handler) == SIG_ERR) { + syslog (LOG_ERR, "SIGTERM signal handler failed: %m"); + return 1; + } + + + if (main_child(avalue, pvalue, qvalue) < 0 ) + return 1; + return 0; } -int main_child (int argc, char *argv[]) { - struct protoent *protocol; - struct sockaddr_in addr_server; - struct sockaddr_in addr_client; - int sockclient; - pthread_t idThread; - int clilen; - +int main_child (char *address, int port, int queue) { + struct protoent *protocol; /*Network protocol*/ + struct sockaddr_in addr_server; /*struct with the server socket address*/ + struct sockaddr_in addr_client;/*struct with the client socket address*/ + int sockclient; /*File descriptor for the accepted socket*/ + pthread_t idThread; /*Thread identifier number*/ + socklen_t clilen; + int optval; + int returnValue = 0; /*OK by default*/ + - daemonize(argv[0], LOG_SYSLOG, LOG_PID); - setsid(); - - signal(SIGPIPE,SIG_IGN); - /*Retrieve protocol number from /etc/protocols file */ - protocol=getprotobyname("tcp"); + protocol=getprotobyname("tcp"); if (protocol == NULL) { - syslog(LOG_ERR, "cannot map \"tcp\" to protocol number"); - exit (1); + syslog(LOG_ERR, "cannot map \"tcp\" to protocol number: %m"); + goto EndwithError; } - + bzero((char*) &addr_server, sizeof(addr_server)); addr_server.sin_family = AF_INET; - if (inet_pton(AF_INET, argv[1], &addr_server.sin_addr.s_addr) <= 0) - syslog (LOG_ERR, "error inet_pton"); + if (inet_pton(AF_INET, address, &addr_server.sin_addr.s_addr) <= 0) { + syslog (LOG_ERR, "error inet_pton: %m"); + goto EndwithError; + } + + addr_server.sin_port = htons(port); + + if ((sockfd = socket(AF_INET, SOCK_STREAM, protocol->p_proto)) < 0) { + syslog (LOG_ERR, "socket creation failed: %m"); + goto EndandClosewithError; + } - addr_server.sin_port = htons(atol(argv[2])); - if ((sockfd = socket(AF_INET, SOCK_STREAM, 0)) < 0) { - syslog (LOG_ERR, "socket creation failed"); - exit(1); + /*We want to avoid issues while trying to bind a socket in TIME_WAIT state.*/ + /*In this application from the client there should not be any trouble if it gets*/ + /*TIME_WAIT states, because it can connect from any source port.*/ + optval = 1; + if(setsockopt(sockfd, SOL_SOCKET, SO_REUSEADDR, &optval, sizeof(optval)) < 0) { + syslog (LOG_ERR, "setsockopt failed: %m"); + goto EndandClosewithError; } - + if (bind(sockfd, (struct sockaddr *) &addr_server, sizeof(addr_server)) < 0) { - syslog (LOG_ERR, "socket bind failed"); - exit(1); + syslog (LOG_ERR, "socket bind failed: %m"); + goto EndandClosewithError; } - - if (listen (sockfd, atoi(argv[3])) < 0 ) { - syslog (LOG_ERR, "socket listen failed"); - exit(1); + + if (listen (sockfd, queue) < 0 ) { + syslog (LOG_ERR, "socket listen failed: %m"); + goto EndandClosewithError; } - + while(1) { clilen = sizeof(addr_client); if ((sockclient = accept (sockfd, (struct sockaddr *) &addr_client, &clilen)) < 0) { - syslog (LOG_ERR, "socket accept failed"); - exit(1); + syslog (LOG_ERR, "socket accept failed: %m"); + goto EndandClosewithError; } if (pthread_create (&idThread, NULL, serverThread, (void *)sockclient) != 0 ) { - syslog (LOG_ERR, "thread creation failed"); + syslog (LOG_ERR, "thread creation failed: %m"); } } + + + EndasUsual: + return returnValue; + EndandClosewithError: + close (sockfd); + EndwithError: + /*When there is an error.*/ + returnValue = -1; + goto EndasUsual; } int daemonize(const char *pname, int facility, int option) { int fd; + + if ( (fd = open( "/dev/tty", O_RDWR, 0) ) == -1) { + /*We already have no tty control*/ + close(fd); + return 0; + } + + /*Sending messages to log*/ + openlog(pname, option, facility); + /*To get a controlling tty*/ + if (ioctl(fd, TIOCNOTTY, (caddr_t)0) <0 ) { + syslog (LOG_ERR, "Getting tty failed: %m"); + return -1; + } + if (close(fd) < 0) { + syslog (LOG_ERR, "Closing tty failed: %m"); + return -1; + } + + if((fd = open( "/dev/null", O_RDWR, 0) ) == -1) { + close(fd); + return -1; + } + dup2(fd,0); + dup2(fd,1); + dup2(fd,2); + close(fd); + + return 0; +} - if ( (fd = open( "/dev/tty", O_RDWR, 0) ) == -1) { - /*We already have no tty control*/ - close(fd); - return 0; - } +void *serverThread (void * arg) { + int sock; - /*Sending messages to log*/ - openlog(pname, option, facility); + /*Variables used for the select function*/ + struct timeval ptime; + fd_set fd_read; + int ret; - ioctl(fd, TIOCNOTTY, (caddr_t)0); - close(fd); + /*Control parameters used while receiving data from the client*/ + int nLeft, nData, iPos; + /*This buffer is intended to store the data received from the client.*/ + char buffer[1025]; - if((fd = open( "/dev/null", O_RDWR, 0) ) == -1) { - close(fd); - return 0; - } - dup2(fd,0); - dup2(fd,1); - dup2(fd,2); - close(fd); + /*The command sent by the client, to be executed by this process*/ + char *command; + /*Store the command length*/ + uint32_t *commandLength; + /*Store the ack*/ + uint32_t *ack; - return 0; -} -void *serverThread (void * arg) { - int sockclient; - int n; - char buf[512]; - sockclient = (int) arg; + sock = (int) arg; + pthread_detach(pthread_self()); - n = recv(sockclient, buf, sizeof(buf), 0); - if(n > 0){ - buf[n] = '\0'; - fork_system(sockclient, buf); - } - close(sockclient); - /*FIXME: dunno why, but after finishing connection from client we have TIME_WAIT in this socket*/ + + /******************************************************************************************/ + /* Just over 1 TCP connection */ + /* COMMAND_LENGTH: Java integer 4 bytes, BIG-ENDIAN (the same as network order) */ + /* ACK: integer 4 bytes big-endian (for Java) with the received comand length */ + /* COMMAND: TPV locale character set encoding */ + /* RESULTS: TPV locale character set encoding */ + /* */ + /* JAVA CLIENT: ------------ COMMAND_LENGTH -------> :SERVER */ + /* JAVA CLIENT: <---------------- ACK -------------- :SERVER */ + /* JAVA CLIENT: -------------- COMMAND ------------> :SERVER */ + /* JAVA CLIENT: <-------------- RESULTS ------------ :SERVER */ + /* JAVA CLIENT: <---------- CLOSE CONNECTION ------- :SERVER */ + /* */ + /******************************************************************************************/ + + + /*Wait 2 seconds for data coming from client.*/ + ptime.tv_sec=2; + ptime.tv_usec=0; + + + /*COMMAND LENGTH*/ + /*First of all we receive the command size as a Java integer (4 bytes primitive type)*/ + if ((commandLength = (uint32_t *) malloc(sizeof(uint32_t))) == NULL) { + syslog (LOG_ERR, "commandLength malloc failed: %m"); + goto Error; + } + bzero(buffer, sizeof(buffer)); + nLeft = sizeof(uint32_t); + nData = iPos = 0; + do { + FD_ZERO(&fd_read); + FD_SET(sock, &fd_read); + ret = select(sock+1, &fd_read,NULL,NULL, &ptime); + if(ret == 0) { + syslog(LOG_INFO, "receiving command length timeout error"); + goto Error; + } + if(ret == -1) { + syslog(LOG_ERR, "receiving command length select error: %m"); + goto Error; + } + if((nData = recv(sock, &buffer[iPos], nLeft, 0)) <= 0) { + syslog (LOG_ERR, "command length reception failed: %m"); + goto Error; + } + nLeft -= nData; + iPos += nData; + } while (nLeft > 0); + if (nLeft < 0) { + syslog (LOG_INFO, "command length excessive data received, expected: 4, received: %d", (-nLeft + sizeof(uint32_t)) ); + } + + /*Retrieve integer (4 bytes) from buffer*/ + memcpy (commandLength, buffer, sizeof(uint32_t)); + /*Java sends the primitive integer using big-endian order (it is the same as network order)*/ + *commandLength = be32toh (*commandLength); + + + /*ACK*/ + /*Sending back the command length as the ACK message*/ + if ((ack = (uint32_t *) malloc(sizeof(uint32_t))) == NULL) { + syslog (LOG_ERR, "commandLength malloc failed: %m"); + goto ErrorAck; + } + *ack = htobe32(*commandLength); + if (send(sock, ack, sizeof(uint32_t), 0) < 0) { + syslog (LOG_ERR, "send ACK failed: %m"); + goto ErrorAck; + } + + + + /*COMMAND*/ + /*Reserving commandLength + 1 because of the string end character*/ + if ((command = (char *) malloc(*commandLength + 1)) == NULL) { + syslog (LOG_ERR, "command malloc failed: %m"); + goto ErrorCommand; + } + bzero(command, ((*commandLength) + 1)); + nLeft = *commandLength; + nData = iPos = 0; + do { + FD_ZERO(&fd_read); + FD_SET(sock, &fd_read); + ret = select(sock+1, &fd_read,NULL,NULL, &ptime); + if(ret == 0) { + syslog(LOG_INFO, "receiving command timeout error"); + goto ErrorCommand; + } + if(ret == -1) { + syslog(LOG_ERR, "receiving command select error: %m"); + goto ErrorCommand; + } + if((nData = recv(sock, &command[iPos], nLeft, 0)) <= 0) { + syslog (LOG_ERR, "command reception failed: %m"); + goto ErrorCommand; + } + nLeft -= nData; + iPos += nData; + } while (nLeft > 0); + if (nLeft < 0) { + syslog (LOG_INFO, "execessive command length, expected: %d, received: %d", *commandLength, (-nLeft + *commandLength)); + } + + + /*RESULTS*/ + fork_system(sock, command); + + + /*CLOSE CONNECTION AND FINISH*/ + + +ErrorCommand: + free(command); +ErrorAck: + free(ack); +Error: + close(sock); + free(commandLength); + pthread_exit(0); } int fork_system(int socket, char *command) { int pid; - int pfdOUT[2]; - int pfdERR[2]; + int out[2], err[2]; char buf[2000]; - char string[100]; + char string[100]; struct pollfd polls[2]; int returnstatus; int n; + int childreturnstatus; + + int returnValue = -1; /*NOK by default*/ - sem_t * semaphore; /*Required variables in order to share memory between processes*/ key_t keyvalue; - int idreturnst; - int *returnst; - - if (pipe(pfdOUT) == -1) { - syslog (LOG_ERR, "pipe for stdout failed"); - close(pfdOUT[0]); - close(pfdOUT[1]); - return -1; - } - if (pipe(pfdERR) == -1) { - syslog (LOG_ERR, "pipe for stderr failed"); - close(pfdOUT[0]); - close(pfdOUT[1]); - close(pfdERR[0]); - close(pfdERR[1]); - return -1; - } - - if ((semaphore = sem_open("javaforksem", O_CREAT, 0644, 1)) == SEM_FAILED) { - syslog (LOG_ERR, "semaphore open failed"); - close(pfdOUT[0]); - close(pfdOUT[1]); - close(pfdERR[0]); - close(pfdERR[1]); - if (sem_close(semaphore) <0 ) { - syslog (LOG_ERR, "semaphore open failed and close failed"); - } - if (sem_unlink("javaforksem") < 0) { - syslog (LOG_ERR, "semaphore open failed and unlink failed"); - } - return -1; + int idreturnst = -1; + /*Store the return status from the process launched using system or execve*/ + /*Using shared memory between the child and parent process*/ + int *returnst = NULL; + /*Required variables in order to share the semaphore between processes*/ + key_t keysemaphore; + int idsemaphore = -1; + /*Used like a barrier: the child process just can start after sending the XML init code*/ + sem_t *semaphore = NULL; + + + out[0] = out[1] = err[0] = err[1] = -1; + + + /*Creating the pipes, they will be attached to the stderr and stdout streams*/ + if (pipe(out) < 0 || pipe(err) < 0) { + syslog (LOG_ERR, "pipe failed: %m"); + goto EndandClosePipes; + } + + + + /*Allocate shared memory because we can not use named semaphores*/ + keysemaphore=ftok("/bin/ls", SHAREMEMSEM); /*the /bin/ls must exist otherwise this does not work... */ + if (keysemaphore == -1) { + syslog (LOG_ERR, "ftok failed: %m"); + goto EndandClosePipes; + } + /*Attach shared memory*/ + if ((idsemaphore = shmget(keysemaphore,sizeof(sem_t), 0660 | IPC_CREAT)) < 0) { + syslog (LOG_ERR, "semaphore initialization failed: %m"); + goto EndandReleaseSem; + } + if ((semaphore = (sem_t *)shmat(idsemaphore, (void *)0, 0)) < 0) { + goto EndandReleaseSem; } if (sem_init(semaphore, 1, 1) < 0) { - syslog (LOG_ERR, "semaphore initialization failed"); - close(pfdOUT[0]); - close(pfdOUT[1]); - close(pfdERR[0]); - close(pfdERR[1]); - if (sem_close(semaphore) <0 ) { - syslog (LOG_ERR, "semaphore open failed and close failed"); - } - if (sem_unlink("javaforksem") < 0) { - syslog (LOG_ERR, "semaphore open failed and unlink failed"); - } - return -1; - } + syslog (LOG_ERR, "semaphore initialization failed: %m"); + goto EndandDestroySem; + } if (sem_wait(semaphore) < 0) { - syslog (LOG_ERR, "semaphore wait failed"); + syslog (LOG_ERR, "semaphore wait failed: %m"); + goto EndandDestroySem; } - /*Allocate shared memory*/ - keyvalue=ftok("/bin/ls", SHAREMEMKEY); /*the /bin/ls must exist otherwise this does not work... */ + + + /*Allocate shared memory for the return status code */ + keyvalue=ftok("/bin/ls", SHAREMEMKEY); /*the /bin/ls must exist otherwise this does not work... */ if (keyvalue == -1) { - syslog (LOG_ERR, "ftok failed"); - /*TODO: Close again pipes and semaphore.*/ - return -1; + syslog (LOG_ERR, "ftok failed: %m"); + goto EndandDestroySem; } /*Attach shared memory*/ - idreturnst=shmget(keyvalue,sizeof(int), 0660 | IPC_CREAT); - if (idreturnst == -1) { - syslog (LOG_ERR, "shmget failed"); - /*TODO: Close again pipes and semaphore.*/ - return -1; + if ((idreturnst=shmget(keyvalue,sizeof(int), 0660 | IPC_CREAT)) < 0) { + syslog (LOG_ERR, "shmget failed: %m"); + goto EndandReleaseMem; } returnst = (int *)shmat(idreturnst, (void *)0, 0); if ((*returnst)== -1) { - syslog (LOG_ERR, "shmat failed"); - /*TODO: Close again pipes and semaphore.*/ - return -1; + syslog (LOG_ERR, "shmat failed: %m"); + goto EndandReleaseMem; } - /*By default*/ (*returnst) = 0; + + if ((pid=fork()) == -1) { - perror("fork: "); - close(pfdOUT[0]); - close(pfdOUT[1]); - close(pfdERR[0]); - close(pfdERR[1]); - if (sem_close(semaphore) <0 ) { - syslog (LOG_ERR, "semaphore open failed and close failed"); - } - if (sem_unlink("javaforksem") < 0) { - syslog (LOG_ERR, "semaphore open failed and unlink failed"); - } - return -1; - } - else { + syslog (LOG_ERR, "fork failed: %m"); + goto EndandReleaseMem; + } + + if (pid == 0) { /*Child process*/ - if (pid == 0) { - if (dup2(pfdOUT[1],1) < 0) { - syslog (LOG_ERR, "child: dup2 pfdOUT failed"); - exit(-1); - } - if (dup2(pfdERR[1],2) < 0) { - syslog (LOG_ERR, "child: dup2 pfdERR failed"); - exit(-1); - } - if (sem_wait(semaphore) < 0) { - syslog (LOG_ERR, "child: semaphore wait failed"); - } - returnstatus=system(command); - if (WIFEXITED(returnstatus) == 1) { - (*returnst) = WEXITSTATUS(returnstatus); - } - else { - (*returnst) = -1; - } - exit(0); + if ((dup2(out[1],1) < 0) || (dup2(err[1],2) < 0)) { + syslog (LOG_ERR, "child dup2 failed: %m"); + exit(-1); } - else { - /*Parent process*/ - polls[0].fd=pfdOUT[0]; - polls[1].fd=pfdERR[0]; - polls[0].events=POLLIN; - polls[1].events=POLLIN; - sprintf(string,""); - send(socket,string,strlen(string),0); - sprintf(string,""); - send(socket,string,strlen(string),0); - /*The child will be woken up*/ - if (sem_post(semaphore) < 0 ) { - syslog (LOG_ERR, "error waiking up child process"); - /*TODO: exit closing the descriptors*/ - } - while(1){ - if(poll(polls,2,100)){ - if(polls[0].revents&&POLLIN) { - bzero(buf,2000); - n=read(pfdOUT[0],buf,1990); - sprintf(string,""); - send(socket,string,strlen(string),0); - } - if(polls[1].revents&&POLLIN) { - bzero(buf,2000); - n=read(pfdERR[0],buf,1990); - sprintf(string,""); - send(socket,string,strlen(string),0); - } - if(!polls[0].revents&&POLLIN && !polls[1].revents&&POLLIN) { - syslog (LOG_ERR, "Error polling pipes"); - break; - } + if (sem_wait(semaphore) < 0) { + syslog (LOG_ERR, "child semaphore wait failed: %m"); + exit(-1); + } + + returnstatus=system(command); + + if (WIFEXITED(returnstatus) == 1) + (*returnst) = WEXITSTATUS(returnstatus); + else + (*returnst) = -1; + exit(0); + } + else { + /*Parent process*/ + polls[0].fd=out[0]; + polls[1].fd=err[0]; + polls[0].events=POLLIN; + polls[1].events=POLLIN; + sprintf(string,""); + send(socket,string,strlen(string),0); + sprintf(string,""); + send(socket,string,strlen(string),0); + + /*Releasing barrier, the child process can keep running*/ + if (sem_post(semaphore) < 0 ) { + syslog (LOG_ERR, "parent error releasing barrier: %m"); + /*Beaware, sig_handler with the child process :]*/ + kill(pid, SIGTERM); + goto EndandReleaseMem; + } + while(1) { + if(poll(polls,2,100)) { + if(polls[0].revents&&POLLIN) { + bzero(buf,2000); + n=read(out[0],buf,1990); + sprintf(string,""); + send(socket,string,strlen(string),0); + } + if(polls[1].revents&&POLLIN) { + bzero(buf,2000); + n=read(err[0],buf,1990); + sprintf(string,""); + send(socket,string,strlen(string),0); } - else { - /*When timeout*/ - if(waitpid(pid,NULL,WNOHANG)) { - /*Child is dead, we can finish the connection*/ - break; - } - /*The child process is not dead, keep polling more data from stdout or stderr streams*/ + if(!polls[0].revents&&POLLIN && !polls[1].revents&&POLLIN) { + syslog (LOG_ERR, "parent error polling pipes: %m"); + /*Beaware, sig_handler with the child process :]*/ + kill(pid, SIGTERM); + /*I want to send an error status to the remote calling process*/ + (*returnst) = -1; + break; } } - - /*We reach this code when the child process is dead or because of an error polling pipes*/ - /*In the second case the result stored in *returnst could be wrong, anyway there was*/ - /*an error so, the result is unpredictable.*/ - /*TODO: if error while polling pipes do not reach this code an exit with -1*/ - sprintf(string,"", (*returnst)); - send(socket,string,strlen(string),0); - sprintf(string,""); - send(socket,string,strlen(string),0); - - close(pfdOUT[0]); - close(pfdOUT[1]); - close(pfdERR[0]); - close(pfdERR[1]); - if (sem_close(semaphore) <0 ) { - syslog (LOG_ERR, "semaphore close failed"); - } - if (sem_unlink("javaforksem") < 0) { - syslog (LOG_ERR, "semaphore unlink failed"); - } - - shmdt ((int *)returnst); - shmctl (idreturnst, IPC_RMID, (struct shmid_ds *)NULL); + else { + /*When timeout*/ + if(waitpid(pid, &childreturnstatus, WNOHANG)) { + /*Child is dead, we can finish the connection*/ + /*First of all, we check the exit status of our child process*/ + /*In case of error send an error status to the remote calling process*/ + if (WIFEXITED(childreturnstatus) != 1) + (*returnst) = -1; + break; + } + /*The child process is not dead, keep polling more data from stdout or stderr streams*/ + } } } + /*Reaching this code when child finished or if error while polling pipes*/ + sprintf(string,"", (*returnst)); + send(socket,string,strlen(string),0); + sprintf(string,""); + send(socket,string,strlen(string),0); - return 0; + /*Stuff just done by the Parent process. The child process ends with exit*/ + + returnValue = 0; /*if everything went OK*/ + +EndandReleaseMem: + if (returnst != NULL) { + /*detach memory*/ + if (shmdt ((int *)returnst) < 0) + syslog (LOG_ERR, "returnstatus shared variable shmdt failed: %m"); + } + /*Mark the segment to be destroyed.*/ + if (shmctl (idreturnst, IPC_RMID, (struct shmid_ds *)NULL) < 0 ) + syslog (LOG_ERR, "returnstatus shared variable shmctl failed: %m"); +EndandDestroySem: + if (sem_destroy(semaphore) <0) + syslog (LOG_ERR, "semaphore destroy failed: %m"); +EndandReleaseSem: + /*after sem_destroy-> input/output parameter NULL?*/ + if (semaphore != NULL) { + /*detach memory*/ + if (shmdt ((sem_t *)semaphore) < 0) + syslog (LOG_ERR, "semaphore shmdt failed: %m"); + } + /*Mark the segment to be destroyed.*/ + if (shmctl (idsemaphore, IPC_RMID, (struct shmid_ds *)NULL) < 0 ) + syslog (LOG_ERR, "semaphore shmctl failed: %m"); +EndandClosePipes: + closeSafely (out[0]); + closeSafely (out[1]); + closeSafely (err[0]); + closeSafely (err[1]); + + return returnValue; } void sigterm_handler(int sig) { - if (currentPID != getpid()) { - //Do nothing - return; - } - if (signal (SIGTERM, SIG_IGN) == SIG_ERR) - { - syslog (LOG_ERR, "signal desactivation failed"); - } - - close (sockfd); - /*TODO: kill child processes and release allocate memory*/ - exit (0); + if (daemonPID != getpid()) { + //Do nothing + return; + } + if (signal (SIGTERM, SIG_IGN) == SIG_ERR) + { + syslog (LOG_ERR, "signal desactivation failed"); + } + + + close (sockfd); + /*TODO: kill child processes and release allocate memory*/ + exit (0); } diff --git a/JavaFork/Daemon/javafork.h b/JavaFork/Daemon/javafork.h index 7f03332..aacdc18 100644 --- a/JavaFork/Daemon/javafork.h +++ b/JavaFork/Daemon/javafork.h @@ -1,8 +1,61 @@ -/*System V IPC key*/ +/*System V IPC keys*/ #define SHAREMEMKEY 1 +#define SHAREMEMSEM 2 +/*Non-argument default values*/ +#define PORT 5193 +#define IPADDRESS "127.0.0.1" +#define QUEUE 6 + + + +/**************************************************************************************** +* This method is used by pthread_create * +* * +* INPUT PARAMETER: socket file descriptor * +* RETURNS: void * +****************************************************************************************/ void *serverThread (void *arg); + + + +/**************************************************************************************** +* This method is used by pthread_create * +* * +* INPUT PARAMETER: socket file descriptor * +* INPUT PARAMETER: +* INPUT PARAMETER: +* RETURNS: void * +****************************************************************************************/ int daemonize(const char *pname, int facility, int option); -int main_child (int argc, char *argv[]); + + + +/**************************************************************************************** +* This method is used by pthread_create * +* * +* INPUT PARAMETER: socket file descriptor * +* RETURNS: int * +****************************************************************************************/ +int main_child (char *address, int port, int queue); + + + +/**************************************************************************************** +* This method is used by pthread_create * +* * +* INPUT PARAMETER: socket file descriptor * +* RETURNS: void * +****************************************************************************************/ int fork_system(int socket, char *command); + + + + +/**************************************************************************************** +* This method is used by pthread_create * +* * +* INPUT PARAMETER: socket file descriptor * +* RETURNS: void * +****************************************************************************************/ void sigterm_handler(); diff --git a/JavaFork/JavaExample/javafork-example/src/main/java/de/fork/java/LauncherProcessesDiaFork.java b/JavaFork/JavaExample/javafork-example/src/main/java/de/fork/java/LauncherProcessesDiaFork.java new file mode 100644 index 0000000..2b32c3e --- /dev/null +++ b/JavaFork/JavaExample/javafork-example/src/main/java/de/fork/java/LauncherProcessesDiaFork.java @@ -0,0 +1,283 @@ +package de.fork.java; + +import java.io.ByteArrayInputStream; +import java.io.IOException; +import java.io.InputStream; +import java.io.PrintStream; +import java.net.UnknownHostException; +import javax.xml.parsers.ParserConfigurationException; +import org.apache.log4j.Logger; +import org.xml.sax.SAXException; + + +public class LauncherProcessesDiaFork { + // Exit process status + private static final int STATUS_ERR = -1; + private static final int DEFAULT_PORT = 5193; + private static final String DEFAULT_HOST = "127.0.0.1"; + + /** + * Run a process. + * + * @param command system command to be executed. + * + * @return return code. + */ + public static int exec(final String command) throws IOException, InterruptedException { + + return exec(command, null, null); + } + + /** + * Run a process. + * + * @param command system command to execute. + * @param standarOutPut if not null, the standard output is redirected to this parameter. + * + * @return return code. + */ + public static int exec(final String command, final PrintStream standarOutPut) throws IOException, InterruptedException { + + return exec(command, standarOutPut, null); + } + + + /** + * Run a process. + * + * @param command system command to be executed. + * @param standarOutPut if not null, the standard output is redirected to this parameter. + * @param errorOutPut if not null, the error output is redirected to this parameter. + * + * @return return code from the executed system command. + */ + public static int exec(final String command, final PrintStream standarOutPut, final PrintStream errorOutPut) throws IOException, InterruptedException { + + return exec(command, standarOutPut, errorOutPut, DEFAULT_HOST, DEFAULT_PORT); + } + + /** + * Run a process. + * + * @param command system command to be executed. + * @param aLogger send the information to log. + */ + public static int exec(final String command, final Logger aLogger) throws IOException, InterruptedException { + + //calling private method to handle logger input/ouput in a common method + return execHandlingLogger(command, aLogger, DEFAULT_HOST, DEFAULT_PORT); + } + + + /** + * Run process. + * + * @param commandAndArguments String array containing system command and its + * arguments to be executed.
+ * For example: + *
+	 * commandAndArguments[0]="ls";
+	 * commandAndArguments[1]="-lr";
+	 * 
+ * @param aLogger + * + * @return return code from the executed system command. + * + * @throws IOException + * @throws InterruptedException + */ + public static int exec(final String[] commandAndArguments, final Logger aLogger) throws IOException, InterruptedException { + String wholeCommand=""; + + for(String argument : commandAndArguments) { + wholeCommand = wholeCommand + " " + argument; + } + + //calling private method to handle logger input/ouput in a common method + return execHandlingLogger(wholeCommand, aLogger, DEFAULT_HOST, DEFAULT_PORT); + } + + + /** + * Run process using a remote process runner. + * + * @param command system command to be executed. + * @param standarOutPut the stdout stream from that command as a PrintStream + * @param errorOutPut the stderr stream from that command as a PrintStream + * @param host the specified host. + * @param port the where the remote process runner accepts connections. + * + *

The host name can either be a machine name, such as + * "java.sun.com", or a textual representation of its + * IP address. If a literal IP address is supplied, only the + * validity of the address format is checked. + *

+ *

For host specified in literal IPv6 address, + * either the form defined in RFC 2732 or the literal IPv6 address + * format defined in RFC 2373 is accepted. IPv6 scoped addresses are also + * supported. See here for a description of IPv6 + * scoped addresses. + *

+ * + * @return the executed command's return code. + * + * @throws UnknownHostException + * @throws IOException + */ + public static int exec(final String command, final PrintStream standarOutPut, + final PrintStream errorOutPut, final String host, final int port) + throws IOException, InterruptedException { + int exitStatus = LauncherProcessesDiaFork.STATUS_ERR; + XmlForkParser forkParser = null; + TCPForkDaemon process = null; + + try { + forkParser = new XmlForkParser(); + process = new TCPForkDaemon(forkParser, host, port); + exitStatus = process.exec(command); + } catch (ParserConfigurationException e) { + // This is not a crazy thing, we are trying to insert this new method without + // breaking the old methods which did not throw SAXException or ParserConfigurationException + // Do not blame me. + throw new IOException(e); + } catch (SAXException e) { + // This is not a crazy thing, we are trying to insert this new method without + // breaking the old methods which did not throw SAXException or ParserConfigurationException + // Do not blame me. + throw new IOException(e); + } + + + + if ((standarOutPut != null) && (process.getStdout() != null)){ + standarOutPut.println(process.getStdout()); + } + + if ((errorOutPut != null) && (process.getStderr() != null)){ + errorOutPut.println(process.getStderr()); + } + + return exitStatus; + } + + + /** + * Run process. + * + * @param command system command to be executed. + * @param aLogger + * @param host the specified host. + * @param port the TCP port where the daemon accepts connections. + * + * @return the executed command's return code. + * + * @throws IOException + * @throws InterruptedException + */ + private static int execHandlingLogger(final String command, final Logger aLogger, + final String host, int port) throws IOException, InterruptedException { + int exitStatus = LauncherProcessesDiaFork.STATUS_ERR; + XmlForkParser forkParser = null; + TCPForkDaemon process = null; + + try { + forkParser = new XmlForkParser(); + process = new TCPForkDaemon(forkParser, host, port); + exitStatus = process.exec(command); + } catch (ParserConfigurationException e) { + throw new IOException(e); + } catch (SAXException e) { + throw new IOException(e); + } + + + + if (process.getStdout() != null) { + aLogger.info(process.getStdout()); + } + if (process.getStderr() != null) { + aLogger.error(process.getStderr()); + } + + return exitStatus; + } + + + /** + * Run process + * + * @param command command and its arguments to be executed.
+ * For example: + *
+	 * commandAndArguments[0]="ls";
+	 * commandAndArguments[1]="-lr";
+	 * 
+ * @param aLogger send information to log + * + * @return the executed command's return code. + * + * @throws IOException + * @throws InterruptedException + */ + public static InputStream execStream (final String [] command, final Logger aLogger) + throws IOException, InterruptedException { + int exitStatus = LauncherProcessesDiaFork.STATUS_ERR; + InputStream stdInput = null; + XmlForkParser forkParser = null; + TCPForkDaemon process = null; + String wholeCommand=""; + + for(String argument : command) { + wholeCommand = wholeCommand + " " + argument; + } + + try { + forkParser = new XmlForkParser(); + process = new TCPForkDaemon(forkParser, DEFAULT_HOST, DEFAULT_PORT); + exitStatus = process.exec(wholeCommand); + } catch (ParserConfigurationException e) { + // This is not a crazy thing, we are trying to insert this new method without + // breaking the old methods which did not throw SAXException or ParserConfigurationException + // Do not blame me. + throw new IOException(e); + } catch (SAXException e) { + // This is not a crazy thing, we are trying to insert this new method without + // breaking the old methods which did not throw SAXException or ParserConfigurationException + // Do not blame me. + throw new IOException(e); + } + + + if(exitStatus == 0) { + stdInput = new ByteArrayInputStream(process.getStdout().getBytes("UTF-8")); + } + else { + aLogger.error(process.getStderr()); + } + + + return stdInput; + } + + /** + *

The command is lunched from location + *

  • #>cd location
  • + *
  • #location> command
  • + * + * @param command the command to be executed by the daemon. + * @param location + * + * @return the executed command's return code.
    + * Usually 0 if execution is OK, otherwise !=0 + * + * @throws IOException + * @throws InterruptedException + */ + public static int execInLocation (final String command, final String location) throws IOException, InterruptedException { + int exitStatus = LauncherProcessesDiaFork.STATUS_ERR; + final String wholeCommand = "cd " + location + " && " + command; + + exitStatus = exec(wholeCommand, null, null, DEFAULT_HOST, DEFAULT_PORT); + return exitStatus; + } +} diff --git a/JavaFork/JavaExample/javafork-example/src/main/java/de/fork/java/RemoteForkMain.java b/JavaFork/JavaExample/javafork-example/src/main/java/de/fork/java/RemoteForkMain.java new file mode 100644 index 0000000..9b4180b --- /dev/null +++ b/JavaFork/JavaExample/javafork-example/src/main/java/de/fork/java/RemoteForkMain.java @@ -0,0 +1,36 @@ +package de.fork.java; + +import java.io.ByteArrayOutputStream; +import java.io.FileNotFoundException; +import java.io.IOException; +import java.io.PrintStream; +import javax.xml.parsers.ParserConfigurationException; +import org.xml.sax.SAXException; + + +public class RemoteForkMain { + + /** + * @param args + * @throws InterruptedException + * @throws IOException + * @throws SAXException + * @throws ParserConfigurationException + * @throws FileNotFoundException + */ + public static void main(String[] args) throws IOException, InterruptedException { + + final ByteArrayOutputStream baos = new ByteArrayOutputStream(); + final PrintStream stdout = new PrintStream(baos); + final String command = "ls -lah"; + final ByteArrayOutputStream baos2 = new ByteArrayOutputStream(); + final PrintStream stderr = new PrintStream(baos2); + int result; + + result = LauncherProcessesDiaFork.exec(command,stdout, stderr, "127.0.0.1", 5193); + System.out.println(result); + System.out.println("Stdout: " + baos.toString()); + System.out.println("Stderr: " + baos2.toString()); + } + +} \ No newline at end of file diff --git a/JavaFork/JavaExample/javafork-example/src/main/java/de/fork/java/TCPForkDaemon.java b/JavaFork/JavaExample/javafork-example/src/main/java/de/fork/java/TCPForkDaemon.java new file mode 100644 index 0000000..0ecb257 --- /dev/null +++ b/JavaFork/JavaExample/javafork-example/src/main/java/de/fork/java/TCPForkDaemon.java @@ -0,0 +1,178 @@ +package de.fork.java; + +import java.io.DataInputStream; +import java.io.DataOutputStream; +import java.io.IOException; +import java.io.PrintWriter; +import java.net.InetAddress; +import java.net.Socket; +import java.net.UnknownHostException; +import org.xml.sax.SAXException; + +/** + *

    + * With this class we can run processes using the intended daemon which is + * waiting for TCP connections in a specified port. + *

    + *

    + * Receiving the results from the daemon where we can find three kinds of + * different fields: stderror, stdout and the return value of the command which was + * run by the remote daemon. Each field is related to the stderr, stdout and + * return code respectively. + *

    + *

    + * This class has to retrieve the results from the remote daemon and it offers two + * methods wich can be used to retrieve the stderr and stdout in a right way + * without having to know about the coding used by the daemon to send us the results. + * The user does not have to know about how the daemon sends the data, he or she + * will work directly with the strings related to each stream using these methods: + * {@link TCPForkDaemon#getStdout()} and {@link TCPForkDaemon#getStderr()}. + * The return code from the command executed by the daemon can be retrieved as the + * return parameter from the method {@link TCPForkDaemon#exec(String, String, int)} + *

    + *

    + * Instances of this class are mutable. To use them concurrently, clients must surround each + * method invocation (or invocation sequence) with external synchronization of the clients choosing. + *

    + */ +public class TCPForkDaemon { + private final XmlForkParser parser; + private final String host; + private final int port; + + + /** + * Default constructor for this {@link TCPForkDaemon} implementation. + * + *

    The host name can either be a machine name, such as + * "java.sun.com", or a textual representation of its + * IP address. If a literal IP address is supplied, only the + * validity of the address format is checked. + *

    + *

    For host specified in literal IPv6 address, + * either the form defined in RFC 2732 or the literal IPv6 address + * format defined in RFC 2373 is accepted. IPv6 scoped addresses are also + * supported. See here for a description of IPv6 + * scoped addresses. + *

    + * @param parser instance implemeting {@link XmlForkParser} which knows about what + * codification uses the daemon to send us the results of the command sent to + * by the remote daemon by the {@link TCPForkDaemon.#exec(String)} method. + * @param host the specified host. + * @param port the TCP port where the daemon accepts connections. + * + */ + public TCPForkDaemon (final XmlForkParser parser, final String host, final int port) { + this.parser = parser; + this.host = host; + this.port = port; + } + + + /** + *

    + * This method sends commands to a remote daemon using a TCP socket. + * We create a new TCP socket every time we send commands. + *

    + *

    + * It uses a TCP connection in order to send commands and receive + * the results related to that command from the remote daemon. The command's + * result code which was run by the remote daemon can be retrieved from the + * return parameter of this method. + *

    + * @param command the command to be executed by the daemon. + * @return the executed command's return code. + * @throws IOException + * @throws UnknownHostException + * @throws SAXException + * @throws SecurityException if a security manager exists + */ + public int exec(final String command) throws UnknownHostException, IOException, SAXException { + PrintWriter out = null; + Socket socket = null; + + /******************************************************************************************/ + /* Just over 1 TCP connection */ + /* COMMAND_LENGTH: Java integer 4 bytes, BIG-ENDIAN (the same as network order) */ + /* ACK: integer 4 bytes big-endian (for Java) with the sent comand length */ + /* COMMAND: TPV locale character set encoding */ + /* RESULTS: TPV locale character set encoding */ + /* */ + /* JAVA CLIENT: ------------ COMMAND_LENGTH -------> :SERVER */ + /* JAVA CLIENT: <---------------- ACK -------------- :SERVER */ + /* JAVA CLIENT: -------------- COMMAND ------------> :SERVER */ + /* JAVA CLIENT: <-------------- RESULTS ------------ :SERVER */ + /* JAVA CLIENT: <---------- CLOSE CONNECTION ------- :SERVER */ + /* */ + /******************************************************************************************/ + + + + socket = new Socket(InetAddress.getByName(host), port); + try { + /*Must be used the remote charset :S*/ + byte [] commandEncoded = command.getBytes("UTF-8"); + + DataOutputStream sendData = new DataOutputStream(socket.getOutputStream()); + DataInputStream receiveData = new DataInputStream(socket.getInputStream()); + + // 1. COMMAND_LENGTH + sendData.writeInt(commandEncoded.length); + + // 2. ACK + // TODO: if the server close the connection we could stay here probably + // until TCP keepalive is sent (20 hours by default in Linux) + int ack = receiveData.readInt(); + if (ack != commandEncoded.length) + throw new IOException("invalid ACK, something went wrong " + + "with the TCPForkDaemon. Check the /var/log/messages file in the TPV"); + + + // 3. COMMAND + sendData.write(commandEncoded); + + + // 4. RESULTS + // TODO: if the server closes the connection we could stay here probably + // until TCP keepalive is sent (20 hours by default in Linux) + parser.setStream(socket.getInputStream()); + + + // 5. SERVER CLOSES CONNECTION + } + finally { + if (out != null) { + out.close(); + } + socket.close(); + } + + //If everything went alright we should be able to retrieve the return + //status of the remotely executed command. + return parser.getReturnValue(); + } + + + /** + * Retrieve the standard output.
    + * When there is nothing from the standard output this method returns null. + * + * @see {@link TCPForkDaemon#getStderr()} + * @return the stdout stream + */ + public String getStdout() { + return parser.getStdout(); + } + + + /** + * Retrieve the stderr stream as a {@link String} from the command which + * was run by the remote daemon + * + * @see {@link TCPForkDaemon#getStdout()} + * @return the stderr stream + */ + public String getStderr() { + return parser.getStderr(); + } +} diff --git a/JavaFork/JavaExample/javafork-example/src/main/java/de/fork/java/XmlForkParser.java b/JavaFork/JavaExample/javafork-example/src/main/java/de/fork/java/XmlForkParser.java new file mode 100644 index 0000000..51fe7f0 --- /dev/null +++ b/JavaFork/JavaExample/javafork-example/src/main/java/de/fork/java/XmlForkParser.java @@ -0,0 +1,195 @@ +package de.fork.java; + +import java.io.IOException; +import java.io.InputStream; +import javax.xml.parsers.ParserConfigurationException; +import javax.xml.parsers.SAXParser; +import javax.xml.parsers.SAXParserFactory; +import org.apache.log4j.Logger; +import org.xml.sax.Attributes; +import org.xml.sax.SAXException; +import org.xml.sax.SAXParseException; +import org.xml.sax.ext.DefaultHandler2; + +/** + *

    + * Class intended to parse the XML stream received from the daemon which is + * waiting to run commands. These commands are sent by the method + * {@link es.dia.pos.n2a.util.os.unix.TCPForkDaemon#exec(String, String, int)} + *

    + *

    + * After processing one command the daemon sends a XML stream with the stderr, + * stdout and return status of that command. With this class we extract those values + * and we can retrieve them using the methods {@link #getStderr() }, {@link #getStdout()} + * and {@link #getReturnValue()} + *

    + *

    + *

    + * Example, stream received from daemon:
    + * {@code
    + * 
    + * }
    + * 
    + *

    + *

    + * Instances of this class are mutable. To use them concurrently, clients must surround each + * method invocation (or invocation sequence) with external synchronization of the clients choosing. + *

    + */ +public class XmlForkParser extends DefaultHandler2 { + private static final Logger logger = Logger.getLogger(XmlForkParser.class); + private StringBuffer accumulator = new StringBuffer(); + private String stderr = new String(); + private String stdout = new String(); + private String returnCode = new String(); + final SAXParserFactory spf = SAXParserFactory.newInstance(); + private final SAXParser saxParser; + + public XmlForkParser() throws ParserConfigurationException, SAXException { + saxParser = spf.newSAXParser(); + } + + public void setStream(InputStream stream) throws SAXException, IOException { + saxParser.parse(stream, this); + } + + /** + *

    + * The daemon sends a XML stream, we parse that stream and the results are + * stored in the instace fields {@link #stderr}, {@link #stdout} and {@link returnCode} + *

    + *

    + * Later we can retrieve the results with {@link #getStderr()}, {@link #getStdout()} and + * {@link #getReturnValue()} + *

    + */ + @Override + public void endElement (final String uri, final String localName, final String qName) { + if (qName.equals("error")) { + // After , we've got the stderror + stderr = stderr + accumulator.toString(); + } else if (qName.equals("out")) { + // After , we've got the stdout + stdout = stdout + accumulator.toString(); + } else if (qName.equals("ret")) { + returnCode = returnCode + accumulator.toString(); + } + } + + /** + *

    + * This method removes the \n characters at the end of the stdout + * or stderr stream. + *

    + * + * @throws SAXException If any SAX errors occur during processing. + */ + @Override + public void endDocument () throws SAXException + { + if (stderr.length() != 0) { + String lastStderr = stderr.replaceFirst("\\\n$", ""); + stderr = lastStderr; + } + else { + //if there is nothing from the stderr stream + stderr = null; + } + if (stdout.length() != 0) { + String lastStdout = stdout.replaceFirst("\\\n$", ""); + stdout = lastStdout; + } + else { + //if there is nothing from the stdout stream + stdout = null; + } + } + + /** + * Retrieve the standard error. + * When there is nothing from the standard error this method returns null. + * + *
    +	 * Example, stream received from daemon:
    +	 * {@code
    +	 * 
    +	 * }
    +	 * 
    + *

    + *

    + *

    +	 * From that example with this method we are going to obtain this return parameter:
    +	 * {@code
    +	 * ls: no se puede acceder a bbb: No existe el fichero o el directorio
    +	 * ls: no se puede acceder a aaa: No existe el fichero o el directorio
    +	 * ls: no se puede acceder a dddd: No existe el fichero o el directorio
    +	 * }
    +	 * 
    + * + * @return stderr + */ + public String getStderr() { + return stderr; + + } + + + /** + * Retrieve the standard output. + * When there is nothing from the standard output this method returns null. + * + * @see {@link XmlForkParser#getStderr()} + * @return stdout + */ + public String getStdout() { + return stdout; + } + + + /** + * Retrieve the return code from the executed command. + * + * @return return status, usually 0 means the command went OK. + */ + public int getReturnValue() { + return new Integer(returnCode).intValue(); + } + + + @Override + public void startElement (final String uri, final String localName, + final String qName, final Attributes attributes) { + accumulator.setLength(0); + } + + + @Override + public void characters(final char[] buffer, final int start, final int length) { + accumulator.append(buffer, start, length); + } + + + @Override + public void warning(final SAXParseException exception) { + logger.error("WARNING line:" + exception.getLineNumber(), exception); + } + + + @Override + public void error(final SAXParseException exception) { + logger.error("ERROR line:" + exception.getLineNumber(), exception); + } + + + @Override + public void fatalError(final SAXParseException exception) throws SAXException { + logger.error("FATAL ERROR line:" + exception.getLineNumber(), exception); + throw (exception); + } +} -- 2.1.4