source: branches/blt4/packages/vizservers/nanoscale/server2.c @ 3892

Last change on this file since 3892 was 3892, checked in by gah, 11 years ago
File size: 13.2 KB
Line 
1
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#define ERROR(...)      SysLog(LOG_ERR, __FILE__, __LINE__, __VA_ARGS__)
31#define TRACE(...)      SysLog(LOG_DEBUG, __FILE__, __LINE__, __VA_ARGS__)
32#define WARN(...)       SysLog(LOG_WARNING, __FILE__, __LINE__, __VA_ARGS__)
33#define INFO(...)       SysLog(LOG_INFO, __FILE__, __LINE__, __VA_ARGS__)
34
35static const char *syslogLevels[] = {
36    "emergency",                        /* System is unusable */
37    "alert",                            /* Action must be taken immediately */
38    "critical",                         /* Critical conditions */
39    "error",                            /* Error conditions */
40    "warning",                          /* Warning conditions */
41    "notice",                           /* Normal but significant condition */
42    "info",                             /* Informational */
43    "debug",                            /* Debug-level messages */
44};
45
46/* RenderServer --
47 *
48 *      Contains information to describe/execute a render server.
49 */
50typedef struct {
51    const char *name;                   /* Name of server. */
52    int port;                           /* Port to listen to. */
53    int numCmdArgs;                     /* # of args in command.  */
54    int numEnvArgs;                     /* # of args in environment.  */
55    char *const *cmdArgs;               /* Command to execute for server. */
56    char *const *envArgs;               /* Environment strings to set. */
57    int listenerFd;                     /* Descriptor of the listener socket. */
58} RenderServer;
59
60static Tcl_HashTable serverTable;       /* Table of render servers
61                                         * representing services available to
62                                         * clients.  A new instance is forked
63                                         * and executed each time a new
64                                         * request is accepted. */
65static int debug = FALSE;
66static pid_t serverPid;
67
68static void
69SysLog(int priority, const char *path, int lineNum, const char* fmt, ...)
70{
71#define MSG_LEN (2047)
72    char message[MSG_LEN+1];
73    const char *s;
74    int length;
75    va_list lst;
76
77    va_start(lst, fmt);
78    s = strrchr(path, '/');
79    if (s == NULL) {
80        s = path;
81    } else {
82        s++;
83    }
84    length = snprintf(message, MSG_LEN, "nanoscale (%d %d) %s: %s:%d ",
85        serverPid, getpid(), syslogLevels[priority],  s, lineNum);
86    length += vsnprintf(message + length, MSG_LEN - length, fmt, lst);
87    message[MSG_LEN] = '\0';
88    if (debug) {
89        fprintf(stderr, "%s\n", message);
90    } else {
91        syslog(priority, message, length);
92    }
93}
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
103/*
104 * RegisterServerCmd --
105 *
106 *      Registers a render server to be run when a client connects
107 *      on the designated port. The form of the commands is
108 *
109 *          register_server <name> <port> <cmd> <environ>
110 *
111 *      where
112 *
113 *          name        Token for the render server.
114 *          port        Port to listen to accept connections.
115 *          cmd         Command to be run to start the render server.
116 *          environ     Name-value pairs of representing environment
117 *                      variables.
118 *
119 *      Note that "cmd" and "environ" are variable and backslash
120 *      substituted.  A listener socket automatically is established on
121 *      the given port to accept client requests. 
122 *     
123 *      Example:
124 *
125 *          register_server myServer 12345 {
126 *               /path/to/myserver arg arg
127 *          } {
128 *               LD_LIBRARY_PATH $libdir/myServer
129 *          }
130 *
131 */
132static int
133RegisterServerCmd(ClientData clientData, Tcl_Interp *interp, int objc,
134                  Tcl_Obj *const *objv)
135{
136    Tcl_Obj *objPtr;
137    const char *serverName;
138    int bool, isNew;
139    int f;
140    int port;
141    int numCmdArgs, numEnvArgs;
142    char *const *cmdArgs;
143    char *const *envArgs;
144    struct sockaddr_in addr;
145    RenderServer *serverPtr;
146    Tcl_HashEntry *hPtr;
147
148    if ((objc < 4) || (objc > 5)) {
149        Tcl_AppendResult(interp, "wrong # args: should be \"",
150                Tcl_GetString(objv[0]), " serverName port cmd ?environ?",
151                (char *)NULL);
152        return TCL_ERROR;
153    }
154    serverName = Tcl_GetString(objv[1]);
155    if (Tcl_GetIntFromObj(interp, objv[2], &port) != TCL_OK) {
156        return TCL_ERROR;
157    }
158    hPtr = Tcl_CreateHashEntry(&serverTable, (char *)((long)port), &isNew);
159    if (!isNew) {
160        Tcl_AppendResult(interp, "a server is already listening on port ",
161                Tcl_GetString(objv[2]), (char *)NULL);
162        return TCL_ERROR;
163    }
164    objPtr = Tcl_SubstObj(interp, objv[3],
165                          TCL_SUBST_VARIABLES | TCL_SUBST_BACKSLASHES);
166    if (Tcl_SplitList(interp, Tcl_GetString(objPtr), &numCmdArgs,
167        (const char ***)&cmdArgs) != TCL_OK) {
168        return TCL_ERROR;
169    }
170
171    /* Create a socket for listening. */
172    f = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
173    if (f < 0) {
174        Tcl_AppendResult(interp, "can't create listerner socket for \"",
175                serverName, "\": ", Tcl_PosixError(interp), (char *)NULL);
176        return TCL_ERROR;
177    }
178 
179    /* If the render server instance should be killed, drop the socket address
180     * reservation immediately, don't linger. */
181    bool = TRUE;
182    if (setsockopt(f, SOL_SOCKET, SO_REUSEADDR, &bool, sizeof(bool)) < 0) {
183        Tcl_AppendResult(interp, "can't create set socket option for \"",
184                serverName, "\": ", Tcl_PosixError(interp), (char *)NULL);
185        return TCL_ERROR;
186    }
187
188    /* Bind this address to the socket. */
189    addr.sin_family = AF_INET;
190    addr.sin_port = htons(port);
191    addr.sin_addr.s_addr = htonl(INADDR_ANY);
192    if (bind(f, (struct sockaddr *)&addr, sizeof(addr)) < 0) {
193        Tcl_AppendResult(interp, "can't bind to socket for \"",
194                serverName, "\": ", Tcl_PosixError(interp), (char *)NULL);
195        return TCL_ERROR;
196    }
197    /* Listen on the specified port. */
198    if (listen(f, 5) < 0) {
199        Tcl_AppendResult(interp, "can't listen to socket for \"",
200                serverName, "\": ", Tcl_PosixError(interp), (char *)NULL);
201        return TCL_ERROR;
202    }
203    numEnvArgs = 0;
204    envArgs = NULL;
205    if (objc == 5) {
206        objPtr = Tcl_SubstObj(interp, objv[4],
207                TCL_SUBST_VARIABLES | TCL_SUBST_BACKSLASHES);
208        if (Tcl_SplitList(interp, Tcl_GetString(objPtr), &numEnvArgs,
209                (const char ***)&envArgs) != TCL_OK) {
210            return TCL_ERROR;
211        }
212        if (numEnvArgs & 0x1) {
213            Tcl_AppendResult(interp, "odd # elements in enviroment list",
214                             (char *)NULL);
215            return TCL_ERROR;
216        }
217    }
218    serverPtr = malloc(sizeof(RenderServer));
219    memset(serverPtr, 0, sizeof(RenderServer));
220    if (serverPtr == NULL) {
221        Tcl_AppendResult(interp, "can't allocate structure for \"",
222                serverName, "\": ", Tcl_PosixError(interp), (char *)NULL);
223        return TCL_ERROR;
224    }
225    serverPtr->name = strdup(serverName);
226    serverPtr->cmdArgs = cmdArgs;
227    serverPtr->numCmdArgs = numCmdArgs;
228    serverPtr->listenerFd = f;
229    serverPtr->envArgs = envArgs;
230    serverPtr->numEnvArgs = numEnvArgs;
231    Tcl_SetHashValue(hPtr, serverPtr);
232    return TCL_OK;
233}
234
235static int
236ParseServersFile(const char *fileName)
237{
238    Tcl_Interp *interp;
239
240    interp = Tcl_CreateInterp();
241    Tcl_MakeSafe(interp);
242    Tcl_CreateObjCommand(interp, "register_server", RegisterServerCmd, NULL,
243                         NULL);
244    if (Tcl_EvalFile(interp, fileName) != TCL_OK) {
245        ERROR("Can't add server: %s", Tcl_GetString(Tcl_GetObjResult(interp)));
246        return FALSE;
247    }
248    Tcl_DeleteInterp(interp);
249    return TRUE;
250}
251
252int
253main(int argc, char **argv)
254{
255#ifdef SA_NOCLDWAIT
256    struct sigaction action;
257#endif
258    fd_set serverFds;
259    int maxFd;                          /* Highest file descriptor in use. */
260    char display[200];                  /* String used to manage the X
261                                         * DISPLAY variable for each render
262                                         * server instance. */
263    int maxCards;                       /* Maximum number of video cards, each
264                                         * represented by a different X
265                                         * screen.  */
266    int screenNum;                      /* Current X screen number. */
267    Tcl_HashEntry *hPtr;
268    Tcl_HashSearch iter;
269    const char *fileName;               /* Path to servers file. */
270 
271    serverPid = getpid();
272    screenNum = 0;
273    maxCards = 1;
274    fileName = SERVERSFILE;
275    debug = FALSE;
276
277    strcpy(display, ":0.0");
278    Tcl_InitHashTable(&serverTable, TCL_ONE_WORD_KEYS);
279
280    /* Process command line switches. */
281    for (;;) {
282        int c;
283        int option_index = 0;
284        struct option long_options[] = {
285            // name, has_arg, flag, val
286            { 0,0,0,0 },
287        };
288
289        c = getopt_long(argc, argv, "x:f:d", long_options, &option_index);
290        if (c == -1) {
291            break;
292        }
293
294        switch(c) {
295        case 'x':                       /* Number of video cards */
296            maxCards = strtoul(optarg, 0, 0);
297            if ((maxCards < 1) || (maxCards > 10)) {
298                fprintf(stderr, "Bad number of max videocards specified\n");
299                return 1;
300            }
301            break;
302        case 'd':                       /* Debug  */
303            debug = TRUE;
304            break;
305
306        case 'f':                       /* Server file path. */
307            fileName = strdup(optarg);
308            break;
309
310        default:
311            fprintf(stderr,"Don't know what option '%c'.\n", c);
312            Help(argv[0]);
313            exit(1);
314        }
315    }
316
317    if (!debug) {
318        /* Detach this process from the controlling terminal process. The
319         * current directory becomes /tmp and redirect stdin/stdout/stderr to
320         * /dev/null. */
321        if (daemon(0,0) < 0) {
322            ERROR("Can't daemonize nanoscale: %s", strerror(errno));
323            exit(1);
324        }
325    }
326    serverPid = getpid();
327    if (!ParseServersFile(fileName)) {
328        exit(1);
329    }   
330
331    if (serverTable.numEntries == 0) {
332        ERROR("No servers designated.");
333        exit(1);
334    }
335    signal(SIGPIPE, SIG_IGN);
336#ifdef SA_NOCLDWAIT
337    memset(&action, 0, sizeof(action));
338    action.sa_flags = SA_NOCLDWAIT;
339    sigaction(SIGCHLD, &action, 0);
340#else
341    signal(SIGCHLD, SIG_IGN);
342#endif
343
344    /* Build the array of servers listener file descriptors. */
345    FD_ZERO(&serverFds);
346    maxFd = -1;
347    for (hPtr = Tcl_FirstHashEntry(&serverTable, &iter); hPtr != NULL;
348         hPtr = Tcl_NextHashEntry(&iter)) {
349        RenderServer *serverPtr;
350       
351        serverPtr = Tcl_GetHashValue(hPtr);
352        FD_SET(serverPtr->listenerFd, &serverFds);
353        if (serverPtr->listenerFd > maxFd) {
354            maxFd = serverPtr->listenerFd;
355        }
356    }
357
358    for (;;) {
359        fd_set readFds;
360
361        /* Reset using the array of server file descriptors. */
362        memcpy(&readFds, &serverFds, sizeof(serverFds));
363        if (select(maxFd+1, &readFds, NULL, NULL, 0) <= 0) {
364            ERROR("Select failed: %s", strerror(errno));
365            break;                      /* Error on select. */
366        }
367        for (hPtr = Tcl_FirstHashEntry(&serverTable, &iter); hPtr != NULL;
368             hPtr = Tcl_NextHashEntry(&iter)) {
369            RenderServer *serverPtr;
370            pid_t child;
371            int f;
372            socklen_t length;
373            struct sockaddr_in newaddr;
374
375            serverPtr = Tcl_GetHashValue(hPtr);
376            if (!FD_ISSET(serverPtr->listenerFd, &readFds)) {
377                continue;               
378            }
379            /* Rotate the display's screen number.  If we have multiple video
380             * cards, try to spread the jobs out among them.  */
381            screenNum++;
382            if (screenNum >= maxCards) {
383                screenNum = 0;
384            }
385            /* Accept the new connection. */
386            length = sizeof(newaddr);
387#ifdef HAVE_ACCEPT4
388            f = accept4(serverPtr->listenerFd, (struct sockaddr *)&newaddr,
389                        &length, SOCK_CLOEXEC);
390#else
391            f = accept(serverPtr->listenerFd, (struct sockaddr *)&newaddr,
392                       &length);
393#endif
394            if (f < 0) {
395                ERROR("Can't accept server \"%s\": %s", serverPtr->name,
396                      strerror(errno));
397                exit(1);
398            }
399#ifndef HAVE_ACCEPT4
400            int flags = fcntl(f, F_GETFD);
401            flags |= FD_CLOEXEC;
402            if (fcntl(f, F_SETFD, flags) < 0) {
403                ERROR("Can't set FD_CLOEXEC on socket \"%s\": %s",
404                        serverPtr->name, strerror(errno));
405                exit(1);
406            }
407#endif
408            INFO("Connecting \"%s\" to %s\n", serverPtr->name,
409                 inet_ntoa(newaddr.sin_addr));
410
411            /* Fork the new process.  Connect I/O to the new socket. */
412            child = fork();
413            if (child < 0) {
414                ERROR("Can't fork \"%s\": %s", serverPtr->name,
415                      strerror(errno));
416                continue;
417            }
418            if (child == 0) {           /* Child process. */
419                int i;
420                int errFd;
421               
422                umask(0);
423                if ((!debug) && (setsid() < 0)) {
424                    ERROR("Can't setsid \"%s\": %s", serverPtr->name,
425                          strerror(errno));
426                    exit(1);
427                }
428                if ((!debug) && ((chdir("/")) < 0)) {
429                    ERROR("Can't change to root directory for \"%s\": %s",
430                          serverPtr->name, strerror(errno));
431                    exit(1);
432                }
433
434                /* Dup the descriptors and start the server.  */
435
436                if (dup2(f, 0) < 0)  {  /* Stdin */
437                    ERROR("%s: can't dup stdin: %s", serverPtr->name,
438                        strerror(errno));
439                    exit(1);
440                }
441                if (dup2(f, 1) < 0) {   /* Stdout */
442                    ERROR("%s: can't dup stdout: %s", serverPtr->name,
443                          strerror(errno));
444                    exit(1);
445                }
446                errFd = open("/dev/null", O_WRONLY, 0600);
447                if (errFd < 0) {
448                    ERROR("%s: can't open /dev/null for read/write: %s",
449                          serverPtr->name, strerror(errno));
450                    exit(1);
451                }
452                if (dup2(errFd, 2) < 0) { /* Stderr */
453                    ERROR("%s: can't dup stderr for \"%s\": %s",
454                          serverPtr->name, strerror(errno));
455                    exit(1);
456                }
457                for(i = 3; i <= FD_SETSIZE; i++) {
458                    close(i);           /* Close all the other descriptors. */
459                }
460
461                /* Set the screen number in the DISPLAY variable. */
462                display[3] = screenNum + '0';
463                setenv("DISPLAY", display, 1);
464                /* Set the enviroment, if necessary. */
465                for (i = 0; i < serverPtr->numEnvArgs; i += 2) {
466                    setenv(serverPtr->envArgs[i], serverPtr->envArgs[i+1], 1);
467                }
468                INFO("Executing %s: client %s, %s on DISPLAY=%s",
469                        serverPtr->name, inet_ntoa(newaddr.sin_addr),
470                        serverPtr->cmdArgs[0], display);
471                /* Replace the current process with the render server. */
472                execvp(serverPtr->cmdArgs[0], serverPtr->cmdArgs);
473                ERROR("Can't execute \"%s\": %s", serverPtr->cmdArgs[0],
474                      strerror(errno));
475                exit(1);
476            } else {
477                close(f);
478            }
479        }
480    }
481    exit(1);
482}
Note: See TracBrowser for help on using the repository browser.