C-Menu 0.2.9
A User Interface Toolkit
Loading...
Searching...
No Matches
futil.c
Go to the documentation of this file.
1/** @file futil.c
2 @brief General utility functions
3 @author Bill Waller
4 Copyright (c) 2025
5 MIT License
6 billxwaller@gmail.com
7 @date 2026-02-09 */
8
9/** @defgroup utility_functions Utility functions
10 @brief string manipulation, file handling, and error reporting.
11 @details These functions provide common operations such as trimming strings,
12 converting case, safely copying and concatenating strings, verifying file and
13 directory access, and locating files in the system PATH. They are designed to
14 be robust and handle edge cases gracefully, making them useful for a wide
15 range of applications.
16 */
17
18#include <cm.h>
19#include <ctype.h>
20#include <dirent.h>
21#include <errno.h>
22#include <fcntl.h>
23#include <grp.h>
24#include <pwd.h>
25#include <regex.h>
26#include <stdbool.h>
27#include <stdio.h>
28#include <stdlib.h>
29#include <string.h>
30#include <sys/stat.h>
31#include <sys/types.h>
32#include <termios.h>
33#include <unistd.h>
34#include <wait.h>
35
36#define LF_LNK 1
37#define LF_DIR 2
38#define LF_REG 4
39
43
44bool lf_find(const char *, const char *, const char *, int, int);
45bool lf_process(const char *, regex_t *, regex_t *, int, int, int);
46size_t strip_ansi(char *, char *);
47int a_toi(char *, bool *);
48bool chrep(char *, char, char);
49size_t trim(char *);
50size_t rtrim(char *);
51bool stripz_quotes(char *);
52bool strip_quotes(char *);
53bool str_to_bool(const char *);
54int str_to_args(char **, char *, int);
55double str_to_double(char *);
56bool str_to_lower(char *);
57bool str_to_upper(char *);
58size_t strz(char *);
59size_t strnz(char *, size_t);
60size_t strnlf(char *, size_t);
61bool str_subc(char *, char *, char, char *, int);
62char *rep_substring(const char *, const char *, const char *);
63bool normalize_file_spec(char *);
64bool file_spec_path(char *, char *);
65bool file_spec_name(char *, char *);
66bool verify_file(char *, int);
67bool verify_dir(char *, int);
68bool locate_file_in_path(char *, char *);
69size_t canonicalize_file_spec(char *);
70bool is_directory(const char *);
71bool is_valid_regex(const char *);
72size_t ssnprintf(char *, size_t, const char *, ...);
73size_t strnz__cpy(char *, const char *, size_t);
74size_t strnz__cat(char *, const char *, size_t);
75size_t string_cpy(String *, const String *);
76size_t string_cat(String *, const String *);
77size_t string_ncat(String *, const String *, size_t);
78size_t string_ncpy(String *, const String *, size_t);
79String to_string(const char *);
80String mk_string(size_t);
81String free_string(String);
82
83/** Global variables for error reporting */
85typedef struct {
86 char fn[MAXLEN];
87 char em0[MAXLEN];
88 char em1[MAXLEN];
89 char em2[MAXLEN];
90 char em3[MAXLEN];
91} error_info_t;
92typedef struct {
93 char *src_name;
94 int src_line;
95} error_source_t;
96error_info_t error_info;
97error_source_t error_source;
99/** @brief Trims trailing spaces from string s in place.
100 @param s - string to trim
101 @returns length of trimmed string */
102size_t rtrim(char *s) {
103 if (s == nullptr || *s == '\0')
104 return 0;
105 char *p = s;
106 char *d = s;
107 while (*p != '\0')
108 *d++ = *p++;
109 while (*(d - 1) == ' ' && d > s)
110 d--;
111 *d = '\0';
112 return (size_t)(d - s);
113}
114/** @brief Trims leading and trailing spaces from string s in place.
115 @ingroup utility_functions
116 @param s - string to trim
117 @returns length of trimmed string */
118size_t trim(char *s) {
119 if (s == nullptr || *s == '\0')
120 return 0;
121 char *p = s;
122 char *d = s;
123 while (*p == ' ')
124 p++;
125 while (*p != '\0')
126 *d++ = *p++;
127 while (*(d - 1) == ' ' && d > s)
128 d--;
129 *d = '\0';
130 return (size_t)(d - s);
131}
132/** @brief ssnprintf was designed to be a safer alternative to snprintf.
133 @ingroup utility_functions
134 @details It ensures that the buffer is not overflowed by taking the buffer
135 size as a parameter and using vsnprintf internally. It also returns the
136 number of characters that would have been written if enough space had been
137 available, allowing the caller to detect truncation. This function is
138 particularly useful in situations where the formatted string may exceed the
139 buffer size, as it prevents buffer overflows and provides a way to handle
140 such cases gracefully.
141 @param buf - buffer to receive formatted string
142 @param buf_size - size of buffer
143 @param format - printf-style format string
144 @param ... - arguments
145 @returns number of characters that would have been written if enough space
146 had been available */
147size_t ssnprintf(char *buf, size_t buf_size, const char *format, ...) {
148 size_t n;
149 va_list args;
150
151 va_start(args, format);
152 n = vsnprintf(buf, buf_size, format, args);
153 va_end(args);
154
155 return n;
156}
157/** @brief Converts a string into an array of argument strings.
158 @ingroup utility_functions
159 @note Handles quoted strings and escaped quotes, preserving text inside
160 quotes as individual arguments. It has been in service for many years without
161 problems.
162 @param argv - array of pointers to arguments
163 @param arg_str - string containing arguments
164 @param max_args - maximum number of arguments to parse
165 @returns argc, a count of allocated vectors in argv
166 @note the caller is responsible for deallocating the strings in argv */
167int str_to_args(char **argv, char *arg_str, int max_args) {
168 if (arg_str == nullptr || *arg_str == '\0')
169 return 0;
170 int argc = 0;
171 char *p = arg_str;
172 char tmp_str[MAXLEN];
173 int in_quotes = 0;
174 char *d = tmp_str;
175
176 while (*p != '\0' && argc < max_args) {
177 while (isspace((unsigned char)*p))
178 p++;
179 if (*p == '\0')
180 break;
181 if (*p == '"') {
182 in_quotes = 1;
183 p++;
184 }
185 while (*p != '\0') {
186 if (in_quotes) {
187 if (*p == '\\' && *(p + 1) == '"') {
188 *d++ = '"';
189 p += 2;
190 } else if (*p == '"') {
191 *d++ = '\0';
192 p++;
193 in_quotes = 0;
194 break;
195 } else
196 *d++ = *p++;
197 } else {
198 if (isspace((unsigned char)*p)) {
199 *d++ = '\0';
200 p++;
201 break;
202 } else
203 *d++ = *p++;
204 }
205 }
206 *d = '\0';
207 d = tmp_str;
208 argv[argc++] = strdup(tmp_str);
209 }
210 argv[argc] = nullptr;
211 return argc;
212}
213/** @brief Deallocates memory allocated for argument strings in argv.
214 @ingroup utility_functions
215 @param argc - count of allocated vectors in argv
216 @param argv - array of pointers to arguments
217 @note the caller must ensure that argc accurately reflects the number of
218 allocated strings in argv, and that argv is not null. After calling this
219 function, the pointers in argv will be set to nullptr to prevent dangling
220 pointers. */
221void destroy_argv(int argc, char **argv) {
222 for (int i = 0; i < argc; i++) {
223 if (argv[i] != nullptr) {
224 free(argv[i]);
225 argv[i] = nullptr;
226 }
227 }
228}
229/** @brief Converts a string to lowercase.
230 @ingroup utility_functions
231 @param s - string to convert
232 @returns true if successful, false if s is nullptr or empty */
233bool str_to_lower(char *s) {
234 if (s == nullptr || *s == '\0')
235 return false;
236 while (*s != '\0') {
237 if (*s >= 'A' && *s <= 'Z')
238 *s = *s + 'a' - 'A';
239 s++;
240 }
241 return true;
242}
243/** @brief Converts a string to uppercase.
244 @ingroup utility_functions
245 @param s - string to convert
246 @returns true if successful, false if s is nullptr or empty */
247bool str_to_upper(char *s) {
248 if (s == nullptr || *s == '\0')
249 return false;
250 while (*s != '\0') {
251 if (*s >= 'a' && *s <= 'z')
252 *s = *s + 'A' - 'a';
253 s++;
254 }
255 return true;
256}
257/** @brief safer alternative to strncpy
258 @ingroup utility_functions
259 @details copies string s to d, ensuring that the total length of d does not
260 exceed max_len, and that the resulting string is null-terminated. It also
261 treats newline and carriage return characters as string terminators,
262 preventing them from being included in the result. This is particularly
263 useful when copying user input or file data, where embedded newlines could
264 cause issues.
265 @param d - destination string
266 @param s - source string
267 @param max_len - maximum length to copy
268 @returns length of resulting string */
269size_t strnz__cpy(char *d, const char *s, size_t max_len) {
270 char *e;
271 size_t len = 0;
272 if (s == nullptr || d == nullptr || max_len == 0) {
273 if (d != nullptr && max_len > 0)
274 *d = '\0';
275 return 0;
276 }
277 e = d + max_len;
278 while (*s != '\0' && *s != '\n' && *s != '\r' && d < e) {
279 *d++ = *s++;
280 len++;
281 }
282 *d = '\0';
283 return len;
284}
285/** @brief safer alternative to strncat
286 @ingroup utility_functions
287 @note It appends string s to d, ensuring that the total length of d does not
288 exceed max_len, and that the resulting string is null-terminated. It also
289 treats newline and carriage return characters as string terminators,
290 preventing them from being included in the result. This is particularly
291 useful when concatenating user input or file data, where embedded newlines
292 could cause issues.
293 @param d - destination string
294 @param s - source string
295 @param max_len - maximum length to copy
296 @returns length of resulting string
297 */
298size_t strnz__cat(char *d, const char *s, size_t max_len) {
299 char *e;
300 size_t len = 0;
301 if (s == nullptr || d == nullptr || max_len == 0) {
302 if (d != nullptr && max_len > 0)
303 *d = '\0';
304 return 0;
305 }
306 e = d + max_len;
307 while (*d != '\0' && *d != '\n' && *d != '\r' && d < e) {
308 d++;
309 len++;
310 }
311 while (*s != '\0' && *s != '\n' && *s != '\r' && d < e) {
312 *d++ = *s++;
313 len++;
314 }
315 *d = '\0';
316 return len;
317}
318/** @brief Terminates string at new line or carriage return
319 @ingroup utility_functions
320 @param s string to terminate
321 */
322size_t strz(char *s) {
323 size_t l = 0;
324 if (s == nullptr || *s == '\0')
325 return 0;
326 while (*s != '\0' && *s != '\n' && *s != '\r') {
327 s++;
328 l++;
329 }
330 *s = '\0';
331 return l;
332}
333/** @brief terminates string at New Line, Carriage Return, or max_len
334 @ingroup utility_functions
335 @note The use case is to ensure that strings read from files or user input
336 do not contain embedded newlines or carriage returns.
337 @param s string to terminate
338 @param max_len - maximum length to scan
339 @returns length of resulting string */
340size_t strnz(char *s, size_t max_len) {
341 char *e;
342 size_t len = 0;
343 if (s == nullptr || *s == '\0' || max_len == 0)
344 return 0;
345 e = s + max_len;
346 while (*s != '\0' && *s != '\n' && *s != '\r' && s < e) {
347 s++;
348 len++;
349 }
350 *s = '\0';
351 return (len);
352}
353/** @brief terminates string with line feed
354 @ingroup utility_functions
355 @param s string to terminate
356 @param max_len maximum length to scan
357 @returns length of resulting string */
358size_t strnlf(char *s, size_t max_len) {
359 char *e;
360 size_t len = 0;
361 if (s == nullptr || *s == '\0' || max_len == 0)
362 return 0;
363 e = s + max_len;
364 while (*s != '\0' && *s != '\n' && *s != '\r' && s < e) {
365 s++;
366 len++;
367 }
368 *s++ = '\n';
369 len++;
370 *s = '\0';
371 return (len);
372}
373/** @brief Allocates memory for and duplicates string s up to length l or until
374 line feed or carriage return
375 @ingroup utility_functions
376 @param s - string to duplicate
377 @param l - maximum length to copy
378 @returns pointer to allocated memory */
379char *strnz_dup(char *s, size_t l) {
380 char *p, *rs, *e;
381 size_t m;
382 if (s == nullptr || *s == '\0' || l == 0)
383 return nullptr;
384 for (p = s, m = 1; *p != '\0'; p++, m++)
385 ;
386 rs = p = (char *)malloc(m);
387 if (rs != nullptr) {
388 e = rs + l;
389 while (*s != '\0' && *s != '\n' && *s != '\r' && p < e)
390 *p++ = *s++;
391 *p = '\0';
392 }
393 return rs;
394}
395/** @brief Replaces "ReplaceChr" in "s" with "Withstr" in "d" won't copy more
396 than "l" bytes to "d" Replaces all occurrences of a character in a string
397 with another string, copying the result to a destination buffer.
398 @ingroup utility_functions
399 @param d - destination string
400 @param s - source string
401 @param ReplaceChr - character to replace
402 @param Withstr - string to insert
403 @param l - maximum length to copy
404 @returns true if successful, false if any parameter is invalid
405 @details This function ensures that the total length of the resulting string
406 does not exceed the specified limit, and that the result is null-terminated.
407 This function is useful for simple string substitutions where you want to
408 replace a single character with a longer string, such as replacing spaces
409 with underscores or tabs with spaces.
410 @note The caller must ensure that "d" has enough space to receive the
411 result, and that "l" is sufficient to hold the result. This function does not
412 perform any bounds checking on "d" or "Withstr", so it is the caller's
413 responsibility to ensure that they are valid and that "l" is appropriate for
414 the operation. */
415bool str_subc(char *d, char *s, char ReplaceChr, char *Withstr, int l) {
416 char *e;
417 if (s == nullptr || d == nullptr || Withstr == nullptr || l == 0) {
418 if (d != nullptr && l > 0)
419 *d = '\0';
420 return false;
421 }
422 e = d + l;
423 while (*s != '\0' && d < e) {
424 if (*s == ReplaceChr) {
425 while (*Withstr != '\0' && d < e)
426 *d++ = *Withstr++;
427 s++;
428 } else
429 *d++ = *s++;
430 }
431 *d = '\0';
432 return true;
433}
434/** @brief Fills string s with character c n
435 @ingroup utility_functions
436 @param s - string to fill
437 @param c - character to fill with
438 @param n - number of characters to fill
439 @returns true if successful, false if s is nullptr or n is non-positive */
440bool strnfill(char *s, char c, int n) {
441 if (s == nullptr || n <= 0)
442 return false;
443 char *e;
444 e = s + n;
445 while (s < e)
446 *s++ = c;
447 *s = '\0';
448 return true;
449}
450/** @brief removes leading and trailing double quotes if present
451 @ingroup utility_functions
452 @param s - string to strip quotes from
453 @returns true if successful, false if s is nullptr or empty */
454bool strip_quotes(char *s) {
455 if (s == nullptr)
456 return false;
457 int l = strlen(s);
458 if (l > 1 && s[l - 1] == '\"') {
459 memmove(s, s + 1, l - 2);
460 s[l - 2] = '\0';
461 }
462 return true;
463}
464/** @brief removes leading and trailing double quotes if present
465 @ingroup utility_functions
466 @param s - string to strip quotes from
467 @returns true if quotes were removed
468 @note Same as STRIP_QUOTES but returns true if quotes were removed */
469bool stripz_quotes(char *s) {
470 if (s == nullptr || strlen(s) < 2)
471 return false;
472 int l = strlen(s);
473 if (l > 1 && s[0] == '\"' && s[l - 1] == '\"') {
474 memmove(s, s + 1, l - 2);
475 s[l - 2] = '\0';
476 return true;
477 }
478 return false;
479}
480/** @brief Replaces all occurrences of old_chr in s with new_chr in place.
481 @ingroup utility_functions
482 @param s - string to modify
483 @param old_chr - character to replace
484 @param new_chr - character to insert
485 @returns true if successful or false if string s is null */
486bool chrep(char *s, char old_chr, char new_chr) {
487 if (s == nullptr)
488 return false;
489 while (*s != '\0') {
490 if (*s == old_chr)
491 *s = new_chr;
492 s++;
493 }
494 return true;
495}
496/** @brief a safer alternative to atoi() for converting ASCII strings to
497 integers.
498 @ingroup utility_functions
499 @param s is the input string
500 @param a_toi_error is a pointer to a boolean that will be set to true if an
501 error occurs during conversion, or false if the conversion is successful.
502 @note accepts positive integers only.
503 @note sets a_toi_error to (-1) on error
504 @returns converted integer value, or -1 if an error occurs */
505int a_toi(char *s, bool *a_toi_error) {
506 int rc = -1;
507 *a_toi_error = false;
508 errno = 0;
509 if (s && *s != 0)
510 rc = (int)strtol(s, nullptr, 10);
511 if (rc < 0 || errno) {
512 rc = -1;
513 *a_toi_error = true;
514 }
515 return rc;
516}
517/** @brief Strips ANSI SGR escape sequences (ending in 'm') from string s to d
518 @ingroup utility_functions
519 @param d Destination string
520 @param s Source string
521 @returns Length of stripped string
522 @code
523 char dest[1024];
524 char src[] = "\033[31mThis is red text\033[0m
525 size_t len = strip_ansi(dest, src);
526 Result: dest = "This is red text", len = 17
527 @example stripansi.c
528 @endcode
529 @note Only handles SGR sequences ending in 'm' or 'K'
530 @note Skips non-ASCII characters
531 @note The caller must ensure that d has enough space to hold the
532 stripped string
533 @note This function does not allocate memory; it assumes d is
534 pre-allocated
535 @note This function processes the entire string until the null
536 terminator
537 @note This function does not modify the source string s */
538size_t strip_ansi(char *d, char *s) {
539 size_t l = 0;
540 while (*s) {
541 if (*s == '\033') {
542 while (*s && *s != 'm' && *s != 'K')
543 s++;
544 if (*s == 'm' || *s == 'K')
545 s++;
546 continue;
547 } else {
548 if ((unsigned char)*s <= 127) {
549 *d++ = *s++;
550 l++;
551 } else
552 s++;
553 }
554 }
555 *d = '\0';
556 return l;
557}
558/** @brief replace backslashes with forward lashes
559 @ingroup utility_functions
560 @param fs - file specification to normalize
561 @returns true if successful, false if fs is nullptr or empty */
562bool normalize_file_spec(char *fs) {
563 if (fs == nullptr || *fs == '\0')
564 return false;
565 while (*fs != '\0') {
566 if (*fs == '\\')
567 *fs = '/';
568 fs++;
569 }
570 return true;
571}
572/** @brief extracts the path component of a file specification
573 @ingroup utility_functions
574 @param fp - path component to return
575 @param fs - full file specification
576 @returns true if successful
577 @note The caller is responsible for ensuring that "fp" has enough space to
578 receive the result. */
579bool file_spec_path(char *fp, char *fs) {
580 if (fs == nullptr || *fs == '\0' || fp == nullptr) {
581 if (fp != nullptr)
582 *fp = '\0';
583 return false;
584 }
585 char *d, *l, *s;
586 s = fp;
587 d = fs;
588 l = nullptr;
589 while (*s != '\0') {
590 if (*s == '/')
591 l = d;
592 *d++ = *s++;
593 }
594 if (l == nullptr)
595 *fp = '\0'; // no slash, so no path
596 else
597 *l = '\0';
598 return true;
599}
600/** @brief extracts the file name component of a file specification
601 @ingroup utility_functions
602 @param fn - name component to return
603 @param fs - full file specification
604 @note The caller is responsible for ensuring that "fn" has enough space to
605 receive the result. */
606bool file_spec_name(char *fn, char *fs) {
607 if (fs == nullptr || *fs == '\0' || fn == nullptr) {
608 if (fn != nullptr)
609 *fn = '\0';
610 return false;
611 }
612 char *d, *l, *s;
613 l = nullptr;
614 s = fs;
615 while (*s != '\0') {
616 if (*s == '/')
617 l = s;
618 s++;
619 }
620 if (l == nullptr)
621 s = fs;
622 else
623 s = ++l;
624 d = fn;
625 while (*s != '\0')
626 *d++ = *s++;
627 *d = '\0';
628 return true;
629}
630/** @brief converts string to double
631 @ingroup utility_functions
632 @param s - string to convert
633 @returns converted double value, or 0.0 if s is nullptr, empty, or invalid
634 @deprecated If the string is invalid, this function returns 0.0, with no
635 indication of error.
636 @note The caller must ensure that the string is a valid representation
637 of a double before calling this function. */
638double str_to_double(char *s) {
639 char *e;
640 double d;
641
642 if (!s || !*s)
643 return false;
644 d = strtod(s, &e);
645 return d;
646}
647/** @brief Converts String to boolean true or false
648 @param s - string to convert
649 @returns boolean true or false */
650bool str_to_bool(const char *s) {
651 if (s == nullptr || *s == '\0')
652 return false;
653 switch (s[0]) {
654 case 't':
655 case 'T':
656 case 'y':
657 case 'Y':
658 case '1':
659 return true;
660 case 'o':
661 case 'O':
662 switch (s[1]) {
663 case 'n':
664 case 'N':
665 return true;
666 default:
667 break;
668 }
669 default:
670 break;
671 }
672 return false;
673}
674/** @brief Replace Leading Tilde With Home Directory
675 @ingroup utility_functions
676 @param path - path to expand
677 @param path_maxlen - maximum length of path
678 @note The caller is responsible for ensuring that "path" has enough space
679 to receive the result, and that "path_maxlen" is sufficient to hold the
680 result. This function does not perform any bounds checking on "path", so it
681 is the caller's responsibility to ensure that it is valid and that
682 "path_maxlen" is appropriate for the operation.
683 @returns true if successful
684 */
685bool expand_tilde(char *path, int path_maxlen) {
686 if (path == nullptr || *path == '\0' || path_maxlen == 0)
687 return false;
688 char *e;
689 char ts[MAXLEN];
690 char *tp;
691
692 if (!path || !*path || !path_maxlen)
693 return false;
694 tp = path;
695 if (*tp == '~') {
696 tp++;
697 while (*tp == '/') {
698 tp++;
699 }
700 e = getenv("HOME");
701 if (e) {
702 strnz__cpy(ts, e, path_maxlen - 1);
703 strnz__cat(ts, "/", path_maxlen - 1);
704 strnz__cat(ts, tp, path_maxlen - 1);
705 strnz__cpy(path, ts, path_maxlen - 1);
706 }
707 }
708 return true;
709}
710/** @brief Trims trailing spaces and slashes from directory path in place.
711 @ingroup utility_functions
712 @param dir - directory path to trim
713 @returns true if successful */
714bool trim_path(char *dir) {
715 if (!dir)
716 return false;
717 char *p;
718
719 if (!dir || !*dir)
720 return false;
721 p = dir;
722 while (*p++ != '\0') {
723 if (*p == ' ' || *p == '\t' || *p == '\n') {
724 *p = '\0';
725 break;
726 }
727 }
728 --p;
729 while (--p > dir && *p == '/') {
730 if (*(p - 1) != '~')
731 *p = '\0';
732 }
733 return true;
734}
735/** @brief trims the file extension from "filename" and copies the result to
736 "buf"
737 @ingroup utility_functions
738 @param buf - buffer to receive result
739 @param filename - filename to trim
740 @note The caller is responsible for ensuring that "buf" has enough space to
741 receive the result. */
742bool trim_ext(char *buf, char *filename) {
743 if (!filename || !*filename || !buf)
744 return false;
745 char *s = filename;
746 char *d = buf;
747 *d = '\0';
748 while (*s)
749 s++;
750 while (filename < --s) {
751 if (*s == '.') {
752 break;
753 }
754 }
755 if (*s != '.') {
756 while (*filename)
757 *d++ = *filename++;
758 } else {
759 while (filename < s) {
760 *d++ = *filename++;
761 }
762 }
763 *d = '\0';
764 if (d == buf)
765 return false;
766 return true;
767}
768/** @brief Returns the base name of a file specification.
769 @ingroup utility_functions
770 @param buf - buffer to receive result
771 @param path - file specification
772 @returns true if successful
773 @note The caller is responsible for ensuring that "buf" has enough space to
774 receive the result.
776bool base_name(char *buf, char *path) {
777 if (!path || !*path || !buf)
778 return false;
779 char *s = path;
780 char *d = buf;
781 *d = '\0';
782 while (*s) {
783 if (*s == '/' || *s == '\\') {
784 d = buf;
785 } else {
786 *d++ = *s;
787 }
788 s++;
789 }
790 *d = '\0';
791 if (d == buf)
792 return false;
793 return true;
794}
795/** @brief Returns the directory name of a file specification.
796 @ingroup utility_functions
797 @param buf - buffer to receive result
798 @param path - file specification
799 @returns true if successful
800 @note The caller is responsible for ensuring that "buf" has enough space to
801 receive the result. */
802bool dir_name(char *buf, char *path) {
803 if (!path || !*path || !buf)
804 return false;
805 char tmp_str[MAXLEN];
806 strnz__cpy(tmp_str, path, MAXLEN);
807 char *s = tmp_str;
808 while (*s++)
809 ;
810 while (tmp_str < --s) {
811 if (*s == '/' || *s == '\\') {
812 *s = '\0';
813 break;
814 }
815 }
816 while (tmp_str < --s && (*s == '/' || *s == '\\'))
817 *s = '\0';
818 char *d = buf;
819 *d = '\0';
820 s = tmp_str;
821 while (*s) {
822 *d++ = *s++;
823 }
824 *d = '\0';
825 if (d == buf)
826 return false;
827 return true;
828}
829/** @brief Verifies that the directory specified by "spec" exists and is
830 accessible with the permissions specified by "imode".
831 @ingroup utility_functions
832 @param spec - directory specification
833 @param imode - access mode
834 F_OK - existence
835 R_OK - read
836 W_OK - Write
837 X_OK - Execute
838 S_WCOK - Write or Create
839 S_QUIET - Suppress Error Messages
840 @note S_WCOK and S_QUIET are stripped before calling faccessat
841 @returns true if successful */
842bool verify_dir(char *spec, int imode) {
843 if (spec == nullptr || *spec == '\0')
844 return false;
846 struct stat sb;
847 errno = 0;
848 src_line = 0;
849 int mode = imode & ~(S_WCOK | S_QUIET);
850 if (faccessat(AT_FDCWD, spec, mode, AT_EACCESS) != 0) {
851 src_line = __LINE__ - 2;
852 src_name = __FILE__;
853 strnz__cpy(fn, "faccessat", MAXLEN - 1);
854 } else {
855 if (fstatat(AT_FDCWD, spec, &sb, 0) != 0) {
856 src_line = __LINE__ - 1;
857 src_name = __FILE__;
858 strnz__cpy(fn, "fstatat", MAXLEN - 1);
859 } else {
860 if ((sb.st_mode & S_IFMT) != S_IFDIR) {
861 src_line = __LINE__ - 1;
862 src_name = __FILE__;
863 strnz__cpy(fn, "verify_file", MAXLEN - 1);
864 strnz__cpy(em2, "Not a regular file.", MAXLEN - 1);
865 }
866 }
867 }
868 if (src_line != 0) {
869 if (!(mode & S_QUIET)) {
870 ssnprintf(em0, MAXLEN - 1, "%s failed in %s at line %d", fn,
872 strnz__cpy(em1, spec, MAXLEN - 1);
873 strnz__cpy(em3, "Check the file", MAXLEN - 1);
875 }
876 return false;
877 }
878 return true;
879}
880/** @brief Verifies that the file specified by "in_spec" exists and is
881 accessible with the permissions specified by "imode".
882 @ingroup utility_functions
883 @param in_spec - directory specification
884 @param imode - access mode
885 F_OK - existence
886 R_OK - read
887 W_OK - Write
888 X_OK - Execute
889 S_WCOK - Write or Create
890 S_QUIET - Suppress Error Messages
891 @note S_WCOK and S_QUIET are stripped before calling faccessat
892 @returns true if successful */
893bool verify_file(char *in_spec, int imode) {
894 if (in_spec == nullptr || *in_spec == '\0')
895 return false;
896 struct stat sb;
897 char spec[MAXLEN];
898 strnz__cpy(spec, in_spec, MAXLEN - 1);
899 int mode = imode & ~(S_WCOK | S_QUIET);
900 errno = 0;
901 src_line = 0;
904 if ((faccessat(AT_FDCWD, spec, mode, AT_EACCESS)) != 0) {
905 src_line = __LINE__ - 1;
906 src_name = __FILE__;
907 strnz__cpy(fn, "faccessat", MAXLEN - 1);
908 } else {
909 if ((fstatat(AT_FDCWD, spec, &sb, 0)) != 0) {
910 src_line = __LINE__ - 1;
911 src_name = __FILE__;
912 strnz__cpy(fn, "fstatat", MAXLEN - 1);
913 } else {
914 if ((sb.st_mode & S_IFMT) != S_IFREG) {
915 src_line = __LINE__ - 1;
916 src_name = __FILE__;
917 strnz__cpy(fn, "verify_file", MAXLEN - 1);
918 strnz__cpy(em2, "Not a regular file.", MAXLEN - 1);
919 }
920 }
921 }
922 if (src_line != 0) {
923 if (imode & S_QUIET)
924 return false;
925 ssnprintf(em0, MAXLEN - 1, "%s failed in %s at line %d", fn, src_name,
926 src_line);
927 strnz__cpy(em1, spec, MAXLEN - 1);
928 strnz__cpy(em3, "Check the file", MAXLEN - 1);
930 return false;
931 }
932 return true;
933}
934/** @brief Locates a file in the system PATH.
935 @ingroup utility_functions
936 @param file_spec - buffer to receive located file specification
937 @param file_name - name of file to locate
938 @returns true if file is located
939 @note file_spec must be large enough to receive the result */
940bool locate_file_in_path(char *file_spec, char *file_name) {
941 if (file_name == nullptr || *file_name == '\0' || file_spec == nullptr)
942 return false;
943 char path[MAXLEN];
944 char fn[MAXLEN];
945 char *p, *fnp, *dir;
946
948 strnz__cpy(fn, file_name, MAXLEN - 1);
949 fnp = fn;
950 while (*fnp && *fnp != '/')
951 fnp++;
952 if (*fnp == '/')
953 return false;
954 if ((p = getenv("PATH")) == nullptr)
955 return false;
956 strnz__cpy(path, p, MAXLEN - 1);
957 dir = strtok(path, ":");
958 while (dir != nullptr) {
959 strnz__cpy(file_spec, dir, MAXLEN - 1);
960 strnz__cat(file_spec, "/", MAXLEN - 1);
961 strnz__cat(file_spec, file_name, MAXLEN - 1);
962 if (access(file_spec, F_OK) == 0) {
963 return true;
964 }
965 dir = strtok(nullptr, ":");
966 }
967 return false;
968}
969/** @brief Find files in a directory matching a regular expression
970 @ingroup utility_functions
971 @param base_path directory to search
972 @param ere regular expression match to exclude
973 @param re regular expression match to include
974 @param max_depth depth of directories to scan
975 @param flags search flags
976 @see lf_process() for flag definitions
977 return true if successful, false otherwise */
978bool lf_find(const char *base_path, const char *re, const char *ere,
979 int max_depth, int flags) {
980 int reti;
981 regex_t compiled_re;
982 regex_t compiled_ere;
983 int REG_FLAGS = REG_EXTENDED;
984 if (flags & LF_ICASE)
985 REG_FLAGS |= REG_ICASE;
986 if (flags & LF_REGEX) {
987 reti = regcomp(&compiled_re, re, REG_FLAGS);
988 if (reti) {
989 ssnprintf(em0, MAXLEN - 1, "lf: \'%s\' Invalid pattern\n", re);
990 ssnprintf(em1, MAXLEN - 1, "for example: \'.*\\.c$\'\n\n");
991 regerror(reti, &compiled_re, em2, sizeof(em2));
993 regfree(&compiled_re);
994 return false;
995 }
996 }
997 if (flags & LF_EXC_REGEX) {
998 regcomp(&compiled_ere, "^$", REG_FLAGS);
999 reti = regcomp(&compiled_ere, ere, REG_FLAGS);
1000 if (reti) {
1001 ssnprintf(em0, MAXLEN - 1, "lf: \'%s\' Invalid exclude pattern\n",
1002 re);
1003 ssnprintf(em1, MAXLEN - 1, "for example: \'.*\\.c$\'\n\n");
1004 regerror(reti, &compiled_ere, em2, sizeof(em2));
1006 regfree(&compiled_ere);
1007 return false;
1008 }
1009 }
1010 reti =
1011 lf_process(base_path, &compiled_re, &compiled_ere, 0, max_depth, flags);
1012 if (flags &= LF_REGEX)
1013 regfree(&compiled_re);
1014 if (flags & LF_EXC_REGEX)
1015 regfree(&compiled_ere);
1016 if (reti)
1017 return false;
1018 return true;
1019}
1020/** @brief logic for lf_find()
1021 @ingroup utility_functions
1022 @param base_path directory to search
1023 @param compiled_ere compiled regular expression to exclude
1024 @param compiled_re compiled regular expression to include
1025 @param depth recursion counter
1026 @param max_depth how deep to descend into the directory structure
1027 @param flags
1028 @return true if successful, false otherwise
1029 @verbatim
1030
1031 LF_ALL = 1, List all files including hidden files
1032 LF_ICASE = 2, Ignore case in search
1033 LF_EXC_REGEX = 4, Exclude files matching regular expression
1034 LF_REGEX = 8, Include files matching regular expression
1035 LF_BLK = 256,
1036 LF_CHR = 512,
1037 LF_DIR = 1024,
1038 LF_FIFO = 2048,
1039 LF_LNK = 4096,
1040 LF_REG = 8192,
1041 LF_SOCK = 16384,
1042 LF_UNKNOWN = 32768,
1043 Include Exclude
1044 ---------- ----------
1045 LF_BLK 1 0 00000001 7 11111110 block device
1046 LF_CHR 2 1 00000010 6 11111101 character device
1047 LF_DIR 4 2 00000100 5 11111011 directory
1048 LF_FIFO 8 3 00001000 4 11110111 named pipe
1049 LF_LNK 16 4 00010000 3 11101111 link
1050 LF_REG 32 5 00100000 2 11011111 regular file
1051 LF_SOCK 64 6 01000000 1 10111111 socket
1052 LF_UNKNOWN 128 7 10000000 0 01111111 unknown
1053
1054 bool s_blk;
1055 bool s_chr;
1056 bool s_dir;
1057 bool s_fifo;
1058 bool s_lnk;
1059 bool s_reg;
1060 bool s_sock;
1061 bool s_unknown;
1062 @endverbatim
1064bool lf_process(const char *base_path, regex_t *compiled_re,
1065 regex_t *compiled_ere, int depth, int max_depth, int flags) {
1066 char tmp_str[MAXLEN];
1067 struct dirent *dir_st;
1068 DIR *dir;
1069 int REG_FLAGS = REG_EXTENDED;
1070 int reti;
1071 regmatch_t pmatch[1];
1072 char file_spec[1024];
1073 bool suppress;
1074 int f_include;
1075 int f_suppress;
1076 bool suppress_hidden = flags & LF_HIDE ? true : false;
1077 char bname[MAXLEN];
1078 if ((dir = opendir(base_path)) == 0)
1079 return false;
1080 while ((dir_st = readdir(dir)) != nullptr) {
1081 strnz__cpy(bname, dir_st->d_name, MAXLEN - 1);
1082 if (bname[0] == '.' && bname[1] == '\0')
1083 continue;
1084 else if (bname[0] == '.' && bname[1] == '.' && bname[2] == '\0')
1085 continue;
1086 else if (bname[0] == '.' && suppress_hidden)
1087 continue;
1088 f_suppress = 0;
1089 f_include = (flags >> 8) & 0xff;
1090 if (f_include)
1091 f_suppress = f_include ^ 0xff;
1092 bool s_blk = f_suppress & FT_BLK ? 1 : 0;
1093 bool s_chr = f_suppress & FT_CHR ? 1 : 0;
1094 bool s_dir = f_suppress & FT_DIR ? 1 : 0;
1095 bool s_fifo = f_suppress & FT_FIFO ? 1 : 0;
1096 bool s_lnk = f_suppress & FT_LNK ? 1 : 0;
1097 bool s_reg = f_suppress & FT_REG ? 1 : 0;
1098 bool s_sock = f_suppress & FT_SOCK ? 1 : 0;
1099 bool s_unknown = f_suppress & FT_UNKNOWN ? 1 : 0;
1100 suppress = false;
1101 switch (dir_st->d_type) {
1102 case DT_BLK:
1103 if (s_blk)
1104 suppress = true;
1105 break;
1106 case DT_CHR:
1107 if (s_chr)
1108 suppress = true;
1109 break;
1110 case DT_DIR:
1111 if (s_dir)
1112 suppress = true;
1113 break;
1114 case DT_FIFO:
1115 if (s_fifo)
1116 suppress = true;
1117 break;
1118 case DT_LNK:
1119 if (s_lnk)
1120 suppress = true;
1121 break;
1122 case DT_REG:
1123 if (s_reg)
1124 suppress = true;
1125 break;
1126 case DT_SOCK:
1127 if (s_sock)
1128 suppress = true;
1129 break;
1130 case DT_UNKNOWN:
1131 if (s_unknown)
1132 suppress = true;
1133 break;
1134 default:
1135 break;
1136 }
1137 if (dir_st->d_name[0] == '.' && dir_st->d_name[1] == '/')
1138 strnz__cpy(file_spec, &dir_st->d_name[2], MAXLEN - 1);
1139 else
1140 strnz__cpy(file_spec, dir_st->d_name, MAXLEN - 1);
1141 ssnprintf(file_spec, sizeof(file_spec), "%s/%s", base_path, bname);
1142 if (bname[0] == '.' && suppress_hidden)
1143 suppress = true;
1144 if (!suppress && (flags & LF_REGEX)) {
1145 reti = regexec(compiled_re, file_spec, compiled_re->re_nsub + 1,
1146 pmatch, REG_FLAGS);
1147 if (reti == REG_NOMATCH) {
1148 suppress = true;
1149 } else if (reti) {
1150 char msgbuf[100];
1151 regerror(reti, compiled_re, msgbuf, sizeof(msgbuf));
1152 strnz__cpy(tmp_str, "Regex match failed: ", MAXLEN - 1);
1153 strnz__cat(tmp_str, msgbuf, MAXLEN - 1);
1154 Perror(tmp_str);
1155 return false;
1156 }
1157 }
1158 if (!suppress && (flags & LF_EXC_REGEX)) {
1159 reti = regexec(compiled_ere, file_spec, compiled_re->re_nsub + 1,
1160 pmatch, REG_FLAGS);
1161 if (reti == 0)
1162 suppress = true;
1163 else if (reti == REG_NOMATCH)
1164 suppress = false;
1165 else if (reti) {
1166 char msgbuf[100];
1167 regerror(reti, compiled_re, msgbuf, sizeof(msgbuf));
1168 strnz__cpy(tmp_str, "Regex match failed: ", MAXLEN - 1);
1169 strnz__cat(tmp_str, msgbuf, MAXLEN - 1);
1170 Perror(tmp_str);
1171 return false;
1172 }
1173 }
1174
1175 if (!suppress) {
1176 if (file_spec[0] == '.' && file_spec[1] == '/')
1177 printf("%s\n", &file_spec[2]);
1178 else
1179 printf("%s\n", file_spec);
1180 }
1181 if (max_depth == 0 ||
1182 (dir_st->d_type == DT_DIR && depth + 1 < max_depth)) {
1183 depth++;
1184 lf_process(file_spec, compiled_re, compiled_ere, depth, max_depth,
1185 flags);
1186 depth--;
1187 }
1188 }
1189 closedir(dir);
1190 return true;
1191}
1192/** @brief If directory doesn't exist, make it
1193 @ingroup utility_functions
1194 @param dir directory name
1195 @return true if directory now exists or false otherwise */
1196bool mk_dir(char *dir) {
1197 expand_tilde(dir, MAXLEN - 1);
1198 if (!verify_dir(dir, S_WCOK | S_QUIET)) {
1199 if (!mkdir(dir, 0755)) {
1200 /** Directory does not exist and unable to create */
1201 ssnprintf(em0, MAXLEN - 1, "%s, line: %d", __FILE__, __LINE__ - 2);
1202 strnz__cpy(em1, "mkdir ", MAXLEN - 1);
1203 strnz__cat(em1, dir, MAXLEN - 1);
1204 strnz__cat(em1, " failed", MAXLEN - 1);
1205 strerror_r(errno, em2, MAXLEN - 1);
1207 return false;
1208 }
1209 return true;
1210 }
1211 return true;
1212}
1213/** @brief Removes quotes and trims at first space
1214 @ingroup utility_functions
1215 @param spec - file specification to canonicalize
1216 @returns length of resulting string */
1217size_t canonicalize_file_spec(char *spec) {
1218 if (spec == nullptr || *spec == '\0')
1219 return 0;
1220 char tmp_s[MAXLEN];
1221 char *s;
1222 s = spec;
1223 char *d;
1224 d = tmp_s;
1225 int l = 0;
1226 while (*s != '\0') {
1227 if (*s == ' ')
1228 break;
1229 if (*s == '\"' || *s == '\'') {
1230 s++;
1231 continue;
1232 ;
1233 }
1234 *d++ = *s++;
1235 l++;
1236 }
1237 *d = '\0';
1238 strnz__cpy(spec, tmp_s, MAXLEN - 1);
1239 l = strlen(spec);
1240 return l;
1241}
1242/** @brief Checks if the given path is a directory
1243 @ingroup utility_functions
1244 @param path - path to check
1245 @returns true if the path is a directory, false otherwise */
1246bool is_directory(const char *path) {
1247 struct stat statbuf;
1248 if (stat(path, &statbuf) != 0) {
1249 return false;
1250 }
1251 return S_ISDIR(statbuf.st_mode);
1252}
1253/** @brief Checks if the given regular expression pattern is valid
1254 @ingroup utility_functions
1255 @param pattern - regular expression pattern to check
1256 @returns true if the pattern is valid, false otherwise */
1257bool is_valid_regex(const char *pattern) {
1258 regex_t regex;
1259 int ret = regcomp(&regex, pattern, REG_EXTENDED);
1260 regfree(&regex);
1261 return (ret == 0);
1262}
1263/** @brief Replace all occurrences of "tgt_s" in "org_s" with "rep_s"
1264 @ingroup utility_functions
1265 @param org_s - original string
1266 @param tgt_s - target substring to replace
1267 @param rep_s - replacement substring
1268 @returns A pointer to the newly allocated string with replacements or a copy
1269 of the replacement string if original string is the same as target string
1270 This is a special case that allows for replacing the entire original string.
1271 If any parameter is nullptr, the function returns nullptr. If "tgt_s" is not
1272 found in "org_s", the function returns a copy of "org_s". If target substring
1273 is not found the function returns a copy of the original string.
1274 @note allocates memory for the return value, so the caller is responsible
1275 for freeing this memory when it is no longer needed to avoid memory leaks.
1276 @note Does not modify the original string "org_s".
1277 @note Assumes that "tgt_s" and "rep_s" are null-terminated strings. If they
1278 are not, the behavior is undefined.
1279 @note Does not perform any bounds checking on the input strings, so it is
1280 the caller's responsibility to ensure that they are valid and that the
1281 resulting string does not exceed available memory.
1282 @note Uses the standard library functions strlen, strstr, malloc, and
1283 strcpy, which may have their own limitations and behaviors that the caller
1284 should be aware of.
1285 @note Does not handle overlapping occurrences of "tgt_s" in "org_s". If
1286 "tgt_s" can overlap with itself in "org_s", the behavior may be unexpected.
1287 The caller should ensure that "tgt_s" does not contain overlapping patterns
1288 to avoid this issue.
1289 @note Does not handle cases where "tgt_s" is a substring of "rep_s", which
1290 could lead to unintended consequences if "tgt_s" appears in "rep_s". The
1291 caller should ensure that "tgt_s" and "rep_s" are distinct to avoid this
1292 issue. */
1293char *rep_substring(const char *org_s, const char *tgt_s, const char *rep_s) {
1294 if (org_s == nullptr || tgt_s == nullptr || rep_s == nullptr)
1295 return nullptr;
1296 if (*org_s == '\0' || *tgt_s == '\0' || *rep_s == '\0')
1297 return nullptr;
1298 if (strstr(org_s, tgt_s) == nullptr)
1299 return strdup(org_s);
1300 if (strstr(rep_s, tgt_s) != nullptr)
1301 return nullptr;
1302 if (tgt_s == rep_s || tgt_s == org_s || rep_s == org_s)
1303 return strdup(org_s);
1304 if (strcmp(org_s, tgt_s) == 0)
1305 return strdup(rep_s);
1306 char *out_s, *ip, *tmp;
1307 int tgt_l = strlen(tgt_s);
1308 int rep_l = strlen(rep_s);
1309 int head_l;
1310 int n = 0;
1311 ip = (char *)org_s;
1312 while ((tmp = strstr(ip, tgt_s)) != nullptr) {
1313 n++;
1314 ip = tmp + tgt_l;
1315 }
1316 out_s = malloc(strlen(org_s) + (rep_l - tgt_l) * n + 1);
1317 if (!out_s) {
1318 return nullptr;
1319 }
1320 tmp = out_s;
1321 ip = (char *)org_s;
1322 while (n--) {
1323 char *p = strstr(ip, tgt_s);
1324 head_l = p - ip;
1325 strnz__cpy(tmp, ip, head_l);
1326 tmp += head_l;
1327 strnz__cpy(tmp, rep_s, MAXLEN - 1);
1328 tmp += rep_l;
1329 ip += head_l + tgt_l;
1330 }
1331 strnz__cpy(tmp, ip, MAXLEN - 1);
1332 return out_s;
1333}
1334/** @defgroup String_Objects String Objects
1335 @brief Simple String Object Library
1336 */
1337/** @brief String functions provide a simple string library to facilitate string
1338 manipulation in C, allowing developers to easily create, copy, concatenate,
1339 and free strings without having to manage memory manually.
1340 @ingroup String_Objects
1341 @note The library includes functions to convert C strings to String structs,
1342 create new String structs with specified lengths, copy and concatenate String
1343 structs, and free the memory used by String structs. By using this library,
1344 developers can avoid common pitfalls of C string handling, such as buffer
1345 overflows and memory leaks, while still benefiting from the performance
1346 advantages of C.
1347 @note Designed to be simple and easy to use, making it a great choice for
1348 developers who want to work with strings in C without having to worry about
1349 the complexities of manual memory management.
1350 @note The String struct is defined as follows:
1351 @code
1352 typedef struct {
1353 size_t l; // length of the string (including null terminator)
1354 char *s; // pointer to the dynamically allocated string
1355 } String;
1356 @endcode
1357 @note All functions in this library that return a String struct allocate
1358 memory for the string using malloc or realloc. It is the caller's
1359 responsibility to free this memory using the free_string function when it is
1360 no longer needed to avoid memory leaks.
1361 @note The String functions in this library do not perform bounds checking on
1362 the input strings or the resulting strings. It is the caller's responsibility
1363 to ensure that all input strings are valid and that the resulting strings do
1364 not exceed available memory.
1365 @note The String functions in this library assume that all input strings are
1366 null-terminated. If any input string is not null-terminated, the behavior is
1367 undefined.
1368 @see snippets/strings_test1.c
1369 */
1370/** @brief Convert C string to String struct
1371 @ingroup String_Objects
1372 @param s C string
1373 @return String struct containing dynamically allocated copy of input string
1374 @note the caller is responsible for freeing the allocated memory.
1376String to_string(const char *s) {
1377 if (s == nullptr) {
1378 String str;
1379 str.l = 0;
1380 str.s = nullptr;
1381 return str;
1382 }
1383 String str;
1384 str.l = strlen(s) + 1;
1385 str.s = (char *)malloc(str.l);
1386 strcpy(str.s, s);
1387 return str;
1388}
1389/** @brief Create a String struct with a dynamically allocated string @param l
1390 length of string to create including null terminator
1391 @returns String struct
1392 @note The returned String struct contains a dynamically allocated string of
1393 he specified length
1394 @sa free_string
1395 @note the caller is responsible for calling free_string to free the allocated
1396 memory. */
1397String mk_string(size_t l) {
1398 if (l == 0) {
1399 String str;
1400 str.l = 0;
1401 str.s = nullptr;
1402 return str;
1403 }
1404 String str;
1405 str.l = l + 1;
1406 str.s = (char *)malloc(str.l);
1407 str.s[0] = '\0';
1408 return str;
1409}
1410/** @brief Free the dynamically allocated String
1411 @ingroup String_Objects
1412 @param string to free
1413 @return string with nullptr pointer and length 0
1414 @note Frees the dynamically allocated string and sets length to 0.
1416String free_string(String string) {
1417 if (string.s == nullptr)
1418 return string;
1419 free(string.s);
1420 string.l = 0;
1421 string.s = nullptr;
1422 return string;
1423}
1424/** @brief Copy src String to dest String, allocating additional memory for dest
1425 String if necessary
1426 @ingroup String_Objects
1427 @param dest - destination String struct
1428 @param src - source String struct
1429 @returns length of dest String
1430 @note the caller is responsible for freeing the allocated memory. */
1431size_t string_cpy(String *dest, const String *src) {
1432 if (dest == nullptr || src == nullptr || src->s == nullptr)
1433 return 0;
1434 if (dest->l < src->l) {
1435 dest->s = (char *)realloc(dest->s, src->l);
1436 dest->l = src->l;
1437 }
1438 strcpy(dest->s, src->s);
1439 return src->l;
1440}
1441/** @brief Concatenates src String to dest String, allocating additional memory
1442 for dest String if necessary
1443 @ingroup String_Objects
1444 @param dest - destination String struct
1445 @param src - source String struct
1446 @returns new length of dest String after concatenation
1447 @note the caller is responsible for freeing the allocated memory. */
1448size_t string_cat(String *dest, const String *src) {
1449 if (dest == nullptr || src == nullptr || src->s == nullptr)
1450 return 0;
1451 size_t new_len = strlen(dest->s) + strlen(src->s) + 1;
1452 if (dest->l < new_len) {
1453 dest->s = (char *)realloc(dest->s, new_len);
1454 dest->l = new_len;
1455 }
1456 strcat(dest->s, src->s);
1457 return new_len;
1458}
1459/** @brief Concatenates up to n characters from src String to dest String,
1460 allocating additional memory for dest String if necessary
1461 @ingroup String_Objects
1462 @param dest - destination String struct
1463 @param src - source String struct
1464 @param n - maximum number of characters to concatenate
1465 @returns new length of dest String after concatenation
1466 @note the caller is responsible for freeing the allocated memory. */
1467size_t string_ncat(String *dest, const String *src, size_t n) {
1468 if (dest == nullptr || src == nullptr || src->s == nullptr)
1469 return 0;
1470 size_t dest_len = strlen(dest->s);
1471 size_t src_len = strlen(src->s);
1472 size_t cat_len = (n < src_len) ? n : src_len;
1473 size_t new_len = dest_len + cat_len + 1;
1474 if (dest->l < new_len) {
1475 dest->s = (char *)realloc(dest->s, new_len);
1476 dest->l = new_len;
1477 }
1478 strncat(dest->s, src->s, cat_len);
1479 return new_len;
1480}
1481/** @brief copies up to n characters from src String to dest String, allocating
1482 additional memory for dest String if necessary
1483 @ingroup String_Objects
1484 @param dest - destination String struct
1485 @param src - source String struct
1486 @param n - maximum number of characters to copy
1487 @note the caller is responsible for freeing the allocated memory. */
1488size_t string_ncpy(String *dest, const String *src, size_t n) {
1489 if (dest == nullptr || src == nullptr || src->s == nullptr)
1490 return 0;
1491 size_t src_len = strlen(src->s);
1492 size_t cpy_len = (n < src_len) ? n : src_len;
1493 size_t new_len = cpy_len + 1;
1494 if (dest->l < new_len) {
1495 dest->s = (char *)realloc(dest->s, new_len);
1496 dest->l = new_len;
1497 }
1498 strncpy(dest->s, src->s, cpy_len);
1499 dest->s[cpy_len] = '\0';
1500 return new_len;
1501}
1502/** @defgroup testing_functions Testing Functions
1503 @brief Functions for Testing Only
1504 */
1505
1506/** @brief Function to intentionally cause a segmentation fault for testing
1507 purposes
1508 @ingroup testing_functions
1509 @note This function is designed to intentionally cause a segmentation fault
1510 by dereferencing a null pointer. It is intended for testing purposes only and
1511 should not be used in production code. The caller should be aware that
1512 executing this function will crash the program. */
1513int segmentation_fault() {
1514 // int *p = NULL;
1515 // *p = 100;
1516
1517 return 0;
1518}
size_t rtrim(char *)
Trims trailing spaces from string s in place.
Definition futil.c:102
int eargc
Definition futil.c:41
#define MAXARGS
Definition cm.h:30
@ FT_UNKNOWN
Definition cm.h:164
@ FT_SOCK
Definition cm.h:163
@ FT_DIR
Definition cm.h:159
@ FT_FIFO
Definition cm.h:160
@ FT_REG
Definition cm.h:162
@ FT_CHR
Definition cm.h:158
@ FT_BLK
Definition cm.h:157
@ FT_LNK
Definition cm.h:161
char earg_str[MAXLEN]
Definition futil.c:40
String mk_string(size_t)
Create a String struct with a dynamically allocated string.
Definition futil.c:1396
int wait_timeout
Definition futil.c:98
char errmsg[]
Definition futil.c:84
@ LF_EXC_REGEX
Definition cm.h:151
@ LF_HIDE
Definition cm.h:149
@ LF_ICASE
Definition cm.h:150
@ LF_REGEX
Definition cm.h:152
#define nullptr
Definition cm.h:23
char * eargv[MAXARGS]
Definition futil.c:42
#define S_QUIET
Definition cm.h:183
bool str_to_bool(const char *)
Converts String to boolean true or false.
Definition futil.c:649
#define S_WCOK
Definition cm.h:182
#define MAXLEN
Definition curskeys.c:15
error_source_t error_source
Definition futil.c:97
error_info_t error_info
Definition futil.c:96
char em1[MAXLEN]
Definition dwin.c:133
int src_line
Definition dwin.c:129
char * src_name
Definition dwin.c:130
char em0[MAXLEN]
Definition dwin.c:132
char em3[MAXLEN]
Definition dwin.c:135
char fn[MAXLEN]
Definition dwin.c:131
char em2[MAXLEN]
Definition dwin.c:134
int Perror(char *)
Display a simple error message window or print to stderr.
Definition dwin.c:1110
int display_error(char *em0, char *em1, char *em2, char *em3)
Display an error message window or print to stderr.
Definition dwin.c:1054
bool locate_file_in_path(char *, char *)
Locates a file in the system PATH.
Definition futil.c:939
bool lf_process(const char *, regex_t *, regex_t *, int, int, int)
logic for lf_find()
Definition futil.c:1063
void destroy_argv(int argc, char **argv)
Deallocates memory allocated for argument strings in argv.
Definition futil.c:221
size_t canonicalize_file_spec(char *)
Removes quotes and trims at first space.
Definition futil.c:1216
size_t strnz__cpy(char *, const char *, size_t)
safer alternative to strncpy
Definition futil.c:269
bool trim_ext(char *, char *)
trims the file extension from "filename" and copies the result to "buf"
Definition futil.c:741
bool stripz_quotes(char *)
removes leading and trailing double quotes if present
Definition futil.c:469
size_t trim(char *)
Trims leading and trailing spaces from string s in place.
Definition futil.c:118
bool is_directory(const char *)
Checks if the given path is a directory.
Definition futil.c:1245
bool file_spec_path(char *, char *)
extracts the path component of a file specification
Definition futil.c:578
bool str_to_upper(char *)
Converts a string to uppercase.
Definition futil.c:247
bool dir_name(char *, char *)
Returns the directory name of a file specification.
Definition futil.c:801
double str_to_double(char *)
converts string to double
Definition futil.c:637
bool str_to_lower(char *)
Converts a string to lowercase.
Definition futil.c:233
bool lf_find(const char *, const char *, const char *, int, int)
Find files in a directory matching a regular expression.
Definition futil.c:977
bool strnfill(char *, char, int)
Fills string s with character c n.
Definition futil.c:440
size_t strnz(char *, size_t)
terminates string at New Line, Carriage Return, or max_len
Definition futil.c:340
size_t ssnprintf(char *, size_t, const char *,...)
ssnprintf was designed to be a safer alternative to snprintf.
Definition futil.c:147
bool strip_quotes(char *)
removes leading and trailing double quotes if present
Definition futil.c:454
bool is_valid_regex(const char *)
Checks if the given regular expression pattern is valid.
Definition futil.c:1256
char * strnz_dup(char *, size_t)
Allocates memory for and duplicates string s up to length l or until line feed or carriage return.
Definition futil.c:379
size_t strip_ansi(char *, char *)
Strips ANSI SGR escape sequences (ending in 'm') from string s to d.
Definition futil.c:537
bool mk_dir(char *dir)
If directory doesn't exist, make it.
Definition futil.c:1195
size_t strnz__cat(char *, const char *, size_t)
safer alternative to strncat
Definition futil.c:298
bool str_subc(char *, char *, char, char *, int)
Replaces "ReplaceChr" in "s" with "Withstr" in "d" won't copy more than "l" bytes to "d" Replaces all...
Definition futil.c:415
bool verify_file(char *, int)
Verifies that the file specified by "in_spec" exists and is accessible with the permissions specified...
Definition futil.c:892
char * rep_substring(const char *, const char *, const char *)
Replace all occurrences of "tgt_s" in "org_s" with "rep_s".
Definition futil.c:1292
size_t strnlf(char *, size_t)
terminates string with line feed
Definition futil.c:358
int a_toi(char *, bool *)
a safer alternative to atoi() for converting ASCII strings to integers.
Definition futil.c:505
bool file_spec_name(char *, char *)
extracts the file name component of a file specification
Definition futil.c:605
bool expand_tilde(char *, int)
Replace Leading Tilde With Home Directory.
Definition futil.c:684
bool verify_dir(char *, int)
Verifies that the directory specified by "spec" exists and is accessible with the permissions specifi...
Definition futil.c:841
bool chrep(char *, char, char)
Replaces all occurrences of old_chr in s with new_chr in place.
Definition futil.c:486
bool base_name(char *, char *)
Returns the base name of a file specification.
Definition futil.c:775
bool normalize_file_spec(char *)
replace backslashes with forward lashes
Definition futil.c:561
int str_to_args(char **, char *, int)
Converts a string into an array of argument strings.
Definition futil.c:167
bool trim_path(char *)
Trims trailing spaces and slashes from directory path in place.
Definition futil.c:713
size_t strz(char *)
Terminates string at new line or carriage return.
Definition futil.c:322
size_t string_cpy(String *, const String *)
Copy src String to dest String, allocating additional memory for dest String if necessary.
Definition futil.c:1430
String to_string(const char *)
String functions provide a simple string library to facilitate string manipulation in C,...
Definition futil.c:1375
size_t string_ncpy(String *, const String *, size_t)
copies up to n characters from src String to dest String, allocating additional memory for dest Strin...
Definition futil.c:1487
size_t string_cat(String *, const String *)
Concatenates src String to dest String, allocating additional memory for dest String if necessary.
Definition futil.c:1447
String free_string(String)
Free the dynamically allocated String.
Definition futil.c:1415
size_t string_ncat(String *, const String *, size_t)
Concatenates up to n characters from src String to dest String, allocating additional memory for dest...
Definition futil.c:1466
int segmentation_fault()
Function to intentionally cause a segmentation fault for testing purposes.
Definition futil.c:1512
size_t l
Definition cm.h:586
char * s
Definition cm.h:585