From bff6e066843e828625bbd8babf272e9d8a814bb8 Mon Sep 17 00:00:00 2001 From: Edward Emelianov Date: Wed, 20 May 2026 17:20:13 +0300 Subject: [PATCH] started weather_logger --- Daemons/Readme.md | 2 +- .../weather_database/Makefile | 0 .../{ => deprecated}/weather_database/Readme | 0 .../{ => deprecated}/weather_database/main.c | 0 .../weather_database/socket.c | 0 .../weather_database/socket.h | 0 .../{ => deprecated}/weather_database/sql.c | 0 .../{ => deprecated}/weather_database/sql.h | 0 .../weather_database/sqlite.cflags | 0 .../weather_database/sqlite.config | 0 .../weather_database/sqlite.creator | 0 .../weather_database/sqlite.cxxflags | 0 .../weather_database/sqlite.files | 0 .../weather_database/sqlite.includes | 0 Daemons/weather_logger/CMakeLists.txt | 99 +++++ Daemons/weather_logger/main.c | 126 ++++++ Daemons/weather_logger/meteologger.cflags | 1 + Daemons/weather_logger/meteologger.config | 3 + Daemons/weather_logger/meteologger.creator | 1 + Daemons/weather_logger/meteologger.cxxflags | 1 + Daemons/weather_logger/meteologger.files | 6 + Daemons/weather_logger/meteologger.includes | 0 Daemons/weather_logger/parseargs.c | 82 ++++ Daemons/weather_logger/parseargs.h | 32 ++ Daemons/weather_logger/server.c | 413 ++++++++++++++++++ Daemons/weather_logger/server.h | 28 ++ 26 files changed, 793 insertions(+), 1 deletion(-) rename Daemons/{ => deprecated}/weather_database/Makefile (100%) rename Daemons/{ => deprecated}/weather_database/Readme (100%) rename Daemons/{ => deprecated}/weather_database/main.c (100%) rename Daemons/{ => deprecated}/weather_database/socket.c (100%) rename Daemons/{ => deprecated}/weather_database/socket.h (100%) rename Daemons/{ => deprecated}/weather_database/sql.c (100%) rename Daemons/{ => deprecated}/weather_database/sql.h (100%) rename Daemons/{ => deprecated}/weather_database/sqlite.cflags (100%) rename Daemons/{ => deprecated}/weather_database/sqlite.config (100%) rename Daemons/{ => deprecated}/weather_database/sqlite.creator (100%) rename Daemons/{ => deprecated}/weather_database/sqlite.cxxflags (100%) rename Daemons/{ => deprecated}/weather_database/sqlite.files (100%) rename Daemons/{ => deprecated}/weather_database/sqlite.includes (100%) create mode 100644 Daemons/weather_logger/CMakeLists.txt create mode 100644 Daemons/weather_logger/main.c create mode 100644 Daemons/weather_logger/meteologger.cflags create mode 100644 Daemons/weather_logger/meteologger.config create mode 100644 Daemons/weather_logger/meteologger.creator create mode 100644 Daemons/weather_logger/meteologger.cxxflags create mode 100644 Daemons/weather_logger/meteologger.files create mode 100644 Daemons/weather_logger/meteologger.includes create mode 100644 Daemons/weather_logger/parseargs.c create mode 100644 Daemons/weather_logger/parseargs.h create mode 100644 Daemons/weather_logger/server.c create mode 100644 Daemons/weather_logger/server.h diff --git a/Daemons/Readme.md b/Daemons/Readme.md index 45cfc72..ddff2ab 100644 --- a/Daemons/Readme.md +++ b/Daemons/Readme.md @@ -10,5 +10,5 @@ Different daemons & tools - *send_coordinates* - get/send coordinates to 10-micron mount through stellarium daemon (almost deprecated) - *teldaemon_astrosib* - work with Astrosib-500 scope equipment by network query - *weatherdaemon_multimeteo* - weather daemon for ALL meteo sensors on "Astro-M" complex -- *weather_database* - make database by data of almost deprecated weather daemons +- *weather_logger* - make file-based database for all meteo sensors - *weather_proxy* - daemon gathering meteo data from `weatherdaemon_multimeteo` and sharing it on localhost over SHM diff --git a/Daemons/weather_database/Makefile b/Daemons/deprecated/weather_database/Makefile similarity index 100% rename from Daemons/weather_database/Makefile rename to Daemons/deprecated/weather_database/Makefile diff --git a/Daemons/weather_database/Readme b/Daemons/deprecated/weather_database/Readme similarity index 100% rename from Daemons/weather_database/Readme rename to Daemons/deprecated/weather_database/Readme diff --git a/Daemons/weather_database/main.c b/Daemons/deprecated/weather_database/main.c similarity index 100% rename from Daemons/weather_database/main.c rename to Daemons/deprecated/weather_database/main.c diff --git a/Daemons/weather_database/socket.c b/Daemons/deprecated/weather_database/socket.c similarity index 100% rename from Daemons/weather_database/socket.c rename to Daemons/deprecated/weather_database/socket.c diff --git a/Daemons/weather_database/socket.h b/Daemons/deprecated/weather_database/socket.h similarity index 100% rename from Daemons/weather_database/socket.h rename to Daemons/deprecated/weather_database/socket.h diff --git a/Daemons/weather_database/sql.c b/Daemons/deprecated/weather_database/sql.c similarity index 100% rename from Daemons/weather_database/sql.c rename to Daemons/deprecated/weather_database/sql.c diff --git a/Daemons/weather_database/sql.h b/Daemons/deprecated/weather_database/sql.h similarity index 100% rename from Daemons/weather_database/sql.h rename to Daemons/deprecated/weather_database/sql.h diff --git a/Daemons/weather_database/sqlite.cflags b/Daemons/deprecated/weather_database/sqlite.cflags similarity index 100% rename from Daemons/weather_database/sqlite.cflags rename to Daemons/deprecated/weather_database/sqlite.cflags diff --git a/Daemons/weather_database/sqlite.config b/Daemons/deprecated/weather_database/sqlite.config similarity index 100% rename from Daemons/weather_database/sqlite.config rename to Daemons/deprecated/weather_database/sqlite.config diff --git a/Daemons/weather_database/sqlite.creator b/Daemons/deprecated/weather_database/sqlite.creator similarity index 100% rename from Daemons/weather_database/sqlite.creator rename to Daemons/deprecated/weather_database/sqlite.creator diff --git a/Daemons/weather_database/sqlite.cxxflags b/Daemons/deprecated/weather_database/sqlite.cxxflags similarity index 100% rename from Daemons/weather_database/sqlite.cxxflags rename to Daemons/deprecated/weather_database/sqlite.cxxflags diff --git a/Daemons/weather_database/sqlite.files b/Daemons/deprecated/weather_database/sqlite.files similarity index 100% rename from Daemons/weather_database/sqlite.files rename to Daemons/deprecated/weather_database/sqlite.files diff --git a/Daemons/weather_database/sqlite.includes b/Daemons/deprecated/weather_database/sqlite.includes similarity index 100% rename from Daemons/weather_database/sqlite.includes rename to Daemons/deprecated/weather_database/sqlite.includes diff --git a/Daemons/weather_logger/CMakeLists.txt b/Daemons/weather_logger/CMakeLists.txt new file mode 100644 index 0000000..15b897e --- /dev/null +++ b/Daemons/weather_logger/CMakeLists.txt @@ -0,0 +1,99 @@ +cmake_minimum_required(VERSION 4.0) +set(PROJ meteologger) +set(MINOR_VERSION "1") +set(MID_VERSION "0") +set(MAJOR_VERSION "0") +set(VERSION "${MAJOR_VERSION}.${MID_VERSION}.${MINOR_VERSION}") + +project(${PROJ} VERSION ${VERSION} LANGUAGES C) +message("${PROJ} version: ${VERSION}") + +option(DEBUG "Compile in debug mode" OFF) +# option() + +# default flags +set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -Wall -W -Wextra -fPIC") +set(CMAKE_COLOR_MAKEFILE ON) + +# here is one of two variants: all .c in directory or .c files in list +aux_source_directory(${CMAKE_CURRENT_SOURCE_DIR} SOURCES) +#list(REMOVE_ITEM SOURCES ${CMAKE_CURRENT_SOURCE_DIR}/) +#set(SOURCES list_of_c_files) + +# we can change file list +#if(NOT DEFINED something) +# set(SOURCES ${SOURCES} one_more_list) +# add_definitions(-DSOME_DEFS) +#endif() + +# cmake -DDEBUG=1 -> debugging +if(DEBUG) + set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -Og -g3 -ggdb -Werror") + add_definitions(-DEBUG) + set(CMAKE_BUILD_TYPE DEBUG) + set(CMAKE_VERBOSE_MAKEFILE "ON") +else() + set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -O3 -march=native -fdata-sections -ffunction-sections -flto=auto") + SET(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} -Wl,--gc-sections -flto=auto") + set(CMAKE_BUILD_TYPE RELEASE) +endif() + +list(APPEND CMAKE_MODULE_PATH "${CMAKE_CURRENT_LIST_DIR}") +###### pkgconfig ###### +find_package(PkgConfig REQUIRED) +pkg_check_modules(${PROJ} REQUIRED usefull_macros>=0.3.5) + +# external modules like OpenMP: +#include(FindOpenMP) +#if(OPENMP_FOUND) +# set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} ${OpenMP_C_FLAGS}") +# set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} ${OpenMP_CXX_FLAGS}") +# set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} ${OpenMP_EXE_LINKER_FLAGS}") +#endif() + +# append data from find_package: +#list(APPEND ${PROJ}_INCLUDE_DIRS ${_INCLUDE_DIR} ${_INCLUDE_DIR}) +#list(APPEND ${PROJ}_LIBRARIES ${_LIBRARY} ${_LIBRARY}) +#list(APPEND ${${PROJ}_LIBRARY_DIRS} ${_LIBRARY_DIRS}) + +# static library +#set(LIBSRC fd.c weathlib.c) +#set(LIBHEADER weathlib.h) +#add_library(${PROJLIB} STATIC ${LIBSRC}) +#set_target_properties(${PROJLIB} PROPERTIES VERSION ${VERSION}) + +# exe file +add_executable(${PROJ} ${SOURCES}) +# another exe, depending on some other files +#add_executable(test_client client.c usefull_macros.c parceargs.c) +# -I +include_directories(${${PROJ}_INCLUDE_DIRS}) +# -L +link_directories(${${PROJ}_LIBRARY_DIRS}) +# -D +add_definitions(-D_XOPEN_SOURCE=1234 -D_DEFAULT_SOURCE -D_GNU_SOURCE + -DPACKAGE_VERSION=\"${VERSION}\" -DMINOR_VERSION=\"${MINOR_VERSION}\" -DMID_VERSION=\"${MID_VERSION}\" + -DMAJOR_VERSION=\"${MAJOR_VERSION}\") + +###### pthreads ###### +#find_package(Threads REQUIRED) +#if(THREADS_HAVE_PTHREAD_ARG) +# set_property(TARGET ${PROJ} PROPERTY COMPILE_OPTIONS "-pthread") +# set_property(TARGET ${PROJ} PROPERTY INTERFACE_COMPILE_OPTIONS "-pthread") +#endif() +#if(CMAKE_THREAD_LIBS_INIT) +# list(APPEND ${PROJ}_LIBRARIES "${CMAKE_THREAD_LIBS_INIT}") +#endif() + +# target libraries +target_link_libraries(${PROJ} ${${PROJ}_LIBRARIES} ${CMAKE_DL_LIBS}) + +# install +include(GNUInstallDirs) +install(TARGETS ${PROJ} DESTINATION "bin") +# Script to be executed at installation time (kind of post-intallation script) to +# change the right accesses on the installed files +#INSTALL(SCRIPT inst.cmake) + +# subdirs +#add_subdirectory("plugins") diff --git a/Daemons/weather_logger/main.c b/Daemons/weather_logger/main.c new file mode 100644 index 0000000..42abc4e --- /dev/null +++ b/Daemons/weather_logger/main.c @@ -0,0 +1,126 @@ +/* + * This file is part of the meteologger project. + * Copyright 2026 Edward V. Emelianov . + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#include +#include +#include +#include +#include +#include +#include + +#include "parseargs.h" +#include "server.h" + +static volatile atomic_int catchsig = 0; +static pid_t childpid = 0; + +void signals(int signo){ + signal(signo, SIG_IGN); + if(catchsig == 0){ + DBG("superloop not inited"); + exit(signo); + } + if(0 == childpid){ + if(signo == SIGUSR1){ // reload logs after rotating + DBG("Got USR1 -> reinit logs"); + if(!reinit_logs()){ + stop_server(); + catchsig = signo; + }else signal(signo, signals); + }else{ // kill + DBG("Got %d -> kill", signo); + stop_server(); + catchsig = signo; // could be 0 or error code / signo + } + }else{ // throw signal to child + if(signo > 0){ + kill(childpid, signo); + LOGMSG("Send received signal %d to child", signo); + if(signo != SIGUSR1) catchsig = signo; + }else catchsig = signo; + } +} + +static void prepare_and_run(glob_pars *G){ + if(!G) return; + sl_socktype_e stype = (G->isunix) ? SOCKT_UNIX : SOCKT_NET; + set_reqinterval(G->req_interval); + set_nettimeout(G->net_timeout); + DBG("Run server"); + run_server(G->node, stype, G->bddir); + DBG("Server died"); +} + +int main(int argc, char **argv){ + sl_init(); + glob_pars *G = parseargs(&argc, &argv); + if(!G) return 1; + sl_check4running(NULL, G->pidfile); + signal(SIGTERM, signals); + signal(SIGINT, signals); + signal(SIGQUIT, signals); + signal(SIGPIPE, SIG_IGN); // for sockets + signal(SIGUSR1, signals); // reload DB +#ifndef EBUG + if(sl_daemonize()) ERRX("Can't daemonize"); + catchsig = INT_MAX; // now `signals` won't run exit() + while(catchsig == INT_MAX){ // guard for dead processes + childpid = fork(); + if(childpid){ + LOGDBG("create child with PID %d\n", childpid); + DBG("Created child with PID %d\n", childpid); + pid_t expid = 0; + while(catchsig == INT_MAX){ + expid = waitpid(childpid, NULL, WNOHANG); + if(expid < 0){ + LOGERR("waitpid() returns -1; exit"); + ERRX("waitpid() returns -1; exit"); + } + if(expid == childpid) break; + usleep(50000); + } + WARNX("Child %d died\n", childpid); + LOGWARN("Child %d died\n", childpid); + sleep(1); + }else{ + prctl(PR_SET_PDEATHSIG, SIGTERM); // send SIGTERM to child when parent dies + break; // go out to normal functional + } + } +#else + // init for DEBUG mode + catchsig = INT_MAX; +#endif + + if(childpid){ + LOGERR("Main process exits with status %d", catchsig); + if(G->pidfile) unlink(G->pidfile); + }else{ + pid_t self = getpid(); + prepare_and_run(G); + if(catchsig == INT_MAX) LOGERR("Child process %d died", self); + else LOGERR("Child process %d exits with status %d", self, catchsig); + ; // cleanup child here +#ifdef EBUG + if(G->pidfile) unlink(G->pidfile); // unlink PID-file in debug-mode +#endif + usleep(10000); // wait processes to die + } + return catchsig; +} diff --git a/Daemons/weather_logger/meteologger.cflags b/Daemons/weather_logger/meteologger.cflags new file mode 100644 index 0000000..a07354f --- /dev/null +++ b/Daemons/weather_logger/meteologger.cflags @@ -0,0 +1 @@ +-std=c23 \ No newline at end of file diff --git a/Daemons/weather_logger/meteologger.config b/Daemons/weather_logger/meteologger.config new file mode 100644 index 0000000..f9a541e --- /dev/null +++ b/Daemons/weather_logger/meteologger.config @@ -0,0 +1,3 @@ +#define _XOPEN_SOURCE=1234 +#define _DEFAULT_SOURCE +#define _GNU_SOURCE diff --git a/Daemons/weather_logger/meteologger.creator b/Daemons/weather_logger/meteologger.creator new file mode 100644 index 0000000..e94cbbd --- /dev/null +++ b/Daemons/weather_logger/meteologger.creator @@ -0,0 +1 @@ +[General] diff --git a/Daemons/weather_logger/meteologger.cxxflags b/Daemons/weather_logger/meteologger.cxxflags new file mode 100644 index 0000000..ea5966d --- /dev/null +++ b/Daemons/weather_logger/meteologger.cxxflags @@ -0,0 +1 @@ +-std=c++23 \ No newline at end of file diff --git a/Daemons/weather_logger/meteologger.files b/Daemons/weather_logger/meteologger.files new file mode 100644 index 0000000..441f13d --- /dev/null +++ b/Daemons/weather_logger/meteologger.files @@ -0,0 +1,6 @@ +CMakeLists.txt +main.c +parseargs.c +parseargs.h +server.c +server.h diff --git a/Daemons/weather_logger/meteologger.includes b/Daemons/weather_logger/meteologger.includes new file mode 100644 index 0000000..e69de29 diff --git a/Daemons/weather_logger/parseargs.c b/Daemons/weather_logger/parseargs.c new file mode 100644 index 0000000..b75aeac --- /dev/null +++ b/Daemons/weather_logger/parseargs.c @@ -0,0 +1,82 @@ +/* + * This file is part of the meteologger project. + * Copyright 2026 Edward V. Emelianov . + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#include +#include + +#include "parseargs.h" +#include "server.h" + +#define DEFAULT_PID "/tmp/meteologger.pid" +#define MIN_REQ_INTERVAL (0.2) +#define MAX_REQ_INTERVAL (900.) +#define MIN_NET_TMOUT (0.1) +#define MAX_NET_TMOUT (30.) + +static int help; + +static glob_pars G = { + .pidfile = DEFAULT_PID, + .req_interval = 0.5, + .net_timeout = 1., +}; + +static sl_option_t opts[] = { + {"help", NO_ARGS, NULL, 'h', arg_int, APTR(&help), "show this help"}, + {"node", NEED_ARG, NULL, 'n', arg_string, APTR(&G.node), "node to connect (host:port or UNIX socket name)"}, + {"logfile", NEED_ARG, NULL, 'l', arg_string, APTR(&G.logfile), "save logs to file"}, + {"pidfile", NEED_ARG, NULL, 'p', arg_string, APTR(&G.pidfile), "pidfile name (default: " DEFAULT_PID ")"}, + {"isunix", NO_ARGS, NULL, 'u', arg_string, APTR(&G.isunix), "use UNIX socket instead of network"}, + {"verbose", NO_ARGS, NULL, 'v', arg_none, APTR(&G.verb), "verbose level (each -v increases)"}, + {"output", NEED_ARG, NULL, 'o', arg_string, APTR(&G.bddir), "output directory to store database"}, + {"interval",NEED_ARG, NULL, 'i', arg_double, APTR(&G.req_interval), "weather request interval, s (min: 0.2, max: 900)"}, + {"timeout", NEED_ARG, NULL, 't', arg_double, APTR(&G.net_timeout),"timeout for server's answer, s (min: 0.1, max: 30)"}, + end_option +}; + +glob_pars *parseargs(int *argc, char ***argv){ + sl_parseargs(argc, argv, opts); + if(help){ + sl_showhelp(-1, opts); + return NULL; + } + if(!G.node) ERRX("Point node to correct"); + if(!G.bddir) ERRX("Need to know path to save DB"); + if(!checkDBpath(G.bddir)) ERRX("Can't create logs in %s", G.bddir); + // remove trailing '/' + int eol = strlen(G.bddir) - 1; + DBG("eol=%d", eol); + while(eol > 0){ + DBG("before: %s", G.bddir); + if(G.bddir[eol] == '/') G.bddir[eol] = 0; + else break; + DBG("after: %s", G.bddir); + --eol; + } + if(G.req_interval < MIN_REQ_INTERVAL || G.req_interval > MAX_REQ_INTERVAL) + ERRX("Wrong time interval %g, should be in [%g, %g]", G.req_interval, MIN_REQ_INTERVAL, MAX_REQ_INTERVAL); + if(G.net_timeout < MIN_NET_TMOUT || G.net_timeout > MAX_NET_TMOUT) + ERRX("Wrong network timeout %g, should be in [%g, %g]", G.net_timeout, MIN_NET_TMOUT, MAX_NET_TMOUT); + if(G.logfile){ + sl_loglevel_e lvl = LOGLEVEL_ERR + G.verb; + if(lvl >= LOGLEVEL_AMOUNT) lvl = LOGLEVEL_AMOUNT - 1; + DBG("Loglevel: %d", lvl); + if(!OPENLOG(G.logfile, lvl, 1)) ERRX("Can't open log file %s", G.logfile); + } + return &G; +} diff --git a/Daemons/weather_logger/parseargs.h b/Daemons/weather_logger/parseargs.h new file mode 100644 index 0000000..e43f096 --- /dev/null +++ b/Daemons/weather_logger/parseargs.h @@ -0,0 +1,32 @@ +/* + * This file is part of the meteologger project. + * Copyright 2026 Edward V. Emelianov . + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#pragma once + +typedef struct{ + int isunix; // use UNIX-sockets instead of net + int verb; // verbocity level + double req_interval; // requests interval + double net_timeout; // network timeout + char *bddir; // directory to store all data + char *logfile; // logfile name + char *node; // node of server + char *pidfile; // pidfile name +} glob_pars; + +glob_pars *parseargs(int *argc, char ***argv); diff --git a/Daemons/weather_logger/server.c b/Daemons/weather_logger/server.c new file mode 100644 index 0000000..b57c553 --- /dev/null +++ b/Daemons/weather_logger/server.c @@ -0,0 +1,413 @@ +/* + * This file is part of the meteologger project. + * Copyright 2026 Edward V. Emelianov . + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#include +#include +#include +#include +#include +#include +#include +#include + +#include "server.h" + +// some "standard" keys from server +static const char *key_pluginname = "PLUGIN"; +static const char *key_nvalues = "NVALUES"; + +// sensor's db filename mask: DBpath/weatherXX.log +static const char *dbfilename_mask = "%s/weather%02d.log"; +// and search regex +static const char *dbfilename_regex = "weather[[:digit:]]{2}\\.log"; + +static volatile atomic_bool isrunning = true; +static volatile atomic_bool logreinit = false; +static double req_interval = 0.5; // request interval, s +static double net_timeout = 1.; // timeout for server's answer, s +static char *DBpath = NULL; // path to storage + +typedef struct{ + int fd; // log file descriptor + int idx; // index of station for requests + int nvalues; // maximal amount of values + char path[PATH_MAX];// path to file + char *sensname; // sensor's name + char **keys; // keyword names for header and index in string + int *levels; // array of `weather level` for each keyword +} senslog_t; + +static int Nsensors = 0; // amount of sensors for logging +static senslog_t *sensors = NULL; // array of files for logging + +// commands for communication with server +typedef enum{ + CMD_LIST, + CMD_GET, + CMD_CHKLEVEL, + CMD_TIME, + CMD_AMOUNT +} req_commands; +static const char *commands[CMD_AMOUNT] = { + [CMD_LIST] = "list", + [CMD_GET] = "get", + [CMD_CHKLEVEL] = "chklevel", + [CMD_TIME] = "time", +}; + +static void delete_senskeys(senslog_t *sensor){ + if(!sensor) return; + for(int j = 0; j < sensor->nvalues; ++j) + FREE(sensor->keys[j]); + FREE(sensor->keys); + sensor->nvalues = 0; +} + +static void sensors_delete(){ + if(!sensors) return; + for(int i = 0; i < Nsensors; ++i){ + senslog_t *sensor = &sensors[i]; + delete_senskeys(sensor); + FREE(sensor->levels); + FREE(sensor->sensname); + } + FREE(sensors); +} + +// stop server process +void stop_server(){ + FNAME(); + isrunning = false; +} + +// set request interval to this value +void set_reqinterval(double dt){ + req_interval = dt; // all checking is in `parseargs.c` +} + +// set network timeout +void set_nettimeout(double dt){ + net_timeout = dt; +} + +/** + * @brief send_request - send request to server + * @param cmd - command index + * @return true if OK, false if disconnected + */ +static bool send_request(sl_sock_t *sock, const char *req){ + if(sl_sock_sendstrmessage(sock, req) < 1){ + LOGERR("Can't send request '%s'", req); + return false; + } + return true; +} + +// reinit logs at start or after rotating +// @return false if failed +bool reinit_logs(){ + FNAME(); + logreinit = true; + return true; +} + +bool checkDBpath(const char *path){ + struct stat path_stat; + if(stat(path, &path_stat)){ + WARNX("Can't stat() %s", path); + return false; // `stat` failed + } + if(!S_ISDIR(path_stat.st_mode)){ + WARNX("%s isn't a directory", path); + return false; // not a directory + } + // now check if we can write there + if(access(path, W_OK)){ + WARNX("Can't write to %s", path); + return false; + } + return true; +} + +// find if this sensor already have opened BD; +// if have, modify sensor->fd and sensor->path, open file for appending and return `true` +static bool find_old_file(senslog_t *sensor){ + if(sensor->fd > -1){ + DBG("found opened file %s with fd %d -> close", sensor->path, sensor->fd); + close(sensor->fd); + return false; // we need to open new file after logrotating + } + + regex_t regex; + + // Compile regex + if(regcomp(®ex, dbfilename_regex, REG_EXTENDED | REG_NOSUB) != 0){ + LOGERR("find_old_file(): error in regcomp(), %s", strerror(errno)); + WARNX("regcomp()"); + return false; + } + + bool ret = false; + DIR *d = opendir(DBpath); + if(d){ + struct dirent *dir; + while((dir = readdir(d))){ + // Check if filename matches regex + if(regexec(®ex, dir->d_name, 0, NULL, 0) == 0){ + DBG("Found: %s", dir->d_name); + char fname[PATH_MAX], line[BUFSIZ]; + snprintf(fname, PATH_MAX, "%s/%s", DBpath, dir->d_name); + FILE *fp = fopen(fname, "r"); + if(!fp){ + LOGERR("Cannot open %s for reading: %s", fname, strerror(errno)); + DBG("Can't open"); + continue; + } + if(fgets(line, sizeof(line), fp) == NULL){ + LOGWARN("Found empty BD file %s - WTF???", fname); + fclose(fp); + continue; + } + fclose(fp); + int len = strlen(line); + if(len > 0 && line[len-1] == '\n') line[len-1] = 0; // remove trailing newline + if(line[0] != '#' || line[1] != ' '){ // should starts from comment with station's name + LOGWARN("Found broken database file: %s", fname); + continue; + } + const char *stname = line + 2; // station name from comment + if(0 == strcmp(stname, sensor->sensname)){ // good, we found this file! + DBG("Found existant file %s -> append to it", fname); + int newfd = open(fname, O_WRONLY | O_APPEND); + if(newfd < 0){ + LOGERR("Can't open existant BD file %s for append, try to create new", fname); + continue; + } + sensor->fd = newfd; + ret = true; + break; + } + } + } + closedir(d); + }else{ + LOGERR("Can't open %s: %s", DBpath, strerror(errno)); + } + regfree(®ex); + return ret; +} + +// create new database file in `DBpath` +static bool create_db_file(senslog_t *sensor){ + FNAME(); + int num = 0; + char path[PATH_MAX]; + for(; num <= 99; ++num){ + snprintf(path, PATH_MAX, dbfilename_mask, DBpath, num); + DBG("Try to create %s", path); + if(access(path, F_OK) != 0) break; // no such file + } + if(num > 99){ + LOGERR("Can't find free filename for station '%s', all numbers from 0 to 99 are busy! WTF???", + sensor->sensname); + WARNX("No free numbers for sensors"); + return false; + } + // create and open write-only + int newfd = open(path, O_WRONLY | O_CREAT, 0644); + if(newfd < 0){ + LOGERR("Can't open file %s for station %s: %s", path, sensor->sensname, strerror(errno)); + WARNX("Can't open %s", path); + return false; + } + DBG("OK, %s opened, try to write header", path); + int len = snprintf(path, PATH_MAX, "# %s\n", sensor->sensname); + if(write(newfd, path, len) != len){ + LOGERR("Can't write sensor's name '%s' to file %s: %s", sensor->sensname, path, strerror(errno)); + WARNX("Can't write header"); + close(newfd); + return false; + } + DBG("%s now have descriptor %d: %s", sensor->sensname, newfd, path); + sensor->fd = newfd; + return true; +} + +static bool get_sensor_keys(senslog_t _U_ *sensor, sl_sock_t _U_ *sock){ + return true; +} + +// prepare BD files and fill `sensors[i].fd` fields; make header in new file or move write pointer to the end of existant +// `sensors` should be prepared already +// this function called at start and on any logs reinit +static bool prepare_files(sl_sock_t *sock){ + if(!sock || !DBpath || Nsensors < 1) return false; + for(int i = 0; i < Nsensors; ++i){ + senslog_t *sensor = &sensors[i]; + if(!get_sensor_keys(sensor, sock)) return false; + DBG("Check if there's something for sensor[%d]", i); + if(!find_old_file(sensor)){ // create new file + DBG("Nothing found, try to create new"); + if(!create_db_file(sensor)){ + DBG("Oops: can't create"); + return false; + } + } + } + DBG("All ready!"); + return true; +} + +static bool analyse_list(sl_sock_t *sock){ + FNAME(); + char str[BUFSIZ], key[SL_KEY_LEN], value[SL_VAL_LEN]; + double tlast = sl_dtime(); + while(sl_dtime() - tlast < net_timeout){ + ssize_t len = sl_sock_readline(sock, str, BUFSIZ-1); + if(len == 0) continue; + if(len < 0){ + WARNX("Seems like server disconnected"); + LOGWARN("Server disconnected?"); + sensors_delete(); + return false; + } + tlast = sl_dtime(); + DBG("Got answer: %s", str); + if(2 != sl_get_keyval(str, key, value)){ + LOGWARN("Wrong answer from meteodaemon for 'list' request: %s", str); + continue; + } + DBG("key: '%s', value: '%s'", key, value); + int idx; + // we don't need `str` now and can use it again + if(2 != sscanf(key, "%[^[][%d]", str, &idx)){ + LOGWARN("Wrong key format: '%s'", key); + continue; + } + DBG("Got key '%s' with idx=%d", str, idx); + if(Nsensors <= idx){ + int oldN = Nsensors; + Nsensors = idx + 1; + sensors = realloc(sensors, Nsensors * sizeof(senslog_t)); + if(!sensors){ + sensors_delete(); + LOGERR("analyse_list(): error in realloc()"); + WARNX("analyse_list(): error in realloc()"); + return false; + } + bzero(&sensors[oldN], sizeof(senslog_t)*(Nsensors - oldN)); + } + senslog_t *sensor = &sensors[idx]; + if(0 == strcmp(str, key_pluginname)){ // found plugin name -> fill this field + if(sensor->sensname) free(sensor->sensname); + sensor->sensname = strdup(value); + sensor->idx = idx; + sensor->fd = -1; // not inited yet + DBG("name[%d]=%s", idx, sensor->sensname); + }else if(0 == strcmp(str, key_nvalues)){ + int nvalues = atoi(value); + if(nvalues < 1){ + LOGWARN("wrong server's responce for sensor[%d] values amount: %d", idx, nvalues); + continue; + } + if(sensor->nvalues) delete_senskeys(sensor); + sensor->nvalues = nvalues; + DBG("sensor[%d] have %d values", idx, sensor->nvalues); + sensor->keys = MALLOC(char*, nvalues); // prepare empty array for keywords + sensor->levels = MALLOC(int, nvalues); + } + } + // now check all we got + if(Nsensors < 1){ + LOGWARN("Found 0 sensors in server's answer"); + WARNX("Found 0 sensors in server's answer"); + return false; + } + bool ans = true; + for(int i = 0; i < Nsensors; ++i){ + senslog_t *sensor = &sensors[i]; + DBG("Check sensor %s with values %d", sensor->sensname, sensor->nvalues); + if(sensor->nvalues < 1){ + LOGWARN("Zero keys for station %s", sensor->sensname); + ans = false; break; + } + } + if(ans) ans = prepare_files(sock); + if(ans == false) sensors_delete(); + return ans; +} + +/*for(int i = 0; i < nvalues; ++i){ + sensor->keys[i] = ; + }*/ + +// prepare DB files at start +static bool prepare_logfiles(sl_sock_t *sock, const char *path){ + FNAME(); + char buf[PATH_MAX]; + if(DBpath) return false; // already inited?? + if(!checkDBpath(path)) return false; + DBpath = strdup(path); + if(!DBpath){ + WARN("strdup()"); + return false; + } + DBG("Store files in %s; send `list` request", DBpath); + snprintf(buf, 255, "%s\n", commands[CMD_LIST]); + if(!send_request(sock, buf)){ + WARNX("Can't send inited request"); + return false; + } + // now we have an answer: 2*N strings a la "PLUGIN[i]=...\nNVALUES[i]=...\n" -> + // prepare `sensors` and try to find opened files like `sensorXX.log` + if(!analyse_list(sock)) return false; + return true; +} + +/** + * @brief run_server - run main server: send weather requests and store data + * @param node - node to connect + * @param type - socket type + * @param path - directory where to store data logs + */ +void run_server(const char *node, sl_socktype_e type, const char *path){ + if(!node || !path) return; + sl_sock_t *sock = sl_sock_run_client(type, node, BUFSIZ); + if(!sock){ + DBG("Can't connect"); + LOGERR("Can't connect to %s", node); + return; + } + if(!prepare_logfiles(sock, path)) return; + + DBG("Superloop"); + int errctr = 0; + while(isrunning){ + if(logreinit){ + if(!prepare_files(sock)) ++errctr; + else logreinit = false; + } + if(errctr > 5){ + LOGERR("Too much errors -> exit"); + break; + } + usleep(1000); + } + if(sock) sl_sock_delete(&sock); + sensors_delete(); +} diff --git a/Daemons/weather_logger/server.h b/Daemons/weather_logger/server.h new file mode 100644 index 0000000..33ec6d2 --- /dev/null +++ b/Daemons/weather_logger/server.h @@ -0,0 +1,28 @@ +/* + * This file is part of the meteologger project. + * Copyright 2026 Edward V. Emelianov . + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#pragma once + +#include + +bool checkDBpath(const char *path); +void stop_server(); +void set_reqinterval(double dt); +void set_nettimeout(double dt); +void run_server(const char *node, sl_socktype_e type, const char *path); +bool reinit_logs();