Files
linuxinstall/src/utils.c

528 lines
13 KiB
C

#include "utils.h"
#include <stdarg.h>
#include <string.h>
#include <errno.h>
#include <stdlib.h>
#include <stdio.h>
#include <unistd.h>
#include <fcntl.h>
#include <ctype.h>
#include <sys/wait.h>
#include <openssl/evp.h>
#include <dirent.h>
char ERROR_BUFFER[ERROR_BUFFER_SIZE];
result_t success() {
return EXIT_SUCCESS;
}
result_t failure(const char* format, ...) {
va_list args;
va_start(args, format);
// Write the error message to the error buffer.
vsnprintf(ERROR_BUFFER, ERROR_BUFFER_SIZE, format, args);
va_end(args);
return EXIT_FAILURE;
}
const char* last_error() {
return ERROR_BUFFER;
}
result_t execute_file(int* _exit_code, char** _stdout_buffer, size_t* _stdout_size, char** _stderr_buffer, size_t* _stderr_size, const char* working_directory, const char* file, ...) {
// Count the number of arguments.
va_list args;
va_start(args, file);
int argc = 1; // The first argument is the file itself.
while (va_arg(args, const char*) != NULL)
argc++;
va_end(args);
// Allocate the arguments array.
const char** argv = malloc((argc + 1) * sizeof(const char*)); // The last element is the NULL terminator.
if (argv == NULL)
return failure("Failed to allocate memory for the arguments (%s).", strerror(errno));
// Fill the arguments array.
va_start(args, file);
argv[0] = file;
for (int i = 1; i < argc; i++)
argv[i] = va_arg(args, const char*);
argv[argc] = NULL;
va_end(args);
// Create the pipes for the standard output and error streams.
int stdout_pipe[2];
if (pipe(stdout_pipe) < 0) {
free(argv);
return failure("Failed to create the standard output pipe (%s).", strerror(errno));
}
int stderr_pipe[2];
if (pipe(stderr_pipe) < 0) {
close(stdout_pipe[0]);
close(stdout_pipe[1]);
free(argv);
return failure("Failed to create the standard error pipe (%s).", strerror(errno));
}
// Fork the process.
pid_t pid = fork();
if (pid < 0) {
close(stdout_pipe[0]);
close(stdout_pipe[1]);
close(stderr_pipe[0]);
close(stderr_pipe[1]);
free(argv);
return failure("Failed to fork the process (%s).", strerror(errno));
}
if (pid == 0) {
// Redirect the standard output and error streams 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]);
// Change the working directory if necessary.
if (working_directory != NULL) {
if (chdir(working_directory) < 0) {
fprintf(stderr, "Failed to change the working directory to '%s' (%s).\n", working_directory, strerror(errno));
exit(EXIT_FAILURE);
}
}
// Execute the file.
execv(file, (char* const*)argv);
// If execv returns, it failed.
fprintf(stderr, "Failed to execute the file '%s' (%s).\n", file, strerror(errno));
exit(EXIT_FAILURE);
}
// Close the write end of the pipes.
close(stdout_pipe[1]);
close(stderr_pipe[1]);
// Free the arguments array as it is no longer needed.
free(argv);
// Wait for the child process to exit.
int status;
if (waitpid(pid, &status, 0) < 0) {
close(stdout_pipe[0]);
close(stderr_pipe[0]);
return failure("Failed to wait for the child process to exit (%s).", strerror(errno));
}
// Read the standard output into a buffer if necessary.
if (_stdout_buffer != NULL) {
result_t result = read_fd(_stdout_buffer, _stdout_size, stdout_pipe[0]);
if (result != success()) {
close(stdout_pipe[0]);
close(stderr_pipe[0]);
return result;
}
}
close(stdout_pipe[0]);
// Read the standard error into a buffer if necessary.
if (_stderr_buffer != NULL) {
result_t result = read_fd(_stderr_buffer, _stderr_size, stderr_pipe[0]);
if (result != success()) {
close(stderr_pipe[0]);
free(*_stdout_buffer);
return result;
}
}
close(stderr_pipe[0]);
// Output the exit code if necessary.
if (_exit_code != NULL)
*_exit_code = WEXITSTATUS(status);
return success();
}
result_t read_fd(char** _buffer, size_t* _size, int fd) {
size_t size = 0;
size_t capacity = 4096;
// Allocate the buffer.
char* buffer = malloc(capacity);
if (buffer == NULL)
return failure("Failed to allocate memory for the buffer (%s).", strerror(errno));
// Read the file descriptor into the buffer.
ssize_t bytes_read;
while ((bytes_read = read(fd, buffer + size, capacity - size)) > 0) {
size += bytes_read;
// Resize the buffer if necessary.
if (size == capacity) {
// Basic doubling strategy.
capacity *= 2;
char* new_buffer = realloc(buffer, capacity);
if (new_buffer == NULL) {
free(buffer);
return failure("Failed to reallocate memory for the buffer (%s).", strerror(errno));
}
buffer = new_buffer;
}
}
// Check for read errors.
if (bytes_read < 0) {
free(buffer);
return failure("Failed to read from the file descriptor (%s).", strerror(errno));
}
// Resize the buffer to the actual size.
char* new_buffer = realloc(buffer, size + 1);
if (new_buffer == NULL) {
free(buffer);
return failure("Failed to reallocate memory for the buffer (%s).", strerror(errno));
}
buffer = new_buffer;
// Null-terminate the buffer.
buffer[size] = '\0';
// Output the buffer and size.
*_buffer = buffer;
if (_size != NULL)
*_size = size;
return success();
}
result_t write_fd(int fd, const char* buffer, size_t size) {
size_t bytes_written = 0;
// Write the buffer to the file descriptor.
while (bytes_written < size) {
ssize_t written = write(fd, buffer + bytes_written, size - bytes_written);
if (written < 0)
return failure("Failed to write to the file descriptor (%s).", strerror(errno));
bytes_written += written;
}
return success();
}
result_t read_file(char** _buffer, size_t* _size, const char* file) {
// Open the file.
int fd = open(file, O_RDONLY);
if (fd < 0)
return failure("Failed to open the file '%s' (%s).", file, strerror(errno));
// Read the file descriptor into a buffer.
result_t result = read_fd(_buffer, _size, fd);
// Close the file.
close(fd);
return result;
}
result_t write_file(const char* file, const char* buffer, size_t size, mode_t mode) {
// Open the file.
int fd = open(file, O_WRONLY | O_CREAT | O_TRUNC, mode);
if (fd < 0)
return failure("Failed to open the file '%s' (%s).", file, strerror(errno));
// Write the buffer to the file descriptor.
result_t result = write_fd(fd, buffer, size);
// Close the file.
close(fd);
return result;
}
result_t copy_fd(int source_fd, int destination_fd) {
char buffer[4096];
ssize_t bytes_read;
// Read the source file descriptor into the buffer.
while ((bytes_read = read(source_fd, buffer, sizeof(buffer))) > 0) {
// Try to write the buffer to the destination file descriptor.
ssize_t bytes_written = 0;
while (bytes_written < bytes_read) {
ssize_t written = write(destination_fd, buffer + bytes_written, bytes_read - bytes_written);
if (written < 0)
return failure("Failed to write to the file descriptor (%s).", strerror(errno));
bytes_written += written;
}
}
// Check for read errors.
if (bytes_read < 0)
return failure("Failed to read from the file descriptor (%s).", strerror(errno));
return success();
}
result_t copy_file(const char* source, const char* destination, mode_t mode) {
// Open the source file.
int source_fd = open(source, O_RDONLY);
if (source_fd < 0)
return failure("Failed to open the source file '%s' (%s).", source, strerror(errno));
// Open the destination file.
int destination_fd = open(destination, O_WRONLY | O_CREAT | O_TRUNC, mode);
if (destination_fd < 0) {
close(source_fd);
return failure("Failed to open the destination file '%s' (%s).", destination, strerror(errno));
}
// Copy the source file to the destination file.
result_t result = copy_fd(source_fd, destination_fd);
// Close the files.
close(source_fd);
close(destination_fd);
return result;
}
char* trim(const char* string) {
// Check for NULL.
if (string == NULL)
return NULL;
// Skip leading whitespace.
while (*string != '\0' && isspace(*string))
string++;
// Find the end of the string.
const char* end = string;
while (*end != '\0')
end++;
// Skip trailing whitespace.
while (end > string && isspace(*(end - 1)))
end--;
// Allocate a new string and copy the trimmed string into it.
size_t length = end - string;
char* trimmed = malloc(length + 1);
if (trimmed == NULL)
return NULL;
memcpy(trimmed, string, length);
trimmed[length] = '\0';
return trimmed;
}
char* file_name(const char* path) {
// Check for NULL.
if (path == NULL)
return NULL;
// Find the last occurrence of the path separator.
const char* file_name = strrchr(path, '/');
// If the path separator was found, return the file name after it.
if (file_name != NULL)
return strdup(file_name + 1);
// Otherwise, return the entire path.
return strdup(path);
}
char* directory_name(const char* path) {
// Check for NULL.
if (path == NULL)
return NULL;
// Find the last occurrence of the path separator.
const char* file_name = strrchr(path, '/');
// If the path separator was found, return the directory name before it.
if (file_name != NULL) {
size_t length = file_name - path;
char* directory_name = malloc(length + 1);
if (directory_name == NULL)
return NULL;
memcpy(directory_name, path, length);
directory_name[length] = '\0';
return directory_name;
}
// Otherwise, return the current directory.
return strdup(".");
}
result_t list_files(char*** _files, const char* directory, result_t (*filter)(const char*)) {
// Open the directory.
DIR* dir = opendir(directory);
if (dir == NULL)
return failure("Failed to open the directory '%s' (%s).", directory, strerror(errno));
// Count the files in the directory.
size_t count = 0;
size_t capacity = 16;
char** files = malloc(capacity * sizeof(char*));
if (files == NULL) {
closedir(dir);
return failure("Failed to allocate memory for the files.");
}
// Read the files in the directory.
struct dirent* entry;
while ((entry = readdir(dir)) != NULL) {
// Skip the current and parent directories.
if (strcmp(entry->d_name, ".") == 0 || strcmp(entry->d_name, "..") == 0)
continue;
// Filter the file if necessary.
if (filter != NULL) {
result_t result = filter(entry->d_name);
if (result != success())
continue;
}
// Add the file to the list.
if (count == capacity) {
// Basic doubling strategy.
capacity *= 2;
char** new_files = realloc(files, capacity * sizeof(char*));
if (new_files == NULL) {
closedir(dir);
for (size_t i = 0; i < count; i++)
free(files[i]);
free(files);
return failure("Failed to reallocate memory for the files.");
}
files = new_files;
}
files[count] = strdup(entry->d_name);
if (files[count] == NULL) {
closedir(dir);
for (size_t i = 0; i < count; i++)
free(files[i]);
free(files);
return failure("Failed to allocate memory for the file.");
}
count++;
}
// Close the directory.
closedir(dir);
// Null-terminate the list of files.
char** new_files = realloc(files, (count + 1) * sizeof(char*));
if (new_files == NULL) {
for (size_t i = 0; i < count; i++)
free(files[i]);
free(files);
return failure("Failed to reallocate memory for the files.");
}
new_files[count] = NULL;
// Output the list of files.
*_files = new_files;
return success();
}
result_t md5_fd(char** _hash, int fd) {
// Initialize the digest context.
EVP_MD_CTX* context = EVP_MD_CTX_new();
if (context == NULL)
return failure("Failed to create the MD5 digest context.");
// Initialize the MD5 algorithm.
const EVP_MD* algorithm = EVP_md5();
if (algorithm == NULL) {
EVP_MD_CTX_free(context);
return failure("Failed to initialize the MD5 algorithm.");
}
// Initialize the digest.
if (EVP_DigestInit_ex(context, algorithm, NULL) != 1) {
EVP_MD_CTX_free(context);
return failure("Failed to initialize the MD5 digest.");
}
char buffer[4096];
ssize_t bytes_read;
// Read the file descriptor into the buffer.
while ((bytes_read = read(fd, buffer, sizeof(buffer))) > 0) {
// Update the digest with the buffer.
if (EVP_DigestUpdate(context, buffer, bytes_read) != 1) {
EVP_MD_CTX_free(context);
return failure("Failed to update the MD5 digest.");
}
}
// Check for read errors.
if (bytes_read < 0) {
EVP_MD_CTX_free(context);
return failure("Failed to read from the file descriptor (%s).", strerror(errno));
}
// Finalize the digest.
unsigned char hash[EVP_MAX_MD_SIZE];
unsigned int length;
if (EVP_DigestFinal_ex(context, hash, &length) != 1) {
EVP_MD_CTX_free(context);
return failure("Failed to finalize the MD5 digest.");
}
// Free the digest context.
EVP_MD_CTX_free(context);
// Convert the hash to a hexadecimal string.
char* hex_hash = malloc(length * 2 + 1);
if (hex_hash == NULL)
return failure("Failed to allocate memory for the hash.");
for (unsigned int i = 0; i < length; i++)
sprintf(hex_hash + i * 2, "%02x", hash[i]);
// Output the hash.
*_hash = hex_hash;
return success();
}
result_t md5_file(char** _hash, const char* file) {
// Open the file.
int fd = open(file, O_RDONLY);
if (fd < 0)
return failure("Failed to open the file '%s' (%s).", file, strerror(errno));
// Calculate the MD5 hash of the file.
result_t result = md5_fd(_hash, fd);
// Close the file.
close(fd);
return result;
}