384 lines
11 KiB
C++
384 lines
11 KiB
C++
/*
|
|
* Copyright (C) 2005-2009 Patrick Ohly <patrick.ohly@gmx.de>
|
|
* Copyright (C) 2009 Intel Corporation
|
|
*
|
|
* This library is free software; you can redistribute it and/or
|
|
* modify it under the terms of the GNU Lesser General Public
|
|
* License as published by the Free Software Foundation; either
|
|
* version 2.1 of the License, or (at your option) version 3.
|
|
*
|
|
* This library 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
|
|
* Lesser General Public License for more details.
|
|
*
|
|
* You should have received a copy of the GNU Lesser General Public
|
|
* License along with this library; if not, write to the Free Software
|
|
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
|
|
* 02110-1301 USA
|
|
*/
|
|
|
|
#include <syncevo/SuspendFlags.h>
|
|
#include <syncevo/util.h>
|
|
#include <syncevo/ThreadSupport.h>
|
|
#include <synthesis/syerror.h>
|
|
|
|
#include <errno.h>
|
|
#include <unistd.h>
|
|
#include <fcntl.h>
|
|
#include <signal.h>
|
|
#include <string.h>
|
|
|
|
#include <glib.h>
|
|
|
|
#include <syncevo/declarations.h>
|
|
SE_BEGIN_CXX
|
|
|
|
static RecMutex suspendRecMutex;
|
|
|
|
SuspendFlags::SuspendFlags() :
|
|
m_level(Logger::INFO),
|
|
m_state(NORMAL),
|
|
m_receivedSignals(0),
|
|
m_lastSuspend(0),
|
|
m_senderFD(-1),
|
|
m_receiverFD(-1),
|
|
m_activeSignals(0)
|
|
{
|
|
}
|
|
|
|
SuspendFlags::~SuspendFlags()
|
|
{
|
|
deactivate();
|
|
}
|
|
|
|
SuspendFlags &SuspendFlags::getSuspendFlags()
|
|
{
|
|
// never free the instance, other singletons might depend on it
|
|
RecMutex::Guard guard = suspendRecMutex.lock();
|
|
static SuspendFlags *flags;
|
|
if (!flags) {
|
|
flags = new SuspendFlags;
|
|
}
|
|
return *flags;
|
|
}
|
|
|
|
static gboolean SignalChannelReadyCB(GIOChannel *source,
|
|
GIOCondition condition,
|
|
gpointer data) throw()
|
|
{
|
|
try {
|
|
RecMutex::Guard guard = suspendRecMutex.lock();
|
|
SuspendFlags &me = SuspendFlags::getSuspendFlags();
|
|
me.printSignals();
|
|
} catch (...) {
|
|
Exception::handle();
|
|
}
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
/**
|
|
* Own glib IO watch for file descriptor
|
|
* which calls printSignals()
|
|
*/
|
|
class GLibGuard : public SuspendFlags::Guard
|
|
{
|
|
GIOChannel *m_channel;
|
|
guint m_channelReady;
|
|
|
|
public:
|
|
GLibGuard(int fd)
|
|
{
|
|
// glib watch which calls printSignals()
|
|
m_channel = g_io_channel_unix_new(fd);
|
|
m_channelReady = g_io_add_watch(m_channel, G_IO_IN, SignalChannelReadyCB, NULL);
|
|
}
|
|
|
|
~GLibGuard()
|
|
{
|
|
if (m_channelReady) {
|
|
g_source_remove(m_channelReady);
|
|
m_channelReady = 0;
|
|
}
|
|
if (m_channel) {
|
|
g_io_channel_unref(m_channel);
|
|
m_channel = NULL;
|
|
}
|
|
}
|
|
};
|
|
|
|
SuspendFlags::State SuspendFlags::getState() const {
|
|
RecMutex::Guard guard = suspendRecMutex.lock();
|
|
if (m_abortBlocker.lock()) {
|
|
// active abort blocker
|
|
return ABORT;
|
|
} else if (m_suspendBlocker.lock()) {
|
|
// active suspend blocker
|
|
return SUSPEND;
|
|
} else {
|
|
return m_state;
|
|
}
|
|
}
|
|
|
|
uint32_t SuspendFlags::getReceivedSignals() const {
|
|
RecMutex::Guard guard = suspendRecMutex.lock();
|
|
return m_receivedSignals;
|
|
}
|
|
|
|
Logger::Level SuspendFlags::getLevel() const {
|
|
RecMutex::Guard guard = suspendRecMutex.lock();
|
|
return m_level;
|
|
}
|
|
|
|
void SuspendFlags::setLevel(Logger::Level level) {
|
|
RecMutex::Guard guard = suspendRecMutex.lock();
|
|
m_level = level;
|
|
}
|
|
|
|
bool SuspendFlags::isAborted()
|
|
{
|
|
RecMutex::Guard guard = suspendRecMutex.lock();
|
|
printSignals();
|
|
return getState() == ABORT;
|
|
}
|
|
|
|
bool SuspendFlags::isSuspended()
|
|
{
|
|
RecMutex::Guard guard = suspendRecMutex.lock();
|
|
printSignals();
|
|
return getState() == SUSPEND;
|
|
}
|
|
|
|
bool SuspendFlags::isNormal()
|
|
{
|
|
RecMutex::Guard guard = suspendRecMutex.lock();
|
|
printSignals();
|
|
return getState() == NORMAL;
|
|
}
|
|
|
|
void SuspendFlags::checkForNormal()
|
|
{
|
|
RecMutex::Guard guard = suspendRecMutex.lock();
|
|
printSignals();
|
|
if (getState() != NORMAL) {
|
|
SE_THROW_EXCEPTION_STATUS(StatusException,
|
|
"aborting as requested by user",
|
|
(SyncMLStatus)sysync::LOCERR_USERABORT);
|
|
}
|
|
}
|
|
|
|
boost::shared_ptr<SuspendFlags::StateBlocker> SuspendFlags::suspend() { return block(m_suspendBlocker); }
|
|
boost::shared_ptr<SuspendFlags::StateBlocker> SuspendFlags::abort() { return block(m_abortBlocker); }
|
|
boost::shared_ptr<SuspendFlags::StateBlocker> SuspendFlags::block(boost::weak_ptr<StateBlocker> &blocker)
|
|
{
|
|
RecMutex::Guard guard = suspendRecMutex.lock();
|
|
State oldState = getState();
|
|
boost::shared_ptr<StateBlocker> res = blocker.lock();
|
|
if (!res) {
|
|
res.reset(new StateBlocker);
|
|
blocker = res;
|
|
}
|
|
State newState = getState();
|
|
// only alert receiving side if going from normal -> suspend
|
|
// or suspend -> abort
|
|
if (newState > oldState &&
|
|
m_senderFD >= 0) {
|
|
unsigned char msg = newState;
|
|
// Retry on some errors, ignore others.
|
|
while (write(m_senderFD, &msg, 1) != 1 &&
|
|
(errno == EAGAIN || errno==EINTR))
|
|
{}
|
|
}
|
|
// don't depend on pipes or detecting that change, alert
|
|
// listeners directly
|
|
if (newState != oldState) {
|
|
m_stateChanged(*this);
|
|
}
|
|
return res;
|
|
}
|
|
|
|
boost::shared_ptr<SuspendFlags::Guard> SuspendFlags::activate(uint32_t sigmask)
|
|
{
|
|
SE_LOG_DEBUG(NULL, "SuspendFlags: (re)activating, currently %s",
|
|
m_senderFD > 0 ? "active" : "inactive");
|
|
if (m_senderFD > 0) {
|
|
return m_guard.lock();
|
|
}
|
|
|
|
int fds[2];
|
|
if (pipe(fds)) {
|
|
SE_THROW(StringPrintf("allocating pipe for signals failed: %s", strerror(errno)));
|
|
}
|
|
// nonblocking, to avoid deadlocks when the pipe's buffer overflows
|
|
fcntl(fds[0], F_SETFL, fcntl(fds[0], F_GETFL) | O_NONBLOCK);
|
|
fcntl(fds[1], F_SETFL, fcntl(fds[1], F_GETFL) | O_NONBLOCK);
|
|
m_senderFD = fds[1];
|
|
m_receiverFD = fds[0];
|
|
SE_LOG_DEBUG(NULL, "SuspendFlags: activating signal handler(s) with fds %d->%d",
|
|
m_senderFD, m_receiverFD);
|
|
for (int sig = 0; sig < 32; sig++) {
|
|
if (sigmask & (1<<sig)) {
|
|
sigaction(sig, NULL, m_oldSignalHandlers + sig);
|
|
}
|
|
}
|
|
|
|
struct sigaction new_action;
|
|
memset(&new_action, 0, sizeof(new_action));
|
|
new_action.sa_handler = handleSignal;
|
|
sigemptyset(&new_action.sa_mask);
|
|
// don't let processing of SIGINT be interrupted
|
|
// of SIGTERM and vice versa, if we are doing the
|
|
// handling
|
|
for (int sig = 0; sig < 32; sig++) {
|
|
if (sigmask & (1<<sig)) {
|
|
if (m_oldSignalHandlers[sig].sa_handler == SIG_DFL) {
|
|
sigaddset(&new_action.sa_mask, sig);
|
|
}
|
|
}
|
|
}
|
|
|
|
for (int sig = 0; sig < 32; sig++) {
|
|
if (sigmask & (1<<sig)) {
|
|
if (m_oldSignalHandlers[sig].sa_handler == SIG_DFL) {
|
|
sigaction(sig, &new_action, NULL);
|
|
SE_LOG_DEBUG(NULL, "SuspendFlags: catch signal %d", sig);
|
|
}
|
|
}
|
|
}
|
|
m_activeSignals = sigmask;
|
|
boost::shared_ptr<Guard> guard(new GLibGuard(m_receiverFD));
|
|
m_guard = guard;
|
|
|
|
return guard;
|
|
}
|
|
|
|
void SuspendFlags::deactivate()
|
|
{
|
|
SE_LOG_DEBUG(NULL, "SuspendFlags: deactivating fds %d->%d",
|
|
m_senderFD, m_receiverFD);
|
|
if (m_receiverFD >= 0) {
|
|
for (int sig = 0; sig < 32; sig++) {
|
|
if (m_activeSignals & (1<<sig)) {
|
|
sigaction(sig, m_oldSignalHandlers + sig, NULL);
|
|
}
|
|
}
|
|
m_activeSignals = 0;
|
|
SE_LOG_DEBUG(NULL, "SuspendFlags: close m_receiverFD %d", m_receiverFD);
|
|
close(m_receiverFD);
|
|
SE_LOG_DEBUG(NULL, "SuspendFlags: close m_senderFD %d", m_senderFD);
|
|
close(m_senderFD);
|
|
m_receiverFD = -1;
|
|
m_senderFD = -1;
|
|
m_guard.reset();
|
|
SE_LOG_DEBUG(NULL, "SuspendFlags: done with deactivation");
|
|
}
|
|
}
|
|
|
|
void SuspendFlags::handleSignal(int sig)
|
|
{
|
|
SuspendFlags &me(getSuspendFlags());
|
|
|
|
// can't use logging infrastructure in signal handler,
|
|
// not reentrant
|
|
|
|
unsigned char msg[2];
|
|
switch (sig) {
|
|
case SIGTERM:
|
|
switch (me.m_state) {
|
|
case ABORT:
|
|
msg[1] = ABORT_AGAIN;
|
|
break;
|
|
default:
|
|
msg[1] = me.m_state = ABORT;
|
|
break;
|
|
}
|
|
break;
|
|
case SIGINT: {
|
|
time_t current;
|
|
time (¤t);
|
|
switch (me.m_state) {
|
|
case NORMAL:
|
|
// first time suspend or already aborted
|
|
msg[1] = me.m_state = SUSPEND;
|
|
me.m_lastSuspend = current;
|
|
break;
|
|
case SUSPEND:
|
|
// turn into abort?
|
|
if (current - me.m_lastSuspend < ABORT_INTERVAL) {
|
|
msg[1] = me.m_state = ABORT;
|
|
} else {
|
|
me.m_lastSuspend = current;
|
|
msg[1] = SUSPEND_AGAIN;
|
|
}
|
|
break;
|
|
case ABORT:
|
|
msg[1] = ABORT_AGAIN;
|
|
break;
|
|
case ABORT_AGAIN:
|
|
case SUSPEND_AGAIN:
|
|
case ABORT_MAX:
|
|
// shouldn't happen
|
|
msg[1] = ABORT_MAX;
|
|
break;
|
|
}
|
|
default:
|
|
msg[1] = ABORT_MAX;
|
|
break;
|
|
}
|
|
}
|
|
if (me.m_senderFD >= 0) {
|
|
msg[0] = (unsigned char)(ABORT_MAX + sig);
|
|
size_t left = msg[1] == ABORT_MAX ? 1 : 2;
|
|
while (left) {
|
|
ssize_t written = write(me.m_senderFD, msg, left);
|
|
if (written > 0) {
|
|
left -= written;
|
|
} else {
|
|
if (errno != EAGAIN &&
|
|
errno != EINTR) {
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
void SuspendFlags::printSignals()
|
|
{
|
|
RecMutex::Guard guard = suspendRecMutex.lock();
|
|
if (m_receiverFD >= 0) {
|
|
unsigned char msg;
|
|
while (read(m_receiverFD, &msg, 1) == 1) {
|
|
SE_LOG_DEBUG(NULL, "SuspendFlags: read %d from fd %d",
|
|
msg, m_receiverFD);
|
|
const char *str = NULL;
|
|
switch (msg) {
|
|
case SUSPEND:
|
|
str = "Asking to suspend...\nPress CTRL-C again quickly (within 2s) to stop immediately (can cause problems in the future!)";
|
|
break;
|
|
case SUSPEND_AGAIN:
|
|
str = "Suspend in progress...\nPress CTRL-C again quickly (within 2s) to stop immediately (can cause problems in the future!)";
|
|
break;
|
|
case ABORT:
|
|
str = "Aborting immediately ...";
|
|
break;
|
|
case ABORT_AGAIN:
|
|
str = "Already aborting as requested earlier ...";
|
|
break;
|
|
default: {
|
|
int sig = msg - ABORT_MAX;
|
|
SE_LOG_DEBUG(NULL, "reveived signal %d", sig);
|
|
m_receivedSignals |= 1<<sig;
|
|
}
|
|
}
|
|
if (str) {
|
|
SE_LOG(NULL, m_level, "%s", str);
|
|
}
|
|
m_stateChanged(*this);
|
|
}
|
|
}
|
|
}
|
|
|
|
SE_END_CXX
|