ftwin 0.8.10
ft_config.c
1#include <apr_getopt.h>
2#include <apr_strings.h>
3#include <apr_user.h>
4#include <pcre.h>
5#include <unistd.h>
6#include <grp.h>
7
8#include "config.h"
9#include "debug.h"
10#include "ft_config.h"
11#include "ft_system.h"
12#include "ft_types.h"
13#include "human_size.h"
14#include "key_hash.h"
15
16/* Forward declarations for key functions defined in ftwin.c */
17const void *ft_fsize_get_key(const void *opaque);
18const void *ft_gids_get_key(const void *opaque);
19
20int ft_file_cmp(const void *param1, const void *param2)
21{
22 const ft_file_t *file1 = (const ft_file_t *) param1;
23 const ft_file_t *file2 = (const ft_file_t *) param2;
24
25 if (file1->size < file2->size) {
26 return -1;
27 }
28 if (file2->size < file1->size) {
29 return 1;
30 }
31
32 return 0;
33}
34
35/* Default ignore patterns */
36static const char *const default_ignores[] = {
37 /* VCS */
38 ".git/", ".hg/", ".svn/",
39 /* Build Artifacts */
40 "build/", "dist/", "out/", "target/", "bin/",
41 "*.o", "*.class", "*.pyc", "*.pyo",
42 /* Dependency Caches */
43 "node_modules/", "vendor/", ".venv/",
44 /* OS & Editor Artifacts */
45 ".DS_Store", "Thumbs.db", "*.swp", "*~", ".idea/", ".vscode/",
46 NULL
47};
48
49static apr_status_t ft_pcre_free_cleanup(void *pcre_space)
50{
51 pcre_free(pcre_space);
52 return APR_SUCCESS;
53}
54
55static pcre *ft_pcre_compile(const char *regex, int caseless, apr_pool_t *pool)
56{
57 const char *errptr = NULL;
58 int erroffset = 0;
59 int options = PCRE_DOLLAR_ENDONLY | PCRE_DOTALL;
60 pcre *result = NULL;
61
62 if (caseless) {
63 options |= PCRE_CASELESS;
64 }
65
66 result = pcre_compile(regex, options, &errptr, &erroffset, NULL);
67 if (NULL == result) {
68 DEBUG_ERR("can't parse %s at [%.*s] for -e / --regex-ignore-file: %s", regex, erroffset, regex, errptr);
69 }
70 else {
71 apr_pool_cleanup_register(pool, result, ft_pcre_free_cleanup, apr_pool_cleanup_null);
72 }
73
74
75 return result;
76}
77
78static const int MAX_GIDS = 256;
79
80static apr_status_t fill_gids_ht(const char *username, napr_hash_t *gids, apr_pool_t *pool)
81{
82 gid_t list[MAX_GIDS];
83 apr_uint32_t hash_value = 0;
84 int nb_gid = 0;
85
86 memset(list, 0, sizeof(list));
87 nb_gid = getgroups((int) (sizeof(list) / sizeof(gid_t)), list);
88 if (nb_gid < 0) {
89 DEBUG_ERR("error calling getgroups()");
90 return APR_EGENERAL;
91 }
92 /*
93 * According to getgroups manpage:
94 * It is unspecified whether the effective group ID of the calling process
95 * is included in the returned list. (Thus, an application should also
96 * call getegid(2) and add or remove the resulting value.)
97 */
98 if (nb_gid < (sizeof(list) / sizeof(gid_t))) {
99 list[nb_gid] = getegid();
100 nb_gid++;
101 }
102
103 for (int idx = 0; idx < nb_gid; idx++) {
104 ft_gid_t *gid = napr_hash_search(gids, &(list[idx]), sizeof(gid_t), &hash_value);
105 if (NULL == gid) {
106 gid = apr_palloc(pool, sizeof(struct ft_gid_t));
107 gid->val = list[idx];
108 napr_hash_set(gids, gid, hash_value);
109 }
110 }
111
112 return APR_SUCCESS;
113}
114
115static void ft_hash_add_ignore_list(napr_hash_t *hash, const char *file_list)
116{
117 const char *filename = NULL;
118 const char *end = NULL;
119 apr_uint32_t hash_value = 0;
120 apr_pool_t *pool = NULL;
121 char *tmp = NULL;
122
123 pool = napr_hash_pool_get(hash);
124 filename = file_list;
125 do {
126 end = strchr(filename, ',');
127 if (NULL != end) {
128 tmp = apr_pstrndup(pool, filename, end - filename);
129 }
130 else {
131 tmp = apr_pstrdup(pool, filename);
132 }
133 napr_hash_search(hash, tmp, strlen(tmp), &hash_value);
134 napr_hash_set(hash, tmp, hash_value);
135
136 if (NULL != end) {
137 filename = end + 1;
138 }
139 } while ((NULL != end) && ('\0' != *filename));
140}
141
142static void ft_load_defaults(ft_conf_t *conf)
143{
144 for (int idx = 0; default_ignores[idx] != NULL; idx++) {
145 ft_ignore_add_pattern_str(conf->global_ignores, default_ignores[idx]);
146 }
147}
148
149static void version(void)
150{
151 (void) fprintf(stdout, PACKAGE_STRING "\n");
152 (void) fprintf(stdout, "Copyright (C) 2007 François Pesce\n");
153 (void) fprintf(stdout, "Licensed under the Apache License, Version 2.0 (the \"License\");\n");
154 (void) fprintf(stdout, "you may not use this file except in compliance with the License.\n");
155 (void) fprintf(stdout, "You may obtain a copy of the License at\n");
156 (void) fprintf(stdout, "\n");
157 (void) fprintf(stdout, "\thttp://www.apache.org/licenses/LICENSE-2.0\n");
158 (void) fprintf(stdout, "\n");
159 (void) fprintf(stdout, "Unless required by applicable law or agreed to in writing, software\n");
160 (void) fprintf(stdout, "distributed under the License is distributed on an \"AS IS\" BASIS,\n");
161 (void) fprintf(stdout, "WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n");
162 (void) fprintf(stdout, "See the License for the specific language governing permissions and\n");
163 (void) fprintf(stdout, "limitations under the License.\n\n");
164 (void) fprintf(stdout, "Report bugs to " PACKAGE_BUGREPORT "\n");
165}
166
167static void usage(const char *name, const apr_getopt_option_t *opt_option)
168{
169 (void) fprintf(stdout, PACKAGE_STRING "\n");
170 (void) fprintf(stdout, "Usage: %s [OPTION]... [FILES or DIRECTORIES]...\n", name);
171 (void) fprintf(stdout, "Find identical files passed as parameter or recursively found in directories.\n");
172 (void) fprintf(stdout, "\n");
173 (void) fprintf(stdout, "Mandatory arguments to long options are mandatory for short options too.\n");
174 (void) fprintf(stdout, "\n");
175
176 for (int idx = 0; NULL != opt_option[idx].name; idx++) {
177 (void) fprintf(stdout, "-%c,\t--%s\t%s\n", opt_option[idx].optch, opt_option[idx].name, opt_option[idx].description);
178 }
179}
180
181static void print_usage_and_exit(const char *name, const apr_getopt_option_t *opt_option, const char *error_msg,
182 const char *arg)
183{
184 if (error_msg) {
185 (void) fprintf(stderr, "Error: %s %s\n\n", error_msg, arg);
186 }
187 usage(name, opt_option);
188 exit(EXIT_FAILURE);
189}
190
191static const int HASH_STR_BUCKET_SIZE = 32;
192static const int HASH_STR_MAX_ENTRIES = 8;
193static const int HASH_SIZE_BUCKET_SIZE = 4096;
194static const int HASH_SIZE_MAX_ENTRIES = 8;
195static const apr_off_t EXCESS_SIZE_DEFAULT = (50LL * 1024 * 1024);
196
197ft_conf_t *ft_config_create(apr_pool_t *pool)
198{
199 apr_uint32_t hash_value = 0;
200 ft_conf_t *conf = apr_pcalloc(pool, sizeof(ft_conf_t));
201
202 conf->pool = pool;
203 conf->heap = napr_heap_make(pool, ft_file_cmp);
204 conf->ig_files = napr_hash_str_make(pool, HASH_STR_BUCKET_SIZE, HASH_STR_MAX_ENTRIES);
205 conf->sizes = napr_hash_make(pool, HASH_SIZE_BUCKET_SIZE, HASH_SIZE_MAX_ENTRIES, ft_fsize_get_key,
206 ft_fsize_get_key_len, apr_off_t_key_cmp, apr_off_t_key_hash);
207 conf->gids = napr_hash_make(pool, HASH_SIZE_BUCKET_SIZE, HASH_SIZE_MAX_ENTRIES, ft_gids_get_key, ft_gid_get_key_len,
208 gid_t_key_cmp, gid_t_key_hash);
209
210 /* To avoid endless loop, ignore looping directory ;) */
211 napr_hash_search(conf->ig_files, ".", 1, &hash_value);
212 napr_hash_set(conf->ig_files, ".", hash_value);
213 napr_hash_search(conf->ig_files, "..", 2, &hash_value);
214 napr_hash_set(conf->ig_files, "..", hash_value);
215
216 conf->ig_regex = NULL;
217 conf->wl_regex = NULL;
218 conf->ar_regex = NULL;
219 conf->p_path = NULL;
220 conf->p_path_len = 0;
221 conf->minsize = 0;
222 conf->maxsize = 0;
223 conf->sep = '\n';
224 conf->excess_size = (apr_off_t) EXCESS_SIZE_DEFAULT;
225 conf->num_threads = ft_get_cpu_cores(); /* Default to number of CPU cores */
226 conf->respect_gitignore = 1; /* Respect .gitignore by default */
227 conf->global_ignores = ft_ignore_context_create(pool, NULL, "/"); /* Initialize global ignores */
228 ft_load_defaults(conf); /* Load default ignore patterns */
229 conf->mask = OPTION_RECSD;
230 conf->threshold = PUZZLE_CVEC_SIMILARITY_LOWER_THRESHOLD;
231
232 return conf;
233}
234
235static const double DEFAULT_THRESHOLD = 0.5;
236
237static const apr_getopt_option_t opt_option[] = {
238 {"hidden", 'a', FALSE, "do not ignore hidden files."},
239 {"case-unsensitive", 'c', FALSE, "this option applies to regex match."},
240 {"display-size", 'd', FALSE, "\tdisplay size before duplicates (human-readable)."},
241 {"dry-run", 'n', FALSE, "\tonly print the operations that would be done."},
242 {"regex-ignore-file", 'e', TRUE, "filenames that match this are ignored."},
243 {"follow-symlink", 'f', FALSE, "follow symbolic links."},
244 {"help", 'h', FALSE, "\t\tdisplay usage."},
245 {"image-cmp", 'I', FALSE, "\twill run ftwin in image cmp mode (using libpuzzle)."},
246 {"image-threshold", 'T', TRUE,
247 "will change the image similarity threshold\n\t\t\t\t (default is [1], accepted [2/3/4/5])."},
248 {"ignore-list", 'i', TRUE, "\tcomma-separated list of file names to ignore."},
249#if HAVE_JANSSON
250 {"json", 'J', FALSE, "\t\toutput results in machine-readable JSON format."},
251#endif
252 {"minimal-length", 'm', TRUE, "minimum size of file to process."},
253 {"max-size", 'M', TRUE, "maximum size of file to process."},
254 {"optimize-memory", 'o', FALSE, "reduce memory usage, but increase process time."},
255 {"priority-path", 'p', TRUE, "\tfile in this path are displayed first when\n\t\t\t\tduplicates are reported."},
256 {"recurse-subdir", 'r', FALSE, "recurse subdirectories (default: on)."},
257 {"no-recurse", 'R', FALSE, "do not recurse in subdirectories."},
258 {"separator", 's', TRUE, "\tseparator character between twins, default: \\n."},
259 {"tar-cmp", 't', FALSE, "\twill process files archived in .tar default: off."},
260 {"threads", 'j', TRUE, "\tnumber of threads for parallel hashing (default: CPU cores)."},
261 {"verbose", 'v', FALSE, "\tdisplay a progress bar."},
262 {"version", 'V', FALSE, "\tdisplay version."},
263 {"whitelist-regex-file", 'w', TRUE, "filenames that doesn't match this are ignored."},
264 {"excessive-size", 'x', TRUE, "excessive size of file that switch off mmap use."},
265 {NULL, 0, 0, NULL}, /* end (a.k.a. sentinel) */
266};
267
275{
279};
280
281/* Forward declarations for helper functions */
282
286typedef struct
287{
290 int value;
292
293static void handle_flag_option(int option, ft_conf_t *conf);
294static void handle_string_option(int option, const char *optarg, ft_conf_t *conf, struct regex_options *opts);
295static void handle_numeric_option(int option, const char *optarg, ft_conf_t *conf, const char *name,
296 const apr_getopt_option_t *opt_option);
297static void handle_special_option(int option, const char *optarg, ft_conf_t *conf, struct regex_options *opts,
298 const char *name, const apr_getopt_option_t *opt_option);
299
300static void process_options(int option, const char *optarg, ft_conf_t *conf, struct regex_options *opts, const char *name)
301{
302 switch (option) {
303 /* Simple Flags */
304 case 'a':
305 case 'c':
306 case 'd':
307 case 'n':
308 case 'f':
309 case 'o':
310 case 'r':
311 case 'R':
312 case 'v':
313 handle_flag_option(option, conf);
314 break;
315
316 /* String Arguments */
317 case 'e':
318 case 'i':
319 case 'p':
320 case 's':
321 case 'w':
322 handle_string_option(option, optarg, conf, opts);
323 break;
324
325 /* Numeric Arguments */
326 case 'j':
327 case 'm':
328 case 'M':
329 case 'x':
330 handle_numeric_option(option, optarg, conf, name, opt_option);
331 break;
332
333 /* Special/Complex Cases */
334 case 'h':
335 case 'V':
336 case 'I':
337 case 'T':
338 case 'J':
339 case 't':
340 handle_special_option(option, optarg, conf, opts, name, opt_option);
341 break;
342
343 default:
344 /* Should not happen. */
345 break;
346 }
347}
348
349static const flag_option_mapping_t flag_mappings[] = {
350 {'a', OPTION_SHOW_HIDDEN, 1},
351 {'c', OPTION_ICASE, 1},
352 {'d', OPTION_SIZED, 1},
353 {'n', OPTION_DRY_RUN, 1},
354 {'f', OPTION_FSYML, 1},
355 {'o', OPTION_OPMEM, 1},
356 {'r', OPTION_RECSD, 1},
357 {'R', OPTION_RECSD, 0}
358};
359
360static void handle_flag_option(int option, ft_conf_t *conf)
361{
362 for (size_t idx = 0; idx < sizeof(flag_mappings) / sizeof(flag_mappings[0]); ++idx) {
363 if (flag_mappings[idx].option_char == option) {
364 set_option(&conf->mask, flag_mappings[idx].option_flag, flag_mappings[idx].value);
365 return;
366 }
367 }
368
369 if (option == 'v') {
370 /* The verbose flag is a special case: it should only be set if JSON output is not enabled. */
371 if (!is_option_set(conf->mask, OPTION_JSON)) {
372 set_option(&conf->mask, OPTION_VERBO, 1);
373 }
374 }
375}
376
377static void handle_string_option(int option, const char *optarg, ft_conf_t *conf, struct regex_options *opts)
378{
379 switch (option) {
380 case 'e':
381 *(opts->ignore_regex) = apr_pstrdup(conf->pool, optarg);
382 break;
383 case 'i':
384 ft_hash_add_ignore_list(conf->ig_files, optarg);
385 break;
386 case 'p':
387 conf->p_path = apr_pstrdup(conf->pool, optarg);
388 conf->p_path_len = strlen(conf->p_path);
389 break;
390 case 's':
391 conf->sep = *optarg;
392 break;
393 case 'w':
394 *(opts->whitelist_regex) = apr_pstrdup(conf->pool, optarg);
395 break;
396 default:
397 /* Should not happen. */
398 break;
399 }
400}
401
402static void handle_numeric_option(int option, const char *optarg, ft_conf_t *conf, const char *name,
403 const apr_getopt_option_t *opt_option)
404{
405 switch (option) {
406 case 'j':{
407 char *endptr = NULL;
408 long threads = strtol(optarg, &endptr, BASE_TEN);
409 if (*endptr != '\0' || threads < 1 || threads > MAX_THREADS) {
410 print_usage_and_exit(name, opt_option, "Invalid number of threads (must be 1-256):", optarg);
411 }
412 conf->num_threads = (unsigned int) threads;
413 break;
414 }
415 case 'm':
416 conf->minsize = parse_human_size(optarg);
417 if (conf->minsize < 0) {
418 print_usage_and_exit(name, opt_option, "Invalid size for --minimal-length:", optarg);
419 }
420 break;
421 case 'M':
422 conf->maxsize = parse_human_size(optarg);
423 if (conf->maxsize < 0) {
424 print_usage_and_exit(name, opt_option, "Invalid size for --max-size:", optarg);
425 }
426 break;
427 case 'x':
428 conf->excess_size = (apr_off_t) strtoul(optarg, NULL, BASE_TEN);
429 if (ULONG_MAX == conf->minsize) {
430 print_usage_and_exit(name, opt_option, "can't parse for -x / --excessive-size", optarg);
431 }
432 break;
433 default:
434 /* Should not happen. */
435 break;
436 }
437}
438
445static void handle_image_options(int option, const char *optarg, ft_conf_t *conf, char **wregex, const char *name,
446 const apr_getopt_option_t *opt_option)
447{
448 switch (option) {
449 case 'I':
450 set_option(&conf->mask, OPTION_ICASE, 1);
451 set_option(&conf->mask, OPTION_PUZZL, 1);
452 *wregex = apr_pstrdup(conf->pool, ".*\\.(gif|png|jpe?g)$");
453 break;
454 case 'T':
455 switch (*optarg) {
456 case '1':
457 conf->threshold = PUZZLE_CVEC_SIMILARITY_LOWER_THRESHOLD;
458 break;
459 case '2':
460 conf->threshold = PUZZLE_CVEC_SIMILARITY_LOW_THRESHOLD;
461 break;
462 case '3':
463 conf->threshold = DEFAULT_THRESHOLD;
464 break;
465 case '4':
466 conf->threshold = PUZZLE_CVEC_SIMILARITY_THRESHOLD;
467 break;
468 case '5':
469 conf->threshold = PUZZLE_CVEC_SIMILARITY_HIGH_THRESHOLD;
470 break;
471 default:
472 print_usage_and_exit(name, opt_option, "invalid threshold:", optarg);
473 }
474 break;
475 default:
476 /* Should not be reached */
477 break;
478 }
479}
480
481static void handle_special_option(int option, const char *optarg, ft_conf_t *conf, struct regex_options *opts,
482 const char *name, const apr_getopt_option_t *opt_option)
483{
484 switch (option) {
485 case 'h':
486 usage(name, opt_option);
487 exit(0);
488 case 'V':
489 version();
490 exit(0);
491 case 'I':
492 case 'T':
493 handle_image_options(option, optarg, conf, opts->whitelist_regex, name, opt_option);
494 break;
495#if HAVE_JANSSON
496 case 'J':
497 set_option(&conf->mask, OPTION_JSON, 1);
498 if (is_option_set(conf->mask, OPTION_VERBO)) {
499 (void) fprintf(stderr, "Warning: Verbose mode disabled for JSON output.\n");
500 set_option(&conf->mask, OPTION_VERBO, 0);
501 }
502 break;
503#endif
504 case 't':
505 set_option(&conf->mask, OPTION_UNTAR, 1);
506 *(opts->archive_regex) = apr_pstrdup(conf->pool, ".*\\.(tar\\.gz|tgz|tar\\.bz2|tbz2|tar\\.xz|txz|zip|rar|7z|tar)$");
507 break;
508 default:
509 /* Should not happen. */
510 break;
511 }
512}
513
514apr_status_t ft_config_parse_args(ft_conf_t *conf, int argc, const char **argv, int *first_arg_index)
515{
516 char errbuf[ERROR_BUFFER_SIZE];
517 char *regex_str = NULL;
518 char *wregex_str = NULL;
519 char *arregex_str = NULL;
520 struct regex_options opts = { &regex_str, &wregex_str, &arregex_str };
521 apr_getopt_t *opt_state = NULL;
522 const char *optarg = NULL;
523 int option = 0;
524 apr_status_t status = APR_SUCCESS;
525
526 memset(errbuf, 0, sizeof(errbuf));
527 status = apr_getopt_init(&opt_state, conf->pool, argc, argv);
528 if (APR_SUCCESS != status) {
529 DEBUG_ERR("error calling apr_getopt_init: %s", apr_strerror(status, errbuf, ERROR_BUFFER_SIZE));
530 return status;
531 }
532
533 while (APR_SUCCESS == (status = apr_getopt_long(opt_state, opt_option, &option, &optarg))) {
534 process_options(option, optarg, conf, &opts, argv[0]);
535 }
536
537 status = apr_uid_current(&(conf->userid), &(conf->groupid), conf->pool);
538 if (APR_SUCCESS != status) {
539 DEBUG_ERR("error calling apr_uid_current: %s", apr_strerror(status, errbuf, ERROR_BUFFER_SIZE));
540 return status;
541 }
542
543 status = apr_uid_name_get(&(conf->username), conf->userid, conf->pool);
544 if (APR_SUCCESS != status) {
545 DEBUG_ERR("error calling apr_uid_name_get: %s", apr_strerror(status, errbuf, ERROR_BUFFER_SIZE));
546 return status;
547 }
548
549 status = fill_gids_ht(conf->username, conf->gids, conf->pool);
550 if (APR_SUCCESS != status) {
551 DEBUG_ERR("error calling fill_gids_ht: %s", apr_strerror(status, errbuf, ERROR_BUFFER_SIZE));
552 return status;
553 }
554
555 if (NULL != regex_str) {
556 conf->ig_regex = ft_pcre_compile(regex_str, is_option_set(conf->mask, OPTION_ICASE), conf->pool);
557 if (NULL == conf->ig_regex) {
558 return APR_EGENERAL;
559 }
560 }
561
562 if (NULL != wregex_str) {
563 conf->wl_regex = ft_pcre_compile(wregex_str, is_option_set(conf->mask, OPTION_ICASE), conf->pool);
564 if (NULL == conf->wl_regex) {
565 return APR_EGENERAL;
566 }
567 }
568
569 if (NULL != arregex_str) {
570 conf->ar_regex = ft_pcre_compile(arregex_str, is_option_set(conf->mask, OPTION_ICASE), conf->pool);
571 if (NULL == conf->ar_regex) {
572 return APR_EGENERAL;
573 }
574 }
575
576 /* Return the index of the first non-option argument */
577 if (first_arg_index != NULL) {
578 *first_arg_index = opt_state->ind;
579 }
580
581 return APR_SUCCESS;
582}
UTIL debug output macros.
#define DEBUG_ERR(str, arg...)
Display error message at the level error.
Definition debug.h:31
apr_status_t ft_ignore_add_pattern_str(ft_ignore_context_t *ctx, const char *pattern_str)
Adds a single pattern string to a context.
Definition ft_ignore.c:197
ft_ignore_context_t * ft_ignore_context_create(apr_pool_t *pool, ft_ignore_context_t *parent, const char *base_dir)
Creates a new ignore context.
Definition ft_ignore.c:184
unsigned int ft_get_cpu_cores(void)
Get the number of available CPU cores on the current system.
Definition ft_system.c:33
System-related utility functions.
apr_off_t parse_human_size(const char *size_str)
Parses a human-readable size string (e.g., "10M", "2.5G") into bytes.
Definition human_size.c:32
Utilities for parsing and formatting human-readable file sizes.
apr_pool_t * napr_hash_pool_get(const napr_hash_t *thehash)
Get a pointer to the pool from which the hash table was allocated.
Definition napr_hash.c:275
napr_hash_t * napr_hash_str_make(apr_pool_t *pool, apr_size_t nel, apr_size_t ffactor)
Create a hash table optimized for storing C strings as keys.
Definition napr_hash.c:95
apr_status_t napr_hash_set(napr_hash_t *hash, void *data, apr_uint32_t hash_value)
Inserts or updates an item in the hash table.
Definition napr_hash.c:242
void * napr_hash_search(napr_hash_t *hash, const void *key, apr_size_t key_len, apr_uint32_t *hash_value)
Searches the hash table for an item.
Definition napr_hash.c:145
napr_hash_t * napr_hash_make(apr_pool_t *pool, apr_size_t nel, apr_size_t ffactor, get_key_callback_fn_t get_key, get_key_len_callback_fn_t get_key_len, key_cmp_callback_fn_t key_cmp, hash_callback_fn_t hash)
Create a hash table with custom key handling and hashing functions.
Definition napr_hash.c:100
napr_heap_t * napr_heap_make(apr_pool_t *pool, napr_heap_cmp_callback_fn_t *cmp)
Creates a new heap.
Definition napr_heap.c:48
Maps a command-line option character to its corresponding flag and value.
Definition ft_config.c:287
int value
The value to set (1 for on, 0 for off).
Definition ft_config.c:290
int option_char
The single-character option, e.g., 'a'.
Definition ft_config.c:288
int option_flag
The flag to set, e.g., OPTION_SHOW_HIDDEN.
Definition ft_config.c:289
Main configuration structure for the ftwin application.
Definition ft_config.h:94
A structure to hold pointers to the various regex string options.
Definition ft_config.c:275
char ** ignore_regex
Pointer to the ignore regex string.
Definition ft_config.c:276
char ** archive_regex
Pointer to the archive regex string.
Definition ft_config.c:278
char ** whitelist_regex
Pointer to the whitelist regex string.
Definition ft_config.c:277