651 lines
16 KiB
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);
|
|
}
|
|
}
|