2
3
4
5
6
7
8
16#include <linux/limits.h>
28#include <sys/syscall.h>
29#include <sys/sysinfo.h>
35#define QUEUE_CAPACITY 16384
37#define MAX_PATH_LEN _POSIX_PATH_MAX
39#define DIR_BUF_SIZE 262144
40#define CACHE_LINE_SIZE 64
43#define ARENA_MAX_NODES (32
* 1024
* 1024
)
45typedef struct CycleNode CycleNode;
58static CycleArena g_cycle_arena = {NULL, 0, 0};
60typedef struct QueuePayload QueuePayload;
84struct linux_dirent64 {
85 unsigned long long d_ino;
87 unsigned short d_reclen;
92#define print_file_type(mask, lf_type, dt_type, name)
94 fprintf(stderr
, "%c %08b (%3d) %08b (%2d) %s\n",
95 (mask & lf_type) ? '*' : ' ', lf_type, lf_type,
96 dt_type, dt_type, name);
102const char doc[] =
_(
"lf list files\nIf specified, DIRECTORY is the top-level\n directory to search. REGULAR_EXPRESSION is a properly\nformatted regular expression for which matching files\nwill be listed.");
105static char args_doc[] =
"[DIRECTORY] [REGULAR_EXPRESSION]";
111 unsigned int nthreads;
112 atomic_size_t file_count;
113 atomic_size_t error_count;
114 pthread_mutex_t output_mutex;
119 bool error_file_open;
131 regex_t compiled_ere;
132 unsigned char include_perms;
133 unsigned char include_types;
134 unsigned char suppress_types;
135 bool report_error_count;
145 bool report_warnings;
147 bool report_badlinks;
162 0, 0b00000001, 0b00000010, 0, 0b00000100, 0, 0b00001000, 0, 0b00010000, 0,
163 0b00100000, 0, 0b01000000, 0, 0b10000000};
171void debug_out(LfContext *lf,
int,
char **);
172bool init_lf(LfContext *lf,
int,
char **);
175bool mpmc_enqueue(LfContext *,
const QueuePayload *child_node);
176bool mpmc_dequeue(LfContext *lf, QueuePayload *output_node);
178void *
finder(LfContext *lf, QueuePayload *current_node, QueuePayload *child_node);
179int scan_file(
const char *file_spec,
const size_t *path_len, LfContext *lf,
const unsigned char,
180 const struct stat *, OutputBuffer *);
181bool build_full_path(
char *, size_t,
const char *,
const char *, size_t *);
185int err_out(LfContext *lf,
const char *format, ...);
188static CycleNode *cycle_arena_alloc(dev_t dev, ino_t ino, CycleNode *parent);
192static struct argp_option options[] = {
193 {
_(
"after"),
'a',
_(
"time"), 0,
_(
"Last Modified after YYYY-MM-DDTHH:MM:SS"), 0},
194 {
_(
"before"),
'b',
_(
"time"), 0,
_(
"Last Modified before YYYY-MM-DDTHH:MM:SS"), 0},
195 {
_(
"max_depth"),
'd',
_(
"number"), 0,
_(
"Depth into directory tree"), 0},
196 {
_(
"error_file_spec"),
'E',
_(
"file_spec"), 0,
_(
"Error message output file"), 0},
197 {
_(
"ere"),
'e',
_(
"regex"), 0,
_(
"Exclude regular expression"), 0},
198 {
_(
"ignore_case"),
'i', 0, 0,
_(
"Search ignore case"), 0},
199 {
_(
"include_perms"),
'p',
_(
"sgrwx"), 0,
200 _(
"x-execute, w-write, r-read, s-setuid, g-setgid"), 0},
201 {
_(
"re"),
'r',
_(
"regex"), 0,
_(
"Regular expression to search for"), 0},
202 {
_(
"include_types"),
't',
_(
"pcdbflsu"), 0,
203 _(
"p-pipe, c-character_dev, d-directory, b-block_dev, f-regular_file, l-link, s-socket, u-unknown"),
205 {
_(
"file_size_min"),
's',
_(
"size"), 0,
206 _(
"No Suffix-bytes, K-kilobytes, M-Megabytes, or G-Gigabytes"), 0},
207 {
_(
"user"),
'u',
_(
"user name"), 0,
_(
"User Name of file owner "), 0},
208 {
_(
"debug"),
'D',
"123456789", 0,
209 _(
"1-config, 2-info, 3-warnings, 4-errors, 5-badlinks, 6-trace, 7-all, 8-only_errors, 9-report_error_count"),
211 {
_(
"include_hidden"),
'H',
"o", OPTION_ARG_OPTIONAL,
_(
"Include hidden files (o=hidden only)"), 0},
212 {
_(
"follow_links"),
'L', 0, 0,
_(
"Follow symbolic links"), 0},
213 {
_(
"sort_reverse"),
'R', 0, 0,
_(
"Sort in Reverse order"), 0},
214 {
_(
"sort"),
'S', 0, 0,
_(
"Sort in Ascending order"), 0},
215 {
_(
"nthreads"),
'T',
"threads", 0,
_(
"Number of nthreads"), 0},
216 {
_(
"count"),
'c',
"s", OPTION_ARG_OPTIONAL,
_(
"Count (s only report count)"), 0},
220static error_t parse_opt(
int key,
char *arg,
struct argp_state *state) {
221 LfContext *lf = state->input;
223 bool a_toi_error =
false;
228 if (lf->after && lf->before && lf->before < lf->after) {
229 fprintf(stderr,
_(
"-b time must be greater than -a time.\n"));
235 if (lf->after && lf->before && lf->before < lf->after) {
236 fprintf(stderr,
_(
"-b time must be greater than -a time.\n"));
242 if (arg && arg[0] !=
'\0') {
243 if (strcmp(arg,
"s") == 0)
244 lf->count_silently |=
true;
248 if (arg && arg[0] !=
'\0')
249 lf->max_depth =
a_toi(arg
, &a_toi_error
);
253 if (arg && arg[0] !=
'\0') {
256 lf->report_errors =
true;
260 lf->report_config =
true;
261 lf->only_errors =
true;
264 lf->report_info =
true;
267 lf->report_warnings =
true;
270 lf->report_errors =
true;
273 lf->report_badlinks =
true;
274 lf->report_errors =
true;
277 lf->report_trace =
true;
278 lf->report_badlinks =
true;
279 lf->report_errors =
true;
282 lf->report_config =
true;
283 lf->report_info =
true;
284 lf->report_warnings =
true;
285 lf->report_errors =
true;
286 lf->report_badlinks =
true;
289 lf->only_errors =
true;
290 lf->report_errors =
true;
293 lf->report_error_count =
true;
304 lf->ere = strdup(arg);
311 lf->include_hidden =
true;
312 if (arg && arg[0] ==
'o') {
313 lf->hidden_only =
true;
314 lf->include_hidden =
true;
321 lf->ignore_case =
true;
324 lf->follow_links =
true;
351 lf->sort_reverse =
true;
354 lf->re = strdup(arg);
361 lf->file_size_min = (intmax_t)(
a_to_ul(arg
));
364 if (arg && arg[0] !=
'\0')
365 lf->nthreads =
a_toi(arg
, &a_toi_error
);
372 lf->include_types |=
LF_BLK;
375 lf->include_types |=
LF_CHR;
378 lf->include_types |=
LF_DIR;
384 lf->include_types |=
LF_LNK;
389 lf->include_types |=
LF_REG;
403 lf->user_name = strdup(arg);
404 struct passwd *pwd = getpwnam(arg);
406 lf->user_id = (uintmax_t)pwd->pw_uid;
409 fprintf(stderr,
_(
"User '%s' not found.\n"), arg);
414 if (state->arg_num == 0 || state->arg_num == 1) {
415 lfargs[state->arg_num] = arg;
416 lfargc = state->arg_num + 1;
424 return ARGP_ERR_UNKNOWN;
428static struct argp argp = {options, parse_opt, args_doc,
doc,
429 nullptr,
nullptr,
nullptr};
431int main(
int argc,
char **argv) {
432 LfContext *lf = (LfContext *)calloc(1,
sizeof(LfContext));
437 lf->report_error_count =
false;
439 lf->ignore_case =
false;
441 lf->sort_reverse =
false;
442 lf->include_hidden =
false;
443 lf->hidden_only =
false;
445 lf->follow_links =
false;
447 lf->count_silently =
false;
448 lf->report_badlinks =
false;
449 lf->sbuffer =
_(
"4G");
450 lf->error_file_spec[0] =
'\0';
451 lf->error_file_open =
false;
454 argp_parse(&argp, argc, argv, 0, 0, lf);
459 lf->base_path = strdup(tmp_str);
461 lf->re = strdup(
lfargs[0]);
466 _(
"lf: arg1: '%s' is neither a directory nor a valid regex.\n"),
472 if (!lf->base_path || lf->base_path[0] ==
'\0') {
476 lf->base_path = strdup(tmp_str);
479 lf->re = strdup(
lfargs[1]);
483 _(
"lf: '%s' is neither a directory nor a valid regular expression.\n"),
488 if (lf->base_path ==
nullptr || lf->base_path[0] ==
'\0')
489 lf->base_path = strdup(
".");
490 unsigned int nprocs = get_nprocs();
494 if (lf->nthreads == 0)
495 lf->nthreads = (nprocs > 1) ? (nprocs - 1) : 1;
497 if (lf->nthreads < 1)
499 if (lf->nthreads > nprocs)
500 lf->nthreads = nprocs;
509 size_t count = atomic_load(&lf->file_count);
510 fprintf(stderr,
_(
"Files: %zu\n"), count);
512 atomic_load(&lf->error_count);
513 if (lf->report_error_count && lf->error_count > 0) {
514 fprintf(stderr,
_(
"Errors: %zu\n"), lf->error_count);
527
528
529
530
531
536 eargv[eargc++] = strdup(
"sort");
541 if (lf->sort_reverse)
542 eargv[eargc++] = strdup(
"-r");
543 eargv[eargc] =
nullptr;
545 setenv(
"LC_ALL",
"C", 1);
556 dup2(fds[0], STDIN_FILENO);
558 execvp(eargv[0], eargv);
559 fprintf(stderr,
_(
"Failed to execute sort: %s\n"), strerror(errno));
563 dup2(fds[1], STDOUT_FILENO);
564 stdout = fdopen(STDOUT_FILENO,
"w");
565 setvbuf(stdout, NULL, _IOLBF, 0);
570 for (
int i = 0; i < eargc; i++)
578
579
580
581
582
588 bool addspace_before =
false;
589 if (lf->debug && (lf->report_config || lf->report_info)) {
591 for (i = 0; i < argc; i++) {
592 len = len + strlen(argv[i]);
594 fprintf(stderr,
"\n");
595 len = strlen(argv[i]);
596 addspace_before =
false;
598 if (addspace_before) {
599 fprintf(stderr,
" ");
602 fprintf(stderr,
"%s", argv[i]);
603 addspace_before =
true;
605 fprintf(stderr,
"\n\n");
607 fprintf(stderr,
_(
"lf debug %s\n"),
608 lf->debug ?
_(
"true") :
_(
" false"));
609 fprintf(stderr,
_(
" 1-config %s\n"),
610 lf->report_config ?
_(
"true") :
_(
"| false"));
611 fprintf(stderr,
_(
" 2-info %s\n"),
612 lf->report_info ?
_(
"true") :
_(
"| false"));
613 fprintf(stderr,
_(
" 3-warnings %s\n"),
614 lf->report_warnings ?
_(
"true") :
_(
"| false"));
615 fprintf(stderr,
_(
" 4-errors %s\n"),
616 lf->report_errors ?
_(
"true") :
_(
"| false"));
617 fprintf(stderr,
_(
" 5-badlinks %s\n"),
618 lf->report_errors ?
_(
"true") :
_(
"| false"));
619 fprintf(stderr,
_(
" 6-trace %s\n"),
620 lf->report_errors ?
_(
"true") :
_(
"| false"));
621 fprintf(stderr,
_(
" 8-only_errors %s\n"),
622 lf->report_errors ?
_(
"true") :
_(
"| false"));
623 fprintf(stderr,
"\n");
624 fprintf(stderr,
_(
"Count files: %s\n"),
625 lf->report_errors ?
_(
"true") :
_(
"| false"));
626 fprintf(stderr,
_(
"Count only: %s\n"),
627 lf->report_errors ?
_(
"true") :
_(
"| false"));
628 fprintf(stderr,
"\n");
629 fprintf(stderr,
_(
"Search directory: %s\n\n"), lf->base_path);
630 if (lf->max_depth == 0)
631 fprintf(stderr,
_(
"Max depth 0 (unlimited)\n\n"));
633 fprintf(stderr,
_(
"Max depth %d\n\n"), lf->max_depth);
634 fprintf(stderr,
_(
"Using %d threads\n\n"), lf->nthreads);
635 fprintf(stderr,
_(
"File types preceeded by an asterisk (\"*\") will be included:\n\n"));
636 fprintf(stderr,
_(
" LF type DT type\n"));
645 fprintf(stderr,
"\n");
646 fprintf(stderr,
"f->include_types = %08b\n", lf->include_types);
647 fprintf(stderr,
"f->suppress_types = %08b\n", lf->suppress_types);
648 fprintf(stderr,
"\n");
650 fprintf(stderr,
_(
"User: %s (%ju)\n"), lf->user_name, lf->user_id);
651 if (lf->include_perms) {
653 fprintf(stderr,
_(
" %08b Execute\n"),
LF_IXUSR);
655 fprintf(stderr,
_(
" %08b Write\n"),
LF_IWUSR);
657 fprintf(stderr,
_(
" %08b Read\n"),
LF_IRUSR);
659 fprintf(stderr,
_(
" %08b SETUID\n"),
LF_ISUID);
661 fprintf(stderr,
_(
" %08b SETGID\n"),
LF_ISGID);
663 fprintf(stderr,
"\n");
665 fprintf(stderr,
_(
"Include regex: %s\n\n"), lf->re);
667 fprintf(stderr,
_(
"Exclude regex: %s\n\n"), lf->ere);
672 fprintf(stderr,
_(
"Modified after: %s\n\n"), buf);
678 fprintf(stderr,
_(
"Modified before: %s\n\n"), buf);
681 if (lf->file_size_min) {
682 const char *units[] = {
"b",
"Kb",
"Mb",
"Gb",
"Tb",
"Pb",
"Eb"};
683 off_t size = lf->file_size_min;
685 while (size >= 1024 && i < 6) {
691 fprintf(stderr,
_(
"Minimum file size: %s\n\n"), buffer);
694 fprintf(stderr,
_(
"Max depth: %d\n\n"), lf->max_depth);
696 fprintf(stderr,
_(
"Ignore case in regex matching.\n\n"));
697 if (lf->include_hidden)
698 fprintf(stderr,
_(
"Include hidden files.\n\n"));
699 if (lf->follow_links)
700 fprintf(stderr,
_(
"Follow symbolic links.\n\n"));
702 fprintf(stderr,
_(
"Sort output in ascending order.\n\n"));
703 if (lf->sort_reverse)
704 fprintf(stderr,
_(
"Sort output in reverse order.\n\n"));
712
713
714
715
716
717
718
719
720
721
722bool init_lf(LfContext *lf,
int argc,
char **argv) {
724 if (!lf->include_types)
725 lf->include_types = 0xff;
726 if (lf->include_types)
727 lf->suppress_types = lf->include_types ^ 0xff;
730 lf->include_hidden = !(lf->flags &
LF_HIDE);
732 lf->reg_flags = REG_EXTENDED;
734 lf->reg_flags |= REG_ICASE;
736 reti = regcomp(&lf->compiled_re, lf->re, lf->reg_flags);
738 fprintf(stderr,
_(
"lf: '%s' Invalid pattern\n"), lf->re);
739 regfree(&lf->compiled_re);
744 reti = regcomp(&lf->compiled_ere, lf->ere, lf->reg_flags);
746 fprintf(stderr,
_(
"lf: '%s' Invalid exclude pattern\n"), lf->ere);
747 regfree(&lf->compiled_ere);
757 rc = pthread_mutex_init(&lf->output_mutex, NULL);
759 perror(
_(
"Mutex initialization failed"));
760 rc = stat(lf->base_path, &sb);
764 QueuePayload root_node;
766 if (S_ISDIR(sb.st_mode)) {
769 CycleNode *root_ctx = cycle_arena_alloc(sb.st_dev, sb.st_ino,
nullptr);
770 root_node.ctx = root_ctx;
772 fprintf(stderr,
_(
"Failed to enqueue initial directory\n"));
775 atomic_fetch_add_explicit(&lf->q->active_tasks, 1, memory_order_relaxed);
779 lf->threads = calloc(lf->nthreads,
sizeof(pthread_t));
781 fprintf(stderr,
_(
"Out of memory allocating threads\n"));
784 for (
unsigned int i = 0; i < lf->nthreads; i++) {
791 fprintf(stderr,
_(
"Error: Unable to create thread %d\n"), rc);
792 lf->q->shut_down = 1;
800 for (
unsigned int i = 0; i < lf->nthreads; i++)
801 pthread_join(lf->threads[i], NULL);
802 rc = pthread_mutex_destroy(&lf->output_mutex);
804 perror(
_(
"Mutex destroy failed"));
806 regfree(&lf->compiled_re);
808 regfree(&lf->compiled_ere);
812 _(
"Warning: Base path '%s' is not a directory. No "
813 "files will be found.\n"),
826
827
830 size_t total_bytes = g_cycle_arena.capacity *
sizeof(CycleNode);
833 g_cycle_arena.nodes = mmap(NULL, total_bytes,
834 PROT_READ | PROT_WRITE,
835 MAP_ANONYMOUS | MAP_PRIVATE,
838 if (g_cycle_arena.nodes == MAP_FAILED) {
839 perror(
_(
"mmap failed to reserve tracking arena memory"));
843 atomic_init(&g_cycle_arena.index, 0);
849
850
852 if (g_cycle_arena.nodes != NULL && g_cycle_arena.nodes != MAP_FAILED) {
853 size_t total_bytes = g_cycle_arena.capacity *
sizeof(CycleNode);
854 munmap(g_cycle_arena.nodes, total_bytes);
861
862
863
864
865
866
867static CycleNode *cycle_arena_alloc(dev_t dev, ino_t ino, CycleNode *parent) {
868 size_t idx = atomic_fetch_add_explicit(&g_cycle_arena.index, 1, memory_order_relaxed);
870 if (idx >= g_cycle_arena.capacity) {
874 CycleNode *node = &g_cycle_arena.nodes[idx];
877 node->parent = parent;
884
885
886
887
888
889
890
891
892
894 const char *name, size_t *out_len) {
895 if (dst ==
nullptr || dir_path ==
nullptr || name ==
nullptr || dst_size == 0)
897 size_t dir_len = strlen(dir_path);
898 size_t name_len = strlen(name);
899 bool need_sep = dir_len > 0 && dir_path[dir_len - 1] !=
'/';
900 size_t full_len = dir_len + (need_sep ? 1 : 0) + name_len;
901 if (full_len >= dst_size)
903 memcpy(dst, dir_path, dir_len);
904 size_t pos = dir_len;
907 memcpy(dst + pos, name, name_len);
908 dst[full_len] =
'\0';
917
918
919
920
922 if (lf ==
nullptr || output ==
nullptr || output->len == 0)
924 pthread_mutex_lock(&lf->output_mutex);
925 fwrite_unlocked(output->data, 1, output->len, stdout);
926 pthread_mutex_unlock(&lf->output_mutex);
933
934
935
936
937
938
939
940
942 size_t path_len,
bool append_slash) {
943 if (lf ==
nullptr || output ==
nullptr || path ==
nullptr)
945 size_t line_len = path_len + (append_slash ? 1 : 0) + 1;
946 if (line_len >
sizeof(output->data)) {
947 pthread_mutex_lock(&lf->output_mutex);
948 fwrite_unlocked(path, 1, path_len, stdout);
950 fputc_unlocked(
'/', stdout);
951 fputc_unlocked(
'\n', stdout);
952 pthread_mutex_unlock(&lf->output_mutex);
955 if (output->len + line_len >
sizeof(output->data))
957 memcpy(output->data + output->len, path, path_len);
958 output->len += path_len;
960 output->data[output->len++] =
'/';
961 output->data[output->len++] =
'\n';
968
969
970
972 MPMCQueue *q = calloc(1,
sizeof(MPMCQueue));
976 atomic_init(&q->sequence[i], i);
977 atomic_init(&q->enqueue_pos, 0);
978 atomic_init(&q->dequeue_pos, 0);
979 atomic_init(&q->active_tasks, 0);
980 atomic_init(&q->shut_down, 0);
987
988
989
990
991
993 size_t pos = atomic_load_explicit(&lf->q->enqueue_pos,
994 memory_order_relaxed);
996 const int SPIN_LIMIT = 16;
998 size_t seq = atomic_load_explicit(&lf->q->sequence[pos &
QUEUE_MASK], memory_order_acquire);
999 intptr_t diff = (intptr_t)seq - (intptr_t)pos;
1001 if (atomic_compare_exchange_weak_explicit(&lf->q->enqueue_pos, &pos, pos + 1,
1002 memory_order_relaxed, memory_order_relaxed)) {
1005 }
else if (diff < 0)
1012 if (++spin_count > SPIN_LIMIT)
1014#if defined(__x86_64__
)
1015 __builtin_ia32_pause();
1018 memcpy(&lf->q->nodes[pos &
QUEUE_MASK], child_node,
sizeof(QueuePayload));
1019 atomic_store_explicit(&lf->q->sequence[pos &
QUEUE_MASK], pos + 1, memory_order_release);
1026
1027
1028
1029
1030
1032 size_t pos = atomic_load_explicit(&lf->q->dequeue_pos, memory_order_relaxed);
1034 if (atomic_load_explicit(&lf->q->shut_down, memory_order_relaxed))
1036 size_t seq = atomic_load_explicit(&lf->q->sequence[pos &
QUEUE_MASK], memory_order_acquire);
1037 intptr_t diff = (intptr_t)seq - (intptr_t)(pos + 1);
1039 if (atomic_compare_exchange_strong_explicit(&lf->q->dequeue_pos, &pos, pos + 1,
1040 memory_order_relaxed, memory_order_relaxed)) {
1043 }
else if (diff < 0) {
1044 if (atomic_load_explicit(&lf->q->active_tasks,
1045 memory_order_relaxed) == 0) {
1046 atomic_store_explicit(&lf->q->shut_down, 1,
1047 memory_order_release);
1051 pos = atomic_load_explicit(&lf->q->dequeue_pos, memory_order_relaxed);
1053 pos = atomic_load_explicit(&lf->q->dequeue_pos, memory_order_relaxed);
1068
1069
1070
1071
1073 LfContext *lf = (LfContext *)arg;
1075 QueuePayload *current_node = calloc(1,
sizeof(QueuePayload));
1076 QueuePayload *child_node = calloc(1,
sizeof(QueuePayload));
1077 if (child_node ==
nullptr) {
1078 fprintf(stderr,
_(
"Out of memory allocating child_node\n"));
1083 memset(child_node, 0,
sizeof(QueuePayload));
1084 if (
finder(lf
, current_node
, child_node
) == NULL) {
1085 if (atomic_fetch_sub_explicit(&lf->q->active_tasks, 1, memory_order_acq_rel) == 1) {
1086 atomic_store_explicit(&lf->q->shut_down, 1, memory_order_release);
1100
1101
1102
1103
1104
1105void *
finder(LfContext *lf, QueuePayload *current_node, QueuePayload *child_node) {
1106 OutputBuffer output = {{
'\0'}, 0};
1109 struct stat sb = {};
1110 struct linux_dirent64 *entry;
1111 dev_t actual_dev = 0, effective_dev = 0;
1112 ino_t actual_ino = 0, effective_ino = 0;
1113 unsigned char actual_type;
1114 unsigned char effective_type;
1115 CycleNode *child_ctx;
1118 int dir_fd = open(current_node->path, O_RDONLY | O_DIRECTORY);
1120 if (lf->report_errors) {
1121 err_out(lf
, _(
"OPEN_FAIL,%s,%s\n"), current_node->path
, strerror(errno)
);
1138 nread = syscall(SYS_getdents64, dir_fd, dir_buf,
DIR_BUF_SIZE);
1140 atomic_fetch_add(&lf->error_count, 1);
1141 if (lf->report_errors) {
1142 err_out(lf
, _(
"READDIR_FAIL,%s,%s\n"), current_node->path
, strerror(errno)
);
1148 for (size_t bpos = 0; bpos < (size_t)nread;) {
1149 entry = (
struct linux_dirent64 *)(dir_buf + bpos);
1150 bpos += entry->d_reclen;
1155 full_path[0] =
'\0';
1156 actual_type = entry->d_type;
1157 effective_type = actual_type;
1158 if (actual_type == DT_DIR) {
1159 if (entry->d_name[0] ==
'.') {
1160 if (entry->d_name[1] ==
'\0')
1162 if (entry->d_name[1] ==
'.' && entry->d_name[2] ==
'\0')
1166 if (((lf->follow_links || lf->report_badlinks) &&
1167 (actual_type == DT_LNK || actual_type == DT_DIR)) ||
1168 actual_type == DT_UNKNOWN) {
1173 memset(&sb, 0,
sizeof(
struct stat));
1174 rc = fstatat(dir_fd, entry->d_name, &sb, AT_SYMLINK_NOFOLLOW);
1176 atomic_fetch_add(&lf->error_count, 1);
1177 if (lf->report_badlinks) {
1182 actual_ino = sb.st_ino;
1183 actual_dev = sb.st_dev;
1184 actual_type = (sb.st_mode & S_IFMT) >> 12;
1185 effective_dev = actual_dev;
1186 effective_ino = actual_ino;
1187 effective_type = actual_type;
1189 if (S_ISLNK(sb.st_mode)) {
1191 if (lf->follow_links) {
1196 rc = fstatat(dir_fd, entry->d_name, &sb, 0);
1198 atomic_fetch_add(&lf->error_count, 1);
1199 if (lf->report_errors) {
1203 entry->d_name
, strerror(errno)
);
1206 effective_ino = sb.st_ino;
1207 effective_dev = sb.st_dev;
1208 effective_type = (sb.st_mode & S_IFMT) >> 12;
1211 effective_type = DT_REG;
1215 if (effective_type == DT_DIR) {
1220 child_ctx = cycle_arena_alloc(effective_dev, effective_ino, current_node->ctx);
1221 child_node->ctx = child_ctx;
1223 sizeof(child_node->path)
,
1226 &child_node->path_len
)) {
1227 atomic_fetch_add(&lf->error_count, 1);
1228 if (lf->report_errors) {
1230 child_node->path
, entry->d_name
);
1234 bool cycle_found =
false;
1245 if (lf->follow_links && actual_type == DT_LNK) {
1251 if (lf->report_trace || lf->report_badlinks) {
1252 ssize_t len = readlinkat(dir_fd, entry->d_name, target_path,
sizeof(target_path) - 1);
1254 target_path[len] =
'\0';
1256 child_ctx = cycle_arena_alloc(effective_dev, effective_ino, current_node->ctx);
1257 if (child_ctx ==
nullptr) {
1258 atomic_fetch_add(&lf->error_count, 1);
1259 if (lf->report_errors) {
1260 err_out(lf
, _(
"CYCLE_ARENA_ALLOC_FAIL,%s\n"), current_node->path
);
1264 child_node->ctx = child_ctx;
1265 if (lf->report_trace) {
1267 err_out(lf
, "ctx: %12p, %8lu/%8lu, %s\n", child_ctx
, child_ctx->dev
, child_ctx->ino
, child_node->path
);
1268 err_out(lf
, _(
"effective: %8lu/%8lu, %s\n"), effective_dev
, effective_ino
, target_path
);
1272 CycleNode *parent = current_node->ctx;
1273 while (parent !=
nullptr) {
1274 if (lf->report_trace) {
1275 err_out(lf
, "ctx: %12p, %8lu/%8lu, %12p, %s\n", parent
, parent->dev
, parent->ino
);
1277 if (effective_dev == parent->dev && effective_ino == parent->ino) {
1281 parent = parent->parent;
1284 if (lf->report_badlinks) {
1285 err_out(lf
, _(
"CYCLER,%lu/%lu,%s,%lu/%lu,%s\n"), child_ctx->dev
, child_ctx->ino
, child_node->path
,
1286 effective_dev
, effective_ino
, target_path
);
1288 atomic_fetch_add(&lf->error_count, 1);
1289 child_node->depth = current_node->depth + 1;
1290 scan_file(child_node->path
, &child_node->path_len
, lf
, effective_type
, &sb
, &output
);
1297 if (lf->max_depth != 0 && current_node->depth + 1 >= lf->max_depth)
1299 child_node->depth = current_node->depth + 1;
1302 atomic_fetch_add_explicit(&lf->q->active_tasks, 1, memory_order_relaxed);
1304 QueuePayload *local_node = calloc(1,
sizeof(QueuePayload));
1305 if (local_node ==
nullptr) {
1306 atomic_fetch_add(&lf->error_count, 1);
1307 if (lf->report_errors) {
1319 if (entry->d_name[0] ==
'.') {
1320 if (!lf->include_hidden && !lf->hidden_only)
1323 if (lf->hidden_only)
1326 if (!lf->only_errors)
1327 scan_file(child_node->path
, &child_node->path_len
, lf
, effective_type
, &sb
, &output
);
1332 if (entry->d_name[0] ==
'.') {
1333 if (!lf->include_hidden && !lf->hidden_only)
1336 if (lf->hidden_only)
1340 if (full_path[0] ==
'\0') {
1346 atomic_fetch_add(&lf->error_count, 1);
1347 if (lf->report_errors) {
1349 current_node->path
, entry->d_name
);
1354 if (!lf->only_errors)
1367 const CycleNode *current = parent;
1368 while (current != NULL) {
1369 if (current->ino == ino && current->dev == dev) {
1372 current = current->parent;
1380
1381
1382
1383
1384
1385
1386
1387
1388int scan_file(
const char *file_spec,
const size_t *path_len, LfContext *lf,
1389 const unsigned char effective_type,
const struct stat *cached_sb,
1390 OutputBuffer *output) {
1391 bool stat_cached = cached_sb !=
nullptr;
1392 struct stat sb = {};
1395 if (lf->suppress_types &
lf_mask[effective_type])
1400 regexec(&lf->compiled_re, file_spec, 0, NULL, 0);
1401 if (reti == REG_NOMATCH)
1408 regexec(&lf->compiled_ere, file_spec, 0, NULL, 0);
1416 if (!stat_cached && stat(file_spec, &sb) == 0)
1418 if (!stat_cached || sb.st_uid != lf->user_id)
1421 if (lf->include_perms) {
1422 if (!stat_cached && stat(file_spec, &sb) == 0)
1426 if ((lf->include_perms &
LF_IRUSR) && !(sb.st_mode & S_IRUSR))
1428 else if ((lf->include_perms &
LF_IWUSR) && !(sb.st_mode & S_IWUSR))
1430 else if ((lf->include_perms &
LF_IXUSR) && !(sb.st_mode & S_IXUSR))
1432 else if ((lf->include_perms &
LF_ISUID) && !(sb.st_mode & S_ISUID))
1434 else if ((lf->include_perms &
LF_ISGID) && !(sb.st_mode & S_ISGID))
1438 if (!stat_cached && stat(file_spec, &sb) == 0)
1440 if (stat_cached && sb.st_mtime > lf->before)
1444 if (!stat_cached && stat(file_spec, &sb) == 0)
1446 if (stat_cached && sb.st_mtime < lf->after)
1449 if (lf->file_size_min) {
1450 if (!stat_cached && stat(file_spec, &sb) == 0)
1452 if (stat_cached && sb.st_size < lf->file_size_min)
1455 if (lf->only_errors)
1457 atomic_fetch_add(&lf->file_count, 1);
1458 if (!lf->count_silently) {
1459 const char *display_path = file_spec;
1460 size_t display_len = *path_len;
1461 if (display_len > 2 && display_path[0] ==
'.' && display_path[1] ==
'/') {
1466 effective_type == DT_DIR
);
1473
1474
1475
1476
1477
1478
1479int err_out(LfContext *lf,
const char *format, ...) {
1481 pthread_mutex_lock(&lf->output_mutex);
1483 va_start(args, format);
1484 if (lf->error_file_spec[0] ==
'\0') {
1485 if (lf->err_fd ==
nullptr) {
1486 lf->err_fd = stderr;
1487 lf->error_file_open =
true;
1488 setvbuf(lf->err_fd, NULL, _IOLBF, 0);
1491 if (lf->error_file_open ==
false) {
1492 lf->err_fd = fopen(lf->error_file_spec,
"a");
1493 if (lf->err_fd ==
nullptr) {
1494 lf->err_fd = stderr;
1495 fprintf(stderr,
_(
"Failed to open error file %s: %s\n"),
1496 lf->error_file_spec, strerror(errno));
1499 lf->error_file_open =
true;
1500 setvbuf(lf->err_fd, NULL, _IOLBF, 0);
1503 int result = vfprintf(lf->err_fd, format, args);
1504 pthread_mutex_unlock(&lf->output_mutex);
void cycle_arena_destroy(void)
Destroy the cycle arena and release allocated resources.
void * finder(LfContext *lf, QueuePayload *current_node, QueuePayload *child_node)
Process a directory and its entries, applying filters and enqueuing subdirectories.
void * worker(void *arg)
Worker thread function that processes tasks from the MPMCQueue.
bool build_full_path(char *, size_t, const char *, const char *, size_t *)
Build a full file path by concatenating a directory path and a file name.
int err_out(LfContext *lf, const char *format,...)
Thread-safe error output function that writes formatted error messages to the specified error file or...
TerminationStatus termination_status
int sort_lf_output(LfContext *lf, int, char **)
Sort the output of the lf command using the system's sort utility.
int scan_file(const char *file_spec, const size_t *path_len, LfContext *lf, const unsigned char, const struct stat *, OutputBuffer *)
Scan a file or directory and apply filters based on the LfContext.
void flush_output_buffer(LfContext *, OutputBuffer *)
Flush the output buffer to stdout in a thread-safe manner.
void cycle_arena_init(void)
Initialize the cycle arena for tracking visited directories.
bool mpmc_enqueue(LfContext *, const QueuePayload *child_node)
Enqueue a task into the MPMCQueue.
MPMCQueue * mpmc_queue_init()
Initialize the MPMCQueue.
bool append_output_buffer(LfContext *, OutputBuffer *, const char *, size_t, bool)
Append a path to the output buffer, flushing if necessary.
bool init_lf(LfContext *lf, int, char **)
Initialize the file search process.
#define print_file_type(mask, lf_type, dt_type, name)
bool mpmc_dequeue(LfContext *lf, QueuePayload *output_node)
Dequeue a task from the MPMCQueue.
unsigned char const lf_mask[15]
void debug_out(LfContext *lf, int, char **)
Output debug information to stderr.
bool is_dirsys(const char *)
bool is_link_cycle(dev_t dev, ino_t ino, CycleNode *parent)
bool is_hidden(const char *)
const char * argp_program_version
const char * argp_program_bug_address
int main(int argc, char **argv)
Capture the current terminal settings for later restoration.
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 is_valid_regex(const char *)
Checks if the given regular expression pattern is valid.
bool expand_tilde(char *, uint)
Replaces "~/" in string with the user's home directory.
char * get_ip_addresses(char *, uint)
Retrieves the IP addresses of the local machine and formats them into a string.
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 * 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,...