#define _GNU_SOURCE #include #include #include #include #include #include #include #include "argparse.h" const char *program_name = "wheres"; static const char *USAGE = "Usage:\n" " patterns or paths...: patterns to search for or paths to search for them in\n" " --help,-h: print this help message\n" " --dir,-d : search in a given directory\n" " --ignorecase,-i: search case-insensitively\n" " --type,-t : search for (f)iles, (d)irectories, (l)inks, (p)ipes, (s)ockets, (b)lock " "devices, (c)haracter devices\n" " --nul,-0: print a nul byte instead of a newline after each match\n"; #ifndef streq #define streq(str, target) (strcmp(str, target) == 0) #endif static int pat_flags = FNM_PATHNAME; static const char *type = NULL; static char print_separator = '\n'; static inline bool matches_any_pattern(const char *name, int patc, const char *patv[patc]) { if (patc == 0) return true; for (int i = 0; i < patc; i++) { if (fnmatch(patv[i], name, pat_flags) == 0) { return true; } } return false; } static inline bool is_valid_type(struct dirent *entry) { if (type == NULL) return true; switch (entry->d_type) { case DT_FIFO: return strchr(type, 'p') != NULL; case DT_CHR: return strchr(type, 'c') != NULL; case DT_DIR: return strchr(type, 'd') != NULL; case DT_BLK: return strchr(type, 'b') != NULL; case DT_REG: return strchr(type, 'f') != NULL; case DT_LNK: return strchr(type, 'l') != NULL; case DT_SOCK: return strchr(type, 's') != NULL; case DT_WHT: return strchr(type, 'w') != NULL; default: return false; } } void find(const char *path, int patc, const char *patv[patc]) { DIR *dir = opendir(path); if (!dir) return; struct dirent *entry; while ((entry = readdir(dir)) != NULL) { const char *name = entry->d_name; if (name[0] == '.' && (name[1] == '\0' || (name[1] == '.' && name[2] == '\0'))) { // Skip '.' and '..' continue; } if (is_valid_type(entry) && matches_any_pattern(name, patc, patv)) { dprintf(STDOUT_FILENO, "%s/%s%c", path, name, print_separator); } if (entry->d_type == DT_DIR) { char new_path[PATH_MAX]; snprintf(new_path, sizeof(new_path), "%s/%s", streq(path, "/") ? "" : path, name); find(new_path, patc, patv); } } closedir(dir); } int main(int argc, char *argv[]) { program_name = argv[0]; int num_dirs = 0; // This array is guaranteed to be big enough, since argc >= 1, and each argument // adds at most one search dir. const char *dirs[argc] = {}; int num_pats = 0; const char *pats[argc] = {}; for (int i = 1; i < argc;) { const char *dir; const char *new_type; if (streq(argv[i], "--")) { break; } else if ((dir = pop_str_arg(program_name, USAGE, "--dir", 'd', &i, argc, argv))) { dirs[num_dirs++] = dir; } else if (pop_bool_arg("--ignorecase", 'i', &i, argc, argv)) { pat_flags |= FNM_CASEFOLD; } else if (pop_bool_arg("--help", 'h', &i, argc, argv)) { printf("%s - find files and directories\n\n%s", program_name, USAGE); return 0; } else if ((new_type = pop_str_arg(program_name, USAGE, "--type", 't', &i, argc, argv)) != NULL) { type = new_type; continue; } else if (pop_bool_arg("--nul", '0', &i, argc, argv)) { print_separator = '\0'; } else if (strchr(argv[i], '/') || streq(argv[i], "..") || streq(argv[i], ".") || streq(argv[i], "~")) { dirs[num_dirs++] = argv[i]; i += 1; } else if (argv[i][0] == '-') { USAGE_ERROR(program_name, USAGE, "Unrecognized argument: %s", argv[i]); } else { pats[num_pats++] = argv[i]; i += 1; } } if (num_dirs == 0) { dirs[num_dirs++] = "."; } for (int i = 0; i < num_dirs; i++) { static char real[PATH_MAX]; char *path = realpath(dirs[i], real); if (!path) { fprintf(stderr, "Not a valid path: %s", dirs[i]); exit(1); } find(path, num_pats, pats); if (path != real) free(path); } return 0; }