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

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