C-Menu 0.2.9
A User Interface Toolkit
Loading...
Searching...
No Matches
lf.c File Reference

list files matching a regular expression More...

#include "include/cm.h"
#include <argp.h>
#include <dirent.h>
#include <errno.h>
#include <fcntl.h>
#include <grp.h>
#include <linux/limits.h>
#include <pthread.h>
#include <pwd.h>
#include <regex.h>
#include <stdatomic.h>
#include <stdbool.h>
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/stat.h>
#include <sys/sysinfo.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <time.h>
#include <unistd.h>
Include dependency graph for lf.c:

Go to the source code of this file.

Macros

#define _GNU_SOURCE
#define DT_LNK_DIR   14
#define print_file_type(mask, lf_type, dt_type, name)

Typedefs

typedef struct TaskNode TaskNode

Functions

void debug_out (SearchFilters *f, int argc, char **argv, int threads)
 Output debug information about the search filters and configuration.
TaskNodedequeue_dir ()
 Dequeue a directory dir_path for processing by finder threads.
void enqueue_dir (TaskNode *new_task)
 Enqueue a directory dir_path for processing by finder threads.
void * finder (void *arg)
 Worker thread function to process directories from the queue.
bool init_find (SearchFilters *f, int argc, char **argv)
 Initialize the file search based on the provided SearchFilters and start finder threads.
bool is_dirsys (const char *)
bool is_hidden (const char *)
int main (int argc, char **argv)
int scan_file (char *file_spec, const SearchFilters *f, const unsigned char effective_type)
 scan a single file against search filters
void sort_lf_output (SearchFilters *, int, char **)

Variables

atomic_int active_tasks = 0
const char * argp_program_bug_address = "billxwaller@gmail.com"
const char * argp_program_version = CM_VERSION
pthread_cond_t cond_var = PTHREAD_COND_INITIALIZER
char * debug_p
const char doc []
char * exec
size_t file_count = 0
char * file_types_p
unsigned char const lf_mask [15]
int lfargc
char * lfargs [3]
unsigned int nthreads
char * perms_p
TaskNodeqhead = NULL
TaskNodeqtail = NULL
pthread_mutex_t queue_mutex = PTHREAD_MUTEX_INITIALIZER
int shutdown = 0
int termination_status = EXIT_SUCCESS
struct tm tm_info

Detailed Description

list files matching a regular expression

NOTICE: This file is part of the lf project, which is currently under development. There are two reasons not to use it at this time. First, it is not yet fully functional and may contain bugs or incomplete, unoptimized code. Second, the API and features are still being finalized, so using it now may lead to compatibility issues in the future as changes are made. Once the project is more mature and stable, this file will be ready for use. In the meantime, it serves as a work in progress and may be subject to significant changes as development continues.

Thank you for your patience and understanding.

Author
Bill Waller Copyright (c) 2025 MIT License billx.nosp@m.wall.nosp@m.er@gm.nosp@m.ail..nosp@m.com
Date
2026-02-09

Definition in file lf.c.

Macro Definition Documentation

◆ _GNU_SOURCE

#define _GNU_SOURCE

Definition at line 21 of file lf.c.

◆ DT_LNK_DIR

#define DT_LNK_DIR   14

Definition at line 101 of file lf.c.

◆ print_file_type

#define print_file_type ( mask,
lf_type,
dt_type,
name )
Value:
{ \
fprintf(stderr, "%c %08b (%3d) %08b (%2d) %s\n", \
(mask & lf_type) ? '*' : ' ', lf_type, lf_type, \
dt_type, dt_type, name); \
}

Definition at line 45 of file lf.c.

45#define print_file_type(mask, lf_type, dt_type, name) \
46 { \
47 fprintf(stderr, "%c %08b (%3d) %08b (%2d) %s\n", \
48 (mask & lf_type) ? '*' : ' ', lf_type, lf_type, \
49 dt_type, dt_type, name); \
50 }

Typedef Documentation

◆ TaskNode

typedef struct TaskNode TaskNode

Definition at line 110 of file lf.c.

Function Documentation

◆ debug_out()

void debug_out ( SearchFilters * f,
int argc,
char ** argv,
int threads )

Output debug information about the search filters and configuration.

Parameters
fA pointer to a SearchFilters struct containing the options and flags for filtering.
argcThe number of command-line arguments.
argvThe array of command-line argument strings.
threadsThe number of threads being used for the search.

This function prints detailed information about the configuration and search filters as well as each of the options and arguments used on the command line invoking lf. The output is sent to the standard error stream, which can be redirected to standard output making it suitable as documentation for an audit trail.

Definition at line 620 of file lf.c.

620 {
621 char user_str[100];
622 char ip_str[MAXLEN];
623 int len = 0;
624 int i;
625 bool addspace_before = false;
626 if (f->debug && (f->report_config || f->report_info || f->report_all)) {
627 fprintf(stderr, "%s,%s,%s,", get_local_timestamp(), get_user_str(user_str, 100), get_ip_addresses(ip_str, MAXLEN));
628 for (i = 0; i < argc; i++) {
629 len = len + strlen(argv[i]);
630 if (len > 72) {
631 fprintf(stderr, "\n");
632 len = strlen(argv[i]);
633 addspace_before = false;
634 }
635 if (addspace_before) {
636 fprintf(stderr, " ");
637 len++;
638 }
639 fprintf(stderr, "%s", argv[i]);
640 addspace_before = true;
641 }
642 fprintf(stderr, "\n\n");
643 fprintf(stderr, "%s\n\n", CM_VERSION);
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");
669 else
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");
674 print_file_type(f->include_types, LF_FIFO, DT_FIFO, "FIFO p-named pipe");
675 print_file_type(f->include_types, LF_CHR, DT_CHR, "CHR c-character device");
676 print_file_type(f->include_types, LF_DIR, DT_DIR, "DIR d-directory");
677 print_file_type(f->include_types, LF_BLK, DT_BLK, "BLK b-block device");
678 print_file_type(f->include_types, LF_REG, DT_REG, "REG f-regular file");
679 print_file_type(f->include_types, LF_LNK, DT_LNK, "LINK l-symbolic link");
680 print_file_type(f->include_types, LF_SOCK, DT_SOCK, "SOCK s-socket");
681 print_file_type(f->include_types, LF_UNKNOWN, DT_UNKNOWN, "UNKNOWN u-unknown");
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");
686 if (f->flags & LF_USER)
687 fprintf(stderr, "User: %s (%ju)\n", f->user_name, f->user_id);
688 if (f->include_perms) {
689 if (f->include_perms & LF_IXUSR)
690 fprintf(stderr, " %08b Execute\n", LF_IXUSR);
691 if (f->include_perms & LF_IWUSR)
692 fprintf(stderr, " %08b Write\n", LF_IWUSR);
693 if (f->include_perms & LF_IRUSR)
694 fprintf(stderr, " %08b Read\n", LF_IRUSR);
695 if (f->include_perms & LF_ISUID)
696 fprintf(stderr, " %08b SETUID\n", LF_ISUID);
697 if (f->include_perms & LF_ISGID)
698 fprintf(stderr, " %08b SETGID\n", LF_ISGID);
699 }
700 fprintf(stderr, "\n");
701 if (f->flags & LF_REGEX)
702 fprintf(stderr, "Include regex: %s\n\n", f->re);
703 if (f->flags & LF_EXC_REGEX)
704 fprintf(stderr, "Exclude regex: %s\n\n", f->ere);
705
706 if (f->after) {
707 char buf[32];
708 format_local_timestamp(f->after, buf, sizeof(buf));
709 fprintf(stderr, "Modified after: %s\n\n", buf);
710 }
711
712 if (f->before) {
713 char buf[32];
714 format_local_timestamp(f->before, buf, sizeof(buf));
715 fprintf(stderr, "Modified before: %s\n\n", buf);
716 }
717
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;
721 i = 0;
722 while (size >= 1024 && i < 6) {
723 size /= 1024;
724 i++;
725 }
726 char buffer[32];
727 ssnprintf(buffer, 32, "%ld %s", size, units[i]);
728 fprintf(stderr, "Minimum file size: %s\n\n", buffer);
729 }
730 if (f->max_depth)
731 fprintf(stderr, "Max depth: %d\n\n", f->max_depth);
732 if (f->ignore_case)
733 fprintf(stderr, "Ignore case in regex matching.\n\n");
734 if (f->include_hidden)
735 fprintf(stderr, "Include hidden files.\n\n");
736 if (f->follow_links)
737 fprintf(stderr, "Follow symbolic links.\n\n");
738 if (f->sort)
739 fprintf(stderr, "Sort output in ascending order.\n\n");
740 if (f->sort_reverse)
741 fprintf(stderr, "Sort output in reverse order.\n\n");
742 if (f->report_config && !f->report_all)
743 exit(EXIT_SUCCESS);
744 }
745 return;
746}
@ LF_EXC_REGEX
Definition cm.h:205
@ LF_ISUID
Definition cm.h:215
@ LF_IRUSR
Definition cm.h:213
@ LF_ISGID
Definition cm.h:214
@ LF_REGEX
Definition cm.h:206
@ LF_USER
Definition cm.h:208
@ LF_IXUSR
Definition cm.h:211
@ LF_IWUSR
Definition cm.h:212
@ LF_FIFO
Definition cm.h:220
@ LF_UNKNOWN
Definition cm.h:227
@ LF_SOCK
Definition cm.h:226
@ LF_BLK
Definition cm.h:223
@ LF_CHR
Definition cm.h:221
#define CM_VERSION
Definition version.h:7
#define MAXLEN
Definition curskeys.c:15
#define print_file_type(mask, lf_type, dt_type, name)
Definition lf.c:45
#define LF_DIR
Definition futil.c:44
#define LF_REG
Definition futil.c:45
#define LF_LNK
Definition futil.c:43
char * get_local_timestamp()
Returns the current local time as an ISO 8601 formatted string.
Definition futil.c:314
size_t ssnprintf(char *, size_t, const char *,...)
ssnprintf was designed to be a safer alternative to snprintf.
Definition futil.c:420
char * get_ip_addresses(char *, int)
Retrieves the IP addresses of the local machine and formats them into a string.
Definition futil.c:354
char * format_local_timestamp(time_t, char *, size_t)
Formats a time_t as an ISO 8601 string in local time.
Definition futil.c:303
char * get_user_str(char *, size_t)
Retrieves the current user's name and UID, and formats it into a string.
Definition futil.c:327

References format_local_timestamp(), get_ip_addresses(), get_local_timestamp(), get_user_str(), LF_BLK, LF_CHR, LF_DIR, LF_EXC_REGEX, LF_FIFO, LF_IRUSR, LF_ISGID, LF_ISUID, LF_IWUSR, LF_IXUSR, LF_LNK, LF_REG, LF_REGEX, LF_SOCK, LF_UNKNOWN, LF_USER, and ssnprintf().

Referenced by init_find().

Here is the call graph for this function:
Here is the caller graph for this function:

◆ dequeue_dir()

TaskNode * dequeue_dir ( )

Dequeue a directory dir_path for processing by finder threads.

Returns
A pointer to a TaskNode containing the directory dir_path and depth, or NULL if the queue is empty and shutdown has been initiated.

This function removes and returns the next TaskNode from the global queue. It uses a mutex to ensure thread-safe access to the queue, and waits on a condition variable if the queue is empty. The function also checks for shutdown conditions to allow finder threads to exit gracefully when there is no more work to process.

Definition at line 790 of file lf.c.

790 {
791 pthread_mutex_lock(&queue_mutex);
792
793 // Wait until there is a task in the queue or shutdown has been
794 // initiated. The loop condition checks if the queue is empty (qhead ==
795 // NULL) and if shutdown has not been initiated (!shutdown). If both
796 // conditions are true, it means there are no tasks to process and the
797 // thread should wait. The thread will be woken up when a new task is
798 // enqueued (via pthread_cond_signal in enqueue_dir) or when shutdown is
799 // initiated (via pthread_cond_broadcast in enqueue_dir or when
800 // active_tasks count reaches zero). This ensures that threads do not
801 // wait indefinitely when there are no tasks left to process and allows
802 // for a graceful shutdown of the program.
803 while (qhead == NULL && !shutdown) {
804 // If there are no active tasks and the queue is empty, we can
805 // safely initiate shutdown. This check is necessary to prevent a
806 // potential race condition where a thread could be waiting
807 // indefinitely on the condition variable if all tasks have been
808 // completed and no new tasks will be enqueued. By checking the
809 // active_tasks count, we can determine when it's safe to signal
810 // shutdown
811 if (atomic_load(&active_tasks) == 0) {
812 shutdown = 1;
813 // and wake up any waiting threads so they can exit gracefully.
814 pthread_cond_broadcast(&cond_var);
815 break;
816 }
817 // Wait for a task to be enqueued or shutdown initiated. The thread
818 // will be woken up when a new task is added to the queue (via
819 // pthread_cond_signal in enqueue_dir) or when shutdown is initiated
820 // (via pthread_cond_broadcast in enqueue_dir or when active_tasks
821 // count reaches zero). This allows the thread to check the
822 // conditions again and either process a new task or exit if
823 // shutdown has been initiated.
824 pthread_cond_wait(&cond_var, &queue_mutex);
825 }
826 if (shutdown && qhead == NULL) {
827 pthread_mutex_unlock(&queue_mutex);
828 return NULL;
829 }
830 TaskNode *temp = qhead;
831 // Move the head pointer to the next task in the queue. If the queue
832 // becomes empty after this operation (qhead becomes NULL), we also set
833 // qtail to NULL to indicate that the queue is empty. This ensures that
834 // both qhead and qtail accurately reflect the state of the queue,
835 // preventing potential issues with enqueuing new tasks or checking for
836 // an empty queue in future operations.
837 qhead = qhead->next_task;
838 if (!qhead)
839 qtail = NULL;
840 atomic_fetch_add(&active_tasks, 1);
841 pthread_mutex_unlock(&queue_mutex);
842 return temp;
843}
int shutdown
Definition lf.c:135
atomic_int active_tasks
Definition lf.c:134
TaskNode * qtail
Definition lf.c:131
TaskNode * qhead
Definition lf.c:130
pthread_cond_t cond_var
Definition lf.c:133
pthread_mutex_t queue_mutex
Definition lf.c:132
struct TaskNode TaskNode
Definition lf.c:110

References active_tasks, cond_var, qhead, qtail, queue_mutex, and shutdown.

Referenced by finder().

Here is the caller graph for this function:

◆ enqueue_dir()

void enqueue_dir ( TaskNode * new_task)

Enqueue a directory dir_path for processing by finder threads.

Parameters
new_taskA pointer to a TaskNode containing the directory path and depth to be enqueued for processing by finder threads.

This function adds a new TaskNode to the global queue of directories to be processed by finder threads. It uses a mutex to ensure thread-safe access to the queue and signals one waiting thread that a new task is available. If shutdown has been initiated, the signal will wake up any waiting threads so they can check the shutdown condition and exit gracefully.

Definition at line 757 of file lf.c.

757 {
758 pthread_mutex_lock(&queue_mutex);
759 if (qtail)
760 // Add the new task to the end of the queue. If qtail is not NULL,
761 // it means there are already tasks in the queue, so we set the
762 // next_task pointer of the current tail to point to the new task.
763 // This effectively adds the new task to the end of the queue.
764 qtail->next_task = new_task;
765 else
766 // If qtail is NULL, it means the queue is currently empty, so we
767 // set qhead to point to the new task, making it the first and only
768 // task in the queue
769 qhead = new_task;
770 // Finally, we update qtail to point to the new task, ensuring that it
771 // always points to the last task in the queue.
772 qtail = new_task;
773 // Signal one waiting thread that a new task is available. If shutdown
774 // hasn't been initiated, this will wake up a finder thread to process
775 // the new task. If shutdown has been initiated, the signal will wake up
776 // any waiting threads so they can check the shutdown condition and exit
777 // gracefully.
778 pthread_cond_signal(&cond_var);
779 pthread_mutex_unlock(&queue_mutex);
780}

References cond_var, qhead, qtail, and queue_mutex.

Referenced by finder(), and init_find().

Here is the caller graph for this function:

◆ finder()

void * finder ( void * arg)

Worker thread function to process directories from the queue.

Parameters
argPointer to the SearchFilters struct containing the options and flags for filtering.
Returns
NULL

This function continuously dequeues directory dir_path from the global queue and processes them. For each directory, it lists its contents and applies the specified filters to each file. If a subdirectory is found and it meets the criteria for further searching (e.g., not hidden if hidden files are suppressed, and within max depth), it is enqueued for processing. The function uses atomic operations to track active tasks and condition variables to manage thread synchronization and shutdown when all work is complete.

Definition at line 857 of file lf.c.

857 {
858 SearchFilters *f = (SearchFilters *)arg;
859 char lnk_path[PATH_MAX] = {'\0'};
860 // regmatch_t pmatch;
861
862 while (1) {
863 TaskNode *current_task = dequeue_dir();
864 if (!current_task)
865 break;
866
867 //-------------------------------------------------------------
868 // Open the directory for reading. We use openat with AT_FDCWD to
869 // open the directory specified by current_task->dir_path, and then
870 // use fdopendir to get a DIR* stream for reading the directory
871 // entries. This approach allows us to handle directories with
872 // special characters in their names more robustly, as it avoids
873 // issues that can arise with functions like opendir that take a
874 // path string directly. If openat or fdopendir fails, we log the
875 // error (if debugging is enabled), clean up resources for the
876 // current task, and continue to the next iteration of the loop to
877 // process another task.
878 int dir_fd =
879 openat(AT_FDCWD, current_task->dir_path, O_RDONLY | O_DIRECTORY);
880 if (dir_fd == -1) {
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,
884 strerror(errno));
885 termination_status = EXIT_FAILURE;
886 free(current_task->dir_path);
887 free(current_task->history);
888 free(current_task);
889 atomic_fetch_sub(&active_tasks, 1);
890 pthread_cond_broadcast(&cond_var);
891 continue;
892 }
893 DIR *dir = fdopendir(dir_fd);
894 if (dir == NULL) {
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));
899 close(dir_fd);
900 termination_status = EXIT_FAILURE;
901 free(current_task->dir_path);
902 free(current_task->history);
903 free(current_task);
904 atomic_fetch_sub(&active_tasks, 1);
905 pthread_cond_broadcast(&cond_var);
906 continue;
907 }
908 // unsigned char real_type;
909 //-------------------------------------------------------------
910 unsigned char effective_type;
911 char full_path[PATH_MAX] = {'\0'};
912 struct dirent *entry;
913 while ((entry = readdir(dir)) != NULL) {
914 struct stat st;
915 stpcpy(stpcpy(stpcpy(full_path, current_task->dir_path), "/"),
916 entry->d_name);
917 // Get link's metadata
918 int rc;
919 // We use fstatat with AT_SYMLINK_NOFOLLOW to get the metadata
920 // of the symbolic link itself, rather than the target it points
921 // to. This allows us to determine if the entry is a symbolic
922 // link and handle it according to the user's options (e.g.,
923 // whether to follow links or not). If fstatat fails, we log the
924 // error (if debugging is enabled) and continue to the next
925 // entry without processing this one further.
926 rc = fstatat(AT_FDCWD, full_path, &st, AT_SYMLINK_NOFOLLOW);
927 if (rc == -1) {
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,
931 strerror(errno));
932 termination_status = EXIT_FAILURE;
933 continue;
934 }
935 effective_type = (st.st_mode & S_IFMT) >> 12;
936 // Determine the real type of the entry. If the entry is a
937 // symbolic link, we set real_type to DT_LNK and then attempt to
938 // get the metadata of the target it points to using fstatat
939 // without AT_SYMLINK_NOFOLLOW. This allows us to determine the
940 // effective type of the entry based on the target's metadata,
941 // which is important for deciding how to process it (e.g.,
942 // whether it's a directory that we should enqueue for further
943 // searching). If fstatat fails when trying to get the target's
944 // metadata, we log the error (if debugging is enabled) but
945 // continue processing the entry based on its symbolic link
946 // metadata.
947 if (S_ISLNK(st.st_mode)) {
948 // Get the target's metadata
949 rc = fstatat(AT_FDCWD, full_path, &st, 0);
950 if (rc == -1) {
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,
954 strerror(errno));
955 }
956 termination_status = EXIT_FAILURE;
957 continue;
958 }
959 if (f->follow_links)
960 effective_type = (st.st_mode & S_IFMT) >> 12;
961 }
962 if (effective_type != DT_DIR) {
963 if (is_hidden(entry->d_name)) {
964 if (!f->include_hidden)
965 continue;
966 } else if (f->hidden_only)
967 continue;
968 } else {
969 if (is_dirsys(entry->d_name))
970 continue;
971 // Determine the effective type of the entry. We use the st_mode
972 // field from the stat struct to determine the file type by
973 // applying the S_IFMT mask and shifting it to get a value that
974 // corresponds to the DT_* constants. If the entry is a symbolic
975 // link and the user has chosen not to follow links, we treat it
976 // as a directory for the purpose of deciding whether to enqueue
977 // it for further searching. This allows us to handle symbolic
978 // links that point to directories in a way that respects the
979 // user's options while still allowing for traversal of linked
980 // directories if desired.
981 //
982 // Check for cycles by comparing the current
983 // directory's dev/inode with the history of dev/inode pairs
984 // from parent directories. If a match is found, it
985 // indicates a cycle and we skip processing this directory.
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);
994 else
995 fprintf(stderr, "%3d %ju %ju\n", i,
996 current_task->history[i].ino, st.st_ino);
997 }
998 if (current_task->history[i].dev == st.st_dev &&
999 current_task->history[i].ino == st.st_ino) {
1000 cycle_found = true;
1001 break;
1002 }
1003 }
1004 if (cycle_found) {
1005 if (f->debug && (f->report_warnings || f->report_errors ||
1006 f->report_trace || f->report_badlinks ||
1007 f->report_all)) {
1008 ssize_t len =
1009 readlink(full_path, lnk_path, sizeof(lnk_path) - 1);
1010 if (len != -1) {
1011 lnk_path[len] = '\0';
1012 fprintf(stderr, "CYCLIC_LINK,%s,%s\n", full_path,
1013 lnk_path);
1014 } else
1015 fprintf(stderr, "CYCLIC_LINK,%s\n", full_path);
1016 }
1017 termination_status = EXIT_FAILURE;
1018 continue;
1019 }
1020 //-------------------------------------------------------
1021 // Create a new TaskNode for the subdirectory and enqueue it
1022 // for processing by finder threads. We duplicate the
1023 // current history of dev/inode pairs and add the current
1024 // directory's dev/inode to the new history for the child
1025 // task. This allows us to maintain a record of the
1026 // directories we've visited in the current path, which is
1027 // essential for cycle detection. By checking this history
1028 // for each new directory we encounter, we can effectively
1029 // prevent infinite loops caused by symbolic links or hard
1030 // links that create cycles in the directory structure.
1031 //
1032 // What about using realloc? NO! All the directories in this
1033 // subdirectory must share the same history array.
1034 if (f->max_depth != 0 && current_task->depth + 1 == f->max_depth)
1035 continue;
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);
1045 }
1046 child_task->history[current_task->depth].dev = st.st_dev;
1047 child_task->history[current_task->depth].ino = st.st_ino;
1048 enqueue_dir(child_task);
1049 }
1050 if (f->hidden_only && !is_hidden(entry->d_name))
1051 continue;
1052 scan_file(full_path, f, effective_type);
1053 }
1054 closedir(dir);
1055 free(current_task->dir_path);
1056 free(current_task->history);
1057 free(current_task);
1058 atomic_fetch_sub(&active_tasks, 1);
1059 }
1060 return NULL;
1061}
int termination_status
Definition lf.c:136
TaskNode * dequeue_dir()
Dequeue a directory dir_path for processing by finder threads.
Definition lf.c:790
void enqueue_dir(TaskNode *)
Enqueue a directory dir_path for processing by finder threads.
Definition lf.c:757
bool is_dirsys(const char *)
Definition lf.c:1073
int scan_file(char *, const SearchFilters *, const unsigned char)
scan a single file against search filters
Definition lf.c:1088
bool is_hidden(const char *)
Definition lf.c:1063

References active_tasks, cond_var, dequeue_dir(), enqueue_dir(), is_dirsys(), is_hidden(), scan_file(), and termination_status.

Referenced by init_find().

Here is the call graph for this function:
Here is the caller graph for this function:

◆ init_find()

bool init_find ( SearchFilters * f,
int argc,
char ** argv )

Initialize the file search based on the provided SearchFilters and start finder threads.

Parameters
fA pointer to a SearchFilters struct containing the options and flags for filtering.
argcThe number of command-line arguments.
argvThe array of command-line argument strings.
Returns
true if the search was initialized successfully, false if an error occurred during initialization (e.g., regex compilation failure).

This function processes the flags and options specified in the SearchFilters struct to set up the search criteria. It compiles any regular expressions provided by the user and initializes the queue with the base directory. It then creates a number of finder threads equal to the number of CPU cores to perform the directory traversal and file scanning concurrently. The function waits for all finder threads to complete before cleaning up resources and returning.

suppress file types that aren't included

Definition at line 523 of file lf.c.

523 {
525 if (!f->include_types)
526 f->include_types = 0xff;
527 if (f->include_types)
528 f->suppress_types = f->include_types ^ 0xff;
529 // LF_HIDE = 0 - include hidden files,
530 // LF_HIDE = 1 - suppress hidden files
531 f->include_hidden = !(f->flags & LF_HIDE);
532 int reti = 0;
533 f->reg_flags = REG_EXTENDED;
534 if (f->flags & LF_ICASE)
535 f->reg_flags |= REG_ICASE;
536 if (f->flags & LF_REGEX) {
537 reti = regcomp(&f->compiled_re, f->re, f->reg_flags);
538 if (reti) {
539 fprintf(stderr, "lf: '%s' Invalid pattern\n", f->re);
540 regfree(&f->compiled_re);
541 return false;
542 }
543 }
544 if (f->flags & LF_EXC_REGEX) {
545 reti = regcomp(&f->compiled_ere, f->ere, f->reg_flags);
546 if (reti) {
547 fprintf(stderr, "lf: '%s' Invalid exclude pattern\n", f->ere);
548 regfree(&f->compiled_ere);
549 return false;
550 }
551 }
552 unsigned int nprocs = get_nprocs();
553 if (nthreads > 1 && nthreads < nprocs)
554 nthreads += nprocs;
555 else {
556 if (nthreads == 0)
557 nthreads = ((nprocs * 40) / 99) + 1;
558 }
559 if (nthreads > nprocs)
560 nthreads = nprocs - 1;
561
562 debug_out(f, argc, argv, nthreads);
563 //--------------------------------------------------------------------
564 // Create and enqueue the first TaskNode
565 struct stat st;
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;
575 enqueue_dir(child_task);
576 pthread_t threads[nthreads];
577 for (unsigned int i = 0; i < nthreads; i++) {
578 pthread_create(&threads[i], NULL, finder, f);
579 }
580 pthread_mutex_lock(&queue_mutex);
581 while (!shutdown) {
582 pthread_cond_wait(&cond_var, &queue_mutex);
583 }
584 pthread_mutex_unlock(&queue_mutex);
585 for (unsigned int i = 0; i < nthreads; i++)
586 pthread_join(threads[i], NULL);
587 } else {
588 fprintf(stderr,
589 "Warning: Base path '%s' is not a directory. No "
590 "files will be found.\n",
591 f->base_path);
592 }
593 }
594 //--------------------------------------------------------------------
595 // End Program
596 if (f->flags & LF_REGEX) {
597 regfree(&f->compiled_re);
598 }
599 if (f->flags & LF_EXC_REGEX) {
600 regfree(&f->compiled_ere);
601 }
602 free(f->base_path);
603 free(f->user_name);
604 free(f->re);
605 free(f->ere);
606 free(f);
607 if (reti)
608 return false;
609 return true;
610}
@ LF_HIDE
Definition cm.h:203
@ LF_ICASE
Definition cm.h:204
void * finder(void *)
Worker thread function to process directories from the queue.
Definition lf.c:857
unsigned int nthreads
Definition lf.c:62
void debug_out(SearchFilters *, int, char **, int)
Output debug information about the search filters and configuration.
Definition lf.c:620

References cond_var, debug_out(), enqueue_dir(), finder(), LF_EXC_REGEX, LF_HIDE, LF_ICASE, LF_REGEX, nthreads, queue_mutex, and shutdown.

Referenced by main(), and sort_lf_output().

Here is the call graph for this function:
Here is the caller graph for this function:

◆ is_dirsys()

bool is_dirsys ( const char * name)

Definition at line 1073 of file lf.c.

1073 {
1074 if (name[0] == '.') {
1075 if (name[1] == '\0')
1076 return true;
1077 if (name[1] == '.' && name[2] == '\0')
1078 return true;
1079 }
1080 return false;
1081}

Referenced by finder().

Here is the caller graph for this function:

◆ is_hidden()

bool is_hidden ( const char * name)

Definition at line 1063 of file lf.c.

1063 {
1064 if (name[0] == '.') {
1065 if (name[1] == '\0')
1066 return false;
1067 if (name[1] == '.' && name[2] == '\0')
1068 return false;
1069 return true;
1070 }
1071 return false;
1072}

Referenced by finder().

Here is the caller graph for this function:

◆ main()

int main ( int argc,
char ** argv )

Definition at line 389 of file lf.c.

389 {
390
391 SearchFilters *f = (SearchFilters *)calloc(1, sizeof(SearchFilters));
392 f->ignore_case = false;
393 f->sort = false;
394 f->sort_reverse = false;
395 f->include_hidden = false; // By default, hidden files are suppressed. Use
396 // -H to include them.
397 // LF_HIDE = 0 - include hidden files,
398 // LF_HIDE = 1 - suppress hidden files
399 f->hidden_only = false;
400 f->flags |= LF_HIDE; // Turn hide flag on
401 f->follow_links = false; // By default, symbolic links are not followed. Use
402 // -L to follow them.
403 f->count = false;
404 f->count_silently = false;
405 char tmp_str[PATH_MAX];
406 argp_parse(&argp, argc, argv, 0, 0, f);
407 if (lfargc > 0) {
408 strnz__cpy(tmp_str, lfargs[0], MAXLEN - 1);
409 expand_tilde(tmp_str, MAXLEN - 1);
410 if (is_directory(tmp_str) || is_symlink_to_dir(tmp_str)) {
411 f->base_path = strdup(tmp_str);
412 } else if (is_valid_regex(lfargs[0])) {
413 f->re = strdup(lfargs[0]);
414 f->flags |= LF_REGEX;
415 } else {
416 fprintf(
417 stderr,
418 "lf: arg1: '%s' is neither a directory nor a valid regex.\n",
419 lfargs[0]);
420 exit(EXIT_FAILURE);
421 }
422 }
423 if (lfargc > 1) {
424 if (!f->base_path || f->base_path[0] == '\0') {
425 strnz__cpy(tmp_str, lfargs[1], MAXLEN - 1);
426 expand_tilde(tmp_str, MAXLEN - 1);
427 if (is_directory(tmp_str) || is_symlink_to_dir(tmp_str))
428 f->base_path = strdup(tmp_str);
429 }
430 if ((!(f->flags & LF_REGEX)) && is_valid_regex(lfargs[1])) {
431 f->re = strdup(lfargs[1]);
432 f->flags |= LF_REGEX;
433 } else {
434 fprintf(stderr,
435 "lf: '%s' is neither a directory nor a valid regular "
436 "expression.\n",
437 lfargs[1]);
438 exit(EXIT_FAILURE);
439 }
440 }
441 if (f->base_path == nullptr || f->base_path[0] == '\0')
442 f->base_path = strdup(".");
443 if (!f->sort) {
444 init_find(f, argc, argv);
445 } else
446 sort_lf_output(f, argc, argv);
447 if (f->count)
448 fprintf(stderr, "Files: %zu\n", file_count);
449 if (file_count == 0)
450 return 1;
451 return 0;
452}
void sort_lf_output(SearchFilters *, int, char **)
Definition lf.c:461
size_t file_count
Definition lf.c:143
int lfargc
Definition lf.c:137
char * lfargs[3]
Definition lf.c:138
bool init_find(SearchFilters *, int, char **)
Initialize the file search based on the provided SearchFilters and start finder threads.
Definition lf.c:523
size_t strnz__cpy(char *, const char *, size_t)
safer alternative to strncpy
Definition futil.c:544
bool is_directory(const char *)
Checks if the given path is a directory.
Definition futil.c:1372
bool expand_tilde(char *, int)
Replaces "~/" in string with the user's home directory.
Definition futil.c:970
bool is_valid_regex(const char *)
Checks if the given regular expression pattern is valid.
Definition futil.c:1400
bool is_symlink_to_dir(const char *)
Checks if the given path is a symbolic link to a directory.
Definition futil.c:1385

References expand_tilde(), file_count, init_find(), is_directory(), is_symlink_to_dir(), is_valid_regex(), LF_HIDE, LF_REGEX, lfargc, lfargs, sort_lf_output(), and strnz__cpy().

Here is the call graph for this function:

◆ scan_file()

int scan_file ( char * file_spec,
const SearchFilters * f,
const unsigned char effective_type )

scan a single file against search filters

Parameters
file_specspecification of file being scanned
fSearchFilters struct
effective_typetype of file being scanned
Returns
true if file selected, false otherwise

Definition at line 1088 of file lf.c.

1089 {
1090 regmatch_t pmatch[2];
1091 bool stat_cached = false;
1092
1093 while (1) {
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);
1097 }
1098 if (f->suppress_types & lf_mask[effective_type])
1099 break;
1100 // Exclude non-matching files
1101 if (f->flags & LF_REGEX) {
1102 int reti =
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);
1108 break;
1109 } else if (reti) {
1110 char errbuf[MAXLEN];
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);
1114 termination_status = EXIT_FAILURE;
1115 return 0;
1116 }
1117 }
1118 // Exclude matching files
1119 if (f->flags & LF_EXC_REGEX) {
1120 int reti =
1121 regexec(&f->compiled_ere, file_spec, f->compiled_re.re_nsub + 1,
1122 pmatch, f->reg_flags);
1123 if (reti == 0) // Match
1124 break;
1125 if (reti != REG_NOMATCH) {
1126 if (reti) {
1127 char errbuf[MAXLEN];
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);
1131 termination_status = EXIT_FAILURE;
1132 return 0;
1133 }
1134 }
1135 }
1136 stat_cached = false;
1137 struct stat sb;
1138 // Exclude files not owned by specified user
1139 if ((f->flags & LF_USER) && stat(file_spec, &sb) == 0) {
1140 stat_cached = true;
1141 if (sb.st_uid != f->user_id)
1142 break;
1143 }
1144 if (f->include_perms) {
1145 if (!stat_cached) {
1146 if (stat(file_spec, &sb) == 0)
1147 stat_cached = true;
1148 }
1149 if ((f->include_perms & LF_IRUSR) && !(sb.st_mode & S_IRUSR))
1150 break;
1151 else if ((f->include_perms & LF_IWUSR) && !(sb.st_mode & S_IWUSR))
1152 break;
1153 else if ((f->include_perms & LF_IXUSR) && !(sb.st_mode & S_IXUSR))
1154 break;
1155 else if ((f->include_perms & LF_ISUID) && !(sb.st_mode & S_ISUID))
1156 break;
1157 else if ((f->include_perms & LF_ISGID) && !(sb.st_mode & S_ISGID))
1158 break;
1159 }
1160 if (f->before) {
1161 if (!stat_cached) {
1162 if (stat(file_spec, &sb) == 0)
1163 stat_cached = true;
1164 }
1165 if (stat_cached && sb.st_mtime > f->before)
1166 break;
1167 }
1168 if (f->after) {
1169 if (!stat_cached) {
1170 if (stat(file_spec, &sb) == 0)
1171 stat_cached = true;
1172 }
1173 if (stat_cached && sb.st_mtime < f->after)
1174 break;
1175 }
1176 if (f->file_size_min) {
1177 if (!stat_cached) {
1178 if (stat(file_spec, &sb) == 0)
1179 stat_cached = true;
1180 }
1181 if (stat_cached && sb.st_size < f->file_size_min)
1182 break;
1183 }
1184 if (effective_type == DT_DIR) {
1185 char *file_p = file_spec;
1186 while (*file_p++ != '\0')
1187 ;
1188 *file_p = '/';
1189 }
1190 if (f->only_errors)
1191 break;
1192 file_count++;
1193 if (!f->count_silently) {
1194 if (file_spec[0] == '.' && file_spec[1] == '/')
1195 printf("%s\n", &file_spec[2]);
1196 else
1197 printf("%s\n", file_spec);
1198 }
1199 break;
1200 }
1201 return true;
1202}
unsigned char const lf_mask[15]
Definition lf.c:102

References file_count, LF_EXC_REGEX, LF_IRUSR, LF_ISGID, LF_ISUID, LF_IWUSR, LF_IXUSR, lf_mask, LF_REGEX, LF_USER, and termination_status.

Referenced by finder().

Here is the caller graph for this function:

◆ sort_lf_output()

void sort_lf_output ( SearchFilters * f,
int argc,
char ** argv )

If sorting is requested, execute the finder and pipe its output to the sort command. We can achieve this by creating a child process that runs the sort command, and redirecting the output of the file finder to the input of the sort command using a pipe. The parent process will run the finder and write its output to the pipe, while the child process will read from the pipe and execute the sort command.

Definition at line 461 of file lf.c.

461 {
462 char *eargv[MAXARGS];
463 int eargc = 0;
464 eargv[eargc++] = strdup("sort");
465 if (f->sort_reverse)
466 eargv[eargc++] = strdup("-r");
467 eargv[eargc] = nullptr;
468 int wstatus;
469
470 // int save_fd = dup(STDOUT_FILENO); // save current STDOUT
471 int fds[2];
472 pipe(fds); // Create the pipes
473
474 pid_t pid1 = fork();
475 if (pid1 == 0) { // Child process
476 // Child's STDIN will be redirected to the read end of the pipe, so that
477 // the sort process will read the output of the finder from the pipe.
478 close(fds[1]); // Close the write end of the pipe in the child
479 dup2(fds[0], STDIN_FILENO); // Clone child's read pipe to STDIN_FILENO
480 close(fds[0]); // Close the original read end of the pipe
481 execvp(eargv[0], eargv); // Execute the sort command
482 fprintf(stderr, "Failed to execute sort: %s\n", strerror(errno));
483 exit(EXIT_FAILURE);
484 }
485 // fclose(stdout);
486 dup2(fds[1], STDOUT_FILENO); // Clone write pipe to STDOUT_FILENO
487 stdout = fdopen(STDOUT_FILENO, "w"); // Reopen STDOUT as a stream
488 setvbuf(stdout, NULL, _IONBF, 0); // line buffering
489 init_find(f, argc, argv); // Initialize and transfer control to the finder
490 fclose(stdout);
491 close(fds[1]);
492 wait(&wstatus);
493 for (int i = 0; i < eargc; i++)
494 free(eargv[i]);
495 // fclose(stdout);
496 // dup2(save_fd, STDOUT_FILENO); // restore STDOUT
497 // stdout = fdopen(STDOUT_FILENO, "w"); // Reopen STDOUT as a stream
498 //
499 // line buffering is essential to ensure that output is flushed to
500 // the sort process in a timely manner, preventing deadlocks and
501 // ensuring that the sort process can start processing input as soon
502 // as it is available. Without line buffering, the output may be
503 // buffered until the buffer is full, which could lead to delays in
504 // processing and potential deadlocks if the sort process is waiting
505 // for input that hasn't been flushed yet.
506}
#define MAXARGS
Definition cm.h:48
int eargc
Definition futil.c:57
char * eargv[MAXARGS]
Definition futil.c:58

References init_find().

Referenced by main().

Here is the call graph for this function:
Here is the caller graph for this function:

Variable Documentation

◆ active_tasks

atomic_int active_tasks = 0

Definition at line 134 of file lf.c.

Referenced by dequeue_dir(), and finder().

◆ argp_program_bug_address

const char* argp_program_bug_address = "billxwaller@gmail.com"

Definition at line 54 of file lf.c.

◆ argp_program_version

const char* argp_program_version = CM_VERSION

Definition at line 53 of file lf.c.

◆ cond_var

pthread_cond_t cond_var = PTHREAD_COND_INITIALIZER

Definition at line 133 of file lf.c.

Referenced by dequeue_dir(), enqueue_dir(), finder(), and init_find().

◆ debug_p

char* debug_p

Definition at line 142 of file lf.c.

◆ doc

const char doc[]
Initial value:
= "lf list files\vIf specified, DIRECTORY is the top-level "
"directory to search. REGULAR_EXPRESSION is a properly "
"formatted regular expression for which matching files "
"will be listed."

Definition at line 55 of file lf.c.

◆ exec

char* exec

Definition at line 139 of file lf.c.

◆ file_count

size_t file_count = 0

Definition at line 143 of file lf.c.

Referenced by main(), and scan_file().

◆ file_types_p

char* file_types_p

Definition at line 140 of file lf.c.

◆ lf_mask

unsigned char const lf_mask[15]
Initial value:
= {
0, 0b00000001, 0b00000010, 0, 0b00000100, 0, 0b00001000, 0, 0b00010000, 0,
0b00100000, 0, 0b01000000, 0, 0b10000000}

Definition at line 102 of file lf.c.

102 {
103 0, 0b00000001, 0b00000010, 0, 0b00000100, 0, 0b00001000, 0, 0b00010000, 0,
104 0b00100000, 0, 0b01000000, 0, 0b10000000};

Referenced by scan_file().

◆ lfargc

int lfargc

Definition at line 137 of file lf.c.

Referenced by main().

◆ lfargs

char* lfargs[3]

Definition at line 138 of file lf.c.

Referenced by main().

◆ nthreads

unsigned int nthreads

Definition at line 62 of file lf.c.

Referenced by init_find().

◆ perms_p

char* perms_p

Definition at line 141 of file lf.c.

◆ qhead

TaskNode* qhead = NULL

Definition at line 130 of file lf.c.

Referenced by dequeue_dir(), and enqueue_dir().

◆ qtail

TaskNode* qtail = NULL

Definition at line 131 of file lf.c.

Referenced by dequeue_dir(), and enqueue_dir().

◆ queue_mutex

pthread_mutex_t queue_mutex = PTHREAD_MUTEX_INITIALIZER

Definition at line 132 of file lf.c.

Referenced by dequeue_dir(), enqueue_dir(), and init_find().

◆ shutdown

int shutdown = 0

Definition at line 135 of file lf.c.

Referenced by dequeue_dir(), and init_find().

◆ termination_status

int termination_status = EXIT_SUCCESS

Definition at line 136 of file lf.c.

Referenced by finder(), and scan_file().

◆ tm_info

struct tm tm_info

Definition at line 52 of file lf.c.