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