/* Copyright (C) 2004 J.F.Dockes
* 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.,
* 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
*/
#include "autoconfig.h"
#include <stdio.h>
#ifdef _WIN32
#include "safewindows.h"
#endif
#include <signal.h>
#include <locale.h>
#include <cstdlib>
#if !defined(PUTENV_ARG_CONST)
#include <string.h>
#endif
#include <thread>
#include "log.h"
#include "rclconfig.h"
#include "rclinit.h"
#include "pathut.h"
#include "rclutil.h"
#include "unac.h"
#include "smallut.h"
#include "execmd.h"
std::thread::id mainthread_id;
// Signal etc. processing. We want to be able to properly close the
// index if we are currently writing to it.
//
// This is active if the sigcleanup parameter to recollinit is set,
// which only recollindex does. We arrange for the handler to be
// called when process termination is requested either by the system
// or a user keyboard intr.
//
// The recollindex handler just sets a global termination flag (plus
// the cancelcheck thing), which are tested in all timeout loops
// etc. When the flag is seen, the main thread processing returns, and
// recollindex calls exit().
//
// The other parameter, to recollinit(), cleanup, is set as an
// atexit() routine, it does the job of actually signalling the
// workers to stop and tidy up. It's automagically called by exit().
#ifndef _WIN32
static void siglogreopen(int)
{
if (recoll_ismainthread())
Logger::getTheLog("")->reopen("");
}
// We would like to block SIGCHLD globally, but we can't because
// QT uses it. Have to block it inside execmd.cpp
static const int catchedSigs[] = {SIGINT, SIGQUIT, SIGTERM, SIGUSR1, SIGUSR2};
void initAsyncSigs(void (*sigcleanup)(int))
{
// We ignore SIGPIPE always. All pieces of code which can write to a pipe
// must check write() return values.
signal(SIGPIPE, SIG_IGN);
// Install app signal handler
if (sigcleanup) {
struct sigaction action;
action.sa_handler = sigcleanup;
action.sa_flags = 0;
sigemptyset(&action.sa_mask);
for (unsigned int i = 0; i < sizeof(catchedSigs) / sizeof(int); i++)
if (signal(catchedSigs[i], SIG_IGN) != SIG_IGN) {
if (sigaction(catchedSigs[i], &action, 0) < 0) {
perror("Sigaction failed");
}
}
}
// Install log rotate sig handler
{
struct sigaction action;
action.sa_handler = siglogreopen;
action.sa_flags = 0;
sigemptyset(&action.sa_mask);
if (signal(SIGHUP, SIG_IGN) != SIG_IGN) {
if (sigaction(SIGHUP, &action, 0) < 0) {
perror("Sigaction failed");
}
}
}
}
void recoll_exitready()
{
}
#else // _WIN32 ->
// Windows signals etc.
//
// ^C can be caught by the signal() emulation, but not ^Break
// apparently, which is why we use the native approach too
//
// When a keyboard interrupt occurs, windows creates a thread inside
// the process and calls the handler. The process exits when the
// handler returns or after at most 10S
//
// This should also work, with different signals (CTRL_LOGOFF_EVENT,
// CTRL_SHUTDOWN_EVENT) when the user exits or the system shuts down).
//
// Unfortunately, this is not the end of the story. It seems that in
// recent Windows version "some kinds" of apps will not reliably
// receive the signals. "Some kind" is variably defined, for example a
// simple test program works when built with vs 2015, but not
// mingw. See the following discussion thread for tentative
// explanations, it seems that importing or not from user32.dll is the
// determining factor.
// https://social.msdn.microsoft.com/Forums/windowsdesktop/en-US/abf09824-4e4c-4f2c-ae1e-5981f06c9c6e/windows-7-console-application-has-no-way-of-trapping-logoffshutdown-event?forum=windowscompatibility
// In any case, it appears that the only reliable way to be advised of
// system shutdown or user exit is to create an "invisible window" and
// process window messages, which we now do.
static void (*l_sigcleanup)(int);
static HANDLE eWorkFinished = INVALID_HANDLE_VALUE;
static BOOL WINAPI CtrlHandler(DWORD fdwCtrlType)
{
LOGDEB("CtrlHandler\n" );
if (l_sigcleanup == 0)
return FALSE;
switch(fdwCtrlType) {
case CTRL_C_EVENT:
case CTRL_CLOSE_EVENT:
case CTRL_BREAK_EVENT:
case CTRL_LOGOFF_EVENT:
case CTRL_SHUTDOWN_EVENT:
{
l_sigcleanup(SIGINT);
LOGDEB0("CtrlHandler: waiting for exit ready\n" );
DWORD res = WaitForSingleObject(eWorkFinished, INFINITE);
if (res != WAIT_OBJECT_0) {
LOGERR("CtrlHandler: exit ack wait failed\n" );
}
LOGDEB0("CtrlHandler: got exit ready event, exiting\n" );
return TRUE;
}
default:
return FALSE;
}
}
LRESULT CALLBACK MainWndProc(HWND hwnd , UINT msg , WPARAM wParam,
LPARAM lParam)
{
switch (msg) {
case WM_QUERYENDSESSION:
case WM_ENDSESSION:
case WM_DESTROY:
case WM_CLOSE:
{
l_sigcleanup(SIGINT);
LOGDEB("MainWndProc: got end message, waiting for work finished\n" );
DWORD res = WaitForSingleObject(eWorkFinished, INFINITE);
if (res != WAIT_OBJECT_0) {
LOGERR("MainWndProc: exit ack wait failed\n" );
}
LOGDEB("MainWindowProc: got exit ready event, exiting\n" );
return TRUE;
}
default:
return DefWindowProc(hwnd, msg, wParam, lParam);
}
return TRUE;
}
bool CreateInvisibleWindow()
{
HWND hwnd;
WNDCLASS wc = {0};
wc.lpfnWndProc = (WNDPROC)MainWndProc;
wc.hInstance = GetModuleHandle(NULL);
wc.hIcon = LoadIcon(GetModuleHandle(NULL), "TestWClass");
wc.lpszClassName = "TestWClass";
RegisterClass(&wc);
hwnd =
CreateWindowEx(0, "TestWClass", "TestWClass", WS_OVERLAPPEDWINDOW,
CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT,
CW_USEDEFAULT, (HWND) NULL, (HMENU) NULL,
GetModuleHandle(NULL), (LPVOID) NULL);
if (!hwnd) {
return FALSE;
}
return TRUE;
}
DWORD WINAPI RunInvisibleWindowThread(LPVOID lpParam)
{
MSG msg;
CreateInvisibleWindow();
while (GetMessage(&msg, (HWND) NULL , 0 , 0)) {
TranslateMessage(&msg);
DispatchMessage(&msg);
}
return 0;
}
static const int catchedSigs[] = {SIGINT, SIGTERM};
void initAsyncSigs(void (*sigcleanup)(int))
{
DWORD tid;
// Install app signal handler
if (sigcleanup) {
l_sigcleanup = sigcleanup;
for (unsigned int i = 0; i < sizeof(catchedSigs) / sizeof(int); i++) {
if (signal(catchedSigs[i], SIG_IGN) != SIG_IGN) {
signal(catchedSigs[i], sigcleanup);
}
}
}
HANDLE hInvisiblethread =
CreateThread(NULL, 0, RunInvisibleWindowThread, NULL, 0, &tid);
SetConsoleCtrlHandler((PHANDLER_ROUTINE)CtrlHandler, TRUE);
eWorkFinished = CreateEvent(NULL, TRUE, FALSE, NULL);
if (eWorkFinished == INVALID_HANDLE_VALUE) {
LOGERR("initAsyncSigs: error creating exitready event\n" );
}
}
void recoll_exitready()
{
LOGDEB("recoll_exitready()\n" );
if (!SetEvent(eWorkFinished)) {
LOGERR("recoll_exitready: SetEvent failed\n" );
}
}
#endif
RclConfig *recollinit(RclInitFlags flags,
void (*cleanup)(void), void (*sigcleanup)(int),
string &reason, const string *argcnf)
{
if (cleanup)
atexit(cleanup);
// Make sure the locale is set. This is only for converting file names
// to utf8 for indexing.
setlocale(LC_CTYPE, "");
Logger::getTheLog("")->setLogLevel(Logger::LLDEB1);
initAsyncSigs(sigcleanup);
RclConfig *config = new RclConfig(argcnf);
if (!config || !config->ok()) {
reason = "Configuration could not be built:\n";
if (config)
reason += config->getReason();
else
reason += "Out of memory ?";
return 0;
}
// Retrieve the log file name and level. Daemon and batch indexing
// processes may use specific values, else fall back on common
// ones.
string logfilename, loglevel;
if (flags & RCLINIT_DAEMON) {
config->getConfParam(string("daemlogfilename"), logfilename);
config->getConfParam(string("daemloglevel"), loglevel);
}
if ((flags & RCLINIT_IDX) && logfilename.empty())
config->getConfParam(string("idxlogfilename"), logfilename);
if ((flags & RCLINIT_IDX) && loglevel.empty())
config->getConfParam(string("idxloglevel"), loglevel);
if (logfilename.empty())
config->getConfParam(string("logfilename"), logfilename);
if (loglevel.empty())
config->getConfParam(string("loglevel"), loglevel);
// Initialize logging
if (!logfilename.empty()) {
logfilename = path_tildexpand(logfilename);
// If not an absolute path or , compute relative to config dir
if (!path_isabsolute(logfilename) &&
logfilename.compare("stderr")) {
logfilename = path_cat(config->getConfDir(), logfilename);
}
Logger::getTheLog("")->reopen(logfilename);
}
if (!loglevel.empty()) {
int lev = atoi(loglevel.c_str());
Logger::getTheLog("")->setLogLevel(Logger::LogLevel(lev));
}
LOGINF("Configuration directory: " << config->getConfDir() << std::endl);
// Make sure the locale charset is initialized (so that multiple
// threads don't try to do it at once).
config->getDefCharset();
mainthread_id = std::this_thread::get_id();
// Init smallut and pathut static values
pathut_init_mt();
smallut_init_mt();
rclutil_init_mt();
// Init execmd.h static PATH and PATHELT splitting
{string bogus;
ExecCmd::which("nosuchcmd", bogus);
}
// Init Unac translation exceptions
string unacex;
if (config->getConfParam("unac_except_trans", unacex) && !unacex.empty())
unac_set_except_translations(unacex.c_str());
#ifndef IDX_THREADS
ExecCmd::useVfork(true);
#else
// Keep threads init behind log init, but make sure it's done before
// we do the vfork choice ! The latter is not used any more actually,
// we always use vfork except if forbidden by config.
if ((flags & RCLINIT_IDX)) {
config->initThrConf();
}
bool novfork;
config->getConfParam("novfork", &novfork);
if (novfork) {
LOGDEB0("rclinit: will use fork() for starting commands\n" );
ExecCmd::useVfork(false);
} else {
LOGDEB0("rclinit: will use vfork() for starting commands\n" );
ExecCmd::useVfork(true);
}
#endif
int flushmb;
if (config->getConfParam("idxflushmb", &flushmb) && flushmb > 0) {
LOGDEB1("rclinit: idxflushmb=" << (flushmb) << ", set XAPIAN_FLUSH_THRESHOLD to 10E6\n" );
static const char *cp = "XAPIAN_FLUSH_THRESHOLD=1000000";
#ifdef PUTENV_ARG_CONST
::putenv(cp);
#else
::putenv(strdup(cp));
#endif
}
return config;
}
// Signals are handled by the main thread. All others should call this
// routine to block possible signals
void recoll_threadinit()
{
#ifndef _WIN32
sigset_t sset;
sigemptyset(&sset);
for (unsigned int i = 0; i < sizeof(catchedSigs) / sizeof(int); i++)
sigaddset(&sset, catchedSigs[i]);
sigaddset(&sset, SIGHUP);
pthread_sigmask(SIG_BLOCK, &sset, 0);
#else
// Not sure that this is needed at all or correct under windows.
for (unsigned int i = 0; i < sizeof(catchedSigs) / sizeof(int); i++) {
if (signal(catchedSigs[i], SIG_IGN) != SIG_IGN) {
signal(catchedSigs[i], SIG_IGN);
}
}
#endif
}
bool recoll_ismainthread()
{
return std::this_thread::get_id() == mainthread_id;
}