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