#include "container.h" #include "image.h" #include "disk.h" #include #include #include #include #include #include #include result_t check_container_identifier(const char* identifier) { result_t result; // Check that the identifier is not null. if (identifier == NULL) { result = failure("The specified container identifier is null."); verbose("Exception -> %s", last_error()); 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) { free(path); // The container does not exist. *_exists = false; verbose("The container '%s' does not exist.", identifier); 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) { result_t result; // 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* identifier) { result_t 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()); 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", 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(container_pool); // Set the container path. *_path = path; return success(); } 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. 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()); return result; } 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); verbose("Successfully added root container '%s' to the container pool, with a size of %zu bytes.", identifier, size); return success(); } result_t add_backed_container(const config_t* config, const char* identifier, const char* image) { result_t result; // Check that the container does not already exist. 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()); return result; } 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; } // Get the image path. char* image_path; result = get_image_path(&image_path, config, image); if (result != success()) { result = failure("Failed to get the image path."); verbose("Exception -> %s", last_error()); free(path); return result; } // Create the container disk. result = create_backed_disk(path, image_path); if (result != success()) { result = failure("Failed to create the container disk."); verbose("Exception -> %s", last_error()); free(image_path); free(path); return result; } // Free the image path. free(image_path); // Free the container path. free(path); verbose("Successfully added backed container '%s' to the container pool, backed by the image '%s'.", identifier, image); return success(); } result_t remove_container(const config_t* config, const char* identifier) { result_t result; // Check that the container exists. 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()); 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, 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()); free(path); 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* identifier) { result_t result; // Check that the container exists. 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()); 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, identifier); if (result != success()) { result = failure("Failed to get the container path."); verbose("Exception -> %s", last_error()); 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* identifier) { result_t result; // Check that the container exists. 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()); 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, identifier); if (result != success()) { result = failure("Failed to get the container path."); verbose("Exception -> %s", last_error()); 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*** _identifiers, size_t* _count, 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; } 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_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. time_t oldest_time = 0; char* oldest_identifier = NULL; for (size_t i = 0; i < count; i++) { // Get the container path. char* path; 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 access time of the container. struct stat st; 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); } // 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; } } // Free the list of 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 reserve_container_pool_space(const config_t* config, size_t space) { result_t result; for (;;) { // 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); 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()); 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, 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()); free(oldest_identifier); return result; } // Free the oldest container identifier. free(oldest_identifier); } }