From 214680db9c7aaadead6e7d18d972662acc9542a4 Mon Sep 17 00:00:00 2001 From: "Begerad, Stefan" Date: Wed, 15 Mar 2023 17:50:14 +0100 Subject: [PATCH] feat: initial commit --- .gitignore | 1 + README.md | 3 - cfg.txt | 8 ++ file2char.c | 84 +++++++++++++++++++ file2char.h | 16 ++++ main.c | 237 ++++++++++++++++++++++++++++++++++++++++++++++++++++ makefile | 25 ++++++ pqconfo.c | 89 ++++++++++++++++++++ pqconfo.h | 21 +++++ readme.md | 21 +++++ 10 files changed, 502 insertions(+), 3 deletions(-) create mode 100644 .gitignore delete mode 100644 README.md create mode 100644 cfg.txt create mode 100644 file2char.c create mode 100644 file2char.h create mode 100644 main.c create mode 100644 makefile create mode 100644 pqconfo.c create mode 100644 pqconfo.h create mode 100644 readme.md diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..06b458e --- /dev/null +++ b/.gitignore @@ -0,0 +1 @@ +cfg-sib00_rgncycle.txt diff --git a/README.md b/README.md deleted file mode 100644 index 9f1aafc..0000000 --- a/README.md +++ /dev/null @@ -1,3 +0,0 @@ -# rgncycle-libulfius - -libulfius API for rgncycle \ No newline at end of file diff --git a/cfg.txt b/cfg.txt new file mode 100644 index 0000000..1dd7aed --- /dev/null +++ b/cfg.txt @@ -0,0 +1,8 @@ +pq = +{ +db = "database"; +host = "localhost"; +port = 5432; +secret = "secret"; +user = "user"; +} diff --git a/file2char.c b/file2char.c new file mode 100644 index 0000000..bff1b25 --- /dev/null +++ b/file2char.c @@ -0,0 +1,84 @@ +/*to use exit*/ +#include +/*to use *print**/ +#include +/*to user file2char*/ +#include "file2char.h" + +int convert_file2char(char const* path, char **buf){ + /*declarations*/ + FILE *fp; + long lSize; + + /*TODO Check path for validity!*/ + /*fopen() + *filename + *mode + *return: set errno value in error case + */ + fp=fopen(path,"r"); + + if(fp){ + fprintf(stdout,"file opened.\n"); + + /*set offset to file end*/ + /*fseek() + *stream + *offset + *origin + */ + fseek(fp,0L,SEEK_END); + if (ferror(fp)){ + printf ("Error seeking file\n"); + } + + /*get file size*/ + lSize = ftell( fp ); + fprintf(stdout,"lSize: %ld.\n",lSize); + + /*set offset to file start*/ + fseek(fp,0,SEEK_SET); + if (ferror(fp)){ + printf ("Error seeking file\n"); + } + + /*allocate memory for entire content + null termination*/ + *buf=(char *)malloc((lSize+1)*sizeof(char)); + if(!*buf){ + fclose(fp); + fputs("Error allocating memory",stderr); + exit(EXIT_FAILURE); + } + fprintf(stdout,"mem allocated.\n"); + + /*copy file into buffer*/ + /*fread() + *ptr + *size + *count + *stream + */ + if( 1!=fread(*buf,lSize,1,fp)){ + fclose(fp); + free(*buf); + fputs("Error reading entire file",stderr); + exit(EXIT_FAILURE); + } + fprintf(stdout,"file read.\n"); + + /*close file*/ + if(EOF==fclose(fp)){ + free(*buf); + exit(EXIT_FAILURE); + } + fprintf(stdout,"file closed.\n"); + + (*buf)[lSize] = '\0'; + fprintf(stdout,"mem null terminated.\n"); + }else{ + perror(path); + exit(EXIT_FAILURE); + } + /*done*/ + return 0; +} diff --git a/file2char.h b/file2char.h new file mode 100644 index 0000000..0e244a0 --- /dev/null +++ b/file2char.h @@ -0,0 +1,16 @@ +/*We can use CPP tricks to avoid parsing the same header file more than once*/ +#ifndef FILE2CHAR_H +#define FILE2CHAR_H + +/* + * convert_file2char reads the file identified by 'path' into a character buffer + * pointed at by 'buf'. + * On success, 0 is returned. + * On failure, exit(EXIT_FAILURE) is returned. + * + * WARNING: convert_file2char malloc()s memory to '*buf' which must be freed by + * the caller. + */ +int convert_file2char(char const* path, char **buf); + +#endif /* FILE2CHAR_H */ diff --git a/main.c b/main.c new file mode 100644 index 0000000..50a34a9 --- /dev/null +++ b/main.c @@ -0,0 +1,237 @@ +/*to use *print**/ +#include +/*to create API*/ +#include +/*to connect to Postgresql*/ +#include +/* to use JSON in response body*/ +#include +/*to use struct pq_confo*/ +#include "pqconfo.h" +/*to user file2char*/ +#include "file2char.h" + +#define ROUTE_HELLO "/hello" +#define ROUTE_ENTITIES "/entities" + +/** + * callback functions declaration + */ +int callback_get_hello (const struct _u_request * request, struct _u_response * response, void * user_data); + +int callback_default (const struct _u_request * request, struct _u_response * response, void * user_data); + +int callback_get_entities (const struct _u_request * request, struct _u_response * response, void * user_data); + +int main(int argc, char *argv[]) { + /*declaration*/ + struct _u_instance instance; + int frameworkRet, apiPort, apiPortRet, cx; + /*TODO How is const defined?*/ + const int pqConInfoSize = 100; + char pqConInfo[pqConInfoSize]; + int pqConfoRet; + const struct pq_confo * pqConfo; + + printf("main() Started...\n"); + if (argc != 3) { + fprintf(stderr, "Usage: ./main \n"); + return 1; + } + + printf("main() argv[1]: %s\n", argv[1]); + printf("main() argv[2]: %s\n", argv[2]); + + /*get Postgresql connection info from config*/ + /*TODO Validate argv!*/ + pqConfoRet=get_pq_confo(argv[2],&pqConfo); + if(pqConfoRet){ + fprintf(stderr, "Postgresql connection information are NOT valid\n"); + return 2; + }else{ + fprintf(stdout,"pqConfoRet: %d\n",pqConfoRet); + } + + /*create pq connection info*/ + cx = snprintf(pqConInfo,pqConInfoSize,"postgresql://%s:%s@%s:%d/%s",pqConfo->user,pqConfo->secret,pqConfo->host,pqConfo->port,pqConfo->db); + if(0>cx && cx>=pqConInfoSize){ + fprintf(stderr,"main() error creating pqConInfo\n"); + /*free Postgresql connection info*/ + pqConfoRet=free_pq_confo(&pqConfo); + if(pqConfoRet){ + fprintf(stderr, "Postgresql connection information are NOT free\n"); + return 3; + } + + return 4; + } + + /*free Postgresql connection info*/ + pqConfoRet=free_pq_confo(&pqConfo); + if(pqConfoRet){ + fprintf(stderr, "Postgresql connection information are NOT free\n"); + return 5; + } + + /*store command line argument in variable*/ + /*validate user input*/ + /*omit injection*/ + /*stream:argv*/ + apiPortRet = sscanf(argv[1], "%d", &apiPort); + /*valid user input:1 successfully filled item*/ + if (apiPortRet != 1) { + fprintf(stderr, "The argument must be an integer\n"); + return 6; + } + if (apiPort < 0) { + fprintf(stderr, "Error passing a negative port\n"); + exit(1); + } + printf("main() port: %d\n", apiPort); + + /*TODO Is mem leaked when connection to pq failes?*/ + + if (ulfius_init_instance(&instance, apiPort, NULL, NULL) != U_OK) { + printf("main() Error ulfius_init_instance, abort"); + return 7; + } + + /*connect to database*/ + //TODO This is a synchronous call! Shall we block the rest? + PGconn *conn = PQconnectdb(pqConInfo); + if (PQstatus(conn) == CONNECTION_BAD) { + fprintf(stderr, "Connection to database failed: %s\n", + PQerrorMessage(conn)); + PQfinish(conn); + return 8; + }else if(PQstatus(conn)==CONNECTION_OK){ + printf("main() connected to database\n"); + }else{ + printf("main() connection status NOT known\n"); + } + + //TODO Why? + u_map_put(instance.default_headers, "Access-Control-Allow-Origin", "*"); + + // Maximum body size sent by the client is 1 Kb + instance.max_post_body_size = 1024; + + // Endpoint list declaration + ulfius_add_endpoint_by_val(&instance, "GET", ROUTE_HELLO, NULL, 0, &callback_get_hello, NULL); + //user_data: pass pq connection information to callback + ulfius_add_endpoint_by_val(&instance, "GET", ROUTE_ENTITIES, NULL, 0, &callback_get_entities, (void *)conn); + + // default_endpoint declaration + ulfius_set_default_endpoint(&instance, &callback_default, NULL); + + // Start the framework + // Open an http connection + frameworkRet = ulfius_start_framework(&instance); + + if (frameworkRet == U_OK) { + printf("main() Start framework on port %d\n", instance.port); + /*wait*/ + while(1){ + sleep(3600); + } + } else { + printf("main() Error starting framework; frameworkRet: %d\n",frameworkRet); + } + + printf("main() End framework\n"); + + /*clean up API*/ + ulfius_stop_framework(&instance); + ulfius_clean_instance(&instance); + + /*clean up Postgresql connection*/ + PQfinish(conn); + + printf("main() Done.\n"); + + return 0; +} + +/** + * Callback function that put a "Hello World!" string in the response + */ +int callback_get_hello (const struct _u_request * request, struct _u_response * response, void * user_data) { + ulfius_set_string_body_response(response, 200, "Hello World!"); + return U_CALLBACK_CONTINUE; +} + +/** + * Default callback function called if no endpoint has a match + */ +int callback_default (const struct _u_request * request, struct _u_response * response, void * user_data) { + ulfius_set_string_body_response(response, 404, "Page not found, do what you want"); + return U_CALLBACK_CONTINUE; +} + +/** + * Callback function for route /entities + */ +int callback_get_entities (const struct _u_request * request, struct _u_response * response, void * user_data) { + /*decalarations*/ + PGconn *conn; + char *prepStmEntities; + PGresult *pqRes; + int nbRecords,nbFields; + json_t *arrayRecords, *arrayFields, *stringField; + + printf("callback_get_entities() Started...\n"); + conn = (PGconn *)user_data; + + /*define prepared statement*/ + prepStmEntities = "SELECT * FROM entities;"; + /*create prepared statement*/ + /*conn:connection*/ + /*stm:statement*/ + /*1:number of passed parameters*/ + /*NULL: server figures out parameter type*/ + /*paramValues:pointer of an array of strings containing parameters*/ + /*NULL:relevant for binary parameters*/ + /*NULL:relevant for binary parameters*/ + /*0:obtain result in text format*/ + pqRes = PQexecParams(conn, prepStmEntities, 0, NULL, NULL, NULL, NULL, 0); + if (PQresultStatus(pqRes) != PGRES_TUPLES_OK) { + printf("callback_get_entities() No data retrieved\n"); + /*clean up result*/ + PQclear(pqRes); + //TODO Why this casting? + (void)(request); + (void)(user_data); + ulfius_set_string_body_response(response, 200, "This is the /entities route: Dba error!"); + printf("callback_get_entities() Done.\n"); + return U_CALLBACK_CONTINUE; + } + + /*evaluate result*/ + nbRecords = PQntuples(pqRes); + nbFields = PQnfields(pqRes); + arrayRecords=json_array(); + for(int i=0;i +/*to use libconfig*/ +#include +/*to use struct pq_confo*/ +#include "pqconfo.h" + +/*get Postgresql connection information*/ +int get_pq_confo(const char * file,const struct pq_confo ** confo){ + /*declaration*/ + config_t cfg; + const char * db, * host, * secret, * user; + int port; + + fprintf(stdout,"get_pq_confo() Started...\n"); + /*initialise new and EMPTY config*/ + config_init(&cfg); + + /* Read the file. If there is an error, report it and exit. */ + /*TODO Validate config file name!*/ + if(! config_read_file(&cfg, file)) + { + fprintf(stderr, "get_pq_confo() %s:%d - %s\n", config_error_file(&cfg), + config_error_line(&cfg), config_error_text(&cfg)); + /*destroy config, BUT NOT the structure*/ + config_destroy(&cfg); + return 2; + } + fprintf(stdout,"get_pq_confo() Parsing config file done\n"); + + /*lookup db string*/ + if(config_lookup_string(&cfg, "pq.db", &db)){ + printf("get_pq_confo() db: %s\n", db); + }else{ + fprintf(stderr, "get_pq_confo() NO 'db' setting in config file found.\n"); + return 3; + } + + /*lookup host string*/ + if(config_lookup_string(&cfg, "pq.host", &host)){ + printf("get_pq_confo() host: %s\n", host); + }else{ + fprintf(stderr, "get_pq_confo() NO 'host' setting in config file found.\n"); + return 4; + } + + /*lookup secret string*/ + if(!config_lookup_string(&cfg, "pq.secret", &secret)){ + fprintf(stderr, "get_pq_confo() NO 'secret' setting in config file found.\n"); + return 5; + } + + /*lookup user string*/ + if(config_lookup_string(&cfg, "pq.user", &user)){ + printf("get_pq_confo() user: %s\n", user); + }else{ + fprintf(stderr, "get_pq_confo() NO 'user' setting in config file found.\n"); + return 6; + } + + /*lookup port int*/ + if(config_lookup_int(&cfg, "pq.port", &port)){ + printf("get_pq_confo() port: %d\n", port); + }else{ + fprintf(stderr, "get_pq_confo() NO 'port' setting in config file found.\n"); + return 7; + } + + /*create struct*/ + struct pq_confo * pqConfo = (struct pq_confo *)malloc( sizeof(struct pq_confo)); + sprintf(pqConfo->db,"%s",db); + sprintf(pqConfo->host,"%s",host); + pqConfo->port=port; + sprintf(pqConfo->secret,"%s",secret); + sprintf(pqConfo->user,"%s",user); + *confo = pqConfo; + + /*destroy config, BUT NOT the structure*/ + config_destroy(&cfg); + + /*return success*/ + return 0; +} + +/*free Postgresql connection information*/ +int free_pq_confo(const struct pq_confo ** confo){ + free((struct pq_confo *)*confo); + return 0; +} diff --git a/pqconfo.h b/pqconfo.h new file mode 100644 index 0000000..24eb475 --- /dev/null +++ b/pqconfo.h @@ -0,0 +1,21 @@ +/*We can use CPP tricks to avoid parsing the same header file more than once*/ +#ifndef PQCONFO_H +# define PQCONFO_H + +/*structure for Postgresql connection information*/ +struct pq_confo +{ + char db[23]; + char host[23]; + int port; + char secret[42]; + char user[23]; +}; + +/*get Postgresql connection information*/ +int get_pq_confo(const char * file,const struct pq_confo ** confo); + +/*free Postgresql connection information*/ +int free_pq_confo(const struct pq_confo ** confo); + +#endif /* PQCONFO_H */ diff --git a/readme.md b/readme.md new file mode 100644 index 0000000..fed76da --- /dev/null +++ b/readme.md @@ -0,0 +1,21 @@ +# rgncycle-libulfius + +libulfius API for rgncycle + +* build + +``` +make +``` + +* run + +``` +./main +``` + +* run Valgrind tool `Memcheck` + +``` +valgrind --leak-check=yes -s ./main +```