/* * gg - Grokkin' Grep: a minimal, fast, portable grep-like file finder * * Usage: gg SEARCH_TERM [FILE_OR_DIRECTORY] * * Created by Luxferre in 2026, released into the public domain */ #ifndef _POSIX_C_SOURCE #define _POSIX_C_SOURCE 200809L #endif #ifndef _DEFAULT_SOURCE #define _DEFAULT_SOURCE #endif #include #include #include #include #include #include #include #include #include #include #ifndef PATH_MAX #define PATH_MAX 4096 #endif #define IO_BUFSZ (32u * 1024) /* per-thread read buffer */ /* ---------- shared scan state ---------- */ static const char *needle; static size_t needle_len; static int found_any = 0; static pthread_mutex_t found_mx = PTHREAD_MUTEX_INITIALIZER; /* ---------- directory work queue (parallel walk + scan) ---------- */ typedef struct DirJob { char *path; struct DirJob *next; } DirJob; static DirJob *dir_stack = NULL; static pthread_mutex_t dir_mx = PTHREAD_MUTEX_INITIALIZER; static void push_dir(const char *path) { DirJob *j = malloc(sizeof *j); if (j == NULL) return; j->path = strdup(path); if (j->path == NULL) { free(j); return; } pthread_mutex_lock(&dir_mx); j->next = dir_stack; dir_stack = j; pthread_mutex_unlock(&dir_mx); } static void die(const char *msg) { perror(msg); exit(2); } static char *pop_dir(void) { pthread_mutex_lock(&dir_mx); DirJob *j = dir_stack; if (j != NULL) dir_stack = j->next; pthread_mutex_unlock(&dir_mx); if (j == NULL) return NULL; char *p = j->path; free(j); return p; } /* ---------- scan one open fd (chunked read + memmem, binary-skipping) ---------- */ typedef struct { char *buf; char *carry; } ScanBufs; static int scan_fd(int fd, ScanBufs *sb) { char *buf = sb->buf; char *carry = sb->carry; size_t carryn = 0; int hit = 0; for (;;) { ssize_t r = read(fd, buf + carryn, IO_BUFSZ); if (r < 0) { if (errno == EINTR) continue; break; } if (r == 0) break; /* EOF */ size_t wn = carryn + (size_t)r; /* Binary detection: a NUL byte marks a binary file, which we skip (like * ripgrep). Stop reading the moment we see one so we do not waste * bandwidth scanning the rest of a large binary file. If a needle occurs * before the first NUL it is still a valid text match. */ void *nul = memchr(buf, '\0', wn); size_t text_end = (nul != NULL) ? (size_t)((const char *)nul - buf) : wn; if (text_end >= needle_len) { if (memmem(buf, text_end, needle, needle_len) != NULL) { hit = 1; break; } } if (nul != NULL) break; /* binary file: no further scanning */ /* No NUL and no match yet: carry the tail across chunks. */ if (wn >= needle_len) carryn = needle_len - 1; else carryn = wn; memcpy(carry, buf + wn - carryn, carryn); memcpy(buf, carry, carryn); } return hit; } static void report_match(const char *path) { pthread_mutex_lock(&found_mx); found_any = 1; puts(path); pthread_mutex_unlock(&found_mx); } static void scan_file(const char *path, ScanBufs *sb) { int fd = open(path, O_RDONLY | O_CLOEXEC); if (fd < 0) return; /* unreadable entries are silently skipped */ if (scan_fd(fd, sb)) report_match(path); close(fd); } /* ---------- worker: pop a directory, scan its files, enqueue subdirs ---------- */ static void *worker(void *arg) { (void)arg; ScanBufs sb; sb.buf = malloc(IO_BUFSZ + needle_len); sb.carry = malloc(needle_len); if (sb.buf == NULL || sb.carry == NULL) { free(sb.buf); free(sb.carry); return NULL; } char child[PATH_MAX]; for (;;) { char *dir = pop_dir(); if (dir == NULL) break; DIR *d = opendir(dir); if (d != NULL) { struct dirent *ent; while ((ent = readdir(d)) != NULL) { if (strcmp(ent->d_name, ".") == 0 || strcmp(ent->d_name, "..") == 0) continue; int n = snprintf(child, sizeof child, "%s/%s", dir, ent->d_name); if (n < 0 || (size_t)n >= sizeof child) continue; if (ent->d_type == DT_REG) { scan_file(child, &sb); } else if (ent->d_type == DT_DIR) { push_dir(child); } else if (ent->d_type == DT_LNK) { continue; /* do not follow symlinks: avoids cycles */ } else { struct stat st; if (lstat(child, &st) != 0) continue; if (S_ISDIR(st.st_mode)) push_dir(child); else if (S_ISREG(st.st_mode)) scan_file(child, &sb); } } closedir(d); } free(dir); } free(sb.buf); free(sb.carry); return NULL; } int main(int argc, char **argv) { if (argc < 2 || argc > 3) { fprintf(stderr, "usage: %s SEARCH_TERM [FILE_OR_DIRECTORY]\n", argv[0]); return 2; } needle = argv[1]; needle_len = strlen(needle); if (needle_len == 0) { fprintf(stderr, "gg: empty search term\n"); return 2; } char cwd[PATH_MAX]; const char *root; if (argc == 3) { struct stat st; if (stat(argv[2], &st) != 0) { fprintf(stderr, "gg: %s: %s\n", argv[2], strerror(errno)); return 2; } if (!S_ISDIR(st.st_mode) && !S_ISREG(st.st_mode)) { fprintf(stderr, "gg: %s: not a regular file or directory\n", argv[2]); return 2; } root = argv[2]; } else { if (getcwd(cwd, sizeof cwd) == NULL) die("getcwd"); root = cwd; } struct stat st; if (stat(root, &st) != 0) { fprintf(stderr, "gg: %s: %s\n", root, strerror(errno)); return 2; } /* Seed the directory work queue, then let the workers walk and scan in * parallel: each worker pops a directory, scans its regular files inline, * and pushes any sub-directories it finds. This overlaps the (metadata) * walk with the (data) scan across all online CPUs. */ if (S_ISREG(st.st_mode)) { scan_file(root, &(ScanBufs){ malloc(IO_BUFSZ + needle_len), malloc(needle_len) }); } else { push_dir(root); } long nproc = sysconf(_SC_NPROCESSORS_ONLN); if (nproc < 1) nproc = 1; int nth = (int)nproc; if (nth > 256) nth = 256; pthread_t t[256]; int started = 0; for (int i = 0; i < nth; i++) { if (pthread_create(&t[i], NULL, worker, NULL) != 0) break; started++; } for (int i = 0; i < started; i++) pthread_join(t[i], NULL); return found_any ? 0 : 1; }