/* BEGIN erlaunch.c */ /* * erlaunch - execute selected Erlang functions from system command line * Copyright (c)2003-2004 Cat's Eye Technologies. All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * * Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * * 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. * * Neither the name of Cat's Eye Technologies nor the names of its * contributors may be used to endorse or promote products derived * from this software without specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND * CONTRIBUTORS ``AS IS'' AND ANY EXPRESS 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 REGENTS OR 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 AsDVISED OF THE * POSSIBILITY OF SUCH DAMAGE. */ /* * erlaunch is a system for efficiently executing utility programs * written for Erlang/OTP from the system command line. * * This module is the C-language component of the erlaunch system. * It provides management (starting and stopping) of the long- * lived TCP/IP server written in Eralng, a short-lived TCP/IP * client which issues command requests to the server, and an * interface to the end user. */ /* HEADERS */ #include #include #include #include #include #include #include #include #include #include #include #include #include /* CONSTANTS */ #define ERLAUNCH_VERSION "2004.0309" #define ERLAUNCH_DEFAULT_HOST "127.0.0.1" #define ERLAUNCH_DEFAULT_PORT 17779 #define ERLAUNCH_HOST_SIZE 255 #define ERLAUNCH_PORT_SIZE 31 #define ERLAUNCH_COOKIE_SIZE 31 #define ERLAUNCH_SHELL_SIZE 63 /* EXTERNS */ extern char ** environ; /* GLOBALS */ char host[ERLAUNCH_HOST_SIZE]; char port_string[ERLAUNCH_PORT_SIZE]; int port; char cookie[ERLAUNCH_COOKIE_SIZE]; char shell[ERLAUNCH_SHELL_SIZE]; /* FUNCTIONS */ /* * banner: print useful identification information about this program */ void banner(void) { printf("erlaunch v%s - execute selected Erlang functions from command line\n", ERLAUNCH_VERSION); printf("Copyright (c)2003-2004 Cat's Eye Technologies. All rights reserved.\n"); } /* * usage: print expected command-line usage of this program. */ void usage(char *prgnm) { banner(); printf("usage:\n"); printf(" %s [-p port] [-s shell] [-f] [-c cookie] start\n", prgnm); printf(" %s [-h host] [-p port] [-s shell] [-f] -c cookie connect\n", prgnm); printf(" %s status\n", prgnm); printf(" %s {args}\n", prgnm); exit(EX_USAGE); } /* * bake_cookie: generate a random string of letters. * changes: cookie */ void bake_cookie(void) { int i; #ifdef __FreeBSD__ srandomdev(); #else struct timeval tv; struct timezone tz; gettimeofday(&tv, &tz); srandom(tv.tv_usec); #endif for(i = 0; i < ERLAUNCH_COOKIE_SIZE-1; i++) cookie[i] = random() % 26 + 'A'; cookie[i] = 0; } /* * open_tcp: create a TCP/IP client socket and connect to the server. * returns a socket descriptor. */ int open_tcp(void) { struct sockaddr_in tcp; int sd; if ((sd = socket(PF_INET, SOCK_STREAM, 0)) == -1) { err(EX_UNAVAILABLE, "socket"); } bzero(&tcp, sizeof(tcp)); tcp.sin_family = AF_INET; tcp.sin_port = htons(port); inet_aton(host, &tcp.sin_addr); if (connect(sd, (struct sockaddr *)&tcp, sizeof(tcp)) != 0) { err(EX_UNAVAILABLE, "connect"); } return sd; } /* * close_tcp: close a client socket. */ void close_tcp(int sd) { close(sd); } /* * send_string: send a string to the server via a client socket. * The string is packetized, along with a 'not an eof' byte, * by preceding it with its length (plus one for the byte) * as a four-byte integer in network byte order. */ void send_string(int sd, char * string) { long length = strlen(string) + 1; long nlength = htonl(length); char no_eof = 0; write(sd, &nlength, 4); write(sd, &no_eof, 1); write(sd, string, (length - 1)); } /* * send_eof: sends an end-of-file indicator to the server. */ void send_eof(int sd) { long nlength = htonl(1); char yes_eof = 1; write(sd, &nlength, 4); write(sd, &yes_eof, 1); } /* * send_array: send an array of strings to the server via a client. * Each string is sent a la send_string, followed by a * zero-length string to indicate end of array. * If 'size' parameter is zero, strings are sent until * a NULL string is found. Otherwise 'size' strings are sent. */ /* each call to send_array accounts for a 50 millisecond delay. */ /* optimize by having it accum to a buffer, */ /* then send the whole buffer at once? */ void send_array(int sd, int size, char * string[]) { int i; if (size == 0) { for(i = 0; string[i] != NULL; i++) { send_string(sd, string[i]); } } else { for(i = 0; i < size; i++) { send_string(sd, string[i]); } } send_string(sd, ""); } /* * recv_packet: receives a packet on the client socket from the server. * Packets from the server consist of a four-byte length * sent in network byte order, followed by the data. * The data consists of a 1-byte 'exit code' followed by * a string of any length. */ long recv_packet(int sd, int maxsize, char * buffer, char * exit_code) { long nlength; long length; read(sd, &nlength, 4); length = ntohl(nlength); if (length < maxsize) { read(sd, exit_code, 1); read(sd, buffer, length - 1); buffer[length - 1] = 0; /* printf("received packet, string = %s, exit_code = %d\n", buffer, *exit_code); */ return length - 1; } else { fprintf(stderr, "packet length %ld exceeded maxsize %d\n", length, maxsize); exit(EX_UNAVAILABLE); } } /* * get_erlaunch_env: gets the environment variables pertaining to erlaunch. * Exits the program if the env vars are not available. * changes: host, port_string, cookie, port */ void get_erlaunch_env(void) { char * h, * p, * c; h = getenv("ERLAUNCH_HOST"); p = getenv("ERLAUNCH_PORT"); c = getenv("ERLAUNCH_COOKIE"); if((h == NULL) || (p == NULL) || (c == NULL)) err(EX_CONFIG, "missing environment variables"); strncpy(host, h, ERLAUNCH_HOST_SIZE); strncpy(port_string, p, ERLAUNCH_PORT_SIZE); strncpy(cookie, c, ERLAUNCH_COOKIE_SIZE); port = atoi(port_string); } /* * set_erlaunch_env: sets the environment variables pertaining to erlaunch. * changes: environ */ void set_erlaunch_env(void) { setenv("ERLAUNCH_HOST", host, 1); setenv("ERLAUNCH_PORT", port_string, 1); setenv("ERLAUNCH_COOKIE", cookie, 1); } /* * stop_erlang: stops the long-running BEAM emulator. */ void stop_erlang(void) { int sd; get_erlaunch_env(); sd = open_tcp(); send_string(sd, cookie); send_string(sd, "stop"); close_tcp(sd); } /* * execute: executes a system command. * returns: the exit status of the command. */ int execute(char * command) { /* fprintf(stderr, ">> %s\n", command); */ return system(command); } /* * spawn_erlang: starts the long-running BEAM emulator. * returns: an exit status. */ int spawn_erlang(int force) { char command[255]; banner(); if ((getenv("ERLAUNCH_COOKIE") == NULL) || ((getenv("ERLAUNCH_COOKIE") != NULL) && force)) { sprintf(command, "erl -detached -run erlaunch server %s %s %s", ERLAUNCH_VERSION, port_string, cookie), execute(command); set_erlaunch_env(); printf("Emulator started, starting erlaunch subshell. Type ^D to exit.\n"); sprintf(command, "%s", shell); execute(command); printf("Stopping emulator. Thank you for using erlaunch!\n"); stop_erlang(); return 0; } else err(EX_UNAVAILABLE, "already running"); } /* * connect_to_erlang: connects to an existing long-running BEAM emulator. * returns: an exit status. */ int connect_to_erlang(int force) { char command[255]; banner(); if ((getenv("ERLAUNCH_COOKIE") == NULL) || ((getenv("ERLAUNCH_COOKIE") != NULL) && force)) { set_erlaunch_env(); printf("Connected to emulator, starting erlaunch subshell. Type ^D to exit.\n"); sprintf(command, "%s", shell); execute(command); printf("Disconnecting from emulator. Thank you for using erlaunch!\n"); return 0; } else err(EX_UNAVAILABLE, "already running"); } /* * tell_erlang: sends a command request to the long-running BEAM emulator. * returns: an exit status. */ int tell_erlang(int cmd_pos, int argc, char * argv[]) { char * command = argv[cmd_pos]; char * cwd = getcwd(NULL, MAXPATHLEN); int sd; char string[4095]; long slength; char exit_code = 0; char inchar; char inbuf[4095]; int inoff = 0; int inerr = 0; fd_set readfds; fd_set writefds; fd_set exceptfds; get_erlaunch_env(); sd = open_tcp(), send_string(sd, cookie); send_string(sd, command); recv_packet(sd, 4095, string, &exit_code); if (!exit_code) { send_array(sd, argc - cmd_pos, &(argv[cmd_pos])); send_string(sd, cwd); send_array(sd, 0, environ); } else warnx("%s: Command not found.", command); free(cwd); FD_ZERO(&writefds); FD_ZERO(&exceptfds); while (exit_code == 0) { FD_ZERO(&readfds); FD_SET(STDIN_FILENO, &readfds); FD_SET(sd, &readfds); select(FD_SETSIZE, &readfds, &writefds, &exceptfds, NULL); if (FD_ISSET(sd, &readfds)) { slength = recv_packet(sd, 4095, string, &exit_code); write(STDOUT_FILENO, string, slength); } if (FD_ISSET(STDIN_FILENO, &readfds)) { inerr = read(STDIN_FILENO, &inchar, 1); if (inerr > 0) { inbuf[inoff++] = inchar; if (inchar == '\n' || inoff > 4094) { inbuf[inoff] = 0; send_string(sd, inbuf); inoff = 0; } } else if (inerr == 0) { send_eof(sd); } else if (inerr < 0) { err(EX_DATAERR, "read(STDIN)"); } } } close_tcp(sd); return ((int)exit_code) - 1; } int erlaunch_status(void) { if (getenv("ERLAUNCH_COOKIE") == NULL) { printf("status: down\n"); } else { printf("status: up\n"); get_erlaunch_env(); printf("host: %s\n", host); printf("port: %d\n", port); printf("cookie: %s\n", cookie); } return 0; } /* MAIN */ int main(int argc, char * argv[]) { int force = 0; int cmd_pos = 1; /* set up reasonable defaults */ bake_cookie(); sprintf(port_string, "%d", ERLAUNCH_DEFAULT_PORT); strncpy(host, ERLAUNCH_DEFAULT_HOST, ERLAUNCH_HOST_SIZE); strncpy(shell, getenv("SHELL"), ERLAUNCH_SHELL_SIZE); if (argc < 2) usage(argv[0]); while(argv[cmd_pos][0] == '-') { switch(argv[cmd_pos][1]) { case 'h': cmd_pos++; strncpy(host, argv[cmd_pos], ERLAUNCH_HOST_SIZE); cmd_pos++; break; case 'p': cmd_pos++; strncpy(port_string, argv[cmd_pos], ERLAUNCH_PORT_SIZE); cmd_pos++; break; case 's': cmd_pos++; strncpy(shell, argv[cmd_pos], ERLAUNCH_SHELL_SIZE); cmd_pos++; break; case 'f': cmd_pos++; force = 1; break; case 'c': cmd_pos++; strncpy(cookie, argv[cmd_pos], ERLAUNCH_COOKIE_SIZE); cmd_pos++; break; default: usage(argv[0]); } if (cmd_pos > argc-1) usage(argv[0]); } port = atoi(port_string); if (!strcmp(argv[cmd_pos], "start")) { return spawn_erlang(force); } else if (!strcmp(argv[cmd_pos], "connect")) { return connect_to_erlang(force); } else if (!strcmp(argv[cmd_pos], "status")) { return erlaunch_status(); } else { return tell_erlang(cmd_pos, argc, argv); } } /* END of erlaunch.c */