#include "utils.h" #include #include #include #include #include #include #include #include #include #include #include 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; }