Subversion Repositories Code-Repo

Rev

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;
}