Perfected the disk utilities

This commit is contained in:
Alexei KADIR 2024-02-15 19:42:22 +01:00
parent e6bc757343
commit 01ea511b2d
10 changed files with 581 additions and 1183 deletions

View File

@ -1,227 +0,0 @@
#include "backing.h"
#include "utils.h"
#include "sandbox.h"
#include <stddef.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <dirent.h>
#include <errno.h>
#include <time.h>
#include <stdio.h>
#include <sys/stat.h>
char* get_backings_path(void) {
return format("%s/%s", MASTER_DIRECTORY, BACKINGS_DIRECTORY);
}
bool is_valid_backing_name(const char* name) {
if (name == NULL)
return false;
// Check that the name is not empty
size_t length = strlen(name);
if (length == 0)
return false;
// Check that the name starts with a number, corresponding to the timestamp of the backing disk creation
size_t timestamp_length = 0;
for (size_t i = 0; i < length; i++)
if (name[i] >= '0' && name[i] <= '9')
timestamp_length++;
else
break;
if (timestamp_length == 0)
return false;
return true;
}
char* get_backing_path(const char* backing) {
char* backings_path = get_backings_path();
if (backings_path == NULL)
return NULL;
// Combine the backings path with the backing name
char* backing_path = format("%s/%s", backings_path, backing);
// Free the backings path
free(backings_path);
return backing_path;
}
bool backing_exists(const char* backing) {
char* backing_path = get_backing_path(backing);
if (backing_path == NULL)
return false;
// Check if the backing path exists
struct stat statbuf;
int result = stat(backing_path, &statbuf);
// Free the backing path
free(backing_path);
if (result != 0)
return false;
if (!S_ISREG(statbuf.st_mode))
return false;
return true;
}
char** list_backings(void) {
char* path = get_backings_path();
if (path == NULL)
return NULL;
// Open the directory
DIR* dir = opendir(path);
if (dir == NULL) {
log_msg(LOG_ERROR, "Failed to open directory '%s' (%s).", path, strerror(errno));
free(path);
return NULL;
}
free(path);
// Count the number of entries
size_t count = 0;
struct dirent* entry;
while ((entry = readdir(dir)) != NULL)
if (is_valid_backing_name(entry->d_name) && backing_exists(entry->d_name))
count++;
// Allocate the array of strings
char** backings = malloc((count + 1) * sizeof(char*));
if (backings == NULL) {
log_msg(LOG_ERROR, "Failed to allocate memory for the backings array.");
closedir(dir);
return NULL;
}
// Fill the array of strings
rewinddir(dir);
size_t index = 0;
while ((entry = readdir(dir)) != NULL) {
if (is_valid_backing_name(entry->d_name) && backing_exists(entry->d_name)) {
backings[index] = strdup(entry->d_name);
if (backings[index] == NULL) {
log_msg(LOG_ERROR, "Failed to allocate memory for the backing name.");
for (size_t i = 0; i < index; i++)
free(backings[i]);
free(backings);
closedir(dir);
return NULL;
}
index++;
}
}
// Terminate the array of strings
backings[count] = NULL;
// Close the directory
closedir(dir);
return backings;
}
uint64_t get_backing_creation_time(const char* backing) {
size_t length = strlen(backing);
size_t timestamp_length = 0;
// Find the length of the timestamp
for (size_t i = 0; i < length; i++)
if (backing[i] >= '0' && backing[i] <= '9')
timestamp_length++;
else
break;
// Extract the timestamp
char* timestamp = strndup(backing, timestamp_length);
if (timestamp == NULL)
return 0;
// Convert the timestamp to a number
uint64_t creation_time = strtoull(timestamp, NULL, 10);
// Free the timestamp
free(timestamp);
return creation_time;
}
char* find_latest_backing(void) {
char** backings = list_backings();
if (backings == NULL)
return NULL;
// Find the latest backing disk
char* latest_backing = NULL;
uint64_t latest_time = 0;
for (size_t i = 0; backings[i] != NULL; i++) {
uint64_t time = get_backing_creation_time(backings[i]);
if (time >= latest_time) {
latest_time = time;
latest_backing = backings[i];
}
}
// Duplicate the latest backing disk
if (latest_backing != NULL)
latest_backing = strdup(latest_backing);
// Free the backings
for (size_t i = 0; backings[i] != NULL; i++)
free(backings[i]);
free(backings);
return latest_backing;
}
bool create_backing(const char* disk, const char* description) {
// Create the backing disk name
uint64_t timestamp = (uint64_t)time(NULL);
char* backing_name = NULL;
if (description == NULL || strlen(description) == 0)
backing_name = format("%lu", timestamp);
else
backing_name = format("%lu-%s", timestamp, description);
if (backing_name == NULL) {
log_msg(LOG_ERROR, "Failed to create the backing disk name.");
return false;
}
// Create the backing disk path
char* backings_path = get_backings_path();
if (backings_path == NULL) {
free(backing_name);
return false;
}
char* backing_path = format("%s/%s", backings_path, backing_name);
free(backing_name);
free(backings_path);
if (backing_path == NULL)
return false;
// Copy the disk to the backing disk
if (!copy_file(disk, backing_path, 0755, 0, 0)) {
free(backing_path);
return false;
}
// Free the backing disk path
free(backing_path);
return true;
}

View File

@ -1,42 +0,0 @@
#pragma once
#include <stdbool.h>
#include <stdint.h>
/// @brief Returns the directory path where the backing disks are stored.
/// @return The directory path where the backing disks are stored. The caller is responsible for freeing the returned string. This function can return NULL.
char* get_backings_path(void);
/// @brief Checks if the specified backing disk name is valid.
/// @param name The backing disk name to check.
/// @return true if the backing disk name is valid, otherwise false.
bool is_valid_backing_name(const char* name);
/// @brief Returns the path to the specified backing disk.
/// @param backing The valid backing disk name.
/// @return The path to the specified backing disk. The caller is responsible for freeing the returned string. This function can return NULL.
char* get_backing_path(const char* backing);
/// @brief Checks if the specified backing disk exists.
/// @param backing The valid backing disk name.
/// @return true if the backing disk exists, otherwise false.
bool backing_exists(const char* backing);
/// @brief Lists the backing disks.
/// @return The list of backing disks. The caller is responsible for freeing the returned array and strings. This function can return NULL.
char** list_backings(void);
/// @brief Get the creation time of the specified backing disk.
/// @param backing The valid backing disk name.
/// @return The creation time of the specified backing disk. If the backing disk does not exist, this function returns 0.
uint64_t get_backing_creation_time(const char* backing);
/// @brief Finds the latest backing disk.
/// @return The latest backing disk. The caller is responsible for freeing the returned string. This function can return NULL.
char* find_latest_backing(void);
/// @brief Creates a new backing disk.
/// @param disk The disk to use as the backing disk. Warning: the disk must be part of the backing disks.
/// @param description The description of the backing disk.
/// @return true if the backing disk was created, otherwise false.
bool create_backing(const char* disk, const char* description);

View File

@ -2,120 +2,233 @@
#include "utils.h" #include "utils.h"
#include <stddef.h> #include <errno.h>
#include <stdlib.h>
#include <string.h> #include <string.h>
#include <stdlib.h>
#include <unistd.h>
#include <stdio.h>
#include <sys/types.h>
#include <json-c/json.h> #include <json-c/json.h>
bool create_empty_disk(const char* disk, uint64_t size) { bool create_empty_disk(const char* path, uint64_t size) {
char* stdoutb = NULL; char* errb = NULL;
char* stderrb = NULL;
// Create an empty disk // Convert the size to a string
int result = execute(&stdoutb, &stderrb, "qemu-img", "create", "-f", "qcow2", disk, format("%" PRIu64, size), NULL); char* size_str = format("%lu", size);
if (size_str == NULL) {
logmsg(LOG_ERROR, "Failed to allocate memory for the size string.");
return false;
}
// Check if the command was successful // Create the disk
if (result != 0) { int ret = exec(NULL, &errb, "qemu-img", "create", "-f", "qcow2", path, size_str, NULL);
// Remove the newline character from the stderr buffer
size_t length = strlen(stderrb);
for (size_t i = 0; i < length; i++)
if (stderrb[i] == '\n')
stderrb[i] = ' ';
log_msg(LOG_ERROR, "Failed to create the empty disk %s (%s).", disk, stderrb);
free(stdoutb); free(size_str);
free(stderrb);
// Check for errors
if (ret != 0) {
if (errb == NULL)
logmsg(LOG_ERROR, "Failed to create disk %s.", path);
else {
size_t length = strlen(errb);
for (size_t i = 0; i < length; i++)
if (errb[i] == '\n')
errb[i] = ' ';
logmsg(LOG_ERROR, "Failed to create disk %s (%s).", path, errb);
}
return false; return false;
} }
free(stdoutb);
free(stderrb);
return true; return true;
} }
bool create_backed_disk(const char* disk, const char* backing) { bool create_backed_disk(const char* path, const char* backing_disk) {
char* stdoutb = NULL; char* errb = NULL;
char* stderrb = NULL;
// Create a backed disk // Create the disk
int result = execute(&stdoutb, &stderrb, "qemu-img", "create", "-f", "qcow2", "-F", "qcow2", "-b", backing, disk, NULL); int ret = exec(NULL, &errb, "qemu-img", "create", "-f", "qcow2", "-F", "qcow2", "-b", backing_disk, path, NULL);
// Check if the command was successful // Check for errors
if (result != 0) { if (ret != 0) {
// Remove the newline character from the stderr buffer if (errb == NULL)
size_t length = strlen(stderrb); logmsg(LOG_ERROR, "Failed to create disk %s.", path);
for (size_t i = 0; i < length; i++) else {
if (stderrb[i] == '\n') size_t length = strlen(errb);
stderrb[i] = ' '; for (size_t i = 0; i < length; i++)
log_msg(LOG_ERROR, "Failed to create the backed disk %s (%s).", disk, stderrb); if (errb[i] == '\n')
errb[i] = ' ';
free(stdoutb); logmsg(LOG_ERROR, "Failed to create disk %s (%s).", path, errb);
free(stderrb); }
return false; return false;
} }
free(stdoutb); return true;
free(stderrb); }
bool trim_disk(const char* path) {
char* tmp_path = format("%s.tmp", path);
if (tmp_path == NULL) {
logmsg(LOG_ERROR, "Failed to allocate memory for the temporary disk path.");
return false;
}
DiskInfo info;
if (!get_disk_info(path, &info)) {
free(tmp_path);
return false;
}
char* errb = NULL;
// Create the trimmed disk
int ret = 0;
if (info.backing_file != NULL) {
char* backing_str = format("backing_file=%s", info.backing_file);
if (backing_str == NULL) {
logmsg(LOG_ERROR, "Failed to allocate memory for the backing file string.");
free_disk_info(&info);
free(tmp_path);
return false;
}
ret = exec(NULL, &errb, "qemu-img", "convert", "-f", "qcow2", "-F", "qcow2", "-O", "qcow2", "-o", backing_str, path, tmp_path, NULL);
free(backing_str);
} else
ret = exec(NULL, &errb, "qemu-img", "convert", "-f", "qcow2", "-O", "qcow2", path, tmp_path, NULL);
// Free the disk info as we don't need it anymore
free_disk_info(&info);
// Check for errors
if (ret != 0) {
if (errb == NULL)
logmsg(LOG_ERROR, "Failed to trim disk %s.", path);
else {
size_t length = strlen(errb);
for (size_t i = 0; i < length; i++)
if (errb[i] == '\n')
errb[i] = ' ';
logmsg(LOG_ERROR, "Failed to trim disk %s (%s).", path, errb);
}
unlink(tmp_path);
free(tmp_path);
return false;
}
// Replace the original disk with the trimmed disk
if (rename(tmp_path, path) != 0) {
logmsg(LOG_ERROR, "Failed to replace disk %s with the trimmed disk (%s).", path, strerror(errno));
unlink(tmp_path);
free(tmp_path);
return false;
}
free(tmp_path);
return true;
}
bool rebase_disk(const char* path, const char* backing_disk) {
char* errb = NULL;
// Rebase the disk
int ret = exec(NULL, &errb, "qemu-img", "rebase", "-u", "-F", "qcow2", "-b", backing_disk, path, NULL);
// Check for errors
if (ret != 0) {
if (errb == NULL)
logmsg(LOG_ERROR, "Failed to rebase disk %s.", path);
else {
size_t length = strlen(errb);
for (size_t i = 0; i < length; i++)
if (errb[i] == '\n')
errb[i] = ' ';
logmsg(LOG_ERROR, "Failed to rebase disk %s (%s).", path, errb);
}
return false;
}
return true; return true;
} }
char* get_backing_file(const char* disk) { bool get_disk_info(const char* path, DiskInfo* info) {
char* stdoutb = NULL; char* outb = NULL;
char* stderrb = NULL; char* errb = NULL;
// Get the backing file of the disk int ret = exec(&outb, &errb, "qemu-img", "info", "--output=json", path, NULL);
int result = execute(&stdoutb, &stderrb, "qemu-img", "info", "--output", "json", disk, NULL);
// Check if the command was successful if (ret != 0) {
if (result != 0) { if (errb == NULL)
// Remove the newline character from the stderr buffer logmsg(LOG_ERROR, "Failed to get information about disk %s.", path);
size_t length = strlen(stderrb); else {
for (size_t i = 0; i < length; i++) size_t length = strlen(errb);
if (stderrb[i] == '\n') for (size_t i = 0; i < length; i++)
stderrb[i] = ' '; if (errb[i] == '\n')
log_msg(LOG_ERROR, "Failed to get the backing file of the disk %s (%s).", disk, stderrb); errb[i] = ' ';
free(stdoutb); logmsg(LOG_ERROR, "Failed to get information about disk %s (%s).", path, errb);
free(stderrb); }
return NULL; free(outb);
return false;
} }
free(stderrb); // Free the error buffer, as we don't need it anymore
free(errb);
json_object* root = json_tokener_parse(outb);
// Free the output buffer, as we don't need it anymore
free(outb);
// Parse the JSON output
json_object* root = json_tokener_parse(stdoutb);
if (root == NULL) { if (root == NULL) {
log_msg(LOG_ERROR, "Failed to parse the JSON output of the command."); logmsg(LOG_ERROR, "Failed to parse the JSON output from qemu-img.");
return false;
free(stdoutb);
return NULL;
} }
// Get the backing file json_object* virtual_size = json_object_object_get(root, "virtual-size");
json_object* backing_file = NULL; if (virtual_size == NULL)
if (!json_object_object_get_ex(root, "backing-filename", &backing_file)) { info->virtual_size = 0;
json_object_put(root); else
free(stdoutb); info->virtual_size = json_object_get_int64(virtual_size);
return NULL; json_object* actual_size = json_object_object_get(root, "actual-size");
if (actual_size == NULL)
info->actual_size = 0;
else
info->actual_size = json_object_get_int64(actual_size);
json_object* backing_file = json_object_object_get(root, "backing-filename");
if (backing_file == NULL)
info->backing_file = NULL;
else {
info->backing_file = strdup(json_object_get_string(backing_file));
if (info->backing_file == NULL) {
logmsg(LOG_ERROR, "Failed to allocate memory for the backing file path.");
json_object_put(root);
return false;
}
} }
// Get the backing file as a string0 json_object_put(root);
char* backing_file_str = strdup(json_object_get_string(backing_file)); return true;
if (backing_file_str == NULL) { }
log_msg(LOG_ERROR, "Failed to get the backing file of the disk %s.", disk);
void free_disk_info(DiskInfo* info) {
json_object_put(root); if (info->backing_file != NULL)
free(stdoutb); free(info->backing_file);
return NULL;
}
return backing_file_str;
} }

View File

@ -1,32 +1,43 @@
#pragma once #pragma once
#include <stdbool.h>
#include <stdint.h> #include <stdint.h>
#include <stdbool.h>
/// @brief Creates an empty disk. typedef struct DiskInfo {
/// @param disk The path to the disk to create. Warning: the disk must not exist, otherwise it will be overwritten. uint64_t virtual_size;
/// @param size The size of the disk to create in bytes. uint64_t actual_size;
/// @return true if the disk is created, otherwise false. char* backing_file;
bool create_empty_disk(const char* disk, uint64_t size); } DiskInfo;
/// @brief Creates a backed disk. /// @brief Creates an empty disk at the given path with the given size.
/// @param disk The path to the disk to create. Warning: the disk must not exist, otherwise it will be overwritten. /// @param path The path to the disk. Any existing file at this path will be overwritten.
/// @param backing The path to the backing file, which exists. /// @param size The size of the disk, in bytes.
/// @return true if the disk is created, otherwise false. /// @return True if the disk was created successfully, false otherwise.
bool create_backed_disk(const char* disk, const char* backing); bool create_empty_disk(const char* path, uint64_t size);
/// @brief Packs the disk to reduce its size. /// @brief Creates a disk at the given path with the given backing disk.
/// @param disk The path to the disk to pack, which exists. /// @param path The path to the disk.
/// @return true if the disk is packed, otherwise false. /// @param backing_disk The path to the backing disk.
bool pack_disk(const char* disk); /// @return True if the disk was created successfully, false otherwise.
bool create_backed_disk(const char* path, const char* backing_disk);
/// @brief Moves the backing file of a disk. /// @brief Trims the given disk to remove any unused space.
/// @param disk The path to the disk, which exists. /// @param path The path to the disk.
/// @param backing The path to the new backing file, which exists. /// @return True if the disk was trimmed successfully, false otherwise.
/// @return true if the backing file is moved, otherwise false. bool trim_disk(const char* path);
bool move_backing_file(const char* disk, const char* backing);
/// @brief Gets the backing file of a disk. /// @brief Changes the backing disk of the given disk.
/// @param disk The path to the disk, which exists. /// @param path The path to the disk.
/// @return The backing file of the disk. The caller is responsible for freeing the returned string. This function can return NULL. /// @param backing_disk The path to the new backing disk.
char* get_backing_file(const char* disk); /// @return True if the disk was rebased successfully, false otherwise.
bool rebase_disk(const char* path, const char* backing_disk);
/// @brief Gets information about the given disk.
/// @param path The path to the disk.
/// @param info The DiskInfo struct to fill with information about the disk.
/// @return True if the disk information was retrieved successfully, false otherwise.
bool get_disk_info(const char* path, DiskInfo* info);
/// @brief Frees the memory used by the given DiskInfo struct.
/// @param info The DiskInfo struct to free.
void free_disk_info(DiskInfo* info);

View File

@ -1,256 +0,0 @@
#include "entry.h"
#include "sandbox.h"
#include "utils.h"
#include <stddef.h>
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <unistd.h>
#include <inttypes.h>
#include <time.h>
#include <errno.h>
#include <sys/stat.h>
#include <sys/statvfs.h>
#include <dirent.h>
char* get_entries_path(void) {
return format("%s/%s", MASTER_DIRECTORY, ENTRIES_DIRECTORY);
}
bool is_valid_entry_name(const char* name) {
if (name == NULL)
return false;
// Check that the name is not empty
size_t length = strlen(name);
if (length == 0)
return false;
// Check that the name does not contain /
for (size_t i = 0; i < length; i++)
if (name[i] == '/')
return false;
// Check that the name is not . or ..
if (strcmp(name, ".") == 0 || strcmp(name, "..") == 0)
return false;
return true;
}
char* get_entry_path(const char* entry) {
char* entries_path = get_entries_path();
if (entries_path == NULL)
return NULL;
// Combine the entries path with the entry name
char* entry_path = format("%s/%s", entries_path, entry);
// Free the entries path
free(entries_path);
return entry_path;
}
bool entry_exists(const char* entry) {
char* entry_path = get_entry_path(entry);
if (entry_path == NULL)
return false;
// Check if the entry path exists
struct stat statbuf;
int result = stat(entry_path, &statbuf);
// Free the entry path
free(entry_path);
if (result != 0)
return false;
if (!S_ISDIR(statbuf.st_mode))
return false;
return true;
}
char* get_entry_disk_path(const char* entry) {
char* entry_path = get_entry_path(entry);
if (entry_path == NULL)
return NULL;
// Combine the entry path with the disk name
char* disk_path = format("%s/%s", entry_path, DISK_PATH);
// Free the entry path
free(entry_path);
return disk_path;
}
char** list_entries(void) {
char* path = get_entries_path();
if (path == NULL)
return NULL;
// Open the directory
DIR* dir = opendir(path);
if (dir == NULL) {
log_msg(LOG_ERROR, "Failed to open directory '%s' (%s).", path, strerror(errno));
free(path);
return NULL;
}
free(path);
// Count the number of entries
size_t count = 0;
struct dirent* entry;
while ((entry = readdir(dir)) != NULL)
if (is_valid_entry_name(entry->d_name) && entry_exists(entry->d_name))
count++;
// Allocate the array of entries
char** entries = malloc((count + 1) * sizeof(char*));
if (entries == NULL) {
log_msg(LOG_ERROR, "Failed to allocate memory for the entries array.");
closedir(dir);
return NULL;
}
// Fill the array of entries
rewinddir(dir);
size_t index = 0;
while ((entry = readdir(dir)) != NULL) {
if (is_valid_entry_name(entry->d_name) && entry_exists(entry->d_name)) {
entries[index] = strdup(entry->d_name);
if (entries[index] == NULL) {
log_msg(LOG_ERROR, "Failed to allocate memory for the entry name.");
for (size_t i = 0; i < index; i++)
free(entries[i]);
free(entries);
closedir(dir);
return NULL;
}
index++;
}
}
// Terminate the array of entries
entries[count] = NULL;
// Close the directory
closedir(dir);
return entries;
}
uint64_t get_entry_last_access(const char* entry) {
char* disk = get_entry_disk_path(entry);
if (disk == NULL)
return 0;
// Get the last access time of the disk
struct stat statbuf;
int result = stat(disk, &statbuf);
// Free the disk path
free(disk);
if (result != 0)
return 0;
return statbuf.st_atime;
}
char* find_oldest_entry(void) {
char** entries = list_entries();
if (entries == NULL)
return NULL;
// Find the oldest entry
char* oldest_entry = NULL;
uint64_t oldest_time = UINT64_MAX;
for (size_t i = 0; entries[i] != NULL; i++) {
uint64_t time = get_entry_last_access(entries[i]);
if (time <= oldest_time) {
oldest_time = time;
oldest_entry = entries[i];
}
}
// Duplicate the oldest entry
if (oldest_entry != NULL)
oldest_entry = strdup(oldest_entry);
// Free the entries
for (size_t i = 0; entries[i] != NULL; i++)
free(entries[i]);
free(entries);
return oldest_entry;
}
bool create_entry(const char* entry) {
char* entry_path = get_entry_path(entry);
if (entry_path == NULL)
return false;
// Create the entry directory
bool result = create_directory(entry_path, 0700, 0, 0);
// Free the entry path
free(entry_path);
return result;
}
bool delete_entry(const char* entry) {
char* entry_path = get_entry_path(entry);
if (entry_path == NULL)
return false;
// Delete the entry directory
bool result = delete_directory(entry_path, 0);
// Free the entry path
free(entry_path);
return result;
}
uint64_t get_available_space(void) {
char* entries_path = get_entries_path();
if (entries_path == NULL)
return 0;
// Get the available space in the entries directory
struct statvfs statbuf;
int result = statvfs(entries_path, &statbuf);
// Free the entries path
free(entries_path);
if (result != 0)
return -1;
return statbuf.f_bsize * statbuf.f_bavail;
}
bool reserve_space(uint64_t space) {
// While the available space is not enough, delete the oldest entry
while (get_available_space() < space) {
char* oldest_entry = find_oldest_entry();
if (oldest_entry == NULL)
return false;
bool result = delete_entry(oldest_entry);
free(oldest_entry);
if (!result)
return false;
}
return true;
}

View File

@ -1,62 +0,0 @@
#pragma once
#include <stdbool.h>
#include <stdint.h>
#define DISK_PATH "disk"
/// @brief Returns the directory path where the entries are stored.
/// @return The directory path where the entries are stored. The caller is responsible for freeing the returned string. This function can return NULL.
char* get_entries_path(void);
/// @brief Checks if the specified entry name is valid.
/// @param name The entry name to check.
/// @return true if the entry name is valid, otherwise false.
bool is_valid_entry_name(const char* name);
/// @brief Returns the path to the specified entry.
/// @param entry The valid entry name.
/// @return The path to the specified entry. The caller is responsible for freeing the returned string. This function can return NULL.
char* get_entry_path(const char* entry);
/// @brief Checks if the specified entry exists.
/// @param entry The valid entry name.
/// @return true if the entry exists, otherwise false.
bool entry_exists(const char* entry);
/// @brief Returns the path to the disk of the specified entry.
/// @param entry The valid entry name.
/// @return The path to the disk of the specified entry. The caller is responsible for freeing the returned string. This function can return NULL.
char* get_entry_disk_path(const char* entry);
/// @brief Lists the entries.
/// @return The list of entries. The caller is responsible for freeing the returned array and strings. This function can return NULL.
char** list_entries(void);
/// @brief Returns the last access time of the specified entry.
/// @param entry The valid entry name, which must exist.
/// @return The last access time of the specified entry. If the entry does not exist, this function returns 0.
uint64_t get_entry_last_access(const char* entry);
/// @brief Finds the oldest entry (based on last access time).
/// @return The oldest entry. The caller is responsible for freeing the returned string. This function can return NULL.
char* find_oldest_entry(void);
/// @brief Creates a new entry.
/// @param entry The valid entry name.
/// @return true if the entry was created, otherwise false.
bool create_entry(const char* entry);
/// @brief Deletes the specified entry.
/// @param entry The valid entry name.
/// @return true if the entry was deleted, otherwise false.
bool delete_entry(const char* entry);
/// @brief Returns the available space in the entries directory.
/// @return The available space in the entries directory. If the entries directory does not exist, this function returns 0.
uint64_t get_available_space(void);
/// @brief Reserves the specified space in the entries directory, by deleting the oldest entries.
/// @param space The space to reserve, in bytes.
/// @return true if the space was fully reserved, otherwise false.
bool reserve_space(uint64_t space);

View File

@ -2,30 +2,16 @@
#include "utils.h" #include "utils.h"
#include "backing.h"
#include "entry.h"
#include "disk.h" #include "disk.h"
#include <stdlib.h> #include <stdarg.h>
#include <unistd.h>
#include <stdio.h> #include <stdio.h>
#include <libgen.h> #include <stdlib.h>
#include <string.h>
#include <errno.h>
int main(int argc, char* argv[]) { int main(int argc, char* argv[]) {
char** backings = list_backings(); create_empty_disk("test.qcow2", 1024 * 1024 * 1024);
create_backed_disk("test2.qcow2", "test.qcow2");
rebase_disk("test2.qcow2", "test.qcow2");
if (backings == NULL) { return 0;
fprintf(stderr, "Failed to list the backing disks: %s\n", strerror(errno));
return EXIT_FAILURE;
}
for (size_t i = 0; backings[i] != NULL; i++)
printf("%s\n", backings[i]);
for (size_t i = 0; backings[i] != NULL; i++)
free(backings[i]);
free(backings);
} }

7
src/sandbox.h Executable file → Normal file
View File

@ -1,8 +1,3 @@
#pragma once #pragma once
#define MASTER_DIRECTORY "/var/lib/sandbox"
#define BACKINGS_DIRECTORY "backings"
#define ENTRIES_DIRECTORY "entries"
int main(int argc, char* argv[]); int main(int argc, char* argv[]);

376
src/utils.c Executable file → Normal file
View File

@ -1,16 +1,15 @@
#include "utils.h" #include "utils.h"
#include <string.h>
#include <stdarg.h> #include <stdarg.h>
#include <stdio.h> #include <stdio.h>
#include <stdlib.h> #include <stdlib.h>
#include <time.h> #include <time.h>
#include <unistd.h> #include <unistd.h>
#include <dirent.h> #include <string.h>
#include <errno.h> #include <errno.h>
#include <sys/wait.h> #include <fcntl.h>
#include <sys/types.h> #include <sys/types.h>
#include <sys/stat.h> #include <sys/wait.h>
char* format(const char* fmt, ...) { char* format(const char* fmt, ...) {
va_list args; va_list args;
@ -27,7 +26,7 @@ char* format(const char* fmt, ...) {
// Allocate a buffer for the formatted string // Allocate a buffer for the formatted string
char* buffer = calloc(length, sizeof(char)); char* buffer = calloc(length, sizeof(char));
if (buffer == NULL) { if (buffer == NULL) {
log_msg(LOG_ERROR, "Failed to allocate memory for the formatted string."); logmsg(LOG_ERROR, "Failed to allocate memory for the formatted string.");
return NULL; return NULL;
} }
@ -41,7 +40,7 @@ char* format(const char* fmt, ...) {
return buffer; return buffer;
} }
void log_msg(LogLevel level, const char* fmt, ...) { void logmsg(LogLevel level, const char* fmt, ...) {
va_list args; va_list args;
va_start(args, fmt); va_start(args, fmt);
@ -67,7 +66,7 @@ void log_msg(LogLevel level, const char* fmt, ...) {
level_str = "ERROR"; level_str = "ERROR";
break; break;
default: default:
color = ""; color = "\033[0;31m";
level_str = "UNKNOWN"; level_str = "UNKNOWN";
break; break;
} }
@ -91,34 +90,61 @@ void log_msg(LogLevel level, const char* fmt, ...) {
va_end(args); va_end(args);
} }
int execute(char** stdout_buffer, char** stderr_buffer, const char* file, ...) { int exec(char** outb, char** errb, const char* file, ...) {
// Count the number of arguments // Count the number of arguments
int argc = 1; // Include space for the file name int argc = 1; // The first argument is the file name
va_list args; va_list args;
va_start(args, file); va_start(args, file);
while (va_arg(args, char*) != NULL)
while (va_arg(args, const char*) != NULL)
argc++; argc++;
va_end(args); va_end(args);
// Allocate an array for the arguments // Allocate an array for the arguments
char** argv = calloc(argc + 1, sizeof(char*)); // Include space for the NULL terminator char** argv = calloc(argc + 1, sizeof(char*));
if (argv == NULL) { if (argv == NULL) {
log_msg(LOG_ERROR, "Failed to allocate memory for the argument array."); logmsg(LOG_ERROR, "Failed to allocate memory for the arguments array.");
return -1; return -1;
} }
// Fill the argument array // Fill the arguments array
va_start(args, file); va_start(args, file);
argv[0] = strdup(file); argv[0] = strdup(file);
for (int i = 1; i < argc; i++) if (argv[0] == NULL) {
argv[i] = strdup(va_arg(args, char*)); logmsg(LOG_ERROR, "Failed to allocate memory for the file name.");
free(argv);
va_end(args);
return -1;
}
for (int i = 1; i < argc; i++) {
argv[i] = strdup(va_arg(args, const char*));
if (argv[i] == NULL) {
logmsg(LOG_ERROR, "Failed to allocate memory for the argument %d.", i);
for (int j = 0; j < i; j++)
free(argv[j]);
free(argv);
va_end(args);
return -1;
}
}
argv[argc] = NULL; argv[argc] = NULL;
va_end(args); va_end(args);
// Create the stdout pipe for the child process // Create a pipe for the standard output
int stdout_pipe[2]; int outp[2];
if (pipe(stdout_pipe) == -1) { if (pipe(outp) != 0) {
log_msg(LOG_ERROR, "Failed to create pipe for stdout."); logmsg(LOG_ERROR, "Failed to create a pipe for the standard output.");
for (int i = 0; i < argc; i++) for (int i = 0; i < argc; i++)
free(argv[i]); free(argv[i]);
@ -127,13 +153,13 @@ int execute(char** stdout_buffer, char** stderr_buffer, const char* file, ...) {
return -1; return -1;
} }
// Create the stderr pipe for the child process // Create a pipe for the standard error
int stderr_pipe[2]; int errp[2];
if (pipe(stderr_pipe) == -1) { if (pipe(errp) != 0) {
log_msg(LOG_ERROR, "Failed to create pipe for stderr."); logmsg(LOG_ERROR, "Failed to create a pipe for the standard error.");
close(stdout_pipe[0]); close(outp[0]);
close(stdout_pipe[1]); close(outp[1]);
for (int i = 0; i < argc; i++) for (int i = 0; i < argc; i++)
free(argv[i]); free(argv[i]);
@ -142,16 +168,16 @@ int execute(char** stdout_buffer, char** stderr_buffer, const char* file, ...) {
return -1; return -1;
} }
// Fork the child process // Fork the process
pid_t pid = fork(); pid_t pid = fork();
if (pid == -1) { if (pid == -1) {
log_msg(LOG_ERROR, "Failed to fork child process."); logmsg(LOG_ERROR, "Failed to fork the process (%s).", strerror(errno));
close(stdout_pipe[0]); close(outp[0]);
close(stdout_pipe[1]); close(outp[1]);
close(stderr_pipe[0]); close(errp[0]);
close(stderr_pipe[1]); close(errp[1]);
for (int i = 0; i < argc; i++) for (int i = 0; i < argc; i++)
free(argv[i]); free(argv[i]);
@ -161,247 +187,137 @@ int execute(char** stdout_buffer, char** stderr_buffer, const char* file, ...) {
} }
if (pid == 0) { if (pid == 0) {
// Redirect stdout and stderr to the pipes // Redirect the standard output
dup2(stdout_pipe[1], STDOUT_FILENO); close(outp[0]);
dup2(stderr_pipe[1], STDERR_FILENO); dup2(outp[1], STDOUT_FILENO);
close(outp[1]);
// Close the pipe file descriptors // Redirect the standard error
close(stdout_pipe[0]); close(errp[0]);
close(stdout_pipe[1]); dup2(errp[1], STDERR_FILENO);
close(stderr_pipe[0]); close(errp[1]);
close(stderr_pipe[1]);
// Execute the command // Execute the command
execvp(file, argv); execvp(file, argv);
// If execvp fails, log an error // If execvp returns, an error occurred
log_msg(LOG_ERROR, "Failed to execute command '%s'.", file); printf("Failed to execute the command %s (%s).\n", file, strerror(errno));
// Free the argument array
for (int i = 0; i < argc; i++)
free(argv[i]);
free(argv);
exit(1); exit(1);
} }
// Close the write end of the pipes // Close the write end of the pipes
close(stdout_pipe[1]); close(outp[1]);
close(stderr_pipe[1]); close(errp[1]);
// Wait for the child process to finish // Wait for the child process to finish
int status; int status;
waitpid(pid, &status, 0); waitpid(pid, &status, 0);
// Read the stdout buffer if (outb != NULL)
char buffer[4096]; *outb = read_file(outp[0]);
ssize_t bytes_read; if (errb != NULL)
*errb = read_file(errp[0]);
if (stdout_buffer != NULL) { // Close the read end of the pipes
size_t stdout_length = 0; close(outp[0]);
*stdout_buffer = NULL; close(errp[0]);
// Read the stdout buffer // Free the arguments array
while ((bytes_read = read(stdout_pipe[0], buffer, sizeof(buffer))) > 0) {
char* new_stdout_buffer = realloc(*stdout_buffer, stdout_length + bytes_read + 1); // Include space for the null terminator
if (new_stdout_buffer == NULL) {
log_msg(LOG_ERROR, "Failed to allocate memory for the stdout buffer.");
free(*stdout_buffer);
close(stdout_pipe[0]);
close(stderr_pipe[0]);
for (int i = 0; i < argc; i++)
free(argv[i]);
free(argv);
return -1;
}
*stdout_buffer = new_stdout_buffer;
memcpy(*stdout_buffer + stdout_length, buffer, bytes_read);
stdout_length += bytes_read;
(*stdout_buffer)[stdout_length] = '\0';
}
}
// Close the read end of the stdout pipe
close(stdout_pipe[0]);
// Read the stderr buffer
if (stderr_buffer != NULL) {
size_t stderr_length = 0;
*stderr_buffer = NULL;
// Read the stderr buffer
while ((bytes_read = read(stderr_pipe[0], buffer, sizeof(buffer))) > 0) {
char* new_stderr_buffer = realloc(*stderr_buffer, stderr_length + bytes_read + 1); // Include space for the null terminator
if (new_stderr_buffer == NULL) {
log_msg(LOG_ERROR, "Failed to allocate memory for the stderr buffer.");
free(*stdout_buffer);
free(*stderr_buffer);
close(stderr_pipe[0]);
for (int i = 0; i < argc; i++)
free(argv[i]);
free(argv);
return -1;
}
*stderr_buffer = new_stderr_buffer;
memcpy(*stderr_buffer + stderr_length, buffer, bytes_read);
stderr_length += bytes_read;
(*stderr_buffer)[stderr_length] = '\0';
}
}
// Close the read end of the stderr pipe
close(stderr_pipe[0]);
// Free the argument array
for (int i = 0; i < argc; i++) for (int i = 0; i < argc; i++)
free(argv[i]); free(argv[i]);
free(argv); free(argv);
// Return the exit status return WEXITSTATUS(status);
return WIFEXITED(status) ? WEXITSTATUS(status) : -1;
} }
bool create_directory(const char* directory, mode_t mode, uid_t uid, gid_t gid) { char* read_file(int fd) {
// Create the directory char buffer[4096];
if (mkdir(directory, mode) != 0) {
log_msg(LOG_ERROR, "Failed to create directory '%s' (%s).", directory, strerror(errno)); // Read the file
return false; ssize_t n;
size_t length = 0;
char* data = NULL;
// Read blocks of data from the file
while ((n = read(fd, buffer, sizeof(buffer))) > 0) {
char* new_data = realloc(data, length + n + 1);
if (new_data == NULL) {
logmsg(LOG_ERROR, "Failed to allocate memory for the file data.");
free(data);
return NULL;
}
data = new_data;
// Copy and null-terminate the data
memcpy(data + length, buffer, n);
data[length + n] = '\0';
length += n;
} }
// Change the owner of the directory // Check for read errors
if (chown(directory, uid, gid) != 0) { if (n < 0) {
log_msg(LOG_ERROR, "Failed to change the owner of directory '%s' (%s).", directory, strerror(errno)); logmsg(LOG_ERROR, "Failed to read the file (%s).", strerror(errno));
return false;
free(data);
return NULL;
} }
return true; return data;
} }
bool delete_directory(const char* directory, int level) { bool copy_file(const char* src, const char* dst, mode_t mode) {
if (level > MAX_RECURSION_LEVEL) {
log_msg(LOG_ERROR, "Too many levels of recursion when deleting directory '%s'.", directory);
return false;
}
// Open the directory
DIR* dir = opendir(directory);
if (dir == NULL) {
log_msg(LOG_ERROR, "Failed to open directory '%s' (%s).", directory, strerror(errno));
return false;
}
// Iterate over the directory entries
struct dirent* entry;
while ((entry = readdir(dir)) != NULL) {
// Skip . and ..
if (strcmp(entry->d_name, ".") == 0 || strcmp(entry->d_name, "..") == 0)
continue;
// Combine the directory path with the entry name
char* entry_path = format("%s/%s", directory, entry->d_name);
if (entry_path == NULL) {
log_msg(LOG_ERROR, "Failed to allocate memory for the entry path.");
closedir(dir);
return false;
}
// Check if the entry is a directory
struct stat statbuf;
int result = stat(entry_path, &statbuf);
if (result != 0) {
log_msg(LOG_ERROR, "Failed to get information about file '%s' (%s).", entry_path, strerror(errno));
free(entry_path);
closedir(dir);
return false;
}
if (S_ISDIR(statbuf.st_mode)) {
// Delete the directory
if (!delete_directory(entry_path, level + 1)) {
log_msg(LOG_ERROR, "Failed to delete directory '%s'.", entry_path);
free(entry_path);
closedir(dir);
return false;
}
} else {
// Delete the file
if (unlink(entry_path) != 0) {
log_msg(LOG_ERROR, "Failed to delete file '%s' (%s).", entry_path, strerror(errno));
free(entry_path);
closedir(dir);
return false;
}
}
free(entry_path);
}
// Close the directory
closedir(dir);
// Delete the directory
if (rmdir(directory) != 0) {
log_msg(LOG_ERROR, "Failed to delete directory '%s' (%s).", directory, strerror(errno));
return false;
}
return true;
}
bool copy_file(const char* source, const char* destination, mode_t mode, uid_t uid, gid_t gid) {
// Open the source file // Open the source file
FILE* source_file = fopen(source, "r"); int src_fd = open(src, O_RDONLY);
if (source_file == NULL) { if (src_fd == -1) {
log_msg(LOG_ERROR, "Failed to open file '%s' for reading (%s).", source, strerror(errno)); logmsg(LOG_ERROR, "Failed to open the source file %s (%s).", src, strerror(errno));
return false; return false;
} }
// Open the destination file // Open the destination file
FILE* destination_file = fopen(destination, "w"); int dst_fd = open(dst, O_WRONLY | O_CREAT | O_TRUNC, mode);
if (destination_file == NULL) { if (dst_fd == -1) {
log_msg(LOG_ERROR, "Failed to open file '%s' for writing (%s).", destination, strerror(errno)); logmsg(LOG_ERROR, "Failed to open the destination file %s (%s).", dst, strerror(errno));
fclose(source_file);
close(src_fd);
return false; return false;
} }
// Copy the file // Copy the file
char buffer[4096]; char buffer[4096];
size_t bytes_read; ssize_t n;
while ((bytes_read = fread(buffer, 1, sizeof(buffer), source_file)) > 0)
if (fwrite(buffer, 1, bytes_read, destination_file) != bytes_read) { while ((n = read(src_fd, buffer, sizeof(buffer))) > 0) {
log_msg(LOG_ERROR, "Failed to write to file '%s' (%s).", destination, strerror(errno)); if (write(dst_fd, buffer, n) != n) {
fclose(source_file); logmsg(LOG_ERROR, "Failed to write to the destination file %s (%s).", dst, strerror(errno));
fclose(destination_file);
close(src_fd);
close(dst_fd);
return false; return false;
} }
}
// Check for read errors
if (n < 0) {
logmsg(LOG_ERROR, "Failed to read the source file %s (%s).", src, strerror(errno));
close(src_fd);
close(dst_fd);
return false;
}
// Close the files // Close the files
fclose(source_file); close(src_fd);
fclose(destination_file); close(dst_fd);
// Change the mode of the destination file
if (chmod(destination, mode) != 0) {
log_msg(LOG_ERROR, "Failed to change the mode of file '%s' (%s).", destination, strerror(errno));
return false;
}
// Change the owner of the destination file
if (chown(destination, uid, gid) != 0) {
log_msg(LOG_ERROR, "Failed to change the owner of file '%s' (%s).", destination, strerror(errno));
return false;
}
return true; return true;
} }

46
src/utils.h Executable file → Normal file
View File

@ -1,12 +1,9 @@
#pragma once #pragma once
#include <stdbool.h> #include <stdbool.h>
#include <stdint.h> #include <stdint.h>
#include <sys/types.h> #include <sys/types.h>
#define MAX_RECURSION_LEVEL 256
/// @brief The log levels.
typedef enum { typedef enum {
LOG_DEBUG, LOG_DEBUG,
LOG_INFO, LOG_INFO,
@ -14,45 +11,12 @@ typedef enum {
LOG_ERROR, LOG_ERROR,
} LogLevel; } LogLevel;
/// @brief Formats a string using the specified format and arguments.
/// @param fmt The format string.
/// @param ... The arguments to use when formatting the string.
/// @return The formatted string. The caller is responsible for freeing the returned string. This function can return NULL.
char* format(const char* fmt, ...); char* format(const char* fmt, ...);
/// @brief Logs a formatted message to the console. void logmsg(LogLevel level, const char* fmt, ...);
/// @param level The log level.
/// @param fmt The format string.
/// @param ... The arguments to use when formatting the string.
void log_msg(LogLevel level, const char* fmt, ...);
/// @brief Executes a command and returns the exit code. int exec(char** outb, char** errb, const char* file, ...);
/// @param stdout_buffer A pointer to a buffer that will receive the standard output of the command. The caller is responsible for freeing the buffer. This parameter can be NULL.
/// @param stderr_buffer A pointer to a buffer that will receive the standard error of the command. The caller is responsible for freeing the buffer. This parameter can be NULL.
/// @param file The path to the command to execute.
/// @param ... The arguments to pass to the command, followed by a NULL pointer. No need to include the command name in the arguments.
/// @return The exit code of the command.
int execute(char** stdout_buffer, char** stderr_buffer, const char* file, ...);
/// @brief Creates the specified directory. char* read_file(int fd);
/// @param directory The directory to create.
/// @param mode The mode to use when creating the directory.
/// @param uid The user ID to use when creating the directory.
/// @param gid The group ID to use when creating the directory.
/// @return true if the directory was created, otherwise false.
bool create_directory(const char* directory, mode_t mode, uid_t uid, gid_t gid);
/// @brief Deletes the specified directory and all its contents. bool copy_file(const char* src, const char* dst, mode_t mode);
/// @param directory The directory to delete.
/// @param level The current level of recursion. This parameter is used internally and should be set to 0.
/// @return true if the directory was deleted, otherwise false.
bool delete_directory(const char* directory, int level);
/// @brief Copies the specified file to the specified destination.
/// @param source The source file to copy.
/// @param destination The destination file.
/// @param mode The mode to use when creating the destination file.
/// @param uid The user ID to use when creating the destination file.
/// @param gid The group ID to use when creating the destination file.
/// @return true if the file was copied, otherwise false.
bool copy_file(const char* source, const char* destination, mode_t mode, uid_t uid, gid_t gid);