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