From 19c14db40bb31c0b4b9ed2e57bba7beff147aed9 Mon Sep 17 00:00:00 2001 From: Edward Emelianov Date: Tue, 10 Dec 2024 17:55:56 +0300 Subject: [PATCH] Version 0.3.1: add sockets --- CMakeLists.txt | 2 +- config.c | 13 +- examples/clientserver.c | 124 ++++- examples/ringbuffer.c | 2 +- locale/ru/LC_MESSAGES/usefull_macros.mo | Bin 4190 -> 4824 bytes locale/ru/messages.po | 43 +- locale/ru/ru.po | 44 +- ringbuffer.c | 48 +- socket.c | 582 ++++++++++++++++++++++++ usefull_macros.c | 75 +++ usefull_macros.h | 121 +++-- 11 files changed, 980 insertions(+), 74 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 31c9332..0c61f5a 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1,7 +1,7 @@ cmake_minimum_required(VERSION 3.9) set(PROJ usefull_macros) set(MINOR_VERSION "1") -set(MID_VERSION "2") +set(MID_VERSION "3") set(MAJOR_VERSION "0") set(VERSION "${MAJOR_VERSION}.${MID_VERSION}.${MINOR_VERSION}") diff --git a/config.c b/config.c index b0d13ea..74e2958 100644 --- a/config.c +++ b/config.c @@ -34,7 +34,7 @@ * also if `key` have several words only first saved, `value` can be long string with or without quotations */ int sl_get_keyval(const char *pair, char key[SL_KEY_LEN], char value[SL_VAL_LEN]){ - DBG("got pair: '%s'", pair); + //DBG("got pair: '%s'", pair); if(!pair || !*pair) return 0; // empty line char *kstart = sl_omitspaces(pair); if(!*kstart || *kstart == SL_COMMENT_CHAR) return 0; // only spaces @@ -44,13 +44,13 @@ int sl_get_keyval(const char *pair, char key[SL_KEY_LEN], char value[SL_VAL_LEN] char *cmnt = strchr(kstart, SL_COMMENT_CHAR); if(eq && cmnt && cmnt < eq) eq = NULL; // comment starting before equal sign if(eq){ do{ - DBG("got equal symbol: '%s'", eq); + //DBG("got equal symbol: '%s'", eq); char *vstart = sl_omitspaces(eq + 1); if(!*vstart) break; // empty line or only spaces after `=` char *vend = sl_omitspacesr(vstart); size_t l = SL_VAL_LEN - 1; // truncate value to given length if(l > (size_t)(vend - vstart)) l = vend - vstart; - DBG("l=%zd", l); + //DBG("l=%zd", l); strncpy(value, vstart, l); value[l] = 0; cmnt = strchr(value, SL_COMMENT_CHAR); @@ -59,18 +59,19 @@ int sl_get_keyval(const char *pair, char key[SL_KEY_LEN], char value[SL_VAL_LEN] *sl_omitspacesr(value) = 0; } if(!*value) break; - DBG("Got value: '%s'", value); + //DBG("Got value: '%s'", value); ret = 2; }while(0); - }else eq = kstart + strlen(kstart) - 1; + }else eq = kstart + strlen(kstart); for(; kend < eq && !isspace(*kend); ++kend); size_t l = SL_KEY_LEN - 1; if(l > (size_t)(kend - kstart)) l = kend - kstart; + //DBG("kend=%c, kstart=%c, l=%zd", *kend, *kstart, l); strncpy(key, kstart, l); key[l] = 0; cmnt = strchr(key, SL_COMMENT_CHAR); if(cmnt) *cmnt = 0; - DBG("Got key: '%s'", key); + //DBG("Got key: '%s'", key); return ret; } diff --git a/examples/clientserver.c b/examples/clientserver.c index cfc1941..f57defe 100644 --- a/examples/clientserver.c +++ b/examples/clientserver.c @@ -16,31 +16,149 @@ * along with this program. If not, see . */ +#include +#include +#include #include +static sl_sock_t *s = NULL; + typedef struct{ int help; int verbose; + int isserver; + int isunix; + int maxclients; char *logfile; + char *node; } parameters; -static parameters G = {0}; +static parameters G = { + .maxclients = 2, +}; static sl_option_t cmdlnopts[] = { {"help", NO_ARGS, NULL, 'h', arg_int, APTR(&G.help), "show this help"}, {"verbose", NO_ARGS, NULL, 'v', arg_none, APTR(&G.verbose), "verbose level (each -v adds 1)"}, {"logfile", NEED_ARG, NULL, 'l', arg_string, APTR(&G.logfile), "log file name"}, + {"node", NEED_ARG, NULL, 'n', arg_string, APTR(&G.node), "node \"IP\", \"name:IP\" or path (could be \"\\0path\" for anonymous UNIX-socket)"}, + {"server", NO_ARGS, NULL, 's', arg_int, APTR(&G.isserver), "create server"}, + {"unixsock", NO_ARGS, NULL, 'u', arg_int, APTR(&G.isunix), "UNIX socket instead of INET"}, + {"maxclients", NEED_ARG, NULL, 'm', arg_int, APTR(&G.maxclients),"max amount of clients connected to server (default: 2)"}, end_option }; +void signals(int sig){ + if(sig){ + signal(sig, SIG_IGN); + DBG("Get signal %d, quit.\n", sig); + } + LOGERR("Exit with status %d", sig); + sl_restore_con(); + if(s) sl_sock_delete(&s); + exit(sig); +} + +static void runclient(sl_sock_t *s){ + char buf[300]; + if(!s) return; + do{ + printf("send > "); + char *r = fgets(buf, 300, stdin); + if(r){ + DBG("try"); + if(-1 == sl_sock_sendstrmessage(s, buf)){ + WARNX("Error send"); + return; + } + DBG("OK"); + }else break; + ssize_t got = 0; + double t0 = sl_dtime(); + do{ + got = sl_sock_readline(s, buf, 299); + if(got > 0) printf("server > %s\n", buf); + }while(s && s->connected && (got > 0 || sl_dtime() - t0 < 0.3)); + if(got < 0) break; + } while(s && s->connected); + WARNX("Ctrl+D or disconnected"); +} + +// flags for standard handlers +static sl_sock_int_t iflag = {0}; +static sl_sock_double_t dflag = {0}; + +static sl_sock_hresult_e dtimeh(sl_sock_t *client, _U_ sl_sock_hitem_t *item, _U_ const char *req){ + char buf[32]; + snprintf(buf, 31, "UNIXT=%.2f\n", sl_dtime()); + sl_sock_sendstrmessage(client, buf); + return RESULT_SILENCE; +} + +static sl_sock_hresult_e show(sl_sock_t *client, _U_ sl_sock_hitem_t *item, _U_ const char *req){ + if(!G.isunix){ + if(*client->IP){ + printf("Client \"%s\" (fd=%d) ask for flags:\n", client->IP, client->fd); + }else printf("Can't get client's IP, flags:\n"); + }else printf("Socket fd=%d asks for flags:\n", client->fd); + printf("\tiflag=%" PRId64 ", dflag=%g\n", iflag.val, dflag.val); + return RESULT_OK; +} + +static void toomuch(int fd){ + const char *m = "Try later: too much clients connected\n"; + send(fd, m, sizeof(m+1), MSG_NOSIGNAL); + LOGWARN("Client fd=%d tried to connect after MAX reached", fd); +} + +static sl_sock_hitem_t handlers[] = { + {sl_sock_inthandler, "int", "set/get integer flag", (void*)&iflag}, + {sl_sock_dblhandler, "dbl", "set/get double flag", (void*)&dflag}, + {show, "show", "show current flags @ server console", NULL}, + {dtimeh, "dtime", "get server's UNIX time", NULL}, + {NULL, NULL, NULL, NULL} +}; + int main(int argc, char **argv){ sl_init(); sl_parseargs(&argc, &argv, cmdlnopts); if(G.help) sl_showhelp(-1, cmdlnopts); + if(!G.node) ERRX("Point node"); + sl_socktype_e type = (G.isunix) ? SOCKT_UNIX : SOCKT_NET; + if(G.isserver){ + sl_sock_changemaxclients(G.maxclients); + sl_sock_maxclhandler(toomuch); + s = sl_sock_run_server(type, G.node, -1, handlers); + } else { + s = sl_sock_run_client(type, G.node, -1); + } + if(!s) ERRX("Can't create socket and/or run threads"); sl_loglevel_e lvl = G.verbose + LOGLEVEL_ERR; if(lvl >= LOGLEVEL_AMOUNT) lvl = LOGLEVEL_AMOUNT - 1; if(G.logfile) OPENLOG(G.logfile, lvl, 1); - LOGMSG("hello"); - LOGERRADD("additional to err"); + LOGMSG("Started"); + signal(SIGTERM, signals); + signal(SIGINT, signals); + signal(SIGQUIT, signals); + signal(SIGTSTP, SIG_IGN); + signal(SIGHUP, signals); + if(G.isserver){ + //double t0 = sl_dtime(); + while(s && s->connected){ + if(!s->rthread){ + WARNX("Server handlers thread is dead"); + LOGERR("Server handlers thread is dead"); + break; + } + /*double tn = sl_dtime(); + if(tn - t0 > 10.){ + sl_sock_sendall((uint8_t*)"PING\n", 5); + t0 = tn; + }*/ + } + }else runclient(s); + LOGMSG("Ended"); + DBG("Close"); + sl_sock_delete(&s); return 0; } diff --git a/examples/ringbuffer.c b/examples/ringbuffer.c index d7c8900..6b615fd 100644 --- a/examples/ringbuffer.c +++ b/examples/ringbuffer.c @@ -43,7 +43,7 @@ int main(int argc, char **argv){ sl_init(); sl_parseargs(&argc, &argv, cmdlnopts); if(G.help) sl_showhelp(-1, cmdlnopts); - sl_ringbuffer *b = sl_RB_new(G.size); + sl_ringbuffer_t *b = sl_RB_new(G.size); if(!b) return 1; printf("Created ring buffer of %d bytes\n", G.size); printf("Enter lines of text to fill it or type (get) to get one line from buffer\n"); diff --git a/locale/ru/LC_MESSAGES/usefull_macros.mo b/locale/ru/LC_MESSAGES/usefull_macros.mo index 00e50e2a9f0082316aa2cc3ac8943221f61e5e76..83e41d1fcd6d017605fa56947e203c3d22213711 100644 GIT binary patch delta 1542 zcmZA0Uu+ab9Ki7@z4FI$t%AtE3d6~@Dl{1RmmEYTK@AlU+ceP!O-grJA(0-wM$xD==2eo_6z&Eq`y3nhtvDjzi0DV3t1K?(FWHsfJ@3_n8|_Z3Pa zQz(IdK}qmt<$djZrB>5lfluKUl!d&BO{z#hZY0nNlmMsjX&gsc*>5N-yNfSla)DB5 z?7+1+h?3A3sPO_e;#HKyuA{{L8zqrN3uDu`yhyosadUtdZ!L-^K93pt*KjK~JQBa( zjUDtyQ4+p|lE8i3jBD!S@$L8w{iFCSp2clw@HJe({%pbyEWXao2sg6gYq$^Z;q%x= z-Q_I2kFVkswqi{KWyDP=D|b*59l+!GHLk~2%FvAMDDemJNj!sX_+2CQmx<~hRcbrF zfLR>CSMYay4%aiwi`a>>l2Mc`xQddipXzYiVqms{9cYyJ5?!QOYidlby3?ME1GbD4s1l=U7n)#Tsmrijb#01N@0> zTe`P$Q-unvHC*zS*}x?yc^MZAv3ourrG(PtP_p&5v>W9?d0*|Znw9y24&D5Ko7X*E zzSHaGLtW^}yIoG{`^3jhd%c`j&_Pyr2fpui7rej^HE+9nT&L7l=N0$&=MFM9=(k^a ze!)FNw^xRR>OeqrzbofzFVs0N3_bsl>WF7^ys#3}b*j(ei8K0Oe>N*Y$tv3$ysNYM zAg5F5Fr_;Eu)nX5u&$$nzJiwud|epqb02yU2Hl6;|7bbRIiAJKK zXxL0f!_g<&TrqQW=FD^}KjYDHGiJu+QS_0SG}Ag7G7~(!Xn)7bo%Qe3Z2p}0Hi9Xe zS)vUSg>NRzC9WA6GG{xJW)^3OGgTU2d^>%PIfu-QnbYPQGxpFRX8qPoYARPco!n6T z^1n&7&2QSwM4yrp;gmUDP5FP>Wn?vy?S!m~iK^32XjV27eGo6gMvMz-PF23WB)K#4 jGRa;vr9YFY($C3Mqg~20Z-&V??UdeVTvcAyuxH)_J$7^- delta 949 zcmYk)%WD%+6vy$Cq)DyDc2cWRTkH5pYDLk}f{D;-Z7PT> zcY`~@f*>fUV79J`LR|>DlU9oQ2M8|2A};*?CR6ah&F5z3GIQpf^EtKdR@R&A9vYEm zwXp_6W+!nz%nvaVG3&rlY{og%xd*r#pJ5W0aWAf79Dm_<4A+_MK?iqX7w*9R+W(48 zuxa7KG-mK_aNs5C0n4c5K4JoYqs})(YYDWY9(WpceE?hWG9JQtRN_m){VFQq6+CYh zv#)GYTu8^v_TwPxMbqfw0yg1e)EB=(y>JEfqA!6Pn523w^;C`PnJs2V@D85Ar>M$) zL)}j{n2qy(n_{EP-=Grsggy8Z^?=k?vrZhwE}X?v_yh-V6^~+iTkZTM9O8ckkK+eC zi|Wfo=*4clhZTM4Yc|U4EiU38JdC%gzYd(G|b^oHZ(DJ6Y-{MX>`c1*72iQiG=;Z$AzO zs_zPYOqxZ0{!CFYKTZ3&nD-kax>-#cVZRe?MCN e9v=>Q*~TB\n" "Language-Team: LANGUAGE \n" @@ -17,31 +17,31 @@ msgstr "" "Content-Type: text/plain; charset=koi8-r\n" "Content-Transfer-Encoding: 8bit\n" -#: /home/eddy/Docs/SAO/C_diff/snippets_library/config.c:225 +#: /home/eddy/Docs/SAO/C_diff/snippets_library/config.c:226 msgid "Unsupported option type" msgstr "" -#: /home/eddy/Docs/SAO/C_diff/snippets_library/config.c:228 +#: /home/eddy/Docs/SAO/C_diff/snippets_library/config.c:229 #, c-format msgid "Wrong number format '%s'" msgstr "" -#: /home/eddy/Docs/SAO/C_diff/snippets_library/config.c:272 +#: /home/eddy/Docs/SAO/C_diff/snippets_library/config.c:273 #, c-format msgid "Can't open %s" msgstr "" -#: /home/eddy/Docs/SAO/C_diff/snippets_library/config.c:283 +#: /home/eddy/Docs/SAO/C_diff/snippets_library/config.c:284 #, c-format msgid "Wrong key: '%s'" msgstr "" -#: /home/eddy/Docs/SAO/C_diff/snippets_library/config.c:289 +#: /home/eddy/Docs/SAO/C_diff/snippets_library/config.c:290 #, c-format msgid "Key '%s' need value" msgstr "" -#: /home/eddy/Docs/SAO/C_diff/snippets_library/config.c:295 +#: /home/eddy/Docs/SAO/C_diff/snippets_library/config.c:296 #, c-format msgid "Key '%s' have no argptr!" msgstr "" @@ -105,6 +105,30 @@ msgstr "" msgid "Wrong argument \"%s\" of parameter \"%s\"" msgstr "" +#: /home/eddy/Docs/SAO/C_diff/snippets_library/socket.c:133 +msgid "Server disconnected" +msgstr "" + +#: /home/eddy/Docs/SAO/C_diff/snippets_library/socket.c:208 +msgid "Can't start server handlers thread" +msgstr "" + +#: /home/eddy/Docs/SAO/C_diff/snippets_library/socket.c:245 +msgid "Limit of connections reached" +msgstr "" + +#. buffer overflow +#: /home/eddy/Docs/SAO/C_diff/snippets_library/socket.c:319 +#, c-format +msgid "Server thread: buffer overflow from \"%s\"" +msgstr "" + +#. never reached +#: /home/eddy/Docs/SAO/C_diff/snippets_library/socket.c:381 +#, c-format +msgid "Unsupported socket type %d" +msgstr "" + #: /home/eddy/Docs/SAO/C_diff/snippets_library/term2.c:87 #, c-format msgid "Wrong USART format \"%s\"; use NPS, where N: 5..8; P: N/E/O/1/0, S: 1/2" @@ -185,6 +209,11 @@ msgid "Wrong double number format '%s'" msgstr "" #: /home/eddy/Docs/SAO/C_diff/snippets_library/usefull_macros.c:416 +#: /home/eddy/Docs/SAO/C_diff/snippets_library/usefull_macros.c:428 #, c-format msgid "Wrong integer number format '%s'" msgstr "" + +#: /home/eddy/Docs/SAO/C_diff/snippets_library/usefull_macros.c:432 +msgid "Number out of integer limits" +msgstr "" diff --git a/locale/ru/ru.po b/locale/ru/ru.po index 34b2c96..07f3beb 100644 --- a/locale/ru/ru.po +++ b/locale/ru/ru.po @@ -7,7 +7,7 @@ msgid "" msgstr "Project-Id-Version: PACKAGE VERSION\n" "Report-Msgid-Bugs-To: \n" - "POT-Creation-Date: 2024-11-15 11:38+0300\n" + "POT-Creation-Date: 2024-12-10 17:44+0300\n" "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" "Last-Translator: FULL NAME \n" "Language-Team: LANGUAGE \n" @@ -49,7 +49,7 @@ msgstr " msgid "Can't munmap" msgstr "Не могу munmap" -#: /home/eddy/Docs/SAO/C_diff/snippets_library/config.c:272 +#: /home/eddy/Docs/SAO/C_diff/snippets_library/config.c:273 #, c-format msgid "Can't open %s" msgstr "Не могу открыть %s" @@ -85,6 +85,10 @@ msgstr " msgid "Can't setup console" msgstr "Не могу настроить консоль" +#: /home/eddy/Docs/SAO/C_diff/snippets_library/socket.c:208 +msgid "Can't start server handlers thread" +msgstr "Не могу запустить поток-обработчик сервера" + #: /home/eddy/Docs/SAO/C_diff/snippets_library/usefull_macros.c:256 #, c-format msgid "Can't stat %s" @@ -103,16 +107,20 @@ msgstr " msgid "Integer out of range" msgstr "Целое вне допустимого диапазона" -#: /home/eddy/Docs/SAO/C_diff/snippets_library/config.c:295 +#: /home/eddy/Docs/SAO/C_diff/snippets_library/config.c:296 #, c-format msgid "Key '%s' have no argptr!" msgstr "У ключа '%s' нет указателя на переменную!" -#: /home/eddy/Docs/SAO/C_diff/snippets_library/config.c:289 +#: /home/eddy/Docs/SAO/C_diff/snippets_library/config.c:290 #, c-format msgid "Key '%s' need value" msgstr "Ключ '%s' требует значения" +#: /home/eddy/Docs/SAO/C_diff/snippets_library/socket.c:245 +msgid "Limit of connections reached" +msgstr "Достигнут предел соединений" + #: /home/eddy/Docs/SAO/C_diff/snippets_library/usefull_macros.c:262 msgid "Mmap error for input" msgstr "Ошибка mmap" @@ -130,14 +138,34 @@ msgstr " msgid "No filename given!" msgstr "Не задано имя файла!" +#: /home/eddy/Docs/SAO/C_diff/snippets_library/usefull_macros.c:432 +msgid "Number out of integer limits" +msgstr "Число за пределами int" + #: /home/eddy/Docs/SAO/C_diff/snippets_library/term2.c:162 msgid "Port name is missing" msgstr "Отсутствует имя порта" -#: /home/eddy/Docs/SAO/C_diff/snippets_library/config.c:225 +#: /home/eddy/Docs/SAO/C_diff/snippets_library/socket.c:133 +msgid "Server disconnected" +msgstr "Сервер отключен" + +#. buffer overflow +#: /home/eddy/Docs/SAO/C_diff/snippets_library/socket.c:319 +#, c-format +msgid "Server thread: buffer overflow from \"%s\"" +msgstr "Поток сервера: переполнение буфера от \"%s\"" + +#: /home/eddy/Docs/SAO/C_diff/snippets_library/config.c:226 msgid "Unsupported option type" msgstr "Неподдерживаемый тип опции" +#. never reached +#: /home/eddy/Docs/SAO/C_diff/snippets_library/socket.c:381 +#, c-format +msgid "Unsupported socket type %d" +msgstr "Неподдерживаемый тип сокета %d" + #: /home/eddy/Docs/SAO/C_diff/snippets_library/term2.c:87 #, c-format msgid "Wrong USART format \"%s\"; use NPS, where N: 5..8; P: N/E/O/1/0, S: " @@ -161,16 +189,17 @@ msgid "Wrong helpstring!" msgstr "Неправильный формат строки помощи" #: /home/eddy/Docs/SAO/C_diff/snippets_library/usefull_macros.c:416 +#: /home/eddy/Docs/SAO/C_diff/snippets_library/usefull_macros.c:428 #, c-format msgid "Wrong integer number format '%s'" msgstr "Неправильный формат целого: %s" -#: /home/eddy/Docs/SAO/C_diff/snippets_library/config.c:283 +#: /home/eddy/Docs/SAO/C_diff/snippets_library/config.c:284 #, c-format msgid "Wrong key: '%s'" msgstr "Неправильный ключ: %s" -#: /home/eddy/Docs/SAO/C_diff/snippets_library/config.c:228 +#: /home/eddy/Docs/SAO/C_diff/snippets_library/config.c:229 #, c-format msgid "Wrong number format '%s'" msgstr "Неправильный формат числа: %s" @@ -189,4 +218,3 @@ msgstr " #, c-format msgid "double short arguments: -%c" msgstr "дублирующийся короткий параметр: -%c" - diff --git a/ringbuffer.c b/ringbuffer.c index e3ad12d..9ba1d2f 100644 --- a/ringbuffer.c +++ b/ringbuffer.c @@ -25,8 +25,8 @@ * @param size - RB size * @return RB */ -sl_ringbuffer *sl_RB_new(size_t size){ - sl_ringbuffer *b = MALLOC(sl_ringbuffer, 1); +sl_ringbuffer_t *sl_RB_new(size_t size){ + sl_ringbuffer_t *b = MALLOC(sl_ringbuffer_t, 1); b->data = MALLOC(uint8_t, size); pthread_mutex_init(&b->busy, NULL); b->head = b->tail = 0; @@ -38,13 +38,11 @@ sl_ringbuffer *sl_RB_new(size_t size){ * @brief sl_RB_delete - free ringbuffer * @param b - buffer to free */ -void sl_RB_delete(sl_ringbuffer **b){ +void sl_RB_delete(sl_ringbuffer_t **b){ if(!b || !*b) return; - sl_ringbuffer *bptr = *b; - pthread_mutex_lock(&bptr->busy); + sl_ringbuffer_t *bptr = *b; FREE(bptr->data); *b = 0; - pthread_mutex_unlock(&bptr->busy); FREE(bptr); } @@ -53,26 +51,34 @@ void sl_RB_delete(sl_ringbuffer **b){ * @param b - buffer * @return N */ -static size_t datalen(sl_ringbuffer *b){ +static size_t datalen(sl_ringbuffer_t *b){ if(b->tail >= b->head) return (b->tail - b->head); else return (b->length - b->head + b->tail); } // datalen but with blocking of RB -size_t sl_RB_datalen(sl_ringbuffer *b){ +size_t sl_RB_datalen(sl_ringbuffer_t *b){ pthread_mutex_lock(&b->busy); size_t l = datalen(b); pthread_mutex_unlock(&b->busy); return l; } +// size of free space in buffer +size_t sl_RB_freesize(sl_ringbuffer_t *b){ + pthread_mutex_lock(&b->busy); + size_t l = b->length - datalen(b); + pthread_mutex_unlock(&b->busy); + return l; +} + /** * @brief hasbyte - check if RB have given byte * @param b - rb * @param byte - what to find * @return index of byte or -1 if not found or no data */ -static ssize_t hasbyte(sl_ringbuffer *b, uint8_t byte){ +static ssize_t hasbyte(sl_ringbuffer_t *b, uint8_t byte){ if(b->head == b->tail) return -1; // no data in buffer size_t startidx = b->head; if(b->head > b->tail){ @@ -86,19 +92,19 @@ static ssize_t hasbyte(sl_ringbuffer *b, uint8_t byte){ } // hasbyte with block -ssize_t sl_RB_hasbyte(sl_ringbuffer *b, uint8_t byte){ +ssize_t sl_RB_hasbyte(sl_ringbuffer_t *b, uint8_t byte){ pthread_mutex_lock(&b->busy); size_t idx = hasbyte(b, byte); pthread_mutex_unlock(&b->busy); return idx; } // increment head or tail -inline void incr(sl_ringbuffer *b, volatile size_t *what, size_t n){ +inline void incr(sl_ringbuffer_t *b, volatile size_t *what, size_t n){ *what += n; if(*what >= b->length) *what -= b->length; } -static size_t rbread(sl_ringbuffer *b, uint8_t *s, size_t len){ +static size_t rbread(sl_ringbuffer_t *b, uint8_t *s, size_t len){ size_t l = datalen(b); if(!l) return 0; if(l > len) l = len; @@ -122,7 +128,7 @@ static size_t rbread(sl_ringbuffer *b, uint8_t *s, size_t len){ * @param len - length of `s` * @return amount of bytes read */ -size_t sl_RB_read(sl_ringbuffer *b, uint8_t *s, size_t len){ +size_t sl_RB_read(sl_ringbuffer_t *b, uint8_t *s, size_t len){ pthread_mutex_lock(&b->busy); size_t got = rbread(b, s, len); pthread_mutex_unlock(&b->busy); @@ -137,7 +143,7 @@ size_t sl_RB_read(sl_ringbuffer *b, uint8_t *s, size_t len){ * @param len - length of `s` * @return amount of bytes read or -1 if `s` have insufficient size */ -ssize_t sl_RB_readto(sl_ringbuffer *b, uint8_t byte, uint8_t *s, size_t len){ +ssize_t sl_RB_readto(sl_ringbuffer_t *b, uint8_t byte, uint8_t *s, size_t len){ ssize_t got = 0; pthread_mutex_lock(&b->busy); ssize_t idx = hasbyte(b, byte); @@ -160,7 +166,7 @@ ret: * @return amount of characters read or -1 if buffer too small * !!! this function changes '\n' to 0 in `s`; `len` should include trailing '\0' too */ -ssize_t sl_RB_readline(sl_ringbuffer *b, char *s, size_t len){ +ssize_t sl_RB_readline(sl_ringbuffer_t *b, char *s, size_t len){ ssize_t got = 0; pthread_mutex_lock(&b->busy); ssize_t idx = hasbyte(b, '\n'); @@ -184,7 +190,7 @@ ret: * @param byte - data byte * @return FALSE if there's no place for `byte` in `b` */ -int sl_RB_putbyte(sl_ringbuffer *b, uint8_t byte){ +int sl_RB_putbyte(sl_ringbuffer_t *b, uint8_t byte){ int rtn = FALSE; pthread_mutex_lock(&b->busy); size_t s = datalen(b); @@ -204,11 +210,11 @@ ret: * @param len - data length * @return amount of bytes wrote (can be less than `len`) */ -size_t sl_RB_write(sl_ringbuffer *b, const uint8_t *str, size_t len){ +size_t sl_RB_write(sl_ringbuffer_t *b, const uint8_t *str, size_t len){ pthread_mutex_lock(&b->busy); size_t r = b->length - 1 - datalen(b); // rest length if(len > r) len = r; - if(!len){ r = 0; goto ret; } + if(!len) goto ret; size_t _1st = b->length - b->tail; if(_1st > len) _1st = len; memcpy(b->data + b->tail, str, _1st); @@ -218,14 +224,14 @@ size_t sl_RB_write(sl_ringbuffer *b, const uint8_t *str, size_t len){ incr(b, &b->tail, len); ret: pthread_mutex_unlock(&b->busy); - return r; + return len; } /** * @brief sl_RB_clearbuf - reset buffer * @param b - rb */ -void sl_RB_clearbuf(sl_ringbuffer *b){ +void sl_RB_clearbuf(sl_ringbuffer_t *b){ pthread_mutex_lock(&b->busy); b->head = 0; b->tail = 0; @@ -238,7 +244,7 @@ void sl_RB_clearbuf(sl_ringbuffer *b){ * @param s - string * @return amount of bytes written (strlen of s) or 0 */ -size_t sl_RB_writestr(sl_ringbuffer *b, char *s){ +size_t sl_RB_writestr(sl_ringbuffer_t *b, char *s){ size_t len = strlen(s); pthread_mutex_lock(&b->busy); size_t r = b->length - 1 - datalen(b); // rest length diff --git a/socket.c b/socket.c index 97b6fab..0bdcaa9 100644 --- a/socket.c +++ b/socket.c @@ -16,5 +16,587 @@ * along with this program. If not, see . */ +#include +#include +#include +#include +#include +#include +#include +#include // unix socket +#include +#include + #include "usefull_macros.h" +// max clients amount +static int maxclients = 32; +// too much clients handler; it is running for client connected with number>maxclients (before closing its fd) +static sl_sock_maxclh_t toomuchclients = NULL; + +/** + * @brief sl_sock_changemaxclients - change amount of max simultaneously connected clients + * SHOULD BE run BEFORE running of server + * @param val - maximal clients number + */ +void sl_sock_changemaxclients(int val){ + maxclients = val; +} +int sl_sock_getmaxclients(){ return maxclients; } + +// setter of "too much clients handler" +void sl_sock_maxclhandler(sl_sock_maxclh_t h){ + toomuchclients = h; +} + +// text messages for `hresult` +static const char *resmessages[RESULT_AMOUNT] = { + [RESULT_OK] = "OK\n", + [RESULT_FAIL] = "FAIL\n", + [RESULT_BADKEY] = "BADKEY\n", + [RESULT_BADVAL] = "BADVAL\n", + [RESULT_SILENCE] = "", +}; + +/** + * @brief sl_sock_hresult2str - get string message by hresult value + * @param r - handler result + * @return string with message + */ +const char *sl_sock_hresult2str(sl_sock_hresult_e r){ + if(r >= RESULT_AMOUNT) return "BADRESULT"; + return resmessages[r]; +} + +// convert UNIX socket name for unaddr; result should be free'd +static char *convunsname(const char *path){ + char *apath = MALLOC(char, 106); + if(*path == 0){ + DBG("convert name starting from 0"); + apath[0] = 0; + strncpy(apath+1, path+1, 104); + }else if(strncmp("\\0", path, 2) == 0){ + DBG("convert name starting from \\0"); + apath[0] = 0; + strncpy(apath+1, path+2, 104); + }else strncpy(apath, path, 105); + return apath; +} + +/** + * @brief sl_sock_delete - close socket and delete descriptor + * @param sock - pointer to socket descriptor + */ +void sl_sock_delete(sl_sock_t **sock){ + if(!sock || !*sock) return; + sl_sock_t *ptr = *sock; + ptr->connected = 0; + if(ptr->rthread){ + DBG("Cancel thread"); + pthread_cancel(ptr->rthread); + } + DBG("close fd=%d", ptr->fd); + if(ptr->fd > -1) close(ptr->fd); + DBG("delete ring buffer"); + sl_RB_delete(&ptr->buffer); + DBG("free addrinfo"); + if(ptr->addrinfo) freeaddrinfo(ptr->addrinfo); + DBG("free other"); + FREE(ptr->node); + FREE(ptr->service); + FREE(ptr->data); + DBG("free sock"); + FREE(*sock); +} + +/** + * @brief clientrbthread - thread to fill client's ringbuffer with incoming data + * If s->handlers is not NULL, process all incoming data HERE, you shouldn't use ringbuffer by hands! + * @param d - socket descriptor + * @return NULL + */ +static void *clientrbthread(void *d){ + char buf[512]; + sl_sock_t *s = (sl_sock_t*) d; + DBG("Start client read buffer thread"); + while(s && s->connected){ + pthread_mutex_lock(&s->mutex); + if(1 != sl_canread(s->fd)){ + pthread_mutex_unlock(&s->mutex); + usleep(1000); + continue; + } + ssize_t n = read(s->fd, buf, 511); + DBG("read %zd from %d, unlock", n, s->fd); + pthread_mutex_unlock(&s->mutex); + if(n < 1){ + WARNX(_("Server disconnected")); + goto errex; + } + ssize_t got = 0; + do{ + ssize_t written = sl_RB_write(s->buffer, (uint8_t*)buf + got, n-got); + //DBG("Put %zd to buffer, got=%zd, n=%zd", written, got, n); + if(got > n) return NULL; + if(written > 0) got += written; + }while(got != n); + //DBG("All messsages done"); + } +errex: + s->rthread = 0; + s->connected = FALSE; + return NULL; +} + +// common for server thread and `sendall` +static sl_sock_t **clients = NULL; + +/** + * @brief sl_sock_sendall - send data to all clients connected (works only for server) + * @param data - message + * @param len - its length + * @return N of sends or -1 if no server process running + */ +int sl_sock_sendall(uint8_t *data, size_t len){ + if(!clients) return -1; + int nsent = 0; + for(int i = maxclients; i > 0; --i){ + if(!clients[i]) continue; + if(clients[i]->fd < 0 || !clients[i]->connected) continue; + if((ssize_t)len == sl_sock_sendbinmessage(clients[i], data, len)) ++nsent; + } + return nsent; +} + +// parser of client's message +static sl_sock_hresult_e msgparser(sl_sock_t *client, char *str){ + char key[SL_KEY_LEN], val[SL_VAL_LEN], *valptr; + if(!str || !*str) return RESULT_BADKEY; + int N = sl_get_keyval(str, key, val); + DBG("getval=%d, key=%s, val=%s", N, key, val); + if(N == 0) return RESULT_BADKEY; + if(N == 1) valptr = NULL; + else valptr = val; + if(0 == strcmp(key, "help")){ + sl_sock_sendstrmessage(client, "\nHelp:\n"); + for(sl_sock_hitem_t *h = client->handlers; h->handler; ++h){ + if(h->help){ + sl_sock_sendstrmessage(client, h->key); + sl_sock_sendstrmessage(client, ": "); + sl_sock_sendstrmessage(client, h->help); + sl_sock_sendbyte(client, '\n'); + } + } + sl_sock_sendbyte(client, '\n'); + return RESULT_SILENCE; + } + for(sl_sock_hitem_t *h = client->handlers; h->handler; ++h){ + if(strcmp(h->key, key)) continue; + return h->handler(client, h, valptr); + } + return RESULT_BADKEY; +} + +/** + * @brief serverrbthread - thread for standard server procedure (when user give non-NULL `handlers`) + * @param d - socket descriptor + * @return NULL + */ +static void *serverthread(void _U_ *d){ + sl_sock_t *s = (sl_sock_t*) d; + if(!s || !s->handlers){ + WARNX(_("Can't start server handlers thread")); + goto errex; + } + int sockfd = s->fd; + if(listen(sockfd, maxclients) == -1){ + WARN("listen"); + goto errex; + } + DBG("Start server handlers thread"); + int nfd = 1; // only one socket @start + struct pollfd *poll_set = MALLOC(struct pollfd, maxclients+1); + clients = MALLOC(sl_sock_t*, maxclients+1); + // init default clients records + for(int i = maxclients; i > 0; --i){ + DBG("fill %dth client info", i); + clients[i] = MALLOC(sl_sock_t, 1); + sl_sock_t *c = clients[i]; + c->type = s->type; + if(s->node) c->node = strdup(s->node); + if(s->service) c->service = strdup(s->service); + c->handlers = s->handlers; + // fill addrinfo + c->addrinfo = MALLOC(struct addrinfo, 1); + c->addrinfo->ai_addr = MALLOC(struct sockaddr, 1); + } + // ZERO - listening server socket + poll_set[0].fd = sockfd; + poll_set[0].events = POLLIN; + while(s && s->connected){ + poll(poll_set, nfd, 1); + if(poll_set[0].revents & POLLIN){ // check main for accept() + struct sockaddr a; + socklen_t len = sizeof(struct sockaddr); + int client = accept(sockfd, &a, &len); + DBG("New connection, nfd=%d, len=%d", nfd, len); + LOGMSG("SERVER got connection, fd=%d", client); + if(nfd == maxclients + 1){ + WARNX(_("Limit of connections reached")); + if(toomuchclients) toomuchclients(client); + close(client); + }else{ + memset(&poll_set[nfd], 0, sizeof(struct pollfd)); + poll_set[nfd].fd = client; + poll_set[nfd].events = POLLIN; + DBG("got client[%d], fd=%d", nfd, client); + sl_sock_t *c = clients[nfd]; + c->fd = client; + DBG("memcpy"); + memcpy(c->addrinfo->ai_addr, &a, len); + DBG("set conn flag"); + c->connected = 1; + struct sockaddr_in* inaddr = (struct sockaddr_in*)&a; + if(!inet_ntop(AF_INET, &inaddr->sin_addr, c->IP, INET_ADDRSTRLEN)){ + WARN("inet_ntop()"); + *c->IP = 0; + } + DBG("got IP:%s", c->IP); + if(!c->buffer){ // allocate memory for client's ringbuffer + DBG("allocate ringbuffer"); + c->buffer = sl_RB_new(s->buffer->length); // the same size as for master + } + ++nfd; + } + } +#define SBUFSZ (SL_KEY_LEN+SL_VAL_LEN+2) + uint8_t buf[SBUFSZ]; + // scan connections + for(int fdidx = 1; fdidx < nfd; ++fdidx){ + if((poll_set[fdidx].revents & POLLIN) == 0) continue; + int fd = poll_set[fdidx].fd; + sl_sock_t *c = clients[fdidx]; + pthread_mutex_lock(&c->mutex); + int nread = sl_RB_freesize(c->buffer); + if(nread > SBUFSZ) nread = SBUFSZ; + else if(nread < 1){ + pthread_mutex_unlock(&c->mutex); + continue; + } + ssize_t got = read(fd, buf, nread); + pthread_mutex_unlock(&c->mutex); + if(got <= 0){ // client disconnected + DBG("client \"%s\" (fd=%d) disconnected", c->IP, fd); + pthread_mutex_lock(&c->mutex); + DBG("close fd %d", fd); + c->connected = 0; + close(fd); + sl_RB_clearbuf(c->buffer); + // now move all data of last client to disconnected + if(nfd > 2 && fdidx != nfd - 1){ // don't move the only or the last + DBG("lock fd=%d", clients[nfd-1]->fd); + pthread_mutex_lock(&clients[nfd-1]->mutex); + clients[fdidx] = clients[nfd-1]; + clients[nfd-1] = c; + DBG("unlock fd=%d", clients[fdidx]->fd); + pthread_mutex_unlock(&clients[fdidx]->mutex); // now fdidx is nfd-1 + poll_set[fdidx] = poll_set[nfd - 1]; + } + DBG("unlock fd=%d", c->fd); + pthread_mutex_unlock(&c->mutex); + --nfd; + --fdidx; + }else{ + sl_RB_write(c->buffer, buf, got); + } + } + // and now check all incoming buffers + for(int fdidx = 1; fdidx < nfd; ++fdidx){ + sl_sock_t *c = clients[fdidx]; + if(!c->connected) continue; + ssize_t got = sl_RB_readline(c->buffer, (char*)buf, SBUFSZ); + if(got < 0){ // buffer overflow + ERRX(_("Server thread: buffer overflow from \"%s\""), c->IP); + sl_RB_clearbuf(c->buffer); + continue; + }else if(got == 0) continue; + sl_sock_hresult_e r = msgparser(c, (char*)buf); + if(r != RESULT_SILENCE) sl_sock_sendstrmessage(c, sl_sock_hresult2str(r)); + } + } + // clear memory + FREE(poll_set); + for(int i = maxclients; i > 0; --i){ + DBG("Clear %dth client data", i); + sl_sock_t *c = clients[i]; + if(c->fd > -1) close(c->fd); + if(c->buffer) sl_RB_delete(&c->buffer); + FREE(c->addrinfo->ai_addr); + FREE(c->addrinfo); + FREE(c->node); + FREE(c->service); + FREE(c); + } + FREE(clients); +errex: + s->rthread = 0; + return NULL; +} + +/** + * @brief sl_sock_open - open socket (client or server) + * @param type - socket type + * @param path - path (for UNIX socket); for NET: ":port" for server, "server:port" for client + * @param handlers - standard handlers when read data (or NULL) + * @param bufsiz - input ring buffer size + * @param isserver - 1 for server, 0 for client + * @return socket descriptor or NULL if failed + * to create anonymous UNIX-socket you can start "path" from 0 or from string "\0" + */ +static sl_sock_t *sl_sock_open(sl_socktype_e type, const char *path, sl_sock_hitem_t *handlers, int bufsiz, int isserver){ + if(!path || type >= SOCKT_AMOUNT) return NULL; + if(bufsiz < 256) bufsiz = 256; + int sock = -1; + struct addrinfo ai = {0}, *res = &ai; + struct sockaddr_un unaddr = {0}; + char *str = NULL; + switch(type){ + case SOCKT_UNIX: + str = convunsname(path); + if(!str) return NULL; + unaddr.sun_family = AF_UNIX; + ai.ai_addr = (struct sockaddr*) &unaddr; + ai.ai_addrlen = sizeof(unaddr); + memcpy(unaddr.sun_path, str, 106); + ai.ai_family = AF_UNIX; + ai.ai_socktype = SOCK_SEQPACKET; + break; + case SOCKT_NET: + case SOCKT_NETLOCAL: + //ai.ai_socktype = SOCK_DGRAM; // = SOCK_STREAM; + ai.ai_family = AF_INET; + if(isserver) ai.ai_flags = AI_PASSIVE; + break; + default: // never reached + WARNX(_("Unsupported socket type %d"), type); + return NULL; + break; + } + sl_sock_t *s = MALLOC(sl_sock_t, 1); + s->type = type; + s->fd = -1; + s->handlers = handlers; + s->buffer = sl_RB_new(bufsiz); + if(!s->buffer) sl_sock_delete(&s); + else{ // fill node/service + if(type == SOCKT_UNIX) s->node = str; // str now is converted path + else{ + char *delim = strchr(path, ':'); + if(!delim) s->service = strdup(path); // only port + else{ + if(delim == path) s->service = strdup(path+1); + else{ + size_t l = delim - path; + s->node = MALLOC(char, l + 1); + strncpy(s->node, path, l); + s->service = strdup(delim + 1); + } + } + DBG("socket->service=%s", s->service); + } + DBG("socket->node=%s", s->node); + } + // now try to open socket + if(type != SOCKT_UNIX){ + DBG("try to get addrinfo for node '%s' and service '%s'", s->node, s->service); + char *node = s->node; + if(isserver){ + if(s->type == SOCKT_NET) node = NULL; // common net server -> node==NULL + else node = "127.0.0.1"; // localhost + } + DBG("---> node '%s', service '%s'", node, s->service); + int e = getaddrinfo(node, s->service, &ai, &res); + if(e){ + WARNX("getaddrinfo(): %s", gai_strerror(e)); + sl_sock_delete(&s); + return NULL; + } + } + for(struct addrinfo *p = res; p; p = p->ai_next){ + if((sock = socket(p->ai_family, p->ai_socktype, p->ai_protocol)) < 0) continue; + DBG("Try proto %d, type %d", p->ai_protocol, p->ai_socktype); + if(isserver){ + int reuseaddr = 1; + if(setsockopt(sock, SOL_SOCKET, SO_REUSEADDR, &reuseaddr, sizeof(int)) == -1){ + WARN("setsockopt()"); + close(sock); sock = -1; + continue; + } + if(bind(sock, p->ai_addr, p->ai_addrlen) == -1){ + WARN("bind()"); + close(sock); sock = -1; + continue; + } + int enable = 1; + if(ioctl(sock, FIONBIO, (void *)&enable) < 0){ // make socket nonblocking + WARN("Can't make socket non-blocked"); + } + }else{ + if(connect(sock, p->ai_addr, p->ai_addrlen) == -1){ + WARN("connect()"); + close(sock); sock = -1; + } + } + break; + } + if(sock < 0) sl_sock_delete(&s); + else{ + s->fd = sock; + pthread_mutex_init(&s->mutex, NULL); + DBG("s->fd=%d, node=%s, service=%s", s->fd, s->node, s->service); + int r = -1; + if(isserver){ + if(s->handlers) + r = pthread_create(&s->rthread, NULL, serverthread, (void*)s); + else r = 0; + }else{ + r = pthread_create(&s->rthread, NULL, clientrbthread, (void*)s); + } + if(r){ + WARN("pthread_create()"); + sl_sock_delete(&s); + }else{ + s->connected = TRUE; + DBG("fd=%d CONNECTED", s->fd); + } + } + return s; +} + +/** + * @brief sl_sock_run_client - try to connect to server and, socket read thread and process optional handlers + * @param type - server type + * @param path - path or address:port + * @param handlers - array with handlers + * @param bufsiz - input ring buffer size + * @return socket descriptor or NULL if failed + */ +sl_sock_t *sl_sock_run_client(sl_socktype_e type, const char *path, int bufsiz){ + sl_sock_t *s = sl_sock_open(type, path, NULL, bufsiz, 0); + return s; +} + +/** + * @brief sl_sock_run_server - run built-in server parser + * @param type - server type + * @param path - path or port + * @param handlers - array with handlers + * @param bufsiz - input ring buffer size + * @return socket descriptor or NULL if failed + */ +sl_sock_t *sl_sock_run_server(sl_socktype_e type, const char *path, int bufsiz, sl_sock_hitem_t *handlers){ + sl_sock_t *s = sl_sock_open(type, path, handlers, bufsiz, 1); + return s; +} + +/** + * @brief sl_sock_sendbinmessage - send binary data + * @param socket - socket + * @param msg - data + * @param l - data length + * @return amount of bytes sent or -1 in case of error + */ +ssize_t sl_sock_sendbinmessage(sl_sock_t *socket, const uint8_t *msg, size_t l){ + if(!msg || l < 1) return -1; + DBG("send to fd=%d message with len=%zd (%s)", socket->fd, l, msg); + while(socket && socket->connected && !sl_canwrite(socket->fd)); + if(!socket || !socket->connected) return -1; + DBG("lock"); + pthread_mutex_lock(&socket->mutex); + DBG("SEND"); + ssize_t r = send(socket->fd, msg, l, MSG_NOSIGNAL); + DBG("unlock"); + pthread_mutex_unlock(&socket->mutex); + return r; +} + +ssize_t sl_sock_sendstrmessage(sl_sock_t *socket, const char *msg){ + if(!msg) return -1; + size_t l = strlen(msg); + return sl_sock_sendbinmessage(socket, (const uint8_t*)msg, l); +} + +/** + * @brief sl_sock_sendbyte - send one byte over socket + * @param socket - socket + * @param byte - byte to send + * @return -1 in case of error, 1 if all OK + */ +ssize_t sl_sock_sendbyte(sl_sock_t *socket, uint8_t byte){ + while(socket && socket->connected && !sl_canwrite(socket->fd)); + if(!socket || !socket->connected) return -1; + DBG("lock"); + pthread_mutex_lock(&socket->mutex); + ssize_t r = send(socket->fd, &byte, 1, MSG_NOSIGNAL); + DBG("unlock"); + pthread_mutex_unlock(&socket->mutex); + return r; +} + +/** + * @brief sl_sock_readline - read string line from incoming ringbuffer + * @param sock - socket + * @param str (o) - buffer to copy + * @param len - length of str + * @return amount of bytes read + */ +ssize_t sl_sock_readline(sl_sock_t *sock, char *str, size_t len){ + if(!sock || !sock->buffer || !str || !len) return -1; + return sl_RB_readline(sock->buffer, str, len); +} + +// default handlers - setters/getters of int64, double and string +sl_sock_hresult_e sl_sock_inthandler(sl_sock_t *client, sl_sock_hitem_t *hitem, const char *str){ + char buf[128]; + sl_sock_int_t *i = (sl_sock_int_t *)hitem->data; + if(!str){ // getter + snprintf(buf, 127, "%s=%" PRId64 "\n", hitem->key, i->val); + sl_sock_sendstrmessage(client, buf); + return RESULT_SILENCE; + } + long long x; + if(!sl_str2ll(&x, str)) return RESULT_BADVAL; + if(x < INT64_MIN || x > INT64_MAX) return RESULT_BADVAL; + i->val = (int64_t)x; + i->timestamp = sl_dtime(); + return RESULT_OK; +} +sl_sock_hresult_e sl_sock_dblhandler(sl_sock_t *client, sl_sock_hitem_t *hitem, const char *str){ + char buf[128]; + sl_sock_double_t *d = (sl_sock_double_t *)hitem->data; + if(!str){ // getter + snprintf(buf, 127, "%s=%g\n", hitem->key, d->val); + sl_sock_sendstrmessage(client, buf); + return RESULT_SILENCE; + } + double dv; + if(!sl_str2d(&dv, str)) return RESULT_BADVAL; + d->val = dv; + d->timestamp = sl_dtime(); + return RESULT_OK; +} +sl_sock_hresult_e sl_sock_strhandler(sl_sock_t *client, sl_sock_hitem_t *hitem, const char *str){ + char buf[SL_VAL_LEN + SL_KEY_LEN + 3]; + sl_sock_string_t *s = (sl_sock_string_t*) hitem->data; + if(!str){ // getter + sprintf(buf, "%s=%s\n", hitem->key, s->val); + sl_sock_sendstrmessage(client, buf); + return RESULT_SILENCE; + } + int l = strlen(str); + if(l > SL_VAL_LEN - 1) return RESULT_BADVAL; + s->len = l; + s->timestamp = sl_dtime(); + memcpy(s->val, str, l); + return RESULT_OK; +} diff --git a/usefull_macros.c b/usefull_macros.c index 59691c4..9680c39 100644 --- a/usefull_macros.c +++ b/usefull_macros.c @@ -419,6 +419,81 @@ int sl_str2ll(long long *num, const char *str){ if(num) *num = res; return TRUE; } +int sl_str2i(int *num, const char *str){ + long long res; + char *endptr; + if(!str) return FALSE; + res = strtoll(str, &endptr, 0); + if(endptr == str || *str == '\0' || *endptr != '\0'){ + WARNX(_("Wrong integer number format '%s'")); + return FALSE; + } + if(res > INT_MAX || res < INT_MIN){ + WARNX(_("Number out of integer limits")); + return FALSE; + } + if(num) *num = (int)res; + return TRUE; +} + + +/** + * @brief sl_canread - run select() for given fd + * @param fd - file descriptor + * @return -1 if EINTR, 0 if none, 1 if ready to read + */ +int sl_canread(int fd){ + if(fd < 0) return -1; + fd_set fds; + struct timeval timeout; + timeout.tv_sec = 0; + timeout.tv_usec = 100; + FD_ZERO(&fds); + FD_SET(fd, &fds); + do{ + int rc = select(fd+1, &fds, NULL, NULL, &timeout); + if(rc < 0){ + if(errno != EINTR){ + WARN("select()"); + return -1; + } + continue; + } + break; + }while(1); + if(FD_ISSET(fd, &fds)) return 1; + return 0; +} + +/** + * @brief sl_canwrite - run select() for given fd + * @param fd - file descriptor + * @return -1 if EINTR, 0 if none, 1 if ready to write without blocking + */ +int sl_canwrite(int fd){ + fd_set fds; + struct timeval timeout; + timeout.tv_sec = 0; + timeout.tv_usec = 100; + FD_ZERO(&fds); + FD_SET(fd, &fds); + //DBG("try ability of written to %d", fd); + do{ + int rc = select(fd+1, NULL, &fds, NULL, &timeout); + if(rc < 0){ + DBG("-1"); + if(errno != EINTR){ + LOGWARN("select()"); + WARN("select()"); + return -1; + } + continue; + } + break; + }while(1); + if(FD_ISSET(fd, &fds)) return 1; + return 0; +} /******************************************************************************\ * Logging to file diff --git a/usefull_macros.h b/usefull_macros.h index f92ddf9..3014ca1 100644 --- a/usefull_macros.h +++ b/usefull_macros.h @@ -28,6 +28,8 @@ #endif #include // errno +#include // struct addrinfo +#include #include // alloc, free #include // pid_t #include // pid_t @@ -152,9 +154,13 @@ char *sl_omitspaces(const char *str); // omit trailing spaces char *sl_omitspacesr(const char *v); -// convert string to double with checking +// convert string to double/integer with checking int sl_str2d(double *num, const char *str); int sl_str2ll(long long *num, const char *str); +int sl_str2i(int *num, const char *str); + +int sl_canread(int fd); +int sl_canwrite(int fd); /******************************************************************************\ The original term.h @@ -389,53 +395,114 @@ typedef struct{ size_t head; // head index size_t tail; // tail index pthread_mutex_t busy; // mutex of buffer activity -} sl_ringbuffer; +} sl_ringbuffer_t; -sl_ringbuffer *sl_RB_new(size_t size); -void sl_RB_delete(sl_ringbuffer **b); -size_t sl_RB_read(sl_ringbuffer *b, uint8_t *s, size_t len); -ssize_t sl_RB_readto(sl_ringbuffer *b, uint8_t byte, uint8_t *s, size_t len); -ssize_t sl_RB_hasbyte(sl_ringbuffer *b, uint8_t byte); -int sl_RB_putbyte(sl_ringbuffer *b, uint8_t byte); -size_t sl_RB_write(sl_ringbuffer *b, const uint8_t *str, size_t len); -size_t sl_RB_datalen(sl_ringbuffer *b); -void sl_RB_clearbuf(sl_ringbuffer *b); -ssize_t sl_RB_readline(sl_ringbuffer *b, char *s, size_t len); -size_t sl_RB_writestr(sl_ringbuffer *b, char *s); +sl_ringbuffer_t *sl_RB_new(size_t size); +void sl_RB_delete(sl_ringbuffer_t **b); +size_t sl_RB_read(sl_ringbuffer_t *b, uint8_t *s, size_t len); +ssize_t sl_RB_readto(sl_ringbuffer_t *b, uint8_t byte, uint8_t *s, size_t len); +ssize_t sl_RB_hasbyte(sl_ringbuffer_t *b, uint8_t byte); +int sl_RB_putbyte(sl_ringbuffer_t *b, uint8_t byte); +size_t sl_RB_write(sl_ringbuffer_t *b, const uint8_t *str, size_t len); +size_t sl_RB_datalen(sl_ringbuffer_t *b); +size_t sl_RB_freesize(sl_ringbuffer_t *b); +void sl_RB_clearbuf(sl_ringbuffer_t *b); +ssize_t sl_RB_readline(sl_ringbuffer_t *b, char *s, size_t len); +size_t sl_RB_writestr(sl_ringbuffer_t *b, char *s); /******************************************************************************\ The original socket.h \******************************************************************************/ // handler result: what to send to client - typedef enum{ RESULT_OK, // all OK RESULT_FAIL, // failed running command RESULT_BADVAL, // bad value RESULT_BADKEY, // bad (non-existant) key RESULT_SILENCE, // send nothing to client (in case of handlers which sends data by themself) - RESULT_NUM // total amount of enum fields -} sl_hresult; + RESULT_AMOUNT // total amount of enum fields +} sl_sock_hresult_e; -typedef sl_hresult (*sl_msghandler)(int fd, const char *key, const char *val); +// data types with timestamp +typedef struct{ + double timestamp; // time of last change + double val; +} sl_sock_double_t; typedef struct{ - sl_msghandler handler; - const char *key; - const char *help; -} sl_handleritem; + double timestamp; // time of last change + int64_t val; +} sl_sock_int_t; +typedef struct{ + double timestamp; // time of last change + char val[SL_VAL_LEN]; + int len; // strlen of `val` +} sl_sock_string_t; + +struct sl_sock_hitem; +struct sl_sock; + +// socket `key` handlers +typedef sl_sock_hresult_e (*sl_sock_msghandler)(struct sl_sock *client, struct sl_sock_hitem *item, const char *val); +// handler item +typedef struct sl_sock_hitem{ + sl_sock_msghandler handler; // function-handler + const char *key; // key name + const char *help; // key help + void *data; // user data (e.g. struct key-varptr-limits) +} sl_sock_hitem_t; + +// socket type typedef enum{ SOCKT_UNIX, // UNIX socket SOCKT_NETLOCAL, // INET socket but only for localhost - SOCKT_NET // true INET socket -} sl_socktype; + SOCKT_NET, // true INET socket + SOCKT_AMOUNT // amount of types +} sl_socktype_e; -const char *sl_hresult2str(sl_hresult r); -int sl_start_socket(int isserver, int isnet, const char *path); -void sl_sendbinmessage(int fd, const uint8_t *msg, int l); -void sl_sendstrmessage(int fd, const char *msg); +// socket itself +typedef struct sl_sock{ + int fd; // file descriptor + int connected; // == TRUE if connected + sl_socktype_e type; // type + sl_ringbuffer_t *buffer; // input data buffer + char *node; // original UNIX-socket path or node name for INET (NULL - localhost client or any server) + char *service; // NULL for UNIX-socket and port for INET + struct addrinfo *addrinfo; // filled addrinfo structure + void *data; // user data + pthread_mutex_t mutex; // read/write mutex + pthread_t rthread; // reading ring buffer thread for client and main server thread for server + char IP[INET_ADDRSTRLEN]; // client's IP address + sl_sock_hitem_t *handlers; // if non-NULL, run handler's thread when opened +} sl_sock_t; +#if 0 +// connected client descriptor +typedef struct sl_sock_client{ + sl_sock_t *socket; // socket file descriptor + const char *IP; // IP address formatted string + void *res; // user data +} sl_sock_client_t; +#endif +const char *sl_sock_hresult2str(sl_sock_hresult_e r); +void sl_sock_delete(sl_sock_t **sock); +sl_sock_t *sl_sock_run_client(sl_socktype_e type, const char *path, int bufsiz); +sl_sock_t *sl_sock_run_server(sl_socktype_e type, const char *path, int bufsiz, sl_sock_hitem_t *handlers); +void sl_sock_changemaxclients(int val); +int sl_sock_getmaxclients(); +typedef void (*sl_sock_maxclh_t)(int fd); +void sl_sock_maxclhandler(sl_sock_maxclh_t h); + +ssize_t sl_sock_sendbinmessage(sl_sock_t *socket, const uint8_t *msg, size_t l); +ssize_t sl_sock_sendbyte(sl_sock_t *socket, uint8_t byte); +ssize_t sl_sock_sendstrmessage(sl_sock_t *socket, const char *msg); +ssize_t sl_sock_readline(sl_sock_t *sock, char *str, size_t len); +int sl_sock_sendall(uint8_t *data, size_t len); + +sl_sock_hresult_e sl_sock_inthandler(sl_sock_t *client, sl_sock_hitem_t *hitem, const char *str); +sl_sock_hresult_e sl_sock_dblhandler(sl_sock_t *client, sl_sock_hitem_t *hitem, const char *str); +sl_sock_hresult_e sl_sock_strhandler(sl_sock_t *client, sl_sock_hitem_t *hitem, const char *str);