Started working on a refactored version

This commit is contained in:
Alexei KADIR 2024-02-17 00:40:09 +01:00
parent 66caca4a7f
commit 85ad0ab103
11 changed files with 254 additions and 762 deletions

View File

@ -1,39 +0,0 @@
#pragma once
#include <stdbool.h>
#include <stdint.h>
#define BACKING_POOL "/var/lib/sandbox/backings"
/// @brief Checks whether the backing pool exists and is a directory.
/// @return True if the backing pool exists and is a directory, false otherwise.
bool check_backing_pool(void);
/// @brief Checks whether the backing disk id is valid.
/// @param backing_id The backing disk id to check.
/// @return True if the backing disk id is valid, false otherwise.
bool is_backing_id_valid(const char* backing_id);
/// @brief Checks whether the backing disk exists.
/// @param backing_id The backing disk id to check.
/// @return True if the backing disk exists, false otherwise.
bool backing_exists(const char* backing_id);
/// @brief Adds a backing disk to the backing pool.
/// @param backing_id The backing disk id to add.
/// @param entry_id The entry from which the backing disk should be added.
/// @return True if the backing disk was added, false otherwise.
bool add_backing(const char* backing_id, const char* entry_id);
/// @brief Removes a backing disk from the backing pool, as well as any backing disks that depend on it.
/// @param backing_id The backing disk id to remove.
/// @return True if the backing disk was removed, false otherwise.
bool remove_backing(const char* backing_id);
/// @brief Lists all backing disks in the backing pool.
/// @return An array of backing disk ids or NULL if an error occurred. The result must be freed with free(), as well as each individual backing disk id.
char** list_backings(void);
/// @brief Clears all backing disks from the backing pool.
/// @return True if all backing disks were removed, false otherwise.
bool clear_backings(void);

View File

@ -1,97 +0,0 @@
#include "command.h"
#include "sandbox.h"
#include <stdio.h>
#include <string.h>
Command COMMANDS[] = {
{CMD_HELP, "help", "[command]", "Shows help for all or a specific command.", "TODO"},
{CMD_VERSION, "version", "", "Shows the version of the program.", "TODO"},
{CMD_SEPARATOR},
{CMD_ADD_ENTRY, "add-entry", "<entry> [--root|-r <size>] [--backing|-b <backing>]", "Adds a new entry to the pool", "TODO"},
{CMD_REMOVE_ENTRY, "remove-entry", "<entry>", "Removes an entry from the pool", "TODO"},
{CMD_LIST_ENTRIES, "list-entries", "", "Lists all entries in the pool", "TODO"},
{CMD_CLEAR_ENTRIES, "clear-entries", "", "Clears all entries from the pool", "TODO"},
{CMD_SEPARATOR},
{CMD_SYNC, "sync", "", "Synchronizes the backing pool with the server", "TODO"},
{CMD_ADD_BACKING, "add-backing", "<entry>", "Adds a backing disk to the pool from the given entry", "TODO"},
{CMD_REMOVE_BACKING, "remove-backing", "<backing>", "Removes a backing disk from the pool", "TODO"},
{CMD_LIST_BACKINGS, "list-backings", "[--tree|-t]", "Lists all backing disks in the pool", "TODO"},
{CMD_CLEAR_BACKINGS, "clear-backings", "", "Clears all backing disks from the pool", "TODO"},
{CMD_SEPARATOR},
{CMD_START, "start", "<entry> [--no-pci] [--vnc <port> <password>] [--iso <iso>]", "Starts the sandbox with the given entry", "TODO"},
{CMD_POWER, "power", "", "Sends a power signal to the sandbox", "TODO"},
{CMD_STOP, "stop", "[--force|-f] [--timeout|-t <timeout>]", "Stops the sandbox", "TODO"},
{CMD_STATUS, "status", "", "Shows the status of the sandbox", "TODO"},
};
Command* find_command(const char* name) {
Command* match = NULL;
// Find all matching commands (starting with the given name)
size_t length = strlen(name);
for (size_t i = 0; i < sizeof(COMMANDS) / sizeof(COMMANDS[0]); i++) {
if (COMMANDS[i].name == NULL)
continue;
// Check the sizes
if (strlen(COMMANDS[i].name) < length)
continue;
if (strncmp(name, COMMANDS[i].name, length) == 0) {
// Check for multiple matches
if (match != NULL)
return NULL;
match = &COMMANDS[i];
}
}
return match;
}
void show_help() {
printf("Usage: sandbox <command> [args]\n\n");
printf("Commands:\n");
for (size_t i = 0; i < sizeof(COMMANDS) / sizeof(COMMANDS[0]); i++) {
if (COMMANDS[i].id == CMD_SEPARATOR) {
printf("\n");
continue;
}
printf(" %s %s\n", COMMANDS[i].name, COMMANDS[i].args);
printf(" %s\n", COMMANDS[i].description);
}
printf("\n");
printf("For more information about a specific command, use 'sandbox help <command>'.\n");
}
int cmd_help(int argc, char** argv) {
if (argc == 0) {
show_help();
return 0;
}
Command* command = find_command(argv[0]);
if (command == NULL) {
printf("Unknown command '%s'.\n", argv[0]);
return 1;
}
printf("Usage: sandbox %s %s\n", command->name, command->args);
printf(" %s\n", command->description);
if (command->details != NULL) {
printf("\n");
printf("%s\n", command->details);
}
return 0;
}
int cmd_version(int argc, char** argv) {
printf("Sandbox version v" SANDBOX_VERSION "\n");
return 0;
}

View File

@ -1,49 +0,0 @@
#pragma once
#include <stdlib.h>
typedef enum {
CMD_UNKNOWN,
CMD_SEPARATOR,
CMD_HELP,
CMD_VERSION,
CMD_ADD_ENTRY,
CMD_REMOVE_ENTRY,
CMD_LIST_ENTRIES,
CMD_CLEAR_ENTRIES,
CMD_SYNC,
CMD_ADD_BACKING,
CMD_REMOVE_BACKING,
CMD_LIST_BACKINGS,
CMD_CLEAR_BACKINGS,
CMD_START,
CMD_POWER,
CMD_STOP,
CMD_STATUS,
} CommandID;
typedef struct {
CommandID id;
const char* name;
const char* args;
const char* description;
const char* details;
} Command;
extern Command COMMANDS[];
/// @brief Finds the best matching command for the given command name.
/// @param name The name of the command to find.
/// @return The best matching command or NULL if no or multiple matches were found. No need to free the result.
Command* find_command(const char* name);
/// @brief Shows the help for all commands.
void show_help();
int cmd_help(int argc, char** argv);
int cmd_version(int argc, char** argv);

View File

@ -1,231 +0,0 @@
#include "disk.h"
#include "utils.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* path, uint64_t size) {
char* errb = NULL;
// Convert the size to a string
char* size_str = format("%lu", size);
if (size_str == NULL)
return false;
// Create the disk
int ret = execute(NULL, &errb, "qemu-img", "create", "-f", "qcow2", path, size_str, NULL);
free(size_str);
// Check for errors
if (ret != 0) {
if (errb == NULL)
log_message(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] = ' ';
log_message(LOG_ERROR, "Failed to create disk %s (%s).", path, errb);
}
return false;
}
return true;
}
bool create_backed_disk(const char* path, const char* backing_disk) {
char* errb = NULL;
// Create the disk
int ret = execute(NULL, &errb, "qemu-img", "create", "-f", "qcow2", "-F", "qcow2", "-b", backing_disk, path, NULL);
// Check for errors
if (ret != 0) {
if (errb == NULL)
log_message(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] = ' ';
log_message(LOG_ERROR, "Failed to create disk %s (%s).", path, errb);
}
return false;
}
return true;
}
bool trim_disk(const char* path) {
char* tmp_path = format("%s.tmp", path);
if (tmp_path == NULL)
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) {
free(tmp_path);
free_disk_info(&info);
return false;
}
ret = execute(NULL, &errb, "qemu-img", "convert", "-f", "qcow2", "-F", "qcow2", "-O", "qcow2", "-o", backing_str, path, tmp_path, NULL);
free(backing_str);
} else
ret = execute(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)
log_message(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] = ' ';
log_message(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) {
log_message(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 = execute(NULL, &errb, "qemu-img", "rebase", "-u", "-F", "qcow2", "-b", backing_disk, path, NULL);
// Check for errors
if (ret != 0) {
if (errb == NULL)
log_message(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] = ' ';
log_message(LOG_ERROR, "Failed to rebase disk %s (%s).", path, errb);
}
return false;
}
return true;
}
bool get_disk_info(const char* path, DiskInfo* info) {
char* outb = NULL;
char* errb = NULL;
int ret = execute(&outb, &errb, "qemu-img", "info", "--output=json", path, NULL);
if (ret != 0) {
if (errb == NULL)
log_message(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 (errb[i] == '\n')
errb[i] = ' ';
log_message(LOG_ERROR, "Failed to get information about disk %s (%s).", path, errb);
}
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);
if (root == NULL) {
log_message(LOG_ERROR, "Failed to parse the JSON output from qemu-img.");
return false;
}
// Get the virtual size
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);
// Get the actual size
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
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) {
log_message(LOG_ERROR, "Failed to allocate memory for the backing file path.");
json_object_put(root);
return false;
}
}
json_object_put(root);
return true;
}
void free_disk_info(DiskInfo* info) {
if (info->backing_file != NULL)
free(info->backing_file);
}

View File

@ -1,43 +0,0 @@
#pragma once
#include <stdbool.h>
#include <stdint.h>
typedef struct DiskInfo {
uint64_t virtual_size;
uint64_t actual_size;
char* backing_file;
} DiskInfo;
/// @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 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 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 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);

23
src/entry.c Normal file
View File

@ -0,0 +1,23 @@
#include "entry.h"
#include "utils.h"
#include <stdlib.h>
#include <string.h>
#include <sys/stat.h>
bool is_entry_id_valid(const char* entry_id) {
if (entry_id == NULL)
return false;
size_t length = strlen(entry_id);
if (length == 0 || length > MAX_ENTRY_LENGTH)
return false;
for (size_t i = 0; i < length; i++)
if (entry_id[i] == '/')
return false;
return true;
}

View File

@ -3,4 +3,10 @@
#include <stdbool.h>
#include <stdint.h>
#define ENTRY_POOL "/var/lib/sandbox/entries"
#define ENTRY_POOL_DIR "/var/lib/sandbox/entries"
#define MAX_ENTRY_LENGTH 64
/// @brief Checks whether the given entry id is valid.
/// @param entry_id The entry id to check.
/// @return True if the entry id is valid, false otherwise.
bool is_entry_id_valid(const char* entry_id);

View File

@ -1,39 +1,134 @@
#include "sandbox.h"
#include "utils.h"
#include "command.h"
#include "entry.h"
#include <stdarg.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
const Command COMMANDS[] = {
{command_help, "help", "[command]", "Prints the help message.",
"Prints the help message for the given command. If no command is given, prints the help message for all commands."},
{command_version, "version", "", "Prints the version.",
"Prints the version of the program."},
{},
{command_add_entry, "add-entry", "<entry id> [--root|-r <size>] [--backed|-b <backing id>]", "Adds an entry to the entry pool.",
"Adds an entry to the entry pool with the given id. The entry can either be a root entry or a backed entry. If the entry is a root entry, the size of the entry must be specified. If the entry is a backed entry, the backing id must be specified. By default, the entry is backed by the latest backing disk available."},
};
int main(int argc, char* argv[]) {
if (argc < 2) {
show_help();
return 0;
if (argc < 2)
return command_help(0, NULL);
size_t input_length = strlen(argv[1]);
const Command* command = NULL;
for (int i = 0; i < ARRAY_SIZE(COMMANDS); i++) {
if (COMMANDS[i].name == NULL)
continue;
// Check that the length of the input is equal or less than the length of the command name
if (input_length > strlen(COMMANDS[i].name))
continue;
// Check that the input matches the command name
if (strncmp(argv[1], COMMANDS[i].name, input_length) == 0) {
// Check for multiple matches
if (command != NULL) {
log_message(LOG_LEVEL_ERROR, "Ambiguous command '%s'.", argv[1]);
return EXIT_FAILURE;
}
Command* command = find_command(argv[1]);
command = &COMMANDS[i];
}
}
// Check if the command is NULL (no matches)
if (command == NULL) {
log_message(LOG_ERROR, "Unknown command '%s'.", argv[1]);
return 1;
log_message(LOG_LEVEL_ERROR, "Unknown command '%s'.", argv[1]);
return EXIT_FAILURE;
}
int consumed = 2;
return command->handler(argc - 2, argv + 2);
}
// TODO: Implement log level parameter
int command_help(int argc, char* argv[]) {
// Check the number of arguments
if (argc == 0) {
fprintf(stdout, "Usage: sandbox [command] [arguments]\n\n");
fprintf(stdout, "Commands:\n");
switch (command->id) {
case CMD_HELP:
return cmd_help(argc - consumed, argv + consumed);
case CMD_VERSION:
return cmd_version(argc - consumed, argv + consumed);
for (int i = 0; i < ARRAY_SIZE(COMMANDS); i++)
if (COMMANDS[i].name == NULL)
printf("\n");
else
fprintf(stdout, " %s %s - %s\n", COMMANDS[i].name, COMMANDS[i].arguments, COMMANDS[i].description);
default:
log_message(LOG_ERROR, "Command '%s' is not implemented.", command->name);
fprintf(stdout, "\nRun 'sandbox help [command]' for more information on a command.\n");
return EXIT_SUCCESS;
} else if (argc == 1) {
// Find the command that matches the given name
const Command* command = NULL;
for (int i = 0; i < ARRAY_SIZE(COMMANDS); i++) {
if (COMMANDS[i].name == NULL)
continue;
if (strcmp(argv[0], COMMANDS[i].name) == 0) {
command = &COMMANDS[i];
break;
}
}
return 0;
// Check if the command is NULL (no matches)
if (command == NULL) {
log_message(LOG_LEVEL_ERROR, "Unknown command '%s'.", argv[0]);
return EXIT_FAILURE;
}
// Print the help message for the command
fprintf(stdout, "Usage: sandbox %s %s\n\n", command->name, command->arguments);
fprintf(stdout, " %s\n", command->details);
return EXIT_SUCCESS;
} else {
log_message(LOG_LEVEL_ERROR, "Too many arguments for 'help' command.");
return EXIT_FAILURE;
}
}
int command_version(int argc, char* argv[]) {
fprintf(stdout, "Sandbox manager v%s\n", VERSION);
return EXIT_SUCCESS;
}
int command_add_entry(int argc, char* argv[]) {
if (argc < 1) {
log_message(LOG_LEVEL_ERROR, "Missing entry id.");
return EXIT_FAILURE;
}
// Extract the entry id
const char* entry_id = argv[0];
// Check if the entry id is valid
if (!is_entry_id_valid(entry_id)) {
log_message(LOG_LEVEL_ERROR, "Invalid entry id '%s'.", entry_id);
return EXIT_FAILURE;
}
// Check if the entry already exists
bool exists;
if (!entry_exists(entry_id, &exists))
return EXIT_FAILURE;
// Create the entry
// TODO
// Add the disk to the entry
// TODO
// If this fails, remove the entry
return EXIT_SUCCESS;
}

View File

@ -1,7 +1,21 @@
#pragma once
#include <stdlib.h>
#define VERSION "0.0.6"
#define SANDBOX_VERSION "1.0.0"
typedef struct {
int (*handler)(int argc, char* argv[]);
const char* name;
const char* arguments;
const char* description;
const char* details;
} Command;
extern const Command COMMANDS[];
int main(int argc, char* argv[]);
int command_help(int argc, char* argv[]);
int command_version(int argc, char* argv[]);
int command_add_entry(int argc, char* argv[]);

View File

@ -2,19 +2,65 @@
#include <stdarg.h>
#include <stdio.h>
#include <stdlib.h>
#include <time.h>
#include <unistd.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h>
#include <fcntl.h>
#include <dirent.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <sys/stat.h>
#include <ftw.h>
char* format(const char* fmt, ...) {
LogLevel log_level = LOG_LEVEL_WARNING;
void set_log_level(LogLevel level) {
log_level = level;
}
void log_message(LogLevel level, const char* format, ...) {
if (level < log_level)
return;
va_list args;
va_start(args, format);
const char* color;
const char* level_str;
// Set the color and level_str based on the log level
switch (level) {
case LOG_LEVEL_DEBUG:
color = "\033[0;90m";
level_str = "DEBUG";
break;
case LOG_LEVEL_INFO:
color = "\033[0;32m";
level_str = "INFO";
break;
case LOG_LEVEL_WARNING:
color = "\033[0;33m";
level_str = "WARNING";
break;
case LOG_LEVEL_ERROR:
color = "\033[0;31m";
level_str = "ERROR";
break;
}
// Get the current time
time_t t = time(NULL);
struct tm* tm_info = localtime(&t);
// Print the label, message and newline
fprintf(stderr, "%s[%02d/%02d/%02d %02d:%02d:%02d - %s]\033[m ", color, tm_info->tm_mday, tm_info->tm_mon + 1, (tm_info->tm_year + 1900) % 100, tm_info->tm_hour, tm_info->tm_min, tm_info->tm_sec, level_str);
vfprintf(stderr, format, args);
fprintf(stderr, "\n");
// Flush the output
fflush(stderr);
va_end(args);
}
Result format(char** out_string, const char* fmt, ...) {
*out_string = NULL;
va_list args;
va_start(args, fmt);
@ -24,15 +70,15 @@ char* format(const char* fmt, ...) {
va_end(args);
if (length <= 0) {
log_message(LOG_ERROR, "Failed to calculate the length of the formatted string.");
return NULL;
log_message(LOG_LEVEL_ERROR, "Failed to calculate the length of the formatted string (%s).", strerror(errno));
return FAILURE;
}
// Allocate a buffer for the formatted string
char* buffer = calloc(length, sizeof(char));
if (buffer == NULL) {
log_message(LOG_ERROR, "Failed to allocate memory for the formatted string.");
return NULL;
log_message(LOG_LEVEL_ERROR, "Failed to allocate memory for the formatted string (%s).", strerror(errno));
return OUT_OF_MEMORY;
}
va_start(args, fmt);
@ -42,237 +88,6 @@ char* format(const char* fmt, ...) {
va_end(args);
return buffer;
}
void log_message(LogLevel level, const char* fmt, ...) {
va_list args;
va_start(args, fmt);
const char* color;
const char* level_str;
// Set the color and level string based on the log level
switch (level) {
case LOG_DEBUG:
color = "\033[0;90m";
level_str = "DEBUG";
break;
case LOG_INFO:
color = "\033[0;34m";
level_str = "INFO";
break;
case LOG_WARN:
color = "\033[0;33m";
level_str = "WARN";
break;
case LOG_ERROR:
color = "\033[0;31m";
level_str = "ERROR";
break;
default:
color = "\033[0;31m";
level_str = "UNKNOWN";
break;
}
// Get the current time
time_t t = time(NULL);
struct tm* tm_info = localtime(&t);
// Print the label
fprintf(stderr, "%s[%02d/%02d/%02d %02d:%02d:%02d - %s]\033[m ", color, tm_info->tm_mday, tm_info->tm_mon + 1, (tm_info->tm_year + 1900) % 100, tm_info->tm_hour, tm_info->tm_min, tm_info->tm_sec, level_str);
// Print the message
vfprintf(stderr, fmt, args);
// Print a newline
fprintf(stderr, "\n");
// Flush the output
fflush(stderr);
va_end(args);
}
int execute(char** outb, char** errb, const char* file, ...) {
// Count the number of arguments
int argc = 1; // The first argument is the file name
va_list args;
va_start(args, file);
while (va_arg(args, const char*) != NULL)
argc++;
va_end(args);
// Allocate an array for the arguments
char** argv = calloc(argc + 1, sizeof(char*));
if (argv == NULL) {
log_message(LOG_ERROR, "Failed to allocate memory for the arguments array.");
return -1;
}
// Fill the arguments array
va_start(args, file);
argv[0] = strdup(file);
if (argv[0] == NULL) {
log_message(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) {
log_message(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) {
log_message(LOG_ERROR, "Failed to create a pipe for the standard output.");
for (int i = 0; i < argc; i++)
free(argv[i]);
free(argv);
return -1;
}
// Create a pipe for the standard error
int errp[2];
if (pipe(errp) != 0) {
log_message(LOG_ERROR, "Failed to create a pipe for the standard error.");
close(outp[0]);
close(outp[1]);
for (int i = 0; i < argc; i++)
free(argv[i]);
free(argv);
return -1;
}
// Fork the process
pid_t pid = fork();
if (pid == -1) {
log_message(LOG_ERROR, "Failed to fork the process (%s).", strerror(errno));
close(outp[0]);
close(outp[1]);
close(errp[0]);
close(errp[1]);
for (int i = 0; i < argc; i++)
free(argv[i]);
free(argv);
return -1;
}
if (pid == 0) {
// Redirect the standard output
close(outp[0]);
dup2(outp[1], STDOUT_FILENO);
close(outp[1]);
// Redirect the standard error
close(errp[0]);
dup2(errp[1], STDERR_FILENO);
close(errp[1]);
// Execute the command
execvp(file, 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(outp[1]);
close(errp[1]);
// Wait for the child process to finish
int status;
waitpid(pid, &status, 0);
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];
// Read the file
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) {
log_message(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;
}
// Check for read errors
if (n < 0) {
log_message(LOG_ERROR, "Failed to read the file (%s).", strerror(errno));
free(data);
return NULL;
}
return data;
*out_string = buffer;
return SUCCESS;
}

View File

@ -1,37 +1,35 @@
#pragma once
#include <stdbool.h>
#include <stdint.h>
#include <sys/types.h>
#define ARRAY_SIZE(x) (sizeof(x) / sizeof((x)[0]))
typedef enum {
LOG_DEBUG,
LOG_INFO,
LOG_WARN,
LOG_ERROR,
SUCCESS,
FAILURE,
OUT_OF_MEMORY,
} Result;
typedef enum {
LOG_LEVEL_DEBUG,
LOG_LEVEL_INFO,
LOG_LEVEL_WARNING,
LOG_LEVEL_ERROR,
} LogLevel;
/// @brief Formats a string using printf-style formatting.
extern LogLevel log_level;
/// @brief Sets the log level.
/// @param level The log level to set.
void set_log_level(LogLevel level);
/// @brief Logs a message.
/// @param level The log level.
/// @param format The format string.
/// @param ... The format arguments.
void log_message(LogLevel level, const char* format, ...);
/// @brief Formats a string.
/// @param out_string The output string pointer.
/// @param fmt The format string.
/// @param ... The arguments to format into the string.
/// @return The formatted string or NULL if an error occurred. The result must be freed with free().
char* format(const char* fmt, ...);
/// @brief Logs a message to the console.
/// @param level The log level of the message.
/// @param fmt The format string of the message.
/// @param ... The arguments to format into the message.
void log_message(LogLevel level, const char* fmt, ...);
/// @brief Executes a command and returns the exit code as well as the output and error streams.
/// @param outb The output stream of the command. May be NULL if the output is not needed.
/// @param errb The error stream of the command. May be NULL if the error is not needed.
/// @param file The file to execute.
/// @param ... The arguments to pass to the file.
/// @return The exit code of the command.
int execute(char** outb, char** errb, const char* file, ...);
/// @brief Reads the contents of a file into a string. The result might contain null bytes.
/// @param fd The file descriptor of the file to read.
/// @return The contents of the file or NULL if an error occurred. The result must be freed with free().
char* read_file(int fd);
/// @param ... The format arguments.
/// @return The result of the operation.
Result format(char** out_string, const char* fmt, ...);