From 01ea511b2dc4d0fedecf12a6c740745b6c4f782f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Alexe=C3=AF=20KADIR?= Date: Thu, 15 Feb 2024 19:42:22 +0100 Subject: [PATCH] Perfected the disk utilities --- src/backing.c | 227 ---------------- src/backing.h | 42 --- src/disk.c | 271 +++++++++++++------ src/disk.h | 59 ++-- src/entry.c | 256 ------------------ src/entry.h | 62 ----- src/sandbox.c | 26 +- src/sandbox.h | 11 +- src/utils.c | 730 ++++++++++++++++++++++---------------------------- src/utils.h | 80 ++---- 10 files changed, 581 insertions(+), 1183 deletions(-) delete mode 100644 src/backing.c delete mode 100644 src/backing.h delete mode 100644 src/entry.c delete mode 100644 src/entry.h mode change 100755 => 100644 src/sandbox.h mode change 100755 => 100644 src/utils.c mode change 100755 => 100644 src/utils.h diff --git a/src/backing.c b/src/backing.c deleted file mode 100644 index aab8fe1..0000000 --- a/src/backing.c +++ /dev/null @@ -1,227 +0,0 @@ -#include "backing.h" - -#include "utils.h" -#include "sandbox.h" - -#include -#include -#include -#include -#include -#include -#include -#include -#include - -char* get_backings_path(void) { - return format("%s/%s", MASTER_DIRECTORY, BACKINGS_DIRECTORY); -} - -bool is_valid_backing_name(const char* name) { - if (name == NULL) - return false; - - // Check that the name is not empty - size_t length = strlen(name); - if (length == 0) - return false; - - // Check that the name starts with a number, corresponding to the timestamp of the backing disk creation - size_t timestamp_length = 0; - for (size_t i = 0; i < length; i++) - if (name[i] >= '0' && name[i] <= '9') - timestamp_length++; - else - break; - - if (timestamp_length == 0) - return false; - - return true; -} - -char* get_backing_path(const char* backing) { - char* backings_path = get_backings_path(); - if (backings_path == NULL) - return NULL; - - // Combine the backings path with the backing name - char* backing_path = format("%s/%s", backings_path, backing); - - // Free the backings path - free(backings_path); - - return backing_path; -} - -bool backing_exists(const char* backing) { - char* backing_path = get_backing_path(backing); - if (backing_path == NULL) - return false; - - // Check if the backing path exists - struct stat statbuf; - int result = stat(backing_path, &statbuf); - - // Free the backing path - free(backing_path); - - if (result != 0) - return false; - - if (!S_ISREG(statbuf.st_mode)) - return false; - - return true; -} - -char** list_backings(void) { - char* path = get_backings_path(); - if (path == NULL) - return NULL; - - // Open the directory - DIR* dir = opendir(path); - if (dir == NULL) { - log_msg(LOG_ERROR, "Failed to open directory '%s' (%s).", path, strerror(errno)); - free(path); - return NULL; - } - free(path); - - // Count the number of entries - size_t count = 0; - struct dirent* entry; - while ((entry = readdir(dir)) != NULL) - if (is_valid_backing_name(entry->d_name) && backing_exists(entry->d_name)) - count++; - - // Allocate the array of strings - char** backings = malloc((count + 1) * sizeof(char*)); - if (backings == NULL) { - log_msg(LOG_ERROR, "Failed to allocate memory for the backings array."); - closedir(dir); - return NULL; - } - - // Fill the array of strings - rewinddir(dir); - size_t index = 0; - while ((entry = readdir(dir)) != NULL) { - if (is_valid_backing_name(entry->d_name) && backing_exists(entry->d_name)) { - backings[index] = strdup(entry->d_name); - if (backings[index] == NULL) { - log_msg(LOG_ERROR, "Failed to allocate memory for the backing name."); - for (size_t i = 0; i < index; i++) - free(backings[i]); - free(backings); - closedir(dir); - return NULL; - } - index++; - } - } - - // Terminate the array of strings - backings[count] = NULL; - - // Close the directory - closedir(dir); - - return backings; -} - -uint64_t get_backing_creation_time(const char* backing) { - size_t length = strlen(backing); - size_t timestamp_length = 0; - - // Find the length of the timestamp - for (size_t i = 0; i < length; i++) - if (backing[i] >= '0' && backing[i] <= '9') - timestamp_length++; - else - break; - - // Extract the timestamp - char* timestamp = strndup(backing, timestamp_length); - if (timestamp == NULL) - return 0; - - // Convert the timestamp to a number - uint64_t creation_time = strtoull(timestamp, NULL, 10); - - // Free the timestamp - free(timestamp); - - return creation_time; -} - -char* find_latest_backing(void) { - char** backings = list_backings(); - if (backings == NULL) - return NULL; - - // Find the latest backing disk - char* latest_backing = NULL; - uint64_t latest_time = 0; - - for (size_t i = 0; backings[i] != NULL; i++) { - uint64_t time = get_backing_creation_time(backings[i]); - if (time >= latest_time) { - latest_time = time; - latest_backing = backings[i]; - } - } - - // Duplicate the latest backing disk - if (latest_backing != NULL) - latest_backing = strdup(latest_backing); - - // Free the backings - for (size_t i = 0; backings[i] != NULL; i++) - free(backings[i]); - free(backings); - - return latest_backing; -} - -bool create_backing(const char* disk, const char* description) { - // Create the backing disk name - uint64_t timestamp = (uint64_t)time(NULL); - char* backing_name = NULL; - - if (description == NULL || strlen(description) == 0) - backing_name = format("%lu", timestamp); - else - backing_name = format("%lu-%s", timestamp, description); - - if (backing_name == NULL) { - log_msg(LOG_ERROR, "Failed to create the backing disk name."); - return false; - } - - // Create the backing disk path - char* backings_path = get_backings_path(); - if (backings_path == NULL) { - free(backing_name); - return false; - } - - char* backing_path = format("%s/%s", backings_path, backing_name); - free(backing_name); - free(backings_path); - - if (backing_path == NULL) - return false; - - // Copy the disk to the backing disk - if (!copy_file(disk, backing_path, 0755, 0, 0)) { - free(backing_path); - return false; - } - - // Free the backing disk path - free(backing_path); - - return true; -} diff --git a/src/backing.h b/src/backing.h deleted file mode 100644 index 0e5d0d0..0000000 --- a/src/backing.h +++ /dev/null @@ -1,42 +0,0 @@ -#pragma once - -#include -#include - -/// @brief Returns the directory path where the backing disks are stored. -/// @return The directory path where the backing disks are stored. The caller is responsible for freeing the returned string. This function can return NULL. -char* get_backings_path(void); - -/// @brief Checks if the specified backing disk name is valid. -/// @param name The backing disk name to check. -/// @return true if the backing disk name is valid, otherwise false. -bool is_valid_backing_name(const char* name); - -/// @brief Returns the path to the specified backing disk. -/// @param backing The valid backing disk name. -/// @return The path to the specified backing disk. The caller is responsible for freeing the returned string. This function can return NULL. -char* get_backing_path(const char* backing); - -/// @brief Checks if the specified backing disk exists. -/// @param backing The valid backing disk name. -/// @return true if the backing disk exists, otherwise false. -bool backing_exists(const char* backing); - -/// @brief Lists the backing disks. -/// @return The list of backing disks. The caller is responsible for freeing the returned array and strings. This function can return NULL. -char** list_backings(void); - -/// @brief Get the creation time of the specified backing disk. -/// @param backing The valid backing disk name. -/// @return The creation time of the specified backing disk. If the backing disk does not exist, this function returns 0. -uint64_t get_backing_creation_time(const char* backing); - -/// @brief Finds the latest backing disk. -/// @return The latest backing disk. The caller is responsible for freeing the returned string. This function can return NULL. -char* find_latest_backing(void); - -/// @brief Creates a new backing disk. -/// @param disk The disk to use as the backing disk. Warning: the disk must be part of the backing disks. -/// @param description The description of the backing disk. -/// @return true if the backing disk was created, otherwise false. -bool create_backing(const char* disk, const char* description); \ No newline at end of file diff --git a/src/disk.c b/src/disk.c index d121134..ca1acfa 100644 --- a/src/disk.c +++ b/src/disk.c @@ -2,120 +2,233 @@ #include "utils.h" -#include -#include +#include #include +#include +#include +#include +#include #include -bool create_empty_disk(const char* disk, uint64_t size) { - char* stdoutb = NULL; - char* stderrb = NULL; +bool create_empty_disk(const char* path, uint64_t size) { + char* errb = NULL; - // Create an empty disk - int result = execute(&stdoutb, &stderrb, "qemu-img", "create", "-f", "qcow2", disk, format("%" PRIu64, size), NULL); + // Convert the size to a string + char* size_str = format("%lu", size); + if (size_str == NULL) { + logmsg(LOG_ERROR, "Failed to allocate memory for the size string."); + return false; + } - // Check if the command was successful - if (result != 0) { - // Remove the newline character from the stderr buffer - size_t length = strlen(stderrb); - for (size_t i = 0; i < length; i++) - if (stderrb[i] == '\n') - stderrb[i] = ' '; - log_msg(LOG_ERROR, "Failed to create the empty disk %s (%s).", disk, stderrb); + // Create the disk + int ret = exec(NULL, &errb, "qemu-img", "create", "-f", "qcow2", path, size_str, NULL); - free(stdoutb); - free(stderrb); + free(size_str); + + // Check for errors + if (ret != 0) { + if (errb == NULL) + logmsg(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] = ' '; + + logmsg(LOG_ERROR, "Failed to create disk %s (%s).", path, errb); + } return false; } - free(stdoutb); - free(stderrb); - return true; } -bool create_backed_disk(const char* disk, const char* backing) { - char* stdoutb = NULL; - char* stderrb = NULL; +bool create_backed_disk(const char* path, const char* backing_disk) { + char* errb = NULL; - // Create a backed disk - int result = execute(&stdoutb, &stderrb, "qemu-img", "create", "-f", "qcow2", "-F", "qcow2", "-b", backing, disk, NULL); + // Create the disk + int ret = exec(NULL, &errb, "qemu-img", "create", "-f", "qcow2", "-F", "qcow2", "-b", backing_disk, path, NULL); - // Check if the command was successful - if (result != 0) { - // Remove the newline character from the stderr buffer - size_t length = strlen(stderrb); - for (size_t i = 0; i < length; i++) - if (stderrb[i] == '\n') - stderrb[i] = ' '; - log_msg(LOG_ERROR, "Failed to create the backed disk %s (%s).", disk, stderrb); + // Check for errors + if (ret != 0) { + if (errb == NULL) + logmsg(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] = ' '; - free(stdoutb); - free(stderrb); + logmsg(LOG_ERROR, "Failed to create disk %s (%s).", path, errb); + } return false; } - free(stdoutb); - free(stderrb); + return true; +} + +bool trim_disk(const char* path) { + char* tmp_path = format("%s.tmp", path); + if (tmp_path == NULL) { + logmsg(LOG_ERROR, "Failed to allocate memory for the temporary disk path."); + 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) { + logmsg(LOG_ERROR, "Failed to allocate memory for the backing file string."); + + free_disk_info(&info); + free(tmp_path); + return false; + } + + ret = exec(NULL, &errb, "qemu-img", "convert", "-f", "qcow2", "-F", "qcow2", "-O", "qcow2", "-o", backing_str, path, tmp_path, NULL); + + free(backing_str); + } else + ret = exec(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) + logmsg(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] = ' '; + + logmsg(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) { + logmsg(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 = exec(NULL, &errb, "qemu-img", "rebase", "-u", "-F", "qcow2", "-b", backing_disk, path, NULL); + + // Check for errors + if (ret != 0) { + if (errb == NULL) + logmsg(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] = ' '; + + logmsg(LOG_ERROR, "Failed to rebase disk %s (%s).", path, errb); + } + + return false; + } return true; } -char* get_backing_file(const char* disk) { - char* stdoutb = NULL; - char* stderrb = NULL; +bool get_disk_info(const char* path, DiskInfo* info) { + char* outb = NULL; + char* errb = NULL; - // Get the backing file of the disk - int result = execute(&stdoutb, &stderrb, "qemu-img", "info", "--output", "json", disk, NULL); + int ret = exec(&outb, &errb, "qemu-img", "info", "--output=json", path, NULL); - // Check if the command was successful - if (result != 0) { - // Remove the newline character from the stderr buffer - size_t length = strlen(stderrb); - for (size_t i = 0; i < length; i++) - if (stderrb[i] == '\n') - stderrb[i] = ' '; - log_msg(LOG_ERROR, "Failed to get the backing file of the disk %s (%s).", disk, stderrb); + if (ret != 0) { + if (errb == NULL) + logmsg(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] = ' '; - free(stdoutb); - free(stderrb); + logmsg(LOG_ERROR, "Failed to get information about disk %s (%s).", path, errb); + } - return NULL; + free(outb); + return false; } - free(stderrb); + // 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); - // Parse the JSON output - json_object* root = json_tokener_parse(stdoutb); if (root == NULL) { - log_msg(LOG_ERROR, "Failed to parse the JSON output of the command."); - - free(stdoutb); - - return NULL; + logmsg(LOG_ERROR, "Failed to parse the JSON output from qemu-img."); + return false; } - // Get the backing file - json_object* backing_file = NULL; - if (!json_object_object_get_ex(root, "backing-filename", &backing_file)) { - json_object_put(root); - free(stdoutb); + 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); - return NULL; + 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); + + 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) { + logmsg(LOG_ERROR, "Failed to allocate memory for the backing file path."); + + json_object_put(root); + return false; + } } - // Get the backing file as a string0 - char* backing_file_str = strdup(json_object_get_string(backing_file)); - if (backing_file_str == NULL) { - log_msg(LOG_ERROR, "Failed to get the backing file of the disk %s.", disk); - - json_object_put(root); - free(stdoutb); - - return NULL; - } - - return backing_file_str; + 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 index 65ef452..8bffe32 100644 --- a/src/disk.h +++ b/src/disk.h @@ -1,32 +1,43 @@ #pragma once -#include #include +#include -/// @brief Creates an empty disk. -/// @param disk The path to the disk to create. Warning: the disk must not exist, otherwise it will be overwritten. -/// @param size The size of the disk to create in bytes. -/// @return true if the disk is created, otherwise false. -bool create_empty_disk(const char* disk, uint64_t size); +typedef struct DiskInfo { + uint64_t virtual_size; + uint64_t actual_size; + char* backing_file; +} DiskInfo; -/// @brief Creates a backed disk. -/// @param disk The path to the disk to create. Warning: the disk must not exist, otherwise it will be overwritten. -/// @param backing The path to the backing file, which exists. -/// @return true if the disk is created, otherwise false. -bool create_backed_disk(const char* disk, const char* backing); +/// @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 Packs the disk to reduce its size. -/// @param disk The path to the disk to pack, which exists. -/// @return true if the disk is packed, otherwise false. -bool pack_disk(const char* disk); +/// @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 Moves the backing file of a disk. -/// @param disk The path to the disk, which exists. -/// @param backing The path to the new backing file, which exists. -/// @return true if the backing file is moved, otherwise false. -bool move_backing_file(const char* disk, const char* backing); +/// @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 Gets the backing file of a disk. -/// @param disk The path to the disk, which exists. -/// @return The backing file of the disk. The caller is responsible for freeing the returned string. This function can return NULL. -char* get_backing_file(const char* disk); +/// @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 deleted file mode 100644 index e20bf1d..0000000 --- a/src/entry.c +++ /dev/null @@ -1,256 +0,0 @@ -#include "entry.h" - -#include "sandbox.h" -#include "utils.h" - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -char* get_entries_path(void) { - return format("%s/%s", MASTER_DIRECTORY, ENTRIES_DIRECTORY); -} - -bool is_valid_entry_name(const char* name) { - if (name == NULL) - return false; - - // Check that the name is not empty - size_t length = strlen(name); - if (length == 0) - return false; - - // Check that the name does not contain / - for (size_t i = 0; i < length; i++) - if (name[i] == '/') - return false; - - // Check that the name is not . or .. - if (strcmp(name, ".") == 0 || strcmp(name, "..") == 0) - return false; - - return true; -} - -char* get_entry_path(const char* entry) { - char* entries_path = get_entries_path(); - if (entries_path == NULL) - return NULL; - - // Combine the entries path with the entry name - char* entry_path = format("%s/%s", entries_path, entry); - - // Free the entries path - free(entries_path); - - return entry_path; -} - -bool entry_exists(const char* entry) { - char* entry_path = get_entry_path(entry); - if (entry_path == NULL) - return false; - - // Check if the entry path exists - struct stat statbuf; - int result = stat(entry_path, &statbuf); - - // Free the entry path - free(entry_path); - - if (result != 0) - return false; - - if (!S_ISDIR(statbuf.st_mode)) - return false; - - return true; -} - -char* get_entry_disk_path(const char* entry) { - char* entry_path = get_entry_path(entry); - if (entry_path == NULL) - return NULL; - - // Combine the entry path with the disk name - char* disk_path = format("%s/%s", entry_path, DISK_PATH); - - // Free the entry path - free(entry_path); - - return disk_path; -} - -char** list_entries(void) { - char* path = get_entries_path(); - if (path == NULL) - return NULL; - - // Open the directory - DIR* dir = opendir(path); - if (dir == NULL) { - log_msg(LOG_ERROR, "Failed to open directory '%s' (%s).", path, strerror(errno)); - free(path); - return NULL; - } - free(path); - - // Count the number of entries - size_t count = 0; - struct dirent* entry; - while ((entry = readdir(dir)) != NULL) - if (is_valid_entry_name(entry->d_name) && entry_exists(entry->d_name)) - count++; - - // Allocate the array of entries - char** entries = malloc((count + 1) * sizeof(char*)); - if (entries == NULL) { - log_msg(LOG_ERROR, "Failed to allocate memory for the entries array."); - closedir(dir); - return NULL; - } - - // Fill the array of entries - rewinddir(dir); - size_t index = 0; - while ((entry = readdir(dir)) != NULL) { - if (is_valid_entry_name(entry->d_name) && entry_exists(entry->d_name)) { - entries[index] = strdup(entry->d_name); - if (entries[index] == NULL) { - log_msg(LOG_ERROR, "Failed to allocate memory for the entry name."); - for (size_t i = 0; i < index; i++) - free(entries[i]); - free(entries); - closedir(dir); - return NULL; - } - index++; - } - } - - // Terminate the array of entries - entries[count] = NULL; - - // Close the directory - closedir(dir); - - return entries; -} - -uint64_t get_entry_last_access(const char* entry) { - char* disk = get_entry_disk_path(entry); - if (disk == NULL) - return 0; - - // Get the last access time of the disk - struct stat statbuf; - int result = stat(disk, &statbuf); - - // Free the disk path - free(disk); - - if (result != 0) - return 0; - - return statbuf.st_atime; -} - -char* find_oldest_entry(void) { - char** entries = list_entries(); - if (entries == NULL) - return NULL; - - // Find the oldest entry - char* oldest_entry = NULL; - uint64_t oldest_time = UINT64_MAX; - - for (size_t i = 0; entries[i] != NULL; i++) { - uint64_t time = get_entry_last_access(entries[i]); - if (time <= oldest_time) { - oldest_time = time; - oldest_entry = entries[i]; - } - } - - // Duplicate the oldest entry - if (oldest_entry != NULL) - oldest_entry = strdup(oldest_entry); - - // Free the entries - for (size_t i = 0; entries[i] != NULL; i++) - free(entries[i]); - free(entries); - - return oldest_entry; -} - -bool create_entry(const char* entry) { - char* entry_path = get_entry_path(entry); - if (entry_path == NULL) - return false; - - // Create the entry directory - bool result = create_directory(entry_path, 0700, 0, 0); - - // Free the entry path - free(entry_path); - - return result; -} - -bool delete_entry(const char* entry) { - char* entry_path = get_entry_path(entry); - if (entry_path == NULL) - return false; - - // Delete the entry directory - bool result = delete_directory(entry_path, 0); - - // Free the entry path - free(entry_path); - - return result; -} - -uint64_t get_available_space(void) { - char* entries_path = get_entries_path(); - if (entries_path == NULL) - return 0; - - // Get the available space in the entries directory - struct statvfs statbuf; - int result = statvfs(entries_path, &statbuf); - - // Free the entries path - free(entries_path); - - if (result != 0) - return -1; - - return statbuf.f_bsize * statbuf.f_bavail; -} - -bool reserve_space(uint64_t space) { - // While the available space is not enough, delete the oldest entry - while (get_available_space() < space) { - char* oldest_entry = find_oldest_entry(); - if (oldest_entry == NULL) - return false; - - bool result = delete_entry(oldest_entry); - free(oldest_entry); - - if (!result) - return false; - } - - return true; -} diff --git a/src/entry.h b/src/entry.h deleted file mode 100644 index 95024cc..0000000 --- a/src/entry.h +++ /dev/null @@ -1,62 +0,0 @@ -#pragma once - -#include -#include - -#define DISK_PATH "disk" - -/// @brief Returns the directory path where the entries are stored. -/// @return The directory path where the entries are stored. The caller is responsible for freeing the returned string. This function can return NULL. -char* get_entries_path(void); - -/// @brief Checks if the specified entry name is valid. -/// @param name The entry name to check. -/// @return true if the entry name is valid, otherwise false. -bool is_valid_entry_name(const char* name); - -/// @brief Returns the path to the specified entry. -/// @param entry The valid entry name. -/// @return The path to the specified entry. The caller is responsible for freeing the returned string. This function can return NULL. -char* get_entry_path(const char* entry); - -/// @brief Checks if the specified entry exists. -/// @param entry The valid entry name. -/// @return true if the entry exists, otherwise false. -bool entry_exists(const char* entry); - -/// @brief Returns the path to the disk of the specified entry. -/// @param entry The valid entry name. -/// @return The path to the disk of the specified entry. The caller is responsible for freeing the returned string. This function can return NULL. -char* get_entry_disk_path(const char* entry); - -/// @brief Lists the entries. -/// @return The list of entries. The caller is responsible for freeing the returned array and strings. This function can return NULL. -char** list_entries(void); - -/// @brief Returns the last access time of the specified entry. -/// @param entry The valid entry name, which must exist. -/// @return The last access time of the specified entry. If the entry does not exist, this function returns 0. -uint64_t get_entry_last_access(const char* entry); - -/// @brief Finds the oldest entry (based on last access time). -/// @return The oldest entry. The caller is responsible for freeing the returned string. This function can return NULL. -char* find_oldest_entry(void); - -/// @brief Creates a new entry. -/// @param entry The valid entry name. -/// @return true if the entry was created, otherwise false. -bool create_entry(const char* entry); - -/// @brief Deletes the specified entry. -/// @param entry The valid entry name. -/// @return true if the entry was deleted, otherwise false. -bool delete_entry(const char* entry); - -/// @brief Returns the available space in the entries directory. -/// @return The available space in the entries directory. If the entries directory does not exist, this function returns 0. -uint64_t get_available_space(void); - -/// @brief Reserves the specified space in the entries directory, by deleting the oldest entries. -/// @param space The space to reserve, in bytes. -/// @return true if the space was fully reserved, otherwise false. -bool reserve_space(uint64_t space); diff --git a/src/sandbox.c b/src/sandbox.c index f806c40..9970a34 100644 --- a/src/sandbox.c +++ b/src/sandbox.c @@ -2,30 +2,16 @@ #include "utils.h" -#include "backing.h" -#include "entry.h" #include "disk.h" -#include -#include +#include #include -#include -#include -#include +#include int main(int argc, char* argv[]) { - char** backings = list_backings(); + create_empty_disk("test.qcow2", 1024 * 1024 * 1024); + create_backed_disk("test2.qcow2", "test.qcow2"); + rebase_disk("test2.qcow2", "test.qcow2"); - if (backings == NULL) { - fprintf(stderr, "Failed to list the backing disks: %s\n", strerror(errno)); - return EXIT_FAILURE; - } - - for (size_t i = 0; backings[i] != NULL; i++) - printf("%s\n", backings[i]); - - for (size_t i = 0; backings[i] != NULL; i++) - free(backings[i]); - - free(backings); + return 0; } \ No newline at end of file diff --git a/src/sandbox.h b/src/sandbox.h old mode 100755 new mode 100644 index 9d117cf..8dd27c5 --- a/src/sandbox.h +++ b/src/sandbox.h @@ -1,8 +1,3 @@ -#pragma once - -#define MASTER_DIRECTORY "/var/lib/sandbox" - -#define BACKINGS_DIRECTORY "backings" -#define ENTRIES_DIRECTORY "entries" - -int main(int argc, char* argv[]); +#pragma once + +int main(int argc, char* argv[]); \ No newline at end of file diff --git a/src/utils.c b/src/utils.c old mode 100755 new mode 100644 index 8dc47db..e2e2083 --- a/src/utils.c +++ b/src/utils.c @@ -1,407 +1,323 @@ -#include "utils.h" - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -char* format(const char* fmt, ...) { - va_list args; - va_start(args, fmt); - - // Calculate the length of the formatted string - size_t length = vsnprintf(NULL, 0, fmt, args) + 1; - - va_end(args); - - if (length <= 0) - return NULL; - - // Allocate a buffer for the formatted string - char* buffer = calloc(length, sizeof(char)); - if (buffer == NULL) { - log_msg(LOG_ERROR, "Failed to allocate memory for the formatted string."); - return NULL; - } - - va_start(args, fmt); - - // Format the string - vsnprintf(buffer, length, fmt, args); - - va_end(args); - - return buffer; -} - -void log_msg(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 = ""; - 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** stdout_buffer, char** stderr_buffer, const char* file, ...) { - // Count the number of arguments - int argc = 1; // Include space for the file name - va_list args; - va_start(args, file); - while (va_arg(args, char*) != NULL) - argc++; - va_end(args); - - // Allocate an array for the arguments - char** argv = calloc(argc + 1, sizeof(char*)); // Include space for the NULL terminator - if (argv == NULL) { - log_msg(LOG_ERROR, "Failed to allocate memory for the argument array."); - return -1; - } - - // Fill the argument array - va_start(args, file); - argv[0] = strdup(file); - for (int i = 1; i < argc; i++) - argv[i] = strdup(va_arg(args, char*)); - argv[argc] = NULL; - va_end(args); - - // Create the stdout pipe for the child process - int stdout_pipe[2]; - if (pipe(stdout_pipe) == -1) { - log_msg(LOG_ERROR, "Failed to create pipe for stdout."); - - for (int i = 0; i < argc; i++) - free(argv[i]); - free(argv); - - return -1; - } - - // Create the stderr pipe for the child process - int stderr_pipe[2]; - if (pipe(stderr_pipe) == -1) { - log_msg(LOG_ERROR, "Failed to create pipe for stderr."); - - close(stdout_pipe[0]); - close(stdout_pipe[1]); - - for (int i = 0; i < argc; i++) - free(argv[i]); - free(argv); - - return -1; - } - - // Fork the child process - pid_t pid = fork(); - - if (pid == -1) { - log_msg(LOG_ERROR, "Failed to fork child process."); - - close(stdout_pipe[0]); - close(stdout_pipe[1]); - close(stderr_pipe[0]); - close(stderr_pipe[1]); - - for (int i = 0; i < argc; i++) - free(argv[i]); - free(argv); - - return -1; - } - - if (pid == 0) { - // Redirect stdout and stderr to the pipes - dup2(stdout_pipe[1], STDOUT_FILENO); - dup2(stderr_pipe[1], STDERR_FILENO); - - // Close the pipe file descriptors - close(stdout_pipe[0]); - close(stdout_pipe[1]); - close(stderr_pipe[0]); - close(stderr_pipe[1]); - - // Execute the command - execvp(file, argv); - - // If execvp fails, log an error - log_msg(LOG_ERROR, "Failed to execute command '%s'.", file); - - // Free the argument array - for (int i = 0; i < argc; i++) - free(argv[i]); - free(argv); - - exit(1); - } - - // Close the write end of the pipes - close(stdout_pipe[1]); - close(stderr_pipe[1]); - - // Wait for the child process to finish - int status; - waitpid(pid, &status, 0); - - // Read the stdout buffer - char buffer[4096]; - ssize_t bytes_read; - - if (stdout_buffer != NULL) { - size_t stdout_length = 0; - *stdout_buffer = NULL; - - // Read the stdout buffer - while ((bytes_read = read(stdout_pipe[0], buffer, sizeof(buffer))) > 0) { - char* new_stdout_buffer = realloc(*stdout_buffer, stdout_length + bytes_read + 1); // Include space for the null terminator - if (new_stdout_buffer == NULL) { - log_msg(LOG_ERROR, "Failed to allocate memory for the stdout buffer."); - - free(*stdout_buffer); - - close(stdout_pipe[0]); - close(stderr_pipe[0]); - - for (int i = 0; i < argc; i++) - free(argv[i]); - free(argv); - - return -1; - } - *stdout_buffer = new_stdout_buffer; - - memcpy(*stdout_buffer + stdout_length, buffer, bytes_read); - stdout_length += bytes_read; - (*stdout_buffer)[stdout_length] = '\0'; - } - } - - // Close the read end of the stdout pipe - close(stdout_pipe[0]); - - // Read the stderr buffer - if (stderr_buffer != NULL) { - size_t stderr_length = 0; - *stderr_buffer = NULL; - - // Read the stderr buffer - while ((bytes_read = read(stderr_pipe[0], buffer, sizeof(buffer))) > 0) { - char* new_stderr_buffer = realloc(*stderr_buffer, stderr_length + bytes_read + 1); // Include space for the null terminator - if (new_stderr_buffer == NULL) { - log_msg(LOG_ERROR, "Failed to allocate memory for the stderr buffer."); - - free(*stdout_buffer); - free(*stderr_buffer); - - close(stderr_pipe[0]); - - for (int i = 0; i < argc; i++) - free(argv[i]); - free(argv); - - return -1; - } - *stderr_buffer = new_stderr_buffer; - - memcpy(*stderr_buffer + stderr_length, buffer, bytes_read); - stderr_length += bytes_read; - (*stderr_buffer)[stderr_length] = '\0'; - } - } - - // Close the read end of the stderr pipe - close(stderr_pipe[0]); - - // Free the argument array - for (int i = 0; i < argc; i++) - free(argv[i]); - free(argv); - - // Return the exit status - return WIFEXITED(status) ? WEXITSTATUS(status) : -1; -} - -bool create_directory(const char* directory, mode_t mode, uid_t uid, gid_t gid) { - // Create the directory - if (mkdir(directory, mode) != 0) { - log_msg(LOG_ERROR, "Failed to create directory '%s' (%s).", directory, strerror(errno)); - return false; - } - - // Change the owner of the directory - if (chown(directory, uid, gid) != 0) { - log_msg(LOG_ERROR, "Failed to change the owner of directory '%s' (%s).", directory, strerror(errno)); - return false; - } - - return true; -} - -bool delete_directory(const char* directory, int level) { - if (level > MAX_RECURSION_LEVEL) { - log_msg(LOG_ERROR, "Too many levels of recursion when deleting directory '%s'.", directory); - return false; - } - - // Open the directory - DIR* dir = opendir(directory); - if (dir == NULL) { - log_msg(LOG_ERROR, "Failed to open directory '%s' (%s).", directory, strerror(errno)); - return false; - } - - // Iterate over the directory entries - struct dirent* entry; - while ((entry = readdir(dir)) != NULL) { - // Skip . and .. - if (strcmp(entry->d_name, ".") == 0 || strcmp(entry->d_name, "..") == 0) - continue; - - // Combine the directory path with the entry name - char* entry_path = format("%s/%s", directory, entry->d_name); - if (entry_path == NULL) { - log_msg(LOG_ERROR, "Failed to allocate memory for the entry path."); - closedir(dir); - return false; - } - - // Check if the entry is a directory - struct stat statbuf; - int result = stat(entry_path, &statbuf); - if (result != 0) { - log_msg(LOG_ERROR, "Failed to get information about file '%s' (%s).", entry_path, strerror(errno)); - free(entry_path); - closedir(dir); - return false; - } - - if (S_ISDIR(statbuf.st_mode)) { - // Delete the directory - if (!delete_directory(entry_path, level + 1)) { - log_msg(LOG_ERROR, "Failed to delete directory '%s'.", entry_path); - - free(entry_path); - closedir(dir); - return false; - } - } else { - // Delete the file - if (unlink(entry_path) != 0) { - log_msg(LOG_ERROR, "Failed to delete file '%s' (%s).", entry_path, strerror(errno)); - - free(entry_path); - closedir(dir); - return false; - } - } - - free(entry_path); - } - - // Close the directory - closedir(dir); - - // Delete the directory - if (rmdir(directory) != 0) { - log_msg(LOG_ERROR, "Failed to delete directory '%s' (%s).", directory, strerror(errno)); - return false; - } - - return true; -} - -bool copy_file(const char* source, const char* destination, mode_t mode, uid_t uid, gid_t gid) { - // Open the source file - FILE* source_file = fopen(source, "r"); - if (source_file == NULL) { - log_msg(LOG_ERROR, "Failed to open file '%s' for reading (%s).", source, strerror(errno)); - return false; - } - - // Open the destination file - FILE* destination_file = fopen(destination, "w"); - if (destination_file == NULL) { - log_msg(LOG_ERROR, "Failed to open file '%s' for writing (%s).", destination, strerror(errno)); - fclose(source_file); - return false; - } - - // Copy the file - char buffer[4096]; - size_t bytes_read; - while ((bytes_read = fread(buffer, 1, sizeof(buffer), source_file)) > 0) - if (fwrite(buffer, 1, bytes_read, destination_file) != bytes_read) { - log_msg(LOG_ERROR, "Failed to write to file '%s' (%s).", destination, strerror(errno)); - fclose(source_file); - fclose(destination_file); - return false; - } - - // Close the files - fclose(source_file); - fclose(destination_file); - - // Change the mode of the destination file - if (chmod(destination, mode) != 0) { - log_msg(LOG_ERROR, "Failed to change the mode of file '%s' (%s).", destination, strerror(errno)); - return false; - } - - // Change the owner of the destination file - if (chown(destination, uid, gid) != 0) { - log_msg(LOG_ERROR, "Failed to change the owner of file '%s' (%s).", destination, strerror(errno)); - return false; - } - - return true; -} +#include "utils.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +char* format(const char* fmt, ...) { + va_list args; + va_start(args, fmt); + + // Calculate the length of the formatted string + size_t length = vsnprintf(NULL, 0, fmt, args) + 1; + + va_end(args); + + if (length <= 0) + return NULL; + + // Allocate a buffer for the formatted string + char* buffer = calloc(length, sizeof(char)); + if (buffer == NULL) { + logmsg(LOG_ERROR, "Failed to allocate memory for the formatted string."); + return NULL; + } + + va_start(args, fmt); + + // Format the string + vsnprintf(buffer, length, fmt, args); + + va_end(args); + + return buffer; +} + +void logmsg(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 exec(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) { + logmsg(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) { + logmsg(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) { + logmsg(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) { + logmsg(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) { + logmsg(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) { + logmsg(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) { + logmsg(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) { + logmsg(LOG_ERROR, "Failed to read the file (%s).", strerror(errno)); + + free(data); + + return NULL; + } + + return data; +} + +bool copy_file(const char* src, const char* dst, mode_t mode) { + // Open the source file + int src_fd = open(src, O_RDONLY); + if (src_fd == -1) { + logmsg(LOG_ERROR, "Failed to open the source file %s (%s).", src, strerror(errno)); + return false; + } + + // Open the destination file + int dst_fd = open(dst, O_WRONLY | O_CREAT | O_TRUNC, mode); + if (dst_fd == -1) { + logmsg(LOG_ERROR, "Failed to open the destination file %s (%s).", dst, strerror(errno)); + + close(src_fd); + + return false; + } + + // Copy the file + char buffer[4096]; + ssize_t n; + + while ((n = read(src_fd, buffer, sizeof(buffer))) > 0) { + if (write(dst_fd, buffer, n) != n) { + logmsg(LOG_ERROR, "Failed to write to the destination file %s (%s).", dst, strerror(errno)); + + close(src_fd); + close(dst_fd); + + return false; + } + } + + // Check for read errors + if (n < 0) { + logmsg(LOG_ERROR, "Failed to read the source file %s (%s).", src, strerror(errno)); + + close(src_fd); + close(dst_fd); + + return false; + } + + // Close the files + close(src_fd); + close(dst_fd); + + return true; +} diff --git a/src/utils.h b/src/utils.h old mode 100755 new mode 100644 index 74b08a8..3c3cf28 --- a/src/utils.h +++ b/src/utils.h @@ -1,58 +1,22 @@ -#pragma once - -#include -#include -#include - -#define MAX_RECURSION_LEVEL 256 - -/// @brief The log levels. -typedef enum { - LOG_DEBUG, - LOG_INFO, - LOG_WARN, - LOG_ERROR, -} LogLevel; - -/// @brief Formats a string using the specified format and arguments. -/// @param fmt The format string. -/// @param ... The arguments to use when formatting the string. -/// @return The formatted string. The caller is responsible for freeing the returned string. This function can return NULL. -char* format(const char* fmt, ...); - -/// @brief Logs a formatted message to the console. -/// @param level The log level. -/// @param fmt The format string. -/// @param ... The arguments to use when formatting the string. -void log_msg(LogLevel level, const char* fmt, ...); - -/// @brief Executes a command and returns the exit code. -/// @param stdout_buffer A pointer to a buffer that will receive the standard output of the command. The caller is responsible for freeing the buffer. This parameter can be NULL. -/// @param stderr_buffer A pointer to a buffer that will receive the standard error of the command. The caller is responsible for freeing the buffer. This parameter can be NULL. -/// @param file The path to the command to execute. -/// @param ... The arguments to pass to the command, followed by a NULL pointer. No need to include the command name in the arguments. -/// @return The exit code of the command. -int execute(char** stdout_buffer, char** stderr_buffer, const char* file, ...); - -/// @brief Creates the specified directory. -/// @param directory The directory to create. -/// @param mode The mode to use when creating the directory. -/// @param uid The user ID to use when creating the directory. -/// @param gid The group ID to use when creating the directory. -/// @return true if the directory was created, otherwise false. -bool create_directory(const char* directory, mode_t mode, uid_t uid, gid_t gid); - -/// @brief Deletes the specified directory and all its contents. -/// @param directory The directory to delete. -/// @param level The current level of recursion. This parameter is used internally and should be set to 0. -/// @return true if the directory was deleted, otherwise false. -bool delete_directory(const char* directory, int level); - -/// @brief Copies the specified file to the specified destination. -/// @param source The source file to copy. -/// @param destination The destination file. -/// @param mode The mode to use when creating the destination file. -/// @param uid The user ID to use when creating the destination file. -/// @param gid The group ID to use when creating the destination file. -/// @return true if the file was copied, otherwise false. -bool copy_file(const char* source, const char* destination, mode_t mode, uid_t uid, gid_t gid); \ No newline at end of file +#pragma once + +#include +#include +#include + +typedef enum { + LOG_DEBUG, + LOG_INFO, + LOG_WARN, + LOG_ERROR, +} LogLevel; + +char* format(const char* fmt, ...); + +void logmsg(LogLevel level, const char* fmt, ...); + +int exec(char** outb, char** errb, const char* file, ...); + +char* read_file(int fd); + +bool copy_file(const char* src, const char* dst, mode_t mode); \ No newline at end of file