Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with
or
.
Download ZIP
Fetching contributors…

Cannot retrieve contributors at this time

511 lines (474 sloc) 14.443 kb
/*
* pseudo_server.c, pseudo's server-side logic and message handling
*
* Copyright (c) 2008-2010 Wind River Systems, Inc.
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the Lesser GNU General Public License version 2.1 as
* published by the Free Software Foundation.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
* See the Lesser GNU General Public License for more details.
*
* You should have received a copy of the Lesser GNU General Public License
* version 2.1 along with this program; if not, write to the Free Software
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
*
*/
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <time.h>
#include <errno.h>
#include <unistd.h>
#include <fcntl.h>
#include <sys/select.h>
#include <sys/time.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <sys/un.h>
#include <sys/stat.h>
#include <sys/wait.h>
#include <signal.h>
#include "pseudo.h"
#include "pseudo_ipc.h"
#include "pseudo_server.h"
#include "pseudo_client.h"
#include "pseudo_db.h"
static int listen_fd = -1;
typedef struct {
int fd;
pid_t pid;
char *tag;
char *program;
} pseudo_client_t;
pseudo_client_t *clients;
/* active_clients: Number of clients we actually have right now.
* highest_client: Highest index into clients table of an active client.
* max_clients: Size of table.
*/
static int active_clients = 0, highest_client = 0, max_clients = 0;
#define LOOP_DELAY 2
int pseudo_server_timeout = 30;
static int die_peacefully = 0;
static int die_forcefully = 0;
/* when the client is linked with pseudo_wrappers, these are defined there.
* when it is linked with pseudo_server, though, we have to provide different
* versions (pseudo_wrappers must not be linked with the server, or Bad Things
* happen).
*/
void pseudo_magic(void) { }
void pseudo_antimagic(void) { }
void
quit_now(int signal) {
pseudo_diag("Received signal %d, quitting.\n", signal);
die_forcefully = 1;
}
static int messages = 0;
static struct timeval message_time = { .tv_sec = 0 };
static void pseudo_server_loop(void);
static int
pseudo_server_write_pid(pid_t pid) {
char *pseudo_path;
FILE *fp;
pseudo_path = pseudo_localstatedir_path(PSEUDO_PIDFILE);
if (!pseudo_path) {
pseudo_diag("Couldn't get path for prefix/%s\n", PSEUDO_PIDFILE);
return 1;
}
fp = fopen(pseudo_path, "w");
if (!fp) {
pseudo_diag("Couldn't open %s: %s\n",
pseudo_path, strerror(errno));
return 1;
}
fprintf(fp, "%lld\n", (long long) pid);
fclose(fp);
free(pseudo_path);
return 0;
}
int
pseudo_server_start(int daemonize) {
struct sockaddr_un sun = { .sun_family = AF_UNIX, .sun_path = PSEUDO_SOCKET };
char *pseudo_path;
int rc, newfd;
#if PSEUDO_PORT_DARWIN
sun.sun_len = strlen(PSEUDO_SOCKET) + 1;
#endif
listen_fd = socket(PF_UNIX, SOCK_STREAM, 0);
if (listen_fd < 0) {
pseudo_diag("couldn't create listening socket: %s\n", strerror(errno));
return 1;
}
if (listen_fd <= 2) {
newfd = fcntl(listen_fd, F_DUPFD, 3);
if (newfd < 0) {
pseudo_diag("couldn't dup listening socket: %s\n", strerror(errno));
close(listen_fd);
return 1;
} else {
close(listen_fd);
listen_fd = newfd;
}
}
/* cd to the data directory */
pseudo_path = pseudo_localstatedir_path(NULL);
if (!pseudo_path || chdir(pseudo_path) == -1) {
pseudo_diag("can't get to '%s': %s\n",
pseudo_path, strerror(errno));
return 1;
}
free(pseudo_path);
/* remove existing socket -- if it exists */
unlink(sun.sun_path);
if (bind(listen_fd, (struct sockaddr *) &sun, sizeof(sun)) == -1) {
pseudo_diag("couldn't bind listening socket: %s\n", strerror(errno));
return 1;
}
if (listen(listen_fd, 5) == -1) {
pseudo_diag("couldn't listen on socket: %s\n", strerror(errno));
return 1;
}
if (daemonize) {
if ((rc = fork()) != 0) {
if (rc == -1) {
pseudo_diag("couldn't spawn server: %s\n", strerror(errno));
return 0;
}
pseudo_debug(2, "started server, pid %d\n", rc);
close(listen_fd);
/* Parent writes pid, that way it's always correct */
return pseudo_server_write_pid(rc);
}
/* In child */
pseudo_new_pid();
fclose(stdin);
fclose(stdout);
pseudo_logfile(PSEUDO_LOGFILE);
} else {
/* Write the pid if we don't daemonize */
pseudo_server_write_pid(getpid());
}
setsid();
signal(SIGHUP, quit_now);
signal(SIGINT, quit_now);
signal(SIGALRM, quit_now);
signal(SIGQUIT, quit_now);
signal(SIGTERM, quit_now);
pseudo_server_loop();
return 0;
}
/* mess with internal tables as needed */
static void
open_client(int fd) {
pseudo_client_t *new_clients;
int i;
/* if possible, use first open client slot */
for (i = 0; i < max_clients; ++i) {
if (clients[i].fd == -1) {
pseudo_debug(2, "reusing client %d for fd %d\n", i, fd);
clients[i].fd = fd;
clients[i].pid = 0;
clients[i].tag = NULL;
clients[i].program = NULL;
++active_clients;
if (i > highest_client)
highest_client = i;
return;
}
}
/* otherwise, allocate a new one */
new_clients = malloc(sizeof(*new_clients) * (max_clients + 16));
if (new_clients) {
memcpy(new_clients, clients, max_clients * sizeof(*clients));
free(clients);
for (i = max_clients; i < max_clients + 16; ++i) {
new_clients[i].fd = -1;
new_clients[i].pid = 0;
new_clients[i].tag = NULL;
new_clients[i].program = NULL;
}
clients = new_clients;
clients[max_clients].fd = fd;
clients[max_clients].pid = 0;
clients[max_clients].tag = NULL;
clients[max_clients].program = NULL;
highest_client = max_clients + 1;
max_clients += 16;
++active_clients;
} else {
pseudo_diag("error allocating new client, fd %d\n", fd);
close(fd);
}
}
/* clear pid/fd. If this was the highest client, iterate downwards looking
* for a lower one to be the new highest client.
*/
static void
close_client(int client) {
pseudo_debug(2, "lost client %d [%d], closing fd %d\n", client,
clients[client].pid, clients[client].fd);
/* client went away... */
if (client > highest_client || client <= 0) {
pseudo_diag("tried to close client %d (highest is %d)\n",
client, highest_client);
return;
}
close(clients[client].fd);
clients[client].fd = -1;
free(clients[client].tag);
free(clients[client].program);
clients[client].pid = 0;
clients[client].tag = NULL;
clients[client].program = NULL;
--active_clients;
if (client == highest_client)
while (clients[highest_client].fd != -1 && highest_client > 0)
--highest_client;
}
/* Actually process a request.
*/
static int
serve_client(int i) {
pseudo_msg_t *in;
int rc;
pseudo_debug(2, "message from client %d [%d:%s - %s] fd %d\n",
i, (int) clients[i].pid,
clients[i].program ? clients[i].program : "???",
clients[i].tag ? clients[i].tag : "NO TAG",
clients[i].fd);
in = pseudo_msg_receive(clients[i].fd);
if (in) {
char *response_path = 0;
pseudo_debug(4, "got a message (%d): %s\n", in->type, (in->pathlen ? in->path : "<no path>"));
/* handle incoming ping */
if (in->type == PSEUDO_MSG_PING && !clients[i].pid) {
pseudo_debug(2, "new client: %d -> %d",
i, in->client);
clients[i].pid = in->client;
if (in->pathlen) {
size_t proglen;
proglen = strlen(in->path);
pseudo_debug(2, " <%s>", in->path);
free(clients[i].program);
clients[i].program = malloc(proglen + 1);
if (clients[i].program) {
snprintf(clients[i].program, proglen + 1, "%s", in->path);
}
if (in->pathlen > proglen) {
pseudo_debug(2, " [%s]", in->path + proglen + 1);
clients[i].tag = malloc(in->pathlen - proglen);
if (clients[i].tag)
snprintf(clients[i].tag, in->pathlen - proglen,
"%s", in->path + proglen + 1);
}
}
pseudo_debug(2, "\n");
}
/* sanity-check client ID */
if (in->client != clients[i].pid) {
pseudo_debug(1, "uh-oh, expected pid %d for client %d, got %d\n",
(int) clients[i].pid, i, in->client);
}
/* regular requests are processed in place by
* pseudo_server_response.
*/
if (in->type != PSEUDO_MSG_SHUTDOWN) {
if (pseudo_server_response(in, clients[i].program, clients[i].tag)) {
in->type = PSEUDO_MSG_NAK;
} else {
in->type = PSEUDO_MSG_ACK;
pseudo_debug(4, "response: %d (%s)\n",
in->result, pseudo_res_name(in->result));
}
/* no path in response */
in->pathlen = 0;
in->client = i;
} else {
/* the server's listen fd is "a client", and
* so is the program connecting to request a shutdown.
* it should never be less than 2, but crazy things
* happen. >2 implies some other active client,
* though.
*/
if (active_clients > 2) {
int j;
char *s;
response_path = malloc(8 * active_clients);
in->type = PSEUDO_MSG_NAK;
in->fd = active_clients - 2;
s = response_path;
for (j = 1; j <= highest_client; ++j) {
if (clients[j].fd != -1 && j != i) {
s += snprintf(s, 8, "%d ", (int) clients[j].pid);
}
}
in->pathlen = (s - response_path) + 1;
/* exit quickly once clients go away, though */
pseudo_server_timeout = 1;
} else {
in->type = PSEUDO_MSG_ACK;
in->pathlen = 0;
in->client = i;
die_peacefully = 1;
}
}
if ((rc = pseudo_msg_send(clients[i].fd, in, -1, response_path)) != 0)
pseudo_debug(1, "failed to send response to client %d [%d]: %d (%s)\n",
i, (int) clients[i].pid, rc, strerror(errno));
rc = in->op;
free(response_path);
return rc;
} else {
/* this should not be happening, but the exceptions aren't
* being detected in select() for some reason.
*/
pseudo_debug(2, "client %d: no message\n", (int) clients[i].pid);
close_client(i);
return 0;
}
}
/* get clients, handle messages, shut down.
* This doesn't actually do any work, it just calls a ton of things which
* do work.
*/
static void
pseudo_server_loop(void) {
struct sockaddr_un client;
socklen_t len;
fd_set reads, writes, events;
int max_fd, current_clients;
struct timeval timeout;
int i;
int rc;
int fd;
int loop_timeout = pseudo_server_timeout;
clients = malloc(16 * sizeof(*clients));
clients[0].fd = listen_fd;
clients[0].pid = getpid();
for (i = 1; i < 16; ++i) {
clients[i].fd = -1;
clients[i].pid = 0;
clients[i].tag = NULL;
clients[i].program = NULL;
}
active_clients = 1;
max_clients = 16;
highest_client = 0;
pseudo_debug(2, "server loop started.\n");
if (listen_fd < 0) {
pseudo_diag("got into loop with no valid listen fd.\n");
exit(1);
}
pdb_log_msg(SEVERITY_INFO, NULL, NULL, NULL, "server started (pid %d)", getpid());
FD_ZERO(&reads);
FD_ZERO(&writes);
FD_ZERO(&events);
FD_SET(clients[0].fd, &reads);
FD_SET(clients[0].fd, &events);
max_fd = clients[0].fd;
timeout = (struct timeval) { .tv_sec = LOOP_DELAY, .tv_usec = 0 };
/* EINTR tends to come from profiling, so it is not a good reason to
* exit; other signals are caught and set the flag causing a graceful
* exit. */
while ((rc = select(max_fd + 1, &reads, &writes, &events, &timeout)) >= 0 || (errno == EINTR)) {
if (rc == 0 || (rc == -1 && errno == EINTR)) {
/* If there's no clients, start timing out. If there
* are active clients, never time out.
*/
if (active_clients == 1) {
loop_timeout -= LOOP_DELAY;
if (loop_timeout <= 0) {
pseudo_debug(1, "no more clients, got bored.\n");
die_peacefully = 1;
} else {
/* display this if not exiting */
pseudo_debug(1, "%d messages handled in %.4f seconds\n",
messages,
(double) message_time.tv_sec +
(double) message_time.tv_usec / 1000000.0);
}
}
} else if (rc > 0) {
loop_timeout = pseudo_server_timeout;
for (i = 1; i <= highest_client; ++i) {
if (clients[i].fd == -1)
continue;
if (FD_ISSET(clients[i].fd, &events)) {
/* this should happen but doesn't... */
close_client(i);
} else if (FD_ISSET(clients[i].fd, &reads)) {
struct timeval tv1, tv2;
int op;
gettimeofday(&tv1, NULL);
op = serve_client(i);
gettimeofday(&tv2, NULL);
++messages;
message_time.tv_sec += (tv2.tv_sec - tv1.tv_sec);
message_time.tv_usec += (tv2.tv_usec - tv1.tv_usec);
if (message_time.tv_usec < 0) {
message_time.tv_usec += 1000000;
--message_time.tv_sec;
} else while (message_time.tv_usec > 1000000) {
message_time.tv_usec -= 1000000;
++message_time.tv_sec;
}
}
if (die_forcefully)
break;
}
if (!(die_peacefully || die_forcefully) &&
(FD_ISSET(clients[0].fd, &events) ||
FD_ISSET(clients[0].fd, &reads))) {
len = sizeof(client);
if ((fd = accept(listen_fd, (struct sockaddr *) &client, &len)) != -1) {
pseudo_debug(2, "new client fd %d\n", fd);
open_client(fd);
}
}
pseudo_debug(2, "server loop complete [%d clients left]\n", active_clients);
}
if (die_peacefully || die_forcefully) {
pseudo_debug(2, "quitting.\n");
pseudo_debug(1, "server %d exiting: handled %d messages in %.4f seconds\n",
getpid(), messages,
(double) message_time.tv_sec +
(double) message_time.tv_usec / 1000000.0);
pdb_log_msg(SEVERITY_INFO, NULL, NULL, NULL, "server %d exiting: handled %d messages in %.4f seconds",
getpid(), messages,
(double) message_time.tv_sec +
(double) message_time.tv_usec / 1000000.0);
close(clients[0].fd);
exit(0);
}
FD_ZERO(&reads);
FD_ZERO(&writes);
FD_ZERO(&events);
FD_SET(clients[0].fd, &reads);
FD_SET(clients[0].fd, &events);
max_fd = clients[0].fd;
/* current_clients is a sanity check; note that for
* purposes of select(), the server is one of the fds,
* and thus, "a client".
*/
current_clients = 1;
for (i = 1; i <= highest_client; ++i) {
if (clients[i].fd != -1) {
++current_clients;
FD_SET(clients[i].fd, &reads);
FD_SET(clients[i].fd, &events);
if (clients[i].fd > max_fd)
max_fd = clients[i].fd;
}
}
if (current_clients != active_clients) {
pseudo_debug(1, "miscount of current clients (%d) against active_clients (%d)?\n",
current_clients, active_clients);
}
/* reinitialize timeout because Linux select alters it */
timeout = (struct timeval) { .tv_sec = LOOP_DELAY, .tv_usec = 0 };
}
pseudo_diag("select failed: %s\n", strerror(errno));
}
Jump to Line
Something went wrong with that request. Please try again.