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

Last change on this file since 4108 was 4108, checked in by gah, 10 years ago

fix nanoscale to dup descriptors 3 and 4 respectively for socket in/out instead of 0 and 1. fix servers to use descriptors 3 and 4

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