123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133 | #define _GNU_SOURCE
#include <dirent.h>
#include <fnmatch.h>
#include <stdbool.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#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 <dir>: search in a given directory\n"
" --ignorecase,-i: search case-insensitively\n"
" --type,-t <type>: 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;
}
|