From fe0f4c935233893ad49ed9a62b86007afbbf53fd Mon Sep 17 00:00:00 2001 From: rdentato Date: Fri, 2 Apr 2021 15:36:06 +0200 Subject: [PATCH 01/10] Changed message on "unexpected client disconnection" (it is expected that the client might disconnect). Set listen() backlog queue to SOMAXCONN (the maximum backlog allowed by the system). Changed MAX_CONNECTIONS to handle 1024 instead of 1000 connection so that that the "mod" operation becomes a cheaper "bitwise and". Fixed a potential issue with a possible infinite loop if no connection is available. Moved the loop into the function nxt_slot() that will wait (with usleep()) before checking again. This is done to play nice with other processes. --- httpd.c | 56 ++++++++++++++++++++++++++++++++++++-------------------- 1 file changed, 36 insertions(+), 20 deletions(-) diff --git a/httpd.c b/httpd.c index 14c280a..49e1774 100644 --- a/httpd.c +++ b/httpd.c @@ -11,51 +11,70 @@ #include #include -#define MAX_CONNECTIONS 1000 +// Use (2^n-1) to speedup modulo +#define MAX_CONNECTIONS 1023 #define BUF_SIZE 65535 -#define QUEUE_SIZE 1000000 -static int listenfd; -int *clients; static void start_server(const char *); static void respond(int); +static int listenfd; +int *clients; 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 +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; +int nxt_slot(int slot) +{ + int nxt_slot = slot; + + do { + nxt_slot = (nxt_slot + 1) & MAX_CONNECTIONS; + + if (nxt_slot == slot) { // There is no slot available for a new client! + fprintf(stderr,"WARNING: no available connection\n"); + usleep(250); // Let's wait some millisecond + } + } while (clients[nxt_slot] != -1); + + return nxt_slot; +} + + void serve_forever(const char *PORT) { struct sockaddr_in clientaddr; socklen_t addrlen; - int slot = 0; + int slot = -1; 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, + clients = mmap(NULL, sizeof(*clients) * (MAX_CONNECTIONS+1), 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++) + for (int i = 0; i <= MAX_CONNECTIONS; i++) clients[i] = -1; + start_server(PORT); // Ignore SIGCHLD to avoid zombie threads signal(SIGCHLD, SIG_IGN); // ACCEPT connections + addrlen = sizeof(clientaddr); while (1) { - addrlen = sizeof(clientaddr); + // Look for slot to be used for next client + slot = nxt_slot(slot); clients[slot] = accept(listenfd, (struct sockaddr *)&clientaddr, &addrlen); if (clients[slot] < 0) { @@ -72,9 +91,6 @@ void serve_forever(const char *PORT) { close(clients[slot]); } } - - while (clients[slot] != -1) - slot = (slot + 1) % MAX_CONNECTIONS; } } @@ -109,7 +125,7 @@ void start_server(const char *port) { freeaddrinfo(res); // listen for incoming connections - if (listen(listenfd, QUEUE_SIZE) != 0) { + if (listen(listenfd, SOMAXCONN ) != 0) { perror("listen() error"); exit(1); } @@ -165,9 +181,9 @@ void respond(int slot) { rcvd = recv(clients[slot], buf, BUF_SIZE, 0); if (rcvd < 0) // receive error - fprintf(stderr, ("recv() error\n")); + fprintf(stderr, ("ERROR: recv() error\n")); else if (rcvd == 0) // receive socket closed - fprintf(stderr, "Client disconnected upexpectedly.\n"); + fprintf(stderr, "INFO: Client disconnected.\n"); else // message received { buf[rcvd] = '\0'; From 8206e0b050dbc47b74ef4a98c541336038ee535b Mon Sep 17 00:00:00 2001 From: rdentato Date: Fri, 2 Apr 2021 21:55:51 +0200 Subject: [PATCH 02/10] Moved choice of next slot to the end of the loop. --- httpd.c | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/httpd.c b/httpd.c index ad523fb..dc0e277 100644 --- a/httpd.c +++ b/httpd.c @@ -52,7 +52,7 @@ void serve_forever(const char *PORT) { struct sockaddr_in clientaddr; socklen_t addrlen; - int slot = -1; + int slot = 0; printf("Server started %shttp://127.0.0.1:%s%s\n", "\033[92m", PORT, "\033[0m"); @@ -73,8 +73,6 @@ void serve_forever(const char *PORT) { // ACCEPT connections addrlen = sizeof(clientaddr); while (1) { - // Look for slot to be used for next client - slot = nxt_slot(slot); clients[slot] = accept(listenfd, (struct sockaddr *)&clientaddr, &addrlen); if (clients[slot] < 0) { @@ -91,6 +89,8 @@ void serve_forever(const char *PORT) { close(clients[slot]); } } + // Look for slot to be used for next client + slot = nxt_slot(slot); } } From 3590521b90b73bee3c56b1ad86d092b4f9c6be90 Mon Sep 17 00:00:00 2001 From: rdentato Date: Fri, 2 Apr 2021 23:41:05 +0200 Subject: [PATCH 03/10] Added CFLAGS. --- Makefile | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/Makefile b/Makefile index 3005465..2c82be0 100644 --- a/Makefile +++ b/Makefile @@ -1,3 +1,5 @@ +CFLAGS= -O2 -Wall + all: server clean: @@ -8,8 +10,8 @@ server: main.o httpd.o gcc -o server $^ main.o: main.c httpd.h - gcc -c -o main.o main.c + gcc $(CFLAGS) -c -o $*.o $*.c httpd.o: httpd.c httpd.h - gcc -c -o httpd.o httpd.c + gcc $(CFLAGS) -c -o $*.o $*.c From 249eb5827917e3c44c7609e147a625b028d4e771 Mon Sep 17 00:00:00 2001 From: rdentato Date: Fri, 2 Apr 2021 23:42:30 +0200 Subject: [PATCH 04/10] Changed method to an int so that routing doesn't need comparing strings multiple times. --- httpd.c | 32 ++++++++++++++++++++++++++++---- httpd.h | 40 +++++++++++++++++++++++++++++++--------- 2 files changed, 59 insertions(+), 13 deletions(-) diff --git a/httpd.c b/httpd.c index dc0e277..caa715c 100644 --- a/httpd.c +++ b/httpd.c @@ -22,14 +22,17 @@ static int listenfd; int *clients; static char *buf; +static header_t reqhdr[17] = {{"\0", "\0"}}; + // Client request -char *method, // "GET" or "POST" +char *method_str, // "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; +int method; int nxt_slot(int slot) { @@ -160,7 +163,7 @@ static void uri_unescape(char *uri) { while (*src && !isspace((int)(*src))) { if (*src == '+') chr = ' '; - else if ((*src == '%') && src[1] && src[2]) { + else if ((*src == '%') && isxdigit((int)(src[1])) && isxdigit((int)(src[2]))) { src++; chr = ((*src & 0x0F) + 9 * (*src > '9')) * 16; src++; @@ -173,6 +176,25 @@ static void uri_unescape(char *uri) { *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; +} + // client connection void respond(int slot) { int rcvd; @@ -188,13 +210,15 @@ void respond(int slot) { { buf[rcvd] = '\0'; - method = strtok(buf, " \t\r\n"); + method_str = strtok(buf, " \t\r\n"); + method = method_code(method_str); + uri = strtok(NULL, " \t"); prot = strtok(NULL, " \t\r\n"); uri_unescape(uri); - fprintf(stderr, "\x1b[32m + [%s] %s\x1b[0m\n", method, uri); + fprintf(stderr, "\x1b[32m + [%s] %s\x1b[0m\n", METHOD_STR(method), uri); qs = strchr(uri, '?'); diff --git a/httpd.h b/httpd.h index 262aa3d..0faabb1 100644 --- a/httpd.h +++ b/httpd.h @@ -5,11 +5,11 @@ #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 char *method_str, // "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; @@ -21,9 +21,30 @@ 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); +#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; + // user shall implement this function void route(); @@ -40,9 +61,10 @@ void 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) + else if ((METHOD == method) && strcmp(URI, uri) == 0 ) { +#define GET(URI) ROUTE(METHOD_GET, URI) +#define POST(URI) ROUTE(METHOD_POST, URI) +#define HEAD(URI) ROUTE(METHOD_HEAD, URI) #define ROUTE_END() \ } \ else HTTP_500; From b8aea7ebdd89e651009af093f78857edb32ddc27 Mon Sep 17 00:00:00 2001 From: rdentato Date: Sat, 3 Apr 2021 18:49:41 +0200 Subject: [PATCH 05/10] Replaced "\n\n" with "\r\n" as required by HTTP protocol. --- httpd.h | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/httpd.h b/httpd.h index 0faabb1..55a90e9 100644 --- a/httpd.h +++ b/httpd.h @@ -7,7 +7,7 @@ // Client request extern char *method_str, // "GET" or "POST" *uri, // "/index.html" things before '?' - *qs, // "a=1&b=2" things after '?' + *querystr, // "a=1&b=2" things after '?' *prot, // "HTTP/1.1" *payload; // for POST @@ -52,10 +52,11 @@ 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) +#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) // some interesting macro for `route()` #define ROUTE_START() if (0) { From 1e15731a8e40394f1090a0b84f65cc56d7aea934 Mon Sep 17 00:00:00 2001 From: rdentato Date: Sat, 3 Apr 2021 19:23:26 +0200 Subject: [PATCH 06/10] Changed method_str to methodstr for consistency with querystr. --- httpd.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/httpd.h b/httpd.h index 55a90e9..c2c616e 100644 --- a/httpd.h +++ b/httpd.h @@ -5,7 +5,7 @@ #include // Client request -extern char *method_str, // "GET" or "POST" +extern char *methodstr, // "GET" or "POST" *uri, // "/index.html" things before '?' *querystr, // "a=1&b=2" things after '?' *prot, // "HTTP/1.1" From 847268c568c9f1511688df311c9a9d7572d46b25 Mon Sep 17 00:00:00 2001 From: rdentato Date: Tue, 6 Apr 2021 18:13:53 +0200 Subject: [PATCH 07/10] Restructuring. --- Makefile | 17 +-- httpd.c | 271 ----------------------------------------- httpd.h => src/httpd.h | 68 ++++++++--- src/httpd_priv.h | 25 ++++ src/listener.c | 103 ++++++++++++++++ src/makefile | 36 ++++++ src/request.c | 153 +++++++++++++++++++++++ main.c => src/server.c | 19 +-- 8 files changed, 385 insertions(+), 307 deletions(-) delete mode 100644 httpd.c rename httpd.h => src/httpd.h (51%) create mode 100644 src/httpd_priv.h create mode 100644 src/listener.c create mode 100644 src/makefile create mode 100644 src/request.c rename main.c => src/server.c (90%) diff --git a/Makefile b/Makefile index 2c82be0..6c9734a 100644 --- a/Makefile +++ b/Makefile @@ -1,17 +1,6 @@ -CFLAGS= -O2 -Wall -all: server +all: + cd src; make clean: - @rm -rf *.o - @rm -rf server - -server: main.o httpd.o - gcc -o server $^ - -main.o: main.c httpd.h - gcc $(CFLAGS) -c -o $*.o $*.c - -httpd.o: httpd.c httpd.h - gcc $(CFLAGS) -c -o $*.o $*.c - + cd src; make cleanall diff --git a/httpd.c b/httpd.c deleted file mode 100644 index caa715c..0000000 --- a/httpd.c +++ /dev/null @@ -1,271 +0,0 @@ -#include "httpd.h" - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -// Use (2^n-1) to speedup modulo -#define MAX_CONNECTIONS 1023 -#define BUF_SIZE 65535 - -static void start_server(const char *); -static void respond(int); - -static int listenfd; -int *clients; -static char *buf; - -static header_t reqhdr[17] = {{"\0", "\0"}}; - -// Client request -char *method_str, // "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; -int method; - -int nxt_slot(int slot) -{ - int nxt_slot = slot; - - do { - nxt_slot = (nxt_slot + 1) & MAX_CONNECTIONS; - - if (nxt_slot == slot) { // There is no slot available for a new client! - fprintf(stderr,"WARNING: no available connection\n"); - usleep(250); // Let's wait some millisecond - } - } while (clients[nxt_slot] != -1); - - return nxt_slot; -} - - -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+1), - PROT_READ | PROT_WRITE, MAP_ANONYMOUS | MAP_SHARED, -1, 0); - - // Setting all elements to -1: signifies there is no client connected - for (int i = 0; i <= MAX_CONNECTIONS; i++) - clients[i] = -1; - - start_server(PORT); - - // Ignore SIGCHLD to avoid zombie threads - signal(SIGCHLD, SIG_IGN); - - // ACCEPT connections - addrlen = sizeof(clientaddr); - while (1) { - 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]); - } - } - // Look for slot to be used for next client - slot = nxt_slot(slot); - } -} - -// 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, SOMAXCONN ) != 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 == '%') && isxdigit((int)(src[1])) && isxdigit((int)(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'; -} - -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; -} - -// 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, ("ERROR: recv() error\n")); - else if (rcvd == 0) // receive socket closed - fprintf(stderr, "INFO: Client disconnected.\n"); - else // message received - { - buf[rcvd] = '\0'; - - method_str = strtok(buf, " \t\r\n"); - method = method_code(method_str); - - uri = strtok(NULL, " \t"); - prot = strtok(NULL, " \t\r\n"); - - uri_unescape(uri); - - fprintf(stderr, "\x1b[32m + [%s] %s\x1b[0m\n", METHOD_STR(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/src/httpd.h similarity index 51% rename from httpd.h rename to src/httpd.h index c2c616e..5d023f4 100644 --- a/httpd.h +++ b/src/httpd.h @@ -1,7 +1,8 @@ -#ifndef _HTTPD_H___ -#define _HTTPD_H___ +#ifndef HTTPD_H___ +#define HTTPD_H___ #include +#include #include // Client request @@ -14,8 +15,7 @@ extern char *methodstr, // "GET" or "POST" extern int payload_size; // Server control functions -void serve_forever(const char *PORT); - +void httpd_start(const char *PORT); char *request_header(const char *name); typedef struct { @@ -45,9 +45,39 @@ header_t *request_headers(void); extern int method; -// user shall implement this function +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_(); -void route(); // Response #define RESPONSE_PROTOCOL "HTTP/1.1" @@ -58,16 +88,26 @@ void route(); #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 ROUTE_START() if (0) { -#define ROUTE(METHOD, URI) \ +#define httpd_route() httpd_route_() { if (0) + +#define HTTPD_ROUTE(METHOD, URI) \ } \ else if ((METHOD == method) && strcmp(URI, uri) == 0 ) { -#define GET(URI) ROUTE(METHOD_GET, URI) -#define POST(URI) ROUTE(METHOD_POST, URI) -#define HEAD(URI) ROUTE(METHOD_HEAD, URI) -#define ROUTE_END() \ - } \ - else HTTP_500; + +#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; + } } From 52120ebf76c57a96d4526c634bc281b6cc9a8d3b Mon Sep 17 00:00:00 2001 From: rdentato Date: Tue, 6 Apr 2021 18:14:20 +0200 Subject: [PATCH 08/10] Utility functions. --- utl/buf.c | 125 +++++++++++ utl/buf.h | 32 +++ utl/dbg.c | 242 +++++++++++++++++++++ utl/dbg.h | 527 ++++++++++++++++++++++++++++++++++++++++++++ utl/makefile | 12 + utl/utl.h | 603 +++++++++++++++++++++++++++++++++++++++++++++++++++ utl/vrg.h | 45 ++++ 7 files changed, 1586 insertions(+) create mode 100644 utl/buf.c create mode 100644 utl/buf.h create mode 100644 utl/dbg.c create mode 100644 utl/dbg.h create mode 100644 utl/makefile create mode 100644 utl/utl.h create mode 100644 utl/vrg.h 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/utl.h b/utl/utl.h new file mode 100644 index 0000000..4433cf5 --- /dev/null +++ b/utl/utl.h @@ -0,0 +1,603 @@ +/* +** (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/* +** (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 +#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/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 From 5f618bb4cb3951f6f85f858fdb9347684275f97d Mon Sep 17 00:00:00 2001 From: rdentato Date: Tue, 6 Apr 2021 18:17:15 +0200 Subject: [PATCH 09/10] Auto generated. --- utl/utl.h | 603 ------------------------------------------------------ 1 file changed, 603 deletions(-) delete mode 100644 utl/utl.h diff --git a/utl/utl.h b/utl/utl.h deleted file mode 100644 index 4433cf5..0000000 --- a/utl/utl.h +++ /dev/null @@ -1,603 +0,0 @@ -/* -** (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/* -** (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 -#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 From 06c91d509aa32c8168ac9e07f8e4f34d63eb1995 Mon Sep 17 00:00:00 2001 From: rdentato Date: Tue, 6 Apr 2021 18:17:31 +0200 Subject: [PATCH 10/10] Added *.a. --- .gitignore | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.gitignore b/.gitignore index 6c46439..d9ed1c8 100644 --- a/.gitignore +++ b/.gitignore @@ -1,2 +1,4 @@ *.o +*.a +utl.h server