Files
zoneminder/tests/zm_logger_rotate.cpp
Isaac ConnorandClaude Opus 5 bde61e7af6 fix: rotate logs on SIGWINCH instead of SIGHUP fixes #5063
SIGHUP means reload. zmc responds by closing its events, disconnecting the
camera and reconnecting; the perl daemons respond by exiting so zmdc restarts
them. zmdc.pl logrot hupped every managed process, so the nightly logrotate
run cost about 8 seconds of capture on a default install.

Rotating a log file only needs the daemon to drop its file handle, so use a
separate signal for it. SIGWINCH is otherwise unused, is ignored by default and
exists on every supported platform.

- Logger (C++) installs a SIGWINCH handler beside its USR1/USR2 handler. The
  handler only sets a flag; the next logPrint closes the file and the write
  reopens it at the original path. This covers every C++ binary without
  touching any daemon's main loop.
- Logger.pm registers WINCH alongside HUP in logSetSignal, which logInit
  already calls, so the scripts that install their own HUP handler still
  rotate.
- zmdc.pl logrot sends WINCH. The logrotate config is unchanged - it still
  calls zmpkg.pl logrot.

Filter.pm and FilterTerm.php justified MAX_EVENT_DAYS by events not outliving
the nightly HUP, which is no longer what bounds them; cite SectionLength.

Tests: tests/zm_logger_rotate.cpp and tests/perl/test_log_rotate_signal.pl both
log, rename the file out from under the process, confirm writes still land in
the renamed file, signal WINCH and confirm the original path is written again.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01NDhTBPj9xEaT52pRmAufaP
2026-08-22 15:30:46 -04:00

69 lines
2.2 KiB
C++

/*
* This file is part of the ZoneMinder Project. See AUTHORS file for Copyright information
*
* 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, see <http://www.gnu.org/licenses/>.
*/
#include "zm_catch2.h"
#include "zm_logger.h"
#include "zm_utils.h"
#include <csignal>
#include <cstdio>
#include <fstream>
#include <sstream>
#include <string>
#include <cstdlib>
#include <unistd.h>
static std::string slurp(const std::string &path) {
std::ifstream in(path);
std::stringstream ss;
ss << in.rdbuf();
return ss.str();
}
// logrotate renames the file and expects the daemon to let go of the old
// handle. SIGHUP does that but also means reload, which costs zmc a camera
// reconnect - see issue #5063 - so SIGWINCH is what logrotate reaches for.
TEST_CASE("SIGWINCH reopens the log file after rotation") {
std::string path = stringtf("/tmp/zm_test_logrot_%d.log", getpid());
std::string rotated = path + ".1";
unlink(path.c_str());
unlink(rotated.c_str());
setenv("LOG_FLUSH", "1", 1);
Logger::Options options(Logger::NOLOG, Logger::NOLOG, Logger::INFO, Logger::NOLOG);
options.mLogFile = path;
logInit("zm_test_logrot", options);
Info("before rotation");
REQUIRE(rename(path.c_str(), rotated.c_str()) == 0);
Info("still holding the old handle");
REQUIRE(slurp(rotated).find("still holding the old handle") != std::string::npos);
REQUIRE(slurp(path).empty());
raise(SIGWINCH);
Info("after rotation");
REQUIRE(slurp(path).find("after rotation") != std::string::npos);
REQUIRE(slurp(rotated).find("after rotation") == std::string::npos);
logTerm();
unlink(path.c_str());
unlink(rotated.c_str());
}