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