Blame | Last modification | View Log | RSS feed
#include "csapp.h"
#include "threadpool_exec.h"
#include "list.h"
#define THREAD_POOL_SIZE 10
// Struct for passing data to threads in the thread pool
struct parse_struct {
int connfd;
char *rootdir;
};
void * runloop(void *arg);
void allocanon(void);
void freeanon(void);
void parse_request(struct parse_struct *p);
void read_requesthdrs(rio_t *rp, char *close_connection);
int parse_uri(char *uri, char *filename, char *cgiargs, char *rootdir);
void serve_file(int fd, char *filename, int filesize);
void serve_json(int fd, char *data);
void check_callback(char *data, char *cgiargs);
void get_filetype(char *filename, char *filetype);
void get_loadavg(char *ret_json);
void get_meminfo(char *ret_json);
void clienterror(int fd, char *cause, char *errnum, char *shortmsg, char *longmsg);
int Open_listenfd_6(int port);
static pthread_mutex_t last_activity_mutex;
static time_t last_activity;
static pthread_mutex_t mem_list_mutex;
static struct list mem_list;
struct mem_elem {
struct list_elem elem;
void * ptr;
};
int main(int argc, char **argv)
{
int listenfd, connfd;
int port = 0, c;
struct sockaddr_in clientaddr;
socklen_t clientlen = sizeof(clientaddr);
char *relayhost = NULL, *rootdir = NULL;
char *default_dir = ".";
// Check arguments passed into program
while ((c = getopt(argc, argv, "p:r:R:")) != -1) {
switch (c) {
case 'p':
port = atoi(optarg);
break;
case 'r':
relayhost = optarg;
break;
case 'R':
rootdir = optarg;
break;
default:
break;
}
}
if (port == 0 && relayhost == NULL) {
fprintf(stderr, "usage: -p <port> -r <relayhost:port> -R <root directory path>\n");
exit(1);
}
// If rootdir is not specified, use current directory
if (rootdir == NULL) {
rootdir = default_dir;
}
// Create the thread pool to process client requests
struct thread_pool * t_pool = thread_pool_new(THREAD_POOL_SIZE);
// Initialize static variables and mutex for such variables
list_init(&mem_list);
pthread_mutex_init(&last_activity_mutex, NULL);
pthread_mutex_init(&mem_list_mutex, NULL);
// Open a listening port if it is specified
if (port != 0) {
listenfd = Open_listenfd_6(port);
while (1) {
connfd = Accept(listenfd, (SA *)&clientaddr, &clientlen);
struct parse_struct p;
p.connfd = connfd;
p.rootdir = rootdir;
thread_pool_submit(t_pool, (thread_pool_callable_func_t) parse_request, &p);
}
} else { // Connect to the relay server
char *c, host[MAXLINE], buf[MAXLINE];
int p;
// Parse the hostname and port
c = strtok(relayhost, ":");
strcpy(host, c);
c = strtok(NULL, ":");
p = atoi(c);
while (1) {
printf("Connecting to relay server\n");
// Initialize the last activity time
pthread_mutex_lock(&last_activity_mutex);
last_activity = time(NULL);
pthread_mutex_unlock(&last_activity_mutex);
// Establish TCP connection to relay server
listenfd = Open_clientfd(host, p);
if (listenfd == -1) {
printf("Relay open error\n");
exit(1);
}
// Send relay identifier
strcpy(buf, "group244\r\n");
Rio_writen(listenfd, buf, strlen(buf));
// Handle HTML requests
struct parse_struct ps;
ps.connfd = listenfd;
ps.rootdir = rootdir;
thread_pool_submit(t_pool, (thread_pool_callable_func_t) parse_request, &ps);
// Reconnect if there has been no activity for 300 seconds
while(1) {
time_t cur_time = time(NULL);
pthread_mutex_lock(&last_activity_mutex);
if (difftime(cur_time, last_activity) > 300) {
pthread_mutex_unlock(&last_activity_mutex);
break;
} else {
pthread_mutex_unlock(&last_activity_mutex);
sleep(1);
}
}
}
}
}
/*
* parse_request - handle one HTTP request/response transaction
*/
void parse_request(struct parse_struct *p)
{
struct parse_struct *p_data = p;
int fd = p_data->connfd;
int parse_ret;
struct stat sbuf;
char buf[MAXLINE], method[MAXLINE], uri[MAXLINE], version[MAXLINE], data[MAXLINE];
char filename[MAXLINE], cgiargs[MAXLINE];
char close_connection = 0;
rio_t rio;
while (1) {
/* Read request line and headers */
Rio_readinitb(&rio, fd);
// Make sure that EOF is not returned for the socket
if (Rio_readlineb(&rio, buf, MAXLINE) > 0) {
// Save current time (activity)
pthread_mutex_lock(&last_activity_mutex);
last_activity = time(NULL);
pthread_mutex_unlock(&last_activity_mutex);
// Parse received data
sscanf(buf, "%s %s %s", method, uri, version);
if (strcasecmp(method, "GET")) {
clienterror(fd, method, "501", "Not Implemented", "Sysstatd does not implement this method");
continue;
}
// Check the headers, we're only looking for Connection:close here
read_requesthdrs(&rio, &close_connection);
if (close_connection == -1) {
Close(fd);
break;
}
/* Parse URI from GET request */
parse_ret = parse_uri(uri, filename, cgiargs, p_data->rootdir);
if (parse_ret == 0) { // Return file
if (stat(filename, &sbuf) < 0) {
clienterror(fd, filename, "404", "Not found", "Sysstatd couldn't find this file");
continue;
}
if (!(S_ISREG(sbuf.st_mode)) || !(S_IRUSR & sbuf.st_mode)) {
clienterror(fd, filename, "403", "Forbidden", "Sysstatd couldn't read the file");
continue;
}
serve_file(fd, filename, sbuf.st_size);
} else if (parse_ret == 1) { // Return /loadavg
get_loadavg(data);
check_callback(data, cgiargs);
serve_json(fd, data);
} else if (parse_ret == 2) { // Return /meminfo
get_meminfo(data);
check_callback(data, cgiargs);
serve_json(fd, data);
} else if (parse_ret == 3) { // Execute /runloop
pthread_t thr;
pthread_create(&thr, NULL, runloop, NULL);
} else if (parse_ret == 4) { // Execute /allocanon
printf("Allocanon\n");
allocanon();
} else if (parse_ret == 5) { // Execute /freeanon
printf("Freeanon\n");
freeanon();
} else {
clienterror(fd, uri, "404", "Not found", "Sysstatd does not implement this URL");
}
// Close the connection if HTTP 1.0 is used or close is specified in the header
if (!strcmp(version, "HTTP/1.0") || close_connection) {
Close(fd);
break;
}
} else {
Close(fd);
break;
}
}
}
/*
* runloop - spins for 15 seconds then returns
*/
void * runloop(void *arg) {
time_t t1 = time(NULL);
time_t t2;
do {
t2 = time(NULL);
} while (difftime(t2,t1) < 15);
return NULL;
}
/*
* allocanon - allocates 64MB in anonymous virtual memory
*/
void allocanon() {
// Get the address mapping for a 64MB
void * new_map = mmap(NULL, 67108864, PROT_WRITE, MAP_PRIVATE | MAP_ANONYMOUS, 0, 0);
if (new_map == MAP_FAILED) {
printf("Allocanon failed\n");
return;
} else {
printf("%p\n", new_map);
}
// Do something with the newly allocated memory
memset(new_map, 0, 67108864);
// Save address in list for later removal
struct mem_elem *m = malloc(sizeof(struct mem_elem));
m->ptr = new_map;
pthread_mutex_lock(&mem_list_mutex);
list_push_back(&mem_list, &m->elem);
pthread_mutex_unlock(&mem_list_mutex);
}
/*
* freeanon - frees a block of anonymous virtual memory previously allocated
*/
void freeanon() {
// Remove the most recently allocated memory block
pthread_mutex_lock(&mem_list_mutex);
if (list_size(&mem_list)) {
struct list_elem *e = list_pop_back(&mem_list);
struct mem_elem *m = list_entry(e, struct mem_elem, elem);
munmap(m->ptr, 67108864);
free(m);
}
pthread_mutex_unlock(&mem_list_mutex);
}
/*
* read_requesthdrs - read and parse HTTP request headers
*/
void read_requesthdrs(rio_t *rp, char *close_connection)
{
char buf[MAXLINE];
do {
if (Rio_readlineb(rp, buf, MAXLINE) > 0) {
// Check if the header contains a request to close connection
if (!strcmp(buf, "Connection: close\r\n"))
*close_connection = 1;
// printf("%s", buf);
} else {
*close_connection = -1;
return;
}
} while (strcmp(buf, "\r\n"));
return;
}
/*
* parse_uri - parse URI into filename and CGI args
* return 0 if file, 1 if loadavg, 2 if meminfo,
* 3 if runloop, 4 if allocanon, 5 if freeanon
* return -1 if uri is not supported
*/
int parse_uri(char *uri, char *filename, char *cgiargs, char *rootdir)
{
char *ptr;
// Save cgiargs if they exist
if (!strstr(uri, "?")) {
strcpy(cgiargs, "");
} else {
ptr = index(uri, '?');
if (ptr) {
strcpy(cgiargs, ptr+1);
*ptr = '\0';
}
else
strcpy(cgiargs, "");
}
// Check uri for specific commands
if (!strncmp(uri, "/files", 6)) {
strcpy(filename, rootdir);
strcat(filename, uri+6);
// Ensure that all "/../" in the filename is replaced with "////"
char *s = strstr(filename, "/../");
while (s) {
strncpy(s, "////", 4);
s = strstr(filename, "/../");
}
// printf("%s\n", filename);
return 0;
} else if (!strcmp(uri, "/loadavg")) {
return 1;
} else if (!strcmp(uri, "/meminfo")) {
return 2;
} else if (!strcmp(uri, "/runloop")) {
return 3;
} else if (!strcmp(uri, "/allocanon")) {
return 4;
} else if (!strcmp(uri, "/freeanon")) {
return 5;
} else {
return -1;
}
}
/*
* serve_json - returns JSON data to the client
*/
void serve_json(int fd, char *data) {
char buf[MAXBUF];
/* Send response headers to client */
sprintf(buf, "HTTP/1.1 200 OK\r\n");
sprintf(buf, "%sServer: Sysstatd Web Server\r\n", buf);
sprintf(buf, "%sContent-length: %d\r\n", buf, (int)strlen(data));
sprintf(buf, "%sContent-type: %s\r\n\r\n", buf, "application/json");
Rio_writen(fd, buf, strlen(buf));
/* Send json data to client */
Rio_writen(fd, data, strlen(data));
}
/*
* get_loadavg - returns /proc/loadavg in JSON format
*/
void get_loadavg(char *ret_json) {
FILE *f;
char line[MAXLINE], load1[10], load2[10], load3[10];
char threads[20], total_threads[20], active_threads[20];
memset(active_threads, 0, 20);
memset(total_threads, 0, 20);
// Read data from /proc/loadavg
f = Fopen("/proc/loadavg", "rt");
Fgets(line, MAXLINE, f);
Fclose(f);
// Format data into JSON format
sscanf(line, "%s %s %s %s", load1, load2, load3, threads);
int i = strcspn(threads, "/");
strncpy(active_threads, threads, i);
strcpy(total_threads, threads+i+1);
sprintf(ret_json, "{\"total_threads\": \"%s\", \"loadavg\": [\"%s\", \"%s\", \"%s\"], \"running_threads\": \"%s\"}",
total_threads, load1, load2, load3, active_threads);
}
/*
* get_meminfo - returns /proc/meminfo in JSON format
*/
void get_meminfo(char *ret_json) {
FILE *f;
char line[MAXLINE], s1[MAXLINE], s2[MAXLINE], buf[MAXLINE];
strcpy(ret_json, "{");
// Read data and convert to JSON format
f = Fopen("/proc/meminfo", "rt");
while (Fgets(line, MAXLINE, f) != NULL) {
sscanf(line, "%s %s", s1, s2);
strcpy(buf, "\"");
strncat(buf, s1, strlen(s1)-1);
strcat(buf, "\": \"");
strcat(buf, s2);
strcat(buf, "\", ");
strcat(ret_json, buf);
}
Fclose(f);
strcpy(ret_json+strlen(ret_json)-2, "}");
}
/*
* serve_file - copy a file back to the client
*/
void serve_file(int fd, char *filename, int filesize)
{
int srcfd;
char *srcp, filetype[MAXLINE], buf[MAXBUF];
/* Send response headers to client */
get_filetype(filename, filetype);
sprintf(buf, "HTTP/1.1 200 OK\r\n");
sprintf(buf, "%sServer: Sysstatd Web Server\r\n", buf);
sprintf(buf, "%sContent-length: %d\r\n", buf, filesize);
sprintf(buf, "%sContent-type: %s\r\n\r\n", buf, filetype);
Rio_writen(fd, buf, strlen(buf));
/* Send response body to client */
srcfd = Open(filename, O_RDONLY, 0);
srcp = Mmap(0, filesize, PROT_READ, MAP_PRIVATE, srcfd, 0);
Close(srcfd);
Rio_writen(fd, srcp, filesize);
Munmap(srcp, filesize);
}
/*
* check_callback - check to see if a callback is specified in cgiargs
*/
void check_callback(char *data, char *cgiargs)
{
char *p, buf[MAXLINE];
// If cgiargs is empty, return
if (!strcmp(cgiargs, ""))
return;
p = strtok(cgiargs, "&");
while (p != NULL) {
if (!strncmp(p, "callback=", 9)) {
strcpy(buf, p+9);
strcat(buf, "(");
strcat(buf, data);
strcat(buf, ")");
strcpy(data, buf);
}
p = strtok(NULL, "&");
}
}
/*
* get_filetype - derive file type from file name
*/
void get_filetype(char *filename, char *filetype)
{
if (strstr(filename, ".html"))
strcpy(filetype, "text/html");
else if (strstr(filename, ".gif"))
strcpy(filetype, "image/gif");
else if (strstr(filename, ".jpg"))
strcpy(filetype, "image/jpeg");
else if (strstr(filename, ".js"))
strcpy(filetype, "application/javascript");
else if (strstr(filename, ".css"))
strcpy(filetype, "text/css");
else
strcpy(filetype, "text/plain");
}
/*
* clienterror - returns an error message to the client
*/
void clienterror(int fd, char *cause, char *errnum, char *shortmsg, char *longmsg)
{
char buf[MAXLINE], body[MAXBUF];
/* Build the HTTP response body */
sprintf(body, "<html><title>Sysstatd Error</title>");
sprintf(body, "%s<body bgcolor=""ffffff"">\r\n", body);
sprintf(body, "%s%s: %s\r\n", body, errnum, shortmsg);
sprintf(body, "%s<p>%s: %s\r\n", body, longmsg, cause);
sprintf(body, "%s<hr><em>The Sysstatd Web server</em>\r\n", body);
/* Print the HTTP response */
sprintf(buf, "HTTP/1.1 %s %s\r\n", errnum, shortmsg);
Rio_writen(fd, buf, strlen(buf));
sprintf(buf, "Content-type: text/html\r\n");
Rio_writen(fd, buf, strlen(buf));
sprintf(buf, "Content-length: %d\r\n\r\n", (int)strlen(body));
Rio_writen(fd, buf, strlen(buf));
Rio_writen(fd, body, strlen(body));
}
/*
* Open_listenfd_6 - opens and returns a listening socket on the specified port.
* will attempt to open an ipv6 socket over an ipv4 socket.
*/
int Open_listenfd_6(int port) {
char p[10];
sprintf(p, "%d", port);
// Initialize stuffs
struct addrinfo *ai;
struct addrinfo hints;
memset (&hints, '\0', sizeof (hints));
hints.ai_flags = AI_PASSIVE | AI_ADDRCONFIG;
hints.ai_socktype = SOCK_STREAM;
int e = getaddrinfo(NULL, p, &hints, &ai);
if (e != 0) {
fprintf(stderr, "Error at getaddrinfo()\n");
exit(1);
}
int nfds = 0;
struct addrinfo *runp = ai;
// Loop for ai_family == 10 (AF_INET6) for an ipv6 socket. Choose the first
// socket in the list otherwise
int inet_6 = -1, i;
while (runp != NULL) {
if (runp->ai_family == 10)
inet_6 = nfds;
++nfds;
runp = runp->ai_next;
}
if (inet_6 > 0) {
runp = ai;
for (i = 0; i < inet_6; i++) {
runp = runp->ai_next;
}
} else {
runp = ai;
}
// Create, bind, and listen on the specified socket
int listenfd;
listenfd = socket(runp->ai_family, runp->ai_socktype, runp->ai_protocol);
if (listenfd == -1) {
fprintf(stderr, "Error at socket()\n");
exit(1);
}
int opt = 1;
setsockopt(listenfd, SOL_SOCKET, SO_REUSEADDR, &opt, sizeof(opt));
if (bind(listenfd, runp->ai_addr, runp->ai_addrlen) != 0) {
fprintf(stderr, "Error at bind()\n");
close(listenfd);
exit(1);
} else {
if (listen(listenfd, SOMAXCONN) != 0) {
fprintf(stderr, "Error at listen()\n");
exit(1);
}
}
freeaddrinfo (ai);
return listenfd;
}