<commit_before>/**
  * Touhou Community Reliant Automatic Patcher
  * Main DLL
  *
  * ----
  *
  * Logging functions.
  */

#include "thcrap.h"
#include <queue>

// -------
// Globals
// -------

struct log_string_t {
	const char* str;
	size_t n;
	bool is_n;
};

static HANDLE log_file = INVALID_HANDLE_VALUE;
static bool console_open = false;
static HANDLE log_thread_handle = INVALID_HANDLE_VALUE;
static std::queue<log_string_t> log_queue;
static CRITICAL_SECTION queue_cs = {};
static HANDLE log_semaphore = INVALID_HANDLE_VALUE;
static bool async_enabled = false;

// Config
DWORD log_async = true;

// For checking nested thcrap instances that access the same log file.
// We only want to print an error message for the first instance.
static HANDLE log_filemapping = INVALID_HANDLE_VALUE;
static const char LOG[] = "logs/thcrap_log.txt";
static const char LOG_ROTATED[] = "logs/thcrap_log.%d.txt";
static constexpr int ROTATIONS = 5; // Number of backups to keep
static void (*log_print_hook)(const char*) = NULL;
static void(*log_nprint_hook)(const char*, size_t) = NULL;
static HWND mbox_owner_hwnd = NULL; // Set by log_mbox_set_owner
// -----------------------

struct lasterror_t {
	char str[DECIMAL_DIGITS_BOUND(DWORD) + 1];
};

THREAD_LOCAL(lasterror_t, lasterror_tls, nullptr, nullptr);

const char* lasterror_str_for(DWORD err)
{
	switch(err) {
	case ERROR_SHARING_VIOLATION:
		return "File in use";
	case ERROR_MOD_NOT_FOUND:
		return "File not found";
	default: // -Wswitch...
		break;
	}
	auto str = lasterror_tls_get();
	if(!str) {
		static lasterror_t lasterror_static;
		str = &lasterror_static;
	}
	snprintf(str->str, sizeof(str->str), "%lu", err);
	return str->str;
}

const char* lasterror_str()
{
	return lasterror_str_for(GetLastError());
}

void log_set_hook(void(*print_hook)(const char*), void(*nprint_hook)(const char*,size_t)){
	log_print_hook = print_hook;
	log_nprint_hook = nprint_hook;
}

// Rotation
// --------
void log_rotate(void)
{
	char file_one[sizeof(LOG) + DECIMAL_DIGITS_BOUND(int) + 1];
	char file_two[sizeof(LOG) + DECIMAL_DIGITS_BOUND(int) + 1];

	char* rot_to = &file_one[0];
	char* rot_from = &file_two[0];

	int rotation = ROTATIONS;
	sprintf(rot_to, LOG_ROTATED, rotation);
	while (--rotation) {
		sprintf(rot_from, LOG_ROTATED, rotation);
		MoveFileExU(rot_from, rot_to, MOVEFILE_REPLACE_EXISTING);
		std::swap(rot_from, rot_to);
	}
	MoveFileExU(LOG, rot_to, MOVEFILE_REPLACE_EXISTING);
}
// --------

static void log_print_real(log_string_t& log_str) {
	DWORD byteRet;
	if (console_open) {
		WriteFile(GetStdHandle(STD_OUTPUT_HANDLE), log_str.str, log_str.n, &byteRet, NULL);
	}
	if (log_file) {
		WriteFile(log_file, log_str.str, log_str.n, &byteRet, NULL);
	}
	if ((log_str.is_n)) {
		if (log_nprint_hook) {
			log_nprint_hook(log_str.str, log_str.n);
		}
	} else {
		if (log_print_hook) {
			log_print_hook(log_str.str);
		}
	}
}

static DWORD WINAPI log_thread(LPVOID lpParameter) {
	for (;;) {
		EnterCriticalSection(&queue_cs);
		if (log_queue.empty()) {
			LeaveCriticalSection(&queue_cs);
			WaitForSingleObject(log_semaphore, INFINITE);
		} else {
			log_string_t log_str = std::move(log_queue.front());
			log_queue.pop();
			LeaveCriticalSection(&queue_cs);

			log_print_real(log_str);
			free((void*)log_str.str);
		}
	}
}

static void log_push(const char* str, size_t n, bool is_n) {
	log_string_t log_str = { str, n, is_n };

	if (async_enabled) {
		log_str.str = strdup_size(log_str.str, log_str.n);
		EnterCriticalSection(&queue_cs);
		log_queue.push(log_str);
		LeaveCriticalSection(&queue_cs);
		ReleaseSemaphore(log_semaphore, 1, NULL);
	} else {
		log_print_real(log_str);
	}
}

void log_nprint(const char* str, size_t n) {
	log_push(str, strnlen(str, n), true);
}

void log_print(const char *str) {
	log_push(str, strlen(str), false);
}

void log_vprintf(const char *format, va_list va) {
	va_list va2;
	va_copy(va2, va);
	const int total_size = vsnprintf(NULL, 0, format, va2);
	va_end(va2);
	if (total_size > 0) {
		VLA(char, str, total_size + 1);
		vsprintf(str, format, va);
		log_push(str, total_size, false);
		VLA_FREE(str);
	}
}

void log_printf(const char *format, ...) {
	va_list va;
	va_start(va, format);
	log_vprintf(format, va);
	va_end(va);
}

void log_flush() {
	if (async_enabled) {
	EnterCriticalSection(&queue_cs);
		while (!log_queue.empty()) {
			log_string_t log_str = std::move(log_queue.front());
			log_queue.pop();
			log_print_real(log_str);
		}
	LeaveCriticalSection(&queue_cs);
	}
	FlushFileBuffers(GetStdHandle(STD_OUTPUT_HANDLE));
	FlushFileBuffers(log_file);
}<commit_msg>thcrap: log: fix log_flush memory leak<commit_after>/**
  * Touhou Community Reliant Automatic Patcher
  * Main DLL
  *
  * ----
  *
  * Logging functions.
  */

#include "thcrap.h"
#include <queue>

// -------
// Globals
// -------

struct log_string_t {
	const char* str;
	size_t n;
	bool is_n;
};

static HANDLE log_file = INVALID_HANDLE_VALUE;
static bool console_open = false;
static HANDLE log_thread_handle = INVALID_HANDLE_VALUE;
static std::queue<log_string_t> log_queue;
static CRITICAL_SECTION queue_cs = {};
static HANDLE log_semaphore = INVALID_HANDLE_VALUE;
static bool async_enabled = false;

// Config
DWORD log_async = true;

// For checking nested thcrap instances that access the same log file.
// We only want to print an error message for the first instance.
static HANDLE log_filemapping = INVALID_HANDLE_VALUE;
static const char LOG[] = "logs/thcrap_log.txt";
static const char LOG_ROTATED[] = "logs/thcrap_log.%d.txt";
static constexpr int ROTATIONS = 5; // Number of backups to keep
static void (*log_print_hook)(const char*) = NULL;
static void(*log_nprint_hook)(const char*, size_t) = NULL;
static HWND mbox_owner_hwnd = NULL; // Set by log_mbox_set_owner
// -----------------------

struct lasterror_t {
	char str[DECIMAL_DIGITS_BOUND(DWORD) + 1];
};

THREAD_LOCAL(lasterror_t, lasterror_tls, nullptr, nullptr);

const char* lasterror_str_for(DWORD err)
{
	switch(err) {
	case ERROR_SHARING_VIOLATION:
		return "File in use";
	case ERROR_MOD_NOT_FOUND:
		return "File not found";
	default: // -Wswitch...
		break;
	}
	auto str = lasterror_tls_get();
	if(!str) {
		static lasterror_t lasterror_static;
		str = &lasterror_static;
	}
	snprintf(str->str, sizeof(str->str), "%lu", err);
	return str->str;
}

const char* lasterror_str()
{
	return lasterror_str_for(GetLastError());
}

void log_set_hook(void(*print_hook)(const char*), void(*nprint_hook)(const char*,size_t)){
	log_print_hook = print_hook;
	log_nprint_hook = nprint_hook;
}

// Rotation
// --------
void log_rotate(void)
{
	char file_one[sizeof(LOG) + DECIMAL_DIGITS_BOUND(int) + 1];
	char file_two[sizeof(LOG) + DECIMAL_DIGITS_BOUND(int) + 1];

	char* rot_to = &file_one[0];
	char* rot_from = &file_two[0];

	int rotation = ROTATIONS;
	sprintf(rot_to, LOG_ROTATED, rotation);
	while (--rotation) {
		sprintf(rot_from, LOG_ROTATED, rotation);
		MoveFileExU(rot_from, rot_to, MOVEFILE_REPLACE_EXISTING);
		std::swap(rot_from, rot_to);
	}
	MoveFileExU(LOG, rot_to, MOVEFILE_REPLACE_EXISTING);
}
// --------

static void log_print_real(log_string_t& log_str) {
	DWORD byteRet;
	if (console_open) {
		WriteFile(GetStdHandle(STD_OUTPUT_HANDLE), log_str.str, log_str.n, &byteRet, NULL);
	}
	if (log_file) {
		WriteFile(log_file, log_str.str, log_str.n, &byteRet, NULL);
	}
	if ((log_str.is_n)) {
		if (log_nprint_hook) {
			log_nprint_hook(log_str.str, log_str.n);
		}
	} else {
		if (log_print_hook) {
			log_print_hook(log_str.str);
		}
	}
}

static DWORD WINAPI log_thread(LPVOID lpParameter) {
	for (;;) {
		EnterCriticalSection(&queue_cs);
		if (log_queue.empty()) {
			LeaveCriticalSection(&queue_cs);
			WaitForSingleObject(log_semaphore, INFINITE);
		} else {
			log_string_t log_str = std::move(log_queue.front());
			log_queue.pop();
			LeaveCriticalSection(&queue_cs);

			log_print_real(log_str);
			free((void*)log_str.str);
		}
	}
}

static void log_push(const char* str, size_t n, bool is_n) {
	log_string_t log_str = { str, n, is_n };

	if (async_enabled) {
		log_str.str = strdup_size(log_str.str, log_str.n);
		EnterCriticalSection(&queue_cs);
		log_queue.push(log_str);
		LeaveCriticalSection(&queue_cs);
		ReleaseSemaphore(log_semaphore, 1, NULL);
	} else {
		log_print_real(log_str);
	}
}

void log_nprint(const char* str, size_t n) {
	log_push(str, strnlen(str, n), true);
}

void log_print(const char *str) {
	log_push(str, strlen(str), false);
}

void log_vprintf(const char *format, va_list va) {
	va_list va2;
	va_copy(va2, va);
	const int total_size = vsnprintf(NULL, 0, format, va2);
	va_end(va2);
	if (total_size > 0) {
		VLA(char, str, total_size + 1);
		vsprintf(str, format, va);
		log_push(str, total_size, false);
		VLA_FREE(str);
	}
}

void log_printf(const char *format, ...) {
	va_list va;
	va_start(va, format);
	log_vprintf(format, va);
	va_end(va);
}

void log_flush() {
	if (async_enabled) {
	EnterCriticalSection(&queue_cs);
		while (!log_queue.empty()) {
			log_string_t log_str = std::move(log_queue.front());
			log_queue.pop();
			log_print_real(log_str);
			free((void*)log_str.str);
		}
	LeaveCriticalSection(&queue_cs);
	}
	FlushFileBuffers(GetStdHandle(STD_OUTPUT_HANDLE));
	FlushFileBuffers(log_file);
}
{'fraction_non_alphanumeric': 0.14265567368192666, 'fraction_numerical': 0.0030375352571056628, 'mean_word_length': 4.687230104873535, 'pattern_counts': {'":': 0, '<': 7, '<?xml version=': 0, '>': 15, 'https://': 0, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '1260093', 'n_tokens_mistral': 3830, 'n_tokens_neox': 3620, 'n_words': 734}