source: trunk/packages/vizservers/nanoscale/server.c @ 4131

Last change on this file since 4131 was 4131, checked in by ldelgass, 10 years ago

Restore whitespace fixes

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