source: nanoscale/branches/1.0/server.c @ 6638

Last change on this file since 6638 was 6623, checked in by ldelgass, 7 years ago

Merge selected nanoscale changes from trunk (by hand)

File size: 23.8 KB
Line 
1/* -*- mode: c; c-basic-offset: 4; indent-tabs-mode: nil -*- */
2/* ======================================================================
3 *  Copyright (c) 2004-2016  HUBzero Foundation, LLC
4 * ----------------------------------------------------------------------
5 *  See the file "license.terms" for information on usage and
6 *  redistribution of this file, and for a DISCLAIMER OF ALL WARRANTIES.
7 * ======================================================================
8 */
9#define _GNU_SOURCE
10#include <sys/socket.h>
11
12#include <stdio.h>
13#include <string.h>
14#include <errno.h>
15#include <arpa/inet.h>
16#include <getopt.h>
17#include <stdarg.h>
18#include <stdlib.h>
19#include <sys/wait.h>
20#include <sys/stat.h>
21#include <sys/file.h>
22#include <syslog.h>
23#include <unistd.h>
24#include <fcntl.h>
25
26#include <tcl.h>
27
28#include "config.h"
29
30#define TRUE    1
31#define FALSE   0
32
33#ifndef SERVERSFILE
34#define SERVERSFILE  "/opt/hubzero/rappture/render/lib/renderservers.tcl"
35#endif
36
37#ifndef LOGDIR
38#define LOGDIR          "/tmp"
39#endif
40
41#define ERROR(...)      SysLog(LOG_ERR, __FILE__, __LINE__, __VA_ARGS__)
42#define WARN(...)       SysLog(LOG_WARNING, __FILE__, __LINE__, __VA_ARGS__)
43#define INFO(...)       SysLog(LOG_INFO, __FILE__, __LINE__, __VA_ARGS__)
44#ifdef WANT_TRACE
45#define TRACE(...)      SysLog(LOG_DEBUG, __FILE__, __LINE__, __VA_ARGS__)
46#else
47#define TRACE(...)
48#endif
49
50static const char *syslogLevels[] = {
51    "emergency",                        /* System is unusable */
52    "alert",                            /* Action must be taken immediately */
53    "critical",                         /* Critical conditions */
54    "error",                            /* Error conditions */
55    "warning",                          /* Warning conditions */
56    "notice",                           /* Normal but significant condition */
57    "info",                             /* Informational */
58    "debug",                            /* Debug-level messages */
59};
60
61/* RenderServer --
62 *
63 *      Contains information to describe/execute a render server.
64 */
65typedef struct {
66    const char *name;                   /* Name of server. */
67    int port;                           /* Port to listen to. */
68    int numCmdArgs;                     /* # of args in command.  */
69    int numEnvArgs;                     /* # of args in environment.  */
70    char *const *cmdArgs;               /* Command to execute for
71                                         * server. */
72    char *const *envArgs;               /* Environment strings to set. */
73    int listenerFd;                     /* Descriptor of the listener
74                                           socket. */
75    int inputFd;                        /* Descriptor to dup input side of
76                                         * server socket. */
77    int outputFd;                       /* Descriptor to dup output side of
78                                         * server socket. */
79    int logStdout;                      /* Redirect server stdout to a
80                                           file. */
81    int logStderr;                      /* Redirect server stderr to a
82                                           file. */
83    int combineLogs;                    /* Combine server stdout/stderr in
84                                         * same file. */
85} RenderServer;
86
87static Tcl_HashTable serverTable;       /* Table of render servers
88                                         * representing services available to
89                                         * clients.  A new instance is forked
90                                         * and executed each time a new
91                                         * request is accepted. */
92static int debug = FALSE;
93static pid_t serverPid;
94
95static void
96Help(const char *program)
97{
98    fprintf(stderr,
99        "Syntax: %s [-d] [-f serversFile] [-x numVideoCards]\n", program);
100    exit(1);
101}
102
103static void
104InitLog()
105{
106    openlog("nanoscale", LOG_CONS | LOG_PID, LOG_USER);
107}
108
109static void
110SysLog(int priority, const char *path, int lineNum, const char* fmt, ...)
111{
112#define MSG_LEN (2047)
113    char message[MSG_LEN+1];
114    const char *s;
115    int length;
116    va_list lst;
117
118    va_start(lst, fmt);
119    s = strrchr(path, '/');
120    if (s == NULL) {
121        s = path;
122    } else {
123        s++;
124    }
125    if (serverPid != getpid()) {
126        length = snprintf(message, MSG_LEN, "(%d) %s: %s:%d ",
127                          serverPid, syslogLevels[priority], s, lineNum);
128    } else {
129        length = snprintf(message, MSG_LEN, "%s: %s:%d ",
130                          syslogLevels[priority], s, lineNum);
131    }
132    length += vsnprintf(message + length, MSG_LEN - length, fmt, lst);
133    message[MSG_LEN] = '\0';
134    if (debug) {
135        fprintf(stderr, "%s\n", message);
136    } else {
137        syslog(priority, message, length);
138    }
139}
140
141static RenderServer *
142NewServer(Tcl_Interp *interp, const char *name)
143{
144    RenderServer *serverPtr;
145
146    serverPtr = malloc(sizeof(RenderServer));
147    memset(serverPtr, 0, sizeof(RenderServer));
148    if (serverPtr == NULL) {
149        Tcl_AppendResult(interp, "can't allocate structure for \"",
150                name, "\": ", Tcl_PosixError(interp), (char *)NULL);
151        return NULL;
152    }
153    serverPtr->name = strdup(name);
154    serverPtr->outputFd = STDOUT_FILENO;
155    serverPtr->inputFd = STDIN_FILENO;
156    serverPtr->combineLogs = TRUE;
157    serverPtr->logStdout = TRUE;
158    serverPtr->logStderr = TRUE;
159    return serverPtr;
160}
161
162static int
163ParseSwitches(Tcl_Interp *interp, RenderServer *serverPtr, int *objcPtr,
164              Tcl_Obj ***objvPtr)
165{
166    int i, objc;
167    Tcl_Obj **objv;
168
169    objc = *objcPtr;
170    objv = *objvPtr;
171    for (i = 3; i < objc; i += 2) {
172        const char *string;
173        char c;
174
175        string = Tcl_GetString(objv[i]);
176        if (string[0] != '-') {
177            break;
178        }
179        c = string[1];
180        if ((c == 'i') && (strcmp(string, "-input") == 0)) {
181            int f;
182
183            if (Tcl_GetIntFromObj(interp, objv[i+1], &f) != TCL_OK) {
184                return TCL_ERROR;
185            }
186            serverPtr->inputFd = f;
187        } else if ((c == 'o') && (strcmp(string, "-output") == 0)) {
188            int f;
189
190            if (Tcl_GetIntFromObj(interp, objv[i+1], &f) != TCL_OK) {
191                return TCL_ERROR;
192            }
193            serverPtr->outputFd = f;
194        } else if ((c == 'l') && (strcmp(string, "-logstdout") == 0)) {
195            int state;
196
197            if (Tcl_GetBooleanFromObj(interp, objv[i+1], &state) != TCL_OK) {
198                return TCL_ERROR;
199            }
200            serverPtr->logStdout = state;
201        } else if ((c == 'l') && (strcmp(string, "-logstderr") == 0)) {
202            int state;
203
204            if (Tcl_GetBooleanFromObj(interp, objv[i+1], &state) != TCL_OK) {
205                return TCL_ERROR;
206            }
207            serverPtr->logStderr = state;
208        } else if ((c == 'c') && (strcmp(string, "-combinelogs") == 0)) {
209            int state;
210
211            if (Tcl_GetBooleanFromObj(interp, objv[i+1], &state) != TCL_OK) {
212                return TCL_ERROR;
213            }
214            serverPtr->combineLogs = state;
215        } else {
216            Tcl_AppendResult(interp, "unknown switch \"", string, "\"",
217                             (char *)NULL);
218            return TCL_ERROR;
219        }
220    }
221    *objcPtr = objc - (i - 3);
222    *objvPtr = objv + (i - 3);
223    return TCL_OK;
224}
225
226/*
227 * RegisterServerCmd --
228 *
229 *      Registers a render server to be run when a client connects
230 *      on the designated port. The form of the commands is
231 *
232 *          register_server <name> <port> <cmd> <environ>
233 *
234 *      where
235 *
236 *          name        Token for the render server.
237 *          port        Port to listen to accept connections.
238 *          cmd         Command to be run to start the render server.
239 *          environ     Name-value pairs of representing environment
240 *                      variables.
241 *
242 *      Note that "cmd" and "environ" are variable and backslash
243 *      substituted.  A listener socket automatically is established on
244 *      the given port to accept client requests.
245 *
246 *      Example:
247 *
248 *          register_server myServer 12345 ?switches? {
249 *               /path/to/myserver arg arg
250 *          } {
251 *               LD_LIBRARY_PATH $libdir/myServer
252 *          }
253 *
254 */
255static int
256RegisterServerCmd(ClientData clientData, Tcl_Interp *interp, int objc,
257                  Tcl_Obj *const *objv)
258{
259    Tcl_Obj *objPtr;
260    const char *serverName;
261    int bool, isNew;
262    int f;
263    int port;
264    int numCmdArgs, numEnvArgs;
265    char *const *cmdArgs;
266    char *const *envArgs;
267    struct sockaddr_in addr;
268    RenderServer *serverPtr;
269    Tcl_HashEntry *hPtr;
270
271    if (objc < 4) {
272        Tcl_AppendResult(interp, "wrong # args: should be \"",
273                Tcl_GetString(objv[0]), " serverName port ?flags? cmd ?environ?",
274                (char *)NULL);
275        return TCL_ERROR;
276    }
277    serverName = Tcl_GetString(objv[1]);
278    if (Tcl_GetIntFromObj(interp, objv[2], &port) != TCL_OK) {
279        return TCL_ERROR;
280    }
281    hPtr = Tcl_CreateHashEntry(&serverTable, (char *)((long)port), &isNew);
282    if (!isNew) {
283        Tcl_AppendResult(interp, "a server is already listening on port ",
284                Tcl_GetString(objv[2]), (char *)NULL);
285        return TCL_ERROR;
286    }
287    serverPtr = NewServer(interp, serverName);
288    if (serverPtr == NULL) {
289        return TCL_ERROR;
290    }
291    Tcl_SetHashValue(hPtr, serverPtr);
292    if (ParseSwitches(interp, serverPtr, &objc, (Tcl_Obj ***)&objv) != TCL_OK) {
293        goto error;
294    }
295    objPtr = Tcl_SubstObj(interp, objv[3],
296                          TCL_SUBST_VARIABLES | TCL_SUBST_BACKSLASHES);
297    if (Tcl_SplitList(interp, Tcl_GetString(objPtr), &numCmdArgs,
298        (const char ***)&cmdArgs) != TCL_OK) {
299        goto error;
300    }
301    serverPtr->cmdArgs = cmdArgs;
302    serverPtr->numCmdArgs = numCmdArgs;
303
304    numEnvArgs = 0;
305    envArgs = NULL;
306    if (objc == 5) {
307        objPtr = Tcl_SubstObj(interp, objv[4],
308                TCL_SUBST_VARIABLES | TCL_SUBST_BACKSLASHES);
309        if (Tcl_SplitList(interp, Tcl_GetString(objPtr), &numEnvArgs,
310                (const char ***)&envArgs) != TCL_OK) {
311            goto error;
312        }
313        if (numEnvArgs & 0x1) {
314            Tcl_AppendResult(interp, "odd # elements in enviroment list",
315                             (char *)NULL);
316            goto error;
317        }
318    }
319    serverPtr->envArgs = envArgs;
320    serverPtr->numEnvArgs = numEnvArgs;
321
322    /* Create a socket for listening. */
323    f = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
324    if (f < 0) {
325        Tcl_AppendResult(interp, "can't create listener socket for \"",
326                serverPtr->name, "\": ", Tcl_PosixError(interp), (char *)NULL);
327        goto error;
328    }
329
330    /* If the render server instance should be killed, drop the socket address
331     * reservation immediately, don't linger. */
332    bool = TRUE;
333    if (setsockopt(f, SOL_SOCKET, SO_REUSEADDR, &bool, sizeof(bool)) < 0) {
334        Tcl_AppendResult(interp, "can't create set socket option for \"",
335                serverPtr->name, "\": ", Tcl_PosixError(interp), (char *)NULL);
336        goto error;
337    }
338
339    /* Bind this address to the socket. */
340    addr.sin_family = AF_INET;
341    addr.sin_port = htons(port);
342    addr.sin_addr.s_addr = htonl(INADDR_ANY);
343    if (bind(f, (struct sockaddr *)&addr, sizeof(addr)) < 0) {
344        Tcl_AppendResult(interp, "can't bind to socket for \"",
345                serverPtr->name, "\": ", Tcl_PosixError(interp), (char *)NULL);
346        goto error;
347    }
348    /* Listen on the specified port. */
349    if (listen(f, 5) < 0) {
350        Tcl_AppendResult(interp, "can't listen to socket for \"",
351                serverPtr->name, "\": ", Tcl_PosixError(interp), (char *)NULL);
352        return TCL_ERROR;
353    }
354    serverPtr->listenerFd = f;
355
356    return TCL_OK;
357 error:
358    free(serverPtr);
359    return TCL_ERROR;
360}
361
362static int
363ParseServersFile(const char *fileName)
364{
365    Tcl_Interp *interp;
366
367    interp = Tcl_CreateInterp();
368    Tcl_MakeSafe(interp);
369    Tcl_CreateObjCommand(interp, "register_server", RegisterServerCmd, NULL,
370                         NULL);
371    if (Tcl_EvalFile(interp, fileName) != TCL_OK) {
372        ERROR("Can't add server: %s", Tcl_GetString(Tcl_GetObjResult(interp)));
373        return FALSE;
374    }
375    Tcl_DeleteInterp(interp);
376    return TRUE;
377}
378
379static void
380InitServerTable()
381{
382    Tcl_InitHashTable(&serverTable, TCL_ONE_WORD_KEYS);
383}
384
385int
386main(int argc, char **argv)
387{
388#ifdef SA_NOCLDWAIT
389    struct sigaction action;
390#endif
391    fd_set serverFds;
392    int maxFd;                          /* Highest file descriptor in use. */
393    char display[200];                  /* String used to manage the X
394                                         * DISPLAY variable for each render
395                                         * server instance. */
396    int maxCards;                       /* Maximum number of video cards, each
397                                         * represented by a different X
398                                         * screen.  */
399    int screenNum;                      /* Current X screen number. */
400    Tcl_HashEntry *hPtr;
401    Tcl_HashSearch iter;
402    const char *fileName;               /* Path to servers file. */
403
404    serverPid = getpid();
405    screenNum = 0;
406    maxCards = 1;
407    fileName = SERVERSFILE;
408    debug = FALSE;
409
410    strcpy(display, ":0.0");
411    InitLog();
412    InitServerTable();
413
414    /* Process command line switches. */
415    for (;;) {
416        int c;
417        int option_index = 0;
418        struct option long_options[] = {
419            // name, has_arg, flag, val
420            { 0,0,0,0 },
421        };
422
423        c = getopt_long(argc, argv, "x:f:d", long_options, &option_index);
424        if (c == -1) {
425            break;
426        }
427
428        switch(c) {
429        case 'x':                       /* Number of video cards */
430            maxCards = strtoul(optarg, 0, 0);
431            if ((maxCards < 1) || (maxCards > 10)) {
432                fprintf(stderr, "Bad number of max videocards specified\n");
433                return 1;
434            }
435            break;
436        case 'd':                       /* Debug  */
437            debug = TRUE;
438            break;
439
440        case 'f':                       /* Server file path. */
441            fileName = strdup(optarg);
442            break;
443
444        default:
445            fprintf(stderr,"Don't know what option '%c'.\n", c);
446            Help(argv[0]);
447            exit(1);
448        }
449    }
450
451    if (!debug) {
452        /* Detach this process from the controlling terminal process. The
453         * current directory becomes /tmp and redirect stdin/stdout/stderr to
454         * /dev/null. */
455        if (daemon(0,0) < 0) {
456            ERROR("Can't daemonize nanoscale: %s", strerror(errno));
457            exit(1);
458        }
459    }
460    serverPid = getpid();
461    if (!ParseServersFile(fileName)) {
462        ERROR("Exiting on invalid configuration");
463        exit(1);
464    }
465
466    if (serverTable.numEntries == 0) {
467        ERROR("No servers designated.");
468        exit(1);
469    }
470    signal(SIGPIPE, SIG_IGN);
471#ifdef SA_NOCLDWAIT
472    memset(&action, 0, sizeof(action));
473    action.sa_flags = SA_NOCLDWAIT;
474    sigaction(SIGCHLD, &action, 0);
475#else
476    signal(SIGCHLD, SIG_IGN);
477#endif
478
479    /* Build the array of servers listener file descriptors. */
480    FD_ZERO(&serverFds);
481    maxFd = -1;
482    for (hPtr = Tcl_FirstHashEntry(&serverTable, &iter); hPtr != NULL;
483         hPtr = Tcl_NextHashEntry(&iter)) {
484        RenderServer *serverPtr;
485
486        serverPtr = Tcl_GetHashValue(hPtr);
487        FD_SET(serverPtr->listenerFd, &serverFds);
488        if (serverPtr->listenerFd > maxFd) {
489            maxFd = serverPtr->listenerFd;
490        }
491    }
492
493    for (;;) {
494        fd_set readFds;
495
496        /* Reset using the array of server file descriptors. */
497        memcpy(&readFds, &serverFds, sizeof(serverFds));
498        if (select(maxFd+1, &readFds, NULL, NULL, 0) <= 0) {
499            ERROR("Select failed: %s", strerror(errno));
500            break;                      /* Error on select. */
501        }
502        for (hPtr = Tcl_FirstHashEntry(&serverTable, &iter); hPtr != NULL;
503             hPtr = Tcl_NextHashEntry(&iter)) {
504            RenderServer *serverPtr;
505            pid_t child;
506            int sock;
507            socklen_t length;
508            struct sockaddr_in newaddr;
509
510            serverPtr = Tcl_GetHashValue(hPtr);
511            if (!FD_ISSET(serverPtr->listenerFd, &readFds)) {
512                continue;
513            }
514            /* Rotate the display's screen number.  If we have multiple video
515             * cards, try to spread the jobs out among them.  */
516            screenNum++;
517            if (screenNum >= maxCards) {
518                screenNum = 0;
519            }
520            /* Accept the new connection. */
521            length = sizeof(newaddr);
522#ifdef HAVE_ACCEPT4
523            sock = accept4(serverPtr->listenerFd, (struct sockaddr *)&newaddr,
524                           &length, SOCK_CLOEXEC);
525#else
526            sock = accept(serverPtr->listenerFd, (struct sockaddr *)&newaddr,
527                          &length);
528#endif
529            if (sock < 0) {
530                ERROR("Can't accept server \"%s\": %s", serverPtr->name,
531                      strerror(errno));
532                exit(1);
533            }
534#ifndef HAVE_ACCEPT4
535            int flags = fcntl(sock, F_GETFD);
536            flags |= FD_CLOEXEC;
537            if (fcntl(sock, F_SETFD, flags) < 0) {
538                ERROR("Can't set FD_CLOEXEC on socket \"%s\": %s",
539                        serverPtr->name, strerror(errno));
540                exit(1);
541            }
542#endif
543            INFO("Connecting \"%s\" to %s\n", serverPtr->name,
544                 inet_ntoa(newaddr.sin_addr));
545
546            /* Fork the new process.  Connect I/O to the new socket. */
547            child = fork();
548            if (child < 0) {
549                ERROR("Can't fork \"%s\": %s", serverPtr->name,
550                      strerror(errno));
551                continue;
552            }
553            if (child == 0) {           /* Child process. */
554                int i;
555
556                if ((!debug) && (setsid() < 0)) {
557                    ERROR("Can't setsid \"%s\": %s", serverPtr->name,
558                          strerror(errno));
559                    exit(1);
560                }
561                if ((!debug) && ((chdir("/")) < 0)) {
562                    ERROR("Can't change to root directory for \"%s\": %s",
563                          serverPtr->name, strerror(errno));
564                    exit(1);
565                }
566                if (serverPtr->combineLogs) {
567                    char path[BUFSIZ];
568                    int newFd;
569
570                    sprintf(path, "%s/%s-%d.log", LOGDIR,
571                        serverPtr->name, getpid());
572                    if (serverPtr->logStdout) {
573                        newFd = open(path, O_WRONLY | O_CREAT | O_TRUNC, 0600);
574                    } else {
575                        newFd = open("/dev/null", O_WRONLY, 0600);
576                    }
577                    if (newFd < 0) {
578                        ERROR("%s: can't open \"%s\": %s", serverPtr->name,
579                              path, strerror(errno));
580                        exit(1);
581                    }
582                    if (dup2(newFd, 1) < 0) {
583                        ERROR("%s: can't dup stdout to \"%s\": %s",
584                              serverPtr->name, path, strerror(errno));
585                        exit(1);
586                    }
587                    if (dup2(newFd, 2) < 0) {
588                        ERROR("%s: can't dup stderr to \"%s\": %s",
589                              serverPtr->name, path, strerror(errno));
590                        exit(1);
591                    }
592                } else {
593                    char path[BUFSIZ];
594                    int newFd;
595
596                    sprintf(path, "%s/%s-%d.stdout", LOGDIR,
597                        serverPtr->name, getpid());
598                    if (serverPtr->logStdout) {
599                        newFd = open(path, O_WRONLY | O_CREAT | O_TRUNC, 0600);
600                    } else {
601                        newFd = open("/dev/null", O_WRONLY, 0600);
602                    }
603                    if (newFd < 0) {
604                        ERROR("%s: can't open \"%s\": %s", serverPtr->name,
605                              path, strerror(errno));
606                        exit(1);
607                    }
608                    if (dup2(newFd, 1) < 0) {
609                        ERROR("%s: can't dup stdout to \"%s\": %s",
610                              serverPtr->name, path, strerror(errno));
611                        exit(1);
612                    }
613                    sprintf(path, "%s/%s-%d.stderr", LOGDIR,
614                        serverPtr->name, getpid());
615                    if (serverPtr->logStderr) {
616                        newFd = open(path, O_WRONLY | O_CREAT | O_TRUNC, 0600);
617                    } else {
618                        newFd = open("/dev/null", O_WRONLY, 0600);
619                    }
620                    if (newFd < 0) {
621                        ERROR("%s: can't open \"%s\": %s", serverPtr->name,
622                              path, strerror(errno));
623                        exit(1);
624                    }
625                    if (dup2(newFd, 2) < 0) {
626                        ERROR("%s: can't dup stderr to \"%s\": %s",
627                              serverPtr->name, path, strerror(errno));
628                        exit(1);
629                    }
630                }
631                /* Make sure to close syslog in case it is using one of
632                 * the file descriptors we are about to close
633                 */
634                closelog();
635                /* Dup the socket to descriptors, e.g. 3 and 4 */
636                if (dup2(sock, serverPtr->inputFd) < 0)  { /* input */
637                    ERROR("%s: can't dup socket to fd %d: %s",
638                          serverPtr->name, serverPtr->inputFd,
639                          strerror(errno));
640                    exit(1);
641                }
642                /* If using a single descriptor for input and output, we don't
643                 * need to call dup2 again
644                 */
645                if (serverPtr->outputFd != serverPtr->inputFd) {
646                    if (dup2(sock, serverPtr->outputFd) < 0) { /* output */
647                        ERROR("%s: can't dup socket to fd %d: %s",
648                              serverPtr->name, serverPtr->outputFd,
649                              strerror(errno));
650                        exit(1);
651                    }
652                }
653                /* Close all the other descriptors. */
654                for (i = 3; i <= FD_SETSIZE; i++) {
655                    if (i != serverPtr->inputFd &&
656                        i != serverPtr->outputFd) {
657                        close(i);
658                    }
659                }
660
661                /* Set the screen number in the DISPLAY variable. */
662                display[3] = screenNum + '0';
663                setenv("DISPLAY", display, 1);
664                /* Don't pollute child's environment with our library path.
665                 * Library path should be explicitly set in config if needed.
666                 */
667                unsetenv("LD_LIBRARY_PATH");
668                /* Set the configured environment */
669                for (i = 0; i < serverPtr->numEnvArgs; i += 2) {
670                    INFO("Env: %s=%s", serverPtr->envArgs[i],
671                         serverPtr->envArgs[i+1]);
672                    setenv(serverPtr->envArgs[i], serverPtr->envArgs[i+1], 1);
673                }
674                {
675                    char *cmd;
676
677                    cmd = Tcl_Merge(serverPtr->numCmdArgs,
678                                    (const char *const *)serverPtr->cmdArgs);
679                    INFO("Executing %s: client=%s, \"%s\" in=%d out=%d on DISPLAY=%s",
680                         serverPtr->name, inet_ntoa(newaddr.sin_addr),
681                         cmd, serverPtr->inputFd, serverPtr->outputFd, display);
682                    Tcl_Free(cmd);
683                    /* Replace the current process with the render server. */
684                    execvp(serverPtr->cmdArgs[0], serverPtr->cmdArgs);
685                    ERROR("Can't execute \"%s\": %s", serverPtr->cmdArgs[0],
686                          strerror(errno));
687                    exit(1);
688                }
689            } else {
690                close(sock);
691            }
692        }
693    }
694    exit(1);
695}
Note: See TracBrowser for help on using the repository browser.