Browse Source
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.
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-cb519fbb1720master
13 changed files with 1847 additions and 5 deletions
-
1prism/.classpath
-
2prism/Makefile
-
8prism/include/PrismNative.h
-
2prism/install.sh
-
3prism/lib/README.md
-
4prism/src/automata/LTL2DA.java
-
5prism/src/prism/Makefile
-
15prism/src/prism/PrismCL.java
-
81prism/src/prism/PrismNG.java
-
12prism/src/prism/PrismNative.cc
-
6prism/src/prism/PrismNative.java
-
863prism/src/prism/ng.c.orig
-
850prism/src/prism/ngprism.c
@ -0,0 +1,81 @@ |
|||||
|
//============================================================================== |
||||
|
// |
||||
|
// Copyright (c) 2015- |
||||
|
// Authors: |
||||
|
// * Joachim Klein <klein@tcs.inf.tu-dresden.de (TU Dresden) |
||||
|
// * Dave Parker <d.a.parker@cs.bham.ac.uk> (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. |
||||
|
* <br/> |
||||
|
* This method is synchronized to ensure that there can only be a single |
||||
|
* running instance of PRISM in the Nailgun server VM. |
||||
|
* <br/> |
||||
|
* 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. |
||||
|
* <br/> |
||||
|
* 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<String, String> env = builder.environment(); |
||||
|
for (Entry<Object, Object> entry : currentNailGunContext.getEnv().entrySet()) { |
||||
|
if (entry.getKey() instanceof String && entry.getValue() instanceof String) { |
||||
|
env.put((String) entry.getKey(), (String) entry.getValue()); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -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 <a href="http://www.martiansoftware.com/contact.html">Marty Lamb</a> |
||||
|
* @author Pete Kirkham (Win32 port) |
||||
|
*/ |
||||
|
|
||||
|
#ifdef WIN32 |
||||
|
#include <direct.h> |
||||
|
#include <winsock2.h> |
||||
|
#else |
||||
|
#include <arpa/inet.h> |
||||
|
#include <netdb.h> |
||||
|
#include <netinet/in.h> |
||||
|
#include <sys/socket.h> |
||||
|
#include <sys/time.h> |
||||
|
#include <sys/types.h> |
||||
|
#endif |
||||
|
|
||||
|
#include <stdio.h> |
||||
|
#include <stdlib.h> |
||||
|
#include <string.h> |
||||
|
#include <unistd.h> |
||||
|
#include <fcntl.h> |
||||
|
#include <errno.h> |
||||
|
|
||||
|
#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<b)?(a):(b)) |
||||
|
#endif |
||||
|
|
||||
|
#ifdef WIN32 |
||||
|
#define NAILGUN_FILESEPARATOR "NAILGUN_FILESEPARATOR=\\" |
||||
|
#define NAILGUN_PATHSEPARATOR "NAILGUN_PATHSEPARATOR=;" |
||||
|
#else |
||||
|
#define NAILGUN_FILESEPARATOR "NAILGUN_FILESEPARATOR=/" |
||||
|
#define NAILGUN_PATHSEPARATOR "NAILGUN_PATHSEPARATOR=:" |
||||
|
#endif |
||||
|
|
||||
|
#define NAILGUN_CLIENT_NAME_EXE "ng.exe" |
||||
|
|
||||
|
#define NAILGUN_PORT_DEFAULT "2113" |
||||
|
#define NAILGUN_CLIENT_NAME "ng" |
||||
|
#define CHUNK_HEADER_LEN (5) |
||||
|
|
||||
|
#define NAILGUN_SOCKET_FAILED (231) |
||||
|
#define NAILGUN_CONNECT_FAILED (230) |
||||
|
#define NAILGUN_UNEXPECTED_CHUNKTYPE (229) |
||||
|
#define NAILGUN_EXCEPTION_ON_SERVER (228) |
||||
|
#define NAILGUN_CONNECTION_BROKEN (227) |
||||
|
#define NAILGUN_BAD_ARGUMENTS (226) |
||||
|
|
||||
|
#define CHUNKTYPE_STDIN '0' |
||||
|
#define CHUNKTYPE_STDOUT '1' |
||||
|
#define CHUNKTYPE_STDERR '2' |
||||
|
#define CHUNKTYPE_STDIN_EOF '.' |
||||
|
#define CHUNKTYPE_ARG 'A' |
||||
|
#define CHUNKTYPE_LONGARG 'L' |
||||
|
#define CHUNKTYPE_ENV 'E' |
||||
|
#define CHUNKTYPE_DIR 'D' |
||||
|
#define CHUNKTYPE_CMD 'C' |
||||
|
#define CHUNKTYPE_EXIT 'X' |
||||
|
#define CHUNKTYPE_SENDINPUT 'S' |
||||
|
#define CHUNKTYPE_HEARTBEAT 'H' |
||||
|
|
||||
|
#define HEARTBEAT_TIMEOUT_MILLIS 500 |
||||
|
|
||||
|
#define NAILGUN_TTY_FORMAT "NAILGUN_TTY_%d=%d" |
||||
|
|
||||
|
/* |
||||
|
the following is required to compile for hp-ux |
||||
|
originally posted at http://jira.codehaus.org/browse/JRUBY-2346 |
||||
|
*/ |
||||
|
#ifndef MSG_WAITALL |
||||
|
#define MSG_WAITALL 0x40 /* wait for full request or error */ |
||||
|
#endif |
||||
|
|
||||
|
/* the socket connected to the nailgun server */ |
||||
|
int nailgunsocket = 0; |
||||
|
|
||||
|
/* buffer used for receiving and writing nail output chunks */ |
||||
|
char buf[BUFSIZE]; |
||||
|
|
||||
|
/* track whether server is ready to receive */ |
||||
|
#ifdef WIN32 |
||||
|
HANDLE readyToSend = 0; |
||||
|
HANDLE sending = 0; |
||||
|
#else |
||||
|
int readyToSend = 0; |
||||
|
struct timeval sendtime; |
||||
|
#endif |
||||
|
|
||||
|
/** |
||||
|
* Clean up the application. |
||||
|
*/ |
||||
|
void cleanUpAndExit (int exitCode) { |
||||
|
|
||||
|
|
||||
|
#ifdef WIN32 |
||||
|
CancelIo(STDIN_FILENO); |
||||
|
WSACleanup(); |
||||
|
if (nailgunsocket) { |
||||
|
closesocket(nailgunsocket); |
||||
|
} |
||||
|
#else |
||||
|
close(nailgunsocket); |
||||
|
#endif |
||||
|
|
||||
|
exit(exitCode); |
||||
|
} |
||||
|
|
||||
|
#ifdef WIN32 |
||||
|
/** |
||||
|
* Handles an error. |
||||
|
* Shows the message for the latest error then exits. |
||||
|
*/ |
||||
|
void handleError () { |
||||
|
LPVOID lpMsgBuf; |
||||
|
int error = GetLastError(); |
||||
|
|
||||
|
FormatMessage( |
||||
|
FORMAT_MESSAGE_ALLOCATE_BUFFER | |
||||
|
FORMAT_MESSAGE_FROM_SYSTEM | |
||||
|
FORMAT_MESSAGE_IGNORE_INSERTS, |
||||
|
NULL, |
||||
|
error, |
||||
|
MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), /* Default language */ |
||||
|
(LPTSTR) &lpMsgBuf, |
||||
|
0, |
||||
|
NULL); |
||||
|
|
||||
|
/* Display the string. */ |
||||
|
MessageBox( NULL, (LPCTSTR)lpMsgBuf, "Error", MB_OK | MB_ICONERROR ); |
||||
|
|
||||
|
/* Free the buffer. */ |
||||
|
LocalFree( lpMsgBuf ); |
||||
|
|
||||
|
cleanUpAndExit(error); |
||||
|
} |
||||
|
#endif |
||||
|
|
||||
|
/** |
||||
|
* Exits the client if the nailgun server ungracefully shut down the connection. |
||||
|
*/ |
||||
|
void handleSocketClose() { |
||||
|
cleanUpAndExit(NAILGUN_CONNECTION_BROKEN); |
||||
|
} |
||||
|
|
||||
|
/** |
||||
|
* Writes everything in the specified buffer to the specified |
||||
|
* socket handle. |
||||
|
* |
||||
|
* @param s the socket descriptor |
||||
|
* @param buf the buffer containing the data to send |
||||
|
* @param len the number of bytes to send. Also used to |
||||
|
* return the number of bytes sent. |
||||
|
* @return total bytes written or 0 if failure |
||||
|
*/ |
||||
|
int sendAll(SOCKET s, char *buf, int len) { |
||||
|
int total = 0; |
||||
|
int bytesleft = len; |
||||
|
int n = 0; |
||||
|
|
||||
|
while(total < len) { |
||||
|
n = send(s, buf+total, bytesleft, SEND_FLAGS); |
||||
|
|
||||
|
if (n == -1) { |
||||
|
break; |
||||
|
} |
||||
|
|
||||
|
total += n; |
||||
|
bytesleft -= n; |
||||
|
} |
||||
|
|
||||
|
return n==-1 ? 0:total; |
||||
|
} |
||||
|
|
||||
|
/** |
||||
|
* Sends a chunk noting the specified payload size and chunk type. |
||||
|
* Waits for sending mutex on Win32. |
||||
|
* |
||||
|
* @param size the payload size |
||||
|
* @param chunkType the chunk type identifier |
||||
|
*/ |
||||
|
void sendChunk(unsigned int size, char chunkType, char* buf) { |
||||
|
/* buffer used for reading and writing chunk headers */ |
||||
|
char header[CHUNK_HEADER_LEN]; |
||||
|
int bytesSent; |
||||
|
|
||||
|
header[0] = (size >> 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<name>=<value> 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 */ |
||||
|
} |
||||
@ -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 <a href="http://www.martiansoftware.com/contact.html">Marty Lamb</a> |
||||
|
* @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 <direct.h> |
||||
|
#include <winsock2.h> |
||||
|
#else |
||||
|
#include <arpa/inet.h> |
||||
|
#include <netdb.h> |
||||
|
#include <netinet/in.h> |
||||
|
#include <sys/socket.h> |
||||
|
#include <sys/time.h> |
||||
|
#include <sys/types.h> |
||||
|
#endif |
||||
|
|
||||
|
#include <stdio.h> |
||||
|
#include <stdlib.h> |
||||
|
#include <string.h> |
||||
|
#include <unistd.h> |
||||
|
#include <fcntl.h> |
||||
|
#include <errno.h> |
||||
|
|
||||
|
#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<b)?(a):(b)) |
||||
|
#endif |
||||
|
|
||||
|
#ifdef WIN32 |
||||
|
#define NAILGUN_FILESEPARATOR "NAILGUN_FILESEPARATOR=\\" |
||||
|
#define NAILGUN_PATHSEPARATOR "NAILGUN_PATHSEPARATOR=;" |
||||
|
#else |
||||
|
#define NAILGUN_FILESEPARATOR "NAILGUN_FILESEPARATOR=/" |
||||
|
#define NAILGUN_PATHSEPARATOR "NAILGUN_PATHSEPARATOR=:" |
||||
|
#endif |
||||
|
|
||||
|
#define NAILGUN_CLIENT_NAME_EXE "ng.exe" |
||||
|
|
||||
|
#define NAILGUN_PORT_DEFAULT "2113" |
||||
|
#define NAILGUN_CLIENT_NAME "ng" |
||||
|
#define CHUNK_HEADER_LEN (5) |
||||
|
|
||||
|
#define NAILGUN_SOCKET_FAILED (231) |
||||
|
#define NAILGUN_CONNECT_FAILED (230) |
||||
|
#define NAILGUN_UNEXPECTED_CHUNKTYPE (229) |
||||
|
#define NAILGUN_EXCEPTION_ON_SERVER (228) |
||||
|
#define NAILGUN_CONNECTION_BROKEN (227) |
||||
|
#define NAILGUN_BAD_ARGUMENTS (226) |
||||
|
|
||||
|
#define CHUNKTYPE_STDIN '0' |
||||
|
#define CHUNKTYPE_STDOUT '1' |
||||
|
#define CHUNKTYPE_STDERR '2' |
||||
|
#define CHUNKTYPE_STDIN_EOF '.' |
||||
|
#define CHUNKTYPE_ARG 'A' |
||||
|
#define CHUNKTYPE_LONGARG 'L' |
||||
|
#define CHUNKTYPE_ENV 'E' |
||||
|
#define CHUNKTYPE_DIR 'D' |
||||
|
#define CHUNKTYPE_CMD 'C' |
||||
|
#define CHUNKTYPE_EXIT 'X' |
||||
|
#define CHUNKTYPE_SENDINPUT 'S' |
||||
|
#define CHUNKTYPE_HEARTBEAT 'H' |
||||
|
|
||||
|
#define HEARTBEAT_TIMEOUT_MILLIS 500 |
||||
|
|
||||
|
#define NAILGUN_TTY_FORMAT "NAILGUN_TTY_%d=%d" |
||||
|
|
||||
|
/* |
||||
|
the following is required to compile for hp-ux |
||||
|
originally posted at http://jira.codehaus.org/browse/JRUBY-2346 |
||||
|
*/ |
||||
|
#ifndef MSG_WAITALL |
||||
|
#define MSG_WAITALL 0x40 /* wait for full request or error */ |
||||
|
#endif |
||||
|
|
||||
|
/* the socket connected to the nailgun server */ |
||||
|
int nailgunsocket = 0; |
||||
|
|
||||
|
/* buffer used for receiving and writing nail output chunks */ |
||||
|
char buf[BUFSIZE]; |
||||
|
|
||||
|
/* track whether server is ready to receive */ |
||||
|
#ifdef WIN32 |
||||
|
HANDLE readyToSend = 0; |
||||
|
HANDLE sending = 0; |
||||
|
#else |
||||
|
int readyToSend = 0; |
||||
|
struct timeval sendtime; |
||||
|
#endif |
||||
|
|
||||
|
/** |
||||
|
* Clean up the application. |
||||
|
*/ |
||||
|
void cleanUpAndExit (int exitCode) { |
||||
|
|
||||
|
|
||||
|
#ifdef WIN32 |
||||
|
CancelIo(STDIN_FILENO); |
||||
|
WSACleanup(); |
||||
|
if (nailgunsocket) { |
||||
|
closesocket(nailgunsocket); |
||||
|
} |
||||
|
#else |
||||
|
close(nailgunsocket); |
||||
|
#endif |
||||
|
|
||||
|
exit(exitCode); |
||||
|
} |
||||
|
|
||||
|
#ifdef WIN32 |
||||
|
/** |
||||
|
* Handles an error. |
||||
|
* Shows the message for the latest error then exits. |
||||
|
*/ |
||||
|
void handleError () { |
||||
|
LPVOID lpMsgBuf; |
||||
|
int error = GetLastError(); |
||||
|
|
||||
|
FormatMessage( |
||||
|
FORMAT_MESSAGE_ALLOCATE_BUFFER | |
||||
|
FORMAT_MESSAGE_FROM_SYSTEM | |
||||
|
FORMAT_MESSAGE_IGNORE_INSERTS, |
||||
|
NULL, |
||||
|
error, |
||||
|
MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), /* Default language */ |
||||
|
(LPTSTR) &lpMsgBuf, |
||||
|
0, |
||||
|
NULL); |
||||
|
|
||||
|
/* Display the string. */ |
||||
|
MessageBox( NULL, (LPCTSTR)lpMsgBuf, "Error", MB_OK | MB_ICONERROR ); |
||||
|
|
||||
|
/* Free the buffer. */ |
||||
|
LocalFree( lpMsgBuf ); |
||||
|
|
||||
|
cleanUpAndExit(error); |
||||
|
} |
||||
|
#endif |
||||
|
|
||||
|
/** |
||||
|
* Exits the client if the nailgun server ungracefully shut down the connection. |
||||
|
*/ |
||||
|
void handleSocketClose() { |
||||
|
cleanUpAndExit(NAILGUN_CONNECTION_BROKEN); |
||||
|
} |
||||
|
|
||||
|
/** |
||||
|
* Writes everything in the specified buffer to the specified |
||||
|
* socket handle. |
||||
|
* |
||||
|
* @param s the socket descriptor |
||||
|
* @param buf the buffer containing the data to send |
||||
|
* @param len the number of bytes to send. Also used to |
||||
|
* return the number of bytes sent. |
||||
|
* @return total bytes written or 0 if failure |
||||
|
*/ |
||||
|
int sendAll(SOCKET s, char *buf, int len) { |
||||
|
int total = 0; |
||||
|
int bytesleft = len; |
||||
|
int n = 0; |
||||
|
|
||||
|
while(total < len) { |
||||
|
n = send(s, buf+total, bytesleft, SEND_FLAGS); |
||||
|
|
||||
|
if (n == -1) { |
||||
|
break; |
||||
|
} |
||||
|
|
||||
|
total += n; |
||||
|
bytesleft -= n; |
||||
|
} |
||||
|
|
||||
|
return n==-1 ? 0:total; |
||||
|
} |
||||
|
|
||||
|
/** |
||||
|
* Sends a chunk noting the specified payload size and chunk type. |
||||
|
* Waits for sending mutex on Win32. |
||||
|
* |
||||
|
* @param size the payload size |
||||
|
* @param chunkType the chunk type identifier |
||||
|
*/ |
||||
|
void sendChunk(unsigned int size, char chunkType, char* buf) { |
||||
|
/* buffer used for reading and writing chunk headers */ |
||||
|
char header[CHUNK_HEADER_LEN]; |
||||
|
int bytesSent; |
||||
|
|
||||
|
header[0] = (size >> 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<name>=<value> 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 */ |
||||
|
} |
||||
Write
Preview
Loading…
Cancel
Save
Reference in new issue