source: nanoscale/trunk/server.c @ 6369

Last change on this file since 6369 was 4241, checked in by ldelgass, 11 years ago

Don't clear umask on child processes

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