Fix MString::read() infinite retry loop, HTTPRequest parser fd leak, Log deadlock, log rotation symlink handling, and JString bounds checks

- MString::read() now gives up after repeated I/O errors instead of
  looping forever and crashing via realloc() failure (root cause of a
  recurring barant.com production crash).
- HTTPRequest::parse() no longer leaks lineParser/headerParser on a
  malformed request.
- Log gained size-based rotation (config-driven) and a non-static-mutex
  fix; a separate fix stopped rotation from destroying the logs symlink
  on its first trigger.
- File.cpp: symlink-preserving rotation support.
- JString: fixed an array-index out-of-bounds and a setString issue.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
Brad ArantandClaude Sonnet 5 committed 2026-10-06 17:38:14 +00:00
1 parent 694c6b80a4
commit 05374bdb15
7 files changed
+213 -14

No files matched your search

+62
View File
@@ -6,6 +6,9 @@
#include <fcntl.h>
#include <sys/types.h>
#include <unistd.h>
#include <cstdio>
#include <cstdlib>
#include <climits>
namespace coreutils {
@@ -16,6 +19,8 @@ namespace coreutils {
void File::open(coreutils::ZString fileName, int mode, int authority) {
this->fileName = fileName;
this->mode = mode;
this->authority = authority;
buffer = NULL;
@@ -89,6 +94,63 @@ namespace coreutils {
int len = ::write(fd, data.getData(), data.getLength());
}
size_t File::currentSize() {
struct stat status;
if (fstat(fd, &status) != 0) {
std::stringstream temp;
temp << "Error statting file " << fileName << ".";
throw Exception(temp.str(), __FILE__, __LINE__);
}
return (size_t)status.st_size;
}
void File::rotate(int maxBackups) {
close(fd);
// Resolve through fileName first, not its raw configured spelling -
// rename() never follows symlinks for either its source or
// destination path. BARANTMail's own "barantmail.log" is a symlink
// into /var/log/ on purpose (keeps a large, fast-growing log off
// the same filesystem/tree as the source checkout) - renaming that
// SYMLINK's own path to ".1" below, then recreating a plain file
// at the original (now-empty) path via open()'s O_CREAT, would
// silently sever the symlink and start a brand new file right back
// in the source tree, undoing that setup the very first time
// rotation ever fires on a symlinked path. Confirmed live: exactly
// this happened to BARANTMail's real log on this code's first
// real trigger (a 23GB pre-existing file, already far past the
// configured threshold, rotated on the very next size check after
// a fresh process started logging to it). realpath() requires the
// target to already exist, which it does here (this object has it
// open) - falls back to the configured path unresolved only if
// that somehow isn't true (e.g. the target was deleted out from
// under it), matching plain (non-symlink) File usage exactly as
// before.
char resolved[PATH_MAX];
std::string configuredPath(fileName.c_str());
std::string base = realpath(configuredPath.c_str(), resolved) ?
std::string(resolved) : configuredPath;
// Oldest-first: delete what's about to fall off the end, then
// shift each remaining backup up by one, working from the
// highest number down so a lower-numbered file is never
// clobbered before it's been moved out of the way.
std::string oldest = base + "." + std::to_string(maxBackups);
::remove(oldest.c_str());
for (int i = maxBackups - 1; i >= 1; i--) {
std::string from = base + "." + std::to_string(i);
std::string to = base + "." + std::to_string(i + 1);
::rename(from.c_str(), to.c_str());
}
::rename(base.c_str(), (base + ".1").c_str());
// Reopens via the ORIGINAL (possibly symlinked) fileName, not the
// resolved `base` - the symlink itself was never touched above
// (only its target was renamed away), so this correctly follows it
// again and creates a fresh file at the real target location.
open(fileName, mode, authority);
}
std::string File::asString() {
return std::string(buffer, size);
}