From 3992e5f7d091ac098d98801ff8258e4365a567e8 Mon Sep 17 00:00:00 2001 From: hg Date: Tue, 17 Jun 2014 01:16:19 +0200 Subject: [PATCH] Added deamon mode. --- Makefile | 2 + config/log4cpp.config | 13 +++ src/GrowControl.cpp | 18 +++- src/GrowControl.h | 2 + src/IPCManager.cpp | 88 ++++++++------- src/IPCManager.h | 3 + src/config.mk | 1 + src/main.cpp | 166 ++++++++++++++++++++++++++-- src/third-party/config.mk | 3 + src/third-party/daemon.cpp | 209 ++++++++++++++++++++++++++++++++++++ src/third-party/daemon.h | 15 +++ src/third-party/pidfile.cpp | 112 +++++++++++++++++++ src/third-party/pidfile.h | 28 +++++ 13 files changed, 610 insertions(+), 50 deletions(-) create mode 100644 config/log4cpp.config create mode 100644 src/third-party/config.mk create mode 100644 src/third-party/daemon.cpp create mode 100644 src/third-party/daemon.h create mode 100644 src/third-party/pidfile.cpp create mode 100644 src/third-party/pidfile.h diff --git a/Makefile b/Makefile index 389e421..6a7d565 100644 --- a/Makefile +++ b/Makefile @@ -32,6 +32,8 @@ install: bin/growcontrold mkdir -p /opt/growcontrol/bin mkdir -p /opt/growcontrol/log cp bin/growcontrold /opt/growcontrol/bin + cp -r config /opt/growcontrol uninstall: rm -rf /opt/growcontrol/bin/growcontrold + rm -rf /opt/growcontrol/config diff --git a/config/log4cpp.config b/config/log4cpp.config new file mode 100644 index 0000000..018fb52 --- /dev/null +++ b/config/log4cpp.config @@ -0,0 +1,13 @@ + +# DEBUG < INFO < NOTICE < WARN < ERROR < CRIT < ALERT < FATAL = EMERG + +log4j.rootCategory=INFO, logAppender + +#log4j.appender.logAppender=org.apache.log4j.ConsoleAppender +#log4j.appender.logAppender.Target= System.err +log4j.appender.logAppender=org.apache.log4j.FileAppender +log4j.appender.logAppender.fileName=/opt/growcontrol/log/growcontrol.log + +log4j.appender.logAppender.layout=org.apache.log4j.PatternLayout +log4j.appender.logAppender.layout.ConversionPattern=%d [%p] %m%n +#log4j.appender.logAppender.layout=org.apache.log4j.BasicLayout diff --git a/src/GrowControl.cpp b/src/GrowControl.cpp index 763ff5d..9c832fc 100644 --- a/src/GrowControl.cpp +++ b/src/GrowControl.cpp @@ -10,6 +10,10 @@ #include #include #include +#include +#include +#include + namespace growcontrol { @@ -38,7 +42,7 @@ bool GrowControl::connect(std::string host , uint16_t port, bool retry) { logger << log4cpp::Priority::INFO << "Successfully connected to brickd (" << tfConnection.getHost() << ":" << tfConnection.getPort() << ")."; } - } while (retry && !breakFlag); + } while (retry && !breakFlag && mainLoopFlag); return breakFlag; } @@ -103,6 +107,8 @@ void GrowControl::newDeviceCallback(TfConnection& conn, TfDevice& device) { && device.getDeviceState() == TfDeviceState::AVAILABLE ) { io16Bricklet = (TfIO16Bricklet*)&device; io16Bricklet->connect(); + io16Bricklet->setPortConfiguration('a', 0x7F, 'o', false); + io16Bricklet->setPort('a', 1); logger << log4cpp::Priority::INFO << "Added io16 device " << device.getDeviceName(true) << "."; } break; @@ -204,6 +210,12 @@ int GrowControl::getFan1() { } +void GrowControl::shutdownController() { + std::lock_guard lock(controlMutex); + mainLoopFlag = false; +} + + void GrowControl::mainLoop() { while (mainLoopFlag) { @@ -241,7 +253,9 @@ void GrowControl::mainLoop() { lcd.controlMainLoopCallback(sensorValues); - sleep(sleepDuration); + if (mainLoopFlag) { + sleep(sleepDuration); + } } } diff --git a/src/GrowControl.h b/src/GrowControl.h index 23fad8a..edce64c 100644 --- a/src/GrowControl.h +++ b/src/GrowControl.h @@ -72,6 +72,8 @@ public: double getHum1(); int getFan1(); + + void shutdownController(); }; } /* namespace growcontrol */ diff --git a/src/IPCManager.cpp b/src/IPCManager.cpp index 41d0a24..4cd8cdb 100644 --- a/src/IPCManager.cpp +++ b/src/IPCManager.cpp @@ -101,50 +101,60 @@ void IPCManager::workerThreadFunc(IPCManager* obj, int socket, std::thread* self IPCManager::IPCManager() {} void IPCManager::init(GrowControl& growControl, std::string socketPath) { - this->growControl = &growControl; - this->socketPath = socketPath; - // Create Socket - if ((socketFD = socket(AF_UNIX, SOCK_STREAM, 0)) == -1) { - logger << log4cpp::Priority::ERROR << "Could not create socket"; - throw GCException("Could not create socket", GCErrorCode::OTHER_ERROR); + if (!configFlag) { + this->growControl = &growControl; + this->socketPath = socketPath; + // Create Socket + if ((socketFD = socket(AF_UNIX, SOCK_STREAM, 0)) == -1) { + logger << log4cpp::Priority::ERROR << "Could not create socket"; + throw GCException("Could not create socket", GCErrorCode::OTHER_ERROR); + } + // Create address struct + struct sockaddr_un local; + local.sun_family = AF_UNIX; + strncpy(local.sun_path, socketPath.c_str(), sizeof(local.sun_path)); + // Unlink the socket path (remove possible remains?) + unlink(local.sun_path); + // bind socket + if (bind(socketFD, (struct sockaddr *)&local, sizeof(local)) == -1) { + logger << log4cpp::Priority::ERROR << "Could not bind socket"; + throw GCException("Could not bind socket", GCErrorCode::OTHER_ERROR); + } + // mark as listen connection + if (listen(socketFD, 5) == -1) { + logger << log4cpp::Priority::ERROR << "Could not switch socket to accepting mode"; + throw GCException("Could not switch socket to accepting mode", GCErrorCode::OTHER_ERROR); + } + logger << log4cpp::Priority::INFO << "Successfully created new ipc socket " << socketPath << "."; + listenerThreadFlag = true; + listenerThread = new std::thread(listenerThreadFunc, this); + configFlag = true; } - // Create address struct - struct sockaddr_un local; - local.sun_family = AF_UNIX; - strncpy(local.sun_path, socketPath.c_str(), sizeof(local.sun_path)); - // Unlink the socket path (remove possible remains?) - unlink(local.sun_path); - // bind socket - if (bind(socketFD, (struct sockaddr *)&local, sizeof(local)) == -1) { - logger << log4cpp::Priority::ERROR << "Could not bind socket"; - throw GCException("Could not bind socket", GCErrorCode::OTHER_ERROR); - } - // mark as listen connection - if (listen(socketFD, 5) == -1) { - logger << log4cpp::Priority::ERROR << "Could not switch socket to accepting mode"; - throw GCException("Could not switch socket to accepting mode", GCErrorCode::OTHER_ERROR); - } - logger << log4cpp::Priority::INFO << "Successfully created new ipc socket " << socketPath << "."; - listenerThreadFlag = true; - listenerThread = new std::thread(listenerThreadFunc, this); } IPCManager::~IPCManager() { - if (socketFD > 0) { - close(socketFD); - unlink(socketPath.c_str()); - } - if (listenerThread != nullptr) { - listenerThreadFlag = false; - listenerThread->join(); - delete listenerThread; - } - std::set workers(workerThreads); - for (auto w : workers) { - w->join(); - delete w; - } + shutdown(); +} + +void IPCManager::shutdown() { + if (configFlag) { + if (socketFD > 0) { + close(socketFD); + unlink(socketPath.c_str()); + } + if (listenerThread != nullptr) { + listenerThreadFlag = false; + listenerThread->join(); + delete listenerThread; + } + std::set workers(workerThreads); + for (auto w : workers) { + w->join(); + delete w; + } + configFlag = false; + } } diff --git a/src/IPCManager.h b/src/IPCManager.h index 13081e5..42ee67e 100644 --- a/src/IPCManager.h +++ b/src/IPCManager.h @@ -28,11 +28,14 @@ private: std::thread* listenerThread = nullptr; bool listenerThreadFlag = false; std::set workerThreads; + bool configFlag = false; public: IPCManager(); virtual ~IPCManager(); + void shutdown(); + void init(GrowControl& growControl, std::string socketPath); static void listenerThreadFunc(IPCManager* obj); diff --git a/src/config.mk b/src/config.mk index d2791ed..b3be13b 100644 --- a/src/config.mk +++ b/src/config.mk @@ -5,3 +5,4 @@ SRC_FILES += src/main.cpp \ src/IPCManager.cpp include src/lcdpages/config.mk +include src/third-party/config.mk diff --git a/src/main.cpp b/src/main.cpp index 05c1aa3..09f9a3e 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -7,31 +7,179 @@ #include #include "GrowControl.h" +#include "GCException.h" #include "IPCManager.h" +#include "third-party/daemon.h" +#include "third-party/pidfile.h" +#include #include #include #include #include +#include +#include +#include +#include + using namespace tfcppsimple; using namespace growcontrol; +static log4cpp::Category& logger = log4cpp::Category::getInstance("growcontrol"); -#define HOST "localhost" -#define PORT 4223 +std::string pidFile = "/var/run/growcontrold.pid"; +std::string socketPath = "/var/run/growcontrol.socket"; +std::string log4cppConfig = "log4cpp.config"; +std::string host = "localhost"; +uint16_t port = 4223; +bool daemonMode = false; +int pid_fd = 0; GrowControl controller; IPCManager ipcManager; +bool termSignalCaught = false; + + + +void signalHandler(int signum) { + signal (signum, signalHandler); + switch (signum) { + case SIGHUP: + case SIGINT: + case SIGTERM: + logger << log4cpp::Priority::WARN << "Caught term signal, exiting ..."; + termSignalCaught = true; + controller.shutdownController(); + break; + default: + break; + } +} + + + +void printUsage(std::ostream& stream, std::string name) { + stream << "Usage: " << name << " [-d] [-pid pid-file] [-s socket-path] [-l log4cpp-config] [-h host] [-p port]" << std::endl + << "\t-d ... daemon mode" << std::endl + << "\t-pid ... pid-file" << std::endl + << "\t-s ... path of ipc socket" << std::endl + << "\t-l ... path to log4cpp config file" << std::endl + << "\t-h ... brickd address" << std::endl + << "\t-p ... brickd port" << std::endl; +} + + int main(int argc, char **argv) { - TfUtils::configureLog4CPP("log4cpp.config"); - controller.connect(HOST, PORT); - sleep(2); - try { - ipcManager.init(controller, "/tmp/growcontrol.socket"); - } catch (GCException &e) { + + for (int t = 1; t < argc; t++) { + if (strcmp(argv[t], "-d") == 0) { + daemonMode = true; + } else if (strcmp(argv[t], "--help") == 0) { + printUsage(std::cerr, argv[0]); + exit(0); + } else if (strcmp(argv[t], "-pid") == 0) { + if (t+1 < argc && argv[t+1][0] != '-') { + t++; + pidFile = argv[t]; + } else { + std::cerr << "Option -pid is missing an argument." << std::endl << std::endl; + printUsage(std::cerr, argv[0]); + exit(1); + } + } else if (strcmp(argv[t], "-s") == 0) { + if (t+1 < argc && argv[t+1][0] != '-') { + t++; + socketPath = argv[t]; + } else { + std::cerr << "Option -s is missing an argument." << std::endl << std::endl; + printUsage(std::cerr, argv[0]); + exit(1); + } + } else if (strcmp(argv[t], "-l") == 0) { + if (t+1 < argc && argv[t+1][0] != '-') { + t++; + log4cppConfig = argv[t]; + } else { + std::cerr << "Option -l is missing an argument." << std::endl << std::endl; + printUsage(std::cerr, argv[0]); + exit(1); + } + } else if (strcmp(argv[t], "-h") == 0) { + if (t+1 < argc && argv[t+1][0] != '-') { + t++; + host = argv[t]; + } else { + std::cerr << "Option -h is missing an argument." << std::endl << std::endl; + printUsage(std::cerr, argv[0]); + exit(1); + } + } else if (strcmp(argv[t], "-p") == 0) { + if (t+1 < argc && argv[t+1][0] != '-') { + t++; + port = std::atoi(argv[t]); + if (port == 0) { + std::cerr << "Option -p has invalid argument \"" << argv[t] << "\"." << std::endl << std::endl; + printUsage(std::cerr, argv[0]); + exit(1); + } + } else { + std::cerr << "Option -p is missing an argument." << std::endl << std::endl; + printUsage(std::cerr, argv[0]); + exit(1); + } + } else { + std::cerr << "Unknown argument \"" << argv[t] << "\"." << std::endl << std::endl; + printUsage(std::cerr, argv[0]); + exit(1); + } + } + + TfUtils::configureLog4CPP(log4cppConfig); + + logger << log4cpp::Priority::INFO << "Daemon Mode: " << daemonMode; + logger << log4cpp::Priority::INFO << "Pid-File: " << pidFile; + logger << log4cpp::Priority::INFO << "socket-path: " << socketPath; + logger << log4cpp::Priority::INFO << "log4cpp-config: " << log4cppConfig; + logger << log4cpp::Priority::INFO << "Brickd-Address: " << host << ":" << port; + + if (daemonMode) { + pid_fd = daemon_start(pidFile.c_str()); + } else { + pid_fd = pidfile_acquire(pidFile.c_str(), getpid()); + } + + if (pid_fd >= 0) { + + signal(SIGTERM, signalHandler); + signal(SIGINT, signalHandler); + signal(SIGHUP, signalHandler); + + controller.connect(host, port); + + if (!termSignalCaught) { + sleep(2); + try { + ipcManager.init(controller, socketPath); + } catch (GCException &e) { + } + + controller.mainLoop(); + + logger << log4cpp::Priority::WARN << "Shutting down ipc ..."; + + ipcManager.shutdown(); + } + + } else { + logger << log4cpp::Priority::WARN << "Problems while forking/writing pid-file."; + } + + logger << log4cpp::Priority::WARN << "Shutting down ..."; + + if (pid_fd >= 0) { + pidfile_release(pidFile.c_str(), pid_fd); } - controller.mainLoop(); } diff --git a/src/third-party/config.mk b/src/third-party/config.mk new file mode 100644 index 0000000..8079273 --- /dev/null +++ b/src/third-party/config.mk @@ -0,0 +1,3 @@ + +SRC_FILES += src/third-party/pidfile.cpp \ + src/third-party/daemon.cpp diff --git a/src/third-party/daemon.cpp b/src/third-party/daemon.cpp new file mode 100644 index 0000000..983c952 --- /dev/null +++ b/src/third-party/daemon.cpp @@ -0,0 +1,209 @@ +/* + * brickd + * Copyright (C) 2012-2013 Matthias Bolte + * + * main_linux.c: Brick Daemon starting point for Linux + * + * 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 2 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, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "pidfile.h" +#include + +static log4cpp::Category& logger = log4cpp::Category::getInstance("growcontrol"); + +static int daemon_parent(pid_t child, int status_read, const char* _pid_filename) { + int8_t status = -1; + ssize_t rc; + + // wait for first child to exit + while (waitpid(child, NULL, 0) < 0 && errno == EINTR) { + } + + /*if (waitpid(pid, NULL, 0) < 0) { + fprintf(stderr, "Could not wait for first child process to exit: %s (%d)\n", + get_errno_name(errno), errno); + + close(status_pipe[0]); + + return -1; + }*/ + + // wait for second child to start successfully + while ((rc = read(status_read, &status, 1)) < 0 && errno == EINTR) { + } + + if (status < 0) { + if (rc < 0) { + fprintf(stderr, "Could not read from status pipe: %s (%d)\n", + strerror(errno), errno); + } + + close(status_read); + + exit(EXIT_FAILURE); + } + + close(status_read); + + if (status != 1) { + if (status == 2) { + fprintf(stderr, "Already running according to '%s'\n", _pid_filename); + } else { + fprintf(stderr, "Second child process exited with an error (status: %d)\n", + status); + } + + exit(EXIT_FAILURE); + } + + // exit first parent + exit(EXIT_SUCCESS); +} + + +int daemon_start(const char* _pid_filename) { + int status_pipe[2]; + pid_t pid; + int pid_fd = -1; + int8_t status = 0; + int null_fd = -1; + + // create status pipe + if (pipe(status_pipe) < 0) { + logger << log4cpp::Priority::ERROR << "Could not create status pipe: " + << strerror(errno) << " (" << errno << ")."; + + return -1; + } + + pid = fork(); + + if (pid < 0) { + logger << log4cpp::Priority::ERROR << "Could not fork first child process: " + << strerror(errno) << " (" << errno << ")."; + + close(status_pipe[0]); + close(status_pipe[1]); + + return -1; + } + + if (pid > 0) { // first parent + close(status_pipe[1]); + + daemon_parent(pid, status_pipe[0], _pid_filename); + } + + // first child, decouple from parent environment + close(status_pipe[0]); + + if (chdir("/") < 0) { + logger << log4cpp::Priority::ERROR << "Could not change directory to '/': " + << strerror(errno) << " (" << errno << ")."; + + close(status_pipe[1]); + + exit(EXIT_FAILURE); + } + + // FIXME: check error + setsid(); + umask(0); + + pid = fork(); + + if (pid < 0) { + logger << log4cpp::Priority::ERROR << "Could not fork second child process: " + << strerror(errno) << " (" << errno << ")."; + + close(status_pipe[1]); + + exit(EXIT_FAILURE); + } + + if (pid > 0) { + // exit second parent + exit(EXIT_SUCCESS); + } + + // second child, write pid + pid_fd = pidfile_acquire(_pid_filename, getpid()); + + if (pid_fd < 0) { + if (pid_fd < -1) { + status = 2; + } + + goto cleanup; + } + + // redirect standard file descriptors + null_fd = open("/dev/null", O_RDONLY); + + if (null_fd < 0) { + logger << log4cpp::Priority::ERROR << "Could not open /dev/null to redirect: " + << strerror(errno) << " (" << errno << ")."; + + goto cleanup; + } + + if (dup2(null_fd, STDIN_FILENO) != STDIN_FILENO) { + logger << log4cpp::Priority::ERROR << "Could not redirect stdin: " + << strerror(errno) << " (" << errno << ")."; + + goto cleanup; + } + + if (dup2(null_fd, STDOUT_FILENO) != STDOUT_FILENO) { + logger << log4cpp::Priority::ERROR << "Could not redirect stdout: " + << strerror(errno) << " (" << errno << ")."; + + goto cleanup; + } + + if (dup2(null_fd, STDERR_FILENO) != STDERR_FILENO) { + logger << log4cpp::Priority::ERROR << "Could not redirect stderr: " + << strerror(errno) << " (" << errno << ")."; + + goto cleanup; + } + + status = 1; + +cleanup: + if (null_fd > STDERR_FILENO) { + close(null_fd); + } + + while (write(status_pipe[1], &status, 1) < 0 && errno == EINTR) { + } + + close(status_pipe[1]); + + return status == 1 ? pid_fd : -1; +} + diff --git a/src/third-party/daemon.h b/src/third-party/daemon.h new file mode 100644 index 0000000..8747795 --- /dev/null +++ b/src/third-party/daemon.h @@ -0,0 +1,15 @@ +/* + * daemon.h + * + * Created on: 16.06.2014 + * Author: matzman + */ + +#ifndef DAEMON_H_ +#define DAEMON_H_ + + +int daemon_start(const char* _pid_filename); + + +#endif /* DAEMON_H_ */ diff --git a/src/third-party/pidfile.cpp b/src/third-party/pidfile.cpp new file mode 100644 index 0000000..3aeefa4 --- /dev/null +++ b/src/third-party/pidfile.cpp @@ -0,0 +1,112 @@ +/* + * brickd + * Copyright (C) 2012-2013 Matthias Bolte + * + * pidfile.c: PID file specific functions + * + * 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 2 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, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +/* + * this functions realize a flock'ed PID file. + */ + +#include +#include +#include +#include +#include +#include +#include + +#include "pidfile.h" + +#include + +static log4cpp::Category& logger = log4cpp::Category::getInstance("growcontrol"); + +int pidfile_acquire(const char *filename, pid_t pid) { + int fd = -1; + struct stat stat1; + struct stat stat2; + struct flock flock; + char buffer[64]; + + for (;;) { + fd = open(filename, O_WRONLY | O_CREAT, 0644); + + if (fd < 0) { + logger << log4cpp::Priority::ERROR << "Could not open PID file '" << filename << "': " + << strerror(errno) << " (" << errno << ")."; + return -1; + } + + if (fstat(fd, &stat1) < 0) { + logger << log4cpp::Priority::ERROR << "Could not get status of PID file '" << filename << "': " + << strerror(errno) << " (" << errno << ")."; + close(fd); + + return -1; + } + + flock.l_type = F_WRLCK; + flock.l_whence = SEEK_SET; + flock.l_start = 0; + flock.l_len = 1; + + if (fcntl(fd, F_SETLK, &flock) < 0) { + if (errno != EAGAIN) { + logger << log4cpp::Priority::ERROR << "Could not lock PID file '" << filename << "': " + << strerror(errno) << " (" << errno << ")."; + } + + close(fd); + + return errno == EAGAIN ? -2 : -1; + } + + if (stat(filename, &stat2) < 0) { + close(fd); + + continue; + } + + if (stat1.st_ino != stat2.st_ino) { + close(fd); + + continue; + } + + break; + } + + snprintf(buffer, sizeof(buffer), "%lld", (long long)pid); + + if (write(fd, buffer, strlen(buffer)) < 0) { + logger << log4cpp::Priority::ERROR << "Could not write to PID file '" << filename << "': " + << strerror(errno) << " (" << errno << ")."; + + close(fd); + + return -1; + } + + return fd; +} + +void pidfile_release(const char *filename, int fd) { + unlink(filename); + close(fd); +} diff --git a/src/third-party/pidfile.h b/src/third-party/pidfile.h new file mode 100644 index 0000000..8868092 --- /dev/null +++ b/src/third-party/pidfile.h @@ -0,0 +1,28 @@ +/* + * brickd + * Copyright (C) 2012-2013 Matthias Bolte + * + * pidfile.h: PID file specific functions + * + * 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 2 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, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#ifndef BRICKD_PIDFILE_H +#define BRICKD_PIDFILE_H + +int pidfile_acquire(const char *filename, pid_t pid); +void pidfile_release(const char *filename, int fd); + +#endif // BRICKD_PIDFILE_H