FreeRDP
Loading...
Searching...
No Matches
winpr/libwinpr/utils/cmdline.c
1
20#include <winpr/config.h>
21
22#include <winpr/crt.h>
23#include <winpr/assert.h>
24#include <winpr/cmdline.h>
25
26#include "../log.h"
27
28#define TAG WINPR_TAG("commandline")
29
50#if !defined(WITH_DEBUG_UTILS_CMDLINE_DUMP)
51static const char censoredmessage[] =
52 "<censored: build with -DWITH_DEBUG_UTILS_CMDLINE_DUMP=ON for details>";
53#endif
54
55#define log_error(flags, msg, index, arg) \
56 log_error_((flags), (msg), (index), (arg), __FILE__, __func__, __LINE__)
57static void log_error_(DWORD flags, LPCSTR message, int index, WINPR_ATTR_UNUSED LPCSTR argv,
58 const char* file, const char* fkt, size_t line)
59{
60 if ((flags & COMMAND_LINE_SILENCE_PARSER) == 0)
61 {
62 const DWORD level = WLOG_ERROR;
63 static wLog* log = nullptr;
64 if (!log)
65 log = WLog_Get(TAG);
66
67 if (!WLog_IsLevelActive(log, level))
68 return;
69
70 WLog_PrintTextMessage(log, level, line, file, fkt, "Failed at index %d [%s]: %s", index,
71#if defined(WITH_DEBUG_UTILS_CMDLINE_DUMP)
72 argv
73#else
74 censoredmessage
75#endif
76 ,
77 message);
78 }
79}
80
81#define log_comma_error(msg, arg) log_comma_error_((msg), (arg), __FILE__, __func__, __LINE__)
82static void log_comma_error_(const char* message, WINPR_ATTR_UNUSED const char* argument,
83 const char* file, const char* fkt, size_t line)
84{
85 const DWORD level = WLOG_ERROR;
86 static wLog* log = nullptr;
87 if (!log)
88 log = WLog_Get(TAG);
89
90 if (!WLog_IsLevelActive(log, level))
91 return;
92
93 WLog_PrintTextMessage(log, level, line, file, fkt, "%s [%s]", message,
94#if defined(WITH_DEBUG_UTILS_CMDLINE_DUMP)
95 argument
96#else
97 censoredmessage
98#endif
99 );
100}
101
102int CommandLineParseArgumentsA(int argc, LPSTR* argv, COMMAND_LINE_ARGUMENT_A* options, DWORD flags,
103 void* context, COMMAND_LINE_PRE_FILTER_FN_A preFilter,
104 COMMAND_LINE_POST_FILTER_FN_A postFilter)
105{
106 int status = 0;
107 int count = 0;
108 BOOL notescaped = FALSE;
109 const char* sigil = nullptr;
110 size_t sigil_length = 0;
111 char* keyword = nullptr;
112 size_t keyword_index = 0;
113 char* separator = nullptr;
114 char* value = nullptr;
115 int toggle = 0;
116
117 if (!argv)
118 return status;
119
120 if (argc == 1)
121 {
122 if (flags & COMMAND_LINE_IGN_UNKNOWN_KEYWORD)
123 status = 0;
124 else
125 status = COMMAND_LINE_STATUS_PRINT_HELP;
126
127 return status;
128 }
129
130 for (int i = 1; i < argc; i++)
131 {
132 size_t keyword_length = 0;
133 BOOL found = FALSE;
134 BOOL escaped = TRUE;
135
136 if (preFilter)
137 {
138 count = preFilter(context, i, argc, argv);
139
140 if (count < 0)
141 {
142 log_error(flags, "PreFilter rule could not be applied", i, argv[i]);
143 status = COMMAND_LINE_ERROR;
144 return status;
145 }
146
147 if (count > 0)
148 {
149 i += (count - 1);
150 continue;
151 }
152 }
153
154 sigil = argv[i];
155 size_t length = strlen(argv[i]);
156
157 if ((sigil[0] == '/') && (flags & COMMAND_LINE_SIGIL_SLASH))
158 {
159 sigil_length = 1;
160 }
161 else if ((sigil[0] == '-') && (flags & COMMAND_LINE_SIGIL_DASH))
162 {
163 sigil_length = 1;
164
165 if (length > 2)
166 {
167 if ((sigil[1] == '-') && (flags & COMMAND_LINE_SIGIL_DOUBLE_DASH))
168 sigil_length = 2;
169 }
170 }
171 else if ((sigil[0] == '+') && (flags & COMMAND_LINE_SIGIL_PLUS_MINUS))
172 {
173 sigil_length = 1;
174 }
175 else if ((sigil[0] == '-') && (flags & COMMAND_LINE_SIGIL_PLUS_MINUS))
176 {
177 sigil_length = 1;
178 }
179 else if (flags & COMMAND_LINE_SIGIL_NONE)
180 {
181 sigil_length = 0;
182 }
183 else if (flags & COMMAND_LINE_SIGIL_NOT_ESCAPED)
184 {
185 if (notescaped)
186 {
187 log_error(flags, "Unescaped sigil", i, argv[i]);
188 return COMMAND_LINE_ERROR;
189 }
190
191 sigil_length = 0;
192 escaped = FALSE;
193 notescaped = TRUE;
194 }
195 else
196 {
197 log_error(flags, "Invalid sigil", i, argv[i]);
198 return COMMAND_LINE_ERROR;
199 }
200
201 if ((sigil_length > 0) || (flags & COMMAND_LINE_SIGIL_NONE) ||
202 (flags & COMMAND_LINE_SIGIL_NOT_ESCAPED))
203 {
204 if (length < (sigil_length + 1))
205 {
206 if ((flags & COMMAND_LINE_IGN_UNKNOWN_KEYWORD))
207 continue;
208
209 log_error(flags, "Unexpected keyword", i, argv[i]);
210 return COMMAND_LINE_ERROR_NO_KEYWORD;
211 }
212
213 keyword_index = sigil_length;
214 keyword = &argv[i][keyword_index];
215 toggle = -1;
216
217 if (flags & COMMAND_LINE_SIGIL_ENABLE_DISABLE)
218 {
219 if (strncmp(keyword, "enable-", 7) == 0)
220 {
221 toggle = TRUE;
222 keyword_index += 7;
223 keyword = &argv[i][keyword_index];
224 }
225 else if (strncmp(keyword, "disable-", 8) == 0)
226 {
227 toggle = FALSE;
228 keyword_index += 8;
229 keyword = &argv[i][keyword_index];
230 }
231 }
232
233 separator = nullptr;
234
235 if ((flags & COMMAND_LINE_SEPARATOR_COLON) && (!separator))
236 separator = strchr(keyword, ':');
237
238 if ((flags & COMMAND_LINE_SEPARATOR_EQUAL) && (!separator))
239 separator = strchr(keyword, '=');
240
241 if (separator)
242 {
243 SSIZE_T separator_index = (separator - argv[i]);
244 SSIZE_T value_index = separator_index + 1;
245 keyword_length = WINPR_ASSERTING_INT_CAST(size_t, (separator - keyword));
246 value = &argv[i][value_index];
247 }
248 else
249 {
250 if (length < keyword_index)
251 {
252 log_error(flags, "Argument required", i, argv[i]);
253 return COMMAND_LINE_ERROR;
254 }
255
256 keyword_length = length - keyword_index;
257 value = nullptr;
258 }
259
260 if (!escaped)
261 continue;
262
263 for (size_t j = 0; options[j].Name != nullptr; j++)
264 {
265 COMMAND_LINE_ARGUMENT_A* cur = &options[j];
266 BOOL match = FALSE;
267
268 if (strncmp(cur->Name, keyword, keyword_length) == 0)
269 {
270 if (strlen(cur->Name) == keyword_length)
271 match = TRUE;
272 }
273
274 if ((!match) && (cur->Alias != nullptr))
275 {
276 if (strncmp(cur->Alias, keyword, keyword_length) == 0)
277 {
278 if (strlen(cur->Alias) == keyword_length)
279 match = TRUE;
280 }
281 }
282
283 if (!match)
284 continue;
285
286 found = match;
287 cur->Index = i;
288
289 if ((flags & COMMAND_LINE_SEPARATOR_SPACE) && ((i + 1) < argc))
290 {
291 BOOL argument = 0;
292 int value_present = 1;
293
294 if (flags & COMMAND_LINE_SIGIL_DASH)
295 {
296 if (strncmp(argv[i + 1], "-", 1) == 0)
297 value_present = 0;
298 }
299
300 if (flags & COMMAND_LINE_SIGIL_DOUBLE_DASH)
301 {
302 if (strncmp(argv[i + 1], "--", 2) == 0)
303 value_present = 0;
304 }
305
306 if (flags & COMMAND_LINE_SIGIL_SLASH)
307 {
308 if (strncmp(argv[i + 1], "/", 1) == 0)
309 value_present = 0;
310 }
311
312 argument = (((cur->Flags & COMMAND_LINE_VALUE_REQUIRED) != 0) ||
313 ((cur->Flags & COMMAND_LINE_VALUE_OPTIONAL) != 0));
314
315 if (value_present && argument)
316 {
317 i++;
318 value = argv[i];
319 }
320 else if (!value_present && (cur->Flags & COMMAND_LINE_VALUE_OPTIONAL))
321 {
322 value = nullptr;
323 }
324 else if (!value_present && argument)
325 {
326 log_error(flags, "Argument required", i, argv[i]);
327 return COMMAND_LINE_ERROR;
328 }
329 }
330
331 if (!(flags & COMMAND_LINE_SEPARATOR_SPACE))
332 {
333 if (value && (cur->Flags & COMMAND_LINE_VALUE_FLAG))
334 {
335 log_error(flags, "Unexpected value", i, argv[i]);
336 return COMMAND_LINE_ERROR_UNEXPECTED_VALUE;
337 }
338 }
339 else
340 {
341 if (value && (cur->Flags & COMMAND_LINE_VALUE_FLAG))
342 {
343 i--;
344 value = nullptr;
345 }
346 }
347
348 if (!value && (cur->Flags & COMMAND_LINE_VALUE_REQUIRED))
349 {
350 log_error(flags, "Missing value", i, argv[i]);
351 status = COMMAND_LINE_ERROR_MISSING_VALUE;
352 return status;
353 }
354
355 cur->Flags |= COMMAND_LINE_ARGUMENT_PRESENT;
356
357 if (value)
358 {
359 if (!(cur->Flags & (COMMAND_LINE_VALUE_OPTIONAL | COMMAND_LINE_VALUE_REQUIRED)))
360 {
361 log_error(flags, "Unexpected value", i, argv[i]);
362 return COMMAND_LINE_ERROR_UNEXPECTED_VALUE;
363 }
364
365 cur->Value = value;
366 cur->Flags |= COMMAND_LINE_VALUE_PRESENT;
367 }
368 else
369 {
370 if (cur->Flags & COMMAND_LINE_VALUE_FLAG)
371 {
372 cur->Value = (LPSTR)1;
373 cur->Flags |= COMMAND_LINE_VALUE_PRESENT;
374 }
375 else if (cur->Flags & COMMAND_LINE_VALUE_BOOL)
376 {
377 if (flags & COMMAND_LINE_SIGIL_ENABLE_DISABLE)
378 {
379 if (toggle == -1)
380 cur->Value = BoolValueTrue;
381 else if (!toggle)
382 cur->Value = BoolValueFalse;
383 else
384 cur->Value = BoolValueTrue;
385 }
386 else
387 {
388 if (sigil[0] == '+')
389 cur->Value = BoolValueTrue;
390 else if (sigil[0] == '-')
391 cur->Value = BoolValueFalse;
392 else
393 cur->Value = BoolValueTrue;
394 }
395
396 cur->Flags |= COMMAND_LINE_VALUE_PRESENT;
397 }
398 }
399
400 if (postFilter)
401 {
402 count = postFilter(context, &options[j]);
403
404 if (count < 0)
405 {
406 log_error(flags, "PostFilter rule could not be applied", i, argv[i]);
407 status = COMMAND_LINE_ERROR;
408 return status;
409 }
410 }
411
412 if (cur->Flags & COMMAND_LINE_PRINT)
413 return COMMAND_LINE_STATUS_PRINT;
414 else if (cur->Flags & COMMAND_LINE_PRINT_HELP)
415 return COMMAND_LINE_STATUS_PRINT_HELP;
416 else if (cur->Flags & COMMAND_LINE_PRINT_VERSION)
417 return COMMAND_LINE_STATUS_PRINT_VERSION;
418 else if (cur->Flags & COMMAND_LINE_PRINT_BUILDCONFIG)
419 return COMMAND_LINE_STATUS_PRINT_BUILDCONFIG;
420 }
421
422 if (!found && (flags & COMMAND_LINE_IGN_UNKNOWN_KEYWORD) == 0)
423 {
424 log_error(flags, "Unexpected keyword", i, argv[i]);
425 return COMMAND_LINE_ERROR_NO_KEYWORD;
426 }
427 }
428 }
429
430 return status;
431}
432
433int CommandLineParseArgumentsW(WINPR_ATTR_UNUSED int argc, WINPR_ATTR_UNUSED LPWSTR* argv,
434 WINPR_ATTR_UNUSED COMMAND_LINE_ARGUMENT_W* options,
435 WINPR_ATTR_UNUSED DWORD flags, WINPR_ATTR_UNUSED void* context,
436 WINPR_ATTR_UNUSED COMMAND_LINE_PRE_FILTER_FN_W preFilter,
437 WINPR_ATTR_UNUSED COMMAND_LINE_POST_FILTER_FN_W postFilter)
438{
439 WLog_ERR("TODO", "TODO: implement");
440 return 0;
441}
442
443int CommandLineClearArgumentsA(COMMAND_LINE_ARGUMENT_A* options)
444{
445 for (size_t i = 0; options[i].Name != nullptr; i++)
446 {
447 options[i].Flags &= COMMAND_LINE_INPUT_FLAG_MASK;
448 options[i].Value = nullptr;
449 }
450
451 return 0;
452}
453
454int CommandLineClearArgumentsW(COMMAND_LINE_ARGUMENT_W* options)
455{
456 for (int i = 0; options[i].Name != nullptr; i++)
457 {
458 options[i].Flags &= COMMAND_LINE_INPUT_FLAG_MASK;
459 options[i].Value = nullptr;
460 }
461
462 return 0;
463}
464
465const COMMAND_LINE_ARGUMENT_A* CommandLineFindArgumentA(const COMMAND_LINE_ARGUMENT_A* options,
466 LPCSTR Name)
467{
468 WINPR_ASSERT(options);
469 WINPR_ASSERT(Name);
470
471 for (size_t i = 0; options[i].Name != nullptr; i++)
472 {
473 if (strcmp(options[i].Name, Name) == 0)
474 return &options[i];
475
476 if (options[i].Alias != nullptr)
477 {
478 if (strcmp(options[i].Alias, Name) == 0)
479 return &options[i];
480 }
481 }
482
483 return nullptr;
484}
485
486const COMMAND_LINE_ARGUMENT_W* CommandLineFindArgumentW(const COMMAND_LINE_ARGUMENT_W* options,
487 LPCWSTR Name)
488{
489 WINPR_ASSERT(options);
490 WINPR_ASSERT(Name);
491
492 for (size_t i = 0; options[i].Name != nullptr; i++)
493 {
494 if (_wcscmp(options[i].Name, Name) == 0)
495 return &options[i];
496
497 if (options[i].Alias != nullptr)
498 {
499 if (_wcscmp(options[i].Alias, Name) == 0)
500 return &options[i];
501 }
502 }
503
504 return nullptr;
505}
506
507const COMMAND_LINE_ARGUMENT_A* CommandLineFindNextArgumentA(const COMMAND_LINE_ARGUMENT_A* argument)
508{
509 const COMMAND_LINE_ARGUMENT_A* nextArgument = nullptr;
510
511 if (!argument || !argument->Name)
512 return nullptr;
513
514 nextArgument = &argument[1];
515
516 if (nextArgument->Name == nullptr)
517 return nullptr;
518
519 return nextArgument;
520}
521
522static int is_quoted(char c)
523{
524 switch (c)
525 {
526 case '"':
527 return 1;
528 case '\'':
529 return -1;
530 default:
531 return 0;
532 }
533}
534
535static size_t get_element_count(const char* list, BOOL* failed, BOOL fullquoted)
536{
537 size_t count = 0;
538 int quoted = 0;
539 bool escaped = false;
540 BOOL finished = FALSE;
541 BOOL first = TRUE;
542 const char* it = list;
543
544 if (!list)
545 return 0;
546 if (strlen(list) == 0)
547 return 0;
548
549 while (!finished)
550 {
551 BOOL nextFirst = FALSE;
552
553 const char cur = *it++;
554
555 /* Ignore the symbol that was escaped. */
556 if (escaped)
557 {
558 escaped = false;
559 continue;
560 }
561
562 switch (cur)
563 {
564 case '\0':
565 if (quoted != 0)
566 {
567 log_comma_error("Invalid argument (missing closing quote)", list);
568 *failed = TRUE;
569 return 0;
570 }
571 finished = TRUE;
572 break;
573 case '\\':
574 if (!escaped)
575 {
576 escaped = true;
577 continue;
578 }
579 break;
580 case '\'':
581 case '"':
582 if (!fullquoted)
583 {
584 int now = is_quoted(cur) && !escaped;
585 if (now == quoted)
586 quoted = 0;
587 else if (quoted == 0)
588 quoted = now;
589 }
590 break;
591 case ',':
592 if (first)
593 {
594 log_comma_error("Invalid argument (empty list elements)", list);
595 *failed = TRUE;
596 return 0;
597 }
598 if (quoted == 0)
599 {
600 nextFirst = TRUE;
601 count++;
602 }
603 break;
604 default:
605 break;
606 }
607
608 first = nextFirst;
609 }
610 return count + 1;
611}
612
613static char* get_next_comma(char* string, BOOL fullquoted)
614{
615 const char* log = string;
616 int quoted = 0;
617 bool first = true;
618 bool escaped = false;
619
620 WINPR_ASSERT(string);
621
622 while (TRUE)
623 {
624 char* last = string;
625 const char cur = *string++;
626 if (escaped)
627 {
628 escaped = false;
629 continue;
630 }
631
632 switch (cur)
633 {
634 case '\0':
635 if (quoted != 0)
636 log_comma_error("Invalid quoted argument", log);
637 return nullptr;
638
639 case '\\':
640 if (!escaped)
641 {
642 escaped = true;
643 continue;
644 }
645 break;
646 case '\'':
647 case '"':
648 if (!fullquoted)
649 {
650 int now = is_quoted(cur);
651 if ((quoted == 0) && !first)
652 {
653 log_comma_error("Invalid quoted argument", log);
654 return nullptr;
655 }
656 if (now == quoted)
657 quoted = 0;
658 else if (quoted == 0)
659 quoted = now;
660 }
661 break;
662
663 case ',':
664 if (first)
665 {
666 log_comma_error("Invalid argument (empty list elements)", log);
667 return nullptr;
668 }
669 if (quoted == 0)
670 return last;
671 break;
672
673 default:
674 break;
675 }
676 first = FALSE;
677 }
678}
679
680static BOOL is_valid_fullquoted(const char* string)
681{
682 char cur = '\0';
683 char last = '\0';
684 const char quote = *string++;
685
686 /* We did not start with a quote. */
687 if (is_quoted(quote) == 0)
688 return FALSE;
689
690 while ((cur = *string++) != '\0')
691 {
692 /* A quote is found. */
693 if (cur == quote)
694 {
695 /* If the quote was escaped, it is valid. */
696 if (last != '\\')
697 {
698 /* Only allow unescaped quote as last character in string. */
699 if (*string != '\0')
700 return FALSE;
701 }
702 /* If the last quote in the string is escaped, it is wrong. */
703 else if (*string != '\0')
704 return FALSE;
705 }
706 last = cur;
707 }
708
709 /* The string did not terminate with the same quote as it started. */
710 return (last == quote);
711}
712
713char** CommandLineParseCommaSeparatedValuesEx(const char* name, const char* list, size_t* count)
714{
715 char** p = nullptr;
716 char* str = nullptr;
717 size_t nArgs = 0;
718 size_t prefix = 0;
719 size_t len = 0;
720 size_t namelen = 0;
721 BOOL failed = FALSE;
722 char* copy = nullptr;
723 char* unquoted = nullptr;
724 BOOL fullquoted = FALSE;
725
726 BOOL success = FALSE;
727 if (count == nullptr)
728 goto fail;
729
730 *count = 0;
731 if (list)
732 {
733 int start = 0;
734 int end = 0;
735 unquoted = copy = _strdup(list);
736 if (!copy)
737 goto fail;
738
739 len = strlen(unquoted);
740 if (len > 0)
741 {
742 start = is_quoted(unquoted[0]);
743 end = is_quoted(unquoted[len - 1]);
744
745 if ((start != 0) && (end != 0))
746 {
747 if (start != end)
748 {
749 log_comma_error("Invalid argument (quote mismatch)", list);
750 goto fail;
751 }
752 if (!is_valid_fullquoted(unquoted))
753 goto fail;
754 unquoted[len - 1] = '\0';
755 unquoted++;
756 len -= 2;
757 fullquoted = TRUE;
758 }
759 }
760 }
761
762 *count = get_element_count(unquoted, &failed, fullquoted);
763 if (failed)
764 goto fail;
765
766 if (*count == 0)
767 {
768 if (!name)
769 goto fail;
770 else
771 {
772 size_t clen = strlen(name);
773 p = (char**)calloc(2UL + clen, sizeof(char*));
774
775 if (p)
776 {
777 char* dst = (char*)&p[1];
778 p[0] = dst;
779 (void)sprintf_s(dst, clen + 1, "%s", name);
780 *count = 1;
781 success = TRUE;
782 goto fail;
783 }
784 }
785 }
786
787 nArgs = *count;
788
789 if (name)
790 nArgs++;
791
792 prefix = (nArgs + 1UL) * sizeof(char*);
793 if (name)
794 namelen = strlen(name);
795 p = (char**)calloc(len + prefix + 1 + namelen + 1, sizeof(char*));
796
797 if (!p)
798 goto fail;
799
800 str = &((char*)p)[prefix];
801 memcpy(str, unquoted, len);
802
803 if (name)
804 {
805 char* namestr = &((char*)p)[prefix + len + 1];
806 memcpy(namestr, name, namelen);
807
808 p[0] = namestr;
809 }
810
811 for (size_t index = name ? 1 : 0; index < nArgs; index++)
812 {
813 char* ptr = str;
814 const int quote = is_quoted(*ptr);
815 char* comma = get_next_comma(str, fullquoted);
816
817 if ((quote != 0) && !fullquoted)
818 ptr++;
819
820 p[index] = ptr;
821
822 if (comma)
823 {
824 char* last = comma - 1;
825 const int lastQuote = is_quoted(*last);
826
827 if (!fullquoted)
828 {
829 if (lastQuote != quote)
830 {
831 log_comma_error("Invalid argument (quote mismatch)", list);
832 goto fail;
833 }
834 else if (lastQuote != 0)
835 *last = '\0';
836 }
837 *comma = '\0';
838
839 str = comma + 1;
840 }
841 else if (quote)
842 {
843 char* end = strrchr(ptr, '"');
844 if (!end)
845 goto fail;
846 *end = '\0';
847 }
848 }
849
850 *count = nArgs;
851 success = TRUE;
852fail:
853 free(copy);
854 if (!success)
855 {
856 if (count)
857 *count = 0;
858 free((void*)p);
859 return nullptr;
860 }
861 return p;
862}
863
864char** CommandLineParseCommaSeparatedValues(const char* list, size_t* count)
865{
866 return CommandLineParseCommaSeparatedValuesEx(nullptr, list, count);
867}
868
869char* CommandLineToCommaSeparatedValues(int argc, char* argv[])
870{
871 return CommandLineToCommaSeparatedValuesEx(argc, argv, nullptr, 0);
872}
873
874static const char* filtered(const char* arg, const char* filters[], size_t number)
875{
876 if (number == 0)
877 return arg;
878 for (size_t x = 0; x < number; x++)
879 {
880 const char* filter = filters[x];
881 size_t len = strlen(filter);
882 if (_strnicmp(arg, filter, len) == 0)
883 return &arg[len];
884 }
885 return nullptr;
886}
887
888char* CommandLineToCommaSeparatedValuesEx(int argc, char* argv[], const char* filters[],
889 size_t number)
890{
891 char* str = nullptr;
892 size_t offset = 0;
893 size_t size = WINPR_ASSERTING_INT_CAST(size_t, argc) + 1;
894 if ((argc <= 0) || !argv)
895 return nullptr;
896
897 for (int x = 0; x < argc; x++)
898 size += strlen(argv[x]);
899
900 str = calloc(size, sizeof(char));
901 if (!str)
902 return nullptr;
903 for (int x = 0; x < argc; x++)
904 {
905 int rc = 0;
906 const char* arg = filtered(argv[x], filters, number);
907 if (!arg)
908 continue;
909 rc = _snprintf(&str[offset], size - offset, "%s,", arg);
910 if (rc <= 0)
911 {
912 free(str);
913 return nullptr;
914 }
915 offset += (size_t)rc;
916 }
917 if (offset > 0)
918 str[offset - 1] = '\0';
919 return str;
920}
921
922void CommandLineParserFree(char** ptr)
923{
924 union
925 {
926 char* p;
927 char** pp;
928 } uptr;
929 uptr.pp = ptr;
930 free(uptr.p);
931}