Started working on a refactored version
This commit is contained in:
parent
66caca4a7f
commit
85ad0ab103
@ -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);
|
@ -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;
|
||||
}
|
@ -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);
|
231
src/disk.c
231
src/disk.c
@ -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);
|
||||
}
|
43
src/disk.h
43
src/disk.h
@ -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
23
src/entry.c
Normal 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;
|
||||
}
|
@ -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);
|
||||
|
131
src/sandbox.c
131
src/sandbox.c
@ -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;
|
||||
}
|
@ -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[]);
|
307
src/utils.c
307
src/utils.c
@ -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;
|
||||
}
|
||||
|
58
src/utils.h
58
src/utils.h
@ -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, ...);
|
Loading…
Reference in New Issue
Block a user