From dca5d04ad4ba1cc5fabf51b213f6384c1cb90367 Mon Sep 17 00:00:00 2001
From: Agustin Ranieri <aguseranieri@gmail.com>
Date: Fri, 16 Aug 2024 09:32:46 -0300
Subject: [PATCH] Logs: Quito macro y mejoro errores (#186)

* Logs: Quito macro y mejoro errores

* Docs: Documento error.h

* Tabs!
---
 README.md                             |   1 +
 docs/navbar.config.json               |   1 +
 src/commons/error.c                   |  10 +-
 src/commons/error.h                   | 169 +++++++++++++++++++++++++-
 src/commons/log.c                     | 114 +++++++++--------
 src/commons/temporal.c                |  21 +++-
 tests/integration-tests/logger/main.c |  13 +-
 7 files changed, 254 insertions(+), 75 deletions(-)

diff --git a/README.md b/README.md
index 5fd9c428..b1caaa56 100644
--- a/README.md
+++ b/README.md
@@ -17,6 +17,7 @@ Provee los siguientes TADs:
 * Manejo de fechas y timestamps (commons/temporal.h)
 * Información de procesos (commons/process.h)
 * Impresión de dumps de memoria (commons/memory.h)
+* Impresión de errores (commons/error.h)
 * Manejo simple de archivos de texto (commons/txt.h)
 
 ## Notas
diff --git a/docs/navbar.config.json b/docs/navbar.config.json
index 908ec10f..ab018075 100644
--- a/docs/navbar.config.json
+++ b/docs/navbar.config.json
@@ -19,6 +19,7 @@
             },
             { "text": "bitarray.h", "url": "bitarray_8h.html" },
             { "text": "config.h", "url": "config_8h.html" },
+            { "text": "error.h", "url": "error_8h.html" },
             { "text": "log.h", "url": "log_8h.html" },
             { "text": "memory.h", "url": "memory_8h.html" },
             { "text": "process.h", "url": "process_8h.html" },
diff --git a/src/commons/error.c b/src/commons/error.c
index 8127e508..a17bdb29 100644
--- a/src/commons/error.c
+++ b/src/commons/error.c
@@ -16,6 +16,7 @@
 
 #include "error.h"
 
+#include <errno.h>
 #include <stdlib.h>
 #include <stdarg.h>
 #include <stdio.h>
@@ -23,16 +24,11 @@
 
 #include "string.h"
 
-
 void error_show(char *message, ...) {
 	va_list arguments;
 	va_start(arguments, message);
-
-	char *error_message = string_duplicate("[[ERROR]]");
-	string_append(&error_message, message);
-
-	vprintf(error_message, arguments);
-
+	char *error_message = string_from_format("[[ERROR]] %s: %s\n", message, strerror(errno));
+	vfprintf(stderr, error_message, arguments);
 	free(error_message);
 	va_end(arguments);
 }
diff --git a/src/commons/error.h b/src/commons/error.h
index f05c839d..e29146b8 100644
--- a/src/commons/error.h
+++ b/src/commons/error.h
@@ -16,14 +16,171 @@
 #ifndef ERROR_H_
 #define ERROR_H_
 
-	/** @cond INCLUDE_INTERNALS */
+	/**
+	 * @file
+	 * @brief `#include <commons/error.h>`
+	 */
 
 	/**
-	* @brief imprime un mensaje con el siguiente formato
-	* 	     [[ERROR]] MESSAGE
+	* @brief imprime por `stderr` un mensaje con el siguiente formato
+	* 	     `[[ERROR]] {{message}}: {{errno}}`
+	*
+	* @note [errno](https://man7.org/linux/man-pages/man3/errno.3.html) es un
+	* número entero que representa el valor del último error ocurrido en el hilo
+	* actual. Varias funciones de la biblioteca estándar de C setean `errno` y
+	* devuelven un valor especial para indicar que hubo un error. Ejemplo:
+	* @code
+	* if (bind(...) == -1) {
+	*     error_show("No se pudo hacer bind");
+	*     abort();
+	* }
+	*
+	* => [[ERROR]] No se pudo hacer bind: address already in use
+	* @endcode
+	*
+	* La función utiliza
+	* [strerror](https://man7.org/linux/man-pages/man3/strerror.3.html) para
+	* imprimir la descripción del error.
+	* Estos son todos los valores posibles de `errno` con sus respectivas
+	* descripciones:
+	*
+	* | number | hex | symbol | description |
+	* |:------:|:---:|--------|-------------|
+	* | 1 | 0x01 | EPERM | Operation not permitted |
+	* | 2 | 0x02 | ENOENT | No such file or directory |
+	* | 3 | 0x03 | ESRCH | No such process |
+	* | 4 | 0x04 | EINTR | Interrupted system call |
+	* | 5 | 0x05 | EIO | Input/output error |
+	* | 6 | 0x06 | ENXIO | No such device or address |
+	* | 7 | 0x07 | E2BIG | Argument list too long |
+	* | 8 | 0x08 | ENOEXEC | Exec format error |
+	* | 9 | 0x09 | EBADF | Bad file descriptor |
+	* | 10 | 0x0a | ECHILD | No child processes |
+	* | 11 | 0x0b | EAGAIN | Resource temporarily unavailable |
+	* | 11 | 0x0b | EWOULDBLOCK | <i>(Same value as EAGAIN)</i> Resource temporarily unavailable |
+	* | 12 | 0x0c | ENOMEM | Cannot allocate memory |
+	* | 13 | 0x0d | EACCES | Permission denied |
+	* | 14 | 0x0e | EFAULT | Bad address |
+	* | 15 | 0x0f | ENOTBLK | Block device required |
+	* | 16 | 0x10 | EBUSY | Device or resource busy |
+	* | 17 | 0x11 | EEXIST | File exists |
+	* | 18 | 0x12 | EXDEV | Invalid cross-device link |
+	* | 19 | 0x13 | ENODEV | No such device |
+	* | 20 | 0x14 | ENOTDIR | Not a directory |
+	* | 21 | 0x15 | EISDIR | Is a directory |
+	* | 22 | 0x16 | EINVAL | Invalid argument |
+	* | 23 | 0x17 | ENFILE | Too many open files in system |
+	* | 24 | 0x18 | EMFILE | Too many open files |
+	* | 25 | 0x19 | ENOTTY | Inappropriate ioctl for device |
+	* | 26 | 0x1a | ETXTBSY | Text file busy |
+	* | 27 | 0x1b | EFBIG | File too large |
+	* | 28 | 0x1c | ENOSPC | No space left on device |
+	* | 29 | 0x1d | ESPIPE | Illegal seek |
+	* | 30 | 0x1e | EROFS | Read-only file system |
+	* | 31 | 0x1f | EMLINK | Too many links |
+	* | 32 | 0x20 | EPIPE | Broken pipe |
+	* | 33 | 0x21 | EDOM | Numerical argument out of domain |
+	* | 34 | 0x22 | ERANGE | Numerical result out of range |
+	* | 35 | 0x23 | EDEADLK | Resource deadlock avoided |
+	* | 35 | 0x23 | EDEADLOCK | <i>(Same value as EDEADLK)</i> Resource deadlock avoided |
+	* | 36 | 0x24 | ENAMETOOLONG | File name too long |
+	* | 37 | 0x25 | ENOLCK | No locks available |
+	* | 38 | 0x26 | ENOSYS | Function not implemented |
+	* | 39 | 0x27 | ENOTEMPTY | Directory not empty |
+	* | 40 | 0x28 | ELOOP | Too many levels of symbolic links |
+	* | 42 | 0x2a | ENOMSG | No message of desired type |
+	* | 43 | 0x2b | EIDRM | Identifier removed |
+	* | 44 | 0x2c | ECHRNG | Channel number out of range |
+	* | 45 | 0x2d | EL2NSYNC | Level 2 not synchronized |
+	* | 46 | 0x2e | EL3HLT | Level 3 halted |
+	* | 47 | 0x2f | EL3RST | Level 3 reset |
+	* | 48 | 0x30 | ELNRNG | Link number out of range |
+	* | 49 | 0x31 | EUNATCH | Protocol driver not attached |
+	* | 50 | 0x32 | ENOCSI | No CSI structure available |
+	* | 51 | 0x33 | EL2HLT | Level 2 halted |
+	* | 52 | 0x34 | EBADE | Invalid exchange |
+	* | 53 | 0x35 | EBADR | Invalid request descriptor |
+	* | 54 | 0x36 | EXFULL | Exchange full |
+	* | 55 | 0x37 | ENOANO | No anode |
+	* | 56 | 0x38 | EBADRQC | Invalid request code |
+	* | 57 | 0x39 | EBADSLT | Invalid slot |
+	* | 59 | 0x3b | EBFONT | Bad font file format |
+	* | 60 | 0x3c | ENOSTR | Device not a stream |
+	* | 61 | 0x3d | ENODATA | No data available |
+	* | 62 | 0x3e | ETIME | Timer expired |
+	* | 63 | 0x3f | ENOSR | Out of streams resources |
+	* | 64 | 0x40 | ENONET | Machine is not on the network |
+	* | 65 | 0x41 | ENOPKG | Package not installed |
+	* | 66 | 0x42 | EREMOTE | Object is remote |
+	* | 67 | 0x43 | ENOLINK | Link has been severed |
+	* | 68 | 0x44 | EADV | Advertise error |
+	* | 69 | 0x45 | ESRMNT | Srmount error |
+	* | 70 | 0x46 | ECOMM | Communication error on send |
+	* | 71 | 0x47 | EPROTO | Protocol error |
+	* | 72 | 0x48 | EMULTIHOP | Multihop attempted |
+	* | 73 | 0x49 | EDOTDOT | RFS specific error |
+	* | 74 | 0x4a | EBADMSG | Bad message |
+	* | 75 | 0x4b | EOVERFLOW | Value too large for defined data type |
+	* | 76 | 0x4c | ENOTUNIQ | Name not unique on network |
+	* | 77 | 0x4d | EBADFD | File descriptor in bad state |
+	* | 78 | 0x4e | EREMCHG | Remote address changed |
+	* | 79 | 0x4f | ELIBACC | Can not access a needed shared library |
+	* | 80 | 0x50 | ELIBBAD | Accessing a corrupted shared library |
+	* | 81 | 0x51 | ELIBSCN | .lib section in a.out corrupted |
+	* | 82 | 0x52 | ELIBMAX | Attempting to link in too many shared libraries |
+	* | 83 | 0x53 | ELIBEXEC | Cannot exec a shared library directly |
+	* | 84 | 0x54 | EILSEQ | Invalid or incomplete multibyte or wide character |
+	* | 85 | 0x55 | ERESTART | Interrupted system call should be restarted |
+	* | 86 | 0x56 | ESTRPIPE | Streams pipe error |
+	* | 87 | 0x57 | EUSERS | Too many users |
+	* | 88 | 0x58 | ENOTSOCK | Socket operation on non-socket |
+	* | 89 | 0x59 | EDESTADDRREQ | Destination address required |
+	* | 90 | 0x5a | EMSGSIZE | Message too long |
+	* | 91 | 0x5b | EPROTOTYPE | Protocol wrong type for socket |
+	* | 92 | 0x5c | ENOPROTOOPT | Protocol not available |
+	* | 93 | 0x5d | EPROTONOSUPPORT | Protocol not supported |
+	* | 94 | 0x5e | ESOCKTNOSUPPORT | Socket type not supported |
+	* | 95 | 0x5f | EOPNOTSUPP | Operation not supported |
+	* | 95 | 0x5f | ENOTSUP | <i>(Same value as EOPNOTSUPP)</i> Operation not supported |
+	* | 96 | 0x60 | EPFNOSUPPORT | Protocol family not supported |
+	* | 97 | 0x61 | EAFNOSUPPORT | Address family not supported by protocol |
+	* | 98 | 0x62 | EADDRINUSE | Address already in use |
+	* | 99 | 0x63 | EADDRNOTAVAIL | Cannot assign requested address |
+	* | 100 | 0x64 | ENETDOWN | Network is down |
+	* | 101 | 0x65 | ENETUNREACH | Network is unreachable |
+	* | 102 | 0x66 | ENETRESET | Network dropped connection on reset |
+	* | 103 | 0x67 | ECONNABORTED | Software caused connection abort |
+	* | 104 | 0x68 | ECONNRESET | Connection reset by peer |
+	* | 105 | 0x69 | ENOBUFS | No buffer space available |
+	* | 106 | 0x6a | EISCONN | Transport endpoint is already connected |
+	* | 107 | 0x6b | ENOTCONN | Transport endpoint is not connected |
+	* | 108 | 0x6c | ESHUTDOWN | Cannot send after transport endpoint shutdown |
+	* | 109 | 0x6d | ETOOMANYREFS | Too many references: cannot splice |
+	* | 110 | 0x6e | ETIMEDOUT | Connection timed out |
+	* | 111 | 0x6f | ECONNREFUSED | Connection refused |
+	* | 112 | 0x70 | EHOSTDOWN | Host is down |
+	* | 113 | 0x71 | EHOSTUNREACH | No route to host |
+	* | 114 | 0x72 | EALREADY | Operation already in progress |
+	* | 115 | 0x73 | EINPROGRESS | Operation now in progress |
+	* | 116 | 0x74 | ESTALE | Stale file handle |
+	* | 117 | 0x75 | EUCLEAN | Structure needs cleaning |
+	* | 118 | 0x76 | ENOTNAM | Not a XENIX named type file |
+	* | 119 | 0x77 | ENAVAIL | No XENIX semaphores available |
+	* | 120 | 0x78 | EISNAM | Is a named type file |
+	* | 121 | 0x79 | EREMOTEIO | Remote I/O error |
+	* | 122 | 0x7a | EDQUOT | Disk quota exceeded |
+	* | 123 | 0x7b | ENOMEDIUM | No medium found |
+	* | 124 | 0x7c | EMEDIUMTYPE | Wrong medium type |
+	* | 125 | 0x7d | ECANCELED | Operation canceled |
+	* | 126 | 0x7e | ENOKEY | Required key not available |
+	* | 127 | 0x7f | EKEYEXPIRED | Key has expired |
+	* | 128 | 0x80 | EKEYREVOKED | Key has been revoked |
+	* | 129 | 0x81 | EKEYREJECTED | Key was rejected by service |
+	* | 130 | 0x82 | EOWNERDEAD | Owner died |
+	* | 131 | 0x83 | ENOTRECOVERABLE | State not recoverable |
+	* | 132 | 0x84 | ERFKILL | Operation not possible due to RF-kill |
+	* | 133 | 0x85 | EHWPOISON | Memory page has hardware error |
 	*/
-	void error_show(char *message, ...);
-
-	/** @endcond */
+	void error_show(char *message, ...) __attribute__((format(printf, 1, 2)));
 
 #endif /* ERROR_H_ */
diff --git a/src/commons/log.c b/src/commons/log.c
index bc322504..476f705a 100644
--- a/src/commons/log.c
+++ b/src/commons/log.c
@@ -24,26 +24,19 @@
 
 #include <stdlib.h>
 #include <stdbool.h>
-#include <stdarg.h>
+
 
 #define LOG_ENUM_SIZE 5
 
 static char *enum_names[LOG_ENUM_SIZE] = {"TRACE", "DEBUG", "INFO", "WARNING", "ERROR"};
 static char *log_colors[LOG_ENUM_SIZE] = {"\x1b[36m", "\x1b[32m", "", "\x1b[33m", "\x1b[31m" };
 static char *reset_color = "\x1b[0m";
+
 /**
  * Private Functions
  */
 static void _log_write_in_level(t_log* logger, t_log_level level, const char* message_template, va_list arguments);
-static bool _isEnableLevelInLogger(t_log* logger, t_log_level level);
-
-#define log_impl_template(log_function, level_enum) 									\
-		void log_function(t_log* logger, const char* message_template, ...) { 			\
-			va_list arguments;															\
-			va_start(arguments, message_template);										\
-			_log_write_in_level(logger, level_enum, message_template, arguments);		\
-			va_end(arguments);															\
-		}																				\
+static bool _is_level_enabled(t_log* logger, t_log_level level);
 
 /**
  * Public Functions
@@ -54,7 +47,7 @@ t_log* log_create(char* file, char *program_name, bool is_active_console, t_log_
 	t_log* logger = malloc(sizeof(t_log));
 
 	if (logger == NULL) {
-		perror("Cannot create logger");
+		error_show("Cannot create logger");
 		return NULL;
 	}
 
@@ -64,7 +57,7 @@ t_log* log_create(char* file, char *program_name, bool is_active_console, t_log_
 		file_opened = txt_open_for_append(file);
 
 		if (file_opened == NULL) {
-			perror("Cannot create/open log file");
+			error_show("Cannot create/open log file");
 			free(logger);
 			return NULL;
 		}
@@ -84,15 +77,40 @@ void log_destroy(t_log* logger) {
 	free(logger);
 }
 
-log_impl_template(log_trace, LOG_LEVEL_TRACE);
+void log_trace(t_log* logger, const char* message_template, ...) {
+	va_list arguments;
+	va_start(arguments, message_template);
+	_log_write_in_level(logger, LOG_LEVEL_TRACE, message_template, arguments);
+	va_end(arguments);
+}
 
-log_impl_template(log_debug, LOG_LEVEL_DEBUG);
+void log_debug(t_log* logger, const char* message_template, ...) {
+	va_list arguments;
+	va_start(arguments, message_template);
+	_log_write_in_level(logger, LOG_LEVEL_DEBUG, message_template, arguments);
+	va_end(arguments);
+}
 
-log_impl_template(log_info, LOG_LEVEL_INFO);
+void log_info(t_log* logger, const char* message_template, ...) {
+	va_list arguments;
+	va_start(arguments, message_template);
+	_log_write_in_level(logger, LOG_LEVEL_INFO, message_template, arguments);
+	va_end(arguments);
+}
 
-log_impl_template(log_warning, LOG_LEVEL_WARNING);
+void log_warning(t_log* logger, const char* message_template, ...) {
+	va_list arguments;
+	va_start(arguments, message_template);
+	_log_write_in_level(logger, LOG_LEVEL_WARNING, message_template, arguments);
+	va_end(arguments);
+}
 
-log_impl_template(log_error, LOG_LEVEL_ERROR);
+void log_error(t_log* logger, const char* message_template, ...) {
+	va_list arguments;
+	va_start(arguments, message_template);
+	_log_write_in_level(logger, LOG_LEVEL_ERROR, message_template, arguments);
+	va_end(arguments);
+}
 
 char *log_level_as_string(t_log_level level) {
 	return enum_names[level];
@@ -103,9 +121,7 @@ char *log_level_color(t_log_level level) {
 }
 
 t_log_level log_level_from_string(char *level) {
-	int i;
-
-	for (i = 0; i < LOG_ENUM_SIZE; i++) {
+	for (int i = 0; i < LOG_ENUM_SIZE; i++) {
 		if (string_equals_ignore_case(level, enum_names[i])){
 			return i;
 		}
@@ -117,42 +133,40 @@ t_log_level log_level_from_string(char *level) {
 /** PRIVATE FUNCTIONS **/
 
 static void _log_write_in_level(t_log* logger, t_log_level level, const char* message_template, va_list list_arguments) {
+	if (!_is_level_enabled(logger, level)) {
+		return;
+	}
 
-	if (_isEnableLevelInLogger(logger, level)) {
-		char *message, *time, *buffer, *console_buffer;
-		unsigned int thread_id;
-
-		message = string_from_vformat(message_template, list_arguments);
-		time = temporal_get_string_time("%H:%M:%S:%MS");
-		thread_id = process_get_thread_id();
-
-		buffer = string_from_format("[%s] %s %s/(%d:%d): %s\n",
-			log_level_as_string(level),
-			time,
-			logger->program_name,
-			logger->pid,
-			thread_id,
-			message);
+	char *message = string_from_vformat(message_template, list_arguments);
+	char *time = temporal_get_string_time("%H:%M:%S:%MS");
+	unsigned int thread_id = process_get_thread_id();
 
-		if (logger->file != NULL) {
-			txt_write_in_file(logger->file, buffer);
-		}
+	char *buffer = string_from_format("[%s] %s %s/(%d:%d): %s\n",
+		log_level_as_string(level),
+		time,
+		logger->program_name,
+		logger->pid,
+		thread_id,
+		message);
 
-		if (logger->is_active_console) {
-			console_buffer = string_from_format("%s%s%s",
-				log_level_color(level),
-				buffer,
-				reset_color);
-			txt_write_in_stdout(console_buffer);
-			free(console_buffer);
-		}
+	if (logger->file != NULL) {
+		txt_write_in_file(logger->file, buffer);
+	}
 
-		free(time);
-		free(message);
-		free(buffer);
+	if (logger->is_active_console) {
+		char *console_buffer = string_from_format("%s%s%s",
+			log_level_color(level),
+			buffer,
+			reset_color);
+		txt_write_in_stdout(console_buffer);
+		free(console_buffer);
 	}
+
+	free(time);
+	free(message);
+	free(buffer);
 }
 
-static bool _isEnableLevelInLogger(t_log* logger, t_log_level level) {
+static bool _is_level_enabled(t_log* logger, t_log_level level) {
 	return level >= logger->detail;
 }
diff --git a/src/commons/temporal.c b/src/commons/temporal.c
index 81c5e19b..cb711a2e 100644
--- a/src/commons/temporal.c
+++ b/src/commons/temporal.c
@@ -55,7 +55,11 @@ t_temporal* temporal_create(void) {
 	self->elapsed_ms = 0;
 	self->status = TEMPORAL_STATUS_RUNNING;
 
-	clock_gettime(CLOCK_MONOTONIC_RAW, &self->current);
+	if (clock_gettime(CLOCK_MONOTONIC_RAW, &self->current) == -1) {
+		error_show("Error getting time!");
+		free(self);
+		return NULL;
+	}
 
 	return self;
 }
@@ -89,7 +93,10 @@ void temporal_resume(t_temporal* temporal) {
 	}
 
 	temporal->status = TEMPORAL_STATUS_RUNNING;
-	clock_gettime(CLOCK_MONOTONIC_RAW, &temporal->current);
+
+	if (clock_gettime(CLOCK_MONOTONIC_RAW, &temporal->current) == -1) {
+		error_show("Error getting time!");
+	}
 }
 
 int64_t temporal_diff(t_temporal* temporal_1, t_temporal* temporal_2) {
@@ -98,9 +105,11 @@ int64_t temporal_diff(t_temporal* temporal_1, t_temporal* temporal_2) {
 
 static int64_t calculate_delta_ms(t_temporal* temporal) {
 	struct timespec now;
-	clock_gettime(CLOCK_MONOTONIC_RAW, &now);
-
-	int64_t delta_ms = (now.tv_sec - temporal->current.tv_sec) * 1000 + (now.tv_nsec - temporal->current.tv_nsec) / 1000000;
+	if (clock_gettime(CLOCK_MONOTONIC_RAW, &now) == -1) {
+		error_show("Error getting time!");
+		return 0;
+	}
 
-	return delta_ms;
+	return (now.tv_sec - temporal->current.tv_sec) * 1000
+		+ (now.tv_nsec - temporal->current.tv_nsec) / 1000000;
 }
diff --git a/tests/integration-tests/logger/main.c b/tests/integration-tests/logger/main.c
index 429ad081..2508eb6e 100644
--- a/tests/integration-tests/logger/main.c
+++ b/tests/integration-tests/logger/main.c
@@ -1,4 +1,4 @@
-/* 
+/*
  * File:   main.c
  * Author: shinichi
  *
@@ -9,6 +9,7 @@
 #include <stdlib.h>
 #include <unistd.h>
 #include <commons/log.h>
+#include <commons/error.h>
 #include <commons/string.h>
 #include <pthread.h>
 
@@ -20,6 +21,7 @@ static void log_in_disk(char* temp_file) {
     log_info(logger, "LOG A NIVEL %s", "INFO");
     log_warning(logger, "LOG A NIVEL %s", "WARNING");
     log_error(logger, "LOG A NIVEL %s", "ERROR");
+    error_show("ERROR DE PRUEBA");
 
     log_destroy(logger);
     free(temp_file);
@@ -27,11 +29,11 @@ static void log_in_disk(char* temp_file) {
 
 int main(int argc, char** argv) {
     pthread_t th1, th2;
-    
+
     char temp_file[] = "build/XXXXXX";
 
     close(mkstemp(temp_file));
-    
+
     if (temp_file != NULL) {
         pthread_create(&th1, NULL, (void*) log_in_disk, string_duplicate(temp_file));
         pthread_create(&th2, NULL, (void*) log_in_disk, string_duplicate(temp_file));
@@ -42,8 +44,7 @@ int main(int argc, char** argv) {
     } else {
         printf("No se pudo generar el archivo de log\n");
     }
-    
-    
+
+
     return (EXIT_SUCCESS);
 }
-