From 85ad0ab10352318f847b7324ad9b67b26e863f55 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Alexe=C3=AF=20KADIR?= Date: Sat, 17 Feb 2024 00:40:09 +0100 Subject: [PATCH] Started working on a refactored version --- src/backing.h | 39 ------- src/command.c | 97 ---------------- src/command.h | 49 -------- src/disk.c | 231 ------------------------------------- src/disk.h | 43 ------- src/entry.c | 23 ++++ src/entry.h | 8 +- src/sandbox.c | 143 +++++++++++++++++++---- src/sandbox.h | 18 ++- src/utils.c | 307 ++++++++++---------------------------------------- src/utils.h | 58 +++++----- 11 files changed, 254 insertions(+), 762 deletions(-) delete mode 100644 src/backing.h delete mode 100644 src/command.c delete mode 100644 src/command.h delete mode 100644 src/disk.c delete mode 100644 src/disk.h create mode 100644 src/entry.c diff --git a/src/backing.h b/src/backing.h deleted file mode 100644 index a829bda..0000000 --- a/src/backing.h +++ /dev/null @@ -1,39 +0,0 @@ -#pragma once - -#include -#include - -#define BACKING_POOL "/var/lib/sandbox/backings" - -/// @brief Checks whether the backing pool exists and is a directory. -/// @return True if the backing pool exists and is a directory, false otherwise. -bool check_backing_pool(void); - -/// @brief Checks whether the backing disk id is valid. -/// @param backing_id The backing disk id to check. -/// @return True if the backing disk id is valid, false otherwise. -bool is_backing_id_valid(const char* backing_id); - -/// @brief Checks whether the backing disk exists. -/// @param backing_id The backing disk id to check. -/// @return True if the backing disk exists, false otherwise. -bool backing_exists(const char* backing_id); - -/// @brief Adds a backing disk to the backing pool. -/// @param backing_id The backing disk id to add. -/// @param entry_id The entry from which the backing disk should be added. -/// @return True if the backing disk was added, false otherwise. -bool add_backing(const char* backing_id, const char* entry_id); - -/// @brief Removes a backing disk from the backing pool, as well as any backing disks that depend on it. -/// @param backing_id The backing disk id to remove. -/// @return True if the backing disk was removed, false otherwise. -bool remove_backing(const char* backing_id); - -/// @brief Lists all backing disks in the backing pool. -/// @return An array of backing disk ids or NULL if an error occurred. The result must be freed with free(), as well as each individual backing disk id. -char** list_backings(void); - -/// @brief Clears all backing disks from the backing pool. -/// @return True if all backing disks were removed, false otherwise. -bool clear_backings(void); diff --git a/src/command.c b/src/command.c deleted file mode 100644 index 3ce4f7a..0000000 --- a/src/command.c +++ /dev/null @@ -1,97 +0,0 @@ -#include "command.h" - -#include "sandbox.h" - -#include -#include - -Command COMMANDS[] = { - {CMD_HELP, "help", "[command]", "Shows help for all or a specific command.", "TODO"}, - {CMD_VERSION, "version", "", "Shows the version of the program.", "TODO"}, - {CMD_SEPARATOR}, - {CMD_ADD_ENTRY, "add-entry", " [--root|-r ] [--backing|-b ]", "Adds a new entry to the pool", "TODO"}, - {CMD_REMOVE_ENTRY, "remove-entry", "", "Removes an entry from the pool", "TODO"}, - {CMD_LIST_ENTRIES, "list-entries", "", "Lists all entries in the pool", "TODO"}, - {CMD_CLEAR_ENTRIES, "clear-entries", "", "Clears all entries from the pool", "TODO"}, - {CMD_SEPARATOR}, - {CMD_SYNC, "sync", "", "Synchronizes the backing pool with the server", "TODO"}, - {CMD_ADD_BACKING, "add-backing", "", "Adds a backing disk to the pool from the given entry", "TODO"}, - {CMD_REMOVE_BACKING, "remove-backing", "", "Removes a backing disk from the pool", "TODO"}, - {CMD_LIST_BACKINGS, "list-backings", "[--tree|-t]", "Lists all backing disks in the pool", "TODO"}, - {CMD_CLEAR_BACKINGS, "clear-backings", "", "Clears all backing disks from the pool", "TODO"}, - {CMD_SEPARATOR}, - {CMD_START, "start", " [--no-pci] [--vnc ] [--iso ]", "Starts the sandbox with the given entry", "TODO"}, - {CMD_POWER, "power", "", "Sends a power signal to the sandbox", "TODO"}, - {CMD_STOP, "stop", "[--force|-f] [--timeout|-t ]", "Stops the sandbox", "TODO"}, - {CMD_STATUS, "status", "", "Shows the status of the sandbox", "TODO"}, -}; - -Command* find_command(const char* name) { - Command* match = NULL; - - // Find all matching commands (starting with the given name) - size_t length = strlen(name); - for (size_t i = 0; i < sizeof(COMMANDS) / sizeof(COMMANDS[0]); i++) { - if (COMMANDS[i].name == NULL) - continue; - - // Check the sizes - if (strlen(COMMANDS[i].name) < length) - continue; - - if (strncmp(name, COMMANDS[i].name, length) == 0) { - // Check for multiple matches - if (match != NULL) - return NULL; - - match = &COMMANDS[i]; - } - } - - return match; -} - -void show_help() { - printf("Usage: sandbox [args]\n\n"); - printf("Commands:\n"); - - for (size_t i = 0; i < sizeof(COMMANDS) / sizeof(COMMANDS[0]); i++) { - if (COMMANDS[i].id == CMD_SEPARATOR) { - printf("\n"); - continue; - } - - printf(" %s %s\n", COMMANDS[i].name, COMMANDS[i].args); - printf(" %s\n", COMMANDS[i].description); - } - - printf("\n"); - printf("For more information about a specific command, use 'sandbox help '.\n"); -} - -int cmd_help(int argc, char** argv) { - if (argc == 0) { - show_help(); - return 0; - } - - Command* command = find_command(argv[0]); - if (command == NULL) { - printf("Unknown command '%s'.\n", argv[0]); - return 1; - } - - printf("Usage: sandbox %s %s\n", command->name, command->args); - printf(" %s\n", command->description); - - if (command->details != NULL) { - printf("\n"); - printf("%s\n", command->details); - } - return 0; -} - -int cmd_version(int argc, char** argv) { - printf("Sandbox version v" SANDBOX_VERSION "\n"); - return 0; -} diff --git a/src/command.h b/src/command.h deleted file mode 100644 index 048606b..0000000 --- a/src/command.h +++ /dev/null @@ -1,49 +0,0 @@ -#pragma once - -#include - -typedef enum { - CMD_UNKNOWN, - CMD_SEPARATOR, - - CMD_HELP, - CMD_VERSION, - - CMD_ADD_ENTRY, - CMD_REMOVE_ENTRY, - CMD_LIST_ENTRIES, - CMD_CLEAR_ENTRIES, - - CMD_SYNC, - CMD_ADD_BACKING, - CMD_REMOVE_BACKING, - CMD_LIST_BACKINGS, - CMD_CLEAR_BACKINGS, - - CMD_START, - CMD_POWER, - CMD_STOP, - CMD_STATUS, -} CommandID; - -typedef struct { - CommandID id; - const char* name; - const char* args; - const char* description; - const char* details; -} Command; - -extern Command COMMANDS[]; - -/// @brief Finds the best matching command for the given command name. -/// @param name The name of the command to find. -/// @return The best matching command or NULL if no or multiple matches were found. No need to free the result. -Command* find_command(const char* name); - -/// @brief Shows the help for all commands. -void show_help(); - -int cmd_help(int argc, char** argv); - -int cmd_version(int argc, char** argv); \ No newline at end of file diff --git a/src/disk.c b/src/disk.c deleted file mode 100644 index a811c4d..0000000 --- a/src/disk.c +++ /dev/null @@ -1,231 +0,0 @@ -#include "disk.h" - -#include "utils.h" - -#include -#include -#include -#include -#include -#include -#include - -bool create_empty_disk(const char* path, uint64_t size) { - char* errb = NULL; - - // Convert the size to a string - char* size_str = format("%lu", size); - if (size_str == NULL) - return false; - - // Create the disk - int ret = execute(NULL, &errb, "qemu-img", "create", "-f", "qcow2", path, size_str, NULL); - - free(size_str); - - // Check for errors - if (ret != 0) { - if (errb == NULL) - log_message(LOG_ERROR, "Failed to create disk %s.", path); - else { - size_t length = strlen(errb); - for (size_t i = 0; i < length; i++) - if (errb[i] == '\n') - errb[i] = ' '; - - log_message(LOG_ERROR, "Failed to create disk %s (%s).", path, errb); - } - - return false; - } - - return true; -} - -bool create_backed_disk(const char* path, const char* backing_disk) { - char* errb = NULL; - - // Create the disk - int ret = execute(NULL, &errb, "qemu-img", "create", "-f", "qcow2", "-F", "qcow2", "-b", backing_disk, path, NULL); - - // Check for errors - if (ret != 0) { - if (errb == NULL) - log_message(LOG_ERROR, "Failed to create disk %s.", path); - else { - size_t length = strlen(errb); - for (size_t i = 0; i < length; i++) - if (errb[i] == '\n') - errb[i] = ' '; - - log_message(LOG_ERROR, "Failed to create disk %s (%s).", path, errb); - } - - return false; - } - - return true; -} - -bool trim_disk(const char* path) { - char* tmp_path = format("%s.tmp", path); - if (tmp_path == NULL) - return false; - - DiskInfo info; - if (!get_disk_info(path, &info)) { - free(tmp_path); - return false; - } - - char* errb = NULL; - - // Create the trimmed disk - int ret = 0; - - if (info.backing_file != NULL) { - char* backing_str = format("backing_file=%s", info.backing_file); - if (backing_str == NULL) { - free(tmp_path); - free_disk_info(&info); - return false; - } - - ret = execute(NULL, &errb, "qemu-img", "convert", "-f", "qcow2", "-F", "qcow2", "-O", "qcow2", "-o", backing_str, path, tmp_path, NULL); - - free(backing_str); - } else - ret = execute(NULL, &errb, "qemu-img", "convert", "-f", "qcow2", "-O", "qcow2", path, tmp_path, NULL); - - // Free the disk info as we don't need it anymore - free_disk_info(&info); - - // Check for errors - if (ret != 0) { - if (errb == NULL) - log_message(LOG_ERROR, "Failed to trim disk %s.", path); - else { - size_t length = strlen(errb); - for (size_t i = 0; i < length; i++) - if (errb[i] == '\n') - errb[i] = ' '; - - log_message(LOG_ERROR, "Failed to trim disk %s (%s).", path, errb); - } - - unlink(tmp_path); - - free(tmp_path); - return false; - } - - // Replace the original disk with the trimmed disk - if (rename(tmp_path, path) != 0) { - log_message(LOG_ERROR, "Failed to replace disk %s with the trimmed disk (%s).", path, strerror(errno)); - - unlink(tmp_path); - - free(tmp_path); - return false; - } - - free(tmp_path); - return true; -} - -bool rebase_disk(const char* path, const char* backing_disk) { - char* errb = NULL; - - // Rebase the disk - int ret = execute(NULL, &errb, "qemu-img", "rebase", "-u", "-F", "qcow2", "-b", backing_disk, path, NULL); - - // Check for errors - if (ret != 0) { - if (errb == NULL) - log_message(LOG_ERROR, "Failed to rebase disk %s.", path); - else { - size_t length = strlen(errb); - for (size_t i = 0; i < length; i++) - if (errb[i] == '\n') - errb[i] = ' '; - - log_message(LOG_ERROR, "Failed to rebase disk %s (%s).", path, errb); - } - - return false; - } - - return true; -} - -bool get_disk_info(const char* path, DiskInfo* info) { - char* outb = NULL; - char* errb = NULL; - - int ret = execute(&outb, &errb, "qemu-img", "info", "--output=json", path, NULL); - - if (ret != 0) { - if (errb == NULL) - log_message(LOG_ERROR, "Failed to get information about disk %s.", path); - else { - size_t length = strlen(errb); - for (size_t i = 0; i < length; i++) - if (errb[i] == '\n') - errb[i] = ' '; - - log_message(LOG_ERROR, "Failed to get information about disk %s (%s).", path, errb); - } - - free(outb); - return false; - } - - // Free the error buffer, as we don't need it anymore - free(errb); - - json_object* root = json_tokener_parse(outb); - - // Free the output buffer, as we don't need it anymore - free(outb); - - if (root == NULL) { - log_message(LOG_ERROR, "Failed to parse the JSON output from qemu-img."); - return false; - } - - // Get the virtual size - json_object* virtual_size = json_object_object_get(root, "virtual-size"); - if (virtual_size == NULL) - info->virtual_size = 0; - else - info->virtual_size = json_object_get_int64(virtual_size); - - // Get the actual size - json_object* actual_size = json_object_object_get(root, "actual-size"); - if (actual_size == NULL) - info->actual_size = 0; - else - info->actual_size = json_object_get_int64(actual_size); - - // Get the backing file - json_object* backing_file = json_object_object_get(root, "backing-filename"); - if (backing_file == NULL) - info->backing_file = NULL; - else { - info->backing_file = strdup(json_object_get_string(backing_file)); - if (info->backing_file == NULL) { - log_message(LOG_ERROR, "Failed to allocate memory for the backing file path."); - - json_object_put(root); - return false; - } - } - - json_object_put(root); - return true; -} - -void free_disk_info(DiskInfo* info) { - if (info->backing_file != NULL) - free(info->backing_file); -} diff --git a/src/disk.h b/src/disk.h deleted file mode 100644 index eea3c81..0000000 --- a/src/disk.h +++ /dev/null @@ -1,43 +0,0 @@ -#pragma once - -#include -#include - -typedef struct DiskInfo { - uint64_t virtual_size; - uint64_t actual_size; - char* backing_file; -} DiskInfo; - -/// @brief Creates an empty disk at the given path with the given size. -/// @param path The path to the disk. Any existing file at this path will be overwritten. -/// @param size The size of the disk, in bytes. -/// @return True if the disk was created successfully, false otherwise. -bool create_empty_disk(const char* path, uint64_t size); - -/// @brief Creates a disk at the given path with the given backing disk. -/// @param path The path to the disk. -/// @param backing_disk The path to the backing disk. -/// @return True if the disk was created successfully, false otherwise. -bool create_backed_disk(const char* path, const char* backing_disk); - -/// @brief Trims the given disk to remove any unused space. -/// @param path The path to the disk. -/// @return True if the disk was trimmed successfully, false otherwise. -bool trim_disk(const char* path); - -/// @brief Changes the backing disk of the given disk. -/// @param path The path to the disk. -/// @param backing_disk The path to the new backing disk. -/// @return True if the disk was rebased successfully, false otherwise. -bool rebase_disk(const char* path, const char* backing_disk); - -/// @brief Gets information about the given disk. -/// @param path The path to the disk. -/// @param info The DiskInfo struct to fill with information about the disk. -/// @return True if the disk information was retrieved successfully, false otherwise. -bool get_disk_info(const char* path, DiskInfo* info); - -/// @brief Frees the memory used by the given DiskInfo struct. -/// @param info The DiskInfo struct to free. -void free_disk_info(DiskInfo* info); \ No newline at end of file diff --git a/src/entry.c b/src/entry.c new file mode 100644 index 0000000..83a15ab --- /dev/null +++ b/src/entry.c @@ -0,0 +1,23 @@ +#include "entry.h" + +#include "utils.h" + +#include +#include +#include + +bool is_entry_id_valid(const char* entry_id) { + if (entry_id == NULL) + return false; + + size_t length = strlen(entry_id); + + if (length == 0 || length > MAX_ENTRY_LENGTH) + return false; + + for (size_t i = 0; i < length; i++) + if (entry_id[i] == '/') + return false; + + return true; +} \ No newline at end of file diff --git a/src/entry.h b/src/entry.h index 4c43558..f6690da 100644 --- a/src/entry.h +++ b/src/entry.h @@ -3,4 +3,10 @@ #include #include -#define ENTRY_POOL "/var/lib/sandbox/entries" +#define ENTRY_POOL_DIR "/var/lib/sandbox/entries" +#define MAX_ENTRY_LENGTH 64 + +/// @brief Checks whether the given entry id is valid. +/// @param entry_id The entry id to check. +/// @return True if the entry id is valid, false otherwise. +bool is_entry_id_valid(const char* entry_id); diff --git a/src/sandbox.c b/src/sandbox.c index b870572..5337409 100644 --- a/src/sandbox.c +++ b/src/sandbox.c @@ -1,39 +1,134 @@ #include "sandbox.h" #include "utils.h" -#include "command.h" +#include "entry.h" -#include #include #include #include +const Command COMMANDS[] = { + {command_help, "help", "[command]", "Prints the help message.", + "Prints the help message for the given command. If no command is given, prints the help message for all commands."}, + {command_version, "version", "", "Prints the version.", + "Prints the version of the program."}, + {}, + {command_add_entry, "add-entry", " [--root|-r ] [--backed|-b ]", "Adds an entry to the entry pool.", + "Adds an entry to the entry pool with the given id. The entry can either be a root entry or a backed entry. If the entry is a root entry, the size of the entry must be specified. If the entry is a backed entry, the backing id must be specified. By default, the entry is backed by the latest backing disk available."}, +}; + int main(int argc, char* argv[]) { - if (argc < 2) { - show_help(); - return 0; + if (argc < 2) + return command_help(0, NULL); + + size_t input_length = strlen(argv[1]); + + const Command* command = NULL; + for (int i = 0; i < ARRAY_SIZE(COMMANDS); i++) { + if (COMMANDS[i].name == NULL) + continue; + + // Check that the length of the input is equal or less than the length of the command name + if (input_length > strlen(COMMANDS[i].name)) + continue; + + // Check that the input matches the command name + if (strncmp(argv[1], COMMANDS[i].name, input_length) == 0) { + // Check for multiple matches + if (command != NULL) { + log_message(LOG_LEVEL_ERROR, "Ambiguous command '%s'.", argv[1]); + return EXIT_FAILURE; + } + + command = &COMMANDS[i]; + } } - Command* command = find_command(argv[1]); + // Check if the command is NULL (no matches) if (command == NULL) { - log_message(LOG_ERROR, "Unknown command '%s'.", argv[1]); - return 1; + log_message(LOG_LEVEL_ERROR, "Unknown command '%s'.", argv[1]); + return EXIT_FAILURE; } - int consumed = 2; - - // TODO: Implement log level parameter - - switch (command->id) { - case CMD_HELP: - return cmd_help(argc - consumed, argv + consumed); - case CMD_VERSION: - return cmd_version(argc - consumed, argv + consumed); - - default: - log_message(LOG_ERROR, "Command '%s' is not implemented.", command->name); - break; - } - - return 0; + return command->handler(argc - 2, argv + 2); } + +int command_help(int argc, char* argv[]) { + // Check the number of arguments + if (argc == 0) { + fprintf(stdout, "Usage: sandbox [command] [arguments]\n\n"); + fprintf(stdout, "Commands:\n"); + + for (int i = 0; i < ARRAY_SIZE(COMMANDS); i++) + if (COMMANDS[i].name == NULL) + printf("\n"); + else + fprintf(stdout, " %s %s - %s\n", COMMANDS[i].name, COMMANDS[i].arguments, COMMANDS[i].description); + + fprintf(stdout, "\nRun 'sandbox help [command]' for more information on a command.\n"); + return EXIT_SUCCESS; + } else if (argc == 1) { + // Find the command that matches the given name + const Command* command = NULL; + + for (int i = 0; i < ARRAY_SIZE(COMMANDS); i++) { + if (COMMANDS[i].name == NULL) + continue; + + if (strcmp(argv[0], COMMANDS[i].name) == 0) { + command = &COMMANDS[i]; + break; + } + } + + // Check if the command is NULL (no matches) + if (command == NULL) { + log_message(LOG_LEVEL_ERROR, "Unknown command '%s'.", argv[0]); + return EXIT_FAILURE; + } + + // Print the help message for the command + fprintf(stdout, "Usage: sandbox %s %s\n\n", command->name, command->arguments); + fprintf(stdout, " %s\n", command->details); + + return EXIT_SUCCESS; + } else { + log_message(LOG_LEVEL_ERROR, "Too many arguments for 'help' command."); + return EXIT_FAILURE; + } +} + +int command_version(int argc, char* argv[]) { + fprintf(stdout, "Sandbox manager v%s\n", VERSION); + return EXIT_SUCCESS; +} + +int command_add_entry(int argc, char* argv[]) { + if (argc < 1) { + log_message(LOG_LEVEL_ERROR, "Missing entry id."); + return EXIT_FAILURE; + } + + // Extract the entry id + const char* entry_id = argv[0]; + + // Check if the entry id is valid + if (!is_entry_id_valid(entry_id)) { + log_message(LOG_LEVEL_ERROR, "Invalid entry id '%s'.", entry_id); + return EXIT_FAILURE; + } + + // Check if the entry already exists + bool exists; + if (!entry_exists(entry_id, &exists)) + return EXIT_FAILURE; + + // Create the entry + // TODO + + // Add the disk to the entry + // TODO + // If this fails, remove the entry + + return EXIT_SUCCESS; +} \ No newline at end of file diff --git a/src/sandbox.h b/src/sandbox.h index cf11893..99e4334 100644 --- a/src/sandbox.h +++ b/src/sandbox.h @@ -1,7 +1,21 @@ #pragma once -#include +#define VERSION "0.0.6" -#define SANDBOX_VERSION "1.0.0" + +typedef struct { + int (*handler)(int argc, char* argv[]); + const char* name; + const char* arguments; + const char* description; + const char* details; +} Command; + +extern const Command COMMANDS[]; int main(int argc, char* argv[]); + +int command_help(int argc, char* argv[]); +int command_version(int argc, char* argv[]); + +int command_add_entry(int argc, char* argv[]); \ No newline at end of file diff --git a/src/utils.c b/src/utils.c index 58f6b59..ee328cb 100644 --- a/src/utils.c +++ b/src/utils.c @@ -2,19 +2,65 @@ #include #include -#include #include -#include +#include #include #include -#include -#include -#include -#include -#include -#include -char* format(const char* fmt, ...) { +LogLevel log_level = LOG_LEVEL_WARNING; + +void set_log_level(LogLevel level) { + log_level = level; +} + +void log_message(LogLevel level, const char* format, ...) { + if (level < log_level) + return; + + va_list args; + va_start(args, format); + + const char* color; + const char* level_str; + + // Set the color and level_str based on the log level + switch (level) { + case LOG_LEVEL_DEBUG: + color = "\033[0;90m"; + level_str = "DEBUG"; + break; + case LOG_LEVEL_INFO: + color = "\033[0;32m"; + level_str = "INFO"; + break; + case LOG_LEVEL_WARNING: + color = "\033[0;33m"; + level_str = "WARNING"; + break; + case LOG_LEVEL_ERROR: + color = "\033[0;31m"; + level_str = "ERROR"; + break; + } + + // Get the current time + time_t t = time(NULL); + struct tm* tm_info = localtime(&t); + + // Print the label, message and newline + fprintf(stderr, "%s[%02d/%02d/%02d %02d:%02d:%02d - %s]\033[m ", color, tm_info->tm_mday, tm_info->tm_mon + 1, (tm_info->tm_year + 1900) % 100, tm_info->tm_hour, tm_info->tm_min, tm_info->tm_sec, level_str); + vfprintf(stderr, format, args); + fprintf(stderr, "\n"); + + // Flush the output + fflush(stderr); + + va_end(args); +} + +Result format(char** out_string, const char* fmt, ...) { + *out_string = NULL; + va_list args; va_start(args, fmt); @@ -24,15 +70,15 @@ char* format(const char* fmt, ...) { va_end(args); if (length <= 0) { - log_message(LOG_ERROR, "Failed to calculate the length of the formatted string."); - return NULL; + log_message(LOG_LEVEL_ERROR, "Failed to calculate the length of the formatted string (%s).", strerror(errno)); + return FAILURE; } // Allocate a buffer for the formatted string char* buffer = calloc(length, sizeof(char)); if (buffer == NULL) { - log_message(LOG_ERROR, "Failed to allocate memory for the formatted string."); - return NULL; + log_message(LOG_LEVEL_ERROR, "Failed to allocate memory for the formatted string (%s).", strerror(errno)); + return OUT_OF_MEMORY; } va_start(args, fmt); @@ -42,237 +88,6 @@ char* format(const char* fmt, ...) { va_end(args); - return buffer; -} - -void log_message(LogLevel level, const char* fmt, ...) { - va_list args; - va_start(args, fmt); - - const char* color; - const char* level_str; - - // Set the color and level string based on the log level - switch (level) { - case LOG_DEBUG: - color = "\033[0;90m"; - level_str = "DEBUG"; - break; - case LOG_INFO: - color = "\033[0;34m"; - level_str = "INFO"; - break; - case LOG_WARN: - color = "\033[0;33m"; - level_str = "WARN"; - break; - case LOG_ERROR: - color = "\033[0;31m"; - level_str = "ERROR"; - break; - default: - color = "\033[0;31m"; - level_str = "UNKNOWN"; - break; - } - - // Get the current time - time_t t = time(NULL); - struct tm* tm_info = localtime(&t); - - // Print the label - fprintf(stderr, "%s[%02d/%02d/%02d %02d:%02d:%02d - %s]\033[m ", color, tm_info->tm_mday, tm_info->tm_mon + 1, (tm_info->tm_year + 1900) % 100, tm_info->tm_hour, tm_info->tm_min, tm_info->tm_sec, level_str); - - // Print the message - vfprintf(stderr, fmt, args); - - // Print a newline - fprintf(stderr, "\n"); - - // Flush the output - fflush(stderr); - - va_end(args); -} - -int execute(char** outb, char** errb, const char* file, ...) { - // Count the number of arguments - int argc = 1; // The first argument is the file name - - va_list args; - va_start(args, file); - - while (va_arg(args, const char*) != NULL) - argc++; - - va_end(args); - - // Allocate an array for the arguments - char** argv = calloc(argc + 1, sizeof(char*)); - if (argv == NULL) { - log_message(LOG_ERROR, "Failed to allocate memory for the arguments array."); - return -1; - } - - // Fill the arguments array - va_start(args, file); - - argv[0] = strdup(file); - if (argv[0] == NULL) { - log_message(LOG_ERROR, "Failed to allocate memory for the file name."); - - free(argv); - va_end(args); - - return -1; - } - - for (int i = 1; i < argc; i++) { - argv[i] = strdup(va_arg(args, const char*)); - if (argv[i] == NULL) { - log_message(LOG_ERROR, "Failed to allocate memory for the argument %d.", i); - - for (int j = 0; j < i; j++) - free(argv[j]); - free(argv); - - va_end(args); - - return -1; - } - } - - argv[argc] = NULL; - - va_end(args); - - // Create a pipe for the standard output - int outp[2]; - if (pipe(outp) != 0) { - log_message(LOG_ERROR, "Failed to create a pipe for the standard output."); - - for (int i = 0; i < argc; i++) - free(argv[i]); - free(argv); - - return -1; - } - - // Create a pipe for the standard error - int errp[2]; - if (pipe(errp) != 0) { - log_message(LOG_ERROR, "Failed to create a pipe for the standard error."); - - close(outp[0]); - close(outp[1]); - - for (int i = 0; i < argc; i++) - free(argv[i]); - free(argv); - - return -1; - } - - // Fork the process - pid_t pid = fork(); - - if (pid == -1) { - log_message(LOG_ERROR, "Failed to fork the process (%s).", strerror(errno)); - - close(outp[0]); - close(outp[1]); - close(errp[0]); - close(errp[1]); - - for (int i = 0; i < argc; i++) - free(argv[i]); - free(argv); - - return -1; - } - - if (pid == 0) { - // Redirect the standard output - close(outp[0]); - dup2(outp[1], STDOUT_FILENO); - close(outp[1]); - - // Redirect the standard error - close(errp[0]); - dup2(errp[1], STDERR_FILENO); - close(errp[1]); - - // Execute the command - execvp(file, argv); - - // If execvp returns, an error occurred - printf("Failed to execute the command %s (%s).\n", file, strerror(errno)); - - exit(1); - } - - // Close the write end of the pipes - close(outp[1]); - close(errp[1]); - - // Wait for the child process to finish - int status; - waitpid(pid, &status, 0); - - if (outb != NULL) - *outb = read_file(outp[0]); - if (errb != NULL) - *errb = read_file(errp[0]); - - // Close the read end of the pipes - close(outp[0]); - close(errp[0]); - - // Free the arguments array - for (int i = 0; i < argc; i++) - free(argv[i]); - free(argv); - - return WEXITSTATUS(status); -} - -char* read_file(int fd) { - char buffer[4096]; - - // Read the file - ssize_t n; - size_t length = 0; - - char* data = NULL; - - // Read blocks of data from the file - while ((n = read(fd, buffer, sizeof(buffer))) > 0) { - char* new_data = realloc(data, length + n + 1); - if (new_data == NULL) { - log_message(LOG_ERROR, "Failed to allocate memory for the file data."); - - free(data); - - return NULL; - } - - data = new_data; - - // Copy and null-terminate the data - memcpy(data + length, buffer, n); - data[length + n] = '\0'; - - length += n; - } - - // Check for read errors - if (n < 0) { - log_message(LOG_ERROR, "Failed to read the file (%s).", strerror(errno)); - - free(data); - - return NULL; - } - - return data; + *out_string = buffer; + return SUCCESS; } diff --git a/src/utils.h b/src/utils.h index 4660e44..3f51dcb 100644 --- a/src/utils.h +++ b/src/utils.h @@ -1,37 +1,35 @@ #pragma once -#include -#include -#include +#define ARRAY_SIZE(x) (sizeof(x) / sizeof((x)[0])) typedef enum { - LOG_DEBUG, - LOG_INFO, - LOG_WARN, - LOG_ERROR, + SUCCESS, + FAILURE, + OUT_OF_MEMORY, +} Result; + +typedef enum { + LOG_LEVEL_DEBUG, + LOG_LEVEL_INFO, + LOG_LEVEL_WARNING, + LOG_LEVEL_ERROR, } LogLevel; -/// @brief Formats a string using printf-style formatting. +extern LogLevel log_level; + +/// @brief Sets the log level. +/// @param level The log level to set. +void set_log_level(LogLevel level); + +/// @brief Logs a message. +/// @param level The log level. +/// @param format The format string. +/// @param ... The format arguments. +void log_message(LogLevel level, const char* format, ...); + +/// @brief Formats a string. +/// @param out_string The output string pointer. /// @param fmt The format string. -/// @param ... The arguments to format into the string. -/// @return The formatted string or NULL if an error occurred. The result must be freed with free(). -char* format(const char* fmt, ...); - -/// @brief Logs a message to the console. -/// @param level The log level of the message. -/// @param fmt The format string of the message. -/// @param ... The arguments to format into the message. -void log_message(LogLevel level, const char* fmt, ...); - -/// @brief Executes a command and returns the exit code as well as the output and error streams. -/// @param outb The output stream of the command. May be NULL if the output is not needed. -/// @param errb The error stream of the command. May be NULL if the error is not needed. -/// @param file The file to execute. -/// @param ... The arguments to pass to the file. -/// @return The exit code of the command. -int execute(char** outb, char** errb, const char* file, ...); - -/// @brief Reads the contents of a file into a string. The result might contain null bytes. -/// @param fd The file descriptor of the file to read. -/// @return The contents of the file or NULL if an error occurred. The result must be freed with free(). -char* read_file(int fd); \ No newline at end of file +/// @param ... The format arguments. +/// @return The result of the operation. +Result format(char** out_string, const char* fmt, ...); \ No newline at end of file