#include "utils.h" #include #include #include #include #include #include #include #include #include #include #include char ERROR_BUFFER[ERROR_BUFFER_SIZE]; result_t success(void) { return EXIT_SUCCESS; } result_t failure(const char* format, ...) { va_list args; va_start(args, format); // Print the error message into the error buffer vsnprintf(ERROR_BUFFER, ERROR_BUFFER_SIZE, format, args); va_end(args); return EXIT_FAILURE; } const char* error(void) { return ERROR_BUFFER; } result_t format(char** _str, const char* format, ...) { // Initialize the output parameters *_str = NULL; // Calculate the length of the formatted string va_list args; va_start(args, format); int length = vsnprintf(NULL, 0, format, args); va_end(args); if (length < 0) return failure("Failed to calculate the length of the formatted string."); // Allocate memory for the formatted string errno = 0; char* str = malloc(length + 1); if (str == NULL) return failure("Failed to allocate memory for the formatted string (%s).", strerror(errno)); // Format the string va_start(args, format); if (vsnprintf(str, length + 1, format, args) < 0) return failure("Failed to format the string."); va_end(args); // Return the formatted string *_str = str; return success(); } result_t substring(char** _str, const char* str, size_t start, size_t length) { // Initialize the output parameters *_str = NULL; // Check the start index size_t str_length = strlen(str); if (start > str_length) return failure("The start index is out of range."); if (start + length > str_length) length = str_length - start; // Allocate memory for the substring errno = 0; char* substr = malloc(length + 1); // +1 for the null terminator if (substr == NULL) return failure("Failed to allocate memory for the substring (%s).", strerror(errno)); // Copy the substring memcpy(substr, str + start, length); substr[length] = '\0'; // Return the substring *_str = substr; return success(); } result_t format_size(char** _str, uint64_t size) { // Initialize the output parameters *_str = NULL; // Format the size if (size < 1024ULL) return format(_str, "%lluB", size); if (size < 1024ULL * 1024) return format(_str, "%.2fKiB", size / (1024.0)); if (size < 1024ULL * 1024 * 1024) return format(_str, "%.2fMiB", size / (1024.0 * 1024.0)); if (size < 1024ULL * 1024 * 1024 * 1024) return format(_str, "%.2fGiB", size / (1024.0 * 1024.0 * 1024.0)); if (size < 1024ULL * 1024 * 1024 * 1024 * 1024) return format(_str, "%.2fTiB", size / (1024.0 * 1024.0 * 1024.0 * 1024.0)); if (size < 1024ULL * 1024 * 1024 * 1024 * 1024 * 1024) return format(_str, "%.2fPiB", size / (1024.0 * 1024.0 * 1024.0 * 1024.0 * 1024.0)); return format(_str, "%.2fEiB", size / (1024.0 * 1024.0 * 1024.0 * 1024.0 * 1024.0 * 1024.0)); } result_t parse_size(uint64_t* _size, const char* str) { // Initialize the output parameters *_size = 0; // Parse the size char* endptr; uint64_t size = strtoull(str, &endptr, 10); if (endptr == str) return failure("Failed to parse the size."); // Check the suffix if (*endptr == '\0') { // No suffix *_size = size; return success(); } // Parse the suffix if (endptr[1] != '\0') return failure("Invalid size suffix."); switch (endptr[0]) { case 'B': *_size = size; return success(); case 'K': *_size = size * 1024; return success(); case 'M': *_size = size * 1024 * 1024; return success(); case 'G': *_size = size * 1024 * 1024 * 1024; return success(); case 'T': *_size = size * 1024 * 1024 * 1024 * 1024; return success(); case 'P': *_size = size * 1024 * 1024 * 1024 * 1024 * 1024; return success(); case 'E': *_size = size * 1024 * 1024 * 1024 * 1024 * 1024 * 1024; return success(); default: return failure("Invalid size suffix."); } } result_t execute(int* _exit_code, char** _stdoutbuf, char** _stderrbuf, const char* executable, ...) { // Initialize the output parameters if (_exit_code != NULL) *_exit_code = 0; if (_stdoutbuf != NULL) *_stdoutbuf = NULL; if (_stderrbuf != NULL) *_stderrbuf = NULL; // Count the number of arguments to pass to the executable int argc = 1; // +1 for the executable itself va_list args; va_start(args, executable); while (va_arg(args, const char*) != NULL) argc++; va_end(args); // Allocate memory for the arguments array errno = 0; char** argv = malloc((argc + 1) * sizeof(char*)); // +1 for the NULL terminator if (argv == NULL) return failure("Failed to allocate memory for the arguments array (%s).", strerror(errno)); // Fill the arguments array errno = 0; argv[0] = strdup(executable); if (argv[0] == NULL) { free(argv); return failure("Failed to duplicate the executable string (%s).", strerror(errno)); } va_start(args, executable); for (int i = 1; i < argc; i++) { argv[i] = strdup(va_arg(args, const char*)); if (argv[i] == NULL) { for (int j = 0; j < i; j++) free(argv[j]); free(argv); va_end(args); return failure("Failed to duplicate the argument string (%s).", strerror(errno)); } } va_end(args); argv[argc] = NULL; // Create pipes for the standard output and standard error of the child process int stdout_pipe[2]; errno = 0; if (pipe(stdout_pipe) < 0) { for (int i = 0; i < argc; i++) free(argv[i]); free(argv); return failure("Failed to create a pipe for the standard output of the child process (%s).", strerror(errno)); } int stderr_pipe[2]; errno = 0; if (pipe(stderr_pipe) < 0) { for (int i = 0; i < argc; i++) free(argv[i]); free(argv); close(stdout_pipe[0]); close(stdout_pipe[1]); return failure("Failed to create a pipe for the standard error of the child process (%s).", strerror(errno)); } // Fork the child process errno = 0; pid_t pid = fork(); if (pid < 0) { 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("Failed to fork the child process (%s).", strerror(errno)); } if (pid == 0) { // Redirect the standard output and standard error of the child process close(stdout_pipe[0]); close(stderr_pipe[0]); if (dup2(stdout_pipe[1], STDOUT_FILENO) < 0) exit(EXIT_FAILURE); if (dup2(stderr_pipe[1], STDERR_FILENO) < 0) exit(EXIT_FAILURE); close(stdout_pipe[1]); close(stderr_pipe[1]); // Execute the child process errno = 0; execvp(executable, argv); // If the child process reaches this point, it failed to execute fprintf(stderr, "Failed to execute the child process (%s).\n", strerror(errno)); exit(EXIT_FAILURE); } // Free the arguments array for (int i = 0; i < argc; i++) free(argv[i]); free(argv); // Close the write ends of the pipes close(stdout_pipe[1]); close(stderr_pipe[1]); // Wait for the child process to terminate int status; errno = 0; if (waitpid(pid, &status, 0) < 0) { close(stdout_pipe[0]); close(stderr_pipe[0]); return failure("Failed to wait for the child process to terminate (%s).", strerror(errno)); } // Read the standard output and standard error of the child process if (_exit_code != NULL) *_exit_code = WEXITSTATUS(status); if (_stdoutbuf != NULL) read_fd(_stdoutbuf, stdout_pipe[0]); if (_stderrbuf != NULL) read_fd(_stderrbuf, stderr_pipe[0]); // Close the read ends of the pipes close(stdout_pipe[0]); close(stderr_pipe[0]); return success(); } result_t read_fd(char** _str, int fd) { // Initialize the output parameters *_str = NULL; // Read the file descriptor size_t length = 0; size_t capacity = 4096; // Allocate memory for the output string errno = 0; char* str = malloc(capacity + 1); // +1 for the null terminator if (str == NULL) return failure("Failed to allocate memory for the output string (%s).", strerror(errno)); ssize_t count; while (1) { // Read the file descriptor into the output string errno = 0; count = read(fd, str + length, capacity - length); if (count < 0) { free(str); return failure("Failed to read the file descriptor %d (%s).", fd, strerror(errno)); } // Update the length of the output string length += count; if (count == 0) break; // Reallocate memory for the output string if necessary if (length >= capacity) { capacity *= 2; errno = 0; char* new_str = realloc(str, capacity + 1); // +1 for the null terminator if (new_str == NULL) { free(str); return failure("Failed to reallocate memory for the output string (%s).", strerror(errno)); } str = new_str; } } // Null-terminate the string str[length] = '\0'; // Return the output string *_str = str; return success(); } result_t write_fd(int fd, const char* str) { // Write the string to the file descriptor size_t length = strlen(str); size_t written = 0; while (written < length) { // Try to write the entire string at once errno = 0; ssize_t count = write(fd, str + written, length - written); if (count < 0) return failure("Failed to write to the file descriptor %d (%s).", fd, strerror(errno)); written += count; } return success(); } result_t read_file(char** _str, const char* path) { // Initialize the output parameters *_str = NULL; // Open the file errno = 0; int fd = open(path, O_RDONLY); if (fd < 0) return failure("Failed to open the file '%s' (%s).", path, strerror(errno)); // Read the file result_t result = read_fd(_str, fd); // Close the file close(fd); return result; } result_t write_file(const char* path, const char* str) { // Open the file errno = 0; int fd = open(path, O_WRONLY | O_CREAT | O_TRUNC, 0644); if (fd < 0) return failure("Failed to open the file '%s' (%s).", path, strerror(errno)); // Write the string to the file result_t result = write_fd(fd, str); // Close the file close(fd); return result; } result_t copy_file(const char* src, const char* dst) { // Open the source file errno = 0; int src_fd = open(src, O_RDONLY); if (src_fd < 0) return failure("Failed to open the source file '%s' (%s).", src, strerror(errno)); // Open the destination file errno = 0; int dst_fd = open(dst, O_WRONLY | O_CREAT | O_TRUNC, 0644); if (dst_fd < 0) { close(src_fd); return failure("Failed to open the destination file '%s' (%s).", dst, strerror(errno)); } // Copy the source file to the destination file char buffer[4096]; ssize_t count; while (1) { // Read from the source file errno = 0; count = read(src_fd, buffer, sizeof(buffer)); if (count < 0) { close(src_fd); close(dst_fd); return failure("Failed to read from the source file '%s' (%s).", src, strerror(errno)); } // If the end of the file is reached, stop copying if (count == 0) break; // Write to the destination file while (count > 0) { errno = 0; ssize_t written = write(dst_fd, buffer, count); if (written < 0) { close(src_fd); close(dst_fd); return failure("Failed to write to the destination file '%s' (%s).", dst, strerror(errno)); } count -= written; } } // Close the source file close(src_fd); // Close the destination file close(dst_fd); return success(); } result_t list_files(char*** _files, const char* path, bool (*filter)(const char*)) { // Initialize the output parameters *_files = NULL; // Allocate memory for the files array size_t length = 0; size_t capacity = 64; errno = 0; char** files = malloc(capacity * sizeof(char*)); if (files == NULL) return failure("Failed to allocate memory for the files array (%s).", strerror(errno)); // Open the directory errno = 0; DIR* dir = opendir(path); if (dir == NULL) { free(files); return failure("Failed to open the directory '%s' (%s).", path, strerror(errno)); } // Read the directory struct dirent* entry; while (1) { // Read the next entry errno = 0; entry = readdir(dir); if (entry == NULL) { if (errno != 0) { for (size_t i = 0; i < length; i++) free(files[i]); free(files); closedir(dir); return failure("Failed to read the directory '%s' (%s).", path, strerror(errno)); } break; } // Filter the entry if (filter != NULL && !filter(entry->d_name)) continue; // Add the entry to the files array files[length] = strdup(entry->d_name); if (files[length] == NULL) { for (size_t i = 0; i < length; i++) free(files[i]); free(files); closedir(dir); return failure("Failed to allocate memory for the file name."); } length++; // Reallocate memory for the files array if necessary if (length > capacity) { capacity *= 2; errno = 0; char** new_files = realloc(files, capacity * sizeof(char*)); if (new_files == NULL) { for (size_t i = 0; i < length; i++) free(files[i]); free(files); closedir(dir); return failure("Failed to reallocate memory for the files array (%s).", strerror(errno)); } files = new_files; } } // Close the directory closedir(dir); // Null-terminate the files array files[length] = NULL; // Return the files array *_files = files; return success(); } result_t md5sum(char** _md5, const char* path) { // Initialize the output parameters *_md5 = NULL; // Open the file errno = 0; int fd = open(path, O_RDONLY); if (fd < 0) return failure("Failed to open the file '%s' (%s).", path, strerror(errno)); // Initialize the MD5 context EVP_MD_CTX* mdctx = EVP_MD_CTX_new(); if (mdctx == NULL) { close(fd); return failure("Failed to initialize the MD5 context."); } if (EVP_DigestInit_ex(mdctx, EVP_md5(), NULL) != 1) { EVP_MD_CTX_free(mdctx); close(fd); return failure("Failed to initialize the MD5 context."); } // Read the file and update the MD5 context char buffer[4096]; ssize_t count; while (1) { // Read from the file errno = 0; count = read(fd, buffer, sizeof(buffer)); if (count < 0) { EVP_MD_CTX_free(mdctx); close(fd); return failure("Failed to read from the file '%s' (%s).", path, strerror(errno)); } // If the end of the file is reached, stop updating the MD5 context if (count == 0) break; // Update the MD5 context if (EVP_DigestUpdate(mdctx, buffer, count) != 1) { EVP_MD_CTX_free(mdctx); close(fd); return failure("Failed to update the MD5 context."); } } // Finalize the MD5 context unsigned char md_value[EVP_MAX_MD_SIZE]; unsigned int md_len; if (EVP_DigestFinal_ex(mdctx, md_value, &md_len) != 1) { EVP_MD_CTX_free(mdctx); close(fd); return failure("Failed to finalize the MD5 context."); } EVP_MD_CTX_free(mdctx); // Close the file close(fd); // Format the MD5 checksum errno = 0; char* md5 = malloc(2 * md_len + 1); // +1 for the null terminator if (md5 == NULL) return failure("Failed to allocate memory for the MD5 checksum (%s).", strerror(errno)); for (unsigned int i = 0; i < md_len; i++) if (snprintf(md5 + 2 * i, 3, "%02x", md_value[i]) != 2) { free(md5); return failure("Failed to format the MD5 checksum."); } md5[2 * md_len] = '\0'; // Return the MD5 checksum *_md5 = md5; return success(); }