commit 827b62fbcd7c4100e84ae3cdd4f945c7c16080bf Author: Alexeï KADIR Date: Thu Feb 15 00:17:20 2024 +0100 Started writing the management program. diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..69f851d --- /dev/null +++ b/.gitignore @@ -0,0 +1 @@ +[bB]in/ diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..b0f27fd --- /dev/null +++ b/Makefile @@ -0,0 +1,28 @@ +# ---- ---- # + +CC = clang +CF = -Wall -lkrb5 -lvirt -ljson-c -g + +# ---- ---- # + +build: $(shell find . -name "*.c") $(shell find . -name "*.h") + @echo "Building sandbox..." + @mkdir -p bin + @$(CC) $(CF) -o ./bin/sandbox $(shell find . -name "*.c") + +run: build + @echo "Running sandbox..." + @./bin/sandbox + +debug: build + @echo "Debugging sandbox..." + @gdb -q ./bin/sandbox + +clean: + @echo "Cleaning sandbox..." + @rm -rf ./bin + +.PHONY: build run clean + +# ---- ---- # + diff --git a/src/backing.c b/src/backing.c new file mode 100644 index 0000000..88f9eca --- /dev/null +++ b/src/backing.c @@ -0,0 +1,66 @@ +#include "backing.h" + +#include "utils.h" +#include "sandbox.h" + +#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 is a number (the number is the timestamp of the backing disk creation) + for (size_t i = 0; i < length; i++) + if (name[i] < '0' || name[i] > '9') + 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; +} \ No newline at end of file diff --git a/src/backing.h b/src/backing.h new file mode 100644 index 0000000..4d81b70 --- /dev/null +++ b/src/backing.h @@ -0,0 +1,32 @@ +#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 Creates a new backing disk. +/// @param backing_disk The disk to use as the backing disk. Warning: the disk must be part of the backing disks. +/// @return true if the backing disk was created, otherwise false. +bool create_backing(const char* backing_disk); \ No newline at end of file diff --git a/src/disk.c b/src/disk.c new file mode 100644 index 0000000..acb957d --- /dev/null +++ b/src/disk.c @@ -0,0 +1,121 @@ +#include "disk.h" + +#include "utils.h" + +#include +#include +#include +#include + +bool create_empty_disk(const char* disk, uint64_t size) { + char* stdoutb = NULL; + char* stderrb = NULL; + + // Create an empty disk + int result = execute(&stdoutb, &stderrb, "qemu-img", "create", "-c", "-f", "qcow2", disk, format("%lu", size), 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 empty disk %s (%s)", disk, stderrb); + + free(stdoutb); + free(stderrb); + + return false; + } + + free(stdoutb); + free(stderrb); + + return true; +} + +bool create_backed_disk(const char* disk, const char* backing) { + char* stdoutb = NULL; + char* stderrb = NULL; + + // Create a backed disk + int result = execute(&stdoutb, &stderrb, "qemu-img", "create", "-c", "-f", "qcow2", "-F", "qcow2", "-b", backing, disk, 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); + + free(stdoutb); + free(stderrb); + + return false; + } + + free(stdoutb); + free(stderrb); + + return true; +} + +char* get_backing_file(const char* disk) { + char* stdoutb = NULL; + char* stderrb = NULL; + + // Get the backing file of the disk + int result = execute(&stdoutb, &stderrb, "qemu-img", "info", "--output", "json", disk, 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); + + free(stdoutb); + free(stderrb); + + return NULL; + } + + free(stderrb); + + // 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; + } + + // 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); + + return NULL; + } + + // 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; +} diff --git a/src/disk.h b/src/disk.h new file mode 100644 index 0000000..5d0e207 --- /dev/null +++ b/src/disk.h @@ -0,0 +1,26 @@ +#pragma once + +#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); + +/// @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 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 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); diff --git a/src/entry.c b/src/entry.c new file mode 100644 index 0000000..3b49cde --- /dev/null +++ b/src/entry.c @@ -0,0 +1,70 @@ +#include "entry.h" + +#include "sandbox.h" +#include "utils.h" + +#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; +} diff --git a/src/entry.h b/src/entry.h new file mode 100644 index 0000000..2e6bfbd --- /dev/null +++ b/src/entry.h @@ -0,0 +1,27 @@ +#pragma once + +#include +#include + +/// @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 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); diff --git a/src/sandbox.c b/src/sandbox.c new file mode 100644 index 0000000..9b56cf2 --- /dev/null +++ b/src/sandbox.c @@ -0,0 +1,16 @@ +#include "sandbox.h" + +#include "utils.h" + +#include "backing.h" +#include "entry.h" +#include "disk.h" + +#include +#include +#include +#include + +int main(int argc, char* argv[]) { + char** backings = list_backings(); +} \ No newline at end of file diff --git a/src/sandbox.h b/src/sandbox.h new file mode 100755 index 0000000..9d117cf --- /dev/null +++ b/src/sandbox.h @@ -0,0 +1,8 @@ +#pragma once + +#define MASTER_DIRECTORY "/var/lib/sandbox" + +#define BACKINGS_DIRECTORY "backings" +#define ENTRIES_DIRECTORY "entries" + +int main(int argc, char* argv[]); diff --git a/src/utils.c b/src/utils.c new file mode 100755 index 0000000..ae84449 --- /dev/null +++ b/src/utils.c @@ -0,0 +1,271 @@ +#include "utils.h" + +#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; +} + diff --git a/src/utils.h b/src/utils.h new file mode 100755 index 0000000..2494a4f --- /dev/null +++ b/src/utils.h @@ -0,0 +1,34 @@ +#pragma once + +/// @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 Returns a list of the files in the specified directory. +/// @param directory The directory to list. +/// @return The list of files in the specified directory. The caller is responsible for freeing the returned array and strings. This function can return NULL. +char** list_files(const char* directory);