yaze 0.3.2
Link to the Past ROM Editor
 
Loading...
Searching...
No Matches
crash_handler.cc
Go to the documentation of this file.
2
3#include <algorithm>
4#include <chrono>
5#include <csignal>
6#include <cstdio>
7#include <ctime>
8#include <fstream>
9#include <iomanip>
10#include <sstream>
11#include <cstring>
12#include <vector>
13
14#include "absl/debugging/failure_signal_handler.h"
15#include "absl/debugging/symbolize.h"
16#include "util/platform_paths.h"
17
18#ifdef _WIN32
19#include <fcntl.h>
20#include <io.h>
21#include <sys/stat.h>
22#define STDERR_FILENO _fileno(stderr)
23#define write _write
24#define close _close
25#define open _open
26#define O_WRONLY _O_WRONLY
27#define O_CREAT _O_CREAT
28#define O_TRUNC _O_TRUNC
29#else
30#include <fcntl.h>
31#include <unistd.h>
32#endif
33
34namespace yaze {
35namespace util {
36
37// Static member definitions
38std::string CrashHandler::version_;
39std::filesystem::path CrashHandler::crash_log_path_;
41
42void CrashHandler::CrashLogWriter(const char* data) {
43 // Prevent re-entrancy - if we crash while handling a crash, just abort
44 static volatile sig_atomic_t in_crash_handler = 0;
45 if (in_crash_handler) {
46 // Already in crash handler - abort to prevent infinite loop
47 _Exit(1);
48 }
49 in_crash_handler = 1;
50
51 if (data == nullptr) {
52 in_crash_handler = 0;
53 return;
54 }
55
56 // Calculate length manually (strlen might crash)
57 size_t len = 0;
58 while (len < 4096 && data[len] != '\0') { // Cap at 4KB to prevent runaway
59 ++len;
60 }
61
62 if (crash_log_fd_ >= 0) {
63 // Write to crash log file (ignore errors - we're crashing anyway)
64 write(crash_log_fd_, data, len);
65 }
66
67 // Also write to stderr for immediate visibility
68 write(STDERR_FILENO, data, len);
69
70 in_crash_handler = 0;
71}
72
73void CrashHandler::Initialize(const std::string& version) {
74 version_ = version;
75
76 // Get or create crash log directory
77 auto crash_dir = GetCrashLogDirectory();
78
79 // Create timestamped crash log filename
80 auto now = std::chrono::system_clock::now();
81 auto time_t_now = std::chrono::system_clock::to_time_t(now);
82 std::tm* tm_now = std::localtime(&time_t_now);
83
84 std::ostringstream filename;
85 filename << "crash_" << std::put_time(tm_now, "%Y%m%d_%H%M%S") << ".log";
86 crash_log_path_ = crash_dir / filename.str();
87
88 // Open crash log file (will be written to if a crash occurs)
89 // Using low-level I/O because signal handlers can't use iostream
90#ifdef _WIN32
92 _open(crash_log_path_.string().c_str(), _O_WRONLY | _O_CREAT | _O_TRUNC,
93 _S_IREAD | _S_IWRITE);
94#else
95 crash_log_fd_ = open(crash_log_path_.c_str(), O_WRONLY | O_CREAT | O_TRUNC,
96 S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH);
97#endif
98
99 if (crash_log_fd_ >= 0) {
100 // Write header immediately
101 std::ostringstream header;
102 header << "=== YAZE Crash Report ===\n";
103 header << "Version: " << version_ << "\n";
104 header << "Timestamp: " << std::put_time(tm_now, "%Y-%m-%d %H:%M:%S") << "\n";
105 header << "Platform: ";
106#if defined(_WIN32)
107 header << "Windows";
108#elif defined(__APPLE__)
109 header << "macOS";
110#elif defined(__linux__)
111 header << "Linux";
112#else
113 header << "Unknown";
114#endif
115 header << "\n";
116 header << "========================\n\n";
117
118 std::string header_str = header.str();
119 write(crash_log_fd_, header_str.c_str(), header_str.size());
120
121 // Don't close the file - it stays open for the signal handler to write to
122 }
123
124 // Configure absl failure signal handler
125 absl::FailureSignalHandlerOptions options;
126 options.symbolize_stacktrace = true;
127 options.use_alternate_stack = true;
128 options.call_previous_handler = true;
129
130 // Set custom writer to write to both file and stderr
131 if (crash_log_fd_ >= 0) {
132 options.writerfn = CrashLogWriter;
133 }
134
135 absl::InstallFailureSignalHandler(options);
136}
137
138std::filesystem::path CrashHandler::GetCrashLogDirectory() {
139 auto app_data_result = PlatformPaths::GetAppDataSubdirectory("crash_logs");
140 if (app_data_result.ok()) {
141 return *app_data_result;
142 }
143
144 // Fallback to temp directory
145 auto temp_result = PlatformPaths::GetTempDirectory();
146 if (temp_result.ok()) {
147 return *temp_result / "crash_logs";
148 }
149
150 // Last resort: current directory
151 return std::filesystem::current_path() / "crash_logs";
152}
153
154std::filesystem::path CrashHandler::GetMostRecentCrashLog() {
155 auto crash_dir = GetCrashLogDirectory();
156
157 if (!std::filesystem::exists(crash_dir)) {
158 return {};
159 }
160
161 std::vector<std::filesystem::path> logs;
162 for (const auto& entry : std::filesystem::directory_iterator(crash_dir)) {
163 if (entry.path().extension() == ".log" &&
164 entry.path().filename().string().starts_with("crash_")) {
165 logs.push_back(entry.path());
166 }
167 }
168
169 if (logs.empty()) {
170 return {};
171 }
172
173 // Sort by modification time, newest first
174 std::sort(logs.begin(), logs.end(),
175 [](const std::filesystem::path& a, const std::filesystem::path& b) {
176 return std::filesystem::last_write_time(a) >
177 std::filesystem::last_write_time(b);
178 });
179
180 return logs.front();
181}
182
184 auto crash_dir = GetCrashLogDirectory();
185 auto ack_file = crash_dir / ".acknowledged";
186
187 auto most_recent = GetMostRecentCrashLog();
188 if (most_recent.empty()) {
189 return false;
190 }
191
192 // Check if there's an acknowledgment file newer than the crash log
193 if (std::filesystem::exists(ack_file)) {
194 auto ack_time = std::filesystem::last_write_time(ack_file);
195 auto crash_time = std::filesystem::last_write_time(most_recent);
196 return crash_time > ack_time;
197 }
198
199 return true;
200}
201
203 auto crash_dir = GetCrashLogDirectory();
204 auto ack_file = crash_dir / ".acknowledged";
205
206 // Create/update the acknowledgment file
207 std::ofstream ack(ack_file);
208 ack << "acknowledged";
209}
210
211void CrashHandler::CleanupOldLogs(int keep_count) {
212 auto crash_dir = GetCrashLogDirectory();
213
214 if (!std::filesystem::exists(crash_dir)) {
215 return;
216 }
217
218 std::vector<std::filesystem::path> logs;
219 for (const auto& entry : std::filesystem::directory_iterator(crash_dir)) {
220 if (entry.path().extension() == ".log" &&
221 entry.path().filename().string().starts_with("crash_")) {
222 logs.push_back(entry.path());
223 }
224 }
225
226 if (static_cast<int>(logs.size()) <= keep_count) {
227 return;
228 }
229
230 // Sort by modification time, newest first
231 std::sort(logs.begin(), logs.end(),
232 [](const std::filesystem::path& a, const std::filesystem::path& b) {
233 return std::filesystem::last_write_time(a) >
234 std::filesystem::last_write_time(b);
235 });
236
237 // Remove older logs
238 for (size_t i = keep_count; i < logs.size(); ++i) {
239 std::error_code ec;
240 std::filesystem::remove(logs[i], ec);
241 }
242}
243
244} // namespace util
245} // namespace yaze
static void CleanupOldLogs(int keep_count=5)
Clean up old crash logs, keeping only the most recent N logs.
static void CrashLogWriter(const char *data)
static void Initialize(const std::string &version)
Initialize the crash handler for the application.
static std::filesystem::path GetCrashLogDirectory()
Get the path where crash logs are stored.
static bool HasUnacknowledgedCrashLog()
Check if there's a crash log from a previous session.
static void AcknowledgeCrashLog()
Mark the current crash log as acknowledged.
static std::string version_
static std::filesystem::path GetMostRecentCrashLog()
Get the path to the most recent crash log, if any.
static std::filesystem::path crash_log_path_
static absl::StatusOr< std::filesystem::path > GetTempDirectory()
Get a temporary directory for the application.
static absl::StatusOr< std::filesystem::path > GetAppDataSubdirectory(const std::string &subdir)
Get a subdirectory within the app data folder.