C · 14159 bytes Raw Blame History
1 /* Error handling and logging utilities
2 * Provides comprehensive error tracking and safe logging for gitswitch-c
3 */
4
5 #include <stdio.h>
6 #include <stdlib.h>
7 #include <string.h>
8 #include <stdarg.h>
9 #include <time.h>
10 #include <unistd.h>
11 #include <errno.h>
12 #include <sys/stat.h>
13 #include <sys/mman.h>
14
15 #include "error.h"
16 #include "gitswitch.h"
17
18 /* Global error context */
19 error_context_t g_last_error = {0};
20
21 /* Logging configuration */
22 log_level_t g_log_level = LOG_LEVEL_INFO;
23 FILE *g_log_file = NULL;
24 bool g_log_to_stderr = true;
25
26 /* Error code to string mapping */
27 static const struct {
28 error_code_t code;
29 const char *message;
30 } error_messages[] = {
31 {ERR_SUCCESS, "Success"},
32 {ERR_INVALID_ARGS, "Invalid arguments"},
33 {ERR_CONFIG_NOT_FOUND, "Configuration file not found"},
34 {ERR_CONFIG_INVALID, "Configuration file is invalid"},
35 {ERR_CONFIG_WRITE_FAILED, "Failed to write configuration"},
36 {ERR_ACCOUNT_NOT_FOUND, "Account not found"},
37 {ERR_ACCOUNT_INVALID, "Account configuration is invalid"},
38 {ERR_ACCOUNT_EXISTS, "Account already exists"},
39 {ERR_GIT_NOT_FOUND, "Git command not found"},
40 {ERR_GIT_CONFIG_FAILED, "Git configuration operation failed"},
41 {ERR_GIT_NOT_REPO, "Not a git repository"},
42 {ERR_GIT_CONFIG_NOT_FOUND, "Git configuration not found"},
43 {ERR_GIT_REPOSITORY_INVALID, "Git repository is invalid"},
44 {ERR_SSH_AGENT_FAILED, "SSH agent operation failed"},
45 {ERR_SSH_KEY_FAILED, "SSH key operation failed"},
46 {ERR_SSH_KEY_NOT_FOUND, "SSH key not found"},
47 {ERR_SSH_CONNECTION_FAILED, "SSH connection test failed"},
48 {ERR_SSH_NOT_FOUND, "SSH command not found"},
49 {ERR_SSH_AGENT_NOT_FOUND, "SSH agent command not found"},
50 {ERR_SSH_KEY_LOAD_FAILED, "Failed to load SSH key"},
51 {ERR_SSH_AGENT_START_FAILED, "Failed to start SSH agent"},
52 {ERR_SSH_KEY_INVALID, "SSH key file is invalid"},
53 {ERR_SSH_KEY_PERMISSIONS, "SSH key file has wrong permissions"},
54 {ERR_SSH_KEY_OWNERSHIP, "SSH key file has wrong ownership"},
55 {ERR_SSH_AGENT_SOCKET_INVALID, "SSH agent socket is invalid"},
56 {ERR_GPG_NOT_FOUND, "GPG command not found"},
57 {ERR_GPG_KEY_FAILED, "GPG key operation failed"},
58 {ERR_GPG_KEY_NOT_FOUND, "GPG key not found"},
59 {ERR_GPG_SIGNING_FAILED, "GPG signing operation failed"},
60 {ERR_MEMORY_ALLOCATION, "Memory allocation failed"},
61 {ERR_FILE_IO, "File I/O operation failed"},
62 {ERR_PERMISSION_DENIED, "Permission denied"},
63 {ERR_NETWORK_ERROR, "Network operation failed"},
64 {ERR_SYSTEM_CALL, "System call failed"},
65 {ERR_SYSTEM_REQUIREMENT, "System requirement not met"},
66 {ERR_SYSTEM_COMMAND_FAILED, "System command execution failed"},
67 {ERR_INVALID_PATH, "Invalid file path"},
68 {ERR_UNKNOWN, "Unknown error"}
69 };
70
71 /* Log level to string mapping */
72 static const char* log_level_strings[] = {
73 "DEBUG", "INFO", "WARN", "ERROR", "CRIT"
74 };
75
76 /* Initialize error handling and logging system */
77 int error_init(log_level_t level, const char *log_file_path) {
78 g_log_level = level;
79
80 /* Close existing log file if open */
81 if (g_log_file && g_log_file != stderr) {
82 fclose(g_log_file);
83 g_log_file = NULL;
84 }
85
86 /* Open new log file if specified */
87 if (log_file_path) {
88 g_log_file = fopen(log_file_path, "a");
89 if (!g_log_file) {
90 /* Fall back to stderr if file can't be opened */
91 g_log_file = stderr;
92 set_error(ERR_FILE_IO, "Failed to open log file: %s", log_file_path);
93 return -1;
94 }
95 /* Set log file to line buffered for immediate output */
96 setvbuf(g_log_file, NULL, _IOLBF, 0);
97 } else {
98 g_log_file = stderr;
99 }
100
101 /* Clear any existing error */
102 clear_error();
103
104 log_info("Error handling system initialized (level=%s)",
105 log_level_strings[level]);
106
107 return 0;
108 }
109
110 /* Cleanup error handling system */
111 void error_cleanup(void) {
112 if (g_log_file && g_log_file != stderr) {
113 log_info("Error handling system shutting down");
114 fclose(g_log_file);
115 g_log_file = NULL;
116 }
117
118 /* Clear error context */
119 clear_error();
120 }
121
122 /* Set error context with detailed information */
123 void set_error_context(error_code_t code, const char *file, int line,
124 const char *function, const char *fmt, ...) {
125 va_list args;
126
127 /* Clear previous error */
128 memset(&g_last_error, 0, sizeof(g_last_error));
129
130 g_last_error.code = code;
131 g_last_error.file = file;
132 g_last_error.line = line;
133 g_last_error.function = function;
134 g_last_error.system_errno = 0;
135
136 /* Format the error message */
137 if (fmt) {
138 va_start(args, fmt);
139 vsnprintf(g_last_error.message, sizeof(g_last_error.message), fmt, args);
140 va_end(args);
141 } else {
142 strncpy(g_last_error.message, error_code_to_string(code),
143 sizeof(g_last_error.message) - 1);
144 }
145
146 /* Log the error */
147 log_error("Error set: %s (%s:%d in %s)",
148 g_last_error.message, file, line, function);
149 }
150
151 /* Set error context including system errno */
152 void set_system_error_context(error_code_t code, const char *file, int line,
153 const char *function, const char *fmt, ...) {
154 va_list args;
155 int saved_errno = errno; /* Save errno before any other operations */
156
157 /* Clear previous error */
158 memset(&g_last_error, 0, sizeof(g_last_error));
159
160 g_last_error.code = code;
161 g_last_error.file = file;
162 g_last_error.line = line;
163 g_last_error.function = function;
164 g_last_error.system_errno = saved_errno;
165
166 /* Format the error message */
167 if (fmt) {
168 va_start(args, fmt);
169 vsnprintf(g_last_error.message, sizeof(g_last_error.message), fmt, args);
170 va_end(args);
171 } else {
172 strncpy(g_last_error.message, error_code_to_string(code),
173 sizeof(g_last_error.message) - 1);
174 }
175
176 /* Add system error details */
177 if (saved_errno != 0) {
178 int msg_len = strlen(g_last_error.message);
179 snprintf(g_last_error.details, sizeof(g_last_error.details),
180 "System error: %s (errno=%d)", strerror(saved_errno), saved_errno);
181
182 /* Append system error to message if there's room */
183 if (msg_len < (int)(sizeof(g_last_error.message) - 50)) {
184 snprintf(g_last_error.message + msg_len,
185 sizeof(g_last_error.message) - msg_len,
186 " (%s)", strerror(saved_errno));
187 }
188 }
189
190 /* Log the error with system details */
191 log_error("System error: %s [errno=%d: %s] (%s:%d in %s)",
192 g_last_error.message, saved_errno, strerror(saved_errno),
193 file, line, function);
194 }
195
196 /* Get last error information */
197 const error_context_t *get_last_error(void) {
198 return &g_last_error;
199 }
200
201 /* Clear last error */
202 void clear_error(void) {
203 memset(&g_last_error, 0, sizeof(g_last_error));
204 }
205
206 /* Convert error code to human-readable string */
207 const char *error_code_to_string(error_code_t code) {
208 for (size_t i = 0; i < sizeof(error_messages) / sizeof(error_messages[0]); i++) {
209 if (error_messages[i].code == code) {
210 return error_messages[i].message;
211 }
212 }
213 return "Unknown error code";
214 }
215
216 /* Log message with context information */
217 void log_message(log_level_t level, const char *file, int line,
218 const char *function, const char *fmt, ...) {
219 va_list args;
220 char timestamp[32];
221 char message[1024];
222
223 /* Check if this level should be logged */
224 if (!should_log(level)) {
225 return;
226 }
227
228 /* Format the message */
229 va_start(args, fmt);
230 vsnprintf(message, sizeof(message), fmt, args);
231 va_end(args);
232
233 /* Get timestamp */
234 get_timestamp(timestamp, sizeof(timestamp));
235
236 /* Format complete log entry */
237 const char *level_str = (level < LOG_LEVEL_CRITICAL) ?
238 log_level_strings[level] : "UNKNOWN";
239
240 /* Log to file/stderr */
241 if (g_log_file) {
242 fprintf(g_log_file, "[%s] %s %s:%d (%s) - %s\n",
243 timestamp, level_str, file, line, function, message);
244 fflush(g_log_file);
245 }
246
247 /* Also log to stderr if enabled and not already logging there */
248 if (g_log_to_stderr && g_log_file != stderr) {
249 fprintf(stderr, "[%s] %s - %s\n", timestamp, level_str, message);
250 }
251 }
252
253 /* Set logging level */
254 void set_log_level(log_level_t level) {
255 g_log_level = level;
256 log_info("Log level changed to %s", log_level_strings[level]);
257 }
258
259 /* Set log output file */
260 int set_log_file(const char *file_path) {
261 FILE *new_file = NULL;
262
263 if (file_path) {
264 new_file = fopen(file_path, "a");
265 if (!new_file) {
266 set_system_error(ERR_FILE_IO, "Failed to open log file: %s", file_path);
267 return -1;
268 }
269 setvbuf(new_file, NULL, _IOLBF, 0);
270 } else {
271 new_file = stderr;
272 }
273
274 /* Close old file if it's not stderr */
275 if (g_log_file && g_log_file != stderr) {
276 fclose(g_log_file);
277 }
278
279 g_log_file = new_file;
280 log_info("Log output changed to %s", file_path ? file_path : "stderr");
281
282 return 0;
283 }
284
285 /* Enable/disable logging to stderr */
286 void set_log_to_stderr(bool enable) {
287 g_log_to_stderr = enable;
288 }
289
290 /* Format error message for user display */
291 void format_error_message(char *buffer, size_t buffer_size,
292 const error_context_t *error) {
293 if (!buffer || buffer_size == 0 || !error) {
294 return;
295 }
296
297 if (error->system_errno != 0) {
298 snprintf(buffer, buffer_size,
299 "Error: %s\nDetails: %s\nLocation: %s:%d in %s()",
300 error->message, error->details,
301 error->file, error->line, error->function);
302 } else {
303 snprintf(buffer, buffer_size,
304 "Error: %s\nLocation: %s:%d in %s()",
305 error->message, error->file, error->line, error->function);
306 }
307 }
308
309 /* Print formatted error to stderr */
310 void print_error(const char *prefix) {
311 char error_msg[2048];
312
313 if (g_last_error.code == ERR_SUCCESS) {
314 return; /* No error to print */
315 }
316
317 format_error_message(error_msg, sizeof(error_msg), &g_last_error);
318
319 if (prefix) {
320 fprintf(stderr, "%s: %s\n", prefix, error_msg);
321 } else {
322 fprintf(stderr, "%s\n", error_msg);
323 }
324 }
325
326 /* Check if error level should be logged */
327 bool should_log(log_level_t level) {
328 return level >= g_log_level;
329 }
330
331 /* Get current timestamp for logging */
332 void get_timestamp(char *buffer, size_t buffer_size) {
333 time_t now;
334 struct tm *tm_info;
335
336 if (!buffer || buffer_size == 0) {
337 return;
338 }
339
340 time(&now);
341 tm_info = localtime(&now);
342
343 if (tm_info) {
344 strftime(buffer, buffer_size, "%Y-%m-%d %H:%M:%S", tm_info);
345 } else {
346 strncpy(buffer, "UNKNOWN-TIME", buffer_size - 1);
347 buffer[buffer_size - 1] = '\0';
348 }
349 }
350
351 /* Safe string copy with error context */
352 int safe_strncpy(char *dest, const char *src, size_t dest_size) {
353 if (!dest || !src || dest_size == 0) {
354 set_error(ERR_INVALID_ARGS, "Invalid arguments to safe_strncpy");
355 return -1;
356 }
357
358 size_t src_len = strlen(src);
359 if (src_len >= dest_size) {
360 set_error(ERR_INVALID_ARGS, "String too long for destination buffer");
361 return -1;
362 }
363
364 strncpy(dest, src, dest_size - 1);
365 dest[dest_size - 1] = '\0';
366 return 0;
367 }
368
369 /* Safe string concatenation with error context */
370 int safe_strncat(char *dest, const char *src, size_t dest_size) {
371 if (!dest || !src || dest_size == 0) {
372 set_error(ERR_INVALID_ARGS, "Invalid arguments to safe_strncat");
373 return -1;
374 }
375
376 size_t dest_len = strlen(dest);
377 size_t src_len = strlen(src);
378
379 if (dest_len + src_len >= dest_size) {
380 set_error(ERR_INVALID_ARGS, "String concatenation would overflow buffer");
381 return -1;
382 }
383
384 strncat(dest, src, dest_size - dest_len - 1);
385 return 0;
386 }
387
388 /* Safe snprintf with error context */
389 int safe_snprintf(char *buffer, size_t buffer_size, const char *fmt, ...) {
390 va_list args;
391 int result;
392
393 if (!buffer || !fmt || buffer_size == 0) {
394 set_error(ERR_INVALID_ARGS, "Invalid arguments to safe_snprintf");
395 return -1;
396 }
397
398 va_start(args, fmt);
399 result = vsnprintf(buffer, buffer_size, fmt, args);
400 va_end(args);
401
402 if (result < 0) {
403 set_error(ERR_INVALID_ARGS, "snprintf formatting error");
404 return -1;
405 }
406
407 if ((size_t)result >= buffer_size) {
408 set_error(ERR_INVALID_ARGS, "snprintf output truncated");
409 return -1;
410 }
411
412 return result;
413 }
414
415 /* Safe memory allocation with error context */
416 void *safe_malloc(size_t size) {
417 void *ptr;
418
419 if (size == 0) {
420 set_error(ERR_INVALID_ARGS, "Attempted to allocate zero bytes");
421 return NULL;
422 }
423
424 ptr = malloc(size);
425 if (!ptr) {
426 set_system_error(ERR_MEMORY_ALLOCATION, "Failed to allocate %zu bytes", size);
427 return NULL;
428 }
429
430 return ptr;
431 }
432
433 /* Safe calloc with error context */
434 void *safe_calloc(size_t nmemb, size_t size) {
435 void *ptr;
436
437 if (nmemb == 0 || size == 0) {
438 set_error(ERR_INVALID_ARGS, "Attempted to allocate zero elements");
439 return NULL;
440 }
441
442 ptr = calloc(nmemb, size);
443 if (!ptr) {
444 set_system_error(ERR_MEMORY_ALLOCATION,
445 "Failed to allocate %zu elements of %zu bytes", nmemb, size);
446 return NULL;
447 }
448
449 return ptr;
450 }
451
452 /* Safe realloc with error context */
453 void *safe_realloc(void *ptr, size_t size) {
454 void *new_ptr;
455
456 if (size == 0) {
457 set_error(ERR_INVALID_ARGS, "Attempted to reallocate to zero bytes");
458 return NULL;
459 }
460
461 new_ptr = realloc(ptr, size);
462 if (!new_ptr) {
463 set_system_error(ERR_MEMORY_ALLOCATION, "Failed to reallocate to %zu bytes", size);
464 return NULL;
465 }
466
467 return new_ptr;
468 }