From 497b61e017be81f23838811e73f35b61a4d6aff4 Mon Sep 17 00:00:00 2001 From: Jean-Francois Dockes Date: Tue, 18 Jan 2022 20:27:44 +0000 Subject: [PATCH] Windows: monitor, reexec, pinging process. Not done yet. --- src/index/rclmonprc.cpp | 2 - src/index/recollindex.cpp | 41 ++++++------------- src/utils/copyfile.cpp | 8 ++-- src/utils/execmd.cpp | 16 +++++--- src/utils/execmd.h | 5 ++- src/utils/pathut.cpp | 83 ++++++++++++++++++++++++++++++++++---- src/utils/pathut.h | 22 +++++----- src/windows/execmd_w.cpp | 84 +++++++++++++++++++++++++++++++++++++++ 8 files changed, 204 insertions(+), 57 deletions(-) diff --git a/src/index/rclmonprc.cpp b/src/index/rclmonprc.cpp index 3702bb7c..ab7efcdb 100644 --- a/src/index/rclmonprc.cpp +++ b/src/index/rclmonprc.cpp @@ -567,7 +567,6 @@ bool startMonitor(RclConfig *conf, int opts) } } -#ifndef _WIN32 // Check for a config change if (!(opts & RCLMON_NOCONFCHECK) && o_reexec && conf->sourceChanged()) { LOGDEB("Rclmonprc: config changed, reexecuting myself\n"); @@ -577,7 +576,6 @@ bool startMonitor(RclConfig *conf, int opts) o_reexec->removeArg("-n"); o_reexec->reexec(); } -#endif // ! _WIN32 } LOGDEB("Rclmonprc: calling queue setTerminate\n"); rclEQ.setTerminate(); diff --git a/src/index/recollindex.cpp b/src/index/recollindex.cpp index 28d3f5b1..208d2d8f 100644 --- a/src/index/recollindex.cpp +++ b/src/index/recollindex.cpp @@ -103,9 +103,7 @@ static struct option long_options[] = { {0, 0, 0, 0} }; -#ifndef _WIN32 ReExec *o_reexec; -#endif // Globals for atexit cleanup static ConfIndexer *confindexer; @@ -458,25 +456,19 @@ static void lockorexit(Pidfile *pidfile, RclConfig *config) if (pid > 0) { cerr << "Can't become exclusive indexer: " << pidfile->getreason() << ". Return (other pid?): " << pid << endl; -#ifndef _WIN32 // Have a look at the status file. If the other process is // a monitor we can tell it to start an incremental pass // by touching the configuration file DbIxStatus status; readIdxStatus(config, status); if (status.hasmonitor) { - string cmd("touch "); string path = path_cat(config->getConfDir(), "recoll.conf"); - cmd += path; - int status; - if ((status = system(cmd.c_str()))) { - cerr << cmd << " failed with status " << status << endl; + if (!path_utimes(path, nullptr)) { + cerr << "Could not notify indexer" << endl; } else { - cerr << "Monitoring indexer process was notified of " - "indexing request\n"; + cerr << "Monitoring indexer process was notified of indexing request" << endl; } } -#endif } else { cerr << "Can't become exclusive indexer: " << pidfile->getreason() << endl; @@ -525,16 +517,17 @@ static void flushIdxReasons() static vector argstovector(int argc, wchar_t **argv, vector& storage) #else #define WARGTOSTRING(w) (w) - static vector argstovector(int argc, char **argv, vector& storage) +static vector argstovector(int argc, char **argv, vector& storage) #endif { vector args(argc+1); - storage.resize(argc+1); + storage.resize(argc); thisprog = path_absolute(WARGTOSTRING(argv[0])); for (int i = 0; i < argc; i++) { storage[i] = WARGTOSTRING(argv[i]); args[i] = storage[i].c_str(); } + args[argc] = 0; return args; } @@ -556,17 +549,13 @@ int wmain(int argc, wchar_t *argv[]) int main(int argc, char *argv[]) #endif { -#ifndef _WIN32 - // The reexec struct is used by the daemon to shed memory after - // the initial indexing pass and to restart when the configuration - // changes - o_reexec = new ReExec; - o_reexec->init(argc, argv); -#endif - // Only actually useful on Windows: convert wargs to utf-8 chars vector astore; vector args = argstovector(argc, argv, astore); + // The reexec struct is used by the daemon to shed memory after + // the initial indexing pass and to restart when the configuration + // changes + o_reexec = new ReExec(astore); vector selpatterns; int sleepsecs{60}; @@ -683,10 +672,7 @@ int main(int argc, char *argv[]) exit(0); } -#ifndef _WIN32 o_reexec->atexit(cleanup); -#endif - vector nonexist; if (!checktopdirs(config, nonexist)) { std::cerr << "topdirs not set or only contains invalid paths.\n"; @@ -848,8 +834,8 @@ int main(int argc, char *argv[]) Usage(); statusUpdater()->setMonitor(true); if (!(op_flags&OPT_D)) { - LOGDEB("recollindex: daemonizing\n"); #ifndef _WIN32 + LOGDEB("recollindex: daemonizing\n"); if (daemon(0,0) != 0) { addIdxReason("monitor", "daemon() failed"); cerr << "daemon() failed, errno " << errno << endl; @@ -895,16 +881,13 @@ int main(int argc, char *argv[]) } } deleteZ(confindexer); -#ifndef _WIN32 o_reexec->insertArgs(vector(1, "-n")); - LOGINFO("recollindex: reexecuting with -n after initial full " - "pass\n"); + LOGINFO("recollindex: reexecuting with -n after initial full pass\n"); // Note that -n will be inside the reexec when we come // back, but the monitor will explicitly strip it before // starting a config change exec to ensure that we do a // purging pass in this latter case (full restart). o_reexec->reexec(); -#endif } statusUpdater()->update(DbIxStatus::DBIXS_MONITOR, ""); diff --git a/src/utils/copyfile.cpp b/src/utils/copyfile.cpp index 5ac37af8..2e39c996 100644 --- a/src/utils/copyfile.cpp +++ b/src/utils/copyfile.cpp @@ -176,13 +176,15 @@ bool renameormove(const char *src, const char *dst, string &reason) reason += string("Chown ") + dst + "Error : " + strerror(errno); } } - struct timeval times[2]; +#endif + + struct path_timeval times[2]; times[0].tv_sec = st.st_atime; times[0].tv_usec = 0; times[1].tv_sec = st.st_mtime; times[1].tv_usec = 0; - utimes(dst, times); -#endif + path_utimes(dst, times); + // All ok, get rid of origin if (!path_unlink(src)) { reason += string("Can't unlink ") + src + "Error : " + strerror(errno); diff --git a/src/utils/execmd.cpp b/src/utils/execmd.cpp index 6a6fd510..f2912533 100644 --- a/src/utils/execmd.cpp +++ b/src/utils/execmd.cpp @@ -1064,11 +1064,6 @@ std::string ExecCmd::waitStatusAsString(int wstatus) /// ReExec class methods /////////////////////////////////////////////////// ReExec::ReExec(int argc, char *args[]) -{ - init(argc, args); -} - -void ReExec::init(int argc, char *args[]) { for (int i = 0; i < argc; i++) { m_argv.push_back(args[i]); @@ -1081,6 +1076,17 @@ void ReExec::init(int argc, char *args[]) free(cd); } +ReExec::ReExec(const std::vector& args) + : m_argv(args) +{ + m_cfd = open(".", 0); + char *cd = getcwd(0, 0); + if (cd) { + m_curdir = cd; + } + free(cd); +} + void ReExec::insertArgs(const vector& args, int idx) { vector::iterator it; diff --git a/src/utils/execmd.h b/src/utils/execmd.h index 987a9e7f..dfb1595f 100644 --- a/src/utils/execmd.h +++ b/src/utils/execmd.h @@ -284,7 +284,8 @@ class ReExec { public: ReExec() {} ReExec(int argc, char *argv[]); - void init(int argc, char *argv[]); + // Mostly useful with Windows and wmain: args must be utf-8 + ReExec(const std::vector& args); int atexit(void (*function)(void)) { m_atexitfuncs.push(function); return 0; @@ -302,7 +303,7 @@ public: private: std::vector m_argv; std::string m_curdir; - int m_cfd; + int m_cfd{-1}; std::string m_reason; std::stack m_atexitfuncs; }; diff --git a/src/utils/pathut.cpp b/src/utils/pathut.cpp index 9e61d224..434c647f 100644 --- a/src/utils/pathut.cpp +++ b/src/utils/pathut.cpp @@ -100,6 +100,7 @@ #include #include #include +#include #if !defined(S_IFLNK) #define S_IFLNK 0 @@ -152,6 +153,7 @@ #else /* !_WIN32 -> */ #include +#include #include #include #include @@ -333,8 +335,6 @@ static bool path_isdriveabs(const string& s) /* Can be OR'd in to one of the above. */ # define LOCK_NB 4 /* Don't block when locking. */ -#include - /* Determine the current size of a file. Because the other braindead * APIs we'll call need lower/upper 32 bit pairs, keep the file size * like that too. @@ -953,6 +953,34 @@ bool path_rmdir(const std::string& path) return RMDIR(syspath) == 0; } +bool path_utimes(const std::string& path, struct path_timeval _tv[2]) +{ +#ifdef _WIN32 + struct _utimbuf times; + if (nullptr == _tv) { + times.actime = times.modtime = time(0L); + } else { + times.actime = _tv[0].tv_sec; + times.modtime = _tv[1].tv_sec; + } + SYSPATH(path, syspath); + return _wutime(syspath, ×) != -1; +#else + struct timeval tvb[2]; + if (nullptr == _tv) { + gettimeofday(tvb, nullptr); + tvb[1].tv_sec = tvb[0].tv_sec; + tvb[1].tv_usec = tvb[0].tv_usec; + } else { + tvb[0].tv_sec = _tv[0].tv_sec; + tvb[0].tv_usec = _tv[0].tv_usec; + tvb[1].tv_sec = _tv[1].tv_sec; + tvb[1].tv_usec = _tv[1].tv_usec; + } + return utimes(path.c_str(), tvb) == 0; +#endif +} + bool path_streamopen(const std::string& path, int mode, std::fstream& outstream) { #if defined(_WIN32) && defined (_MSC_VER) @@ -1462,11 +1490,35 @@ Pidfile::~Pidfile() this->close(); } +#ifdef _WIN32 +// It appears that we can't read the locked file on Windows, so we use +// separate files for locking and holding the data. +static std::string pid_data_path(const std::string& path) +{ + // Remove extension. append -data to name, add back extension. + auto ext = path_suffix(path); + auto spath = path_cat(path_getfather(path), path_basename(path, ext)); + if (spath.back() == '.') + spath.pop_back(); + if (!ext.empty()) + spath += std::string("-data") + "." + ext; + return spath; +} +#endif // _WIN32 + int Pidfile::read_pid() { +#ifdef _WIN32 + // It appears that we can't read the locked file on Windows, so use an aux file + auto path = pid_data_path(m_path); + SYSPATH(path, syspath); +#else SYSPATH(m_path, syspath); +#endif int fd = OPEN(syspath, O_RDONLY); if (fd == -1) { + if (errno != ENOENT) + m_reason = "Open RDONLY failed: [" + m_path + "]: " + strerror(errno); return -1; } @@ -1474,12 +1526,14 @@ int Pidfile::read_pid() int i = read(fd, buf, sizeof(buf) - 1); ::close(fd); if (i <= 0) { + m_reason = "Read failed: [" + m_path + "]: " + strerror(errno); return -1; } buf[i] = '\0'; char *endptr; int pid = strtol(buf, &endptr, 10); if (endptr != &buf[i]) { + m_reason = "Bad pid contents: [" + m_path + "]: " + strerror(errno); return - 1; } return pid; @@ -1501,8 +1555,8 @@ int Pidfile::flopen() lockdata.l_whence = SEEK_SET; if (fcntl(m_fd, F_SETLK, &lockdata) != 0) { int serrno = errno; - this->close() - errno = serrno; + this->close(); + errno = serrno; m_reason = "fcntl lock failed"; return -1; } @@ -1538,18 +1592,33 @@ int Pidfile::open() int Pidfile::write_pid() { +#ifdef _WIN32 + // It appears that we can't read the locked file on Windows, so use an aux file + auto path = pid_data_path(m_path); + SYSPATH(path, syspath); + int fd; + if ((fd = OPEN(syspath, O_RDWR | O_CREAT, 0644)) == -1) { + m_reason = "Open failed: [" + path + "]: " + strerror(errno); + return -1; + } +#else + int fd = m_fd; +#endif /* truncate to allow multiple calls */ - if (ftruncate(m_fd, 0) == -1) { + if (ftruncate(fd, 0) == -1) { m_reason = "ftruncate failed"; return -1; } char pidstr[20]; sprintf(pidstr, "%u", int(getpid())); - lseek(m_fd, 0, 0); - if (::write(m_fd, pidstr, strlen(pidstr)) != (PATHUT_SSIZE_T)strlen(pidstr)) { + lseek(fd, 0, 0); + if (::write(fd, pidstr, strlen(pidstr)) != (PATHUT_SSIZE_T)strlen(pidstr)) { m_reason = "write failed"; return -1; } +#ifdef _WIN32 + close(fd); +#endif return 0; } diff --git a/src/utils/pathut.h b/src/utils/pathut.h index ee16aec2..a6b29b43 100644 --- a/src/utils/pathut.h +++ b/src/utils/pathut.h @@ -143,8 +143,7 @@ struct PathStat { uint64_t pst_blocks; uint64_t pst_blksize; }; -extern int path_fileprops(const std::string path, struct PathStat *stp, - bool follow = true); +extern int path_fileprops(const std::string path, struct PathStat *stp, bool follow = true); /// Return separator for PATH environment variable @@ -171,8 +170,7 @@ private: }; /// Dump directory -extern bool listdir(const std::string& dir, std::string& reason, - std::set& entries); +extern bool listdir(const std::string& dir, std::string& reason, std::set& entries); /** A small wrapper around statfs et al, to return percentage of disk occupation @@ -189,7 +187,15 @@ bool path_chdir(const std::string& path); std::string path_cwd(); bool path_unlink(const std::string& path); bool path_rmdir(const std::string& path); - + +// Setting file times. Windows defines timeval in winsock2.h but it seems safer to use local def +// Also on Windows, we use _wutime and ignore the tv_usec part. +typedef struct path_timeval { + long tv_sec; + long tv_usec; +} path_timeval; +bool path_utimes(const std::string& path, struct path_timeval times[2]); + /* Open file, trying to do the right thing with non-ASCII paths on * Windows, where it only works with MSVC at the moment if the path is * not ASCII, because it uses fstream(wchar_t*), which is an MSVC @@ -200,12 +206,10 @@ bool path_rmdir(const std::string& path); * * @param path an utf-8 file path. * @param mode is an std::fstream mode (ios::in etc.) */ -extern bool path_streamopen( - const std::string& path, int mode, std::fstream& outstream); +extern bool path_streamopen(const std::string& path, int mode, std::fstream& outstream); /// Encode according to rfc 1738 -extern std::string url_encode(const std::string& url, - std::string::size_type offs = 0); +extern std::string url_encode(const std::string& url, std::string::size_type offs = 0); extern std::string url_decode(const std::string& encoded); //// Convert to file path if url is like file://. This modifies the //// input (and returns a copy for convenience) diff --git a/src/windows/execmd_w.cpp b/src/windows/execmd_w.cpp index 8e5ad2a5..ff63b3dd 100644 --- a/src/windows/execmd_w.cpp +++ b/src/windows/execmd_w.cpp @@ -1168,3 +1168,87 @@ std::string ExecCmd::waitStatusAsString(int wstatus) return oss.str(); } + +///////////// ReExec class methods. +ReExec::ReExec(int argc, char *args[]) +{ + for (int i = 0; i < argc; i++) { + m_argv.push_back(args[i]); + } +} + +ReExec::ReExec(const std::vector& args) + : m_argv(args) +{ +} + +void ReExec::insertArgs(const vector& args, int idx) +{ + vector::iterator it; + unsigned int cmpoffset = (unsigned int) - 1; + + if (idx == -1 || string::size_type(idx) >= m_argv.size()) { + it = m_argv.end(); + if (m_argv.size() >= args.size()) { + cmpoffset = m_argv.size() - args.size(); + } + } else { + it = m_argv.begin() + idx; + if (idx + args.size() <= m_argv.size()) { + cmpoffset = idx; + } + } + + // Check that the option is not already there + if (cmpoffset != (unsigned int) - 1) { + bool allsame = true; + for (unsigned int i = 0; i < args.size(); i++) { + if (m_argv[cmpoffset + i] != args[i]) { + allsame = false; + break; + } + } + if (allsame) { + return; + } + } + + m_argv.insert(it, args.begin(), args.end()); +} + +void ReExec::removeArg(const string& arg) +{ + for (vector::iterator it = m_argv.begin(); it != m_argv.end(); it++) { + if (*it == arg) { + it = m_argv.erase(it); + } + } +} + +void ReExec::reexec() +{ + // Execute the atexit funcs + while (!m_atexitfuncs.empty()) { + (m_atexitfuncs.top())(); + m_atexitfuncs.pop(); + } + + // Allocate arg vector (1 more for final 0) + typedef const wchar_t *Ccharp; + Ccharp *argv; + argv = (Ccharp *)malloc((m_argv.size() + 1) * sizeof(wchar_t *)); + if (argv == 0) { + LOGERR("ExecCmd::doexec: malloc() failed. errno " << errno << "\n"); + return; + } + + // Fill up argv + int i = 0; + std::vector> ptrs; + for (const auto& arg: m_argv) { + ptrs.push_back(utf8towchar(arg)); + argv[i++] = ptrs.back().get(); + } + argv[i] = nullptr; + _wexecvp(argv[0], argv); +}