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 <stddef.h>
-#include <stdlib.h>
-#include <string.h>
-#include <unistd.h>
-#include <dirent.h>
-#include <errno.h>
-#include <time.h>
-#include <stdio.h>
-#include <sys/stat.h>
-
-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 <stdbool.h>
-#include <stdint.h>
-
-/// @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 <stddef.h>
-#include <stdlib.h>
+#include <errno.h>
 #include <string.h>
+#include <stdlib.h>
+#include <unistd.h>
+#include <stdio.h>
+#include <sys/types.h>
 #include <json-c/json.h>
 
-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 <stdbool.h>
 #include <stdint.h>
+#include <stdbool.h>
 
-/// @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 <stddef.h>
-#include <stdlib.h>
-#include <stdio.h>
-#include <string.h>
-#include <unistd.h>
-#include <inttypes.h>
-#include <time.h>
-#include <errno.h>
-#include <sys/stat.h>
-#include <sys/statvfs.h>
-#include <dirent.h>
-
-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 <stdbool.h>
-#include <stdint.h>
-
-#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 <stdlib.h>
-#include <unistd.h>
+#include <stdarg.h>
 #include <stdio.h>
-#include <libgen.h>
-#include <string.h>
-#include <errno.h>
+#include <stdlib.h>
 
 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 <string.h>
-#include <stdarg.h>
-#include <stdio.h>
-#include <stdlib.h>
-#include <time.h>
-#include <unistd.h>
-#include <dirent.h>
-#include <errno.h>
-#include <sys/wait.h>
-#include <sys/types.h>
-#include <sys/stat.h>
-
-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 <stdarg.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <time.h>
+#include <unistd.h>
+#include <string.h>
+#include <errno.h>
+#include <fcntl.h>
+#include <sys/types.h>
+#include <sys/wait.h>
+
+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 <stdbool.h>
-#include <stdint.h>
-#include <sys/types.h>
-
-#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 <stdbool.h>
+#include <stdint.h>
+#include <sys/types.h>
+
+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