diff options
author | Yang Tse <yangsita@gmail.com> | 2010-02-14 13:14:17 +0000 |
---|---|---|
committer | Yang Tse <yangsita@gmail.com> | 2010-02-14 13:14:17 +0000 |
commit | 68d83a8142e52643cc98d692dce54a49d9e2c386 (patch) | |
tree | 6f43b3dfcb3d2c9aa665fea513159d926339ea01 | |
parent | b4ff6d3007be58423a1e6b8b8aa2e038dd1b04c0 (diff) | |
download | curl-68d83a8142e52643cc98d692dce54a49d9e2c386.tar.gz |
Overhauled test suite getpart() function. Fixing potential out of bounds
stack and memory overwrites triggered with huge test case definitions.
-rw-r--r-- | CHANGES | 4 | ||||
-rw-r--r-- | tests/server/getpart.c | 411 | ||||
-rw-r--r-- | tests/server/getpart.h | 19 | ||||
-rw-r--r-- | tests/server/rtspd.c | 53 | ||||
-rw-r--r-- | tests/server/sws.c | 51 | ||||
-rw-r--r-- | tests/server/testpart.c | 15 | ||||
-rw-r--r-- | tests/server/tftpd.c | 6 |
7 files changed, 405 insertions, 154 deletions
@@ -6,6 +6,10 @@ Changelog +Yang Tse (14 Feb 2010) +- Overhauled test suite getpart() function. Fixing potential out of bounds + stack and memory overwrites triggered with huge test case definitions. + Daniel Stenberg (13 Feb 2010) - Martin Hager reported and fixed a problem with a missing quote in libcurl.m4 diff --git a/tests/server/getpart.c b/tests/server/getpart.c index 59cb4ab4c..ec4198cec 100644 --- a/tests/server/getpart.c +++ b/tests/server/getpart.c @@ -41,11 +41,11 @@ struct SessionHandle { /* include memdebug.h last */ #include "memdebug.h" -#define EAT_SPACE(ptr) while( ptr && *ptr && ISSPACE(*ptr) ) ptr++ -#define EAT_WORD(ptr) while( ptr && *ptr && !ISSPACE(*ptr) && \ - ('>' != *ptr)) ptr++ +#define EAT_SPACE(p) while(*(p) && ISSPACE(*(p))) (p)++ -#ifdef DEBUG +#define EAT_WORD(p) while(*(p) && !ISSPACE(*(p)) && ('>' != *(p))) (p)++ + +#ifdef DEBUG_GETPART #define show(x) printf x #else #define show(x) @@ -65,191 +65,352 @@ curl_calloc_callback Curl_ccalloc = (curl_calloc_callback)calloc; # pragma warning(default:4232) /* MSVC extension, dllimport identity */ #endif -static -char *appendstring(char *string, /* original string */ - char *buffer, /* to append */ - size_t *stringlen, /* length of string */ - size_t *stralloc, /* allocated size */ - char base64) /* 1 if base64 encoded */ +/* + * readline() + * + * Reads a complete line from a file into a dynamically allocated buffer. + * + * Calling function may call this multiple times with same 'buffer' + * and 'bufsize' pointers to avoid multiple buffer allocations. Buffer + * will be reallocated and 'bufsize' increased until whole line fits in + * buffer before returning it. + * + * Calling function is responsible to free allocated buffer. + * + * This function may return: + * GPE_OUT_OF_MEMORY + * GPE_END_OF_FILE + * GPE_OK + */ + +static int readline(char **buffer, size_t *bufsize, FILE *stream) +{ + size_t offset = 0; + size_t length; + char *newptr; + + if(!*buffer) { + *buffer = malloc(128); + if(!*buffer) + return GPE_OUT_OF_MEMORY; + *bufsize = 128; + } + + for(;;) { + if(!fgets(*buffer + offset, (int)(*bufsize - offset), stream)) + return (offset != 0) ? GPE_OK : GPE_END_OF_FILE ; + + length = offset + strlen(*buffer + offset); + if(*(*buffer + length - 1) == '\n') + break; + offset = length; + if(length < *bufsize - 1) + continue; + + newptr = realloc(*buffer, *bufsize * 2); + if(!newptr) + return GPE_OUT_OF_MEMORY; + *buffer = newptr; + *bufsize *= 2; + } + + return GPE_OK; +} + +/* + * appenddata() + * + * This appends data from a given source buffer to the end of the used part of + * a destination buffer. Arguments relative to the destination buffer are, the + * address of a pointer to the destination buffer 'dst_buf', the length of data + * in destination buffer excluding potential null string termination 'dst_len', + * the allocated size of destination buffer 'dst_alloc'. All three destination + * buffer arguments may be modified by this function. Arguments relative to the + * source buffer are, a pointer to the source buffer 'src_buf' and indication + * whether the source buffer is base64 encoded or not 'src_b64'. + * + * If the source buffer is indicated to be base64 encoded, this appends the + * decoded data, binary or whatever, to the destination. The source buffer + * may not hold binary data, only a null terminated string is valid content. + * + * Destination buffer will be enlarged and relocated as needed. + * + * Calling function is responsible to provide preallocated destination + * buffer and also to deallocate it when no longer needed. + * + * This function may return: + * GPE_OUT_OF_MEMORY + * GPE_OK + */ + +static int appenddata(char **dst_buf, /* dest buffer */ + size_t *dst_len, /* dest buffer data length */ + size_t *dst_alloc, /* dest buffer allocated size */ + char *src_buf, /* source buffer */ + int src_b64) /* != 0 if source is base64 encoded */ { + size_t need_alloc, src_len; union { - unsigned char * as_uchar; - char * as_char; + unsigned char *as_uchar; + char *as_char; } buf64; - size_t len = strlen(buffer); - size_t needed_len = len + *stringlen + 1; + src_len = strlen(src_buf); + if(!src_len) + return GPE_OK; buf64.as_char = NULL; - if(base64) { - /* decode the given buffer first */ - len = Curl_base64_decode(buffer, &buf64.as_uchar); /* updated len */ - buffer = buf64.as_char; - needed_len = len + *stringlen + 1; /* recalculate */ + if(src_b64) { + /* base64 decode the given buffer */ + src_len = Curl_base64_decode(src_buf, &buf64.as_uchar); + src_buf = buf64.as_char; + if(!src_len || !src_buf) { + /* + ** currently there is no way to tell apart an OOM condition in + ** Curl_base64_decode() from zero length decoded data. For now, + ** let's just assume it is an OOM condition, currently we have + ** no input for this function that decodes to zero length data. + */ + if(buf64.as_char) + free(buf64.as_char); + return GPE_OUT_OF_MEMORY; + } } - if(needed_len >= *stralloc) { - char *newptr; - size_t newsize = needed_len*2; /* get twice the needed size */ + need_alloc = src_len + *dst_len + 1; - newptr = realloc(string, newsize); - if(newptr) { - string = newptr; - *stralloc = newsize; - } - else { + /* enlarge destination buffer if required */ + if(need_alloc > *dst_alloc) { + size_t newsize = need_alloc * 2; + char *newptr = realloc(*dst_buf, newsize); + if(!newptr) { if(buf64.as_char) free(buf64.as_char); - return NULL; + return GPE_OUT_OF_MEMORY; } + *dst_alloc = newsize; + *dst_buf = newptr; } + /* memcpy to support binary blobs */ - memcpy(&string[*stringlen], buffer, len); - *stringlen += len; - string[*stringlen]=0; + memcpy(*dst_buf + *dst_len, src_buf, src_len); + *dst_len += src_len; + *(*dst_buf + *dst_len) = '\0'; if(buf64.as_char) free(buf64.as_char); - return string; + return GPE_OK; } -const char *spitout(FILE *stream, - const char *main, - const char *sub, size_t *size) +/* + * getpart() + * + * This returns whole contents of specified XML-like section and subsection + * from the given file. This is mostly used to retrieve a specific part from + * a test definition file for consumption by test suite servers. + * + * Data is returned in a dynamically allocated buffer, a pointer to this data + * and the size of the data is stored at the addresses that caller specifies. + * + * If the returned data is a string the returned size will be the length of + * the string excluding null termination. Otherwise it will just be the size + * of the returned binary data. + * + * Calling function is responsible to free returned buffer. + * + * This function may return: + * GPE_NO_BUFFER_SPACE + * GPE_OUT_OF_MEMORY + * GPE_OK + */ + +int getpart(char **outbuf, size_t *outlen, + const char *main, const char *sub, FILE *stream) { - char buffer[8192]; /* big enough for anything */ - char cmain[128]=""; /* current main section */ - char csub[128]=""; /* current sub section */ +# define MAX_TAG_LEN 79 + char couter[MAX_TAG_LEN+1]; /* current outermost section */ + char cmain[MAX_TAG_LEN+1]; /* current main section */ + char csub[MAX_TAG_LEN+1]; /* current sub section */ + char ptag[MAX_TAG_LEN+1]; /* potential tag */ + char patt[MAX_TAG_LEN+1]; /* potential attributes */ + char *buffer = NULL; char *ptr; char *end; - char display = 0; - - char *string; - size_t stringlen=0; - size_t stralloc=256; - char base64 = 0; /* set to 1 if true */ + size_t length; + size_t bufsize = 0; + size_t outalloc = 256; + int in_wanted_part = 0; + int base64 = 0; + int error; enum { - STATE_OUTSIDE, - STATE_OUTER, - STATE_INMAIN, - STATE_INSUB, - STATE_ILLEGAL + STATE_OUTSIDE = 0, + STATE_OUTER = 1, + STATE_INMAIN = 2, + STATE_INSUB = 3, + STATE_ILLEGAL = 4 } state = STATE_OUTSIDE; - string = malloc(stralloc); - if(!string) - return NULL; + *outlen = 0; + *outbuf = malloc(outalloc); + if(!*outbuf) + return GPE_OUT_OF_MEMORY; + *(*outbuf) = '\0'; - string[0] = 0; /* zero first byte in case of no data */ + couter[0] = cmain[0] = csub[0] = ptag[0] = patt[0] = '\0'; - while(fgets(buffer, sizeof(buffer), stream)) { + while((error = readline(&buffer, &bufsize, stream)) == GPE_OK) { ptr = buffer; - - /* pass white spaces */ EAT_SPACE(ptr); if('<' != *ptr) { - if(display) { + if(in_wanted_part) { show(("=> %s", buffer)); - string = appendstring(string, buffer, &stringlen, &stralloc, base64); - show(("* %s\n", buffer)); + error = appenddata(outbuf, outlen, &outalloc, buffer, base64); + if(error) + break; } continue; } ptr++; - EAT_SPACE(ptr); if('/' == *ptr) { - /* end of a section */ - ptr++; - EAT_SPACE(ptr); + /* + ** closing section tag + */ + ptr++; end = ptr; EAT_WORD(end); - *end = 0; - - if((state == STATE_INSUB) && - !strcmp(csub, ptr)) { - /* this is the end of the currently read sub section */ - state--; - csub[0]=0; /* no sub anymore */ - display=0; + if((length = end - ptr) > MAX_TAG_LEN) { + error = GPE_NO_BUFFER_SPACE; + break; } - else if((state == STATE_INMAIN) && - !strcmp(cmain, ptr)) { - /* this is the end of the currently read main section */ - state--; - cmain[0]=0; /* no main anymore */ - display=0; + memcpy(ptag, ptr, length); + ptag[length] = '\0'; + + if((STATE_INSUB == state) && !strcmp(csub, ptag)) { + /* end of current sub section */ + state = STATE_INMAIN; + csub[0] = '\0'; + if(in_wanted_part) { + /* end of wanted part */ + in_wanted_part = 0; + break; + } + } + else if((STATE_INMAIN == state) && !strcmp(cmain, ptag)) { + /* end of current main section */ + state = STATE_OUTER; + cmain[0] = '\0'; + if(in_wanted_part) { + /* end of wanted part */ + in_wanted_part = 0; + break; + } } - else if(state == STATE_OUTER) { - /* this is the end of the outermost file section */ - state--; + else if((STATE_OUTER == state) && !strcmp(couter, ptag)) { + /* end of outermost file section */ + state = STATE_OUTSIDE; + couter[0] = '\0'; + if(in_wanted_part) { + /* end of wanted part */ + in_wanted_part = 0; + break; + } } + } - else if(!display) { - /* this is the beginning of a section */ + else if(!in_wanted_part) { + /* + ** opening section tag + */ + + /* get potential tag */ end = ptr; EAT_WORD(end); - - *end = 0; - switch(state) { - case STATE_OUTSIDE: - /* Skip over the outermost element (<testcase>), but if it turns out - to be a comment, completely ignore it below */ - strcpy(cmain, ptr); - state = STATE_OUTER; - break; - case STATE_OUTER: - strcpy(cmain, ptr); - state = STATE_INMAIN; - break; - case STATE_INMAIN: - strcpy(csub, ptr); - state = STATE_INSUB; + if((length = end - ptr) > MAX_TAG_LEN) { + error = GPE_NO_BUFFER_SPACE; break; - default: + } + memcpy(ptag, ptr, length); + ptag[length] = '\0'; + + /* ignore comments, doctypes and xml declarations */ + if(('!' == ptag[0]) || ('?' == ptag[0])) { + show(("* ignoring (%s)", buffer)); + continue; + } + + /* get all potential attributes */ + ptr = end; + EAT_SPACE(ptr); + end = ptr; + while(*end && ('>' != *end)) + end++; + if((length = end - ptr) > MAX_TAG_LEN) { + error = GPE_NO_BUFFER_SPACE; break; } + memcpy(patt, ptr, length); + patt[length] = '\0'; - if(!end[1] != '>') { - /* There might be attributes here. Check for those we know of and care - about. */ - if(strstr(&end[1], "base64=")) { - /* rough and dirty, but "mostly" functional */ - /* Treat all data as base64 encoded */ - base64 = 1; + if(STATE_OUTSIDE == state) { + /* outermost element (<testcase>) */ + strcpy(couter, ptag); + state = STATE_OUTER; + continue; + } + else if(STATE_OUTER == state) { + /* start of a main section */ + strcpy(cmain, ptag); + state = STATE_INMAIN; + continue; + } + else if(STATE_INMAIN == state) { + /* start of a sub section */ + strcpy(csub, ptag); + state = STATE_INSUB; + if(!strcmp(cmain, main) && !strcmp(csub, sub)) { + /* start of wanted part */ + in_wanted_part = 1; + if(strstr(patt, "base64=")) + /* bit rough test, but "mostly" functional, */ + /* treat wanted part data as base64 encoded */ + base64 = 1; } + continue; } - } - if(display) { - string = appendstring(string, buffer, &stringlen, &stralloc, base64); - show(("* %s\n", buffer)); - } - if((STATE_INSUB == state) && - !strcmp(cmain, main) && - !strcmp(csub, sub)) { - show(("* (%d bytes) %s\n", stringlen, buffer)); - display = 1; /* start displaying */ } - else if ((*cmain == '?') || (*cmain == '!') || (*csub == '!')) { - /* Ignore comments, DOCTYPEs and XML declarations */ - show(("%d ignoring (%s/%s)\n", state, cmain, csub)); - state--; + + if(in_wanted_part) { + show(("=> %s", buffer)); + error = appenddata(outbuf, outlen, &outalloc, buffer, base64); + if(error) + break; } + + } /* while */ + + if(buffer) + free(buffer); + + if(error != GPE_OK) { + if(error == GPE_END_OF_FILE) + error = GPE_OK; else { - show(("%d (%s/%s): %s\n", state, cmain, csub, buffer)); - display = 0; /* no display */ + if(*outbuf) + free(*outbuf); + *outbuf = NULL; + *outlen = 0; } } - *size = stringlen; - return string; + return error; } diff --git a/tests/server/getpart.h b/tests/server/getpart.h index a13badb9f..276d41de7 100644 --- a/tests/server/getpart.h +++ b/tests/server/getpart.h @@ -1,3 +1,5 @@ +#ifndef HEADER_SERVER_GETPART_H +#define HEADER_SERVER_GETPART_H /*************************************************************************** * _ _ ____ _ * Project ___| | | | _ \| | @@ -5,7 +7,7 @@ * | (__| |_| | _ <| |___ * \___|\___/|_| \_\_____| * - * Copyright (C) 1998 - 2009, Daniel Stenberg, <daniel@haxx.se>, et al. + * Copyright (C) 1998 - 2010, Daniel Stenberg, <daniel@haxx.se>, et al. * * This software is licensed as described in the file COPYING, which * you should have received as part of this distribution. The terms @@ -20,8 +22,13 @@ * * $Id$ ***************************************************************************/ -const char * -spitout(FILE *stream, - const char *main, - const char *sub, - size_t *size); + +#define GPE_NO_BUFFER_SPACE -2 +#define GPE_OUT_OF_MEMORY -1 +#define GPE_OK 0 +#define GPE_END_OF_FILE 1 + +int getpart(char **outbuf, size_t *outlen, + const char *main, const char *sub, FILE *stream); + +#endif /* HEADER_SERVER_GETPART_H */ diff --git a/tests/server/rtspd.c b/tests/server/rtspd.c index 5bc88a27f..c2910e07a 100644 --- a/tests/server/rtspd.c +++ b/tests/server/rtspd.c @@ -423,9 +423,14 @@ static int ProcessRequest(struct httprequest *req) char *rtp_scratch = NULL; /* get the custom server control "commands" */ - cmd = (char *)spitout(stream, "reply", "servercmd", &cmdsize); - ptr = cmd; + error = getpart(&cmd, &cmdsize, "reply", "servercmd", stream); fclose(stream); + if(error) { + logmsg("getpart() failed with error: %d", error); + req->open = FALSE; /* closes connection */ + return 1; /* done */ + } + ptr = cmd; if(cmdsize) { logmsg("Found a reply-servercmd section!"); @@ -505,6 +510,8 @@ static int ProcessRequest(struct httprequest *req) } while(ptr && *ptr); logmsg("Done parsing server commands"); } + if(cmd) + free(cmd); } } else { @@ -950,13 +957,20 @@ static int send_doc(curl_socket_t sock, struct httprequest *req) return 0; } else { - buffer = spitout(stream, "reply", partbuf, &count); - ptr = (char *)buffer; + error = getpart(&buffer, &count, "reply", partbuf, stream); fclose(stream); + if(error) { + logmsg("getpart() failed with error: %d", error); + return 0; + } + ptr = (char *)buffer; } - if(got_exit_signal) + if(got_exit_signal) { + if(ptr) + free(ptr); return -1; + } /* re-open the same file again */ stream=fopen(filename, "rb"); @@ -965,17 +979,30 @@ static int send_doc(curl_socket_t sock, struct httprequest *req) logmsg("fopen() failed with error: %d %s", error, strerror(error)); logmsg("Error opening file: %s", filename); logmsg("Couldn't open test file"); + if(ptr) + free(ptr); return 0; } else { /* get the custom server control "commands" */ - cmd = (char *)spitout(stream, "reply", "postcmd", &cmdsize); + error = getpart(&cmd, &cmdsize, "reply", "postcmd", stream); fclose(stream); + if(error) { + logmsg("getpart() failed with error: %d", error); + if(ptr) + free(ptr); + return 0; + } } } - if(got_exit_signal) + if(got_exit_signal) { + if(ptr) + free(ptr); + if(cmd) + free(cmd); return -1; + } /* If the word 'swsclose' is present anywhere in the reply chunk, the connection will be closed after the data has been sent to the requesting @@ -997,6 +1024,10 @@ static int send_doc(curl_socket_t sock, struct httprequest *req) logmsg("fopen() failed with error: %d %s", error, strerror(error)); logmsg("Error opening file: %s", RESPONSE_DUMP); logmsg("couldn't create logfile: " RESPONSE_DUMP); + if(ptr) + free(ptr); + if(cmd) + free(cmd); return -1; } @@ -1045,7 +1076,6 @@ static int send_doc(curl_socket_t sock, struct httprequest *req) req->rtp_buffersize = 0; } - do { res = fclose(dump); } while(res && ((error = ERRNO) == EINTR)); @@ -1053,8 +1083,13 @@ static int send_doc(curl_socket_t sock, struct httprequest *req) logmsg("Error closing file %s error: %d %s", RESPONSE_DUMP, error, strerror(error)); - if(got_exit_signal) + if(got_exit_signal) { + if(ptr) + free(ptr); + if(cmd) + free(cmd); return -1; + } if(sendfailure) { logmsg("Sending response failed. Only (%zu bytes) of (%zu bytes) were sent", diff --git a/tests/server/sws.c b/tests/server/sws.c index 16485f2f9..e1dfe45d8 100644 --- a/tests/server/sws.c +++ b/tests/server/sws.c @@ -389,8 +389,13 @@ static int ProcessRequest(struct httprequest *req) int num=0; /* get the custom server control "commands" */ - cmd = (char *)spitout(stream, "reply", "servercmd", &cmdsize); + error = getpart(&cmd, &cmdsize, "reply", "servercmd", stream); fclose(stream); + if(error) { + logmsg("getpart() failed with error: %d", error); + req->open = FALSE; /* closes connection */ + return 1; /* done */ + } if(cmdsize) { logmsg("Found a reply-servercmd section!"); @@ -423,8 +428,9 @@ static int ProcessRequest(struct httprequest *req) else { logmsg("funny instruction found: %s", cmd); } - free(cmd); } + if(cmd) + free(cmd); } } else { @@ -863,13 +869,20 @@ static int send_doc(curl_socket_t sock, struct httprequest *req) return 0; } else { - buffer = spitout(stream, "reply", partbuf, &count); - ptr = (char *)buffer; + error = getpart(&ptr, &count, "reply", partbuf, stream); fclose(stream); + if(error) { + logmsg("getpart() failed with error: %d", error); + return 0; + } + buffer = ptr; } - if(got_exit_signal) + if(got_exit_signal) { + if(ptr) + free(ptr); return -1; + } /* re-open the same file again */ stream=fopen(filename, "rb"); @@ -878,17 +891,30 @@ static int send_doc(curl_socket_t sock, struct httprequest *req) logmsg("fopen() failed with error: %d %s", error, strerror(error)); logmsg("Error opening file: %s", filename); logmsg("Couldn't open test file"); + if(ptr) + free(ptr); return 0; } else { /* get the custom server control "commands" */ - cmd = (char *)spitout(stream, "reply", "postcmd", &cmdsize); + error = getpart(&cmd, &cmdsize, "reply", "postcmd", stream); fclose(stream); + if(error) { + logmsg("getpart() failed with error: %d", error); + if(ptr) + free(ptr); + return 0; + } } } - if(got_exit_signal) + if(got_exit_signal) { + if(ptr) + free(ptr); + if(cmd) + free(cmd); return -1; + } /* If the word 'swsclose' is present anywhere in the reply chunk, the connection will be closed after the data has been sent to the requesting @@ -910,6 +936,10 @@ static int send_doc(curl_socket_t sock, struct httprequest *req) logmsg("fopen() failed with error: %d %s", error, strerror(error)); logmsg("Error opening file: %s", RESPONSE_DUMP); logmsg("couldn't create logfile: " RESPONSE_DUMP); + if(ptr) + free(ptr); + if(cmd) + free(cmd); return -1; } @@ -945,8 +975,13 @@ static int send_doc(curl_socket_t sock, struct httprequest *req) logmsg("Error closing file %s error: %d %s", RESPONSE_DUMP, error, strerror(error)); - if(got_exit_signal) + if(got_exit_signal) { + if(ptr) + free(ptr); + if(cmd) + free(cmd); return -1; + } if(sendfailure) { logmsg("Sending response failed. Only (%zu bytes) of (%zu bytes) were sent", diff --git a/tests/server/testpart.c b/tests/server/testpart.c index d2be529de..a78799ca7 100644 --- a/tests/server/testpart.c +++ b/tests/server/testpart.c @@ -35,15 +35,20 @@ int main(int argc, char **argv) { + int rc; + char *part; + size_t partlen, i; + if(argc< 3) { printf("./testpart main sub\n"); } else { - size_t size; - unsigned int i; - const char *buffer = spitout(stdin, argv[1], argv[2], &size); - for(i=0; i< size; i++) - printf("%c", buffer[i]); + rc = getpart(&part, &partlen, argv[1], argv[2], stdin); + if(rc) + return(rc); + for(i = 0; i < partlen; i++) + printf("%c", part[i]); + free(part); } return 0; } diff --git a/tests/server/tftpd.c b/tests/server/tftpd.c index 91e51cf42..152324904 100644 --- a/tests/server/tftpd.c +++ b/tests/server/tftpd.c @@ -1051,8 +1051,12 @@ static int validate_access(struct testcase *test, } else { size_t count; - test->buffer = (char *)spitout(stream, "reply", partbuf, &count); + error = getpart(&test->buffer, &count, "reply", partbuf, stream); fclose(stream); + if(error) { + logmsg("getpart() failed with error: %d", error); + return EACCESS; + } if(test->buffer) { test->rptr = test->buffer; /* set read pointer */ test->bufsize = count; /* set total count */ |