diff --git a/.gitignore b/.gitignore index 6c46439..d9ed1c8 100644 --- a/.gitignore +++ b/.gitignore @@ -1,2 +1,4 @@ *.o +*.a +utl.h server diff --git a/Makefile b/Makefile index 3005465..6c9734a 100644 --- a/Makefile +++ b/Makefile @@ -1,15 +1,6 @@ -all: server -clean: - @rm -rf *.o - @rm -rf server - -server: main.o httpd.o - gcc -o server $^ - -main.o: main.c httpd.h - gcc -c -o main.o main.c - -httpd.o: httpd.c httpd.h - gcc -c -o httpd.o httpd.c +all: + cd src; make +clean: + cd src; make cleanall diff --git a/httpd.c b/httpd.c deleted file mode 100644 index 0b3d432..0000000 --- a/httpd.c +++ /dev/null @@ -1,231 +0,0 @@ -#include "httpd.h" - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#define MAX_CONNECTIONS 1000 -#define BUF_SIZE 65535 -#define QUEUE_SIZE 1000000 - -static int listenfd; -int *clients; -static void start_server(const char *); -static void respond(int); - -static char *buf; - -// Client request -char *method, // "GET" or "POST" - *uri, // "/index.html" things before '?' - *qs, // "a=1&b=2" things after '?' - *prot, // "HTTP/1.1" - *payload; // for POST - -int payload_size; - -void serve_forever(const char *PORT) { - struct sockaddr_in clientaddr; - socklen_t addrlen; - - int slot = 0; - - printf("Server started %shttp://127.0.0.1:%s%s\n", "\033[92m", PORT, - "\033[0m"); - - // create shared memory for client slot array - clients = mmap(NULL, sizeof(*clients) * MAX_CONNECTIONS, - PROT_READ | PROT_WRITE, MAP_ANONYMOUS | MAP_SHARED, -1, 0); - - // Setting all elements to -1: signifies there is no client connected - int i; - for (i = 0; i < MAX_CONNECTIONS; i++) - clients[i] = -1; - start_server(PORT); - - // Ignore SIGCHLD to avoid zombie threads - signal(SIGCHLD, SIG_IGN); - - // ACCEPT connections - while (1) { - addrlen = sizeof(clientaddr); - clients[slot] = accept(listenfd, (struct sockaddr *)&clientaddr, &addrlen); - - if (clients[slot] < 0) { - perror("accept() error"); - exit(1); - } else { - if (fork() == 0) { - close(listenfd); - respond(slot); - close(clients[slot]); - clients[slot] = -1; - exit(0); - } else { - close(clients[slot]); - } - } - - while (clients[slot] != -1) - slot = (slot + 1) % MAX_CONNECTIONS; - } -} - -// start server -void start_server(const char *port) { - struct addrinfo hints, *res, *p; - - // getaddrinfo for host - memset(&hints, 0, sizeof(hints)); - hints.ai_family = AF_INET; - hints.ai_socktype = SOCK_STREAM; - hints.ai_flags = AI_PASSIVE; - if (getaddrinfo(NULL, port, &hints, &res) != 0) { - perror("getaddrinfo() error"); - exit(1); - } - // socket and bind - for (p = res; p != NULL; p = p->ai_next) { - int option = 1; - listenfd = socket(p->ai_family, p->ai_socktype, 0); - setsockopt(listenfd, SOL_SOCKET, SO_REUSEADDR, &option, sizeof(option)); - if (listenfd == -1) - continue; - if (bind(listenfd, p->ai_addr, p->ai_addrlen) == 0) - break; - } - if (p == NULL) { - perror("socket() or bind()"); - exit(1); - } - - freeaddrinfo(res); - - // listen for incoming connections - if (listen(listenfd, QUEUE_SIZE) != 0) { - perror("listen() error"); - exit(1); - } -} - -// get request header by name -char *request_header(const char *name) { - header_t *h = reqhdr; - while (h->name) { - if (strcmp(h->name, name) == 0) - return h->value; - h++; - } - return NULL; -} - -// get all request headers -header_t *request_headers(void) { return reqhdr; } - -// Handle escape characters (%xx) -static void uri_unescape(char *uri) { - char chr = 0; - char *src = uri; - char *dst = uri; - - // Skip inital non encoded character - while (*src && !isspace((int)(*src)) && (*src != '%')) - src++; - - // Replace encoded characters with corresponding code. - dst = src; - while (*src && !isspace((int)(*src))) { - if (*src == '+') - chr = ' '; - else if ((*src == '%') && src[1] && src[2]) { - src++; - chr = ((*src & 0x0F) + 9 * (*src > '9')) * 16; - src++; - chr += ((*src & 0x0F) + 9 * (*src > '9')); - } else - chr = *src; - *dst++ = chr; - src++; - } - *dst = '\0'; -} - -// client connection -void respond(int slot) { - int rcvd; - - buf = malloc(BUF_SIZE); - rcvd = recv(clients[slot], buf, BUF_SIZE, 0); - - if (rcvd < 0) // receive error - fprintf(stderr, ("recv() error\n")); - else if (rcvd == 0) // receive socket closed - fprintf(stderr, "Client disconnected upexpectedly.\n"); - else // message received - { - buf[rcvd] = '\0'; - - method = strtok(buf, " \t\r\n"); - uri = strtok(NULL, " \t"); - prot = strtok(NULL, " \t\r\n"); - - uri_unescape(uri); - - fprintf(stderr, "\x1b[32m + [%s] %s\x1b[0m\n", method, uri); - - qs = strchr(uri, '?'); - - if (qs) - *qs++ = '\0'; // split URI - else - qs = uri - 1; // use an empty string - - header_t *h = reqhdr; - char *t, *t2; - while (h < reqhdr + 16) { - char *key, *val; - - key = strtok(NULL, "\r\n: \t"); - if (!key) - break; - - val = strtok(NULL, "\r\n"); - while (*val && *val == ' ') - val++; - - h->name = key; - h->value = val; - h++; - fprintf(stderr, "[H] %s: %s\n", key, val); - t = val + 1 + strlen(val); - if (t[1] == '\r' && t[2] == '\n') - break; - } - t = strtok(NULL, "\r\n"); - t2 = request_header("Content-Length"); // and the related header if there is - payload = t; - payload_size = t2 ? atol(t2) : (rcvd - (t - buf)); - - // bind clientfd to stdout, making it easier to write - int clientfd = clients[slot]; - dup2(clientfd, STDOUT_FILENO); - close(clientfd); - - // call router - route(); - - // tidy up - fflush(stdout); - shutdown(STDOUT_FILENO, SHUT_WR); - close(STDOUT_FILENO); - } - - free(buf); -} diff --git a/httpd.h b/httpd.h deleted file mode 100644 index 262aa3d..0000000 --- a/httpd.h +++ /dev/null @@ -1,50 +0,0 @@ -#ifndef _HTTPD_H___ -#define _HTTPD_H___ - -#include -#include - -// Client request -extern char *method, // "GET" or "POST" - *uri, // "/index.html" things before '?' - *qs, // "a=1&b=2" things after '?' - *prot, // "HTTP/1.1" - *payload; // for POST - -extern int payload_size; - -// Server control functions -void serve_forever(const char *PORT); - -char *request_header(const char *name); - -typedef struct { - char *name, *value; -} header_t; -static header_t reqhdr[17] = {{"\0", "\0"}}; -header_t *request_headers(void); - -// user shall implement this function - -void route(); - -// Response -#define RESPONSE_PROTOCOL "HTTP/1.1" - -#define HTTP_200 printf("%s 200 OK\n\n", RESPONSE_PROTOCOL) -#define HTTP_201 printf("%s 201 Created\n\n", RESPONSE_PROTOCOL) -#define HTTP_404 printf("%s 404 Not found\n\n", RESPONSE_PROTOCOL) -#define HTTP_500 printf("%s 500 Internal Server Error\n\n", RESPONSE_PROTOCOL) - -// some interesting macro for `route()` -#define ROUTE_START() if (0) { -#define ROUTE(METHOD, URI) \ - } \ - else if (strcmp(URI, uri) == 0 && strcmp(METHOD, method) == 0) { -#define GET(URI) ROUTE("GET", URI) -#define POST(URI) ROUTE("POST", URI) -#define ROUTE_END() \ - } \ - else HTTP_500; - -#endif diff --git a/src/httpd.h b/src/httpd.h new file mode 100644 index 0000000..5d023f4 --- /dev/null +++ b/src/httpd.h @@ -0,0 +1,113 @@ +#ifndef HTTPD_H___ +#define HTTPD_H___ + +#include +#include +#include + +// Client request +extern char *methodstr, // "GET" or "POST" + *uri, // "/index.html" things before '?' + *querystr, // "a=1&b=2" things after '?' + *prot, // "HTTP/1.1" + *payload; // for POST + +extern int payload_size; + +// Server control functions +void httpd_start(const char *PORT); +char *request_header(const char *name); + +typedef struct { + char *name, *value; +} header_t; + +header_t *request_headers(void); + +#define METHOD_NONE 0 +#define METHOD_GET 1 +#define METHOD_POST 2 +#define METHOD_HEAD 3 +#define METHOD_DELETE 4 +#define METHOD_OPTIONS 5 +#define METHOD_PUT 6 +#define METHOD_TRACE 7 + +#define METHOD_STR(m) "NONE\0 " \ + "GET\0 " \ + "POST\0 " \ + "HEAD\0 " \ + "DELETE\0 " \ + "OPTIONS\0" \ + "PUT\0 " \ + "TRACE\0 " \ + + (((m) & 0x07)*8) + +extern int method; + +typedef struct { + char *buffer; + int32_t buffer_size; + int32_t buffer_count; + int32_t protocol_start; + int32_t headers_start; + int32_t querystr_start; + int32_t payload_start; + int32_t payload_size; + int16_t headers_num; + int16_t method; +} httpd_req_t; + +char *httpd_header(httpd_req_t *req, char *hdr); +char *httpd_header_first(httpd_req_t *req); +char *httpd_header_next(httpd_req_t *req); +char *httpd_header_value(char *hdr); +int httpd_header_count(httpd_req_t *req); +char *httpd_query(httpd_req_t *req); +char *httpd_queryarg_first(httpd_req_t *req); +char *httpd_payload(httpd_req_t *req); +char *httpd_protocol(httpd_req_t *req); +int httpd_payload_size(httpd_req_t *req); +int httpd_method(httpd_req_t *req); + +// user shall implement the function +// void httpd_route() + +// Thise one will be called by the listener upon +// successful request. + +void httpd_route_(); + + +// Response +#define RESPONSE_PROTOCOL "HTTP/1.1" + +#define HTTP_200 printf("%s 200 OK\r\n", RESPONSE_PROTOCOL) +#define HTTP_201 printf("%s 201 Created\r\n", RESPONSE_PROTOCOL) +#define HTTP_400 printf("%s 400 Bad Request\r\n", RESPONSE_PROTOCOL) +#define HTTP_404 printf("%s 404 Not found\r\n", RESPONSE_PROTOCOL) +#define HTTP_500 printf("%s 500 Internal Server Error\r\n", RESPONSE_PROTOCOL) + +#define HTTPD_RESP(n) + +#define HTTP_HDR(HDR,...) do { \ + printf("%s: ",HDR); \ + printf(__VA_ARGS__); \ + puts("\r\n"); \ + } while(0) + +// some interesting macro for `route()` +#define httpd_route() httpd_route_() { if (0) + +#define HTTPD_ROUTE(METHOD, URI) \ + } \ + else if ((METHOD == method) && strcmp(URI, uri) == 0 ) { + +#define HTTPD_DEFAULT() } else + +#define HTTPD_ERR() } else HTTP_500 + +#define HTTPD_GET(URI) HTTPD_ROUTE(METHOD_GET, URI) +#define HTTPD_POST(URI) HTTPD_ROUTE(METHOD_POST, URI) + +#endif diff --git a/src/httpd_priv.h b/src/httpd_priv.h new file mode 100644 index 0000000..9a72909 --- /dev/null +++ b/src/httpd_priv.h @@ -0,0 +1,25 @@ + +#ifndef HTTPD_PRIV_H___ +#define HTTPD_PRIV_H___ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "httpd.h" + +#define BUF_SIZE 65536 + +int get_request(int clientfd, char *buffer); + + +//#include "utl.h" +#endif diff --git a/src/listener.c b/src/listener.c new file mode 100644 index 0000000..2e55b92 --- /dev/null +++ b/src/listener.c @@ -0,0 +1,103 @@ +#include "httpd_priv.h" + +static int listenfd; + +static void start_server(const char *); + +// client connection +static void respond(int clientfd) +{ + int goodrequest = 0; + char *buffer = NULL; + + buffer = malloc(BUF_SIZE); + goodrequest = get_request(clientfd,buffer); + + // bind clientfd to stdout, making it easier to write + dup2(clientfd, STDOUT_FILENO); + + if (goodrequest) + httpd_route_(); // call router + else + HTTP_400; + + // tidy up + fflush(stdout); + shutdown(STDOUT_FILENO, SHUT_WR); + close(STDOUT_FILENO); + + free(buffer); +} + +// start server +static void start_server(const char *port) +{ + struct addrinfo hints, *res, *p; + + // getaddrinfo for host + memset(&hints, 0, sizeof(hints)); + hints.ai_family = AF_INET; + hints.ai_socktype = SOCK_STREAM; + hints.ai_flags = AI_PASSIVE; + if (getaddrinfo(NULL, port, &hints, &res) != 0) { + perror("getaddrinfo() error"); + exit(1); + } + // socket and bind + for (p = res; p != NULL; p = p->ai_next) { + int option = 1; + listenfd = socket(p->ai_family, p->ai_socktype, 0); + setsockopt(listenfd, SOL_SOCKET, SO_REUSEADDR, &option, sizeof(option)); + if (listenfd == -1) + continue; + if (bind(listenfd, p->ai_addr, p->ai_addrlen) == 0) + break; + } + if (p == NULL) { + perror("socket() or bind()"); + exit(1); + } + + freeaddrinfo(res); + + // listen for incoming connections + if (listen(listenfd, SOMAXCONN ) != 0) { + perror("listen() error"); + exit(1); + } +} + +void httpd_start(const char *PORT) +{ + struct sockaddr_in clientaddr; + socklen_t addrlen; + int clientfd; + + fprintf(stderr, "Server started %shttp://127.0.0.1:%s%s\n", "\033[92m", PORT, + "\033[0m"); + + start_server(PORT); + + // Ignore SIGCHLD to avoid zombie threads + signal(SIGCHLD, SIG_IGN); + + // ACCEPT connections + addrlen = sizeof(clientaddr); + while (1) { + clientfd = accept(listenfd, (struct sockaddr *)&clientaddr, &addrlen); + + if (clientfd < 0) { + perror("accept() error"); + exit(1); + } else { + if (fork() == 0) { // child + close(listenfd); + respond(clientfd); + close(clientfd); + exit(0); + } else { // parent + close(clientfd); + } + } + } +} diff --git a/src/makefile b/src/makefile new file mode 100644 index 0000000..431dae5 --- /dev/null +++ b/src/makefile @@ -0,0 +1,36 @@ + +CFLAGS = -O2 -Wall -DDEBUG=DBG_TEST -I../utl -I. +LNFLAGS = -L../utl -L. +LIBS = -lutl -lhttpd + +.c.o: + gcc $(CFLAGS) -c -o $*.o $*.c + +all: ../server + +# Server +../server: ../utl/libutl.a libhttpd.a server.o + gcc $(LNFLAGS) -o $@ $^ $(LIBS) + +server.o: server.c httpd.h + +# HTTPD library +request.o: request.c httpd.h +listener.o: listener.c httpd.h + +libhttpd.a: listener.o request.o + ar -r $@ $^ + +## UTL (mini) library + +../utl/libutl.a: + cd ../utl ; make + +clean: + rm -rf *.o libhttpd.a + rm -rf ../server ./server + +cleanall: + make clean + cd ../utl; make clean + diff --git a/src/request.c b/src/request.c new file mode 100644 index 0000000..66ea2cb --- /dev/null +++ b/src/request.c @@ -0,0 +1,153 @@ +#include "httpd_priv.h" + + +static header_t reqhdr[17] = {{"\0", "\0"}}; + +// Client request +char *methodstr, // "GET" or "POST" + *uri, // "/index.html" things before '?' + *querystr, // "a=1&b=2" things after '?' + *prot, // "HTTP/1.1" + *payload; // for POST + +int payload_size; +int method; + +// get request header by name +char *request_header(const char *name) +{ + header_t *h = reqhdr; + while (h->name) { + if (strcmp(h->name, name) == 0) + return h->value; + h++; + } + return NULL; +} + +// get all request headers +header_t *request_headers(void) { return reqhdr; } + +// Handle escape characters (%xx) +static void uri_unescape(char *uri) +{ + char chr = 0; + char *src = uri; + char *dst = uri; + char hex[4] = {0}; + + assert(hex[3]=='\0'); + + // Skip inital non encoded character + while (*src && !isspace((int)(*src)) && (*src != '%')) + src++; + + // Replace encoded characters with corresponding code. + dst = src; + while (*src && !isspace((int)(*src))) { + chr = *src++; + if (chr == '%') { + hex[0] = src[0]; hex[1] = src[1]; + if ((chr = strtol(hex,NULL,16)) == 0) + chr = '%'; + else + src += 2; + } + else if (chr == '+') + chr = ' '; + + *dst++ = chr; + } + *dst = '\0'; +} + +static int method_code(char *meth) +{ + int code; + + switch (*meth) { + case 'P' : code = (meth[1] == 'O') ? METHOD_POST : METHOD_PUT; + break; + + case 'G' : code = METHOD_GET; break; + case 'H' : code = METHOD_HEAD; break; + case 'D' : code = METHOD_DELETE; break; + case 'O' : code = METHOD_OPTIONS; break; + case 'T' : code = METHOD_TRACE; break; + default : code = METHOD_NONE; break; + } + + return code; +} + + +/* + _________________ __________ + V \ / V + -->[REQ LINE] --o-> [HEADER LINE] -o-->[CRLF]-o-->[BODY]--o-->[end] +*/ + +char linebuf[1024]; + +int get_request(int clientfd, char *buffer) +{ + int ret = 0; + int rcvd; + + rcvd = recv(clientfd, buffer, BUF_SIZE, 0); + + if (rcvd < 0) // receive error + fprintf(stderr, ("ERROR: recv() error\n")); + else if (rcvd == 0) // receive socket closed + fprintf(stderr, "INFO: Client disconnected.\n"); + else { // message received + buffer[rcvd] = '\0'; + + methodstr = strtok(buffer, " \t\r\n"); + method = method_code(methodstr); + + if (method != METHOD_NONE) { + uri = strtok(NULL, " \t"); + querystr = strchr(uri, '?'); + + prot = strtok(NULL, " \t\r\n"); + + uri_unescape(uri); + fprintf(stderr, "\x1b[32m + [%s] %s\x1b[0m\n", METHOD_STR(method), uri); + + if (querystr) + *querystr++ = '\0'; // split URI + else + querystr = uri - 1; // use an empty string + + header_t *h = reqhdr; + char *t, *t2; + while (h < reqhdr + 16) { + char *key, *val; + + key = strtok(NULL, "\r\n: \t"); + if (!key) + break; + + val = strtok(NULL, "\r\n"); + while (*val && *val == ' ') + val++; + + h->name = key; + h->value = val; + h++; + fprintf(stderr, "[H] %s: %s\n", key, val); + t = val + 1 + strlen(val); + if (t[1] == '\r' && t[2] == '\n') + break; + } + t = strtok(NULL, "\r\n"); + t2 = request_header("Content-Length"); // and the related header if there is + payload = t; + payload_size = t2 ? atol(t2) : (rcvd - (t - buffer)); + ret = 1; + } + } + + return ret; +} diff --git a/main.c b/src/server.c similarity index 90% rename from main.c rename to src/server.c index 1b375d6..d2236a6 100644 --- a/main.c +++ b/src/server.c @@ -10,7 +10,7 @@ int main(int c, char **v) { char *port = c == 1 ? "8000" : v[1]; - serve_forever(port); + httpd_start(port); return 0; } @@ -41,10 +41,11 @@ int read_file(const char *file_name) { return err; } -void route() { - ROUTE_START() +void httpd_route() +{ + // ROUTE_START() - GET("/") { + HTTPD_GET("/") { char index_html[20]; sprintf(index_html, "%s%s", PUBLIC_DIR, INDEX_HTML); @@ -56,7 +57,7 @@ void route() { } } - GET("/test") { + HTTPD_GET("/test") { HTTP_200; printf("List of request headers:\n\n"); @@ -68,7 +69,7 @@ void route() { } } - POST("/") { + HTTPD_POST("/") { HTTP_201; printf("Wow, seems that you POSTed %d bytes.\n", payload_size); printf("Fetch the data using `payload` variable.\n"); @@ -76,7 +77,7 @@ void route() { printf("Request body: %s", payload); } - GET(uri) { + HTTPD_GET(uri) { char file_name[255]; sprintf(file_name, "%s%s", PUBLIC_DIR, uri); @@ -91,5 +92,7 @@ void route() { } } - ROUTE_END() + HTTPD_DEFAULT() { + HTTP_500; + } } diff --git a/utl/buf.c b/utl/buf.c new file mode 100644 index 0000000..7ad4bd2 --- /dev/null +++ b/utl/buf.c @@ -0,0 +1,125 @@ + +#include "buf.h" + +buf_t buf_new() +{ + buf_t buf; + + buf = malloc(sizeof(struct buf_s)); + if (buf) { + buf->buffer = NULL; + buf->size = 0; + buf->count = 0; + buf->pos = 0; + } + return buf; +} + +buf_t buf_free(buf_t buf) +{ + if (buf) { + free(buf->buffer); + buf->buffer = NULL; + buf->size = 0; + buf->count = 0; + buf->pos = 0; + free(buf); + } + return NULL; +} + +int32_t buf_size(buf_t buf) +{ return (buf?buf->size:0); } + +int32_t buf_pos(buf_t buf) +{ return (buf?buf->pos:0); } + +int32_t buf_count(buf_t buf) +{ return (buf?buf->count:0); } + +char *buf_str(buf_t buf, int32_t pos) +{ + if (buf == NULL) return NULL; + if (pos<0) pos = 0; + else if (pos > buf->pos) pos= buf->pos; + + return buf->buffer + pos; +} + +int32_t buf_makeroom(buf_t buf, int32_t size) +{ + int32_t new_size = 1; + char *new_buffer = NULL; + + if (buf == NULL) return 0; + if (size <= buf->size) return 1; + new_size = buf->size ? buf->size : 1; + while (new_size <= size) { + new_size += (new_size / 2); /* (new_size *= 1.5) */ + new_size += (new_size & 1); /* ensure new size is even */ + } + + new_buffer = realloc(buf->buffer, new_size); + if (new_buffer) { + buf->buffer = new_buffer; + buf->size = new_size; + return 1; + } + + errno = ENOMEM; + return 0; +} + +int32_t buf_printf(buf_t buf, const char *fmt, ...) +{ + va_list args; + int len,pos; + char chr; + + pos = buf->pos; + + va_start(args, fmt); + len = vsnprintf(NULL,0,fmt,args); + va_end(args); + + if (len <= 0 || !buf_makeroom(buf,buf->pos+len+4)) return 0; + chr = buf->buffer[buf->pos+len]; + + va_start(args, fmt); + len = vsnprintf(((char*)(buf->buffer))+pos,len+1,fmt,args); + va_end(args); + + buf->buffer[buf->pos+len] = chr; + buf->pos += len; + if (buf->count < buf->pos) buf->count = buf->pos; + buf->buffer[buf->count] = '\0'; + + return len; +} + +static int32_t buf_write_(buf_t buf, char* src, int32_t len, int32_t raw) +{ + if (!src || !buf) return 0; + if (!raw && (len <= 0)) len = strlen(src); + if (len <= 0) return 0; + if (!buf_makeroom(buf,buf->pos+len+4)) return 0; + int n; + for (n = len; (raw || *src) && (n > 0); n--) { + buf->buffer[buf->pos++] = *src++; + } + if (buf->count < buf->pos) buf->count = buf->pos; + buf->buffer[buf->count] = '\0'; + return (len - n); +} + +int32_t buf_putc(buf_t buf, int c) +{ + char ch = (char)c; + return buf_write_(buf,&ch,1,1); +} + +int32_t buf_puts(buf_t buf, char *src) +{ return buf_write_(buf,src,0,0); } + +int32_t buf_write(buf_t buf, char *src, int32_t len) +{ return buf_write_(buf,src,len,1); } diff --git a/utl/buf.h b/utl/buf.h new file mode 100644 index 0000000..5476bf5 --- /dev/null +++ b/utl/buf.h @@ -0,0 +1,32 @@ +#ifndef BUF_H___ +#define BUF_H___ + +#include +#include +#include +#include +#include +#include + +typedef struct buf_s { + char *buffer; + int32_t size; + int32_t count; + int32_t pos; +} *buf_t; + + +buf_t buf_new(); +buf_t buf_free(buf_t buf); +int32_t buf_size(buf_t buf); +int32_t buf_pos(buf_t buf); +int32_t buf_count(buf_t buf); +char *buf_str(buf_t buf, int32_t pos); +int32_t buf_makeroom(buf_t buf, int32_t size); +int buf_putc(buf_t buf,int c); +int32_t buf_printf(buf_t b, const char *fmt, ...); +int32_t buf_putc(buf_t buf, int c); +int32_t buf_puts(buf_t buf, char *src); +int32_t buf_write(buf_t buf, char *src, int32_t len); + +#endif \ No newline at end of file diff --git a/utl/dbg.c b/utl/dbg.c new file mode 100644 index 0000000..35b78a5 --- /dev/null +++ b/utl/dbg.c @@ -0,0 +1,242 @@ +/* +** (C) 2020 by Remo Dentato (rdentato@gmail.com) +** +** This software is distributed under the terms of the MIT license: +** https://opensource.org/licenses/MIT +*/ + +// Globals +// ======= + +#include + +void *((*malloc_std)(size_t)) = malloc; +void ((*free_std)(void *)) = free; +void *((*realloc_std)(void *,size_t)) = realloc; +void *((*calloc_std)(size_t,size_t)) = calloc; + +#ifdef DEBUG +#undef DEBUG +#endif + +#define DEBUG DBG_TEST + +#include "dbg.h" + +// The global dbg_tst is to ensure dbgchk() can work outside a dbgtst() scope. It is not meant to be used in any way. + dbg_tst_t dbg_tst = {0, 0}; +volatile int dbg = 0; // dbg will always be 0. Used to suppress warnings in some macros + int dbg_lvl = DBG_WARN; + int dbg_tmsp = DBG_NOTIME; + char *dbg_lvls = "NEWItT"; // NONE, ERROR, WARNING, INFO, TEST, TEST_BDD + + +// Debugging levels +// ================ + +char dbglvl_(char *lvl, char *tms) +{ + if (lvl) { + switch(*lvl) { + case 'T' : dbg_lvl = lvl[1] == '-'? DBG_TEST : DBG_BDD; break; + case 't' : dbg_lvl = DBG_TEST; break; + case 'I' : dbg_lvl = DBG_INFO; break; + case 'W' : dbg_lvl = DBG_WARN; break; + case 'E' : dbg_lvl = DBG_ERROR; break; + case 'N' : dbg_lvl = DBG_NONE; break; + } + } + if (tms) { + switch(*tms) { + case 'T' : dbg_tmsp = DBG_TIME; break; + case 'N' : dbg_tmsp = DBG_NOTIME; break; + } + } + return dbg_lvls[(dbg_lvl+1)%6]; +} + +// Writing messages +// ================ + // 0 1 2 3 4 5 6 7 8 9 10 +char *dbg_msgtag = "XXXX" "FAIL" "PASS" "WARN" "INFO" "TRCE" "TST[" "TST]" + "CLK[" "CLK]" "TRK[" "TRK]" "GIVN" "WHEN" "THEN" "X"; + +int dbg_prttime(void) +{ + struct timespec clock; + struct tm *tm; + + clock_gettime(CLOCK_REALTIME,&clock); + tm = localtime(&(clock.tv_sec)); + fprintf(stderr,"%4d-%02d-%02d %02d:%02d:%02d.%06ld ",1900+tm->tm_year,tm->tm_mon+1,tm->tm_mday,tm->tm_hour,tm->tm_min, tm->tm_sec,clock.tv_nsec/1000); + + return 0; +} + +int dbg_dmp(char *s, char *file, int line) +{ + if (dbg_lvl >= DBG_TEST) { + if (s) dbgprt("DMP[: \x9%s:%d",file,line); + if (s && s != dbg_lvls) fprintf(stderr,"%s\n",s); + if (s != dbg_lvls) dbgprt("DMP]: \x9%s:%d",file,line); + } + return 0; +} + +/* Trace memory allocation +** ======================= +** +** +--------------+ <-- Actually allocated memory +** | 0xCA5ABA5E | +** +--------------+ +** |size (4 bytes)| +** +--------------+ <-- Returned pointer +** | | +** // // +** | | +** +--------------+ +** | 0x10CCADD1 | <-- Marker to identify overflow +** +--------------+ +*/ + +#define dbg_BEGCHK 0xCA5ABA5E +#define dbg_ENDCHK 0x10CCADD1 +#define dbg_CLRCHK 0xB5B0CC1A + +#define dbg_memptr(p) ((dbgmem_t *)(((uint8_t *)p) - offsetof(dbgmem_t,mem))) + +static char *dbg_memerr[] = { "Valid","Freed","Invalid","Overflown" }; + +int dbg_memcheck(int inv,void *m, char *file, int line, dbg_tst_t *tst) +{ + dbgmem_t *p = &((dbgmem_t){0}); + int err = 0; + int pass; + + if (dbg_lvl >= DBG_TEST) dbgprt("TRCE: MEM CHECK(%p) START\x9%s:%d",m, file, line); + if (m != NULL) { + p = dbg_memptr(m); + + if (memcmp(p->head, &((uint32_t){dbg_CLRCHK}), 4) == 0) err = 1; + else if (memcmp(p->head, &((uint32_t){dbg_BEGCHK}), 4)) err = 2; + else if (memcmp(p->mem+p->size,&((uint32_t){dbg_ENDCHK}),4)) err = 3; + } + + // We pass the test if we have no error and we were checking for a valid pointer + // or if we have an error and we were checking for an invalid pointer + pass = ((!!err) == inv); + + dbgprt("%s: MEM CHECK %p[%d] (%s)\x9%s:%d", (pass?"PASS":"FAIL"), m, (err?0:p->size), dbg_memerr[err], file, line); + + tst->fail += !pass; + tst->count++; + errno = err; + return err; +} + +static void dbg_memmark(dbgmem_t *p, uint32_t beg, uint32_t end) +{ + memcpy(p->head, (void *)(&beg), 4); + memcpy(p->mem + p->size, (void *)(&end), 4) ; +} + +void *dbg_malloc(int sz, char *file,int line, dbg_tst_t *dbg_tst) +{ + dbgmem_t *p = NULL; + void *ret = NULL; + + if (sz > 0) p = malloc_std(sizeof(dbgmem_t)+4+sz); + + if (p == NULL) { + dbg_tst->fail++; + dbg_tst->count++; + } + else { + p->size = sz; + dbg_memmark(p,dbg_BEGCHK, dbg_ENDCHK); + ret = p->mem; + } + if (dbg_lvl >= DBG_TEST) + dbgprt("%s: MEM malloc(%d) -> %p \x9%s:%d", p?"TRCE":"FAIL", sz, ret, file, line); + + return ret; +} + +void *dbg_calloc(int nitems, int size,char *file, int line, dbg_tst_t *dbg_tst) +{ + dbgmem_t *p = NULL; + int sz; + void *ret = NULL; + + sz = nitems*size ; + + if (sz > 0) p = calloc_std(sizeof(dbgmem_t)+4+sz, 1); + + if (p == NULL) { + dbg_tst->fail++; + dbg_tst->count++; + } + else { + p->size = sz; + dbg_memmark(p, dbg_BEGCHK, dbg_ENDCHK); + ret = p->mem; + } + if (dbg_lvl >= DBG_TEST) + dbgprt("%s: MEM calloc(%d,%d) -> %p\x9%s:%d",p?"TRCE":"FAIL",nitems,size,ret,file,line); + return ret; +} + + void *dbg_realloc(void *m, int sz, char *file,int line, dbg_tst_t *tst) + { + dbgmem_t *p=NULL; + void *ret = NULL; + + if (dbg_memcheck(0,m,file,line,tst) == 0) { + if (m) p = dbg_memptr(m); + if (sz) { + p = realloc_std(p,sizeof(dbgmem_t)+4+sz); + if (p) { + p->size = sz; + dbg_memmark(p, dbg_BEGCHK, dbg_ENDCHK); + ret = p->mem; + } + } + else { + if (p) { + dbg_memmark(p, dbg_CLRCHK, 0); + ret = realloc_std(p,0); + dbg_memcheck(1,((dbgmem_t *)p)->mem,file,line,tst); + } + } + } + + if (dbg_lvl >= DBG_TEST) dbgprt("TRCE: MEM realloc(%p,%d) -> %p\x9%s:%d",m,sz,ret,file,line); + + return ret; + } + + void dbg_free(void *m, char *file,int line, dbg_tst_t *tst) + { + void *p = NULL; + if ((dbg_memcheck(0,m,file,line,tst) == 0) && m) { + p =dbg_memptr(m); + dbg_memmark(p, dbg_CLRCHK, 0); + ((dbgmem_t *)p)->size = 0; + } + if (dbg_lvl >= DBG_TEST) dbgprt("TRCE: MEM free(%p)\x9%s:%d",m,file,line); + if (p) { + free_std(p); + //dbg_memcheck(1,m,file,line,tst); + } + } + + char *dbg_strdup(char *s, char *file, int line, dbg_tst_t *tst) + { + char *p=NULL; + if (dbg_lvl >= DBG_TEST) dbgprt("TRCE: MEM strdup(%p)\x9%s:%d",s,file,line); + if (s) { + p = dbg_malloc(strlen(s)+1, file, line, tst); + if (p) strcpy(p,s); + } + return p; + } diff --git a/utl/dbg.h b/utl/dbg.h new file mode 100644 index 0000000..f9f9bce --- /dev/null +++ b/utl/dbg.h @@ -0,0 +1,527 @@ +/* +** (C) 2020 by Remo Dentato (rdentato@gmail.com) +** +** This software is distributed under the terms of the MIT license: +** https://opensource.org/licenses/MIT +** +** DEBUG, TESTING (and LOGGING) MACROS. +** ==================================== +** +** Contents +== -------- +** * Introduction +** * Debugging levels +** * Writing messages +** * Unit tests (and BDD) +** * Debug Blocks +** * Timing +** * Tracking and watching +** * Trace memory allocation +*/ + +/* +** Debugging Groups +** ================ +** +** DBGx(...) // __VA_ARGS__ // Description of the group (disabled) +** +** DBGx(...) __VA_ARGS__ // Description of the group (enabled) +** +** DBG_(...) is an always enabled group +** DBG0(...) is an always disabled group +** +*/ + +#ifndef DBG_VERSION +#define DBG_VERSION 0x0103000B +#define DBG_VERSION_STR "dbg 1.3.0-beta" + +#ifdef DEBUG +extern volatile int dbg; // dbg will always be 0. Used to suppress warnings in some macros +#endif + +#ifdef DBG_FLOCK + #ifdef __MINGW32__ + #define FLOCKFILE _lock_file + #define FUNLOCKFILE _unlock_file + #else + #define FLOCKFILE flockfile + #define FUNLOCKFILE funlockfile + #endif +#else + #define FLOCKFILE(x) (dbg=0) + #define FUNLOCKFILE(x) (dbg=0) +#endif + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +/* Debugging levels +** ================ +** +** The functions behaviours depend on the `DEBUG` macro +** +** Reference +** --------- +** +** DEBUG --> If undefined, or defined as DBG_NONE removes all +** the debugging functions. +** If defined sets the level of debugging and enables +** the debuggin functions: +** +** level enabled functions +** --------- -------------------------- +** DBG_ERROR dbgerr() dbgmsg() dbgprt() dbgmst() +** DBG_WARN as above plus dbgwrn() +** DBG_INFO as above plus dbginf() +** DBG_TEST all the dbg functions. +** +** Note NDEBUG has higher priority than DEBUG, if +** NDEBUG is defined, DEBUG will be undefined. +** +** char dbglvl(char *lvl [, char* tms]) +** --> Sets the *running level* for debugging. The `lvl` argument can be +** one of "TEST", "test", "INFO", "WARN", "ERROR" or "NONE". +** Returns the firt characted of the currently set level ('T','t','I','W','N') +** The level "test" is the same as "TEST" but with BDD functions disabled. +** You can enable/disable the timestamp with the tms argument: +** dbglvl("TEST","NOTIMESTAMP") <-- timestamp disabled (default) +** dbglvl("TEST","TIMESTAMP") <-- timestamp enabled (default) +** +** You can disable timestamp permanently defining DBG_NOTIMESTAMP before including the dbg.h header. +** +*/ + +#define DBG_NONE -1 +#define DBG_ERROR 0 +#define DBG_WARN 1 +#define DBG_INFO 2 +#define DBG_TEST 3 +#define DBG_BDD 4 + +// Defining NDEBUG or DBG_NONE disables everything +#if defined(DEBUG) && (defined(NDEBUG) || (DEBUG == DBG_NONE)) + #undef DEBUG +#endif + +// The highest level of debugging is actually DBG_BDD +// To eliminate BDD messages from logs, set the level +// to "test" (lowercase!) with dbglvl("test"); +#if defined(DEBUG) && (DEBUG == DBG_TEST) + #undef DEBUG + #define DEBUG DBG_BDD +#endif + +// Macros to help defining variable arguments functions +#define dbg_exp(...) __VA_ARGS__ +#define dbg_0(x,...) (x) +#define dbg_1(y,x,...) (x) + +// Used to keep track of the number of testcases within a dbgtst() scope +typedef struct dbg_tst_s { + uint16_t count; // Number of tests + uint16_t fail; // Number of failed tests +} dbg_tst_t; + +#ifdef DEBUG + extern dbg_tst_t dbg_tst; // The global dbg_tst is to ensure dbgchk() can work outside of + // a dbgtst() scope. It is not meant to be used in any way. + extern volatile int dbg; // dbg will always be 0. Used to suppress warnings in some macros + extern int dbg_lvl; // Initialize to DBG_INFO + extern int dbg_tmsp; // Initialize to 1 - Print timestamp + extern char *dbg_lvls; // "NEWItT" NONE, ERROR, WARNING, INFO, TEST, TEST_BDD +#endif + +#ifdef DEBUG + char dbglvl_(char *lvl,char *tms); + #define dbglvl(...) dbglvl_(dbg_exp(dbg_0(__VA_ARGS__,NULL)),dbg_exp(dbg_1(__VA_ARGS__,NULL,NULL))) +#else + #define dbglvl(...) (DBG_NONE) +#endif + + +/* Timing & Timestamps +** =================== +** +** This takes a very crude measurement of the execution time of a block of code. +** You can use for profiling purpose or to check that the code has the expected +** performance even after a change. +** +** If DEBUG is undefined or lower than DBG_TEST, code in the dbgclk scope is +** executed but time measurement is not taken. +** +** The messages are formatted as explained in the "Writing messages" section. +** The first one is of type `LPS[:` and the last one, of type `LPS]:`, will +** report the elapsed time. +** +** You can pair the two messages using the filename:linenumber at the end +** of the line. +** +** 2020-09-19 13:44:30.174397 LPS[: myfile.c:17 <--, +** .... other messages produced by the code in the block ... | pair +** 2020-09-19 13:44:30.321648 LPS]: 00s 147.250800ms myfile.c:17 <--' +** +** Reference +** --------- +** +** dbgclk {...} --> Measure the time needed to execute the block. +** dbgnow() --> Print a timestamp in the log +** +** _dbgclk {...} --> Execute the block but don't measure time. +** _dbgnow() --> Do nothing. +** +*/ + +#define _dbgclk +#define _dbgnow + +#define DBG_TIME 1 +#define DBG_NOTIME 0 + +#if defined(DEBUG) && (DEBUG >= DBG_TEST) + + typedef struct { + struct timespec clk_start; + struct timespec clk_end; + long int elapsed; + long int nelapsed; + } dbgclk_t; + + + #define dbg_prtclk(s) (FLOCKFILE(stderr), dbg_time(), fputs("\xE" s,stderr),dbg = (dbg_tmsp?0:dbg_prttime()), \ + fprintf(stderr,"\x9%s:%d\xF\n",__FILE__,__LINE__), \ + fflush(stderr), FUNLOCKFILE(stderr), dbg) + + #define dbgnow() dbg_prtclk("NOW=: ") + + #define dbgclk \ + for ( dbgclk_t dbg_ = {.elapsed = -1} \ + ; \ + (dbg_.elapsed < 0) \ + && ((dbg_lvl >= DBG_TEST) ? (dbg_prtclk("CLK[: "),clock_gettime(CLOCK_REALTIME,&dbg_.clk_start),1) \ + : 1) \ + ; \ + clock_gettime(CLOCK_REALTIME,&dbg_.clk_end), \ + dbg_.elapsed = ((dbg_lvl >= DBG_TEST) \ + ? ( dbg_.elapsed = (dbg_.clk_end.tv_sec - dbg_.clk_start.tv_sec), \ + dbg_.nelapsed = (dbg_.clk_end.tv_nsec - dbg_.clk_start.tv_nsec), \ + (dbg_.nelapsed < 0)? (dbg_.elapsed--, dbg_.nelapsed += 1000000000) : 0, \ + dbgmsg("CLK]: %02lds %010.6fms", dbg_.elapsed,(double)dbg_.nelapsed/1000000.0))\ + : 0 )\ + ) +#else + #define dbgclk _dbgclk + #define dbgnow() +#endif + + +/* Writing messages +** ================ +** +** To ease the extraction of information (e.g. via grep), the following +** functions will print a single line with the following structure. +** +** 2020-09-19 12:32:43.229469 \xEINFO: Informative text\x9myfile.c:120\xF\n +** \_________________________/ \___/ \______..._____/ \__..._/ \_/\___/ +** \_ timestamp (optional) / / / \ +** message type__/ filename __/ line _/ \_ EOL +** number +** +** The TAB character (0x09) makes easier to identify the file name; just +** start from the end of the line and move backward. +** +** Note that End of line is '0x0F 0x0A' (or 0x0F 0x0D 0X0A). This is done to +** avoid confusion if your text contains any LF. +** +** Logs can also contain messages produced by dbgchk(). Check it down +** +** Reference +** --------- +** +** Note that the first argument **must** be a literal string (i.e: "xxx"). +** +** dbgmsg(char *, ...) --> Prints a message on stderr (works as printf(...)). +** If DEBUG is not defined, do nothing. +** RETURNS 0 +** +** dbgprt(char *, ...) --> Prints a message on stderr (works as printf(...)) omitting +** filename and line. If DEBUG is not defined, do nothing. +** +** dbgerr(char *, ...) --> Prints an "FAIL:" message (if level >= DBG_ERROR).. +** dbgwrn(char *, ...) --> Prints a "WARN:" message (if level >= DBG_WARN). +** dbginf(char *, ...) --> Prints an "INFO:" message (if level >= DBG_INFO). +** dbgtrc(char *, ...) --> Prints an "TRCE:" message (if level >= DBG_TEST). +** +** _dbgmsg(char *, ...) --> Do nothing. Used to disable the debug message. +** _dbgprt(char *, ...) --> Do nothing. Used to disable the debug message. +** _dbgtrc(char *, ...) --> Do nothing. Used to disable the debug message. +** _dbginf(char *, ...) --> Do nothing. Used to disable the debug message. +** _dbgwrn(char *, ...) --> Do nothing. Used to disable the debug message. +** _dbgerr(char *, ...) --> Do nothing. Used to disable the debug message. +** +*/ + +#define _dbgmsg(...) +#define _dbgprt(...) +#define _dbgtrc(...) +#define _dbginf(...) +#define _dbgwrn(...) +#define _dbgerr(...) +#define _dbgdmpstart() +#define _dbgdmpstop() + +#ifdef DEBUG + int dbg_tms(int); + int dbg_prttime(void); + + #ifdef DBG_NOTIMESTAMP + #define dbg_time() (dbg=0) + #else + #define dbg_time() (dbg = (dbg_tmsp && dbg_prttime())) + #endif + + #define dbgtms(x) (dbg_tmsp=(x)) + + #define dbgprt(...) (FLOCKFILE(stderr), dbg_time(), fprintf(stderr,"\xE" __VA_ARGS__), \ + fputs("\xF\n",stderr), \ + fflush(stderr), FUNLOCKFILE(stderr), dbg) + + #define dbgmsg(...) (FLOCKFILE(stderr), dbg_time(), fprintf(stderr,"\xE" __VA_ARGS__), \ + fprintf(stderr,"\x9%s:%d\xF\n",__FILE__,__LINE__), \ + fflush(stderr), FUNLOCKFILE(stderr), dbg) + + #define dbgerr(...) ((dbg_lvl >= DBG_ERROR) ? (dbg_tst.count++, dbg_tst.fail++, dbgmsg("FAIL: " __VA_ARGS__)):0) + + #if DEBUG < DBG_WARN + #define dbgwrn _dbgwrn + #else + #define dbgwrn(...) ((dbg_lvl >= DBG_WARN) ? dbgmsg("WARN: " __VA_ARGS__):0) + #endif + + #if DEBUG < DBG_INFO + #define dbginf _dbginf + #else + #define dbginf(...) ((dbg_lvl >= DBG_INFO) ? dbgmsg("INFO: " __VA_ARGS__):0) + #endif + + int dbg_dmp(char *s, char *file, int line); + + #if DEBUG < DBG_TEST + #define dbgtrc _dbgtrc + #define dbgdmp _dbgdmp + #define dbgdmpstart() + #define dbgdmpstop() + #else + #define dbgtrc(...) ((dbg_lvl >= DBG_TEST) ? dbgmsg("TRCE: " __VA_ARGS__):0) + #define dbgdmp(s) dbg_dmp(s,__FILE__,__LINE__) + #define dbgdmpstart() dbgdmp(dbg_lvls) + #define dbgdmpstop() dbgdmp(NULL) + #endif + +#else + #define dbgmsg _dbgmsg + #define dbgprt _dbgprt + #define dbginf _dbginf + #define dbgwrn _dbgwrn + #define dbgerr _dbgerr + #define dbgtrc _dbgtrc + #define dbgdmpstart() _dbgdmpstart() + #define dbgdmpstop() _dbgdmpstop() + + #define dbgtms(x) +#endif + +/* Unit tests (and BDD) +** ==================== +** +** These functions are used to write unit tests. Check the tst directory to +** see many examples on how to use them. +** +** If DEBUG is undefined or lower than DBG_TEST, each function dbgxxx() behaves +** as its counterpart _dbgxxx(). +** +** Note that the formatting string **must** be a literal (i.e.: "xxx"). +** +** Reference +** --------- +** +** dbgtst(char *) --> Starts a test scenario. +** +** dbgchk(test, char *, ...) --> Perform the test and set errno (0: OK, 1: KO). If test fails +** prints a message on stderr (works as printf(...)). +** +** dbggvn(char *) {...} --> Print the GIVEN clause (BDD) +** dbgwhn(char *) {...} --> Print the WHEN clause (BDD) +** dbgthn(char *) {...} --> Print the THEN clause (BDD) +** +** dbgmst(test, char *, ...) --> Works as assert() but prints a "FAIL:" message on stderr. +** +** _dbgtst(char *) --> Do nothing. Used to disable the debug message. +** _dbgchk(test, char *, ...) --> Do nothing. Used to disable the debug message. +** _dbggvn(char *) {...} --> Do not print the GIVEN clause (BDD) +** _dbgwhn(char *) {...} --> Do not print the WHEN clause (BDD) +** _dbgthn(char *) {...} --> Do not print the THEN clause (BDD) +** _dbgmst(e,...) --> Equivalent to assert() +** +*/ + +#define _dbgchk(...) +#define _dbgmst(e,...) assert(e) +#define _dbgtst(...) if (1) ; else + +#define _dbggvn(...) +#define _dbgwhn(...) +#define _dbgthn(...) + +#if defined(DEBUG) && (DEBUG >= DBG_TEST) + #define dbgtst(desc_) \ + for ( dbg_tst_t dbg_tst = {0,0} \ + ; \ + (dbg_tst.fail <= dbg_tst.count) \ + && (dbg_lvl >= DBG_TEST) && !dbgmsg("TST[: %s",desc_) \ + ; \ + dbgmsg("TST]: FAILED %d/%d - %s", dbg_tst.fail,dbg_tst.count, desc_), \ + dbg_tst.fail = dbg_tst.count + 1 \ + ) + + #define dbgchk(e,...) \ + do { if (dbg_lvl >= DBG_TEST) { \ + int dbg_err=!(e); dbg_tst.count++; dbg_tst.fail+=dbg_err; \ + FLOCKFILE(stderr); dbg_time(); \ + fprintf(stderr,"\xE%s: (%s)\x9%s:%d\xF\n",(dbg_err?"FAIL":"PASS"),#e,__FILE__,__LINE__); \ + if (dbg_err && *(dbg_exp(dbg_0(__VA_ARGS__)))) \ + { fprintf(stderr,"\xE" __VA_ARGS__); fputs("\xF\n",stderr); } \ + fflush(stderr); FUNLOCKFILE(stderr); \ + errno = dbg_err; dbg=0; \ + }} while(0) + + #define dbgmst(e,...) do { dbgchk(e, __VA_ARGS__); if (errno) abort();} while(0) + + #define dbggvn(...) if ((dbg_lvl >= DBG_BDD) && dbgmsg("GIVN: " __VA_ARGS__)) ; else + #define dbgwhn(...) if ((dbg_lvl >= DBG_BDD) && dbgmsg("WHEN: " __VA_ARGS__)) ; else + #define dbgthn(...) if ((dbg_lvl >= DBG_BDD) && dbgmsg("THEN: " __VA_ARGS__)) ; else + + #define dbgchkfail() do { if (dbg_lvl >= DBG_TEST) {\ + int err = errno; \ + dbg_tst.count++; \ + dbgmsg("%s",err ? (dbg_tst.fail--, "PASS: Previous test failed as expected") \ + : (dbg_tst.fail+=2,"FAIL: Previous test was expected to fail")); \ + errno = !err; \ + }} while (0) + +#else + #define dbgchk _dbgchk + #define dbgmst _dbgmst + #define dbgtst _dbgtst + + #define dbggvn _dbggvn + #define dbgwhn _dbgwhn + #define dbgthn _dbgthn +#endif + +/* Debug Blocks +** ============ +** +** If you want to execute a block of code exclusively for debugging purpose (and +** easily exclude it from the production code), you may use the dbgblk macro. +** +** Reference +** --------- +** +** dbgblk {...} --> Execute the block if DEBUG is defined as DBG_TEST. +** _dbgblk {...} --> Do not execute the code block. +*/ +#define _dbgblk if (1) ; else + +#ifdef DEBUG + #define dbgblk if (dbg_lvl < DBG_TEST) ; else +#else + #define dbgblk _dbgblk +#endif + +/* Tracking and watching +** ===================== +** +** Check the `dbgtrk.c` source file for further details on these functions. +** If DEBUG is undefined or lower than DBG_TEST, dbgtrk() behaves as _dbgtrk() +** +** Reference +** --------- +** +** dbgtrk(s) {...} --> Specify the patterns to be tracked within log generated by +** the instructions in the scope of the code block. +** Patterns are separated by a '\1' character. The first +** character specify what should be checked about the pattern: +** - the pattern does not appear +** = the pattern appears exactly once +** + the paterrn appears one or more times +** +** _dbgtrk(s) {...} --> Execute the block but don't mark string tracking. +** +*/ + +#define _dbgtrk(...) + +#if defined(DEBUG) && (DEBUG >= DBG_TEST) + #define dbgtrk(patterns) for (int dbg_trk = (dbg_lvl >= DBG_TEST)? !dbgmsg("TRK[: %s",patterns):1; \ + dbg_trk; \ + dbg_trk = (dbg_lvl >= DBG_TEST)? dbgmsg("TRK]: "):0) +#else + #define dbgtrk _dbgtrk +#endif + +// Trace memory allocation +// ======================= + + typedef struct dbgmem_s { + int size; + char head[4]; + char mem[]; + } dbgmem_t; + + #if defined(DEBUG) && defined(DBG_MEM) + + char *dbg_strdup(char *s, char *file, int line, dbg_tst_t *tst); + void dbg_free(void *p, char *file,int line, dbg_tst_t *tst); + void *dbg_realloc(void *m, int sz, char *file,int line, dbg_tst_t *tst); + void *dbg_calloc(int nitems, int size,char *file, int line,dbg_tst_t *tst); + void *dbg_malloc(int sz, char *file,int line, dbg_tst_t *tst); + int dbg_memcheck(int inv,void *m, char *file, int line, dbg_tst_t *tst); + + extern void *((*malloc_std)(size_t)) ; + extern void ((*free_std)(void *)) ; + extern void *((*realloc_std)(void *,size_t)); + extern void *((*calloc_std)(size_t,size_t)) ; + + #define malloc(n) dbg_malloc(n,__FILE__,__LINE__,&dbg_tst) + #define calloc(n,s) dbg_calloc(n,s,__FILE__,__LINE__,&dbg_tst) + #define free(p) dbg_free(p,__FILE__,__LINE__,&dbg_tst) + #define realloc(p,s) dbg_realloc(p,s,__FILE__,__LINE__,&dbg_tst) + #define dbgchkmem(p) dbg_memcheck(0,p,__FILE__,__LINE__,&dbg_tst) + #define dbgchkmeminv(p) dbg_memcheck(1,p,__FILE__,__LINE__,&dbg_tst) + + #ifdef strdup + #undef strdup + #endif + + #define strdup(s) dbg_strdup(s,__FILE__,__LINE__,&dbg_tst) + +#else // DBG_MEM + + #define free_std free + #define malloc_std malloc + #define realloc_std realloc + #define calloc_std calloc + #define dbgchkmem(p) (errno = 0) + #define dbgchkmeminv(p) (errno = 0) + +#endif // DBG_MEM + +#endif // DBG_VERSION diff --git a/utl/makefile b/utl/makefile new file mode 100644 index 0000000..ed36abd --- /dev/null +++ b/utl/makefile @@ -0,0 +1,12 @@ +CFLAGS = -O2 + +.c.o: + gcc $(CFLAGS) -c -o $*.o $*.c + +libutl.a: dbg.o buf.o + cat vrg.h dbg.h buf.h > utl.h + ar -r $@ $^ + +clean: + rm -rf *.o + rm -rf libutl.a utl.h \ No newline at end of file diff --git a/utl/vrg.h b/utl/vrg.h new file mode 100644 index 0000000..ad14df5 --- /dev/null +++ b/utl/vrg.h @@ -0,0 +1,45 @@ +/* +** (C) by Remo Dentato (rdentato@gmail.com) +** +** This software is distributed under the terms of the MIT license: +** https://opensource.org/licenses/MIT +*/ + +/* [[[ +# Variadic functions + +Say you want to define a variadic function with the following prototype: + + myfunc(int a [, char b [, void *c]]) + +In other words, you want `b` and `c` to be optional. + +Simply, define your function with another name (say `my_func()`) and specify +how it should be called when invoked with 1, 2 or 3 paramenters as shown +in the example below. + +Example: + + #include "utl.h" + + int my_func(int a, char b, void *c); + + #define myfunc(...) vrg(myfunc, __VA_ARGS__) + #define myfunc1(a) my_func(a,'\0',NULL) + #define myfunc2(a,b) my_func(a,b,NULL) + #define myfunc3(a,b,c) my_func(a,b,c) + +** +]]] */ + +#ifndef VRG_VERSION +#define VRG_VERSION 0x0001000C + +#define vrg_cnt(vrg1,vrg2,vrg3,vrg4,vrg5,vrg6,vrg7,vrg8,vrgN, ...) vrgN +#define vrg_argn(...) vrg_cnt(__VA_ARGS__, 8, 7, 6, 5, 4, 3, 2, 1, 0) +#define vrg_cat0(x,y) x ## y +#define vrg_cat(x,y) vrg_cat0(x,y) + +#define vrg(vrg_f,...) vrg_cat(vrg_f, vrg_argn(__VA_ARGS__))(__VA_ARGS__) + +#endif \ No newline at end of file