/* * OpenMODBUS/TCP to RS-232/485 MODBUS RTU gateway * * main.c - main module * * Copyright (c) 2002-2003, 2013, Victor Antonovich (v.antonovich@gmail.com) * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * * - Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * * - Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. * * $Id: main.c,v 1.6 2015/02/25 10:33:57 kapyar Exp $ */ #include "globals.h" #include "string.h" #include "errno.h" #include "cfg.h" #include "tty.h" #include "sock.h" #include "conn.h" #include "queue.h" #include "sig.h" #ifdef LOG # include "log.h" #endif extern char logfullname[]; int isdaemon = TRUE; void usage(char *exename); /* Server socket */ int server_sd = -1; /* TTY related data storage variable */ ttydata_t tty; /* Connections queue descriptor */ queue_t queue; static int main_is_empty(char *s) { char *p = s + strlen(s); if (strlen(s) == 0) return 1; while (p > s && isspace((unsigned char )(*--p))) { //no-op } if (p == s && isspace((unsigned char )(*p))) return 1; return 0; } #ifndef HAVE_DAEMON #include #include /* * System function daemon() replacement based on FreeBSD implementation. * Original source file CVS tag: * $FreeBSD: src/lib/libc/gen/daemon.c,v 1.3 2000/01/27 23:06:14 jasone Exp $ */ int daemon(nochdir, noclose) int nochdir, noclose; { int fd; switch (fork()) { case -1: return (-1); case 0: break; default: _exit(0); } if (setsid() == -1) return (-1); if (!nochdir) (void)chdir("/"); if (!noclose && (fd = open("/dev/null", O_RDWR, 0)) != -1) { (void)dup2(fd, STDIN_FILENO); (void)dup2(fd, STDOUT_FILENO); (void)dup2(fd, STDERR_FILENO); if (fd > 2) (void)close(fd); } return (0); } #endif void usage(char *exename) { cfg_init(); printf("%s-%s Copyright (C) 2002-2003, 2011, 2013-2023 Victor Antonovich , " "Andrew Denysenko \n\n" "Usage: %s [-h] [-d] " #ifdef LOG "[-L logfile] [-v level] " #endif "[-c cfgfile] \n" " [-p device] [-s speed] [-m mode]" #ifdef HAVE_TIOCRS485 " [-S]" #endif "\n" #ifdef TRXCTL " [-t] [-r] [-y sysfsfile] [-Y sysfsfile]\n" #endif " [-A address] [-P port] [-C maxconn] [-N retries]\n" " [-R pause] [-W wait] [-T timeout] [-b]\n\n" "Options:\n" " -h : this help\n" " -d : don't fork (non-daemonize)\n" #ifdef LOG " -L logfile : set log file name (default is %s%s, \n" " '-' for logging to STDOUT only)\n" #ifdef DEBUG " -v level : set log level (0-9, default is %d, 0 - errors only)\n" #else " -v level : set log level (0-2, default %d, 0 - errors only)\n" #endif #endif " -c cfgfile : read configuration from cfgfile\n" " -p device : set serial port device name (default is %s)\n" " -s speed : set serial port speed (default is %d)\n" " -m mode : set serial port mode (default is %s)\n" #ifdef HAVE_TIOCRS485 " -S : enable Linux RS-485 support for given serial port device\n" #endif #ifdef TRXCTL " -t : enable RTS RS-485 data direction control using RTS, active transmit\n" " -r : enable RTS RS-485 data direction control using RTS, active receive\n" " -y : enable RTS RS-485 data direction control using sysfs file, active transmit\n" " (writes '1' to sysfs file for transmit enable, '0' for transmit disable)\n" " -Y : enable RTS RS-485 data direction control using sysfs file, active receive\n" " (writes '0' to sysfs file for transmit enable, '1' for transmit disable)\n" #endif " -A address : set TCP server address to bind (default is %s)\n" " -P port : set TCP server port number (default is %d)\n" " -C maxconn : set maximum number of simultaneous TCP connections\n" " (1-%d, default is %d)\n" " -N retries : set maximum number of request retries\n" " (0-%d, default is %d, 0 disables retrying)\n" " -R pause : set pause between requests in milliseconds\n" " (1-%d, default is %lu)\n" " -W wait : set response wait time in milliseconds\n" " (1-%d, default is %lu)\n" " -T timeout : set connection timeout value in seconds\n" " (0-%d, default is %d, 0 disables timeout)\n" " -b : enable reply on broadcast" "\n", PACKAGE, VERSION, exename, #ifdef LOG LOGPATH, LOGNAME, cfg.dbglvl, #endif cfg.ttyport, cfg.ttyspeed, cfg.ttymode, cfg.serveraddr, cfg.serverport, MAX_MAXCONN, cfg.maxconn, MAX_MAXTRY, cfg.maxtry, MAX_RQSTPAUSE, cfg.rqstpause, MAX_RESPWAIT, cfg.respwait, MAX_CONNTIMEOUT, cfg.conntimeout); exit(0); } int main(int argc, char *argv[]) { int err = 0, rc, err_line; char *exename; char ttyparity; char *end; sig_init(); cfg_init(); if ((exename = strrchr(argv[0], '/')) == NULL) exename = argv[0]; else exename++; /* command line argument list parsing */ while ((rc = getopt(argc, argv, "dh" #ifdef TRXCTL "ty:Y:" #endif #ifdef HAVE_TIOCRS485 "S" #endif #ifdef LOG "v:L:" #endif "p:s:m:A:P:C:N:R:W:T:c:b")) != RC_ERR) { switch (rc) { case '?': exit(-1); case 'd': isdaemon = FALSE; break; case 'c': if ((err_line = cfg_read_file(optarg)) != 0) { if (err_line > 0) printf("%s: can't read config file %s: error at line %d: %s\n", exename, optarg, err_line, cfg_err); else printf("%s: can't read config file %s: %s\n", exename, optarg, strerror(errno)); exit(-1); } break; #ifdef TRXCTL case 't': cfg.trxcntl = TRX_RTS_1; break; case 'r': cfg.trxcntl = TRX_RTS_0; break; case 'y': cfg.trxcntl = TRX_SYSFS_1; strncpy(cfg.trxcntl_file, optarg, INTBUFSIZE); break; case 'Y': cfg.trxcntl = TRX_SYSFS_0; strncpy(cfg.trxcntl_file, optarg, INTBUFSIZE); break; #endif #ifdef HAVE_TIOCRS485 case 'S': cfg.rs485 = TRUE; break; #endif #ifdef LOG case 'v': cfg.dbglvl = (char)strtol(optarg, NULL, 0); # ifdef DEBUG if (!(isdigit(*optarg)) || cfg.dbglvl < 0 || cfg.dbglvl > 9) { /* report about invalid log level */ printf("%s: -v: invalid loglevel value" " (%s, must be 0-9)\n", exename, optarg); # else if (!(isdigit(*optarg)) || cfg.dbglvl < 0 || cfg.dbglvl > 2) { /* report about invalid log level */ printf("%s: -v: invalid loglevel value" " (%s, must be 0-2)\n", exename, optarg); # endif exit(-1); } break; case 'L': if (main_is_empty(optarg)) { /* report about invalid log file */ printf("%s: -L: missing logfile value\n", exename, optarg); exit(-1); } else if (*optarg != '/') { if (*optarg == '-') { /* logging to file disabled */ *cfg.logname = '\0'; } else { /* concatenate given log file name with default path */ strncpy(cfg.logname, LOGPATH, INTBUFSIZE); strncat(cfg.logname, optarg, INTBUFSIZE - strlen(cfg.logname)); } } else strncpy(cfg.logname, optarg, INTBUFSIZE); break; #endif case 'p': if (*optarg != '/') { /* concatenate given port name with default path to devices mountpoint */ strncpy(cfg.ttyport, "/dev/", INTBUFSIZE); strncat(cfg.ttyport, optarg, INTBUFSIZE - strlen(cfg.ttyport)); } else strncpy(cfg.ttyport, optarg, INTBUFSIZE); break; case 's': cfg.ttyspeed = strtoul(optarg, &end, 10); if (!cfg.ttyspeed || optarg == end || '\0' != *end) { printf("%s: -s: invalid serial port speed (%s)\n", exename, optarg); exit(-1); } break; case 'm': strncpy(cfg.ttymode, optarg, INTBUFSIZE); /* tty mode sanity checks */ if (strlen(cfg.ttymode) != 3) { printf("%s: -m: invalid serial port mode ('%s')\n", exename, cfg.ttymode); exit(-1); } if (cfg.ttymode[0] != '8') { printf("%s: -m: invalid serial port character size " "(%c, must be 8)\n", exename, cfg.ttymode[0]); exit(-1); } ttyparity = toupper(cfg.ttymode[1]); if (ttyparity != 'N' && ttyparity != 'E' && ttyparity != 'O') { printf("%s: -m: invalid serial port parity " "(%c, must be N, E or O)\n", exename, ttyparity); exit(-1); } if (cfg.ttymode[2] != '1' && cfg.ttymode[2] != '2') { printf("%s: -m: invalid serial port stop bits " "(%c, must be 1 or 2)\n", exename, cfg.ttymode[2]); exit(-1); } break; case 'A': strncpy(cfg.serveraddr, optarg, INTBUFSIZE); break; case 'P': cfg.serverport = strtoul(optarg, NULL, 0); break; case 'C': cfg.maxconn = strtoul(optarg, NULL, 0); if (cfg.maxconn < 1 || cfg.maxconn > MAX_MAXCONN) { /* report about invalid max conn number */ printf("%s: -C: invalid maxconn value" " (%d, must be 1-%d)\n", exename, cfg.maxconn, MAX_MAXCONN); exit(-1); } break; case 'N': cfg.maxtry = strtoul(optarg, NULL, 0); if (cfg.maxtry > MAX_MAXTRY) { /* report about invalid max try number */ printf("%s: -N: invalid maxtry value" " (%d, must be 0-%d)\n", exename, cfg.maxtry, MAX_MAXTRY); exit(-1); } break; case 'R': cfg.rqstpause = strtoul(optarg, NULL, 0); if (cfg.rqstpause < 1 || cfg.rqstpause > MAX_RQSTPAUSE) { /* report about invalid rqst pause value */ printf("%s: -R: invalid inter-request pause value" " (%lu, must be 1-%d)\n", exename, cfg.rqstpause, MAX_RQSTPAUSE); exit(-1); } break; case 'W': cfg.respwait = strtoul(optarg, NULL, 0); if (cfg.respwait < 1 || cfg.respwait > MAX_RESPWAIT) { /* report about invalid resp wait value */ printf("%s: -W: invalid response wait time value" " (%lu, must be 1-%d)\n", exename, cfg.respwait, MAX_RESPWAIT); exit(-1); } break; case 'T': cfg.conntimeout = strtoul(optarg, NULL, 0); if (cfg.conntimeout > MAX_CONNTIMEOUT) { /* report about invalid conn timeout value */ printf("%s: -T: invalid conn timeout value" " (%d, must be 1-%d)\n", exename, cfg.conntimeout, MAX_CONNTIMEOUT); exit(-1); } break; case 'b': cfg.replyonbroadcast = 1; break; case 'h': usage(exename); break; } } #ifdef LOG if (log_init(cfg.logname) != RC_OK) { printf("%s: can't open logfile '%s' (%s), exiting...\n", exename, logfullname[0] ? logfullname : "no log name was given", strerror(errno)); exit(-1); } logw(2, "%s-%s started...", PACKAGE, VERSION); #endif if (conn_init()) { #ifdef LOG err = errno; logw(2, "conn_init() failed, exiting..."); #endif exit(err); } /* go or not to daemon mode? */ if (isdaemon && (rc = daemon(TRUE, FALSE))) { #ifdef LOG logw(0, "Can't be daemonized (%s), exiting...", strerror(errno)); #endif exit(rc); } conn_loop(); err = errno; #ifdef LOG logw(2, "%s-%s exited...", PACKAGE, VERSION); #endif return (err); }