Started writing the management program.

This commit is contained in:
Alexei KADIR 2024-02-15 00:17:20 +01:00
commit 827b62fbcd
12 changed files with 700 additions and 0 deletions

1
.gitignore vendored Normal file
View File

@ -0,0 +1 @@
[bB]in/

28
Makefile Normal file
View File

@ -0,0 +1,28 @@
# ---- ---- #
CC = clang
CF = -Wall -lkrb5 -lvirt -ljson-c -g
# ---- ---- #
build: $(shell find . -name "*.c") $(shell find . -name "*.h")
@echo "Building sandbox..."
@mkdir -p bin
@$(CC) $(CF) -o ./bin/sandbox $(shell find . -name "*.c")
run: build
@echo "Running sandbox..."
@./bin/sandbox
debug: build
@echo "Debugging sandbox..."
@gdb -q ./bin/sandbox
clean:
@echo "Cleaning sandbox..."
@rm -rf ./bin
.PHONY: build run clean
# ---- ---- #

66
src/backing.c Normal file
View File

@ -0,0 +1,66 @@
#include "backing.h"
#include "utils.h"
#include "sandbox.h"
#include <stddef.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/stat.h>
char* get_backings_path(void) {
return format("%s/%s", MASTER_DIRECTORY, BACKINGS_DIRECTORY);
}
bool is_valid_backing_name(const char* name) {
if (name == NULL)
return false;
// Check that the name is not empty
size_t length = strlen(name);
if (length == 0)
return false;
// Check that the name is a number (the number is the timestamp of the backing disk creation)
for (size_t i = 0; i < length; i++)
if (name[i] < '0' || name[i] > '9')
return false;
return true;
}
char* get_backing_path(const char* backing) {
char* backings_path = get_backings_path();
if (backings_path == NULL)
return NULL;
// Combine the backings path with the backing name
char* backing_path = format("%s/%s", backings_path, backing);
// Free the backings path
free(backings_path);
return backing_path;
}
bool backing_exists(const char* backing) {
char* backing_path = get_backing_path(backing);
if (backing_path == NULL)
return false;
// Check if the backing path exists
struct stat statbuf;
int result = stat(backing_path, &statbuf);
// Free the backing path
free(backing_path);
if (result != 0)
return false;
if (!S_ISREG(statbuf.st_mode))
return false;
return true;
}

32
src/backing.h Normal file
View File

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

121
src/disk.c Normal file
View File

@ -0,0 +1,121 @@
#include "disk.h"
#include "utils.h"
#include <stddef.h>
#include <stdlib.h>
#include <string.h>
#include <json-c/json.h>
bool create_empty_disk(const char* disk, uint64_t size) {
char* stdoutb = NULL;
char* stderrb = NULL;
// Create an empty disk
int result = execute(&stdoutb, &stderrb, "qemu-img", "create", "-c", "-f", "qcow2", disk, format("%lu", size), NULL);
// Check if the command was successful
if (result != 0) {
// Remove the newline character from the stderr buffer
size_t length = strlen(stderrb);
for (size_t i = 0; i < length; i++)
if (stderrb[i] == '\n')
stderrb[i] = ' ';
log_msg(LOG_ERROR, "Failed to create the empty disk %s (%s)", disk, stderrb);
free(stdoutb);
free(stderrb);
return false;
}
free(stdoutb);
free(stderrb);
return true;
}
bool create_backed_disk(const char* disk, const char* backing) {
char* stdoutb = NULL;
char* stderrb = NULL;
// Create a backed disk
int result = execute(&stdoutb, &stderrb, "qemu-img", "create", "-c", "-f", "qcow2", "-F", "qcow2", "-b", backing, disk, NULL);
// Check if the command was successful
if (result != 0) {
// Remove the newline character from the stderr buffer
size_t length = strlen(stderrb);
for (size_t i = 0; i < length; i++)
if (stderrb[i] == '\n')
stderrb[i] = ' ';
log_msg(LOG_ERROR, "Failed to create the backed disk %s (%s)", disk, stderrb);
free(stdoutb);
free(stderrb);
return false;
}
free(stdoutb);
free(stderrb);
return true;
}
char* get_backing_file(const char* disk) {
char* stdoutb = NULL;
char* stderrb = NULL;
// Get the backing file of the disk
int result = execute(&stdoutb, &stderrb, "qemu-img", "info", "--output", "json", disk, NULL);
// Check if the command was successful
if (result != 0) {
// Remove the newline character from the stderr buffer
size_t length = strlen(stderrb);
for (size_t i = 0; i < length; i++)
if (stderrb[i] == '\n')
stderrb[i] = ' ';
log_msg(LOG_ERROR, "Failed to get the backing file of the disk %s (%s)", disk, stderrb);
free(stdoutb);
free(stderrb);
return NULL;
}
free(stderrb);
// Parse the JSON output
json_object* root = json_tokener_parse(stdoutb);
if (root == NULL) {
log_msg(LOG_ERROR, "Failed to parse the JSON output of the command.");
free(stdoutb);
return NULL;
}
// Get the backing file
json_object* backing_file = NULL;
if (!json_object_object_get_ex(root, "backing-filename", &backing_file)) {
json_object_put(root);
free(stdoutb);
return NULL;
}
// Get the backing file as a string0
char* backing_file_str = strdup(json_object_get_string(backing_file));
if (backing_file_str == NULL) {
log_msg(LOG_ERROR, "Failed to get the backing file of the disk %s.", disk);
json_object_put(root);
free(stdoutb);
return NULL;
}
return backing_file_str;
}

26
src/disk.h Normal file
View File

@ -0,0 +1,26 @@
#pragma once
#include <stdbool.h>
#include <stdint.h>
/// @brief Creates an empty disk.
/// @param disk The path to the disk to create. Warning: the disk must not exist, otherwise it will be overwritten.
/// @param size The size of the disk to create in bytes.
/// @return true if the disk is created, otherwise false.
bool create_empty_disk(const char* disk, uint64_t size);
/// @brief Creates a backed disk.
/// @param disk The path to the disk to create. Warning: the disk must not exist, otherwise it will be overwritten.
/// @param backing The path to the backing file, which exists.
/// @return true if the disk is created, otherwise false.
bool create_backed_disk(const char* disk, const char* backing);
/// @brief Packs the disk to reduce its size.
/// @param disk The path to the disk to pack, which exists.
/// @return true if the disk is packed, otherwise false.
bool pack_disk(const char* disk);
/// @brief Gets the backing file of a disk.
/// @param disk The path to the disk, which exists.
/// @return The backing file of the disk. The caller is responsible for freeing the returned string. This function can return NULL.
char* get_backing_file(const char* disk);

70
src/entry.c Normal file
View File

@ -0,0 +1,70 @@
#include "entry.h"
#include "sandbox.h"
#include "utils.h"
#include <stddef.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/stat.h>
char* get_entries_path(void) {
return format("%s/%s", MASTER_DIRECTORY, ENTRIES_DIRECTORY);
}
bool is_valid_entry_name(const char* name) {
if (name == NULL)
return false;
// Check that the name is not empty
size_t length = strlen(name);
if (length == 0)
return false;
// Check that the name does not contain /
for (size_t i = 0; i < length; i++)
if (name[i] == '/')
return false;
// Check that the name is not . or ..
if (strcmp(name, ".") == 0 || strcmp(name, "..") == 0)
return false;
return true;
}
char* get_entry_path(const char* entry) {
char* entries_path = get_entries_path();
if (entries_path == NULL)
return NULL;
// Combine the entries path with the entry name
char* entry_path = format("%s/%s", entries_path, entry);
// Free the entries path
free(entries_path);
return entry_path;
}
bool entry_exists(const char* entry) {
char* entry_path = get_entry_path(entry);
if (entry_path == NULL)
return false;
// Check if the entry path exists
struct stat statbuf;
int result = stat(entry_path, &statbuf);
// Free the entry path
free(entry_path);
if (result != 0)
return false;
if (!S_ISDIR(statbuf.st_mode))
return false;
return true;
}

27
src/entry.h Normal file
View File

@ -0,0 +1,27 @@
#pragma once
#include <stdbool.h>
#include <stdint.h>
/// @brief Returns the directory path where the entries are stored.
/// @return The directory path where the entries are stored. The caller is responsible for freeing the returned string. This function can return NULL.
char* get_entries_path(void);
/// @brief Checks if the specified entry name is valid.
/// @param name The entry name to check.
/// @return true if the entry name is valid, otherwise false.
bool is_valid_entry_name(const char* name);
/// @brief Returns the path to the specified entry.
/// @param entry The valid entry name.
/// @return The path to the specified entry. The caller is responsible for freeing the returned string. This function can return NULL.
char* get_entry_path(const char* entry);
/// @brief Checks if the specified entry exists.
/// @param entry The valid entry name.
/// @return true if the entry exists, otherwise false.
bool entry_exists(const char* entry);
/// @brief Lists the entries.
/// @return The list of entries. The caller is responsible for freeing the returned array and strings. This function can return NULL.
char** list_entries(void);

16
src/sandbox.c Normal file
View File

@ -0,0 +1,16 @@
#include "sandbox.h"
#include "utils.h"
#include "backing.h"
#include "entry.h"
#include "disk.h"
#include <stdlib.h>
#include <unistd.h>
#include <stdio.h>
#include <libgen.h>
int main(int argc, char* argv[]) {
char** backings = list_backings();
}

8
src/sandbox.h Executable file
View File

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

271
src/utils.c Executable file
View File

@ -0,0 +1,271 @@
#include "utils.h"
#include <string.h>
#include <stdarg.h>
#include <stdio.h>
#include <stdlib.h>
#include <time.h>
#include <unistd.h>
#include <dirent.h>
#include <sys/wait.h>
#include <sys/types.h>
char* format(const char* fmt, ...) {
va_list args;
va_start(args, fmt);
// Calculate the length of the formatted string
size_t length = vsnprintf(NULL, 0, fmt, args) + 1;
va_end(args);
if (length <= 0)
return NULL;
// Allocate a buffer for the formatted string
char* buffer = calloc(length, sizeof(char));
if (buffer == NULL) {
log_msg(LOG_ERROR, "Failed to allocate memory for the formatted string.");
return NULL;
}
va_start(args, fmt);
// Format the string
vsnprintf(buffer, length, fmt, args);
va_end(args);
return buffer;
}
void log_msg(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 = "";
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** stdout_buffer, char** stderr_buffer, const char* file, ...) {
// Count the number of arguments
int argc = 1; // Include space for the file name
va_list args;
va_start(args, file);
while (va_arg(args, char*) != NULL)
argc++;
va_end(args);
// Allocate an array for the arguments
char** argv = calloc(argc + 1, sizeof(char*)); // Include space for the NULL terminator
if (argv == NULL) {
log_msg(LOG_ERROR, "Failed to allocate memory for the argument array.");
return -1;
}
// Fill the argument array
va_start(args, file);
argv[0] = strdup(file);
for (int i = 1; i < argc; i++)
argv[i] = strdup(va_arg(args, char*));
argv[argc] = NULL;
va_end(args);
// Create the stdout pipe for the child process
int stdout_pipe[2];
if (pipe(stdout_pipe) == -1) {
log_msg(LOG_ERROR, "Failed to create pipe for stdout.");
for (int i = 0; i < argc; i++)
free(argv[i]);
free(argv);
return -1;
}
// Create the stderr pipe for the child process
int stderr_pipe[2];
if (pipe(stderr_pipe) == -1) {
log_msg(LOG_ERROR, "Failed to create pipe for stderr.");
close(stdout_pipe[0]);
close(stdout_pipe[1]);
for (int i = 0; i < argc; i++)
free(argv[i]);
free(argv);
return -1;
}
// Fork the child process
pid_t pid = fork();
if (pid == -1) {
log_msg(LOG_ERROR, "Failed to fork child process.");
close(stdout_pipe[0]);
close(stdout_pipe[1]);
close(stderr_pipe[0]);
close(stderr_pipe[1]);
for (int i = 0; i < argc; i++)
free(argv[i]);
free(argv);
return -1;
}
if (pid == 0) {
// Redirect stdout and stderr to the pipes
dup2(stdout_pipe[1], STDOUT_FILENO);
dup2(stderr_pipe[1], STDERR_FILENO);
// Close the pipe file descriptors
close(stdout_pipe[0]);
close(stdout_pipe[1]);
close(stderr_pipe[0]);
close(stderr_pipe[1]);
// Execute the command
execvp(file, argv);
// If execvp fails, log an error
log_msg(LOG_ERROR, "Failed to execute command '%s'.", file);
// Free the argument array
for (int i = 0; i < argc; i++)
free(argv[i]);
free(argv);
exit(1);
}
// Close the write end of the pipes
close(stdout_pipe[1]);
close(stderr_pipe[1]);
// Wait for the child process to finish
int status;
waitpid(pid, &status, 0);
// Read the stdout buffer
char buffer[4096];
ssize_t bytes_read;
if (stdout_buffer != NULL) {
size_t stdout_length = 0;
*stdout_buffer = NULL;
// Read the stdout buffer
while ((bytes_read = read(stdout_pipe[0], buffer, sizeof(buffer))) > 0) {
char* new_stdout_buffer = realloc(*stdout_buffer, stdout_length + bytes_read + 1); // Include space for the null terminator
if (new_stdout_buffer == NULL) {
log_msg(LOG_ERROR, "Failed to allocate memory for the stdout buffer.");
free(*stdout_buffer);
close(stdout_pipe[0]);
close(stderr_pipe[0]);
for (int i = 0; i < argc; i++)
free(argv[i]);
free(argv);
return -1;
}
*stdout_buffer = new_stdout_buffer;
memcpy(*stdout_buffer + stdout_length, buffer, bytes_read);
stdout_length += bytes_read;
(*stdout_buffer)[stdout_length] = '\0';
}
}
// Close the read end of the stdout pipe
close(stdout_pipe[0]);
// Read the stderr buffer
if (stderr_buffer != NULL) {
size_t stderr_length = 0;
*stderr_buffer = NULL;
// Read the stderr buffer
while ((bytes_read = read(stderr_pipe[0], buffer, sizeof(buffer))) > 0) {
char* new_stderr_buffer = realloc(*stderr_buffer, stderr_length + bytes_read + 1); // Include space for the null terminator
if (new_stderr_buffer == NULL) {
log_msg(LOG_ERROR, "Failed to allocate memory for the stderr buffer.");
free(*stdout_buffer);
free(*stderr_buffer);
close(stderr_pipe[0]);
for (int i = 0; i < argc; i++)
free(argv[i]);
free(argv);
return -1;
}
*stderr_buffer = new_stderr_buffer;
memcpy(*stderr_buffer + stderr_length, buffer, bytes_read);
stderr_length += bytes_read;
(*stderr_buffer)[stderr_length] = '\0';
}
}
// Close the read end of the stderr pipe
close(stderr_pipe[0]);
// Free the argument array
for (int i = 0; i < argc; i++)
free(argv[i]);
free(argv);
// Return the exit status
return WIFEXITED(status) ? WEXITSTATUS(status) : -1;
}

34
src/utils.h Executable file
View File

@ -0,0 +1,34 @@
#pragma once
/// @brief The log levels.
typedef enum {
LOG_DEBUG,
LOG_INFO,
LOG_WARN,
LOG_ERROR,
} LogLevel;
/// @brief Formats a string using the specified format and arguments.
/// @param fmt The format string.
/// @param ... The arguments to use when formatting the string.
/// @return The formatted string. The caller is responsible for freeing the returned string. This function can return NULL.
char* format(const char* fmt, ...);
/// @brief Logs a formatted message to the console.
/// @param level The log level.
/// @param fmt The format string.
/// @param ... The arguments to use when formatting the string.
void log_msg(LogLevel level, const char* fmt, ...);
/// @brief Executes a command and returns the exit code.
/// @param stdout_buffer A pointer to a buffer that will receive the standard output of the command. The caller is responsible for freeing the buffer. This parameter can be NULL.
/// @param stderr_buffer A pointer to a buffer that will receive the standard error of the command. The caller is responsible for freeing the buffer. This parameter can be NULL.
/// @param file The path to the command to execute.
/// @param ... The arguments to pass to the command, followed by a NULL pointer. No need to include the command name in the arguments.
/// @return The exit code of the command.
int execute(char** stdout_buffer, char** stderr_buffer, const char* file, ...);
/// @brief Returns a list of the files in the specified directory.
/// @param directory The directory to list.
/// @return The list of files in the specified directory. The caller is responsible for freeing the returned array and strings. This function can return NULL.
char** list_files(const char* directory);