Started writing the management program.
This commit is contained in:
commit
827b62fbcd
1
.gitignore
vendored
Normal file
1
.gitignore
vendored
Normal file
@ -0,0 +1 @@
|
|||||||
|
[bB]in/
|
28
Makefile
Normal file
28
Makefile
Normal 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
66
src/backing.c
Normal 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
32
src/backing.h
Normal 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
121
src/disk.c
Normal 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
26
src/disk.h
Normal 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
70
src/entry.c
Normal 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
27
src/entry.h
Normal 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
16
src/sandbox.c
Normal 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
8
src/sandbox.h
Executable 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
271
src/utils.c
Executable 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
34
src/utils.h
Executable 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);
|
Loading…
Reference in New Issue
Block a user