2
3
4
5
6
7
8
9
10
11
14
15
16
17
18
19
20
22#include "include/cm.h"
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 "
61static char args_doc[] =
"[DIRECTORY] [REGULAR_EXPRESSION]";
79 unsigned char include_perms;
80 unsigned char include_types;
81 unsigned char suppress_types;
103 0, 0b00000001, 0b00000010, 0, 0b00000100, 0, 0b00001000, 0, 0b00010000, 0,
104 0b00100000, 0, 0b01000000, 0, 0b10000000};
110typedef struct TaskNode TaskNode;
144void debug_out(SearchFilters *,
int,
char **,
int);
145bool init_find(SearchFilters *,
int,
char **);
150int scan_file(
char *,
const SearchFilters *,
const unsigned char);
153static struct argp_option options[] = {
154 {
"after",
'a',
"time", 0,
"Modified after YYYY-MM-DDTHH:MM:SS", 0},
155 {
"before",
'b',
"time", 0,
"Modified before YYYY-MM-DDTHH:MM:SS", 0},
156 {
"max_depth",
'd',
"number", 0,
"Depth into directory tree", 0},
157 {
"ere",
'e',
"regex", 0,
"Exclude regular expression", 0},
158 {
"ignore_case",
'i', 0, 0,
"Search ignore case", 0},
159 {
"include_perms",
'p',
"sgrwx", 0,
160 "x-execute, w-write, r-read, s-setuid, g-setgid", 0},
161 {
"re",
'r',
"regex", 0,
"Regular expression to search for", 0},
162 {
"include_types",
't',
"pcdbflsu", 0,
163 "p-pipe, c-character_dev, d-directory, b-block_dev, f-regular_file, l-link, s-socket, u-unknown",
165 {
"file_size_min",
's',
"size", 0,
166 "No Suffix-bytes, K-kilobytes, M-Megabytes, or G-Gigabytes", 0},
167 {
"user",
'u',
"user name", 0,
"User Name of file owner ", 0},
169 {
"exec",
'x',
"command", 0,
"execute external command", 0},
171 {
"debug",
'D',
"12345678", 0,
172 "1-config, 2-info, 3-warnings, 4-errors, 5-badlinks, 6-trace, 7-all, "
175 {
"include_hidden",
'H',
"o", 0,
"Include hidden files (Ho=hidden only)", 0},
176 {
"follow_links",
'L', 0, 0,
"Follow symbolic links", 0},
177 {
"sort_reverse",
'R', 0, 0,
"Sort in Reverse order", 0},
178 {
"sort",
'S', 0, 0,
"Sort in Ascending order", 0},
179 {
"nthreads",
'T',
"threads", 0,
"Number of nthreads", 0},
180 {
"count",
'c',
"s", 0,
"Count (s only report count)", 0},
184static error_t parse_opt(
int key,
char *arg,
struct argp_state *state) {
185 SearchFilters *f = state->input;
187 bool a_toi_error =
false;
192 if (f->after && f->before && f->before < f->after) {
193 fprintf(stderr,
"-b time must be greater than -a time.\n");
199 if (f->after && f->before && f->before < f->after) {
200 fprintf(stderr,
"-b time must be greater than -a time.\n");
206 if (arg && arg[0] !=
'\0') {
207 if (strcmp(arg,
"s") == 0)
208 f->count_silently |=
true;
212 if (arg && arg[0] !=
'\0')
213 f->max_depth =
a_toi(arg
, &a_toi_error
);
217 if (arg && arg[0] !=
'\0') {
223 f->report_config =
true;
226 f->report_info =
true;
229 f->report_warnings =
true;
232 f->report_errors =
true;
235 f->report_badlinks =
true;
238 f->report_trace =
true;
241 f->report_config =
true;
242 f->report_info =
true;
243 f->report_warnings =
true;
244 f->report_errors =
true;
245 f->report_badlinks =
true;
246 f->report_all =
true;
249 f->only_errors =
true;
260 f->ere = strdup(arg);
264 f->include_hidden =
true;
265 if (arg && arg[0] ==
'o') {
266 f->hidden_only =
true;
267 f->include_hidden =
true;
277 f->follow_links =
true;
304 f->sort_reverse =
true;
314 f->file_size_min = (intmax_t)(
a_to_ul(arg
));
317 if (arg && arg[0] !=
'\0')
325 f->include_types |=
LF_BLK;
328 f->include_types |=
LF_CHR;
331 f->include_types |=
LF_DIR;
337 f->include_types |=
LF_LNK;
342 f->include_types |=
LF_REG;
356 f->user_name = strdup(arg);
357 struct passwd *pwd = getpwnam(arg);
359 f->user_id = (uintmax_t)pwd->pw_uid;
362 fprintf(stderr,
"User '%s' not found.\n", arg);
368 f->exec = strdup(arg);
372 if (state->arg_num == 0 || state->arg_num == 1) {
373 lfargs[state->arg_num] = arg;
374 lfargc = state->arg_num + 1;
382 return ARGP_ERR_UNKNOWN;
386static struct argp argp = {options, parse_opt, args_doc,
doc,
387 nullptr,
nullptr,
nullptr};
389int main(
int argc,
char **argv) {
391 SearchFilters *f = (SearchFilters *)calloc(1,
sizeof(SearchFilters));
392 f->ignore_case =
false;
394 f->sort_reverse =
false;
395 f->include_hidden =
false;
399 f->hidden_only =
false;
401 f->follow_links =
false;
404 f->count_silently =
false;
405 char tmp_str[PATH_MAX];
406 argp_parse(&argp, argc, argv, 0, 0, f);
411 f->base_path = strdup(tmp_str);
413 f->re = strdup(
lfargs[0]);
418 "lf: arg1: '%s' is neither a directory nor a valid regex.\n",
424 if (!f->base_path || f->base_path[0] ==
'\0') {
428 f->base_path = strdup(tmp_str);
431 f->re = strdup(
lfargs[1]);
435 "lf: '%s' is neither a directory nor a valid regular "
441 if (f->base_path ==
nullptr || f->base_path[0] ==
'\0')
442 f->base_path = strdup(
".");
455
456
457
458
459
460
464 eargv[eargc++] = strdup(
"sort");
466 eargv[eargc++] = strdup(
"-r");
467 eargv[eargc] =
nullptr;
479 dup2(fds[0], STDIN_FILENO);
481 execvp(eargv[0], eargv);
482 fprintf(stderr,
"Failed to execute sort: %s\n", strerror(errno));
486 dup2(fds[1], STDOUT_FILENO);
487 stdout = fdopen(STDOUT_FILENO,
"w");
488 setvbuf(stdout, NULL, _IONBF, 0);
493 for (
int i = 0; i < eargc; i++)
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523bool init_find(SearchFilters *f,
int argc,
char **argv) {
525 if (!f->include_types)
526 f->include_types = 0xff;
527 if (f->include_types)
528 f->suppress_types = f->include_types ^ 0xff;
531 f->include_hidden = !(f->flags &
LF_HIDE);
533 f->reg_flags = REG_EXTENDED;
535 f->reg_flags |= REG_ICASE;
537 reti = regcomp(&f->compiled_re, f->re, f->reg_flags);
539 fprintf(stderr,
"lf: '%s' Invalid pattern\n", f->re);
540 regfree(&f->compiled_re);
545 reti = regcomp(&f->compiled_ere, f->ere, f->reg_flags);
547 fprintf(stderr,
"lf: '%s' Invalid exclude pattern\n", f->ere);
548 regfree(&f->compiled_ere);
552 unsigned int nprocs = get_nprocs();
557 nthreads = ((nprocs * 40) / 99) + 1;
566 if (stat(f->base_path, &st) == 0) {
567 if (S_ISDIR(st.st_mode)) {
568 TaskNode *child_task = malloc(
sizeof(TaskNode));
569 child_task->dir_path = strdup(f->base_path);
570 child_task->depth = 0;
571 child_task->next_task = NULL;
572 child_task->history = malloc(
sizeof(History));
573 child_task->history[0].dev = st.st_dev;
574 child_task->history[0].ino = st.st_ino;
577 for (
unsigned int i = 0; i <
nthreads; i++) {
578 pthread_create(&threads[i], NULL,
finder, f);
585 for (
unsigned int i = 0; i <
nthreads; i++)
586 pthread_join(threads[i], NULL);
589 "Warning: Base path '%s' is not a directory. No "
590 "files will be found.\n",
597 regfree(&f->compiled_re);
600 regfree(&f->compiled_ere);
612
613
614
615
616
617
618
620void debug_out(SearchFilters *f,
int argc,
char **argv,
int threads) {
625 bool addspace_before =
false;
626 if (f->debug && (f->report_config || f->report_info || f->report_all)) {
628 for (i = 0; i < argc; i++) {
629 len = len + strlen(argv[i]);
631 fprintf(stderr,
"\n");
632 len = strlen(argv[i]);
633 addspace_before =
false;
635 if (addspace_before) {
636 fprintf(stderr,
" ");
639 fprintf(stderr,
"%s", argv[i]);
640 addspace_before =
true;
642 fprintf(stderr,
"\n\n");
644 fprintf(stderr,
"lf debug %s\n",
645 f->debug ?
"true" :
" false");
646 fprintf(stderr,
" 1-config %s\n",
647 f->report_config ?
"true" :
"| false");
648 fprintf(stderr,
" 2-info %s\n",
649 f->report_info ?
"true" :
"| false");
650 fprintf(stderr,
" 3-warnings %s\n",
651 f->report_warnings ?
"true" :
"| false");
652 fprintf(stderr,
" 4-errors %s\n",
653 f->report_errors ?
"true" :
"| false");
654 fprintf(stderr,
" 5-badlinks %s\n",
655 f->report_trace ?
"true" :
"| false");
656 fprintf(stderr,
" 6-trace %s\n",
657 f->report_trace ?
"true" :
"| false");
658 fprintf(stderr,
" 7-all %s\n",
659 f->report_all ?
"true" :
"| false");
660 fprintf(stderr,
" 8-only_errors %s\n",
661 f->only_errors ?
"true" :
"| false");
662 fprintf(stderr,
"\n");
663 fprintf(stderr,
"Count files: %s\n", f->count ?
"true" :
"false");
664 fprintf(stderr,
"Count only: %s\n", f->count_silently ?
"true" :
"false");
665 fprintf(stderr,
"\n");
666 fprintf(stderr,
"Search directory: %s\n\n", f->base_path);
667 if (f->max_depth == 0)
668 fprintf(stderr,
"Max depth 0 (unlimited)\n\n");
670 fprintf(stderr,
"Max depth %d\n\n", f->max_depth);
671 fprintf(stderr,
"Using %d threads\n\n", threads);
672 fprintf(stderr,
"File types preceeded by an asterisk (\"*\") will be included:\n\n");
673 fprintf(stderr,
" LF type DT type\n");
682 fprintf(stderr,
"\n");
683 fprintf(stderr,
"f->include_types = %08b\n", f->include_types);
684 fprintf(stderr,
"f->suppress_types = %08b\n", f->suppress_types);
685 fprintf(stderr,
"\n");
687 fprintf(stderr,
"User: %s (%ju)\n", f->user_name, f->user_id);
688 if (f->include_perms) {
690 fprintf(stderr,
" %08b Execute\n",
LF_IXUSR);
692 fprintf(stderr,
" %08b Write\n",
LF_IWUSR);
694 fprintf(stderr,
" %08b Read\n",
LF_IRUSR);
696 fprintf(stderr,
" %08b SETUID\n",
LF_ISUID);
698 fprintf(stderr,
" %08b SETGID\n",
LF_ISGID);
700 fprintf(stderr,
"\n");
702 fprintf(stderr,
"Include regex: %s\n\n", f->re);
704 fprintf(stderr,
"Exclude regex: %s\n\n", f->ere);
709 fprintf(stderr,
"Modified after: %s\n\n", buf);
715 fprintf(stderr,
"Modified before: %s\n\n", buf);
718 if (f->file_size_min) {
719 const char *units[] = {
"b",
"Kb",
"Mb",
"Gb",
"Tb",
"Pb",
"Eb"};
720 off_t size = f->file_size_min;
722 while (size >= 1024 && i < 6) {
728 fprintf(stderr,
"Minimum file size: %s\n\n", buffer);
731 fprintf(stderr,
"Max depth: %d\n\n", f->max_depth);
733 fprintf(stderr,
"Ignore case in regex matching.\n\n");
734 if (f->include_hidden)
735 fprintf(stderr,
"Include hidden files.\n\n");
737 fprintf(stderr,
"Follow symbolic links.\n\n");
739 fprintf(stderr,
"Sort output in ascending order.\n\n");
741 fprintf(stderr,
"Sort output in reverse order.\n\n");
742 if (f->report_config && !f->report_all)
748
749
750
751
752
753
754
755
756
764 qtail->next_task = new_task;
782
783
784
785
786
787
788
789
830 TaskNode *temp =
qhead;
845
846
847
848
849
850
851
852
853
854
855
856
858 SearchFilters *f = (SearchFilters *)arg;
859 char lnk_path[PATH_MAX] = {
'\0'};
879 openat(AT_FDCWD, current_task->dir_path, O_RDONLY | O_DIRECTORY);
881 if (f->debug && (f->report_warnings || f->report_errors ||
882 f->report_badlinks || f->report_all))
883 fprintf(stderr,
"OPEN_FAIL,%s,%s\n", current_task->dir_path,
886 free(current_task->dir_path);
887 free(current_task->history);
893 DIR *dir = fdopendir(dir_fd);
895 if (f->debug && (f->report_warnings || f->report_errors ||
896 f->report_badlinks || f->report_all))
897 fprintf(stderr,
"\nFDOPENDIR_FAIL,%s,%s\n",
898 current_task->dir_path, strerror(errno));
901 free(current_task->dir_path);
902 free(current_task->history);
910 unsigned char effective_type;
911 char full_path[PATH_MAX] = {
'\0'};
912 struct dirent *entry;
913 while ((entry = readdir(dir)) != NULL) {
915 stpcpy(stpcpy(stpcpy(full_path, current_task->dir_path),
"/"),
926 rc = fstatat(AT_FDCWD, full_path, &st, AT_SYMLINK_NOFOLLOW);
928 if (f->debug && (f->report_errors || f->report_warnings ||
929 f->report_badlinks || f->report_all))
930 fprintf(stderr,
"LSTAT_FAIL,%s,%s\n", full_path,
935 effective_type = (st.st_mode & S_IFMT) >> 12;
947 if (S_ISLNK(st.st_mode)) {
949 rc = fstatat(AT_FDCWD, full_path, &st, 0);
951 if (f->debug && (f->report_all || f->report_warnings ||
952 f->report_errors || f->report_badlinks)) {
953 fprintf(stderr,
"STAT_FAIL,%s,%s\n", full_path,
960 effective_type = (st.st_mode & S_IFMT) >> 12;
962 if (effective_type != DT_DIR) {
964 if (!f->include_hidden)
966 }
else if (f->hidden_only)
986 bool cycle_found =
false;
987 if (f->debug && (f->report_trace || f->report_all))
988 fprintf(stderr,
"Checking for cycles in: %s\n", full_path);
989 for (
int i = 0; i < current_task->depth; i++) {
990 if (f->debug && (f->report_trace || f->report_all)) {
991 if (current_task->history[i].ino == st.st_ino)
992 fprintf(stderr,
"%3d %ju %ju<===========\n", i,
993 current_task->history[i].ino, st.st_ino);
995 fprintf(stderr,
"%3d %ju %ju\n", i,
996 current_task->history[i].ino, st.st_ino);
998 if (current_task->history[i].dev == st.st_dev &&
999 current_task->history[i].ino == st.st_ino) {
1005 if (f->debug && (f->report_warnings || f->report_errors ||
1006 f->report_trace || f->report_badlinks ||
1009 readlink(full_path, lnk_path,
sizeof(lnk_path) - 1);
1011 lnk_path[len] =
'\0';
1012 fprintf(stderr,
"CYCLIC_LINK,%s,%s\n", full_path,
1015 fprintf(stderr,
"CYCLIC_LINK,%s\n", full_path);
1034 if (f->max_depth != 0 && current_task->depth + 1 == f->max_depth)
1036 TaskNode *child_task = malloc(
sizeof(TaskNode));
1037 child_task->dir_path = strdup(full_path);
1038 child_task->depth = current_task->depth + 1;
1039 child_task->next_task = NULL;
1040 child_task->history =
1041 malloc((child_task->depth) *
sizeof(History));
1042 if (current_task->depth > 0) {
1043 memcpy(child_task->history, current_task->history,
1044 sizeof(History) * current_task->depth);
1046 child_task->history[current_task->depth].dev = st.st_dev;
1047 child_task->history[current_task->depth].ino = st.st_ino;
1055 free(current_task->dir_path);
1056 free(current_task->history);
1064 if (name[0] ==
'.') {
1065 if (name[1] ==
'\0')
1067 if (name[1] ==
'.' && name[2] ==
'\0')
1074 if (name[0] ==
'.') {
1075 if (name[1] ==
'\0')
1077 if (name[1] ==
'.' && name[2] ==
'\0')
1083
1084
1085
1086
1087
1089 const unsigned char effective_type) {
1090 regmatch_t pmatch[2];
1091 bool stat_cached =
false;
1094 if (f->debug && (f->report_trace || f->report_all)) {
1095 printf(
"suppress %08b, effective %08b, lf_mask %08b, & %08b %s\n",
1096 f->suppress_types, effective_type,
lf_mask[effective_type], f->suppress_types &
lf_mask[effective_type], file_spec);
1098 if (f->suppress_types &
lf_mask[effective_type])
1103 regexec(&f->compiled_re, file_spec, f->compiled_re.re_nsub + 1,
1104 pmatch, f->reg_flags);
1105 if (reti == REG_NOMATCH) {
1106 if (f->debug && (f->report_info || f->report_all))
1107 fprintf(stderr,
"Regex no match: %s\n", file_spec);
1111 regerror(reti, &f->compiled_re, errbuf,
sizeof(errbuf));
1112 if (f->debug && (f->report_errors || f->report_all))
1113 fprintf(stderr,
"regex error: %s\n", errbuf);
1121 regexec(&f->compiled_ere, file_spec, f->compiled_re.re_nsub + 1,
1122 pmatch, f->reg_flags);
1125 if (reti != REG_NOMATCH) {
1128 regerror(reti, &f->compiled_ere, errbuf,
sizeof(errbuf));
1129 if (f->debug && (f->report_errors || f->report_all))
1130 fprintf(stderr,
"Exclude regex error: %s\n", errbuf);
1136 stat_cached =
false;
1139 if ((f->flags &
LF_USER) && stat(file_spec, &sb) == 0) {
1141 if (sb.st_uid != f->user_id)
1144 if (f->include_perms) {
1146 if (stat(file_spec, &sb) == 0)
1149 if ((f->include_perms &
LF_IRUSR) && !(sb.st_mode & S_IRUSR))
1151 else if ((f->include_perms &
LF_IWUSR) && !(sb.st_mode & S_IWUSR))
1153 else if ((f->include_perms &
LF_IXUSR) && !(sb.st_mode & S_IXUSR))
1155 else if ((f->include_perms &
LF_ISUID) && !(sb.st_mode & S_ISUID))
1157 else if ((f->include_perms &
LF_ISGID) && !(sb.st_mode & S_ISGID))
1162 if (stat(file_spec, &sb) == 0)
1165 if (stat_cached && sb.st_mtime > f->before)
1170 if (stat(file_spec, &sb) == 0)
1173 if (stat_cached && sb.st_mtime < f->after)
1176 if (f->file_size_min) {
1178 if (stat(file_spec, &sb) == 0)
1181 if (stat_cached && sb.st_size < f->file_size_min)
1184 if (effective_type == DT_DIR) {
1185 char *file_p = file_spec;
1186 while (*file_p++ !=
'\0')
1193 if (!f->count_silently) {
1194 if (file_spec[0] ==
'.' && file_spec[1] ==
'/')
1195 printf(
"%s\n", &file_spec[2]);
1197 printf(
"%s\n", file_spec);
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
unsigned char const lf_mask[15]
bool is_dirsys(const char *)
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.
bool is_hidden(const char *)
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,...