diff --git a/src/disk.h b/src/disk.h index 82b4ee8..1c7a87e 100644 --- a/src/disk.h +++ b/src/disk.h @@ -6,6 +6,18 @@ #include #include +/// @brief The information about a disk. +typedef struct { + /// @brief The virtual size of the disk. + uint64_t virtual_size; + + /// @brief The actual size of the disk. + uint64_t actual_size; + + /// @brief The path to the backing file of the disk. This is NULL if the disk is not backed. + char* backing_file; +} DiskInfo; + /// @brief Creates an empty disk at the given path, with the given size and with the given permissions. /// @param path The path to the disk to create. /// @param size The size of the disk to create. @@ -30,3 +42,13 @@ Result trim_disk(const char* path); /// @param backing_disk The new path to the backing disk. /// @return The result of the operation. Result rebase_disk(const char* path, const char* backing_disk); + +/// @brief Gathers information about a disk. +/// @param path The path to the disk to gather information about. +/// @param out_info The information about the disk. +/// @return The result of the operation. +Result get_disk_info(const char* path, DiskInfo* out_info); + +/// @brief Frees the resources used by the given disk information. +/// @param info The disk information to free. +void free_disk_info(DiskInfo* info); diff --git a/src/entry.h b/src/entry.h index 973463a..0432f64 100644 --- a/src/entry.h +++ b/src/entry.h @@ -15,7 +15,7 @@ bool is_entry_id_valid(const char* entry_id); /// @brief Gets the path of the given entry. /// @param entry_id The entry id. -/// @param out_path The pointer to the output path string. +/// @param out_path The pointer to the output path string. The caller is responsible for freeing the memory. /// @return The result of the operation. Result get_entry_path(const char* entry_id, char** out_path); diff --git a/src/sandbox.c b/src/sandbox.c index 5fa6e74..99097d6 100644 --- a/src/sandbox.c +++ b/src/sandbox.c @@ -6,6 +6,7 @@ #include #include #include +#include const Command COMMANDS[] = { {command_help, "help", "[command]", "Prints the help message.", @@ -109,6 +110,8 @@ int command_add_entry(int argc, char* argv[]) { return EXIT_FAILURE; } + // TODO: Parse arguments + // Extract the entry id const char* entry_id = argv[0]; diff --git a/src/utils.c b/src/utils.c index 06fc68b..5abc80b 100644 --- a/src/utils.c +++ b/src/utils.c @@ -6,6 +6,9 @@ #include #include #include +#include +#include +#include LogLevel log_level = LOG_LEVEL_WARNING; @@ -87,3 +90,200 @@ Result format(char** out_string, const char* fmt, ...) { *out_string = buffer; return SUCCESS; } + +Result run_executable(int* out_exit_code, char** out_stdout, char** out_stderr, const char* executable, ...) { + if (out_exit_code != NULL) + *out_exit_code = -1; + + if (out_stdout != NULL) + *out_stdout = NULL; + if (out_stderr != NULL) + *out_stderr = NULL; + + // Count the number of arguments + int argc = 1; // The first argument is the executable itself + + va_list args; + va_start(args, executable); + + 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_LEVEL_ERROR, "Failed to allocate memory for the arguments (%s).", strerror(errno)); + return OUT_OF_MEMORY; + } + + // Fill the array with the arguments + argv[0] = strdup(executable); + if (argv[0] == NULL) { + log_message(LOG_LEVEL_ERROR, "Failed to duplicate the executable string (%s).", strerror(errno)); + + free(argv); + + return OUT_OF_MEMORY; + } + + va_start(args, executable); + + for (int i = 1; i < argc; i++) { + argv[i] = strdup(va_arg(args, const char*)); + if (argv[i] == NULL) { + log_message(LOG_LEVEL_ERROR, "Failed to duplicate the argument string (%s).", strerror(errno)); + + for (int j = 0; j < i; j++) + free(argv[j]); + free(argv); + + va_end(args); + + return OUT_OF_MEMORY; + } + } + + va_end(args); + + // Set the last element of the array to NULL + argv[argc] = NULL; + + // Create pipes for the stdout and stderr + int stdout_pipe[2]; + if (pipe(stdout_pipe) == -1) { + log_message(LOG_LEVEL_ERROR, "Failed to create the stdout pipe (%s).", strerror(errno)); + + for (int i = 0; i < argc; i++) + free(argv[i]); + free(argv); + + return FAILURE; + } + + int stderr_pipe[2]; + if (pipe(stderr_pipe) == -1) { + log_message(LOG_LEVEL_ERROR, "Failed to create the stderr pipe (%s).", strerror(errno)); + + for (int i = 0; i < argc; i++) + free(argv[i]); + free(argv); + + close(stdout_pipe[0]); + close(stdout_pipe[1]); + + return FAILURE; + } + + // Fork the process + pid_t pid = fork(); + + if (pid == -1) { + log_message(LOG_LEVEL_ERROR, "Failed to fork the process (%s).", strerror(errno)); + + for (int i = 0; i < argc; i++) + free(argv[i]); + free(argv); + + close(stdout_pipe[0]); + close(stdout_pipe[1]); + close(stderr_pipe[0]); + close(stderr_pipe[1]); + + return FAILURE; + } + + if (pid == 0) { + // Redirect the stdout and stderr to the pipes + dup2(stdout_pipe[1], STDOUT_FILENO); + dup2(stderr_pipe[1], STDERR_FILENO); + + // Close the unused ends of the pipes + close(stdout_pipe[0]); + close(stdout_pipe[1]); + close(stderr_pipe[0]); + close(stderr_pipe[1]); + + // Execute the command + execvp(executable, argv); + + // If the command failed to execute, print an error message and exit + fprintf(stderr, "Failed to execute the command '%s' (%s).\n", executable, strerror(errno)); + exit(EXIT_FAILURE); + } + + // Free the arguments array + for (int i = 0; i < argc; i++) + free(argv[i]); + free(argv); + + // Close the unused ends 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 output from the pipes + if (out_stdout != NULL) + read_fd(stdout_pipe[0], out_stdout); + if (out_stderr != NULL) + read_fd(stderr_pipe[0], out_stderr); + + // Close the pipes + close(stdout_pipe[0]); + close(stderr_pipe[0]); + + // Set the exit code + if (out_exit_code != NULL) + *out_exit_code = WEXITSTATUS(status); + + return SUCCESS; +} + +Result read_fd(int fd, char** out_string) { + *out_string = NULL; + + // Create a buffer for the output + char buffer[4096]; + size_t length = 0; + + // Read the output from the file descriptor + ssize_t bytes_read; + while ((bytes_read = read(fd, buffer, sizeof(buffer))) > 0) { + // Reallocate the buffer + char* new_buffer = realloc(*out_string, length + bytes_read + 1); + if (new_buffer == NULL) { + log_message(LOG_LEVEL_ERROR, "Failed to reallocate memory for the output string (%s).", strerror(errno)); + + free(*out_string); + *out_string = NULL; + + return OUT_OF_MEMORY; + } + + *out_string = new_buffer; + + // Copy the new data to the buffer + memcpy(*out_string + length, buffer, bytes_read); + length += bytes_read; + } + + // Check if an error occurred + if (bytes_read == -1) { + log_message(LOG_LEVEL_ERROR, "Failed to read from the file descriptor (%s).", strerror(errno)); + + free(*out_string); + *out_string = NULL; + + return FAILURE; + } + + // Null-terminate the string + if (length > 0) + (*out_string)[length] = '\0'; + + return SUCCESS; +} diff --git a/src/utils.h b/src/utils.h index b69d8f4..750f5a3 100644 --- a/src/utils.h +++ b/src/utils.h @@ -27,8 +27,23 @@ void set_log_level(LogLevel level); void log_message(LogLevel level, const char* format, ...); /// @brief Formats a string. -/// @param out_string The pointer to the output string. +/// @param out_string The pointer to the output string. The caller is responsible for freeing the memory. /// @param fmt The format string. /// @param ... The format arguments. /// @return The result of the operation. -Result format(char** out_string, const char* fmt, ...); \ No newline at end of file +Result format(char** out_string, const char* fmt, ...); + +/// @brief Runs an external executable with the given arguments, and waits for it to finish, then returns the exit code and the output. +/// @param out_exit_code The pointer to the output exit code. This argument can be NULL if the exit code is not needed. +/// @param out_stdout The pointer to the output stdout string. This argument can be NULL if the output is not needed. The caller is responsible for freeing the memory. +/// @param out_stderr The pointer to the output stderr string. This argument can be NULL if the output is not needed. The caller is responsible for freeing the memory. +/// @param executable The path of the executable to run. +/// @param ... The arguments of the executable. The last argument must be NULL. Note: No need to include the executable path in the arguments. +/// @return The result of the operation. +Result run_executable(int* out_exit_code, char** out_stdout, char** out_stderr, const char* executable, ...); + +/// @brief Reads the contents of a file descriptor into a string. +/// @param fd The file descriptor to read from. +/// @param out_string The pointer to the output string. The caller is responsible for freeing the memory. +/// @return The result of the operation. +Result read_fd(int fd, char** out_string); \ No newline at end of file