code / wheres

Lines496 YAML273 C178 Markdown33 make12
(133 lines)
1 #define _GNU_SOURCE
2 #include <dirent.h>
3 #include <fnmatch.h>
4 #include <stdbool.h>
5 #include <stdio.h>
6 #include <stdlib.h>
7 #include <string.h>
8 #include <unistd.h>
10 #include "argparse.h"
12 const char *program_name = "wheres";
13 static const char *USAGE =
14 "Usage:\n"
15 " patterns or paths...: patterns to search for or paths to search for them in\n"
16 " --help,-h: print this help message\n"
17 " --dir,-d <dir>: search in a given directory\n"
18 " --ignorecase,-i: search case-insensitively\n"
19 " --type,-t <type>: search for (f)iles, (d)irectories, (l)inks, (p)ipes, (s)ockets, (b)lock "
20 "devices, (c)haracter devices\n"
21 " --nul,-0: print a nul byte instead of a newline after each match\n";
23 #ifndef streq
24 #define streq(str, target) (strcmp(str, target) == 0)
25 #endif
27 static int pat_flags = FNM_PATHNAME;
28 static const char *type = NULL;
29 static char print_separator = '\n';
31 static inline bool matches_any_pattern(const char *name, int patc, const char *patv[patc]) {
32 if (patc == 0) return true;
33 for (int i = 0; i < patc; i++) {
34 if (fnmatch(patv[i], name, pat_flags) == 0) {
35 return true;
38 return false;
41 static inline bool is_valid_type(struct dirent *entry) {
42 if (type == NULL) return true;
43 switch (entry->d_type) {
44 case DT_FIFO: return strchr(type, 'p') != NULL;
45 case DT_CHR: return strchr(type, 'c') != NULL;
46 case DT_DIR: return strchr(type, 'd') != NULL;
47 case DT_BLK: return strchr(type, 'b') != NULL;
48 case DT_REG: return strchr(type, 'f') != NULL;
49 case DT_LNK: return strchr(type, 'l') != NULL;
50 case DT_SOCK: return strchr(type, 's') != NULL;
51 case DT_WHT: return strchr(type, 'w') != NULL;
52 default: return false;
56 void find(const char *path, int patc, const char *patv[patc]) {
57 DIR *dir = opendir(path);
58 if (!dir) return;
60 struct dirent *entry;
61 while ((entry = readdir(dir)) != NULL) {
62 const char *name = entry->d_name;
63 if (name[0] == '.' && (name[1] == '\0' || (name[1] == '.' && name[2] == '\0'))) {
64 // Skip '.' and '..'
65 continue;
68 if (is_valid_type(entry) && matches_any_pattern(name, patc, patv)) {
69 dprintf(STDOUT_FILENO, "%s/%s%c", path, name, print_separator);
72 if (entry->d_type == DT_DIR) {
73 char new_path[PATH_MAX];
74 snprintf(new_path, sizeof(new_path), "%s/%s", streq(path, "/") ? "" : path, name);
75 find(new_path, patc, patv);
78 closedir(dir);
81 int main(int argc, char *argv[]) {
82 program_name = argv[0];
83 int num_dirs = 0;
84 // This array is guaranteed to be big enough, since argc >= 1, and each argument
85 // adds at most one search dir.
86 const char *dirs[argc] = {};
87 int num_pats = 0;
88 const char *pats[argc] = {};
89 for (int i = 1; i < argc;) {
90 const char *dir;
91 const char *new_type;
92 if (streq(argv[i], "--")) {
93 break;
94 } else if ((dir = pop_str_arg(program_name, USAGE, "--dir", 'd', &i, argc, argv))) {
95 dirs[num_dirs++] = dir;
96 } else if (pop_bool_arg("--ignorecase", 'i', &i, argc, argv)) {
97 pat_flags |= FNM_CASEFOLD;
98 } else if (pop_bool_arg("--help", 'h', &i, argc, argv)) {
99 printf("%s - find files and directories\n\n%s", program_name, USAGE);
100 return 0;
101 } else if ((new_type = pop_str_arg(program_name, USAGE, "--type", 't', &i, argc, argv)) != NULL) {
102 type = new_type;
103 continue;
104 } else if (pop_bool_arg("--nul", '0', &i, argc, argv)) {
105 print_separator = '\0';
106 } else if (strchr(argv[i], '/') || streq(argv[i], "..") || streq(argv[i], ".") || streq(argv[i], "~")) {
107 dirs[num_dirs++] = argv[i];
108 i += 1;
109 } else if (argv[i][0] == '-') {
110 USAGE_ERROR(program_name, USAGE, "Unrecognized argument: %s", argv[i]);
111 } else {
112 pats[num_pats++] = argv[i];
113 i += 1;
117 if (num_dirs == 0) {
118 dirs[num_dirs++] = ".";
121 for (int i = 0; i < num_dirs; i++) {
122 static char real[PATH_MAX];
123 char *path = realpath(dirs[i], real);
124 if (!path) {
125 fprintf(stderr, "Not a valid path: %s", dirs[i]);
126 exit(1);
128 find(path, num_pats, pats);
129 if (path != real) free(path);
132 return 0;