diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..677b78a --- /dev/null +++ b/.gitignore @@ -0,0 +1,3 @@ +*.o +*.swp +test diff --git a/datalab-handout/bits.c b/datalab-handout/bits.c index 36296bc..f42474b 100644 --- a/datalab-handout/bits.c +++ b/datalab-handout/bits.c @@ -150,7 +150,7 @@ int bitAnd(int x, int y) { * Rating: 1 */ int tmin(void) { - return (~0)<<31; + return 1<<31; } /* * negate - return -x diff --git a/malloclab-handout/mm.c b/malloclab-handout/mm.c index dff0dc8..98bc00f 100644 --- a/malloclab-handout/mm.c +++ b/malloclab-handout/mm.c @@ -9,6 +9,7 @@ #include #include #include +#include #include "mm.h" #include "memlib.h" @@ -23,6 +24,29 @@ #endif +/* The order of free block list. + * + * FIFO: first in first out. + * LIFO: last in first out. + * ADDRESS_ORDER: free block list in address order. Smaller address first. + * SIZE_ORDER: free block list in size order. Smaller size first. + * + * According to the test, FIFO performs the best. + */ +#define FIFO +#ifdef LIFO + #define insert_free_block insert_free_block_lifo +#elif defined(FIFO) + #define insert_free_block insert_free_block_fifo +#elif defined(ADDRESS_ORDER) + #define insert_free_block insert_free_block_address_order +#elif defined(SIZE_ORDER) + #define insert_free_block insert_free_block_size_order +#else + #define insert_free_block insert_free_block_lifo +#endif + + /* do not change the following! */ #ifdef DRIVER /* create aliases for driver tests */ @@ -39,8 +63,17 @@ #define WSIZE 4 #define DSIZE 8 #define ALIGNMENT 8 -#define CHUNKSIZE (1<<12) -#define MIN_BLOCK_SIZE DSIZE +#define CHUNKSIZE (1<<9) +/* + * The minimum free block size. + * For each free block, we need to have a header and a footer, a successor and + * a predecessor. + * We store the successor as the offset from the beginning of the whole heap* + * As the heap's max size is 2^31, we can store it in WSIZE bytes. + */ +#define MIN_FREE_BLOCK_SIZE (2*DSIZE) +#define MIN_BLOCK_SIZE (2*DSIZE) +#define MAX_BLOCK_SIZE INT_MAX /* rounds up to the nearest multiple of ALIGNMENT */ #define ALIGN(p) (((size_t)(p) + (ALIGNMENT-1)) & ~0x7) @@ -50,40 +83,234 @@ #define GET(p) (*(unsigned int*)(p)) #define PUT(p, val) (*(unsigned int *)(p) = (val)) +/* GET_SIZE return the whole size(including header/footer) of the block */ #define GET_SIZE(p) (GET(p) & ~0x7) +/* GET_USABLE_SIZE return the real size to store content + * (excluding header/footer) + * */ +// #define GET_USABLE_SIZE(p) (GET_SIZE(p)-DSIZE) #define GET_ALLOC(p) (GET(p) & 0x1) +/* get offset of this ptr */ +#define GET_OFFSET(p) ((char *)(p) - free_listp) #define HDRP(bp) ((char *)(bp) - WSIZE) #define FTRP(bp) ((char *)(bp) + GET_SIZE(HDRP(bp)) - DSIZE) +#define PRED(bp) (bp) +#define SUCC(bp) ((char *)(bp) + WSIZE) #define NEXT_BLKP(bp) ((char *)(bp) + GET_SIZE(((char *)(bp) - WSIZE))) #define PREV_BLKP(bp) ((char *)(bp) - GET_SIZE(((char *)(bp) - DSIZE))) +/* + * Note PRED_BLKP/SUCC_BLKP may return the free list ptr. + */ +#define PRED_BLKP(bp) (free_listp + GET(PRED(bp))) +#define SUCC_BLKP(bp) (free_listp + GET(SUCC(bp))) #define BEGIN_BLOCK NEXT_BLKP(heap_listp) +#define END_CLASS_PTR (free_listp + FREE_LIST_LEN * FREE_LIST_SENTINEL_SIZE) /* bp points to epilogue block */ #define IS_EPILOGUE(bp) (GET(HDRP(bp)) == 1) #define IS_FREE(bp) (GET_ALLOC(HDRP(bp)) == 0) +/* + * for loops + */ + /* iterate through every block between epilogue and prologue block */ #define for_each_block(ptr) \ for ((ptr) = BEGIN_BLOCK; !IS_EPILOGUE(ptr); (ptr)=NEXT_BLKP(ptr)) - -static char *heap_listp; +/* iterate through every free block in the free list */ +#define for_each_free_block(class_ptr, ptr) \ + for ((ptr) = SUCC_BLKP(class_ptr); (ptr) != (class_ptr);\ + (ptr) = SUCC_BLKP(ptr)) +/* enumerate free list in range */ +#define for_range_free_list(begin_ptr, end_ptr, ptr) \ + for ((ptr) = (begin_ptr); (ptr) != (end_ptr);\ + (ptr) = (char*)(ptr) + FREE_LIST_SENTINEL_SIZE) +/* enumerate all free lists */ +#define for_each_free_list(ptr) \ + for_range_free_list(free_listp, END_CLASS_PTR, (ptr)) + +static char *heap_listp; // point to the first block +static char *free_listp; // pointer to free list static void *extend_heap(size_t words); static void *coalesce(void *bp); static void place(void *bp, size_t size); static void *find_fit(size_t size); -static int aligned(const void *p); +static void *split_block(void *bp, size_t pack_v1, size_t pack_v2); + + +/* segregated free list */ +#define FREE_LIST_LEN 10 +/* sentinel size(PREV | SUCC) */ +#define FREE_LIST_SENTINEL_SIZE DSIZE +/* get the ith free list */ +#define FREE_LIST_REF(k) (free_listp + (k) * FREE_LIST_SENTINEL_SIZE) +/* return the index of the free list pointer */ +#define FREE_LIST_IDX(p) (((char*)p - free_listp) / FREE_LIST_SENTINEL_SIZE) +/* get free list class ptr + * size: size of the free block. + * assert (size >= MIN_BLOCK_SIZE) + * */ +inline static void *get_class_ptr(size_t size) +{ + /* + * size is a multiple of 8. size = k * 8. + * We have k >= 2 since MIN_FREE_BLOCK_SIZE is 2*DSIZE + * {2}, {3-4}, ..., {257, 512}, {513, +inf} + * 2^1, 2^2, 2^3, ...., 2^9, ... + */ + + int offset; + // TODO: remove this assert + // assert(size % 8 == 0); + // assert(size/8 >= 2); + if (size > 4096) { + offset = FREE_LIST_LEN - 1; + } else { + // k >= 2 + size_t k = size >> 3; + offset = 31 - __builtin_clz(k-1); + } + return FREE_LIST_REF(offset); +} + + +/* + * insert free block bp after prev_bp + */ +inline static void insert_free_block_after(void *prev_bp, void *bp) +{ + void *succ_bp = SUCC_BLKP(prev_bp); + PUT(SUCC(bp), GET(SUCC(prev_bp))); + PUT(PRED(bp), GET_OFFSET(prev_bp)); + PUT(SUCC(prev_bp), GET_OFFSET(bp)); + PUT(PRED(succ_bp), GET_OFFSET(bp)); +} + +/* + * first in first out + */ +inline static void insert_free_block_fifo(void *bp) +{ + // usable block size should exlude the header/footer size + size_t size = GET_SIZE(HDRP(bp)); + void *class_ptr = get_class_ptr(size); + void *tail_bp = PRED_BLKP(class_ptr); + + insert_free_block_after(tail_bp, bp); +} + + + + +/* + * insert free block in address order + */ +inline static void insert_free_block_address_order(void *bp) +{ + size_t size = GET_SIZE(HDRP(bp)); + void *class_ptr = get_class_ptr(size); + void *cur_bp; + for_each_free_block(class_ptr, cur_bp) { + if (cur_bp > bp) { + break; + } + } + insert_free_block_after(PRED_BLKP(cur_bp), bp); +} + +/* + * last in first out + */ +inline static void insert_free_block_lifo(void *bp) +{ + size_t size = GET_SIZE(HDRP(bp)); + void *class_ptr = get_class_ptr(size); + insert_free_block_after(class_ptr, bp); +} + + +/* + * insert according to size order + */ + +inline static void insert_free_block_size_order(void *bp) +{ + size_t size = GET_SIZE(HDRP(bp)); + void *class_ptr = get_class_ptr(size); + void *cur_bp; + for_each_free_block(class_ptr, cur_bp) { + size_t cur_size = GET_SIZE(HDRP(bp)); + if (cur_size >= size) { + break; + } + } + insert_free_block_after(PRED_BLKP(cur_bp), bp); +} + +/* + * remove free block from free list + * assert bp is not a class_ptr + */ +inline static void remove_free_block(void *bp) +{ + void *pred_bp = PRED_BLKP(bp), + *succ_bp = SUCC_BLKP(bp); + PUT(SUCC(pred_bp), GET_OFFSET(succ_bp)); + PUT(PRED(succ_bp), GET_OFFSET(pred_bp)); +} + + /* * Initialize: return -1 on error, 0 on success. */ -int mm_init(void) { - // initial empty heap +int mm_init(void) +{ + /* + * Generally structure of the heap is: + * + * [ FREE LIST POINTERS | PRELOGUE BLOCK | HEAP MEMORY | EPILOGUE BLOCK ] + */ + int i; + + /* allocate memory for free block pointers */ + if ((free_listp = mem_sbrk( + FREE_LIST_LEN*FREE_LIST_SENTINEL_SIZE)) == (void*)-1) { + return -1; + } + + /* prologue and epilogue */ if ((heap_listp = mem_sbrk(4*WSIZE)) == (void*) - 1) { return -1; } + + /* initialize free list pointers + * + * Each free list pointer has DSIZE bytes consisting of predecessor and + * successor. + * + * [PRED | SUCC]. + * + * The PRED points to the last free block while SUCC points to the + * first free block. + * Generally, the free list pointer works as the sentinel for + * the free list. + * Use both PRED and SUCC makes the code easier to maintain and less + * if/else conditional judgements. + */ + for (i = 0; i < FREE_LIST_LEN; i++) { + /* initially, as each free list is empty, the free list pointer + * just points to the sentinel + */ + PUT(PRED(FREE_LIST_REF(i)), i * FREE_LIST_SENTINEL_SIZE); + PUT(SUCC(FREE_LIST_REF(i)), i * FREE_LIST_SENTINEL_SIZE); + } + + + // initialize prologue and epilogue PUT(heap_listp, 0); PUT(heap_listp + (1*WSIZE), PACK(DSIZE, 1)); PUT(heap_listp + (2*WSIZE), PACK(DSIZE, 1)); @@ -93,9 +320,13 @@ int mm_init(void) { if (extend_heap(CHUNKSIZE/WSIZE) == NULL) { return -1; } + mm_checkheap(__LINE__); return 0; } +/* + * extend_heap - extend the heap size + */ static void *extend_heap(size_t words) { char *bp; @@ -112,56 +343,88 @@ static void *extend_heap(size_t words) // HDRP(NEXT_BLKP(bp)) points to the new epilogue header PUT(HDRP(NEXT_BLKP(bp)), PACK(0, 1)); - return coalesce(bp); + bp = coalesce(bp); + return bp; } +/* + * coalesce: coalesce the free block. + * + * after coalesce, we append the new free block + */ static void *coalesce(void *bp) { - size_t prev_alloc = GET_ALLOC(FTRP(PREV_BLKP(bp))); - size_t next_alloc = GET_ALLOC(HDRP(NEXT_BLKP(bp))); + // previous block + void *prev_bp = PREV_BLKP(bp); + size_t prev_alloc = GET_ALLOC(FTRP(prev_bp)); + // next block + void *next_bp = NEXT_BLKP(bp); + size_t next_alloc = GET_ALLOC(HDRP(next_bp)); size_t size = GET_SIZE(HDRP(bp)); if (prev_alloc && next_alloc) { - return bp; + // pass } else if (prev_alloc && !next_alloc) { size += GET_SIZE(HDRP(NEXT_BLKP(bp))); + remove_free_block(next_bp); PUT(HDRP(bp), PACK(size, 0)); PUT(FTRP(bp), PACK(size, 0)); } else if (!prev_alloc && next_alloc) { size += GET_SIZE(HDRP(PREV_BLKP(bp))); + remove_free_block(prev_bp); PUT(FTRP(bp), PACK(size, 0)); PUT(HDRP(PREV_BLKP(bp)), PACK(size, 0)); bp = PREV_BLKP(bp); } else { size += GET_SIZE(HDRP(PREV_BLKP(bp))) + GET_SIZE(FTRP(NEXT_BLKP(bp))); + remove_free_block(prev_bp); + remove_free_block(next_bp); PUT(HDRP(PREV_BLKP(bp)), PACK(size, 0)); PUT(FTRP(NEXT_BLKP(bp)), PACK(size, 0)); bp = PREV_BLKP(bp); } + // insert the new free block + insert_free_block(bp); return bp; } /* - * malloc + * Generally, the allocated size is larger than the size we want since + * we need to store the header and footer. This function return + * the real size we need to allocate. */ -void *malloc (size_t size) { +inline size_t get_real_malloc_size(size_t size) +{ size_t asize; - char *bp; - size_t extendsize; - if (size <= 0) return NULL; if (size <= DSIZE) { asize = 2*DSIZE; } else { asize = DSIZE * ((size + DSIZE + DSIZE - 1) / DSIZE); } + return asize; +} + +/* + * malloc + */ +void *malloc (size_t size) +{ + size_t asize; + char *bp; + size_t extendsize; + if (size <= 0) return NULL; + + asize = get_real_malloc_size(size); if ((bp = find_fit(asize)) != NULL) { place(bp, asize); return bp; } + // printf("can't find fit\n"); + extendsize = MAX(asize, CHUNKSIZE); if ((bp = extend_heap(extendsize / WSIZE)) == NULL) { @@ -171,33 +434,68 @@ void *malloc (size_t size) { return bp; } + + /* * allocate memory on the free block */ static void place(void *bp, size_t size) { - size_t total_block_size = GET_SIZE(HDRP(bp)); - PUT(HDRP(bp), PACK(size, 1)); - PUT(FTRP(bp), PACK(size, 1)); - // TODO: possible to create empty free blocks, which is useless - if (total_block_size > size) { - size_t rest_block_size = total_block_size - size; - void *next_bp = NEXT_BLKP(bp); - PUT(HDRP(next_bp), PACK(rest_block_size, 0)); - PUT(FTRP(next_bp), PACK(rest_block_size, 0)); + size_t total_size = GET_SIZE(HDRP(bp)); + remove_free_block(bp); + if (total_size >= size + MIN_FREE_BLOCK_SIZE) { + /* + * split the blocks into an allocated block + * and a free block + */ + void *free_bp = split_block( + bp, + PACK(size, 1), // allocated block + PACK(total_size - size, 0) // free block + ); + insert_free_block(free_bp); + } else { + PUT(HDRP(bp), PACK(total_size, 1)); + PUT(FTRP(bp), PACK(total_size, 1)); } } +/* split the block into two blocks + * assert the two blocks are not both free. + * assert GET_SIZE(pack_v1) + GET_SIZE(pack_v2) = GET_SIZE(bp) + * return the ptr of newly created block + */ +static void *split_block( + void *bp, + size_t pack_v1, + size_t pack_v2) { + // TODO: check pack_v1 and pack_v2 + void *next_bp; + PUT(HDRP(bp), pack_v1); + PUT(FTRP(bp), pack_v1); + next_bp = NEXT_BLKP(bp); + PUT(HDRP(next_bp), pack_v2); + PUT(FTRP(next_bp), pack_v2); + return next_bp; +} + /* * find a block of memory with size >= "size" + * We use first fit stratergy. I have tried other strategies, + * but first fit works very well. */ static void *find_fit(size_t size) { - char *bp; - for_each_block(bp) { - size_t alloc = GET_ALLOC(HDRP(bp)); - size_t size_of_bp = GET_SIZE(HDRP(bp)); - if (!alloc && size_of_bp >= size) return bp; + void *begin_class_ptr = get_class_ptr(size); + void *class_ptr; + void *bp; + /* enumerate all free list with blocksize >= "size" */ + for_range_free_list(begin_class_ptr, END_CLASS_PTR, class_ptr) { + for_each_free_block(class_ptr, bp) { + size_t alloc = GET_ALLOC(HDRP(bp)); + size_t size_of_bp = GET_SIZE(HDRP(bp)); + if (!alloc && size_of_bp >= size) return bp; + } } return NULL; } @@ -205,7 +503,8 @@ static void *find_fit(size_t size) /* * free */ -void free (void *ptr) { +void free (void *ptr) +{ if(!ptr) return; size_t size = GET_SIZE(HDRP(ptr)); PUT(HDRP(ptr), PACK(size, 0)); @@ -213,17 +512,24 @@ void free (void *ptr) { coalesce(ptr); } + /* - * realloc - you may want to look at mm-naive.c + * realloc - Change the size of the block by mallocing a new block, + * copying its data, and freeing the old block. + * + * This is an optimized realloc implementation, but it performs the same + * as the unoptimized version... */ -void *realloc(void *oldptr, size_t size) { +void *realloc(void *oldptr, size_t size) +{ size_t oldsize; void *newptr; + size_t asize; /* If size == 0 then this is just free, and we return NULL. */ if(size == 0) { free(oldptr); - return NULL; + return 0; } /* If oldptr is NULL, then this is just malloc. */ @@ -231,21 +537,47 @@ void *realloc(void *oldptr, size_t size) { return malloc(size); } - newptr = malloc(size); - - /* If realloc() fails the original block is left untouched */ - if(!newptr) { - return 0; + oldsize = GET_SIZE(HDRP(oldptr)); + /* if the block next to oldptr is a free block, we merge it */ + void *next_bp = NEXT_BLKP(oldptr); + if (IS_FREE(next_bp)) { + // append next free block to the old block + remove_free_block(next_bp); + size_t next_bp_size = GET_SIZE(HDRP(next_bp)); + PUT(HDRP(oldptr), PACK(oldsize+next_bp_size, 1)); + PUT(FTRP(oldptr), PACK(oldsize+next_bp_size, 1)); + oldsize += next_bp_size; } - /* Copy the old data. */ - oldsize = GET_SIZE(HDRP(oldptr)); - if(size < oldsize) oldsize = size; - memcpy(newptr, oldptr, oldsize); + asize = get_real_malloc_size(size); + + if (oldsize >= asize) { + /* since oldsize is large enough, we don't need to find a new block + * of memory + */ + if (oldsize >= asize + MIN_FREE_BLOCK_SIZE) { + void *free_bp = split_block( + oldptr, + PACK(asize, 1), + PACK(oldsize - asize, 0)); + insert_free_block(free_bp); + } + newptr = oldptr; + } else { + newptr = malloc(size); + + /* If realloc() fails the original block is left untouched */ + if(!newptr) { + return 0; + } - /* Free the old block. */ - free(oldptr); + /* Copy the old data. */ + if(size < oldsize) oldsize = size; + memcpy(newptr, oldptr, oldsize); + /* Free the old block. */ + free(oldptr); + } return newptr; } @@ -254,7 +586,8 @@ void *realloc(void *oldptr, size_t size) { * This function is not tested by mdriver, but it is * needed to run the traces. */ -void *calloc (size_t nmemb, size_t size) { +void *calloc (size_t nmemb, size_t size) +{ size_t bytes = nmemb * size; void *newptr; @@ -301,38 +634,96 @@ static int aligned(const void *p) { } #define CHECK_GREATER_EQUAL(v, cmp_v, lineno, msg) if (!((v) >= (cmp_v))) {\ - err_report(lineno, msg); \ + err_report(lineno, msg); \ } +#define CHECK_LESS_EQUAL(v, cmp_v, lineno, msg) if (!((v) <= (cmp_v))) {\ + err_report(lineno, msg); \ +} + +#define CHECK_LESS(v, cmp_v, lineno, msg) if (!((v) < cmp_v)) {\ + err_report(lineno, msg); \ +} +/* + * check_block_consistency - check header/footer size and content, check + * minimum block size + */ static void check_block_consistency(const char *bp, int lineno) { const char *header, *footer; header = HDRP(bp); footer = FTRP(bp); - CHECK_EQUAL(GET_SIZE(header), GET_SIZE(footer), lineno, "check header/footer size"); - CHECK_EQUAL(GET_ALLOC(header), GET_ALLOC(footer), lineno, "check header/footer alloc"); - CHECK_GREATER_EQUAL(GET_SIZE(header), MIN_BLOCK_SIZE, lineno, "check minimum block size"); + CHECK_EQUAL(GET_SIZE(header), GET_SIZE(footer), + lineno, + "check header/footer size"); + CHECK_EQUAL(GET_ALLOC(header), GET_ALLOC(footer), + lineno, + "check header/footer alloc"); + CHECK_GREATER_EQUAL(GET_SIZE(header), + MIN_BLOCK_SIZE, + lineno, + "check minimum block size"); + if (IS_FREE(bp)) { + CHECK_GREATER_EQUAL(GET_SIZE(header), MIN_FREE_BLOCK_SIZE, lineno, + "check minimum free block size"); + } } +/* + * check_coalescing - check whether every free block is coalesced, i.e., + * there should be no free blocks next to every free block + */ static void check_coalescing(const char *bp, int lineno) { if (IS_FREE(bp)) { - CHECK_EQUAL(GET_ALLOC(HDRP(NEXT_BLKP(bp))), 1, lineno, "next block is free"); - CHECK_EQUAL(GET_ALLOC(HDRP(PREV_BLKP(bp))), 1, lineno, "prev block is free"); + CHECK_EQUAL(GET_ALLOC(HDRP(NEXT_BLKP(bp))), 1, + lineno, + "next block is free"); + CHECK_EQUAL(GET_ALLOC(HDRP(PREV_BLKP(bp))), 1, lineno, + "prev block is free"); + } +} + + +/* + * return the size for this class + * This function is for debugging usage. + */ +static void get_class_size_range(void *class_ptr, size_t *pmin_size, size_t *pmax_size) +{ + size_t ref_offset = ((char *)class_ptr - (char *)free_listp) / + FREE_LIST_SENTINEL_SIZE; + if (ref_offset == FREE_LIST_LEN - 1) { + *pmin_size = 4097; + *pmax_size = MAX_BLOCK_SIZE; + } else if (ref_offset == 0) { + *pmin_size = *pmax_size = MIN_BLOCK_SIZE; + } else { + *pmin_size = ((1< + +free-port.sh + Handy script that identifies an unused TCP port that you can use + for your proxy or tiny. + usage: ./free-port.sh + +driver.sh + The autograder for Basic, Concurrency, and Cache. + usage: ./driver.sh + +nop-server.py + helper for the autograder. + +tiny + Tiny Web server from the CS:APP text diff --git a/proxylab-handout/bytes.c b/proxylab-handout/bytes.c new file mode 100644 index 0000000..d414e85 --- /dev/null +++ b/proxylab-handout/bytes.c @@ -0,0 +1,75 @@ +#include "bytes.h" +#include + +#define DEFAULT_LENGTH 1024 + +void bytes_malloc(Bytes *pstr) +{ + pstr->size = DEFAULT_LENGTH; + pstr->buf = (char *)malloc(pstr->size * sizeof(char)); + pstr->len = 0; +} + +void bytes_free(Bytes *pstr) +{ + pstr->len = 0; + pstr->size = 0; + free(pstr->buf); +} + +/* ceil_divide: a divide by b, return ceil value + * Assert a > b. + */ +inline static size_t ceil_divide(size_t a, size_t b) +{ + size_t m = 2; + for (m = 2; m * b < a; m+=1) {} + return m; +} + +/* bytes_append : append cstr to the end of pstr. + * Assert cstr and pstr is not overlapped. + * + * TODO: support overlapping + */ +void bytes_append(Bytes *pstr, const char *cstr) +{ + size_t cstr_len = strlen(cstr); + bytes_appendn(pstr, cstr, cstr_len); +} + +void bytes_appendn(Bytes *pstr, const char *buf, size_t buf_len) +{ + if (pstr->len + buf_len + 1 <= pstr->size) { + int i; + for (i = 0; i < buf_len; i++) { + pstr->buf[pstr->len+i] = buf[i]; + } + pstr->len += buf_len; + } else { + size_t multipler = ceil_divide((buf_len + pstr->len + 1), + pstr->size); + size_t new_size = multipler * pstr->size; + char *new_buf = (char *)malloc(new_size*sizeof(char)); + int i; + for (i = 0; i < pstr->len; i++) { + new_buf[i] = pstr->buf[i]; + } + for (i = 0; i < buf_len; i++) { + new_buf[pstr->len+i] = buf[i]; + } + pstr->len += buf_len; + pstr->size = new_size; + pstr->buf = new_buf; + } +} + +/* return bytes as a cstr */ +void bytes_cstr(Bytes str, char *cstr) +{ + int i; + for (i = 0; i < str.len; i++) { + cstr[i] = str.buf[i]; + } + cstr[str.len] = '\0'; +} diff --git a/proxylab-handout/bytes.h b/proxylab-handout/bytes.h new file mode 100644 index 0000000..6d785eb --- /dev/null +++ b/proxylab-handout/bytes.h @@ -0,0 +1,26 @@ +#ifndef __DBYTES_H__ +#define __DBYTES_H__ + +#include + +typedef struct Bytes { + char *buf; + size_t size; /* content size */ + size_t len; /* bytes length */ +} Bytes; + +#define bytes_length(str) ((str).len) +#define bytes_buf(str) ((str).buf) +#define bytes_size(str) ((str).size) +#define bytes_ref(str, i) (bytes_buf(str)[(i)]) + +void bytes_malloc(Bytes *pstr); + +void bytes_free(Bytes *pstr); + +void bytes_append(Bytes *pstr, const char *cstr); +void bytes_appendn(Bytes *pstr, const char *buf, size_t buf_len); + +void bytes_cstr(Bytes str, char *cstr); + +#endif diff --git a/proxylab-handout/cache.c b/proxylab-handout/cache.c new file mode 100644 index 0000000..f8e2120 --- /dev/null +++ b/proxylab-handout/cache.c @@ -0,0 +1,135 @@ +/* + * cache.c - lru cache + */ + +#include "cache.h" + +/* return the cache size of the node */ +#define node_cache_size(pnode) (((pnode)->value_len)*sizeof(char)) + +/* create_empty_node */ +static lru_cache_node_t *create_empty_node() +{ + lru_cache_node_t *pnode = (lru_cache_node_t*)malloc( + sizeof(lru_cache_node_t)); + if (pnode == NULL) return NULL; + pnode->value_len = 0; + pnode->key[0] = '\0'; + pnode->value = NULL; + pnode->next = NULL; + pnode->prev = NULL; + return pnode; +} + +/* free_node */ +static void free_node(lru_cache_node_t *pnode) +{ + free(pnode->value); + free(pnode); +} + +/* create_node_from -- create a node from (key, value) pair */ +static lru_cache_node_t *create_node_from( + const char *key, + const char *value, + size_t value_len) +{ + lru_cache_node_t *pnode = create_empty_node(); + pnode->value = (char *)malloc(value_len*sizeof(char)); + pnode->value_len = value_len; + strcpy(pnode->key, key); + int i; + for (i = 0; i < value_len; i++) { + pnode->value[i] = value[i]; + } + return pnode; +} + +/* lru_cache_init -- init lru cache */ +void lru_cache_init(lru_cache_t *pcache, size_t max_cache_size) +{ + pcache->max_cache_size = max_cache_size; + pcache->sentinel = create_empty_node(); + pcache->sentinel->next = pcache->sentinel; + pcache->sentinel->prev = pcache->sentinel; +} + +/* lru_cache_free -- free the lru cache */ +void lru_cache_free(lru_cache_t *pcache) +{ + lru_cache_node_t *cur, *next = NULL; + cur = pcache->sentinel->next; + while (cur != pcache->sentinel) { + next = cur->next; + free_node(cur); + cur = next; + } + free_node(pcache->sentinel); +} + + +/* lru_cache_insert_next -- insert a node to the list */ +static inline void lru_cache_insert_next(lru_cache_t *pcache, + lru_cache_node_t *prev, + lru_cache_node_t *pnode) +{ + + pnode->next = prev->next; + pnode->prev = prev; + prev->next->prev = pnode; + prev->next = pnode; + pcache->cache_size += node_cache_size(pnode); +} + +/* lru_cache_remove -- remove the node from the list */ +static inline void lru_cache_remove(lru_cache_t *pcache, + lru_cache_node_t *cur) +{ + cur->prev->next = cur->next; + cur->next->prev = cur->prev; + pcache->cache_size -= node_cache_size(cur); +} + +/* + * lru_cache_raise -- raise the position of the node as the most + * recently visited node + * assert prev->next != NULL + */ +static void lru_cache_raise(lru_cache_t *pcache, lru_cache_node_t *cur) +{ + // remove the node + lru_cache_remove(pcache, cur); + lru_cache_insert_next(pcache, pcache->sentinel, cur); +} + +/* lru_cache_find -- find a node according to the key */ +lru_cache_node_t *lru_cache_find(lru_cache_t *pcache, const char *key) +{ + lru_cache_node_t *cur; + for (cur = pcache->sentinel->next; + cur != pcache->sentinel; cur = cur->next) { + if (strcasecmp(cur->key, key) == 0) { + /* If a node is found, it's visited once */ + lru_cache_raise(pcache, cur); + return cur; + } + } + return NULL; +} + +/* lru_cache_insert -- insert a new node to the lru cache */ +void lru_cache_insert(lru_cache_t *pcache, + const char *key, + const char *value, + size_t value_len) +{ + lru_cache_node_t *pnode = create_node_from(key, value, value_len); + lru_cache_insert_next(pcache, pcache->sentinel, pnode); + while (pcache->cache_size > pcache->max_cache_size) { + /* remove the least recently used node */ + lru_cache_node_t *node_to_remove = lru_cache_tail(pcache); + lru_cache_remove(pcache, node_to_remove); + /* free the removed node */ + free_node(node_to_remove); + } +} diff --git a/proxylab-handout/cache.h b/proxylab-handout/cache.h new file mode 100644 index 0000000..2c801ac --- /dev/null +++ b/proxylab-handout/cache.h @@ -0,0 +1,41 @@ +/* + * cache.h - lru cache + */ + +#ifndef __CACHE_H__ +#define __CACHE_H__ + +#include "csapp.h" + +typedef struct lru_cache_node_t { + char *value; /* cache content. This is not a null terminated string */ + size_t value_len; /* size of the cache */ + // for simplicity, assume key is a null terminated string + char key[MAXLINE]; /* key - value */ + struct lru_cache_node_t *next; /* next node */ + struct lru_cache_node_t *prev; /* prev node */ +} lru_cache_node_t; + +/* lru cache is implemented as a bidirectional list */ +typedef struct lru_cache_t { + size_t max_cache_size; /* maximum size allowed for the cache. + If this is exceeded, + we simply evict the least recently used block */ + lru_cache_node_t *sentinel; /* sentinel node is a fake node */ + lru_cache_node_t *front; /* front points to most recently used */ + // lru_cache_node_t *tail; /* tail points to least recently used */ + size_t cache_size; /* the size of the cache */ +} lru_cache_t; + +/* lru cache operations */ +void lru_cache_init(lru_cache_t *pcache, size_t max_cache_size); +void lru_cache_free(lru_cache_t *pcache); + +lru_cache_node_t *lru_cache_find(lru_cache_t *pcache, const char *key); + +void lru_cache_insert(lru_cache_t *pcache, + const char *key, const char *value, size_t value_len); + +#define lru_cache_tail(pcache) ((pcache)->sentinel->prev) + +#endif diff --git a/proxylab-handout/csapp.c b/proxylab-handout/csapp.c new file mode 100644 index 0000000..facfa84 --- /dev/null +++ b/proxylab-handout/csapp.c @@ -0,0 +1,1119 @@ +/* + * csapp.c - Functions for the CS:APP3e book + * + * Updated 8/2014 droh: + * - New versions of open_clientfd and open_listenfd are reentrant and + * protocol independent. + * + * - Added protocol-independent inet_ntop and inet_pton functions. The + * inet_ntoa and inet_aton functions are obsolete. + * + * Updated 7/2014 droh: + * - Aded reentrant sio (signal-safe I/O) routines + * + * Updated 4/2013 droh: + * - rio_readlineb: fixed edge case bug + * - rio_readnb: removed redundant EINTR check + */ +/* $begin csapp.c */ +#include "csapp.h" + +/************************** + * Error-handling functions + **************************/ +/* $begin errorfuns */ +/* $begin unixerror */ +void unix_error(char *msg) /* Unix-style error */ +{ + fprintf(stderr, "%s: %s\n", msg, strerror(errno)); + exit(0); +} +/* $end unixerror */ + +void posix_error(int code, char *msg) /* Posix-style error */ +{ + fprintf(stderr, "%s: %s\n", msg, strerror(code)); + exit(0); +} + +void gai_error(int code, char *msg) /* Getaddrinfo-style error */ +{ + fprintf(stderr, "%s: %s\n", msg, gai_strerror(code)); + exit(0); +} + +void app_error(char *msg) /* Application error */ +{ + fprintf(stderr, "%s\n", msg); + exit(0); +} +/* $end errorfuns */ + +void dns_error(char *msg) /* Obsolete gethostbyname error */ +{ + fprintf(stderr, "%s\n", msg); + exit(0); +} + + +/********************************************* + * Wrappers for Unix process control functions + ********************************************/ + +/* $begin forkwrapper */ +pid_t Fork(void) +{ + pid_t pid; + + if ((pid = fork()) < 0) + unix_error("Fork error"); + return pid; +} +/* $end forkwrapper */ + +void Execve(const char *filename, char *const argv[], char *const envp[]) +{ + if (execve(filename, argv, envp) < 0) + unix_error("Execve error"); +} + +/* $begin wait */ +pid_t Wait(int *status) +{ + pid_t pid; + + if ((pid = wait(status)) < 0) + unix_error("Wait error"); + return pid; +} +/* $end wait */ + +pid_t Waitpid(pid_t pid, int *iptr, int options) +{ + pid_t retpid; + + if ((retpid = waitpid(pid, iptr, options)) < 0) + unix_error("Waitpid error"); + return(retpid); +} + +/* $begin kill */ +void Kill(pid_t pid, int signum) +{ + int rc; + + if ((rc = kill(pid, signum)) < 0) + unix_error("Kill error"); +} +/* $end kill */ + +void Pause() +{ + (void)pause(); + return; +} + +unsigned int Sleep(unsigned int secs) +{ + unsigned int rc; + + if ((rc = sleep(secs)) < 0) + unix_error("Sleep error"); + return rc; +} + +unsigned int Alarm(unsigned int seconds) { + return alarm(seconds); +} + +void Setpgid(pid_t pid, pid_t pgid) { + int rc; + + if ((rc = setpgid(pid, pgid)) < 0) + unix_error("Setpgid error"); + return; +} + +pid_t Getpgrp(void) { + return getpgrp(); +} + +/************************************ + * Wrappers for Unix signal functions + ***********************************/ + +/* $begin sigaction */ +handler_t *Signal(int signum, handler_t *handler) +{ + struct sigaction action, old_action; + + action.sa_handler = handler; + sigemptyset(&action.sa_mask); /* Block sigs of type being handled */ + action.sa_flags = SA_RESTART; /* Restart syscalls if possible */ + + if (sigaction(signum, &action, &old_action) < 0) + unix_error("Signal error"); + return (old_action.sa_handler); +} +/* $end sigaction */ + +void Sigprocmask(int how, const sigset_t *set, sigset_t *oldset) +{ + if (sigprocmask(how, set, oldset) < 0) + unix_error("Sigprocmask error"); + return; +} + +void Sigemptyset(sigset_t *set) +{ + if (sigemptyset(set) < 0) + unix_error("Sigemptyset error"); + return; +} + +void Sigfillset(sigset_t *set) +{ + if (sigfillset(set) < 0) + unix_error("Sigfillset error"); + return; +} + +void Sigaddset(sigset_t *set, int signum) +{ + if (sigaddset(set, signum) < 0) + unix_error("Sigaddset error"); + return; +} + +void Sigdelset(sigset_t *set, int signum) +{ + if (sigdelset(set, signum) < 0) + unix_error("Sigdelset error"); + return; +} + +int Sigismember(const sigset_t *set, int signum) +{ + int rc; + if ((rc = sigismember(set, signum)) < 0) + unix_error("Sigismember error"); + return rc; +} + +int Sigsuspend(const sigset_t *set) +{ + int rc = sigsuspend(set); /* always returns -1 */ + if (errno != EINTR) + unix_error("Sigsuspend error"); + return rc; +} + +/************************************************************* + * The Sio (Signal-safe I/O) package - simple reentrant output + * functions that are safe for signal handlers. + *************************************************************/ + +/* Private sio functions */ + +/* $begin sioprivate */ +/* sio_reverse - Reverse a string (from K&R) */ +static void sio_reverse(char s[]) +{ + int c, i, j; + + for (i = 0, j = strlen(s)-1; i < j; i++, j--) { + c = s[i]; + s[i] = s[j]; + s[j] = c; + } +} + +/* sio_ltoa - Convert long to base b string (from K&R) */ +static void sio_ltoa(long v, char s[], int b) +{ + int c, i = 0; + + do { + s[i++] = ((c = (v % b)) < 10) ? c + '0' : c - 10 + 'a'; + } while ((v /= b) > 0); + s[i] = '\0'; + sio_reverse(s); +} + +/* sio_strlen - Return length of string (from K&R) */ +static size_t sio_strlen(char s[]) +{ + int i = 0; + + while (s[i] != '\0') + ++i; + return i; +} +/* $end sioprivate */ + +/* Public Sio functions */ +/* $begin siopublic */ + +ssize_t sio_puts(char s[]) /* Put string */ +{ + return write(STDOUT_FILENO, s, sio_strlen(s)); //line:csapp:siostrlen +} + +ssize_t sio_putl(long v) /* Put long */ +{ + char s[128]; + + sio_ltoa(v, s, 10); /* Based on K&R itoa() */ //line:csapp:sioltoa + return sio_puts(s); +} + +void sio_error(char s[]) /* Put error message and exit */ +{ + sio_puts(s); + _exit(1); //line:csapp:sioexit +} +/* $end siopublic */ + +/******************************* + * Wrappers for the SIO routines + ******************************/ +ssize_t Sio_putl(long v) +{ + ssize_t n; + + if ((n = sio_putl(v)) < 0) + sio_error("Sio_putl error"); + return n; +} + +ssize_t Sio_puts(char s[]) +{ + ssize_t n; + + if ((n = sio_puts(s)) < 0) + sio_error("Sio_puts error"); + return n; +} + +void Sio_error(char s[]) +{ + sio_error(s); +} + +/******************************** + * Wrappers for Unix I/O routines + ********************************/ + +int Open(const char *pathname, int flags, mode_t mode) +{ + int rc; + + if ((rc = open(pathname, flags, mode)) < 0) + unix_error("Open error"); + return rc; +} + +ssize_t Read(int fd, void *buf, size_t count) +{ + ssize_t rc; + + if ((rc = read(fd, buf, count)) < 0) + unix_error("Read error"); + return rc; +} + +ssize_t Write(int fd, const void *buf, size_t count) +{ + ssize_t rc; + + if ((rc = write(fd, buf, count)) < 0) + unix_error("Write error"); + return rc; +} + +off_t Lseek(int fildes, off_t offset, int whence) +{ + off_t rc; + + if ((rc = lseek(fildes, offset, whence)) < 0) + unix_error("Lseek error"); + return rc; +} + +void Close(int fd) +{ + int rc; + + if ((rc = close(fd)) < 0) + unix_error("Close error"); +} + +int Select(int n, fd_set *readfds, fd_set *writefds, + fd_set *exceptfds, struct timeval *timeout) +{ + int rc; + + if ((rc = select(n, readfds, writefds, exceptfds, timeout)) < 0) + unix_error("Select error"); + return rc; +} + +int Dup2(int fd1, int fd2) +{ + int rc; + + if ((rc = dup2(fd1, fd2)) < 0) + unix_error("Dup2 error"); + return rc; +} + +void Stat(const char *filename, struct stat *buf) +{ + if (stat(filename, buf) < 0) + unix_error("Stat error"); +} + +void Fstat(int fd, struct stat *buf) +{ + if (fstat(fd, buf) < 0) + unix_error("Fstat error"); +} + +/********************************* + * Wrappers for directory function + *********************************/ + +DIR *Opendir(const char *name) +{ + DIR *dirp = opendir(name); + + if (!dirp) + unix_error("opendir error"); + return dirp; +} + +struct dirent *Readdir(DIR *dirp) +{ + struct dirent *dep; + + errno = 0; + dep = readdir(dirp); + if ((dep == NULL) && (errno != 0)) + unix_error("readdir error"); + return dep; +} + +int Closedir(DIR *dirp) +{ + int rc; + + if ((rc = closedir(dirp)) < 0) + unix_error("closedir error"); + return rc; +} + +/*************************************** + * Wrappers for memory mapping functions + ***************************************/ +void *Mmap(void *addr, size_t len, int prot, int flags, int fd, off_t offset) +{ + void *ptr; + + if ((ptr = mmap(addr, len, prot, flags, fd, offset)) == ((void *) -1)) + unix_error("mmap error"); + return(ptr); +} + +void Munmap(void *start, size_t length) +{ + if (munmap(start, length) < 0) + unix_error("munmap error"); +} + +/*************************************************** + * Wrappers for dynamic storage allocation functions + ***************************************************/ + +void *Malloc(size_t size) +{ + void *p; + + if ((p = malloc(size)) == NULL) + unix_error("Malloc error"); + return p; +} + +void *Realloc(void *ptr, size_t size) +{ + void *p; + + if ((p = realloc(ptr, size)) == NULL) + unix_error("Realloc error"); + return p; +} + +void *Calloc(size_t nmemb, size_t size) +{ + void *p; + + if ((p = calloc(nmemb, size)) == NULL) + unix_error("Calloc error"); + return p; +} + +void Free(void *ptr) +{ + free(ptr); +} + +/****************************************** + * Wrappers for the Standard I/O functions. + ******************************************/ +void Fclose(FILE *fp) +{ + if (fclose(fp) != 0) + unix_error("Fclose error"); +} + +FILE *Fdopen(int fd, const char *type) +{ + FILE *fp; + + if ((fp = fdopen(fd, type)) == NULL) + unix_error("Fdopen error"); + + return fp; +} + +char *Fgets(char *ptr, int n, FILE *stream) +{ + char *rptr; + + if (((rptr = fgets(ptr, n, stream)) == NULL) && ferror(stream)) + app_error("Fgets error"); + + return rptr; +} + +FILE *Fopen(const char *filename, const char *mode) +{ + FILE *fp; + + if ((fp = fopen(filename, mode)) == NULL) + unix_error("Fopen error"); + + return fp; +} + +void Fputs(const char *ptr, FILE *stream) +{ + if (fputs(ptr, stream) == EOF) + unix_error("Fputs error"); +} + +size_t Fread(void *ptr, size_t size, size_t nmemb, FILE *stream) +{ + size_t n; + + if (((n = fread(ptr, size, nmemb, stream)) < nmemb) && ferror(stream)) + unix_error("Fread error"); + return n; +} + +void Fwrite(const void *ptr, size_t size, size_t nmemb, FILE *stream) +{ + if (fwrite(ptr, size, nmemb, stream) < nmemb) + unix_error("Fwrite error"); +} + + +/**************************** + * Sockets interface wrappers + ****************************/ + +int Socket(int domain, int type, int protocol) +{ + int rc; + + if ((rc = socket(domain, type, protocol)) < 0) + unix_error("Socket error"); + return rc; +} + +void Setsockopt(int s, int level, int optname, const void *optval, int optlen) +{ + int rc; + + if ((rc = setsockopt(s, level, optname, optval, optlen)) < 0) + unix_error("Setsockopt error"); +} + +void Bind(int sockfd, struct sockaddr *my_addr, int addrlen) +{ + int rc; + + if ((rc = bind(sockfd, my_addr, addrlen)) < 0) + unix_error("Bind error"); +} + +void Listen(int s, int backlog) +{ + int rc; + + if ((rc = listen(s, backlog)) < 0) + unix_error("Listen error"); +} + +int Accept(int s, struct sockaddr *addr, socklen_t *addrlen) +{ + int rc; + + if ((rc = accept(s, addr, addrlen)) < 0) + unix_error("Accept error"); + return rc; +} + +void Connect(int sockfd, struct sockaddr *serv_addr, int addrlen) +{ + int rc; + + if ((rc = connect(sockfd, serv_addr, addrlen)) < 0) + unix_error("Connect error"); +} + +/******************************* + * Protocol-independent wrappers + *******************************/ +/* $begin getaddrinfo */ +void Getaddrinfo(const char *node, const char *service, + const struct addrinfo *hints, struct addrinfo **res) +{ + int rc; + + if ((rc = getaddrinfo(node, service, hints, res)) != 0) + gai_error(rc, "Getaddrinfo error"); +} +/* $end getaddrinfo */ + +void Getnameinfo(const struct sockaddr *sa, socklen_t salen, char *host, + size_t hostlen, char *serv, size_t servlen, int flags) +{ + int rc; + + if ((rc = getnameinfo(sa, salen, host, hostlen, serv, + servlen, flags)) != 0) + gai_error(rc, "Getnameinfo error"); +} + +void Freeaddrinfo(struct addrinfo *res) +{ + freeaddrinfo(res); +} + +void Inet_ntop(int af, const void *src, char *dst, socklen_t size) +{ + if (!inet_ntop(af, src, dst, size)) + unix_error("Inet_ntop error"); +} + +void Inet_pton(int af, const char *src, void *dst) +{ + int rc; + + rc = inet_pton(af, src, dst); + if (rc == 0) + app_error("inet_pton error: invalid dotted-decimal address"); + else if (rc < 0) + unix_error("Inet_pton error"); +} + +/******************************************* + * DNS interface wrappers. + * + * NOTE: These are obsolete because they are not thread safe. Use + * getaddrinfo and getnameinfo instead + ***********************************/ + +/* $begin gethostbyname */ +struct hostent *Gethostbyname(const char *name) +{ + struct hostent *p; + + if ((p = gethostbyname(name)) == NULL) + dns_error("Gethostbyname error"); + return p; +} +/* $end gethostbyname */ + +struct hostent *Gethostbyaddr(const char *addr, int len, int type) +{ + struct hostent *p; + + if ((p = gethostbyaddr(addr, len, type)) == NULL) + dns_error("Gethostbyaddr error"); + return p; +} + +/************************************************ + * Wrappers for Pthreads thread control functions + ************************************************/ + +void Pthread_create(pthread_t *tidp, pthread_attr_t *attrp, + void * (*routine)(void *), void *argp) +{ + int rc; + + if ((rc = pthread_create(tidp, attrp, routine, argp)) != 0) + posix_error(rc, "Pthread_create error"); +} + +void Pthread_cancel(pthread_t tid) { + int rc; + + if ((rc = pthread_cancel(tid)) != 0) + posix_error(rc, "Pthread_cancel error"); +} + +void Pthread_join(pthread_t tid, void **thread_return) { + int rc; + + if ((rc = pthread_join(tid, thread_return)) != 0) + posix_error(rc, "Pthread_join error"); +} + +/* $begin detach */ +void Pthread_detach(pthread_t tid) { + int rc; + + if ((rc = pthread_detach(tid)) != 0) + posix_error(rc, "Pthread_detach error"); +} +/* $end detach */ + +void Pthread_exit(void *retval) { + pthread_exit(retval); +} + +pthread_t Pthread_self(void) { + return pthread_self(); +} + +void Pthread_once(pthread_once_t *once_control, void (*init_function)()) { + pthread_once(once_control, init_function); +} + +/******************************* + * Wrappers for Posix semaphores + *******************************/ + +void Sem_init(sem_t *sem, int pshared, unsigned int value) +{ + if (sem_init(sem, pshared, value) < 0) + unix_error("Sem_init error"); +} + +void P(sem_t *sem) +{ + if (sem_wait(sem) < 0) + unix_error("P error"); +} + +void V(sem_t *sem) +{ + if (sem_post(sem) < 0) + unix_error("V error"); +} + +/**************************************** + * The Rio package - Robust I/O functions + ****************************************/ + +/* + * rio_readn - Robustly read n bytes (unbuffered) + */ +/* $begin rio_readn */ +ssize_t rio_readn(int fd, void *usrbuf, size_t n) +{ + size_t nleft = n; + ssize_t nread; + char *bufp = usrbuf; + + while (nleft > 0) { + if ((nread = read(fd, bufp, nleft)) < 0) { + if (errno == EINTR) /* Interrupted by sig handler return */ + nread = 0; /* and call read() again */ + else + return -1; /* errno set by read() */ + } + else if (nread == 0) + break; /* EOF */ + nleft -= nread; + bufp += nread; + } + return (n - nleft); /* return >= 0 */ +} +/* $end rio_readn */ + +/* + * rio_writen - Robustly write n bytes (unbuffered) + */ +/* $begin rio_writen */ +ssize_t rio_writen(int fd, void *usrbuf, size_t n) +{ + size_t nleft = n; + ssize_t nwritten; + char *bufp = usrbuf; + + while (nleft > 0) { + if ((nwritten = write(fd, bufp, nleft)) <= 0) { + if (errno == EINTR) /* Interrupted by sig handler return */ + nwritten = 0; /* and call write() again */ + else + return -1; /* errno set by write() */ + } + nleft -= nwritten; + bufp += nwritten; + } + return n; +} +/* $end rio_writen */ + + +/* + * rio_read - This is a wrapper for the Unix read() function that + * transfers min(n, rio_cnt) bytes from an internal buffer to a user + * buffer, where n is the number of bytes requested by the user and + * rio_cnt is the number of unread bytes in the internal buffer. On + * entry, rio_read() refills the internal buffer via a call to + * read() if the internal buffer is empty. + */ +/* $begin rio_read */ +static ssize_t rio_read(rio_t *rp, char *usrbuf, size_t n) +{ + int cnt; + + while (rp->rio_cnt <= 0) { /* Refill if buf is empty */ + rp->rio_cnt = read(rp->rio_fd, rp->rio_buf, + sizeof(rp->rio_buf)); + if (rp->rio_cnt < 0) { + if (errno != EINTR) /* Interrupted by sig handler return */ + return -1; + } + else if (rp->rio_cnt == 0) /* EOF */ + return 0; + else + rp->rio_bufptr = rp->rio_buf; /* Reset buffer ptr */ + } + + /* Copy min(n, rp->rio_cnt) bytes from internal buf to user buf */ + cnt = n; + if (rp->rio_cnt < n) + cnt = rp->rio_cnt; + memcpy(usrbuf, rp->rio_bufptr, cnt); + rp->rio_bufptr += cnt; + rp->rio_cnt -= cnt; + return cnt; +} +/* $end rio_read */ + +/* + * rio_readinitb - Associate a descriptor with a read buffer and reset buffer + */ +/* $begin rio_readinitb */ +void rio_readinitb(rio_t *rp, int fd) +{ + rp->rio_fd = fd; + rp->rio_cnt = 0; + rp->rio_bufptr = rp->rio_buf; +} +/* $end rio_readinitb */ + +/* + * rio_readnb - Robustly read n bytes (buffered) + */ +/* $begin rio_readnb */ +ssize_t rio_readnb(rio_t *rp, void *usrbuf, size_t n) +{ + size_t nleft = n; + ssize_t nread; + char *bufp = usrbuf; + + while (nleft > 0) { + if ((nread = rio_read(rp, bufp, nleft)) < 0) + return -1; /* errno set by read() */ + else if (nread == 0) + break; /* EOF */ + nleft -= nread; + bufp += nread; + } + return (n - nleft); /* return >= 0 */ +} +/* $end rio_readnb */ + +/* + * rio_readlineb - Robustly read a text line (buffered) + */ +/* $begin rio_readlineb */ +ssize_t rio_readlineb(rio_t *rp, void *usrbuf, size_t maxlen) +{ + int n, rc; + char c, *bufp = usrbuf; + + for (n = 1; n < maxlen; n++) { + if ((rc = rio_read(rp, &c, 1)) == 1) { + *bufp++ = c; + if (c == '\n') { + n++; + break; + } + } else if (rc == 0) { + if (n == 1) + return 0; /* EOF, no data read */ + else + break; /* EOF, some data was read */ + } else + return -1; /* Error */ + } + *bufp = 0; + return n-1; +} +/* $end rio_readlineb */ + +/********************************** + * Wrappers for robust I/O routines + **********************************/ +ssize_t Rio_readn(int fd, void *ptr, size_t nbytes) +{ + ssize_t n; + + if ((n = rio_readn(fd, ptr, nbytes)) < 0) + unix_error("Rio_readn error"); + return n; +} + +void Rio_writen(int fd, void *usrbuf, size_t n) +{ + if (rio_writen(fd, usrbuf, n) != n) + unix_error("Rio_writen error"); +} + +void Rio_readinitb(rio_t *rp, int fd) +{ + rio_readinitb(rp, fd); +} + +ssize_t Rio_readnb(rio_t *rp, void *usrbuf, size_t n) +{ + ssize_t rc; + + if ((rc = rio_readnb(rp, usrbuf, n)) < 0) + unix_error("Rio_readnb error"); + return rc; +} + +ssize_t Rio_readlineb(rio_t *rp, void *usrbuf, size_t maxlen) +{ + ssize_t rc; + + if ((rc = rio_readlineb(rp, usrbuf, maxlen)) < 0) + unix_error("Rio_readlineb error"); + return rc; +} + + +ssize_t rio_readn_ww(int fd, void *usrbuf, size_t n) +{ + ssize_t rc; + if ((rc = rio_readn(fd, usrbuf, n)) < 0) { + fprintf(stderr, "[ERROR] read from %d failed\n", + fd); + } + return rc; +} + +ssize_t rio_writen_ww(int fd, void *usrbuf, size_t n) +{ + ssize_t rc; + if ((rc = rio_writen(fd, usrbuf, n)) < 0) { + fprintf(stderr, "[ERROR] write to %d failed\n", + fd); + } + return rc; +} + +ssize_t rio_readnb_ww(rio_t *rp, void *usrbuf, size_t n) +{ + ssize_t rc; + if ((rc = rio_readnb(rp, usrbuf, n)) < 0) { + fprintf(stderr, "[ERROR] read from %d failed\n", + rp->rio_fd); + } + return rc; +} + +ssize_t rio_readlineb_ww(rio_t *rp, void *usrbuf, size_t maxlen) +{ + ssize_t rc; + if ((rc = rio_readlineb(rp, usrbuf, maxlen)) < 0) { + fprintf(stderr, "[ERROR] read from %d failed\n", + rp->rio_fd); + } + return rc; +} + +/******************************** + * Client/server helper functions + ********************************/ +/* + * open_clientfd - Open connection to server at and + * return a socket descriptor ready for reading and writing. This + * function is reentrant and protocol-independent. + * + * On error, returns -1 and sets errno. + */ +/* $begin open_clientfd */ +int open_clientfd(char *hostname, char *port) { + int clientfd; + struct addrinfo hints, *listp, *p; + + /* Get a list of potential server addresses */ + memset(&hints, 0, sizeof(struct addrinfo)); + hints.ai_socktype = SOCK_STREAM; /* Open a connection */ + hints.ai_flags = AI_NUMERICSERV; /* ... using a numeric port arg. */ + hints.ai_flags |= AI_ADDRCONFIG; /* Recommended for connections */ + + /* fixed by weili. + * To avoid crashing the server, we don't report an error directly + */ + if (getaddrinfo(hostname, port, &hints, &listp) != 0) { + fprintf(stderr, "Could not resolve host: %s\n", hostname); + return -1; + } + // Getaddrinfo(hostname, port, &hints, &listp); + + /* Walk the list for one that we can successfully connect to */ + for (p = listp; p; p = p->ai_next) { + /* Create a socket descriptor */ + if ((clientfd = socket(p->ai_family, p->ai_socktype, p->ai_protocol)) < 0) + continue; /* Socket failed, try the next */ + + /* Connect to the server */ + if (connect(clientfd, p->ai_addr, p->ai_addrlen) != -1) + break; /* Success */ + Close(clientfd); /* Connect failed, try another */ //line:netp:openclientfd:closefd + } + + /* Clean up */ + Freeaddrinfo(listp); + if (!p) /* All connects failed */ + return -1; + else /* The last connect succeeded */ + return clientfd; +} +/* $end open_clientfd */ + +/* + * open_listenfd - Open and return a listening socket on port. This + * function is reentrant and protocol-independent. + * + * On error, returns -1 and sets errno. + */ +/* $begin open_listenfd */ +int open_listenfd(char *port) +{ + struct addrinfo hints, *listp, *p; + int listenfd, optval=1; + + /* Get a list of potential server addresses */ + memset(&hints, 0, sizeof(struct addrinfo)); + hints.ai_socktype = SOCK_STREAM; /* Accept connections */ + hints.ai_flags = AI_PASSIVE | AI_ADDRCONFIG; /* ... on any IP address */ + hints.ai_flags |= AI_NUMERICSERV; /* ... using port number */ + Getaddrinfo(NULL, port, &hints, &listp); + + /* Walk the list for one that we can bind to */ + for (p = listp; p; p = p->ai_next) { + /* Create a socket descriptor */ + if ((listenfd = socket(p->ai_family, p->ai_socktype, p->ai_protocol)) < 0) + continue; /* Socket failed, try the next */ + + /* Eliminates "Address already in use" error from bind */ + Setsockopt(listenfd, SOL_SOCKET, SO_REUSEADDR, //line:netp:csapp:setsockopt + (const void *)&optval , sizeof(int)); + + /* Bind the descriptor to the address */ + if (bind(listenfd, p->ai_addr, p->ai_addrlen) == 0) + break; /* Success */ + Close(listenfd); /* Bind failed, try the next */ + } + + /* Clean up */ + Freeaddrinfo(listp); + if (!p) /* No address worked */ + return -1; + + /* Make it a listening socket ready to accept connection requests */ + if (listen(listenfd, LISTENQ) < 0) { + Close(listenfd); + return -1; + } + return listenfd; +} +/* $end open_listenfd */ + +/**************************************************** + * Wrappers for reentrant protocol-independent helpers + ****************************************************/ +int Open_clientfd(char *hostname, char *port) +{ + int rc; + + if ((rc = open_clientfd(hostname, port)) < 0) + unix_error("Open_clientfd error"); + return rc; +} + +int Open_listenfd(char *port) +{ + int rc; + + if ((rc = open_listenfd(port)) < 0) + unix_error("Open_listenfd error"); + return rc; +} + +int open_clientfd_ww(char *hostname, char *port) +{ + int rc; + if ((rc = open_clientfd(hostname, port)) < 0) { + fprintf(stderr, "[ERROR] open %s:%s failed\n", + hostname, port); + } + return rc; +} + +int open_listenfd_ww(char *port) +{ + int rc; + + if ((rc = open_listenfd(port)) < 0) { + fprintf(stderr, "[ERROR] listen on %s failed\n", + port); + } + return rc; +} + +void close_ww(int fd) +{ + if (close(fd) < 0) { + fprintf(stderr, "[ERROR] %d is closed failed\n", + fd); + } +} + + +/* $end csapp.c */ + + + + diff --git a/proxylab-handout/csapp.h b/proxylab-handout/csapp.h new file mode 100644 index 0000000..eccd1c2 --- /dev/null +++ b/proxylab-handout/csapp.h @@ -0,0 +1,213 @@ +/* + * csapp.h - prototypes and definitions for the CS:APP3e book + */ +/* $begin csapp.h */ +#ifndef __CSAPP_H__ +#define __CSAPP_H__ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +/* Default file permissions are DEF_MODE & ~DEF_UMASK */ +/* $begin createmasks */ +#define DEF_MODE S_IRUSR|S_IWUSR|S_IRGRP|S_IWGRP|S_IROTH|S_IWOTH +#define DEF_UMASK S_IWGRP|S_IWOTH +/* $end createmasks */ + +/* Simplifies calls to bind(), connect(), and accept() */ +/* $begin sockaddrdef */ +typedef struct sockaddr SA; +/* $end sockaddrdef */ + +/* Persistent state for the robust I/O (Rio) package */ +/* $begin rio_t */ +#define RIO_BUFSIZE 8192 +typedef struct { + int rio_fd; /* Descriptor for this internal buf */ + int rio_cnt; /* Unread bytes in internal buf */ + char *rio_bufptr; /* Next unread byte in internal buf */ + char rio_buf[RIO_BUFSIZE]; /* Internal buffer */ +} rio_t; +/* $end rio_t */ + +/* External variables */ +extern int h_errno; /* Defined by BIND for DNS errors */ +extern char **environ; /* Defined by libc */ + +/* Misc constants */ +#define MAXLINE 8192 /* Max text line length */ +#define MAXBUF 8192 /* Max I/O buffer size */ +#define LISTENQ 1024 /* Second argument to listen() */ + +/* Our own error-handling functions */ +void unix_error(char *msg); +void posix_error(int code, char *msg); +void dns_error(char *msg); +void gai_error(int code, char *msg); +void app_error(char *msg); + +/* Process control wrappers */ +pid_t Fork(void); +void Execve(const char *filename, char *const argv[], char *const envp[]); +pid_t Wait(int *status); +pid_t Waitpid(pid_t pid, int *iptr, int options); +void Kill(pid_t pid, int signum); +unsigned int Sleep(unsigned int secs); +void Pause(void); +unsigned int Alarm(unsigned int seconds); +void Setpgid(pid_t pid, pid_t pgid); +pid_t Getpgrp(); + +/* Signal wrappers */ +typedef void handler_t(int); +handler_t *Signal(int signum, handler_t *handler); +void Sigprocmask(int how, const sigset_t *set, sigset_t *oldset); +void Sigemptyset(sigset_t *set); +void Sigfillset(sigset_t *set); +void Sigaddset(sigset_t *set, int signum); +void Sigdelset(sigset_t *set, int signum); +int Sigismember(const sigset_t *set, int signum); +int Sigsuspend(const sigset_t *set); + +/* Sio (Signal-safe I/O) routines */ +ssize_t sio_puts(char s[]); +ssize_t sio_putl(long v); +void sio_error(char s[]); + +/* Sio wrappers */ +ssize_t Sio_puts(char s[]); +ssize_t Sio_putl(long v); +void Sio_error(char s[]); + +/* Unix I/O wrappers */ +int Open(const char *pathname, int flags, mode_t mode); +ssize_t Read(int fd, void *buf, size_t count); +ssize_t Write(int fd, const void *buf, size_t count); +off_t Lseek(int fildes, off_t offset, int whence); +void Close(int fd); +int Select(int n, fd_set *readfds, fd_set *writefds, fd_set *exceptfds, + struct timeval *timeout); +int Dup2(int fd1, int fd2); +void Stat(const char *filename, struct stat *buf); +void Fstat(int fd, struct stat *buf) ; + +/* Directory wrappers */ +DIR *Opendir(const char *name); +struct dirent *Readdir(DIR *dirp); +int Closedir(DIR *dirp); + +/* Memory mapping wrappers */ +void *Mmap(void *addr, size_t len, int prot, int flags, int fd, off_t offset); +void Munmap(void *start, size_t length); + +/* Standard I/O wrappers */ +void Fclose(FILE *fp); +FILE *Fdopen(int fd, const char *type); +char *Fgets(char *ptr, int n, FILE *stream); +FILE *Fopen(const char *filename, const char *mode); +void Fputs(const char *ptr, FILE *stream); +size_t Fread(void *ptr, size_t size, size_t nmemb, FILE *stream); +void Fwrite(const void *ptr, size_t size, size_t nmemb, FILE *stream); + +/* Dynamic storage allocation wrappers */ +void *Malloc(size_t size); +void *Realloc(void *ptr, size_t size); +void *Calloc(size_t nmemb, size_t size); +void Free(void *ptr); + +/* Sockets interface wrappers */ +int Socket(int domain, int type, int protocol); +void Setsockopt(int s, int level, int optname, const void *optval, int optlen); +void Bind(int sockfd, struct sockaddr *my_addr, int addrlen); +void Listen(int s, int backlog); +int Accept(int s, struct sockaddr *addr, socklen_t *addrlen); +void Connect(int sockfd, struct sockaddr *serv_addr, int addrlen); + +/* Protocol independent wrappers */ +void Getaddrinfo(const char *node, const char *service, + const struct addrinfo *hints, struct addrinfo **res); +void Getnameinfo(const struct sockaddr *sa, socklen_t salen, char *host, + size_t hostlen, char *serv, size_t servlen, int flags); +void Freeaddrinfo(struct addrinfo *res); +void Inet_ntop(int af, const void *src, char *dst, socklen_t size); +void Inet_pton(int af, const char *src, void *dst); + +/* DNS wrappers */ +struct hostent *Gethostbyname(const char *name); +struct hostent *Gethostbyaddr(const char *addr, int len, int type); + +/* Pthreads thread control wrappers */ +void Pthread_create(pthread_t *tidp, pthread_attr_t *attrp, + void * (*routine)(void *), void *argp); +void Pthread_join(pthread_t tid, void **thread_return); +void Pthread_cancel(pthread_t tid); +void Pthread_detach(pthread_t tid); +void Pthread_exit(void *retval); +pthread_t Pthread_self(void); +void Pthread_once(pthread_once_t *once_control, void (*init_function)()); + +/* POSIX semaphore wrappers */ +void Sem_init(sem_t *sem, int pshared, unsigned int value); +void P(sem_t *sem); +void V(sem_t *sem); + +/* Rio (Robust I/O) package */ +ssize_t rio_readn(int fd, void *usrbuf, size_t n); +ssize_t rio_writen(int fd, void *usrbuf, size_t n); +void rio_readinitb(rio_t *rp, int fd); +ssize_t rio_readnb(rio_t *rp, void *usrbuf, size_t n); +ssize_t rio_readlineb(rio_t *rp, void *usrbuf, size_t maxlen); + +/* Wrappers for Rio package */ +ssize_t Rio_readn(int fd, void *usrbuf, size_t n); +void Rio_writen(int fd, void *usrbuf, size_t n); +void Rio_readinitb(rio_t *rp, int fd); +ssize_t Rio_readnb(rio_t *rp, void *usrbuf, size_t n); +ssize_t Rio_readlineb(rio_t *rp, void *usrbuf, size_t maxlen); + + +/* rio read with warning message if error happended */ +ssize_t rio_readn_ww(int fd, void *usrbuf, size_t n); +ssize_t rio_writen_ww(int fd, void *usrbuf, size_t n); +ssize_t rio_readnb_ww(rio_t *rp, void *usrbuf, size_t n); +ssize_t rio_readlineb_ww(rio_t *rp, void *usrbuf, size_t maxlen); + +/* Reentrant protocol-independent client/server helpers */ +int open_clientfd(char *hostname, char *port); +int open_listenfd(char *port); + +/* Wrappers for reentrant protocol-independent client/server helpers */ +int Open_clientfd(char *hostname, char *port); +int Open_listenfd(char *port); + + +/* reentrant protocol-independent client/server helpers with warning + * message if error happended*/ +int open_clientfd_ww(char *hostname, char *porT); +int open_listenfd_ww(char *port); +void close_ww(int fd); + + +#endif /* __CSAPP_H__ */ +/* $end csapp.h */ diff --git a/proxylab-handout/driver.sh b/proxylab-handout/driver.sh new file mode 100755 index 0000000..940b798 --- /dev/null +++ b/proxylab-handout/driver.sh @@ -0,0 +1,410 @@ +#!/bin/bash +# +# driver.sh - This is a simple autograder for the Proxy Lab. It does +# basic sanity checks that determine whether or not the code +# behaves like a concurrent caching proxy. +# +# David O'Hallaron, Carnegie Mellon University +# updated: 12/09/2013 +# +# usage: ./driver.sh +# + +# Point values +MAX_BASIC=40 +MAX_CONCURRENCY=15 +MAX_CACHE=15 + +# Various constants +HOME_DIR=`pwd` +PROXY_DIR="./.proxy" +NOPROXY_DIR="./.noproxy" +TIMEOUT=5 +MAX_RAND=63000 +PORT_START=1024 +PORT_MAX=65000 +MAX_PORT_TRIES=10 + +# List of text and binary files for the basic test +BASIC_LIST="home.html + csapp.c + tiny.c + godzilla.jpg + tiny" + +# List of text files for the cache test +CACHE_LIST="tiny.c + home.html + csapp.c" + +# The file we will fetch for various tests +FETCH_FILE="home.html" + +##### +# Helper functions +# + +# +# download_proxy - download a file from the origin server via the proxy +# usage: download_proxy +# +function download_proxy { + cd $1 + curl --max-time ${TIMEOUT} --silent --proxy $4 --output $2 $3 + (( $? == 28 )) && echo "Error: Fetch timed out after ${TIMEOUT} seconds" + cd $HOME_DIR +} + +# +# download_noproxy - download a file directly from the origin server +# usage: download_noproxy +# +function download_noproxy { + cd $1 + curl --max-time ${TIMEOUT} --silent --output $2 $3 + (( $? == 28 )) && echo "Error: Fetch timed out after ${TIMEOUT} seconds" + cd $HOME_DIR +} + +# +# clear_dirs - Clear the download directories +# +function clear_dirs { + rm -rf ${PROXY_DIR}/* + rm -rf ${NOPROXY_DIR}/* +} + +# +# wait_for_port_use - Spins until the TCP port number passed as an +# argument is actually being used. Times out after 5 seconds. +# +function wait_for_port_use() { + timeout_count="0" + portsinuse=`netstat --numeric-ports --numeric-hosts -a --protocol=tcpip \ + | grep tcp | cut -c21- | cut -d':' -f2 | cut -d' ' -f1 \ + | grep -E "[0-9]+" | uniq | tr "\n" " "` + + echo "${portsinuse}" | grep -wq "${1}" + while [ "$?" != "0" ] + do + timeout_count=`expr ${timeout_count} + 1` + if [ "${timeout_count}" == "${MAX_PORT_TRIES}" ]; then + kill -ALRM $$ + fi + + sleep 1 + portsinuse=`netstat --numeric-ports --numeric-hosts -a --protocol=tcpip \ + | grep tcp | cut -c21- | cut -d':' -f2 | cut -d' ' -f1 \ + | grep -E "[0-9]+" | uniq | tr "\n" " "` + echo "${portsinuse}" | grep -wq "${1}" + done +} + + +# +# free_port - returns an available unused TCP port +# +function free_port { + # Generate a random port in the range [PORT_START, PORT_START+MAX_RAND] + port=$((( RANDOM % ${MAX_RAND}) + ${PORT_START})) + + while [ TRUE ] + do + portsinuse=`netstat --numeric-ports --numeric-hosts -a --protocol=tcpip \ + | grep tcp | cut -c21- | cut -d':' -f2 | cut -d' ' -f1 \ + | grep -E "[0-9]+" | uniq | tr "\n" " "` + + echo "${portsinuse}" | grep -wq "${port}" + if [ "$?" == "0" ]; then + if [ $port -eq ${PORT_MAX} ] + then + echo "-1" + return + fi + port=`expr ${port} + 1` + else + echo "${port}" + return + fi + done +} + + +####### +# Main +####### + +###### +# Verify that we have all of the expected files with the right +# permissions +# + +# Kill any stray proxies or tiny servers owned by this user +killall -q proxy tiny nop-server.py 2> /dev/null + +# Make sure we have a Tiny directory +if [ ! -d ./tiny ] +then + echo "Error: ./tiny directory not found." + exit +fi + +# If there is no Tiny executable, then try to build it +if [ ! -x ./tiny/tiny ] +then + echo "Building the tiny executable." + (cd ./tiny; make) + echo "" +fi + +# Make sure we have all the Tiny files we need +if [ ! -x ./tiny/tiny ] +then + echo "Error: ./tiny/tiny not found or not an executable file." + exit +fi +for file in ${BASIC_LIST} +do + if [ ! -e ./tiny/${file} ] + then + echo "Error: ./tiny/${file} not found." + exit + fi +done + +# Make sure we have an existing executable proxy +if [ ! -x ./proxy ] +then + echo "Error: ./proxy not found or not an executable file. Please rebuild your proxy and try again." + exit +fi + +# Make sure we have an existing executable nop-server.py file +if [ ! -x ./nop-server.py ] +then + echo "Error: ./nop-server.py not found or not an executable file." + exit +fi + +# Create the test directories if needed +if [ ! -d ${PROXY_DIR} ] +then + mkdir ${PROXY_DIR} +fi + +if [ ! -d ${NOPROXY_DIR} ] +then + mkdir ${NOPROXY_DIR} +fi + +# Add a handler to generate a meaningful timeout message +trap 'echo "Timeout waiting for the server to grab the port reserved for it"; kill $$' ALRM + +##### +# Basic +# +echo "*** Basic ***" + +# Run the Tiny Web server +tiny_port=$(free_port) +echo "Starting tiny on ${tiny_port}" +cd ./tiny +./tiny ${tiny_port} &> /dev/null & +tiny_pid=$! +cd ${HOME_DIR} + +# Wait for tiny to start in earnest +wait_for_port_use "${tiny_port}" + +# Run the proxy +proxy_port=$(free_port) +echo "Starting proxy on ${proxy_port}" +./proxy ${proxy_port} &> /dev/null & +proxy_pid=$! + +# Wait for the proxy to start in earnest +wait_for_port_use "${proxy_port}" + + +# Now do the test by fetching some text and binary files directly from +# Tiny and via the proxy, and then comparing the results. +numRun=0 +numSucceeded=0 +for file in ${BASIC_LIST} +do + numRun=`expr $numRun + 1` + echo "${numRun}: ${file}" + clear_dirs + + # Fetch using the proxy + echo " Fetching ./tiny/${file} into ${PROXY_DIR} using the proxy" + download_proxy $PROXY_DIR ${file} "http://localhost:${tiny_port}/${file}" "http://localhost:${proxy_port}" + + # Fetch directly from Tiny + echo " Fetching ./tiny/${file} into ${NOPROXY_DIR} directly from Tiny" + download_noproxy $NOPROXY_DIR ${file} "http://localhost:${tiny_port}/${file}" + + # Compare the two files + echo " Comparing the two files" + diff -q ${PROXY_DIR}/${file} ${NOPROXY_DIR}/${file} # &> /dev/null + if [ $? -eq 0 ]; then + numSucceeded=`expr ${numSucceeded} + 1` + echo " Success: Files are identical." + else + echo " Failure: Files differ." + fi +done + +echo "Killing tiny and proxy" +kill $tiny_pid 2> /dev/null +wait $tiny_pid 2> /dev/null +kill $proxy_pid 2> /dev/null +wait $proxy_pid 2> /dev/null + +basicScore=`expr ${MAX_BASIC} \* ${numSucceeded} / ${numRun}` + +echo "Basic: $basicScore / ${MAX_BASIC}" + +###### +# Concurrency +# + +echo "" +echo "*** Concurrency ***" + +# Run the Tiny Web server +tiny_port=$(free_port) +echo "Starting tiny on port ${tiny_port}" +cd ./tiny +./tiny ${tiny_port} &> /dev/null & +tiny_pid=$! +cd ${HOME_DIR} + +# Wait for tiny to start in earnest +wait_for_port_use "${tiny_port}" + +# Run the proxy +proxy_port=$(free_port) +echo "Starting proxy on port ${proxy_port}" +./proxy ${proxy_port} &> /dev/null & +proxy_pid=$! + +# Wait for the proxy to start in earnest +wait_for_port_use "${proxy_port}" + +# Run a special blocking nop-server that never responds to requests +nop_port=$(free_port) +echo "Starting the blocking NOP server on port ${nop_port}" +./nop-server.py ${nop_port} &> /dev/null & +nop_pid=$! + +# Wait for the nop server to start in earnest +wait_for_port_use "${nop_port}" + +# Try to fetch a file from the blocking nop-server using the proxy +clear_dirs +echo "Trying to fetch a file from the blocking nop-server" +download_proxy $PROXY_DIR "nop-file.txt" "http://localhost:${nop_port}/nop-file.txt" "http://localhost:${proxy_port}" & + +# Fetch directly from Tiny +echo "Fetching ./tiny/${FETCH_FILE} into ${NOPROXY_DIR} directly from Tiny" +download_noproxy $NOPROXY_DIR ${FETCH_FILE} "http://localhost:${tiny_port}/${FETCH_FILE}" + +# Fetch using the proxy +echo "Fetching ./tiny/${FETCH_FILE} into ${PROXY_DIR} using the proxy" +download_proxy $PROXY_DIR ${FETCH_FILE} "http://localhost:${tiny_port}/${FETCH_FILE}" "http://localhost:${proxy_port}" + +# See if the proxy fetch succeeded +echo "Checking whether the proxy fetch succeeded" +diff -q ${PROXY_DIR}/${FETCH_FILE} ${NOPROXY_DIR}/${FETCH_FILE} # &> /dev/null +if [ $? -eq 0 ]; then + concurrencyScore=${MAX_CONCURRENCY} + echo "Success: Was able to fetch tiny/${FETCH_FILE} from the proxy." +else + concurrencyScore=0 + echo "Failure: Was not able to fetch tiny/${FETCH_FILE} from the proxy." +fi + +# Clean up +echo "Killing tiny, proxy, and nop-server" +kill $tiny_pid 2> /dev/null +wait $tiny_pid 2> /dev/null +kill $proxy_pid 2> /dev/null +wait $proxy_pid 2> /dev/null +kill $nop_pid 2> /dev/null +wait $nop_pid 2> /dev/null + +echo "Concurrency: $concurrencyScore / ${MAX_CONCURRENCY}" + +##### +# Caching +# +echo "" +echo "*** Cache ***" + +# Run the Tiny Web server +tiny_port=$(free_port) +echo "Starting tiny on port ${tiny_port}" +cd ./tiny +./tiny ${tiny_port} &> /dev/null & +tiny_pid=$! +cd ${HOME_DIR} + +# Wait for tiny to start in earnest +wait_for_port_use "${tiny_port}" + +# Run the proxy +proxy_port=$(free_port) +echo "Starting proxy on port ${proxy_port}" +./proxy ${proxy_port} &> /dev/null & +proxy_pid=$! + +# Wait for the proxy to start in earnest +wait_for_port_use "${proxy_port}" + +# Fetch some files from tiny using the proxy +clear_dirs +for file in ${CACHE_LIST} +do + echo "Fetching ./tiny/${file} into ${PROXY_DIR} using the proxy" + download_proxy $PROXY_DIR ${file} "http://localhost:${tiny_port}/${file}" "http://localhost:${proxy_port}" +done + +# Kill Tiny +echo "Killing tiny" +kill $tiny_pid 2> /dev/null +wait $tiny_pid 2> /dev/null + +# Now try to fetch a cached copy of one of the fetched files. +echo "Fetching a cached copy of ./tiny/${FETCH_FILE} into ${NOPROXY_DIR}" +download_proxy $NOPROXY_DIR ${FETCH_FILE} "http://localhost:${tiny_port}/${FETCH_FILE}" "http://localhost:${proxy_port}" + +# See if the proxy fetch succeeded by comparing it with the original +# file in the tiny directory +diff -q ./tiny/${FETCH_FILE} ${NOPROXY_DIR}/${FETCH_FILE} # &> /dev/null +if [ $? -eq 0 ]; then + cacheScore=${MAX_CACHE} + echo "Success: Was able to fetch tiny/${FETCH_FILE} from the cache." +else + cacheScore=0 + echo "Failure: Was not able to fetch tiny/${FETCH_FILE} from the proxy cache." +fi + +# Kill the proxy +echo "Killing proxy" +kill $proxy_pid 2> /dev/null +wait $proxy_pid 2> /dev/null + +echo "Cache: $cacheScore / ${MAX_CACHE}" + +# Emit the total score +totalScore=`expr ${basicScore} + ${cacheScore} + ${concurrencyScore}` +maxScore=`expr ${MAX_BASIC} + ${MAX_CACHE} + ${MAX_CONCURRENCY}` +echo "" +echo "totalScore = ${totalScore} / ${maxScore}" + +echo "" +echo "{ \"scores\": {\"Basic\":${basicScore}, \"Concurrency\":${concurrencyScore}, \"Caching\":${cacheScore}}, \"scoreboard\": [${totalScore}, ${basicScore}, ${concurrencyScore}, ${cacheScore}]}" + +exit + diff --git a/proxylab-handout/free-port.sh b/proxylab-handout/free-port.sh new file mode 100755 index 0000000..f31ef27 --- /dev/null +++ b/proxylab-handout/free-port.sh @@ -0,0 +1,26 @@ +#!/bin/bash + +# +# free-port.sh - returns an unused TCP port. +# +PORT_START=4500 +PORT_MAX=65000 +port=${PORT_START} + +while [ TRUE ] +do + portsinuse=`netstat --numeric-ports --numeric-hosts -a --protocol=tcpip | grep tcp | \ + cut -c21- | cut -d':' -f2 | cut -d' ' -f1 | grep -E "[0-9]+" | uniq | tr "\n" " "` + + echo "${portsinuse}" | grep -wq "${port}" + if [ "$?" == "0" ]; then + if [ $port -eq ${PORT_MAX} ] + then + exit -1 + fi + port=`expr ${port} + 1` + else + echo $port + exit 0 + fi +done diff --git a/proxylab-handout/nop-server.py b/proxylab-handout/nop-server.py new file mode 100755 index 0000000..e6ab317 --- /dev/null +++ b/proxylab-handout/nop-server.py @@ -0,0 +1,20 @@ +#!/usr/bin/python + +# nop-server.py - This is a server that we use to create head-of-line +# blocking for the concurrency test. It accepts a +# connection, and then spins forever. +# +# usage: nop-server.py +# +import socket +import sys + +#create an INET, STREAMing socket +serversocket = socket.socket(socket.AF_INET, socket.SOCK_STREAM) +serversocket.bind(('', int(sys.argv[1]))) +serversocket.listen(5) + +while 1: + channel, details = serversocket.accept() + while 1: + continue diff --git a/proxylab-handout/port-for-user.pl b/proxylab-handout/port-for-user.pl new file mode 100755 index 0000000..35da92c --- /dev/null +++ b/proxylab-handout/port-for-user.pl @@ -0,0 +1,39 @@ +#! /usr/bin/perl -w +use strict; +use Digest::MD5; +# +# port-for-user.pl - Return a port number, p, for a given user, with a +# low probability of collisions. The port p is always even, so that +# users can use p and p+1 for testing with proxy and the Tiny web +# server. +# +# usage: ./port-for-user.pl [optional user name] +# +my $maxport = 65536; +my $minport = 1024; + + +# hashname - compute an even port number from a hash of the argument +sub hashname { + my $name = shift; + my $port; + my $hash = Digest::MD5::md5_hex($name); + # take only the last 32 bits => last 8 hex digits + $hash = substr($hash, -8); + $hash = hex($hash); + $port = $hash % ($maxport - $minport) + $minport; + $port = $port & 0xfffffffe; + print "$name: $port\n"; +} + + +# If called with no command line arg, then hash the userid, otherwise +# hash the command line argument(s). +if($#ARGV == -1) { + my ($username) = getpwuid($<); + hashname($username); +} else { + foreach(@ARGV) { + hashname($_); + } +} diff --git a/proxylab-handout/proxy.c b/proxylab-handout/proxy.c new file mode 100644 index 0000000..dc8c25b --- /dev/null +++ b/proxylab-handout/proxy.c @@ -0,0 +1,287 @@ +/* + * proxy.c -- a simple http proxy supporting GET operation + */ + +#include +#include +#include +#include "csapp.h" +#include "util.h" +#include "bytes.h" +#include "cache.h" + +// #define DEBUG +#undef DEBUG + +/* Recommended max cache and object sizes */ +#define MAX_CACHE_SIZE 1049000 +#define MAX_OBJECT_SIZE 102400 + +/* global lru cache */ +lru_cache_t lru_cache; + +/* mutex to protect the lru cache update operations */ +sem_t mutex; + + +/* You won't lose style points for including these long lines in your code */ +static const char *user_agent_hdr = "User-Agent: Mozilla/5.0 (X11; Linux x86_64; rv:10.0.3) Gecko/20120305 Firefox/10.0.3\r\n"; +static const char *accept_hdr = "Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8\r\n"; +static const char *accept_encoding_hdr = "Accept-Encoding: gzip, deflate\r\n"; + + +/* usage */ +void usage() +{ + printf("Usage: proxy [port]\n"); + exit(-1); +} + + + +/* forward_response -- forward the response back to the client + * key: the formatted url of the content. Used for lru_cache + * infd: the file descriptor of remote server. We read response from infd + * outfd: the client file descriptor. We write response back to outfd + */ +void forward_response(const char *key, int infd, int outfd) +{ + rio_t rio; + rio_readinitb(&rio, infd); + + /* Since we don't know the length of the response, we extend + * the response buffer dynamically. + * First, we read MAXLINE number of bytes into the buffer. + * Then we append the buffer to the response buffer. + */ + + /* temporary buffer */ + char buf[MAXLINE]; + + /* response buffer */ + struct Bytes response; + + /* allocate and initialize the response buffer */ + bytes_malloc(&response); + + /* num_bytes is the number of bytes read for each rio_readnb operation */ + int num_bytes; + while ((num_bytes=rio_readnb_ww(&rio, buf, MAXLINE)) > 0) { + bytes_appendn(&response, buf, num_bytes); + } + + /* The remote connection may be closed during the read operation. + * The return value is < 0. + */ + if (num_bytes < 0) { + /* goto is used here to make sure that response is freed + */ + goto FORWARD_RESPONSE_RETURN; + } + +#ifdef DEBUG + fprintf(stderr, "response length: %zu\n", bytes_length(response)); +#endif + + /* the client may have closed the connection */ + if (!rio_writen_ww(outfd, + bytes_buf(response), + bytes_length(response))) { + goto FORWARD_RESPONSE_RETURN; + } + + /* cache the response if necessary */ + if (bytes_length(response) < MAX_OBJECT_SIZE) { + sem_wait(&mutex); + lru_cache_insert(&lru_cache, key, bytes_buf(response), + bytes_length(response)); + sem_post(&mutex); + } +FORWARD_RESPONSE_RETURN: + /* free the response */ + bytes_free(&response); +} + + +/* forward -- forward the request to remote server and the response from + * remote server + */ +void forward(int fromfd) +{ + rio_t rio; + rio_readinitb(&rio, fromfd); + char linebuf[MAXLINE], // temporary buffer for each line we read + method[MAXLINE], // http method + uri[MAXLINE], // uri + version[MAXLINE], // http version. + host[MAXLINE], + port[MAXLINE], + dir[MAXLINE], // the director after the host in the uri + /* TODO: is it necessary to use a dynamic buffer to store + * the request? + * For all the cases tested now, seems it's not necessary + */ + request_buf[MAXBUF], // The request is stored in the buffer + header_name[MAXLINE], // used for parsing the header + header_value[MAXLINE]; + + if (!rio_readlineb_ww(&rio, linebuf, MAXLINE)) { + return; + } + sscanf(linebuf, "%s %s %s", method, uri, version); + if (strcasecmp(method, "GET") != 0) { + // TODO + fprintf(stderr, "[ERROR] method is not GET\n"); + return; + } + parse_uri(uri, host, port, dir); +#ifdef DEBUG + fprintf(stderr, "uri: %s\n", uri); + fprintf(stderr, "host: %s port: %s dir: %s\n", host, port, dir); +#endif + + char formated_uri[MAXLINE]; + sprintf(formated_uri, "%s:%s%s", host, port, dir); + + /* + * if we find the content in the cache, we return it directly. + * TODO: What if the content has been modifed? + */ + sem_wait(&mutex); + + lru_cache_node_t *pnode = lru_cache_find(&lru_cache, formated_uri); + + if (pnode) { + rio_writen_ww(fromfd, pnode->value, pnode->value_len); + sem_post(&mutex); + return; + } + sem_post(&mutex); + + /* According to the requirement, all requests are + * forwarded as HTTP/1.0 + */ + sprintf(request_buf, "%s %s %s\r\n", method, dir, "HTTP/1.0"); + + /* if the header contains host, we forward it directly. + * Otherwise we fill the host according to the uri + */ + int has_host = 0; + if (rio_readlineb_ww(&rio, linebuf, MAXLINE) < 0) { + return; + } + while (strcmp(linebuf, "\r\n") != 0) { + parse_header(linebuf, header_name, header_value); + if (strcasecmp(header_name, "Host") == 0) { + has_host = 1; + sprintf(request_buf, "%s%s", request_buf, linebuf); + } else if (strcasecmp(header_name, "User-Agent") == 0 || + strcasecmp(header_name, "Accept") == 0 || + strcasecmp(header_name, "Accept-Encoding") == 0 || + strcasecmp(header_name, "Connection") == 0 || + strcasecmp(header_name, "Proxy-Connection") == 0) { + // we have default values for these headers + } else { + // for other headers, we forward it directly + sprintf(request_buf, "%s%s", request_buf, linebuf); + } + if (rio_readlineb_ww(&rio, linebuf, MAXLINE) < 0) { + return; + } + } + if (!has_host) { + /* add host according to uri parsing result */ + sprintf(request_buf, "%s%s: %s:%s\r\n", request_buf, "Host", + host, port); + } + + /* add default value for these headers */ + sprintf(request_buf, "%s%s", request_buf, + user_agent_hdr); + sprintf(request_buf, "%s%s", request_buf, + accept_hdr); + sprintf(request_buf, "%s%s", request_buf, + accept_encoding_hdr); + sprintf(request_buf, "%s%s:close\r\n", request_buf, + "Connection"); + sprintf(request_buf, "%s%s:close\r\n", request_buf, + "Proxy-Connection"); + sprintf(request_buf, "%s\r\n", request_buf); +#ifdef DEBUG + fprintf(stderr, "request buf:\n%s\n", request_buf); +#endif + int clientfd = open_clientfd_ww(host, port); + if (clientfd < 0) { + // TODO: is it ok to directly return? + // Maybe better error handling expected. + return ; + } + if (rio_writen_ww(clientfd, request_buf, strlen(request_buf)) < 0) { + close_ww(clientfd); + return; + } + + /* forward the response of the server to the client */ + forward_response(formated_uri, clientfd, fromfd); + close_ww(clientfd); +} + + +/* thread -- the things to do for each thread */ +void *thread(void *vargp) +{ + int connfd = *((int*)vargp); + pthread_detach(pthread_self()); + free(vargp); + forward(connfd); + close(connfd); + return NULL; +} + + +/* sigpipe_handler -- handle sig pipe + * SIGPIPE is generated when the connection is closed by another end early. + * */ +void sigpipe_handler(int sig) +{ + fprintf(stderr, "[WARNING] Catch a sigpipe signal\n"); +} + + +/* main */ +int main(int argc, char **argv) +{ + if (argc != 2) { + usage(); + } + Signal(SIGPIPE, sigpipe_handler); + int listenfd = Open_listenfd(argv[1]); + struct sockaddr_storage clientaddr; + socklen_t clientlen = sizeof(clientaddr); + int *connfdp; + lru_cache_init(&lru_cache, MAX_CACHE_SIZE); + sem_init(&mutex, 0, 1); + while (1) { + connfdp = malloc(sizeof(int)); + if (connfdp == NULL) { + fprintf(stderr, "malloc failed\n"); + continue; + } +#ifdef DEBUG + fprintf(stderr, "accepting...\n"); +#endif + *connfdp = accept(listenfd, + (SA *)&clientaddr, + &clientlen); + if (*connfdp == -1) { + fprintf(stderr, "Accept failed: %d\n", listenfd); + free(connfdp); + } else { + // TODO: use thread pool + pthread_t tid; + pthread_create(&tid, NULL, thread, connfdp); + } + } + lru_cache_free(&lru_cache); + return 0; +} diff --git a/proxylab-handout/test.c b/proxylab-handout/test.c new file mode 100644 index 0000000..6417d46 --- /dev/null +++ b/proxylab-handout/test.c @@ -0,0 +1,143 @@ +/* + * test.c -- simple test + */ +#include "util.h" +#include "bytes.h" +#include "csapp.h" +#include "cache.h" + +#include +#include + +/* simple check */ + +/* check whether two strings are equal */ +#define CHECK_STREQUAL(s1, s2) if (strcmp((s1), (s2))) { \ + fprintf(stderr, "%d [CHECK_STREQUAL] %s != %s\n", __LINE__, s1, s2); \ + exit(-1);\ +} + + +/* check whether two scalar values(int,char,float,...) are equal */ +#define CHECK_EQUAL(a, b) if (!((a) == (b))) {\ + fprintf(stderr, "%d [CHECK_EQUAL] failed", __LINE__); \ + fflush(stderr);\ + exit(-1);\ +} + +/* test_bytes_append -- test append a cstr to the bytes */ +void test_bytes_append() +{ + Bytes str; + bytes_malloc(&str); + bytes_append(&str, "abc"); + CHECK_EQUAL(bytes_length(str), 3); + char cstr[4]; + bytes_cstr(str, cstr); + CHECK_STREQUAL(cstr, "abc"); + bytes_free(&str); +} + +/* test_bytes_resizing -- when the size of the bytes content exceeds + * the allocated memory, we dynamically reallocate a block of memory + * to fit the bytes + */ +void test_bytes_resizing() +{ + Bytes str; + bytes_malloc(&str); + char fake[2]; + char c; + int i; + fake[1] = '\0'; + for (c = 'a'; c <= 'z'; c+=1) { + for (i = 0; i < 1024; i++) { + fake[0] = c; + bytes_append(&str, fake); + } + } + CHECK_EQUAL(bytes_length(str), 26*1024); + CHECK_EQUAL(bytes_size(str), 32*1024); + CHECK_EQUAL(bytes_ref(str, bytes_length(str)), + '\0'); + for (c = 'a'; c <= 'z'; c+=1) { + for (i = 0; i < 1024; i++) { + int idx = (c-'a'); + idx *= 1024; + idx += i; + CHECK_EQUAL(bytes_ref(str, idx), c); + } + } + bytes_free(&str); +} + +/* test_parse_uri -- test parse_uri basic functionality */ +void test_parse_uri() +{ + char host[MAXLINE]; + char port[MAXLINE]; + char dir[MAXLINE]; + parse_uri("http://www.cmu.edu/hub/index.html", + host, + port, + dir + ); + CHECK_STREQUAL(host, "www.cmu.edu"); + CHECK_STREQUAL(port, "80"); + CHECK_STREQUAL(dir, "/hub/index.html"); + parse_uri("www.cmu.edu:8000/hub/index.html", host, port, dir); + CHECK_STREQUAL(host, "www.cmu.edu"); + CHECK_STREQUAL(port, "8000"); + CHECK_STREQUAL(dir, "/hub/index.html"); +} + +/* print_cache -- print the content of the cache */ +void print_cache(lru_cache_t *pcache) +{ + lru_cache_node_t *p; + for (p = pcache->sentinel->next; + p != pcache->sentinel; p=p->next) { + printf("%c ", p->value[0]); + } + printf("\n"); + fflush(stdin); +} + +/* test_cache -- test basic lru_cache functionality */ +void test_cache() +{ + lru_cache_t cache; + lru_cache_init(&cache, 16); + + char str[2]; + char c; + for (c = 'a'; c < 'a'+16; c += 1) { + str[0] = c; + str[1] = '\0'; + lru_cache_insert(&cache, str, str, 1); + CHECK_EQUAL(cache.cache_size, c-'a'+1); + CHECK_EQUAL(cache.sentinel->next->value[0], + str[0]); + } + print_cache(&cache); + // printf("%zu\n", cache.cache_size); + CHECK_EQUAL(cache.cache_size, 16); + for (c = 'a'+16; c < 'a'+26; c+=1) { + str[0] = c; + lru_cache_insert(&cache, str, str, 1); + CHECK_EQUAL(cache.cache_size, 16); + CHECK_EQUAL(cache.sentinel->next->value[0], + str[0]); + } + print_cache(&cache); + lru_cache_free(&cache); +} + +int main() +{ + test_parse_uri(); + test_bytes_append(); + test_bytes_resizing(); + test_cache(); + return 0; +} diff --git a/proxylab-handout/tiny/Makefile b/proxylab-handout/tiny/Makefile new file mode 100644 index 0000000..a401cfa --- /dev/null +++ b/proxylab-handout/tiny/Makefile @@ -0,0 +1,22 @@ +CC = gcc +CFLAGS = -O2 -Wall -I . + +# This flag includes the Pthreads library on a Linux box. +# Others systems will probably require something different. +LIB = -lpthread + +all: tiny cgi + +tiny: tiny.c csapp.o + $(CC) $(CFLAGS) -o tiny tiny.c csapp.o $(LIB) + +csapp.o: csapp.c + $(CC) $(CFLAGS) -c csapp.c + +cgi: + (cd cgi-bin; make) + +clean: + rm -f *.o tiny *~ + (cd cgi-bin; make clean) + diff --git a/proxylab-handout/tiny/README b/proxylab-handout/tiny/README new file mode 100644 index 0000000..fdbbdda --- /dev/null +++ b/proxylab-handout/tiny/README @@ -0,0 +1,38 @@ +Tiny Web server +Dave O'Hallaron +Carnegie Mellon University + +This is the home directory for the Tiny server, a 200-line Web +server that we use in "15-213: Intro to Computer Systems" at Carnegie +Mellon University. Tiny uses the GET method to serve static content +(text, HTML, GIF, and JPG files) out of ./ and to serve dynamic +content by running CGI programs out of ./cgi-bin. The default +page is home.html (rather than index.html) so that we can view +the contents of the directory from a browser. + +Tiny is neither secure nor complete, but it gives students an +idea of how a real Web server works. Use for instructional purposes only. + +The code compiles and runs cleanly using gcc 2.95.3 +on a Linux 2.2.20 kernel. + +To install Tiny: + Type "tar xvf tiny.tar" in a clean directory. + +To run Tiny: + Run "tiny " on the server machine, + e.g., "tiny 8000". + Point your browser at Tiny: + static content: http://:8000 + dynamic content: http://:8000/cgi-bin/adder?1&2 + +Files: + tiny.tar Archive of everything in this directory + tiny.c The Tiny server + Makefile Makefile for tiny.c + home.html Test HTML page + godzilla.gif Image embedded in home.html + README This file + cgi-bin/adder.c CGI program that adds two numbers + cgi-bin/Makefile Makefile for adder.c + diff --git a/proxylab-handout/tiny/cgi-bin/Makefile b/proxylab-handout/tiny/cgi-bin/Makefile new file mode 100644 index 0000000..3610af2 --- /dev/null +++ b/proxylab-handout/tiny/cgi-bin/Makefile @@ -0,0 +1,10 @@ +CC = gcc +CFLAGS = -O2 -Wall -I .. + +all: adder + +adder: adder.c + $(CC) $(CFLAGS) -o adder adder.c + +clean: + rm -f adder *~ diff --git a/proxylab-handout/tiny/cgi-bin/adder.c b/proxylab-handout/tiny/cgi-bin/adder.c new file mode 100644 index 0000000..a1dfa7c --- /dev/null +++ b/proxylab-handout/tiny/cgi-bin/adder.c @@ -0,0 +1,38 @@ +/* + * adder.c - a minimal CGI program that adds two numbers together + */ +/* $begin adder */ +#include "csapp.h" + +int main(void) { + char *buf, *p; + char arg1[MAXLINE], arg2[MAXLINE], content[MAXLINE]; + int n1=0, n2=0; + + /* Extract the two arguments */ + if ((buf = getenv("QUERY_STRING")) != NULL) { + p = strchr(buf, '&'); + *p = '\0'; + strcpy(arg1, buf); + strcpy(arg2, p+1); + n1 = atoi(arg1); + n2 = atoi(arg2); + } + + /* Make the response body */ + sprintf(content, "Welcome to add.com: "); + sprintf(content, "%sTHE Internet addition portal.\r\n

", content); + sprintf(content, "%sThe answer is: %d + %d = %d\r\n

", + content, n1, n2, n1 + n2); + sprintf(content, "%sThanks for visiting!\r\n", content); + + /* Generate the HTTP response */ + printf("Connection: close\r\n"); + printf("Content-length: %d\r\n", (int)strlen(content)); + printf("Content-type: text/html\r\n\r\n"); + printf("%s", content); + fflush(stdout); + + exit(0); +} +/* $end adder */ diff --git a/proxylab-handout/tiny/csapp.c b/proxylab-handout/tiny/csapp.c new file mode 100644 index 0000000..148e1b5 --- /dev/null +++ b/proxylab-handout/tiny/csapp.c @@ -0,0 +1,1036 @@ +/* + * Updated 8/14 droh: + * - open_clientfd and open_listenfd are now reentrant and protocol + * independent. + * + * - Added protocol-independent inet_ntop and inet_pton functions. The + * inet_ntoa and inet_aton functions are obsolete. + * + * Updated 7/14 droh: + * - Aded reentrant sio (signal-safe I/O) routines + * + * Updated 4/13 droh: + * - rio_readlineb: fixed edge case bug + * - rio_readnb: removed redundant EINTR check + */ +/* $begin csapp.c */ +#include "csapp.h" + +/************************** + * Error-handling functions + **************************/ +/* $begin errorfuns */ +/* $begin unixerror */ +void unix_error(char *msg) /* Unix-style error */ +{ + fprintf(stderr, "%s: %s\n", msg, strerror(errno)); + exit(0); +} +/* $end unixerror */ + +void posix_error(int code, char *msg) /* Posix-style error */ +{ + fprintf(stderr, "%s: %s\n", msg, strerror(code)); + exit(0); +} + +void dns_error(char *msg) +{ + fprintf(stderr, "%s\n", msg); + exit(0); +} + +void gai_error(int code, char *msg) /* Getaddrinfo-style error */ +{ + fprintf(stderr, "%s: %s\n", msg, gai_strerror(code)); + exit(0); +} + +void app_error(char *msg) /* Application error */ +{ + fprintf(stderr, "%s\n", msg); + exit(0); +} +/* $end errorfuns */ + +/********************************************* + * Wrappers for Unix process control functions + ********************************************/ + +/* $begin forkwrapper */ +pid_t Fork(void) +{ + pid_t pid; + + if ((pid = fork()) < 0) + unix_error("Fork error"); + return pid; +} +/* $end forkwrapper */ + +void Execve(const char *filename, char *const argv[], char *const envp[]) +{ + if (execve(filename, argv, envp) < 0) + unix_error("Execve error"); +} + +/* $begin wait */ +pid_t Wait(int *status) +{ + pid_t pid; + + if ((pid = wait(status)) < 0) + unix_error("Wait error"); + return pid; +} +/* $end wait */ + +pid_t Waitpid(pid_t pid, int *iptr, int options) +{ + pid_t retpid; + + if ((retpid = waitpid(pid, iptr, options)) < 0) + unix_error("Waitpid error"); + return(retpid); +} + +/* $begin kill */ +void Kill(pid_t pid, int signum) +{ + int rc; + + if ((rc = kill(pid, signum)) < 0) + unix_error("Kill error"); +} +/* $end kill */ + +void Pause() +{ + (void)pause(); + return; +} + +unsigned int Sleep(unsigned int secs) +{ + unsigned int rc; + + if ((rc = sleep(secs)) < 0) + unix_error("Sleep error"); + return rc; +} + +unsigned int Alarm(unsigned int seconds) { + return alarm(seconds); +} + +void Setpgid(pid_t pid, pid_t pgid) { + int rc; + + if ((rc = setpgid(pid, pgid)) < 0) + unix_error("Setpgid error"); + return; +} + +pid_t Getpgrp(void) { + return getpgrp(); +} + +/************************************ + * Wrappers for Unix signal functions + ***********************************/ + +/* $begin sigaction */ +handler_t *Signal(int signum, handler_t *handler) +{ + struct sigaction action, old_action; + + action.sa_handler = handler; + sigemptyset(&action.sa_mask); /* Block sigs of type being handled */ + action.sa_flags = SA_RESTART; /* Restart syscalls if possible */ + + if (sigaction(signum, &action, &old_action) < 0) + unix_error("Signal error"); + return (old_action.sa_handler); +} +/* $end sigaction */ + +void Sigprocmask(int how, const sigset_t *set, sigset_t *oldset) +{ + if (sigprocmask(how, set, oldset) < 0) + unix_error("Sigprocmask error"); + return; +} + +void Sigemptyset(sigset_t *set) +{ + if (sigemptyset(set) < 0) + unix_error("Sigemptyset error"); + return; +} + +void Sigfillset(sigset_t *set) +{ + if (sigfillset(set) < 0) + unix_error("Sigfillset error"); + return; +} + +void Sigaddset(sigset_t *set, int signum) +{ + if (sigaddset(set, signum) < 0) + unix_error("Sigaddset error"); + return; +} + +void Sigdelset(sigset_t *set, int signum) +{ + if (sigdelset(set, signum) < 0) + unix_error("Sigdelset error"); + return; +} + +int Sigismember(const sigset_t *set, int signum) +{ + int rc; + if ((rc = sigismember(set, signum)) < 0) + unix_error("Sigismember error"); + return rc; +} + +int Sigsuspend(const sigset_t *set) +{ + int rc = sigsuspend(set); /* always returns -1 */ + if (errno != EINTR) + unix_error("Sigsuspend error"); + return rc; +} + +/************************************************************* + * The Sio (Signal-safe I/O) package - simple reentrant output + * functions that are safe for signal handlers. + *************************************************************/ + +/* Private sio functions */ + +/* $begin sioprivate */ +/* sio_reverse - Reverse a string (from K&R) */ +static void sio_reverse(char s[]) +{ + int c, i, j; + + for (i = 0, j = strlen(s)-1; i < j; i++, j--) { + c = s[i]; + s[i] = s[j]; + s[j] = c; + } +} + +/* sio_ltoa - Convert long to base b string (from K&R) */ +static void sio_ltoa(long v, char s[], int b) +{ + int c, i = 0; + + do { + s[i++] = ((c = (v % b)) < 10) ? c + '0' : c - 10 + 'a'; + } while ((v /= b) > 0); + s[i] = '\0'; + sio_reverse(s); +} + +/* sio_strlen - Return length of string (from K&R) */ +static size_t sio_strlen(char s[]) +{ + int i = 0; + + while (s[i] != '\0') + ++i; + return i; +} +/* $end sioprivate */ + +/* Public Sio functions */ +/* $begin siopublic */ + +ssize_t sio_puts(char s[]) /* Put string */ +{ + return write(STDOUT_FILENO, s, sio_strlen(s)); //line:csapp:siostrlen +} + +ssize_t sio_putl(long v) /* Put long */ +{ + char s[128]; + + sio_ltoa(v, s, 10); /* Based on K&R itoa() */ //line:csapp:sioltoa + return sio_puts(s); +} + +void sio_error(char s[]) /* Put error message and exit */ +{ + sio_puts(s); + _exit(1); //line:csapp:sioexit +} +/* $end siopublic */ + +/******************************* + * Wrappers for the SIO routines + ******************************/ +ssize_t Sio_putl(long v) +{ + ssize_t n; + + if ((n = sio_putl(v)) < 0) + sio_error("Sio_putl error"); + return n; +} + +ssize_t Sio_puts(char s[]) +{ + ssize_t n; + + if ((n = sio_puts(s)) < 0) + sio_error("Sio_puts error"); + return n; +} + +void Sio_error(char s[]) +{ + sio_error(s); +} + +/******************************** + * Wrappers for Unix I/O routines + ********************************/ + +int Open(const char *pathname, int flags, mode_t mode) +{ + int rc; + + if ((rc = open(pathname, flags, mode)) < 0) + unix_error("Open error"); + return rc; +} + +ssize_t Read(int fd, void *buf, size_t count) +{ + ssize_t rc; + + if ((rc = read(fd, buf, count)) < 0) + unix_error("Read error"); + return rc; +} + +ssize_t Write(int fd, const void *buf, size_t count) +{ + ssize_t rc; + + if ((rc = write(fd, buf, count)) < 0) + unix_error("Write error"); + return rc; +} + +off_t Lseek(int fildes, off_t offset, int whence) +{ + off_t rc; + + if ((rc = lseek(fildes, offset, whence)) < 0) + unix_error("Lseek error"); + return rc; +} + +void Close(int fd) +{ + int rc; + + if ((rc = close(fd)) < 0) + unix_error("Close error"); +} + +int Select(int n, fd_set *readfds, fd_set *writefds, + fd_set *exceptfds, struct timeval *timeout) +{ + int rc; + + if ((rc = select(n, readfds, writefds, exceptfds, timeout)) < 0) + unix_error("Select error"); + return rc; +} + +int Dup2(int fd1, int fd2) +{ + int rc; + + if ((rc = dup2(fd1, fd2)) < 0) + unix_error("Dup2 error"); + return rc; +} + +void Stat(const char *filename, struct stat *buf) +{ + if (stat(filename, buf) < 0) + unix_error("Stat error"); +} + +void Fstat(int fd, struct stat *buf) +{ + if (fstat(fd, buf) < 0) + unix_error("Fstat error"); +} + +/********************************* + * Wrappers for directory function + *********************************/ + +DIR *Opendir(const char *name) +{ + DIR *dirp = opendir(name); + + if (!dirp) + unix_error("opendir error"); + return dirp; +} + +struct dirent *Readdir(DIR *dirp) +{ + struct dirent *dep; + + errno = 0; + dep = readdir(dirp); + if ((dep == NULL) && (errno != 0)) + unix_error("readdir error"); + return dep; +} + +int Closedir(DIR *dirp) +{ + int rc; + + if ((rc = closedir(dirp)) < 0) + unix_error("closedir error"); + return rc; +} + +/*************************************** + * Wrappers for memory mapping functions + ***************************************/ +void *Mmap(void *addr, size_t len, int prot, int flags, int fd, off_t offset) +{ + void *ptr; + + if ((ptr = mmap(addr, len, prot, flags, fd, offset)) == ((void *) -1)) + unix_error("mmap error"); + return(ptr); +} + +void Munmap(void *start, size_t length) +{ + if (munmap(start, length) < 0) + unix_error("munmap error"); +} + +/*************************************************** + * Wrappers for dynamic storage allocation functions + ***************************************************/ + +void *Malloc(size_t size) +{ + void *p; + + if ((p = malloc(size)) == NULL) + unix_error("Malloc error"); + return p; +} + +void *Realloc(void *ptr, size_t size) +{ + void *p; + + if ((p = realloc(ptr, size)) == NULL) + unix_error("Realloc error"); + return p; +} + +void *Calloc(size_t nmemb, size_t size) +{ + void *p; + + if ((p = calloc(nmemb, size)) == NULL) + unix_error("Calloc error"); + return p; +} + +void Free(void *ptr) +{ + free(ptr); +} + +/****************************************** + * Wrappers for the Standard I/O functions. + ******************************************/ +void Fclose(FILE *fp) +{ + if (fclose(fp) != 0) + unix_error("Fclose error"); +} + +FILE *Fdopen(int fd, const char *type) +{ + FILE *fp; + + if ((fp = fdopen(fd, type)) == NULL) + unix_error("Fdopen error"); + + return fp; +} + +char *Fgets(char *ptr, int n, FILE *stream) +{ + char *rptr; + + if (((rptr = fgets(ptr, n, stream)) == NULL) && ferror(stream)) + app_error("Fgets error"); + + return rptr; +} + +FILE *Fopen(const char *filename, const char *mode) +{ + FILE *fp; + + if ((fp = fopen(filename, mode)) == NULL) + unix_error("Fopen error"); + + return fp; +} + +void Fputs(const char *ptr, FILE *stream) +{ + if (fputs(ptr, stream) == EOF) + unix_error("Fputs error"); +} + +size_t Fread(void *ptr, size_t size, size_t nmemb, FILE *stream) +{ + size_t n; + + if (((n = fread(ptr, size, nmemb, stream)) < nmemb) && ferror(stream)) + unix_error("Fread error"); + return n; +} + +void Fwrite(const void *ptr, size_t size, size_t nmemb, FILE *stream) +{ + if (fwrite(ptr, size, nmemb, stream) < nmemb) + unix_error("Fwrite error"); +} + + +/**************************** + * Sockets interface wrappers + ****************************/ + +int Socket(int domain, int type, int protocol) +{ + int rc; + + if ((rc = socket(domain, type, protocol)) < 0) + unix_error("Socket error"); + return rc; +} + +void Setsockopt(int s, int level, int optname, const void *optval, int optlen) +{ + int rc; + + if ((rc = setsockopt(s, level, optname, optval, optlen)) < 0) + unix_error("Setsockopt error"); +} + +void Bind(int sockfd, struct sockaddr *my_addr, int addrlen) +{ + int rc; + + if ((rc = bind(sockfd, my_addr, addrlen)) < 0) + unix_error("Bind error"); +} + +void Listen(int s, int backlog) +{ + int rc; + + if ((rc = listen(s, backlog)) < 0) + unix_error("Listen error"); +} + +int Accept(int s, struct sockaddr *addr, socklen_t *addrlen) +{ + int rc; + + if ((rc = accept(s, addr, addrlen)) < 0) + unix_error("Accept error"); + return rc; +} + +void Connect(int sockfd, struct sockaddr *serv_addr, int addrlen) +{ + int rc; + + if ((rc = connect(sockfd, serv_addr, addrlen)) < 0) + unix_error("Connect error"); +} + +/******************************* + * Protocol-independent wrappers + *******************************/ +void Getaddrinfo(const char *node, const char *service, + const struct addrinfo *hints, struct addrinfo **res) +{ + int rc; + + if ((rc = getaddrinfo(node, service, hints, res)) != 0) + gai_error(rc, "Getaddrinfo error"); +} + +void Getnameinfo(const struct sockaddr *sa, socklen_t salen, char *host, + size_t hostlen, char *serv, size_t servlen, int flags) +{ + int rc; + + if ((rc = getnameinfo(sa, salen, host, hostlen, serv, + servlen, flags)) != 0) + gai_error(rc, "Getnameinfo error"); +} + +void Freeaddrinfo(struct addrinfo *res) +{ + freeaddrinfo(res); +} + +void Inet_ntop(int af, const void *src, char *dst, socklen_t size) +{ + if (!inet_ntop(af, src, dst, size)) + unix_error("Inet_ntop error"); +} + +void Inet_pton(int af, const char *src, void *dst) +{ + int rc; + + rc = inet_pton(af, src, dst); + if (rc == 0) + app_error("inet_pton error: invalid dotted-decimal address"); + else if (rc < 0) + unix_error("Inet_pton error"); +} + +/******************************************* + * DNS interface wrappers. + * + * NOTE: These are obsolete because they are not thread safe. Use + * getaddrinfo and getnameinfo instead + ***********************************/ + +/* $begin gethostbyname */ +struct hostent *Gethostbyname(const char *name) +{ + struct hostent *p; + + if ((p = gethostbyname(name)) == NULL) + dns_error("Gethostbyname error"); + return p; +} +/* $end gethostbyname */ + +struct hostent *Gethostbyaddr(const char *addr, int len, int type) +{ + struct hostent *p; + + if ((p = gethostbyaddr(addr, len, type)) == NULL) + dns_error("Gethostbyaddr error"); + return p; +} + +/************************************************ + * Wrappers for Pthreads thread control functions + ************************************************/ + +void Pthread_create(pthread_t *tidp, pthread_attr_t *attrp, + void * (*routine)(void *), void *argp) +{ + int rc; + + if ((rc = pthread_create(tidp, attrp, routine, argp)) != 0) + posix_error(rc, "Pthread_create error"); +} + +void Pthread_cancel(pthread_t tid) { + int rc; + + if ((rc = pthread_cancel(tid)) != 0) + posix_error(rc, "Pthread_cancel error"); +} + +void Pthread_join(pthread_t tid, void **thread_return) { + int rc; + + if ((rc = pthread_join(tid, thread_return)) != 0) + posix_error(rc, "Pthread_join error"); +} + +/* $begin detach */ +void Pthread_detach(pthread_t tid) { + int rc; + + if ((rc = pthread_detach(tid)) != 0) + posix_error(rc, "Pthread_detach error"); +} +/* $end detach */ + +void Pthread_exit(void *retval) { + pthread_exit(retval); +} + +pthread_t Pthread_self(void) { + return pthread_self(); +} + +void Pthread_once(pthread_once_t *once_control, void (*init_function)()) { + pthread_once(once_control, init_function); +} + +/******************************* + * Wrappers for Posix semaphores + *******************************/ + +void Sem_init(sem_t *sem, int pshared, unsigned int value) +{ + if (sem_init(sem, pshared, value) < 0) + unix_error("Sem_init error"); +} + +void P(sem_t *sem) +{ + if (sem_wait(sem) < 0) + unix_error("P error"); +} + +void V(sem_t *sem) +{ + if (sem_post(sem) < 0) + unix_error("V error"); +} + +/**************************************** + * The Rio package - Robust I/O functions + ****************************************/ + +/* + * rio_readn - Robustly read n bytes (unbuffered) + */ +/* $begin rio_readn */ +ssize_t rio_readn(int fd, void *usrbuf, size_t n) +{ + size_t nleft = n; + ssize_t nread; + char *bufp = usrbuf; + + while (nleft > 0) { + if ((nread = read(fd, bufp, nleft)) < 0) { + if (errno == EINTR) /* Interrupted by sig handler return */ + nread = 0; /* and call read() again */ + else + return -1; /* errno set by read() */ + } + else if (nread == 0) + break; /* EOF */ + nleft -= nread; + bufp += nread; + } + return (n - nleft); /* return >= 0 */ +} +/* $end rio_readn */ + +/* + * rio_writen - Robustly write n bytes (unbuffered) + */ +/* $begin rio_writen */ +ssize_t rio_writen(int fd, void *usrbuf, size_t n) +{ + size_t nleft = n; + ssize_t nwritten; + char *bufp = usrbuf; + + while (nleft > 0) { + if ((nwritten = write(fd, bufp, nleft)) <= 0) { + if (errno == EINTR) /* Interrupted by sig handler return */ + nwritten = 0; /* and call write() again */ + else + return -1; /* errno set by write() */ + } + nleft -= nwritten; + bufp += nwritten; + } + return n; +} +/* $end rio_writen */ + + +/* + * rio_read - This is a wrapper for the Unix read() function that + * transfers min(n, rio_cnt) bytes from an internal buffer to a user + * buffer, where n is the number of bytes requested by the user and + * rio_cnt is the number of unread bytes in the internal buffer. On + * entry, rio_read() refills the internal buffer via a call to + * read() if the internal buffer is empty. + */ +/* $begin rio_read */ +static ssize_t rio_read(rio_t *rp, char *usrbuf, size_t n) +{ + int cnt; + + while (rp->rio_cnt <= 0) { /* Refill if buf is empty */ + rp->rio_cnt = read(rp->rio_fd, rp->rio_buf, + sizeof(rp->rio_buf)); + if (rp->rio_cnt < 0) { + if (errno != EINTR) /* Interrupted by sig handler return */ + return -1; + } + else if (rp->rio_cnt == 0) /* EOF */ + return 0; + else + rp->rio_bufptr = rp->rio_buf; /* Reset buffer ptr */ + } + + /* Copy min(n, rp->rio_cnt) bytes from internal buf to user buf */ + cnt = n; + if (rp->rio_cnt < n) + cnt = rp->rio_cnt; + memcpy(usrbuf, rp->rio_bufptr, cnt); + rp->rio_bufptr += cnt; + rp->rio_cnt -= cnt; + return cnt; +} +/* $end rio_read */ + +/* + * rio_readinitb - Associate a descriptor with a read buffer and reset buffer + */ +/* $begin rio_readinitb */ +void rio_readinitb(rio_t *rp, int fd) +{ + rp->rio_fd = fd; + rp->rio_cnt = 0; + rp->rio_bufptr = rp->rio_buf; +} +/* $end rio_readinitb */ + +/* + * rio_readnb - Robustly read n bytes (buffered) + */ +/* $begin rio_readnb */ +ssize_t rio_readnb(rio_t *rp, void *usrbuf, size_t n) +{ + size_t nleft = n; + ssize_t nread; + char *bufp = usrbuf; + + while (nleft > 0) { + if ((nread = rio_read(rp, bufp, nleft)) < 0) + return -1; /* errno set by read() */ + else if (nread == 0) + break; /* EOF */ + nleft -= nread; + bufp += nread; + } + return (n - nleft); /* return >= 0 */ +} +/* $end rio_readnb */ + +/* + * rio_readlineb - Robustly read a text line (buffered) + */ +/* $begin rio_readlineb */ +ssize_t rio_readlineb(rio_t *rp, void *usrbuf, size_t maxlen) +{ + int n, rc; + char c, *bufp = usrbuf; + + for (n = 1; n < maxlen; n++) { + if ((rc = rio_read(rp, &c, 1)) == 1) { + *bufp++ = c; + if (c == '\n') { + n++; + break; + } + } else if (rc == 0) { + if (n == 1) { + return 0; /* EOF, no data read */ + } + else { + break; /* EOF, some data was read */ + } + } else + return -1; /* Error */ + } + *bufp = '\0'; + return n-1; +} +/* $end rio_readlineb */ + +/********************************** + * Wrappers for robust I/O routines + **********************************/ +ssize_t Rio_readn(int fd, void *ptr, size_t nbytes) +{ + ssize_t n; + + if ((n = rio_readn(fd, ptr, nbytes)) < 0) + unix_error("Rio_readn error"); + return n; +} + +void Rio_writen(int fd, void *usrbuf, size_t n) +{ + if (rio_writen(fd, usrbuf, n) != n) + unix_error("Rio_writen error"); +} + +void Rio_readinitb(rio_t *rp, int fd) +{ + rio_readinitb(rp, fd); +} + +ssize_t Rio_readnb(rio_t *rp, void *usrbuf, size_t n) +{ + ssize_t rc; + + if ((rc = rio_readnb(rp, usrbuf, n)) < 0) + unix_error("Rio_readnb error"); + return rc; +} + +ssize_t Rio_readlineb(rio_t *rp, void *usrbuf, size_t maxlen) +{ + ssize_t rc; + + if ((rc = rio_readlineb(rp, usrbuf, maxlen)) < 0) + unix_error("Rio_readlineb error"); + return rc; +} + +/******************************** + * Client/server helper functions + ********************************/ +/* + * open_clientfd - Open connection to server at and + * return a socket descriptor ready for reading and writing. This + * function is reentrant and protocol-independent. + * + * On error, returns -1 and sets errno. + */ +/* $begin open_clientfd */ +int open_clientfd(char *hostname, char *port) { + int clientfd; + struct addrinfo hints, *listp, *p; + + /* Get a list of potential server addresses */ + memset(&hints, 0, sizeof(struct addrinfo)); + hints.ai_socktype = SOCK_STREAM; /* Open a connection */ + hints.ai_flags = AI_NUMERICSERV; /* ... using a numeric port arg. */ + hints.ai_flags |= AI_ADDRCONFIG; /* Recommended for connections */ + Getaddrinfo(hostname, port, &hints, &listp); + + /* Walk the list for one that we can successfully connect to */ + for (p = listp; p; p = p->ai_next) { + + /* Create the socket descriptor */ + if ((clientfd = socket(p->ai_family, p->ai_socktype, p->ai_protocol)) < 0) + continue; /* Socket failed, try the next */ + if (connect(clientfd, p->ai_addr, p->ai_addrlen) != -1) + break; /* Success */ + Close(clientfd); /* Connect failed, try another */ + } + + /* Clean up */ + Freeaddrinfo(listp); + if (!p) /* All connects failed */ + return -1; + else /* The last connect succeeded */ + return clientfd; +} +/* $end open_clientfd */ + +/* + * open_listenfd - Open and return a listening socket on port. This + * function is reentrant and protocol-independent. + * + * On error, returns -1 and sets errno. + */ +/* $begin open_listenfd */ +int open_listenfd(char *port) +{ + struct addrinfo hints, *listp, *p; + int listenfd, optval=1; + + /* Get a list of potential server addresses */ + memset(&hints, 0, sizeof(struct addrinfo)); + hints.ai_socktype = SOCK_STREAM; /* Accept TCP connections */ + hints.ai_flags = AI_PASSIVE; /* ... on any IP address */ + hints.ai_flags |= AI_NUMERICSERV; /* ... using a numeric port arg. */ + hints.ai_flags |= AI_ADDRCONFIG; /* Recommended for connections */ + Getaddrinfo(NULL, port, &hints, &listp); + + /* Walk the list for one that we can bind to */ + for (p = listp; p; p = p->ai_next) { + + /* Create a socket descriptor */ + if ((listenfd = socket(p->ai_family, p->ai_socktype, p->ai_protocol)) < 0) + continue; /* Socket failed, try the next */ + + /* Eliminates "Address already in use" error from bind */ + Setsockopt(listenfd, SOL_SOCKET, SO_REUSEADDR, + (const void *)&optval , sizeof(int)); + + /* Bind the descriptor to the address */ + if (bind(listenfd, p->ai_addr, p->ai_addrlen) == 0) + break; /* Success */ + Close(listenfd); /* Bind failed, try the next */ + } + + /* Clean up */ + Freeaddrinfo(listp); + if (!p) /* No address worked */ + return -1; + + /* Make it a listening socket ready to accept connection requests */ + if (listen(listenfd, LISTENQ) < 0) + return -1; + return listenfd; +} +/* $end open_listenfd */ + +/**************************************************** + * Wrappers for reentrant protocol-independent helpers + ****************************************************/ +int Open_clientfd(char *hostname, char *port) +{ + int rc; + + if ((rc = open_clientfd(hostname, port)) < 0) + unix_error("Open_clientfd error"); + return rc; +} + +int Open_listenfd(char *port) +{ + int rc; + + if ((rc = open_listenfd(port)) < 0) + unix_error("Open_listenfd error"); + return rc; +} + +/* $end csapp.c */ + + + + diff --git a/proxylab-handout/tiny/csapp.h b/proxylab-handout/tiny/csapp.h new file mode 100644 index 0000000..8ff0692 --- /dev/null +++ b/proxylab-handout/tiny/csapp.h @@ -0,0 +1,195 @@ +/* $begin csapp.h */ +#ifndef __CSAPP_H__ +#define __CSAPP_H__ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +/* Default file permissions are DEF_MODE & ~DEF_UMASK */ +/* $begin createmasks */ +#define DEF_MODE S_IRUSR|S_IWUSR|S_IRGRP|S_IWGRP|S_IROTH|S_IWOTH +#define DEF_UMASK S_IWGRP|S_IWOTH +/* $end createmasks */ + +/* Simplifies calls to bind(), connect(), and accept() */ +/* $begin sockaddrdef */ +typedef struct sockaddr SA; +/* $end sockaddrdef */ + +/* Persistent state for the robust I/O (Rio) package */ +/* $begin rio_t */ +#define RIO_BUFSIZE 8192 +typedef struct { + int rio_fd; /* Descriptor for this internal buf */ + int rio_cnt; /* Unread bytes in internal buf */ + char *rio_bufptr; /* Next unread byte in internal buf */ + char rio_buf[RIO_BUFSIZE]; /* Internal buffer */ +} rio_t; +/* $end rio_t */ + +/* External variables */ +extern int h_errno; /* Defined by BIND for DNS errors */ +extern char **environ; /* Defined by libc */ + +/* Misc constants */ +#define MAXLINE 8192 /* Max text line length */ +#define MAXBUF 8192 /* Max I/O buffer size */ +#define LISTENQ 1024 /* Second argument to listen() */ + +/* Our own error-handling functions */ +void unix_error(char *msg); +void posix_error(int code, char *msg); +void dns_error(char *msg); +void gai_error(int code, char *msg); +void app_error(char *msg); + +/* Process control wrappers */ +pid_t Fork(void); +void Execve(const char *filename, char *const argv[], char *const envp[]); +pid_t Wait(int *status); +pid_t Waitpid(pid_t pid, int *iptr, int options); +void Kill(pid_t pid, int signum); +unsigned int Sleep(unsigned int secs); +void Pause(void); +unsigned int Alarm(unsigned int seconds); +void Setpgid(pid_t pid, pid_t pgid); +pid_t Getpgrp(); + +/* Signal wrappers */ +typedef void handler_t(int); +handler_t *Signal(int signum, handler_t *handler); +void Sigprocmask(int how, const sigset_t *set, sigset_t *oldset); +void Sigemptyset(sigset_t *set); +void Sigfillset(sigset_t *set); +void Sigaddset(sigset_t *set, int signum); +void Sigdelset(sigset_t *set, int signum); +int Sigismember(const sigset_t *set, int signum); +int Sigsuspend(const sigset_t *set); + +/* Sio (Signal-safe I/O) routines */ +ssize_t sio_puts(char s[]); +ssize_t sio_putl(long v); +void sio_error(char s[]); + +/* Sio wrappers */ +ssize_t Sio_puts(char s[]); +ssize_t Sio_putl(long v); +void Sio_error(char s[]); + +/* Unix I/O wrappers */ +int Open(const char *pathname, int flags, mode_t mode); +ssize_t Read(int fd, void *buf, size_t count); +ssize_t Write(int fd, const void *buf, size_t count); +off_t Lseek(int fildes, off_t offset, int whence); +void Close(int fd); +int Select(int n, fd_set *readfds, fd_set *writefds, fd_set *exceptfds, + struct timeval *timeout); +int Dup2(int fd1, int fd2); +void Stat(const char *filename, struct stat *buf); +void Fstat(int fd, struct stat *buf) ; + +/* Directory wrappers */ +DIR *Opendir(const char *name); +struct dirent *Readdir(DIR *dirp); +int Closedir(DIR *dirp); + +/* Memory mapping wrappers */ +void *Mmap(void *addr, size_t len, int prot, int flags, int fd, off_t offset); +void Munmap(void *start, size_t length); + +/* Standard I/O wrappers */ +void Fclose(FILE *fp); +FILE *Fdopen(int fd, const char *type); +char *Fgets(char *ptr, int n, FILE *stream); +FILE *Fopen(const char *filename, const char *mode); +void Fputs(const char *ptr, FILE *stream); +size_t Fread(void *ptr, size_t size, size_t nmemb, FILE *stream); +void Fwrite(const void *ptr, size_t size, size_t nmemb, FILE *stream); + +/* Dynamic storage allocation wrappers */ +void *Malloc(size_t size); +void *Realloc(void *ptr, size_t size); +void *Calloc(size_t nmemb, size_t size); +void Free(void *ptr); + +/* Sockets interface wrappers */ +int Socket(int domain, int type, int protocol); +void Setsockopt(int s, int level, int optname, const void *optval, int optlen); +void Bind(int sockfd, struct sockaddr *my_addr, int addrlen); +void Listen(int s, int backlog); +int Accept(int s, struct sockaddr *addr, socklen_t *addrlen); +void Connect(int sockfd, struct sockaddr *serv_addr, int addrlen); + +/* Protocol independent wrappers */ +void Getaddrinfo(const char *node, const char *service, + const struct addrinfo *hints, struct addrinfo **res); +void Getnameinfo(const struct sockaddr *sa, socklen_t salen, char *host, + size_t hostlen, char *serv, size_t servlen, int flags); +void Freeaddrinfo(struct addrinfo *res); +void Inet_ntop(int af, const void *src, char *dst, socklen_t size); +void Inet_pton(int af, const char *src, void *dst); + +/* DNS wrappers */ +struct hostent *Gethostbyname(const char *name); +struct hostent *Gethostbyaddr(const char *addr, int len, int type); + +/* Pthreads thread control wrappers */ +void Pthread_create(pthread_t *tidp, pthread_attr_t *attrp, + void * (*routine)(void *), void *argp); +void Pthread_join(pthread_t tid, void **thread_return); +void Pthread_cancel(pthread_t tid); +void Pthread_detach(pthread_t tid); +void Pthread_exit(void *retval); +pthread_t Pthread_self(void); +void Pthread_once(pthread_once_t *once_control, void (*init_function)()); + +/* POSIX semaphore wrappers */ +void Sem_init(sem_t *sem, int pshared, unsigned int value); +void P(sem_t *sem); +void V(sem_t *sem); + +/* Rio (Robust I/O) package */ +ssize_t rio_readn(int fd, void *usrbuf, size_t n); +ssize_t rio_writen(int fd, void *usrbuf, size_t n); +void rio_readinitb(rio_t *rp, int fd); +ssize_t rio_readnb(rio_t *rp, void *usrbuf, size_t n); +ssize_t rio_readlineb(rio_t *rp, void *usrbuf, size_t maxlen); + +/* Wrappers for Rio package */ +ssize_t Rio_readn(int fd, void *usrbuf, size_t n); +void Rio_writen(int fd, void *usrbuf, size_t n); +void Rio_readinitb(rio_t *rp, int fd); +ssize_t Rio_readnb(rio_t *rp, void *usrbuf, size_t n); +ssize_t Rio_readlineb(rio_t *rp, void *usrbuf, size_t maxlen); + +/* Reentrant protocol-independent client/server helpers */ +int open_clientfd(char *hostname, char *port); +int open_listenfd(char *port); + +/* Wrappers for reentrant protocol-independent client/server helpers */ +int Open_clientfd(char *hostname, char *port); +int Open_listenfd(char *port); + + +#endif /* __CSAPP_H__ */ +/* $end csapp.h */ diff --git a/proxylab-handout/tiny/godzilla.gif b/proxylab-handout/tiny/godzilla.gif new file mode 100644 index 0000000..c3fa658 Binary files /dev/null and b/proxylab-handout/tiny/godzilla.gif differ diff --git a/proxylab-handout/tiny/godzilla.jpg b/proxylab-handout/tiny/godzilla.jpg new file mode 100644 index 0000000..b839fcd Binary files /dev/null and b/proxylab-handout/tiny/godzilla.jpg differ diff --git a/proxylab-handout/tiny/home.html b/proxylab-handout/tiny/home.html new file mode 100644 index 0000000..4eff78b --- /dev/null +++ b/proxylab-handout/tiny/home.html @@ -0,0 +1,7 @@ + +test + + +Dave O'Hallaron + + diff --git a/proxylab-handout/tiny/tiny.c b/proxylab-handout/tiny/tiny.c new file mode 100644 index 0000000..b7417bc --- /dev/null +++ b/proxylab-handout/tiny/tiny.c @@ -0,0 +1,240 @@ +/* $begin tinymain */ +/* + * tiny.c - A simple, iterative HTTP/1.0 Web server that uses the + * GET method to serve static and dynamic content. + */ +#include "csapp.h" + +void doit(int fd); +void read_requesthdrs(rio_t *rp); +int parse_uri(char *uri, char *filename, char *cgiargs); +void serve_static(int fd, char *filename, int filesize); +void get_filetype(char *filename, char *filetype); +void serve_dynamic(int fd, char *filename, char *cgiargs); +void clienterror(int fd, char *cause, char *errnum, + char *shortmsg, char *longmsg); + +int main(int argc, char **argv) +{ + int listenfd, connfd; + char hostname[MAXLINE], port[MAXLINE]; + socklen_t clientlen; + struct sockaddr_storage clientaddr; + + /* Check command line args */ + if (argc != 2) { + fprintf(stderr, "usage: %s \n", argv[0]); + exit(1); + } + + listenfd = Open_listenfd(argv[1]); + while (1) { + clientlen = sizeof(clientaddr); + connfd = Accept(listenfd, (SA *)&clientaddr, &clientlen); //line:netp:tiny:accept + Getnameinfo((SA *) &clientaddr, clientlen, hostname, MAXLINE, + port, MAXLINE, 0); + printf("Accepted connection from (%s, %s)\n", hostname, port); + doit(connfd); //line:netp:tiny:doit + Close(connfd); //line:netp:tiny:close + } +} +/* $end tinymain */ + +/* + * doit - handle one HTTP request/response transaction + */ +/* $begin doit */ +void doit(int fd) +{ + int is_static; + struct stat sbuf; + char buf[MAXLINE], method[MAXLINE], uri[MAXLINE], version[MAXLINE]; + char filename[MAXLINE], cgiargs[MAXLINE]; + rio_t rio; + + /* Read request line and headers */ + Rio_readinitb(&rio, fd); + if (!Rio_readlineb(&rio, buf, MAXLINE)) //line:netp:doit:readrequest + return; + printf("%s", buf); + sscanf(buf, "%s %s %s", method, uri, version); //line:netp:doit:parserequest + if (strcasecmp(method, "GET")) { //line:netp:doit:beginrequesterr + clienterror(fd, method, "501", "Not Implemented", + "Tiny does not implement this method"); + return; + } //line:netp:doit:endrequesterr + read_requesthdrs(&rio); //line:netp:doit:readrequesthdrs + + /* Parse URI from GET request */ + is_static = parse_uri(uri, filename, cgiargs); //line:netp:doit:staticcheck + if (stat(filename, &sbuf) < 0) { //line:netp:doit:beginnotfound + clienterror(fd, filename, "404", "Not found", + "Tiny couldn't find this file"); + return; + } //line:netp:doit:endnotfound + + if (is_static) { /* Serve static content */ + if (!(S_ISREG(sbuf.st_mode)) || !(S_IRUSR & sbuf.st_mode)) { //line:netp:doit:readable + clienterror(fd, filename, "403", "Forbidden", + "Tiny couldn't read the file"); + return; + } + serve_static(fd, filename, sbuf.st_size); //line:netp:doit:servestatic + } + else { /* Serve dynamic content */ + if (!(S_ISREG(sbuf.st_mode)) || !(S_IXUSR & sbuf.st_mode)) { //line:netp:doit:executable + clienterror(fd, filename, "403", "Forbidden", + "Tiny couldn't run the CGI program"); + return; + } + serve_dynamic(fd, filename, cgiargs); //line:netp:doit:servedynamic + } +} +/* $end doit */ + +/* + * read_requesthdrs - read HTTP request headers + */ +/* $begin read_requesthdrs */ +void read_requesthdrs(rio_t *rp) +{ + char buf[MAXLINE]; + + Rio_readlineb(rp, buf, MAXLINE); + printf("%s", buf); + while(strcmp(buf, "\r\n")) { //line:netp:readhdrs:checkterm + Rio_readlineb(rp, buf, MAXLINE); + printf("%s", buf); + } + return; +} +/* $end read_requesthdrs */ + +/* + * parse_uri - parse URI into filename and CGI args + * return 0 if dynamic content, 1 if static + */ +/* $begin parse_uri */ +int parse_uri(char *uri, char *filename, char *cgiargs) +{ + char *ptr; + + if (!strstr(uri, "cgi-bin")) { /* Static content */ //line:netp:parseuri:isstatic + strcpy(cgiargs, ""); //line:netp:parseuri:clearcgi + strcpy(filename, "."); //line:netp:parseuri:beginconvert1 + strcat(filename, uri); //line:netp:parseuri:endconvert1 + if (uri[strlen(uri)-1] == '/') //line:netp:parseuri:slashcheck + strcat(filename, "home.html"); //line:netp:parseuri:appenddefault + return 1; + } + else { /* Dynamic content */ //line:netp:parseuri:isdynamic + ptr = index(uri, '?'); //line:netp:parseuri:beginextract + if (ptr) { + strcpy(cgiargs, ptr+1); + *ptr = '\0'; + } + else + strcpy(cgiargs, ""); //line:netp:parseuri:endextract + strcpy(filename, "."); //line:netp:parseuri:beginconvert2 + strcat(filename, uri); //line:netp:parseuri:endconvert2 + return 0; + } +} +/* $end parse_uri */ + +/* + * serve_static - copy a file back to the client + */ +/* $begin serve_static */ +void serve_static(int fd, char *filename, int filesize) +{ + int srcfd; + char *srcp, filetype[MAXLINE], buf[MAXBUF]; + + /* Send response headers to client */ + get_filetype(filename, filetype); //line:netp:servestatic:getfiletype + sprintf(buf, "HTTP/1.0 200 OK\r\n"); //line:netp:servestatic:beginserve + sprintf(buf, "%sServer: Tiny Web Server\r\n", buf); + sprintf(buf, "%sConnection: close\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)); //line:netp:servestatic:endserve + printf("Response headers:\n"); + printf("%s", buf); + + /* Send response body to client */ + srcfd = Open(filename, O_RDONLY, 0); //line:netp:servestatic:open + srcp = Mmap(0, filesize, PROT_READ, MAP_PRIVATE, srcfd, 0);//line:netp:servestatic:mmap + Close(srcfd); //line:netp:servestatic:close + Rio_writen(fd, srcp, filesize); //line:netp:servestatic:write + Munmap(srcp, filesize); //line:netp:servestatic:munmap +} + +/* + * 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, ".png")) + strcpy(filetype, "image/png"); + else if (strstr(filename, ".jpg")) + strcpy(filetype, "image/jpeg"); + else + strcpy(filetype, "text/plain"); +} +/* $end serve_static */ + +/* + * serve_dynamic - run a CGI program on behalf of the client + */ +/* $begin serve_dynamic */ +void serve_dynamic(int fd, char *filename, char *cgiargs) +{ + char buf[MAXLINE], *emptylist[] = { NULL }; + + /* Return first part of HTTP response */ + sprintf(buf, "HTTP/1.0 200 OK\r\n"); + Rio_writen(fd, buf, strlen(buf)); + sprintf(buf, "Server: Tiny Web Server\r\n"); + Rio_writen(fd, buf, strlen(buf)); + + if (Fork() == 0) { /* Child */ //line:netp:servedynamic:fork + /* Real server would set all CGI vars here */ + setenv("QUERY_STRING", cgiargs, 1); //line:netp:servedynamic:setenv + Dup2(fd, STDOUT_FILENO); /* Redirect stdout to client */ //line:netp:servedynamic:dup2 + Execve(filename, emptylist, environ); /* Run CGI program */ //line:netp:servedynamic:execve + } + Wait(NULL); /* Parent waits for and reaps child */ //line:netp:servedynamic:wait +} +/* $end serve_dynamic */ + +/* + * clienterror - returns an error message to the client + */ +/* $begin clienterror */ +void clienterror(int fd, char *cause, char *errnum, + char *shortmsg, char *longmsg) +{ + char buf[MAXLINE], body[MAXBUF]; + + /* Build the HTTP response body */ + sprintf(body, "Tiny Error"); + sprintf(body, "%s\r\n", body); + sprintf(body, "%s%s: %s\r\n", body, errnum, shortmsg); + sprintf(body, "%s

%s: %s\r\n", body, longmsg, cause); + sprintf(body, "%s


The Tiny Web server\r\n", body); + + /* Print the HTTP response */ + sprintf(buf, "HTTP/1.0 %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)); +} +/* $end clienterror */ diff --git a/proxylab-handout/util.c b/proxylab-handout/util.c new file mode 100644 index 0000000..a13e37f --- /dev/null +++ b/proxylab-handout/util.c @@ -0,0 +1,96 @@ +#include "util.h" +#include +#include + + +/* match -- match the string with the pattern. The match + * is started from `cur`. If we can't find the pattern + * in the string, we simply ignore the pattern. + * + * Return: the index of the next character in string that's not + * a part of the pattern + */ +size_t match(const char *uri, + size_t cur, + const char *pattern) +{ + size_t tmp_cur = cur; + int pattern_cur = 0; + while (uri[cur] != '\0' && pattern[pattern_cur] != '\0' && + tolower(uri[cur]) == pattern[pattern_cur]) { + cur += 1; + pattern_cur += 1; + } + if (pattern[pattern_cur] == '\0') { + // the string matches the pattern. + return cur; + } else { + // string doesn't match the pattern. return old `cur` + return tmp_cur; + } +} + + +/* contain -- whether c is in the chars */ +int contain(const char *chars, char c) +{ + size_t cur = 0; + while (chars[cur] != '\0') { + if (chars[cur] == c) { + return 1; + } + cur += 1; + } + return 0; +} + +/* copy_until -- copy src(starting from `cur`) to the dest until + * one of chars in `terminators` or '\0' is encountered + * */ +size_t copy_until(const char *src, size_t cur, const char *terminators, + char *dest) +{ + size_t dest_cur = 0; + while(src[cur] != '\0' && !contain(terminators, src[cur])) { + dest[dest_cur] = src[cur]; + cur += 1; + dest_cur += 1; + } + dest[dest_cur] = '\0'; + return cur; +} + +/* tolowercstr -- convert the string to lower case cstr */ +static void tolowercstr(char *cstr) +{ + int i; + for (i = 0; cstr[i] != '\0'; i+=1) { + cstr[i] = tolower(cstr[i]); + } +} + +/* parse_uri -- parse the uri into host:port/dir */ +void parse_uri(const char *uri, char *host, char *port, char *dir) +{ + size_t cur = 0; + cur = match(uri, cur, "http://"); + cur = match(uri, cur, "https://"); + cur = copy_until(uri, cur, "/:", host); + + if (uri[cur] == ':') { + cur = copy_until(uri, cur+1, "/:", port); + } else { + strcpy(port, "80"); + } + + copy_until(uri, cur, "", dir); + tolowercstr(host); +} + +/* parse_header -- parse the header into (name,value) pair */ +void parse_header(const char *line, char *name, char *value) +{ + size_t cur = 0; + cur = copy_until(line, cur, ":", name); + copy_until(line, cur+1, "\r\n", value); +} diff --git a/proxylab-handout/util.h b/proxylab-handout/util.h new file mode 100644 index 0000000..3634407 --- /dev/null +++ b/proxylab-handout/util.h @@ -0,0 +1,22 @@ +#ifndef __UTIL_H__ +#define __UTIL_H__ + +#include + +/* parse utilities */ + +size_t match(const char *uri, + size_t cur, + const char *pattern); + +int contain(const char *chars, char c); + +size_t copy_until(const char *src, size_t cur, const char *terminators, + char *dest); + +void parse_uri(const char *uri, char *host, char *port, char *dir); + +void parse_header(const char *line, char *name, char *value); + + +#endif