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 */
+}