diff --git a/src/disk.h b/src/disk.h
index 82b4ee8..1c7a87e 100644
--- a/src/disk.h
+++ b/src/disk.h
@@ -6,6 +6,18 @@
 #include <stdint.h>
 #include <sys/types.h>
 
+/// @brief The information about a disk.
+typedef struct {
+	/// @brief The virtual size of the disk.
+	uint64_t virtual_size;
+
+	/// @brief The actual size of the disk.
+	uint64_t actual_size;
+
+	/// @brief The path to the backing file of the disk. This is NULL if the disk is not backed.
+	char* backing_file;
+} DiskInfo;
+
 /// @brief Creates an empty disk at the given path, with the given size and with the given permissions.
 /// @param path The path to the disk to create.
 /// @param size The size of the disk to create.
@@ -30,3 +42,13 @@ Result trim_disk(const char* path);
 /// @param backing_disk The new path to the backing disk.
 /// @return The result of the operation.
 Result rebase_disk(const char* path, const char* backing_disk);
+
+/// @brief Gathers information about a disk.
+/// @param path The path to the disk to gather information about.
+/// @param out_info The information about the disk.
+/// @return The result of the operation.
+Result get_disk_info(const char* path, DiskInfo* out_info);
+
+/// @brief Frees the resources used by the given disk information.
+/// @param info The disk information to free.
+void free_disk_info(DiskInfo* info);
diff --git a/src/entry.h b/src/entry.h
index 973463a..0432f64 100644
--- a/src/entry.h
+++ b/src/entry.h
@@ -15,7 +15,7 @@ bool is_entry_id_valid(const char* entry_id);
 
 /// @brief Gets the path of the given entry.
 /// @param entry_id The entry id.
-/// @param out_path The pointer to the output path string.
+/// @param out_path The pointer to the output path string. The caller is responsible for freeing the memory.
 /// @return The result of the operation.
 Result get_entry_path(const char* entry_id, char** out_path);
 
diff --git a/src/sandbox.c b/src/sandbox.c
index 5fa6e74..99097d6 100644
--- a/src/sandbox.c
+++ b/src/sandbox.c
@@ -6,6 +6,7 @@
 #include <stdio.h>
 #include <stdlib.h>
 #include <string.h>
+#include <sys/resource.h>
 
 const Command COMMANDS[] = {
 	{command_help, "help", "[command]", "Prints the help message.",
@@ -109,6 +110,8 @@ int command_add_entry(int argc, char* argv[]) {
 		return EXIT_FAILURE;
 	}
 
+	// TODO: Parse arguments
+
 	// Extract the entry id
 	const char* entry_id = argv[0];
 
diff --git a/src/utils.c b/src/utils.c
index 06fc68b..5abc80b 100644
--- a/src/utils.c
+++ b/src/utils.c
@@ -6,6 +6,9 @@
 #include <stdlib.h>
 #include <string.h>
 #include <errno.h>
+#include <unistd.h>
+#include <sys/types.h>
+#include <sys/wait.h>
 
 LogLevel log_level = LOG_LEVEL_WARNING;
 
@@ -87,3 +90,200 @@ Result format(char** out_string, const char* fmt, ...) {
 	*out_string = buffer;
 	return SUCCESS;
 }
+
+Result run_executable(int* out_exit_code, char** out_stdout, char** out_stderr, const char* executable, ...) {
+	if (out_exit_code != NULL)
+		*out_exit_code = -1;
+
+	if (out_stdout != NULL)
+		*out_stdout = NULL;
+	if (out_stderr != NULL)
+		*out_stderr = NULL;
+
+	// Count the number of arguments
+	int argc = 1; // The first argument is the executable itself
+
+	va_list args;
+	va_start(args, executable);
+
+	while (va_arg(args, const char*) != NULL)
+		argc++;
+
+	va_end(args);
+
+	// Allocate an array for the arguments
+	char** argv = calloc(argc + 1, sizeof(char*));
+	if (argv == NULL) {
+		log_message(LOG_LEVEL_ERROR, "Failed to allocate memory for the arguments (%s).", strerror(errno));
+		return OUT_OF_MEMORY;
+	}
+
+	// Fill the array with the arguments
+	argv[0] = strdup(executable);
+	if (argv[0] == NULL) {
+		log_message(LOG_LEVEL_ERROR, "Failed to duplicate the executable string (%s).", strerror(errno));
+
+		free(argv);
+
+		return OUT_OF_MEMORY;
+	}
+
+	va_start(args, executable);
+
+	for (int i = 1; i < argc; i++) {
+		argv[i] = strdup(va_arg(args, const char*));
+		if (argv[i] == NULL) {
+			log_message(LOG_LEVEL_ERROR, "Failed to duplicate the argument string (%s).", strerror(errno));
+
+			for (int j = 0; j < i; j++)
+				free(argv[j]);
+			free(argv);
+
+			va_end(args);
+
+			return OUT_OF_MEMORY;
+		}
+	}
+
+	va_end(args);
+
+	// Set the last element of the array to NULL
+	argv[argc] = NULL;
+
+	// Create pipes for the stdout and stderr
+	int stdout_pipe[2];
+	if (pipe(stdout_pipe) == -1) {
+		log_message(LOG_LEVEL_ERROR, "Failed to create the stdout pipe (%s).", strerror(errno));
+
+		for (int i = 0; i < argc; i++)
+			free(argv[i]);
+		free(argv);
+
+		return FAILURE;
+	}
+
+	int stderr_pipe[2];
+	if (pipe(stderr_pipe) == -1) {
+		log_message(LOG_LEVEL_ERROR, "Failed to create the stderr pipe (%s).", strerror(errno));
+
+		for (int i = 0; i < argc; i++)
+			free(argv[i]);
+		free(argv);
+
+		close(stdout_pipe[0]);
+		close(stdout_pipe[1]);
+
+		return FAILURE;
+	}
+
+	// Fork the process
+	pid_t pid = fork();
+
+	if (pid == -1) {
+		log_message(LOG_LEVEL_ERROR, "Failed to fork the process (%s).", strerror(errno));
+
+		for (int i = 0; i < argc; i++)
+			free(argv[i]);
+		free(argv);
+
+		close(stdout_pipe[0]);
+		close(stdout_pipe[1]);
+		close(stderr_pipe[0]);
+		close(stderr_pipe[1]);
+
+		return FAILURE;
+	}
+
+	if (pid == 0) {
+		// Redirect the stdout and stderr to the pipes
+		dup2(stdout_pipe[1], STDOUT_FILENO);
+		dup2(stderr_pipe[1], STDERR_FILENO);
+
+		// Close the unused ends of the pipes
+		close(stdout_pipe[0]);
+		close(stdout_pipe[1]);
+		close(stderr_pipe[0]);
+		close(stderr_pipe[1]);
+
+		// Execute the command
+		execvp(executable, argv);
+
+		// If the command failed to execute, print an error message and exit
+		fprintf(stderr, "Failed to execute the command '%s' (%s).\n", executable, strerror(errno));
+		exit(EXIT_FAILURE);
+	}
+
+	// Free the arguments array
+	for (int i = 0; i < argc; i++)
+		free(argv[i]);
+	free(argv);
+
+	// Close the unused ends 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 output from the pipes
+	if (out_stdout != NULL)
+		read_fd(stdout_pipe[0], out_stdout);
+	if (out_stderr != NULL)
+		read_fd(stderr_pipe[0], out_stderr);
+
+	// Close the pipes
+	close(stdout_pipe[0]);
+	close(stderr_pipe[0]);
+
+	// Set the exit code
+	if (out_exit_code != NULL)
+		*out_exit_code = WEXITSTATUS(status);
+
+	return SUCCESS;
+}
+
+Result read_fd(int fd, char** out_string) {
+	*out_string = NULL;
+
+	// Create a buffer for the output
+	char buffer[4096];
+	size_t length = 0;
+
+	// Read the output from the file descriptor
+	ssize_t bytes_read;
+	while ((bytes_read = read(fd, buffer, sizeof(buffer))) > 0) {
+		// Reallocate the buffer
+		char* new_buffer = realloc(*out_string, length + bytes_read + 1);
+		if (new_buffer == NULL) {
+			log_message(LOG_LEVEL_ERROR, "Failed to reallocate memory for the output string (%s).", strerror(errno));
+
+			free(*out_string);
+			*out_string = NULL;
+
+			return OUT_OF_MEMORY;
+		}
+
+		*out_string = new_buffer;
+
+		// Copy the new data to the buffer
+		memcpy(*out_string + length, buffer, bytes_read);
+		length += bytes_read;
+	}
+
+	// Check if an error occurred
+	if (bytes_read == -1) {
+		log_message(LOG_LEVEL_ERROR, "Failed to read from the file descriptor (%s).", strerror(errno));
+
+		free(*out_string);
+		*out_string = NULL;
+
+		return FAILURE;
+	}
+
+	// Null-terminate the string
+	if (length > 0)
+		(*out_string)[length] = '\0';
+
+	return SUCCESS;
+}
diff --git a/src/utils.h b/src/utils.h
index b69d8f4..750f5a3 100644
--- a/src/utils.h
+++ b/src/utils.h
@@ -27,8 +27,23 @@ void set_log_level(LogLevel level);
 void log_message(LogLevel level, const char* format, ...);
 
 /// @brief Formats a string.
-/// @param out_string The pointer to the output string.
+/// @param out_string The pointer to the output string. The caller is responsible for freeing the memory.
 /// @param fmt The format string.
 /// @param ... The format arguments.
 /// @return The result of the operation.
-Result format(char** out_string, const char* fmt, ...);
\ No newline at end of file
+Result format(char** out_string, const char* fmt, ...);
+
+/// @brief Runs an external executable with the given arguments, and waits for it to finish, then returns the exit code and the output.
+/// @param out_exit_code The pointer to the output exit code. This argument can be NULL if the exit code is not needed.
+/// @param out_stdout The pointer to the output stdout string. This argument can be NULL if the output is not needed. The caller is responsible for freeing the memory.
+/// @param out_stderr The pointer to the output stderr string. This argument can be NULL if the output is not needed. The caller is responsible for freeing the memory.
+/// @param executable The path of the executable to run.
+/// @param ... The arguments of the executable. The last argument must be NULL. Note: No need to include the executable path in the arguments.
+/// @return The result of the operation.
+Result run_executable(int* out_exit_code, char** out_stdout, char** out_stderr, const char* executable, ...);
+
+/// @brief Reads the contents of a file descriptor into a string.
+/// @param fd The file descriptor to read from.
+/// @param out_string The pointer to the output string. The caller is responsible for freeing the memory.
+/// @return The result of the operation.
+Result read_fd(int fd, char** out_string);
\ No newline at end of file