blob: c36538beb7c152bb9c0b70372d668531b1e61e50 [file] [log] [blame]
/*
*
* Copyright (c) 2006 The Apache Software Foundation
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
*/
/*
* TODO: Note this is really a Posix specific implementation and so should be
* refactored together with windows/QpiddBroker into a more coherent daemon driver/
* platform specific split
*/
#include "qpid/broker/Daemon.h"
#include "qpid/log/Statement.h"
#include "qpid/Exception.h"
#include "qpid/sys/posix/PidFile.h"
#include <errno.h>
#include <fcntl.h>
#include <signal.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <unistd.h>
namespace qpid {
namespace broker {
using namespace std;
using qpid::sys::PidFile;
Daemon::Daemon(std::string _pidDir) : pidDir(_pidDir) {
struct stat s;
pid = -1;
pipeFds[0] = pipeFds[1] = -1;
if (::stat(pidDir.c_str(), &s)) {
if (errno == ENOENT) {
if (::mkdir(pidDir.c_str(), 0755))
throw Exception ("Can't create PID directory: " + pidDir);
}
else
throw Exception ("PID directory not found: " + pidDir);
}
}
string Daemon::pidFile(string pidDir, uint16_t port) {
ostringstream path;
path << pidDir << "/qpidd." << port << ".pid";
return path.str();
}
/*
* Rewritten using low-level IO, for compatibility
* with earlier Boost versions, i.e. 103200.
*/
void Daemon::fork()
{
if(::pipe(pipeFds) < 0) throw ErrnoException("Can't create pipe");
if ((pid = ::fork()) < 0) throw ErrnoException("Daemon fork failed");
if (pid == 0) { // Child
try {
QPID_LOG(debug, "Forked daemon child process");
// File descriptors
if(::close(pipeFds[0])<0) throw ErrnoException("Cannot close read pipe");
if(::close(0)<0) throw ErrnoException("Cannot close stdin");
if(::close(1)<0) throw ErrnoException("Cannot close stdout");
if(::close(2)<0) throw ErrnoException("Cannot close stderr");
int fd=::open("/dev/null",O_RDWR); // stdin
if(fd != 0) throw ErrnoException("Cannot re-open stdin");
if(::dup(fd)<0) throw ErrnoException("Cannot re-open stdout");
if(::dup(fd)<0) throw ErrnoException("Cannot re-open stderror");
// Misc
if(setsid()<0) throw ErrnoException("Cannot set session ID");
if(chdir(pidDir.c_str()) < 0) throw ErrnoException("Cannot change directory to "+pidDir);
umask(027);
// Child behavior
child();
}
catch (const exception& e) {
QPID_LOG(critical, "Unexpected error: " << e.what());
uint16_t port = 0;
(void) write(pipeFds[1], &port, sizeof(uint16_t));
std::string pipeFailureMessage = e.what();
(void) write ( pipeFds[1],
pipeFailureMessage.c_str(),
strlen(pipeFailureMessage.c_str())
);
}
}
else { // Parent
close(pipeFds[1]); // Write side.
parent();
}
}
Daemon::~Daemon() {
if (!lockFile.empty())
unlink(lockFile.c_str());
}
uint16_t Daemon::wait(int timeout) { // parent waits for child.
try {
errno = 0;
struct timeval tv;
tv.tv_sec = timeout;
tv.tv_usec = 0;
/*
* Rewritten using low-level IO, for compatibility
* with earlier Boost versions, i.e. 103200.
*/
fd_set fds;
FD_ZERO(&fds);
FD_SET(pipeFds[0], &fds);
int n=select(FD_SETSIZE, &fds, 0, 0, &tv);
if(n==0) throw Exception("Timed out waiting for daemon (If store recovery is in progress, use longer wait time)");
if(n<0) throw ErrnoException("Error waiting for daemon");
uint16_t port = 0;
/*
* Read the child's port number from the pipe.
*/
int desired_read = sizeof(uint16_t);
if ( desired_read > ::read(pipeFds[0], & port, desired_read) )
throw Exception("Cannot read from child process.");
/*
* If the port number is 0, the child has put an error message
* on the pipe. Get it and throw it.
*/
if ( 0 == port ) {
// Skip whitespace
char c = ' ';
while ( isspace(c) ) {
if ( 1 > ::read(pipeFds[0], &c, 1) )
throw Exception("Child port == 0, and no error message on pipe.");
}
// Get Message
string errmsg;
do {
errmsg += c;
} while (::read(pipeFds[0], &c, 1));
throw Exception("Daemon startup failed"+
(errmsg.empty() ? string(".") : ": " + errmsg));
}
return port;
}
catch (const std::exception& e) {
// Print directly to cerr. The caller will catch and log the
// exception, but in the case of a daemon parent process we
// also need to be sure the error goes to stderr. A
// dameon's logging configuration normally does not log to
// stderr.
std::cerr << e.what() << endl;
throw;
}
}
/*
* When the child is ready, it writes its pid to the
* lockfile and its port number on the pipe back to
* its parent process. This indicates that the
* child has successfully daemonized. When the parent
* hears the good news, it ill exit.
*/
void Daemon::ready(uint16_t port) { // child
lockFile = pidFile(pidDir, port);
PidFile lf(lockFile, true);
/*
* Write the PID to the lockfile.
*/
lf.writePid();
/*
* Write the port number to the parent.
*/
int desired_write = sizeof(uint16_t);
if ( desired_write > ::write(pipeFds[1], & port, desired_write) ) {
throw Exception("Error writing to parent." );
}
QPID_LOG(debug, "Daemon ready on port: " << port);
}
/*
* The parent process reads the child's pid
* from the lockfile.
*/
pid_t Daemon::getPid(string _pidDir, uint16_t port) {
string name = pidFile(_pidDir, port);
PidFile lf(name, false);
pid_t pid = lf.readPid();
if (kill(pid, 0) < 0 && errno != EPERM) {
unlink(name.c_str());
throw Exception("Removing stale lock file "+name);
}
return pid;
}
}} // namespace qpid::broker