#include #include #include #include #include #include #include #include #define print(s) fputs((s), stdout) #ifdef __cplusplus #define restrict __restrict__ #endif #if __GNUC__ #define likely(x) __builtin_expect(!!(x), true) #define unlikely(x) __builtin_expect(!!(x), false) #else #define likely(x) (x) #define unlikely(x) (x) #endif static bool rename_mode = false; static const char *prog_name = NULL; static bool verbose_mode = false; static size_t len1 = 0, len2 = 0; static void err() { perror("Error"); exit(1); } static bool is_valid_utf8(const char *str, size_t len) { size_t i = 0; while (i < len) { if ((str[i] & 0x80) == 0) { // ASCII character (0xxxxxxx) i++; } else if ((str[i] & 0xE0) == 0xC0) { // 2-byte UTF-8 character (110xxxxx) if (i + 1 >= len || (str[i + 1] & 0xC0) != 0x80) return 0; i += 2; } else if ((str[i] & 0xF0) == 0xE0) { // 3-byte UTF-8 character (1110xxxx) if (i + 2 >= len || (str[i + 1] & 0xC0) != 0x80 || (str[i + 2] & 0xC0) != 0x80) return 0; i += 3; } else if ((str[i] & 0xF8) == 0xF0) { // 4-byte UTF-8 character (11110xxx) if (i + 3 >= len || (str[i + 1] & 0xC0) != 0x80 || (str[i + 2] & 0xC0) != 0x80 || (str[i + 3] & 0xC0) != 0x80) return 0; i += 4; } else { // Invalid UTF-8 encoding return false; } } return true; } static void string_replace(const char *source, const char *find, const char *replace, size_t source_len, size_t find_len, size_t replace_len, bool *use_orig, size_t *num_matches, char **new_text) { size_t matches = 0; // Count the occurrences of 'find' in 'source' const char *p = source; while ((p = strstr(p, find)) != NULL) { ++matches; p += find_len; } if (!num_matches) { *use_orig = true; return; } *use_orig = false; *num_matches = matches; // Calculate the final length of the replaced string size_t final_len = source_len + matches * (replace_len - find_len); // Allocate memory for the replaced string char *result = (char *)malloc(final_len + 1); *new_text = result; if (unlikely(result == NULL)) { err(); } // Perform the replacement char *rp = result; p = source; while ((p = strstr(p, find)) != NULL) { size_t prefix_len = p - source; memcpy(rp, source, prefix_len); rp += prefix_len; memcpy(rp, replace, replace_len); rp += replace_len; p += find_len; source = p; } // Copy the remaining part of the source string size_t remaining_len = strlen(source); memcpy(rp, source, remaining_len); rp[remaining_len] = '\0'; } static void proc_stdin(const char *restrict one, const char *restrict two) { const size_t inc = 4096; size_t size = inc + 1; char *data = (char *)malloc(size); size_t base = 0; size_t num_to_read = inc; for (;;) { if (base + num_to_read > size) { data = (char *)realloc( data, (base + num_to_read) * 1.5); // 1.5 is safe because lowest it coud be is 0 + // 4096 * 1.5, which is always bigger than 4096 } size_t br = fread(data + base, 1, num_to_read, stdin); base += br; if (br < num_to_read) { data[base] = '\0'; break; } } bool use_orig; size_t num_matches; char *new_text; string_replace(data, one, two, base, len1, len2, &use_orig, &num_matches, &new_text); print(new_text); free(data); if (!use_orig) { free(new_text); return; } return; } static void process_file(const char *restrict filename, const char *restrict one, const char *restrict two) { if (!strcmp("/dev/stdin", filename)) { if (verbose_mode) { print("STDIN\n"); } proc_stdin(one, two); return; } if (!strncmp(filename, "-", 1)) { proc_stdin(one, two); return; } FILE *f = fopen(filename, "r"); if (unlikely(f == NULL)) { printf("Failed to open file: '%s'\n", filename); return; } // Get the size of the file fseek(f, 0, SEEK_END); size_t fsize = ftell(f); if (fsize == 0) { fclose(f); return; } fseek(f, 0, SEEK_SET); // Allocate memory for the file content // Add to surpress valgrind warnings. char *text = (char *)malloc(fsize + 1); if (unlikely(text == NULL)) { fclose(f); printf("Memory allocation failed\n"); return; } size_t bytes_read = 0; size_t total_bytes_read = 0; // Read the file content in chunks while (total_bytes_read < fsize) { size_t len_to_read = fsize - total_bytes_read; if (len_to_read > 4096) { len_to_read = 4096; } bytes_read = fread(text + total_bytes_read, 1, len_to_read, f); if (unlikely(bytes_read == 0)) { fclose(f); free(text); printf("Failed to read file: '%s'\n", filename); return; } if (!is_valid_utf8(text + total_bytes_read, bytes_read)) { // printf("Skipping non-text file '%s'\n", filename); fclose(f); free(text); return; } total_bytes_read += bytes_read; } fclose(f); text[fsize] = '\0'; bool use_orig; size_t num_matches; char *new_text; string_replace(text, one, two, total_bytes_read, len1, len2, &use_orig, &num_matches, &new_text); free(text); if (use_orig) { return; } const size_t rep_len = strlen(new_text); f = fopen(filename, "w"); if (unlikely(f == NULL)) { free(text); printf("Failed to open file for writing: '%s'\n", filename); return; } // Write the replaced content to the file { size_t bytes_written = fwrite(new_text, 1, rep_len, f); if (bytes_written == 0) { fclose(f); free(new_text); printf("Failed to write file: '%s'\n", filename); return; } } free(new_text); fclose(f); if (num_matches == 1) { printf("%s: %s: 1 replacement for %s => %s\n", prog_name, filename, one, two); } else { printf("%s: %s: %ld replacements for %s => %s\n", prog_name, filename, num_matches, one, two); } } static void process_directory(const char *dirPath, const char *one, const char *two, bool check_file) { if (strstr(dirPath, "/.")) { // printf("Skipping hidden file '%s'\n", filename); return; } DIR *dir = opendir(dirPath); if (dir == NULL) { if (check_file) { process_file(dirPath, one, two); return; } printf("Failed to open directory: '%s'\n", dirPath); return; } struct dirent *entry; while ((entry = readdir(dir)) != NULL) { if ((!strcmp(entry->d_name, ".")) || strcmp(entry->d_name, "..")) continue; char path[PATH_MAX]; snprintf(path, sizeof(path), "%s/%s", dirPath, entry->d_name); if (rename_mode) { bool use_orig; size_t num_matches; char *new_text; string_replace(path, one, two, strlen(path), strlen(one), strlen(two), &use_orig, &num_matches, &new_text); if (use_orig) continue; printf("%s: Renamed %s => %s\n", prog_name, path, new_text); rename(path, new_text); free(new_text); continue; } struct stat statbuf; if (stat(path, &statbuf) == -1) { printf("Failed to get file information: %s\n", path); continue; } if (S_ISDIR(statbuf.st_mode)) { process_directory(path, one, two, false); } else if (S_ISREG(statbuf.st_mode)) { process_file(path, one, two); } } closedir(dir); } int main(int argc, char **argv) { prog_name = basename(argv[0]); if (argc < 2) { printf( "Usage: %s [optional list of files instead of " "recursive search]\n\nIf a file is \"-\" or stdin, read from stdin " "and output to stdout.\nOptions" "\n\n\n -r, rename files instead " "of replacing contents of files. find and replace in file and dir " "names instead" "\n\n\n -v, verbose mode\n\n\n -- Indicate end of options.\n\n", prog_name); return 1; } char *actual_argv[argc]; int actual_argc = 0; bool auto_continue = false; size_t lens[argc]; for (int i = 0; i < argc; ++i) { if (auto_continue) continue; { size_t len; len = strlen(argv[i]); if (len == 2) { if (argv[i][0] == '-') { switch (argv[i][1]) { case '-': auto_continue = true; continue; case 'r': rename_mode = true; if (verbose_mode) { print("File name replace mode\n"); } continue; case 'v': verbose_mode = true; print("Verbose mode\n"); continue; } } } lens[actual_argc] = len; actual_argv[actual_argc] = argv[i]; actual_argc++; } } len1 = lens[1]; len2 = lens[2]; // printf("%d %d\n", actual_argc, actual_argc); if (actual_argc == 3) { process_directory(".", actual_argv[1], actual_argv[2], false); } else if (actual_argc > 3) { for (int i = 3; i < actual_argc; ++i) { if (verbose_mode) { printf("Processing directory '%s'\n", argv[i]); } {} process_directory(actual_argv[i], actual_argv[1], actual_argv[2], true); } } return 0; }