diff --git a/Makefile b/Makefile index 64c1e09..0c16172 100755 --- a/Makefile +++ b/Makefile @@ -1,22 +1,22 @@ # ---- ---- # CC = gcc -CF = -Wall -lkrb5 -lvirt -ljson-c -lcrypto -g +CF = -Wall -lkrb5 -lvirt -ljson-c -lcrypto -g -pedantic # ---- ---- # build: $(shell find . -name "*.c") $(shell find . -name "*.h") @mkdir -p bin - @$(CC) $(CF) -o ./bin/sandbox $(shell find . -name "*.c") + $(CC) $(CF) -o ./bin/sandbox $(shell find . -name "*.c") run: build - @./bin/sandbox + ./bin/sandbox debug: build - @gdb -q ./bin/sandbox + gdb -q ./bin/sandbox clean: - @rm -rf ./bin + rm -rf ./bin .PHONY: build run clean diff --git a/src/auth.h b/src/auth.h new file mode 100644 index 0000000..cbbb812 --- /dev/null +++ b/src/auth.h @@ -0,0 +1,4 @@ +#pragma once + +#include "utils.h" + diff --git a/src/backing.c b/src/backing.c old mode 100755 new mode 100644 index 6a2ae38..842e4d3 --- a/src/backing.c +++ b/src/backing.c @@ -1,496 +1,362 @@ #include "backing.h" - -#include "sandbox.h" #include "container.h" #include "disk.h" +#include "sandbox.h" -#include -#include -#include -#include #include +#include +#include +#include #include -#include #include result_t check_backing_identifier(const char* backing) { - // Check that the identifier is not null + // Check that the backing identifier is not null. if (backing == NULL) return failure("Backing identifier cannot be null."); - // Check that the identifier is the length of a md5 hash + // Check that the backing identifier is a valid md5 hash. size_t length = strlen(backing); - if (length != MD5_DIGEST_STRING_LENGTH - 1) // -1 because the string is null-terminated - return failure("Backing identifier must be %d characters long.", MD5_DIGEST_STRING_LENGTH - 1); + if (length != 32) + return failure("Backing identifier must be a 32-character md5 hash."); - // Check that the identifier only contains allowed characters + // Check that the backing identifier only contains valid characters. for (size_t i = 0; i < length; i++) { char c = backing[i]; - if (c >= 'a' && c <= 'f') - continue; - if (c >= '0' && c <= '9') + if ((c >= 'a' && c <= 'f') || (c >= '0' && c <= '9')) continue; - return failure("Backing identifier cannot contain the character '%c' at index %d.", c, i); + return failure("Backing identifier must be a 32-character md5 hash."); } return success(); } -result_t check_backing_exists(const char* backing) { - // Get the backing path - char* path; - result_t result = get_backing_path(&path, backing); - if (result != success()) - return result; +result_t get_backing_pool_path(char** _path, const config_t* config) { + // Get the path of the backing pool. + char* path = strdup(config->backing_pool); + if (path == NULL) + return failure("Failed to allocate memory for the backing pool path."); - // Check that the backing exists - struct stat st; - errno = 0; - if (stat(path, &st) != 0) { - if (errno == ENOENT) - result = failure("Backing '%s' does not exist.", backing); - else - result = failure("Failed to check if backing '%s' exists (%s).", backing, strerror(errno)); - - free(path); - return result; - } - - // Check that the backing is a file - if (!S_ISREG(st.st_mode)) { - result = failure("Backing '%s' is not a regular file.", backing); - free(path); - return result; - } - - // Free the backing path - free(path); + // Output the path. + *_path = path; return success(); } -result_t get_backing_pool_path(char** _path) { - // Initialize the output parameters - *_path = NULL; - - return format(_path, "/var/lib/sandbox/backings"); -} - -result_t get_backing_path(char** _path, const char* backing) { - // Initialize the output parameters - *_path = NULL; - - // Check the backing identifier +result_t get_backing_path(char** _path, const config_t* config, const char* backing) { + // Check that the backing identifier is valid. result_t result = check_backing_identifier(backing); if (result != success()) return result; - // Get the backing pool path + // Get the path of the backing pool. char* pool_path; - result = get_backing_pool_path(&pool_path); + result = get_backing_pool_path(&pool_path, config); if (result != success()) return result; - // Format the backing path - result = format(_path, "%s/%s", pool_path, backing); - - free(pool_path); - return result; -} - -result_t get_backing_default_path(char** _path) { - // Initialize the output parameters - *_path = NULL; - - // Get the backing pool path - char* pool_path; - result_t result = get_backing_pool_path(&pool_path); - if (result != success()) - return result; - - // Format the backing path - result = format(_path, "%s/default", pool_path); - - free(pool_path); - return result; -} - -result_t get_backing_temp_path(char** _path) { - // Initialize the output parameters - *_path = NULL; - - // Get the backing pool path - char* pool_path; - result_t result = get_backing_pool_path(&pool_path); - if (result != success()) - return result; - - // Format the backing path - result = format(_path, "%s/temp", pool_path); - - free(pool_path); - return result; -} - -result_t add_backing(char** _backing, const char* container) { - // Initialize the output parameters - *_backing = NULL; - - // Get the container path + // Append the backing identifier to the backing pool path. char* path; - result_t result = get_container_path(&path, container); - if (result != success()) - return result; - - // Import the container as a backing - result = import_backing(_backing, path); - - free(path); - return result; -} - -result_t import_backing(char** _backing, const char* disk) { - // Initialize the output parameters - *_backing = NULL; - - // Get the temporary backing path - char* temp_path; - result_t result = get_backing_temp_path(&temp_path); - if (result != success()) - return result; - - // Copy the disk to the temporary backing path - result = copy_file(disk, temp_path); - if (result != success()) { - free(temp_path); - return result; + if (asprintf(&path, "%s/%s", pool_path, backing) == -1) { + free(pool_path); + return failure("Failed to allocate memory for the backing path."); } - // Get the disk info to know if the disk is backed - disk_info_t info; - result = get_disk_info(&info, temp_path); - if (result != success()) { - remove(temp_path); - free(temp_path); - return result; - } + free(pool_path); - if (info.backing_path != NULL) { - // Get the backing identifier of the parent - errno = 0; - char* backing = strdup(basename(info.backing_path)); - if (backing == NULL) { - free_disk_info(&info); - remove(temp_path); - free(temp_path); - return failure("Failed to get the backing identifier of the parent of '%s' (%s).", disk, strerror(errno)); - } + // Output the path. + *_path = path; - // Check that the backing exists - result = check_backing_exists(backing); - if (result != success()) { - free_disk_info(&info); - remove(temp_path); - free(temp_path); - free(backing); - return result; - } - - // Reback the disk to the parent - result = reback_disk(temp_path, backing); - if (result != success()) { - free_disk_info(&info); - remove(temp_path); - free(temp_path); - free(backing); - return result; - } - - free(backing); - } - - free_disk_info(&info); - - // Get the md5 hash of the disk as the backing identifier - char* backing; - result = md5sum(&backing, temp_path); - if (result != success()) { - remove(temp_path); - free(temp_path); - return result; - } - - // Check if the backing already exists - result = check_backing_exists(backing); - if (result == success()) { - result = failure("Backing '%s' already exists.", backing); - free(backing); - remove(temp_path); - free(temp_path); - return result; - } - - // Get the path of the final backing - char* backing_path; - result = get_backing_path(&backing_path, backing); - if (result != success()) { - free(backing); - remove(temp_path); - free(temp_path); - return result; - } - - // Move the temporary backing to the final backing path - errno = 0; - if (rename(temp_path, backing_path) != 0) { - result = failure("Failed to move the temporary backing to the final backing path (%s).", strerror(errno)); - free(backing); - free(backing_path); - remove(temp_path); - free(temp_path); - return result; - } - - free(backing_path); - free(temp_path); - - *_backing = backing; - return result; -} - -result_t remove_backing(const char* backing) { - // Check that the backing exists - result_t result = check_backing_exists(backing); - if (result != success()) - return result; - - // Get the backing path - char* path; - result = get_backing_path(&path, backing); - if (result != success()) - return result; - - // Remove the backing - errno = 0; - remove(path); - - // Check for errors during the removal - if (errno != 0) { - free(path); - return failure("Failed to remove backing '%s' (%s).", backing, strerror(errno)); - } - - free(path); return success(); } -bool backing_filter(const char* file) { - // Check that a backing with the same name exists - return check_backing_exists(file) == success(); -} - -result_t list_backings(char*** _backings) { - // Initialize the output parameters - *_backings = NULL; - - // Get the backing pool path +result_t get_backing_default_path(char** _path, const config_t* config) { + // Get the path of the backing pool. char* pool_path; - result_t result = get_backing_pool_path(&pool_path); + result_t result = get_backing_pool_path(&pool_path, config); if (result != success()) return result; - // List the backings - result = list_files(_backings, pool_path, &backing_filter); + // Append the default backing identifier to the backing pool path. + char* path; + if (asprintf(&path, "%s/default", pool_path) == -1) { + free(pool_path); + return failure("Failed to allocate memory for the default backing path."); + } free(pool_path); - return result; + + // Output the path. + *_path = path; + + return success(); } -result_t get_default_backing(char** _backing) { - // Initialize the output parameters - *_backing = NULL; - - // Get the backing default path - char* path; - result_t result = get_backing_default_path(&path); +result_t get_backing_temporary_path(char** _path, const config_t* config) { + // Get the path of the backing pool. + char* pool_path; + result_t result = get_backing_pool_path(&pool_path, config); if (result != success()) return result; - char* backing; + // Append the temporary backing identifier to the backing pool path. + char* path; + if (asprintf(&path, "%s/temporary", pool_path) == -1) { + free(pool_path); + return failure("Failed to allocate memory for the temporary backing path."); + } - // Read the default backing - result = read_file(&backing, path); + free(pool_path); + // Output the path. + *_path = path; + + return success(); +} + +result_t check_backing_exists(const config_t* config, const char* backing) { + // Get the path of the backing. + char* path; + result_t result = get_backing_path(&path, config, backing); + if (result != success()) + return result; + + // Check whether the backing exists. + struct stat st; + if (stat(path, &st) == -1) { + free(path); + if (errno == ENOENT) + return failure("Backing '%s' does not exist.", backing); + return failure("Failed to check whether backing '%s' exists: %s.", backing, strerror(errno)); + } + + if (!S_ISREG(st.st_mode)) { + free(path); + return failure("Backing '%s' is not a regular file.", backing); + } + + // Free the backing path. free(path); - if (result != success()) - return failure("No default backing configured."); + return success(); +} - // Check that the backing is valid +result_t get_default_backing(char** _backing, const config_t* config) { + // Get the path of the default backing. + char* path; + result_t result = get_backing_default_path(&path, config); + if (result != success()) + return result; + + // Read the backing identifier from the default backing file. + char* backing = NULL; + result = read_file(&backing, NULL, path); + if (result != success()) { + free(path); + return result; + } + + // Free the default backing path. + free(path); + + // Check that the backing identifier is valid. result = check_backing_identifier(backing); if (result != success()) { free(backing); - return failure("Default backing '%s' is not a valid backing identifier.", backing); - } - - // Check that the backing exists - result = check_backing_exists(backing); - if (result != success()) { - free(backing); - return failure("Default backing '%s' does not exist.", backing); + return result; } + // Output the backing identifier. *_backing = backing; - return result; + + return success(); } -result_t set_default_backing(const char* backing) { - // Get the backing default path - char* path; - result_t result = get_backing_default_path(&path); +result_t set_default_backing(const config_t* config, const char* backing) { + // Check that the backing identifier is valid. + result_t result = check_backing_identifier(backing); if (result != success()) return result; - // If the backing is null, remove the default backing file - if (backing == NULL) { - errno = 0; - remove(path); + // Get the path of the default backing. + char* path; + result = get_backing_default_path(&path, config); + if (result != success()) + return result; + + // Write the backing identifier to the default backing file. + result = write_file(backing, path, sizeof(path), 0644); + if (result != success()) { free(path); + return result; + } - if (errno == ENOENT) - return success(); - else if (errno != 0) - return failure("Failed to remove the default backing file (%s).", strerror(errno)); + // Free the default backing path. + free(path); - return success(); - } else { - // Check that the backing exists - result = check_backing_exists(backing); + return success(); +} + +result_t add_backing(char** _backing, const config_t* config, const char* container) { + // Check that the container identifier is valid. + result_t result = check_container_identifier(container); + if (result != success()) + return result; + + // Check that the container exists. + result = check_container_exists(config, container); + if (result != success()) + return result; + + // Get the path of the container. + char* path; + result = get_container_path(&path, config, container); + if (result != success()) + return result; + + // Get the path of the temporary backing. + char* temporary_path; + result = get_backing_temporary_path(&temporary_path, config); + if (result != success()) { + free(path); + return result; + } + + // Copy the container to the temporary backing. + result = copy_file(path, temporary_path, 0644); + if (result != success()) { + free(path); + free(temporary_path); + return result; + } + + // Free the container path. + free(path); + + // Get information about the temporary backing. + disk_info_t* info; + result = read_disk_info(&info, temporary_path); + if (result != success()) { + unlink(temporary_path); + free(temporary_path); + return result; + } + + if (info->backing_name != NULL) { + result = check_backing_exists(config, info->backing_name); if (result != success()) { - free(path); + free_disk_info(info); + unlink(temporary_path); + free(temporary_path); return result; } - // Write the default backing - result = write_file(path, backing); - - free(path); - return result; + // Reback the temporary backing to a relative path. + result = reback_disk(temporary_path, info->backing_name); + if (result != success()) { + free_disk_info(info); + unlink(temporary_path); + free(temporary_path); + return result; + } } -} -result_t get_backing_parent(char** _parent, const char* backing) { - // Initialize the output parameters - *_parent = NULL; + free_disk_info(info); - // Get the backing path - char* path; - result_t result = get_backing_path(&path, backing); - if (result != success()) - return result; - - // Get the disk info - disk_info_t info; - result = get_disk_info(&info, path); + // Get the md5 hash of the temporary backing. + char* hash; + result = md5_file(&hash, temporary_path); if (result != success()) { - free(path); + unlink(temporary_path); + free(temporary_path); return result; } - // Free the backing path as it is not needed anymore - free(path); + // Check if the backing already exists. + result = check_backing_exists(config, hash); + if (result == success()) { + unlink(temporary_path); + free(temporary_path); - // Check if the disk is backed - if (info.backing_path == NULL) { - free_disk_info(&info); - return success(); + result = failure("Backing '%s' already exists.", hash); + free(hash); + return result; } - // Get the backing identifier of the parent - errno = 0; - char* parent = strdup(basename(info.backing_path)); + // Move the temporary backing to the backing pool. + char* backing_path; + result = get_backing_path(&backing_path, config, hash); + if (result != success()) { + unlink(temporary_path); + free(temporary_path); + free(hash); + return result; + } - // Free the disk info as it is not needed anymore - free_disk_info(&info); + if (rename(temporary_path, backing_path) == -1) { + unlink(temporary_path); + free(temporary_path); + free(backing_path); + free(hash); + return failure("Failed to move temporary backing to the backing pool: %s.", strerror(errno)); + } - if (parent == NULL) - return failure("Failed to get the backing identifier of the parent of '%s' (%s).", backing, strerror(errno)); + free(temporary_path); + + // Output the backing identifier. + *_backing = hash; - *_parent = parent; return success(); } -result_t get_backing_info(disk_info_t* _info, const char* backing) { - // Initialize the output parameters - _info->size = 0; - _info->allocated = 0; - _info->backing_path = NULL; - - // Check that the backing exists - result_t result = check_backing_exists(backing); +result_t remove_backing(const config_t* config, const char* backing) { + // Check that the backing exists. + result_t result = check_backing_exists(config, backing); if (result != success()) return result; - // Get the backing path + // Get the path of the backing. char* path; - result = get_backing_path(&path, backing); + result = get_backing_path(&path, config, backing); if (result != success()) return result; - // Get the disk information - result = get_disk_info(_info, path); + // Remove the backing. + if (unlink(path) == -1) { + free(path); + return failure("Failed to remove backing '%s': %s.", backing, strerror(errno)); + } + // Free the backing path. free(path); + return success(); +} + +result_t list_backings(char*** _backings, const config_t* config) { + // Get the path of the backing pool. + char* pool_path; + result_t result = get_backing_pool_path(&pool_path, config); + if (result != success()) + return result; + + // Get the list of backings. + return list_files(_backings, pool_path, check_backing_identifier); +} + +result_t sync_backing(const config_t* config) { + // Get the backing pool path. + char* pool_path; + result_t result = get_backing_pool_path(&pool_path, config); + if (result != success()) + return result; + + // Execute the synchronization script. + result = execute_file(NULL, NULL, NULL, NULL, NULL, pool_path, SYNCRONIZATION_FILE, NULL); + + // Free the backing pool path. + free(pool_path); + return result; } - -result_t sync_backing_pool(void) { - // Check that the file /etc/sandbox.d/sync exists - struct stat st; - errno = 0; - if (stat("/etc/sandbox.d/sync", &st) != 0) { - if (errno == ENOENT) - return success(); - else - return failure("Failed to check if /etc/sandbox.d/sync exists (%s).", strerror(errno)); - } - - // Execute /etc/sandbox.d/sync with the backing pool as the working directory - int exit_code; - // char* stdoutbuf; - char* stderrbuf; - - // Get the path of the backing pool - char* pool_path; - result_t result = get_backing_pool_path(&pool_path); - if (result != success()) - return result; - - // Execute the sync script - result = execute(&exit_code, NULL, &stderrbuf, pool_path, "/etc/sandbox.d/sync", NULL); - - free(pool_path); - - // Check for errors during the execution - if (result != success()) - return result; - - // Check the exit code - if (exit_code != 0) { - // Remove all newlines from the stderr buffer - for (char* c = stderrbuf; *c != '\0'; c++) - if (*c == '\n') - *c = ' '; - - result = failure("Failed to synchronize the backing pool (%s).", stderrbuf); - free(stderrbuf); - return result; - } - - // Free the stderr buffer as it is not needed anymore - free(stderrbuf); - - return success(); -} diff --git a/src/backing.h b/src/backing.h old mode 100755 new mode 100644 index 332638a..2a5afaa --- a/src/backing.h +++ b/src/backing.h @@ -1,88 +1,76 @@ #pragma once #include "utils.h" -#include "disk.h" +#include "config.h" -/// @brief Checks whether the given string is a valid backing identifier. If the string is not a valid backing identifier, the call will return a failure result with an error message. -/// @param backing The string to check. +/// @brief Checks whether the specified backing identifier is valid. This call returns a failure if the identifier is invalid. +/// @param backing The backing identifier to check. /// @return The result of the operation. result_t check_backing_identifier(const char* backing); -/// @brief Checks whether the given backing exists. If the backing does not exist, the call will return a failure result with an error message. -/// @param backing The backing to check. +/// @brief Returns the path of the backing pool. +/// @param _path The pointer to where the path should be stored. The caller is responsible for freeing the path. +/// @param config The program configuration to use. /// @return The result of the operation. -result_t check_backing_exists(const char* backing); +result_t get_backing_pool_path(char** _path, const config_t* config); -/// @brief Gets the backing pool path. -/// @param _path The string pointer to store the resulting path in. The caller is responsible for freeing the string. +/// @brief Returns the path of the specified backing. +/// @param _path The pointer to where the path should be stored. The caller is responsible for freeing the path. +/// @param config The program configuration to use. +/// @param backing The identifier of the backing to get the path of. /// @return The result of the operation. -result_t get_backing_pool_path(char** _path); +result_t get_backing_path(char** _path, const config_t* config, const char* backing); -/// @brief Gets the path of the given backing. -/// @param _path The string pointer to store the resulting path in. The caller is responsible for freeing the string. -/// @param backing The backing to get the path of. +/// @brief Returns the path of the default disk file. +/// @param _path The pointer to where the path should be stored. The caller is responsible for freeing the path. +/// @param config The program configuration to use. /// @return The result of the operation. -result_t get_backing_path(char** _path, const char* backing); +result_t get_backing_default_path(char** _path, const config_t* config); -/// @brief Gets the path of the file containing the default backing identifier. -/// @param _path The string pointer to store the resulting path in. The caller is responsible for freeing the string. +/// @brief Returns the path of the temporary disk file. +/// @param _path The pointer to where the path should be stored. The caller is responsible for freeing the path. +/// @param config The program configuration to use. /// @return The result of the operation. -result_t get_backing_default_path(char** _path); +result_t get_backing_temporary_path(char** _path, const config_t* config); -/// @brief Gets the path of the temporary path to use for backing files. -/// @param _path The string pointer to store the resulting path in. The caller is responsible for freeing the string. +/// @brief Checks whether the specified backing exists. This call returns a failure if the backing does not exist. +/// @param config The program configuration to use. +/// @param backing The identifier of the backing to check. +/// @return Whether the backing exists. +result_t check_backing_exists(const config_t* config, const char* backing); + +/// @brief Returns the default backing identifier. +/// @param _backing The pointer to where the backing should be stored. The caller is responsible for freeing the backing. +/// @param config The program configuration to use. /// @return The result of the operation. -result_t get_backing_temp_path(char** _path); - -/// @brief Adds a backing to the pool, from the given container. -/// @param _backing The string pointer to store the resulting backing identifier in. The caller is responsible for freeing the string. -/// @param container The container to use as the source for the backing. -/// @return The result of the operation. -result_t add_backing(char** _backing, const char* container); - -/// @brief Imports a backing from the given disk. -/// @param _backing The string pointer to store the resulting backing identifier in. The caller is responsible for freeing the string. -/// @param disk The disk to use as the source for the backing. -/// @return The result of the operation. -result_t import_backing(char** _backing, const char* disk); - -/// @brief Removes the given backing from the pool. If the backing does not exist, the call will return a failure result with an error message. -/// @param backing The backing to remove. -/// @return The result of the operation. -result_t remove_backing(const char* backing); - -/// @brief Checks that the given file is a backing. This function is used as a filter for listing backings. -/// @param file The file to check. -/// @return Whether the file is a backing. -bool backing_filter(const char* file); - -/// @brief Lists the backings in the pool. -/// @param _backings The string array pointer to store the resulting array in. The caller is responsible for freeing the strings and the array. -/// @return The result of the operation. -result_t list_backings(char*** _backings); - -/// @brief Gets the default backing identifier. -/// @param _backing The string pointer to store the resulting backing identifier in. The caller is responsible for freeing the string. -/// @return The result of the operation. -result_t get_default_backing(char** _backing); +result_t get_default_backing(char** _backing, const config_t* config); /// @brief Sets the default backing identifier. -/// @param backing The backing identifier to set as the default. This parameter can be NULL to remove the default backing. +/// @param config The program configuration to use. +/// @param backing The identifier of the backing to set as default. /// @return The result of the operation. -result_t set_default_backing(const char* backing); +result_t set_default_backing(const config_t* config, const char* backing); -/// @brief Gets the backing parent of the given backing. -/// @param _parent The string pointer to store the resulting backing parent identifier in. The caller is responsible for freeing the string. -/// @param backing The backing to get the parent of. +/// @brief Adds a new backing to the backing pool from the specified container. +/// @param _backing The pointer to where the backing identifier should be stored. The caller is responsible for freeing the backing. +/// @param config The program configuration to use. +/// @param container The identifier of the container to add the backing from. /// @return The result of the operation. -result_t get_backing_parent(char** _parent, const char* backing); +result_t add_backing(char** _backing, const config_t* config, const char* container); -/// @brief Gets information about the given backing. If the backing does not exist, the call will return a failure result with an error message. -/// @param _info The string pointer to store the resulting disk information in. The caller is responsible for freeing the string. -/// @param backing The backing to get information about. +/// @brief Removes the specified backing from the backing pool. +/// @param config The program configuration to use. +/// @param backing The identifier of the backing to remove. /// @return The result of the operation. -result_t get_backing_info(disk_info_t* _info, const char* backing); +result_t remove_backing(const config_t* config, const char* backing); -/// @brief Executes /etc/sandbox.d/sync to synchronize the backing pool. +/// @brief Lists the backings in the backing pool, and returns the result in a null-terminated array of strings. +/// @param _backings The pointer to where the backings array should be stored. The caller is responsible for freeing the backings array as well as the strings in the array. +/// @param config The program configuration to use. /// @return The result of the operation. -result_t sync_backing_pool(void); +result_t list_backings(char*** _backings, const config_t* config); + +/// @brief Executes the syncronization script. +/// @param config The program configuration to use. +/// @return The result of the operation. +result_t sync_backing(const config_t* config); \ No newline at end of file diff --git a/src/cache.h b/src/cache.h new file mode 100644 index 0000000..cbbb812 --- /dev/null +++ b/src/cache.h @@ -0,0 +1,4 @@ +#pragma once + +#include "utils.h" + diff --git a/src/config.c b/src/config.c new file mode 100644 index 0000000..ae25173 --- /dev/null +++ b/src/config.c @@ -0,0 +1,31 @@ +#include "config.h" + +#include "sandbox.h" + +#include +#include +#include +#include +#include + +result_t init_config(config_t** _config) { + // Create the configuration. + config_t* config = malloc(sizeof(config_t)); + if (config == NULL) + return failure("Failed to allocate memory for the configuration."); + + // Initialize the configuration. + config->container_pool = strdup("/var/lib/sandbox/containers"); + config->backing_pool = strdup("/var/lib/sandbox/backings"); + + // Output the configuration. + *_config = config; + + return success(); +} + +void free_config(config_t* config) { + free(config->container_pool); + + free(config); +} diff --git a/src/config.h b/src/config.h new file mode 100644 index 0000000..e90ea90 --- /dev/null +++ b/src/config.h @@ -0,0 +1,21 @@ +#pragma once + +#include "utils.h" + +/// @brief Structure used to store the program configuration. +typedef struct { + /// @brief The path of the directory where the containers are stored. + char* container_pool; + + /// @brief The path of the directory where the backings are stored. + char* backing_pool; +} config_t; + +/// @brief Initializes the program configuration. +/// @param _config The pointer to where the configuration should be stored. +/// @return The result of the operation. +result_t init_config(config_t** _config); + +/// @brief Frees the program configuration. +/// @param config The configuration to free. +void free_config(config_t* config); diff --git a/src/container.c b/src/container.c old mode 100755 new mode 100644 index 4d983fb..9284e5f --- a/src/container.c +++ b/src/container.c @@ -1,30 +1,27 @@ #include "container.h" - +#include "backing.h" #include "disk.h" #include "backing.h" -#include -#include #include +#include +#include #include -#include -#include +#include #include -#include +#include result_t check_container_identifier(const char* container) { - // Check that the container identifier is not null + // Check that the container identifier is not null. if (container == NULL) return failure("Container identifier cannot be null."); - // Check that the container identifier is not empty, nor too long + // Check that the container identifier is not empty, nor too long. size_t length = strlen(container); - if (length == 0) - return failure("Container identifier cannot be empty."); - if (length > CONTAINER_IDENTIFIER_MAX_LENGTH) - return failure("Container identifier cannot be longer than %d characters.", CONTAINER_IDENTIFIER_MAX_LENGTH); + if (length == 0 || length > MAX_CONTAINER_IDENTIFIER_LENGTH) + return failure("Container identifier cannot be empty or be longer than %d characters.", MAX_CONTAINER_IDENTIFIER_LENGTH); - // Check that the container identifier only contains allowed characters + // Check that the container identifier only contains valid characters. for (size_t i = 0; i < length; i++) { char c = container[i]; if (c >= 'a' && c <= 'z') @@ -33,334 +30,300 @@ result_t check_container_identifier(const char* container) { continue; if (c >= '0' && c <= '9') continue; - if ((c == '-' || c == '_' || c == '.') && i > 0) // Not at the beginning + if ((c == '-' || c == '_' || c == '.') && i > 0) // Not as the first character. continue; - return failure("Container identifier cannot contain the character '%c' at index %d.", c, i); + return failure("Container identifier cannot contain the character '%c' at position %d.", c, i); } return success(); } -result_t check_container_exists(const char* container) { - // Get the container path - char* path; - result_t result = get_container_path(&path, container); - if (result != success()) - return result; +result_t get_container_pool_path(char** _path, const config_t* config) { + // Get the path of the container pool. + char* path = strdup(config->container_pool); + if (path == NULL) + return failure("Failed to allocate memory for the container pool path."); - // Check that the container exists - struct stat st; - errno = 0; - if (stat(path, &st) != 0) { - if (errno == ENOENT) - result = failure("Container '%s' does not exist.", container); - else - result = failure("Failed to check if container '%s' exists (%s).", container, strerror(errno)); + // Output the path. + *_path = path; - free(path); - return result; - } - - // Check that the container is a file - if (!S_ISREG(st.st_mode)) { - result = failure("Container '%s' is not a file.", container); - free(path); - return result; - } - - free(path); return success(); } -result_t get_container_pool_path(char** _path) { - // Initialize the output parameters - *_path = NULL; - - return format(_path, "/var/lib/sandbox/containers"); -} - -result_t get_container_path(char** _path, const char* container) { - // Initialize the output parameters - *_path = NULL; - - // Check the container identifier +result_t get_container_path(char** _path, const config_t* config, const char* container) { + // Check that the container identifier is valid. result_t result = check_container_identifier(container); if (result != success()) return result; - // Get the container pool path + // Get the path of the container pool. char* pool_path; - result = get_container_pool_path(&pool_path); + result = get_container_pool_path(&pool_path, config); if (result != success()) return result; - // Format the container path - result = format(_path, "%s/%s", pool_path, container); + // Append the container identifier to the container pool path. + char* path; + if (asprintf(&path, "%s/%s", pool_path, container) == -1) { + free(pool_path); + return failure("Failed to allocate memory for the container path."); + } + // Free the container pool path. free(pool_path); - return result; + + // Output the path. + *_path = path; + + return success(); } -result_t add_root_container(const char* container, uint64_t size) { - // Check that the container does not already exist - result_t result = check_container_exists(container); +result_t check_container_exists(const config_t* config, const char* container) { + // Get the path of the container. + char* path; + result_t result = get_container_path(&path, config, container); + if (result != success()) + return result; + + // Check whether the container exists. + struct stat st; + if (stat(path, &st) == -1) { + free(path); + if (errno == ENOENT) + return failure("Container '%s' does not exist.", container); + return failure("Failed to check whether container '%s' exists: %s.", container, strerror(errno)); + } + + if (!S_ISREG(st.st_mode)) { + free(path); + return failure("Container '%s' is not a regular file.", container); + } + + // Free the container path. + free(path); + + return success(); +} + +result_t add_root_container(const config_t* config, const char* container, uint64_t size) { + // Check that the container identifier is valid. + result_t result = check_container_identifier(container); + if (result != success()) + return result; + + // Check that the container does not already exist. + result = check_container_exists(config, container); if (result == success()) return failure("Container '%s' already exists.", container); - // Get the container path + // Get the path of the container. char* path; - result = get_container_path(&path, container); + result = get_container_path(&path, config, container); if (result != success()) return result; - // Create the root container + // Create the root container. result = create_root_disk(path, size); + if (result != success()) { + free(path); + return result; + } + // Free the container path. free(path); - return result; + + return success(); } -result_t add_backed_container(const char* container, const char* backing) { - // Check that the container does not already exist - result_t result = check_container_exists(container); +result_t add_backed_container(const config_t* config, const char* container, const char* backing) { + // Check that the container identifier is valid. + result_t result = check_container_identifier(container); + if (result != success()) + return result; + + // Check that the container does not already exist. + result = check_container_exists(config, container); if (result == success()) return failure("Container '%s' already exists.", container); - // Get the container path - char* path; - result = get_container_path(&path, container); + // Check that the backing exists. + result = check_backing_exists(config, backing); if (result != success()) return result; - // Check that the backing exists - result = check_backing_exists(backing); - if (result != success()) { - free(path); + // Get the path of the container. + char* path; + result = get_container_path(&path, config, container); + if (result != success()) return result; - } - // Get the backing path + // Get the path of the backing. char* backing_path; - result = get_backing_path(&backing_path, backing); + result = get_backing_path(&backing_path, config, backing); if (result != success()) { free(path); return result; } - // Create the backed container + // Create the backed container. result = create_backed_disk(path, backing_path); + if (result != success()) { + free(backing_path); + free(path); + return result; + } + // Free the backing path. free(backing_path); + + // Free the container path. free(path); - return result; -} - -result_t remove_container(const char* container) { - // Check that the container exists - result_t result = check_container_exists(container); - if (result != success()) - return result; - - // Get the container path - char* path; - result = get_container_path(&path, container); - if (result != success()) - return result; - - // Delete the container - errno = 0; - remove(path); - - free(path); - - if (errno != 0) - return failure("Failed to remove container '%s' (%s).", container, strerror(errno)); return success(); } -result_t trim_container(const char* container) { - // Check that the container exists - result_t result = check_container_exists(container); +result_t remove_container(const config_t* config, const char* container) { + // Check that the container identifier is valid. + result_t result = check_container_identifier(container); if (result != success()) return result; - // Get the container path + // Check that the container exists. + result = check_container_exists(config, container); + if (result != success()) + return result; + + // Get the path of the container. char* path; - result = get_container_path(&path, container); + result = get_container_path(&path, config, container); if (result != success()) return result; - // Trim the container - result = trim_disk(path); + // Remove the container. + if (unlink(path) == -1) { + free(path); + return failure("Failed to remove container '%s': %s.", container, strerror(errno)); + } + // Free the container path. free(path); - return result; + + return success(); } -result_t reset_container(const char* container) { - // Check that the container exists - result_t result = check_container_exists(container); +result_t reset_container(const config_t* config, const char* container) { + // Check that the container identifier is valid. + result_t result = check_container_identifier(container); if (result != success()) return result; - // Get the container path + // Check that the container exists. + result = check_container_exists(config, container); + if (result != success()) + return result; + + // Get the path of the container. char* path; - result = get_container_path(&path, container); + result = get_container_path(&path, config, container); if (result != success()) return result; - // Reset the container + // Reset the container. result = reset_disk(path); - - free(path); - return result; -} - -bool container_filter(const char* file) { - // Check that a container with the same name exists - return check_container_exists(file) == success(); -} - -result_t list_containers(char*** _containers) { - // Initialize the output parameters - *_containers = NULL; - - // Get the container pool path - char* pool_path; - result_t result = get_container_pool_path(&pool_path); - if (result != success()) - return result; - - // List the files in the container pool - result = list_files(_containers, pool_path, container_filter); - - // Free the path - free(pool_path); - return result; -} - -result_t get_container_backing(char** _backing, const char* container) { - // Initialize the output parameters - *_backing = NULL; - - // Check that the container exists - result_t result = check_container_exists(container); - if (result != success()) - return result; - - // Get the container path - char* path; - result = get_container_path(&path, container); - if (result != success()) - return result; - - // Get the disk information - disk_info_t info; - result = get_disk_info(&info, path); if (result != success()) { free(path); return result; } - // Free the container path as it is no longer needed + // Free the container path. free(path); - // Check if the disk is backed - if (info.backing_path == NULL) { - free_disk_info(&info); - success(); - } - - // Get the backing identifier - errno = 0; - char* backing = strdup(basename(info.backing_path)); - - // Free the disk information as it is no longer needed - free_disk_info(&info); - - if (backing == NULL) - return failure("Failed to get backing identifier for container '%s' (%s).", container, strerror(errno)); - - *_backing = backing; return success(); } -result_t get_container_info(disk_info_t* _info, const char* container) { - // Initialize the output parameters - _info->size = 0; - _info->allocated = 0; - _info->backing_path = NULL; - - // Check that the container exists - result_t result = check_container_exists(container); +result_t trim_container(const config_t* config, const char* container) { + // Check that the container identifier is valid. + result_t result = check_container_identifier(container); if (result != success()) return result; - // Get the container path + // Check that the container exists. + result = check_container_exists(config, container); + if (result != success()) + return result; + + // Get the path of the container. char* path; - result = get_container_path(&path, container); + result = get_container_path(&path, config, container); if (result != success()) return result; - // Get the disk information - result = get_disk_info(_info, path); + // Trim the container. + result = trim_disk(path); + if (result != success()) { + free(path); + return result; + } + // Free the container path. free(path); - return result; + return success(); } -result_t get_oldest_container(char** _container) { - // Initialize the output parameters - *_container = NULL; - - // List the containers - char** containers; - result_t result = list_containers(&containers); +result_t list_containers(char*** _containers, const config_t* config) { + // Get the path of the container pool. + char* pool_path; + result_t result = get_container_pool_path(&pool_path, config); if (result != success()) return result; - // Find the oldest container + // Get the list of containers. + return list_files(_containers, pool_path, check_container_identifier); +} + +result_t get_oldest_container(char** _container, const config_t* config) { + // Get the list of containers. + char** containers; + result_t result = list_containers(&containers, config); + if (result != success()) + return result; + + // Find the oldest container. char* oldest_container = NULL; time_t oldest_time = 0; for (size_t i = 0; containers[i] != NULL; i++) { - // Get the container path + // Get the path of the container. char* path; - result = get_container_path(&path, containers[i]); + result = get_container_path(&path, config, containers[i]); if (result != success()) continue; - // Get information about the container file + // Get the last modification time of the container. + time_t time; + struct stat st; - errno = 0; - if (stat(path, &st) != 0) { - free(path); - continue; - } + if (stat(path, &st) == -1) + time = 0; + else + time = st.st_mtime; - // Check if the container is older than the current oldest - if (oldest_container == NULL || st.st_mtime < oldest_time) { + // Free the container path. + free(path); + + // Check whether the container is older than the oldest container. + if (oldest_container == NULL || time < oldest_time) { oldest_container = containers[i]; - oldest_time = st.st_mtime; + oldest_time = time; } } - // Duplicate the oldest container - if (oldest_container != NULL) { - errno = 0; - *_container = strdup(oldest_container); - if (*_container == NULL) { - for (size_t i = 0; containers[i] != NULL; i++) - free(containers[i]); - free(containers); - return failure("Failed to allocate memory for the oldest container (%s).", strerror(errno)); - } - } + // Output the oldest container. + *_container = strdup(oldest_container); - // Free the containers + // Free the list of containers. for (size_t i = 0; containers[i] != NULL; i++) free(containers[i]); free(containers); @@ -368,58 +331,54 @@ result_t get_oldest_container(char** _container) { return success(); } -result_t get_container_pool_space(uint64_t* _space) { - // Initialize the output parameters - *_space = 0; - - // Get the container pool path +result_t get_available_space(uint64_t* _size, const config_t* config) { + // Get the path of the container pool. char* pool_path; - result_t result = get_container_pool_path(&pool_path); + result_t result = get_container_pool_path(&pool_path, config); if (result != success()) return result; - // Get the space of the container pool + // Calculate the total available space in the container pool. struct statvfs st; - errno = 0; - if (statvfs(pool_path, &st) != 0) { + if (statvfs(pool_path, &st) == -1) { free(pool_path); - return failure("Failed to get space of container pool (%s).", strerror(errno)); + return failure("Failed to calculate the total available space in the container pool: %s.", strerror(errno)); } - *_space = st.f_bsize * st.f_bavail; - + // Free the container pool path. free(pool_path); + + // Output the total available space. + *_size = st.f_bsize * st.f_bavail; + return success(); } -result_t reserve_container_pool_space(uint64_t size) { - // As long as there is not enough space in the container pool, remove the oldest container - while (true) { - // Get the available space in the container pool - uint64_t space; - result_t result = get_container_pool_space(&space); +result_t reserve_space(const config_t* config, uint64_t size) { + // While the total available space is less than the requested size, remove the oldest container. + for (;;) { + // Get the total available space. + uint64_t available_space; + result_t result = get_available_space(&available_space, config); if (result != success()) return result; - // Check if there is enough space - if (space >= size) + // Check whether the total available space is enough. + if (available_space >= size) return success(); - // Get the oldest container + // Get the oldest container. char* container; - result = get_oldest_container(&container); + result = get_oldest_container(&container, config); if (result != success()) return result; - if (container == NULL) - return success(); // No more containers to remove - - // Remove the oldest container - result = remove_container(container); - + // Remove the oldest container. + result = remove_container(config, container); free(container); - if (result != success()) return result; } + + return success(); } diff --git a/src/container.h b/src/container.h old mode 100755 new mode 100644 index f091a0e..8998218 --- a/src/container.h +++ b/src/container.h @@ -1,91 +1,87 @@ #pragma once #include "utils.h" -#include "disk.h" +#include "config.h" -#define CONTAINER_IDENTIFIER_MAX_LENGTH 64 +/// @brief The maximum length of a container identifier. +#define MAX_CONTAINER_IDENTIFIER_LENGTH 64 -/// @brief Checks whether the given string is a valid container identifier. If the string is not a valid container identifier, the call will return a failure result with an error message. -/// @param container The string to check. +/// @brief Checks whether the specified container identifier is valid. This call returns a failure if the identifier is invalid. +/// @param container The container identifier to check. /// @return The result of the operation. result_t check_container_identifier(const char* container); -/// @brief Checks whether the given container exists. If the container does not exist, the call will return a failure result with an error message. -/// @param container The container to check. +/// @brief Returns the path of the container pool. +/// @param _path The pointer to where the path should be stored. The caller is responsible for freeing the path. +/// @param config The program configuration to use. /// @return The result of the operation. -result_t check_container_exists(const char* container); +result_t get_container_pool_path(char** _path, const config_t* config); -/// @brief Gets the container pool path. -/// @param _path The string pointer to store the resulting path in. The caller is responsible for freeing the string. +/// @brief Returns the path of the specified container. +/// @param _path The pointer to where the path should be stored. The caller is responsible for freeing the path. +/// @param config The program configuration to use. +/// @param container The identifier of the container to get the path of. /// @return The result of the operation. -result_t get_container_pool_path(char** _path); +result_t get_container_path(char** _path, const config_t* config, const char* container); -/// @brief Gets the container path. -/// @param _path The string pointer to store the resulting path in. The caller is responsible for freeing the string. -/// @param container The container to get the path of. -/// @return The result of the operation. -result_t get_container_path(char** _path, const char* container); +/// @brief Checks whether the specified container exists. This call returns a failure if the container does not exist. +/// @param config The program configuration to use. +/// @param container The identifier of the container to check. +/// @return Whether the container exists. +result_t check_container_exists(const config_t* config, const char* container); -/// @brief Adds a root (empty) container to the pool, with the given size. If the container already exists, the call will return a failure result with an error message. -/// @param container The container to add. +/// @brief Adds a new root container to the container pool, with the specified identifier and size. +/// @param config The program configuration to use. +/// @param container The identifier of the container to add. /// @param size The size of the container to add, in bytes. /// @return The result of the operation. -result_t add_root_container(const char* container, uint64_t size); +result_t add_root_container(const config_t* config, const char* container, uint64_t size); -/// @brief Adds a backed container to the pool, with the given backing. If the container already exists, or the backing does not, the call will return a failure result with an error message. -/// @param container The container to add. -/// @param backing The backing to use for the container. +/// @brief Adds a new backed container to the container pool, with the specified identifier and backing. +/// @param config The program configuration to use. +/// @param container The identifier of the container to add. +/// @param backing The identifier of the backing to use. /// @return The result of the operation. -result_t add_backed_container(const char* container, const char* backing); +result_t add_backed_container(const config_t* config, const char* container, const char* backing); -/// @brief Removes the given container from the pool. If the container does not exist, the call will return a failure result with an error message. -/// @param container The container to remove. +/// @brief Removes the specified container from the container pool. +/// @param config The program configuration to use. +/// @param container The identifier of the container to remove. /// @return The result of the operation. -result_t remove_container(const char* container); +result_t remove_container(const config_t* config, const char* container); -/// @brief Trims (removes unused space) the given container. -/// @param container The container to trim. +/// @brief Resets the specified container to its original state. +/// @param config The program configuration to use. +/// @param container The identifier of the container to reset. /// @return The result of the operation. -result_t trim_container(const char* container); +result_t reset_container(const config_t* config, const char* container); -/// @brief Resets the given container to its initial state. -/// @param container The container to reset. +/// @brief Trims the specified container. +/// @param config The program configuration to use. +/// @param container The identifier of the container to trim. /// @return The result of the operation. -result_t reset_container(const char* container); +result_t trim_container(const config_t* config, const char* container); -/// @brief Checks that the given file is a container. This function is used as a filter for listing containers. -/// @param file The file to check. -/// @return Whether the file is a container. -bool container_filter(const char* file); - -/// @brief Lists the containers in the pool. -/// @param _containers The string array pointer to store the resulting array in. The caller is responsible for freeing the strings and the array. +/// @brief Lists the containers in the container pool, and returns the result in a null-terminated array of strings. +/// @param _containers The pointer to where the containers array should be stored. The caller is responsible for freeing the containers array as well as the strings in the array. +/// @param config The program configuration to use. /// @return The result of the operation. -result_t list_containers(char*** _containers); +result_t list_containers(char*** _containers, const config_t* config); -/// @brief Gets the backing of the given container. If the container does not exist, the call will return a failure result with an error message. -/// @param _backing The string pointer to store the resulting backing in. -/// @param container The container to get the backing of. +/// @brief Returns the oldest container in the container pool. +/// @param _container The pointer to where the container should be stored. The caller is responsible for freeing the container. +/// @param config The program configuration to use. /// @return The result of the operation. -result_t get_container_backing(char** _backing, const char* container); +result_t get_oldest_container(char** _container, const config_t* config); -/// @brief Gets information about the given container. If the container does not exist, the call will return a failure result with an error message. -/// @param _info The string pointer to store the resulting disk information in. The caller is responsible for freeing the string. -/// @param container The container to get information about. +/// @brief Calculates the total available space in the container pool. +/// @param _size The pointer to where the size should be stored. +/// @param config The program configuration to use. /// @return The result of the operation. -result_t get_container_info(disk_info_t* _info, const char* container); +result_t get_available_space(uint64_t* _size, const config_t* config); -/// @brief Gets the oldest container in the pool. -/// @param _container The string pointer to store the resulting container in. The caller is responsible for freeing the string. -/// @return The result of the operation. -result_t get_oldest_container(char** _container); - -/// @brief Gets the available space in the pool. -/// @param _space The string pointer to store the resulting space in, in bytes. The caller is responsible for freeing the string. -/// @return The result of the operation. -result_t get_container_pool_space(uint64_t* _space); - -/// @brief Reserves the given space in the pool. If the pool does not have enough space, the oldest containers will be removed to make space. +/// @brief Reserves the specified space in the container pool, by removing the oldest containers. +/// @param config The program configuration to use. /// @param size The size to reserve, in bytes. /// @return The result of the operation. -result_t reserve_container_pool_space(uint64_t size); \ No newline at end of file +result_t reserve_space(const config_t* config, uint64_t size); diff --git a/src/disk.c b/src/disk.c old mode 100755 new mode 100644 index b6dce3b..d587d60 --- a/src/disk.c +++ b/src/disk.c @@ -1,302 +1,362 @@ #include "disk.h" -#include #include -#include #include -#include +#include +#include +#include +#include -result_t create_root_disk(const char* path, uint64_t size) { - // Convert the size to a string - char* size_str; - result_t result = format(&size_str, "%llu", size); - if (result != success()) - return result; - - // Create the disk +result_t read_disk_info(disk_info_t** _info, const char* disk) { + // Execute qemu-img info to read the disk information. int exit_code; - // char* stdoutbuf; - char* stderrbuf; + char* stdout_buffer; + char* stderr_buffer; - result = execute(&exit_code, NULL, &stderrbuf, NULL, "qemu-img", "create", "-f", "qcow2", path, size_str, NULL); + result_t result = execute_file(&exit_code, &stdout_buffer, NULL, &stderr_buffer, NULL, NULL, "/usr/bin/qemu-img", "info", "--output", "json", disk, NULL); - // Free the size string as it is no longer needed - free(size_str); - - // Check for errors during the execution + // If the execution failed, return the error. if (result != success()) return result; - // Check the exit code + // If qemu-img info failed, return the error. if (exit_code != 0) { - // Remove all newlines from the stderr buffer - for (char* c = stderrbuf; *c != '\0'; c++) - if (*c == '\n') - *c = ' '; + // Remove all newlines from the error. + if (stderr_buffer != NULL) + for (size_t i = 0; stderr_buffer[i] != '\0'; i++) + if (stderr_buffer[i] == '\n') + stderr_buffer[i] = ' '; + + char* error = trim(stderr_buffer); + + // Free the buffers as they are no longer needed. + free(stdout_buffer); + free(stderr_buffer); + + result = failure("Failed to read the disk information of '%s' (%s).", disk, error); + + free(error); - result = failure("Failed to create root disk (%s).", stderrbuf); - free(stderrbuf); return result; } - // Free the stderr buffer - free(stderrbuf); + // Free the standard error buffer as it is no longer needed. + free(stderr_buffer); - return success(); -} + // Parse the JSON output. + json_object* root = json_tokener_parse(stdout_buffer); -result_t create_backed_disk(const char* path, const char* backing_path) { - // Create the disk - int exit_code; - // char* stdoutbuf; - char* stderrbuf; + // Free the standard output buffer as it is no longer needed. + free(stdout_buffer); - result_t result = execute(&exit_code, NULL, &stderrbuf, NULL, "qemu-img", "create", "-f", "qcow2", "-F", "qcow2", "-b", backing_path, path, NULL); + // If the JSON parsing failed, return the error. + if (root == NULL) + return failure("Failed to parse the JSON output of qemu-img info."); - // Check for errors during the execution - if (result != success()) - return result; - - // Check the exit code - if (exit_code != 0) { - // Remove all newlines from the stderr buffer - for (char* c = stderrbuf; *c != '\0'; c++) - if (*c == '\n') - *c = ' '; - - result = failure("Failed to create backed disk (%s).", stderrbuf); - free(stderrbuf); - return result; + // Get the virtual size of the disk. + json_object* virtual_size_object = json_object_object_get(root, "virtual-size"); + if (virtual_size_object == NULL) { + json_object_put(root); + return failure("Failed to read the virtual size of the disk."); } - // Free the stderr buffer - free(stderrbuf); - - return success(); -} - -result_t trim_disk(const char* path) { - // Get the disk info - disk_info_t info; - result_t result = get_disk_info(&info, path); - if (result != success()) - return result; - - // Generate a new path for the temporary disk - char* temp_path; - result = format(&temp_path, "%s.tmp", path); - if (result != success()) { - free_disk_info(&info); - return result; - } - - // Use convert to trim the disk - int exit_code; - // char* stdoutbuf; - char* stderrbuf; - - if (info.backing_path == NULL) - result = execute(&exit_code, NULL, &stderrbuf, NULL, "qemu-img", "convert", "-f", "qcow2", "-O", "qcow2", path, temp_path, NULL); - else - result = execute(&exit_code, NULL, &stderrbuf, NULL, "qemu-img", "convert", "-f", "qcow2", "-F", "qcow2", "-O", "qcow2", "-B", info.backing_path, path, temp_path, NULL); - - // Free the disk info - free_disk_info(&info); - - // Check for errors during the execution - if (result != success()) { - free(temp_path); - return result; - } - - // Check the exit code - if (exit_code != 0) { - // Remove all newlines from the stderr buffer - for (char* c = stderrbuf; *c != '\0'; c++) - if (*c == '\n') - *c = ' '; - - result = failure("Failed to trim disk (%s).", stderrbuf); - free(temp_path); - free(stderrbuf); - return result; - } - - // Free the stderr buffer as it is no longer needed - free(stderrbuf); - - // Replace the original disk with the temporary disk + // Parse the virtual size of the disk. errno = 0; - rename(temp_path, path); - - // Free the temporary path - free(temp_path); - - // Check for errors during the renaming + uint64_t virtual_size = json_object_get_uint64(virtual_size_object); if (errno != 0) { - // Try to remove the temporary disk if the renaming failed - remove(temp_path); - return failure("Failed to replace the original disk with the temporary disk (%s).", strerror(errno)); + json_object_put(root); + return failure("Failed to parse the virtual size of the disk (%s).", strerror(errno)); } + // Get the actual size of the disk. + json_object* actual_size_object = json_object_object_get(root, "actual-size"); + if (actual_size_object == NULL) { + json_object_put(root); + return failure("Failed to read the actual size of the disk."); + } + + // Parse the actual size of the disk. + errno = 0; + uint64_t actual_size = json_object_get_uint64(actual_size_object); + if (errno != 0) { + json_object_put(root); + return failure("Failed to parse the actual size of the disk (%s).", strerror(errno)); + } + + // Get the backing file of the disk. + char* backing_path = NULL; + char* backing_name = NULL; + + json_object* backing_file_object = json_object_object_get(root, "backing-filename"); + if (backing_file_object != NULL) { + // Get the string value of the backing file. + const char* string = json_object_get_string(backing_file_object); + if (string == NULL) { + json_object_put(root); + return failure("Failed to read the backing file of the disk (Invalid JSON)."); + } + + // Copy the backing file path. + backing_path = strdup(string); + if (backing_path == NULL) { + json_object_put(root); + return failure("Failed to read the backing file of the disk (Failed to duplicate the backing file path)."); + } + + // Extract the file name of the backing path. + backing_name = file_name(backing_path); + if (backing_name == NULL) { + json_object_put(root); + free(backing_path); + return failure("Failed to read the backing file of the disk (Failed to extract the backing file name)."); + } + } + + // Free the JSON root object as it is no longer needed. + json_object_put(root); + + // Create the disk information. + disk_info_t* info = malloc(sizeof(disk_info_t)); + if (info == NULL) { + free(backing_path); + free(backing_name); + return failure("Failed to allocate memory for the disk information."); + } + + info->virtual_size = virtual_size; + info->actual_size = actual_size; + + info->backing_path = backing_path; + info->backing_name = backing_name; + + // Output the disk information. + *_info = info; + return success(); } -result_t reset_disk(const char* path) { - // Get the disk info - disk_info_t info; - result_t result = get_disk_info(&info, path); +void free_disk_info(disk_info_t* info) { + free(info->backing_path); + free(info->backing_name); + + free(info); +} + +result_t create_root_disk(const char* disk, uint64_t size) { + // Convert the size to a string. + char* size_string; + if (asprintf(&size_string, "%" PRIu64, size) < 0) + return failure("Failed to convert the size to a string (%s).", strerror(errno)); + + // Execute qemu-img create to create the root disk. + int exit_code; + // char* stdout_buffer; + char* stderr_buffer; + + result_t result = execute_file(&exit_code, NULL, NULL, &stderr_buffer, NULL, NULL, "/usr/bin/qemu-img", "create", "-f", "qcow2", disk, size_string, NULL); + + // Free the size string as it is no longer needed. + free(size_string); + + // If the execution failed, return the error. if (result != success()) return result; - if (info.backing_path == NULL) - // If the disk is not backed, simply create a new disk with the same size on top of it - result = create_root_disk(path, info.size); - else - // If the disk is backed, create a new disk with the same backing path - result = create_backed_disk(path, info.backing_path); + // If qemu-img create failed, return the error. + if (exit_code != 0) { + // Remove all newlines from the error. + if (stderr_buffer != NULL) + for (size_t i = 0; stderr_buffer[i] != '\0'; i++) + if (stderr_buffer[i] == '\n') + stderr_buffer[i] = ' '; + + char* error = trim(stderr_buffer); + + // Free the standard error buffer as it is no longer needed. + free(stderr_buffer); + + result = failure("Failed to create the root disk '%s' (%s).", disk, error); + + free(error); + + return result; + } + + // Free the standard error buffer as it is no longer needed. + free(stderr_buffer); + + return success(); +} + +result_t create_backed_disk(const char* disk, const char* backing) { + // Execute qemu-img create to create the backed disk. + int exit_code; + // char* stdout_buffer; + char* stderr_buffer; + + result_t result = execute_file(&exit_code, NULL, NULL, &stderr_buffer, NULL, NULL, "/usr/bin/qemu-img", "create", "-f", "qcow2", "-F", "qcow2", "-b", backing, disk, NULL); + + // If the execution failed, return the error. + if (result != success()) + return result; + + // If qemu-img create failed, return the error. + if (exit_code != 0) { + // Remove all newlines from the error. + if (stderr_buffer != NULL) + for (size_t i = 0; stderr_buffer[i] != '\0'; i++) + if (stderr_buffer[i] == '\n') + stderr_buffer[i] = ' '; + + char* error = trim(stderr_buffer); + + // Free the standard error buffer as it is no longer needed. + free(stderr_buffer); + + result = failure("Failed to create the backed disk '%s' (%s).", disk, error); + + free(error); + + return result; + } + + // Free the standard error buffer as it is no longer needed. + free(stderr_buffer); + + return success(); +} + +result_t reback_disk(const char* disk, const char* backing) { + // Execute qemu-img rebase to reback the disk. + int exit_code; + // char* stdout_buffer; + char* stderr_buffer; + + result_t result = execute_file(&exit_code, NULL, NULL, &stderr_buffer, NULL, NULL, "/usr/bin/qemu-img", "rebase", "-u", "-F", "qcow2", "-b", backing, disk, NULL); + + // If the execution failed, return the error. + if (result != success()) + return result; + + // If qemu-img rebase failed, return the error. + if (exit_code != 0) { + // Remove all newlines from the error. + if (stderr_buffer != NULL) + for (size_t i = 0; stderr_buffer[i] != '\0'; i++) + if (stderr_buffer[i] == '\n') + stderr_buffer[i] = ' '; + + char* error = trim(stderr_buffer); + + // Free the standard error buffer as it is no longer needed. + free(stderr_buffer); + + result = failure("Failed to reback the disk '%s' to '%s' (%s).", disk, backing, error); + + free(error); + + return result; + } + + // Free the standard error buffer as it is no longer needed. + free(stderr_buffer); + + return success(); +} + +result_t reset_disk(const char* disk) { + // Get information about the disk. + disk_info_t* info; + result_t result = read_disk_info(&info, disk); + if (result != success()) + return result; + + if (info->backing_path != NULL) + // If the disk is a backed disk, create a new disk with the same backing disk. + result = create_backed_disk(disk, info->backing_path); + else + // If the disk is a root disk, create a new disk with the same size. + result = create_root_disk(disk, info->virtual_size); + + // Free the disk information as it is no longer needed. + free_disk_info(info); - // Free the disk info - free_disk_info(&info); return result; } -result_t reback_disk(const char* path, const char* backing_path) { - // Create the disk - int exit_code; - // char* stdoutbuf; - char* stderrbuf; +result_t trim_disk(const char* disk) { + // Get a temporary file name to use as the trimmed disk output. + char* trimmed_disk; + if (asprintf(&trimmed_disk, "%s.trimmed", disk) < 0) + return failure("Failed to get a temporary file name for the trimmed disk (%s).", strerror(errno)); - result_t result = execute(&exit_code, NULL, &stderrbuf, NULL, "qemu-img", "rebase", "-u", "-f", "qcow2", "-F", "qcow2", "-b", backing_path, path, NULL); - - // Check for errors during the execution - if (result != success()) - return result; - - // Check the exit code - if (exit_code != 0) { - // Remove all newlines from the stderr buffer - for (char* c = stderrbuf; *c != '\0'; c++) - if (*c == '\n') - *c = ' '; - - result = failure("Failed to reback disk (%s).", stderrbuf); - free(stderrbuf); + // Get information about the disk. + disk_info_t* info; + result_t result = read_disk_info(&info, disk); + if (result != success()) { + free(trimmed_disk); return result; } - // Free the stderr buffer - free(stderrbuf); + // Execute qemu-img convert to trim the disk. + int exit_code; + // char* stdout_buffer; + char* stderr_buffer; + + if (info->backing_path != NULL) + result = execute_file(&exit_code, NULL, NULL, &stderr_buffer, NULL, NULL, "/usr/bin/qemu-img", "convert", "-c", "-f", "qcow2", "-F", "qcow2", "-O", "qcow2", "-B", info->backing_path, disk, trimmed_disk, NULL); + else + result = execute_file(&exit_code, NULL, NULL, &stderr_buffer, NULL, NULL, "/usr/bin/qemu-img", "convert", "-c", "-f", "qcow2", "-O", "qcow2", disk, trimmed_disk, NULL); + + // Free the disk information as it is no longer needed. + free_disk_info(info); + + // If the execution failed, return the error. + if (result != success()) { + free(trimmed_disk); + return result; + } + + // If qemu-img convert failed, return the error. + if (exit_code != 0) { + // Remove all newlines from the error. + if (stderr_buffer != NULL) + for (size_t i = 0; stderr_buffer[i] != '\0'; i++) + if (stderr_buffer[i] == '\n') + stderr_buffer[i] = ' '; + + char* error = trim(stderr_buffer); + + // Free the standard error buffer as it is no longer needed. + free(stderr_buffer); + + result = failure("Failed to trim the disk '%s' (%s).", disk, error); + + free(error); + + // Try to remove the temporary file as it may be incomplete. + remove(trimmed_disk); + free(trimmed_disk); + + return result; + } + + // Free the standard error buffer as it is no longer needed. + free(stderr_buffer); + + // Move the trimmed disk to the original disk. + if (rename(trimmed_disk, disk) < 0) { + result = failure("Failed to move the trimmed disk to the original disk (%s).", strerror(errno)); + + // Try to remove the temporary file as it not needed anymore. + remove(trimmed_disk); + free(trimmed_disk); + + return result; + } + + // Free the temporary file name as it is no longer needed. + free(trimmed_disk); return success(); } - -result_t get_disk_info(disk_info_t* _info, const char* path) { - // Initialize the output parameters - _info->size = 0; - _info->allocated = 0; - _info->backing_path = NULL; - - // Get the disk info - int exit_code; - char* stdoutbuf; - char* stderrbuf; - - result_t result = execute(&exit_code, &stdoutbuf, &stderrbuf, NULL, "qemu-img", "info", "--output", "json", path, NULL); - - // Check for errors during the execution - if (result != success()) - return result; - - // Check the exit code - if (exit_code != 0) { - // Remove all newlines from the stderr buffer - for (char* c = stderrbuf; *c != '\0'; c++) - if (*c == '\n') - *c = ' '; - - result = failure("Failed to get disk info (%s).", stderrbuf); - free(stdoutbuf); - free(stderrbuf); - return result; - } - - // Free the stderr buffer as it is no longer needed - free(stderrbuf); - - // Parse the JSON output - json_object* root = json_tokener_parse(stdoutbuf); - - // Free the stdout buffer as it is no longer needed - free(stdoutbuf); - - // Check for errors during the parsing - if (root == NULL) - return failure("Failed to parse the JSON output."); - - // Get the size - json_object* size_obj; - if (!json_object_object_get_ex(root, "virtual-size", &size_obj)) { - json_object_put(root); - return failure("Missing 'virtual-size' field in the JSON output. Cannot get the disk size."); - } - - errno = 0; - uint64_t size = json_object_get_int64(size_obj); - if (errno != 0) { - json_object_put(root); - return failure("Failed to parse the 'virtual-size' field in the JSON output (%s).", strerror(errno)); - } - - // Get the allocated size - json_object* allocated_obj; - if (!json_object_object_get_ex(root, "actual-size", &allocated_obj)) { - json_object_put(root); - return failure("Missing 'actual-size' field in the JSON output. Cannot get the allocated size."); - } - - errno = 0; - uint64_t allocated = json_object_get_int64(allocated_obj); - if (errno != 0) { - json_object_put(root); - return failure("Failed to parse the 'actual-size' field in the JSON output (%s).", strerror(errno)); - } - - // Get the backing file - json_object* backing_obj; - char* backing_path = NULL; - if (json_object_object_get_ex(root, "backing-filename", &backing_obj)) { - const char* backing_str = json_object_get_string(backing_obj); - if (backing_str == NULL) { - json_object_put(root); - return failure("Failed to parse the 'backing-filename' field in the JSON output."); - } - - // Duplicate the backing path string - errno = 0; - backing_path = strdup(backing_str); - if (backing_path == NULL) { - json_object_put(root); - return failure("Failed to allocate memory for the backing path (%s).", strerror(errno)); - } - } - - // Free the root object - json_object_put(root); - - // Write the disk info to the output - _info->size = size; - _info->allocated = allocated; - _info->backing_path = backing_path; - return success(); -} - -void free_disk_info(disk_info_t* _info) { - // Free the backing path string - free(_info->backing_path); - - // Clear the fields - _info->size = 0; - _info->allocated = 0; - _info->backing_path = NULL; -} diff --git a/src/disk.h b/src/disk.h old mode 100755 new mode 100644 index a188823..827004e --- a/src/disk.h +++ b/src/disk.h @@ -2,50 +2,52 @@ #include "utils.h" -/// @brief The disk information structure. +/// @brief Structure used to store information about a qcow2 disk. typedef struct { /// @brief The virtual size of the disk, in bytes. - uint64_t size; + uint64_t virtual_size; /// @brief The actual size of the disk, in bytes. - uint64_t allocated; - /// @brief The path to the backing file of the disk, or NULL if the disk is not backed. + uint64_t actual_size; + /// @brief The path of the backing disk of this disk. If this disk is not a backing disk, this field is NULL. char* backing_path; + /// @brief The name of the backing disk of this disk. If this disk is not a backing disk, this field is NULL. + char* backing_name; } disk_info_t; -/// @brief Creates a root disk (empty disk) at the given path with the given size. -/// @param path The path to create the disk at. Any existing file at this path will be overwritten. +/// @brief Reads the information of the specified qcow2 disk. +/// @param _info The pointer to where the disk information should be stored. The caller is responsible for freeing the disk information. +/// @param disk The disk to read the information of. +/// @return The result of the operation. +result_t read_disk_info(disk_info_t** _info, const char* disk); + +/// @brief Frees the specified disk information. +/// @param info The disk information to free. +void free_disk_info(disk_info_t* info); + +/// @brief Creates a new root (empty) qcow2 disk with the specified size. +/// @param disk The path of the disk to create. Any existing file at this path will be overwritten. /// @param size The size of the disk to create, in bytes. /// @return The result of the operation. -result_t create_root_disk(const char* path, uint64_t size); +result_t create_root_disk(const char* disk, uint64_t size); -/// @brief Creates a backed disk at the given path with the given backing path. -/// @param path The path to create the disk at. Any existing file at this path will be overwritten. -/// @param backing_path The path to the backing file to use for the disk. +/// @brief Creates a new backed qcow2 disk with the specified backing disk. +/// @param disk The path of the disk to create. Any existing file at this path will be overwritten. +/// @param backing The backing disk to use. /// @return The result of the operation. -result_t create_backed_disk(const char* path, const char* backing_path); +result_t create_backed_disk(const char* disk, const char* backing); -/// @brief Trims (removes unused space) the given disk. -/// @param path The path to the disk to trim. +/// @brief Rebacks the specified qcow2 disk to the specified backing disk. +/// @param disk The disk to reback. +/// @param backing The backing disk to use. /// @return The result of the operation. -result_t trim_disk(const char* path); +result_t reback_disk(const char* disk, const char* backing); -/// @brief Resets the given disk to its initial state. -/// @param path The path to the disk to reset. +/// @brief Resets the specified qcow2 disk to its original state. +/// @param disk The disk to reset. /// @return The result of the operation. -result_t reset_disk(const char* path); +result_t reset_disk(const char* disk); -/// @brief Changes the backing of the given disk to the given backing path. This call does not change the contents of the disk, but only the backing file path. -/// @param path The path to the disk to reback. -/// @param backing_path The path to the new backing file to use for the disk. +/// @brief Trims (removes unused sparse space) the specified qcow2 disk. +/// @param disk The disk to trim. /// @return The result of the operation. -result_t reback_disk(const char* path, const char* backing_path); - -/// @brief Gets the disk information of the disk at the given path. -/// @param _info The disk information pointer to store the resulting disk information in. The caller is responsible for freeing the disk information. -/// @param path The path to the disk to get the information of. -/// @return The result of the operation. -result_t get_disk_info(disk_info_t* _info, const char* path); - -/// @brief Frees the given disk information. -/// @param _info The disk information to free. -void free_disk_info(disk_info_t* _info); +result_t trim_disk(const char* disk); diff --git a/src/domain.c b/src/domain.c deleted file mode 100644 index 1fc2dbf..0000000 --- a/src/domain.c +++ /dev/null @@ -1,38 +0,0 @@ -#include "domain.h" - -#include "sandbox.h" - -#include - -result_t container_to_domain(char** _domain, const char* container) { - // Initialize the output parameter - *_domain = NULL; - - // Convert the container identifier to a domain identifier - result_t result = format(_domain, "%s%s", DOMAIN_IDENTIFIER_PREFIX, container); - if (result != success()) - return result; - - return success(); -} - -result_t domain_to_container(char** _container, const char* domain) { - // Initialize the output parameter - *_container = NULL; - - // Check that the domain identifier is not too short - size_t prefix_length = strlen(DOMAIN_IDENTIFIER_PREFIX); - if (strlen(domain) <= prefix_length) - return failure("Invalid domain identifier. The domain identifier is too short. A domain identifier must be prefixed with '%s'.", DOMAIN_IDENTIFIER_PREFIX); - - // Check that the domain identifier has the correct prefix - if (strncmp(domain, DOMAIN_IDENTIFIER_PREFIX, prefix_length) != 0) - return failure("Invalid domain identifier. The domain identifier must be prefixed with '%s'.", DOMAIN_IDENTIFIER_PREFIX); - - // Convert the domain identifier to a container identifier - result_t result = format(_container, "%s", domain + prefix_length); - if (result != success()) - return result; - - return success(); -} diff --git a/src/domain.h b/src/domain.h deleted file mode 100644 index eb6003b..0000000 --- a/src/domain.h +++ /dev/null @@ -1,15 +0,0 @@ -#pragma once - -#include "utils.h" - -/// @brief Converts a container identifier to a domain identifier. -/// @param _domain The string pointer to store the resulting domain identifier in. The caller is responsible for freeing the string. -/// @param container The container identifier to convert. -/// @return The result of the operation. -result_t container_to_domain(char** _domain, const char* container); - -/// @brief Converts a domain identifier to a container identifier. -/// @param _container The string pointer to store the resulting container identifier in. The caller is responsible for freeing the string. -/// @param domain The domain identifier to convert. -/// @return The result of the operation. -result_t domain_to_container(char** _container, const char* domain); diff --git a/src/pci.c b/src/pci.c deleted file mode 100644 index f6c77d6..0000000 --- a/src/pci.c +++ /dev/null @@ -1,239 +0,0 @@ -#include "pci.h" - -#include -#include -#include -#include -#include -#include -#include - -result_t check_pci_address(const char* pci_address) { - // Check the length of the string (should be 12 characters, eg. 0000:00:00.0) - if (strlen(pci_address) != 12) - return failure("Invalid PCI address '%s'. Valid PCI addresses are in the format '0000:00:00.0'.", pci_address); - - // Check the format of the string - for (int i = 0; i < 12; i++) - if (i == 4 || i == 7) { - if (pci_address[i] != ':') - return failure("Invalid PCI address '%s'. Missing colon at position %d. Valid PCI addresses are in the format '0000:00:00.0'.", pci_address, i); - } else if (i == 10) { - if (pci_address[i] != '.') - return failure("Invalid PCI address '%s'. Missing dot at position %d. Valid PCI addresses are in the format '0000:00:00.0'.", pci_address, i); - } else { - if (!isxdigit(pci_address[i])) - return failure("Invalid PCI address '%s'. Non-hexadecimal character '%c' at position %d. Valid PCI addresses are in the format '0000:00:00.0'.", pci_address, pci_address[i], i); - } - - return success(); -} - -result_t check_pci_exists(const char* pci_address) { - // Check the PCI address format - result_t result = check_pci_address(pci_address); - if (result != success()) - return result; - - // Check if the PCI address exists by checking if the sysfs directory exists - char* path; - result = format(&path, "/sys/bus/pci/devices/%s", pci_address); - if (result != success()) - return result; - - // Check that the directory exists - struct stat st; - if (stat(path, &st) != 0 || !S_ISDIR(st.st_mode)) { - free(path); - return failure("PCI address '%s' does not exist.", pci_address); - } - - // Free the path - free(path); - - return success(); -} - -result_t get_iommu_group(int* _group, const char* pci_address) { - // Initialize the output parameter - *_group = -1; - - // Check that the PCI address exists - result_t result = check_pci_exists(pci_address); - if (result != success()) - return result; - - // Get the IOMMU group if the PCI device has one - char* path; - result = format(&path, "/sys/bus/pci/devices/%s/iommu_group", pci_address); - if (result != success()) - return result; - - // Check that the file exists and is a symlink - struct stat st; - if (lstat(path, &st) != 0 || !S_ISLNK(st.st_mode)) { - free(path); - return failure("PCI address '%s' does not have an IOMMU group. Please ensure that the IOMMU is enabled in the kernel.", pci_address); - } - - // Read the IOMMU group by getting the path of the symlink, and getting the basename of the path - char iommu_group_path[256]; // 256 should be enough for the path - ssize_t len = readlink(path, iommu_group_path, sizeof(iommu_group_path) - 1); - - free(path); - - // Check for errors during the readlink call - if (len == -1) - return failure("Failed to read IOMMU group of PCI address '%s'.", pci_address); - - // Null-terminate the path - iommu_group_path[len] = '\0'; - - // Get the basename of the path, and try to parse it as an integer - *_group = atoi(basename(iommu_group_path)); - - return success(); -} - -result_t get_iommu_groups(int** _groups, int* _count, const char** pci_addresses) { - // Initialize the output parameters - *_groups = NULL; - *_count = 0; - - // Count the number of PCI devices - int count = 0; - while (pci_addresses[count] != NULL) - count++; - - // Allocate memory for the groups - int* groups = malloc(count * sizeof(int)); - if (groups == NULL) - return failure("Failed to allocate memory for IOMMU groups."); - - // Get the IOMMU groups of the PCI devices - for (int i = 0; i < count; i++) { - // Get the IOMMU group of the PCI device - result_t result = get_iommu_group(&groups[i], pci_addresses[i]); - if (result != success()) { - free(groups); - return result; - } - } - - // Remove duplicates from the groups - for (int i = 0; i < count; i++) { - for (int j = i + 1; j < count; j++) { - if (groups[i] == groups[j]) { - // Shift the elements to the left and decrement the count - for (int k = j; k < count - 1; k++) - groups[k] = groups[k + 1]; - - count--; - j--; - } - } - } - - *_groups = groups; - *_count = count; - - return success(); -} - -bool pci_filter(const char* file) { - // Check that the PCI device exists - return check_pci_exists(file) == success(); -} - -result_t get_iommu_group_devices(char*** _devices, int group) { - // Initialize the output parameters - *_devices = NULL; - - // Get the path of the IOMMU group - char* path; - result_t result = format(&path, "/sys/kernel/iommu_groups/%d/devices", group); - if (result != success()) - return result; - - // List the devices in the IOMMU group - result = list_files(_devices, path, pci_filter); - - // Free the path - free(path); - return result; -} - -result_t get_iommu_groups_devices(char*** _devices, const int* groups, int groups_count) { - // Initialize the output parameters - *_devices = NULL; - - char*** devices = malloc(groups_count * sizeof(char**)); - if (devices == NULL) - return failure("Failed to allocate memory for IOMMU group devices."); - - // Get the devices of each IOMMU group - for (int i = 0; i < groups_count; i++) { - result_t result = get_iommu_group_devices(&devices[i], groups[i]); - if (result != success()) { - // Free every individual device - for (int j = 0; j < i; j++) { - char** group_devices = devices[j]; - for (int k = 0; group_devices[k] != NULL; k++) - free(group_devices[k]); - free(devices[j]); - } - - // Free the devices array - free(devices); - return result; - } - } - - // Count the number of devices - int count = 0; - for (int i = 0; i < groups_count; i++) { - char** group_devices = devices[i]; - for (int j = 0; group_devices[j] != NULL; j++) - count++; - } - - // Allocate memory for the output - char** output_devices = malloc((count + 1) * sizeof(char*)); - if (output_devices == NULL) { - // Free every individual device - for (int i = 0; i < groups_count; i++) { - char** group_devices = devices[i]; - for (int j = 0; group_devices[j] != NULL; j++) - free(group_devices[j]); - free(devices[i]); - } - - // Free the devices array - free(devices); - return failure("Failed to allocate memory for IOMMU group devices."); - } - - // Move the devices to the output - int index = 0; - for (int i = 0; i < groups_count; i++) { - char** group_devices = devices[i]; - for (int j = 0; group_devices[j] != NULL; j++) { - output_devices[index] = group_devices[j]; - group_devices[j] = NULL; - index++; - } - - // Free the group devices - free(group_devices); - } - - // Null-terminate the output - output_devices[count] = NULL; - - // Free the devices array - free(devices); - - *_devices = output_devices; - - return success(); -} diff --git a/src/pci.h b/src/pci.h deleted file mode 100644 index 40db76f..0000000 --- a/src/pci.h +++ /dev/null @@ -1,44 +0,0 @@ -#pragma once - -#include "utils.h" - -/// @brief Checks whether the given string is a valid PCI address. If the string is not a valid PCI address, the call will return a failure result with an error message. -/// @param pci_address The string to check. -/// @return The result of the operation. -result_t check_pci_address(const char* pci_address); - -/// @brief Checks whether the given PCI address exists. If the PCI address does not exist, the call will return a failure result with an error message. -/// @param pci_address The PCI address to check. -/// @return The result of the operation. -result_t check_pci_exists(const char* pci_address); - -/// @brief Gets the IOMMU group of the given PCI address. -/// @param _group The integer pointer to store the resulting IOMMU group in. -/// @param pci_address The PCI address to get the IOMMU group of. -/// @return The result of the operation. -result_t get_iommu_group(int* _group, const char* pci_address); - -/// @brief Gets the IOMMU groups of the given PCI addresses. -/// @param _groups The integer array pointer to store the resulting IOMMU groups in. The caller is responsible for freeing the array. -/// @param _count The integer pointer to store the number of IOMMU groups in. -/// @param pci_addresses The PCI addresses to get the IOMMU groups of. -/// @return The result of the operation. -result_t get_iommu_groups(int** _groups, int* _count, const char** pci_addresses); - -/// @brief Checks that the given file is a PCI device. This function is used as a filter for listing PCI devices. -/// @param file The file to check. -/// @return Whether the file is a PCI device. -bool pci_filter(const char* file); - -/// @brief Get the PCI devices in the given IOMMU group. -/// @param _devices The string array pointer to store the resulting devices in. The caller is responsible for freeing the strings and the array. -/// @param group The IOMMU group to get the devices of. -/// @return The result of the operation. -result_t get_iommu_group_devices(char*** _devices, int group); - -/// @brief Get the PCI devices in the given IOMMU groups. -/// @param _devices The string array pointer to store the resulting devices in. The caller is responsible for freeing the strings and the array. -/// @param groups The IOMMU groups to get the devices of. -/// @param groups_count The number of IOMMU groups. -/// @return The result of the operation. -result_t get_iommu_groups_devices(char*** _devices, const int* groups, int groups_count); \ No newline at end of file diff --git a/src/sandbox.c b/src/sandbox.c old mode 100755 new mode 100644 index f4d1c97..ea2f8c5 --- a/src/sandbox.c +++ b/src/sandbox.c @@ -1,115 +1,22 @@ -#include "sandbox.h" - #include "utils.h" -#include "backing.h" + +#include "disk.h" +#include "config.h" #include "container.h" +#include "backing.h" -#include -#include -#include -#include +#include #include +#include +#include -#define ALIAS(...) \ - (const char*[]) { \ - __VA_ARGS__, NULL \ +int main(int argc, char* argv[]) { + config_t* config; + result_t result = init_config(&config); + if (result != success()) { + fprintf(stderr, "Failed to initialize the configuration: %s\n", last_error()); + return 1; } -#define ALIASES(...) \ - (const char**[]) { \ - __VA_ARGS__, NULL \ - } - -#define ARGUMENTS(...) \ - (const Argument[]) { \ - __VA_ARGS__, {} \ - } - -#define OPTIONS(...) \ - (const Option[]) { \ - __VA_ARGS__, {} \ - } - -#define OPTION_ALIASES(...) \ - (const char*[]) { \ - __VA_ARGS__, NULL \ - } - -const Command COMMANDS[] = { - { - .handler = command_help, - .description = "Display help information", - .details = "Display help information for a specific command", - - .aliases = ALIASES(ALIAS("h"), ALIAS("help", "me")), - .arguments = ARGUMENTS({ - .name = "command", - .description = "The command to display help information for", - .optional = true, - }), - .options = OPTIONS({ - .aliases = OPTION_ALIASES("verbose", "v"), - .description = "Display verbose help information", - .arguments = ARGUMENTS({ - .name = "level", - .description = "The level of verbosity to display", - }), - }), - }, - {}}; - -int main(int argc, char** argv) { - if (argc == 1) { - // Handle no command - fprintf(stderr, "No command specified\n"); - return EXIT_FAILURE; - } - - // For each command - for (int i = 0; COMMANDS[i].handler != NULL; i++) { - const Command* command = &COMMANDS[i]; - - // For each alias - bool command_matched = false; - int k = 0; - for (int j = 0; command->aliases[j] != NULL; j++) { - const char** alias = command->aliases[j]; - - // For each string in the alias - bool alias_matched = true; - for (k = 0; alias[k] != NULL; k++) - if (argc <= 1 + k || strcmp(argv[1 + k], alias[k]) != 0) { - alias_matched = false; - break; - } - - // Check if the alias matched - if (alias_matched) { - command_matched = true; - break; - } - } - - // If the command matched - if (command_matched) { - // Call the command handler - return command->handler(argc - 1 - k, argv + 1 + k); - } - } - - fprintf(stderr, "Unknown command: "); - for (int i = 1; i < argc; i++) - fprintf(stderr, "%s ", argv[i]); - fprintf(stderr, "\n"); - - return EXIT_FAILURE; -} - -int command_help(int argc, char* argv[]) { - // Print argv - printf("argc: %d\n", argc); - for (int i = 0; i < argc; i++) - printf("%s\n", argv[i]); - - return EXIT_SUCCESS; -} + free_config(config); +} \ No newline at end of file diff --git a/src/sandbox.h b/src/sandbox.h old mode 100755 new mode 100644 index a8c79ff..f46f5d1 --- a/src/sandbox.h +++ b/src/sandbox.h @@ -1,40 +1,4 @@ #pragma once -#include - -#define SANDBOX_VERSION "0.1.4" -#define SANDBOX_USER "sandbox" - -#define DOMAIN_IDENTIFIER_PREFIX "sandbox-" -#define LIBVIRT_DRIVER "qemu:///system" - -#define CONFIG_FILE "/etc/sandbox.d/sandbox.conf" -#define SYNC_FILE "/etc/sandbox.d/sync" - -typedef struct { - const char* name; - const char* description; - const bool optional; -} Argument; - -typedef struct { - const char** aliases; - const char* description; - const Argument* arguments; -} Option; - -typedef struct { - int (*handler)(int argc, char* argv[]); - const char* description; - const char* details; - - const char*** aliases; - const Argument* arguments; - const Option* options; -} Command; - -extern const Command COMMANDS[]; - -int main(int argc, char** argv); - -int command_help(int argc, char* argv[]); \ No newline at end of file +#define CONFIGURATION_FILE "/etc/sandbox.d/config.json" +#define SYNCRONIZATION_FILE "/etc/sandbox.d/sync" \ No newline at end of file diff --git a/src/utils.c b/src/utils.c old mode 100755 new mode 100644 index 826215d..6e79b1c --- a/src/utils.c +++ b/src/utils.c @@ -1,78 +1,20 @@ #include "utils.h" #include -#include -#include -#include #include -#include -#include #include -#include +#include +#include +#include #include +#include +#include #include +#include -char _ERROR_BUFFER[ERROR_BUFFER_SIZE]; +char ERROR_BUFFER[ERROR_BUFFER_SIZE]; -log_level_t _LOG_LEVEL = LOG_LEVEL_INFO; - -void set_log_level(log_level_t level) { - _LOG_LEVEL = level; -} - -void log_message(log_level_t level, const char* format, ...) { - // Check that the log level is high enough - if (level < _LOG_LEVEL) - return; - - // Convert the log level to a color and a string - const char* color_str; - const char* level_str; - - switch (level) { - case LOG_LEVEL_DEBUG: - color_str = "\033[0m"; - level_str = "DEBUG"; - break; - case LOG_LEVEL_INFO: - color_str = "\033[36m"; - level_str = "INFO"; - break; - case LOG_LEVEL_WARNING: - color_str = "\033[33m"; - level_str = "WARNING"; - break; - case LOG_LEVEL_ERROR: - color_str = "\033[31m"; - level_str = "ERROR"; - break; - default: - color_str = "\033[0m"; - level_str = "UNKNOWN"; - break; - } - - // Get the current time - time_t t = time(NULL); - struct tm* tm = localtime(&t); - - // Print the log message - fprintf(stderr, "%s[%02d/%02d/%04d %02d:%02d:%02d - %s]\033[0m ", color_str, tm->tm_mday, tm->tm_mon + 1, tm->tm_year + 1900, tm->tm_hour, tm->tm_min, tm->tm_sec, level_str); - - va_list args; - va_start(args, format); - - vfprintf(stderr, format, args); - - va_end(args); - - fprintf(stderr, "\n"); - - // Flush the standard error stream - fflush(stderr); -} - -result_t success(void) { +result_t success() { return EXIT_SUCCESS; } @@ -80,623 +22,506 @@ result_t failure(const char* format, ...) { va_list args; va_start(args, format); - // Print the error message into the error buffer - vsnprintf(_ERROR_BUFFER, ERROR_BUFFER_SIZE, format, args); + // Write the error message to the error buffer. + vsnprintf(ERROR_BUFFER, ERROR_BUFFER_SIZE, format, args); va_end(args); return EXIT_FAILURE; } -const char* error(void) { - return _ERROR_BUFFER; +const char* last_error() { + return ERROR_BUFFER; } -result_t format(char** _str, const char* format, ...) { - // Initialize the output parameters - *_str = NULL; - - // Calculate the length of the formatted string +result_t execute_file(int* _exit_code, char** _stdout_buffer, size_t* _stdout_size, char** _stderr_buffer, size_t* _stderr_size, const char* working_directory, const char* file, ...) { + // Count the number of arguments. va_list args; - va_start(args, format); - - int length = vsnprintf(NULL, 0, format, args); - - va_end(args); - - if (length < 0) - return failure("Failed to calculate the length of the formatted string."); - - // Allocate memory for the formatted string - errno = 0; - char* str = malloc(length + 1); - if (str == NULL) - return failure("Failed to allocate memory for the formatted string (%s).", strerror(errno)); - - // Format the string - va_start(args, format); - - if (vsnprintf(str, length + 1, format, args) < 0) - return failure("Failed to format the string."); - - va_end(args); - - // Return the formatted string - *_str = str; - return success(); -} - -result_t substring(char** _str, const char* str, size_t start, size_t length) { - // Initialize the output parameters - *_str = NULL; - - // Check the start index - size_t str_length = strlen(str); - - if (start > str_length) - return failure("The start index is out of range."); - if (start + length > str_length) - length = str_length - start; - - // Allocate memory for the substring - errno = 0; - char* substr = malloc(length + 1); // +1 for the null terminator - if (substr == NULL) - return failure("Failed to allocate memory for the substring (%s).", strerror(errno)); - - // Copy the substring - memcpy(substr, str + start, length); - substr[length] = '\0'; - - // Return the substring - *_str = substr; - return success(); -} - -result_t format_size(char** _str, uint64_t size) { - // Initialize the output parameters - *_str = NULL; - - // Format the size - if (size < 1024ULL) - return format(_str, "%lluB", size); - if (size < 1024ULL * 1024) - return format(_str, "%.2fKiB", size / (1024.0)); - if (size < 1024ULL * 1024 * 1024) - return format(_str, "%.2fMiB", size / (1024.0 * 1024.0)); - if (size < 1024ULL * 1024 * 1024 * 1024) - return format(_str, "%.2fGiB", size / (1024.0 * 1024.0 * 1024.0)); - if (size < 1024ULL * 1024 * 1024 * 1024 * 1024) - return format(_str, "%.2fTiB", size / (1024.0 * 1024.0 * 1024.0 * 1024.0)); - if (size < 1024ULL * 1024 * 1024 * 1024 * 1024 * 1024) - return format(_str, "%.2fPiB", size / (1024.0 * 1024.0 * 1024.0 * 1024.0 * 1024.0)); - - return format(_str, "%.2fEiB", size / (1024.0 * 1024.0 * 1024.0 * 1024.0 * 1024.0 * 1024.0)); -} - -result_t parse_size(uint64_t* _size, const char* str) { - // Initialize the output parameters - *_size = 0; - - // Parse the size - char* endptr; - uint64_t size = strtoull(str, &endptr, 10); - if (endptr == str) - return failure("Failed to parse the size."); - - // Check the suffix - if (*endptr == '\0') { - // No suffix - *_size = size; - return success(); - } - - // Parse the suffix - if (endptr[1] != '\0') - return failure("Invalid size suffix."); - - switch (endptr[0]) { - case 'B': - *_size = size; - return success(); - case 'K': - *_size = size * 1024; - return success(); - case 'M': - *_size = size * 1024 * 1024; - return success(); - case 'G': - *_size = size * 1024 * 1024 * 1024; - return success(); - case 'T': - *_size = size * 1024 * 1024 * 1024 * 1024; - return success(); - case 'P': - *_size = size * 1024 * 1024 * 1024 * 1024 * 1024; - return success(); - case 'E': - *_size = size * 1024 * 1024 * 1024 * 1024 * 1024 * 1024; - return success(); - default: - return failure("Invalid size suffix."); - } -} - -result_t execute(int* _exit_code, char** _stdoutbuf, char** _stderrbuf, const char* working_directory, const char* executable, ...) { - // Initialize the output parameters - if (_exit_code != NULL) - *_exit_code = 0; - if (_stdoutbuf != NULL) - *_stdoutbuf = NULL; - if (_stderrbuf != NULL) - *_stderrbuf = NULL; - - // Count the number of arguments to pass to the executable - int argc = 1; // +1 for the executable itself - - va_list args; - va_start(args, executable); + va_start(args, file); + int argc = 1; // The first argument is the file itself. while (va_arg(args, const char*) != NULL) argc++; va_end(args); - // Allocate memory for the arguments array - errno = 0; - char** argv = malloc((argc + 1) * sizeof(char*)); // +1 for the null terminator + // Allocate the arguments array. + const char** argv = malloc((argc + 1) * sizeof(const char*)); // The last element is the NULL terminator. if (argv == NULL) - return failure("Failed to allocate memory for the arguments array (%s).", strerror(errno)); + return failure("Failed to allocate memory for the arguments (%s).", strerror(errno)); - // Fill the arguments array + // Fill the arguments array. + va_start(args, file); - errno = 0; - argv[0] = strdup(executable); - if (argv[0] == NULL) { - free(argv); - return failure("Failed to duplicate the executable string (%s).", strerror(errno)); - } - - va_start(args, executable); - - for (int i = 1; i < argc; i++) { - errno = 0; - argv[i] = strdup(va_arg(args, const char*)); - if (argv[i] == NULL) { - for (int j = 0; j < i; j++) - free(argv[j]); - free(argv); - va_end(args); - return failure("Failed to duplicate the argument string (%s).", strerror(errno)); - } - } + argv[0] = file; + for (int i = 1; i < argc; i++) + argv[i] = va_arg(args, const char*); + argv[argc] = NULL; va_end(args); - argv[argc] = NULL; - - // Create pipes for the standard output and standard error of the child process + // Create the pipes for the standard output and error streams. int stdout_pipe[2]; - errno = 0; if (pipe(stdout_pipe) < 0) { - for (int i = 0; i < argc; i++) - free(argv[i]); free(argv); - return failure("Failed to create a pipe for the standard output of the child process (%s).", strerror(errno)); + return failure("Failed to create the standard output pipe (%s).", strerror(errno)); } int stderr_pipe[2]; - errno = 0; if (pipe(stderr_pipe) < 0) { - for (int i = 0; i < argc; i++) - free(argv[i]); - free(argv); close(stdout_pipe[0]); close(stdout_pipe[1]); - return failure("Failed to create a pipe for the standard error of the child process (%s).", strerror(errno)); + free(argv); + return failure("Failed to create the standard error pipe (%s).", strerror(errno)); } - // Fork the child process - errno = 0; + // Fork the process. pid_t pid = fork(); if (pid < 0) { - 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("Failed to fork the child process (%s).", strerror(errno)); + free(argv); + return failure("Failed to fork the process (%s).", strerror(errno)); } if (pid == 0) { - // Redirect the standard output and standard error of the child process + // Redirect the standard output and error streams 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(stderr_pipe[0]); - - if (dup2(stdout_pipe[1], STDOUT_FILENO) < 0) - exit(EXIT_FAILURE); - - if (dup2(stderr_pipe[1], STDERR_FILENO) < 0) - exit(EXIT_FAILURE); - close(stdout_pipe[1]); + close(stderr_pipe[0]); close(stderr_pipe[1]); - // Change the working directory of the child process + // Change the working directory if necessary. if (working_directory != NULL) { - errno = 0; if (chdir(working_directory) < 0) { - fprintf(stderr, "Failed to change the working directory of the child process (%s).\n", strerror(errno)); + fprintf(stderr, "Failed to change the working directory to '%s' (%s).\n", working_directory, strerror(errno)); exit(EXIT_FAILURE); } } - // Execute the child process - errno = 0; - execvp(executable, argv); + // Execute the file. + execv(file, (char* const*)argv); - // If the child process reaches this point, it failed to execute - fprintf(stderr, "Failed to execute the child process (%s).\n", strerror(errno)); + // If execv returns, it failed. + fprintf(stderr, "Failed to execute the file '%s' (%s).\n", file, strerror(errno)); exit(EXIT_FAILURE); } - // Free the arguments array - for (int i = 0; i < argc; i++) - free(argv[i]); - free(argv); - - // Close the write ends of the pipes + // Close the write end of the pipes. close(stdout_pipe[1]); close(stderr_pipe[1]); - // Wait for the child process to terminate + // Free the arguments array as it is no longer needed. + free(argv); + + // Wait for the child process to exit. int status; - errno = 0; if (waitpid(pid, &status, 0) < 0) { close(stdout_pipe[0]); close(stderr_pipe[0]); - return failure("Failed to wait for the child process to terminate (%s).", strerror(errno)); + return failure("Failed to wait for the child process to exit (%s).", strerror(errno)); } - // Read the standard output and standard error of the child process - if (_exit_code != NULL) - *_exit_code = WEXITSTATUS(status); - if (_stdoutbuf != NULL) - read_fd(_stdoutbuf, stdout_pipe[0]); - if (_stderrbuf != NULL) - read_fd(_stderrbuf, stderr_pipe[0]); - - // Close the read ends of the pipes + // Read the standard output into a buffer if necessary. + if (_stdout_buffer != NULL) { + result_t result = read_fd(_stdout_buffer, _stdout_size, stdout_pipe[0]); + if (result != success()) { + close(stdout_pipe[0]); + close(stderr_pipe[0]); + return result; + } + } close(stdout_pipe[0]); + + // Read the standard error into a buffer if necessary. + if (_stderr_buffer != NULL) { + result_t result = read_fd(_stderr_buffer, _stderr_size, stderr_pipe[0]); + if (result != success()) { + close(stderr_pipe[0]); + free(*_stdout_buffer); + return result; + } + } close(stderr_pipe[0]); + // Output the exit code if necessary. + if (_exit_code != NULL) + *_exit_code = WEXITSTATUS(status); + return success(); } -result_t read_fd(char** _str, int fd) { - // Initialize the output parameters - *_str = NULL; - - // Read the file descriptor - size_t length = 0; +result_t read_fd(char** _buffer, size_t* _size, int fd) { + size_t size = 0; size_t capacity = 4096; - // Allocate memory for the output string - errno = 0; - char* str = malloc(capacity + 1); // +1 for the null terminator - if (str == NULL) - return failure("Failed to allocate memory for the output string (%s).", strerror(errno)); + // Allocate the buffer. + char* buffer = malloc(capacity); + if (buffer == NULL) + return failure("Failed to allocate memory for the buffer (%s).", strerror(errno)); - ssize_t count; - while (1) { - // Read the file descriptor into the output string - errno = 0; - count = read(fd, str + length, capacity - length); - if (count < 0) { - free(str); - return failure("Failed to read the file descriptor %d (%s).", fd, strerror(errno)); - } + // Read the file descriptor into the buffer. + ssize_t bytes_read; + while ((bytes_read = read(fd, buffer + size, capacity - size)) > 0) { + size += bytes_read; - // Update the length of the output string - length += count; - if (count == 0) - break; - - // Reallocate memory for the output string if necessary - if (length >= capacity) { + // Resize the buffer if necessary. + if (size == capacity) { + // Basic doubling strategy. capacity *= 2; - - errno = 0; - char* new_str = realloc(str, capacity + 1); // +1 for the null terminator - if (new_str == NULL) { - free(str); - return failure("Failed to reallocate memory for the output string (%s).", strerror(errno)); + char* new_buffer = realloc(buffer, capacity); + if (new_buffer == NULL) { + free(buffer); + return failure("Failed to reallocate memory for the buffer (%s).", strerror(errno)); } - str = new_str; + buffer = new_buffer; } } - // Null-terminate the string - str[length] = '\0'; + // Check for read errors. + if (bytes_read < 0) { + free(buffer); + return failure("Failed to read from the file descriptor (%s).", strerror(errno)); + } + + // Resize the buffer to the actual size. + char* new_buffer = realloc(buffer, size + 1); + if (new_buffer == NULL) { + free(buffer); + return failure("Failed to reallocate memory for the buffer (%s).", strerror(errno)); + } + buffer = new_buffer; + + // Null-terminate the buffer. + buffer[size] = '\0'; + + // Output the buffer and size. + *_buffer = buffer; + + if (_size != NULL) + *_size = size; - // Return the output string - *_str = str; return success(); } -result_t write_fd(int fd, const char* str) { - // Write the string to the file descriptor - size_t length = strlen(str); - size_t written = 0; +result_t write_fd(int fd, const char* buffer, size_t size) { + size_t bytes_written = 0; - while (written < length) { - // Try to write the entire string at once - errno = 0; - ssize_t count = write(fd, str + written, length - written); - if (count < 0) - return failure("Failed to write to the file descriptor %d (%s).", fd, strerror(errno)); - - written += count; + // Write the buffer to the file descriptor. + while (bytes_written < size) { + ssize_t written = write(fd, buffer + bytes_written, size - bytes_written); + if (written < 0) + return failure("Failed to write to the file descriptor (%s).", strerror(errno)); + bytes_written += written; } return success(); } -result_t read_file(char** _str, const char* path) { - // Initialize the output parameters - *_str = NULL; - - // Open the file - errno = 0; - int fd = open(path, O_RDONLY); +result_t read_file(char** _buffer, size_t* _size, const char* file) { + // Open the file. + int fd = open(file, O_RDONLY); if (fd < 0) - return failure("Failed to open the file '%s' (%s).", path, strerror(errno)); + return failure("Failed to open the file '%s' (%s).", file, strerror(errno)); - // Read the file - result_t result = read_fd(_str, fd); + // Read the file descriptor into a buffer. + result_t result = read_fd(_buffer, _size, fd); - // Close the file + // Close the file. close(fd); return result; } -result_t write_file(const char* path, const char* str) { - // Open the file - errno = 0; - int fd = open(path, O_WRONLY | O_CREAT | O_TRUNC, 0644); +result_t write_file(const char* file, const char* buffer, size_t size, mode_t mode) { + // Open the file. + int fd = open(file, O_WRONLY | O_CREAT | O_TRUNC, mode); if (fd < 0) - return failure("Failed to open the file '%s' (%s).", path, strerror(errno)); + return failure("Failed to open the file '%s' (%s).", file, strerror(errno)); - // Write the string to the file - result_t result = write_fd(fd, str); + // Write the buffer to the file descriptor. + result_t result = write_fd(fd, buffer, size); - // Close the file + // Close the file. close(fd); return result; } -result_t copy_file(const char* src, const char* dst) { - // Open the source file - errno = 0; - int src_fd = open(src, O_RDONLY); - if (src_fd < 0) - return failure("Failed to open the source file '%s' (%s).", src, strerror(errno)); - - // Open the destination file - errno = 0; - int dst_fd = open(dst, O_WRONLY | O_CREAT | O_TRUNC, 0644); - if (dst_fd < 0) { - close(src_fd); - return failure("Failed to open the destination file '%s' (%s).", dst, strerror(errno)); - } - - // Copy the source file to the destination file +result_t copy_fd(int source_fd, int destination_fd) { char buffer[4096]; - ssize_t count; + ssize_t bytes_read; - while (1) { - // Read from the source file - errno = 0; - count = read(src_fd, buffer, sizeof(buffer)); - if (count < 0) { - close(src_fd); - close(dst_fd); - return failure("Failed to read from the source file '%s' (%s).", src, strerror(errno)); - } - - // If the end of the file is reached, stop copying - if (count == 0) - break; - - // Write to the destination file - while (count > 0) { - errno = 0; - ssize_t written = write(dst_fd, buffer, count); - if (written < 0) { - close(src_fd); - close(dst_fd); - return failure("Failed to write to the destination file '%s' (%s).", dst, strerror(errno)); - } - - count -= written; + // Read the source file descriptor into the buffer. + while ((bytes_read = read(source_fd, buffer, sizeof(buffer))) > 0) { + // Try to write the buffer to the destination file descriptor. + ssize_t bytes_written = 0; + while (bytes_written < bytes_read) { + ssize_t written = write(destination_fd, buffer + bytes_written, bytes_read - bytes_written); + if (written < 0) + return failure("Failed to write to the file descriptor (%s).", strerror(errno)); + bytes_written += written; } } - // Close the source file - close(src_fd); - - // Close the destination file - close(dst_fd); + // Check for read errors. + if (bytes_read < 0) + return failure("Failed to read from the file descriptor (%s).", strerror(errno)); return success(); } -result_t list_files(char*** _files, const char* path, bool (*filter)(const char*)) { - // Initialize the output parameters - *_files = NULL; +result_t copy_file(const char* source, const char* destination, mode_t mode) { + // Open the source file. + int source_fd = open(source, O_RDONLY); + if (source_fd < 0) + return failure("Failed to open the source file '%s' (%s).", source, strerror(errno)); - // Allocate memory for the files array - size_t length = 0; - size_t capacity = 64; - - errno = 0; - char** files = malloc(capacity * sizeof(char*)); - if (files == NULL) - return failure("Failed to allocate memory for the files array (%s).", strerror(errno)); - - // Open the directory - errno = 0; - DIR* dir = opendir(path); - if (dir == NULL) { - free(files); - return failure("Failed to open the directory '%s' (%s).", path, strerror(errno)); + // Open the destination file. + int destination_fd = open(destination, O_WRONLY | O_CREAT | O_TRUNC, mode); + if (destination_fd < 0) { + close(source_fd); + return failure("Failed to open the destination file '%s' (%s).", destination, strerror(errno)); } - // Read the directory - struct dirent* entry; - while (1) { - // Read the next entry - errno = 0; - entry = readdir(dir); - if (entry == NULL) { - if (errno != 0) { - for (size_t i = 0; i < length; i++) - free(files[i]); - free(files); - closedir(dir); - return failure("Failed to read the directory '%s' (%s).", path, strerror(errno)); - } - break; - } + // Copy the source file to the destination file. + result_t result = copy_fd(source_fd, destination_fd); - // Filter the entry - if (filter != NULL && !filter(entry->d_name)) + // Close the files. + close(source_fd); + close(destination_fd); + + return result; +} + +char* trim(const char* string) { + // Check for NULL. + if (string == NULL) + return NULL; + + // Skip leading whitespace. + while (*string != '\0' && isspace(*string)) + string++; + + // Find the end of the string. + const char* end = string; + while (*end != '\0') + end++; + + // Skip trailing whitespace. + while (end > string && isspace(*(end - 1))) + end--; + + // Allocate a new string and copy the trimmed string into it. + size_t length = end - string; + char* trimmed = malloc(length + 1); + if (trimmed == NULL) + return NULL; + + memcpy(trimmed, string, length); + trimmed[length] = '\0'; + + return trimmed; +} + +char* file_name(const char* path) { + // Check for NULL. + if (path == NULL) + return NULL; + + // Find the last occurrence of the path separator. + const char* file_name = strrchr(path, '/'); + + // If the path separator was found, return the file name after it. + if (file_name != NULL) + return strdup(file_name + 1); + + // Otherwise, return the entire path. + return strdup(path); +} + +char* directory_name(const char* path) { + // Check for NULL. + if (path == NULL) + return NULL; + + // Find the last occurrence of the path separator. + const char* file_name = strrchr(path, '/'); + + // If the path separator was found, return the directory name before it. + if (file_name != NULL) { + size_t length = file_name - path; + char* directory_name = malloc(length + 1); + if (directory_name == NULL) + return NULL; + + memcpy(directory_name, path, length); + directory_name[length] = '\0'; + + return directory_name; + } + + // Otherwise, return the current directory. + return strdup("."); +} + +result_t list_files(char*** _files, const char* directory, result_t (*filter)(const char*)) { + // Open the directory. + DIR* dir = opendir(directory); + if (dir == NULL) + return failure("Failed to open the directory '%s' (%s).", directory, strerror(errno)); + + // Count the files in the directory. + size_t count = 0; + size_t capacity = 16; + + char** files = malloc(capacity * sizeof(char*)); + if (files == NULL) { + closedir(dir); + return failure("Failed to allocate memory for the files."); + } + + // Read the files in the directory. + struct dirent* entry; + while ((entry = readdir(dir)) != NULL) { + // Skip the current and parent directories. + if (strcmp(entry->d_name, ".") == 0 || strcmp(entry->d_name, "..") == 0) continue; - // Add the entry to the files array - errno = 0; - files[length] = strdup(entry->d_name); - if (files[length] == NULL) { - for (size_t i = 0; i < length; i++) - free(files[i]); - free(files); - closedir(dir); - return failure("Failed to allocate memory for the file name (%s).", strerror(errno)); + // Filter the file if necessary. + if (filter != NULL) { + result_t result = filter(entry->d_name); + if (result != success()) + continue; } - length++; - - // Reallocate memory for the files array if necessary - if (length > capacity) { + // Add the file to the list. + if (count == capacity) { + // Basic doubling strategy. capacity *= 2; - - errno = 0; char** new_files = realloc(files, capacity * sizeof(char*)); if (new_files == NULL) { - for (size_t i = 0; i < length; i++) + closedir(dir); + for (size_t i = 0; i < count; i++) free(files[i]); free(files); - closedir(dir); - return failure("Failed to reallocate memory for the files array (%s).", strerror(errno)); + return failure("Failed to reallocate memory for the files."); } files = new_files; } + + files[count] = strdup(entry->d_name); + if (files[count] == NULL) { + closedir(dir); + for (size_t i = 0; i < count; i++) + free(files[i]); + free(files); + return failure("Failed to allocate memory for the file."); + } + + count++; } - // Close the directory + // Close the directory. closedir(dir); - // Null-terminate the files array - files[length] = NULL; + // Null-terminate the list of files. + char** new_files = realloc(files, (count + 1) * sizeof(char*)); + if (new_files == NULL) { + for (size_t i = 0; i < count; i++) + free(files[i]); + free(files); + return failure("Failed to reallocate memory for the files."); + } + + new_files[count] = NULL; + + // Output the list of files. + *_files = new_files; - // Return the files array - *_files = files; return success(); } -result_t md5sum(char** _md5, const char* path) { - // Initialize the output parameters - *_md5 = NULL; +result_t md5_fd(char** _hash, int fd) { + // Initialize the digest context. + EVP_MD_CTX* context = EVP_MD_CTX_new(); + if (context == NULL) + return failure("Failed to create the MD5 digest context."); - // Open the file - errno = 0; - int fd = open(path, O_RDONLY); - if (fd < 0) - return failure("Failed to open the file '%s' (%s).", path, strerror(errno)); - - // Initialize the MD5 context - EVP_MD_CTX* mdctx = EVP_MD_CTX_new(); - if (mdctx == NULL) { - close(fd); - return failure("Failed to initialize the MD5 context."); + // Initialize the MD5 algorithm. + const EVP_MD* algorithm = EVP_md5(); + if (algorithm == NULL) { + EVP_MD_CTX_free(context); + return failure("Failed to initialize the MD5 algorithm."); } - if (EVP_DigestInit_ex(mdctx, EVP_md5(), NULL) != 1) { - EVP_MD_CTX_free(mdctx); - close(fd); - return failure("Failed to initialize the MD5 context."); + // Initialize the digest. + if (EVP_DigestInit_ex(context, algorithm, NULL) != 1) { + EVP_MD_CTX_free(context); + return failure("Failed to initialize the MD5 digest."); } - // Read the file and update the MD5 context char buffer[4096]; - ssize_t count; + ssize_t bytes_read; - while (1) { - // Read from the file - errno = 0; - count = read(fd, buffer, sizeof(buffer)); - if (count < 0) { - EVP_MD_CTX_free(mdctx); - close(fd); - return failure("Failed to read from the file '%s' (%s).", path, strerror(errno)); - } - - // If the end of the file is reached, stop updating the MD5 context - if (count == 0) - break; - - // Update the MD5 context - if (EVP_DigestUpdate(mdctx, buffer, count) != 1) { - EVP_MD_CTX_free(mdctx); - close(fd); - return failure("Failed to update the MD5 context."); + // Read the file descriptor into the buffer. + while ((bytes_read = read(fd, buffer, sizeof(buffer))) > 0) { + // Update the digest with the buffer. + if (EVP_DigestUpdate(context, buffer, bytes_read) != 1) { + EVP_MD_CTX_free(context); + return failure("Failed to update the MD5 digest."); } } - // Finalize the MD5 context - unsigned char md_value[EVP_MAX_MD_SIZE]; - unsigned int md_len; - - if (EVP_DigestFinal_ex(mdctx, md_value, &md_len) != 1) { - EVP_MD_CTX_free(mdctx); - close(fd); - return failure("Failed to finalize the MD5 context."); + // Check for read errors. + if (bytes_read < 0) { + EVP_MD_CTX_free(context); + return failure("Failed to read from the file descriptor (%s).", strerror(errno)); } - EVP_MD_CTX_free(mdctx); + // Finalize the digest. + unsigned char hash[EVP_MAX_MD_SIZE]; + unsigned int length; + if (EVP_DigestFinal_ex(context, hash, &length) != 1) { + EVP_MD_CTX_free(context); + return failure("Failed to finalize the MD5 digest."); + } - // Close the file + // Free the digest context. + EVP_MD_CTX_free(context); + + // Convert the hash to a hexadecimal string. + char* hex_hash = malloc(length * 2 + 1); + if (hex_hash == NULL) + return failure("Failed to allocate memory for the hash."); + + for (unsigned int i = 0; i < length; i++) + sprintf(hex_hash + i * 2, "%02x", hash[i]); + + // Output the hash. + *_hash = hex_hash; + + return success(); +} + +result_t md5_file(char** _hash, const char* file) { + // Open the file. + int fd = open(file, O_RDONLY); + if (fd < 0) + return failure("Failed to open the file '%s' (%s).", file, strerror(errno)); + + // Calculate the MD5 hash of the file. + result_t result = md5_fd(_hash, fd); + + // Close the file. close(fd); - // Format the MD5 checksum - errno = 0; - char* md5 = malloc(2 * md_len + 1); // +1 for the null terminator - if (md5 == NULL) - return failure("Failed to allocate memory for the MD5 checksum (%s).", strerror(errno)); - - for (unsigned int i = 0; i < md_len; i++) - if (snprintf(md5 + 2 * i, 3, "%02x", md_value[i]) != 2) { - free(md5); - return failure("Failed to format the MD5 checksum."); - } - md5[2 * md_len] = '\0'; - - // Return the MD5 checksum - *_md5 = md5; - return success(); -} \ No newline at end of file + return result; +} diff --git a/src/utils.h b/src/utils.h old mode 100755 new mode 100644 index 2334c5a..59d6c21 --- a/src/utils.h +++ b/src/utils.h @@ -1,135 +1,117 @@ #pragma once -#include #include -#include +#include +#include +#include -#define ERROR_BUFFER_SIZE 4096 - -/// @brief The global error buffer. It can be accessed using the error() function. -extern char _ERROR_BUFFER[]; - -/// @brief The level of importance of a log message. -typedef enum { - /// @brief A debug message. These messages are used for debugging purposes, and are not meant to be seen by the user. - LOG_LEVEL_DEBUG, - - /// @brief An info message. These messages are used to inform the user about the progress of the program. - LOG_LEVEL_INFO, - - /// @brief A warning message. These messages are used to warn the user about potential issues. - LOG_LEVEL_WARNING, - - /// @brief An error message. These messages are used to inform the user that a critical error has occurred. - LOG_LEVEL_ERROR, -} log_level_t; - -/// @brief The minimum log level of the program. -extern log_level_t _LOG_LEVEL; - -/// @brief The result of an operation. +/// @brief Type representing a result of an operation, 0 for success, non-zero for failure. typedef int result_t; -/// @brief Sets the log level of the program. -/// @param level The log level to set. -void set_log_level(log_level_t level); +/// @brief The size of the error buffer. +#define ERROR_BUFFER_SIZE 4096 -/// @brief Logs a message with the given level, and the given format string and arguments. -/// @param level The level of the message. -/// @param format The format string to use for the message. -/// @param ... The arguments to use for the format string. -void log_message(log_level_t level, const char* format, ...); +/// @brief The error buffer, containing the last error message. +extern char ERROR_BUFFER[]; -/// @brief Returns a generic success result. -/// @return The success result. -result_t success(void); +/// @brief Returns a result_t representing a successful operation. +/// @return The successful result_t. +result_t success(); -/// @brief Returns a generic failure result, with an error message formatted using the given format string and arguments. The error message is stored in a global buffer, and can be retrieved using the error() function. -/// @param format The format string to use for the error message. -/// @param ... The arguments to use for the format string. -/// @return The failure result. +/// @brief Returns a result_t representing a failed operation, and writes the formatted error message to the error buffer, which can be accessed using last_error(). +/// @param format The format string for the error message. +/// @param ... The arguments for the format string. +/// @return The failed result_t. result_t failure(const char* format, ...); -/// @brief Returns the error message from the last failure result. -/// @return The error message from the last failure result. -const char* error(void); +/// @brief Returns the last error message. +/// @return The last error message. +const char* last_error(); -/// @brief Formats a string using the given format string and arguments, and stores the result in the given string pointer. -/// @param _str The string pointer to store the resulting string in. The caller is responsible for freeing the string. -/// @param format The format string to use for the string. -/// @param ... The arguments to use for the format string. +/// @brief Executes the specified file using the given arguments. The standard output and error streams are captured into buffers, as well as the exit code. +/// @param _exit_code The pointer to where the exit code should be stored. This output parameter is optional, and can be NULL. +/// @param _stdout_buffer The pointer to where the standard output buffer should be stored. This output parameter is optional, and can be NULL. The caller is responsible for freeing the buffer. +/// @param _stdout_size The pointer to where the size of the standard output buffer should be stored. This output parameter is optional, and can be NULL. +/// @param _stderr_buffer The pointer to where the standard error buffer should be stored. This output parameter is optional, and can be NULL. The caller is responsible for freeing the buffer. +/// @param _stderr_size The pointer to where the size of the standard error buffer should be stored. This output parameter is optional, and can be NULL. +/// @param working_directory The working directory for the process, or NULL to use the current working directory. +/// @param file The file to execute. +/// @param ... The null-terminated list of arguments for the file. /// @return The result of the operation. -result_t format(char** _str, const char* format, ...); +result_t execute_file(int* _exit_code, char** _stdout_buffer, size_t* _stdout_size, char** _stderr_buffer, size_t* _stderr_size, const char* working_directory, const char* file, ...); -/// @brief Extracts a substring from the given string, starting at the given index and with the given length, and stores the resulting string in the given string pointer. -/// @param _str The string pointer to store the resulting string in. The caller is responsible for freeing the string. -/// @param str The string to extract the substring from. -/// @param start The index to start the substring at. -/// @param length The length of the substring. If the length goes beyond the end of the string, the substring will be truncated to the end of the string. +/// @brief Reads the specified file descriptor into a buffer. +/// @param _buffer The pointer to where the buffer should be stored. The caller is responsible for freeing the buffer. +/// @param _size The pointer to where the size of the buffer should be stored. This output parameter is optional, and can be NULL. +/// @param fd The file descriptor to read. /// @return The result of the operation. -result_t substring(char** _str, const char* str, size_t start, size_t length); +result_t read_fd(char** _buffer, size_t* _size, int fd); -/// @brief Formats a size in bytes to a human-readable string, and stores the resulting string in the given string pointer. -/// @param _str The string pointer to store the resulting string in. The caller is responsible for freeing the string. -/// @param size The size in bytes to format. +/// @brief Writes the specified buffer to the specified file descriptor. +/// @param fd The file descriptor to write to. +/// @param buffer The buffer to write. +/// @param size The size of the buffer. /// @return The result of the operation. -result_t format_size(char** _str, uint64_t size); +result_t write_fd(int fd, const char* buffer, size_t size); -/// @brief Parses a size from the given string, and stores the resulting integer in the given size pointer. -/// @param _size The size pointer to store the resulting integer in. -/// @param str The string to parse the size from. +/// @brief Reads the specified file into a buffer. +/// @param _buffer The pointer to where the buffer should be stored. The caller is responsible for freeing the buffer. +/// @param _size The pointer to where the size of the buffer should be stored. This output parameter is optional, and can be NULL. +/// @param file The file to read. /// @return The result of the operation. -result_t parse_size(uint64_t* _size, const char* str); +result_t read_file(char** _buffer, size_t* _size, const char* file); -/// @brief Executes the given command, and stores the resulting exit code in the given exit code pointer, and the standard output and standard error in the given string pointers. -/// @param _exit_code The exit code pointer to store the resulting exit code in. -/// @param _stdoutbuf The standard output string pointer to store the resulting string in. The caller is responsible for freeing the string. This parameter can be NULL if the standard output is not needed. -/// @param _stderrbuf The standard error string pointer to store the resulting string in. The caller is responsible for freeing the string. This parameter can be NULL if the standard error is not needed. -/// @param working_directory The working directory to execute the command in. If this parameter is NULL, the command will be executed in the current working directory. -/// @param executable The command to execute. -/// @param ... The arguments to pass to the command. The last argument must be NULL. No need to pass the executable as the first argument. +/// @brief Writes the specified buffer to the specified file. +/// @param file The file to write to. +/// @param buffer The buffer to write. +/// @param size The size of the buffer. +/// @param mode The permissions to set on the file. /// @return The result of the operation. -result_t execute(int* _exit_code, char** _stdoutbuf, char** _stderrbuf, const char* working_directory, const char* executable, ...); +result_t write_file(const char* file, const char* buffer, size_t size, mode_t mode); -// @brief Reads the given file descriptor, and stores the resulting string in the given string pointer. The string can contain null characters. -// @param _str The string pointer to store the resulting string in. The caller is responsible for freeing the string. -// @param fd The file descriptor to read from. -// @return The result of the operation. -result_t read_fd(char** _str, int fd); - -// @brief Writes the given string to the given file descriptor. -// @param fd The file descriptor to write to. -// @param str The string to write. -// @return The result of the operation. -result_t write_fd(int fd, const char* str); - -/// @brief Reads the given file, and stores the resulting string in the given string pointer. The string can contain null characters. -/// @param _str The string pointer to store the resulting string in. The caller is responsible for freeing the string. -/// @param path The path to the file to read. +/// @brief Copies the contents of the specified source file descriptor to the specified destination file descriptor. +/// @param source_fd The source file descriptor to copy. +/// @param destination_fd The destination file descriptor to copy to. /// @return The result of the operation. -result_t read_file(char** _str, const char* path); +result_t copy_fd(int source_fd, int destination_fd); -/// @brief Writes the given string to the given file. -/// @param path The path to the file to write to. -/// @param str The string to write. +/// @brief Copies the contents of the specified source file to the specified destination file. +/// @param source The source file to copy. +/// @param destination The destination to copy the file to. +/// @param mode The permissions to set on the destination file. /// @return The result of the operation. -result_t write_file(const char* path, const char* str); +result_t copy_file(const char* source, const char* destination, mode_t mode); -/// @brief Copies the given file to the given destination. -/// @param src The path to the file to copy. -/// @param dst The path to the destination to copy the file to. -/// @return The result of the operation. -result_t copy_file(const char* src, const char* dst); +/// @brief Trims whitespace from the beginning and end of the specified string. +/// @param string The string to trim. +/// @return The trimmed string. The caller is responsible for freeing the string. +char* trim(const char* string); -/// @brief Lists the files in the given directory, and stores the resulting array in the given string array pointer. A filter can be provided to only include files that match the filter. -/// @param _files The string array pointer to store the resulting array in. The caller is responsible for freeing the strings and the array. -/// @param path The path to the directory to list the files in. -/// @param filter The filter to use to only include files that match the filter. This parameter can be NULL if no filter is needed. -/// @return The result of the operation. -result_t list_files(char*** _files, const char* path, bool (*filter)(const char*)); +/// @brief Extracts the file name from the specified path. +/// @param path The path to extract the file name from. +/// @return The file name. The caller is responsible for freeing the string. +char* file_name(const char* path); -/// @brief Calculates the MD5 checksum of the given file, and stores the resulting hash in the given string pointer. -/// @param _md5 The string pointer to store the resulting hash in. The caller is responsible for freeing the string. -/// @param path The path to the file to calculate the MD5 checksum of. +/// @brief Extracts the directory name from the specified path. +/// @param path The path to extract the directory name from. +/// @return The directory name. The caller is responsible for freeing the string. +char* directory_name(const char* path); + +/// @brief Lists the files in the specified directory, and returns the result in a null-terminated array of strings. A filter function can be provided to filter the files. +/// @param _files The pointer to where the files should be stored. The caller is responsible for freeing the files. +/// @param directory The directory to list the files of. +/// @param filter The filter function to use, or NULL to list all files. The filter function should return a success result if the file should be included in the list. /// @return The result of the operation. -result_t md5sum(char** _md5, const char* path); +result_t list_files(char*** _files, const char* directory, result_t (*filter)(const char*)); + +/// @brief Reads the specified file descriptor, and calculates the MD5 hash of the contents. +/// @param _hash The pointer to where the hash should be stored. The caller is responsible for freeing the hash. +/// @param fd The file descriptor to read. +/// @return The result of the operation. +result_t md5_fd(char** _hash, int fd); + +/// @brief Reads the specified file, and calculates the MD5 hash of the contents. +/// @param _hash The pointer to where the hash should be stored. The caller is responsible for freeing the hash. +/// @param file The file to read. +/// @return The result of the operation. +result_t md5_file(char** _hash, const char* file); \ No newline at end of file diff --git a/src/xml.c b/src/xml.c deleted file mode 100644 index b81e1e2..0000000 --- a/src/xml.c +++ /dev/null @@ -1,275 +0,0 @@ -#include "xml.h" - -#include "pci.h" -#include "container.h" -#include "domain.h" - -#include -#include -#include -#include - -result_t generate_domain_xml(char** _xml, const char* container, uint64_t memory, int vcpus, const char** pci_addresses, const char** iso_paths, int vnc_port, const char* vnc_password) { - // Initialize the output parameters - *_xml = NULL; - - // Generate the XML configuration for the PCI devices - char* pci_xml; - result_t result = generate_pci_xml(&pci_xml, pci_addresses); - if (result != success()) - return result; - - // Generate the XML configuration for the ISO images - char* iso_xml; - result = generate_iso_xml(&iso_xml, iso_paths); - if (result != success()) { - free(pci_xml); - return result; - } - - // Generate the XML configuration for the VNC server - char* vnc_xml; - result = generate_vnc_xml(&vnc_xml, vnc_port, vnc_password); - if (result != success()) { - free(pci_xml); - free(iso_xml); - return result; - } - - // Get the container path - char* container_path; - result = get_container_path(&container_path, container); - if (result != success()) { - free(pci_xml); - free(iso_xml); - free(vnc_xml); - return result; - } - - // Get the domain identifier - char* domain; - result = container_to_domain(&domain, container); - if (result != success()) { - free(pci_xml); - free(iso_xml); - free(vnc_xml); - free(container_path); - return result; - } - - // Generate the XML configuration for the domain - char* xml; - result = format(&xml, "\n" - "%s\n" - "\n\n" - "%lu\n" - "%d\n" - "\n" - "\n\n" - "\n" - "hvm\n" - "\n" - "\n" - "\n\n" - "\n" - "\n" - "\n" - "\n" - "\n\n" - "\n" - "\n" - "\n" - "\n" - "\n" - "\n\n" - "destroy\n" - "destroy\n" - "destroy\n" - "\n" - "\n" - "\n" - "\n" - "\n\n" - "\n" - "/usr/bin/qemu-system-x86_64\n" - "\n\n" - "\n" - "\n" - "\n" - "\n" - "\n" - "\n" - "\n\n" - "%s" - "\n\n" - "%s" - "\n\n" - "%s" - "\n\n" - "\n" - "\n" - "\n" - "\n", - domain, memory, vcpus, container_path, pci_xml, iso_xml, vnc_xml); - free(pci_xml); - free(iso_xml); - free(vnc_xml); - free(container_path); - free(domain); - - if (result != success()) - return result; - - // Return the result - *_xml = xml; - return success(); -} - -result_t generate_pci_xml(char** _xml, const char** pci_addresses) { - // Initialize the output parameters - *_xml = NULL; - - // Get the IOMMU group for each PCI address - int* iommu_groups; - int iommu_groups_count; - result_t result = get_iommu_groups(&iommu_groups, &iommu_groups_count, pci_addresses); - if (result != success()) - return result; - - // Get the PCI devices for each IOMMU group (the devices that should be passed through) - char** pci_devices; - result = get_iommu_groups_devices(&pci_devices, iommu_groups, iommu_groups_count); - - // Free the IOMMU groups as they are no longer needed - free(iommu_groups); - - if (result != success()) - return result; - - // Generate the XML configuration for the PCI devices - errno = 0; - char* xml = strdup(""); - if (xml == NULL) { - for (int i = 0; pci_devices[i] != NULL; i++) - free(pci_devices[i]); - free(pci_devices); - return failure("Failed to allocate memory for the initial XML configuration (%s).", strerror(errno)); - } - - // For each PCI device, generate the XML configuration, and append it to the result - for (int i = 0; pci_devices[i] != NULL; i++) { - // Split the PCI address into domain, bus, device, and function - int domain, bus, device, function; - if (sscanf(pci_devices[i], "%04x:%02x:%02x.%x", &domain, &bus, &device, &function) != 4) { - free(xml); - for (int j = 0; pci_devices[j] != NULL; j++) - free(pci_devices[j]); - free(pci_devices); - return failure("Failed to parse PCI address."); - } - - // Generate the XML configuration for the PCI device - char* device_xml; - result = format(&device_xml, "\n" - "\n" - "
\n" - "\n" - "\n", - domain, bus, device, function); - if (result != success()) { - free(xml); - for (int j = 0; pci_devices[j] != NULL; j++) - free(pci_devices[j]); - free(pci_devices); - return result; - } - - // Append the new XML configuration to the result - char* new_xml; - result = format(&new_xml, "%s%s", xml, device_xml); - free(device_xml); - - if (result != success()) { - free(xml); - for (int j = 0; pci_devices[j] != NULL; j++) - free(pci_devices[j]); - free(pci_devices); - return result; - } - - // Swap the old and new XML configurations - free(xml); - xml = new_xml; - } - - // Free the PCI devices as they are no longer needed - for (int i = 0; pci_devices[i] != NULL; i++) - free(pci_devices[i]); - free(pci_devices); - - // Return the result - *_xml = xml; - return success(); -} - -result_t generate_iso_xml(char** _xml, const char** iso_paths) { - // Initialize the output parameters - *_xml = NULL; - - // Generate the XML configuration for the ISO images - errno = 0; - char* xml = strdup(""); - if (xml == NULL) - return failure("Failed to allocate memory for the initial XML configuration (%s).", strerror(errno)); - - // For each ISO image, generate the XML configuration, and append it to the result - for (int i = 0; iso_paths[i] != NULL; i++) { - if (i > 25) { - free(xml); - return failure("Too many ISO images."); - } - - char* iso_xml; - result_t result = format(&iso_xml, "\n" - "\n" - "\n" - "\n" - "\n" - "\n" - "\n", - iso_paths[i], 'b' + i, 2 + i); // hda is reserved for the main disk - if (result != success()) { - free(xml); - return result; - } - - // Append the new XML configuration to the result - char* new_xml; - result = format(&new_xml, "%s%s", xml, iso_xml); - free(iso_xml); - - if (result != success()) { - free(xml); - return result; - } - - // Swap the old and new XML configurations - free(xml); - xml = new_xml; - } - - // Return the result - *_xml = xml; - return success(); -} - -result_t generate_vnc_xml(char** _xml, int vnc_port, const char* vnc_password) { - // Initialize the output parameters - *_xml = NULL; - - // Generate the XML configuration for the VNC server (if enabled) - if (vnc_port != 0) - return format(_xml, "", vnc_port, vnc_password); - - return format(_xml, ""); -} diff --git a/src/xml.h b/src/xml.h deleted file mode 100644 index f89e930..0000000 --- a/src/xml.h +++ /dev/null @@ -1,34 +0,0 @@ -#pragma once - -#include "utils.h" - -/// @brief Generates the XML configuration for a sandbox domain, using the given parameters. -/// @param _xml The string pointer to store the resulting XML configuration in. The caller is responsible for freeing the string. -/// @param container The container to generate the XML configuration for. -/// @param memory The memory size of the domain, in bytes. -/// @param vcpus The number of virtual CPUs of the domain. -/// @param pci_addresses The PCI addresses of the devices to pass through to the domain. -/// @param iso_paths The paths to the ISO images to pass through to the domain. -/// @param vnc_port The VNC port to use for the domain, or 0 to disable VNC. -/// @param vnc_password The VNC password to use for the domain. If the VNC server is disabled, this parameter is ignored. -/// @return The result of the operation. -result_t generate_domain_xml(char** _xml, const char* container, uint64_t memory, int vcpus, const char** pci_addresses, const char** iso_paths, int vnc_port, const char* vnc_password); - -/// @brief Generates the XML configuration for passing through the given PCI devices. -/// @param _xml The string pointer to store the resulting XML configuration in. The caller is responsible for freeing the string. -/// @param pci_addresses The PCI addresses of the devices to pass through. -/// @return The result of the operation. -result_t generate_pci_xml(char** _xml, const char** pci_addresses); - -/// @brief Generates the XML configuration for attaching the given ISO images. -/// @param _xml The string pointer to store the resulting XML configuration in. The caller is responsible for freeing the string. -/// @param iso_paths The paths to the ISO images to attach. -/// @return The result of the operation. -result_t generate_iso_xml(char** _xml, const char** iso_paths); - -/// @brief Generates the XML configuration for a VNC server. -/// @param _xml The string pointer to store the resulting XML configuration in. The caller is responsible for freeing the string. -/// @param vnc_port The VNC port to use. If 0, the VNC server will be disabled. -/// @param vnc_password The VNC password to use. If the VNC server is disabled, this parameter is ignored. -/// @return The result of the operation. -result_t generate_vnc_xml(char** _xml, int vnc_port, const char* vnc_password);