0,0 → 1,582 |
|
#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; |
} |