From d311e1def36074fcfe7378695aa2095357ddd7c4 Mon Sep 17 00:00:00 2001 From: Edward Emelianov Date: Wed, 11 Nov 2020 19:26:11 +0300 Subject: [PATCH] add pre-pre-prealpha of canserver --- canserver/CMakeLists.txt | 71 +++++++++ canserver/Readme | 1 + canserver/aux.c | 104 +++++++++++++ canserver/aux.h | 28 ++++ canserver/cmdlnopts.c | 172 ++++++++++++++++++++++ canserver/cmdlnopts.h | 78 ++++++++++ canserver/main.c | 104 +++++++++++++ canserver/socket.c | 307 +++++++++++++++++++++++++++++++++++++++ canserver/socket.h | 30 ++++ 9 files changed, 895 insertions(+) create mode 100644 canserver/CMakeLists.txt create mode 100644 canserver/Readme create mode 100644 canserver/aux.c create mode 100644 canserver/aux.h create mode 100644 canserver/cmdlnopts.c create mode 100644 canserver/cmdlnopts.h create mode 100644 canserver/main.c create mode 100644 canserver/socket.c create mode 100644 canserver/socket.h diff --git a/canserver/CMakeLists.txt b/canserver/CMakeLists.txt new file mode 100644 index 0000000..63577f1 --- /dev/null +++ b/canserver/CMakeLists.txt @@ -0,0 +1,71 @@ +cmake_minimum_required(VERSION 3.0) +set(PROJ canserver) +set(MINOR_VERSION "1") +set(MID_VERSION "0") +set(MAJOR_VERSION "0") +set(VERSION "${MAJOR_VERSION}.${MID_VERSION}.${MINOR_VERSION}") + +project(${PROJ} VERSION ${PROJ_VERSION} LANGUAGES C) +#enable_language(C) + +message("VER: ${VERSION}") + +# default flags +set(CMAKE_C_FLAGS_RELEASE "") +set(CMAKE_C_FLAGS_DEBUG "") +set(CMAKE_C_FLAGS "-O2 -std=gnu99 -D_XOPEN_SOURCE=666 -D_DEFAULT_SOURCE") + +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) + +# cmake -DDEBUG=1 -> debugging +if(DEFINED EBUG) + set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -Wextra -Wall -Werror -W") + set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wextra -Wall -Werror -W") + set(CMAKE_BUILD_TYPE DEBUG) + set(CMAKE_VERBOSE_MAKEFILE "ON") + add_definitions(-DEBUG) +else() + set(CMAKE_BUILD_TYPE RELEASE) +endif() + +###### pkgconfig ###### +# pkg-config modules (for pkg-check-modules) +set(MODULES usefull_macros libudev) + +# find packages: +find_package(PkgConfig REQUIRED) +pkg_check_modules(${PROJ} REQUIRED ${MODULES}) + +#pthreads +find_package(Threads REQUIRED) +###### additional flags ###### +list(APPEND ${PROJ}_LIBRARIES ${CMAKE_THREAD_LIBS_INIT}) + +# change wrong behaviour with install prefix +if(CMAKE_INSTALL_PREFIX_INITIALIZED_TO_DEFAULT AND CMAKE_INSTALL_PREFIX MATCHES "/usr/local") +else() + message("Change default install path to /usr/local") + set(CMAKE_INSTALL_PREFIX "/usr/local") +endif() +message("Install dir prefix: ${CMAKE_INSTALL_PREFIX}") + +# exe file +add_executable(${PROJ} ${SOURCES}) +# -I +include_directories(${${PROJ}_INCLUDE_DIRS}) +# -L +link_directories(${${PROJ}_LIBRARY_DIRS}) +# -D +add_definitions(${CFLAGS} -DLOCALEDIR=\"${LOCALEDIR}\" + -DPACKAGE_VERSION=\"${VERSION}\" -DGETTEXT_PACKAGE=\"${PROJ}\" + -DMINOR_VERSION=\"${MINOR_VERSION}\" -DMID_VERSION=\"${MID_VERSION}\" + -DMAJOR_VERSION=\"${MAJOR_VESION}\") + +# -l +target_link_libraries(${PROJ} ${${PROJ}_LIBRARIES} -lm) + +# Installation of the program +INSTALL(TARGETS ${PROJ} DESTINATION "bin") diff --git a/canserver/Readme b/canserver/Readme new file mode 100644 index 0000000..22f9331 --- /dev/null +++ b/canserver/Readme @@ -0,0 +1 @@ +here will be a simple canopen server for motors management over local sockets diff --git a/canserver/aux.c b/canserver/aux.c new file mode 100644 index 0000000..76c6822 --- /dev/null +++ b/canserver/aux.c @@ -0,0 +1,104 @@ +/* + * This file is part of the CANserver project. + * Copyright 2020 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 . + */ + +// auxiliary functions + +#include "aux.h" + +#include +#include +#include +#include +#include + +static char *VID = NULL, *PID = NULL; +// try to find serial device with given vid/pid/filepath +// @return device filepath (allocated here!) or NULL if not found +char *find_device(){ + char *path = NULL; + struct udev *udev; + struct udev_enumerate *enumerate; + struct udev_list_entry *devices, *dev_list_entry; + udev = udev_new(); + if(!udev){ + WARN("Can't create udev"); + return NULL; + } + // Create a list of the devices in the 'hidraw' subsystem. + enumerate = udev_enumerate_new(udev); + udev_enumerate_add_match_subsystem(enumerate, "tty"); + udev_enumerate_scan_devices(enumerate); + devices = udev_enumerate_get_list_entry(enumerate); + // Check out each device found + udev_list_entry_foreach(dev_list_entry, devices){ + struct udev_device *dev; + const char *lpath = udev_list_entry_get_name(dev_list_entry); + dev = udev_device_new_from_syspath(udev, lpath); + const char *devpath = udev_device_get_devnode(dev); + dev = udev_device_get_parent_with_subsystem_devtype(dev, "usb", "usb_device"); + if(!dev){ + udev_device_unref(dev); + continue; + } + //const char *pardevpath = udev_device_get_devnode(dev); + const char *vid, *pid; + vid = udev_device_get_sysattr_value(dev,"idVendor"); + pid = udev_device_get_sysattr_value(dev, "idProduct"); + DBG("Next device: VID/PID= %s/%s; path=%s; devpath=%s\n", vid, pid, lpath, devpath); + if(!devpath){ + udev_device_unref(dev); + continue; + } + int found = 0; + do{ + // 1st try to check recently found VID/PID + int good = 0; + if(VID){ if(strcmp(VID, vid)) break; else ++good;} + if(PID){ if(strcmp(PID, pid)) break; else ++good;} + if(good == 2){ + DBG("Use recently found VID/PID"); + found = 1; break; + } + // user give us VID and/or PID? Check them + if(GP->vid){ if(strcmp(GP->vid, vid)) break;} // user give another vid + if(GP->pid){ if(strcmp(GP->vid, vid)) break;} // user give another pid + // now try to check by user given device name + if(GP->device){ + if(strcmp(GP->device, devpath) == 0) found = 1; + break; + } + // still didn't found? OK, take the first comer! + DBG("The first comer"); + found = 1; + }while(0); + if(found){ + FREE(VID); FREE(PID); + VID = strdup(vid); PID = strdup(pid); + path = strdup(devpath); + DBG("Found: VID=%s, PID=%s, path=%s", VID, PID, path); + } + /*printf("\tman: %s, prod: %s\n", udev_device_get_sysattr_value(dev,"manufacturer"), + udev_device_get_sysattr_value(dev,"product")); + printf("\tparent: %s\n", pardevpath);*/ + udev_device_unref(dev); + if(found) break; + } + // Free the enumerator object + udev_enumerate_unref(enumerate); + return path; +} diff --git a/canserver/aux.h b/canserver/aux.h new file mode 100644 index 0000000..1b01999 --- /dev/null +++ b/canserver/aux.h @@ -0,0 +1,28 @@ +/* + * This file is part of the CANserver project. + * Copyright 2020 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 +#ifndef AUX_H__ +#define AUX_H__ + +#include "cmdlnopts.h" + +extern glob_pars *GP; +char *find_device(); + +#endif // AUX_H__ diff --git a/canserver/cmdlnopts.c b/canserver/cmdlnopts.c new file mode 100644 index 0000000..e97c3e3 --- /dev/null +++ b/canserver/cmdlnopts.c @@ -0,0 +1,172 @@ +/* + * This file is part of the CANserver project. + * Copyright 2020 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 "cmdlnopts.h" + +/* + * here are global parameters initialisation + */ +int help; +static glob_pars G; + +// default values for Gdefault & help +#define DEFAULT_PORT "4444" +#define DEFAULT_PIDFILE "/tmp/canserver.pid" + + +// DEFAULTS +// default global parameters +glob_pars const Gdefault = { + .device = NULL, + .port = DEFAULT_PORT, + .terminal = 0, + .echo = 0, + .logfile = NULL, + .rest_pars = NULL, + .rest_pars_num = 0 +}; + +/* + * Define command line options by filling structure: + * name has_arg flag val type argptr help +*/ +myoption cmdlnopts[] = { +// common options + {"help", NO_ARGS, NULL, 'h', arg_int, APTR(&help), _("show this help")}, + {"device", NEED_ARG, NULL, 'i', arg_string, APTR(&G.device), _("serial device name (default: none)")}, + {"port", NEED_ARG, NULL, 'p', arg_string, APTR(&G.port), _("network port to connect (default: " DEFAULT_PORT ")")}, + {"logfile", NEED_ARG, NULL, 'l', arg_string, APTR(&G.logfile), _("save logs to file (default: none)")}, + {"echo", NO_ARGS, NULL, 'e', arg_int, APTR(&G.echo), _("echo users commands back")}, + {"pidfile", NEED_ARG, NULL, 0, arg_string, APTR(&G.pidfile), _("name of PID file (default: " DEFAULT_PIDFILE ")")}, + {"verbose", NO_ARGS, NULL, 'v', arg_none, APTR(&G.verb), _("increase verbosity level of log file (each -v increased by 1)")}, + end_option +}; + +/** + * Parse command line options and return dynamically allocated structure + * to global parameters + * @param argc - copy of argc from main + * @param argv - copy of argv from main + * @return allocated structure with global parameters + */ +glob_pars *parse_args(int argc, char **argv){ + int i; + void *ptr; + ptr = memcpy(&G, &Gdefault, sizeof(G)); assert(ptr); + // format of help: "Usage: progname [args]\n" + change_helpstring("Usage: %s [args]\n\n\tWhere args are:\n"); + // parse arguments + parseargs(&argc, &argv, cmdlnopts); + if(help) showhelp(-1, cmdlnopts); + if(argc > 0){ + G.rest_pars_num = argc; + G.rest_pars = calloc(argc, sizeof(char*)); + for (i = 0; i < argc; i++) + G.rest_pars[i] = strdup(argv[i]); + } + return &G; +} + +Cl_log *globlog = NULL; + +/** + * @brief Cl_createlog - create log file: init mutex, test file open ability + * @param logpath - path to log file + * @param level - lowest message level (e.g. LOGLEVEL_ERR won't allow to write warn/msg/dbg) + * @return allocated structure (should be free'd later by Cl_deletelog) or NULL + */ +Cl_log *Cl_createlog(const char *logpath, Cl_loglevel level){ + if(level < LOGLEVEL_NONE || level > LOGLEVEL_ANY) return NULL; + if(!logpath) return NULL; + FILE *logfd = fopen(logpath, "a"); + if(!logfd){ + WARN("Can't open log file"); + return NULL; + } + fclose(logfd); + Cl_log *log = MALLOC(Cl_log, 1); + if(pthread_mutex_init(&log->mutex, NULL)){ + WARN("Can't init log mutex"); + FREE(log); + return NULL; + } + log->logpath = strdup(logpath); + if(!log->logpath){ + WARN("strdup()"); + FREE(log); + return NULL; + } + log->loglevel = level; + return log; +} + +void Cl_deletelog(Cl_log **log){ + if(!log || !*log) return; + FREE((*log)->logpath); + FREE(*log); +} + +/** + * @brief Cl_putlog - put message to log file with/without timestamp + * @param timest - ==1 to put timestamp + * @param log - pointer to log structure + * @param lvl - message loglevel (if lvl > loglevel, message won't be printed) + * @param fmt - format and the rest part of message + * @return amount of symbols saved in file + */ +int Cl_putlogt(int timest, Cl_log *log, Cl_loglevel lvl, const char *fmt, ...){ + if(!log || !log->logpath) return 0; + if(lvl > log->loglevel) return 0; + if(pthread_mutex_lock(&log->mutex)){ + WARN("Can't lock log mutex"); + return 0; + } + int i = 0; + FILE *logfd = fopen(log->logpath, "a+"); + if(!logfd) goto rtn; + if(timest){ + char strtm[128]; + time_t t = time(NULL); + struct tm *curtm = localtime(&t); + strftime(strtm, 128, "%Y/%m/%d-%H:%M:%S", curtm); + i = fprintf(logfd, "%s", strtm); + } + i += fprintf(logfd, "\t"); + va_list ar; + va_start(ar, fmt); + i += vfprintf(logfd, fmt, ar); + va_end(ar); + fseek(logfd, -1, SEEK_CUR); + char c; + ssize_t r = fread(&c, 1, 1, logfd); + if(1 == r){ // add '\n' if there was no newline + if(c != '\n') i += fprintf(logfd, "\n"); + } + fclose(logfd); +rtn: + pthread_mutex_unlock(&log->mutex); + return i; +} diff --git a/canserver/cmdlnopts.h b/canserver/cmdlnopts.h new file mode 100644 index 0000000..aba32e7 --- /dev/null +++ b/canserver/cmdlnopts.h @@ -0,0 +1,78 @@ +/* + * This file is part of the CANserver project. + * Copyright 2020 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 +#ifndef __CMDLNOPTS_H__ +#define __CMDLNOPTS_H__ + +#include +#include +#include "term.h" + +/* + * here are some typedef's for global data + */ +typedef struct{ + char *pidfile; // name of PID file (default: /tmp/canserver.pid) + char *device; // serial device name + char *vid; // vendor id + char *pid; // product id + char *port; // port to connect + char *logfile; // logfile name + int verb; // increase logfile verbosity level + int terminal; // run as terminal + int echo; // echo user commands back + int rest_pars_num; // number of rest parameters + char** rest_pars; // the rest parameters: array of char* (path to logfile and thrash) +} glob_pars; + + +glob_pars *parse_args(int argc, char **argv); + +typedef enum{ + LOGLEVEL_NONE, // no logs + LOGLEVEL_ERR, // only errors + LOGLEVEL_WARN, // only warnings and errors + LOGLEVEL_MSG, // all without debug + LOGLEVEL_DBG, // all messages + LOGLEVEL_ANY // all shit +} Cl_loglevel; + +typedef struct{ + char *logpath; // full path to logfile + Cl_loglevel loglevel; // loglevel + pthread_mutex_t mutex; // log mutex +} Cl_log; + +// logging +extern Cl_log *globlog; // global log file +Cl_log *Cl_createlog(const char *logpath, Cl_loglevel level); +#define OPENLOG(nm, lvl) (globlog = Cl_createlog(nm, lvl)) +void Cl_deletelog(Cl_log **log); +int Cl_putlogt(int timest, Cl_log *log, Cl_loglevel lvl, const char *fmt, ...); +// shortcuts for different log levels; ..ADD - add message without timestamp +#define LOGERR(...) do{Cl_putlogt(1, globlog, LOGLEVEL_ERR, __VA_ARGS__);}while(0) +#define LOGERRADD(...) do{Cl_putlogt(1, globlog, LOGLEVEL_ERR, __VA_ARGS__);}while(0) +#define LOGWARN(...) do{Cl_putlogt(1, globlog, LOGLEVEL_WARN, __VA_ARGS__);}while(0) +#define LOGWARNADD(...) do{Cl_putlogt(0, globlog, LOGLEVEL_WARN, __VA_ARGS__);}while(0) +#define LOGMSG(...) do{Cl_putlogt(1, globlog, LOGLEVEL_MSG, __VA_ARGS__);}while(0) +#define LOGMSGADD(...) do{Cl_putlogt(0, globlog, LOGLEVEL_MSG, __VA_ARGS__);}while(0) +#define LOGDBG(...) do{Cl_putlogt(1, globlog, LOGLEVEL_DBG, __VA_ARGS__);}while(0) +#define LOGDBGADD(...) do{Cl_putlogt(0, globlog, LOGLEVEL_DBG, __VA_ARGS__);}while(0) + +#endif // __CMDLNOPTS_H__ diff --git a/canserver/main.c b/canserver/main.c new file mode 100644 index 0000000..5469068 --- /dev/null +++ b/canserver/main.c @@ -0,0 +1,104 @@ +/* + * This file is part of the CANserver project. + * Copyright 2020 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 // wait +#include // prctl +#include // daemon +#include + +#include "aux.h" +#include "cmdlnopts.h" +#include "socket.h" + +glob_pars *GP; // non-static: to use in outhern functions + +void signals(int signo){ + //restore_tty(); + unlink(GP->pidfile); + LOGERR("Exit with status %d", signo); + exit(signo); +} + +void iffound_default(pid_t pid){ + LOGWARN("Another copy of this process found, pid=%d. Exit.", pid); + ERRX("Another copy of this process found, pid=%d. Exit.", pid); +} + +int main(int argc, char **argv){ +#ifndef EBUG + char *self = strdup(argv[0]); +#endif + initial_setup(); + GP = parse_args(argc, argv); + if(!GP->device && !GP->vid && !GP->pid) red("No device PID/VID/filename given, try to find firs comer!\n"); + /* if(GP->checkfile){ // just check and exit + return parse_data_file(GP->checkfile, 0); + }*/ + char *dev = find_device(); + red("dev = %s\n", dev); + if(!dev) ERRX("Serial device not found!"); + FREE(dev); + signal(SIGTERM, signals); // kill (-15) - quit + signal(SIGHUP, SIG_IGN); // hup - ignore + signal(SIGINT, signals); // ctrl+C - quit + signal(SIGQUIT, signals); // ctrl+\ - quit + signal(SIGTSTP, SIG_IGN); // ignore ctrl+Z + + if(GP->logfile){ + Cl_loglevel lvl = LOGLEVEL_ERR; + int v = GP->verb; + while(v--){ // increase loglevel + if(++lvl == LOGLEVEL_ANY) break; + } + green("Log level: %d\n", lvl); + OPENLOG(GP->logfile, lvl); + } + #ifndef EBUG + if(daemon(1, 0)){ + ERR("daemon()"); + } + check4running(self, GP->pidfile); + FREE(self); + while(1){ // guard for dead processes + pid_t childpid = fork(); + if(childpid){ + LOGDBG("create child with PID %d", childpid); + DBG("Created child with PID %d\n", childpid); + wait(NULL); + LOGDBG("child %d died", childpid); + WARNX("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 + } + } + #endif + + /* + * INSERT CODE HERE + * connection check & device validation + */ + //if(!G->terminal) signals(15); // there's not main controller connected to given terminal + daemonize(GP->port); + return 0; +} diff --git a/canserver/socket.c b/canserver/socket.c new file mode 100644 index 0000000..aacc497 --- /dev/null +++ b/canserver/socket.c @@ -0,0 +1,307 @@ +/* + * This file is part of the CANserver project. + * Copyright 2020 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 "aux.h" +#include "cmdlnopts.h" // glob_pars +#include "socket.h" +#include "term.h" + +#include // inet_ntop +#include // INT_xxx +#include // addrinfo +#include +#include // pthread_kill +#include +#include +#include // open +#include // syscall +#include // open +#include // daemon +#include + +#define BUFLEN (10240) +// Max amount of connections +#define BACKLOG (30) + +extern glob_pars *GP; + +/* + * Define global data buffers here + */ + +/**************** COMMON FUNCTIONS ****************/ +/** + * wait for answer from socket + * @param sock - socket fd + * @return 0 in case of error or timeout, 1 in case of socket ready + */ +static int waittoread(int sock){ + fd_set fds; + struct timeval timeout; + int rc; + timeout.tv_sec = 1; // wait not more than 1 second + timeout.tv_usec = 0; + FD_ZERO(&fds); + FD_SET(sock, &fds); + do{ + rc = select(sock+1, &fds, NULL, NULL, &timeout); + if(rc < 0){ + if(errno != EINTR){ + WARN("select()"); + LOGWARN("waittoread(): select() error"); + return 0; + } + continue; + } + break; + }while(1); + if(FD_ISSET(sock, &fds)) return 1; + return 0; +} + +/**************** SERVER FUNCTIONS ****************/ +pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER; +/** + * Send data over socket + * @param sock - socket fd + * @param textbuf - zero-trailing buffer with data to send + * @return 1 if all OK + */ +static int send_data(int sock, char *textbuf){ + ssize_t Len = strlen(textbuf); + if(Len != write(sock, textbuf, Len)){ + WARN("write()"); + LOGERR("send_data(): write() failed"); + return 0; + }else LOGDBG("send_data(): sent '%s'", textbuf); + return 1; +} + +#if 0 +// search a first word after needle without spaces +static char* stringscan(char *str, char *needle){ + char *a;//, *e; + char *end = str + strlen(str); + a = strstr(str, needle); + if(!a) return NULL; + a += strlen(needle); + while (a < end && (*a == ' ' || *a == '\r' || *a == '\t' || *a == '\r')) a++; + if(a >= end) return NULL; +// e = strchr(a, ' '); +// if(e) *e = 0; + return a; +} +#endif + +static void *handle_socket(void *asock){ + //putlog("handle_socket(): getpid: %d, pthread_self: %lu, tid: %lu",getpid(), pthread_self(), syscall(SYS_gettid)); + FNAME(); + int sock = *((int*)asock); + char buff[BUFLEN]; + ssize_t rd; + double t0 = dtime(); + /* + * INSERT CODE HERE + * change to while(1) if socket shouldn't be closed after data transmission + */ + while(dtime() - t0 < SOCKET_TIMEOUT){ + if(!waittoread(sock)){ // no data incoming + continue; + } + if(!(rd = read(sock, buff, BUFLEN-1))){ + DBG("Client closed socket"); + LOGDBG("Socket %d closed", sock); + break; + } + DBG("Got %zd bytes", rd); + if(rd < 0){ // error + LOGDBG("Close socket %d: read=%d", sock, rd); + DBG("Nothing to read from fd %d (ret: %zd)", sock, rd); + break; + } + // add trailing zero to be on the safe side + buff[rd] = 0; + // now we should check what do user want + // here we can process user data + DBG("user send: %s", buff); + LOGDBG("user send %s", buff); + if(GP->echo){ + if(!send_data(sock, buff)){ + LOGWARN("Can't send data to user, some error occured"); + } + } + pthread_mutex_lock(&mutex); + /* + * INSERT CODE HERE + * Process user commands here & send him an answer + */ + pthread_mutex_unlock(&mutex); + t0 = dtime(); + } + if(dtime() - t0 > SOCKET_TIMEOUT){ + LOGDBG("Socket %d closed on timeout", sock); + DBG("Closed on timeout"); + }else{ + LOGDBG("Socket %d closed", sock); + DBG("Socket closed"); + } + close(sock); + pthread_exit(NULL); + return NULL; +} + +// main socket server +static void *server(void *asock){ + LOGMSG("server(): getpid: %d, tid: %lu",getpid(), syscall(SYS_gettid)); + int sock = *((int*)asock); + if(listen(sock, BACKLOG) == -1){ + LOGERR("server(): listen() failed"); + WARN("listen"); + return NULL; + } + while(1){ + socklen_t size = sizeof(struct sockaddr_in); + struct sockaddr_in their_addr; + int newsock; + if(!waittoread(sock)) continue; + newsock = accept(sock, (struct sockaddr*)&their_addr, &size); + if(newsock <= 0){ + LOGERR("server(): accept() failed"); + WARN("accept()"); + continue; + } + pthread_t handler_thread; + if(pthread_create(&handler_thread, NULL, handle_socket, (void*) &newsock)){ + LOGERR("server(): pthread_create() failed"); + WARN("pthread_create()"); + }else{ + LOGDBG("server(): listen thread created"); + DBG("Thread created, detouch"); + pthread_detach(handler_thread); // don't care about thread state + } + } + LOGERR("server(): UNREACHABLE CODE REACHED!"); +} + +// data gathering & socket management +static void daemon_(int sock){ + if(sock < 0) return; + pthread_t sock_thread; + if(pthread_create(&sock_thread, NULL, server, (void*) &sock)){ + LOGERR("daemon_(): pthread_create() failed"); + ERR("pthread_create()"); + } + double tgot = 0.; + char *devname = find_device(); + if(!devname){ + LOGERR("Can't find serial device"); + ERRX("Can't find serial device"); + } + do{ + if(pthread_kill(sock_thread, 0) == ESRCH){ // died + WARNX("Sockets thread died"); + LOGERR("Sockets thread died"); + pthread_join(sock_thread, NULL); + if(pthread_create(&sock_thread, NULL, server, (void*) &sock)){ + LOGERR("daemon_(): new pthread_create() failed"); + ERR("pthread_create()"); + } + } + usleep(1000); // sleep a little or thread's won't be able to lock mutex + if(dtime() - tgot < T_INTERVAL) continue; + tgot = dtime(); + /* + * INSERT CODE HERE + * Gather data (poll_device) + */ + // copy temporary buffers to main + pthread_mutex_lock(&mutex); + int fd = open(devname, O_RDONLY); + if(fd == -1){ + WARN("open()"); + LOGWARN("Device %s is absent", devname); + FREE(devname); + double t0 = dtime(); + while(dtime() - t0 < 5.){ + if((devname = find_device())) break; + usleep(1000); + } + if(!devname){ + LOGERR("Can't open serial device, kill myself"); + ERRX("Can't open device, kill myself"); + }else LOGMSG("Change device to %s", devname); + }else close(fd); + /* + * INSERT CODE HERE + * fill global data buffers + */ + pthread_mutex_unlock(&mutex); + }while(1); + LOGERR("daemon_(): UNREACHABLE CODE REACHED!"); +} + +/** + * Run daemon service + */ +void daemonize(char *port){ + FNAME(); + int sock = -1; + struct addrinfo hints, *res, *p; + memset(&hints, 0, sizeof(hints)); + hints.ai_family = AF_INET; + hints.ai_socktype = SOCK_STREAM; + hints.ai_flags = AI_PASSIVE; + if(getaddrinfo("127.0.0.1", port, &hints, &res) != 0){ // accept only local connections + LOGERR("daemonize(): getaddrinfo() failed"); + ERR("getaddrinfo"); + } + struct sockaddr_in *ia = (struct sockaddr_in*)res->ai_addr; + char str[INET_ADDRSTRLEN]; + inet_ntop(AF_INET, &(ia->sin_addr), str, INET_ADDRSTRLEN); + // loop through all the results and bind to the first we can + for(p = res; p != NULL; p = p->ai_next){ + if((sock = socket(p->ai_family, p->ai_socktype, p->ai_protocol)) == -1){ + LOGWARN("daemonize(): socket() failed"); + WARN("socket"); + continue; + } + int reuseaddr = 1; + if(setsockopt(sock, SOL_SOCKET, SO_REUSEADDR, &reuseaddr, sizeof(int)) == -1){ + LOGERR("daemonize(): setsockopt() failed"); + ERR("setsockopt"); + } + if(bind(sock, p->ai_addr, p->ai_addrlen) == -1){ + close(sock); + LOGERR("daemonize(): bind() failed"); + WARN("bind"); + continue; + } + break; // if we get here, we have a successfull connection + } + if(p == NULL){ + LOGERR("daemonize(): failed to bind socket, exit"); + // looped off the end of the list with no successful bind + ERRX("failed to bind socket"); + } + freeaddrinfo(res); + daemon_(sock); + close(sock); + LOGERR("daemonize(): UNREACHABLE CODE REACHED!"); + signals(0); +} + diff --git a/canserver/socket.h b/canserver/socket.h new file mode 100644 index 0000000..12c7a22 --- /dev/null +++ b/canserver/socket.h @@ -0,0 +1,30 @@ +/* + * This file is part of the CANserver project. + * Copyright 2020 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 +#ifndef __SOCKET_H__ +#define __SOCKET_H__ + +// timeout for socket closing +#define SOCKET_TIMEOUT (5.0) +// time interval for data polling (seconds) +#define T_INTERVAL (10.) + +void daemonize(char *port); + +#endif // __SOCKET_H__