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 <stdbool.h>
|
||||||
#include <stdint.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);
|
||||||
|
143
src/sandbox.c
143
src/sandbox.c
@ -1,39 +1,134 @@
|
|||||||
#include "sandbox.h"
|
#include "sandbox.h"
|
||||||
|
|
||||||
#include "utils.h"
|
#include "utils.h"
|
||||||
#include "command.h"
|
#include "entry.h"
|
||||||
|
|
||||||
#include <stdarg.h>
|
|
||||||
#include <stdio.h>
|
#include <stdio.h>
|
||||||
#include <stdlib.h>
|
#include <stdlib.h>
|
||||||
#include <string.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[]) {
|
int main(int argc, char* argv[]) {
|
||||||
if (argc < 2) {
|
if (argc < 2)
|
||||||
show_help();
|
return command_help(0, NULL);
|
||||||
return 0;
|
|
||||||
|
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 = &COMMANDS[i];
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Command* command = find_command(argv[1]);
|
// Check if the command is NULL (no matches)
|
||||||
if (command == NULL) {
|
if (command == NULL) {
|
||||||
log_message(LOG_ERROR, "Unknown command '%s'.", argv[1]);
|
log_message(LOG_LEVEL_ERROR, "Unknown command '%s'.", argv[1]);
|
||||||
return 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[]) {
|
||||||
switch (command->id) {
|
// Check the number of arguments
|
||||||
case CMD_HELP:
|
if (argc == 0) {
|
||||||
return cmd_help(argc - consumed, argv + consumed);
|
fprintf(stdout, "Usage: sandbox [command] [arguments]\n\n");
|
||||||
case CMD_VERSION:
|
fprintf(stdout, "Commands:\n");
|
||||||
return cmd_version(argc - consumed, argv + consumed);
|
|
||||||
|
for (int i = 0; i < ARRAY_SIZE(COMMANDS); i++)
|
||||||
default:
|
if (COMMANDS[i].name == NULL)
|
||||||
log_message(LOG_ERROR, "Command '%s' is not implemented.", command->name);
|
printf("\n");
|
||||||
break;
|
else
|
||||||
}
|
fprintf(stdout, " %s %s - %s\n", COMMANDS[i].name, COMMANDS[i].arguments, COMMANDS[i].description);
|
||||||
|
|
||||||
return 0;
|
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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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
|
#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 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 <stdarg.h>
|
||||||
#include <stdio.h>
|
#include <stdio.h>
|
||||||
#include <stdlib.h>
|
|
||||||
#include <time.h>
|
#include <time.h>
|
||||||
#include <unistd.h>
|
#include <stdlib.h>
|
||||||
#include <string.h>
|
#include <string.h>
|
||||||
#include <errno.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_list args;
|
||||||
va_start(args, fmt);
|
va_start(args, fmt);
|
||||||
|
|
||||||
@ -24,15 +70,15 @@ char* format(const char* fmt, ...) {
|
|||||||
va_end(args);
|
va_end(args);
|
||||||
|
|
||||||
if (length <= 0) {
|
if (length <= 0) {
|
||||||
log_message(LOG_ERROR, "Failed to calculate the length of the formatted string.");
|
log_message(LOG_LEVEL_ERROR, "Failed to calculate the length of the formatted string (%s).", strerror(errno));
|
||||||
return NULL;
|
return FAILURE;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Allocate a buffer for the formatted string
|
// Allocate a buffer for the formatted string
|
||||||
char* buffer = calloc(length, sizeof(char));
|
char* buffer = calloc(length, sizeof(char));
|
||||||
if (buffer == NULL) {
|
if (buffer == NULL) {
|
||||||
log_message(LOG_ERROR, "Failed to allocate memory for the formatted string.");
|
log_message(LOG_LEVEL_ERROR, "Failed to allocate memory for the formatted string (%s).", strerror(errno));
|
||||||
return NULL;
|
return OUT_OF_MEMORY;
|
||||||
}
|
}
|
||||||
|
|
||||||
va_start(args, fmt);
|
va_start(args, fmt);
|
||||||
@ -42,237 +88,6 @@ char* format(const char* fmt, ...) {
|
|||||||
|
|
||||||
va_end(args);
|
va_end(args);
|
||||||
|
|
||||||
return buffer;
|
*out_string = buffer;
|
||||||
}
|
return SUCCESS;
|
||||||
|
|
||||||
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;
|
|
||||||
}
|
}
|
||||||
|
58
src/utils.h
58
src/utils.h
@ -1,37 +1,35 @@
|
|||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
#include <stdbool.h>
|
#define ARRAY_SIZE(x) (sizeof(x) / sizeof((x)[0]))
|
||||||
#include <stdint.h>
|
|
||||||
#include <sys/types.h>
|
|
||||||
|
|
||||||
typedef enum {
|
typedef enum {
|
||||||
LOG_DEBUG,
|
SUCCESS,
|
||||||
LOG_INFO,
|
FAILURE,
|
||||||
LOG_WARN,
|
OUT_OF_MEMORY,
|
||||||
LOG_ERROR,
|
} Result;
|
||||||
|
|
||||||
|
typedef enum {
|
||||||
|
LOG_LEVEL_DEBUG,
|
||||||
|
LOG_LEVEL_INFO,
|
||||||
|
LOG_LEVEL_WARNING,
|
||||||
|
LOG_LEVEL_ERROR,
|
||||||
} LogLevel;
|
} 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 fmt The format string.
|
||||||
/// @param ... The arguments to format into the string.
|
/// @param ... The format arguments.
|
||||||
/// @return The formatted string or NULL if an error occurred. The result must be freed with free().
|
/// @return The result of the operation.
|
||||||
char* format(const char* fmt, ...);
|
Result format(char** out_string, 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);
|
|
Loading…
Reference in New Issue
Block a user