blob: 86029c5e15f42b0efe3672f70b5b85ad3521b636 [file] [log] [blame]
/* Copyright (C) 2009 Intel Corporation
Author: Andi Kleen
Simple event-driven unix network server for client access.
Process commands and buffer output.
mcelog is free software; you can redistribute it and/or
modify it under the terms of the GNU General Public
License as published by the Free Software Foundation; version
2.
mcelog 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 GNU
General Public License for more details.
You should find a copy of v2 of the GNU General Public License somewhere
on your Linux system; if not, write to the Free Software Foundation,
Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */
#define _GNU_SOURCE 1
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <errno.h>
#include <assert.h>
#include <ctype.h>
#include <unistd.h>
#include <sys/socket.h>
#include <sys/ioctl.h>
#include <sys/un.h>
#include <signal.h>
#include <setjmp.h>
#include "mcelog.h"
#include "server.h"
#include "eventloop.h"
#include "config.h"
#include "memdb.h"
#include "memutil.h"
#include "paths.h"
#include "page.h"
#define PAIR(x) x, sizeof(x)-1
struct clientcon {
char *inbuf; /* 0 terminated */
char *inptr;
char *outbuf;
size_t outcur;
size_t outlen;
};
static char *client_path = SOCKET_PATH;
static int initial_ping_timeout = 2;
static struct config_cred acc = { .uid = 0, .gid = -1U };
static void free_outbuf(struct clientcon *cc)
{
free(cc->outbuf);
cc->outbuf = NULL;
cc->outcur = cc->outlen = 0;
}
static void free_inbuf(struct clientcon *cc)
{
free(cc->inbuf);
cc->inbuf = NULL;
cc->inptr = NULL;
}
static void free_cc(struct clientcon *cc)
{
free(cc->outbuf);
free(cc->inbuf);
free(cc);
}
static void sendstring(int fd, char *str)
{
(void)send(fd, str, strlen(str), MSG_DONTWAIT|MSG_NOSIGNAL);
}
static void dispatch_dump(FILE *fh, char *s)
{
char *p;
enum printflags printflags = 0;
while ((p = strsep(&s, " ")) != NULL) {
if (!strcmp(p, "dump"))
;
else if (!strcmp(p, "bios"))
printflags |= DUMP_BIOS;
else if (!strcmp(p, "all"))
printflags |= DUMP_ALL;
else
fprintf(fh, "Unknown dump parameter\n");
}
dump_memory_errors(fh, printflags);
fprintf(fh, "done\n");
}
static void dispatch_pages(FILE *fh)
{
dump_page_errors(fh);
fprintf(fh, "done\n");
}
static void dispatch_commands(char *line, FILE *fh)
{
char *s;
while ((s = strsep(&line, "\n")) != NULL) {
while (isspace(*s))
line++;
if (!strncmp(s, "dump", 4))
dispatch_dump(fh, s);
else if (!strncmp(s, "pages", 5))
dispatch_pages(fh);
else if (!strcmp(s, "ping"))
fprintf(fh, "pong\n");
else if (*s != 0)
fprintf(fh, "Unknown command\n");
}
}
/* assumes commands don't cross records */
static void process_cmd(struct clientcon *cc)
{
FILE *fh;
assert(cc->outbuf == NULL);
fh = open_memstream(&cc->outbuf, &cc->outlen);
if (!fh)
Enomem();
cc->outcur = 0;
dispatch_commands(cc->inbuf, fh);
if (ferror(fh) || fclose(fh) != 0)
Enomem();
}
/* check if client is allowed to access */
static int access_check(int fd, struct msghdr *msg)
{
struct cmsghdr *cmsg;
struct ucred *uc;
/* check credentials */
cmsg = CMSG_FIRSTHDR(msg);
if (cmsg == NULL ||
cmsg->cmsg_level != SOL_SOCKET ||
cmsg->cmsg_type != SCM_CREDENTIALS) {
Eprintf("Did not receive credentials over client unix socket %p\n",
cmsg);
return -1;
}
uc = (struct ucred *)CMSG_DATA(cmsg);
if (uc->uid == 0 ||
(acc.uid != -1U && uc->uid == acc.uid) ||
(acc.gid != -1U && uc->gid == acc.gid))
return 0;
Eprintf("rejected client access from pid:%u uid:%u gid:%u\n",
uc->pid, uc->uid, uc->gid);
sendstring(fd, "permission denied\n");
return -1;
}
/* retrieve commands from client */
static int client_input(int fd, struct clientcon *cc)
{
char ctlbuf[CMSG_SPACE(sizeof(struct ucred))];
struct iovec miov;
struct msghdr msg = {
.msg_iov = &miov,
.msg_iovlen = 1,
.msg_control = ctlbuf,
.msg_controllen = sizeof(ctlbuf),
};
int n, n2;
assert(cc->inbuf == NULL);
if (ioctl(fd, FIONREAD, &n) < 0)
return -1;
if (n == 0)
return 0;
cc->inbuf = xalloc_nonzero(n + 1);
cc->inbuf[n] = 0;
cc->inptr = cc->inbuf;
miov.iov_base = cc->inbuf;
miov.iov_len = n;
n2 = recvmsg(fd, &msg, 0);
if (n2 < n)
return -1;
return access_check(fd, &msg) == 0 ? n : -1;
}
/* process input/out on client socket */
static void client_event(struct pollfd *pfd, void *data)
{
int events = pfd->revents;
struct clientcon *cc = (struct clientcon *)data;
int n;
if (events & ~(POLLIN|POLLOUT)) /* error/close */
goto error;
if (events & POLLOUT) {
if (cc->outcur < cc->outlen) {
n = send(pfd->fd, cc->outbuf + cc->outcur,
cc->outlen - cc->outcur,
MSG_DONTWAIT|MSG_NOSIGNAL);
if (n < 0) {
/* EAGAIN here? but should not happen */
goto error;
}
cc->outcur += n;
}
if (cc->outcur == cc->outlen)
free_outbuf(cc);
}
if (events & POLLIN) {
n = client_input(pfd->fd, cc);
if (n < 0)
goto error;
process_cmd(cc);
free_inbuf(cc);
}
pfd->events = cc->outbuf ? POLLOUT : POLLIN;
return;
error:
if (pfd->revents & POLLERR)
SYSERRprintf("error while reading from client");
close(pfd->fd);
unregister_pollcb(pfd);
free_cc(cc);
}
/* accept a new client */
static void client_accept(struct pollfd *pfd, void *data)
{
struct clientcon *cc = NULL;
int nfd = accept(pfd->fd, NULL, 0);
int on;
if (nfd < 0) {
SYSERRprintf("accept failed on client socket");
return;
}
on = 1;
if (setsockopt(nfd, SOL_SOCKET, SO_PASSCRED, &on, sizeof(on)) < 0) {
SYSERRprintf("Cannot enable credentials passing on client socket");
goto cleanup;
}
cc = xalloc(sizeof(struct clientcon));
if (register_pollcb(nfd, POLLIN, client_event, cc) < 0) {
sendstring(nfd, "mcelog server too busy\n");
goto cleanup;
}
return;
cleanup:
free(cc);
close(nfd);
}
static void server_config(void)
{
char *s;
long v;
config_cred("server", "client", &acc);
if ((s = config_string("server", "socket-path")) != NULL)
client_path = s;
if (config_number("server", "initial-ping-timeout", "%u", &v) == 0)
initial_ping_timeout = v;
}
static sigjmp_buf ping_timeout_ctx;
static void ping_timeout(int sig)
{
siglongjmp(ping_timeout_ctx, 1);
}
/* server still running? */
static int server_ping(struct sockaddr_un *un)
{
struct sigaction oldsa;
struct sigaction sa = { .sa_handler = ping_timeout };
int ret, n;
char buf[10];
int fd = socket(PF_UNIX, SOCK_STREAM, 0);
if (fd < 0)
return 0;
sigaction(SIGALRM, &sa, &oldsa);
if (sigsetjmp(ping_timeout_ctx, 1) == 0) {
ret = -1;
alarm(initial_ping_timeout);
if (connect(fd, un, sizeof(struct sockaddr_un)) < 0)
goto cleanup;
if (write(fd, PAIR("ping\n")) < 0)
goto cleanup;
if ((n = read(fd, buf, 10)) < 0)
goto cleanup;
if (n == 5 && !memcmp(buf, "pong\n", 5))
ret = 0;
} else
ret = -1;
cleanup:
sigaction(SIGALRM, &oldsa, NULL);
alarm(0);
close(fd);
return ret;
}
void server_setup(void)
{
int fd;
struct sockaddr_un adr;
int on;
server_config();
if (client_path[0] == 0)
return;
if (strlen(client_path) >= sizeof(adr.sun_path) - 1) {
Eprintf("Client socket path `%s' too long for unix socket",
client_path);
return;
}
memset(&adr, 0, sizeof(struct sockaddr_un));
adr.sun_family = AF_UNIX;
strncpy(adr.sun_path, client_path, sizeof(adr.sun_path) - 1);
if (access(client_path, F_OK) == 0) {
if (server_ping(&adr) == 0) {
Eprintf("mcelog server already running\n");
exit(1);
}
unlink(client_path);
}
fd = socket(PF_UNIX, SOCK_STREAM, 0);
if (fd < 0) {
SYSERRprintf("cannot open listening socket");
return;
}
if (bind(fd, (struct sockaddr *)&adr, sizeof(struct sockaddr_un)) < 0) {
SYSERRprintf("Cannot bind to client unix socket `%s'",
client_path);
goto cleanup;
}
listen(fd, 10);
/* Set SO_PASSCRED to avoid race with client connecting too fast */
/* Ignore error for old kernels */
on = 1;
setsockopt(fd, SOL_SOCKET, SO_PASSCRED, &on, sizeof(on));
register_pollcb(fd, POLLIN, client_accept, NULL);
return;
cleanup:
close(fd);
exit(1);
}