source: nanoscale/trunk/server.c @ 6716

Last change on this file since 6716 was 6624, checked in by ldelgass, 7 years ago

Some minor cleanups to sync with 1.0 branch

File size: 16.4 KB
Line 
1/* -*- mode: c; c-basic-offset: 4; indent-tabs-mode: nil -*- */
2/* ======================================================================
3 *  Copyright (c) 2004-2016  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#ifdef TCL_CONFIG
27#include <tcl.h>
28#endif
29
30#include "config.h"
31#include "server.h"
32
33static const char *syslogLevels[] = {
34    "emergency",                        /* System is unusable */
35    "alert",                            /* Action must be taken immediately */
36    "critical",                         /* Critical conditions */
37    "error",                            /* Error conditions */
38    "warning",                          /* Warning conditions */
39    "notice",                           /* Normal but significant condition */
40    "info",                             /* Informational */
41    "debug",                            /* Debug-level messages */
42};
43
44static int numServers = 0;
45/* Table of render servers representing services available to
46 * clients.  A new instance is forked and executed each time a
47 * new request is accepted. */
48static RenderServer *serverTable[MAX_SERVERS];
49
50static int debug = FALSE;
51static pid_t serverPid;
52
53static void
54Help(const char *program)
55{
56    fprintf(stderr,
57        "Syntax: %s [-d] [-f serversFile] [-x numVideoCards]\n", program);
58    exit(1);
59}
60
61static void
62InitLog()
63{
64    openlog("nanoscale", LOG_CONS | LOG_PID, LOG_USER);
65}
66
67void
68SysLog(int priority, const char *path, int lineNum, const char* fmt, ...)
69{
70#define MSG_LEN (2047)
71    char message[MSG_LEN+1];
72    const char *s;
73    int length;
74    va_list lst;
75
76    va_start(lst, fmt);
77    s = strrchr(path, '/');
78    if (s == NULL) {
79        s = path;
80    } else {
81        s++;
82    }
83    if (serverPid != getpid()) {
84        length = snprintf(message, MSG_LEN, "(%d) %s: %s:%d ",
85                          serverPid, syslogLevels[priority], s, lineNum);
86    } else {
87        length = snprintf(message, MSG_LEN, "%s: %s:%d ",
88                          syslogLevels[priority], s, lineNum);
89    }
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
99RenderServer *
100NewServer(const char *name)
101{
102    RenderServer *serverPtr;
103
104    serverPtr = malloc(sizeof(RenderServer));
105    memset(serverPtr, 0, sizeof(RenderServer));
106    if (serverPtr == NULL) {
107        ERROR("can't allocate structure: %s", strerror(errno));
108        return NULL;
109    }
110    if (name != NULL) {
111        serverPtr->name = strdup(name);
112    }
113    serverPtr->outputFd = STDOUT_FILENO;
114    serverPtr->inputFd = STDIN_FILENO;
115    serverPtr->combineLogs = TRUE;
116    serverPtr->logStdout = TRUE;
117    serverPtr->logStderr = TRUE;
118    return serverPtr;
119}
120
121static void
122InitServerTable()
123{
124    memset(serverTable, 0, sizeof(serverTable));
125}
126
127int
128main(int argc, char **argv)
129{
130    int si;
131#ifdef SA_NOCLDWAIT
132    struct sigaction action;
133#endif
134    fd_set serverFds;
135    int maxFd;                          /* Highest file descriptor in use. */
136    char display[200];                  /* String used to manage the X
137                                         * DISPLAY variable for each render
138                                         * server instance. */
139    int maxCards;                       /* Maximum number of video cards, each
140                                         * represented by a different X
141                                         * screen.  */
142    int screenNum;                      /* Current X screen number. */
143    const char *fileName;               /* Path to servers file. */
144
145    serverPid = getpid();
146    screenNum = 0;
147    maxCards = 1;
148    fileName = SERVERSFILE;
149    debug = FALSE;
150
151    strcpy(display, ":0.0");
152    InitLog();
153    InitServerTable();
154
155    /* Process command line switches. */
156    for (;;) {
157        int c;
158        int option_index = 0;
159        struct option long_options[] = {
160            // name, has_arg, flag, val
161            { 0,0,0,0 },
162        };
163
164        c = getopt_long(argc, argv, "x:f:d", long_options, &option_index);
165        if (c == -1) {
166            break;
167        }
168
169        switch(c) {
170        case 'x':                       /* Number of video cards */
171            maxCards = strtoul(optarg, 0, 0);
172            if ((maxCards < 1) || (maxCards > 10)) {
173                fprintf(stderr, "Bad number of max videocards specified\n");
174                return 1;
175            }
176            break;
177        case 'd':                       /* Debug  */
178            debug = TRUE;
179            break;
180
181        case 'f':                       /* Server file path. */
182            fileName = strdup(optarg);
183            break;
184
185        default:
186            fprintf(stderr,"Don't know what option '%c'.\n", c);
187            Help(argv[0]);
188            exit(1);
189        }
190    }
191
192    if (!debug) {
193        /* Detach this process from the controlling terminal process. The
194         * current directory becomes /tmp and redirect stdin/stdout/stderr to
195         * /dev/null. */
196        if (daemon(0,0) < 0) {
197            ERROR("Can't daemonize nanoscale: %s", strerror(errno));
198            exit(1);
199        }
200    }
201    serverPid = getpid();
202    if (!ParseServersFile(fileName, &numServers, serverTable)) {
203        ERROR("Exiting on invalid configuration");
204        exit(1);
205    }
206
207    /* Create listening sockets */
208    for (si = 0; si < numServers; si++) {
209        int fd, bool;
210        struct sockaddr_in addr;
211        RenderServer *serverPtr;
212
213        serverPtr = serverTable[si];
214
215        /* Create a socket for listening. */
216        fd = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
217        if (fd < 0) {
218            ERROR("can't create listener socket for \"%s\": %s",
219                  serverPtr->name, strerror(errno));
220            exit(1);
221        }
222        /* If the render server instance should be killed, drop the socket
223           address reservation immediately, don't linger. */
224        bool = TRUE;
225        if (setsockopt(fd, SOL_SOCKET, SO_REUSEADDR, &bool, sizeof(bool)) < 0) {
226            ERROR("can't create set socket option for \"%s\": %s",
227                  serverPtr->name, strerror(errno));
228            exit(1);
229        }
230        /* Bind this address to the socket. */
231        addr.sin_family = AF_INET;
232        addr.sin_port = htons(serverPtr->port);
233        addr.sin_addr.s_addr = htonl(INADDR_ANY);
234        if (bind(fd, (struct sockaddr *)&addr, sizeof(addr)) < 0) {
235            ERROR("can't bind to socket for \"%s\": %s",
236                  serverPtr->name, strerror(errno));
237            exit(1);
238        }
239        /* Listen on the specified port. */
240        if (listen(fd, 5) < 0) {
241            ERROR("can't listen to socket for \"%s\": %s",
242                  serverPtr->name, strerror(errno));
243            exit(1);
244        }
245        serverPtr->listenerFd = fd;
246        INFO("%s listening on %d", serverPtr->name, serverPtr->port);
247    }
248
249    if (numServers == 0) {
250        ERROR("No servers designated.");
251        exit(1);
252    }
253    signal(SIGPIPE, SIG_IGN);
254#ifdef SA_NOCLDWAIT
255    memset(&action, 0, sizeof(action));
256    action.sa_flags = SA_NOCLDWAIT;
257    sigaction(SIGCHLD, &action, 0);
258#else
259    signal(SIGCHLD, SIG_IGN);
260#endif
261
262    /* Build the array of servers listener file descriptors. */
263    FD_ZERO(&serverFds);
264    maxFd = -1;
265    for (si = 0; si < numServers; si++) {
266        RenderServer *serverPtr;
267
268        serverPtr = serverTable[si];
269
270        FD_SET(serverPtr->listenerFd, &serverFds);
271        if (serverPtr->listenerFd > maxFd) {
272            maxFd = serverPtr->listenerFd;
273        }
274    }
275
276    for (;;) {
277        fd_set readFds;
278
279        /* Reset using the array of server file descriptors. */
280        memcpy(&readFds, &serverFds, sizeof(serverFds));
281        if (select(maxFd+1, &readFds, NULL, NULL, 0) <= 0) {
282            ERROR("Select failed: %s", strerror(errno));
283            break;                      /* Error on select. */
284        }
285        for (si = 0; si < numServers; si++) {
286            RenderServer *serverPtr;
287            pid_t child;
288            int sock;
289            socklen_t length;
290            struct sockaddr_in newaddr;
291
292            serverPtr = serverTable[si];
293            if (!FD_ISSET(serverPtr->listenerFd, &readFds)) {
294                continue;
295            }
296            /* Rotate the display's screen number.  If we have multiple video
297             * cards, try to spread the jobs out among them.  */
298            screenNum++;
299            if (screenNum >= maxCards) {
300                screenNum = 0;
301            }
302            /* Accept the new connection. */
303            length = sizeof(newaddr);
304#ifdef HAVE_ACCEPT4
305            sock = accept4(serverPtr->listenerFd, (struct sockaddr *)&newaddr,
306                           &length, SOCK_CLOEXEC);
307#else
308            sock = accept(serverPtr->listenerFd, (struct sockaddr *)&newaddr,
309                          &length);
310#endif
311            if (sock < 0) {
312                ERROR("Can't accept server \"%s\": %s", serverPtr->name,
313                      strerror(errno));
314                exit(1);
315            }
316#ifndef HAVE_ACCEPT4
317            int flags = fcntl(sock, F_GETFD);
318            flags |= FD_CLOEXEC;
319            if (fcntl(sock, F_SETFD, flags) < 0) {
320                ERROR("Can't set FD_CLOEXEC on socket \"%s\": %s",
321                        serverPtr->name, strerror(errno));
322                exit(1);
323            }
324#endif
325            INFO("Connecting \"%s\" to %s\n", serverPtr->name,
326                 inet_ntoa(newaddr.sin_addr));
327
328            /* Fork the new process.  Connect I/O to the new socket. */
329            child = fork();
330            if (child < 0) {
331                ERROR("Can't fork \"%s\": %s", serverPtr->name,
332                      strerror(errno));
333                continue;
334            }
335            if (child == 0) {           /* Child process. */
336                int i;
337
338                if ((!debug) && (setsid() < 0)) {
339                    ERROR("Can't setsid \"%s\": %s", serverPtr->name,
340                          strerror(errno));
341                    exit(1);
342                }
343                if ((!debug) && ((chdir("/")) < 0)) {
344                    ERROR("Can't change to root directory for \"%s\": %s",
345                          serverPtr->name, strerror(errno));
346                    exit(1);
347                }
348                if (serverPtr->combineLogs) {
349                    char path[BUFSIZ];
350                    int newFd;
351
352                    sprintf(path, "%s/%s-%d.log", LOGDIR,
353                        serverPtr->name, getpid());
354                    if (serverPtr->logStdout) {
355                        newFd = open(path, O_WRONLY | O_CREAT | O_TRUNC, 0600);
356                    } else {
357                        newFd = open("/dev/null", O_WRONLY, 0600);
358                    }
359                    if (newFd < 0) {
360                        ERROR("%s: can't open \"%s\": %s", serverPtr->name,
361                              path, strerror(errno));
362                        exit(1);
363                    }
364                    if (dup2(newFd, 1) < 0) {
365                        ERROR("%s: can't dup stdout to \"%s\": %s",
366                              serverPtr->name, path, strerror(errno));
367                        exit(1);
368                    }
369                    if (dup2(newFd, 2) < 0) {
370                        ERROR("%s: can't dup stderr to \"%s\": %s",
371                              serverPtr->name, path, strerror(errno));
372                        exit(1);
373                    }
374                } else {
375                    char path[BUFSIZ];
376                    int newFd;
377
378                    sprintf(path, "%s/%s-%d.stdout", LOGDIR,
379                        serverPtr->name, getpid());
380                    if (serverPtr->logStdout) {
381                        newFd = open(path, O_WRONLY | O_CREAT | O_TRUNC, 0600);
382                    } else {
383                        newFd = open("/dev/null", O_WRONLY, 0600);
384                    }
385                    if (newFd < 0) {
386                        ERROR("%s: can't open \"%s\": %s", serverPtr->name,
387                              path, strerror(errno));
388                        exit(1);
389                    }
390                    if (dup2(newFd, 1) < 0) {
391                        ERROR("%s: can't dup stdout to \"%s\": %s",
392                              serverPtr->name, path, strerror(errno));
393                        exit(1);
394                    }
395                    sprintf(path, "%s/%s-%d.stderr", LOGDIR,
396                        serverPtr->name, getpid());
397                    if (serverPtr->logStderr) {
398                        newFd = open(path, O_WRONLY | O_CREAT | O_TRUNC, 0600);
399                    } else {
400                        newFd = open("/dev/null", O_WRONLY, 0600);
401                    }
402                    if (newFd < 0) {
403                        ERROR("%s: can't open \"%s\": %s", serverPtr->name,
404                              path, strerror(errno));
405                        exit(1);
406                    }
407                    if (dup2(newFd, 2) < 0) {
408                        ERROR("%s: can't dup stderr to \"%s\": %s",
409                              serverPtr->name, path, strerror(errno));
410                        exit(1);
411                    }
412                }
413                /* Make sure to close syslog in case it is using one of
414                 * the file descriptors we are about to close
415                 */
416                closelog();
417                /* Dup the socket to descriptors, e.g. 3 and 4 */
418                if (dup2(sock, serverPtr->inputFd) < 0)  { /* input */
419                    ERROR("%s: can't dup socket to fd %d: %s",
420                          serverPtr->name, serverPtr->inputFd,
421                          strerror(errno));
422                    exit(1);
423                }
424                /* If using a single descriptor for input and output, we don't
425                 * need to call dup2 again
426                 */
427                if (serverPtr->outputFd != serverPtr->inputFd) {
428                    if (dup2(sock, serverPtr->outputFd) < 0) { /* output */
429                        ERROR("%s: can't dup socket to fd %d: %s",
430                              serverPtr->name, serverPtr->outputFd,
431                              strerror(errno));
432                        exit(1);
433                    }
434                }
435                /* Close all the other descriptors. */
436                for (i = 3; i <= FD_SETSIZE; i++) {
437                    if (i != serverPtr->inputFd &&
438                        i != serverPtr->outputFd) {
439                        close(i);
440                    }
441                }
442
443                /* Set the screen number in the DISPLAY variable. */
444                display[3] = screenNum + '0';
445                setenv("DISPLAY", display, 1);
446                /* Don't pollute child's environment with our library path.
447                 * Library path should be explicitly set in config if needed.
448                 */
449                unsetenv("LD_LIBRARY_PATH");
450                /* Set the configured environment */
451                for (i = 0; i < serverPtr->numEnvArgs; i += 2) {
452                    INFO("Env: %s=%s", serverPtr->envArgs[i],
453                         serverPtr->envArgs[i+1]);
454                    setenv(serverPtr->envArgs[i], serverPtr->envArgs[i+1], 1);
455                }
456                {
457                    char *cmd;
458
459                    cmd = MergeArgs(serverPtr->numCmdArgs, serverPtr->cmdArgs);
460                    INFO("Executing %s: client=%s, \"%s\" in=%d out=%d on DISPLAY=%s",
461                         serverPtr->name, inet_ntoa(newaddr.sin_addr),
462                         cmd, serverPtr->inputFd, serverPtr->outputFd, display);
463                    FreeArgs(cmd);
464                    //closelog();
465                    /* Replace the current process with the render server. */
466                    execvp(serverPtr->cmdArgs[0], serverPtr->cmdArgs);
467                    ERROR("Can't execute \"%s\": %s", serverPtr->cmdArgs[0],
468                          strerror(errno));
469                    exit(1);
470                }
471            } else {
472                close(sock);
473            }
474        }
475    }
476    exit(1);
477}
Note: See TracBrowser for help on using the repository browser.