diff --git a/src/backing.c b/src/backing.c deleted file mode 100644 index 842e4d3..0000000 --- a/src/backing.c +++ /dev/null @@ -1,362 +0,0 @@ -#include "backing.h" -#include "container.h" -#include "disk.h" -#include "sandbox.h" - -#include -#include -#include -#include -#include -#include - -result_t check_backing_identifier(const char* backing) { - // Check that the backing identifier is not null. - if (backing == NULL) - return failure("Backing identifier cannot be null."); - - // Check that the backing identifier is a valid md5 hash. - size_t length = strlen(backing); - if (length != 32) - return failure("Backing identifier must be a 32-character md5 hash."); - - // 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') || (c >= '0' && c <= '9')) - continue; - - return failure("Backing identifier must be a 32-character md5 hash."); - } - - return success(); -} - -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."); - - // Output the path. - *_path = path; - - return success(); -} - -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 path of the backing pool. - char* pool_path; - result = get_backing_pool_path(&pool_path, config); - if (result != success()) - return result; - - // Append the backing identifier to the backing pool path. - char* path; - if (asprintf(&path, "%s/%s", pool_path, backing) == -1) { - free(pool_path); - return failure("Failed to allocate memory for the backing path."); - } - - free(pool_path); - - // Output the path. - *_path = path; - - return success(); -} - -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, config); - if (result != success()) - return result; - - // 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); - - // Output the path. - *_path = path; - - return success(); -} - -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; - - // 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."); - } - - 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); - - return success(); -} - -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 result; - } - - // Output the backing identifier. - *_backing = backing; - - return success(); -} - -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; - - // 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; - } - - // Free the default backing path. - free(path); - - 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_disk_info(info); - unlink(temporary_path); - free(temporary_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; - } - } - - free_disk_info(info); - - // Get the md5 hash of the temporary backing. - char* hash; - result = md5_file(&hash, temporary_path); - if (result != success()) { - unlink(temporary_path); - free(temporary_path); - return result; - } - - // Check if the backing already exists. - result = check_backing_exists(config, hash); - if (result == success()) { - unlink(temporary_path); - free(temporary_path); - - result = failure("Backing '%s' already exists.", hash); - free(hash); - return result; - } - - // 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; - } - - 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)); - } - - free(temporary_path); - - // Output the backing identifier. - *_backing = hash; - - return success(); -} - -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 path of the backing. - char* path; - result = get_backing_path(&path, config, backing); - if (result != success()) - return result; - - // 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; -} diff --git a/src/config.c b/src/config.c index ae25173..072d272 100644 --- a/src/config.c +++ b/src/config.c @@ -1,31 +1,136 @@ #include "config.h" -#include "sandbox.h" - -#include -#include -#include #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."); + result_t result; - // Initialize the configuration. - config->container_pool = strdup("/var/lib/sandbox/containers"); - config->backing_pool = strdup("/var/lib/sandbox/backings"); + // Allocate the configuration. + config_t* config = (config_t*)malloc(sizeof(config_t)); + if (config == NULL) { + result = failure("Failed to allocate memory for the configuration (%s).", strerror(errno)); + verbose("Exception -> %s", last_error()); - // Output the configuration. - *_config = config; + return result; + } + + // Read the configuration file. + char* config_json; + result = read_file(&config_json, NULL, CONFIGURATION_FILE); + if (result != success()) { + result = failure("Failed to read the configuration file '%s'.", CONFIGURATION_FILE); + verbose("Exception -> %s", last_error()); + + free(config); + + return result; + } + + // Parse the configuration JSON text. + json_object* root = json_tokener_parse(config_json); + if (root == NULL) { + result = failure("Failed to parse the configuration JSON."); + verbose("Exception -> %s", last_error()); + + free(config_json); + free(config); + + return result; + } + + // Free the configuration JSON text as it is no longer needed. + free(config_json); + + // Get the container pool path. + json_object* container_pool; + if (!json_object_object_get_ex(root, "container-pool", &container_pool)) { + result = failure("Failed to get the container pool path from the configuration JSON."); + verbose("Exception -> %s", last_error()); + + json_object_put(root); + free(config); + + return result; + } + + // Get the container pool path string. + const char* container_pool_string = json_object_get_string(container_pool); + if (container_pool_string == NULL) { + result = failure("Failed to parse the container pool path from the configuration JSON."); + verbose("Exception -> %s", last_error()); + + json_object_put(root); + free(config); + } + + // Get the backing pool path. + json_object* backing_pool; + if (!json_object_object_get_ex(root, "backing-pool", &backing_pool)) { + result = failure("Failed to get the backing pool path from the configuration JSON."); + verbose("Exception -> %s", last_error()); + + json_object_put(root); + free(config); + + return result; + } + + // Get the backing pool path string. + const char* backing_pool_string = json_object_get_string(backing_pool); + if (backing_pool_string == NULL) { + result = failure("Failed to parse the backing pool path from the configuration JSON."); + verbose("Exception -> %s", last_error()); + + json_object_put(root); + free(config); + } + + // Set the container pool path in the configuration. + config->container_pool = strdup(container_pool_string); + if (config->container_pool == NULL) { + result = failure("Failed to allocate memory for the container pool path."); + verbose("Exception -> %s", last_error()); + + json_object_put(root); + free(config); + + return result; + } + verbose("Configuration -> Container pool: %s", config->container_pool); + + // Set the backing pool path in the configuration. + config->backing_pool = strdup(backing_pool_string); + if (config->backing_pool == NULL) { + result = failure("Failed to allocate memory for the backing pool path."); + verbose("Exception -> %s", last_error()); + + json_object_put(root); + free(config->container_pool); + free(config); + + return result; + } + verbose("Configuration -> Backing pool: %s", config->backing_pool); + + // Set the configuration if requested. + if (_config != NULL) + *_config = config; + else + free_config(config); + + // Free the configuration JSON. + json_object_put(root); return success(); } void free_config(config_t* config) { free(config->container_pool); + free(config->backing_pool); free(config); } diff --git a/src/config.h b/src/config.h index e90ea90..13d56c5 100644 --- a/src/config.h +++ b/src/config.h @@ -2,11 +2,12 @@ #include "utils.h" +#define CONFIGURATION_FILE "/etc/sandbox.d/config.json" + /// @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; diff --git a/src/container.c b/src/container.c index 9284e5f..50d8fc6 100644 --- a/src/container.c +++ b/src/container.c @@ -1,384 +1,584 @@ #include "container.h" -#include "backing.h" + #include "disk.h" -#include "backing.h" -#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. - if (container == NULL) - return failure("Container identifier cannot be null."); +result_t check_container_identifier(const char* identifier) { + result_t result; - // Check that the container identifier is not empty, nor too long. - size_t length = strlen(container); - 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 identifier is not null. + if (identifier == NULL) { + result = failure("The specified container identifier is null."); + verbose("Exception -> %s", last_error()); - // 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') - continue; - if (c >= 'A' && c <= 'Z') - continue; - if (c >= '0' && c <= '9') - continue; - if ((c == '-' || c == '_' || c == '.') && i > 0) // Not as the first character. - continue; - - return failure("Container identifier cannot contain the character '%c' at position %d.", c, i); + return result; } + // Check that the identifier is not empty. + size_t length = strlen(identifier); + if (length == 0) { + result = failure("The specified container identifier is empty."); + verbose("Exception -> %s", last_error()); + + return result; + } + + // Check that the identifier is not too long. + if (length > MAX_CONTAINER_IDENTIFIER_LENGTH) { + result = failure("The specified container identifier is too long (maximum length is %d).", MAX_CONTAINER_IDENTIFIER_LENGTH); + verbose("Exception -> %s", last_error()); + + return result; + } + + // Check that the identifier contains only alphanumeric characters and underscores. + for (size_t i = 0; i < length; i++) { + if (isalnum(identifier[i])) + continue; + if ((identifier[i] == '_' || identifier[i] == '-' || identifier[i] == '.') && i > 0) + continue; + + result = failure("The specified container identifier contains an invalid character '%c' at index %zu.", identifier[i], i); + verbose("Exception -> %s", last_error()); + + return result; + } + + return success(); +} + +result_t check_container_exists(bool* _exists, const config_t* config, const char* identifier) { + result_t result; + + // Get the container path. + char* path; + result = get_container_path(&path, config, identifier); + if (result != success()) { + result = failure("Failed to get the container path."); + verbose("Exception -> %s", last_error()); + + return result; + } + + // Check that the container exists. + struct stat st; + if (stat(path, &st) == -1) { + if (errno == ENOENT) { + verbose("The container '%s' does not exist.", identifier); + + *_exists = false; + + free(path); + + return success(); + } + + result = failure("Failed to check whether the container exists."); + verbose("Exception -> %s", last_error()); + + free(path); + + return result; + } + + free(path); + + // Check that the container is a regular file. + if (!S_ISREG(st.st_mode)) { + result = failure("The specified container path is not a regular file."); + verbose("Exception -> %s", last_error()); + + return result; + } + + // The container exists and is a regular file. + *_exists = true; + + verbose("The container '%s' exists.", identifier); + return success(); } 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."); + result_t result; - // Output the path. + // Check that the configuration is not null. + if (config == NULL) { + result = failure("The specified program configuration is null."); + verbose("Exception -> %s", last_error()); + + return result; + } + + // Create a copy of the container pool path. + char* path = strdup(config->container_pool); + if (path == NULL) { + result = failure("Failed to copy the container pool path."); + verbose("Exception -> %s", last_error()); + + return result; + } + + // Set the container pool path. *_path = path; return success(); } -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; +result_t get_container_path(char** _path, const config_t* config, const char* identifier) { + result_t result; - // Get the path of the container pool. - char* pool_path; - result = get_container_pool_path(&pool_path, config); - if (result != success()) - return result; + // Check that the identifier is valid. + result = check_container_identifier(identifier); + if (result != success()) { + result = failure("The specified container identifier is invalid."); + verbose("Exception -> %s", last_error()); - // Append the container identifier to the container pool path. + return result; + } + + // Get the container pool path. + char* container_pool; + result = get_container_pool_path(&container_pool, config); + if (result != success()) { + result = failure("Failed to get the container pool path."); + verbose("Exception -> %s", last_error()); + + return result; + } + + // Append the 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."); + if (asprintf(&path, "%s/%s", container_pool, identifier) == -1) { + result = failure("Failed to allocate memory for the container path."); + verbose("Exception -> %s", last_error()); + + free(container_pool); + + return result; } // Free the container pool path. - free(pool_path); + free(container_pool); - // Output the path. + // Set the container path. *_path = path; return success(); } -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; +result_t add_root_container(const config_t* config, const char* identifier, size_t size) { + result_t 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); + bool exists; + result = check_container_exists(&exists, config, identifier); + if (result != success()) { + result = failure("Failed to check whether the container exists."); + verbose("Exception -> %s", last_error()); - // Get the path of the container. - char* path; - result = get_container_path(&path, config, container); - if (result != success()) return result; + } - // Create the root container. + if (exists) { + result = failure("The specified container already exists."); + verbose("Exception -> %s", last_error()); + + return result; + } + + // Get the container path. + char* path; + result = get_container_path(&path, config, identifier); + if (result != success()) { + result = failure("Failed to get the container path."); + verbose("Exception -> %s", last_error()); + + return result; + } + + // Create the container disk. result = create_root_disk(path, size); if (result != success()) { + result = failure("Failed to create the container disk."); + verbose("Exception -> %s", last_error()); + free(path); + return result; } // Free the container path. free(path); - return success(); -} - -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); - - // Check that the backing exists. - result = check_backing_exists(config, backing); - 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 backing. - char* backing_path; - result = get_backing_path(&backing_path, config, backing); - if (result != success()) { - free(path); - return result; - } - - // 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); + verbose("Successfully added root container '%s' to the container pool, with a size of %zu bytes.", identifier, size); return success(); } -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; +result_t remove_container(const config_t* config, const char* identifier) { + result_t result; // Check that the container exists. - result = check_container_exists(config, container); - if (result != success()) - return result; + bool exists; + result = check_container_exists(&exists, config, identifier); + if (result != success()) { + result = failure("Failed to check whether the container exists."); + verbose("Exception -> %s", last_error()); - // Get the path of the container. + return result; + } + + if (!exists) { + result = failure("The specified container does not exist."); + verbose("Exception -> %s", last_error()); + + return result; + } + + // Get the container path. char* path; - result = get_container_path(&path, config, container); - if (result != success()) - return result; + result = get_container_path(&path, config, identifier); + if (result != success()) { + result = failure("Failed to get the container path."); + verbose("Exception -> %s", last_error()); + + return result; + } + + // Remove the container disk. + if (remove(path) == -1) { + result = failure("Failed to remove the container disk (%s).", strerror(errno)); + verbose("Exception -> %s", last_error()); - // Remove the container. - if (unlink(path) == -1) { free(path); - return failure("Failed to remove container '%s': %s.", container, strerror(errno)); + + return result; } // Free the container path. free(path); + verbose("Successfully removed the container '%s' from the container pool.", identifier); + return success(); } -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; +result_t reset_container(const config_t* config, const char* identifier) { + result_t result; // Check that the container exists. - result = check_container_exists(config, container); - if (result != success()) - return result; + bool exists; + result = check_container_exists(&exists, config, identifier); + if (result != success()) { + result = failure("Failed to check whether the container exists."); + verbose("Exception -> %s", last_error()); - // Get the path of the container. + return result; + } + + if (!exists) { + result = failure("The specified container does not exist."); + verbose("Exception -> %s", last_error()); + + return result; + } + + // Get the container path. char* path; - result = get_container_path(&path, config, container); - if (result != success()) - return result; + result = get_container_path(&path, config, identifier); + if (result != success()) { + result = failure("Failed to get the container path."); + verbose("Exception -> %s", last_error()); - // Reset the container. + return result; + } + + // Reset the container disk. result = reset_disk(path); if (result != success()) { + result = failure("Failed to reset the container disk."); + verbose("Exception -> %s", last_error()); + free(path); + return result; } // Free the container path. free(path); + verbose("Successfully reset the container '%s'.", identifier); + return success(); } -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; +result_t trim_container(const config_t* config, const char* identifier) { + result_t result; // Check that the container exists. - result = check_container_exists(config, container); - if (result != success()) - return result; + bool exists; + result = check_container_exists(&exists, config, identifier); + if (result != success()) { + result = failure("Failed to check whether the container exists."); + verbose("Exception -> %s", last_error()); - // Get the path of the container. + return result; + } + + if (!exists) { + result = failure("The specified container does not exist."); + verbose("Exception -> %s", last_error()); + + return result; + } + + // Get the container path. char* path; - result = get_container_path(&path, config, container); - if (result != success()) - return result; + result = get_container_path(&path, config, identifier); + if (result != success()) { + result = failure("Failed to get the container path."); + verbose("Exception -> %s", last_error()); - // Trim the container. + return result; + } + + // Trim the container disk. result = trim_disk(path); if (result != success()) { + result = failure("Failed to trim the container disk."); + verbose("Exception -> %s", last_error()); + free(path); + return result; } // Free the container path. free(path); + verbose("Successfully trimmed the container '%s'.", identifier); + return success(); } -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; +result_t list_containers(char*** _identifiers, size_t* _count, const config_t* config) { + result_t result; - // Get the list of containers. - return list_files(_containers, pool_path, check_container_identifier); + // Get the container pool path. + char* container_pool; + result = get_container_pool_path(&container_pool, config); + if (result != success()) { + result = failure("Failed to get the container pool path."); + verbose("Exception -> %s", last_error()); + + return result; + } + + char** identifiers; + size_t count; + result = list_files(&identifiers, &count, container_pool, check_container_identifier); + if (result != success()) { + result = failure("Failed to list the containers in the pool."); + verbose("Exception -> %s", last_error()); + + free(container_pool); + + return result; + } + + // Free the container pool path. + free(container_pool); + + // Set the list of identifiers. + if (_identifiers != NULL) + *_identifiers = identifiers; + else { + for (size_t i = 0; i < count; i++) + free(identifiers[i]); + free(identifiers); + } + + // Set the count of identifiers. + if (_count != NULL) + *_count = count; + + verbose("The container pool contains %zu containers.", count); + + return success(); } -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()) +result_t get_container_pool_space(size_t* _space, const config_t* config) { + result_t result; + + // Get the container pool path. + char* container_pool; + result = get_container_pool_path(&container_pool, config); + if (result != success()) { + result = failure("Failed to get the container pool path."); + verbose("Exception -> %s", last_error()); + return result; + } + + // Get the available space in the container pool. + struct statvfs st; + if (statvfs(container_pool, &st) == -1) { + result = failure("Failed to get the available space in the container pool (%s).", strerror(errno)); + verbose("Exception -> %s", last_error()); + + free(container_pool); + + return result; + } + + // Free the container pool path. + free(container_pool); + + // Set the available space. + *_space = st.f_bsize * st.f_bavail; + + verbose("The available space in the container pool is %zu bytes.", st.f_bsize * st.f_bavail); + + return success(); +} + +result_t find_oldest_container(char** _identifier, const config_t* config) { + result_t result; + + // Get the list of containers. + char** identifiers; + size_t count; + result = list_containers(&identifiers, &count, config); + if (result != success()) { + result = failure("Failed to find the oldest container due to a failure to list the containers."); + verbose("Exception -> %s", last_error()); + + return result; + } // Find the oldest container. - char* oldest_container = NULL; time_t oldest_time = 0; + char* oldest_identifier = NULL; - for (size_t i = 0; containers[i] != NULL; i++) { - // Get the path of the container. + for (size_t i = 0; i < count; i++) { + // Get the container path. char* path; - result = get_container_path(&path, config, containers[i]); - if (result != success()) + result = get_container_path(&path, config, identifiers[i]); + if (result != success()) { + verbose("Skipping the container '%s' due to an exception.", identifiers[i]); + continue; + } - // Get the last modification time of the container. - time_t time; - + // Get the last access time of the container. struct stat st; - if (stat(path, &st) == -1) - time = 0; - else - time = st.st_mtime; + if (stat(path, &st) == -1) { + verbose("Skipping the container '%s' due to a failure to get the last access time.", identifiers[i]); + + free(path); + + continue; + } + + // Check if the container is the oldest. + if (oldest_identifier == NULL || st.st_mtime < oldest_time) { + oldest_time = st.st_mtime; + oldest_identifier = identifiers[i]; + } // 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 = time; + // Set the oldest container identifier. + if (oldest_identifier != NULL) { + oldest_identifier = strdup(oldest_identifier); + if (oldest_identifier == NULL) { + result = failure("Failed to allocate memory for the oldest container identifier."); + verbose("Exception -> %s", last_error()); + + for (size_t i = 0; i < count; i++) + free(identifiers[i]); + free(identifiers); + + return result; } } - // Output the oldest container. - *_container = strdup(oldest_container); - // Free the list of containers. - for (size_t i = 0; containers[i] != NULL; i++) - free(containers[i]); - free(containers); + for (size_t i = 0; i < count; i++) + free(identifiers[i]); + free(identifiers); + + verbose("The oldest container in the pool is '%s'.", oldest_identifier); + + // Set the oldest container identifier. + *_identifier = oldest_identifier; return success(); } -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, config); - if (result != success()) - return result; +result_t reserve_container_pool_space(const config_t* config, size_t space) { + result_t result; - // Calculate the total available space in the container pool. - struct statvfs st; - if (statvfs(pool_path, &st) == -1) { - free(pool_path); - return failure("Failed to calculate the total available space in the container pool: %s.", strerror(errno)); - } - - // 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_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; + // Get the available space in the container pool. + size_t available_space; + result = get_container_pool_space(&available_space, config); + if (result != success()) { + result = failure("Failed to reserve the container pool space due to a failure to get the available space."); + verbose("Exception -> %s", last_error()); + + return result; + } + + // Check if the available space is sufficient. + if (available_space >= space) { + verbose("Successfully reserved %zu bytes in the container pool.", space); - // Check whether the total available space is enough. - if (available_space >= size) return success(); + } + + verbose("Insufficient space in the container pool (Wanted: %zu bytes, Available: %zu bytes). Removing the oldest container.", space, available_space); + + // Find the oldest container. + char* oldest_identifier; + result = find_oldest_container(&oldest_identifier, config); + if (result != success()) { + result = failure("Failed to reserve the container pool space due to a failure to find the oldest container."); + verbose("Exception -> %s", last_error()); - // Get the oldest container. - char* container; - result = get_oldest_container(&container, config); - if (result != success()) return result; + } + + // Check if there are no containers to remove. + if (oldest_identifier == NULL) { + result = failure("Insufficient space in the container pool (Wanted: %zu bytes, Available: %zu bytes), and no more containers to remove.", space, available_space); + verbose("Exception -> %s", last_error()); + + return result; + } // Remove the oldest container. - result = remove_container(config, container); - free(container); - if (result != success()) - return result; - } + result = remove_container(config, oldest_identifier); + if (result != success()) { + result = failure("Failed to reserve the container pool space due to a failure to remove the oldest container."); + verbose("Exception -> %s", last_error()); - return success(); + free(oldest_identifier); + + return result; + } + + // Free the oldest container identifier. + free(oldest_identifier); + } } diff --git a/src/container.h b/src/container.h index 8998218..1502c5c 100644 --- a/src/container.h +++ b/src/container.h @@ -6,82 +6,84 @@ /// @brief The maximum length of a container identifier. #define MAX_CONTAINER_IDENTIFIER_LENGTH 64 -/// @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. +/// @brief Checks whether the specified container identifier is valid. This call will return an error with a message describing the problem if the identifier is invalid. +/// @param identifier The identifier to check. /// @return The result of the operation. -result_t check_container_identifier(const char* container); +result_t check_container_identifier(const char* identifier); -/// @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. +/// @brief Checks whether the specified container exists. +/// @param _exists The pointer to where the result should be stored. +/// @param config The program configuration to use. +/// @param identifier The identifier of the container to check. +/// @return The result of the operation. +result_t check_container_exists(bool* _exists, const config_t* config, const char* identifier); + +/// @brief Returns the path of the container pool directory. +/// @param _path The pointer to where the path should be stored. The caller is responsible for freeing the memory. /// @param config The program configuration to use. /// @return The result of the operation. result_t get_container_pool_path(char** _path, const config_t* config); /// @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 _path The pointer to where the path should be stored. The caller is responsible for freeing the memory. /// @param config The program configuration to use. -/// @param container The identifier of the container to get the path of. +/// @param identifier The identifier of the container. /// @return The result of the operation. -result_t get_container_path(char** _path, const config_t* config, const char* container); +result_t get_container_path(char** _path, const config_t* config, const char* identifier); -/// @brief Checks whether the specified container exists. This call returns a failure if the container does not exist. +/// @brief Adds a new root container to the pool, with the specified identifier and size. /// @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 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 identifier 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 config_t* config, const char* container, uint64_t size); +result_t add_root_container(const config_t* config, const char* identifier, size_t size); -/// @brief Adds a new backed container to the container pool, with the specified identifier and backing. +/// @brief Adds a new backed container to the pool, with the specified identifier and backing. /// @param config The program configuration to use. -/// @param container The identifier of the container to add. +/// @param identifier 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 config_t* config, const char* container, const char* backing); +result_t add_backed_container(const config_t* config, const char* identifier, const char* backing); -/// @brief Removes the specified container from the container pool. +/// @brief Removes the specified container from the pool. /// @param config The program configuration to use. -/// @param container The identifier of the container to remove. +/// @param identifier The identifier of the container to remove. /// @return The result of the operation. -result_t remove_container(const config_t* config, const char* container); +result_t remove_container(const config_t* config, const char* identifier); /// @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. +/// @param identifier The identifier of the container to reset. /// @return The result of the operation. -result_t reset_container(const config_t* config, const char* container); +result_t reset_container(const config_t* config, const char* identifier); /// @brief Trims the specified container. /// @param config The program configuration to use. -/// @param container The identifier of the container to trim. +/// @param identifier The identifier of the container to trim. /// @return The result of the operation. -result_t trim_container(const config_t* config, const char* container); +result_t trim_container(const config_t* config, const char* identifier); -/// @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. +/// @brief Lists the containers in the pool. +/// @param _identifiers The pointer to where the list of identifiers should be stored. The caller is responsible for freeing the strings and the array. +/// @param _count The pointer to where the count of identifiers should be stored. /// @param config The program configuration to use. /// @return The result of the operation. -result_t list_containers(char*** _containers, const config_t* config); +result_t list_containers(char*** _identifiers, size_t* _count, const config_t* config); -/// @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. +/// @brief Returns the available space in the pool. +/// @param _space The pointer to where the available space should be stored, in bytes. /// @param config The program configuration to use. /// @return The result of the operation. -result_t get_oldest_container(char** _container, const config_t* config); +result_t get_container_pool_space(size_t* _space, const config_t* config); -/// @brief Calculates the total available space in the container pool. -/// @param _size The pointer to where the size should be stored. +/// @brief Find the oldest container in the pool. +/// @param _identifier The pointer to where the identifier of the oldest container should be stored. /// @param config The program configuration to use. /// @return The result of the operation. -result_t get_available_space(uint64_t* _size, const config_t* config); +result_t find_oldest_container(char** _identifier, const config_t* config); -/// @brief Reserves the specified space in the container pool, by removing the oldest containers. +/// @brief Reserves the specified space in the pool by removing the oldest containers. /// @param config The program configuration to use. -/// @param size The size to reserve, in bytes. +/// @param space The space to reserve, in bytes. /// @return The result of the operation. -result_t reserve_space(const config_t* config, uint64_t size); +result_t reserve_container_pool_space(const config_t* config, size_t space); \ No newline at end of file diff --git a/src/disk.c b/src/disk.c index d587d60..43cf9f6 100644 --- a/src/disk.c +++ b/src/disk.c @@ -1,41 +1,41 @@ #include "disk.h" -#include -#include -#include -#include -#include #include +#include +#include +#include result_t read_disk_info(disk_info_t** _info, const char* disk) { - // Execute qemu-img info to read the disk information. + result_t result; + + // Execute the qemu-img info to get the disk information in JSON format. int exit_code; char* stdout_buffer; char* stderr_buffer; - result_t result = execute_file(&exit_code, &stdout_buffer, NULL, &stderr_buffer, NULL, NULL, "/usr/bin/qemu-img", "info", "--output", "json", disk, NULL); + result = execute_file(&exit_code, &stdout_buffer, NULL, &stderr_buffer, NULL, NULL, "/usr/bin/qemu-img", "info", "--output", "json", disk, NULL); + + if (result != success()) { + result = failure("Failed to execute qemu-img info on the disk '%s'.", disk); + verbose("Exception -> %s", last_error()); - // If the execution failed, return the error. - if (result != success()) return result; + } - // If qemu-img info failed, return the error. + // If the exit code is not 0, return the standard 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] = ' '; - + // Remove all newlines from the standard error. + 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); + result = failure("Failed to get information about the disk '%s' (%s).", disk, error); + verbose("Exception -> %s", last_error()); free(error); + free(stdout_buffer); + free(stderr_buffer); return result; } @@ -43,94 +43,136 @@ result_t read_disk_info(disk_info_t** _info, const char* disk) { // Free the standard error buffer as it is no longer needed. free(stderr_buffer); - // Parse the JSON output. + // Parse the JSON text. json_object* root = json_tokener_parse(stdout_buffer); + if (root == NULL) { + result = failure("Failed to parse the JSON text."); + verbose("Exception -> %s", last_error()); + + free(stdout_buffer); + + return result; + } // Free the standard output buffer as it is no longer needed. free(stdout_buffer); - // If the JSON parsing failed, return the error. - if (root == NULL) - return failure("Failed to parse the JSON output of qemu-img info."); + // Get the virtual size. + json_object* virtual_size_json; + if (!json_object_object_get_ex(root, "virtual-size", &virtual_size_json)) { + result = failure("Failed to get the virtual size from the JSON."); + verbose("Exception -> %s", last_error()); - // 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."); + + return result; } - // Parse the virtual size of the disk. + // Get the virtual size as a uint64_t. errno = 0; - uint64_t virtual_size = json_object_get_uint64(virtual_size_object); + uint64_t virtual_size = json_object_get_int64(virtual_size_json); if (errno != 0) { + result = failure("Failed to parse the virtual size from the JSON (%s).", strerror(errno)); + verbose("Exception -> %s", last_error()); + json_object_put(root); - return failure("Failed to parse the virtual size of the disk (%s).", strerror(errno)); + + return result; } - // Get the actual size of the disk. - json_object* actual_size_object = json_object_object_get(root, "actual-size"); - if (actual_size_object == NULL) { + // Get the actual size. + json_object* actual_size_json; + if (!json_object_object_get_ex(root, "actual-size", &actual_size_json)) { + result = failure("Failed to get the actual size from the JSON."); + verbose("Exception -> %s", last_error()); + json_object_put(root); - return failure("Failed to read the actual size of the disk."); + + return result; } - // Parse the actual size of the disk. + // Get the actual size as a uint64_t. errno = 0; - uint64_t actual_size = json_object_get_uint64(actual_size_object); + uint64_t actual_size = json_object_get_int64(actual_size_json); if (errno != 0) { + result = failure("Failed to parse the actual size from the JSON (%s).", strerror(errno)); + verbose("Exception -> %s", last_error()); + json_object_put(root); - return failure("Failed to parse the actual size of the disk (%s).", strerror(errno)); + + return result; } - // Get the backing file of the disk. + // Get the backing path. 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* backing_path_json; + if (json_object_object_get_ex(root, "backing-filename", &backing_path_json)) { + // Get the backing path string. + const char* backing_path_string = json_object_get_string(backing_path_json); + if (backing_path_string == NULL) { + result = failure("Failed to parse the backing path from the JSON."); + verbose("Exception -> %s", last_error()); + json_object_put(root); - return failure("Failed to read the backing file of the disk (Invalid JSON)."); + + return result; } - // Copy the backing file path. - backing_path = strdup(string); + // Get the backing path. + backing_path = strdup(backing_path_string); if (backing_path == NULL) { + result = failure("Failed to allocate memory for the backing path."); + verbose("Exception -> %s", last_error()); + json_object_put(root); - return failure("Failed to read the backing file of the disk (Failed to duplicate the backing file path)."); + + return result; } - // Extract the file name of the backing path. + // Get the backing name. backing_name = file_name(backing_path); if (backing_name == NULL) { - json_object_put(root); + result = failure("Failed to allocate memory for the backing name."); + verbose("Exception -> %s", last_error()); + free(backing_path); - return failure("Failed to read the backing file of the disk (Failed to extract the backing file name)."); + json_object_put(root); + + return result; } } - // Free the JSON root object as it is no longer needed. + // Free the JSON object as it is no longer needed. json_object_put(root); - // Create the disk information. - disk_info_t* info = malloc(sizeof(disk_info_t)); + // Allocate the disk information. + disk_info_t* info = (disk_info_t*)malloc(sizeof(disk_info_t)); if (info == NULL) { + result = failure("Failed to allocate memory for the disk information."); + verbose("Exception -> %s", last_error()); + free(backing_path); free(backing_name); - return failure("Failed to allocate memory for the disk information."); + + return result; } + // Set 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; + // Set the disk information if requested. + if (_info != NULL) + *_info = info; + else + free_disk_info(info); + + verbose("Successfully read disk information from disk '%s':\n - Virtual size: %lu\n - Actual size: %lu\n - Backing path: %s\n - Backing name: %s", disk, virtual_size, actual_size, backing_path, backing_name); return success(); } @@ -143,41 +185,47 @@ void free_disk_info(disk_info_t* info) { } result_t create_root_disk(const char* disk, uint64_t size) { + result_t result; + // 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)); + if (asprintf(&size_string, "%lu", size) == -1) { + result = failure("Failed to convert the size to a string."); + verbose("Exception -> %s", last_error()); - // Execute qemu-img create to create the root disk. + return result; + } + + // Execute the 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); + 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()) + if (result != success()) { + result = failure("Failed to execute qemu-img create on the root disk '%s'.", disk); + verbose("Exception -> %s", last_error()); + return result; + } - // If qemu-img create failed, return the error. + // If the exit code is not 0, return the standard 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] = ' '; - + // Remove all newlines from the standard error. + 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); + verbose("Exception -> %s", last_error()); free(error); + free(stderr_buffer); return result; } @@ -185,37 +233,41 @@ result_t create_root_disk(const char* disk, uint64_t size) { // Free the standard error buffer as it is no longer needed. free(stderr_buffer); + verbose("Successfully created root disk '%s' with size of %lu bytes.", disk, size); + return success(); } result_t create_backed_disk(const char* disk, const char* backing) { - // Execute qemu-img create to create the backed disk. + result_t result; + + // Execute the 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); + result = execute_file(&exit_code, NULL, NULL, &stderr_buffer, NULL, NULL, "/usr/bin/qemu-img", "create", "-f", "qcow2", "-o", "backing_file", backing, disk, NULL); + + if (result != success()) { + result = failure("Failed to execute qemu-img create on the backed disk '%s'.", disk); + verbose("Exception -> %s", last_error()); - // If the execution failed, return the error. - if (result != success()) return result; + } - // If qemu-img create failed, return the error. + // If the exit code is not 0, return the standard 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] = ' '; - + // Remove all newlines from the standard error. + 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); + verbose("Exception -> %s", last_error()); free(error); + free(stderr_buffer); return result; } @@ -223,37 +275,41 @@ result_t create_backed_disk(const char* disk, const char* backing) { // Free the standard error buffer as it is no longer needed. free(stderr_buffer); + verbose("Successfully created backed disk '%s' with backing disk '%s'.", disk, backing); + return success(); } result_t reback_disk(const char* disk, const char* backing) { - // Execute qemu-img rebase to reback the disk. + result_t result; + + // Execute the qemu-img rebase to rebase 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); + result = execute_file(&exit_code, NULL, NULL, &stderr_buffer, NULL, NULL, "/usr/bin/qemu-img", "rebase", "-u", "-F", "qcow2", "-b", backing, disk, NULL); + + if (result != success()) { + result = failure("Failed to execute qemu-img rebase on the disk '%s'.", disk); + verbose("Exception -> %s", last_error()); - // If the execution failed, return the error. - if (result != success()) return result; + } - // If qemu-img rebase failed, return the error. + // If the exit code is not 0, return the standard 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] = ' '; - + // Remove all newlines from the standard error. + 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); + result = failure("Failed to rebase the disk '%s' to the backing disk '%s' (%s).", disk, backing, error); + verbose("Exception -> %s", last_error()); free(error); + free(stderr_buffer); return result; } @@ -261,6 +317,8 @@ result_t reback_disk(const char* disk, const char* backing) { // Free the standard error buffer as it is no longer needed. free(stderr_buffer); + verbose("Successfully rebacked disk '%s' to backing disk '%s'.", disk, backing); + return success(); } @@ -268,74 +326,92 @@ 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 (result != success()) { + result = failure("Failed to get information about the disk '%s', meaning it cannot be reset.", disk); + verbose("Exception -> %s", last_error()); - 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. + return result; + } + + if (info->backing_path == NULL) + // If the disk is a root disk, create a new root disk with the same size. result = create_root_disk(disk, info->virtual_size); + else + // If the disk is a backed disk, create a new backed disk with the same backing disk. + result = create_backed_disk(disk, info->backing_path); // Free the disk information as it is no longer needed. free_disk_info(info); - return result; + if (result != success()) { + result = failure("Failed to reset the disk '%s'.", disk); + verbose("Exception -> %s", last_error()); + + return result; + } + + verbose("Successfully reset disk '%s'.", disk); + + return success(); } result_t trim_disk(const char* disk) { - // Get a temporary file name to use as the trimmed disk output. + // Get a temporary file name for the trimmed disk. 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)); + if (asprintf(&trimmed_disk, "%s.trimmed", disk) == -1) { + result_t result = failure("Failed to get a temporary file name for the trimmed disk."); + verbose("Exception -> %s", last_error()); + + return result; + } // Get information about the disk. disk_info_t* info; result_t result = read_disk_info(&info, disk); if (result != success()) { + result = failure("Failed to get information about the disk '%s', meaning it cannot be trimmed.", disk); + verbose("Exception -> %s", last_error()); + free(trimmed_disk); + return result; } - // Execute qemu-img convert to trim the disk. + // Execute the 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 + if (info->backing_path == NULL) 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); + else + 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); // Free the disk information as it is no longer needed. free_disk_info(info); - // If the execution failed, return the error. if (result != success()) { + result = failure("Failed to execute qemu-img convert on the disk '%s' to trim it.", disk); + verbose("Exception -> %s", last_error()); + free(trimmed_disk); + return result; } - // If qemu-img convert failed, return the error. + // If the exit code is not 0, return the standard 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] = ' '; - + // Remove all newlines from the standard error. + 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); + verbose("Exception -> %s", last_error()); free(error); - - // Try to remove the temporary file as it may be incomplete. - remove(trimmed_disk); + free(stderr_buffer); free(trimmed_disk); return result; @@ -344,19 +420,31 @@ result_t trim_disk(const char* disk) { // 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)); + // Remove the original disk. + if (remove(disk) != 0) { + result = failure("Failed to remove the original disk '%s' after trimming it.", disk); + verbose("Exception -> %s", last_error()); - // 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. + // Rename the trimmed disk to the original disk. + if (rename(trimmed_disk, disk) != 0) { + result = failure("Failed to rename the trimmed disk '%s' to the original disk '%s'.", trimmed_disk, disk); + verbose("Exception -> %s", last_error()); + + free(trimmed_disk); + + return result; + } + + // Free the temporary file name for the trimmed disk as it is no longer needed. free(trimmed_disk); + verbose("Successfully trimmed disk '%s'.", disk); + return success(); } diff --git a/src/sandbox.c b/src/sandbox.c index ea2f8c5..181c56c 100644 --- a/src/sandbox.c +++ b/src/sandbox.c @@ -1,22 +1,16 @@ #include "utils.h" - #include "disk.h" #include "config.h" #include "container.h" -#include "backing.h" -#include -#include #include -#include +#include 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; - } + if (result != success()) + return -1; free_config(config); } \ No newline at end of file diff --git a/src/sandbox.h b/src/sandbox.h index f46f5d1..30226fa 100644 --- a/src/sandbox.h +++ b/src/sandbox.h @@ -1,4 +1,5 @@ #pragma once -#define CONFIGURATION_FILE "/etc/sandbox.d/config.json" -#define SYNCRONIZATION_FILE "/etc/sandbox.d/sync" \ No newline at end of file +#include "utils.h" + +int main(int argc, char* argv[]); diff --git a/src/utils.c b/src/utils.c index 6e79b1c..2763a80 100644 --- a/src/utils.c +++ b/src/utils.c @@ -8,11 +8,13 @@ #include #include #include +#include #include #include -#include -char ERROR_BUFFER[ERROR_BUFFER_SIZE]; +char ERROR_BUFFER[ERROR_BUFFER_SIZE] = {0}; + +bool VERBOSE = true; result_t success() { return EXIT_SUCCESS; @@ -22,8 +24,13 @@ result_t failure(const char* format, ...) { va_list args; va_start(args, format); - // Write the error message to the error buffer. - vsnprintf(ERROR_BUFFER, ERROR_BUFFER_SIZE, format, args); + // Write the error message into a temporary buffer. + char buffer[ERROR_BUFFER_SIZE]; + vsnprintf(buffer, sizeof(buffer), format, args); + + // Copy the error message into the error buffer. + strncpy(ERROR_BUFFER, buffer, sizeof(ERROR_BUFFER) - 1); + ERROR_BUFFER[ERROR_BUFFER_SIZE - 1] = '\0'; va_end(args); @@ -34,7 +41,33 @@ const char* last_error() { return ERROR_BUFFER; } +void verbose(const char* format, ...) { + // If verbose mode is not enabled, return. + if (!VERBOSE) + return; + + va_list args; + va_start(args, format); + + // Get the current time. + time_t now = time(NULL); + struct tm* time = localtime(&now); + + // Write the message to the standard error. + fprintf(stderr, "\033[90m[%02d:%02d:%02d] ", time->tm_hour, time->tm_min, time->tm_sec); + vfprintf(stderr, format, args); + fprintf(stderr, "\033[0m\n"); + + va_end(args); +} + +void set_verbose(bool verbose) { + VERBOSE = verbose; +} + 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, ...) { + result_t result; + // Count the number of arguments. va_list args; va_start(args, file); @@ -45,12 +78,15 @@ result_t execute_file(int* _exit_code, char** _stdout_buffer, size_t* _stdout_si va_end(args); - // 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 (%s).", strerror(errno)); + // Create the argument list. + const char** argv = (const char**)malloc((argc + 1) * sizeof(const char*)); // The last element is NULL. + if (argv == NULL) { + result = failure("Failed to allocate memory for the argument list (%s).", strerror(errno)); + verbose("Exception -> %s", last_error()); + + return result; + } - // Fill the arguments array. va_start(args, file); argv[0] = file; @@ -60,35 +96,52 @@ result_t execute_file(int* _exit_code, char** _stdout_buffer, size_t* _stdout_si va_end(args); - // Create the pipes for the standard output and error streams. + // Create the standard output pipe. int stdout_pipe[2]; - if (pipe(stdout_pipe) < 0) { - free(argv); - return failure("Failed to create the standard output pipe (%s).", strerror(errno)); + if (pipe(stdout_pipe) == -1) { + result = failure("Failed to create the standard output pipe (%s).", strerror(errno)); + verbose("Exception -> %s", last_error()); + + free((void*)argv); + + return result; } + // Create the standard error pipe. int stderr_pipe[2]; - if (pipe(stderr_pipe) < 0) { + if (pipe(stderr_pipe) == -1) { + result = failure("Failed to create the standard error pipe (%s).", strerror(errno)); + verbose("Exception -> %s", last_error()); + close(stdout_pipe[0]); close(stdout_pipe[1]); - free(argv); - return failure("Failed to create the standard error pipe (%s).", strerror(errno)); + free((void*)argv); + + return result; } + verbose("Opened standard output pipe (%d), and standard error pipe (%d).", stdout_pipe[0], stderr_pipe[0]); + // Fork the process. pid_t pid = fork(); - if (pid < 0) { + if (pid == -1) { + result = failure("Failed to fork the process (%s).", strerror(errno)); + verbose("Exception -> %s", last_error()); + close(stdout_pipe[0]); close(stdout_pipe[1]); close(stderr_pipe[0]); close(stderr_pipe[1]); - free(argv); - return failure("Failed to fork the process (%s).", strerror(errno)); + free((void*)argv); + + return result; } if (pid == 0) { - // Redirect the standard output and error streams to the pipes. + // Child process. + + // Redirect the standard output and error to the pipes. dup2(stdout_pipe[1], STDOUT_FILENO); dup2(stderr_pipe[1], STDERR_FILENO); @@ -98,430 +151,652 @@ result_t execute_file(int* _exit_code, char** _stdout_buffer, size_t* _stdout_si close(stderr_pipe[0]); close(stderr_pipe[1]); - // Change the working directory if necessary. - if (working_directory != NULL) { - if (chdir(working_directory) < 0) { - fprintf(stderr, "Failed to change the working directory to '%s' (%s).\n", working_directory, strerror(errno)); - exit(EXIT_FAILURE); - } + // Change the working directory if specified. + if (working_directory != NULL && chdir(working_directory) == -1) { + fprintf(stderr, "Failed to change the working directory to '%s': %s\n", working_directory, strerror(errno)); + exit(EXIT_FAILURE); } // Execute the file. execv(file, (char* const*)argv); - // If execv returns, it failed. - fprintf(stderr, "Failed to execute the file '%s' (%s).\n", file, strerror(errno)); + // If the execvp call fails, write the error to the standard error. + fprintf(stderr, "Failed to execute the file '%s': %s\n", file, strerror(errno)); + + // Exit the child process. exit(EXIT_FAILURE); } + // Parent process. + + // Free the argument list. + free((void*)argv); + // Close the write end of the pipes. close(stdout_pipe[1]); close(stderr_pipe[1]); - // Free the arguments array as it is no longer needed. - free(argv); - // Wait for the child process to exit. int status; - if (waitpid(pid, &status, 0) < 0) { + if (waitpid(pid, &status, 0) == -1) { + result = failure("Failed to wait for the child process to exit (%s).", strerror(errno)); + verbose("Exception -> %s", last_error()); + close(stdout_pipe[0]); close(stderr_pipe[0]); - return failure("Failed to wait for the child process to exit (%s).", strerror(errno)); + + return result; } - // 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; - } + // Read the standard output into a buffer. + char* stdout_buffer; + size_t stdout_size; + result = read_fd(&stdout_buffer, &stdout_size, stdout_pipe[0]); + if (result != success()) { + result = failure("Failed to read the standard output of the process."); + verbose("Exception -> %s", last_error()); + + close(stdout_pipe[0]); + close(stderr_pipe[0]); + + return result; } + + // Close the read end of the standard output pipe. 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; - } + // Read the standard error into a buffer. + char* stderr_buffer; + size_t stderr_size; + result = read_fd(&stderr_buffer, &stderr_size, stderr_pipe[0]); + if (result != success()) { + result = failure("Failed to read the standard error of the process."); + verbose("Exception -> %s", last_error()); + + close(stderr_pipe[0]); + free(stdout_buffer); + + return result; } + + // Close the read end of the standard error pipe. close(stderr_pipe[0]); - // Output the exit code if necessary. + // Set the exit code if requested. if (_exit_code != NULL) *_exit_code = WEXITSTATUS(status); + // Set the standard output buffer if requested. + if (_stdout_buffer != NULL) + *_stdout_buffer = stdout_buffer; + else + free(stdout_buffer); + + // Set the standard output size if requested. + if (_stdout_size != NULL) + *_stdout_size = stdout_size; + + // Set the standard error buffer if requested. + if (_stderr_buffer != NULL) + *_stderr_buffer = stderr_buffer; + else + free(stderr_buffer); + + // Set the standard error size if requested. + if (_stderr_size != NULL) + *_stderr_size = stderr_size; + + verbose("Successfully executed file '%s' with exit code %d.", file, WEXITSTATUS(status)); + return success(); } result_t read_fd(char** _buffer, size_t* _size, int fd) { + result_t result; + size_t size = 0; size_t capacity = 4096; - // Allocate the buffer. - char* buffer = malloc(capacity); - if (buffer == NULL) - return failure("Failed to allocate memory for the buffer (%s).", strerror(errno)); + // Allocate the initial buffer. + char* buffer = (char*)malloc(capacity); + if (buffer == NULL) { + result = failure("Failed to allocate memory for the buffer (%s).", strerror(errno)); + verbose("Exception -> %s", last_error()); + + return result; + } // Read the file descriptor into the buffer. ssize_t bytes_read; while ((bytes_read = read(fd, buffer + size, capacity - size)) > 0) { size += bytes_read; - // Resize the buffer if necessary. + // If the buffer is full, reallocate it. if (size == capacity) { - // Basic doubling strategy. capacity *= 2; - char* new_buffer = realloc(buffer, capacity); + + char* new_buffer = (char*)realloc(buffer, capacity); if (new_buffer == NULL) { + result = failure("Failed to reallocate memory for the buffer (%s).", strerror(errno)); + verbose("Exception -> %s", last_error()); + free(buffer); - return failure("Failed to reallocate memory for the buffer (%s).", strerror(errno)); + + return result; } + buffer = new_buffer; } } - // Check for read errors. - if (bytes_read < 0) { + // If an error occurred, return it. + if (bytes_read == -1) { + result = failure("Failed to read the file descriptor (%s).", strerror(errno)); + verbose("Exception -> %s", last_error()); + free(buffer); - return failure("Failed to read from the file descriptor (%s).", strerror(errno)); + + return result; } - // Resize the buffer to the actual size. - char* new_buffer = realloc(buffer, size + 1); - if (new_buffer == NULL) { + // Set the buffer if requested. + if (_buffer != NULL) + *_buffer = buffer; + else 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; + // Set the size if requested. if (_size != NULL) *_size = size; + verbose("Successfully read %zu bytes from file descriptor %d.", size, fd); + return success(); } result_t write_fd(int fd, const char* buffer, size_t size) { - size_t bytes_written = 0; + result_t result; // 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; + ssize_t bytes_written = write(fd, buffer, size); + if (bytes_written == -1) { + result = failure("Failed to write to the file descriptor (%s).", strerror(errno)); + verbose("Exception -> %s", last_error()); + + return result; } + verbose("Successfully wrote %zu bytes to file descriptor %d.", size, fd); + return success(); } result_t read_file(char** _buffer, size_t* _size, const char* file) { + result_t result; + // Open the file. int fd = open(file, O_RDONLY); - if (fd < 0) - return failure("Failed to open the file '%s' (%s).", file, strerror(errno)); + if (fd == -1) { + result = failure("Failed to open the file '%s' for reading (%s).", file, strerror(errno)); + verbose("Exception -> %s", last_error()); - // Read the file descriptor into a buffer. - result_t result = read_fd(_buffer, _size, fd); + return result; + } + + verbose("Opened file '%s' for reading with file descriptor %d.", file, fd); + + // Read the file into a buffer. + result = read_fd(_buffer, _size, fd); + if (result != success()) { + result = failure("Failed to read the file '%s'.", file); + verbose("Exception -> %s", last_error()); + + close(fd); + + return result; + } // Close the file. close(fd); - return result; + return success(); } result_t write_file(const char* file, const char* buffer, size_t size, mode_t mode) { + result_t result; + // 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).", file, strerror(errno)); + if (fd == -1) { + result = failure("Failed to open the file '%s' for writing (%s).", file, strerror(errno)); + verbose("Exception -> %s", last_error()); - // Write the buffer to the file descriptor. - result_t result = write_fd(fd, buffer, size); + return result; + } + + verbose("Opened file '%s' for writing with file descriptor %d.", file, fd); + + // Write the buffer to the file. + result = write_fd(fd, buffer, size); + if (result != success()) { + result = failure("Failed to write to the file '%s'.", file); + verbose("Exception -> %s", last_error()); + + close(fd); + + return result; + } // Close the file. close(fd); - return result; + return success(); } result_t copy_fd(int source_fd, int destination_fd) { + result_t result; + + // Read the source file descriptor into a buffer. char buffer[4096]; ssize_t bytes_read; - - // 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. + // 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)); + if (written == -1) { + result = failure("Failed to write to the file descriptor (%s).", strerror(errno)); + verbose("Exception -> %s", last_error()); + + return result; + } + bytes_written += written; } } - // Check for read errors. - if (bytes_read < 0) - return failure("Failed to read from the file descriptor (%s).", strerror(errno)); + // If an error occurred, return it. + if (bytes_read == -1) { + result = failure("Failed to read the file descriptor (%s).", strerror(errno)); + verbose("Exception -> %s", last_error()); + + return result; + } + + verbose("Successfully copied contents from file descriptor %d to file descriptor %d.", source_fd, destination_fd); return success(); } result_t copy_file(const char* source, const char* destination, mode_t mode) { + result_t result; + // 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)); + if (source_fd == -1) { + result = failure("Failed to open the source file '%s' for reading (%s).", source, strerror(errno)); + verbose("Exception -> %s", last_error()); + + return result; + } // Open the destination file. int destination_fd = open(destination, O_WRONLY | O_CREAT | O_TRUNC, mode); - if (destination_fd < 0) { + if (destination_fd == -1) { + result = failure("Failed to open the destination file '%s' for writing (%s).", destination, strerror(errno)); + verbose("Exception -> %s", last_error()); + close(source_fd); - return failure("Failed to open the destination file '%s' (%s).", destination, strerror(errno)); + + return result; } - // Copy the source file to the destination file. - result_t result = copy_fd(source_fd, destination_fd); + verbose("Opened source file '%s' for reading (%d), and destination file '%s' for writing (%d).", source, source_fd, destination, destination_fd); - // Close the files. + // Copy the source file to the destination file. + result = copy_fd(source_fd, destination_fd); + if (result != success()) { + result = failure("Failed to copy the source file '%s' to the destination file '%s'.", source, destination); + verbose("Exception -> %s", last_error()); + + close(source_fd); + close(destination_fd); + + return result; + } + + // Close the source file. close(source_fd); + + // Close the destination file. close(destination_fd); - return result; + return success(); } char* trim(const char* string) { - // Check for NULL. + // Check for null. if (string == NULL) return NULL; - // Skip leading whitespace. - while (*string != '\0' && isspace(*string)) - string++; + // Find the first non-whitespace character. + const char* start = string; + while (isspace(*start)) + start++; - // Find the end of the string. - const char* end = string; - while (*end != '\0') - end++; - - // Skip trailing whitespace. - while (end > string && isspace(*(end - 1))) + // Find the last non-whitespace character. + const char* end = start + strlen(start); + while (end > start && 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); + // Calculate the length of the trimmed string. + size_t length = end - start; + + // Allocate the trimmed string. + char* trimmed = (char*)malloc(length + 1); if (trimmed == NULL) return NULL; - memcpy(trimmed, string, length); + // Copy the trimmed string. + strncpy(trimmed, start, 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, '/'); + const char* file = strrchr(path, '/'); - // If the path separator was found, return the file name after it. - if (file_name != NULL) - return strdup(file_name + 1); + // If the path separator was not found, return the path. + if (file == NULL) + return strdup(path); - // Otherwise, return the entire path. - return strdup(path); + // Return the file name. + return strdup(file + 1); } char* directory_name(const char* path) { - // Check for NULL. - if (path == NULL) + // Find the last occurrence of the path separator. + const char* directory = strrchr(path, '/'); + + // If the path separator was not found, return the current directory. + if (directory == NULL) + return strdup("."); + + // Calculate the length of the directory name. + size_t length = directory - path; + + // Allocate the directory name. + char* name = (char*)malloc(length + 1); + if (name == NULL) return NULL; - // Find the last occurrence of the path separator. - const char* file_name = strrchr(path, '/'); + // Copy the directory name. + strncpy(name, path, length); + name[length] = '\0'; - // 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("."); + return name; } -result_t list_files(char*** _files, const char* directory, result_t (*filter)(const char*)) { +result_t list_files(char*** _files, size_t* _count, const char* directory, result_t (*filter)(const char*)) { + result_t result; + // Open the directory. DIR* dir = opendir(directory); - if (dir == NULL) - return failure("Failed to open the directory '%s' (%s).", directory, strerror(errno)); + if (dir == NULL) { + result = failure("Failed to open the directory '%s' (%s).", directory, strerror(errno)); + verbose("Exception -> %s", last_error()); - // 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."); + return result; } - // Read the files in the directory. + verbose("Opened directory '%s' for listing.", directory); + + size_t count = 0; + size_t capacity = 16; + + // Allocate the initial files array. + char** files = (char**)malloc(capacity * sizeof(char*)); + if (files == NULL) { + result = failure("Failed to allocate memory for the files array (%s).", strerror(errno)); + verbose("Exception -> %s", last_error()); + + closedir(dir); + + return result; + } + + // Read the directory entries. struct dirent* entry; - while ((entry = readdir(dir)) != NULL) { - // Skip the current and parent directories. + for (;;) { + errno = 0; + entry = readdir(dir); + + // If there are no more entries, break. + if (entry == NULL) + break; + + // Skip the current and parent directory entries. if (strcmp(entry->d_name, ".") == 0 || strcmp(entry->d_name, "..") == 0) continue; - // Filter the file if necessary. - if (filter != NULL) { - result_t result = filter(entry->d_name); - if (result != success()) - continue; - } + // Filter the entry if a filter function is provided. + if (filter != NULL && filter(entry->d_name) != success()) + continue; - // Add the file to the list. + // If the files array is full, reallocate it. if (count == capacity) { - // Basic doubling strategy. capacity *= 2; - char** new_files = realloc(files, capacity * sizeof(char*)); + + char** new_files = (char**)realloc(files, capacity * sizeof(char*)); if (new_files == NULL) { - closedir(dir); + result = failure("Failed to reallocate memory for the files array (%s).", strerror(errno)); + verbose("Exception -> %s", last_error()); + for (size_t i = 0; i < count; i++) free(files[i]); free(files); - return failure("Failed to reallocate memory for the files."); + closedir(dir); + + return result; } files = new_files; } + // Add the file to the files array. files[count] = strdup(entry->d_name); if (files[count] == NULL) { - closedir(dir); + result = failure("Failed to allocate memory for the file name (%s).", strerror(errno)); + verbose("Exception -> %s", last_error()); + for (size_t i = 0; i < count; i++) free(files[i]); free(files); - return failure("Failed to allocate memory for the file."); + closedir(dir); + + return result; } count++; } - // Close the directory. - closedir(dir); + // If an error occurred, return it. + if (entry == NULL && errno != 0) { + result = failure("Failed to read the contents of the directory '%s' (%s).", directory, strerror(errno)); + verbose("Exception -> %s", last_error()); - // 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."); + closedir(dir); + + return result; } - new_files[count] = NULL; + // Close the directory. + closedir(dir); - // Output the list of files. - *_files = new_files; + // Reallocate the files array to the actual size. + char** new_files = (char**)realloc(files, (count + 1) * sizeof(char*)); // Add one for the null terminator. + if (new_files == NULL) { + result = failure("Failed to reallocate memory for the files array (%s).", strerror(errno)); + verbose("Exception -> %s", last_error()); + + for (size_t i = 0; i < count; i++) + free(files[i]); + free(files); + + return result; + } + files = new_files; + + // Null-terminate the files array. + files[count] = NULL; + + // Set the files array if requested. + if (_files != NULL) + *_files = files; + else { + for (size_t i = 0; i < count; i++) + free(files[i]); + free(files); + } + + // Set the count if requested. + if (_count != NULL) + *_count = count; + + verbose("Successfully listed %zu files in directory '%s'.", count, directory); return success(); } result_t md5_fd(char** _hash, int fd) { - // Initialize the digest context. + result_t result; + + // Create the MD5 context. EVP_MD_CTX* context = EVP_MD_CTX_new(); - if (context == NULL) - return failure("Failed to create the MD5 digest context."); + if (context == NULL) { + result = failure("Failed to create the MD5 context."); + verbose("Exception -> %s", last_error()); + + return result; + } // Initialize the MD5 algorithm. const EVP_MD* algorithm = EVP_md5(); if (algorithm == NULL) { + result = failure("Failed to get the MD5 algorithm."); + verbose("Exception -> %s", last_error()); + EVP_MD_CTX_free(context); - return failure("Failed to initialize the MD5 algorithm."); + + return result; } - // Initialize the digest. + // Initialize the MD5 digest. if (EVP_DigestInit_ex(context, algorithm, NULL) != 1) { + result = failure("Failed to initialize the MD5 digest."); + verbose("Exception -> %s", last_error()); + EVP_MD_CTX_free(context); - return failure("Failed to initialize the MD5 digest."); + + return result; } + // Read the file descriptor into a buffer. char buffer[4096]; ssize_t bytes_read; - - // Read the file descriptor into the buffer. + ssize_t total_bytes_read = 0; while ((bytes_read = read(fd, buffer, sizeof(buffer))) > 0) { - // Update the digest with the buffer. + // Update the MD5 digest with the buffer. if (EVP_DigestUpdate(context, buffer, bytes_read) != 1) { + result = failure("Failed to update the MD5 digest."); + verbose("Exception -> %s", last_error()); + EVP_MD_CTX_free(context); - return failure("Failed to update the MD5 digest."); + + return result; } + + total_bytes_read += bytes_read; } - // Check for read errors. - if (bytes_read < 0) { + // If an error occurred, return it. + if (bytes_read == -1) { + result = failure("Failed to read the file descriptor."); + verbose("Exception -> %s", last_error()); + EVP_MD_CTX_free(context); - return failure("Failed to read from the file descriptor (%s).", strerror(errno)); + + return result; } - // Finalize the digest. + // Finalize the MD5 digest. unsigned char hash[EVP_MAX_MD_SIZE]; unsigned int length; if (EVP_DigestFinal_ex(context, hash, &length) != 1) { + result = failure("Failed to finalize the MD5 digest."); + verbose("Exception -> %s", last_error()); + EVP_MD_CTX_free(context); - return failure("Failed to finalize the MD5 digest."); + + return result; } - // Free the digest context. + // Free the MD5 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."); + // Allocate the hash. + char* md5 = (char*)malloc(length * 2 + 1); + if (md5 == NULL) { + result = failure("Failed to allocate memory for the MD5 hash."); + verbose("Exception -> %s", last_error()); + return result; + } + + // Convert the hash to a string. for (unsigned int i = 0; i < length; i++) - sprintf(hex_hash + i * 2, "%02x", hash[i]); + sprintf(md5 + i * 2, "%02x", hash[i]); + md5[length * 2] = '\0'; - // Output the hash. - *_hash = hex_hash; + // Set the hash. + *_hash = md5; + + verbose("Calculated MD5 hash '%s' from file descriptor %d (%zd bytes).", md5, fd, total_bytes_read); return success(); } result_t md5_file(char** _hash, const char* file) { + result_t result; + // Open the file. int fd = open(file, O_RDONLY); - if (fd < 0) - return failure("Failed to open the file '%s' (%s).", file, strerror(errno)); + if (fd == -1) { + result = failure("Failed to open the file '%s' for reading (%s).", file, strerror(errno)); + verbose("Exception -> %s", last_error()); + + return result; + } + + verbose("Opened file '%s' for reading with file descriptor %d.", file, fd); // Calculate the MD5 hash of the file. - result_t result = md5_fd(_hash, fd); + result = md5_fd(_hash, fd); + if (result != success()) { + result = failure("Failed to calculate the MD5 hash of the file '%s'.", file); + verbose("Exception -> %s", last_error()); + + close(fd); + + return result; + } // Close the file. close(fd); - return result; + return success(); } diff --git a/src/utils.h b/src/utils.h index 59d6c21..1bfb49d 100644 --- a/src/utils.h +++ b/src/utils.h @@ -2,6 +2,7 @@ #include #include +#include #include #include @@ -14,6 +15,9 @@ typedef int result_t; /// @brief The error buffer, containing the last error message. extern char ERROR_BUFFER[]; +/// @brief The verbose mode, which controls whether verbose messages are logged to the console or not. +extern bool VERBOSE; + /// @brief Returns a result_t representing a successful operation. /// @return The successful result_t. result_t success(); @@ -28,6 +32,15 @@ result_t failure(const char* format, ...); /// @return The last error message. const char* last_error(); +/// @brief Logs a message if verbose mode is enabled. +/// @param format The format string for the message. +/// @param ... The arguments for the format string. +void verbose(const char* format, ...); + +/// @brief Enables or disables the verbose mode. +/// @param verbose The boolean value to set the verbose mode to. +void set_verbose(bool verbose); + /// @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. @@ -102,7 +115,7 @@ char* directory_name(const char* path); /// @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 list_files(char*** _files, const char* directory, result_t (*filter)(const char*)); +result_t list_files(char*** _files, size_t* _count, 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.