diff options
author | Sascha Volkenandt <sascha (at) akv-soft (dot) de> | 2007-01-02 19:18:27 +0000 |
---|---|---|
committer | Sascha Volkenandt <sascha (at) akv-soft (dot) de> | 2007-01-02 19:18:27 +0000 |
commit | 48c46dfdd986ad4a7a0692d05992f7882bef6a88 (patch) | |
tree | 88a3a88a7ab43632850569cba3ab48a1924d9e52 /httpd/tntnet.cpp | |
download | vdr-plugin-live-48c46dfdd986ad4a7a0692d05992f7882bef6a88.tar.gz vdr-plugin-live-48c46dfdd986ad4a7a0692d05992f7882bef6a88.tar.bz2 |
- initial checkin
Diffstat (limited to 'httpd/tntnet.cpp')
-rw-r--r-- | httpd/tntnet.cpp | 832 |
1 files changed, 832 insertions, 0 deletions
diff --git a/httpd/tntnet.cpp b/httpd/tntnet.cpp new file mode 100644 index 0000000..ebaad62 --- /dev/null +++ b/httpd/tntnet.cpp @@ -0,0 +1,832 @@ +/* tntnet.cpp + * Copyright (C) 2003-2005 Tommi Maekitalo + * + * 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 + * is provided AS IS, WITHOUT ANY WARRANTY; without even the implied + * warranty of MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE, and + * NON-INFRINGEMENT. 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 St, Fifth Floor, Boston, MA 02110-1301 USA + * + */ + +#include "tnt/worker.h" +#include "tnt/tntnet.h" +#include "tnt/listener.h" +#include "tnt/http.h" +#include "tnt/httpreply.h" +#include "tnt/sessionscope.h" + +#include <cxxtools/tcpstream.h> +#include <cxxtools/log.h> +#include <cxxtools/loginit.h> + +#include <iostream> +#include <fstream> +#include <vector> +#include <sys/types.h> +#include <sys/stat.h> +#include <unistd.h> +#include <grp.h> +#include <pwd.h> +#include <signal.h> +#include <sys/wait.h> +#include <errno.h> +#ifdef HAVE_CONFIG_H +# include <config.h> +#endif + +#ifndef TNTNET_CONF +# define TNTNET_CONF "/etc/tntnet.conf" +#endif + +#ifndef TNTNET_PID +# define TNTNET_PID "/var/run/tntnet.pid" +#endif + +log_define("tntnet.tntnet") + +#define log_error_master(expr) \ + do { \ + std::cout << "ERROR: " << expr << std::endl; \ + } while(false) + +#define log_warn_master(expr) \ + do { \ + std::cout << "WARN: " << expr << std::endl; \ + } while(false) + +#define log_info_master(expr) \ + do { \ + std::cout << "INFO: " << expr << std::endl; \ + } while(false) + +#define log_debug_master(expr) \ + do { \ + std::cout << "DEBUG: " << expr << std::endl; \ + } while(false) + +namespace +{ + void sigEnd(int) + { + tnt::Tntnet::shutdown(); + } + + void sigReload(int) + { + // stopping child with 111 signals monitor-process to restart child + tnt::Tntnet::restart(); + } + + void configureDispatcher(tnt::Dispatcher& dis, const tnt::Tntconfig& config) + { + typedef tnt::Dispatcher::CompidentType CompidentType; + + const tnt::Tntconfig::config_entries_type& params = config.getConfigValues(); + + tnt::Tntconfig::config_entries_type::const_iterator vi; + for (vi = params.begin(); vi != params.end(); ++vi) + { + const tnt::Tntconfig::config_entry_type& v = *vi; + const tnt::Tntconfig::params_type& args = v.params; + if (v.key == "MapUrl") + { + if (args.size() < 2) + { + std::ostringstream msg; + msg << "invalid number of parameters (" << args.size() << ") in MapUrl"; + throw std::runtime_error(msg.str()); + } + + std::string url = args[0]; + + CompidentType ci = CompidentType(args[1]); + if (args.size() > 2) + { + ci.setPathInfo(args[2]); + if (args.size() > 3) + ci.setArgs(CompidentType::args_type(args.begin() + 3, args.end())); + } + + dis.addUrlMapEntry(url, ci); + } + } + } + + bool checkChildSuccess(int fd) + { + log_debug("checkChildSuccess"); + + char buffer; + int ret = ::read(fd, &buffer, 1); + if (ret < 0) + throw std::runtime_error( + std::string("error in read: ") + strerror(errno)); + close(fd); + return ret > 0; + } + + void signalParentSuccess(int fd) + { + log_debug("signalParentSuccess"); + + ssize_t s = write(fd, "1", 1); + if (s < 0) + throw std::runtime_error( + std::string("error in write(): ") + strerror(errno)); + close(fd); + } + +} + +namespace tnt +{ + //////////////////////////////////////////////////////////////////////// + // Tntnet + // + bool Tntnet::stop = false; + int Tntnet::ret = 0; + std::string Tntnet::pidFileName; + + Tntnet::Tntnet(int& argc, char* argv[]) + : propertyfilename(argc, argv, 'P'), + debug(argc, argv, 'd'), + queue(1000), + pollerthread(queue) + { + // check for argument -c + cxxtools::Arg<const char*> conf(argc, argv, 'c'); + if (conf.isSet()) + configFile = conf; + else + { + // read 1st parameter from argument-list + cxxtools::Arg<const char*> conf(argc, argv); + if (conf.isSet()) + configFile = conf; + else + { + // check environment-variable TNTNET_CONF + const char* tntnetConf = ::getenv("TNTNET_CONF"); + if (tntnetConf) + configFile = tntnetConf; + else + configFile = TNTNET_CONF; // take default + } + } + } + + void Tntnet::setGroup() const + { + Tntconfig::params_type group = config.getConfigValue("Group"); + if (group.size() >= 1) + { + struct group * gr = getgrnam(group.begin()->c_str()); + if (gr == 0) + throw std::runtime_error("unknown group " + *group.begin()); + + log_debug("change group to " << *group.begin() << '(' << gr->gr_gid << ')'); + + int ret = setgid(gr->gr_gid); + if (ret != 0) + { + std::ostringstream msg; + msg << "cannot change group to " << *group.begin() + << '(' << gr->gr_gid << "): " << strerror(errno); + throw std::runtime_error(msg.str()); + } + } + } + + void Tntnet::setDir(const char* def) const + { + std::string dir = config.getValue("Dir", def); + + if (!dir.empty()) + { + log_debug("chdir(" << dir << ')'); + if (chdir(dir.c_str()) == -1) + { + throw std::runtime_error( + std::string("error in chdir(): ") + + strerror(errno)); + } + } + + std::string chrootdir = config.getValue("Chroot"); + if (!chrootdir.empty() && chroot(chrootdir.c_str()) == -1) + throw std::runtime_error( + std::string("error in chroot(): ") + + strerror(errno)); + } + + void Tntnet::setUser() const + { + Tntconfig::params_type user = config.getConfigValue("User"); + if (user.size() >= 1) + { + struct passwd * pw = getpwnam(user.begin()->c_str()); + if (pw == 0) + throw std::runtime_error("unknown user " + *user.begin()); + + log_debug("change user to " << *user.begin() << '(' << pw->pw_uid << ')'); + + int ret = setuid(pw->pw_uid); + if (ret != 0) + { + std::ostringstream msg; + msg << "cannot change user to " << *user.begin() + << '(' << pw->pw_uid << "): " << strerror(errno); + throw std::runtime_error(msg.str()); + } + } + } + + int Tntnet::mkDaemon() const + { + log_info("start daemon-mode"); + + int filedes[2]; + + if (pipe(filedes) != 0) + throw std::runtime_error( + std::string("error in pipe(int[2]): ") + strerror(errno)); + + int pid = fork(); + if (pid > 0) + { + // parent + + close(filedes[1]); // close write-fd + + // exit with error, when nothing read + ::exit (checkChildSuccess(filedes[0]) ? 0 : 1); + } + else if (pid < 0) + throw std::runtime_error( + std::string("error in fork(): ") + strerror(errno)); + + // child + + close(filedes[0]); // close read-fd + + // setsid + if (setsid() == -1) + throw std::runtime_error( + std::string("error in setsid(): ") + + strerror(errno)); + + // return write-fd + return filedes[1]; + } + + void Tntnet::closeStdHandles() const + { + // close stdin, stdout and stderr + bool noclosestd = config.getBoolValue("NoCloseStdout", false); + if (noclosestd) + { + log_debug("not closing stdout"); + return; + } + + if (freopen("/dev/null", "r", stdin) == 0) + throw std::runtime_error( + std::string("unable to replace stdin with /dev/null: ") + + strerror(errno)); + + if (freopen("/dev/null", "w", stdout) == 0) + throw std::runtime_error( + std::string("unable to replace stdout with /dev/null: ") + + strerror(errno)); + + if (freopen("/dev/null", "w", stderr) == 0) + throw std::runtime_error( + std::string("unable to replace stderr with /dev/null: ") + + strerror(errno)); + } + + int Tntnet::run() + { + loadConfiguration(); + + if (debug) + { + log_init_debug(); + log_warn("Debugmode"); + isDaemon = false; + } + else + { + isDaemon = config.getBoolValue("Daemon", false); + } + + if (isDaemon) + { + int filedes = mkDaemon(); + + setDir(""); + + bool nomonitor = config.getBoolValue("NoMonitor", false); + if (nomonitor) + { + log_debug("start worker-process without monitor"); + writePidfile(getpid()); + initWorkerProcess(); + + // change group and user + setGroup(); + setUser(); + + initLogging(); + workerProcess(filedes); + } + else + { + initWorkerProcess(); + do + { + int filedes_monitor[2]; + + if (pipe(filedes_monitor) != 0) + throw std::runtime_error( + std::string("error in pipe(int[2]): ") + strerror(errno)); + + // fork workerprocess + int pid = fork(); + if (pid < 0) + throw std::runtime_error( + std::string("error in forking workerprocess: ") + + strerror(errno)); + + if (pid == 0) + { + // workerprocess + + close(filedes_monitor[0]); // close read-fd + + // change group and user + setGroup(); + setUser(); + + initLogging(); + workerProcess(filedes_monitor[1]); + return ret; + } + else + { + close(filedes_monitor[1]); // close write-fd + + // write child-pid + writePidfile(pid); + + // wait for worker to signal success + if (!checkChildSuccess(filedes_monitor[0])) + ::exit(1); + if (filedes >= 0) + { + signalParentSuccess(filedes); + filedes = -1; + } + + monitorProcess(pid); + if (!stop) + sleep(1); + } + + } while (!stop); + } + } + else + { + log_info("no daemon-mode"); + initLogging(); + initWorkerProcess(); + workerProcess(); + } + + return 0; + } + + void Tntnet::initLogging() + { + if (debug) + return; // logging already initialized + + std::string pf; + if (propertyfilename.isSet()) + pf = propertyfilename.getValue(); + else + pf = config.getValue("PropertyFile"); + + if (pf.empty()) + log_init(); + else + { + struct stat properties_stat; + if (stat(pf.c_str(), &properties_stat) != 0) + throw std::runtime_error("propertyfile " + pf + " not found"); + + log_init(pf.c_str()); + } + } + + void Tntnet::writePidfile(int pid) + { + pidFileName = config.getValue("PidFile", TNTNET_PID); + + log_debug("pidfile=" << pidFileName); + + if (!pidFileName.empty()) + { + if (pidFileName[0] != '/') + { + // prepend current working-directory to pidfilename if not absolute + std::vector<char> buf(256); + const char* cwd; + while (true) + { + cwd = ::getcwd(&buf[0], buf.size()); + if (cwd) + break; + else if (errno == ERANGE) + buf.resize(buf.size() * 2); + else + throw std::runtime_error( + std::string("error in getcwd: ") + strerror(errno)); + } + pidFileName = std::string(cwd) + '/' + pidFileName; + log_debug("pidfile=" << pidFileName); + } + + std::ofstream pidfile(pidFileName.c_str()); + if (!pidfile) + throw std::runtime_error("unable to open pid-file " + pidFileName); + pidfile << pid; + } + } + + void Tntnet::monitorProcess(int workerPid) + { + setDir(""); + + // close stdin, stdout and stderr + closeStdHandles(); + + int status; + waitpid(workerPid, &status, 0); + + if (WIFSIGNALED(status)) + { + // SIGTERM means normal exit + if (WTERMSIG(status) == SIGTERM) + { + log_info_master("child terminated normally"); + stop = true; + } + else + { + log_warn_master("child terminated with signal " + << WTERMSIG(status) << " - restart child"); + } + } + else if (WEXITSTATUS(status) == 111) + { + log_info_master("child requested restart"); + } + else + { + log_info_master("child exited with exitcode " << WEXITSTATUS(status)); + stop = true; + } + + if (unlink(pidFileName.c_str()) != 0) + log_error_master("failed to remove pidfile \"" << pidFileName << "\" error " << errno); + } + + void Tntnet::initWorkerProcess() + { + log_debug("init workerprocess"); + + signal(SIGPIPE, SIG_IGN); + signal(SIGABRT, SIG_IGN); + signal(SIGTERM, sigEnd); + signal(SIGHUP, sigReload); + + configureDispatcher(d_dispatcher, config); + + // create listener-threads + Tntconfig::config_entries_type configListen; + config.getConfigValues("Listen", configListen); + + if (configListen.empty()) + { + log_warn("no listeners defined - using 0.0.0.0:80"); + ListenerBase* s = new tnt::Listener("0.0.0.0", 80, queue); + listeners.insert(s); + } + else + { + for (Tntconfig::config_entries_type::const_iterator it = configListen.begin(); + it != configListen.end(); ++it) + { + if (it->params.empty()) + throw std::runtime_error("empty Listen-entry"); + + unsigned short int port = 80; + if (it->params.size() >= 2) + { + std::istringstream p(it->params[1]); + p >> port; + if (!p) + { + std::ostringstream msg; + msg << "invalid port " << it->params[1]; + throw std::runtime_error(msg.str()); + } + } + + std::string ip(it->params[0]); + log_debug("create listener ip=" << ip << " port=" << port); + ListenerBase* s = new tnt::Listener(ip, port, queue); + listeners.insert(s); + } + } + +#ifdef USE_SSL + // create ssl-listener-threads + std::string defaultCertificateFile = config.getValue("SslCertificate"); + std::string defaultCertificateKey = config.getValue("SslKey"); + configListen.clear(); + config.getConfigValues("SslListen", configListen); + + for (Tntconfig::config_entries_type::const_iterator it = configListen.begin(); + it != configListen.end(); ++it) + { + if (it->params.empty()) + throw std::runtime_error("empty SslListen-entry"); + + unsigned short int port = 443; + if (it->params.size() >= 2) + { + std::istringstream p(it->params[1]); + p >> port; + if (!p) + { + std::ostringstream msg; + msg << "invalid port " << it->params[1]; + throw std::runtime_error(msg.str()); + } + } + + std::string certificateFile = + it->params.size() >= 3 ? it->params[2] + : defaultCertificateFile; + std::string certificateKey = + it->params.size() >= 4 ? it->params[3] : + it->params.size() >= 3 ? it->params[2] : defaultCertificateKey; + + if (certificateFile.empty()) + throw std::runtime_error("Ssl-certificate not configured"); + + std::string ip(it->params[0]); + log_debug("create ssl-listener ip=" << ip << " port=" << port); + ListenerBase* s = new Ssllistener(certificateFile.c_str(), + certificateKey.c_str(), ip, port, queue); + listeners.insert(s); + } +#endif // USE_SSL + + // configure worker (static) + Comploader::configure(config); + + // configure http + HttpMessage::setMaxRequestSize( + config.getValue("MaxRequestSize", HttpMessage::getMaxRequestSize())); + Job::setSocketReadTimeout( + config.getValue("SocketReadTimeout", Job::getSocketReadTimeout())); + Job::setSocketWriteTimeout( + config.getValue("SocketWriteTimeout", Job::getSocketWriteTimeout())); + Job::setKeepAliveMax( + config.getValue("KeepAliveMax", Job::getKeepAliveMax())); + Job::setSocketBufferSize( + config.getValue("BufferSize", Job::getSocketBufferSize())); + HttpReply::setMinCompressSize( + config.getValue("MinCompressSize", HttpReply::getMinCompressSize())); + HttpReply::setKeepAliveTimeout( + config.getValue("KeepAliveTimeout", HttpReply::getKeepAliveTimeout())); + HttpReply::setDefaultContentType( + config.getValue("DefaultContentType", HttpReply::getDefaultContentType())); + + log_debug("listeners.size()=" << listeners.size()); + } + + void Tntnet::workerProcess(int filedes) + { + log_debug("worker-process"); + + // reload configuration + config = Tntconfig(); + loadConfiguration(); + + // initialize worker-process + minthreads = config.getValue<unsigned>("MinThreads", 5); + maxthreads = config.getValue<unsigned>("MaxThreads", 100); + threadstartdelay = config.getValue<unsigned>("ThreadStartDelay", 10); + Worker::setMinThreads(minthreads); + Worker::setMaxRequestTime(config.getValue<unsigned>("MaxRequestTime", Worker::getMaxRequestTime())); + Worker::setEnableCompression(config.getBoolValue("EnableCompression", Worker::getEnableCompression())); + queue.setCapacity(config.getValue<unsigned>("QueueSize", queue.getCapacity())); + Sessionscope::setDefaultTimeout(config.getValue<unsigned>("SessionTimeout", Sessionscope::getDefaultTimeout())); + Listener::setBacklog(config.getValue<int>("ListenBacklog", Listener::getBacklog())); + Listener::setListenRetry(config.getValue<int>("ListenRetry", Listener::getListenRetry())); + Dispatcher::setMaxUrlMapCache(config.getValue<unsigned>("MaxUrlMapCache", Dispatcher::getMaxUrlMapCache())); + + Tntconfig::config_entries_type configSetEnv; + config.getConfigValues("SetEnv", configSetEnv); + for (Tntconfig::config_entries_type::const_iterator it = configSetEnv.begin(); + it != configSetEnv.end(); ++it) + { + if (it->params.size() >= 2) + { +#ifdef HAVE_SETENV + log_debug("setenv " << it->params[0] << "=\"" << it->params[1] << '"'); + ::setenv(it->params[0].c_str(), it->params[1].c_str(), 1); +#else + std::string name = it->params[0]; + std::string value = it->params[1]; + + char* env = new char[name.size() + value.size() + 2]; + name.copy(env, name.size()); + env[name.size()] = '='; + value.copy(env + name.size() + 1, value.size()); + env[name.size() + value.size() + 1] = '\0'; + + log_debug("putenv(" << env); + ::putenv(env); +#endif + } + } + + // create worker-threads + log_info("create " << minthreads << " worker threads"); + for (unsigned i = 0; i < minthreads; ++i) + { + log_debug("create worker " << i); + Worker* s = new Worker(*this); + s->create(); + } + + // create poller-thread + log_debug("start poller thread"); + pollerthread.create(); + + // launch listener-threads + log_info("create " << listeners.size() << " listener threads"); + for (listeners_type::iterator it = listeners.begin(); + it != listeners.end(); ++it) + (*it)->create(); + + log_debug("start timer thread"); + cxxtools::MethodThread<Tntnet, cxxtools::AttachedThread> timerThread(*this, &Tntnet::timerTask); + timerThread.create(); + + if (filedes >= 0) + { + signalParentSuccess(filedes); + closeStdHandles(); + } + + // mainloop + cxxtools::Mutex mutex; + while (!stop) + { + { + cxxtools::MutexLock lock(mutex); + queue.noWaitThreads.wait(lock); + } + + if (stop) + break; + + if (Worker::getCountThreads() < maxthreads) + { + log_info("create workerthread"); + Worker* s = new Worker(*this); + s->create(); + } + else + log_warn("max worker-threadcount " << maxthreads << " reached"); + + if (threadstartdelay > 0) + usleep(threadstartdelay); + } + + log_warn("stopping Tntnet"); + + // join-loop + while (!listeners.empty()) + { + listeners_type::value_type s = *listeners.begin(); + log_debug("remove listener from listener-list"); + listeners.erase(s); + + log_debug("request listener to stop"); + s->doStop(); + + log_debug("join listener-thread"); + s->join(); + delete s; + + log_debug("listener stopped"); + } + + log_info("listeners stopped"); + } + + void Tntnet::timerTask() + { + log_debug("timer thread"); + + while (!stop) + { + sleep(1); + + log_debug("check sessiontimeout"); + getScopemanager().checkSessionTimeout(); + + log_debug("worker-timer"); + Worker::timer(); + } + + log_warn("stopping Tntnet"); + + if (!pidFileName.empty()) + unlink(pidFileName.c_str()); + + queue.noWaitThreads.signal(); + Worker::setMinThreads(0); + pollerthread.doStop(); + } + + void Tntnet::loadConfiguration() + { + config = Tntconfig(); + config.load(configFile.c_str()); + } + + void Tntnet::shutdown() + { + stop = true; + } + + void Tntnet::restart() + { + // stopping child with 111 signals monitor-process to restart child + stop = true; + ret = 111; + } + +} + +//////////////////////////////////////////////////////////////////////// +// main +// +#ifdef HAVE_TNTNET_MAIN +int main(int argc, char* argv[]) +{ + signal(SIGPIPE, SIG_IGN); + signal(SIGABRT, SIG_IGN); + signal(SIGTERM, sigEnd); + std::ios::sync_with_stdio(false); + + try + { + tnt::Tntnet app(argc, argv); + if (argc != 1) + { + std::cout << PACKAGE_STRING "\n\n" << + "usage: " << argv[0] << " {options}\n\n" + " -c file configurationfile (default: " TNTNET_CONF ")\n" + " -d enable all debug output (ignoring properties-file)\n"; + return -1; + } + + return app.run(); + } + catch(const std::exception& e) + { + log_fatal(e.what()); + std::cerr << e.what() << std::endl; + return -1; + } +} +#endif |