From 3464a921ad462cccfe36249f37ae5cdc9d743144 Mon Sep 17 00:00:00 2001 From: Dave Parker Date: Thu, 27 Aug 2015 07:30:43 +0000 Subject: [PATCH] First version of Nailgun support built directly into PRISM. Adding switch -ng when running prism will instead start a Nailgun server ready to run PRISM instances, and running ngprism runs PRISM as a Nailgun client. git-svn-id: https://www.prismmodelchecker.org/svn/prism/prism/trunk@10588 bbc10eb1-c90d-0410-af57-cb519fbb1720 --- prism/.classpath | 1 + prism/Makefile | 2 + prism/include/PrismNative.h | 8 + prism/install.sh | 2 +- prism/lib/README.md | 3 +- prism/src/automata/LTL2DA.java | 4 + prism/src/prism/Makefile | 5 +- prism/src/prism/PrismCL.java | 15 +- prism/src/prism/PrismNG.java | 81 +++ prism/src/prism/PrismNative.cc | 12 + prism/src/prism/PrismNative.java | 6 + prism/src/prism/ng.c.orig | 863 +++++++++++++++++++++++++++++++ prism/src/prism/ngprism.c | 850 ++++++++++++++++++++++++++++++ 13 files changed, 1847 insertions(+), 5 deletions(-) create mode 100644 prism/src/prism/PrismNG.java create mode 100644 prism/src/prism/ng.c.orig create mode 100644 prism/src/prism/ngprism.c diff --git a/prism/.classpath b/prism/.classpath index c2f245c0..1bef3aa5 100644 --- a/prism/.classpath +++ b/prism/.classpath @@ -11,5 +11,6 @@ + diff --git a/prism/Makefile b/prism/Makefile index 343b2d9b..084d28af 100644 --- a/prism/Makefile +++ b/prism/Makefile @@ -420,6 +420,8 @@ bin_scripts: echo Copying "$(SRC_DIR)/bin/$$target -> bin/$$target_trunc" && \ cp $(SRC_DIR)/bin/$$target bin/$$target_trunc; \ done; + @echo Copying "$(OBJ_DIR)/prism/ngprism$(EXE) -> bin/ngprism$(EXE)" && \ + cp $(OBJ_DIR)/prism/ngprism$(EXE) bin/ngprism$(EXE) @./install.sh silent # Unless VERSION has been passed in (as VERSION=xxx), diff --git a/prism/include/PrismNative.h b/prism/include/PrismNative.h index e8ed62e7..d6c28117 100644 --- a/prism/include/PrismNative.h +++ b/prism/include/PrismNative.h @@ -127,6 +127,14 @@ JNIEXPORT void JNICALL Java_prism_PrismNative_PN_1SetExportAdv JNIEXPORT void JNICALL Java_prism_PrismNative_PN_1SetExportAdvFilename (JNIEnv *, jclass, jstring); +/* + * Class: prism_PrismNative + * Method: PN_SetWorkingDirectory + * Signature: (Ljava/lang/String;)I + */ +JNIEXPORT jint JNICALL Java_prism_PrismNative_PN_1SetWorkingDirectory + (JNIEnv *, jclass, jstring); + /* * Class: prism_PrismNative * Method: PN_GetStdout diff --git a/prism/install.sh b/prism/install.sh index 26dea940..24782a68 100755 --- a/prism/install.sh +++ b/prism/install.sh @@ -17,7 +17,7 @@ if [ ! "$1" = "silent" ] ; then echo "Installing PRISM (directory=$PRISM_DIR)" fi TEMP_FILE=tmp -FILES_TO_CHANGE=`find bin -maxdepth 1 ! -type d ! -name '*.bat'` +FILES_TO_CHANGE=`find bin -maxdepth 1 ! -type d ! -name '*.bat' ! -name ngprism` for FILE_TO_CHANGE in $FILES_TO_CHANGE do if [ -f "$PRISM_DIR"/$FILE_TO_CHANGE ]; then diff --git a/prism/lib/README.md b/prism/lib/README.md index 0a335c39..94d78c04 100644 --- a/prism/lib/README.md +++ b/prism/lib/README.md @@ -7,9 +7,8 @@ To simplify maintenance of scripts and config files, we mostly omit version numb * jcommon.jar - JCommon, version 1.0.16 * jfreechart.jar - JFreeChart, version 1.0.13 * jhoafparser.jar - jhoafparser, version 1.1.0 +* nailgun-server.jar - Nailgun, version 0.9.2-SNAPSHOT See here for more details and links: http://www.prismmodelchecker.org/other-downloads.php - - diff --git a/prism/src/automata/LTL2DA.java b/prism/src/automata/LTL2DA.java index 2d1a9f0d..34e58d1f 100644 --- a/prism/src/automata/LTL2DA.java +++ b/prism/src/automata/LTL2DA.java @@ -204,6 +204,10 @@ public class LTL2DA extends PrismComponent builder.redirectOutput(tool_output); builder.redirectErrorStream(true); + // if we are running under the Nailgun environment, setup the + // environment to include the environment variables of the Nailgun client + prism.PrismNG.setupChildProcessEnvironment(builder); + Process p = builder.start(); p.getInputStream().close(); diff --git a/prism/src/prism/Makefile b/prism/src/prism/Makefile index f2e01b38..78361053 100644 --- a/prism/src/prism/Makefile +++ b/prism/src/prism/Makefile @@ -31,7 +31,7 @@ O_FILES = $(CC_FILES:%.cc=$(PRISM_DIR_REL)/$(OBJ_DIR)/$(THIS_DIR)/%.o) default: all -all: checks $(CLASS_FILES) $(PRISM_DIR_REL)/$(INCLUDE_DIR)/PrismNative.h $(PRISM_DIR_REL)/$(LIB_DIR)/$(LIBPREFIX)prism$(LIBSUFFIX) +all: checks $(CLASS_FILES) $(PRISM_DIR_REL)/$(INCLUDE_DIR)/PrismNative.h $(PRISM_DIR_REL)/$(LIB_DIR)/$(LIBPREFIX)prism$(LIBSUFFIX) $(PRISM_DIR_REL)/$(OBJ_DIR)/$(THIS_DIR)/ngprism # Try and prevent accidental makes (i.e. called manually, not from top-level Makefile) checks: @@ -54,6 +54,9 @@ $(PRISM_DIR_REL)/$(LIB_DIR)/$(LIBPREFIX)prism$(LIBSUFFIX): $(O_FILES) $(PRISM_DIR_REL)/$(OBJ_DIR)/$(THIS_DIR)/%.o: %.cc $(CPP) $(CPPFLAGS) -c $< -o $@ $(INCLUDES) +$(PRISM_DIR_REL)/$(OBJ_DIR)/$(THIS_DIR)/ngprism: ngprism.c + $(C) $(CPPFLAGS) -o $@ $< + clean: checks @rm -f $(CLASS_FILES) $(PRISM_DIR_REL)/$(LIB_DIR)/$(LIBPREFIX)prism$(LIBSUFFIX) $(O_FILES) diff --git a/prism/src/prism/PrismCL.java b/prism/src/prism/PrismCL.java index 003220d1..3b3ebb81 100644 --- a/prism/src/prism/PrismCL.java +++ b/prism/src/prism/PrismCL.java @@ -28,6 +28,7 @@ package prism; import java.io.File; import java.io.FileNotFoundException; +import java.net.UnknownHostException; import java.util.ArrayList; import java.util.List; @@ -2369,7 +2370,19 @@ public class PrismCL implements PrismModelListener public static void main(String[] args) { - new PrismCL().go(args); + // Normal operation: just run PrismCL + if (!(args.length > 0 && "-ng".equals(args[0]))) { + new PrismCL().go(args); + } + // Nailgun server mode (-ng switch) + else { + try { + System.out.println("Starting PRISM-Nailgun server..."); + com.martiansoftware.nailgun.NGServer.main(new String[0]); + } catch (NumberFormatException | UnknownHostException e) { + System.out.println("Failed to launch Nailgun server: " + e); + } + } } } diff --git a/prism/src/prism/PrismNG.java b/prism/src/prism/PrismNG.java new file mode 100644 index 00000000..2be47bf3 --- /dev/null +++ b/prism/src/prism/PrismNG.java @@ -0,0 +1,81 @@ +//============================================================================== +// +// Copyright (c) 2015- +// Authors: +// * Joachim Klein (University of Birmingham/Oxford) +// +//------------------------------------------------------------------------------ +// +// This file is part of PRISM. +// +// PRISM 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. +// +// PRISM 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 PRISM; if not, write to the Free Software Foundation, +// Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +// +//============================================================================== + +package prism; + +import java.util.Map; +import java.util.Map.Entry; + +import com.martiansoftware.nailgun.NGContext; + +/** + * Entry point for Nailgun. + */ +public class PrismNG +{ + /** The current NailGun context */ + private static NGContext currentNailGunContext = null; + + /** + * This method is called on the Nailgun server to start a new PRISM instance + * to serve as the counterpart for a client invocation. + *
+ * This method is synchronized to ensure that there can only be a single + * running instance of PRISM in the Nailgun server VM. + *
+ * Sets the working directory to that of the client. + */ + public synchronized static void nailMain(NGContext context) throws InterruptedException + { + currentNailGunContext = context; + if (PrismNative.setWorkingDirectory(context.getWorkingDirectory()) != 0) { + System.err.println("Nailgun: Can not change working directory to " + context.getWorkingDirectory()); + System.exit(1); + } + + PrismCL.main(context.getArgs()); + } + + /** + * Modifies the environment of the ProcessBuilder by adding the environment + * variables passed by the Nailgun client. + *
+ * If we are not running in a Nailgun context, this is a no-op. + * */ + public static void setupChildProcessEnvironment(ProcessBuilder builder) + { + if (currentNailGunContext == null) + return; + + Map env = builder.environment(); + for (Entry entry : currentNailGunContext.getEnv().entrySet()) { + if (entry.getKey() instanceof String && entry.getValue() instanceof String) { + env.put((String) entry.getKey(), (String) entry.getValue()); + } + } + } +} diff --git a/prism/src/prism/PrismNative.cc b/prism/src/prism/PrismNative.cc index e4b2d75f..daf8fd29 100644 --- a/prism/src/prism/PrismNative.cc +++ b/prism/src/prism/PrismNative.cc @@ -29,6 +29,7 @@ #include #include #include +#include #include "PrismNativeGlob.h" #include "jnipointer.h" @@ -179,6 +180,17 @@ JNIEXPORT void JNICALL Java_prism_PrismNative_PN_1SetExportAdvFilename(JNIEnv *e export_adv_filename = NULL; } } + +//------------------------------------------------------------------------------ + +JNIEXPORT jint JNICALL Java_prism_PrismNative_PN_1SetWorkingDirectory(JNIEnv *env, jclass cls, jstring dn) { + int rv; + const char* dirname = env->GetStringUTFChars(dn, 0); + rv = chdir(dirname); + env->ReleaseStringUTFChars(dn, dirname); + return rv; +} + //------------------------------------------------------------------------------ // Some miscellaneous native methods //------------------------------------------------------------------------------ diff --git a/prism/src/prism/PrismNative.java b/prism/src/prism/PrismNative.java index a9fd494c..7847978f 100644 --- a/prism/src/prism/PrismNative.java +++ b/prism/src/prism/PrismNative.java @@ -152,6 +152,12 @@ public class PrismNative { PN_SetExportAdvFilename(filename); } + + private static native int PN_SetWorkingDirectory(String dirname); + /** Changes the current working directory. Returns 0 on success. */ + public static int setWorkingDirectory(String dirname) { + return PN_SetWorkingDirectory(dirname); + } // Some miscellaneous native methods public static native long PN_GetStdout(); diff --git a/prism/src/prism/ng.c.orig b/prism/src/prism/ng.c.orig new file mode 100644 index 00000000..807810ea --- /dev/null +++ b/prism/src/prism/ng.c.orig @@ -0,0 +1,863 @@ +/* + + Copyright 2004-2012, Martian Software, Inc. + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + +*/ + +/** + * @author Marty Lamb + * @author Pete Kirkham (Win32 port) + */ + +#ifdef WIN32 + #include + #include +#else + #include + #include + #include + #include + #include + #include +#endif + +#include +#include +#include +#include +#include +#include + +#define NAILGUN_VERSION "0.9.0" + +#define BUFSIZE (2048) + +#ifdef WIN32 + HANDLE NG_STDIN_FILENO; + HANDLE NG_STDOUT_FILENO; + HANDLE NG_STDERR_FILENO; + #define FILE_SEPARATOR '\\' + #define MSG_WAITALL 0 +#else + #define NG_STDIN_FILENO STDIN_FILENO + #define NG_STDOUT_FILENO STDOUT_FILENO + #define NG_STDERR_FILENO STDERR_FILENO + #define FILE_SEPARATOR '/' + typedef int HANDLE; + typedef unsigned int SOCKET; +#endif + +#ifdef __APPLE__ + #define SEND_FLAGS 0 +#else + #define SEND_FLAGS MSG_NOSIGNAL +#endif + +#ifndef MIN +#define MIN(a,b) ((a> 24) & 0xff; + header[1] = (size >> 16) & 0xff; + header[2] = (size >> 8) & 0xff; + header[3] = size & 0xff; + header[4] = chunkType; + +#ifdef WIN32 + if (WaitForSingleObject(sending, INFINITE) != WAIT_OBJECT_0) { + handleError(); + } +#else + gettimeofday(&sendtime, NULL); +#endif + + bytesSent = sendAll(nailgunsocket, header, CHUNK_HEADER_LEN); + if (bytesSent != 0 && size > 0) { + bytesSent = sendAll(nailgunsocket, buf, size); + } else if (bytesSent == 0 && (chunkType != CHUNKTYPE_HEARTBEAT || errno != EPIPE)) { + perror("send"); + handleSocketClose(); + } + +#ifdef WIN32 + ReleaseMutex(sending); +#endif +} + +/** + * Sends the contents of the specified file as a long argument (--nailgun-filearg) + * This is sent as one or more chunks of type CHUNK_LONGARG. The end of the argument + * is indicated by an empty chunk. + * + * @param filename the name of the file to send. + * @return nonzero on failure + */ +int sendFileArg(char *filename) { + int i, f; + + if ((f = open(filename, O_RDONLY)) < 0) { + perror("--nailgun-filearg"); + return 1; + } + + i = read(f, buf, BUFSIZE); + while (i > 0) { + sendChunk(i, CHUNKTYPE_LONGARG, buf); + i = read(f, buf, BUFSIZE); + } + if (i < 0) { + perror("--nailgun-filearg"); + return 1; + } + sendChunk(0, CHUNKTYPE_LONGARG, buf); + + close(f); + return 0; +} + +/** + * Sends a null-terminated string with the specified chunk type. + * + * @param chunkType the chunk type identifier + * @param text the null-terminated string to send + */ +void sendText(char chunkType, char *text) { + int len = text ? strlen(text) : 0; + sendChunk(len, chunkType, text); +} + +/** + * Receives len bytes from the nailgun socket and copies them to the specified file descriptor. + * Used to route data to stdout or stderr on the client. + * + * @param destFD the destination file descriptor (stdout or stderr) + * @param len the number of bytes to copy + */ +void recvToFD(HANDLE destFD, char *buf, unsigned long len) { + unsigned long bytesRead = 0; + int bytesCopied; + + while (bytesRead < len) { + unsigned long bytesRemaining = len - bytesRead; + int bytesToRead = (BUFSIZE < bytesRemaining) ? BUFSIZE : bytesRemaining; + int thisPass = 0; + + thisPass = recv(nailgunsocket, buf, bytesToRead, MSG_WAITALL); + if (thisPass == 0 || thisPass == -1) { + perror("recv"); + handleSocketClose(); + } + bytesRead += thisPass; + + bytesCopied = 0; + + while(bytesCopied < thisPass) { + #ifdef WIN32 + DWORD thisWrite = 0; + + WriteFile(destFD, buf + bytesCopied, thisPass - bytesCopied, + &thisWrite, NULL); + + if (thisWrite < 0) { + break; + } + + bytesCopied += thisWrite; + #else + int bytesWritten = write(destFD, buf + bytesCopied, thisPass - bytesCopied); + if (bytesWritten == -1) { + perror("write"); + handleSocketClose(); + } + bytesCopied += bytesWritten; + #endif + } + } +} + +unsigned long recvToBuffer(unsigned long len) { + unsigned long bytesRead = 0; + while(bytesRead < len) { + int thisPass = recv(nailgunsocket, buf + bytesRead, len - bytesRead, MSG_WAITALL); + if (thisPass == 0 || thisPass == -1) { + perror("recv"); + handleSocketClose(); + } + bytesRead += thisPass; + } + return bytesRead; +} + +/** + * Processes an exit chunk from the server. This is just a string + * containing the exit code in decimal format. It should fit well + * within our buffer, so assume that it does. + * + * @param len the current length of the buffer containing the exit code. + */ +void processExit(char *buf, unsigned long len) { + int exitcode; + int bytesToRead = (BUFSIZE - 1 < len) ? BUFSIZE - 1 : len; + int bytesRead = recvToBuffer(bytesToRead); + + if (bytesRead < 0) { + handleSocketClose(); + } + + buf[bytesRead] = 0; + + exitcode = atoi(buf); + + cleanUpAndExit(exitcode); +} + + +/** + * Sends len bytes from buf to the nailgun server in a stdin chunk. + * + * @param buf the bytes to send + * @param len the number of bytes to send + */ +void sendStdin(char *buf, unsigned int len) { +#ifndef WIN32 + readyToSend = 0; +#endif + sendChunk(len, CHUNKTYPE_STDIN, buf); +} + +/** + * Sends a stdin-eof chunk to the nailgun server + */ +void processEof() { + sendChunk(0, CHUNKTYPE_STDIN_EOF, buf); +} + +/** + * Sends a heartbeat chunk to let the server know the client is still alive. + */ +void sendHeartbeat() { + sendChunk(0, CHUNKTYPE_HEARTBEAT, buf); +} + +#ifdef WIN32 + +HANDLE createEvent(BOOL manualReset) { + return CreateEvent(NULL, /* default security */ + manualReset, + FALSE, /* initial state unsignalled */ + NULL /* unnamed event */); +} + +DWORD WINAPI sendHeartbeats(LPVOID lpParameter) { + + /* this could be made more efficient by only sending heartbeats when stdin chunks aren't being sent */ + for (;;) { + Sleep(HEARTBEAT_TIMEOUT_MILLIS); + sendHeartbeat(); + } +} + +/** + * Thread main for reading from stdin and sending + */ +DWORD WINAPI processStdin (LPVOID lpParameter) { + /* buffer used for reading and sending stdin chunks */ + char wbuf[BUFSIZE]; + + /* number of bytes read */ + DWORD numberOfBytes; + + for (;;) { + + /* wait for ready to send */ + if(WaitForSingleObject(readyToSend, INFINITE) != WAIT_OBJECT_0) { + handleError(); + } + + /* read data from stdin */ + if (! ReadFile(NG_STDIN_FILENO, wbuf, BUFSIZE, &numberOfBytes, NULL)) { + if (numberOfBytes != 0) { + handleError(); + } + } + + /* send data to server */ + if (numberOfBytes > 0) { + sendStdin(wbuf, numberOfBytes); + } else { + processEof(); + break; + } + + } + return 0; +} +#else +/** + * Reads from stdin and transmits it to the nailgun server in a stdin chunk. + * Sends a stdin-eof chunk if necessary. + * + * @return zero if eof has been reached. + */ +int processStdin() { + int bytesread = read(STDIN_FILENO, buf, BUFSIZE); + if (bytesread > 0) { + sendStdin(buf, bytesread); + } else if (bytesread == 0) { + processEof(); + } + return(bytesread); +} +#endif + +#ifdef WIN32 +/** + * Initialise Windows sockets + */ +void initSockets () { + WSADATA win_socket_data; /* required to initialise winsock */ + + WSAStartup(2, &win_socket_data); + + /* create flow control event and mutex */ + readyToSend = createEvent(FALSE); + sending = CreateMutex(NULL, FALSE, NULL); +} +#endif + +#ifdef WIN32 +/** + * Initialise the asynchronous io. + */ +void initIo () { + /* create non-blocking console io */ + AllocConsole(); + + NG_STDIN_FILENO = GetStdHandle(STD_INPUT_HANDLE); + NG_STDOUT_FILENO = GetStdHandle(STD_OUTPUT_HANDLE); + NG_STDERR_FILENO = GetStdHandle(STD_ERROR_HANDLE); +} +#endif + +#ifdef WIN32 +/** + * Initialise the asynchronous io. + */ +void winStartInput () { + SECURITY_ATTRIBUTES securityAttributes; + DWORD threadId = 0; + + securityAttributes.bInheritHandle = TRUE; + securityAttributes.lpSecurityDescriptor = NULL; + securityAttributes.nLength = 0; + + if (!CreateThread(&securityAttributes, 0, &processStdin, NULL, 0, &threadId)) { + handleError(); + } + + if (!CreateThread(&securityAttributes, 0, &sendHeartbeats, NULL, 0, &threadId)) { + handleError(); + } +} +#endif + +/** + * Processes data from the nailgun server. + */ +void processnailgunstream() { + + /*for (;;) {*/ + unsigned long len; + char chunkType; + + recvToBuffer(CHUNK_HEADER_LEN); + + len = ((buf[0] << 24) & 0xff000000) + | ((buf[1] << 16) & 0x00ff0000) + | ((buf[2] << 8) & 0x0000ff00) + | ((buf[3]) & 0x000000ff); + + chunkType = buf[4]; + + switch(chunkType) { + case CHUNKTYPE_STDOUT: recvToFD(NG_STDOUT_FILENO, buf, len); + break; + case CHUNKTYPE_STDERR: recvToFD(NG_STDERR_FILENO, buf, len); + break; + case CHUNKTYPE_EXIT: processExit(buf, len); + break; + case CHUNKTYPE_SENDINPUT: +#ifdef WIN32 + SetEvent(readyToSend); +#else + readyToSend = 1; +#endif + break; + default: fprintf(stderr, "Unexpected chunk type %d ('%c')\n", chunkType, chunkType); + cleanUpAndExit(NAILGUN_UNEXPECTED_CHUNKTYPE); + } + /*}*/ +} + +/** + * Returns the time interval between start and end in milliseconds. + * @param end the end time + * @param start the start time + */ +int intervalMillis(struct timeval end, struct timeval start) { + + return ((end.tv_sec - start.tv_sec) * 1000) + + ((end.tv_usec - start.tv_usec) /1000); +} + +/** + * Trims any path info from the beginning of argv[0] to determine + * the name used to launch the client. + * + * @param s argv[0] + */ +char *shortClientName(char *s) { + char *result = strrchr(s, FILE_SEPARATOR); + return ((result == NULL) ? s : result + 1); +} + +/** + * Returns true if the specified string is the name of the nailgun + * client. The comparison is made case-insensitively for windows. + * + * @param s the program name to check + */ +int isNailgunClientName(char *s) { + /* VMS can't get the command name from argv[0] when defined + as a foreign command, as the symbol is expanded. Aliases + are supported by defining a foreign command that includes + the class alias argument, e.g. + ng=="$path:[to]ng.exe" + alias==ng+" ""alias""" + */ + #ifdef __VMS + return (1); + #else + #ifdef WIN32 + return (!strcasecmp(s, NAILGUN_CLIENT_NAME) || + !strcasecmp(s, NAILGUN_CLIENT_NAME_EXE)); + #else + return(!(strcmp(s, NAILGUN_CLIENT_NAME))); + #endif + #endif +} + +/** + * Displays usage info and bails + */ +void usage(int exitcode) { + fprintf(stderr, "NailGun v%s\n\n", NAILGUN_VERSION); + fprintf(stderr, "Usage: ng class [--nailgun-options] [args]\n"); + fprintf(stderr, " (to execute a class)\n"); + fprintf(stderr, " or: ng alias [--nailgun-options] [args]\n"); + fprintf(stderr, " (to execute an aliased class)\n"); + fprintf(stderr, " or: alias [--nailgun-options] [args]\n"); + fprintf(stderr, " (to execute an aliased class, where \"alias\"\n"); + fprintf(stderr, " is both the alias for the class and a symbolic\n"); + fprintf(stderr, " link to the ng client)\n\n"); + + fprintf(stderr, "where options include:\n"); + fprintf(stderr, " --nailgun-D= set/override a client environment variable\n"); + fprintf(stderr, " --nailgun-version print product version and exit\n"); + fprintf(stderr, " --nailgun-showversion print product version and continue\n"); + fprintf(stderr, " --nailgun-server to specify the address of the nailgun server\n"); + fprintf(stderr, " (default is NAILGUN_SERVER environment variable\n"); + fprintf(stderr, " if set, otherwise localhost)\n"); + fprintf(stderr, " --nailgun-port to specify the port of the nailgun server\n"); + fprintf(stderr, " (default is NAILGUN_PORT environment variable\n"); + fprintf(stderr, " if set, otherwise 2113)\n"); + fprintf(stderr, " --nailgun-filearg FILE places the entire contents of FILE into the\n"); + fprintf(stderr, " next argument, which is interpreted as a string\n"); + fprintf(stderr, " using the server's default character set. May be\n"); + fprintf(stderr, " specified more than once.\n"); + fprintf(stderr, " --nailgun-help print this message and exit\n"); + + cleanUpAndExit(exitcode); +} + +int main(int argc, char *argv[], char *env[]) { + int i; + struct sockaddr_in server_addr; + char *nailgun_server; /* server as specified by user */ + char *nailgun_port; /* port as specified by user */ + char *cwd; + u_short port; /* port */ + struct hostent *hostinfo; + char *cmd; + int firstArgIndex; /* the first argument _to pass to the server_ */ + char isattybuf[] = NAILGUN_TTY_FORMAT; + + #ifndef WIN32 + fd_set readfds; + int eof = 0; + struct timeval readtimeout; + struct timeval currenttime; + memset(&sendtime, '\0', sizeof(sendtime)); + #endif + + #ifdef WIN32 + initSockets(); + #endif + + /* start with environment variable. default to localhost if not defined. */ + nailgun_server = getenv("NAILGUN_SERVER"); + if (nailgun_server == NULL) { + nailgun_server = "127.0.0.1"; + } + + /* start with environment variable. default to normal nailgun port if not defined */ + nailgun_port = getenv("NAILGUN_PORT"); + if (nailgun_port == NULL) { + nailgun_port = NAILGUN_PORT_DEFAULT; + } + + /* look at the command used to launch this program. if it was "ng", then the actual + command to issue to the server must be specified as another argument. if it + wasn't ng, assume that the desired command name was symlinked to ng in the user's + filesystem, and use the symlink name (without path info) as the command for the server. */ + cmd = shortClientName(argv[0]); + + if (isNailgunClientName(cmd)) { + cmd = NULL; + } + + /* if executing just the ng client with no arguments or -h|--help, then + display usage and exit. Don't handle -h|--help if a command other than + ng or ng.exe was used, since the appropriate nail should then handle + --help. */ + if (cmd == NULL && + (argc == 1 || + (argc == 2 && strcmp("--help", argv[1]) == 0) || + (argc == 2 && strcmp("-h", argv[1]) == 0))) usage(0); + + firstArgIndex = 1; + + /* quite possibly the lamest commandline parsing ever. + look for the two args we care about (--nailgun-server and + --nailgun-port) and NULL them and their parameters after + reading them if found. later, when we send args to the + server, skip the null args. */ + for (i = 1; i < argc; ++i) { + if (!strcmp("--nailgun-server", argv[i])) { + if (i == argc - 1) usage(NAILGUN_BAD_ARGUMENTS); + nailgun_server = argv[i + 1]; + argv[i] = argv[i + 1] = NULL; + ++i; + } else if(!strcmp("--nailgun-port", argv[i])) { + if (i == argc - 1) usage(NAILGUN_BAD_ARGUMENTS); + nailgun_port = argv[i + 1]; + argv[i] = argv[i + 1]= NULL; + ++i; + } else if (!strcmp("--nailgun-filearg", argv[i])) { + /* just verify usage here. do the rest when sending args. */ + if (i == argc - 1) usage (NAILGUN_BAD_ARGUMENTS); + } else if (!strcmp("--nailgun-version", argv[i])) { + printf("NailGun client version %s\n", NAILGUN_VERSION); + cleanUpAndExit(0); + } else if (!strcmp("--nailgun-showversion", argv[i])) { + printf("NailGun client version %s\n", NAILGUN_VERSION); + argv[i] = NULL; + } else if (!strcmp("--nailgun-help", argv[i])) { + usage(0); + } else if (cmd == NULL) { + cmd = argv[i]; + firstArgIndex = i + 1; + } + } + + /* if there's no command, we should only display usage info + if the version number was not displayed. */ + if (cmd == NULL) { + usage(NAILGUN_BAD_ARGUMENTS); + } + + /* jump through a series of connection hoops */ + hostinfo = gethostbyname(nailgun_server); + + if (hostinfo == NULL) { + fprintf(stderr, "Unknown host: %s\n", nailgun_server); + cleanUpAndExit(NAILGUN_CONNECT_FAILED); + } + + port = atoi(nailgun_port); + + if ((nailgunsocket = socket(AF_INET, SOCK_STREAM, 0)) == -1) { + perror("socket"); + cleanUpAndExit(NAILGUN_SOCKET_FAILED); + } + + server_addr.sin_family = AF_INET; + server_addr.sin_port = htons(port); + server_addr.sin_addr = *(struct in_addr *) hostinfo->h_addr; + + memset(&(server_addr.sin_zero), '\0', 8); + + if (connect(nailgunsocket, (struct sockaddr *)&server_addr, + sizeof(struct sockaddr)) == -1) { + perror("connect"); + cleanUpAndExit(NAILGUN_CONNECT_FAILED); + } + + /* ok, now we're connected. first send all of the command line + arguments for the server, if any. remember that we may have + marked some arguments NULL if we read them to specify the + nailgun server and/or port */ + for(i = firstArgIndex; i < argc; ++i) { + if (argv[i] != NULL) { + if (!strcmp("--nailgun-filearg", argv[i])) { + int sendResult = sendFileArg(argv[++i]); + if (sendResult != 0) { + perror("send"); + handleSocketClose(); + } + } else sendText(CHUNKTYPE_ARG, argv[i]); + } + } + + /* now send environment */ + sendText(CHUNKTYPE_ENV, NAILGUN_FILESEPARATOR); + sendText(CHUNKTYPE_ENV, NAILGUN_PATHSEPARATOR); +#ifndef WIN32 + /* notify isatty for standard pipes */ + for(i = 0; i < 3; i++) { + sprintf(isattybuf, NAILGUN_TTY_FORMAT, i, isatty(i)); + sendText(CHUNKTYPE_ENV, isattybuf); + } +#endif + /* forward the client process environment */ + for(i = 0; env[i]; ++i) { + sendText(CHUNKTYPE_ENV, env[i]); + } + + /* now send the working directory */ + cwd = getcwd(NULL, 0); + sendText(CHUNKTYPE_DIR, cwd); + free(cwd); + + /* and finally send the command. this marks the point at which + streams are linked between client and server. */ + sendText(CHUNKTYPE_CMD, cmd); + + + /* initialise the std-* handles and the thread to send stdin to the server */ + #ifdef WIN32 + initIo(); + winStartInput(); + #endif + + /* stream forwarding loop */ + while(1) { + #ifndef WIN32 + FD_ZERO(&readfds); + + /* don't select on stdin if we've already reached its end */ + if (readyToSend && !eof) { + FD_SET(NG_STDIN_FILENO, &readfds); + } + + FD_SET(nailgunsocket, &readfds); + + memset(&readtimeout, '\0', sizeof(readtimeout)); + readtimeout.tv_usec = HEARTBEAT_TIMEOUT_MILLIS * 1000; + if(select (nailgunsocket + 1, &readfds, NULL, NULL, &readtimeout) == -1) { + perror("select"); + } + + if (FD_ISSET(nailgunsocket, &readfds)) { + #endif + processnailgunstream(); + #ifndef WIN32 + } else if (FD_ISSET(NG_STDIN_FILENO, &readfds)) { + int result = processStdin(); + if (result == -1) { + perror("read"); + handleSocketClose(); + } else if (result == 0) { + FD_CLR(NG_STDIN_FILENO, &readfds); + eof = 1; + } + } + gettimeofday(¤ttime, NULL); + if (intervalMillis(currenttime, sendtime) > HEARTBEAT_TIMEOUT_MILLIS) { + sendHeartbeat(); + } + #endif + } + + /* normal termination is triggered by the server, and so occurs in processExit(), above */ +} diff --git a/prism/src/prism/ngprism.c b/prism/src/prism/ngprism.c new file mode 100644 index 00000000..88ddd90a --- /dev/null +++ b/prism/src/prism/ngprism.c @@ -0,0 +1,850 @@ +/* + + Copyright 2004-2012, Martian Software, Inc. + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + +*/ + +/** + * @author Marty Lamb + * @author Pete Kirkham (Win32 port) + */ + +/** + * This file (ngprism.c) is a simple customisation of ng.c) + * which fixes the command (to prism.PrismNG) by default. + */ + +#ifdef WIN32 + #include + #include +#else + #include + #include + #include + #include + #include + #include +#endif + +#include +#include +#include +#include +#include +#include + +#define NAILGUN_VERSION "0.9.0" + +#define BUFSIZE (2048) + +#ifdef WIN32 + HANDLE NG_STDIN_FILENO; + HANDLE NG_STDOUT_FILENO; + HANDLE NG_STDERR_FILENO; + #define FILE_SEPARATOR '\\' + #define MSG_WAITALL 0 +#else + #define NG_STDIN_FILENO STDIN_FILENO + #define NG_STDOUT_FILENO STDOUT_FILENO + #define NG_STDERR_FILENO STDERR_FILENO + #define FILE_SEPARATOR '/' + typedef int HANDLE; + typedef unsigned int SOCKET; +#endif + +#ifdef __APPLE__ + #define SEND_FLAGS 0 +#else + #define SEND_FLAGS MSG_NOSIGNAL +#endif + +#ifndef MIN +#define MIN(a,b) ((a> 24) & 0xff; + header[1] = (size >> 16) & 0xff; + header[2] = (size >> 8) & 0xff; + header[3] = size & 0xff; + header[4] = chunkType; + +#ifdef WIN32 + if (WaitForSingleObject(sending, INFINITE) != WAIT_OBJECT_0) { + handleError(); + } +#else + gettimeofday(&sendtime, NULL); +#endif + + bytesSent = sendAll(nailgunsocket, header, CHUNK_HEADER_LEN); + if (bytesSent != 0 && size > 0) { + bytesSent = sendAll(nailgunsocket, buf, size); + } else if (bytesSent == 0 && (chunkType != CHUNKTYPE_HEARTBEAT || errno != EPIPE)) { + perror("send"); + handleSocketClose(); + } + +#ifdef WIN32 + ReleaseMutex(sending); +#endif +} + +/** + * Sends the contents of the specified file as a long argument (--nailgun-filearg) + * This is sent as one or more chunks of type CHUNK_LONGARG. The end of the argument + * is indicated by an empty chunk. + * + * @param filename the name of the file to send. + * @return nonzero on failure + */ +int sendFileArg(char *filename) { + int i, f; + + if ((f = open(filename, O_RDONLY)) < 0) { + perror("--nailgun-filearg"); + return 1; + } + + i = read(f, buf, BUFSIZE); + while (i > 0) { + sendChunk(i, CHUNKTYPE_LONGARG, buf); + i = read(f, buf, BUFSIZE); + } + if (i < 0) { + perror("--nailgun-filearg"); + return 1; + } + sendChunk(0, CHUNKTYPE_LONGARG, buf); + + close(f); + return 0; +} + +/** + * Sends a null-terminated string with the specified chunk type. + * + * @param chunkType the chunk type identifier + * @param text the null-terminated string to send + */ +void sendText(char chunkType, char *text) { + int len = text ? strlen(text) : 0; + sendChunk(len, chunkType, text); +} + +/** + * Receives len bytes from the nailgun socket and copies them to the specified file descriptor. + * Used to route data to stdout or stderr on the client. + * + * @param destFD the destination file descriptor (stdout or stderr) + * @param len the number of bytes to copy + */ +void recvToFD(HANDLE destFD, char *buf, unsigned long len) { + unsigned long bytesRead = 0; + int bytesCopied; + + while (bytesRead < len) { + unsigned long bytesRemaining = len - bytesRead; + int bytesToRead = (BUFSIZE < bytesRemaining) ? BUFSIZE : bytesRemaining; + int thisPass = 0; + + thisPass = recv(nailgunsocket, buf, bytesToRead, MSG_WAITALL); + if (thisPass == 0 || thisPass == -1) { + perror("recv"); + handleSocketClose(); + } + bytesRead += thisPass; + + bytesCopied = 0; + + while(bytesCopied < thisPass) { + #ifdef WIN32 + DWORD thisWrite = 0; + + WriteFile(destFD, buf + bytesCopied, thisPass - bytesCopied, + &thisWrite, NULL); + + if (thisWrite < 0) { + break; + } + + bytesCopied += thisWrite; + #else + int bytesWritten = write(destFD, buf + bytesCopied, thisPass - bytesCopied); + if (bytesWritten == -1) { + perror("write"); + handleSocketClose(); + } + bytesCopied += bytesWritten; + #endif + } + } +} + +unsigned long recvToBuffer(unsigned long len) { + unsigned long bytesRead = 0; + while(bytesRead < len) { + int thisPass = recv(nailgunsocket, buf + bytesRead, len - bytesRead, MSG_WAITALL); + if (thisPass == 0 || thisPass == -1) { + perror("recv"); + handleSocketClose(); + } + bytesRead += thisPass; + } + return bytesRead; +} + +/** + * Processes an exit chunk from the server. This is just a string + * containing the exit code in decimal format. It should fit well + * within our buffer, so assume that it does. + * + * @param len the current length of the buffer containing the exit code. + */ +void processExit(char *buf, unsigned long len) { + int exitcode; + int bytesToRead = (BUFSIZE - 1 < len) ? BUFSIZE - 1 : len; + int bytesRead = recvToBuffer(bytesToRead); + + if (bytesRead < 0) { + handleSocketClose(); + } + + buf[bytesRead] = 0; + + exitcode = atoi(buf); + + cleanUpAndExit(exitcode); +} + + +/** + * Sends len bytes from buf to the nailgun server in a stdin chunk. + * + * @param buf the bytes to send + * @param len the number of bytes to send + */ +void sendStdin(char *buf, unsigned int len) { +#ifndef WIN32 + readyToSend = 0; +#endif + sendChunk(len, CHUNKTYPE_STDIN, buf); +} + +/** + * Sends a stdin-eof chunk to the nailgun server + */ +void processEof() { + sendChunk(0, CHUNKTYPE_STDIN_EOF, buf); +} + +/** + * Sends a heartbeat chunk to let the server know the client is still alive. + */ +void sendHeartbeat() { + sendChunk(0, CHUNKTYPE_HEARTBEAT, buf); +} + +#ifdef WIN32 + +HANDLE createEvent(BOOL manualReset) { + return CreateEvent(NULL, /* default security */ + manualReset, + FALSE, /* initial state unsignalled */ + NULL /* unnamed event */); +} + +DWORD WINAPI sendHeartbeats(LPVOID lpParameter) { + + /* this could be made more efficient by only sending heartbeats when stdin chunks aren't being sent */ + for (;;) { + Sleep(HEARTBEAT_TIMEOUT_MILLIS); + sendHeartbeat(); + } +} + +/** + * Thread main for reading from stdin and sending + */ +DWORD WINAPI processStdin (LPVOID lpParameter) { + /* buffer used for reading and sending stdin chunks */ + char wbuf[BUFSIZE]; + + /* number of bytes read */ + DWORD numberOfBytes; + + for (;;) { + + /* wait for ready to send */ + if(WaitForSingleObject(readyToSend, INFINITE) != WAIT_OBJECT_0) { + handleError(); + } + + /* read data from stdin */ + if (! ReadFile(NG_STDIN_FILENO, wbuf, BUFSIZE, &numberOfBytes, NULL)) { + if (numberOfBytes != 0) { + handleError(); + } + } + + /* send data to server */ + if (numberOfBytes > 0) { + sendStdin(wbuf, numberOfBytes); + } else { + processEof(); + break; + } + + } + return 0; +} +#else +/** + * Reads from stdin and transmits it to the nailgun server in a stdin chunk. + * Sends a stdin-eof chunk if necessary. + * + * @return zero if eof has been reached. + */ +int processStdin() { + int bytesread = read(STDIN_FILENO, buf, BUFSIZE); + if (bytesread > 0) { + sendStdin(buf, bytesread); + } else if (bytesread == 0) { + processEof(); + } + return(bytesread); +} +#endif + +#ifdef WIN32 +/** + * Initialise Windows sockets + */ +void initSockets () { + WSADATA win_socket_data; /* required to initialise winsock */ + + WSAStartup(2, &win_socket_data); + + /* create flow control event and mutex */ + readyToSend = createEvent(FALSE); + sending = CreateMutex(NULL, FALSE, NULL); +} +#endif + +#ifdef WIN32 +/** + * Initialise the asynchronous io. + */ +void initIo () { + /* create non-blocking console io */ + AllocConsole(); + + NG_STDIN_FILENO = GetStdHandle(STD_INPUT_HANDLE); + NG_STDOUT_FILENO = GetStdHandle(STD_OUTPUT_HANDLE); + NG_STDERR_FILENO = GetStdHandle(STD_ERROR_HANDLE); +} +#endif + +#ifdef WIN32 +/** + * Initialise the asynchronous io. + */ +void winStartInput () { + SECURITY_ATTRIBUTES securityAttributes; + DWORD threadId = 0; + + securityAttributes.bInheritHandle = TRUE; + securityAttributes.lpSecurityDescriptor = NULL; + securityAttributes.nLength = 0; + + if (!CreateThread(&securityAttributes, 0, &processStdin, NULL, 0, &threadId)) { + handleError(); + } + + if (!CreateThread(&securityAttributes, 0, &sendHeartbeats, NULL, 0, &threadId)) { + handleError(); + } +} +#endif + +/** + * Processes data from the nailgun server. + */ +void processnailgunstream() { + + /*for (;;) {*/ + unsigned long len; + char chunkType; + + recvToBuffer(CHUNK_HEADER_LEN); + + len = ((buf[0] << 24) & 0xff000000) + | ((buf[1] << 16) & 0x00ff0000) + | ((buf[2] << 8) & 0x0000ff00) + | ((buf[3]) & 0x000000ff); + + chunkType = buf[4]; + + switch(chunkType) { + case CHUNKTYPE_STDOUT: recvToFD(NG_STDOUT_FILENO, buf, len); + break; + case CHUNKTYPE_STDERR: recvToFD(NG_STDERR_FILENO, buf, len); + break; + case CHUNKTYPE_EXIT: processExit(buf, len); + break; + case CHUNKTYPE_SENDINPUT: +#ifdef WIN32 + SetEvent(readyToSend); +#else + readyToSend = 1; +#endif + break; + default: fprintf(stderr, "Unexpected chunk type %d ('%c')\n", chunkType, chunkType); + cleanUpAndExit(NAILGUN_UNEXPECTED_CHUNKTYPE); + } + /*}*/ +} + +/** + * Returns the time interval between start and end in milliseconds. + * @param end the end time + * @param start the start time + */ +int intervalMillis(struct timeval end, struct timeval start) { + + return ((end.tv_sec - start.tv_sec) * 1000) + + ((end.tv_usec - start.tv_usec) /1000); +} + +/** + * Trims any path info from the beginning of argv[0] to determine + * the name used to launch the client. + * + * @param s argv[0] + */ +char *shortClientName(char *s) { + char *result = strrchr(s, FILE_SEPARATOR); + return ((result == NULL) ? s : result + 1); +} + +/** + * Returns true if the specified string is the name of the nailgun + * client. The comparison is made case-insensitively for windows. + * + * @param s the program name to check + */ +int isNailgunClientName(char *s) { + /* VMS can't get the command name from argv[0] when defined + as a foreign command, as the symbol is expanded. Aliases + are supported by defining a foreign command that includes + the class alias argument, e.g. + ng=="$path:[to]ng.exe" + alias==ng+" ""alias""" + */ + #ifdef __VMS + return (1); + #else + #ifdef WIN32 + return (!strcasecmp(s, NAILGUN_CLIENT_NAME) || + !strcasecmp(s, NAILGUN_CLIENT_NAME_EXE)); + #else + return(!(strcmp(s, NAILGUN_CLIENT_NAME))); + #endif + #endif +} + +/** + * Displays usage info and bails + */ +void usage(int exitcode) { + fprintf(stderr, "NailGun v%s\n\n", NAILGUN_VERSION); + fprintf(stderr, "Usage: ng class [--nailgun-options] [args]\n"); + fprintf(stderr, " (to execute a class)\n"); + fprintf(stderr, " or: ng alias [--nailgun-options] [args]\n"); + fprintf(stderr, " (to execute an aliased class)\n"); + fprintf(stderr, " or: alias [--nailgun-options] [args]\n"); + fprintf(stderr, " (to execute an aliased class, where \"alias\"\n"); + fprintf(stderr, " is both the alias for the class and a symbolic\n"); + fprintf(stderr, " link to the ng client)\n\n"); + + fprintf(stderr, "where options include:\n"); + fprintf(stderr, " --nailgun-D= set/override a client environment variable\n"); + fprintf(stderr, " --nailgun-version print product version and exit\n"); + fprintf(stderr, " --nailgun-showversion print product version and continue\n"); + fprintf(stderr, " --nailgun-server to specify the address of the nailgun server\n"); + fprintf(stderr, " (default is NAILGUN_SERVER environment variable\n"); + fprintf(stderr, " if set, otherwise localhost)\n"); + fprintf(stderr, " --nailgun-port to specify the port of the nailgun server\n"); + fprintf(stderr, " (default is NAILGUN_PORT environment variable\n"); + fprintf(stderr, " if set, otherwise 2113)\n"); + fprintf(stderr, " --nailgun-filearg FILE places the entire contents of FILE into the\n"); + fprintf(stderr, " next argument, which is interpreted as a string\n"); + fprintf(stderr, " using the server's default character set. May be\n"); + fprintf(stderr, " specified more than once.\n"); + fprintf(stderr, " --nailgun-help print this message and exit\n"); + + cleanUpAndExit(exitcode); +} + +int main(int argc, char *argv[], char *env[]) { + int i; + struct sockaddr_in server_addr; + char *nailgun_server; /* server as specified by user */ + char *nailgun_port; /* port as specified by user */ + char *cwd; + u_short port; /* port */ + struct hostent *hostinfo; + char *cmd = "prism.PrismNG"; + int firstArgIndex; /* the first argument _to pass to the server_ */ + char isattybuf[] = NAILGUN_TTY_FORMAT; + + #ifndef WIN32 + fd_set readfds; + int eof = 0; + struct timeval readtimeout; + struct timeval currenttime; + memset(&sendtime, '\0', sizeof(sendtime)); + #endif + + #ifdef WIN32 + initSockets(); + #endif + + /* start with environment variable. default to localhost if not defined. */ + nailgun_server = getenv("NAILGUN_SERVER"); + if (nailgun_server == NULL) { + nailgun_server = "127.0.0.1"; + } + + /* start with environment variable. default to normal nailgun port if not defined */ + nailgun_port = getenv("NAILGUN_PORT"); + if (nailgun_port == NULL) { + nailgun_port = NAILGUN_PORT_DEFAULT; + } + + /* if executing just the ng client with no arguments or -h|--help, then + display usage and exit. Don't handle -h|--help if a command other than + ng or ng.exe was used, since the appropriate nail should then handle + --help. */ + if (((argc == 2 && strcmp("--help", argv[1]) == 0) || + (argc == 2 && strcmp("-h", argv[1]) == 0))) usage(0); + + firstArgIndex = 1; + + /* quite possibly the lamest commandline parsing ever. + look for the two args we care about (--nailgun-server and + --nailgun-port) and NULL them and their parameters after + reading them if found. later, when we send args to the + server, skip the null args. */ + for (i = 1; i < argc; ++i) { + if (!strcmp("--nailgun-server", argv[i])) { + if (i == argc - 1) usage(NAILGUN_BAD_ARGUMENTS); + nailgun_server = argv[i + 1]; + argv[i] = argv[i + 1] = NULL; + ++i; + } else if(!strcmp("--nailgun-port", argv[i])) { + if (i == argc - 1) usage(NAILGUN_BAD_ARGUMENTS); + nailgun_port = argv[i + 1]; + argv[i] = argv[i + 1]= NULL; + ++i; + } else if (!strcmp("--nailgun-filearg", argv[i])) { + /* just verify usage here. do the rest when sending args. */ + if (i == argc - 1) usage (NAILGUN_BAD_ARGUMENTS); + } else if (!strcmp("--nailgun-version", argv[i])) { + printf("NailGun client version %s\n", NAILGUN_VERSION); + cleanUpAndExit(0); + } else if (!strcmp("--nailgun-showversion", argv[i])) { + printf("NailGun client version %s\n", NAILGUN_VERSION); + argv[i] = NULL; + } else if (!strcmp("--nailgun-help", argv[i])) { + usage(0); + } else if (cmd == NULL) { + cmd = argv[i]; + firstArgIndex = i + 1; + } + } + + /* jump through a series of connection hoops */ + hostinfo = gethostbyname(nailgun_server); + + if (hostinfo == NULL) { + fprintf(stderr, "Unknown host: %s\n", nailgun_server); + cleanUpAndExit(NAILGUN_CONNECT_FAILED); + } + + port = atoi(nailgun_port); + + if ((nailgunsocket = socket(AF_INET, SOCK_STREAM, 0)) == -1) { + perror("socket"); + cleanUpAndExit(NAILGUN_SOCKET_FAILED); + } + + server_addr.sin_family = AF_INET; + server_addr.sin_port = htons(port); + server_addr.sin_addr = *(struct in_addr *) hostinfo->h_addr; + + memset(&(server_addr.sin_zero), '\0', 8); + + if (connect(nailgunsocket, (struct sockaddr *)&server_addr, + sizeof(struct sockaddr)) == -1) { + perror("connect"); + cleanUpAndExit(NAILGUN_CONNECT_FAILED); + } + + /* ok, now we're connected. first send all of the command line + arguments for the server, if any. remember that we may have + marked some arguments NULL if we read them to specify the + nailgun server and/or port */ + for(i = firstArgIndex; i < argc; ++i) { + if (argv[i] != NULL) { + if (!strcmp("--nailgun-filearg", argv[i])) { + int sendResult = sendFileArg(argv[++i]); + if (sendResult != 0) { + perror("send"); + handleSocketClose(); + } + } else sendText(CHUNKTYPE_ARG, argv[i]); + } + } + + /* now send environment */ + sendText(CHUNKTYPE_ENV, NAILGUN_FILESEPARATOR); + sendText(CHUNKTYPE_ENV, NAILGUN_PATHSEPARATOR); +#ifndef WIN32 + /* notify isatty for standard pipes */ + for(i = 0; i < 3; i++) { + sprintf(isattybuf, NAILGUN_TTY_FORMAT, i, isatty(i)); + sendText(CHUNKTYPE_ENV, isattybuf); + } +#endif + /* forward the client process environment */ + for(i = 0; env[i]; ++i) { + sendText(CHUNKTYPE_ENV, env[i]); + } + + /* now send the working directory */ + cwd = getcwd(NULL, 0); + sendText(CHUNKTYPE_DIR, cwd); + free(cwd); + + /* and finally send the command. this marks the point at which + streams are linked between client and server. */ + sendText(CHUNKTYPE_CMD, cmd); + + + /* initialise the std-* handles and the thread to send stdin to the server */ + #ifdef WIN32 + initIo(); + winStartInput(); + #endif + + /* stream forwarding loop */ + while(1) { + #ifndef WIN32 + FD_ZERO(&readfds); + + /* don't select on stdin if we've already reached its end */ + if (readyToSend && !eof) { + FD_SET(NG_STDIN_FILENO, &readfds); + } + + FD_SET(nailgunsocket, &readfds); + + memset(&readtimeout, '\0', sizeof(readtimeout)); + readtimeout.tv_usec = HEARTBEAT_TIMEOUT_MILLIS * 1000; + if(select (nailgunsocket + 1, &readfds, NULL, NULL, &readtimeout) == -1) { + perror("select"); + } + + if (FD_ISSET(nailgunsocket, &readfds)) { + #endif + processnailgunstream(); + #ifndef WIN32 + } else if (FD_ISSET(NG_STDIN_FILENO, &readfds)) { + int result = processStdin(); + if (result == -1) { + perror("read"); + handleSocketClose(); + } else if (result == 0) { + FD_CLR(NG_STDIN_FILENO, &readfds); + eof = 1; + } + } + gettimeofday(¤ttime, NULL); + if (intervalMillis(currenttime, sendtime) > HEARTBEAT_TIMEOUT_MILLIS) { + sendHeartbeat(); + } + #endif + } + + /* normal termination is triggered by the server, and so occurs in processExit(), above */ +}