Perfected the disk utilities
This commit is contained in:
parent
e6bc757343
commit
01ea511b2d
227
src/backing.c
227
src/backing.c
@ -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;
|
||||
}
|
@ -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);
|
261
src/disk.c
261
src/disk.c
@ -2,120 +2,233 @@
|
||||
|
||||
#include "utils.h"
|
||||
|
||||
#include <stddef.h>
|
||||
#include <stdlib.h>
|
||||
#include <errno.h>
|
||||
#include <string.h>
|
||||
#include <stdlib.h>
|
||||
#include <unistd.h>
|
||||
#include <stdio.h>
|
||||
#include <sys/types.h>
|
||||
#include <json-c/json.h>
|
||||
|
||||
bool create_empty_disk(const char* disk, uint64_t size) {
|
||||
char* stdoutb = NULL;
|
||||
char* stderrb = NULL;
|
||||
bool create_empty_disk(const char* path, uint64_t size) {
|
||||
char* errb = NULL;
|
||||
|
||||
// Create an empty disk
|
||||
int result = execute(&stdoutb, &stderrb, "qemu-img", "create", "-f", "qcow2", disk, format("%" PRIu64, size), NULL);
|
||||
// Convert the size to a string
|
||||
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
|
||||
if (result != 0) {
|
||||
// Remove the newline character from the stderr buffer
|
||||
size_t length = strlen(stderrb);
|
||||
// Create the disk
|
||||
int ret = exec(NULL, &errb, "qemu-img", "create", "-f", "qcow2", path, size_str, NULL);
|
||||
|
||||
free(size_str);
|
||||
|
||||
// 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 (stderrb[i] == '\n')
|
||||
stderrb[i] = ' ';
|
||||
log_msg(LOG_ERROR, "Failed to create the empty disk %s (%s).", disk, stderrb);
|
||||
if (errb[i] == '\n')
|
||||
errb[i] = ' ';
|
||||
|
||||
free(stdoutb);
|
||||
free(stderrb);
|
||||
logmsg(LOG_ERROR, "Failed to create disk %s (%s).", path, errb);
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
free(stdoutb);
|
||||
free(stderrb);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool create_backed_disk(const char* disk, const char* backing) {
|
||||
char* stdoutb = NULL;
|
||||
char* stderrb = NULL;
|
||||
bool create_backed_disk(const char* path, const char* backing_disk) {
|
||||
char* errb = NULL;
|
||||
|
||||
// Create a backed disk
|
||||
int result = execute(&stdoutb, &stderrb, "qemu-img", "create", "-f", "qcow2", "-F", "qcow2", "-b", backing, disk, NULL);
|
||||
// Create the disk
|
||||
int ret = exec(NULL, &errb, "qemu-img", "create", "-f", "qcow2", "-F", "qcow2", "-b", backing_disk, path, NULL);
|
||||
|
||||
// Check if the command was successful
|
||||
if (result != 0) {
|
||||
// Remove the newline character from the stderr buffer
|
||||
size_t length = strlen(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 (stderrb[i] == '\n')
|
||||
stderrb[i] = ' ';
|
||||
log_msg(LOG_ERROR, "Failed to create the backed disk %s (%s).", disk, stderrb);
|
||||
if (errb[i] == '\n')
|
||||
errb[i] = ' ';
|
||||
|
||||
free(stdoutb);
|
||||
free(stderrb);
|
||||
logmsg(LOG_ERROR, "Failed to create disk %s (%s).", path, errb);
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
free(stdoutb);
|
||||
free(stderrb);
|
||||
return true;
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
char* get_backing_file(const char* disk) {
|
||||
char* stdoutb = NULL;
|
||||
char* stderrb = NULL;
|
||||
bool get_disk_info(const char* path, DiskInfo* info) {
|
||||
char* outb = NULL;
|
||||
char* errb = NULL;
|
||||
|
||||
// Get the backing file of the disk
|
||||
int result = execute(&stdoutb, &stderrb, "qemu-img", "info", "--output", "json", disk, NULL);
|
||||
int ret = exec(&outb, &errb, "qemu-img", "info", "--output=json", path, NULL);
|
||||
|
||||
// Check if the command was successful
|
||||
if (result != 0) {
|
||||
// Remove the newline character from the stderr buffer
|
||||
size_t length = strlen(stderrb);
|
||||
if (ret != 0) {
|
||||
if (errb == NULL)
|
||||
logmsg(LOG_ERROR, "Failed to get information about disk %s.", path);
|
||||
else {
|
||||
size_t length = strlen(errb);
|
||||
for (size_t i = 0; i < length; i++)
|
||||
if (stderrb[i] == '\n')
|
||||
stderrb[i] = ' ';
|
||||
log_msg(LOG_ERROR, "Failed to get the backing file of the disk %s (%s).", disk, stderrb);
|
||||
if (errb[i] == '\n')
|
||||
errb[i] = ' ';
|
||||
|
||||
free(stdoutb);
|
||||
free(stderrb);
|
||||
|
||||
return NULL;
|
||||
logmsg(LOG_ERROR, "Failed to get information about disk %s (%s).", path, errb);
|
||||
}
|
||||
|
||||
free(stderrb);
|
||||
free(outb);
|
||||
return false;
|
||||
}
|
||||
|
||||
// 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) {
|
||||
log_msg(LOG_ERROR, "Failed to parse the JSON output of the command.");
|
||||
|
||||
free(stdoutb);
|
||||
|
||||
return NULL;
|
||||
logmsg(LOG_ERROR, "Failed to parse the JSON output from qemu-img.");
|
||||
return false;
|
||||
}
|
||||
|
||||
// Get the backing file
|
||||
json_object* backing_file = NULL;
|
||||
if (!json_object_object_get_ex(root, "backing-filename", &backing_file)) {
|
||||
json_object_put(root);
|
||||
free(stdoutb);
|
||||
json_object* virtual_size = json_object_object_get(root, "virtual-size");
|
||||
if (virtual_size == NULL)
|
||||
info->virtual_size = 0;
|
||||
else
|
||||
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);
|
||||
|
||||
// Get the backing file as a string0
|
||||
char* backing_file_str = strdup(json_object_get_string(backing_file));
|
||||
if (backing_file_str == NULL) {
|
||||
log_msg(LOG_ERROR, "Failed to get the backing file of the disk %s.", disk);
|
||||
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);
|
||||
free(stdoutb);
|
||||
|
||||
return NULL;
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return backing_file_str;
|
||||
json_object_put(root);
|
||||
return true;
|
||||
}
|
||||
|
||||
void free_disk_info(DiskInfo* info) {
|
||||
if (info->backing_file != NULL)
|
||||
free(info->backing_file);
|
||||
}
|
||||
|
59
src/disk.h
59
src/disk.h
@ -1,32 +1,43 @@
|
||||
#pragma once
|
||||
|
||||
#include <stdbool.h>
|
||||
#include <stdint.h>
|
||||
#include <stdbool.h>
|
||||
|
||||
/// @brief Creates an empty disk.
|
||||
/// @param disk The path to the disk to create. Warning: the disk must not exist, otherwise it will be overwritten.
|
||||
/// @param size The size of the disk to create in bytes.
|
||||
/// @return true if the disk is created, otherwise false.
|
||||
bool create_empty_disk(const char* disk, uint64_t size);
|
||||
typedef struct DiskInfo {
|
||||
uint64_t virtual_size;
|
||||
uint64_t actual_size;
|
||||
char* backing_file;
|
||||
} DiskInfo;
|
||||
|
||||
/// @brief Creates a backed disk.
|
||||
/// @param disk The path to the disk to create. Warning: the disk must not exist, otherwise it will be overwritten.
|
||||
/// @param backing The path to the backing file, which exists.
|
||||
/// @return true if the disk is created, otherwise false.
|
||||
bool create_backed_disk(const char* disk, const char* backing);
|
||||
/// @brief Creates an empty disk at the given path with the given size.
|
||||
/// @param path The path to the disk. Any existing file at this path will be overwritten.
|
||||
/// @param size The size of the disk, in bytes.
|
||||
/// @return True if the disk was created successfully, false otherwise.
|
||||
bool create_empty_disk(const char* path, uint64_t size);
|
||||
|
||||
/// @brief Packs the disk to reduce its size.
|
||||
/// @param disk The path to the disk to pack, which exists.
|
||||
/// @return true if the disk is packed, otherwise false.
|
||||
bool pack_disk(const char* disk);
|
||||
/// @brief Creates a disk at the given path with the given backing disk.
|
||||
/// @param path The path to the disk.
|
||||
/// @param backing_disk The path to the backing 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.
|
||||
/// @param disk The path to the disk, which exists.
|
||||
/// @param backing The path to the new backing file, which exists.
|
||||
/// @return true if the backing file is moved, otherwise false.
|
||||
bool move_backing_file(const char* disk, const char* backing);
|
||||
/// @brief Trims the given disk to remove any unused space.
|
||||
/// @param path The path to the disk.
|
||||
/// @return True if the disk was trimmed successfully, false otherwise.
|
||||
bool trim_disk(const char* path);
|
||||
|
||||
/// @brief Gets the backing file of a disk.
|
||||
/// @param disk The path to the disk, which exists.
|
||||
/// @return The backing file of the disk. The caller is responsible for freeing the returned string. This function can return NULL.
|
||||
char* get_backing_file(const char* disk);
|
||||
/// @brief Changes the backing disk of the given disk.
|
||||
/// @param path The path to the disk.
|
||||
/// @param backing_disk The path to the new backing 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);
|
256
src/entry.c
256
src/entry.c
@ -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;
|
||||
}
|
62
src/entry.h
62
src/entry.h
@ -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);
|
@ -2,30 +2,16 @@
|
||||
|
||||
#include "utils.h"
|
||||
|
||||
#include "backing.h"
|
||||
#include "entry.h"
|
||||
#include "disk.h"
|
||||
|
||||
#include <stdlib.h>
|
||||
#include <unistd.h>
|
||||
#include <stdarg.h>
|
||||
#include <stdio.h>
|
||||
#include <libgen.h>
|
||||
#include <string.h>
|
||||
#include <errno.h>
|
||||
#include <stdlib.h>
|
||||
|
||||
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) {
|
||||
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);
|
||||
return 0;
|
||||
}
|
7
src/sandbox.h
Executable file → Normal file
7
src/sandbox.h
Executable file → Normal file
@ -1,8 +1,3 @@
|
||||
#pragma once
|
||||
|
||||
#define MASTER_DIRECTORY "/var/lib/sandbox"
|
||||
|
||||
#define BACKINGS_DIRECTORY "backings"
|
||||
#define ENTRIES_DIRECTORY "entries"
|
||||
#pragma once
|
||||
|
||||
int main(int argc, char* argv[]);
|
374
src/utils.c
Executable file → Normal file
374
src/utils.c
Executable file → Normal file
@ -1,16 +1,15 @@
|
||||
#include "utils.h"
|
||||
#include "utils.h"
|
||||
|
||||
#include <string.h>
|
||||
#include <stdarg.h>
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <time.h>
|
||||
#include <unistd.h>
|
||||
#include <dirent.h>
|
||||
#include <string.h>
|
||||
#include <errno.h>
|
||||
#include <sys/wait.h>
|
||||
#include <fcntl.h>
|
||||
#include <sys/types.h>
|
||||
#include <sys/stat.h>
|
||||
#include <sys/wait.h>
|
||||
|
||||
char* format(const char* fmt, ...) {
|
||||
va_list args;
|
||||
@ -27,7 +26,7 @@ char* format(const char* fmt, ...) {
|
||||
// Allocate a buffer for the formatted string
|
||||
char* buffer = calloc(length, sizeof(char));
|
||||
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;
|
||||
}
|
||||
|
||||
@ -41,7 +40,7 @@ char* format(const char* fmt, ...) {
|
||||
return buffer;
|
||||
}
|
||||
|
||||
void log_msg(LogLevel level, const char* fmt, ...) {
|
||||
void logmsg(LogLevel level, const char* fmt, ...) {
|
||||
va_list args;
|
||||
va_start(args, fmt);
|
||||
|
||||
@ -67,7 +66,7 @@ void log_msg(LogLevel level, const char* fmt, ...) {
|
||||
level_str = "ERROR";
|
||||
break;
|
||||
default:
|
||||
color = "";
|
||||
color = "\033[0;31m";
|
||||
level_str = "UNKNOWN";
|
||||
break;
|
||||
}
|
||||
@ -91,34 +90,61 @@ void log_msg(LogLevel level, const char* fmt, ...) {
|
||||
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
|
||||
int argc = 1; // Include space for the file name
|
||||
int argc = 1; // The first argument is the file name
|
||||
|
||||
va_list args;
|
||||
va_start(args, file);
|
||||
while (va_arg(args, char*) != NULL)
|
||||
|
||||
while (va_arg(args, const char*) != NULL)
|
||||
argc++;
|
||||
|
||||
va_end(args);
|
||||
|
||||
// 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) {
|
||||
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;
|
||||
}
|
||||
|
||||
// Fill the argument array
|
||||
// Fill the arguments array
|
||||
va_start(args, file);
|
||||
|
||||
argv[0] = strdup(file);
|
||||
for (int i = 1; i < argc; i++)
|
||||
argv[i] = strdup(va_arg(args, char*));
|
||||
argv[argc] = NULL;
|
||||
if (argv[0] == NULL) {
|
||||
logmsg(LOG_ERROR, "Failed to allocate memory for the file name.");
|
||||
|
||||
free(argv);
|
||||
va_end(args);
|
||||
|
||||
// Create the stdout pipe for the child process
|
||||
int stdout_pipe[2];
|
||||
if (pipe(stdout_pipe) == -1) {
|
||||
log_msg(LOG_ERROR, "Failed to create pipe for stdout.");
|
||||
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;
|
||||
|
||||
va_end(args);
|
||||
|
||||
// Create a pipe for the standard output
|
||||
int outp[2];
|
||||
if (pipe(outp) != 0) {
|
||||
logmsg(LOG_ERROR, "Failed to create a pipe for the standard output.");
|
||||
|
||||
for (int i = 0; i < argc; i++)
|
||||
free(argv[i]);
|
||||
@ -127,13 +153,13 @@ int execute(char** stdout_buffer, char** stderr_buffer, const char* file, ...) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
// Create the stderr pipe for the child process
|
||||
int stderr_pipe[2];
|
||||
if (pipe(stderr_pipe) == -1) {
|
||||
log_msg(LOG_ERROR, "Failed to create pipe for stderr.");
|
||||
// Create a pipe for the standard error
|
||||
int errp[2];
|
||||
if (pipe(errp) != 0) {
|
||||
logmsg(LOG_ERROR, "Failed to create a pipe for the standard error.");
|
||||
|
||||
close(stdout_pipe[0]);
|
||||
close(stdout_pipe[1]);
|
||||
close(outp[0]);
|
||||
close(outp[1]);
|
||||
|
||||
for (int i = 0; i < argc; i++)
|
||||
free(argv[i]);
|
||||
@ -142,16 +168,16 @@ int execute(char** stdout_buffer, char** stderr_buffer, const char* file, ...) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
// Fork the child process
|
||||
// Fork the process
|
||||
pid_t pid = fork();
|
||||
|
||||
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(stdout_pipe[1]);
|
||||
close(stderr_pipe[0]);
|
||||
close(stderr_pipe[1]);
|
||||
close(outp[0]);
|
||||
close(outp[1]);
|
||||
close(errp[0]);
|
||||
close(errp[1]);
|
||||
|
||||
for (int i = 0; i < argc; i++)
|
||||
free(argv[i]);
|
||||
@ -161,247 +187,137 @@ int execute(char** stdout_buffer, char** stderr_buffer, const char* file, ...) {
|
||||
}
|
||||
|
||||
if (pid == 0) {
|
||||
// Redirect stdout and stderr to the pipes
|
||||
dup2(stdout_pipe[1], STDOUT_FILENO);
|
||||
dup2(stderr_pipe[1], STDERR_FILENO);
|
||||
// Redirect the standard output
|
||||
close(outp[0]);
|
||||
dup2(outp[1], STDOUT_FILENO);
|
||||
close(outp[1]);
|
||||
|
||||
// Close the pipe file descriptors
|
||||
close(stdout_pipe[0]);
|
||||
close(stdout_pipe[1]);
|
||||
close(stderr_pipe[0]);
|
||||
close(stderr_pipe[1]);
|
||||
// Redirect the standard error
|
||||
close(errp[0]);
|
||||
dup2(errp[1], STDERR_FILENO);
|
||||
close(errp[1]);
|
||||
|
||||
// Execute the command
|
||||
execvp(file, argv);
|
||||
|
||||
// If execvp fails, log an error
|
||||
log_msg(LOG_ERROR, "Failed to execute command '%s'.", file);
|
||||
|
||||
// Free the argument array
|
||||
for (int i = 0; i < argc; i++)
|
||||
free(argv[i]);
|
||||
free(argv);
|
||||
// If execvp returns, an error occurred
|
||||
printf("Failed to execute the command %s (%s).\n", file, strerror(errno));
|
||||
|
||||
exit(1);
|
||||
}
|
||||
|
||||
// Close the write end of the pipes
|
||||
close(stdout_pipe[1]);
|
||||
close(stderr_pipe[1]);
|
||||
close(outp[1]);
|
||||
close(errp[1]);
|
||||
|
||||
// Wait for the child process to finish
|
||||
int status;
|
||||
waitpid(pid, &status, 0);
|
||||
|
||||
// Read the stdout buffer
|
||||
if (outb != NULL)
|
||||
*outb = read_file(outp[0]);
|
||||
if (errb != NULL)
|
||||
*errb = read_file(errp[0]);
|
||||
|
||||
// Close the read end of the pipes
|
||||
close(outp[0]);
|
||||
close(errp[0]);
|
||||
|
||||
// Free the arguments array
|
||||
for (int i = 0; i < argc; i++)
|
||||
free(argv[i]);
|
||||
free(argv);
|
||||
|
||||
return WEXITSTATUS(status);
|
||||
}
|
||||
|
||||
char* read_file(int fd) {
|
||||
char buffer[4096];
|
||||
ssize_t bytes_read;
|
||||
|
||||
if (stdout_buffer != NULL) {
|
||||
size_t stdout_length = 0;
|
||||
*stdout_buffer = NULL;
|
||||
// Read the file
|
||||
ssize_t n;
|
||||
size_t length = 0;
|
||||
|
||||
// Read the stdout buffer
|
||||
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.");
|
||||
char* data = NULL;
|
||||
|
||||
free(*stdout_buffer);
|
||||
// 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.");
|
||||
|
||||
close(stdout_pipe[0]);
|
||||
close(stderr_pipe[0]);
|
||||
free(data);
|
||||
|
||||
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';
|
||||
}
|
||||
return NULL;
|
||||
}
|
||||
|
||||
// Close the read end of the stdout pipe
|
||||
close(stdout_pipe[0]);
|
||||
data = new_data;
|
||||
|
||||
// Read the stderr buffer
|
||||
if (stderr_buffer != NULL) {
|
||||
size_t stderr_length = 0;
|
||||
*stderr_buffer = NULL;
|
||||
// Copy and null-terminate the data
|
||||
memcpy(data + length, buffer, n);
|
||||
data[length + n] = '\0';
|
||||
|
||||
// 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';
|
||||
}
|
||||
length += n;
|
||||
}
|
||||
|
||||
// Close the read end of the stderr pipe
|
||||
close(stderr_pipe[0]);
|
||||
// Check for read errors
|
||||
if (n < 0) {
|
||||
logmsg(LOG_ERROR, "Failed to read the file (%s).", strerror(errno));
|
||||
|
||||
// Free the argument array
|
||||
for (int i = 0; i < argc; i++)
|
||||
free(argv[i]);
|
||||
free(argv);
|
||||
free(data);
|
||||
|
||||
// Return the exit status
|
||||
return WIFEXITED(status) ? WEXITSTATUS(status) : -1;
|
||||
return NULL;
|
||||
}
|
||||
|
||||
return data;
|
||||
}
|
||||
|
||||
bool create_directory(const char* directory, mode_t mode, uid_t uid, gid_t gid) {
|
||||
// Create the directory
|
||||
if (mkdir(directory, mode) != 0) {
|
||||
log_msg(LOG_ERROR, "Failed to create directory '%s' (%s).", directory, strerror(errno));
|
||||
return false;
|
||||
}
|
||||
|
||||
// Change the owner of the directory
|
||||
if (chown(directory, uid, gid) != 0) {
|
||||
log_msg(LOG_ERROR, "Failed to change the owner of directory '%s' (%s).", directory, strerror(errno));
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool delete_directory(const char* directory, int level) {
|
||||
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) {
|
||||
bool copy_file(const char* src, const char* dst, mode_t mode) {
|
||||
// Open the source file
|
||||
FILE* source_file = fopen(source, "r");
|
||||
if (source_file == NULL) {
|
||||
log_msg(LOG_ERROR, "Failed to open file '%s' for reading (%s).", source, strerror(errno));
|
||||
int src_fd = open(src, O_RDONLY);
|
||||
if (src_fd == -1) {
|
||||
logmsg(LOG_ERROR, "Failed to open the source file %s (%s).", src, strerror(errno));
|
||||
return false;
|
||||
}
|
||||
|
||||
// Open the destination file
|
||||
FILE* destination_file = fopen(destination, "w");
|
||||
if (destination_file == NULL) {
|
||||
log_msg(LOG_ERROR, "Failed to open file '%s' for writing (%s).", destination, strerror(errno));
|
||||
fclose(source_file);
|
||||
int dst_fd = open(dst, O_WRONLY | O_CREAT | O_TRUNC, mode);
|
||||
if (dst_fd == -1) {
|
||||
logmsg(LOG_ERROR, "Failed to open the destination file %s (%s).", dst, strerror(errno));
|
||||
|
||||
close(src_fd);
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
// Copy the file
|
||||
char buffer[4096];
|
||||
size_t bytes_read;
|
||||
while ((bytes_read = fread(buffer, 1, sizeof(buffer), source_file)) > 0)
|
||||
if (fwrite(buffer, 1, bytes_read, destination_file) != bytes_read) {
|
||||
log_msg(LOG_ERROR, "Failed to write to file '%s' (%s).", destination, strerror(errno));
|
||||
fclose(source_file);
|
||||
fclose(destination_file);
|
||||
ssize_t n;
|
||||
|
||||
while ((n = read(src_fd, buffer, sizeof(buffer))) > 0) {
|
||||
if (write(dst_fd, buffer, n) != n) {
|
||||
logmsg(LOG_ERROR, "Failed to write to the destination file %s (%s).", dst, strerror(errno));
|
||||
|
||||
close(src_fd);
|
||||
close(dst_fd);
|
||||
|
||||
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
|
||||
fclose(source_file);
|
||||
fclose(destination_file);
|
||||
|
||||
// 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;
|
||||
}
|
||||
close(src_fd);
|
||||
close(dst_fd);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
46
src/utils.h
Executable file → Normal file
46
src/utils.h
Executable file → Normal file
@ -1,12 +1,9 @@
|
||||
#pragma once
|
||||
#pragma once
|
||||
|
||||
#include <stdbool.h>
|
||||
#include <stdint.h>
|
||||
#include <sys/types.h>
|
||||
|
||||
#define MAX_RECURSION_LEVEL 256
|
||||
|
||||
/// @brief The log levels.
|
||||
typedef enum {
|
||||
LOG_DEBUG,
|
||||
LOG_INFO,
|
||||
@ -14,45 +11,12 @@ typedef enum {
|
||||
LOG_ERROR,
|
||||
} 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, ...);
|
||||
|
||||
/// @brief Logs a formatted message to the console.
|
||||
/// @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, ...);
|
||||
void logmsg(LogLevel level, const char* fmt, ...);
|
||||
|
||||
/// @brief Executes a command and returns the exit code.
|
||||
/// @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, ...);
|
||||
int exec(char** outb, char** errb, const char* file, ...);
|
||||
|
||||
/// @brief Creates the specified directory.
|
||||
/// @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);
|
||||
char* read_file(int fd);
|
||||
|
||||
/// @brief Deletes the specified directory and all its contents.
|
||||
/// @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);
|
||||
bool copy_file(const char* src, const char* dst, mode_t mode);
|
Loading…
Reference in New Issue
Block a user