- 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>
167 lines
5.7 KiB
C++
167 lines
5.7 KiB
C++
#include "File.h"
|
|
#include "Exception.h"
|
|
#include <sys/stat.h>
|
|
#include <iostream>
|
|
#include <sstream>
|
|
#include <fcntl.h>
|
|
#include <sys/types.h>
|
|
#include <unistd.h>
|
|
#include <cstdio>
|
|
#include <cstdlib>
|
|
#include <climits>
|
|
|
|
namespace coreutils {
|
|
|
|
File::File(coreutils::ZString fileName, int mode, int authority) {
|
|
open(fileName, mode, authority);
|
|
}
|
|
|
|
void File::open(coreutils::ZString fileName, int mode, int authority) {
|
|
|
|
this->fileName = fileName;
|
|
this->mode = mode;
|
|
this->authority = authority;
|
|
|
|
buffer = NULL;
|
|
|
|
fd = ::open(fileName.c_str(), mode, authority);
|
|
if(fd < 0) {
|
|
std::stringstream temp;
|
|
temp << "Error opening file " << fileName << ".";
|
|
throw Exception(temp.str(), __FILE__, __LINE__);
|
|
}
|
|
|
|
// stat() only after a successful open(), and only used to size the
|
|
// read buffer - previously this ran BEFORE the open()/fd check with
|
|
// its return value ignored, so a missing file left the stat struct fully
|
|
// uninitialized and fed that garbage st_size straight into
|
|
// setBufferSize()'s realloc(), before the open-failure path ever
|
|
// got a chance to throw its clean Exception. Found while adding a
|
|
// static-asset route (GET /assets/{filename}) that legitimately
|
|
// hits this path on every request for a nonexistent file - a
|
|
// garbage-sized realloc there could abort/crash the whole
|
|
// long-running APIServer process (shared with every other live
|
|
// route) instead of cleanly 500ing just that one request.
|
|
struct stat status;
|
|
if (fstat(fd, &status) != 0) {
|
|
std::stringstream temp;
|
|
temp << "Error statting file " << fileName << ".";
|
|
throw Exception(temp.str(), __FILE__, __LINE__);
|
|
}
|
|
|
|
setBufferSize(status.st_size);
|
|
}
|
|
|
|
File::~File() {
|
|
close(fd);
|
|
free(buffer);
|
|
}
|
|
|
|
void File::setBufferSize(size_t size) {
|
|
buffer = (char *)realloc(buffer, size);
|
|
this->size = size;
|
|
}
|
|
|
|
int File::read() {
|
|
size = ::read(fd, buffer, size);
|
|
setBufferSize(size);
|
|
return size;
|
|
}
|
|
|
|
int File::readLine() {
|
|
setBufferSize(4096);
|
|
size = 0;
|
|
int len = ::read(fd, buffer + size, 1);
|
|
if(len == 0)
|
|
mEof = true;
|
|
while(!mEof && (*(buffer + size) != '\n')) {
|
|
if(*(buffer + size) != '\r') {
|
|
// setBufferSize(size);
|
|
++size;
|
|
}
|
|
len = ::read(fd, buffer + size, 1);
|
|
if(len == 0)
|
|
mEof = true;
|
|
}
|
|
return size;
|
|
}
|
|
|
|
void File::write(std::string data) {
|
|
::write(fd, data.c_str(), data.length());
|
|
}
|
|
|
|
void File::write(coreutils::ZString &data) {
|
|
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);
|
|
}
|
|
|
|
coreutils::ZString File::asZString() {
|
|
return ZString(buffer, size);
|
|
}
|
|
|
|
bool File::eof() {
|
|
return mEof;
|
|
}
|
|
|
|
}
|