2
3
4
5
6
7
8
9
10
11
14
15
16
17
18
19
20
28#include <linux/limits.h>
39#include <sys/sysinfo.h>
45#define print_file_type(mask, lf_type, dt_type, name)
47 fprintf(stderr
, "%c %08b (%3d) %08b (%2d) %s\n",
48 (mask & lf_type) ? '*' : ' ', lf_type, lf_type,
49 dt_type, dt_type, name);
55const char doc[] =
"lf list files\vIf specified, DIRECTORY is the top-level "
56 "directory to search. REGULAR_EXPRESSION is a properly "
57 "formatted regular expression for which matching files "
59static char args_doc[] =
"[DIRECTORY] [REGULAR_EXPRESSION]";
77 unsigned char include_perms;
78 unsigned char include_types;
79 unsigned char suppress_types;
103typedef struct TaskNode TaskNode;
136void debug_out(SearchFilters *,
int,
char **,
int);
137bool
init_find(SearchFilters *,
int,
char **);
142int scan_file(
char *,
const SearchFilters *,
const unsigned char);
145static struct argp_option options[] = {
146 {
"after",
'a',
"time", 0,
"Modified after YYYY-MM-DDTHH:MM:SS", 0},
147 {
"before",
'b',
"time", 0,
"Modified before YYYY-MM-DDTHH:MM:SS", 0},
148 {
"max_depth",
'd',
"number", 0,
"Depth into directory tree", 0},
149 {
"ere",
'e',
"regex", 0,
"Exclude regular expression", 0},
150 {
"ignore_case",
'i', 0, 0,
"Search ignore case", 0},
151 {
"include_perms",
'p',
"sgrwx", 0,
152 "s-setuid, g-setgid, r-read, w-write, x-execute", 0},
153 {
"re",
'r',
"regex", 0,
"Regular expression to search for", 0},
154 {
"include_types",
't',
"bcdplrsu", 0,
155 "b-block, c-character, d-directory, p-pipe, l-link, r-regular, s-"
158 {
"file_size_min",
's',
"size", 0,
159 "No Suffix-bytes, K-kilobytes, M-Megabytes, or G-Gigabytes", 0},
160 {
"user",
'u',
"user name", 0,
"User Name of file owner ", 0},
162 {
"exec",
'x',
"command", 0,
"execute external command", 0},
164 {
"debug",
'D',
"12345678", 0,
165 "1-config, 2-info, 3-warnings, 4-errors, 5-badlinks, 6-trace, 7-all, "
168 {
"include_hidden",
'H', 0, 0,
"Include hidden files", 0},
169 {
"follow_links",
'L', 0, 0,
"Follow symbolic links", 0},
170 {
"sort_reverse",
'R', 0, 0,
"Sort in Reverse order", 0},
171 {
"sort",
'S', 0, 0,
"Sort in Ascending order", 0},
172 {
"nthreads",
'T',
"threads", 0,
"Number of nthreads", 0},
176static error_t parse_opt(
int key,
char *arg,
struct argp_state *state) {
177 SearchFilters *f = state->input;
179 bool a_toi_error = false;
184 if (f->after && f->before && f->before < f->after) {
185 fprintf(stderr,
"-b time must be greater than -a time.\n");
191 if (f->after && f->before && f->before < f->after) {
192 fprintf(stderr,
"-b time must be greater than -a time.\n");
197 if (arg && arg[0] !=
'\0')
198 f->max_depth =
a_toi(arg
, &a_toi_error
);
202 if (arg && arg[0] !=
'\0') {
208 f->report_config = true;
211 f->report_info = true;
214 f->report_warnings = true;
217 f->report_errors = true;
220 f->report_badlinks = true;
223 f->report_trace = true;
226 f->report_config = true;
227 f->report_info = true;
228 f->report_warnings = true;
229 f->report_errors = true;
230 f->report_badlinks = true;
231 f->report_all = true;
234 f->only_errors = true;
245 f->ere = strdup(arg);
249 f->include_hidden = true;
258 f->follow_links = true;
285 f->sort_reverse = true;
295 f->file_size_min = (intmax_t)(
a_to_ul(arg
));
298 if (arg && arg[0] !=
'\0')
306 f->include_types |=
LF_BLK;
309 f->include_types |=
LF_CHR;
312 f->include_types |=
LF_DIR;
318 f->include_types |=
LF_LNK;
323 f->include_types |=
LF_REG;
337 f->user_name = strdup(arg);
338 struct passwd *pwd = getpwnam(arg);
340 f->user_id = (uintmax_t)pwd->pw_uid;
343 fprintf(stderr,
"User '%s' not found.\n", arg);
349 f->exec = strdup(arg);
353 if (state->arg_num == 0 || state->arg_num == 1) {
354 lfargs[state->arg_num] = arg;
355 lfargc = state->arg_num + 1;
363 return ARGP_ERR_UNKNOWN;
367static struct argp argp = {options, parse_opt, args_doc,
doc,
370int main(
int argc,
char **argv) {
372 SearchFilters *f = (SearchFilters *)calloc(1,
sizeof(SearchFilters));
373 f->ignore_case = false;
375 f->sort_reverse = false;
376 f->include_hidden = false;
381 f->follow_links = false;
383 char tmp_str[PATH_MAX];
384 argp_parse(&argp, argc, argv, 0, 0, f);
389 f->base_path = strdup(tmp_str);
391 f->re = strdup(
lfargs[0]);
396 "lf: arg1: '%s' is neither a directory nor a valid regex.\n",
402 if (!f->base_path || f->base_path[0] ==
'\0') {
406 f->base_path = strdup(tmp_str);
409 f->re = strdup(
lfargs[1]);
413 "lf: '%s' is neither a directory nor a valid regular "
419 if (f->base_path ==
nullptr || f->base_path[0] ==
'\0')
420 f->base_path = strdup(
".");
429
430
431
432
433
434
438 eargv[eargc++] = strdup(
"sort");
440 eargv[eargc++] = strdup(
"-r");
443 int save_fd = dup(STDOUT_FILENO);
452 execvp(eargv[0], eargv);
456 dup2(fds[1], STDOUT_FILENO);
458 setvbuf(stdout, NULL, _IOLBF, 0);
466 dup2(save_fd, STDOUT_FILENO);
467 waitpid(pid1, &wstatus, 0);
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486bool
init_find(SearchFilters *f,
int argc,
char **argv) {
488 if (!f->include_types)
489 f->include_types = 0xff;
490 if (f->include_types)
491 f->suppress_types = f->include_types ^ 0xff;
494 f->include_hidden = !(f->flags &
LF_HIDE);
496 f->reg_flags = REG_EXTENDED;
498 f->reg_flags |= REG_ICASE;
500 reti = regcomp(&f->compiled_re, f->re, f->reg_flags);
502 fprintf(stderr,
"lf: '%s' Invalid pattern\n", f->re);
503 regfree(&f->compiled_re);
508 reti = regcomp(&f->compiled_ere, f->ere, f->reg_flags);
510 fprintf(stderr,
"lf: '%s' Invalid exclude pattern\n", f->ere);
511 regfree(&f->compiled_ere);
515 int n = get_nprocs();
527 if (stat(f->base_path, &st) == 0) {
528 if (S_ISDIR(st.st_mode)) {
529 TaskNode *child_task = malloc(
sizeof(TaskNode));
530 child_task->dir_path = strdup(f->base_path);
531 child_task->depth = 0;
532 child_task->next_task = NULL;
533 child_task->history = malloc(
sizeof(History));
534 child_task->history[0].dev = st.st_dev;
535 child_task->history[0].ino = st.st_ino;
538 for (
int i = 0; i <
nthreads; i++) {
539 pthread_create(&threads[i], NULL,
finder, f);
547 pthread_join(threads[i], NULL);
550 "Warning: Base path '%s' is not a directory. No "
551 "files will be found.\n",
558 regfree(&f->compiled_re);
561 regfree(&f->compiled_ere);
573
574
575
576
577
578
579
581void debug_out(SearchFilters *f,
int argc,
char **argv,
int nthreads) {
586 bool addspace_before = false;
587 if (f->debug && (f->report_config || f->report_info || f->report_all)) {
589 for (i = 0; i < argc; i++) {
590 len = len + strlen(argv[i]);
592 fprintf(stderr,
"\n");
593 len = strlen(argv[i]);
594 addspace_before = false;
596 if (addspace_before) {
597 fprintf(stderr,
" ");
600 fprintf(stderr,
"%s", argv[i]);
601 addspace_before = true;
603 fprintf(stderr,
"\n\n");
605 fprintf(stderr,
"lf debug %s\n",
606 f->debug ?
"true" :
" false");
607 fprintf(stderr,
" 1-config %s\n",
608 f->report_config ?
"true" :
"| false");
609 fprintf(stderr,
" 2-info %s\n",
610 f->report_info ?
"true" :
"| false");
611 fprintf(stderr,
" 3-warnings %s\n",
612 f->report_warnings ?
"true" :
"| false");
613 fprintf(stderr,
" 4-errors %s\n",
614 f->report_errors ?
"true" :
"| false");
615 fprintf(stderr,
" 5-badlinks %s\n",
616 f->report_trace ?
"true" :
"| false");
617 fprintf(stderr,
" 6-trace %s\n",
618 f->report_trace ?
"true" :
"| false");
619 fprintf(stderr,
" 7-all %s\n",
620 f->report_all ?
"true" :
"| false");
621 fprintf(stderr,
" 8-only_errors %s\n",
622 f->only_errors ?
"true" :
"| false");
623 fprintf(stderr,
"\n");
624 fprintf(stderr,
"Search directory: %s\n\n", f->base_path);
625 fprintf(stderr,
"Using %d threads\n\n", nthreads);
626 fprintf(stderr,
"File types preceeded by an asterisk (\"*\") will be included:\n\n");
627 fprintf(stderr,
" LF type DT type\n");
636 fprintf(stderr,
"\n");
638 fprintf(stderr,
"User: %s (%ju)\n", f->user_name, f->user_id);
639 if (f->include_perms) {
641 fprintf(stderr,
" %08b Execute\n",
LF_IXUSR);
643 fprintf(stderr,
" %08b Write\n",
LF_IWUSR);
645 fprintf(stderr,
" %08b Read\n",
LF_IRUSR);
647 fprintf(stderr,
" %08b SETUID\n",
LF_ISUID);
649 fprintf(stderr,
" %08b SETGID\n",
LF_ISGID);
651 fprintf(stderr,
"\n");
653 fprintf(stderr,
"Include regex: %s\n\n", f->re);
655 fprintf(stderr,
"Exclude regex: %s\n\n", f->ere);
660 fprintf(stderr,
"Modified after: %s\n\n", buf);
666 fprintf(stderr,
"Modified before: %s\n\n", buf);
669 if (f->file_size_min) {
670 const char *units[] = {
"b",
"Kb",
"Mb",
"Gb",
"Tb",
"Pb",
"Eb"};
671 off_t size = f->file_size_min;
673 while (size >= 1024 && i < 6) {
679 fprintf(stderr,
"Minimum file size: %s\n\n", buffer);
682 fprintf(stderr,
"Max depth: %d\n\n", f->max_depth);
684 fprintf(stderr,
"Ignore case in regex matching.\n\n");
685 if (f->include_hidden)
686 fprintf(stderr,
"Include hidden files.\n\n");
688 fprintf(stderr,
"Follow symbolic links.\n\n");
690 fprintf(stderr,
"Sort output in ascending order.\n\n");
692 fprintf(stderr,
"Sort output in reverse order.\n\n");
693 if (f->report_config && !f->report_all)
699
700
701
702
703
704
705
706
707
715 qtail->next_task = new_task;
733
734
735
736
737
738
739
740
781 TaskNode *temp =
qhead;
796
797
798
799
800
801
802
803
804
805
806
807
809 SearchFilters *f = (SearchFilters *)arg;
810 char lnk_path[PATH_MAX] = {
'\0'};
830 openat(AT_FDCWD, current_task->dir_path, O_RDONLY | O_DIRECTORY);
832 if (f->debug && (f->report_warnings || f->report_errors ||
833 f->report_badlinks || f->report_all))
834 fprintf(stderr,
"OPEN_FAIL,%s,%s\n", current_task->dir_path,
837 free(current_task->dir_path);
838 free(current_task->history);
844 DIR *dir = fdopendir(dir_fd);
846 if (f->debug && (f->report_warnings || f->report_errors ||
847 f->report_badlinks || f->report_all))
848 fprintf(stderr,
"\nFDOPENDIR_FAIL,%s,%s\n",
849 current_task->dir_path, strerror(errno));
852 free(current_task->dir_path);
853 free(current_task->history);
860 unsigned char effective_type;
862 struct dirent *entry;
863 while ((entry = readdir(dir)) != NULL) {
866 effective_type =
'\0';
867 if (entry->d_name[0] ==
'.') {
868 if (entry->d_name[1] ==
'\0')
870 if (entry->d_name[1] ==
'.' && entry->d_name[2] ==
'\0')
872 if (!f->include_hidden)
875 char full_path[PATH_MAX] = {
'\0'};
876 stpcpy(stpcpy(stpcpy(full_path, current_task->dir_path),
"/"),
887 rc = fstatat(AT_FDCWD, full_path, &st, AT_SYMLINK_NOFOLLOW);
889 if (f->debug && (f->report_errors || f->report_warnings ||
890 f->report_badlinks || f->report_all))
891 fprintf(stderr,
"LSTAT_FAIL,%s,%s\n", full_path,
896 effective_type = (st.st_mode & S_IFMT) >> 12;
908 if (S_ISLNK(st.st_mode)) {
910 rc = fstatat(AT_FDCWD, full_path, &st, 0);
912 if (f->debug && (f->report_all || f->report_warnings ||
913 f->report_errors || f->report_badlinks)) {
914 fprintf(stderr,
"STAT_FAIL,%s,%s\n", full_path,
921 effective_type = (st.st_mode & S_IFMT) >> 12;
933 if (effective_type == DT_DIR) {
934 if (f->max_depth != 0 && current_task->depth == f->max_depth)
940 bool cycle_found = false;
941 if (f->debug && (f->report_trace || f->report_all))
942 fprintf(stderr,
"Checking for cycles in: %s\n", full_path);
943 for (
int i = 0; i < current_task->depth; i++) {
944 if (f->debug && (f->report_trace || f->report_all)) {
946 if (current_task->history[i].ino == st.st_ino)
947 fprintf(stderr,
"%3d %ju %ju<===========\n", i,
948 current_task->history[i].ino, st.st_ino);
950 fprintf(stderr,
"%3d %ju %ju\n", i,
951 current_task->history[i].ino, st.st_ino);
953 if (current_task->history[i].dev == st.st_dev &&
954 current_task->history[i].ino == st.st_ino) {
960 if (f->debug && (f->report_warnings || f->report_errors ||
961 f->report_trace || f->report_badlinks ||
964 readlink(full_path, lnk_path,
sizeof(lnk_path) - 1);
966 lnk_path[len] =
'\0';
967 fprintf(stderr,
"CYCLIC_LINK,%s,%s\n", full_path,
970 fprintf(stderr,
"CYCLIC_LINK,%s\n", full_path);
989 TaskNode *child_task = malloc(
sizeof(TaskNode));
990 child_task->dir_path = strdup(full_path);
991 child_task->depth = current_task->depth + 1;
992 child_task->next_task = NULL;
993 child_task->history =
994 malloc((child_task->depth) *
sizeof(History));
995 if (current_task->depth > 0) {
996 memcpy(child_task->history, current_task->history,
997 sizeof(History) * current_task->depth);
999 child_task->history[current_task->depth].dev = st.st_dev;
1000 child_task->history[current_task->depth].ino = st.st_ino;
1006 free(current_task->dir_path);
1007 free(current_task->history);
1014
1015
1016
1017
1018
1020 const unsigned char effective_type) {
1021 regmatch_t pmatch[2];
1022 bool stat_cached = false;
1025 if (f->suppress_types & effective_type)
1030 regexec(&f->compiled_re, file_spec, f->compiled_re.re_nsub + 1,
1031 pmatch, f->reg_flags);
1032 if (reti == REG_NOMATCH) {
1033 if (f->debug && (f->report_info || f->report_all))
1034 fprintf(stderr,
"Regex no match: %s\n", file_spec);
1038 regerror(reti, &f->compiled_re, errbuf,
sizeof(errbuf));
1039 if (f->debug && (f->report_errors || f->report_all))
1040 fprintf(stderr,
"regex error: %s\n", errbuf);
1048 regexec(&f->compiled_ere, file_spec, f->compiled_re.re_nsub + 1,
1049 pmatch, f->reg_flags);
1052 if (reti != REG_NOMATCH) {
1055 regerror(reti, &f->compiled_ere, errbuf,
sizeof(errbuf));
1056 if (f->debug && (f->report_errors || f->report_all))
1057 fprintf(stderr,
"Exclude regex error: %s\n", errbuf);
1063 stat_cached = false;
1066 if ((f->flags &
LF_USER) && stat(file_spec, &sb) == 0) {
1068 if (sb.st_uid != f->user_id)
1071 if (f->include_perms) {
1073 if (stat(file_spec, &sb) == 0)
1076 if ((f->include_perms &
LF_IRUSR) && !(sb.st_mode & S_IRUSR))
1078 else if ((f->include_perms &
LF_IWUSR) && !(sb.st_mode & S_IWUSR))
1080 else if ((f->include_perms &
LF_IXUSR) && !(sb.st_mode & S_IXUSR))
1082 else if ((f->include_perms &
LF_ISUID) && !(sb.st_mode & S_ISUID))
1084 else if ((f->include_perms &
LF_ISGID) && !(sb.st_mode & S_ISGID))
1089 if (stat(file_spec, &sb) == 0)
1092 if (stat_cached && sb.st_mtime > f->before)
1097 if (stat(file_spec, &sb) == 0)
1100 if (stat_cached && sb.st_mtime < f->after)
1103 if (f->file_size_min) {
1105 if (stat(file_spec, &sb) == 0)
1108 if (stat_cached && sb.st_size < f->file_size_min)
1111 if (effective_type == DT_DIR) {
1112 char *file_p = file_spec;
1113 while (*file_p++ !=
'\0')
1119 if (file_spec[0] ==
'.' && file_spec[1] ==
'/')
1120 printf(
"%s\n", &file_spec[2]);
1122 printf(
"%s\n", file_spec);
#define max(a, b)
max macro evaluates two expressions, returning greatest result.
void sort_lf_output(SearchFilters *, int, char **)
void * finder(void *)
Worker thread function to process directories from the queue.
TaskNode * dequeue_dir()
Dequeue a directory dir_path for processing by finder threads.
void debug_out(SearchFilters *, int, char **, int)
Output debug information about the search filters and configuration.
#define print_file_type(mask, lf_type, dt_type, name)
void enqueue_dir(TaskNode *)
Enqueue a directory dir_path for processing by finder threads.
pthread_mutex_t queue_mutex
int scan_file(char *, const SearchFilters *, const unsigned char)
scan a single file against search filters
bool init_find(SearchFilters *, int, char **)
Initialize the file search based on the provided SearchFilters and start finder threads.
const char * argp_program_version
const char * argp_program_bug_address
int main(int argc, char **argv)
size_t strnz__cpy(char *, const char *, size_t)
safer alternative to strncpy
bool parse_local_timestamp(const char *, time_t *)
Parses an ISO 8601 timestamp string in local time and converts it to time_t.
char * get_local_timestamp()
Returns the current local time as an ISO 8601 formatted string.
bool is_directory(const char *)
Checks if the given path is a directory.
size_t ssnprintf(char *, size_t, const char *,...)
ssnprintf was designed to be a safer alternative to snprintf.
bool expand_tilde(char *, int)
Replaces "~/" in string with the user's home directory.
bool is_valid_regex(const char *)
Checks if the given regular expression pattern is valid.
bool is_symlink_to_dir(const char *)
Checks if the given path is a symbolic link to a directory.
int a_toi(char *, bool *)
a safer alternative to atoi() for converting ASCII strings to integers.
char * get_ip_addresses(char *, int)
Retrieves the IP addresses of the local machine and formats them into a string.
char * format_local_timestamp(time_t, char *, size_t)
Formats a time_t as an ISO 8601 string in local time.
char * get_user_str(char *, size_t)
Retrieves the current user's name and UID, and formats it into a string.
unsigned long a_to_ul(const char *)
Converts a string to an unsigned long long integer, with support for suffixes K, M,...