source: trunk/packages/vizservers/nanoscale/server2.c @ 2384

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