pax_global_header00006660000000000000000000000064132336554670014530gustar00rootroot0000000000000052 comment=2b0ade9c035ffc37d072cdb3683c269531f83a99 nailgun-nailgun-all-0.9.3/000077500000000000000000000000001323365546700153775ustar00rootroot00000000000000nailgun-nailgun-all-0.9.3/.gitignore000066400000000000000000000001201323365546700173600ustar00rootroot00000000000000/nailgun-server/target/ /nailgun-examples/target/ /target/ ng *.iml *.pyc .idea/nailgun-nailgun-all-0.9.3/.travis.yml000066400000000000000000000001061323365546700175050ustar00rootroot00000000000000language: java jdk: - oraclejdk8 script: - scripts/travis_run.sh nailgun-nailgun-all-0.9.3/LICENSE.txt000066400000000000000000000261641323365546700172330ustar00rootroot00000000000000Nailgun Copyright © 2004-2012, Martian Software, Inc. Apache License Version 2.0, January 2004 http://www.apache.org/licenses/ TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 1. Definitions. "License" shall mean the terms and conditions for use, reproduction, and distribution as defined by Sections 1 through 9 of this document. "Licensor" shall mean the copyright owner or entity authorized by the copyright owner that is granting the License. "Legal Entity" shall mean the union of the acting entity and all other entities that control, are controlled by, or are under common control with that entity. For the purposes of this definition, "control" means (i) the power, direct or indirect, to cause the direction or management of such entity, whether by contract or otherwise, or (ii) ownership of fifty percent (50%) or more of the outstanding shares, or (iii) beneficial ownership of such entity. "You" (or "Your") shall mean an individual or Legal Entity exercising permissions granted by this License. "Source" form shall mean the preferred form for making modifications, including but not limited to software source code, documentation source, and configuration files. "Object" form shall mean any form resulting from mechanical transformation or translation of a Source form, including but not limited to compiled object code, generated documentation, and conversions to other media types. "Work" shall mean the work of authorship, whether in Source or Object form, made available under the License, as indicated by a copyright notice that is included in or attached to the work (an example is provided in the Appendix below). "Derivative Works" shall mean any work, whether in Source or Object form, that is based on (or derived from) the Work and for which the editorial revisions, annotations, elaborations, or other modifications represent, as a whole, an original work of authorship. For the purposes of this License, Derivative Works shall not include works that remain separable from, or merely link (or bind by name) to the interfaces of, the Work and Derivative Works thereof. "Contribution" shall mean any work of authorship, including the original version of the Work and any modifications or additions to that Work or Derivative Works thereof, that is intentionally submitted to Licensor for inclusion in the Work by the copyright owner or by an individual or Legal Entity authorized to submit on behalf of the copyright owner. For the purposes of this definition, "submitted" means any form of electronic, verbal, or written communication sent to the Licensor or its representatives, including but not limited to communication on electronic mailing lists, source code control systems, and issue tracking systems that are managed by, or on behalf of, the Licensor for the purpose of discussing and improving the Work, but excluding communication that is conspicuously marked or otherwise designated in writing by the copyright owner as "Not a Contribution." "Contributor" shall mean Licensor and any individual or Legal Entity on behalf of whom a Contribution has been received by Licensor and subsequently incorporated within the Work. 2. Grant of Copyright License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable copyright license to reproduce, prepare Derivative Works of, publicly display, publicly perform, sublicense, and distribute the Work and such Derivative Works in Source or Object form. 3. Grant of Patent License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable (except as stated in this section) patent license to make, have made, use, offer to sell, sell, import, and otherwise transfer the Work, where such license applies only to those patent claims licensable by such Contributor that are necessarily infringed by their Contribution(s) alone or by combination of their Contribution(s) with the Work to which such Contribution(s) was submitted. If You institute patent litigation against any entity (including a cross-claim or counterclaim in a lawsuit) alleging that the Work or a Contribution incorporated within the Work constitutes direct or contributory patent infringement, then any patent licenses granted to You under this License for that Work shall terminate as of the date such litigation is filed. 4. Redistribution. You may reproduce and distribute copies of the Work or Derivative Works thereof in any medium, with or without modifications, and in Source or Object form, provided that You meet the following conditions: (a) You must give any other recipients of the Work or Derivative Works a copy of this License; and (b) You must cause any modified files to carry prominent notices stating that You changed the files; and (c) You must retain, in the Source form of any Derivative Works that You distribute, all copyright, patent, trademark, and attribution notices from the Source form of the Work, excluding those notices that do not pertain to any part of the Derivative Works; and (d) If the Work includes a "NOTICE" text file as part of its distribution, then any Derivative Works that You distribute must include a readable copy of the attribution notices contained within such NOTICE file, excluding those notices that do not pertain to any part of the Derivative Works, in at least one of the following places: within a NOTICE text file distributed as part of the Derivative Works; within the Source form or documentation, if provided along with the Derivative Works; or, within a display generated by the Derivative Works, if and wherever such third-party notices normally appear. The contents of the NOTICE file are for informational purposes only and do not modify the License. You may add Your own attribution notices within Derivative Works that You distribute, alongside or as an addendum to the NOTICE text from the Work, provided that such additional attribution notices cannot be construed as modifying the License. You may add Your own copyright statement to Your modifications and may provide additional or different license terms and conditions for use, reproduction, or distribution of Your modifications, or for any such Derivative Works as a whole, provided Your use, reproduction, and distribution of the Work otherwise complies with the conditions stated in this License. 5. Submission of Contributions. Unless You explicitly state otherwise, any Contribution intentionally submitted for inclusion in the Work by You to the Licensor shall be under the terms and conditions of this License, without any additional terms or conditions. Notwithstanding the above, nothing herein shall supersede or modify the terms of any separate license agreement you may have executed with Licensor regarding such Contributions. 6. Trademarks. This License does not grant permission to use the trade names, trademarks, service marks, or product names of the Licensor, except as required for reasonable and customary use in describing the origin of the Work and reproducing the content of the NOTICE file. 7. Disclaimer of Warranty. Unless required by applicable law or agreed to in writing, Licensor provides the Work (and each Contributor provides its Contributions) on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied, including, without limitation, any warranties or conditions of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A PARTICULAR PURPOSE. You are solely responsible for determining the appropriateness of using or redistributing the Work and assume any risks associated with Your exercise of permissions under this License. 8. Limitation of Liability. In no event and under no legal theory, whether in tort (including negligence), contract, or otherwise, unless required by applicable law (such as deliberate and grossly negligent acts) or agreed to in writing, shall any Contributor be liable to You for damages, including any direct, indirect, special, incidental, or consequential damages of any character arising as a result of this License or out of the use or inability to use the Work (including but not limited to damages for loss of goodwill, work stoppage, computer failure or malfunction, or any and all other commercial damages or losses), even if such Contributor has been advised of the possibility of such damages. 9. Accepting Warranty or Additional Liability. While redistributing the Work or Derivative Works thereof, You may choose to offer, and charge a fee for, acceptance of support, warranty, indemnity, or other liability obligations and/or rights consistent with this License. However, in accepting such obligations, You may act only on Your own behalf and on Your sole responsibility, not on behalf of any other Contributor, and only if You agree to indemnify, defend, and hold each Contributor harmless for any liability incurred by, or claims asserted against, such Contributor by reason of your accepting any such warranty or additional liability. END OF TERMS AND CONDITIONS APPENDIX: How to apply the Apache License to your work. To apply the Apache License to your work, attach the following boilerplate notice, with the fields enclosed by brackets "{}" replaced with your own identifying information. (Don't include the brackets!) The text should be enclosed in the appropriate comment syntax for the file format. We also recommend that a file or class name and description of purpose be included on the same "printed page" as the copyright notice for easier identification within third-party archives. Copyright {yyyy} {name of copyright owner} 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. nailgun-nailgun-all-0.9.3/Makefile000066400000000000000000000020631323365546700170400ustar00rootroot00000000000000# This Makefile has only been tested on linux. It uses # MinGW32 to cross-compile for windows. To install and # configure MinGW32 on linux, see http://www.mingw.org # This is where the mingw32 compiler exists in Ubuntu 8.04. # Your compiler location may vary. WIN32_CC=/usr/bin/i586-mingw32msvc-gcc CFLAGS=-Wall -pedantic -O2 SRCDIR=nailgun-client PREFIX=/usr/local ng: ${SRCDIR}/ng.c @echo "Building ng client. To build a Windows binary, type 'make ng.exe'" ${CC} $(CPPFLAGS) $(CFLAGS) $(LDFLAGS) -o ng ${SRCDIR}/ng.c install: ng install -d ${PREFIX}/bin install ng ${PREFIX}/bin ng.exe: ${SRCDIR}/ng.c ${WIN32_CC} -o ng.exe ${SRCDIR}/ng.c -lwsock32 -O3 ${CFLAGS} # any idea why the command line is so sensitive to the order of # the arguments? If CFLAGS is at the beginning, it won't link. clean: @echo "" @echo "If you have a Windows binary, 'make clean' won't delete it." @echo "You must remove this manually. Most users won't have MinGW" @echo "installed - so I'd rather not delete something you can't rebuild." @echo "" rm -f ng # rm -f ng.exe nailgun-nailgun-all-0.9.3/README.md000066400000000000000000000020771323365546700166640ustar00rootroot00000000000000nailgun ======= --- **Note:** Nailgun is based on original code developed by Marty Lamb. In October, 2017, Marty transferred the repository to Facebook, where it is currently maintained by Buck team. Nailgun will remain available under the Apache license, version 2.0. --- Nailgun is a client, protocol, and server for running Java programs from the command line without incurring the JVM startup overhead. Programs run in the server (which is implemented in Java), and are triggered by the client (written in C), which handles all I/O. The server and examples are built using maven. From the project directory, "mvn clean install" will do it. The client is built using make. From the project directory, "make && sudo make install" will do it. To create the windows client you will additionally need to "make ng.exe". A ruby client is available through the [railgun](https://github.com/timuralp/railgun) project. For more information, see [the nailgun website](http://martiansoftware.com/nailgun/). nailgun-nailgun-all-0.9.3/appveyor.yml000066400000000000000000000006211323365546700177660ustar00rootroot00000000000000os: Visual Studio 2015 version: '{build}' environment: matrix: - JAVA_HOME: C:\Program Files\Java\jdk1.8.0 install: - cmd: choco install maven -y -f -i - cmd: refreshenv - cmd: set - cmd: python --version - cmd: java -version - cmd: mvn -version # to disable automatic builds by MsBuild build: off build_script: - "mvn package" test_script: - "python -m pynailgun.test_ng" nailgun-nailgun-all-0.9.3/nailgun-client/000077500000000000000000000000001323365546700203105ustar00rootroot00000000000000nailgun-nailgun-all-0.9.3/nailgun-client/ng.c000066400000000000000000000577451323365546700211020ustar00rootroot00000000000000/* 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 #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 || errno == ECONNRESET))) { 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 *server_addr; socklen_t server_addr_len; struct sockaddr_in server_addr_in; #ifndef WIN32 struct sockaddr_un server_addr_un; #endif 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); } #ifndef WIN32 if (strncmp(nailgun_server, "local:", 6) == 0) { const char *socket_path = nailgun_server + 6; size_t socket_path_len = strlen(socket_path); if (socket_path_len > sizeof(server_addr_un.sun_path) - 1) { fprintf(stderr, "Socket path [%s] too long (%ld)\n", socket_path, (long) socket_path_len); cleanUpAndExit(NAILGUN_SOCKET_FAILED); } if ((nailgunsocket = socket(PF_LOCAL, SOCK_STREAM, 0)) == -1) { perror("socket"); cleanUpAndExit(NAILGUN_SOCKET_FAILED); } server_addr_un.sun_family = AF_LOCAL; strncpy(server_addr_un.sun_path, socket_path, socket_path_len); server_addr_un.sun_path[socket_path_len] = '\0'; #ifdef BSD server_addr_un.sun_len = offsetof(struct sockaddr_un, sun_path) + socket_path_len; #endif server_addr = (struct sockaddr *)&server_addr_un; server_addr_len = sizeof(server_addr_un); } else { #endif /* 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_in.sin_family = AF_INET; server_addr_in.sin_port = htons(port); server_addr_in.sin_addr = *(struct in_addr *) hostinfo->h_addr; memset(&(server_addr_in.sin_zero), '\0', 8); server_addr = (struct sockaddr *)&server_addr_in; server_addr_len = sizeof(server_addr_in); #ifndef WIN32 } #endif if (connect(nailgunsocket, server_addr, server_addr_len) == -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 */ } nailgun-nailgun-all-0.9.3/nailgun-examples/000077500000000000000000000000001323365546700206505ustar00rootroot00000000000000nailgun-nailgun-all-0.9.3/nailgun-examples/pom.xml000066400000000000000000000024111323365546700221630ustar00rootroot00000000000000 4.0.0 com.martiansoftware nailgun-examples jar nailgun-examples Nailgun is a client, protocol, and server for running Java programs from the command line without incurring the JVM startup overhead. Programs run in the server (which is implemented in Java), and are triggered by the client (written in C), which handles all I/O. This project contains the EXAMPLE CODE ONLY. http://martiansoftware.com/nailgun com.martiansoftware nailgun-all 0.9.3-SNAPSHOT com.martiansoftware nailgun-server ${version} compile nailgun-nailgun-all-0.9.3/nailgun-examples/src/000077500000000000000000000000001323365546700214375ustar00rootroot00000000000000nailgun-nailgun-all-0.9.3/nailgun-examples/src/main/000077500000000000000000000000001323365546700223635ustar00rootroot00000000000000nailgun-nailgun-all-0.9.3/nailgun-examples/src/main/java/000077500000000000000000000000001323365546700233045ustar00rootroot00000000000000nailgun-nailgun-all-0.9.3/nailgun-examples/src/main/java/com/000077500000000000000000000000001323365546700240625ustar00rootroot00000000000000nailgun-nailgun-all-0.9.3/nailgun-examples/src/main/java/com/martiansoftware/000077500000000000000000000000001323365546700272705ustar00rootroot00000000000000nailgun-nailgun-all-0.9.3/nailgun-examples/src/main/java/com/martiansoftware/nailgun/000077500000000000000000000000001323365546700307255ustar00rootroot00000000000000nailgun-nailgun-all-0.9.3/nailgun-examples/src/main/java/com/martiansoftware/nailgun/examples/000077500000000000000000000000001323365546700325435ustar00rootroot00000000000000DumpAll.java000066400000000000000000000036631323365546700346750ustar00rootroot00000000000000nailgun-nailgun-all-0.9.3/nailgun-examples/src/main/java/com/martiansoftware/nailgun/examples/* 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. */ package com.martiansoftware.nailgun.examples; import java.util.Iterator; import java.util.TreeSet; import com.martiansoftware.nailgun.NGContext; /** * Simply displays command line arguments to System.out. * * @author Marty Lamb */ public class DumpAll { public static void nailMain(NGContext context) { context.out.println(); context.out.println(" context.getCommand(): " + context.getCommand()); context.out.println(" context.getInetAddress(): " + context.getInetAddress()); context.out.println(" context.getPort(): " + context.getPort()); context.out.println("context.getWorkingDirectory(): " + context.getWorkingDirectory()); context.out.println(" context.getFileSeparator(): " + context.getFileSeparator()); context.out.println(" context.getPathSeparator(): " + context.getPathSeparator()); context.out.println("\ncontext.getArgs():"); for (int i = 0; i < context.getArgs().length; ++i) { context.out.println(" args[" + i + "]=" + context.getArgs()[i]); } context.out.println("\ncontext.getEnv():"); TreeSet keys = new TreeSet(context.getEnv().keySet()); for (Iterator i = keys.iterator(); i.hasNext();) { String key = (String) i.next(); context.out.println(" env[\"" + key + "\"]=" + context.getEnv().getProperty(key)); } } } Echo.java000066400000000000000000000020321323365546700342020ustar00rootroot00000000000000nailgun-nailgun-all-0.9.3/nailgun-examples/src/main/java/com/martiansoftware/nailgun/examples/* 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. */ package com.martiansoftware.nailgun.examples; /** * Echos everything it reads from System.in to System.out. * * @author Marty Lamb */ public class Echo { public static void main(String[] args) throws Exception { byte[] b = new byte[1024]; int bytesRead = System.in.read(b); while (bytesRead != -1) { System.out.write(b, 0, bytesRead); bytesRead = System.in.read(b); } } } Exit.java000066400000000000000000000020121323365546700342330ustar00rootroot00000000000000nailgun-nailgun-all-0.9.3/nailgun-examples/src/main/java/com/martiansoftware/nailgun/examples/* 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. */ package com.martiansoftware.nailgun.examples; public class Exit { public static void main (String[] args) { int exitCode = (int) ((Math.random() * 1000) + 1); if (args.length > 0) { try { exitCode = Integer.parseInt(args[0]); } catch (Exception e) {} } // Close stdout to test the exit code is returned properly // even in such case System.out.close(); System.exit(exitCode); } } Hash.java000066400000000000000000000076461323365546700342270ustar00rootroot00000000000000nailgun-nailgun-all-0.9.3/nailgun-examples/src/main/java/com/martiansoftware/nailgun/examples/* 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. */ package com.martiansoftware.nailgun.examples; import java.security.MessageDigest; import java.security.Provider; import java.security.Security; import java.util.Iterator; import java.util.Set; import com.martiansoftware.nailgun.NGContext; /** * Hashes the client's stdin to the client's stdout in the form of * a hexadecimal string. Command line requires one parameter: either the name * of the algorithm to use (e.g., "MD5"), or "?" to request a list of * available algorithms. * * @author Marty Lamb */ public class Hash { // used to turn byte[] to string private static final char[] HEXCHARS = {'0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'a', 'b', 'c', 'd', 'e', 'f'}; /** * Provides a list of algorithms for the specified service (which, for * our purposes, is "MessageDigest". * * This method was only very slightly adapted (to use a TreeSet) from * the Java Almanac at http://javaalmanac.com/egs/java.security/ListServices.html * @param serviceType The name of the service we're looking for. It's "MessageDigest" */ private static Set getCryptoImpls(String serviceType) { Set result = new java.util.TreeSet(); // All all providers Provider[] providers = Security.getProviders(); for (int i=0; i> 4) & 0x0f]); buf.append(HEXCHARS[result[i] & 0x0f]); } context.out.println(buf); } } } Heartbeat.java000066400000000000000000000042071323365546700352310ustar00rootroot00000000000000nailgun-nailgun-all-0.9.3/nailgun-examples/src/main/java/com/martiansoftware/nailgun/examples/* Copyright 2004-2012, Jim Purbrick. 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. */ package com.martiansoftware.nailgun.examples; import com.martiansoftware.nailgun.NGContext; import java.io.IOException; /** * Print one hash per second to standard out while the client is running. * * @author Jim Purbrick */ public class Heartbeat { /** * Registers a {@link com.martiansoftware.nailgun.NGClientListener} with the session then * loops forever printing hashes until * {@link com.martiansoftware.nailgun.NGClientListener#clientDisconnected()} is called. * @param context the Nailgun context used to register the nail as a * {@link com.martiansoftware.nailgun.NGClientListener}. */ public static void nailMain(final NGContext context) throws IOException { try { // Register a new NGClientListener. As clientDisconnected is called from // another thread any nail state access must be properly synchronized. Thread mainThread = Thread.currentThread(); context.addClientListener(mainThread::interrupt); // Register a new NGHeartbeatListener. This is normally only used for debugging disconnection problems. context.addHeartbeatListener(() -> context.out.print("H")); // Loop printing a hash to the client every second until client disconnects. while(!Thread.currentThread().isInterrupted()) { Thread.sleep(5000); context.out.print("S"); } } catch (InterruptedException ignored) { System.exit(42); } System.exit(0); } }HelloWorld.java000066400000000000000000000016161323365546700354060ustar00rootroot00000000000000nailgun-nailgun-all-0.9.3/nailgun-examples/src/main/java/com/martiansoftware/nailgun/examples/* 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. */ package com.martiansoftware.nailgun.examples; /** * A truly amazing program that must be seen to be believed. * * @author Marty Lamb */ public class HelloWorld { public static void main(String[] args) { System.out.println("Hello, world!"); } } Prompt.java000066400000000000000000000022551323365546700346140ustar00rootroot00000000000000nailgun-nailgun-all-0.9.3/nailgun-examples/src/main/java/com/martiansoftware/nailgun/examples/* 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. */ package com.martiansoftware.nailgun.examples; import com.martiansoftware.nailgun.NGContext; /** * Prompts the user for input using a JOptionPane, and displays the * result to the client's stdout. If the user clicks "cancel", the * client exits with exit code 1. * * @author Marty Lamb */ public class Prompt { public static void nailMain(NGContext context) { String result = javax.swing.JOptionPane.showInputDialog(null, "Input:"); if (result == null) { context.exit(1); } else { context.out.println(result); } } } Stack.java000066400000000000000000000047641323365546700344070ustar00rootroot00000000000000nailgun-nailgun-all-0.9.3/nailgun-examples/src/main/java/com/martiansoftware/nailgun/examples/* 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. */ package com.martiansoftware.nailgun.examples; import com.martiansoftware.nailgun.NGContext; import com.martiansoftware.nailgun.NGServer; /** * Provides some nice command-line stack operations. This nail must * have the aliases "push" and "pop" associated with it in order to * work properly. * * If the "push" command is used, each argument on the command line * is pushed onto the stack (in order) and the program returns * immediately. * * If the "pop" command is used, the top item on the stack is displayed * to the client's stdout. If the stack is empty, the client will * block until another process calls push. If the nailgun server is * shutdown while pop is blocking, pop will cause the client to exit * with exit code 1. This is thread-safe: you can have multiple * clients waiting on "pop" and only one of them (determined by the VM * and the magic of synchronization) will receive any one pushed item. * * @author Marty Lamb */ public class Stack { private static java.util.Stack sharedStack = new java.util.Stack(); private static boolean done = false; public static void nailShutdown(NGServer server) { done = true; synchronized(sharedStack) { sharedStack.notifyAll(); } } public static void nailMain(NGContext context) throws InterruptedException { if (context.getCommand().equals("push")) { synchronized(sharedStack) { String[] args = context.getArgs(); for (int i = 0; i < args.length; ++i) { sharedStack.push(args[i]); } sharedStack.notifyAll(); context.exit(0); return; } } else if (context.getCommand().equals("pop")) { int exitCode = 1; synchronized(sharedStack) { while (!done && (sharedStack.size() == 0)) { sharedStack.wait(); } if (sharedStack.size() > 0) { context.out.println(sharedStack.pop()); exitCode = 0; } } context.exit(exitCode); return; } } } ThreadTest.java000066400000000000000000000030311323365546700353730ustar00rootroot00000000000000nailgun-nailgun-all-0.9.3/nailgun-examples/src/main/java/com/martiansoftware/nailgun/examples/* 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. */ package com.martiansoftware.nailgun.examples; /** * A very silly test to verify that the System.in/out/err overrides are * inherited by child threads. A bunch of numbers and thread ids are displayed * to the client's stdout. The important thing is that all threads launched * by the nail are writing to the same client. * * @author Marty Lamb */ public class ThreadTest implements Runnable { private String name = null; public ThreadTest(String name) { this.name = name; } public void run() { for (int i = 0; i < 10; ++i) { System.out.println(name + ": " + i); try {Thread.sleep(100);} catch (Throwable t){} } } public static void main(String[] args) { for (int i = 0; i < 3; ++i) { Thread t = new Thread(new ThreadTest("T" + i)); t.start(); System.out.println("Started number " + i); } try {Thread.sleep(2000);} catch (Throwable t) {} System.out.println("Done."); } } nailgun-nailgun-all-0.9.3/nailgun-server/000077500000000000000000000000001323365546700203405ustar00rootroot00000000000000nailgun-nailgun-all-0.9.3/nailgun-server/pom.xml000066400000000000000000000061421323365546700216600ustar00rootroot00000000000000 4.0.0 com.martiansoftware nailgun-server jar nailgun-server Nailgun is a client, protocol, and server for running Java programs from the command line without incurring the JVM startup overhead. Programs run in the server (which is implemented in Java), and are triggered by the client (written in C), which handles all I/O. This project contains the SERVER ONLY. http://martiansoftware.com/nailgun com.martiansoftware nailgun-all 0.9.3-SNAPSHOT net.java.dev.jna jna 4.1.0 net.java.dev.jna jna-platform 4.1.0 org.apache.maven.plugins maven-jar-plugin 2.4 true com.martiansoftware.nailgun.NGServer org.apache.maven.plugins maven-shade-plugin 3.0.0 package shade true uber false com.martiansoftware.nailgun.NGServer 1.8 1.8 nailgun-nailgun-all-0.9.3/nailgun-server/src/000077500000000000000000000000001323365546700211275ustar00rootroot00000000000000nailgun-nailgun-all-0.9.3/nailgun-server/src/main/000077500000000000000000000000001323365546700220535ustar00rootroot00000000000000nailgun-nailgun-all-0.9.3/nailgun-server/src/main/java/000077500000000000000000000000001323365546700227745ustar00rootroot00000000000000nailgun-nailgun-all-0.9.3/nailgun-server/src/main/java/com/000077500000000000000000000000001323365546700235525ustar00rootroot00000000000000nailgun-nailgun-all-0.9.3/nailgun-server/src/main/java/com/martiansoftware/000077500000000000000000000000001323365546700267605ustar00rootroot00000000000000nailgun-nailgun-all-0.9.3/nailgun-server/src/main/java/com/martiansoftware/nailgun/000077500000000000000000000000001323365546700304155ustar00rootroot00000000000000nailgun-nailgun-all-0.9.3/nailgun-server/src/main/java/com/martiansoftware/nailgun/Alias.java000066400000000000000000000063501323365546700323150ustar00rootroot00000000000000/* 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. */ package com.martiansoftware.nailgun; /** * Provides a means to map memorable, short names to classes in order * to make the issuing of commands more convenient. For example, an * Alias can map the "mycommand" command to the com.yourdomain.yourpackage.YourClass * class. Obviously, it's a lot easier to type "ng mycommand" than the fully * qualified class name. * * @author Marty Lamb */ public class Alias implements Comparable { /** * The alias name */ private String name; /** * The alias description (may be used to provide help to users) */ private String description; /** * The class providing a main() or nailMain() method */ private Class clazz; /** * Creates a new Alias with the specified properties. * @param name the alias name (short command) * @param description a description of the command * @param clazz the class implementing the command */ public Alias(String name, String description, Class clazz) { if (name == null) throw (new IllegalArgumentException("Alias must have a name.")); this.name = name.trim(); if (this.name.length() == 0) throw (new IllegalArgumentException("Alias must have a name.")); if (clazz == null) throw (new IllegalArgumentException("Alias must have an associated class.")); this.description = description; this.clazz = clazz; } /** * Returns the Class object providing a static main() or nailMain() method * for this command. * @return the Class object providing a static main() or nailMain() method * for this command. */ public Class getAliasedClass() { return(clazz); } /** * Returns the name of the aliased command * @return the name of the aliased command */ public String getName() { return (name); } /** * Returns a description for the aliased command * @return a description for the aliased command */ public String getDescription() { return (description); } /** * @see Object#hashCode() */ public int hashCode() { return (name.hashCode()); } /** * Checks whether two Aliases have the same name. Does not * compare any other fields. * @param o the other Alias to check * @return true if the specified Alias has the same name as this Alias. */ public boolean equals(Object o) { return (compareTo(o) == 0); } /** * Compares Alias names - no other fields are compared. * @see Comparable#compareTo(Object) */ public int compareTo(Object o) { return (name.compareTo(((Alias) o).getName())); } } nailgun-nailgun-all-0.9.3/nailgun-server/src/main/java/com/martiansoftware/nailgun/AliasManager.java000066400000000000000000000106131323365546700336050ustar00rootroot00000000000000/* 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. */ package com.martiansoftware.nailgun; import java.util.Iterator; import java.util.Map; import java.util.Properties; import java.util.Set; /** * An AliasManager is used to store and lookup command Aliases by name. See Alias for more details. * * @author Marty Lamb */ public class AliasManager { /** * actual alias storage */ private Map aliases; /** * Creates a new AliasManager, populating it with default Aliases. */ public AliasManager() { aliases = new java.util.HashMap(); try { Properties props = new Properties(); ClassLoader cl = getClass().getClassLoader(); if (cl == null) cl = ClassLoader.getSystemClassLoader(); // needed if nailgun classes are loaded in the boot classpath. props.load(cl.getResourceAsStream("com/martiansoftware/nailgun/builtins/builtins.properties")); loadFromProperties(props); } catch (java.io.IOException e) { System.err.println("Unable to load builtins.properties: " + e.getMessage()); } } /** * Loads Aliases from a java.util.Properties file located at the specified * URL. The properties must be of the form: *
[alias name]=[fully qualified classname]
each of * which may have an optional *
[alias name].desc=[alias description]
* * For example, to create an alias called " * myprog" for class * com.mydomain.myapp.MyProg, the following properties would be * defined: * *
myprog=com.mydomain.myapp.MyProg
     *myprog.desc=Runs my program.
     * 
* * @param properties the Properties to load. */ public void loadFromProperties(java.util.Properties properties) { for (Iterator i = properties.keySet().iterator(); i.hasNext();) { String key = (String) i.next(); if (!key.endsWith(".desc")) { try { Class clazz = Class.forName(properties.getProperty(key)); String desc = properties.getProperty(key + ".desc", ""); addAlias(new Alias(key, desc, clazz)); } catch (ClassNotFoundException e) { System.err.println("Unable to locate class " + properties.getProperty(key)); } } } } /** * Adds an Alias, replacing any previous entries with the same name. * * @param alias the Alias to add */ public void addAlias(Alias alias) { synchronized (aliases) { aliases.put(alias.getName(), alias); } } /** * Returns a Set that is a snapshot of the Alias list. Modifications to this * Set will not impact the AliasManager in any way. * * @return a Set that is a snapshot of the Alias list. */ public Set getAliases() { Set result = new java.util.TreeSet(); synchronized (aliases) { result.addAll(aliases.values()); } return (result); } /** * Removes the Alias with the specified name from the AliasManager. If no * such Alias exists in this AliasManager, this method has no effect. * * @param aliasName the name of the Alias to remove */ public void removeAlias(String aliasName) { synchronized (aliases) { aliases.remove(aliasName); } } /** * Returns the Alias with the specified name * * @param aliasName the name of the Alias to retrieve * @return the requested Alias, or null if no such Alias is defined in this * AliasManager. */ public Alias getAlias(String aliasName) { return ((Alias) aliases.get(aliasName)); } } NGClientListener.java000066400000000000000000000005101323365546700343460ustar00rootroot00000000000000nailgun-nailgun-all-0.9.3/nailgun-server/src/main/java/com/martiansoftware/nailgunpackage com.martiansoftware.nailgun; public interface NGClientListener { /** * Called by an internal nailgun thread when the server detects that the nailgun client has disconnected. * {@link NGClientListener}s can be registered using {@link NGContext#addClientListener}. */ void clientDisconnected(); } nailgun-nailgun-all-0.9.3/nailgun-server/src/main/java/com/martiansoftware/nailgun/NGConstants.java000066400000000000000000000076511323365546700334720ustar00rootroot00000000000000/* 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. */ package com.martiansoftware.nailgun; import java.util.Properties; /** * Just a simple holder for various NailGun-related contants. * * @author Marty Lamb */ public class NGConstants { /** * The default NailGun port (2113) */ public static final int DEFAULT_PORT = 2113; /** * The exit code sent to clients if an exception occurred on the server */ public static final int EXIT_EXCEPTION = 899; /** * The exit code sent to clients if an invalid command is sent */ public static final int EXIT_NOSUCHCOMMAND = 898; /** * Chunk type marker for command line arguments */ public static final byte CHUNKTYPE_ARGUMENT = 'A'; /** * Chunk type marker for client environment variables */ public static final byte CHUNKTYPE_ENVIRONMENT = 'E'; /** * Chunk type marker for the command (alias or class) */ public static final byte CHUNKTYPE_COMMAND = 'C'; /** * Chunk type marker for client working directory */ public static final byte CHUNKTYPE_WORKINGDIRECTORY = 'D'; /** * Chunk type marker for stdin */ public static final byte CHUNKTYPE_STDIN = '0'; /** * Chunk type marker for the end of stdin */ public static final byte CHUNKTYPE_STDIN_EOF = '.'; /** * Chunk type marker for stdout */ public static final byte CHUNKTYPE_STDOUT = '1'; /** * Chunk type marker for stderr */ public static final byte CHUNKTYPE_STDERR = '2'; /** * Chunk type marker for client exit chunks */ public static final byte CHUNKTYPE_EXIT = 'X'; /** * Chunk type marker for a "startinput" chunk. This chunk type is sent from * the server to the client and indicates that the client should begin * sending stdin to the server. It is automatically sent the first time the * client's inputstream is read. */ public static final byte CHUNKTYPE_SENDINPUT = 'S'; /** * Chunk type marker for heartbeats sent to let the server know the client is still alive. */ public static final byte CHUNKTYPE_HEARTBEAT = 'H'; /** * Server version number */ public static final String VERSION; /** * Expected interval between heartbeats in milliseconds. */ public static final short HEARTBEAT_INTERVAL_MILLIS = 1000; /** * Maximum interval to wait between heartbeats before considering client to have disconnected. */ public static final short HEARTBEAT_TIMEOUT_MILLIS = 10000; /** * Maximum chunk len sent from client. */ public static final short MAXIMUM_CHUNK_LENGTH = 2048; /** * Returns the Nailgun version number * * @return the Nailgun version number */ public static String getVersion() { String result = "[unknown]"; return result; } /** * Loads the version number from a file generated by Maven. */ static { Properties props = new Properties(); try { props.load(NGConstants.class.getResourceAsStream("/META-INF/maven/com.martiansoftware/nailgun-server/pom.properties")); } catch (Exception e) { System.err.println("Unable to load nailgun-version.properties."); } VERSION = props.getProperty("version", "[UNKNOWN]"); } } nailgun-nailgun-all-0.9.3/nailgun-server/src/main/java/com/martiansoftware/nailgun/NGContext.java000066400000000000000000000203351323365546700331340ustar00rootroot00000000000000/* 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. */ package com.martiansoftware.nailgun; import java.io.InputStream; import java.io.PrintStream; import java.net.InetAddress; import java.net.NetworkInterface; import java.util.Properties; /** *

Provides quite a bit of potentially useful information to classes * specifically written for NailGun. The NailGun server itself, its * AliasManager, the remote client's environment variables, and other * information is available via this class. For all intents and purposes, * the NGContext represents a single connection from a NailGun client.

* * If a class is written with a * *

 * public static void nailMain(NGContext context)
 * 
* * method, that method will be called by NailGun instead of the traditional * main(String[]) method normally used for programs. A fully populated NGContext * object will then be provided to nailMain(). * * @author Marty Lamb */ public class NGContext { /** * The remote host's environment variables */ private Properties remoteEnvironment = null; /** * The remote host's address */ private InetAddress remoteHost = null; /** * The port on the remote host that is communicating with NailGun */ private int remotePort = 0; /** * Command line arguments for the nail */ private String[] args = null; /** * A stream to which a client exit code can be printed */ private PrintStream exitStream = null; /** * The NGServer that accepted this connection */ private NGServer server = null; /** * The command that was issued for this connection */ private String command = null; private String workingDirectory = null; /** * The client's stdin */ public InputStream in = null; /** * The client's stdout */ public PrintStream out = null; /** * The client's stderr */ public PrintStream err = null; /** * Creates a new, empty NGContext */ public NGContext() { super(); } public void setExitStream(PrintStream exitStream) { this.exitStream = exitStream; } public void setPort(int remotePort) { this.remotePort = remotePort; } public void setCommand(String command) { this.command = command; } /** * Returns the command that was issued by the client (either an alias or the name of a class). * This allows multiple aliases to point to the same class but result in different behaviors. * @return the command issued by the client */ public String getCommand() { return (command); } void setWorkingDirectory(String workingDirectory) { this.workingDirectory = workingDirectory; } /** * Returns the current working directory of the client, as reported by the client. * This is a String that will use the client's File.separator ('/' or '\'), * which may differ from the separator on the server. * @return the current working directory of the client */ public String getWorkingDirectory() { return (workingDirectory); } void setEnv(Properties remoteEnvironment) { this.remoteEnvironment = remoteEnvironment; } void setInetAddress(InetAddress remoteHost) { this.remoteHost = remoteHost; } public void setArgs(String[] args) { this.args = args; } void setNGServer(NGServer server) { this.server = server; } /** * Returns a java.util.Properties object containing a copy * of the client's environment variables * @see java.util.Properties * @return a java.util.Properties object containing a copy * of the client's environment variables */ public Properties getEnv() { return (remoteEnvironment); } /** * Returns the file separator ('/' or '\\') used by the client's os. * @return the file separator ('/' or '\\') used by the client's os. */ public String getFileSeparator() { return (remoteEnvironment.getProperty("NAILGUN_FILESEPARATOR")); } /** * Returns the path separator (':' or ';') used by the client's os. * @return the path separator (':' or ';') used by the client's os. */ public String getPathSeparator() { return (remoteEnvironment.getProperty("NAILGUN_PATHSEPARATOR")); } /** * Returns the address of the client at the other side of this connection. * @return the address of the client at the other side of this connection. */ public InetAddress getInetAddress() { return (remoteHost); } /** * Returns the command line arguments for the command * implementation (nail) on the server. * @return the command line arguments for the command * implementation (nail) on the server. */ public String[] getArgs() { return (args); } /** * Returns the NGServer that accepted this connection * @return the NGServer that accepted this connection */ public NGServer getNGServer() { return (server); } /** * Sends an exit command with the specified exit code to * the client. The client will exit immediately with * the specified exit code; you probably want to return * from nailMain immediately after calling this. * * @param exitCode the exit code with which the client * should exit */ public void exit(int exitCode) { exitStream.println(exitCode); } /** * Returns the port on the client connected to the NailGun * server. * @return the port on the client connected to the NailGun * server. */ public int getPort() { return (remotePort); } /** * Throws a java.lang.SecurityException if the client is not * connected via the loopback address. */ public void assertLoopbackClient() { if (!getInetAddress().isLoopbackAddress()) { throw (new SecurityException("Client is not at loopback address.")); } } /** * Throws a java.lang.SecurityException if the client is not * connected from the local machine. */ public void assertLocalClient() { NetworkInterface iface = null; try { iface = NetworkInterface.getByInetAddress(getInetAddress()); } catch (java.net.SocketException se) { throw (new SecurityException("Unable to determine if client is local. Assuming he isn't.")); } if ((iface == null) && (!getInetAddress().isLoopbackAddress())) { throw (new SecurityException("Client is not local.")); } } /** * @return the {@link NGInputStream} for this session. */ private NGInputStream getInputStream() { return (NGInputStream) this.in; } /** * @return true if client is connected, false if a client exit has been detected. */ public boolean isClientConnected() { return getInputStream().isClientConnected(); } /** * @param listener the {@link NGClientListener} to be notified of client events. */ public void addClientListener(NGClientListener listener) { getInputStream().addClientListener(listener); } /** * @param listener the {@link NGClientListener} to no longer be notified of client events. */ public void removeClientListener(NGClientListener listener) { getInputStream().removeClientListener(listener); } /** * Do not notify about client exit */ public void removeAllClientListeners() { getInputStream().removeAllClientListeners(); } /** * @param listener the {@link com.martiansoftware.nailgun.NGHeartbeatListener} to be notified of client events. */ public void addHeartbeatListener(NGHeartbeatListener listener) { getInputStream().addHeartbeatListener(listener); } /** * @param listener the {@link NGHeartbeatListener} to no longer be notified of client events. */ public void removeHeartbeatListener(NGHeartbeatListener listener) { getInputStream().removeHeartbeatListener(listener); } } NGExitException.java000066400000000000000000000073461323365546700342300ustar00rootroot00000000000000nailgun-nailgun-all-0.9.3/nailgun-server/src/main/java/com/martiansoftware/nailgun/* * This class is based upon org.apache.tools.ant.ExitException and is * subject to the following: * * * The Apache Software License, Version 1.1 * * Copyright (c) 2001-2003 The Apache Software Foundation. All rights * reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in * the documentation and/or other materials provided with the * distribution. * * 3. The end-user documentation included with the redistribution, if * any, must include the following acknowlegement: * "This product includes software developed by the * Apache Software Foundation (http://www.apache.org/)." * Alternately, this acknowlegement may appear in the software itself, * if and wherever such third-party acknowlegements normally appear. * * 4. The names "Ant" and "Apache Software * Foundation" must not be used to endorse or promote products derived * from this software without prior written permission. For written * permission, please contact apache@apache.org. * * 5. Products derived from this software may not be called "Apache" * nor may "Apache" appear in their names without prior written * permission of the Apache Group. * * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE * DISCLAIMED. IN NO EVENT SHALL THE APACHE SOFTWARE FOUNDATION OR * ITS CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF * USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF * SUCH DAMAGE. * ==================================================================== * * This software consists of voluntary contributions made by many * individuals on behalf of the Apache Software Foundation. For more * information on the Apache Software Foundation, please see * . */ package com.martiansoftware.nailgun; import java.io.PrintStream; /** * Security exception which wraps an exit status code. * * @author Pete Kirkham */ public class NGExitException extends SecurityException { /** * Status code */ private int status; /** * Constructs an exit exception. * * @param status the status code returned via System.exit() */ public NGExitException(int status) { super("ExitException: status " + status); this.status = status; } /** * A lot of code out there, for example ant's Launcher, runs inside a * try/catch (Throwable) which will squash this exception; most also calll * printStackTrace(), so this re-throws the exception to escape the handling * code. */ public void printStackTrace(PrintStream out) { throw this; } public void reallyPrintStackTrace(PrintStream out) { super.printStackTrace(out); } /** * The status code returned by System.exit() * * @return the status code returned by System.exit() */ public int getStatus() { return status; } }NGHeartbeatListener.java000066400000000000000000000006501323365546700350340ustar00rootroot00000000000000nailgun-nailgun-all-0.9.3/nailgun-server/src/main/java/com/martiansoftware/nailgunpackage com.martiansoftware.nailgun; public interface NGHeartbeatListener { /** * Called by an internal nailgun thread when the server receives a heartbeat from the client. * This can normally be implemented as a no-op handler and is primarily useful for debugging. * {@link NGClientListener}s can be registered using {@link NGContext.registerHeartbeatListener}. */ void heartbeatReceived(); } NGInputStream.java000066400000000000000000000413251323365546700337060ustar00rootroot00000000000000nailgun-nailgun-all-0.9.3/nailgun-server/src/main/java/com/martiansoftware/nailgun/* 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. */ package com.martiansoftware.nailgun; import java.io.*; import java.net.SocketTimeoutException; import java.util.ArrayList; import java.util.HashSet; import java.util.List; import java.util.Set; import java.util.concurrent.*; import java.util.concurrent.atomic.AtomicLong; import java.util.logging.Level; import java.util.logging.Logger; /** * A FilterInputStream that is able to read the chunked stdin stream from a NailGun client. * * @author Marty Lamb */ public class NGInputStream extends FilterInputStream implements Closeable { private static final Logger LOG = Logger.getLogger(NGInputStream.class.getName()); private final ExecutorService orchestratorExecutor; private final ExecutorService readExecutor; private final DataInputStream din; private InputStream stdin = null; private boolean eof = false; private boolean clientConnected = true; private int remaining = 0; private byte[] oneByteBuffer = null; private final DataOutputStream out; private boolean started = false; private final Set clientListeners = new HashSet<>(); private final Set heartbeatListeners = new HashSet<>(); private static final long TERMINATION_TIMEOUT_MS = 1000; /** * Creates a new NGInputStream wrapping the specified InputStream. Also sets up a timer to * periodically consume heartbeats sent from the client and call registered NGClientListeners if * a client disconnection is detected. * * @param in the InputStream to wrap * @param out the OutputStream to which SENDINPUT chunks should be sent * @param heartbeatTimeoutMillis the interval between heartbeats before considering the client * disconnected */ public NGInputStream( DataInputStream in, DataOutputStream out, final int heartbeatTimeoutMillis) { super(in); this.din = in; this.out = out; /** Thread factory that overrides name and priority for executor threads */ final class NamedThreadFactory implements ThreadFactory { private final String threadName; public NamedThreadFactory(String threadName) { this.threadName = threadName; } @Override public Thread newThread(Runnable r) { SecurityManager s = System.getSecurityManager(); ThreadGroup group = (s != null) ? s.getThreadGroup() : Thread.currentThread().getThreadGroup(); Thread t = new Thread(group, r, this.threadName, 0); if (t.isDaemon()) { t.setDaemon(false); } if (t.getPriority() != Thread.MAX_PRIORITY) { // warning - it may actually set lower priority if current thread group does not allow // higher priorities t.setPriority(Thread.MAX_PRIORITY); } return t; } } Thread mainThread = Thread.currentThread(); this.orchestratorExecutor = Executors.newSingleThreadExecutor( new NamedThreadFactory(mainThread.getName() + " (NGInputStream orchestrator)")); this.readExecutor = Executors.newSingleThreadExecutor( new NamedThreadFactory(mainThread.getName() + " (NGInputStream reader)")); // Read timeout, including heartbeats, should be handled by socket. // However Java Socket/Stream API does not enforce that. To stay on safer side, // use timeout on a future // let socket timeout first, set rough timeout to 110% of original long futureTimeout = heartbeatTimeoutMillis + heartbeatTimeoutMillis / 10; orchestratorExecutor.submit(() -> { try { boolean isMoreData = true; while (isMoreData) { Future readFuture = readExecutor.submit(() -> { try { // return false if client sends EOF readChunk(); return true; } catch (EOFException e) { // EOFException means that underlying stream is closed by the server // There will be no more data and it is time to close the circus return false; } catch (IOException e) { throw new ExecutionException(e); } }); isMoreData = readFuture.get(futureTimeout, TimeUnit.MILLISECONDS); } } catch (InterruptedException e) { LOG.log(Level.WARNING, "Nailgun client read future was interrupted", e); } catch (ExecutionException e) { Throwable cause = e.getCause(); if (cause != null && cause instanceof SocketTimeoutException) { LOG.log(Level.WARNING, "Nailgun client socket timed out after " + heartbeatTimeoutMillis + " ms", cause); } else { LOG.log(Level.WARNING, "Nailgun client read future raised an exception", e); } } catch (TimeoutException e) { LOG.log(Level.WARNING, "Nailgun client read future timed out after " + futureTimeout + " ms", e); } finally { LOG.log(Level.FINE, "Nailgun client read shutting down"); // notify stream readers there are no more data setEof(); // set client disconnected flag setClientDisconnected(); // notify listeners that client has disconnected notifyClientListeners(); } }); } /** * Calls clientDisconnected method on all registered NGClientListeners. */ private void notifyClientListeners() { // copy collection under monitor to avoid blocking monitor on potentially expensive // callbacks List listeners; synchronized (this) { listeners = new ArrayList<>(clientListeners); clientListeners.clear(); } for (NGClientListener listener : listeners) { listener.clientDisconnected(); } } /** * Cancel the thread reading from the NailGun client and close underlying input stream */ public void close() throws IOException { setEof(); // this will close `in` and trigger any in.read() calls from readExecutor to unblock super.close(); // the order or termination is important because readExecutor will send a completion // signal to orchestratorExecutor terminateExecutor(readExecutor, "read"); terminateExecutor(orchestratorExecutor, "orchestrator"); } private static void terminateExecutor(ExecutorService service, String which) { LOG.log(Level.FINE, "Shutting down {0} ExecutorService", which); service.shutdown(); boolean terminated = false; try { terminated = service .awaitTermination(TERMINATION_TIMEOUT_MS, TimeUnit.MILLISECONDS); } catch (InterruptedException e) { // It can happen if a thread calling close() is already interrupted // do not do anything here but do hard shutdown later with shutdownNow() // It is calling thread's responsibility to not be in interrupted state LOG.log(Level.WARNING, "Interruption is signaled in close(), terminating a thread forcefully"); service.shutdownNow(); return; } if (!terminated) { // something went wrong, executor task did not receive a signal and did not complete on time // shot executor in the head then LOG.log(Level.WARNING, "{0} thread did not unblock on a signal within timeout and will be" + " forcefully terminated", which); service.shutdownNow(); } } /** * Reads a NailGun chunk payload from {@link #in} and returns an InputStream that reads from * that chunk. * * @param in the InputStream to read the chunk payload from. * @param len the size of the payload chunk read from the chunkHeader. * @return an InputStream containing the read data. * @throws IOException if thrown by the underlying InputStream * @throws EOFException if EOF is reached by underlying stream * before the payload has been read. */ private InputStream readPayload(InputStream in, int len) throws IOException { byte[] receiveBuffer = new byte[len]; int totalRead = 0; while (totalRead < len) { int currentRead = in.read(receiveBuffer, totalRead, len - totalRead); if (currentRead < 0) { // server may forcefully close the socket/stream and this will cause InputStream to // return -1. Throw EOFException (same what DataInputStream does) to signal up // that we are in shutdown mode throw new EOFException("stdin EOF before payload read."); } totalRead += currentRead; } return new ByteArrayInputStream(receiveBuffer); } /** * Reads a NailGun chunk header from the underlying InputStream. * * @throws EOFException if underlying stream / socket is closed which happens on client * disconnection * @throws IOException if thrown by the underlying InputStream, or if an unexpected NailGun * chunk type is encountered. */ private void readChunk() throws IOException { int chunkLen = din.readInt(); byte chunkType = din.readByte(); long readTime = System.currentTimeMillis(); switch (chunkType) { case NGConstants.CHUNKTYPE_STDIN: InputStream chunkStream = readPayload(in, chunkLen); synchronized (this) { if (remaining != 0) { // TODO(buck_team) have better passthru streaming and remove this // limitation throw new IOException("Data received before stdin stream was emptied"); } LOG.log(Level.FINEST, "Got stdin chunk, len {0}", chunkLen); stdin = chunkStream; remaining = chunkLen; notifyAll(); } break; case NGConstants.CHUNKTYPE_STDIN_EOF: LOG.log(Level.FINEST, "Got stdin closed chunk"); setEof(); break; case NGConstants.CHUNKTYPE_HEARTBEAT: LOG.log(Level.FINEST, "Got client heartbeat"); ArrayList listeners; synchronized (this) { // copy collection to avoid executing callbacks under lock listeners = new ArrayList<>(heartbeatListeners); } // TODO(buck_team): should probably dispatch to a different thread(pool) for (NGHeartbeatListener listener : listeners) { listener.heartbeatReceived(); } break; default: LOG.log(Level.WARNING, "Unknown chunk type: {0}", (char) chunkType); throw new IOException("Unknown stream type: " + (char) chunkType); } } /** * Notify threads waiting in read() on either EOF chunk read or client disconnection. */ private synchronized void setEof() { eof = true; notifyAll(); } /** * Notify threads waiting in read() on either EOF chunk read or client disconnection. */ private synchronized void setClientDisconnected() { clientConnected = false; } /** * @see java.io.InputStream#available() */ public synchronized int available() throws IOException { if (eof) { return 0; } if (stdin == null) { return 0; } return stdin.available(); } /** * @see java.io.InputStream#markSupported() */ public boolean markSupported() { return false; } /** * @see java.io.InputStream#read() */ public synchronized int read() throws IOException { if (oneByteBuffer == null) { oneByteBuffer = new byte[1]; } return read(oneByteBuffer, 0, 1) == -1 ? -1 : (int) oneByteBuffer[0]; } /** * @see java.io.InputStream#read(byte[]) */ public synchronized int read(byte[] b) throws IOException { return read(b, 0, b.length); } /** * @see java.io.InputStream#read(byte[], int, int) */ public synchronized int read(byte[] b, int offset, int length) throws IOException { if (!started) { sendSendInput(); started = true; } if (remaining == 0) { if (eof) { return -1; } try { // wait for monitor to signal for either new data packet or eof (termination) wait(); } catch (InterruptedException e) { // this is a signal to stop listening and close // it should never trigger this code path as we always explicitly unblock monitor return -1; } } if (remaining == 0) { // still no data, stream/socket is probably terminated; return -1 return -1; } int bytesToRead = Math.min(remaining, length); int result = stdin.read(b, offset, bytesToRead); remaining -= result; if (remaining == 0) { sendSendInput(); } return result; } private void sendSendInput() throws IOException { // Need to synchronize out because some other thread may write to out too at the same time // hopefully this 'other thread' will synchronize on 'out' as well // also we synchronize on both streams which is a potential deadlock // TODO(buck_team): move acknowledgement packet out of NGInputStream synchronized (out) { out.writeInt(0); out.writeByte(NGConstants.CHUNKTYPE_SENDINPUT); } out.flush(); } /** * @return true if interval since last read is less than heartbeat timeout interval. */ public synchronized boolean isClientConnected() { return clientConnected; } /** * Registers a new NGClientListener to be called on client disconnection or calls the listeners * clientDisconnected method if the client has already disconnected to avoid races. * * @param listener the {@link NGClientListener} to be notified of client events. */ public void addClientListener(NGClientListener listener) { boolean shouldNotifyNow = false; synchronized (this) { if (clientConnected) { clientListeners.add(listener); } else { shouldNotifyNow = true; } } if (shouldNotifyNow) { listener.clientDisconnected(); } } /** * @param listener the {@link NGClientListener} to no longer be notified of client events. */ public synchronized void removeClientListener(NGClientListener listener) { clientListeners.remove(listener); } /** * Do not notify anymore about client disconnects */ public synchronized void removeAllClientListeners() { clientListeners.clear(); } /** * @param listener the {@link NGHeartbeatListener} to be notified of client events. */ public synchronized void addHeartbeatListener(NGHeartbeatListener listener) { heartbeatListeners.add(listener); } /** * @param listener the {@link NGClientListener} to no longer be notified of client events. */ public synchronized void removeHeartbeatListener(NGHeartbeatListener listener) { heartbeatListeners.remove(listener); } } NGListeningAddress.java000066400000000000000000000055371323365546700347020ustar00rootroot00000000000000nailgun-nailgun-all-0.9.3/nailgun-server/src/main/java/com/martiansoftware/nailgun/* Copyright 2004-2015, 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. */ package com.martiansoftware.nailgun; import java.net.InetAddress; /** * Represents the address on which the Nailgun server listens. */ public class NGListeningAddress { private final boolean isInet; private final boolean isLocal; private final InetAddress inetAddress; private final int inetPort; private final String localAddress; /** * Constructs a listening address for an internet address and port. */ public NGListeningAddress(InetAddress inetAddress, int inetPort) { this.isInet = true; this.isLocal = false; this.inetAddress = inetAddress; this.inetPort = inetPort; this.localAddress = null; } /** * Constructs a listening address for a local (Unix domain) address. */ public NGListeningAddress(String localAddress) { this.isInet = false; this.isLocal = true; this.inetAddress = null; this.inetPort = -1; this.localAddress = localAddress; } /** * Returns true if this listening address has an internet address and port. */ public boolean isInetAddress() { return isInet; } /** * Returns true if this listening address has a local (Unix domain) address. */ public boolean isLocalAddress() { return isLocal; } /** * Returns the listening internet address if {@link #isInetAddress()} returns * true. Otherwise, throws. */ public InetAddress getInetAddress() { if (!isInet) { throw new IllegalStateException("Family is not INET"); } return inetAddress; } /** * Returns the listening internet port if {@link #isInetAddress()} returns * true. Otherwise, throws. */ public int getInetPort() { if (!isInet) { throw new IllegalStateException("Family is not INET"); } return inetPort; } /** * Returns the listening local address if {@link #isLocalAddress()} returns * true. Otherwise, throws. */ public String getLocalAddress() { if (!isLocal) { throw new IllegalStateException("Family is not LOCAL"); } return localAddress; } public String toString() { if (isInet) { if (inetAddress != null) { return "address " + inetAddress + " port " + inetPort; } else { return "all addresses, port " + inetPort; } } else { return "local socket " + localAddress; } } } NGOutputStream.java000066400000000000000000000062311323365546700341040ustar00rootroot00000000000000nailgun-nailgun-all-0.9.3/nailgun-server/src/main/java/com/martiansoftware/nailgun/* 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. */ package com.martiansoftware.nailgun; import java.io.IOException; /** * Wraps an OutputStream to send writes in NailGun chunks. Because * multiple NGOutputStreams wrap the same OutputStream (that is, * the OutputStream obtained from the Socket connection with * the client), writes are synchronized on the underlying OutputStream. * If this were not the case, write interleaving could completely * break the NailGun protocol. * * @author Marty Lamb */ class NGOutputStream extends java.io.DataOutputStream { private final Object lock; private byte streamCode; private boolean closed = false; /** * Creates a new NGOutputStream wrapping the specified * OutputStream and using the specified Nailgun chunk code. * @param out the OutputStream to wrap * @param streamCode the NailGun chunk code associated with this * stream (i.e., '1' for stdout, '2' for stderr). */ public NGOutputStream(java.io.OutputStream out, byte streamCode) { super(out); this.lock = out; this.streamCode = streamCode; } /** * @see java.io.OutputStream#write(byte[]) */ public void write(byte[] b) throws IOException { throwIfClosed(); write(b, 0, b.length); } /** * @see java.io.OutputStream#write(int) */ public void write(int b) throws IOException { throwIfClosed(); byte[] b2 = {(byte) b}; write(b2, 0, 1); } /** * @see java.io.OutputStream#write(byte[],int,int) */ public void write(byte[] b, int offset, int len) throws IOException { throwIfClosed(); synchronized(lock) { writeInt(len); writeByte(streamCode); out.write(b, offset, len); } flush(); } /** * @see java.io.OutputStream#close() * * Implement an empty close function, to allow the client to close * the stdout and/or stderr, without this closing the connection * socket to the client. */ public void close() throws IOException { throwIfClosed(); closed = true; // Since we override DataOutputStream.close() but don't // call super.close(), make sure to at least flush the stream // so the last chunk written makes it to the client. super.flush(); } /** * @see java.io.OutputStream#flush() */ public void flush() throws IOException { throwIfClosed(); super.flush(); } /** * Check if stream is closed and throw an IOException if yes. * * In the case of a public operation is being performed while the stream * is already closed throws an IOException. */ private void throwIfClosed() throws IOException { if(closed) { throw new IOException(); } } } NGSecurityManager.java000066400000000000000000000046451323365546700345410ustar00rootroot00000000000000nailgun-nailgun-all-0.9.3/nailgun-server/src/main/java/com/martiansoftware/nailgun/* 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. */ package com.martiansoftware.nailgun; import java.security.Permission; import java.io.PrintStream; /** * Security manager which does nothing other than trap * checkExit, or delegate all non-deprecated methods to * a base manager. * * @author Pete Kirkham * */ public class NGSecurityManager extends SecurityManager { private static final ThreadLocal EXIT = new InheritableThreadLocal(); final SecurityManager base; /** * Construct an NGSecurityManager with the given base. * @param base the base security manager, or null for no base. */ public NGSecurityManager (SecurityManager base) { this.base = base; } public void checkExit (int status) { if (base != null) { base.checkExit(status); } final PrintStream exit = (PrintStream)EXIT.get(); if (exit != null) { exit.println(status); } throw new NGExitException(status); } public void checkPermission(Permission perm) { if (base != null) { base.checkPermission(perm); } } public void checkPermission(Permission perm, Object context) { if (base != null) { base.checkPermission(perm, context); } } public static void setExit (PrintStream exit) { EXIT.set(exit); } /** * Avoid constructing a FilePermission object in checkRead if base manager is null. */ public void checkRead(String file) { if (base != null) { super.checkRead(file); } } }nailgun-nailgun-all-0.9.3/nailgun-server/src/main/java/com/martiansoftware/nailgun/NGServer.java000066400000000000000000000514671323365546700327700ustar00rootroot00000000000000/* 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. */ package com.martiansoftware.nailgun; import java.io.InputStream; import java.io.PrintStream; import java.lang.reflect.Method; import java.net.InetAddress; import java.net.ServerSocket; import java.net.Socket; import java.net.UnknownHostException; import java.util.Iterator; import java.util.Map; import java.util.concurrent.atomic.AtomicBoolean; import com.martiansoftware.nailgun.builtins.DefaultNail; import com.sun.jna.Platform; /** *

Listens for new connections from NailGun clients and launches NGSession * threads to process them.

* *

This class can be run as a standalone server or can be embedded within * larger applications as a means of providing command-line interaction with the * application.

* * @author Marty Lamb */ public class NGServer implements Runnable { /** * Default size for thread pool */ public static final int DEFAULT_SESSIONPOOLSIZE = 10; /** * The address on which to listen */ private final NGListeningAddress listeningAddress; /** * The socket doing the listening */ private ServerSocket serversocket; /** * True if this NGServer has received instructions to shut down */ private final AtomicBoolean shutdown = new AtomicBoolean(false); /** * True if this NGServer has been started and is accepting connections */ private final AtomicBoolean running = new AtomicBoolean(false); /** * This NGServer's AliasManager, which maps aliases to classes */ private final AliasManager aliasManager; /** * If true, fully-qualified classnames are valid commands */ private boolean allowNailsByClassName = true; /** * The default class to use if an invalid alias or classname is specified by * the client. */ private Class defaultNailClass = null; /** * A pool of NGSessions ready to handle client connections */ private final NGSessionPool sessionPool; /** * System.out at the time of the NGServer's creation */ public final PrintStream out = System.out; /** * System.err at the time of the NGServer's creation */ public final PrintStream err = System.err; /** * System.in at the time of the NGServer's creation */ public final InputStream in = System.in; /** * a collection of all classes executed by this server so far */ private final Map allNailStats; /** * Remember the security manager we start with so we can restore it later */ private SecurityManager originalSecurityManager = null; private final int heartbeatTimeoutMillis; /** * Creates a new NGServer that will listen at the specified address and on * the specified port with the specified session pool size. This does * not cause the server to start listening. To do so, create a new * Thread wrapping this * NGServer and start it. * * @param addr the address at which to listen, or * null to bind to all local addresses * @param port the port on which to listen. * @param sessionPoolSize the max number of idle sessions allowed by the * pool */ public NGServer(InetAddress addr, int port, int sessionPoolSize, int timeoutMillis) { this(new NGListeningAddress(addr, port), sessionPoolSize, timeoutMillis); } /** * Creates a new NGServer that will listen at the specified address and on * the specified port with the default session pool size. This does * not cause the server to start listening. To do so, create a new * Thread wrapping this * NGServer and start it. * * @param addr the address at which to listen, or * null to bind to all local addresses * @param port the port on which to listen. */ public NGServer(InetAddress addr, int port) { this(new NGListeningAddress(addr, port), DEFAULT_SESSIONPOOLSIZE, NGConstants.HEARTBEAT_TIMEOUT_MILLIS); } /** * Creates a new NGServer that will listen on the default port (defined in * NGConstants.DEFAULT_PORT). This does not cause the * server to start listening. To do so, create a new * Thread wrapping this * NGServer and start it. */ public NGServer() { this(new NGListeningAddress(null, NGConstants.DEFAULT_PORT), DEFAULT_SESSIONPOOLSIZE, NGConstants.HEARTBEAT_TIMEOUT_MILLIS); } /** * Creates a new NGServer that will listen at the specified address and on * the specified port with the specified session pool size. This does * not cause the server to start listening. To do so, create a new * Thread wrapping this * NGServer and start it. * * @param listeningAddress the address at which to listen * @param sessionPoolSize the max number of idle sessions allowed by the * pool * @param timeoutMillis timeout in millis to wait for a heartbeat from the client * before disconnecting them */ public NGServer(NGListeningAddress listeningAddress, int sessionPoolSize, int timeoutMillis) { this.listeningAddress = listeningAddress; aliasManager = new AliasManager(); allNailStats = new java.util.HashMap(); // allow a maximum of 10 idle threads. probably too high a number // and definitely should be configurable in the future sessionPool = new NGSessionPool(this, sessionPoolSize); heartbeatTimeoutMillis = timeoutMillis; } /** * Sets a flag that determines whether Nails can be executed by class name. * If this is false, Nails can only be run via aliases (and you should * probably remove ng-alias from the AliasManager). * * @param allowNailsByClassName true iff Nail lookups by classname are * allowed */ public void setAllowNailsByClassName(boolean allowNailsByClassName) { this.allowNailsByClassName = allowNailsByClassName; } /** * Returns a flag that indicates whether Nail lookups by classname are * allowed. If this is false, Nails can only be run via aliases. * * @return a flag that indicates whether Nail lookups by classname are * allowed. */ public boolean allowsNailsByClassName() { return (allowNailsByClassName); } /** * Sets the default class to use for the Nail if no Nails can be found via * alias or classname. (may be * null, in which case NailGun will use its own default) * * @param defaultNailClass the default class to use for the Nail if no Nails * can be found via alias or classname. (may be * null, in which case NailGun will use its own default) */ public void setDefaultNailClass(Class defaultNailClass) { this.defaultNailClass = defaultNailClass; } /** * Returns the default class that will be used if no Nails can be found via * alias or classname. * * @return the default class that will be used if no Nails can be found via * alias or classname. */ public Class getDefaultNailClass() { return ((defaultNailClass == null) ? DefaultNail.class : defaultNailClass); } /** * Returns the current NailStats object for the specified class, creating a * new one if necessary * * @param nailClass the class for which we're gathering stats * @return a NailStats object for the specified class */ private NailStats getOrCreateStatsFor(Class nailClass) { NailStats result = null; synchronized (allNailStats) { result = (NailStats) allNailStats.get(nailClass); if (result == null) { result = new NailStats(nailClass); allNailStats.put(nailClass, result); } } return (result); } /** * Provides a means for an NGSession to register the starting of a nail * execution with the server. * * @param nailClass the nail class that was launched */ void nailStarted(Class nailClass) { NailStats stats = getOrCreateStatsFor(nailClass); stats.nailStarted(); } /** * Provides a means for an NGSession to register the completion of a nails * execution with the server. * * @param nailClass the nail class that finished */ void nailFinished(Class nailClass) { NailStats stats; synchronized (allNailStats) { stats = (NailStats) allNailStats.get(nailClass); } stats.nailFinished(); } /** * Returns a snapshot of this NGServer's nail statistics. The result is a * java.util.Map, keyed by class name, with NailStats objects as values. * * @return a snapshot of this NGServer's nail statistics. */ public Map getNailStats() { Map result = new java.util.TreeMap(); synchronized (allNailStats) { for (Iterator i = allNailStats.keySet().iterator(); i.hasNext();) { Class nailclass = (Class) i.next(); result.put(nailclass.getName(), ((NailStats) allNailStats.get(nailclass)).clone()); } } return (result); } /** * Returns the AliasManager in use by this NGServer. * * @return the AliasManager in use by this NGServer. */ public AliasManager getAliasManager() { return (aliasManager); } /** *

Shuts down the server. The server will stop listening and its thread * will finish. Any running nails will be allowed to finish.

* *

Any nails that provide a *

public static void nailShutdown(NGServer)
method * will have this method called with this NGServer as its sole * parameter.

* * @param exitVM if true, this method will also exit the JVM after calling * nailShutdown() on any nails. This may prevent currently running nails * from exiting gracefully, but may be necessary in order to perform some * tasks, such as shutting down any AWT or Swing threads implicitly launched * by your nails. */ public void shutdown(boolean exitVM) { if (shutdown.getAndSet(true)) { return; } try { serversocket.close(); } catch (Throwable toDiscard) { } sessionPool.shutdown(); Class[] argTypes = new Class[1]; argTypes[0] = NGServer.class; Object[] argValues = new Object[1]; argValues[0] = this; // make sure that all aliased classes have associated nailstats // so they can be shut down. for (Iterator i = getAliasManager().getAliases().iterator(); i.hasNext();) { Alias alias = (Alias) i.next(); getOrCreateStatsFor(alias.getAliasedClass()); } synchronized (allNailStats) { for (Iterator i = allNailStats.values().iterator(); i.hasNext();) { NailStats ns = (NailStats) i.next(); Class nailClass = ns.getNailClass(); // yes, I know this is lazy, relying upon the exception // to handle the case of no nailShutdown method. try { Method nailShutdown = nailClass.getMethod("nailShutdown", argTypes); nailShutdown.invoke(null, argValues); } catch (Throwable toDiscard) { } } } // restore system streams System.setIn(in); System.setOut(out); System.setErr(err); System.setSecurityManager(originalSecurityManager); if (exitVM) { System.exit(0); } } /** * Returns true iff the server is currently running. * * @return true iff the server is currently running. */ public boolean isRunning() { return running.get(); } /** * Returns the port on which this server is (or will be) listening. * * @return the port on which this server is (or will be) listening. */ public int getPort() { return ((serversocket == null) ? listeningAddress.getInetPort() : serversocket.getLocalPort()); } /** * Listens for new connections and launches NGSession threads to process * them. */ public void run() { running.set(true); NGSession sessionOnDeck = null; originalSecurityManager = System.getSecurityManager(); System.setSecurityManager( new NGSecurityManager( originalSecurityManager)); synchronized (System.in) { if (!(System.in instanceof ThreadLocalInputStream)) { System.setIn(new ThreadLocalInputStream(in)); System.setOut(new ThreadLocalPrintStream(out)); System.setErr(new ThreadLocalPrintStream(err)); } } try { if (listeningAddress.isInetAddress()) { if (listeningAddress.getInetAddress() == null) { serversocket = new ServerSocket(listeningAddress.getInetPort()); } else { serversocket = new ServerSocket(listeningAddress.getInetPort(), 0, listeningAddress.getInetAddress()); } } else { if (Platform.isWindows()) { boolean requireStrictLength = true; serversocket = new NGWin32NamedPipeServerSocket(listeningAddress.getLocalAddress(), requireStrictLength); } else { serversocket = new NGUnixDomainServerSocket(listeningAddress.getLocalAddress()); } } String portDescription; if (listeningAddress.isInetAddress() && listeningAddress.getInetPort() == 0) { // if the port is 0, it will be automatically determined. // add this little wait so the ServerSocket can fully // initialize and we can see what port it chose. int runningPort = getPort(); while (runningPort == 0) { try { Thread.sleep(50); } catch (Throwable toIgnore) { } runningPort = getPort(); } portDescription = ", port " + runningPort; } else { portDescription = ""; } // Only after this point nailgun server is ready to accept connections on all platforms. // test_ng.py on *nix relies on reading this line from stdout to start connecting to server. out.println("NGServer " + NGConstants.VERSION + " started on " + listeningAddress.toString() + portDescription + "."); while (!shutdown.get()) { sessionOnDeck = sessionPool.take(); Socket socket = serversocket.accept(); sessionOnDeck.run(socket); } } catch (Throwable t) { // if shutdown is called while the accept() method is blocking, // an exception will be thrown that we don't care about. filter // those out. if (!shutdown.get()) { t.printStackTrace(); } } if (sessionOnDeck != null) { sessionOnDeck.shutdown(); } running.set(false); } private static void usage() { System.err.println("Usage: java com.martiansoftware.nailgun.NGServer"); System.err.println(" or: java com.martiansoftware.nailgun.NGServer port"); System.err.println(" or: java com.martiansoftware.nailgun.NGServer IPAddress"); System.err.println(" or: java com.martiansoftware.nailgun.NGServer IPAddress:port"); System.err.println(" or: java com.martiansoftware.nailgun.NGServer IPAddress:port timeout"); } /** * Creates and starts a new * NGServer. A single optional argument is valid, specifying * the port on which this * NGServer should listen. If omitted, * NGServer.DEFAULT_PORT will be used. * * @param args a single optional argument specifying the port on which to * listen. * @throws NumberFormatException if a non-numeric port is specified */ public static void main(String[] args) throws NumberFormatException, UnknownHostException { if (args.length > 2) { usage(); return; } // null server address means bind to everything local NGListeningAddress listeningAddress; int timeoutMillis = NGConstants.HEARTBEAT_TIMEOUT_MILLIS; // parse the command line parameters, which // may be an inetaddress to bind to, a port number, // an inetaddress followed by a port, separated // by a colon, or the string "local:/path/to/socket" // for a Unix domain socket or Windows named pipe. // if a second parameter is provided it // is interpreted as the number of milliseconds to // wait between heartbeats before considering the // client to have disconnected. if (args.length != 0) { String[] argParts = args[0].split(":"); String addrPart = null; String portPart = null; if (argParts.length == 2) { addrPart = argParts[0]; portPart = argParts[1]; } else if (argParts[0].indexOf('.') >= 0) { addrPart = argParts[0]; } else { portPart = argParts[0]; } if ("local".equals(addrPart) && portPart != null) { // Treat the port part as a path to a local Unix domain socket // or Windows named pipe. listeningAddress = new NGListeningAddress(portPart); } else if (addrPart != null && portPart != null) { listeningAddress = new NGListeningAddress( InetAddress.getByName(addrPart), Integer.parseInt(portPart)); } else if (addrPart != null && portPart == null) { listeningAddress = new NGListeningAddress( InetAddress.getByName(addrPart), NGConstants.DEFAULT_PORT); } else { listeningAddress = new NGListeningAddress(null, Integer.parseInt(portPart)); } if (args.length == 2) { timeoutMillis = Integer.parseInt(args[1]); } } else { listeningAddress = new NGListeningAddress(null, NGConstants.DEFAULT_PORT); } NGServer server = new NGServer(listeningAddress, DEFAULT_SESSIONPOOLSIZE, timeoutMillis); Thread t = new Thread(server); t.setName("NGServer(" + listeningAddress.toString() + ")"); t.start(); Runtime.getRuntime().addShutdownHook(new NGServerShutdowner(server)); } public int getHeartbeatTimeout() { return heartbeatTimeoutMillis; } /** * A shutdown hook that will cleanly bring down the NGServer if it is * interrupted. * * @author Marty * Lamb */ private static class NGServerShutdowner extends Thread { private NGServer server = null; NGServerShutdowner(NGServer server) { this.server = server; } public void run() { int count = 0; server.shutdown(false); // give the server up to five seconds to stop. is that enough? // remember that the shutdown will call nailShutdown in any // nails as well while (server.isRunning() && (count < 50)) { try { Thread.sleep(100); } catch (InterruptedException e) { } ++count; } if (server.isRunning()) { System.err.println("Unable to cleanly shutdown server. Exiting JVM Anyway."); } else { System.out.println("NGServer shut down."); } } } } nailgun-nailgun-all-0.9.3/nailgun-server/src/main/java/com/martiansoftware/nailgun/NGSession.java000066400000000000000000000372421323365546700331400ustar00rootroot00000000000000/* 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. */ package com.martiansoftware.nailgun; import java.io.DataInputStream; import java.io.DataOutputStream; import java.io.IOException; import java.io.InputStream; import java.io.PrintStream; import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; import java.net.Socket; import java.net.SocketException; import java.util.List; import java.util.Properties; import java.util.concurrent.atomic.AtomicLong; import java.util.logging.Level; import java.util.logging.Logger; /** * Reads the NailGun stream from the client through the command, then hands off * processing to the appropriate class. The NGSession obtains its sockets from * an NGSessionPool, which created this NGSession. * * @author Marty Lamb */ public class NGSession extends Thread { private static final Logger LOG = Logger.getLogger(NGSession.class.getName()); /** * The server this NGSession is working for */ private final NGServer server; /** * The pool this NGSession came from, and to which it will return itself */ private final NGSessionPool sessionPool; /** * Synchronization object */ private final Object lock = new Object(); /** * The next socket this NGSession has been tasked with processing (by * NGServer) */ private Socket nextSocket = null; /** * True if the server has been shutdown and this NGSession should terminate * completely */ private boolean done = false; /** * The instance number of this NGSession. That is, if this is the Nth * NGSession to be created, then this is the value for N. */ private final long instanceNumber; /** * The interval to wait between heartbeats before considering the client to have disconnected. */ private final int heartbeatTimeoutMillis; /** * The instance counter shared among all NGSessions */ private static AtomicLong instanceCounter = new AtomicLong(0); /** * signature of main(String[]) for reflection operations */ private final static Class[] mainSignature = { String[].class, }; /** * signature of nailMain(NGContext) for reflection operations */ private final static Class[] nailMainSignature = { NGContext.class, }; /** * A ClassLoader that may be set by a client. Defaults to the classloader of this class. */ public static volatile ClassLoader classLoader = null; // initialized in the static initializer - see below static { try { classLoader = NGSession.class.getClassLoader(); } catch (SecurityException e) { throw e; } } /** * Creates a new NGSession running for the specified NGSessionPool and * NGServer. * * @param sessionPool The NGSessionPool we're working for * @param server The NGServer we're working for */ NGSession(NGSessionPool sessionPool, NGServer server) { super(); this.sessionPool = sessionPool; this.server = server; this.heartbeatTimeoutMillis = server.getHeartbeatTimeout(); this.instanceNumber = instanceCounter.incrementAndGet(); } /** * Shuts down this NGSession gracefully */ void shutdown() { synchronized (lock) { done = true; nextSocket = null; lock.notifyAll(); } } /** * Instructs this NGSession to process the specified socket, after which * this NGSession will return itself to the pool from which it came. * * @param socket the socket (connected to a client) to process */ public void run(Socket socket) { synchronized (lock) { nextSocket = socket; lock.notify(); } Thread.yield(); } /** * Returns the next socket to process. This will block the NGSession thread * until there's a socket to process or the NGSession has been shut down. * * @return the next socket to process, or * null if the NGSession has been shut down. */ private Socket nextSocket() { Socket result; synchronized (lock) { result = nextSocket; while (!done && result == null) { try { lock.wait(); } catch (InterruptedException e) { done = true; } result = nextSocket; } nextSocket = null; } if (result != null) { // Java InputStream API is blocking by default with no reliable way to stop pending // read() call. Setting the timeout to underlying socket will make socket's underlying // read() calls throw SocketTimeoutException which unblocks read(). The exception must // be properly handled by calling code. try { result.setSoTimeout(this.heartbeatTimeoutMillis); } catch (SocketException e) { // this exception might be thrown if socket is already closed // so we just return null return null; } } return result; } /** * The main NGSession loop. This gets the next socket to process, runs the * nail for the socket, and loops until shut down. */ public void run() { updateThreadName(null); LOG.log(Level.FINE, "Waiting for first client to connect"); Socket socket = nextSocket(); while (socket != null) { LOG.log(Level.FINE, "Client connected"); try { DataInputStream sockin = new DataInputStream(socket.getInputStream()); DataOutputStream sockout = new DataOutputStream(socket.getOutputStream()); // client info - command line arguments and environment List remoteArgs = new java.util.ArrayList(); Properties remoteEnv = new Properties(); String cwd = null; // working directory String command = null; // alias or class name // read everything from the client up to and including the command while (command == null) { int bytesToRead = sockin.readInt(); byte chunkType = sockin.readByte(); byte[] b = new byte[(int) bytesToRead]; sockin.readFully(b); String line = new String(b, "UTF-8"); switch (chunkType) { case NGConstants.CHUNKTYPE_ARGUMENT: // command line argument remoteArgs.add(line); break; case NGConstants.CHUNKTYPE_ENVIRONMENT: // parse environment into property int equalsIndex = line.indexOf('='); if (equalsIndex > 0) { remoteEnv.setProperty( line.substring(0, equalsIndex), line.substring(equalsIndex + 1)); } String key = line.substring(0, equalsIndex); break; case NGConstants.CHUNKTYPE_COMMAND: // command (alias or classname) command = line; break; case NGConstants.CHUNKTYPE_WORKINGDIRECTORY: // client working directory cwd = line; break; default: // freakout? } } String threadName; if (socket.getInetAddress() != null) { threadName = socket.getInetAddress().getHostAddress() + ": " + command; } else { threadName = command; } updateThreadName(threadName); // can't create NGInputStream until we've received a command, because at // that point the stream from the client will only include stdin and stdin-eof // chunks try ( InputStream in = new NGInputStream(sockin, sockout, heartbeatTimeoutMillis); PrintStream out = new PrintStream(new NGOutputStream(sockout, NGConstants.CHUNKTYPE_STDOUT)); PrintStream err = new PrintStream(new NGOutputStream(sockout, NGConstants.CHUNKTYPE_STDERR)); PrintStream exit = new PrintStream(new NGOutputStream(sockout, NGConstants.CHUNKTYPE_EXIT)); ) { // ThreadLocal streams for System.in/out/err redirection ((ThreadLocalInputStream) System.in).init(in); ((ThreadLocalPrintStream) System.out).init(out); ((ThreadLocalPrintStream) System.err).init(err); try { Alias alias = server.getAliasManager().getAlias(command); Class cmdclass = null; if (alias != null) { cmdclass = alias.getAliasedClass(); } else if (server.allowsNailsByClassName()) { cmdclass = Class.forName(command, true, classLoader); } else { cmdclass = server.getDefaultNailClass(); } Object[] methodArgs = new Object[1]; Method mainMethod = null; // will be either main(String[]) or nailMain(NGContext) String[] cmdlineArgs = (String[]) remoteArgs.toArray(new String[remoteArgs.size()]); boolean isStaticNail = true; // See: NonStaticNail.java Class[] interfaces = cmdclass.getInterfaces(); for (int i = 0; i < interfaces.length; i++){ if (interfaces[i].equals(NonStaticNail.class)){ isStaticNail = false; break; } } if (!isStaticNail){ mainMethod = cmdclass.getMethod("nailMain", new Class[]{ String[].class }); methodArgs[0] = cmdlineArgs; } else { try { mainMethod = cmdclass.getMethod("nailMain", nailMainSignature); NGContext context = new NGContext(); context.setArgs(cmdlineArgs); context.in = in; context.out = out; context.err = err; context.setCommand(command); context.setExitStream(exit); context.setNGServer(server); context.setEnv(remoteEnv); context.setInetAddress(socket.getInetAddress()); context.setPort(socket.getPort()); context.setWorkingDirectory(cwd); methodArgs[0] = context; } catch (NoSuchMethodException toDiscard) { // that's ok - we'll just try main(String[]) next. } if (mainMethod == null) { mainMethod = cmdclass.getMethod("main", mainSignature); methodArgs[0] = cmdlineArgs; } } if (mainMethod != null) { server.nailStarted(cmdclass); NGSecurityManager.setExit(exit); try { if (isStaticNail){ mainMethod.invoke(null, methodArgs); } else { mainMethod.invoke(cmdclass.newInstance(), methodArgs); } } catch (InvocationTargetException ite) { throw (ite.getCause()); } catch (InstantiationException e){ throw (e); } catch (IllegalAccessException e){ throw (e); } catch (Throwable t) { throw (t); } finally { server.nailFinished(cmdclass); } exit.println(0); } } catch (NGExitException exitEx) { LOG.log(Level.INFO, "Server cleanly exited with status " + exitEx.getStatus(), exitEx); exit.println(exitEx.getStatus()); server.out.println(Thread.currentThread().getName() + " exited with status " + exitEx.getStatus()); } catch (Throwable t) { LOG.log(Level.INFO, "Server unexpectedly exited with unhandled exception", t); t.printStackTrace(); exit.println(NGConstants.EXIT_EXCEPTION); // remote exception constant } } finally { LOG.log(Level.FINE, "Flushing client socket"); sockout.flush(); try { socket.shutdownInput(); socket.shutdownOutput(); } catch (IOException e) { LOG.log( Level.FINE, "Error shutting down socket I/O (this is expected if the client disconnected already)", e); } LOG.log(Level.FINE, "Closing client socket"); socket.close(); LOG.log(Level.FINE, "Finished tearing down client socket"); } } catch (Throwable t) { LOG.log(Level.WARNING, "Internal error in session", t); t.printStackTrace(); } ((ThreadLocalInputStream) System.in).init(null); ((ThreadLocalPrintStream) System.out).init(null); ((ThreadLocalPrintStream) System.err).init(null); updateThreadName(null); sessionPool.give(this); LOG.log(Level.FINE, "Waiting for next client to connect"); socket = nextSocket(); } LOG.log(Level.INFO, "NGSession shutting down"); } /** * Updates the current thread name (useful for debugging). */ private void updateThreadName(String detail) { setName("NGSession " + instanceNumber + ": " + ((detail == null) ? "(idle)" : detail)); } } NGSessionPool.java000066400000000000000000000056421323365546700337120ustar00rootroot00000000000000nailgun-nailgun-all-0.9.3/nailgun-server/src/main/java/com/martiansoftware/nailgun/* 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. */ package com.martiansoftware.nailgun; /** * Provides NGSession pooling functionality. One parameter, "maxIdle", * governs its behavior by setting the maximum number of idle NGSession * threads it will allow. It creates a pool of size maxIdle - 1, because * one NGSession is kept "on deck" by the NGServer in order to eke out * a little extra responsiveness. * * @author Marty Lamb */ class NGSessionPool { /** * number of sessions to store in the pool */ final int poolSize; /** * the pool itself */ final NGSession[] pool; /** * The number of sessions currently in the pool */ int poolEntries = 0; /** * reference to server we're working for */ final NGServer server; /** * have we been shut down? */ boolean done = false; /** * synchronization object */ private final Object lock = new Object(); /** * Creates a new NGSessionRunner operating for the specified server, with * the specified number of threads * @param server the server to work for * @param poolsize the maximum number of idle threads to allow */ NGSessionPool(NGServer server, int poolsize) { this.server = server; this.poolSize = Math.min(0, poolsize); pool = new NGSession[poolSize]; poolEntries = 0; } /** * Returns an NGSession from the pool, or creates one if necessary * @return an NGSession ready to work */ NGSession take() { NGSession result; synchronized(lock) { if (poolEntries == 0) { result = new NGSession(this, server); result.start(); } else { --poolEntries; result = pool[poolEntries]; } } return (result); } /** * Returns an NGSession to the pool. The pool may choose to shutdown * the thread if the pool is full * @param session the NGSession to return to the pool */ void give(NGSession session) { boolean shutdown = false; synchronized(lock) { if (done || poolEntries == poolSize) { shutdown = true; } else { pool[poolEntries] = session; ++poolEntries; } } if (shutdown) session.shutdown(); } /** * Shuts down the pool. Running nails are allowed to finish. */ void shutdown() { synchronized(lock) { done = true; while (poolEntries > 0) { take().shutdown(); } } } } NGUnixDomainServerSocket.java000066400000000000000000000133151323365546700360440ustar00rootroot00000000000000nailgun-nailgun-all-0.9.3/nailgun-server/src/main/java/com/martiansoftware/nailgun/* Copyright 2004-2015, 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. */ package com.martiansoftware.nailgun; import java.io.IOException; import java.net.ServerSocket; import java.net.Socket; import java.net.SocketAddress; import java.util.concurrent.atomic.AtomicInteger; import com.sun.jna.LastErrorException; import com.sun.jna.ptr.IntByReference; /** * Implements a {@link ServerSocket} which binds to a local Unix domain socket * and returns instances of {@link NGUnixDomainSocket} from * {@link #accept()}. */ public class NGUnixDomainServerSocket extends ServerSocket { private static final int DEFAULT_BACKLOG = 50; // We use an AtomicInteger to prevent a race in this situation which // could happen if fd were just an int: // // Thread 1 -> NGUnixDomainServerSocket.accept() // -> lock this // -> check isBound and isClosed // -> unlock this // -> descheduled while still in method // Thread 2 -> NGUnixDomainServerSocket.close() // -> lock this // -> check isClosed // -> NGUnixDomainSocketLibrary.close(fd) // -> now fd is invalid // -> unlock this // Thread 1 -> re-scheduled while still in method // -> NGUnixDomainSocketLibrary.accept(fd, which is invalid and maybe re-used) // // By using an AtomicInteger, we'll set this to -1 after it's closed, which // will cause the accept() call above to cleanly fail instead of possibly // being called on an unrelated fd (which may or may not fail). private final AtomicInteger fd; private final int backlog; private boolean isBound; private boolean isClosed; public static class NGUnixDomainServerSocketAddress extends SocketAddress { private final String path; public NGUnixDomainServerSocketAddress(String path) { this.path = path; } public String getPath() { return path; } } /** * Constructs an unbound Unix domain server socket. */ public NGUnixDomainServerSocket() throws IOException { this(DEFAULT_BACKLOG, null); } /** * Constructs an unbound Unix domain server socket with the specified listen backlog. */ public NGUnixDomainServerSocket(int backlog) throws IOException { this(backlog, null); } /** * Constructs and binds a Unix domain server socket to the specified path. */ public NGUnixDomainServerSocket(String path) throws IOException { this(DEFAULT_BACKLOG, path); } /** * Constructs and binds a Unix domain server socket to the specified path * with the specified listen backlog. */ public NGUnixDomainServerSocket(int backlog, String path) throws IOException { try { fd = new AtomicInteger( NGUnixDomainSocketLibrary.socket( NGUnixDomainSocketLibrary.PF_LOCAL, NGUnixDomainSocketLibrary.SOCK_STREAM, 0)); this.backlog = backlog; if (path != null) { bind(new NGUnixDomainServerSocketAddress(path)); } } catch (LastErrorException e) { throw new IOException(e); } } public synchronized void bind(SocketAddress endpoint) throws IOException { if (!(endpoint instanceof NGUnixDomainServerSocketAddress)) { throw new IllegalArgumentException( "endpoint must be an instance of NGUnixDomainServerSocketAddress"); } if (isBound) { throw new IllegalStateException("Socket is already bound"); } if (isClosed) { throw new IllegalStateException("Socket is already closed"); } NGUnixDomainServerSocketAddress unEndpoint = (NGUnixDomainServerSocketAddress) endpoint; NGUnixDomainSocketLibrary.SockaddrUn address = new NGUnixDomainSocketLibrary.SockaddrUn(unEndpoint.getPath()); try { int socketFd = fd.get(); NGUnixDomainSocketLibrary.bind(socketFd, address, address.size()); NGUnixDomainSocketLibrary.listen(socketFd, backlog); isBound = true; } catch (LastErrorException e) { throw new IOException(e); } } public Socket accept() throws IOException { // We explicitly do not make this method synchronized, since the // call to NGUnixDomainSocketLibrary.accept() will block // indefinitely, causing another thread's call to close() to deadlock. synchronized (this) { if (!isBound) { throw new IllegalStateException("Socket is not bound"); } if (isClosed) { throw new IllegalStateException("Socket is already closed"); } } try { NGUnixDomainSocketLibrary.SockaddrUn sockaddrUn = new NGUnixDomainSocketLibrary.SockaddrUn(); IntByReference addressLen = new IntByReference(); addressLen.setValue(sockaddrUn.size()); int clientFd = NGUnixDomainSocketLibrary.accept(fd.get(), sockaddrUn, addressLen); return new NGUnixDomainSocket(clientFd); } catch (LastErrorException e) { throw new IOException(e); } } public synchronized void close() throws IOException { if (isClosed) { throw new IllegalStateException("Socket is already closed"); } try { // Ensure any pending call to accept() fails. NGUnixDomainSocketLibrary.close(fd.getAndSet(-1)); isClosed = true; } catch (LastErrorException e) { throw new IOException(e); } } } NGUnixDomainSocket.java000066400000000000000000000113261323365546700346550ustar00rootroot00000000000000nailgun-nailgun-all-0.9.3/nailgun-server/src/main/java/com/martiansoftware/nailgun/* Copyright 2004-2015, 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. */ package com.martiansoftware.nailgun; import com.sun.jna.LastErrorException; import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; import java.nio.ByteBuffer; import java.net.Socket; /** * Implements a {@link Socket} backed by a native Unix domain socket. * * Instances of this class always return {@code null} for * {@link Socket#getInetAddress()}, {@link Socket#getLocalAddress()}, * {@link Socket#getLocalSocketAddress()}, {@link Socket#getRemoteSocketAddress()}. */ public class NGUnixDomainSocket extends Socket { private final ReferenceCountedFileDescriptor fd; private final InputStream is; private final OutputStream os; /** * Creates a Unix domain socket backed by a native file descriptor. */ public NGUnixDomainSocket(int fd) { this.fd = new ReferenceCountedFileDescriptor(fd); this.is = new NGUnixDomainSocketInputStream(); this.os = new NGUnixDomainSocketOutputStream(); } public InputStream getInputStream() { return is; } public OutputStream getOutputStream() { return os; } public void shutdownInput() throws IOException { doShutdown(NGUnixDomainSocketLibrary.SHUT_RD); } public void shutdownOutput() throws IOException { doShutdown(NGUnixDomainSocketLibrary.SHUT_WR); } private void doShutdown(int how) throws IOException { try { int socketFd = fd.acquire(); if (socketFd != -1) { NGUnixDomainSocketLibrary.shutdown(socketFd, how); } } catch (LastErrorException e) { throw new IOException(e); } finally { fd.release(); } } public void close() throws IOException { super.close(); try { // This might not close the FD right away. In case we are about // to read or write on another thread, it will delay the close // until the read or write completes, to prevent the FD from // being re-used for a different purpose and the other thread // reading from a different FD. fd.close(); } catch (LastErrorException e) { throw new IOException(e); } } private class NGUnixDomainSocketInputStream extends InputStream { public int read() throws IOException { ByteBuffer buf = ByteBuffer.allocate(1); int result; if (doRead(buf) == 0) { result = -1; } else { // Make sure to & with 0xFF to avoid sign extension result = 0xFF & buf.get(); } return result; } public int read(byte[] b, int off, int len) throws IOException { if (len == 0) { return 0; } ByteBuffer buf = ByteBuffer.wrap(b, off, len); int result = doRead(buf); if (result == 0) { result = -1; } return result; } private int doRead(ByteBuffer buf) throws IOException { try { int fdToRead = fd.acquire(); if (fdToRead == -1) { return -1; } return NGUnixDomainSocketLibrary.read(fdToRead, buf, buf.remaining()); } catch (LastErrorException e) { throw new IOException(e); } finally { fd.release(); } } } private class NGUnixDomainSocketOutputStream extends OutputStream { public void write(int b) throws IOException { ByteBuffer buf = ByteBuffer.allocate(1); buf.put(0, (byte) (0xFF & b)); doWrite(buf); } public void write(byte[] b, int off, int len) throws IOException { if (len == 0) { return; } ByteBuffer buf = ByteBuffer.wrap(b, off, len); doWrite(buf); } private void doWrite(ByteBuffer buf) throws IOException { try { int fdToWrite = fd.acquire(); if (fdToWrite == -1) { return; } int ret = NGUnixDomainSocketLibrary.write(fdToWrite, buf, buf.remaining()); if (ret != buf.remaining()) { // This shouldn't happen with standard blocking Unix domain sockets. throw new IOException("Could not write " + buf.remaining() + " bytes as requested " + "(wrote " + ret + " bytes instead)"); } } catch (LastErrorException e) { throw new IOException(e); } finally { fd.release(); } } } } NGUnixDomainSocketLibrary.java000066400000000000000000000111441323365546700362000ustar00rootroot00000000000000nailgun-nailgun-all-0.9.3/nailgun-server/src/main/java/com/martiansoftware/nailgun/* Copyright 2004-2015, 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. */ package com.martiansoftware.nailgun; import com.sun.jna.LastErrorException; import com.sun.jna.Native; import com.sun.jna.Platform; import com.sun.jna.Structure; import com.sun.jna.Union; import com.sun.jna.ptr.IntByReference; import java.io.IOException; import java.nio.ByteBuffer; import java.util.Arrays; import java.util.List; /** * Utility class to bridge native Unix domain socket calls to Java using JNA. */ public class NGUnixDomainSocketLibrary { public static final int PF_LOCAL = 1; public static final int AF_LOCAL = 1; public static final int SOCK_STREAM = 1; public static final int SHUT_RD = 0; public static final int SHUT_WR = 1; // Utility class, do not instantiate. private NGUnixDomainSocketLibrary() { } // BSD platforms write a length byte at the start of struct sockaddr_un. private static final boolean HAS_SUN_LEN = Platform.isMac() || Platform.isFreeBSD() || Platform.isNetBSD() || Platform.isOpenBSD() || Platform.iskFreeBSD(); /** * Bridges {@code struct sockaddr_un} to and from native code. */ public static class SockaddrUn extends Structure implements Structure.ByReference { /** * On BSD platforms, the {@code sun_len} and {@code sun_family} values in * {@code struct sockaddr_un}. */ public static class SunLenAndFamily extends Structure { public byte sunLen; public byte sunFamily; protected List getFieldOrder() { return Arrays.asList(new String[] { "sunLen", "sunFamily" }); } } /** * On BSD platforms, {@code sunLenAndFamily} will be present. * On other platforms, only {@code sunFamily} will be present. */ public static class SunFamily extends Union { public SunLenAndFamily sunLenAndFamily; public short sunFamily; } public SunFamily sunFamily = new SunFamily(); public byte[] sunPath = new byte[104]; /** * Constructs an empty {@code struct sockaddr_un}. */ public SockaddrUn() { if (HAS_SUN_LEN) { sunFamily.sunLenAndFamily = new SunLenAndFamily(); sunFamily.setType(SunLenAndFamily.class); } else { sunFamily.setType(Short.TYPE); } allocateMemory(); } /** * Constructs a {@code struct sockaddr_un} with a path whose bytes are encoded * using the default encoding of the platform. */ public SockaddrUn(String path) throws IOException { byte[] pathBytes = path.getBytes(); if (pathBytes.length > sunPath.length - 1) { throw new IOException("Cannot fit name [" + path + "] in maximum unix domain socket length"); } System.arraycopy(pathBytes, 0, sunPath, 0, pathBytes.length); sunPath[pathBytes.length] = (byte) 0; if (HAS_SUN_LEN) { int len = fieldOffset("sunPath") + pathBytes.length; sunFamily.sunLenAndFamily = new SunLenAndFamily(); sunFamily.sunLenAndFamily.sunLen = (byte) len; sunFamily.sunLenAndFamily.sunFamily = AF_LOCAL; sunFamily.setType(SunLenAndFamily.class); } else { sunFamily.sunFamily = AF_LOCAL; sunFamily.setType(Short.TYPE); } allocateMemory(); } protected List getFieldOrder() { return Arrays.asList(new String[] { "sunFamily", "sunPath" }); } } static { Native.register(Platform.C_LIBRARY_NAME); } public static native int socket(int domain, int type, int protocol) throws LastErrorException; public static native int bind(int fd, SockaddrUn address, int addressLen) throws LastErrorException; public static native int listen(int fd, int backlog) throws LastErrorException; public static native int accept(int fd, SockaddrUn address, IntByReference addressLen) throws LastErrorException; public static native int read(int fd, ByteBuffer buffer, int count) throws LastErrorException; public static native int write(int fd, ByteBuffer buffer, int count) throws LastErrorException; public static native int close(int fd) throws LastErrorException; public static native int shutdown(int fd, int how) throws LastErrorException; } NGWin32NamedPipeLibrary.java000066400000000000000000000051601323365546700354420ustar00rootroot00000000000000nailgun-nailgun-all-0.9.3/nailgun-server/src/main/java/com/martiansoftware/nailgun/* Copyright 2004-2017, 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. */ package com.martiansoftware.nailgun; import java.nio.ByteBuffer; import com.sun.jna.*; import com.sun.jna.platform.win32.WinNT; import com.sun.jna.ptr.IntByReference; import com.sun.jna.win32.W32APIOptions; public interface NGWin32NamedPipeLibrary extends WinNT { int PIPE_ACCESS_DUPLEX = 3; int PIPE_UNLIMITED_INSTANCES = 255; int FILE_FLAG_FIRST_PIPE_INSTANCE = 524288; NGWin32NamedPipeLibrary INSTANCE = (NGWin32NamedPipeLibrary) Native.loadLibrary( "kernel32", NGWin32NamedPipeLibrary.class, W32APIOptions.UNICODE_OPTIONS); HANDLE CreateNamedPipe( String lpName, int dwOpenMode, int dwPipeMode, int nMaxInstances, int nOutBufferSize, int nInBufferSize, int nDefaultTimeOut, SECURITY_ATTRIBUTES lpSecurityAttributes); boolean ConnectNamedPipe( HANDLE hNamedPipe, Pointer lpOverlapped); boolean DisconnectNamedPipe( HANDLE hObject); boolean ReadFile( HANDLE hFile, Memory lpBuffer, int nNumberOfBytesToRead, IntByReference lpNumberOfBytesRead, Pointer lpOverlapped); boolean WriteFile( HANDLE hFile, ByteBuffer lpBuffer, int nNumberOfBytesToWrite, IntByReference lpNumberOfBytesWritten, Pointer lpOverlapped); boolean CloseHandle( HANDLE hObject); boolean GetOverlappedResult( HANDLE hFile, Pointer lpOverlapped, IntByReference lpNumberOfBytesTransferred, boolean wait); boolean CancelIoEx( HANDLE hObject, Pointer lpOverlapped); HANDLE CreateEvent( SECURITY_ATTRIBUTES lpEventAttributes, boolean bManualReset, boolean bInitialState, String lpName); int WaitForSingleObject( HANDLE hHandle, int dwMilliseconds ); int GetLastError(); } NGWin32NamedPipeServerSocket.java000066400000000000000000000176001323365546700364570ustar00rootroot00000000000000nailgun-nailgun-all-0.9.3/nailgun-server/src/main/java/com/martiansoftware/nailgun/* Copyright 2004-2017, 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. */ package com.martiansoftware.nailgun; import com.sun.jna.platform.win32.WinBase; import com.sun.jna.platform.win32.WinError; import com.sun.jna.platform.win32.WinNT; import com.sun.jna.platform.win32.WinNT.HANDLE; import com.sun.jna.ptr.IntByReference; import java.io.IOException; import java.net.ServerSocket; import java.net.Socket; import java.net.SocketAddress; import java.util.ArrayList; import java.util.List; import java.util.concurrent.LinkedBlockingQueue; public class NGWin32NamedPipeServerSocket extends ServerSocket { private static final NGWin32NamedPipeLibrary API = NGWin32NamedPipeLibrary.INSTANCE; private static final String WIN32_PIPE_PREFIX = "\\\\.\\pipe\\"; private static final int BUFFER_SIZE = 65535; private final LinkedBlockingQueue openHandles; private final LinkedBlockingQueue connectedHandles; private final NGWin32NamedPipeSocket.CloseCallback closeCallback; private final String path; private final int maxInstances; private final HANDLE lockHandle; private final boolean requireStrictLength; public NGWin32NamedPipeServerSocket(String path) throws IOException { this(NGWin32NamedPipeLibrary.PIPE_UNLIMITED_INSTANCES, path); } /** * The doc for InputStream#read(byte[] b, int off, int len) states that * "An attempt is made to read as many as len bytes, but a smaller number may be read." * However, using requireStrictLength, NGWin32NamedPipeSocketInputStream can require that * len matches up exactly the number of bytes to read. */ public NGWin32NamedPipeServerSocket(String path, boolean requireStrictLength) throws IOException { this(NGWin32NamedPipeLibrary.PIPE_UNLIMITED_INSTANCES, path, requireStrictLength); } public NGWin32NamedPipeServerSocket(int maxInstances, String path) throws IOException { this(maxInstances, path, NGWin32NamedPipeSocket.DEFAULT_REQUIRE_STRICT_LENGTH); } /** * The doc for InputStream#read(byte[] b, int off, int len) states that * "An attempt is made to read as many as len bytes, but a smaller number may be read." * However, using requireStrictLength, NGWin32NamedPipeSocketInputStream can require that * len matches up exactly the number of bytes to read. */ public NGWin32NamedPipeServerSocket( int maxInstances, String path, boolean requireStrictLength) throws IOException { this.openHandles = new LinkedBlockingQueue<>(); this.connectedHandles = new LinkedBlockingQueue<>(); this.closeCallback = handle -> { if (connectedHandles.remove(handle)) { closeConnectedPipe(handle, false); } if (openHandles.remove(handle)) { closeOpenPipe(handle); } }; this.maxInstances = maxInstances; this.requireStrictLength = requireStrictLength; if (!path.startsWith(WIN32_PIPE_PREFIX)) { this.path = WIN32_PIPE_PREFIX + path; } else { this.path = path; } String lockPath = this.path + "_lock"; lockHandle = API.CreateNamedPipe( lockPath, NGWin32NamedPipeLibrary.FILE_FLAG_FIRST_PIPE_INSTANCE | NGWin32NamedPipeLibrary.PIPE_ACCESS_DUPLEX, 0, 1, BUFFER_SIZE, BUFFER_SIZE, 0, null); if (lockHandle == NGWin32NamedPipeLibrary.INVALID_HANDLE_VALUE) { throw new IOException(String.format("Could not create lock for %s, error %d", lockPath, API.GetLastError())); } else { if (!API.DisconnectNamedPipe(lockHandle)) { throw new IOException(String.format("Could not disconnect lock %d", API.GetLastError())); } } } public void bind(SocketAddress endpoint) throws IOException { throw new IOException("Win32 named pipes do not support bind(), pass path to constructor"); } public Socket accept() throws IOException { HANDLE handle = API.CreateNamedPipe( path, NGWin32NamedPipeLibrary.PIPE_ACCESS_DUPLEX | WinNT.FILE_FLAG_OVERLAPPED, 0, maxInstances, BUFFER_SIZE, BUFFER_SIZE, 0, null); if (handle == NGWin32NamedPipeLibrary.INVALID_HANDLE_VALUE) { throw new IOException(String.format("Could not create named pipe, error %d", API.GetLastError())); } openHandles.add(handle); HANDLE connWaitable = API.CreateEvent(null, true, false, null); WinBase.OVERLAPPED olap = new WinBase.OVERLAPPED(); olap.hEvent = connWaitable; olap.write(); boolean immediate = API.ConnectNamedPipe(handle, olap.getPointer()); if (immediate) { openHandles.remove(handle); connectedHandles.add(handle); return new NGWin32NamedPipeSocket(handle, closeCallback, requireStrictLength); } int connectError = API.GetLastError(); if (connectError == WinError.ERROR_PIPE_CONNECTED) { openHandles.remove(handle); connectedHandles.add(handle); return new NGWin32NamedPipeSocket(handle, closeCallback, requireStrictLength); } else if (connectError == WinError.ERROR_NO_DATA) { // Client has connected and disconnected between CreateNamedPipe() and ConnectNamedPipe() // connection is broken, but it is returned it avoid loop here. // Actual error will happen for NGSession when it will try to read/write from/to pipe return new NGWin32NamedPipeSocket(handle, closeCallback, requireStrictLength); } else if (connectError == WinError.ERROR_IO_PENDING) { if (!API.GetOverlappedResult(handle, olap.getPointer(), new IntByReference(), true)) { openHandles.remove(handle); closeOpenPipe(handle); throw new IOException("GetOverlappedResult() failed for connect operation: " + API.GetLastError()); } openHandles.remove(handle); connectedHandles.add(handle); return new NGWin32NamedPipeSocket(handle, closeCallback, requireStrictLength); } else { throw new IOException("ConnectNamedPipe() failed with: " + connectError); } } public void close() throws IOException { try { List handlesToClose = new ArrayList<>(); openHandles.drainTo(handlesToClose); for (HANDLE handle : handlesToClose) { closeOpenPipe(handle); } List handlesToDisconnect = new ArrayList<>(); connectedHandles.drainTo(handlesToDisconnect); for (HANDLE handle : handlesToDisconnect) { closeConnectedPipe(handle, true); } } finally { API.CloseHandle(lockHandle); } } private void closeOpenPipe(HANDLE handle) throws IOException { API.CancelIoEx(handle, null); API.CloseHandle(handle); } private void closeConnectedPipe(HANDLE handle, boolean shutdown) throws IOException { if (!shutdown) { API.WaitForSingleObject(handle, 10000); } API.DisconnectNamedPipe(handle); API.CloseHandle(handle); } } NGWin32NamedPipeSocket.java000066400000000000000000000150321323365546700352650ustar00rootroot00000000000000nailgun-nailgun-all-0.9.3/nailgun-server/src/main/java/com/martiansoftware/nailgun/* Copyright 2004-2017, 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. */ package com.martiansoftware.nailgun; import com.sun.jna.Memory; import com.sun.jna.platform.win32.WinBase; import com.sun.jna.platform.win32.WinError; import com.sun.jna.platform.win32.WinNT.HANDLE; import com.sun.jna.ptr.IntByReference; import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; import java.net.Socket; import java.nio.ByteBuffer; public class NGWin32NamedPipeSocket extends Socket { private static final NGWin32NamedPipeLibrary API = NGWin32NamedPipeLibrary.INSTANCE; static final boolean DEFAULT_REQUIRE_STRICT_LENGTH = false; private final HANDLE handle; private final CloseCallback closeCallback; private final boolean requireStrictLength; private final InputStream is; private final OutputStream os; private final HANDLE readerWaitable; private final HANDLE writerWaitable; interface CloseCallback { void onNamedPipeSocketClose(HANDLE handle) throws IOException; } /** * The doc for InputStream#read(byte[] b, int off, int len) states that * "An attempt is made to read as many as len bytes, but a smaller number may be read." * However, using requireStrictLength, NGWin32NamedPipeSocketInputStream can require that * len matches up exactly the number of bytes to read. */ public NGWin32NamedPipeSocket( HANDLE handle, CloseCallback closeCallback, boolean requireStrictLength) throws IOException { this.handle = handle; this.closeCallback = closeCallback; this.requireStrictLength = requireStrictLength; this.readerWaitable = API.CreateEvent(null, true, false, null); if (readerWaitable == null) { throw new IOException("CreateEvent() failed "); } writerWaitable = API.CreateEvent(null, true, false, null); if (writerWaitable == null) { throw new IOException("CreateEvent() failed "); } this.is = new NGWin32NamedPipeSocketInputStream(handle); this.os = new NGWin32NamedPipeSocketOutputStream(handle); } public NGWin32NamedPipeSocket( HANDLE handle, CloseCallback closeCallback) throws IOException { this(handle, closeCallback, DEFAULT_REQUIRE_STRICT_LENGTH); } @Override public InputStream getInputStream() { return is; } @Override public OutputStream getOutputStream() { return os; } @Override public void close() throws IOException { closeCallback.onNamedPipeSocketClose(handle); } @Override public void shutdownInput() throws IOException { } @Override public void shutdownOutput() throws IOException { } private class NGWin32NamedPipeSocketInputStream extends InputStream { private final HANDLE handle; NGWin32NamedPipeSocketInputStream(HANDLE handle) { this.handle = handle; } @Override public int read() throws IOException { int result; byte[] b = new byte[1]; if (read(b) == 0) { result = -1; } else { result = 0xFF & b[0]; } return result; } @Override public int read(byte[] b, int off, int len) throws IOException { Memory readBuffer = new Memory(len); WinBase.OVERLAPPED olap = new WinBase.OVERLAPPED(); olap.hEvent = readerWaitable; olap.write(); boolean immediate = API.ReadFile(handle, readBuffer, len, null, olap.getPointer()); if (!immediate) { int lastError = API.GetLastError(); if (lastError != WinError.ERROR_IO_PENDING) { throw new IOException("ReadFile() failed: " + lastError); } } IntByReference r = new IntByReference(); if (!API.GetOverlappedResult(handle, olap.getPointer(), r, true)) { int lastError = API.GetLastError(); throw new IOException("GetOverlappedResult() failed for read operation: " + lastError); } int actualLen = r.getValue(); if (requireStrictLength && (actualLen != len)) { throw new IOException("ReadFile() read less bytes than requested: expected " + len + " bytes, but read " + actualLen + " bytes"); } byte[] byteArray = readBuffer.getByteArray(0, actualLen); System.arraycopy(byteArray, 0, b, off, actualLen); return actualLen; } } private class NGWin32NamedPipeSocketOutputStream extends OutputStream { private final HANDLE handle; NGWin32NamedPipeSocketOutputStream(HANDLE handle) { this.handle = handle; } @Override public void write(int b) throws IOException { write(new byte[]{(byte) (0xFF & b)}); } @Override public void write(byte[] b, int off, int len) throws IOException { ByteBuffer data = ByteBuffer.wrap(b, off, len); WinBase.OVERLAPPED olap = new WinBase.OVERLAPPED(); olap.hEvent = writerWaitable; olap.write(); boolean immediate = API.WriteFile(handle, data, len, null, olap.getPointer()); if (!immediate) { int lastError = API.GetLastError(); if (lastError != WinError.ERROR_IO_PENDING) { throw new IOException("WriteFile() failed: " + lastError); } } IntByReference written = new IntByReference(); if (!API.GetOverlappedResult(handle, olap.getPointer(), written, true)) { int lastError = API.GetLastError(); throw new IOException("GetOverlappedResult() failed for write operation: " + lastError); } if (written.getValue() != len) { throw new IOException("WriteFile() wrote less bytes than requested"); } } } } nailgun-nailgun-all-0.9.3/nailgun-server/src/main/java/com/martiansoftware/nailgun/NailStats.java000066400000000000000000000063311323365546700331650ustar00rootroot00000000000000/* 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. */ package com.martiansoftware.nailgun; /** *

Collects and provides statistics on a nail.

* * @author Marty Lamb */ public class NailStats implements Cloneable { private final Class nailclass; private long runCounter; private long refCounter; private final Object lock; /** * Creates a new NailStats object for the specified class * @param nailclass the class for which we'll collect statistics */ NailStats(Class nailclass) { this.nailclass = nailclass; runCounter = 0; refCounter = 0; lock = new Object(); } /** * Logs the fact that an instance of this nail has started */ void nailStarted() { synchronized(lock) { ++runCounter; ++refCounter; } } /** * Logs the fact that an instance of this nail has finished */ void nailFinished() { synchronized(lock) { --refCounter; } } /** * Returns the number of times this nail has been run. Nails * that have started but not yet finished are included in this * number. * @return the number of times this nail has been run. */ public long getRunCount() { synchronized (lock) { return (runCounter); } } /** * Returns the number of sessions currently running this nail. * @return the number of sessions currently running this nail. */ public long getRefCount() { synchronized (lock) { return (refCounter); } } /** * Returns the class for which we're tracking statistics * @return the class for which we're tracking statistics */ public Class getNailClass() { return (nailclass); } /** * @see java.lang.Object#hashCode */ public int hashCode() { return (nailclass.hashCode()); } /** * Returns true iff the specified NailStats object * is tracking the same class. * @param o the NailStats object to check * @return true iff the specified NailStats object * is tracking the same class. */ public boolean equals(Object o) { NailStats other = (NailStats) o; return (nailclass.equals(other.nailclass)); } /** * Creates a copy of this NailStats object. * @return a copy of this NailStats object. */ public Object clone() { Object result = null; try { result = super.clone(); } catch (CloneNotSupportedException toDiscard) {} return (result); } /** * Returns a String representation of this NailStats * object, in the form "classname: runcount/refcount". * *return a String representation of this NailStats * object. */ public String toString() { return (nailclass.getName() + ": " + getRunCount() + "/" + getRefCount()); } } NonStaticNail.java000066400000000000000000000005221323365546700337060ustar00rootroot00000000000000nailgun-nailgun-all-0.9.3/nailgun-server/src/main/java/com/martiansoftware/nailgunpackage com.martiansoftware.nailgun; /** Allows providing a instance (non-static) main method. * Potentially helpful for users of JVM languages other than Java. * * Implementations of this interface MUST provide a public, no-args constructor. */ public interface NonStaticNail { public void nailMain(String[] args); }ReferenceCountedFileDescriptor.java000066400000000000000000000037441323365546700372700ustar00rootroot00000000000000nailgun-nailgun-all-0.9.3/nailgun-server/src/main/java/com/martiansoftware/nailgun/* Copyright 2004-2015, 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. */ package com.martiansoftware.nailgun; import com.sun.jna.LastErrorException; import java.io.IOException; /** * Encapsulates a file descriptor plus a reference count to ensure close requests * only close the file descriptor once the last reference to the file descriptor * is released. * * If not explicitly closed, the file descriptor will be closed when * this object is finalized. */ public class ReferenceCountedFileDescriptor { private int fd; private int fdRefCount; private boolean closePending; public ReferenceCountedFileDescriptor(int fd) { this.fd = fd; this.fdRefCount = 0; this.closePending = false; } protected void finalize() throws IOException { close(); } public synchronized int acquire() { fdRefCount++; return fd; } public synchronized void release() throws IOException { fdRefCount--; if (fdRefCount == 0 && closePending && fd != -1) { doClose(); } } public synchronized void close() throws IOException { if (fd == -1 || closePending) { return; } if (fdRefCount == 0) { doClose(); } else { // Another thread has the FD. We'll close it when they release the reference. closePending = true; } } private void doClose() throws IOException { try { NGUnixDomainSocketLibrary.close(fd); fd = -1; } catch (LastErrorException e) { throw new IOException(e); } } } ThreadLocalInputStream.java000066400000000000000000000077641323365546700355750ustar00rootroot00000000000000nailgun-nailgun-all-0.9.3/nailgun-server/src/main/java/com/martiansoftware/nailgun/* 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. */ package com.martiansoftware.nailgun; import java.io.IOException; import java.io.InputStream; /** * The class name is pretty descriptive. This creates an InputStream * much like a FilterInputStream, but with the wrapped InputStream * being local to the current Thread. By setting System.in to a * ThreadLocalInputStream, different Threads can read from different * InputStreams simply by using System.in. Of course, the init() * method must be called by the Thread that wishes to use the * wrapped stream. * * @author Marty Lamb */ class ThreadLocalInputStream extends InputStream { /** * The InputStreams for the various threads */ private InheritableThreadLocal streams = null; private InputStream defaultInputStream = null; /** * @param defaultInputStream the InputStream that will be used if the * current thread has not called init() */ ThreadLocalInputStream(InputStream defaultInputStream) { super(); streams = new InheritableThreadLocal(); this.defaultInputStream = defaultInputStream; init(null); } /** * Sets the InputStream for the current thread * @param streamForCurrentThread the InputStream for the current thread */ void init(InputStream streamForCurrentThread) { streams.set(streamForCurrentThread); } /** * Returns this thread's InputStream * @return this thread's InputStream */ InputStream getInputStream() { InputStream result = (InputStream) streams.get(); return ((result == null) ? defaultInputStream : result); } // BEGIN delegated java.io.InputStream methods /** * @see java.io.InputStream#available() */ public int available() throws IOException { return (getInputStream().available()); } /** * @see java.io.InputStream#close() */ public void close() throws IOException { getInputStream().close(); } /** * @see java.io.InputStream#mark(int) */ public void mark(int readlimit) { getInputStream().mark(readlimit); } /** * @see java.io.InputStream#markSupported() */ public boolean markSupported() { return (getInputStream().markSupported()); } /** * @see java.io.InputStream#read() */ public int read() throws IOException { return (getInputStream().read()); } /** * @see java.io.InputStream#read(byte[]) */ public int read(byte[] b) throws IOException { return (getInputStream().read(b)); } /** * @see java.io.InputStream#read(byte[],int,int) */ public int read(byte[] b, int off, int len) throws IOException { return (getInputStream().read(b, off, len)); } /** * @see java.io.InputStream#reset() */ public void reset() throws IOException { getInputStream().reset(); } /** * @see java.io.InputStream#skip(long) */ public long skip(long n) throws IOException { return (getInputStream().skip(n)); } // BEGIN delegated java.io.InputStream methods // Note: Should java.lang.Object methods be delegated? If not, and // someone synchronizes on this stream, processes might be blocked // that shouldn't be. It would certainly be stupid to delegate // finalize(). Not so clear are hashcode(), equals(), notify(), and // the wait() methods. } ThreadLocalPrintStream.java000066400000000000000000000143401323365546700355560ustar00rootroot00000000000000nailgun-nailgun-all-0.9.3/nailgun-server/src/main/java/com/martiansoftware/nailgun/* 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. */ package com.martiansoftware.nailgun; import java.io.IOException; import java.io.PrintStream; /** * The class name is pretty descriptive. This creates a PrintStream * much like a FilterOutputStream, but with the wrapped PrintStream * being local to the current Thread. By setting System.out to a * ThreadLocalPrintStream, different Threads can write to different * PrintStreams simply by using System.out. Of course, the init() * method must be called by the Thread that wishes to use the * wrapped stream. * * @author Marty Lamb */ class ThreadLocalPrintStream extends PrintStream { /** * The PrintStreams for the various threads */ private InheritableThreadLocal streams = null; private PrintStream defaultPrintStream = null; /** * Creates a new InheritedThreadLocalPrintStream * @param defaultPrintStream the PrintStream that will be used if the * current thread has not called init() */ public ThreadLocalPrintStream(PrintStream defaultPrintStream) { super(defaultPrintStream); streams = new InheritableThreadLocal(); this.defaultPrintStream = defaultPrintStream; init(null); } /** * Sets the PrintStream for the current thread * @param streamForCurrentThread the PrintStream for the current thread */ void init(PrintStream streamForCurrentThread) { streams.set(streamForCurrentThread); } /** * Returns this thread's PrintStream * @return this thread's PrintStream */ PrintStream getPrintStream() { PrintStream result = (PrintStream) streams.get(); return ((result == null) ? defaultPrintStream : result); } // BEGIN delegated java.io.PrintStream methods /** * @see java.io.PrintStream#checkError() */ public boolean checkError() { return (getPrintStream().checkError()); } /** * @see java.io.PrintStream#close() */ public void close() { getPrintStream().close(); } /** * @see java.io.PrintStream#flush() */ public void flush() { getPrintStream().flush(); } /** * @see java.io.PrintStream#print(boolean) */ public void print(boolean b) { getPrintStream().print(b); } /** * @see java.io.PrintStream#print(char) */ public void print(char c) { getPrintStream().print(c); } /** * @see java.io.PrintStream#print(char[]) */ public void print(char[] s) { getPrintStream().print(s); } /** * @see java.io.PrintStream#print(double) */ public void print(double d) { getPrintStream().print(d); } /** * @see java.io.PrintStream#print(float) */ public void print(float f) { getPrintStream().print(f); } /** * @see java.io.PrintStream#print(int) */ public void print(int i) { getPrintStream().print(i); } /** * @see java.io.PrintStream#print(long) */ public void print(long l) { getPrintStream().print(l); } /** * @see java.io.PrintStream#print(Object) */ public void print(Object obj) { getPrintStream().print(obj); } /** * @see java.io.PrintStream#print(String) */ public void print(String s) { getPrintStream().print(s); } /** * @see java.io.PrintStream#println() */ public void println() { getPrintStream().println(); } /** * @see java.io.PrintStream#println(boolean) */ public void println(boolean x) { getPrintStream().println(x); } /** * @see java.io.PrintStream#println(char) */ public void println(char x) { getPrintStream().println(x); } /** * @see java.io.PrintStream#println(char[]) */ public void println(char[] x) { getPrintStream().println(x); } /** * @see java.io.PrintStream#println(double) */ public void println(double x) { getPrintStream().println(x); } /** * @see java.io.PrintStream#println(float) */ public void println(float x) { getPrintStream().println(x); } /** * @see java.io.PrintStream#println(int) */ public void println(int x) { getPrintStream().println(x); } /** * @see java.io.PrintStream#println(long) */ public void println(long x) { getPrintStream().println(x); } /** * @see java.io.PrintStream#println(Object) */ public void println(Object x) { getPrintStream().println(x); } /** * @see java.io.PrintStream#println(String) */ public void println(String x) { getPrintStream().println(x); } /** * @see java.io.PrintStream#write(byte[],int,int) */ public void write(byte[] buf, int off, int len) { getPrintStream().write(buf, off, len); } /** * @see java.io.PrintStream#write(int) */ public void write(int b) { getPrintStream().write(b); } // END delegated java.io.PrintStream methods // BEGIN delegated java.io.FilterOutputStream methods /** * @see java.io.FilterOutputStream#write(byte[]) */ public void write(byte[] b) throws IOException { getPrintStream().write(b); } // END delegated java.io.FilterOutputStream methods // Note: Should java.lang.Object methods be delegated? If not, and // someone synchronizes on this stream, processes might be blocked // that shouldn't be. It would certainly be stupid to delegate // finalize(). Not so clear are hashcode(), equals(), notify(), and // the wait() methods. } nailgun-nailgun-all-0.9.3/nailgun-server/src/main/java/com/martiansoftware/nailgun/builtins/000077500000000000000000000000001323365546700322465ustar00rootroot00000000000000DefaultNail.java000066400000000000000000000023511323365546700352230ustar00rootroot00000000000000nailgun-nailgun-all-0.9.3/nailgun-server/src/main/java/com/martiansoftware/nailgun/builtins/* 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. */ package com.martiansoftware.nailgun.builtins; import com.martiansoftware.nailgun.NGContext; import com.martiansoftware.nailgun.NGConstants; /** * The default nail class used by the server when an invalid command (nonexisting * classname or alias) is issued. This simply displays an error message to the * client's stdout and has the client exit with value NGConstants.EXIT_NOSUCHCOMMAND. * * @author Marty Lamb */ public class DefaultNail { public static void nailMain(NGContext context) { context.err.println("No such command: " + context.getCommand()); context.exit(NGConstants.EXIT_NOSUCHCOMMAND); } } NGAlias.java000066400000000000000000000054031323365546700343120ustar00rootroot00000000000000nailgun-nailgun-all-0.9.3/nailgun-server/src/main/java/com/martiansoftware/nailgun/builtins/* 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. */ package com.martiansoftware.nailgun.builtins; import java.util.Iterator; import java.util.Set; import com.martiansoftware.nailgun.Alias; import com.martiansoftware.nailgun.NGContext; import com.martiansoftware.nailgun.NGServer; /** *

Provides a means to view and add aliases. This is aliased by default * to the command "ng-alias".

* *

No command line validation is performed. If you trigger an exception, * your client will display it.

* *

To view the current alias list, issue the command: *

ng-alias
* with no arguments.

* *

To add or replace an alias, issue the command: *

ng-alias [alias name] [fully qualified aliased class name]
*

* * @author Marty Lamb */ public class NGAlias { private static String padl(String s, int len) { StringBuffer buf = new StringBuffer(s); while(buf.length() < len) buf.append(" "); return (buf.toString()); } public static void nailMain(NGContext context) throws ClassNotFoundException { String[] args = context.getArgs(); NGServer server = context.getNGServer(); if (args.length == 0) { Set aliases = server.getAliasManager().getAliases(); // let's pad this nicely. first, find the longest alias // name. then pad the others to that width. int maxAliasLength = 0; int maxClassnameLength = 0; for (Iterator i = aliases.iterator(); i.hasNext();) { Alias alias = (Alias) i.next(); maxAliasLength = Math.max(maxAliasLength, alias.getName().length()); maxClassnameLength = Math.max(maxClassnameLength, alias.getAliasedClass().getName().length()); } for (Iterator i = aliases.iterator(); i.hasNext();) { Alias alias = (Alias) i.next(); context.out.println(padl(alias.getName(), maxAliasLength) + "\t" + padl(alias.getAliasedClass().getName(), maxClassnameLength)); context.out.println(padl("", maxAliasLength) + "\t" + alias.getDescription()); context.out.println(); } } else if (args.length == 2) { server.getAliasManager().addAlias(new Alias(args[0], "", Class.forName(args[1]))); } } } NGClasspath.java000066400000000000000000000054431323365546700352070ustar00rootroot00000000000000nailgun-nailgun-all-0.9.3/nailgun-server/src/main/java/com/martiansoftware/nailgun/builtins/* 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. */ package com.martiansoftware.nailgun.builtins; import java.io.File; import java.net.URL; import java.net.URLClassLoader; import com.martiansoftware.nailgun.NGContext; /** *

Provides a means to display and add to the system classpath at runtime. * If called with no arguments, the classpath is displayed. Otherwise, each * argument is turned into a java.io.File and added to the classpath. Relative * paths will be resolved relative to the directory in which the nailgun server * is running. This is very likely to change in the future.

* *

This is aliased by default to the command "ng-cp".

* * @author Marty Lamb */ public class NGClasspath { /** * Adds the specified URL (for a jar or a directory) to the System * ClassLoader. This code was written by antony_miguel and posted on * http://forum.java.sun.com/thread.jsp?forum=32&thread=300557&message=1191210 * I assume it has been placed in the public domain. * * @param url the URL of the resource (directory or jar) to add to the * System classpath * @throws Exception if anything goes wrong. The most likely culprit, should * this ever arise, would be that your VM is not using a URLClassLoader as the * System ClassLoader. This would result in a ClassClastException that you * probably can't do much about. */ private static void addToSystemClassLoader(URL url) throws Exception { URLClassLoader sysloader = (URLClassLoader) ClassLoader.getSystemClassLoader(); Class sysclass = URLClassLoader.class; java.lang.reflect.Method method = sysclass.getDeclaredMethod("addURL", new Class[] {URL.class}); method.setAccessible(true); method.invoke(sysloader, new Object[]{url}); } public static void nailMain(NGContext context) throws Exception { String[] args = context.getArgs(); if (args.length == 0) { URLClassLoader sysLoader = (URLClassLoader) ClassLoader.getSystemClassLoader(); URL[] urls = sysLoader.getURLs(); for (int i = 0; i < urls.length; ++i) { context.out.println(urls[i]); } } else { for (int i = 0; i < args.length; ++i) { File file = new File(args[i]); addToSystemClassLoader(file.toURL()); } } } } NGServerStats.java000066400000000000000000000032031323365546700355420ustar00rootroot00000000000000nailgun-nailgun-all-0.9.3/nailgun-server/src/main/java/com/martiansoftware/nailgun/builtins/* 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. */ package com.martiansoftware.nailgun.builtins; import java.util.Iterator; import java.util.Map; import com.martiansoftware.nailgun.NGServer; import com.martiansoftware.nailgun.NGContext; /** *

Displays all NailStats tracked by the server.

* *

This can be run standalone with no arguments. It will also run automatically * upon NGServer shutdown, sending its output to the server's System.out.

* *

This is aliased by default to the command "ng-stats".

* @author Marty Lamb */ public class NGServerStats { public static void nailShutdown(NGServer server) { dumpStats(server, server.out); } public static void nailMain(NGContext context) { dumpStats(context.getNGServer(), context.out); } private static void dumpStats(NGServer server, java.io.PrintStream out) { Map stats = server.getNailStats(); for (Iterator i = stats.values().iterator(); i.hasNext();) { out.println(i.next()); } } } NGStop.java000066400000000000000000000020131323365546700342000ustar00rootroot00000000000000nailgun-nailgun-all-0.9.3/nailgun-server/src/main/java/com/martiansoftware/nailgun/builtins/* 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. */ package com.martiansoftware.nailgun.builtins; import com.martiansoftware.nailgun.NGContext; /** *

Shuts down the currently running server.

* *

This is aliased by default to the command "ng-stop".

* * @author Marty Lamb */ public class NGStop { public static void nailMain(NGContext context) { context.getNGServer().shutdown(true); } } NGVersion.java000066400000000000000000000006751323365546700347140ustar00rootroot00000000000000nailgun-nailgun-all-0.9.3/nailgun-server/src/main/java/com/martiansoftware/nailgun/builtinspackage com.martiansoftware.nailgun.builtins; import com.martiansoftware.nailgun.NGConstants; import com.martiansoftware.nailgun.NGContext; /** * Displays the version of the NailGun server and exits. * * @author Marty Lamb */ public class NGVersion { public static void nailMain(NGContext context) { context.out.println("NailGun server version " + NGConstants.VERSION); } } nailgun-version.properties000066400000000000000000000001321323365546700355700ustar00rootroot00000000000000nailgun-nailgun-all-0.9.3/nailgun-server/src/main/java/com/martiansoftware/nailgun#Automatically generated by Ant build script. #Fri May 22 21:04:25 EDT 2009 version=0.9.0 nailgun-nailgun-all-0.9.3/nailgun-server/src/main/resources/000077500000000000000000000000001323365546700240655ustar00rootroot00000000000000nailgun-nailgun-all-0.9.3/nailgun-server/src/main/resources/com/000077500000000000000000000000001323365546700246435ustar00rootroot00000000000000nailgun-nailgun-all-0.9.3/nailgun-server/src/main/resources/com/martiansoftware/000077500000000000000000000000001323365546700300515ustar00rootroot00000000000000nailgun-nailgun-all-0.9.3/nailgun-server/src/main/resources/com/martiansoftware/nailgun/000077500000000000000000000000001323365546700315065ustar00rootroot00000000000000nailgun-nailgun-all-0.9.3/nailgun-server/src/main/resources/com/martiansoftware/nailgun/builtins/000077500000000000000000000000001323365546700333375ustar00rootroot00000000000000builtins.properties000066400000000000000000000010211323365546700372210ustar00rootroot00000000000000nailgun-nailgun-all-0.9.3/nailgun-server/src/main/resources/com/martiansoftware/nailgun/builtinsng-alias=com.martiansoftware.nailgun.builtins.NGAlias ng-alias.desc=Displays and manages command aliases ng-cp=com.martiansoftware.nailgun.builtins.NGClasspath ng-cp.desc=Displays and manages the current system classpath ng-stop=com.martiansoftware.nailgun.builtins.NGStop ng-stop.desc=Shuts down the nailgun server ng-stats=com.martiansoftware.nailgun.builtins.NGServerStats ng-stats.desc=Displays nail statistics ng-version=com.martiansoftware.nailgun.builtins.NGVersion ng-version.desc=Displays the server version number. nailgun-nailgun-all-0.9.3/pom.xml000066400000000000000000000117051323365546700167200ustar00rootroot00000000000000 4.0.0 org.sonatype.oss oss-parent 7 com.martiansoftware nailgun-all 0.9.3-SNAPSHOT pom UTF-8 -Xdoclint:none nailgun-all Nailgun is a client, protocol, and server for running Java programs from the command line without incurring the JVM startup overhead. Programs run in the server (which is implemented in Java), and are triggered by the client (written in C), which handles all I/O. This project contains the server and examples. http://martiansoftware.com/nailgun The Apache Software License, Version 2.0 http://www.apache.org/licenses/LICENSE-2.0.txt repo scm:git:git@github.com:martylamb/nailgun.git scm:git:git@github.com:martylamb/nailgun.git scm:git:git@github.com:martylamb/nailgun.git nailgun-all-0.9.3 mlamb@martiansoftware.com Marty Lamb http://martiansoftware.com nailgun-server nailgun-examples org.apache.maven.plugins maven-compiler-plugin 3.0 1.8 1.8 org.apache.maven.plugins maven-source-plugin 2.2.1 attach-sources jar org.apache.maven.plugins maven-javadoc-plugin 2.9 attach-javadocs jar org.apache.maven.plugins maven-release-plugin 2.3.2 -Dgpg.passphrase=${gpg.passphrase} sonatype-nexus-snapshots Sonatype Nexus snapshot repository https://oss.sonatype.org/content/repositories/snapshots sonatype-nexus-staging Sonatype Nexus release repository https://oss.sonatype.org/service/local/staging/deploy/maven2/ release-sign-artifacts performRelease true org.apache.maven.plugins maven-gpg-plugin 1.4 ${gpg.passphrase} sign-artifacts verify sign nailgun-nailgun-all-0.9.3/pynailgun/000077500000000000000000000000001323365546700174055ustar00rootroot00000000000000nailgun-nailgun-all-0.9.3/pynailgun/__init__.py000066400000000000000000000000631323365546700215150ustar00rootroot00000000000000from ng import NailgunConnection, NailgunException nailgun-nailgun-all-0.9.3/pynailgun/ng.py000066400000000000000000000724641323365546700204000ustar00rootroot00000000000000#!/usr/bin/env python # # Copyright 2004-2015, 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. import ctypes import platform import optparse import os import os.path import Queue import select import socket import struct import sys from threading import Condition, Event, Thread, RLock # @author Marty Lamb # @author Pete Kirkham (Win32 port) # @author Sergey Balabanov, Ben Hamilton (Python port) # # Please try to keep this working on Python 2.6. NAILGUN_VERSION = '0.9.3' BUFSIZE = 2048 NAILGUN_PORT_DEFAULT = 2113 CHUNK_HEADER_LEN = 5 THREAD_TERMINATION_TIMEOUT_SEC = 0.5 STDIN_BUFFER_LINE_SIZE = 10 CHUNKTYPE_STDIN = '0' CHUNKTYPE_STDOUT = '1' CHUNKTYPE_STDERR = '2' CHUNKTYPE_STDIN_EOF = '.' CHUNKTYPE_ARG = 'A' CHUNKTYPE_LONGARG = 'L' CHUNKTYPE_ENV = 'E' CHUNKTYPE_DIR = 'D' CHUNKTYPE_CMD = 'C' CHUNKTYPE_EXIT = 'X' CHUNKTYPE_SENDINPUT = 'S' CHUNKTYPE_HEARTBEAT = 'H' NSEC_PER_SEC = 1000000000 # 500 ms heartbeat timeout HEARTBEAT_TIMEOUT_NANOS = NSEC_PER_SEC / 2 HEARTBEAT_TIMEOUT_SECS = HEARTBEAT_TIMEOUT_NANOS / (NSEC_PER_SEC * 1.0) # We need to support Python 2.6 hosts which lack memoryview(). import __builtin__ HAS_MEMORYVIEW = 'memoryview' in dir(__builtin__) EVENT_STDIN_CHUNK = 0 EVENT_STDIN_CLOSED = 1 EVENT_STDIN_EXCEPTION = 2 class NailgunException(Exception): SOCKET_FAILED = 231 CONNECT_FAILED = 230 UNEXPECTED_CHUNKTYPE = 229 CONNECTION_BROKEN = 227 def __init__(self, message, code): self.message = message self.code = code def __str__(self): return self.message class Transport(object): def close(self): raise NotImplementedError() def sendall(self, data): raise NotImplementedError() def recv(self, size): raise NotImplementedError() def recv_into(self, buffer, size=None): raise NotImplementedError() def select(self, timeout_secs): raise NotImplementedError() class UnixTransport(Transport): def __init__(self, __socket): self.__socket = __socket self.recv_flags = 0 self.send_flags = 0 if hasattr(socket, 'MSG_WAITALL'): self.recv_flags |= socket.MSG_WAITALL if hasattr(socket, 'MSG_NOSIGNAL'): self.send_flags |= socket.MSG_NOSIGNAL def close(self): return self.__socket.close() def sendall(self, data): result = self.__socket.sendall(data, self.send_flags) return result def recv(self, nbytes): return self.__socket.recv(nbytes, self.recv_flags) def recv_into(self, buffer, nbytes=None): return self.__socket.recv_into(buffer, nbytes, self.recv_flags) def select(self, timeout_secs): select_list = [self.__socket] readable, _, exceptional = select.select( select_list, [], select_list, timeout_secs) return (self.__socket in readable), (self.__socket in exceptional) if os.name == 'nt': import ctypes.wintypes wintypes = ctypes.wintypes GENERIC_READ = 0x80000000 GENERIC_WRITE = 0x40000000 FILE_FLAG_OVERLAPPED = 0x40000000 OPEN_EXISTING = 3 INVALID_HANDLE_VALUE = ctypes.c_void_p(-1).value FORMAT_MESSAGE_FROM_SYSTEM = 0x00001000 FORMAT_MESSAGE_ALLOCATE_BUFFER = 0x00000100 FORMAT_MESSAGE_IGNORE_INSERTS = 0x00000200 WAIT_FAILED = 0xFFFFFFFF WAIT_TIMEOUT = 0x00000102 WAIT_OBJECT_0 = 0x00000000 WAIT_IO_COMPLETION = 0x000000C0 INFINITE = 0xFFFFFFFF # Overlapped I/O operation is in progress. (997) ERROR_IO_PENDING = 0x000003E5 ERROR_PIPE_BUSY = 231 # The pointer size follows the architecture # We use WPARAM since this type is already conditionally defined ULONG_PTR = ctypes.wintypes.WPARAM class OVERLAPPED(ctypes.Structure): _fields_ = [ ("Internal", ULONG_PTR), ("InternalHigh", ULONG_PTR), ("Offset", wintypes.DWORD), ("OffsetHigh", wintypes.DWORD), ("hEvent", wintypes.HANDLE) ] LPDWORD = ctypes.POINTER(wintypes.DWORD) CreateFile = ctypes.windll.kernel32.CreateFileW CreateFile.argtypes = [wintypes.LPCWSTR, wintypes.DWORD, wintypes.DWORD, wintypes.LPVOID, wintypes.DWORD, wintypes.DWORD, wintypes.HANDLE] CreateFile.restype = wintypes.HANDLE CloseHandle = ctypes.windll.kernel32.CloseHandle CloseHandle.argtypes = [wintypes.HANDLE] CloseHandle.restype = wintypes.BOOL ReadFile = ctypes.windll.kernel32.ReadFile ReadFile.argtypes = [wintypes.HANDLE, wintypes.LPVOID, wintypes.DWORD, LPDWORD, ctypes.POINTER(OVERLAPPED)] ReadFile.restype = wintypes.BOOL WriteFile = ctypes.windll.kernel32.WriteFile WriteFile.argtypes = [wintypes.HANDLE, wintypes.LPVOID, wintypes.DWORD, LPDWORD, ctypes.POINTER(OVERLAPPED)] WriteFile.restype = wintypes.BOOL GetLastError = ctypes.windll.kernel32.GetLastError GetLastError.argtypes = [] GetLastError.restype = wintypes.DWORD SetLastError = ctypes.windll.kernel32.SetLastError SetLastError.argtypes = [wintypes.DWORD] SetLastError.restype = None FormatMessage = ctypes.windll.kernel32.FormatMessageW FormatMessage.argtypes = [wintypes.DWORD, wintypes.LPVOID, wintypes.DWORD, wintypes.DWORD, ctypes.POINTER(wintypes.LPCWSTR), wintypes.DWORD, wintypes.LPVOID] FormatMessage.restype = wintypes.DWORD LocalFree = ctypes.windll.kernel32.LocalFree GetOverlappedResult = ctypes.windll.kernel32.GetOverlappedResult GetOverlappedResult.argtypes = [wintypes.HANDLE, ctypes.POINTER(OVERLAPPED), LPDWORD, wintypes.BOOL] GetOverlappedResult.restype = wintypes.BOOL CreateEvent = ctypes.windll.kernel32.CreateEventW CreateEvent.argtypes = [LPDWORD, wintypes.BOOL, wintypes.BOOL, wintypes.LPCWSTR] CreateEvent.restype = wintypes.HANDLE PeekNamedPipe = ctypes.windll.kernel32.PeekNamedPipe PeekNamedPipe.argtypes = [ wintypes.HANDLE, wintypes.LPVOID, wintypes.DWORD, LPDWORD, LPDWORD, LPDWORD, ] PeekNamedPipe.restype = wintypes.BOOL WaitNamedPipe = ctypes.windll.kernel32.WaitNamedPipeW WaitNamedPipe.argtypes = [ wintypes.LPCWSTR, wintypes.DWORD, ] WaitNamedPipe.restype = wintypes.BOOL def _win32_strerror(err): """ expand a win32 error code into a human readable message """ # FormatMessage will allocate memory and assign it here buf = ctypes.c_wchar_p() FormatMessage( FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_IGNORE_INSERTS, None, err, 0, buf, 0, None) try: return buf.value finally: LocalFree(buf) class WindowsNamedPipeTransport(Transport): """ connect to a named pipe """ def __init__(self, sockpath): self.sockpath = ur'\\.\pipe\{0}'.format(sockpath) while True: self.pipe = CreateFile(self.sockpath, GENERIC_READ | GENERIC_WRITE, 0, None, OPEN_EXISTING, FILE_FLAG_OVERLAPPED, None) err1 = GetLastError() msg = _win32_strerror(err1) if self.pipe != INVALID_HANDLE_VALUE: break if err1 != ERROR_PIPE_BUSY: self.pipe = None raise NailgunException( msg, NailgunException.CONNECT_FAILED) if not WaitNamedPipe(self.sockpath, 5000): self.pipe = None raise NailgunException( "time out while waiting for a pipe", NailgunException.CONNECT_FAILED) # event for the overlapped I/O operations self.read_waitable = CreateEvent(None, True, False, None) if self.read_waitable is None: raise NailgunException( 'CreateEvent failed', NailgunException.CONNECT_FAILED) self.write_waitable = CreateEvent(None, True, False, None) if self.write_waitable is None: raise NailgunException( 'CreateEvent failed', NailgunException.CONNECT_FAILED) def _raise_win_err(self, msg, err): raise IOError('%s win32 error code: %d %s' % (msg, err, _win32_strerror(err))) def close(self): if self.pipe: CloseHandle(self.pipe) self.pipe = None if self.read_waitable is not None: CloseHandle(self.read_waitable) self.read_waitable = None if self.write_waitable is not None: CloseHandle(self.write_waitable) self.write_waitable = None def recv_into(self, buffer, nbytes): # we don't use memoryview because OVERLAPPED I/O happens # after the method (ReadFile) returns buf = ctypes.create_string_buffer(nbytes) olap = OVERLAPPED() olap.hEvent = self.read_waitable immediate = ReadFile(self.pipe, buf, nbytes, None, olap) if not immediate: err = GetLastError() if err != ERROR_IO_PENDING: self._raise_win_err('failed to read %d bytes' % nbytes, GetLastError()) nread = wintypes.DWORD() if not GetOverlappedResult(self.pipe, olap, nread, True): err = GetLastError() self._raise_win_err('error while waiting for read', err) nread = nread.value buffer[:nread] = buf[:nread] return nread def sendall(self, data): olap = OVERLAPPED() olap.hEvent = self.write_waitable p = (ctypes.c_ubyte*len(data))(*(bytearray(data))) immediate = WriteFile(self.pipe, p, len(data), None, olap) if not immediate: err = GetLastError() if err != ERROR_IO_PENDING: self._raise_win_err('failed to write %d bytes' % len(data), GetLastError()) # Obtain results, waiting if needed nwrote = wintypes.DWORD() if not GetOverlappedResult(self.pipe, olap, nwrote, True): err = GetLastError() self._raise_win_err('error while waiting for write', err) nwrote = nwrote.value if nwrote != len(data): raise IOError('Async wrote less bytes!') return nwrote def select(self, timeout_secs): start = monotonic_time_nanos() timeout_nanos = timeout_secs * NSEC_PER_SEC while True: readable, exceptional = self.select_now() if readable or exceptional or monotonic_time_nanos() - start > timeout_nanos: return readable, exceptional def select_now(self): available_total = wintypes.DWORD() exceptional = not PeekNamedPipe(self.pipe, None, 0, None, available_total, None) readable = available_total.value > 0 result = readable, exceptional return result class NailgunConnection(object): """Stateful object holding the connection to the Nailgun server.""" def __init__( self, server_name, server_port=None, stdin=sys.stdin, stdout=sys.stdout, stderr=sys.stderr, cwd=None): self.transport = make_nailgun_transport(server_name, server_port, cwd) self.stdin = stdin self.stdout = stdout self.stderr = stderr self.recv_flags = 0 self.send_flags = 0 self.header_buf = ctypes.create_string_buffer(CHUNK_HEADER_LEN) self.buf = ctypes.create_string_buffer(BUFSIZE) self.sendtime_nanos = 0 self.exit_code = None self.shutdown_event = Event() self.error_lock = RLock() self.error = None self.stdin_condition = Condition() self.stdin_thread = Thread(target=stdin_thread_main, args=(self,)) self.stdin_thread.daemon = True self.send_queue = Queue.Queue() self.send_condition = Condition() self.send_thread = Thread(target=send_thread_main, args=(self,)) self.send_thread.daemon = True def send_command( self, cmd, cmd_args=[], filearg=None, env=os.environ, cwd=os.getcwd()): """ Sends the command and environment to the nailgun server, then loops forever reading the response until the server sends an exit chunk. Returns the exit value, or raises NailgunException on error. """ try: return self._send_command_and_read_response(cmd, cmd_args, filearg, env, cwd) except socket.error as e: raise NailgunException( 'Server disconnected unexpectedly: {0}'.format(e), NailgunException.CONNECTION_BROKEN) def _send_command_and_read_response(self, cmd, cmd_args, filearg, env, cwd): self.stdin_thread.start() self.send_thread.start() try: if filearg: self._send_file_arg(filearg) for cmd_arg in cmd_args: self._send_chunk(cmd_arg, CHUNKTYPE_ARG) self._send_env_var('NAILGUN_FILESEPARATOR', os.sep) self._send_env_var('NAILGUN_PATHSEPARATOR', os.pathsep) self._send_tty_format(self.stdin) self._send_tty_format(self.stdout) self._send_tty_format(self.stderr) for k, v in env.iteritems(): self._send_env_var(k, v) self._send_chunk(cwd, CHUNKTYPE_DIR) self._send_chunk(cmd, CHUNKTYPE_CMD) while self.exit_code is None: self._process_next_chunk() finally: self.shutdown_event.set() with self.stdin_condition: self.stdin_condition.notify() with self.send_condition: self.send_condition.notify() self.stdin_thread.join(THREAD_TERMINATION_TIMEOUT_SEC) self.send_thread.join(THREAD_TERMINATION_TIMEOUT_SEC) return self.exit_code def _process_next_chunk(self): """ Processes the next chunk from the nailgun server. """ readable, exceptional = self.transport.select(HEARTBEAT_TIMEOUT_SECS) if readable: self._process_nailgun_stream() now = monotonic_time_nanos() if now - self.sendtime_nanos > HEARTBEAT_TIMEOUT_NANOS: self._send_heartbeat() if exceptional: raise NailgunException( 'Server disconnected in select', NailgunException.CONNECTION_BROKEN) # if daemon thread threw, rethrow here if self.shutdown_event.is_set(): e = None with self.error_lock: e = self.error if e is not None: raise e def _send_chunk(self, buf, chunk_type): """ Send chunk to the server asynchronously """ self.send_queue.put((chunk_type, buf)) with self.send_condition: self.send_condition.notify() def _send_env_var(self, name, value): """ Sends an environment variable in KEY=VALUE format. """ self._send_chunk('='.join((name, value)), CHUNKTYPE_ENV) def _send_tty_format(self, f): """ Sends a NAILGUN_TTY_# environment variable. """ if not f or not hasattr(f, 'fileno'): return fileno = f.fileno() isatty = os.isatty(fileno) self._send_env_var('NAILGUN_TTY_' + str(fileno), str(int(isatty))) def _send_file_arg(self, filename): """ Sends the contents of a file to the server. """ with open(filename) as f: while True: num_bytes = f.readinto(self.buf) if not num_bytes: break self._send_chunk(self.buf.raw[:num_bytes], CHUNKTYPE_LONGARG) def _recv_to_fd(self, dest_file, num_bytes): """ Receives num_bytes bytes from the nailgun socket and copies them to the specified file object. Used to route data to stdout or stderr on the client. """ bytes_read = 0 while bytes_read < num_bytes: bytes_to_read = min(len(self.buf), num_bytes - bytes_read) bytes_received = self.transport.recv_into( self.buf, bytes_to_read) if dest_file: dest_file.write(self.buf[:bytes_received]) bytes_read += bytes_received def _recv_to_buffer(self, num_bytes, buf): """ Receives num_bytes from the nailgun socket and writes them into the specified buffer. """ # We'd love to use socket.recv_into() everywhere to avoid # unnecessary copies, but we need to support Python 2.6. The # only way to provide an offset to recv_into() is to use # memoryview(), which doesn't exist until Python 2.7. if HAS_MEMORYVIEW: self._recv_into_memoryview(num_bytes, memoryview(buf)) else: self._recv_to_buffer_with_copy(num_bytes, buf) def _recv_into_memoryview(self, num_bytes, buf_view): """ Receives num_bytes from the nailgun socket and writes them into the specified memoryview to avoid an extra copy. """ bytes_read = 0 while bytes_read < num_bytes: bytes_received = self.transport.recv_into( buf_view[bytes_read:], num_bytes - bytes_read) if not bytes_received: raise NailgunException( 'Server unexpectedly disconnected in recv_into()', NailgunException.CONNECTION_BROKEN) bytes_read += bytes_received def _recv_to_buffer_with_copy(self, num_bytes, buf): """ Receives num_bytes from the nailgun socket and writes them into the specified buffer. """ bytes_read = 0 while bytes_read < num_bytes: recv_buf = self.transport.recv( num_bytes - bytes_read) if not len(recv_buf): raise NailgunException( 'Server unexpectedly disconnected in recv()', NailgunException.CONNECTION_BROKEN) buf[bytes_read:bytes_read + len(recv_buf)] = recv_buf bytes_read += len(recv_buf) def _process_exit(self, exit_len): """ Receives an exit code from the nailgun server and sets nailgun_connection.exit_code to indicate the client should exit. """ num_bytes = min(len(self.buf), exit_len) self._recv_to_buffer(num_bytes, self.buf) self.exit_code = int(''.join(self.buf.raw[:num_bytes])) def _send_heartbeat(self): """ Sends a heartbeat to the nailgun server to indicate the client is still alive. """ self._send_chunk('', CHUNKTYPE_HEARTBEAT) def _process_nailgun_stream(self): """ Processes a single chunk from the nailgun server. """ self._recv_to_buffer(len(self.header_buf), self.header_buf) (chunk_len, chunk_type) = struct.unpack_from('>ic', self.header_buf.raw) if chunk_type == CHUNKTYPE_STDOUT: self._recv_to_fd(self.stdout, chunk_len) elif chunk_type == CHUNKTYPE_STDERR: self._recv_to_fd(self.stderr, chunk_len) elif chunk_type == CHUNKTYPE_EXIT: self._process_exit(chunk_len) elif chunk_type == CHUNKTYPE_SENDINPUT: # signal stdin thread to get and send more data with self.stdin_condition: self.stdin_condition.notify() else: raise NailgunException( 'Unexpected chunk type: {0}'.format(chunk_type), NailgunException.UNEXPECTED_CHUNKTYPE) def __enter__(self): return self def __exit__(self, type, value, traceback): try: self.transport.close() except socket.error: pass def monotonic_time_nanos(): """Returns a monotonically-increasing timestamp value in nanoseconds. The epoch of the return value is undefined. To use this, you must call it more than once and calculate the delta between two calls. """ # This function should be overwritten below on supported platforms. raise Exception('Unsupported platform: ' + platform.system()) if platform.system() == 'Linux': # From , available since 2.6.28 (released 24-Dec-2008). CLOCK_MONOTONIC_RAW = 4 librt = ctypes.CDLL('librt.so.1', use_errno=True) clock_gettime = librt.clock_gettime class struct_timespec(ctypes.Structure): _fields_ = [('tv_sec', ctypes.c_long), ('tv_nsec', ctypes.c_long)] clock_gettime.argtypes = [ctypes.c_int, ctypes.POINTER(struct_timespec)] def _monotonic_time_nanos_linux(): t = struct_timespec() clock_gettime(CLOCK_MONOTONIC_RAW, ctypes.byref(t)) return t.tv_sec * NSEC_PER_SEC + t.tv_nsec monotonic_time_nanos = _monotonic_time_nanos_linux elif platform.system() == 'Darwin': # From KERN_SUCCESS = 0 libSystem = ctypes.CDLL('/usr/lib/libSystem.dylib', use_errno=True) mach_timebase_info = libSystem.mach_timebase_info class struct_mach_timebase_info(ctypes.Structure): _fields_ = [('numer', ctypes.c_uint32), ('denom', ctypes.c_uint32)] mach_timebase_info.argtypes = [ctypes.POINTER(struct_mach_timebase_info)] mach_ti = struct_mach_timebase_info() ret = mach_timebase_info(ctypes.byref(mach_ti)) if ret != KERN_SUCCESS: raise Exception('Could not get mach_timebase_info, error: ' + str(ret)) mach_absolute_time = libSystem.mach_absolute_time mach_absolute_time.restype = ctypes.c_uint64 def _monotonic_time_nanos_darwin(): return (mach_absolute_time() * mach_ti.numer) / mach_ti.denom monotonic_time_nanos = _monotonic_time_nanos_darwin elif platform.system() == 'Windows': # From perf_frequency = ctypes.c_uint64() ctypes.windll.kernel32.QueryPerformanceFrequency(ctypes.byref(perf_frequency)) def _monotonic_time_nanos_windows(): perf_counter = ctypes.c_uint64() ctypes.windll.kernel32.QueryPerformanceCounter(ctypes.byref(perf_counter)) return perf_counter.value * NSEC_PER_SEC / perf_frequency.value monotonic_time_nanos = _monotonic_time_nanos_windows elif sys.platform == 'cygwin': k32 = ctypes.CDLL('Kernel32', use_errno=True) perf_frequency = ctypes.c_uint64() k32.QueryPerformanceFrequency(ctypes.byref(perf_frequency)) def _monotonic_time_nanos_cygwin(): perf_counter = ctypes.c_uint64() k32.QueryPerformanceCounter(ctypes.byref(perf_counter)) return perf_counter.value * NSEC_PER_SEC / perf_frequency.value monotonic_time_nanos = _monotonic_time_nanos_cygwin def send_thread_main(conn): """ Sending thread worker function Waits for data and transmits it to server """ try: header_buf = ctypes.create_string_buffer(CHUNK_HEADER_LEN) while not conn.shutdown_event.is_set(): while not conn.send_queue.empty(): # only this thread can deplete the queue, so it is safe to use blocking get() (chunk_type, buf) = conn.send_queue.get() struct.pack_into('>ic', header_buf, 0, len(buf), chunk_type) conn.sendtime_nanos = monotonic_time_nanos() conn.transport.sendall(header_buf.raw) conn.transport.sendall(buf) with conn.send_condition: conn.send_condition.wait() except Exception as e: # save exception to rethrow on main thread with conn.error_lock: conn.error = e conn.shutdown_event.set() def stdin_thread_main(conn): """ Stdin thread reading worker function If stdin is available, read it to internal buffer and send to server """ try: eof = False while True: # wait for signal to read new line from stdin or shutdown # we do not start reading from stdin before server actually requests that with conn.stdin_condition: conn.stdin_condition.wait() if conn.shutdown_event.is_set(): return if not conn.stdin or eof: conn._send_chunk(buf, CHUNKTYPE_STDIN_EOF) continue buf = conn.stdin.readline() if buf == '': eof = True conn._send_chunk(buf, CHUNKTYPE_STDIN_EOF) continue conn._send_chunk(buf, CHUNKTYPE_STDIN) except Exception as e: # save exception to rethrow on main thread with conn.error_lock: conn.error = e conn.shutdown_event.set() def make_nailgun_transport(nailgun_server, nailgun_port=None, cwd=None): """ Creates and returns a socket connection to the nailgun server. """ transport = None if nailgun_server.startswith('local:'): if platform.system() == 'Windows': pipe_addr = nailgun_server[6:] transport = WindowsNamedPipeTransport(pipe_addr) else: try: s = socket.socket(socket.AF_UNIX) except socket.error as msg: raise NailgunException( 'Could not create local socket connection to server: {0}'.format(msg), NailgunException.SOCKET_FAILED) socket_addr = nailgun_server[6:] prev_cwd = os.getcwd() try: if cwd is not None: os.chdir(cwd) s.connect(socket_addr) transport = UnixTransport(s) except socket.error as msg: raise NailgunException( 'Could not connect to local server at {0}: {1}'.format(socket_addr, msg), NailgunException.CONNECT_FAILED) finally: if cwd is not None: os.chdir(prev_cwd) else: socket_addr = nailgun_server socket_family = socket.AF_UNSPEC for (af, socktype, proto, _, sa) in socket.getaddrinfo( nailgun_server, nailgun_port, socket.AF_UNSPEC, socket.SOCK_STREAM): try: s = socket.socket(af, socktype, proto) except socket.error as msg: s = None continue try: s.connect(sa) transport = UnixTransport(s) except socket.error as msg: s.close() s = None continue break if transport is None: raise NailgunException( 'Could not connect to server {0}:{1}'.format(nailgun_server, nailgun_port), NailgunException.CONNECT_FAILED) return transport def main(): """ Main entry point to the nailgun client. """ default_nailgun_server = os.environ.get('NAILGUN_SERVER', '127.0.0.1') default_nailgun_port = int(os.environ.get('NAILGUN_PORT', NAILGUN_PORT_DEFAULT)) parser = optparse.OptionParser(usage='%prog [options] cmd arg1 arg2 ...') parser.add_option('--nailgun-server', default=default_nailgun_server) parser.add_option('--nailgun-port', type='int', default=default_nailgun_port) parser.add_option('--nailgun-filearg') parser.add_option('--nailgun-showversion', action='store_true') parser.add_option('--nailgun-help', action='help') (options, args) = parser.parse_args() if options.nailgun_showversion: print 'NailGun client version ' + NAILGUN_VERSION if len(args): cmd = args.pop(0) else: cmd = os.path.basename(sys.argv[0]) # Pass any remaining command line arguments to the server. cmd_args = args try: with NailgunConnection( options.nailgun_server, server_port=options.nailgun_port) as c: exit_code = c.send_command(cmd, cmd_args, options.nailgun_filearg) sys.exit(exit_code) except NailgunException as e: print >>sys.stderr, str(e) sys.exit(e.code) except KeyboardInterrupt as e: pass if __name__ == '__main__': main() nailgun-nailgun-all-0.9.3/pynailgun/test_ng.py000066400000000000000000000152141323365546700214250ustar00rootroot00000000000000import subprocess import os import time import StringIO import unittest import tempfile import shutil import uuid import sys from pynailgun import NailgunException, NailgunConnection if os.name == 'posix': def transport_exists(transport_file): return os.path.exists(transport_file) if os.name == 'nt': import ctypes from ctypes.wintypes import WIN32_FIND_DATAW as WIN32_FIND_DATA INVALID_HANDLE_VALUE = -1 FindFirstFile = ctypes.windll.kernel32.FindFirstFileW FindClose = ctypes.windll.kernel32.FindClose # on windows os.path.exists doen't allow to check reliably that a pipe exists # (os.path.exists tries to open connection to a pipe) def transport_exists(transport_path): wfd = WIN32_FIND_DATA() handle = FindFirstFile(transport_path, ctypes.byref(wfd)) result = handle != INVALID_HANDLE_VALUE FindClose(handle) return result class TestNailgunConnection(unittest.TestCase): def setUp(self): self.setUpTransport() self.startNailgun() def setUpTransport(self): self.tmpdir = tempfile.mkdtemp() if os.name == 'posix': self.transport_file = os.path.join(self.tmpdir, 'sock') self.transport_address = 'local:{0}'.format(self.transport_file) else: pipe_name = u'nailgun-test-{0}'.format(uuid.uuid4().hex) self.transport_address = u'local:{0}'.format(pipe_name) self.transport_file = ur'\\.\pipe\{0}'.format(pipe_name) def getClassPath(self): cp = [ 'nailgun-server/target/nailgun-server-0.9.3-SNAPSHOT-uber.jar', 'nailgun-examples/target/nailgun-examples-0.9.3-SNAPSHOT.jar', ] if os.name == 'nt': return ';'.join(cp) return ':'.join(cp) def startNailgun(self): if os.name == 'posix': def preexec_fn(): # Close any open file descriptors to further separate buckd from its # invoking context (e.g. otherwise we'd hang when running things like # `ssh localhost buck clean`). dev_null_fd = os.open("/dev/null", os.O_RDWR) os.dup2(dev_null_fd, 0) os.dup2(dev_null_fd, 2) os.close(dev_null_fd) creationflags = 0 else: preexec_fn = None # https://msdn.microsoft.com/en-us/library/windows/desktop/ms684863.aspx#DETACHED_PROCESS DETACHED_PROCESS = 0x00000008 creationflags = DETACHED_PROCESS stdout = None if os.name == 'posix': stdout=subprocess.PIPE cmd = ['java', '-Djna.nosys=true', '-classpath', self.getClassPath()] if os.environ.get('DEBUG_MODE') == '1': cmd.append('-agentlib:jdwp=transport=dt_socket,address=localhost:8888,server=y,suspend=y') cmd = cmd + ['com.martiansoftware.nailgun.NGServer', self.transport_address] self.ng_server_process = subprocess.Popen( cmd, preexec_fn=preexec_fn, creationflags=creationflags, stdout=stdout, ) self.assertIsNone(self.ng_server_process.poll()) if os.name == 'posix': # on *nix we have to wait for server to be ready to accept connections while True: the_first_line = self.ng_server_process.stdout.readline().strip() if "NGServer" in the_first_line and "started" in the_first_line: break if the_first_line is None or the_first_line == '': break else: for _ in range(0, 600): # on windows it is OK to rely on existence of the pipe file if not transport_exists(self.transport_file): time.sleep(0.01) else: break self.assertTrue(transport_exists(self.transport_file)) def test_nailgun_stats(self): output = StringIO.StringIO() with NailgunConnection( self.transport_address, stderr=None, stdin=None, stdout=output) as c: exit_code = c.send_command('ng-stats') self.assertEqual(exit_code, 0) actual_out = output.getvalue().strip() expected_out = 'com.martiansoftware.nailgun.builtins.NGServerStats: 1/1' self.assertEqual(actual_out, expected_out) def test_nailgun_exit_code(self): output = StringIO.StringIO() expected_exit_code = 10 with NailgunConnection( self.transport_address, stderr=None, stdin=None, stdout=output) as c: exit_code = c.send_command('com.martiansoftware.nailgun.examples.Exit', [str(expected_exit_code)]) self.assertEqual(exit_code, expected_exit_code) def test_nailgun_stdin(self): lines = [str(i) for i in range(100)] echo = '\n'.join(lines) output = StringIO.StringIO() input = StringIO.StringIO(echo) with NailgunConnection( self.transport_address, stderr=None, stdin=input, stdout=output) as c: exit_code = c.send_command('com.martiansoftware.nailgun.examples.Echo') self.assertEqual(exit_code, 0) actual_out = output.getvalue().strip() self.assertEqual(actual_out, echo) def test_nailgun_default_streams(self): with NailgunConnection(self.transport_address) as c: exit_code = c.send_command('ng-stats') self.assertEqual(exit_code, 0) def tearDown(self): try: with NailgunConnection( self.transport_address, cwd=os.getcwd(), stderr=None, stdin=None, stdout=None) as c: c.send_command('ng-stop') except NailgunException as e: # stopping server is a best effort # if something wrong has happened, we will kill it anyways pass # Python2 compatible wait with timeout process_exit_code = None for _ in range(0, 500): process_exit_code = self.ng_server_process.poll() if process_exit_code is not None: break time.sleep(0.02) # 1 second total if process_exit_code is None: # some test has failed, ng-server was not stopped. killing it self.ng_server_process.kill() shutil.rmtree(self.tmpdir) if __name__ == '__main__': for i in range(10): was_successful = unittest.main(exit=False).result.wasSuccessful() if not was_successful: sys.exit(1) nailgun-nailgun-all-0.9.3/scripts/000077500000000000000000000000001323365546700170665ustar00rootroot00000000000000nailgun-nailgun-all-0.9.3/scripts/travis_run.sh000077500000000000000000000000751323365546700216230ustar00rootroot00000000000000#!/bin/sh set -eux mvn package python -m pynailgun.test_ng