linuxinstall/src/container.c

651 lines
16 KiB
C

#include "container.h"
#include "image.h"
#include "disk.h"
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <ctype.h>
#include <errno.h>
#include <sys/stat.h>
#include <sys/statvfs.h>
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);
}
}