Blame | Last modification | View Log | Download | 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 poolstruct 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 programwhile ((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 directoryif (rootdir == NULL) {rootdir = default_dir;}// Create the thread pool to process client requestsstruct thread_pool * t_pool = thread_pool_new(THREAD_POOL_SIZE);// Initialize static variables and mutex for such variableslist_init(&mem_list);pthread_mutex_init(&last_activity_mutex, NULL);pthread_mutex_init(&mem_list_mutex, NULL);// Open a listening port if it is specifiedif (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 serverchar *c, host[MAXLINE], buf[MAXLINE];int p;// Parse the hostname and portc = strtok(relayhost, ":");strcpy(host, c);c = strtok(NULL, ":");p = atoi(c);while (1) {printf("Connecting to relay server\n");// Initialize the last activity timepthread_mutex_lock(&last_activity_mutex);last_activity = time(NULL);pthread_mutex_unlock(&last_activity_mutex);// Establish TCP connection to relay serverlistenfd = Open_clientfd(host, p);if (listenfd == -1) {printf("Relay open error\n");exit(1);}// Send relay identifierstrcpy(buf, "group244\r\n");Rio_writen(listenfd, buf, strlen(buf));// Handle HTML requestsstruct 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 secondswhile(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 socketif (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 datasscanf(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 hereread_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 fileif (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 /loadavgget_loadavg(data);check_callback(data, cgiargs);serve_json(fd, data);} else if (parse_ret == 2) { // Return /meminfoget_meminfo(data);check_callback(data, cgiargs);serve_json(fd, data);} else if (parse_ret == 3) { // Execute /runlooppthread_t thr;pthread_create(&thr, NULL, runloop, NULL);} else if (parse_ret == 4) { // Execute /allocanonprintf("Allocanon\n");allocanon();} else if (parse_ret == 5) { // Execute /freeanonprintf("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 headerif (!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 64MBvoid * 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 memorymemset(new_map, 0, 67108864);// Save address in list for later removalstruct 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 blockpthread_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 connectionif (!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 existif (!strstr(uri, "?")) {strcpy(cgiargs, "");} else {ptr = index(uri, '?');if (ptr) {strcpy(cgiargs, ptr+1);*ptr = '\0';}elsestrcpy(cgiargs, "");}// Check uri for specific commandsif (!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/loadavgf = Fopen("/proc/loadavg", "rt");Fgets(line, MAXLINE, f);Fclose(f);// Format data into JSON formatsscanf(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 formatf = 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, returnif (!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");elsestrcpy(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 stuffsstruct 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 otherwiseint 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 socketint 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;}