diff options
author | Alan Conway <aconway@apache.org> | 2007-07-19 02:03:02 +0000 |
---|---|---|
committer | Alan Conway <aconway@apache.org> | 2007-07-19 02:03:02 +0000 |
commit | 7221b1ca1cda94ea831942db53f15c14cd3cfe1d (patch) | |
tree | b6748eef9174601950bb51fd0d380e8f649ec215 | |
parent | 3b064908eb76e43db6e82bbd51a373590b61b44b (diff) | |
download | qpid-python-7221b1ca1cda94ea831942db53f15c14cd3cfe1d.tar.gz |
* lib/broker/Daemon.cpp, .h
- Rewrote to remove libdaemon dependency.
- PID file stored in /var/run if root, /tmp otherwise.
* src/qpidd.cpp: Use new Daemon.cpp.
- lock files stored in /var/run (for root) or /tmp.
- updated to trunk daemon flag behavior.
* lib/broker/Makefile.am (libqpidbroker_la_LIBADD):
- Daemon.cpp now needs -lboost_iostreams
* NOTICE, README: Removed mention of libdaemon.
git-svn-id: https://svn.apache.org/repos/asf/incubator/qpid/branches/M2@557452 13f79535-47bb-0310-9956-ffa450edef68
-rw-r--r-- | cpp/NOTICE | 10 | ||||
-rw-r--r-- | cpp/README | 4 | ||||
-rw-r--r-- | cpp/lib/broker/Daemon.cpp | 221 | ||||
-rw-r--r-- | cpp/lib/broker/Daemon.h | 72 | ||||
-rw-r--r-- | cpp/lib/broker/Makefile.am | 2 | ||||
-rw-r--r-- | cpp/src/qpidd.cpp | 145 | ||||
-rwxr-xr-x | cpp/tests/daemon_test | 9 |
7 files changed, 234 insertions, 229 deletions
diff --git a/cpp/NOTICE b/cpp/NOTICE index 36bb499887..c194a63903 100644 --- a/cpp/NOTICE +++ b/cpp/NOTICE @@ -22,13 +22,3 @@ Project dependancies: * boost vesrion 1.33.1 or later under the Boost Software License, and can be downloaded from http://www.boost.org - Included in most OS platfroms by defualt. - -Optional modules: - * libdaemon version 0.10 under LGPL and can be downloaded from - http://www.stud.uni-hamburg.de/users/lennart/projects/libdaemon - -Optional module detected by configure scripts if present to create - daemon process. - - - - diff --git a/cpp/README b/cpp/README index 4c9afdbdbf..2f3f2d81d3 100644 --- a/cpp/README +++ b/cpp/README @@ -33,7 +33,7 @@ Qpid is compiled against libraries: * apr <http://apr.apache.org> (1.2.7) * boost <http://www.boost.org> (1.33.1) * cppunit <http://cppunit.sourceforge.net> (1.11.4) - * libdaemon <http://www.stud.uni-hamburg.de/users/lennart/projects/libdaemon> (0.10) + Using tools: * boost-jam <http://boost.sourceforge.net/> (3.1.13) @@ -70,7 +70,7 @@ Building without documentaion does not require: On linux most packages can be installed using your distribution's package management tool. For example on Fedora: - # yum install apr-devel boost-devel cppunit-devel libdaemon-devel + # yum install apr-devel boost-devel cppunit-devel # yum install pkgconfig doxygen graphviz help2man diff --git a/cpp/lib/broker/Daemon.cpp b/cpp/lib/broker/Daemon.cpp index 9c7c7c9b81..498ce7f4ea 100644 --- a/cpp/lib/broker/Daemon.cpp +++ b/cpp/lib/broker/Daemon.cpp @@ -15,130 +15,165 @@ * limitations under the License. * */ -#include "QpidError.h" #include "Daemon.h" -#include <libdaemon/daemon.h> +#include "Exception.h" + +#include <boost/iostreams/stream.hpp> +#include <boost/iostreams/device/file_descriptor.hpp> + +#include <sstream> + #include <errno.h> -#include <unistd.h> -#include <sys/stat.h> +#include <fcntl.h> #include <signal.h> -#include <boost/filesystem/path.hpp> -#include <boost/filesystem/operations.hpp> +#include <sys/stat.h> +#include <sys/types.h> +#include <unistd.h> namespace qpid { namespace broker { using namespace std; +typedef boost::iostreams::stream<boost::iostreams::file_descriptor> fdstream; -string Daemon::pidFile; -string Daemon::name; - -string Daemon::nameFromArgv0(const char* argv0) { - return string(daemon_ident_from_argv0(const_cast<char*>(argv0))); +namespace { +/** Throw an exception containing msg and strerror if throwIf is true. + * Name is supposed to be reminiscent of perror(). + */ +void terror(bool throwIf, const string& msg, int errNo=errno) { + if (throwIf) + throw Exception(msg + (errNo? ": "+strError(errNo) : string("."))); } -const char* Daemon::getPidFile() { - if (pidFile.empty()) { - const char* home=getenv("HOME"); - if (!home) - throw(Exception("$HOME is not set, cant create $HOME/.qpidd.")); - using namespace boost::filesystem; - path dir = path(home,native) / path(".qpidd", native); - create_directory(dir); - dir /= name; - pidFile = dir.string(); + +struct LockFile : public fdstream { + + LockFile(const std::string& path_, bool create) + : path(path_), fd(-1), created(create) + { + errno = 0; + int flags=create ? O_WRONLY|O_CREAT|O_NOFOLLOW : O_RDWR; + fd = ::open(path.c_str(), flags, 0644); + terror(fd < 0,"Cannot open "+path); + terror(::lockf(fd, F_TLOCK, 0) < 0, "Cannot lock "+path); + open(boost::iostreams::file_descriptor(fd)); } - return pidFile.c_str(); -} -Daemon::Daemon(const string& name_, int secs) : pid(-1), timeout(secs) -{ - name = name_; - daemon_pid_file_ident = daemon_log_ident = name.c_str(); - if (getuid() != 0) { - // For normal users put pid file under $HOME/.qpid - daemon_pid_file_proc = getPidFile; + ~LockFile() { + if (fd >= 0) { + ::lockf(fd, F_ULOCK, 0); + close(); + } } - // For root use the libdaemon default: /var/run. + + std::string path; + int fd; + bool created; +}; + +} // namespace + +Daemon::Daemon() { + pid = -1; + pipeFds[0] = pipeFds[1] = -1; } -Daemon::~Daemon() { - if (isChild()) - daemon_pid_file_remove(); +string Daemon::dir() { + return (getuid() == 0 ? "/var/run" : "/tmp"); } -class Daemon::Retval { - public: - Retval(); - ~Retval(); - int send(int s); - int wait(int timeout); - private: - bool completed; -}; +string Daemon::pidFile(uint16_t port) { + ostringstream path; + path << dir() << "/qpidd." << port << ".pid"; + return path.str(); +} -pid_t Daemon::fork() { - retval.reset(new Retval()); - pid = daemon_fork(); - if (pid < 0) - throw Exception("Failed to fork daemon: "+strError(errno)); - else if (pid > 0) { - int ret = retval->wait(timeout); // parent, wait for child. - if (ret != 0) { - string err; - if (ret > 0) - err = strError(ret); - else if (ret == -1) - err= strError(errno); - else - err= "unknown error"; - throw Exception("Deamon startup failed: "+err); +void Daemon::fork() +{ + terror(pipe(pipeFds) < 0, "Can't create pipe"); + terror((pid = ::fork()) < 0, "Daemon fork failed"); + if (pid == 0) { // Child + try { + // File descriptors + terror(::close(pipeFds[0])<0, "Cannot close read pipe"); + terror(::close(0)<0, "Cannot close stdin"); + terror(::close(1)<0, "Cannot close stdout"); + terror(::close(2)<0, "Cannot close stderr"); + int fd=::open("/dev/null",O_RDWR); // stdin + terror(fd != 0, "Cannot re-open stdin"); + terror(::dup(fd)<0, "Cannot re-open stdout"); + terror(::dup(fd)<0, "Cannot re-open stderror"); + + // Misc + terror(setsid()<0, "Cannot set session ID"); + terror(chdir(dir().c_str()) < 0, "Cannot change directory to "+dir()); + umask(027); + + // Child behavior + child(); + } + catch (const exception& e) { + fdstream pipe(pipeFds[1]); + assert(pipe.is_open()); + pipe << "0 " << e.what() << endl; } } - else if (pid == 0) { // child. - // TODO aconway 2007-04-26: Should log failures. - if (daemon_pid_file_create()) - failed(); + else { // Parent + close(pipeFds[1]); // Write side. + parent(); } - return pid; } -void Daemon::notify(int i) { - assert(retval); - if (retval->send(i)) - throw Exception("Failed to notify parent: "+strError(errno)); +Daemon::~Daemon() { + if (!lockFile.empty()) + unlink(lockFile.c_str()); } -void Daemon::ready() { notify(0); } - -// NB: Not -1, confused with failure of fork() on the parent side. -void Daemon::failed() { notify(errno? errno:-2); } - -void Daemon::quit() { - if (daemon_pid_file_kill_wait(SIGINT, timeout)) - throw Exception("Failed to stop daemon: " + strError(errno)); +uint16_t Daemon::wait(int timeout) { // parent waits for child. + errno = 0; + struct timeval tv; + tv.tv_sec = timeout; + tv.tv_usec = 0; + + fd_set fds; + FD_ZERO(&fds); + FD_SET(pipeFds[0], &fds); + terror(1 != select(FD_SETSIZE, &fds, 0, 0, &tv), "No response from daemon process"); + + fdstream pipe(pipeFds[0]); + uint16_t value = 0; + pipe >> value >> skipws; + if (value == 0) { + string errmsg; + getline(pipe, errmsg); + throw Exception("Daemon startup failed"+ (errmsg.empty() ? string(".") : ": " + errmsg)); + } + return value; } -void Daemon::kill() { - if (daemon_pid_file_kill_wait(SIGKILL, timeout) < 0) - throw Exception("Failed to stop daemon: " + strError(errno)); +void Daemon::ready(uint16_t port) { // child + lockFile = pidFile(port); + LockFile lf(lockFile, true); + lf << getpid() << endl; + if (lf.fail()) + throw Exception("Cannot write lock file "+lockFile); + fdstream pipe(pipeFds[1]); + pipe << port << endl;; } -pid_t Daemon::check() { - return daemon_pid_file_is_running(); +pid_t Daemon::getPid(uint16_t port) { + string name = pidFile(port); + LockFile lockFile(name, false); + pid_t pid; + lockFile >> pid; + if (lockFile.fail()) + throw Exception("Cannot read lock file "+name); + if (kill(pid, 0) < 0 && errno != EPERM) { + unlink(name.c_str()); + throw Exception("Removing stale lock file "+name); + } + return pid; } -Daemon::Retval::Retval() : completed(false) { - daemon_retval_init(); -} -Daemon::Retval::~Retval() { - if (!completed) daemon_retval_done(); -} -int Daemon::Retval::send(int s) { - return daemon_retval_send(s); -} -int Daemon::Retval::wait(int timeout) { - return daemon_retval_wait(timeout); -} }} // namespace qpid::broker diff --git a/cpp/lib/broker/Daemon.h b/cpp/lib/broker/Daemon.h index cf9bf13764..821334c11e 100644 --- a/cpp/lib/broker/Daemon.h +++ b/cpp/lib/broker/Daemon.h @@ -21,6 +21,8 @@ #include <string> #include <boost/scoped_ptr.hpp> +#include <boost/function.hpp> +#include <boost/noncopyable.hpp> namespace qpid { namespace broker { @@ -29,63 +31,51 @@ namespace broker { * Tools for forking and managing a daemon process. * NB: Only one Daemon instance is allowed in a process. */ -class Daemon +class Daemon : private boost::noncopyable { public: + /** Check daemon is running on port, throw exception if not */ + static pid_t getPid(uint16_t port); + + Daemon(); + + virtual ~Daemon(); - /** Extract the daemon's name from argv[0] */ - static std::string nameFromArgv0(const char* argv0); - /** - * Creating a Daemon instance forks a daemon process. - *@param name used to create pid files etc. - *@param timeout in seconds for all operations that wait. + * Fork a daemon process. + * Call parent() in the parent process, child() in the child. */ - Daemon(const std::string& name, int timeout); + void fork(); - ~Daemon(); - - /** Fork the daemon, wait till it signals readiness */ - pid_t fork(); + protected: - /** Child only, send ready signal so parent fork() will return. */ - void ready(); - - /** Child only, send failed signal so parent fork() will throw. */ - void failed(); - - /** Kill the daemon with SIGINT. */ - void quit(); - - /** Kill the daemon with SIGKILL. */ - void kill(); + /** Called in parent process */ + virtual void parent() = 0; - /** Check daemon is running, throw exception if not */ - pid_t check(); + /** Called in child process */ + virtual void child() = 0; - bool isParent() { return pid > 0; } + /** Call from parent(): wait for child to indicate it is ready. + * @timeout in seconds to wait for response. + * @return port passed by child to ready(). + */ + uint16_t wait(int timeout); - bool isChild() { return pid == 0; } + /** Call from child(): Notify the parent we are ready and write the + * PID file. + *@param port returned by parent call to wait(). + */ + void ready(uint16_t port); - std::string getName() const { return name; } - - pid_t getPid() const {return pid; } - private: - class Retval; + static std::string dir(); + static std::string pidFile(uint16_t port); - void notify(int); - - static std::string name; - static std::string pidFile; - static const char* getPidFile(); - boost::scoped_ptr<Retval> retval; pid_t pid; - int timeout; + int pipeFds[2]; + std::string lockFile; }; }} // namespace qpid::broker - - #endif /*!_broker_Daemon_h*/ diff --git a/cpp/lib/broker/Makefile.am b/cpp/lib/broker/Makefile.am index b0d655c872..1300e1edf0 100644 --- a/cpp/lib/broker/Makefile.am +++ b/cpp/lib/broker/Makefile.am @@ -28,7 +28,7 @@ lib_LTLIBRARIES = libqpidbroker.la libqpidbroker_la_LIBADD = \ ../common/libqpidcommon.la \ -ldaemon \ - -lboost_filesystem + -lboost_iostreams libqpidbroker_la_LDFLAGS = -version-info $(LIBTOOL_VERSION_INFO_ARG) libqpidbroker_la_SOURCES = \ diff --git a/cpp/src/qpidd.cpp b/cpp/src/qpidd.cpp index e199c72683..45da8c019f 100644 --- a/cpp/src/qpidd.cpp +++ b/cpp/src/qpidd.cpp @@ -33,14 +33,6 @@ using namespace qpid::broker; using namespace qpid::sys; using namespace std; -Broker::shared_ptr brokerPtr; - -void handle_signal(int /*signal*/){ - std::cerr << "Shutting down..." << std::endl; - brokerPtr->shutdown(); -} - - /** Command line options */ struct QpiddOptions : public Broker::Options { @@ -50,14 +42,13 @@ struct QpiddOptions : public Broker::Options bool quit; bool kill; bool check; - bool ppid; int wait; string config; po::options_description desc; - + QpiddOptions() : help(false), version(false), daemon(false), - quit(false), kill(false), check(false), ppid(false), wait(10), + quit(false), kill(false), check(false), wait(10), config("/etc/qpidd.conf"), desc("Options") { @@ -66,10 +57,8 @@ struct QpiddOptions : public Broker::Options ("daemon,d", optValue(daemon), "Run as a daemon.") ("quit,q", optValue(quit), "Stop the running daemon politely.") ("kill,k", optValue(kill), "Kill the running daemon harshly.") - ("check,c", optValue(check), "If daemon is running return 0.") - ("wait", optValue(wait, "SECONDS"), - "Maximum wait for daemon response.") - ("ppid", optValue(ppid), "Print daemon pid to stdout" ); + ("check,c", optValue(check), "If daemon is running print PID and return 0.") + ("wait", optValue(wait, "SECONDS"), "Maximum wait for daemon response."); po::options_description brokerOpts; Broker::Options::addTo(desc); desc.add_options() @@ -93,13 +82,13 @@ struct QpiddOptions : public Broker::Options try { ifstream conf(config.c_str()); po::store(po::parse_config_file(conf, desc), vm); - } + } catch (const logic_error& e) { throw logic_error(string("parsing config file: ")+ e.what()); } po::notify(vm); - }; - +}; + void usage(ostream& out) const { out << "Usage: qpidd [OPTIONS]" << endl << endl << desc << endl; @@ -110,80 +99,86 @@ ostream& operator<<(ostream& out, const QpiddOptions& config) { config.usage(out); return out; } +// Globals +Broker::shared_ptr brokerPtr; +QpiddOptions options; + +void shutdownHandler(int /*signal*/){ + brokerPtr->shutdown(); +} + +struct QpiddDaemon : public Daemon { + /** Code for parent process */ + void parent() { + uint16_t port = wait(options.wait); + if (options.port == 0) + cout << port << endl; + } + + /** Code for forked child process */ + void child() { + brokerPtr = Broker::create(options); + uint16_t port=brokerPtr->getPort(); + ready(port); // Notify parent. + brokerPtr->run(); + } +}; + + int main(int argc, char* argv[]) { - QpiddOptions config; try { - config.parse(argc, argv); - string name=(format("%s.%d") - % Daemon::nameFromArgv0(argv[0]) - % (config.port)).str(); - // Spelled 'demon' to avoid clash with daemon.h function. - Daemon demon(name, config.wait); + options.parse(argc, argv); // Options that just print information. - if(config.help) { - config.usage(cout); - return 0; - } - if (config.version) { - cout << "qpidd (" << PACKAGE_NAME << ") version " - << PACKAGE_VERSION << endl; + if(options.help || options.version) { + if (options.version) + cout << "qpidd (" << PACKAGE_NAME << ") version " + << PACKAGE_VERSION << endl; + else if (options.help) + options.usage(cout); return 0; } - // Options that affect an already running daemon. - if (config.quit || config.kill || config.check) { - pid_t pid = demon.check(); - if (config.ppid && pid > 0) - cout << pid << endl; - if (config.kill) - demon.kill(); - else if (config.quit) - demon.quit(); - if (config.check && pid <= 0) + // Options that affect a running daemon. + if (options.check || options.quit) { + pid_t pid = Daemon::getPid(options.port); + if (pid < 0) return 1; + if (options.check) + cout << pid << endl; + if (options.quit && kill(pid, SIGINT) < 0) + throw Exception("Failed to stop daemon: " + strError(errno)); return 0; } - // Starting the broker: - signal(SIGINT, handle_signal); - if (config.daemon) { - pid_t pid = demon.fork(); - if (pid == 0) { // Child - try { - brokerPtr=Broker::create(config); - demon.ready(); // Notify parent we're ready. - brokerPtr->run(); - } catch (const exception& e) { - // TODO aconway 2007-04-26: Log this, cerr is lost. - cerr << "Broker daemon failed: " << e.what() << endl; - demon.failed(); // Notify parent we failed. - return 1; - } - } - else if (pid > 0) { // Parent - if (config.ppid) - cout << pid << endl; - return 0; - } - else { // pid < 0 - throw Exception("fork failed"+strError(errno)); - } - } // Non-daemon broker. - else { - brokerPtr = Broker::create(config); + // Starting the broker. + + // Signal handling + signal(SIGINT,shutdownHandler); + signal(SIGTERM,shutdownHandler); + signal(SIGHUP,SIG_IGN); // TODO aconway 2007-07-18: reload config. + + signal(SIGCHLD,SIG_IGN); + signal(SIGTSTP,SIG_IGN); + signal(SIGTTOU,SIG_IGN); + signal(SIGTTIN,SIG_IGN); + + if (options.daemon) { + // Fork the daemon + QpiddDaemon d; + d.fork(); + } + else { // Non-daemon broker. + brokerPtr = Broker::create(options); + if (options.port == 0) + cout << uint16_t(brokerPtr->getPort()) << endl; brokerPtr->run(); } return 0; } - catch(const po::error& e) { - // Command line parsing error. - cerr << "Error: " << e.what() << endl - << "Type 'qpidd --help' for usage." << endl; - } catch(const exception& e) { - cerr << "Error: " << e.what() << endl; + cerr << e.what() << endl; } return 1; } diff --git a/cpp/tests/daemon_test b/cpp/tests/daemon_test index 0800ee9cd5..05e9ed88a0 100755 --- a/cpp/tests/daemon_test +++ b/cpp/tests/daemon_test @@ -29,14 +29,9 @@ client_test=./client_test fail() { echo FAIL: $0:$* 1>&2; exit 1; } # Start and stop daemon. -PID=`$qpidd --check --ppid` && fail $LINENO: qpidd already running $PID +PID=`$qpidd --check>/dev/null 2>&1` && fail $LINENO: qpidd already running $PID $qpidd -d || $LINENO: qpidd -d failed -$qpidd --check || fail $LINENO: qpidd --check says qpidd didnt start +$qpidd --check >/dev/null || fail $LINENO: qpidd --check says qpidd didnt start $client_test > $TEMP || fail $LINENO: client_test: `cat $TEMP` $qpidd -q || fail $LINENO: qpidd -q failed -$qpidd -d || fail $LINENO: restart after quit failed. -$qpidd -k || fail $LINENO: qpidd -k failed -# Supress expected message re. cleanup of old PID file. -PID=`$qpidd --check --ppid 2>/dev/null` && fail $LINENO: $PID still running after kill. - true |