1#include <time.h>
2#include <ctype.h>
3#include <errno.h>
4
5#if defined(__linux__) || defined(__bsd__) || defined(__sunos__)
6#include <unistd.h>
7#include <pthread.h>
8#endif
9
10#if defined(__windows__)
11#include <windows.h>
12#endif
13
14#include <assert.h>
15#include <string.h>
16#include <stdbool.h>
17#include <stdio.h>
18#include <stdarg.h>
19
20#include <openssl/bio.h>
21#include <openssl/buffer.h>
22#include <openssl/crypto.h>
23#include <openssl/err.h>
24#include <openssl/evp.h>
25#include <openssl/rand.h>
26#include <openssl/sha.h>
27#include <openssl/ssl.h>
28
29#include "vws.h"
30
31//------------------------------------------------------------------------------
32// Utility functions
33//------------------------------------------------------------------------------
34
35// Function to handle the submission of an error by default
36static int vws_error_default_submit(int code, cstr message, ...);
37
38// Function to clear an error by default
39static void vws_error_clear_default();
40
41// Function to process an error by default
42static int vws_error_default_process(int code, cstr message);
43
44//------------------------------------------------------------------------------
45// Tracing
46//------------------------------------------------------------------------------
47
48// Color codes for log message highlighting
49#define ANSI_COLOR_RESET "\x1b[0m"
50#define ANSI_COLOR_RED "\x1b[31m"
51#define ANSI_COLOR_GREEN "\x1b[32m"
52#define ANSI_COLOR_YELLOW "\x1b[33m"
53#define ANSI_COLOR_BLUE "\x1b[34m"
54#define ANSI_COLOR_MAGENTA "\x1b[35m"
55#define ANSI_COLOR_CYAN "\x1b[36m"
56#define ANSI_COLOR_WHITE "\x1b[37m"
57
58#ifdef __windows__
59// Windows Mutex for thread-safe logging
60HANDLE log_mutex;
61#else
62// POSIX Mutex for thread-safe logging
63pthread_mutex_t log_mutex = PTHREAD_MUTEX_INITIALIZER;
64#endif
65
66typedef struct
67{
68 const char* color;
69 const char* level;
70} log_level_info;
71
72const log_level_info log_level_infos[VL_LEVEL_COUNT] =
73{
74 { ANSI_COLOR_WHITE, "DEBG" },
75 { ANSI_COLOR_BLUE, "INFO" },
76 { ANSI_COLOR_MAGENTA, "WARN" },
77 { ANSI_COLOR_RED, "CRIT" }
78};
79
80static void unlock_mutex(void* arg)
81{
82 pthread_mutex_unlock((pthread_mutex_t*)arg);
83}
84
85void vws_trace_lock()
86{
87#if defined(__windows__)
88
89 DWORD tid = GetCurrentThreadId();
90
91 // Windows implementation using Windows API for thread synchronization
92 if (WaitForSingleObject(log_mutex, INFINITE) != WAIT_OBJECT_0)
93 {
94 vws_error_default_submit(VE_SYS, "WaitForSingleObject failed");
95 return;
96 }
97
98#else
99
100 pthread_t tid = pthread_self();
101
102 if (pthread_mutex_lock(&log_mutex) != 0)
103 {
104 vws_error_default_submit(VE_SYS, "pthread_mutex_lock failed");
105 return;
106 }
107
108#endif
109}
110
111void vws_trace_unlock()
112{
113#if defined(__windows__)
114
115 // Windows implementation using Windows API for thread synchronization
116 if (!ReleaseMutex(log_mutex))
117 {
118 vws_error_default_submit(VE_SYS, "ReleaseMutex failed");
119 }
120
121#else
122
123 if (pthread_mutex_unlock(&log_mutex) != 0)
124 {
125 vws_error_default_submit(VE_SYS, "pthread_mutex_unlock failed");
126 }
127
128#endif
129}
130
131void vws_trace(vws_log_level_t level, const char* format, ...)
132{
133 if (level < 0 || level >= VL_LEVEL_COUNT)
134 {
135 vws_error_default_submit(VE_WARN, "Invalid log level");
136 return;
137 }
138
139 time_t raw_time;
140 struct tm time_info;
141 char stamp[20];
142
143 time(&raw_time);
144
145#ifdef __windows__
146 // Windows implementation using localtime_s
147 if (localtime_s(&time_info, &raw_time) != 0)
148 {
149 vws_error_default_submit(VE_SYS, "localtime_s failed");
150 return;
151 }
152#else
153 // Non-Windows implementation using localtime_r
154 if (localtime_r(&raw_time, &time_info) == NULL)
155 {
156 vws_error_default_submit(VE_SYS, "localtime_r failed");
157 return;
158 }
159#endif
160
161 if (strftime(stamp, sizeof(stamp), "%Y-%m-%d %H:%M:%S", &time_info) == 0)
162 {
163 vws_error_default_submit(VE_SYS, "strftime returned 0");
164 return;
165 }
166
167 const char* color_code = log_level_infos[level].color;
168 const char* level_name = log_level_infos[level].level;
169
170#if defined(__windows__)
171
172 DWORD tid = GetCurrentThreadId();
173
174 // Windows implementation using Windows API for thread synchronization
175 if (WaitForSingleObject(log_mutex, INFINITE) != WAIT_OBJECT_0)
176 {
177 vws_error_default_submit(VE_SYS, "WaitForSingleObject failed");
178 return;
179 }
180
181#else
182
183 pthread_t tid = pthread_self();
184
185 if (pthread_mutex_lock(&log_mutex) != 0)
186 {
187 vws_error_default_submit(VE_SYS, "pthread_mutex_lock failed");
188 return;
189 }
190
191 pthread_cleanup_push(unlock_mutex, &log_mutex);
192
193#endif
194
195 fprintf(stderr, "%s[%s] [%lu] [%s]%s ",
196 color_code,
197 stamp,
198#if defined(__windows__)
199 tid,
200#else
201 (unsigned long)tid,
202#endif
203 level_name,
204 ANSI_COLOR_RESET);
205
206 va_list args;
207 va_start(args, format);
208 vfprintf(stderr, format, args);
209 va_end(args);
210
211 fprintf(stderr, "\n");
212 fflush(stderr);
213
214#if defined(__windows__)
215
216 // Windows implementation using Windows API for thread synchronization
217 if (!ReleaseMutex(log_mutex))
218 {
219 vws_error_default_submit(VE_SYS, "ReleaseMutex failed");
220 }
221
222#else
223
224 pthread_cleanup_pop(1);
225
226#endif
227}
228
229//------------------------------------------------------------------------------
230// Memory allocation
231//------------------------------------------------------------------------------
232
233void* vws_malloc(size_t size)
234{
235 void* ptr = malloc(size);
236
237 if (ptr == NULL)
238 {
239 return vws.malloc_error(size);
240 }
241
242 return ptr;
243}
244
245void* vws_malloc_error(size_t size)
246{
247 // No error string since memory allocation has already failed and error
248 // handler uses malloc() for copying error string.
249 vws.error(VE_MEM, NULL);
250
251 // Default does not provide any recovery attempt.
252 return NULL;
253}
254
255void vws_free(void* data)
256{
257 free(data);
258}
259
260void* vws_calloc(size_t nmemb, size_t size)
261{
262 void* ptr = calloc(nmemb, size);
263
264 if (ptr == NULL)
265 {
266 return vws.calloc_error(nmemb, size);
267 }
268
269 return ptr;
270}
271
272void* vws_calloc_error(size_t nmemb, size_t size)
273{
274 // No error string since memory allocation has already failed and error
275 // handler uses malloc() for copying error string.
276 vws.error(VE_MEM, NULL);
277
278 // Default does not provide any recovery attempt.
279 return NULL;
280}
281
282void* vws_realloc(void* ptr, size_t size)
283{
284 ptr = realloc(ptr, size);
285
286 if (ptr == NULL)
287 {
288 return vws.realloc_error(ptr, size);
289 }
290
291 return ptr;
292}
293
294void* vws_realloc_error(void* ptr, size_t size)
295{
296 // No error string since memory allocation has already failed and error
297 // handler uses malloc() for copying error string.
298 vws.error(VE_MEM, NULL);
299
300 // Default does not provide any recovery attempt.
301 return NULL;
302}
303
304void* vws_strdup(cstr ptr)
305{
306 ptr = strdup(ptr);
307
308 if (ptr == NULL)
309 {
310 return vws.strdup_error(ptr);
311 }
312
313 return ptr;
314}
315
316void* vws_strdup_error(cstr ptr)
317{
318 // No error string since memory allocation has already failed and error
319 // handler uses malloc() for copying error string.
320 vws.error(VE_MEM, NULL);
321
322 // Default does not provide any recovery attempt.
323 return NULL;
324}
325
326//------------------------------------------------------------------------------
327// Error handling
328//------------------------------------------------------------------------------
329
330// Sets the last error for the current thread
331void vws_set_error(vws_error_code_t code, const char* message)
332{
333 if (vws.e.text != NULL)
334 {
335 vws.free(vws.e.text);
336 vws.e.text = NULL;
337 }
338
339 vws.e.code = code;
340
341 if (message != NULL)
342 {
343 vws.e.text = strdup(message);
344 }
345}
346
347// Get the error value for the current thread
348vws_error_value vws_get_error()
349{
350 return vws.e;
351}
352
353int vws_error_default_submit(int code, cstr format, ...)
354{
355 va_list args, args_copy;
356 va_start(args, format);
357
358 va_copy(args_copy, args);
359
360 // Determine the length of the formatted string
361 int length = vsnprintf(NULL, 0, format, args_copy);
362
363 va_end(args_copy); // End args_copy. We're done with it now.
364
365 // Allocate a buffer for the formatted string
366 char* buffer = malloc(length + 1);
367
368 // Format the string into the buffer
369 vsnprintf(buffer, length + 1, format, args);
370
371 // Set
372 vws_set_error(code, buffer);
373
374 // Process
375 vws.process_error(code, buffer);
376
377 // Cleanup
378 vws.free(buffer);
379 va_end(args);
380
381 return 0;
382}
383
384int vws_error_default_process(int code, cstr message)
385{
386 if (vws.tracelevel >= 1)
387 {
388 switch (code)
389 {
390 case VE_WARN:
391 {
392 vws.trace(VL_WARN, "%s", message);
393 break;
394 }
395
396 case VE_TIMEOUT:
397 {
398 vws.trace(VL_WARN, "timeout: %s", message);
399 break;
400 }
401
402 case VE_SOCKET:
403 {
404 vws.trace(VL_WARN, "disconnect: %s", message);
405 break;
406 }
407
408 case VE_SYS:
409 case VE_RT:
410 {
411 vws.trace(VL_INFO, "error %i: %s", message);
412 break;
413 }
414
415 case VE_MEM:
416 case VE_FATAL:
417 {
418 vws.trace(VL_ERROR, "fatal %i: %s", code, message);
419 break;
420 }
421
422 default:
423 {
424 vws.trace(VL_INFO, "no error");
425 }
426 }
427 }
428
429 switch (code)
430 {
431 case VE_MEM:
432 {
433 break;
434 }
435
436 case VE_FATAL:
437 {
438 exit(1);
439 }
440
441 default:
442 {
443 if (message != NULL)
444 {
445
446 }
447 }
448 }
449
450 return 0;
451}
452
453void vws_error_clear_default()
454{
455 vws_set_error(VE_SUCCESS, NULL);
456}
457
458void vws_error_success_default()
459{
460 vws_set_error(VE_SUCCESS, NULL);
461}
462
463// Global SSL context
464SSL_CTX* vws_ssl_ctx = NULL;
465
466// Initialization of the vrtql environment. The environment is initialized with
467// default error handling functions and the trace flag is turned off
468__thread vws_env vws =
469{
470 .malloc = vws_malloc,
471 .malloc_error = vws_malloc_error,
472 .calloc = vws_calloc,
473 .calloc_error = vws_calloc_error,
474 .realloc = vws_realloc,
475 .realloc_error = vws_realloc_error,
476 .strdup = vws_strdup,
477 .strdup_error = vws_strdup_error,
478 .free = vws_free,
479 .error = vws_error_default_submit,
480 .process_error = vws_error_default_process,
481 .clear_error = vws_error_clear_default,
482 .success = vws_error_success_default,
483 .e = {.code=VE_SUCCESS, .text=NULL},
484 .trace = vws_trace,
485 .tracelevel = 0,
486 .state = 0
487};
488
489void vws_cleanup()
490{
491 if (vws.e.text != NULL)
492 {
493 free(vws.e.text);
494 }
495}
496
497//------------------------------------------------------------------------------
498// Buffer
499//------------------------------------------------------------------------------
500
501vws_buffer* vws_buffer_new()
502{
503 vws_buffer* buffer = vws.malloc(sizeof(vws_buffer));
504
505 buffer->data = NULL;
506 buffer->allocated = 0;
507 buffer->size = 0;
508
509 return buffer;
510}
511
512void vws_buffer_clear(vws_buffer* buffer)
513{
514 if (buffer != NULL)
515 {
516 if (buffer->data != NULL)
517 {
518 vws.free(buffer->data);
519 }
520
521 buffer->data = NULL;
522 buffer->allocated = 0;
523 buffer->size = 0;
524 }
525}
526
527void vws_buffer_free(vws_buffer* buffer)
528{
529 if (buffer != NULL)
530 {
531 vws_buffer_clear(buffer);
532 vws.free(buffer);
533 }
534}
535
536void vws_buffer_printf(vws_buffer* buffer, cstr format, ...)
537{
538 va_list args, args_copy;
539 va_start(args, format);
540
541 va_copy(args_copy, args);
542
543 // Determine the length of the formatted string
544 int length = vsnprintf(NULL, 0, format, args_copy);
545
546 va_end(args_copy);
547
548 // Allocate a buffer for the formatted string
549 char* data = malloc(length + 1);
550
551 // Format the string into the buffer
552 vsnprintf(data, length + 1, format, args);
553
554 vws_buffer_append(buffer, (ucstr)data, length);
555
556 // Cleanup
557 vws.free(data);
558 va_end(args);
559}
560
561void vws_buffer_append(vws_buffer* buffer, ucstr data, size_t size)
562{
563 if (buffer == NULL || data == NULL)
564 {
565 return;
566 }
567
568 size_t total_size = buffer->size + size;
569
570 if (total_size > buffer->allocated)
571 {
572 buffer->allocated = total_size * 1.5;
573
574 ucstr mem;
575 if (buffer->data == NULL)
576 {
577 mem = (ucstr)vws.malloc(buffer->allocated);
578 }
579 else
580 {
581 mem = (ucstr)vws.realloc(buffer->data, buffer->allocated);
582 }
583
584 buffer->data = mem;
585 }
586
587 memcpy(buffer->data + buffer->size, data, size);
588 buffer->size = total_size;
589}
590
591void vws_buffer_drain(vws_buffer* buffer, size_t size)
592{
593 if (buffer == NULL || buffer->data == NULL)
594 {
595 return;
596 }
597
598 if (size >= buffer->size)
599 {
600 // When size >= buffer->size, clear the whole buffer
601 vws_buffer_clear(buffer);
602 }
603 else
604 {
605 memmove(buffer->data, buffer->data + size, buffer->size - size);
606 buffer->size -= size;
607 buffer->data[buffer->size] = 0;
608 }
609}
610
611//------------------------------------------------------------------------------
612// Hashtable
613//------------------------------------------------------------------------------
614
615cstr vws_map_get(struct sc_map_str* map, cstr key)
616{
617 // See if we have an existing entry
618 cstr v = sc_map_get_str(map, key);
619
620 if (sc_map_found(map) == false)
621 {
622 return NULL;
623 }
624
625 return v;
626}
627
628void vws_map_set(struct sc_map_str* map, cstr key, cstr value)
629{
630 sc_map_get_str(map, key);
631
632 if (sc_map_found(map) == true)
633 {
634 // We have an existing entry
635 vws_map_remove(map, key);
636 }
637
638 sc_map_put_str(map, strdup(key), strdup(value));
639}
640
641void vws_map_remove(struct sc_map_str* map, cstr key)
642{
643 // See if we have an existing entry
644 cstr v = sc_map_get_str(map, key);
645
646 if (sc_map_found(map) == true)
647 {
648 vws.free(v);
649 }
650
651 sc_map_del_str(map, key);
652}
653
654void vws_map_clear(struct sc_map_str* map)
655{
656 cstr key; cstr value;
657 sc_map_foreach(map, key, value)
658 {
659 vws.free(key);
660 vws.free(value);
661 }
662
663 sc_map_clear_str(map);
664}
665
666//------------------------------------------------------------------------------
667// Key/value store
668//------------------------------------------------------------------------------
669
670int vws_kvs_comp(const void* a, const void* b)
671{
672 return strcmp(((vws_kvp*)a)->key, ((vws_kvp*)b)->key);
673}
674
675vws_kvs* vws_kvs_new(size_t size)
676{
677 vws_kvs* m = (vws_kvs*)malloc(sizeof(vws_kvs));
678
679 if (m)
680 {
681 m->array = (vws_kvp*)malloc(size * sizeof(vws_kvp));
682 m->used = 0;
683 m->size = size;
684 }
685
686 return m;
687}
688
689void vws_kvs_clear(vws_kvs* m)
690{
691 for (size_t i = 0; i < m->used; i++)
692 {
693 free((void*)m->array[i].key); // Free copied key
694 free(m->array[i].value.data); // Free copied data
695 }
696
697 m->used = 0;
698}
699
700void vws_kvs_free(vws_kvs* m)
701{
702 vws_kvs_clear(m);
703
704 free(m->array);
705 free(m);
706}
707
708size_t vws_kvs_size(vws_kvs* m)
709{
710 return m->used;
711}
712
713void vws_kvs_set(vws_kvs* m, const char* key, void* data, size_t size)
714{
715 if (m->used == m->size)
716 {
717 m->size *= 2;
718 m->array = (vws_kvp*)realloc(m->array, m->size * sizeof(vws_kvp));
719 }
720
721 // Copy the key
722 char* key_copy = strdup(key);
723
724 // Copy the data
725 void* data_copy = malloc(size);
726 memcpy(data_copy, data, size);
727
728 vws_kvp kvp = {key_copy, {data_copy, size}};
729
730 size_t i;
731 for (i = m->used; i > 0 && vws_kvs_comp(&kvp, &m->array[i-1]) < 0; i--)
732 {
733 m->array[i] = m->array[i - 1];
734 }
735
736 m->array[i] = kvp;
737 m->used++;
738}
739
740vws_value* vws_kvs_get(vws_kvs* m, const char* key)
741{
742 vws_kvp kvp = {key, {NULL, 0}};
743 vws_kvp* result = bsearch( &kvp,
744 m->array,
745 m->used,
746 sizeof(m->array[0]),
747 vws_kvs_comp );
748
749 if (result != NULL)
750 {
751 return &result->value;
752 }
753
754 return NULL;
755}
756
757void vws_kvs_set_cstring(vws_kvs* m, const char* key, const char* value)
758{
759 vws_kvs_set(m, key, (void*)value, strlen(value) + 1);
760}
761
762const char* vws_kvs_get_cstring(vws_kvs* m, const char* key)
763{
764 vws_value* value = vws_kvs_get(m, key);
765
766 if (value != NULL)
767 {
768 return (const char*)value->data;
769 }
770
771 return NULL;
772}
773
774int vws_kvs_remove(vws_kvs* m, const char* key)
775{
776 vws_kvp kvp = {key, {NULL, 0}};
777 vws_kvp* result = bsearch( &kvp,
778 m->array,
779 m->used,
780 sizeof(m->array[0]),
781 vws_kvs_comp );
782
783 if (result != NULL)
784 {
785 size_t index = result - m->array;
786
787 // Free the copied key and data
788 free((void*)m->array[index].key);
789 free(m->array[index].value.data);
790
791 // Shift elements to maintain order
792 if (index < m->used - 1)
793 {
794 memmove( &m->array[index],
795 &m->array[index + 1],
796 (m->used - index - 1) * sizeof(vws_kvp) );
797 }
798
799 m->used--;
800
801 // Key was found and removed
802 return 1;
803 }
804
805 // Key not found
806 return 0;
807}
808
809//------------------------------------------------------------------------------
810// UUID
811//------------------------------------------------------------------------------
812
813char* vws_generate_uuid()
814{
815 unsigned char uuid[16];
816
817 if (RAND_bytes(uuid, sizeof(uuid)) != 1)
818 {
819 return NULL;
820 }
821
822 // Set the version (4) and variant bits
823 uuid[6] = (uuid[6] & 0x0F) | 0x40;
824 uuid[8] = (uuid[8] & 0x3F) | 0x80;
825
826 // Format the UUID as a string
827 char* encoded_uuid = vws_base64_encode(uuid, sizeof(uuid));
828
829 if (encoded_uuid == NULL)
830 {
831 return NULL;
832 }
833
834 // Remove padding characters and dashes
835 size_t encoded_uuid_length = strlen(encoded_uuid);
836 for (size_t i = 0; i < encoded_uuid_length; i++)
837 {
838 if ( encoded_uuid[i] == '=' ||
839 encoded_uuid[i] == '\n' ||
840 encoded_uuid[i] == '\r' ||
841 encoded_uuid[i] == '-' )
842 {
843 encoded_uuid[i] = '_';
844 }
845 }
846
847 return encoded_uuid;
848}
849
850//------------------------------------------------------------------------------
851// Base 64
852//------------------------------------------------------------------------------
853
854char* vws_base64_encode(const unsigned char* data, size_t length)
855{
856 BIO* bio = BIO_new(BIO_s_mem());
857 BIO* base64 = BIO_new(BIO_f_base64());
858
859 BIO_set_flags(base64, BIO_FLAGS_BASE64_NO_NL);
860 BIO_push(base64, bio);
861 BIO_write(base64, data, length);
862 BIO_flush(base64);
863
864 BUF_MEM* ptr;
865 BIO_get_mem_ptr(base64, &ptr);
866 size_t output_length = ptr->length;
867 char* encoded_data = (char*)vws.malloc(output_length + 1);
868
869 memcpy(encoded_data, ptr->data, output_length);
870 encoded_data[output_length] = '\0';
871
872 BIO_free_all(base64);
873
874 return encoded_data;
875}
876
877unsigned char* vws_base64_decode(const char* data, size_t* size)
878{
879 BIO* bio = BIO_new_mem_buf(data, -1);
880 BIO* base64 = BIO_new(BIO_f_base64());
881
882 BIO_set_flags(base64, BIO_FLAGS_BASE64_NO_NL);
883 BIO_push(base64, bio);
884
885 // Determine the size of the decoded data
886 size_t encoded_length = strlen(data);
887 size_t decoded_length = (encoded_length * 3) / 4; // Rough estimate
888 unsigned char* decoded_data = (unsigned char*)vws.malloc(decoded_length);
889
890 // Decode the base64 data
891 *size = BIO_read(base64, decoded_data, encoded_length);
892
893 BIO_free_all(base64);
894
895 return decoded_data;
896}
897
898//------------------------------------------------------------------------------
899// Utilities
900//------------------------------------------------------------------------------
901
902/**
903 * @brief Sleeps for the specified number of milliseconds.
904 *
905 * This function provides a platform-independent way to sleep for a specific
906 * duration in milliseconds. The behavior is similar to the standard `sleep`
907 * function, but with millisecond precision.
908 *
909 * @param ms The number of milliseconds to sleep.
910 *
911 * @note This function may not be available on all platforms. Make sure to
912 * include the necessary headers and handle any compilation errors or
913 * warnings specific to your environment.
914 */
915void vws_msleep(unsigned int ms)
916{
917#if defined(__windows__) || defined(_WIN64)
918
919 Sleep(ms);
920
921#elif defined(__linux__)
922
923 usleep(ms * 1000);
924
925#elif defined(__bsd__)
926
927 usleep(ms * 1000);
928
929#elif defined(__sunos__)
930
931 struct timespec ts;
932 ts.tv_sec = ms / 1000;
933 ts.tv_nsec = (ms % 1000) * 1000000;
934 nanosleep(&ts, NULL);
935
936#else
937
938#error "Unsupported platform. msleep is not implemented for this platform."
939
940#endif
941}
942
943uint8_t vws_is_flag(const uint64_t* flags, uint64_t flag)
944{
945 return (*flags & flag) == flag;
946}
947
948void vws_set_flag(uint64_t* flags, uint64_t flag)
949{
950 *flags |= flag;
951}
952
953void vws_clear_flag(uint64_t* flags, uint64_t flag)
954{
955 *flags &= ~flag;
956}
957
958char* vws_file_path(const char* root, const char* filename)
959{
960 // Initial guess for the required buffer size
961
962 // +2 for slash and null terminator
963 size_t size = strlen(root) + strlen(filename) + 2;
964 char* path = vws.malloc(size);
965
966 if (path == NULL)
967 {
968 fprintf(stderr, "Memory allocation failed\n");
969 return NULL;
970 }
971
972 // Attempt to write to the buffer
973 size_t written = snprintf(path, size, "%s/%s", root, filename);
974
975 // Check if the buffer was large enough
976 if (written >= size)
977 {
978 // Buffer was too small, allocate again with the correct size
979 size = written + 1; // +1 for the null terminator
980 char* temp = vws.realloc(path, size);
981
982 if (temp == NULL)
983 {
984 fprintf(stderr, "Memory reallocation failed\n");
985 free(path);
986 return NULL;
987 }
988
989 path = temp;
990
991 // Write again
992 snprintf(path, size, "%s/%s", root, filename);
993 }
994
995 return path;
996}
997
998bool vws_cstr_to_long(cstr str, long* value)
999{
1000 char* endptr;
1001
1002 // To distinguish success/failure after call
1003 errno = 0;
1004
1005 if (str == NULL || *str == '\0' || isspace(*str))
1006 {
1007 // String is empty or whitespace only
1008 return false;
1009 }
1010
1011 *value = strtol(str, &endptr, 10); // Base 10 conversion
1012
1013 // Check for conversion errors or incomplete conversion
1014 if (errno == ERANGE)
1015 {
1016 // Value out of range for long
1017 return false;
1018 }
1019 if (*endptr != '\0' || endptr == str)
1020 {
1021 // Whole string wasn’t converted
1022 return false;
1023 }
1024
1025 // Successful conversion
1026 return true;
1027}
1028
1029