Added a verbose logging system

This commit is contained in:
Alexei KADIR 2024-02-29 09:50:28 +01:00
parent a02cd9fe20
commit 0373d159a2
10 changed files with 1345 additions and 1028 deletions

View File

@ -1,362 +0,0 @@
#include "backing.h"
#include "container.h"
#include "disk.h"
#include "sandbox.h"
#include <errno.h>
#include <string.h>
#include <stdlib.h>
#include <stdio.h>
#include <unistd.h>
#include <sys/stat.h>
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;
}

View File

@ -1,31 +1,136 @@
#include "config.h"
#include "sandbox.h"
#include <errno.h>
#include <string.h>
#include <stdlib.h>
#include <stdio.h>
#include <errno.h>
#include <stdlib.h>
#include <string.h>
#include <json-c/json.h>
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);
}

View File

@ -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;

File diff suppressed because it is too large Load Diff

View File

@ -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);

File diff suppressed because it is too large Load Diff

View File

@ -1,22 +1,16 @@
#include "utils.h"
#include "disk.h"
#include "config.h"
#include "container.h"
#include "backing.h"
#include <errno.h>
#include <string.h>
#include <stdio.h>
#include <stdlib.h>
#include <dirent.h>
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);
}

View File

@ -1,4 +1,5 @@
#pragma once
#define CONFIGURATION_FILE "/etc/sandbox.d/config.json"
#define SYNCRONIZATION_FILE "/etc/sandbox.d/sync"
#include "utils.h"
int main(int argc, char* argv[]);

File diff suppressed because it is too large Load Diff

View File

@ -2,6 +2,7 @@
#include <stdint.h>
#include <stdlib.h>
#include <stdbool.h>
#include <inttypes.h>
#include <sys/types.h>
@ -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.