bootstrap/main.c

696 lines
13 KiB
C

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <strings.h>
#include <signal.h>
#include <stdarg.h>
#include <fcntl.h>
#include <errno.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <sys/stat.h>
#include <sys/sendfile.h>
#include <netinet/in.h>
#include <netinet/tcp.h>
#include <arpa/inet.h>
#include <sys/select.h>
#include <pthread.h>
#ifdef DEBUG
#include <ctype.h>
#endif
#define VERSION "1.3"
#define CRLF "\r\n"
#ifdef DEBUG
#define LISTEN_PORT 80
#define ROOT "/mnt/hd2/af/bootstrap/files/"
#else
#define LISTEN_PORT 80
#define ROOT "/var/bootstrap/"
#endif
/* How to cast fd_set structures on this OS */
#define FD_CAST (fd_set *)
/* The network socket on which we're listening - only written to by main
* thread before other threads are created. */
int listen_sock = -1;
/* Flag indicating reload required. Only accessed using atomic ops. */
int reload = 0;
/* Index page and its lock. */
char *xindex = "index.html";
pthread_mutex_t xindex_lock = PTHREAD_MUTEX_INITIALIZER;
static struct {
char *ext;
char *type;
} mime_types[] = {
{ ".css", "text/css" },
{ ".js", "text/javascript" },
{ ".gif", "image/gif" },
{ ".png", "image/png" },
{ ".html", "text/html" },
{ NULL, "text/html" }
};
/********************************************************************
* A fatal error has occured, log and exit.
*/
void
fatal_error(char *fmt, ...)
{
va_list argp;
va_start(argp, fmt);
vfprintf(stderr, fmt, argp);
va_end(argp);
fprintf(stderr, "\n");
exit(1);
}
#ifdef DEBUG
/********************************************************************
* Log a message.
*/
void
debug(char *fmt, ...)
{
va_list argp;
printf("[%d] ", (int)pthread_self());
va_start(argp, fmt);
vprintf(fmt, argp);
va_end(argp);
printf("\n");
fflush(stdout);
}
void
hexdump(char *s, unsigned long len)
{
unsigned long offset;
if (!s)
{
debug("Hexdump - empty string.");
return;
}
if (!len)
len = strlen(s);
printf("----------------------------------------------------------\n");
printf("Hexdump: (%lu bytes)\n", len);
printf("----------------------------------------------------------\n");
for (offset = 0; offset < len; offset += 16)
{
unsigned long i;
printf("%08lx ", offset);
for (i = offset; i - offset < 16; i++)
{
if (i < len)
printf("%02x", s[i]);
else
printf(" ");
}
printf(" ");
for (i = offset; i < len && i - offset < 16; i++)
printf("%c", isprint((int)s[i]) ? s[i] : '.');
printf("\n");
}
}
#ifdef DEBUGMAX
#define debugmax(...) debug(__VA_ARGS__)
#else
#define debugmax(...)
#endif
#else /* !DEBUG */
#define debug(...)
#define debugmax(...)
#endif /* DEBUG */
/********************************************************************
* Network / socket utility functions
*/
int
write_fd(int fd, char *ptr, int len)
{
if (!len)
len = strlen(ptr);
debugmax("write_fd: %s", ptr);
while (len > 0)
{
struct timeval timeout;
int nbytes;
fd_set fs;
FD_ZERO(&fs);
FD_SET(fd, &fs);
timeout.tv_sec = 10;
timeout.tv_usec = 0;
if (select(fd + 1, FD_CAST NULL, FD_CAST&fs, FD_CAST NULL,
(struct timeval *)&timeout) == 0 ||
!FD_ISSET(fd, &fs))
{
/* Timeout */
debug("write_fd() Timeout waiting for socket.");
errno = ETIMEDOUT;
return -1;
}
nbytes = write(fd, ptr, len);
if (nbytes == -1)
{
if (errno == EINTR)
continue;
if (errno == EAGAIN)
{
sleep(1);
continue;
}
return -1;
}
len -= nbytes, ptr += nbytes;
}
return 0;
}
int
send_fd(int fd, char *fmt, ...)
{
char buf[0x400];
va_list argp;
va_start(argp, fmt);
vsprintf(buf, fmt, argp);
va_end(argp);
return write_fd(fd, buf, strlen(buf));
}
char *
read_fd_line(int fd, int timeout, char *buf, int bufl, int strip)
{
struct timeval tm;
fd_set fs;
char *q;
int i, l;
*buf = '\0';
l = 0;
while (!strchr(buf, '\r') && !strchr(buf, '\n') && l < bufl)
{
FD_ZERO(&fs);
FD_SET(fd, &fs);
tm.tv_sec = timeout;
tm.tv_usec = 100;
if (select(fd + 1, FD_CAST&fs, FD_CAST NULL,
FD_CAST NULL, (struct timeval *)&tm) == 0 ||
!FD_ISSET(fd, &fs))
{
debug("read_fd_line() Timeout when reading response.");
return (char *)NULL;
}
if ((i = read(fd, buf + l, bufl - l)) < 1)
{
debugmax("read_fd_line(): %d", i);
break;
}
l += i;
buf[l] = '\0';
#ifdef DEBUGMAX
debugmax("read_fd_line() %d bytes", i);
hexdump(buf, l);
#endif
}
if (strip && (q = strpbrk(buf, "\n\r")))
*q = '\0';
return buf;
}
void
headers(int fd, char *type, char *extra, int cache)
{
write_fd(fd, "HTTP/1.1 200 OK" CRLF, 0);
if (extra)
write_fd(fd, extra, 0);
if (cache)
{
time_t t = time(NULL) + 3600;
struct tm *my_tm = gmtime(&t);
char expiry[80];
strftime(expiry, sizeof(expiry),
"%a, %d %b %Y %H:%M:%S GMT", my_tm);
send_fd(fd,
"Content-Type: %s" CRLF
"Cache-Control: public" CRLF
"Expires: %s" CRLF,
type, expiry);
}
else
send_fd(fd,
"Content-Type: %s" CRLF
"Pragma: no-cache" CRLF
"Cache-Control: no-cache" CRLF
"Expires: -1" CRLF,
type);
write_fd(fd, CRLF, 2);
}
void
send_file(int fd, char *file, char *type, int chunked, int size, int cache)
{
int ifd;
off_t off = 0;
struct stat st;
debug("send_file(%s, %s)", file, type ? type : "NULL");
if (strstr(file, ".."))
{
debug(".. found in file.");
return;
}
if (type && !strcmp(type, "auto"))
{
int i;
type = "text/html";
for (i = 0; mime_types[i].ext; i++)
{
if (strstr(file, mime_types[i].ext))
{
type = mime_types[i].type;
break;
}
}
}
if ((ifd = open(file, O_RDONLY)) < 0)
{
/* Don't set caching for the 404 response. */
cache = 0;
if ((ifd = open("404.html", O_RDONLY)) < 0)
{
headers(fd, "text/plain", NULL, 0);
send_fd(fd, "Error opening %s\n", file);
return;
}
}
if (type)
headers(fd, type, NULL, cache);
fstat(ifd, &st);
if (size <= 0 || size > st.st_size)
size = st.st_size;
if (chunked)
send_fd(fd, "%x" CRLF, size);
sendfile(fd, ifd, &off, size);
if (chunked)
write_fd(fd, CRLF, 2);
close(ifd);
}
void
install_webif(int fd)
{
char buf[0x111];
FILE *fp;
headers(fd, "text/html", "Transfer-Encoding: chunked" CRLF, 0);
/* Send a dummy 257 byte block of space characters to kick the
* browser into life. */
memset(buf, ' ', sizeof(buf));
buf[0x101] = '\0';
send_fd(fd, "101" CRLF "%s" CRLF, buf);
send_file(fd, "install.html", NULL, 1, 0, 0);
if ((fp = popen("./bin/install_webif", "r")))
{
char buf[0x400];
while (!feof(fp) && fgets(buf, sizeof(buf), fp))
{
send_fd(fd, "%x" CRLF, strlen(buf) + 5);
write_fd(fd, buf, 0);
write_fd(fd, "<br/>", 0);
write_fd(fd, CRLF, 2);
}
pclose(fp);
}
send_file(fd, "installed.html", NULL, 1, 0, 0);
write_fd(fd, "0" CRLF CRLF, 5);
}
void
send_image(int fd, char *img)
{
char buf[0x100];
sprintf(buf, "/opt/share/images/blue/%s", img);
send_file(fd, buf, "image/png", 0, 0, 1);
}
void *
handle_request(void *arg)
{
char line[0x100];
char req[80];
int fd;
fd = (int)(unsigned long)arg;
debug("New thread starting for fd %d", fd);
if (!read_fd_line(fd, 10, line, sizeof(line), 1))
goto cleanup;
debug("Got request line: [%s]", line);
*req = '\0';
if (!sscanf(line, "GET %80s HTTP", req))
{
debug("Could not extract req from first line.");
goto cleanup;
}
else
{
debug("Extracted req: %s", req);
}
/* Skip additional request headers. */
do
{
if (!read_fd_line(fd, 0, line, sizeof(line), 1))
break;
debugmax("Skipping request line: [%s]", line);
} while (strlen(line));
pthread_mutex_lock(&xindex_lock);
if (!strncmp(req, "/i/", 3))
send_image(fd, req + 3);
else if (!strcmp(xindex, "index.html") && !strncmp(req, "/install", 8))
install_webif(fd);
else if (!strcmp(xindex, "safe.html") && !strncmp(req, "/nosafe", 7))
{
unlink("/var/lib/humaxtv/mod/safemode");
send_file(fd, "nosafe.html", "text/html", 0, 0, 0);
}
else if (!strcmp(xindex, "maint.html") && !strncmp(req, "/exit", 5))
{
send_file(fd, "nomaint.html", "text/html", 0, 0, 0);
sleep(5);
system("./bin/reboot");
}
else if (strcmp(xindex, "init.html") && !strncmp(req, "/reboot", 7))
{
send_file(fd, "rebooting.html", "text/html", 0, 0, 0);
sleep(5);
system("./bin/reboot");
}
else if (!strcmp(req, "/ping"))
{
if (!strcmp(xindex, "init.html"))
write_fd(fd, "HTTP/1.1 204 OK" CRLF CRLF, 0);
else
send_file(fd, "pong.html", "text/html", 0, 0, 0);
}
else if (!strcmp(req, "/") || !strncmp(req, "/?", 2))
send_file(fd, xindex, "text/html", 0, 0, 0);
else
send_file(fd, req + 1, "auto", 0, 0, 1);
pthread_mutex_unlock(&xindex_lock);
cleanup:
shutdown(fd, SHUT_RDWR);
close(fd);
return 0;
}
/********************************************************************
* Main program
*/
int
nonblock(int fd)
{
int tmp = 1;
#if defined(_AIX) || defined(HPUX)
if (ioctl(fd, FIONBIO, &tmp) == -1)
return 0;
#else
fcntl(fd, F_SETOWN, getpid());
if ((tmp = fcntl(fd, F_GETFL)) == -1)
tmp = 0;
tmp |= O_NDELAY;
if (fcntl(fd, F_SETFL, tmp) == -1)
return 0;
#endif /* _AIX || HPUX */
return 1;
}
static int
setup_socket(int port_number, int type, int family)
{
struct sockaddr_in sin;
int tmp, sockfd;
memset((char *)&sin, '\0', sizeof(sin));
sin.sin_port = htons((unsigned short)port_number);
sin.sin_family = family;
sin.sin_addr.s_addr = INADDR_ANY;
if ((sockfd = socket(sin.sin_family, type, 0)) == -1)
fatal_error("Could not create socket.");
tmp = 1;
if (setsockopt(sockfd, SOL_SOCKET, SO_REUSEADDR,
(char *)&tmp, sizeof(tmp)) < 0)
fatal_error("Could not set socket re-useable.");
if (bind(sockfd, (struct sockaddr *)&sin, sizeof(sin)) == -1)
fatal_error("Could not bind socket.");
if (type == SOCK_STREAM && listen(sockfd, 7) == -1)
fatal_error("Could not listen on port");
nonblock(sockfd);
return sockfd;
}
void
sighup_handler(int sig __attribute__((unused)))
{
/* Any thread can receive this signal so use atomic or to set
* the global reload variable. */
__sync_fetch_and_or(&reload, 1);
}
static void
setup_signals()
{
struct sigaction sa;
signal(SIGPIPE, SIG_IGN);
memset(&sa, '\0', sizeof(sa));
sa.sa_handler = sighup_handler;
/*sa.sa_flags = SA_RESTART; */
if (sigaction(SIGHUP, &sa, NULL) == -1)
perror("sigaction");
}
void
check_reload()
{
char buf[0x100];
int fd, n;
char *p;
if (!__sync_bool_compare_and_swap(&reload, 1, 0))
return;
debug("Reload requested.");
if ((fd = open("/tmp/.bootstrap", O_RDONLY)) == -1)
{
perror("open");
return;
}
if ((n = read(fd, buf, sizeof(buf) - 1)) > 0)
{
buf[n] = '\0';
if ((p = strpbrk(buf, "\n\r ")))
*p = '\0';
pthread_mutex_lock(&xindex_lock);
free(xindex);
xindex = strdup(buf);
pthread_mutex_unlock(&xindex_lock);
debug("Landing page set to '%s'", xindex);
}
else if (n == -1)
perror("read");
close(fd);
unlink("/tmp/.bootstrap");
}
int
main(int argc, char **argv)
{
#ifndef DEBUG
int fd;
switch(fork())
{
case -1:
perror("fork");
return -1;
case 0: /* child */
break;
default: /* parent */
return 0;
}
#ifdef TIOCNOTTY
ioctl(fileno(stdin), TIOCNOTTY);
#endif
freopen("/dev/null", "r", stdin);
fd = open("/dev/null", O_WRONLY, 0666);
dup2(fd, fileno(stdout));
dup2(fd, fileno(stderr));
close(fd);
setsid();
#endif /* DEBUG */
if (argc > 1)
xindex = argv[1];
xindex = strdup(xindex);
debug("Installing signal handlers...");
setup_signals();
chdir(ROOT);
listen_sock = setup_socket(LISTEN_PORT, SOCK_STREAM, AF_INET);
debug("Listening...");
/* Main loop - wait for a connection on the socket then spawn a
* child thread to perform the health check.
*/
for (;;)
{
struct timeval delay;
fd_set readfds;
fd_set exfds;
FD_ZERO(&readfds);
FD_ZERO(&exfds);
FD_SET(listen_sock, &readfds);
#ifdef DEBUG
delay.tv_sec = 30;
#else
delay.tv_sec = 1200;
#endif
delay.tv_usec = 0;
check_reload();
if (select(listen_sock + 1, FD_CAST&readfds, FD_CAST NULL,
FD_CAST&exfds, (struct timeval *)&delay) < 0)
{
debug("select() error: %s", strerror(errno));
}
else if (FD_ISSET(listen_sock, &exfds))
{
debug("Socket excpetion: %s", strerror(errno));
}
else if (FD_ISSET(listen_sock, &readfds))
{
/* New connection, accept and spawn child. */
struct sockaddr_in addr;
int newfd;
socklen_t length;
pthread_t tid;
length = sizeof(addr);
if ((newfd = accept(listen_sock,
(struct sockaddr *)&addr, &length)) < 0)
{
debug("Accept error: %s", strerror(errno));
continue;
}
debug("Connection from %s", inet_ntoa(addr.sin_addr));
nonblock(newfd);
if (pthread_create(&tid, NULL, handle_request,
(void *)(unsigned long)newfd))
{
debug("Async thread creation failed - %s",
strerror(errno));
}
else
{
debug("Created thread %d", (int)tid);
pthread_detach(tid);
}
}
debug("Main Loop poll.");
}
return 0;
}