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

list files based on matching criteria More...

#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/mman.h>
#include <sys/stat.h>
#include <sys/syscall.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 ARENA_MAX_NODES   (32 * 1024 * 1024)
#define CACHE_LINE_SIZE   64
#define DIR_BUF_SIZE   262144
#define DT_LNK_DIR   14
#define MAX_DEPTH   64
#define MAX_PATH_LEN   _POSIX_PATH_MAX
#define print_file_type(mask, lf_type, dt_type, name)
#define QUEUE_CAPACITY   16384
#define QUEUE_MASK   (QUEUE_CAPACITY - 1)

Typedefs

typedef struct CycleNode CycleNode
typedef struct QueuePayload QueuePayload

Enumerations

enum  TerminationStatus {
  TS_SUCCESS = 0 , TS_ERROR = 1 , TS_MATCH = 2 , TS_MATCH_PLUS_ERROR = 3 ,
  TS_SUCCESS = 0 , TS_ERROR = 1 , TS_MATCH = 2 , TS_MATCH_PLUS_ERROR = 3
}
enum  TerminationStatus {
  TS_SUCCESS = 0 , TS_ERROR = 1 , TS_MATCH = 2 , TS_MATCH_PLUS_ERROR = 3 ,
  TS_SUCCESS = 0 , TS_ERROR = 1 , TS_MATCH = 2 , TS_MATCH_PLUS_ERROR = 3
}

Functions

bool append_output_buffer (LfContext *lf, OutputBuffer *output, const char *path, size_t path_len, bool append_slash)
 Append a path to the output buffer, flushing if necessary.
bool build_full_path (char *dst, size_t dst_size, const char *dir_path, const char *name, size_t *out_len)
 Build a full file path by concatenating a directory path and a file name.
void cycle_arena_destroy (void)
 Destroy the cycle arena and release allocated resources.
void cycle_arena_init (void)
 Initialize the cycle arena for tracking visited directories.
void debug_out (LfContext *lf, int argc, char **argv)
 Output debug information to stderr.
int err_out (LfContext *lf, const char *format,...)
 Thread-safe error output function that writes formatted error messages to the specified error file or stderr.
void * finder (LfContext *lf, QueuePayload *current_node, QueuePayload *child_node)
 Process a directory and its entries, applying filters and enqueuing subdirectories.
void flush_output_buffer (LfContext *lf, OutputBuffer *output)
 Flush the output buffer to stdout in a thread-safe manner.
bool init_lf (LfContext *lf, int argc, char **argv)
 Initialize the file search process.
bool is_dirsys (const char *)
bool is_hidden (const char *)
bool is_link_cycle (dev_t dev, ino_t ino, CycleNode *parent)
int main (int argc, char **argv)
bool mpmc_dequeue (LfContext *lf, QueuePayload *out_node)
 Dequeue a task from the MPMCQueue.
bool mpmc_enqueue (LfContext *lf, const QueuePayload *child_node)
 Enqueue a task into the MPMCQueue.
MPMCQueue * mpmc_queue_init ()
 Initialize the MPMCQueue.
int scan_file (const char *file_spec, const size_t *path_len, LfContext *lf, const unsigned char effective_type, const struct stat *cached_sb, OutputBuffer *output)
 Scan a file or directory and apply filters based on the LfContext.
int sort_lf_output (LfContext *lf, int argc, char **argv)
 Sort the output of the lf command using the system's sort utility.
void * worker (void *arg)
 Worker thread function that processes tasks from the MPMCQueue.

Variables

const char * argp_program_bug_address = "billxwaller@gmail.com"
const char * argp_program_version = CM_VERSION
char * debug_p
const 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.")
char * exec
char * file_types_p
unsigned char const lf_mask [15]
int lfargc
char * lfargs [3]
char * perms_p
TerminationStatus termination_status
struct tm tm_info

Detailed Description

list files based on matching criteria

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

Definition in file lf.c.

Macro Definition Documentation

◆ _GNU_SOURCE

#define _GNU_SOURCE

Definition at line 9 of file lf.c.

◆ ARENA_MAX_NODES

#define ARENA_MAX_NODES   (32 * 1024 * 1024)

Definition at line 43 of file lf.c.

◆ CACHE_LINE_SIZE

#define CACHE_LINE_SIZE   64

Definition at line 40 of file lf.c.

◆ DIR_BUF_SIZE

#define DIR_BUF_SIZE   262144

Definition at line 39 of file lf.c.

◆ DT_LNK_DIR

#define DT_LNK_DIR   14

Definition at line 160 of file lf.c.

◆ MAX_DEPTH

#define MAX_DEPTH   64

Definition at line 38 of file lf.c.

◆ MAX_PATH_LEN

#define MAX_PATH_LEN   _POSIX_PATH_MAX

Definition at line 37 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 92 of file lf.c.

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

◆ QUEUE_CAPACITY

#define QUEUE_CAPACITY   16384

Definition at line 35 of file lf.c.

◆ QUEUE_MASK

#define QUEUE_MASK   (QUEUE_CAPACITY - 1)

Definition at line 36 of file lf.c.

Typedef Documentation

◆ CycleNode

typedef struct CycleNode CycleNode

Definition at line 45 of file lf.c.

◆ QueuePayload

typedef struct QueuePayload QueuePayload

Definition at line 60 of file lf.c.

Enumeration Type Documentation

◆ TerminationStatus [1/2]

Enumerator
TS_SUCCESS 
TS_ERROR 
TS_MATCH 
TS_MATCH_PLUS_ERROR 
TS_SUCCESS 
TS_ERROR 
TS_MATCH 
TS_MATCH_PLUS_ERROR 

Definition at line 77 of file lf.c.

77 {
78 TS_SUCCESS = 0,
79 TS_ERROR = 1,
80 TS_MATCH = 2,
TerminationStatus
Definition lf.c:77
@ TS_SUCCESS
Definition lf.c:78
@ TS_ERROR
Definition lf.c:79
@ TS_MATCH_PLUS_ERROR
Definition lf.c:81
@ TS_MATCH
Definition lf.c:80

◆ TerminationStatus [2/2]

Enumerator
TS_SUCCESS 
TS_ERROR 
TS_MATCH 
TS_MATCH_PLUS_ERROR 
TS_SUCCESS 
TS_ERROR 
TS_MATCH 
TS_MATCH_PLUS_ERROR 

Definition at line 77 of file lf.c.

77 {
78 TS_SUCCESS = 0,
79 TS_ERROR = 1,
80 TS_MATCH = 2,

Function Documentation

◆ append_output_buffer()

bool append_output_buffer ( LfContext * lf,
OutputBuffer * output,
const char * path,
size_t path_len,
bool append_slash )

Append a path to the output buffer, flushing if necessary.

Parameters
lfA pointer to the LfContext struct containing the output mutex.
outputA pointer to the OutputBuffer struct where the path will be appended.
pathThe path string to append to the output buffer.
path_lenThe length of the path string.
append_slashA boolean indicating whether to append a slash after the path.
Returns
true if the path was successfully appended or printed, false if an error occurred (e.g., null pointers).

This function appends a given path to an output buffer. If the buffer is full, it flushes the buffer to stdout. If the path is too long to fit in the buffer, it prints it directly to stdout. The function uses a mutex to ensure thread-safe access to stdout when printing directly.

Definition at line 941 of file lf.c.

942 {
943 if (lf == nullptr || output == nullptr || path == nullptr)
944 return false;
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);
949 if (append_slash)
950 fputc_unlocked('/', stdout);
951 fputc_unlocked('\n', stdout);
952 pthread_mutex_unlock(&lf->output_mutex);
953 return true;
954 }
955 if (output->len + line_len > sizeof(output->data))
956 flush_output_buffer(lf, output);
957 memcpy(output->data + output->len, path, path_len);
958 output->len += path_len;
959 if (append_slash)
960 output->data[output->len++] = '/';
961 output->data[output->len++] = '\n';
962 return true;
963}
void flush_output_buffer(LfContext *, OutputBuffer *)
Flush the output buffer to stdout in a thread-safe manner.
Definition lf.c:921

References flush_output_buffer().

Referenced by scan_file().

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

◆ build_full_path()

bool build_full_path ( char * dst,
size_t dst_size,
const char * dir_path,
const char * name,
size_t * out_len )

Build a full file path by concatenating a directory path and a file name.

Parameters
dstA pointer to the destination buffer where the full path will be stored.
dst_sizeThe size of the destination buffer.
dir_pathThe directory path to be concatenated.
nameThe file name to be concatenated.
out_lenA pointer to a size_t variable where the length of the resulting full path will be stored (optional).
Returns
true if the full path was successfully built, false if an error occurred (e.g., null pointers or insufficient buffer size).

This function constructs a full file path by concatenating the provided directory path and file name. It ensures that there is a single '/' separator between the directory and file name, and it checks for sufficient buffer size before performing the concatenation. If successful, it null-terminates the resulting string and optionally returns its length.

Definition at line 893 of file lf.c.

894 {
895 if (dst == nullptr || dir_path == nullptr || name == nullptr || dst_size == 0)
896 return false;
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)
902 return false;
903 memcpy(dst, dir_path, dir_len);
904 size_t pos = dir_len;
905 if (need_sep)
906 dst[pos++] = '/';
907 memcpy(dst + pos, name, name_len);
908 dst[full_len] = '\0';
909 if (out_len)
910 *out_len = full_len;
911 return true;
912}

Referenced by finder().

Here is the caller graph for this function:

◆ cycle_arena_destroy()

void cycle_arena_destroy ( void )

Destroy the cycle arena and release allocated resources.

This function releases the resources allocated for the global cycle arena structure. It unmaps the virtual memory space reserved for the arena using munmap, allowing the operating system to reclaim the memory. This function should be called when the cycle arena is no longer needed to prevent memory leaks.

Definition at line 851 of file lf.c.

851 {
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);
855 }
856}
struct CycleNode CycleNode
Definition lf.c:45

◆ cycle_arena_init()

void cycle_arena_init ( void )

Initialize the cycle arena for tracking visited directories.

This function initializes a global cycle arena structure that is used to track visited directories during the file search process. It reserves virtual memory space for the arena using mmap, allowing for efficient allocation of CycleNode structures without immediate physical memory allocation. The arena is designed to handle a maximum number of nodes defined by ARENA_MAX_NODES.

Definition at line 828 of file lf.c.

828 {
829 g_cycle_arena.capacity = ARENA_MAX_NODES;
830 size_t total_bytes = g_cycle_arena.capacity * sizeof(CycleNode);
831
832 // Reserve virtual space. The OS marks it as yours but allocates NO physical RAM yet. MAP_ANONYMOUS guarantees the memory block is zero-initialized automatically.
833 g_cycle_arena.nodes = mmap(NULL, total_bytes,
834 PROT_READ | PROT_WRITE,
835 MAP_ANONYMOUS | MAP_PRIVATE,
836 -1, 0);
837
838 if (g_cycle_arena.nodes == MAP_FAILED) {
839 perror(_("mmap failed to reserve tracking arena memory"));
840 exit(EXIT_FAILURE);
841 }
842
843 atomic_init(&g_cycle_arena.index, 0);
844}
#define ARENA_MAX_NODES
Definition lf.c:43
#define _(String)
Definition amort.c:23

Referenced by init_lf().

Here is the caller graph for this function:

◆ debug_out()

void debug_out ( LfContext * lf,
int argc,
char ** argv )

Output debug information to stderr.

Parameters
lfA pointer to the LfContext struct containing the debug settings.
argcThe number of command-line arguments.
argvAn array of command-line argument strings.

This function outputs debug information to stderr if debugging is enabled in the LfContext. It includes a timestamp, user information, IP addresses, command-line arguments, and various configuration settings. The output is formatted for readability and includes details about file types, permissions, regex patterns, and other relevant settings.

Definition at line 583 of file lf.c.

583 {
584 char user_str[100];
585 char ip_str[MAXLEN];
586 int len = 0;
587 int i;
588 bool addspace_before = false;
589 if (lf->debug && (lf->report_config || lf->report_info)) {
590 fprintf(stderr, "%s,%s,%s,", get_local_timestamp(), get_user_str(user_str, 100), get_ip_addresses(ip_str, MAXLEN));
591 for (i = 0; i < argc; i++) {
592 len = len + strlen(argv[i]);
593 if (len > 72) {
594 fprintf(stderr, "\n");
595 len = strlen(argv[i]);
596 addspace_before = false;
597 }
598 if (addspace_before) {
599 fprintf(stderr, " ");
600 len++;
601 }
602 fprintf(stderr, "%s", argv[i]);
603 addspace_before = true;
604 }
605 fprintf(stderr, "\n\n");
606 fprintf(stderr, "%s\n\n", CM_VERSION);
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"));
632 else
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"));
637 print_file_type(lf->include_types, LF_FIFO, DT_FIFO, _("FIFO p-named pipe"));
638 print_file_type(lf->include_types, LF_CHR, DT_CHR, _("CHR c-character device"));
639 print_file_type(lf->include_types, LF_DIR, DT_DIR, _("DIR d-directory"));
640 print_file_type(lf->include_types, LF_BLK, DT_BLK, _("BLK b-block device"));
641 print_file_type(lf->include_types, LF_REG, DT_REG, _("REG f-regular file"));
642 print_file_type(lf->include_types, LF_LNK, DT_LNK, _("LINK l-symbolic link"));
643 print_file_type(lf->include_types, LF_SOCK, DT_SOCK, _("SOCK s-socket"));
644 print_file_type(lf->include_types, LF_UNKNOWN, DT_UNKNOWN, _("UNKNOWN u-unknown"));
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");
649 if (lf->flags & LF_USER)
650 fprintf(stderr, _("User: %s (%ju)\n"), lf->user_name, lf->user_id);
651 if (lf->include_perms) {
652 if (lf->include_perms & LF_IXUSR)
653 fprintf(stderr, _(" %08b Execute\n"), LF_IXUSR);
654 if (lf->include_perms & LF_IWUSR)
655 fprintf(stderr, _(" %08b Write\n"), LF_IWUSR);
656 if (lf->include_perms & LF_IRUSR)
657 fprintf(stderr, _(" %08b Read\n"), LF_IRUSR);
658 if (lf->include_perms & LF_ISUID)
659 fprintf(stderr, _(" %08b SETUID\n"), LF_ISUID);
660 if (lf->include_perms & LF_ISGID)
661 fprintf(stderr, _(" %08b SETGID\n"), LF_ISGID);
662 }
663 fprintf(stderr, "\n");
664 if (lf->flags & LF_REGEX)
665 fprintf(stderr, _("Include regex: %s\n\n"), lf->re);
666 if (lf->flags & LF_EXC_REGEX)
667 fprintf(stderr, _("Exclude regex: %s\n\n"), lf->ere);
668
669 if (lf->after) {
670 char buf[32];
671 format_local_timestamp(lf->after, buf, sizeof(buf));
672 fprintf(stderr, _("Modified after: %s\n\n"), buf);
673 }
674
675 if (lf->before) {
676 char buf[32];
677 format_local_timestamp(lf->before, buf, sizeof(buf));
678 fprintf(stderr, _("Modified before: %s\n\n"), buf);
679 }
680
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;
684 i = 0;
685 while (size >= 1024 && i < 6) {
686 size /= 1024;
687 i++;
688 }
689 char buffer[32];
690 ssnprintf(buffer, 32, "%ld %s", size, units[i]);
691 fprintf(stderr, _("Minimum file size: %s\n\n"), buffer);
692 }
693 if (lf->max_depth)
694 fprintf(stderr, _("Max depth: %d\n\n"), lf->max_depth);
695 if (lf->ignore_case)
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"));
701 if (lf->sort)
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"));
705 }
706 return;
707}
@ LF_EXC_REGEX
Definition cm.h:184
@ LF_ISUID
Definition cm.h:194
@ LF_IRUSR
Definition cm.h:192
@ LF_ISGID
Definition cm.h:193
@ LF_REGEX
Definition cm.h:185
@ LF_USER
Definition cm.h:187
@ LF_IXUSR
Definition cm.h:190
@ LF_IWUSR
Definition cm.h:191
@ LF_FIFO
Definition cm.h:199
@ LF_UNKNOWN
Definition cm.h:206
@ LF_SOCK
Definition cm.h:205
@ LF_BLK
Definition cm.h:202
@ LF_CHR
Definition cm.h:200
#define CM_VERSION
Definition version.h:5
#define MAXLEN
Definition curskeys.c:15
#define print_file_type(mask, lf_type, dt_type, name)
Definition lf.c:92
#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:284
size_t ssnprintf(char *, size_t, const char *,...)
ssnprintf was designed to be a safer alternative to snprintf.
Definition futil.c:390
char * get_ip_addresses(char *, uint)
Retrieves the IP addresses of the local machine and formats them into a string.
Definition futil.c:324
char * format_local_timestamp(time_t, char *, size_t)
Formats a time_t as an ISO 8601 string in local time.
Definition futil.c:273
char * get_user_str(char *, size_t)
Retrieves the current user's name and UID, and formats it into a string.
Definition futil.c:297

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_lf().

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

◆ err_out()

int err_out ( LfContext * lf,
const char * format,
... )

Thread-safe error output function that writes formatted error messages to the specified error file or stderr.

Parameters
lfA pointer to the LfContext struct containing the output mutex and error file information.
formatA printf-style format string for the error message.
...Additional arguments corresponding to the format string.
Returns
The number of characters written, or a negative value if an error occurred.

This function locks the output mutex to ensure that only one thread can write to the error output at a time. It checks if an error file has been specified; if not, it defaults to stderr. If an error file is specified but not yet opened, it attempts to open it in append mode. The function uses vfprintf to write the formatted error message and then unlocks the mutex before returning.

Definition at line 1479 of file lf.c.

1479 {
1480
1481 pthread_mutex_lock(&lf->output_mutex);
1482 va_list args;
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); // line buffering
1489 }
1490 } else {
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));
1497 return TS_ERROR;
1498 }
1499 lf->error_file_open = true;
1500 setvbuf(lf->err_fd, NULL, _IOLBF, 0); // line buffering
1501 }
1502 }
1503 int result = vfprintf(lf->err_fd, format, args);
1504 pthread_mutex_unlock(&lf->output_mutex);
1505 va_end(args);
1506 return result;
1507}

References TS_ERROR.

Referenced by finder().

Here is the caller graph for this function:

◆ finder()

void * finder ( LfContext * lf,
QueuePayload * current_node,
QueuePayload * child_node )

Process a directory and its entries, applying filters and enqueuing subdirectories.

Parameters
lfA pointer to the LfContext struct containing the queue and other context information.
current_nodeA pointer to the QueuePayload struct representing the current directory to process.
Returns
NULL upon completion.

This function opens the specified directory, reads its entries, and processes each entry according to the specified filters (e.g., file types, hidden files, max depth). It uses fstatat to get metadata for each entry and determines the effective type. If an entry is a directory and meets the criteria, it is enqueued for further processing. The function handles errors gracefully, logging them if necessary, and ensures that output is written in a thread-safe manner.

Definition at line 1105 of file lf.c.

1105 {
1106 OutputBuffer output = {{'\0'}, 0};
1107 long nread;
1108 char dir_buf[DIR_BUF_SIZE];
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;
1116 char full_path[MAX_PATH_LEN] = {'\0'};
1117 int rc;
1118 int dir_fd = open(current_node->path, O_RDONLY | O_DIRECTORY);
1119 if (dir_fd == -1) {
1120 if (lf->report_errors) {
1121 err_out(lf, _("OPEN_FAIL,%s,%s\n"), current_node->path, strerror(errno));
1122 }
1123 flush_output_buffer(lf, &output);
1124 return NULL;
1125 }
1126 while (1) {
1127 //--------------------------------------------------------------------
1128 // READ DIRECTORY
1129 //--------------------------------------------------------------------
1130 // Read the directory entries and process each one. We use SYS_getdents
1131 // to iterate over the entries in the directory. For each entry, we
1132 // construct the full path and use fstatat to get the metadata of the
1133 // entry. If the entry is a symbolic link, we check if the user has
1134 // chosen to follow links and get the metadata of the target it points
1135 // to. We then determine the effective type of the entry and apply the
1136 // specified filters (e.g., hidden files, max depth) to decide whether
1137 // to process it further or enqueue it for searching.
1138 nread = syscall(SYS_getdents64, dir_fd, dir_buf, DIR_BUF_SIZE);
1139 if (nread == -1) {
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));
1143 }
1144 break;
1145 }
1146 if (nread == 0)
1147 break;
1148 for (size_t bpos = 0; bpos < (size_t)nread;) {
1149 entry = (struct linux_dirent64 *)(dir_buf + bpos);
1150 bpos += entry->d_reclen;
1151 //--------------------------------------------------------------------
1152 // PROCESS DIRECTORY ENTRIES
1153 //--------------------------------------------------------------------
1154 // Get link's metadata We use fstatat with AT_SYMLINK_NOFOLLOW to get the metadata of the symbolic link itself, rather than the target it points to. This allows us to determine if the entry is a symbolic link and handle it according to the user's options (e.g., whether to follow links or not). If fstatat fails, we log the error (if debugging is enabled) and continue to the next entry without processing this one further.
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')
1161 continue;
1162 if (entry->d_name[1] == '.' && entry->d_name[2] == '\0')
1163 continue;
1164 }
1165 }
1166 if (((lf->follow_links || lf->report_badlinks) &&
1167 (actual_type == DT_LNK || actual_type == DT_DIR)) ||
1168 actual_type == DT_UNKNOWN) {
1169 //--------------------------------------------------------------------
1170 // GET ADVANCED METADATA
1171 //--------------------------------------------------------------------
1172 // We use fstatat with AT_SYMLINK_NOFOLLOW to get the metadata of the symbolic link itself, rather than the target it points to. This allows us to determine if the entry is a symbolic link and handle it according to the user's options (e.g., whether to follow links or not). If fstatat fails, we log the error (if debugging is enabled) and continue to the next entry without processing this one further.
1173 memset(&sb, 0, sizeof(struct stat));
1174 rc = fstatat(dir_fd, entry->d_name, &sb, AT_SYMLINK_NOFOLLOW);
1175 if (rc == -1) {
1176 atomic_fetch_add(&lf->error_count, 1);
1177 if (lf->report_badlinks) {
1178 err_out(lf, _("LSTAT_FAIL,%s,%s\n"), full_path,
1179 strerror(errno));
1180 }
1181 } else {
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;
1188 }
1189 if (S_ISLNK(sb.st_mode)) {
1190 // Determine the real type of the entry. If the entry is a symbolic link, we set actual_type to DT_LNK and then attempt to get the metadata of the target it points to using fstatat without AT_SYMLINK_NOFOLLOW. This allows us to determine the effective type of the entry based on the target's metadata, which is important for deciding how to process it (e.g., whether it's a directory that we should enqueue for further searching). If fstatat fails when trying to get the target's metadata, we log the error (if debugging is enabled) but continue processing the entry based on its symbolic link metadata.
1191 if (lf->follow_links) {
1192 //------------------------------------------------------------
1193 // GET LINK METADATA
1194 //------------------------------------------------------------
1195 char tmp_str[MAXLEN];
1196 rc = fstatat(dir_fd, entry->d_name, &sb, 0);
1197 if (rc == -1) {
1198 atomic_fetch_add(&lf->error_count, 1);
1199 if (lf->report_errors) {
1200 err_out(lf, _("STAT_FAIL,%s,%s\n"), entry->d_name,
1201 strerror(errno));
1202 ssnprintf(tmp_str, MAXLEN - 1, _("FSTATAT_FAIL,%s/%s\n"),
1203 entry->d_name, strerror(errno));
1204 }
1205 } else {
1206 effective_ino = sb.st_ino;
1207 effective_dev = sb.st_dev;
1208 effective_type = (sb.st_mode & S_IFMT) >> 12;
1209 }
1210 } else {
1211 effective_type = DT_REG;
1212 }
1213 }
1214 }
1215 if (effective_type == DT_DIR) {
1216 //--------------------------------------------------------------------
1217 // PROCESS DIRECTORY ENTRY - CREATE CHILD_NODE
1218 //--------------------------------------------------------------------
1219 // We build the full path for the child directory and check if it exceeds the maximum path length. If it does, we log an error and continue to the next entry. If the child directory is within the allowed depth, we create a new QueuePayload for it, copying the current history of dev/inode pairs for cycle detection. We then attempt to enqueue the child directory for further processing. If the queue is full, we handle it by running the finder function inline recursively.
1220 child_ctx = cycle_arena_alloc(effective_dev, effective_ino, current_node->ctx);
1221 child_node->ctx = child_ctx;
1222 if (!build_full_path(child_node->path,
1223 sizeof(child_node->path),
1224 current_node->path,
1225 entry->d_name,
1226 &child_node->path_len)) {
1227 atomic_fetch_add(&lf->error_count, 1);
1228 if (lf->report_errors) {
1229 err_out(lf, _("PATH_TOO_LONG,%s/%s\n"),
1230 child_node->path, entry->d_name);
1231 }
1232 continue;
1233 }
1234 bool cycle_found = false;
1235 // Determine the effective type of the entry. We use the st_mode
1236 // field from the stat struct to determine the file type by
1237 // applying the S_IFMT mask and shifting it to get a value that
1238 // corresponds to the DT_* constants. If the entry is a symbolic
1239 // link and the user has chosen not to follow links, we treat it
1240 // as a directory for the purpose of deciding whether to enqueue
1241 // it for further searching. This allows us to handle symbolic
1242 // links that point to directories in a way that respects the
1243 // user's options while still allowing for traversal of linked
1244 // directories if desired.
1245 if (lf->follow_links && actual_type == DT_LNK) {
1246 // -------------------------------------------------------
1247 // CYCLE_DETECTION
1248 // -------------------------------------------------------
1249 // Check for cycles by comparing the current directory's dev/inode with the history of dev/inode pairs from parent directories. If a match is found, it indicates a cycle and we skip processing this directory.
1250 char target_path[MAX_PATH_LEN] = {'\0'};
1251 if (lf->report_trace || lf->report_badlinks) {
1252 ssize_t len = readlinkat(dir_fd, entry->d_name, target_path, sizeof(target_path) - 1);
1253 if (len != -1)
1254 target_path[len] = '\0';
1255 }
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);
1261 }
1262 continue;
1263 }
1264 child_node->ctx = child_ctx;
1265 if (lf->report_trace) {
1266 err_out(lf, _("---CYCLE_DETECTION---\n"));
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);
1269 }
1270 // is_link_cycle(effective_dev, effective_ino,
1271 // current_node->ctx);
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);
1276 }
1277 if (effective_dev == parent->dev && effective_ino == parent->ino) {
1278 cycle_found = true;
1279 break;
1280 }
1281 parent = parent->parent;
1282 }
1283 if (cycle_found) {
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);
1287 }
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);
1291 continue;
1292 }
1293 }
1294 // --------------------------------------------------------
1295 // BUILD AND ENQUEUE CHILD TASK
1296 // --------------------------------------------------------
1297 if (lf->max_depth != 0 && current_node->depth + 1 >= lf->max_depth)
1298 continue;
1299 child_node->depth = current_node->depth + 1;
1300 if (!cycle_found) {
1301 if (mpmc_enqueue(lf, child_node)) {
1302 atomic_fetch_add_explicit(&lf->q->active_tasks, 1, memory_order_relaxed);
1303 } else {
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) {
1308 err_out(lf, _("MEM_ALLOC_FAIL,%s\n"), strerror(errno));
1309 }
1310 continue;
1311 }
1312 finder(lf, child_node, local_node);
1313 free(local_node);
1314 }
1315 }
1316 // --------------------------------------------------------
1317 // CONDITIONALLY PRINT THE DIRECTORY
1318 // --------------------------------------------------------
1319 if (entry->d_name[0] == '.') {
1320 if (!lf->include_hidden && !lf->hidden_only)
1321 continue;
1322 } else {
1323 if (lf->hidden_only)
1324 continue;
1325 }
1326 if (!lf->only_errors)
1327 scan_file(child_node->path, &child_node->path_len, lf, effective_type, &sb, &output);
1328 } else {
1329 // --------------------------------------------------------
1330 // NOT DIRECTORY - CONDITIONALLY PRINT THE ENTRY
1331 // --------------------------------------------------------
1332 if (entry->d_name[0] == '.') {
1333 if (!lf->include_hidden && !lf->hidden_only)
1334 continue;
1335 } else {
1336 if (lf->hidden_only)
1337 continue;
1338 }
1339 size_t path_len;
1340 if (full_path[0] == '\0') {
1341 if (!build_full_path(full_path,
1342 sizeof(full_path),
1343 current_node->path,
1344 entry->d_name,
1345 &path_len)) {
1346 atomic_fetch_add(&lf->error_count, 1);
1347 if (lf->report_errors) {
1348 err_out(lf, _("PATH_TOO_LONG,%s/%s\n"),
1349 current_node->path, entry->d_name);
1350 }
1351 continue;
1352 }
1353 }
1354 if (!lf->only_errors)
1355 scan_file(full_path, &path_len, lf, effective_type, &sb, &output);
1356 }
1357 }
1358 }
1359 close(dir_fd);
1360 flush_output_buffer(lf, &output);
1361 return NULL;
1362}
void * finder(LfContext *lf, QueuePayload *current_node, QueuePayload *child_node)
Process a directory and its entries, applying filters and enqueuing subdirectories.
Definition lf.c:1105
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.
Definition lf.c:893
int err_out(LfContext *lf, const char *format,...)
Thread-safe error output function that writes formatted error messages to the specified error file or...
Definition lf.c:1479
#define DIR_BUF_SIZE
Definition lf.c:39
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.
Definition lf.c:1388
struct QueuePayload QueuePayload
Definition lf.c:60
bool mpmc_enqueue(LfContext *, const QueuePayload *child_node)
Enqueue a task into the MPMCQueue.
Definition lf.c:992
#define MAX_PATH_LEN
Definition lf.c:37

References build_full_path(), err_out(), finder(), flush_output_buffer(), mpmc_enqueue(), scan_file(), and ssnprintf().

Referenced by finder(), and worker().

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

◆ flush_output_buffer()

void flush_output_buffer ( LfContext * lf,
OutputBuffer * output )

Flush the output buffer to stdout in a thread-safe manner.

Parameters
lfA pointer to the LfContext struct containing the output mutex.
outputA pointer to the OutputBuffer struct containing the data to be flushed.

This function writes the contents of the output buffer to stdout using fwrite_unlocked for efficiency. It uses a mutex to ensure that only one thread can write to stdout at a time, preventing interleaved output from multiple threads. After flushing, it resets the length of the output buffer to zero.

Definition at line 921 of file lf.c.

921 {
922 if (lf == nullptr || output == nullptr || output->len == 0)
923 return;
924 pthread_mutex_lock(&lf->output_mutex);
925 fwrite_unlocked(output->data, 1, output->len, stdout);
926 pthread_mutex_unlock(&lf->output_mutex);
927 output->len = 0;
928}

Referenced by append_output_buffer(), and finder().

Here is the caller graph for this function:

◆ init_lf()

bool init_lf ( LfContext * lf,
int argc,
char ** argv )

Initialize the file search process.

Parameters
lfA pointer to the LfContext struct containing the search settings.
argcThe number of command-line arguments.
argvAn array of command-line argument strings.
Returns
true if initialization is successful, false otherwise.

This function initializes the file search process based on the provided settings in the LfContext struct. It sets up file type inclusion and suppression, compiles regular expressions if specified, and creates a multi-producer, multi-consumer queue for managing directory traversal tasks. It also initializes threads for concurrent processing of directory entries.

suppress file types that aren't included

Definition at line 722 of file lf.c.

722 {
724 if (!lf->include_types)
725 lf->include_types = 0xff;
726 if (lf->include_types)
727 lf->suppress_types = lf->include_types ^ 0xff;
728 // LF_HIDE = 0 - include hidden files,
729 // LF_HIDE = 1 - suppress hidden files
730 lf->include_hidden = !(lf->flags & LF_HIDE);
731 int reti = 0;
732 lf->reg_flags = REG_EXTENDED;
733 if (lf->ignore_case)
734 lf->reg_flags |= REG_ICASE;
735 if (lf->flags & LF_REGEX) {
736 reti = regcomp(&lf->compiled_re, lf->re, lf->reg_flags);
737 if (reti) {
738 fprintf(stderr, _("lf: '%s' Invalid pattern\n"), lf->re);
739 regfree(&lf->compiled_re);
740 return false;
741 }
742 }
743 if (lf->flags & LF_EXC_REGEX) {
744 reti = regcomp(&lf->compiled_ere, lf->ere, lf->reg_flags);
745 if (reti) {
746 fprintf(stderr, _("lf: '%s' Invalid exclude pattern\n"), lf->ere);
747 regfree(&lf->compiled_ere);
748 return false;
749 }
750 }
751 debug_out(lf, argc, argv);
752 //--------------------------------------------------------------------
753 // Create and enqueue the first QueuePayload
755 int rc = 0;
756 struct stat sb;
757 rc = pthread_mutex_init(&lf->output_mutex, NULL);
758 if (rc != 0)
759 perror(_("Mutex initialization failed"));
760 rc = stat(lf->base_path, &sb);
761 if (rc != 0)
762 return false;
763 lf->q = mpmc_queue_init();
764 QueuePayload root_node;
766 if (S_ISDIR(sb.st_mode)) {
767 root_node.path_len = strnz__cpy(root_node.path, lf->base_path, MAX_PATH_LEN - 1);
768 root_node.depth = 0;
769 CycleNode *root_ctx = cycle_arena_alloc(sb.st_dev, sb.st_ino, nullptr);
770 root_node.ctx = root_ctx;
771 if (!mpmc_enqueue(lf, &root_node)) {
772 fprintf(stderr, _("Failed to enqueue initial directory\n"));
773 return false;
774 }
775 atomic_fetch_add_explicit(&lf->q->active_tasks, 1, memory_order_relaxed);
776 //------------------------------------------------------------
777 // INITIALIZE THREADS
778 //------------------------------------------------------------
779 lf->threads = calloc(lf->nthreads, sizeof(pthread_t));
780 if (!lf->threads) {
781 fprintf(stderr, _("Out of memory allocating threads\n"));
782 return false;
783 }
784 for (unsigned int i = 0; i < lf->nthreads; i++) {
785 rc = pthread_create(
786 &lf->threads[i],
787 NULL,
788 worker,
789 lf);
790 if (rc != 0) {
791 fprintf(stderr, _("Error: Unable to create thread %d\n"), rc);
792 lf->q->shut_down = 1;
794 return false;
795 }
796 }
797 //------------------------------------------------------------
798 // END THREADS
799 //------------------------------------------------------------
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);
803 if (rc != 0)
804 perror(_("Mutex destroy failed"));
805 if (lf->flags & LF_REGEX)
806 regfree(&lf->compiled_re);
807 if (lf->flags & LF_EXC_REGEX)
808 regfree(&lf->compiled_ere);
809 return true;
810 } else {
811 fprintf(stderr,
812 _("Warning: Base path '%s' is not a directory. No "
813 "files will be found.\n"),
814 lf->base_path);
816 return false;
817 }
818 if (reti)
819 return false;
820 return true;
821}
@ LF_HIDE
Definition cm.h:182
void * worker(void *arg)
Worker thread function that processes tasks from the MPMCQueue.
Definition lf.c:1072
TerminationStatus termination_status
Definition lf.c:107
void cycle_arena_init(void)
Initialize the cycle arena for tracking visited directories.
Definition lf.c:828
MPMCQueue * mpmc_queue_init()
Initialize the MPMCQueue.
Definition lf.c:971
void debug_out(LfContext *lf, int, char **)
Output debug information to stderr.
Definition lf.c:583
size_t strnz__cpy(char *, const char *, size_t)
safer alternative to strncpy
Definition whence.c:291

References cycle_arena_init(), debug_out(), LF_EXC_REGEX, LF_HIDE, LF_REGEX, mpmc_enqueue(), mpmc_queue_init(), strnz__cpy(), termination_status, TS_ERROR, TS_SUCCESS, and worker().

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 * )

◆ is_hidden()

bool is_hidden ( const char * )

◆ is_link_cycle()

bool is_link_cycle ( dev_t dev,
ino_t ino,
CycleNode * parent )

Definition at line 1366 of file lf.c.

1366 {
1367 const CycleNode *current = parent;
1368 while (current != NULL) {
1369 if (current->ino == ino && current->dev == dev) {
1370 return true; // We hit a cycle!
1371 }
1372 current = current->parent; // Safe to dereference: addresses never change
1373 }
1374 return false;
1375}

◆ main()

int main ( int argc,
char ** argv )

Definition at line 431 of file lf.c.

431 {
432 LfContext *lf = (LfContext *)calloc(1, sizeof(LfContext));
433
434 lf->count = 0;
436 lf->error_count = 0;
437 lf->report_error_count = false;
438 lf->nthreads = 0;
439 lf->ignore_case = false;
440 lf->sort = false;
441 lf->sort_reverse = false;
442 lf->include_hidden = false; // By default, hidden files are suppressed.
443 lf->hidden_only = false;
444 lf->flags |= LF_HIDE; // Turn hide flag on
445 lf->follow_links = false;
446 lf->count = 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;
452
453 char tmp_str[MAX_PATH_LEN];
454 argp_parse(&argp, argc, argv, 0, 0, lf);
455 if (lfargc > 0) {
456 strnz__cpy(tmp_str, lfargs[0], MAXLEN - 1);
457 expand_tilde(tmp_str, MAXLEN - 1);
458 if (is_directory(tmp_str) || is_symlink_to_dir(tmp_str)) {
459 lf->base_path = strdup(tmp_str);
460 } else if (is_valid_regex(lfargs[0])) {
461 lf->re = strdup(lfargs[0]);
462 lf->flags |= LF_REGEX;
463 } else {
464 fprintf(
465 stderr,
466 _("lf: arg1: '%s' is neither a directory nor a valid regex.\n"),
467 lfargs[0]);
468 exit(EXIT_FAILURE);
469 }
470 }
471 if (lfargc > 1) {
472 if (!lf->base_path || lf->base_path[0] == '\0') {
473 strnz__cpy(tmp_str, lfargs[1], MAXLEN - 1);
474 expand_tilde(tmp_str, MAXLEN - 1);
475 if (is_directory(tmp_str) || is_symlink_to_dir(tmp_str))
476 lf->base_path = strdup(tmp_str);
477 }
478 if ((!(lf->flags & LF_REGEX)) && is_valid_regex(lfargs[1])) {
479 lf->re = strdup(lfargs[1]);
480 lf->flags |= LF_REGEX;
481 } else {
482 fprintf(stderr,
483 _("lf: '%s' is neither a directory nor a valid regular expression.\n"),
484 lfargs[1]);
485 exit(EXIT_FAILURE);
486 }
487 }
488 if (lf->base_path == nullptr || lf->base_path[0] == '\0')
489 lf->base_path = strdup(".");
490 unsigned int nprocs = get_nprocs();
491 if (nprocs == 0)
492 nprocs = 1;
493
494 if (lf->nthreads == 0)
495 lf->nthreads = (nprocs > 1) ? (nprocs - 1) : 1;
496 else {
497 if (lf->nthreads < 1)
498 lf->nthreads = 1;
499 if (lf->nthreads > nprocs)
500 lf->nthreads = nprocs;
501 }
503 if (!lf->sort) {
504 init_lf(lf, argc, argv);
505 } else
506 termination_status = sort_lf_output(lf, argc, argv);
507
508 if (lf->count) {
509 size_t count = atomic_load(&lf->file_count);
510 fprintf(stderr, _("Files: %zu\n"), count);
511 }
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);
516 }
517 free(lf->q);
518 free(lf->base_path);
519 free(lf->user_name);
520 free(lf->re);
521 free(lf->ere);
522 free(lf->threads);
523 free(lf);
524 exit(termination_status);
525}
char * lfargs[3]
Definition lf.c:166
int sort_lf_output(LfContext *lf, int, char **)
Sort the output of the lf command using the system's sort utility.
Definition lf.c:532
bool init_lf(LfContext *lf, int, char **)
Initialize the file search process.
Definition lf.c:722
int lfargc
Definition lf.c:165
bool is_directory(const char *)
Checks if the given path is a directory.
Definition futil.c:1381
bool is_valid_regex(const char *)
Checks if the given regular expression pattern is valid.
Definition futil.c:1409
bool expand_tilde(char *, uint)
Replaces "~/" in string with the user's home directory.
Definition futil.c:989
bool is_symlink_to_dir(const char *)
Checks if the given path is a symbolic link to a directory.
Definition futil.c:1394

References expand_tilde(), init_lf(), is_directory(), is_symlink_to_dir(), is_valid_regex(), LF_HIDE, LF_REGEX, lfargc, lfargs, sort_lf_output(), strnz__cpy(), termination_status, TS_ERROR, and TS_SUCCESS.

Here is the call graph for this function:

◆ mpmc_dequeue()

bool mpmc_dequeue ( LfContext * lf,
QueuePayload * out_node )

Dequeue a task from the MPMCQueue.

Parameters
lfA pointer to the LfContext struct containing the queue.
taskA pointer to the QueuePayload struct where the dequeued task will be stored.
Returns
true if a task was successfully dequeued, false if the queue is empty or shut down.

This function attempts to dequeue a task from the MPMCQueue. It uses atomic operations to ensure thread-safe access to the queue's dequeue position and sequence numbers. If the queue is empty, it yields the processor to reduce contention. If the queue has been shut down, it returns false, allowing for graceful termination of worker threads.

Definition at line 1031 of file lf.c.

1031 {
1032 size_t pos = atomic_load_explicit(&lf->q->dequeue_pos, memory_order_relaxed);
1033 while (true) {
1034 if (atomic_load_explicit(&lf->q->shut_down, memory_order_relaxed))
1035 return false;
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);
1038 if (diff == 0) { // Full queue, ready to dequeue
1039 if (atomic_compare_exchange_strong_explicit(&lf->q->dequeue_pos, &pos, pos + 1,
1040 memory_order_relaxed, memory_order_relaxed)) {
1041 break;
1042 }
1043 } else if (diff < 0) { // Queue is temporarily empty
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);
1048 return false;
1049 }
1050 sched_yield();
1051 pos = atomic_load_explicit(&lf->q->dequeue_pos, memory_order_relaxed);
1052 } else {
1053 pos = atomic_load_explicit(&lf->q->dequeue_pos, memory_order_relaxed);
1054 }
1055 }
1056 // memcpy(output_node, &lf->q->nodes[pos & QUEUE_MASK],
1057 // sizeof(QueuePayload));
1058 if (out_node)
1059 *out_node = lf->q->nodes[pos & QUEUE_MASK];
1060 // current_node = &lf->q->nodes[pos & QUEUE_MASK];
1061 atomic_store_explicit(&lf->q->sequence[pos & QUEUE_MASK], pos + QUEUE_CAPACITY, memory_order_release);
1062 return true;
1063}
#define QUEUE_CAPACITY
Definition lf.c:35
#define QUEUE_MASK
Definition lf.c:36

Referenced by worker().

Here is the caller graph for this function:

◆ mpmc_enqueue()

bool mpmc_enqueue ( LfContext * lf,
const QueuePayload * child_node )

Enqueue a task into the MPMCQueue.

Parameters
lfA pointer to the LfContext struct containing the queue.
dirA pointer to the QueuePayload struct representing the task to enqueue.
Returns
true if the task was successfully enqueued, false if the queue is full or an error occurred.

This function attempts to enqueue a task into the MPMCQueue. It uses atomic operations to ensure thread-safe access to the queue's enqueue position and sequence numbers. If the queue is full, it returns false, allowing for potential recursion or other handling by the caller. The function also includes a spin-wait mechanism to reduce contention when multiple threads are trying to enqueue tasks simultaneously.

Definition at line 992 of file lf.c.

992 {
993 size_t pos = atomic_load_explicit(&lf->q->enqueue_pos,
994 memory_order_relaxed);
995 int spin_count = 0;
996 const int SPIN_LIMIT = 16;
997 while (true) {
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;
1000 if (diff == 0) { // Queue is not full, ready to enqueue
1001 if (atomic_compare_exchange_weak_explicit(&lf->q->enqueue_pos, &pos, pos + 1,
1002 memory_order_relaxed, memory_order_relaxed)) {
1003 break;
1004 }
1005 } else if (diff < 0) // Queue is full
1006 return false; // trigger recursion
1007 else {
1008 pos++;
1009 // pos = atomic_load_explicit(&lf->q->enqueue_pos,
1010 // memory_order_relaxed);
1011 }
1012 if (++spin_count > SPIN_LIMIT)
1013 return false;
1014#if defined(__x86_64__)
1015 __builtin_ia32_pause();
1016#endif
1017 }
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);
1020 return true;
1021}

Referenced by finder(), and init_lf().

Here is the caller graph for this function:

◆ mpmc_queue_init()

MPMCQueue * mpmc_queue_init ( )

Initialize the MPMCQueue.

Parameters
qA pointer to the MPMCQueue struct representing the queue.

This function initializes the MPMCQueue by setting up the sequence numbers for each node in the queue and initializing the enqueue and dequeue indexes. It ensures that the queue is ready for concurrent access by multiple producer and consumer threads.

Definition at line 971 of file lf.c.

971 {
972 MPMCQueue *q = calloc(1, sizeof(MPMCQueue));
973 if (q == nullptr)
974 return nullptr;
975 for (size_t i = 0; i < QUEUE_CAPACITY; i++)
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);
981 return q;
982}

Referenced by init_lf().

Here is the caller graph for this function:

◆ scan_file()

int scan_file ( const char * file_spec,
const size_t * path_len,
LfContext * lf,
const unsigned char effective_type,
const struct stat * cached_sb,
OutputBuffer * output )

Scan a file or directory and apply filters based on the LfContext.

Parameters
file_specThe full path of the file or directory to scan.
lfA pointer to the LfContext struct containing the search filters and options.
effective_typeThe effective type of the file or directory (e.g., DT_REG, DT_DIR).
cached_sbA pointer to a stat struct containing cached metadata for the file, or nullptr if not available.
outputA pointer to the OutputBuffer struct where matching paths will be appended.
Returns
true if the file or directory passes all filters and is processed, false otherwise.

This function checks various conditions based on the search filters specified in the LfContext. It evaluates whether the file or directory should be included in the output based on type, regex matching, ownership, permissions, modification time, and size. If all conditions are met, it appends the path to the output buffer. The function also handles caching of stat information to avoid redundant system calls when possible.

Definition at line 1388 of file lf.c.

1390 {
1391 bool stat_cached = cached_sb != nullptr;
1392 struct stat sb = {};
1393
1394 while (1) {
1395 if (lf->suppress_types & lf_mask[effective_type])
1396 break;
1397 // Exclude non-matching files
1398 if (lf->flags & LF_REGEX) {
1399 int reti =
1400 regexec(&lf->compiled_re, file_spec, 0, NULL, 0);
1401 if (reti == REG_NOMATCH)
1402 break;
1404 }
1405 // Exclude matching files
1406 if (lf->flags & LF_EXC_REGEX) {
1407 int reti =
1408 regexec(&lf->compiled_ere, file_spec, 0, NULL, 0);
1409 if (reti == 0) {
1411 break;
1412 }
1413 }
1414 // Exclude files not owned by specified user
1415 if (lf->flags & LF_USER) {
1416 if (!stat_cached && stat(file_spec, &sb) == 0)
1417 stat_cached = true;
1418 if (!stat_cached || sb.st_uid != lf->user_id)
1419 break;
1420 }
1421 if (lf->include_perms) {
1422 if (!stat_cached && stat(file_spec, &sb) == 0)
1423 stat_cached = true;
1424 if (!stat_cached)
1425 break;
1426 if ((lf->include_perms & LF_IRUSR) && !(sb.st_mode & S_IRUSR))
1427 break;
1428 else if ((lf->include_perms & LF_IWUSR) && !(sb.st_mode & S_IWUSR))
1429 break;
1430 else if ((lf->include_perms & LF_IXUSR) && !(sb.st_mode & S_IXUSR))
1431 break;
1432 else if ((lf->include_perms & LF_ISUID) && !(sb.st_mode & S_ISUID))
1433 break;
1434 else if ((lf->include_perms & LF_ISGID) && !(sb.st_mode & S_ISGID))
1435 break;
1436 }
1437 if (lf->before) { // Last file modification
1438 if (!stat_cached && stat(file_spec, &sb) == 0)
1439 stat_cached = true;
1440 if (stat_cached && sb.st_mtime > lf->before)
1441 break;
1442 }
1443 if (lf->after) { // Last file modification
1444 if (!stat_cached && stat(file_spec, &sb) == 0)
1445 stat_cached = true;
1446 if (stat_cached && sb.st_mtime < lf->after)
1447 break;
1448 }
1449 if (lf->file_size_min) {
1450 if (!stat_cached && stat(file_spec, &sb) == 0)
1451 stat_cached = true;
1452 if (stat_cached && sb.st_size < lf->file_size_min)
1453 break;
1454 }
1455 if (lf->only_errors)
1456 break;
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] == '/') {
1462 display_path += 2;
1463 display_len -= 2;
1464 }
1465 append_output_buffer(lf, output, display_path, display_len,
1466 effective_type == DT_DIR);
1467 }
1468 break;
1469 }
1470 return true;
1471}
bool append_output_buffer(LfContext *, OutputBuffer *, const char *, size_t, bool)
Append a path to the output buffer, flushing if necessary.
Definition lf.c:941
unsigned char const lf_mask[15]
Definition lf.c:161

References append_output_buffer(), LF_EXC_REGEX, LF_IRUSR, LF_ISGID, LF_ISUID, LF_IWUSR, LF_IXUSR, lf_mask, LF_REGEX, LF_USER, termination_status, and TS_MATCH.

Referenced by finder().

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

◆ sort_lf_output()

int sort_lf_output ( LfContext * lf,
int argc,
char ** argv )

Sort the output of the lf command using the system's sort utility.

Parameters
lfA pointer to the LfContext struct containing the search settings.
argcThe number of command-line arguments.
argvAn array of command-line argument strings.

This function sets up a pipeline to sort the output of the lf command. It creates a pipe, forks a child process to execute the sort command, and redirects the standard input and output streams accordingly. The sort command is executed with optional flags for parallel processing and buffer size, as well as a reverse sort option if specified in the LfContext. The function waits for the child process to complete before returning.

Definition at line 532 of file lf.c.

532 {
533 // char tmp_str[MAXLEN];
534 char *eargv[MAXARGS];
535 int eargc = 0;
536 eargv[eargc++] = strdup("sort");
537 // snprintf(tmp_str, MAXLEN - 1, "--parallel=%d", lf->nthreads);
538 // eargv[eargc++] = strdup(tmp_str);
539 // snprintf(tmp_str, MAXLEN - 1, "--buffer-size=%s", lf->sbuffer);
540 // eargv[eargc++] = strdup(tmp_str);
541 if (lf->sort_reverse)
542 eargv[eargc++] = strdup("-r");
543 eargv[eargc] = nullptr;
544
545 setenv("LC_ALL", "C", 1); // Set locale to C for consistent sorting
546 int wstatus;
547
548 int fds[2];
549 pipe(fds); // Create the pipes
550
551 pid_t pid1 = fork();
552 if (pid1 == 0) { // Child process
553 // Child's STDIN will be redirected to the read end of the pipe, so that the sort process will read the output of the finder from the pipe.
554 // Close the write end of the pipe in the child
555 close(fds[1]);
556 dup2(fds[0], STDIN_FILENO); // Clone child's read pipe to STDIN_FILENO
557 close(fds[0]); // Close the original read end of the pipe
558 execvp(eargv[0], eargv); // Execute the sort command
559 fprintf(stderr, _("Failed to execute sort: %s\n"), strerror(errno));
560 return TS_ERROR;
561 }
562 // fclose(stdout);
563 dup2(fds[1], STDOUT_FILENO); // Clone write pipe to STDOUT_FILENO
564 stdout = fdopen(STDOUT_FILENO, "w"); // Reopen STDOUT as a stream
565 setvbuf(stdout, NULL, _IOLBF, 0); // line buffering
566 init_lf(lf, argc, argv);
567 fclose(stdout);
568 close(fds[1]);
569 wait(&wstatus);
570 for (int i = 0; i < eargc; i++)
571 free(eargv[i]);
572 return 0;
573}
#define MAXARGS
Definition cm.h:64
char * eargv[MAXARGS]
Definition futil.c:51
int eargc
Definition futil.c:50

References init_lf(), and TS_ERROR.

Referenced by main().

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

◆ worker()

void * worker ( void * arg)

Worker thread function that processes tasks from the MPMCQueue.

Parameters
argA pointer to the LfContext struct containing the queue and other context information.
Returns
NULL upon completion.

This function runs in a loop, dequeuing tasks from the MPMCQueue and processing them using the finder function. It decrements the active task count after processing each task. If the active task count reaches zero, it sets the shut_down flag to true, signaling other threads to terminate. The function exits when there are no more tasks to process or when a shutdown is detected.

Definition at line 1072 of file lf.c.

1072 {
1073 LfContext *lf = (LfContext *)arg;
1074 // current_node is a pointer to the current task being processed, while child_node is a local variable used to hold the next task to be enqueued. The worker thread continuously dequeues tasks from the queue and processes them using the finder function. If the finder function returns NULL, indicating that there are no more tasks to process, the active task count is decremented. If the active task count reaches zero, the shut_down flag is set to true, signaling other threads to terminate.
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"));
1079 return NULL;
1080 }
1081 while (true) {
1082 if (mpmc_dequeue(lf, current_node) == true) {
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);
1087 }
1088 }
1089 } else
1090 break;
1091 }
1092 free(child_node);
1093 free(current_node);
1094 return NULL;
1095}
bool mpmc_dequeue(LfContext *lf, QueuePayload *output_node)
Dequeue a task from the MPMCQueue.
Definition lf.c:1031

References finder(), and mpmc_dequeue().

Referenced by init_lf().

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

Variable Documentation

◆ argp_program_bug_address

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

Definition at line 101 of file lf.c.

◆ argp_program_version

const char* argp_program_version = CM_VERSION

Definition at line 100 of file lf.c.

◆ debug_p

char * debug_p

Definition at line 170 of file lf.c.

◆ doc

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.")

Definition at line 102 of file lf.c.

◆ exec

char * exec

Definition at line 167 of file lf.c.

◆ file_types_p

char * file_types_p

Definition at line 168 of file lf.c.

◆ lf_mask

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

Definition at line 161 of file lf.c.

161 {
162 0, 0b00000001, 0b00000010, 0, 0b00000100, 0, 0b00001000, 0, 0b00010000, 0,
163 0b00100000, 0, 0b01000000, 0, 0b10000000};

Referenced by scan_file().

◆ lfargc

int lfargc

Definition at line 165 of file lf.c.

Referenced by main().

◆ lfargs

char * lfargs

Definition at line 166 of file lf.c.

Referenced by main().

◆ perms_p

char * perms_p

Definition at line 169 of file lf.c.

◆ termination_status

TerminationStatus termination_status

Definition at line 107 of file lf.c.

Referenced by init_lf(), main(), and scan_file().

◆ tm_info

struct tm tm_info

Definition at line 99 of file lf.c.