1 /*
2  * Copyright (c) 2006-2025, RT-Thread Development Team
3  *
4  * SPDX-License-Identifier: Apache-2.0
5  *
6  * Change Logs:
7  * Date           Author              Notes
8  * 2024-11-24     Meco Man            port to klibc
9  * 2025-01-04     Meco Man            using Phoenix version
10  */
11 
12 /*
13  * Copyright 2017, 2022-2023 Phoenix Systems
14  * Author: Adrian Kepka, Gerard Swiderski
15  */
16 
17 #include <rtthread.h>
18 #include <stdlib.h> /* for strtod */
19 #include <ctype.h> /* for isspace */
20 #include <stdarg.h> /* for va_list */
21 
22 #define FORMAT_NIL_STR     "(nil)"
23 #define FORMAT_NIL_STR_LEN (sizeof(FORMAT_NIL_STR) - 1)
24 
25 #define LONG       0x01   /* l: long or double */
26 #define LONGDOUBLE 0x02   /* L: long double */
27 #define SHORT      0x04   /* h: short */
28 #define SUPPRESS   0x08   /* *: suppress assignment */
29 #define POINTER    0x10   /* p: void * (as hex) */
30 #define NOSKIP     0x20   /* [ or c: do not skip blanks */
31 #define LONGLONG   0x400  /* ll: long long (+ deprecated q: quad) */
32 #define PTRDIFF    0x800  /* t: ptrdiff_t */
33 #define SHORTSHORT 0x4000 /* hh: char */
34 #define UNSIGNED   0x8000 /* %[oupxX] conversions */
35 
36 #define SIGNOK     0x40  /* +/- is (still) legal */
37 #define NDIGITS    0x80  /* no digits detected */
38 #define PFXOK      0x100 /* 0x prefix is (still) legal */
39 #define NZDIGITS   0x200 /* no zero digits detected */
40 
41 #define CT_CHAR    0 /* %c conversion */
42 #define CT_CCL     1 /* %[...] conversion */
43 #define CT_STRING  2 /* %s conversion */
44 #define CT_INT     3 /* %[dioupxX] conversion */
45 #define CT_FLOAT   4 /* %[aefgAEFG] conversion */
46 #define CT_NONE    5 /* No conversion (ex. %n) */
47 
__sccl(char * tab,const unsigned char * fmt)48 static const unsigned char *__sccl(char *tab, const unsigned char *fmt)
49 {
50     int c, n, v;
51 
52     c = *fmt++;
53     if (c == '^') {
54         v = 1;
55         c = *fmt++;
56     }
57     else {
58         v = 0;
59     }
60 
61     rt_memset(tab, (uint8_t)v, 256);
62 
63     if (c == 0) {
64         return (fmt - 1);
65     }
66 
67     v = 1 - v;
68     tab[c] = v;
69     for (;;) {
70         n = *fmt++;
71         switch (n) {
72 
73             case 0:
74                 return (fmt - 1);
75 
76             case '-':
77                 n = *fmt;
78                 if ((n == ']') || (n < c)) {
79                     c = '-';
80                     tab[c] = v;
81                     break;
82                 }
83                 fmt++;
84 
85                 do {
86                     tab[++c] = v;
87                 } while (c < n);
88                 c = n;
89                 break;
90 
91             case ']':
92                 return (fmt);
93 
94             default:
95                 c = n;
96                 tab[c] = v;
97                 break;
98         }
99     }
100 }
101 
scanf_parse(char * ccltab,const char * inp,int * inr,char const * fmt0,va_list ap)102 static int scanf_parse(char *ccltab, const char *inp, int *inr, char const *fmt0, va_list ap)
103 {
104     const unsigned char *fmt = (const unsigned char *)fmt0;
105     int c, n, flags, nassigned, nconversions, nread, base;
106     rt_size_t width;
107     char *p, *p0;
108     char buf[32];
109 
110     static const short basefix[17] = { 10, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16 };
111 
112     *inr = rt_strlen(inp);
113 
114     nassigned = 0;
115     nconversions = 0;
116     nread = 0;
117     base = 0;
118     for (;;) {
119         int convType = CT_NONE;
120         c = *fmt++;
121         if (c == '\0') {
122             return (nassigned);
123         }
124 
125         if (isspace(c) != 0) {
126             while ((*inr > 0) && (isspace((int)*inp) != 0)) {
127                 nread++;
128                 (*inr)--;
129                 inp++;
130             }
131             continue;
132         }
133 
134         if (c != '%') {
135             if (*inr <= 0) {
136                 return (nconversions != 0 ? nassigned : -1);
137             }
138 
139             if (*inp != c) {
140                 return nassigned;
141             }
142 
143             nread++;
144             (*inr)--;
145             inp++;
146             continue;
147         }
148 
149         width = 0;
150         flags = 0;
151         for (;;) {
152             c = *fmt++;
153             if (c == '\0') {
154                 return nassigned;
155             }
156 
157             if (c == '%') {
158                 if (*inr <= 0) {
159                     return (nconversions != 0 ? nassigned : -1);
160                 }
161 
162                 if (*inp != c) {
163                     return nassigned;
164                 }
165 
166                 nread++;
167                 (*inr)--;
168                 inp++;
169                 break;
170             }
171 
172             switch (c) {
173                 case '*':
174                     flags |= SUPPRESS;
175                     continue;
176 
177                 case 'l':
178                     if ((flags & LONG) != 0) {
179                         flags &= ~LONG;
180                         flags |= LONGLONG;
181                     }
182                     else {
183                         flags |= LONG;
184                     }
185                     continue;
186 
187                 case 'L':
188                     flags |= LONGDOUBLE;
189                     continue;
190 
191                 case 'q':
192                 case 'j':
193                     flags |= LONGLONG;
194                     continue;
195 
196                 case 't':
197                     flags |= PTRDIFF;
198                     continue;
199 
200                 case 'z':
201                     if (sizeof(rt_size_t) == sizeof(uint64_t)) {
202                         flags |= LONGLONG;
203                     }
204                     continue;
205 
206                 case 'h':
207                     if ((flags & SHORT) != 0) {
208                         flags &= ~SHORT;
209                         flags |= SHORTSHORT;
210                     }
211                     else {
212                         flags |= SHORT;
213                     }
214                     continue;
215 
216                 case '0':
217                 case '1':
218                 case '2':
219                 case '3':
220                 case '4':
221                 case '5':
222                 case '6':
223                 case '7':
224                 case '8':
225                 case '9':
226                     width = width * 10 + c - '0';
227                     continue;
228                 default:
229                     break;
230             }
231 
232             /* conversions */
233             switch (c) {
234                 case 'd':
235                     convType = CT_INT;
236                     base = 10;
237                     break;
238 
239                 case 'i':
240                     convType = CT_INT;
241                     base = 0;
242                     break;
243 
244                 case 'o':
245                     convType = CT_INT;
246                     flags |= UNSIGNED;
247                     base = 8;
248                     break;
249 
250                 case 'u':
251                     convType = CT_INT;
252                     flags |= UNSIGNED;
253                     base = 10;
254                     break;
255 
256                 case 'X':
257                 case 'x':
258                     flags |= PFXOK; /* enable 0x prefixing */
259                     convType = CT_INT;
260                     flags |= UNSIGNED;
261                     base = 16;
262                     break;
263 
264                 case 'A':
265                 case 'E':
266                 case 'F':
267                 case 'G':
268                 case 'a':
269                 case 'e':
270                 case 'f':
271                 case 'g':
272                     convType = CT_FLOAT;
273                     break;
274 
275 
276                 case 's':
277                     convType = CT_STRING;
278                     break;
279 
280                 case '[':
281                     fmt = __sccl(ccltab, fmt);
282                     flags |= NOSKIP;
283                     convType = CT_CCL;
284                     break;
285 
286                 case 'c':
287                     flags |= NOSKIP;
288                     convType = CT_CHAR;
289                     break;
290 
291                 case 'p':
292                     flags |= POINTER | PFXOK | UNSIGNED;
293                     convType = CT_INT;
294                     base = 16;
295                     break;
296 
297                 case 'n':
298                     nconversions++;
299                     if ((flags & SUPPRESS) != 0) {
300                         break;
301                     }
302                     if ((flags & SHORTSHORT) != 0) {
303                         *va_arg(ap, char *) = nread;
304                     }
305                     else if ((flags & SHORT) != 0) {
306                         *va_arg(ap, short *) = nread;
307                     }
308                     else if ((flags & LONG) != 0) {
309                         *va_arg(ap, long *) = nread;
310                     }
311                     else if ((flags & LONGLONG) != 0) {
312                         *va_arg(ap, long long *) = nread;
313                     }
314                     else if ((flags & PTRDIFF) != 0) {
315                         *va_arg(ap, ptrdiff_t *) = nread;
316                     }
317                     else {
318                         *va_arg(ap, int *) = nread;
319                     }
320                     break;
321 
322                 default:
323                     /* Character not a conversion specifier; end parsing */
324                     return nassigned;
325             }
326 
327             break;
328         }
329 
330         if (convType == CT_NONE) {
331             continue;
332         }
333 
334         if (*inr <= 0) {
335             return (nconversions != 0 ? nassigned : -1);
336         }
337 
338         if ((flags & NOSKIP) == 0) {
339             while (isspace((int)*inp) != 0) {
340                 nread++;
341                 if (--(*inr) > 0) {
342                     inp++;
343                 }
344                 else {
345                     return (nconversions != 0 ? nassigned : -1);
346                 }
347             }
348         }
349 
350         /* do the conversion */
351         switch (convType) {
352             case CT_CHAR:
353                 if (width == 0) {
354                     width = 1;
355                 }
356 
357                 if (*inr <= 0) {
358                     return (nconversions != 0 ? nassigned : -1);
359                 }
360 
361                 if (width > *inr) {
362                     width = *inr;
363                 }
364 
365                 if ((flags & SUPPRESS) == 0) {
366                     rt_memcpy(va_arg(ap, char *), inp, width);
367                     nassigned++;
368                 }
369 
370                 *inr -= width;
371                 inp += width;
372                 nread += width;
373                 nconversions++;
374                 break;
375 
376             case CT_CCL:
377                 if (width == 0) {
378                     width = (rt_size_t)~0;
379                 }
380                 if ((flags & SUPPRESS) != 0) {
381                     n = 0;
382                     while (ccltab[(unsigned char)*inp] != 0) {
383                         n++;
384                         (*inr)--;
385                         inp++;
386                         if (--width == 0) {
387                             break;
388                         }
389                         if (*inr <= 0) {
390                             if (n == 0) {
391                                 return (nconversions != 0 ? nassigned : -1);
392                             }
393                             break;
394                         }
395                     }
396                     if (n == 0) {
397                         return nassigned;
398                     }
399                 }
400                 else {
401                     p0 = p = va_arg(ap, char *);
402                     while (ccltab[(unsigned char)*inp] != 0) {
403                         (*inr)--;
404                         *p++ = *inp++;
405                         if (--width == 0) {
406                             break;
407                         }
408                         if (*inr <= 0) {
409                             if (p == p0) {
410                                 return (nconversions != 0 ? nassigned : -1);
411                             }
412                             break;
413                         }
414                     }
415                     n = p - p0;
416                     if (n == 0) {
417                         return nassigned;
418                     }
419                     *p = 0;
420                     nassigned++;
421                 }
422                 nread += n;
423                 nconversions++;
424                 break;
425 
426             case CT_STRING:
427                 if (width == 0) {
428                     width = (rt_size_t)~0;
429                 }
430                 if ((flags & SUPPRESS) != 0) {
431                     while (isspace((int)*inp) == 0) {
432                         nread++;
433                         (*inr)--;
434                         inp++;
435                         if (--width == 0) {
436                             break;
437                         }
438                         if (*inr <= 0) {
439                             break;
440                         }
441                     }
442                 }
443                 else {
444                     p0 = p = va_arg(ap, char *);
445                     while (isspace((int)*inp) == 0) {
446                         (*inr)--;
447                         *p++ = *inp++;
448                         if (--width == 0) {
449                             break;
450                         }
451                         if (*inr <= 0) {
452                             break;
453                         }
454                     }
455                     *p = 0;
456                     nread += p - p0;
457                     nassigned++;
458                 }
459                 nconversions++;
460                 continue;
461 
462             case CT_INT:
463                 if (((flags & POINTER) != 0) && ((*inr) >= FORMAT_NIL_STR_LEN) && (rt_strncmp(FORMAT_NIL_STR, inp, FORMAT_NIL_STR_LEN) == 0)) {
464                     *va_arg(ap, void **) = RT_NULL;
465                     nassigned++;
466                     nconversions++;
467                     nread += FORMAT_NIL_STR_LEN;
468                     inp += FORMAT_NIL_STR_LEN;
469                     (*inr) -= FORMAT_NIL_STR_LEN;
470                     break;
471                 }
472 
473                 if (--width > (sizeof(buf) - 2)) {
474                     width = sizeof(buf) - 2;
475                 }
476                 width++;
477 
478                 if ((flags & SUPPRESS) != 0) {
479                     width = ~0;
480                 }
481 
482                 flags |= SIGNOK | NDIGITS | NZDIGITS;
483                 for (p = buf; width; width--) {
484                     int ok = 0;
485                     c = *inp;
486                     switch (c) {
487                         case '0':
488                             if (base == 0) {
489                                 base = 8;
490                                 flags |= PFXOK;
491                             }
492                             if ((flags & NZDIGITS) != 0) {
493                                 flags &= ~(SIGNOK | NZDIGITS | NDIGITS);
494                             }
495                             else {
496                                 flags &= ~(SIGNOK | PFXOK | NDIGITS);
497                             }
498                             ok = 1;
499                             break;
500 
501                         case '1':
502                         case '2':
503                         case '3':
504                         case '4':
505                         case '5':
506                         case '6':
507                         case '7':
508                             base = basefix[base];
509                             flags &= ~(SIGNOK | PFXOK | NDIGITS);
510                             ok = 1;
511                             break;
512 
513                         case '8':
514                         case '9':
515                             base = basefix[base];
516                             if (base <= 8) {
517                                 break; /* not legal here */
518                             }
519                             flags &= ~(SIGNOK | PFXOK | NDIGITS);
520                             ok = 1;
521                             break;
522 
523                         case 'A':
524                         case 'B':
525                         case 'C':
526                         case 'D':
527                         case 'E':
528                         case 'F':
529                         case 'a':
530                         case 'b':
531                         case 'c':
532                         case 'd':
533                         case 'e':
534                         case 'f':
535                             if (base <= 10) {
536                                 break;
537                             }
538                             flags &= ~(SIGNOK | PFXOK | NDIGITS);
539                             ok = 1;
540                             break;
541 
542                         case '+':
543                         case '-':
544                             if ((flags & SIGNOK) != 0) {
545                                 flags &= ~SIGNOK;
546                                 ok = 1;
547                             }
548                             break;
549 
550                         case 'x':
551                         case 'X':
552                             if (((flags & PFXOK) != 0) && (p == buf + 1)) {
553                                 base = 16; /* if %i */
554                                 flags &= ~PFXOK;
555                                 ok = 1;
556                             }
557                             break;
558                     }
559                     if (!ok)
560                         break;
561 
562                     if ((flags & SUPPRESS) == 0) {
563                         *p++ = c;
564                     }
565                     if (--(*inr) > 0) {
566                         inp++;
567                     }
568                     else {
569                         break;
570                     }
571                 }
572                 if ((flags & NDIGITS) != 0) {
573                     return (nconversions != 0 ? nassigned : -1);
574                 }
575 
576                 c = ((unsigned char *)p)[-1];
577                 if ((c == 'x') || (c == 'X')) {
578                     --p;
579                     inp--;
580                     (*inr)++;
581                 }
582 
583                 if ((flags & SUPPRESS) == 0) {
584                     uint64_t res;
585 
586                     *p = 0;
587                     if ((flags & UNSIGNED) == 0) {
588                         res = strtoll(buf, (char **)RT_NULL, base);
589                     }
590                     else {
591                         res = strtoull(buf, (char **)RT_NULL, base);
592                     }
593                     if ((flags & POINTER) != 0) {
594                         *va_arg(ap, void **) = (void *)(unsigned long)res;
595                     }
596                     else if ((flags & SHORTSHORT) != 0) {
597                         *va_arg(ap, char *) = res;
598                     }
599                     else if ((flags & SHORT) != 0) {
600                         *va_arg(ap, short *) = res;
601                     }
602                     else if ((flags & LONG) != 0) {
603                         *va_arg(ap, long *) = res;
604                     }
605                     else if ((flags & LONGLONG) != 0) {
606                         *va_arg(ap, long long *) = res;
607                     }
608                     else if ((flags & PTRDIFF) != 0) {
609                         *va_arg(ap, ptrdiff_t *) = res;
610                     }
611                     else {
612                         *va_arg(ap, int *) = res;
613                     }
614                     nassigned++;
615                 }
616 
617                 nread += p - buf;
618                 nconversions++;
619                 break;
620 
621             case CT_FLOAT: {
622                 union {
623                     float f;
624                     double d;
625                     long double ld;
626                 } res;
627 
628                 const char *srcbuf = inp;
629                 if ((width != 0) && (width < *inr)) {
630                     /* TODO: handle larger widths */
631                     if (width > (sizeof(buf) - 1)) {
632                         return (nconversions != 0 ? nassigned : -1);
633                     }
634 
635                     rt_memcpy(buf, inp, width);
636                     buf[width] = '\0';
637                     srcbuf = buf;
638                 }
639 
640                 int is_zero;
641                 if ((flags & LONGDOUBLE) != 0) {
642                     res.ld = strtold(srcbuf, &p);
643                     is_zero = res.ld == 0;
644                 }
645                 else if ((flags & LONG) != 0) {
646                     res.d = strtod(srcbuf, &p);
647                     is_zero = res.d == 0;
648                 }
649                 else {
650                     res.f = strtof(srcbuf, &p);
651                     is_zero = res.f == 0;
652                 }
653 
654                 if (is_zero && (srcbuf == p)) {
655                     return (nconversions != 0 ? nassigned : -1);
656                 }
657 
658                 int consumed = p - srcbuf;
659                 *inr -= consumed;
660                 inp += consumed;
661                 nread += consumed;
662                 nconversions++;
663                 if ((flags & SUPPRESS) == 0) {
664                     if ((flags & LONGDOUBLE) != 0) {
665                         *va_arg(ap, long double *) = res.ld;
666                     }
667                     else if ((flags & LONG) != 0) {
668                         *va_arg(ap, double *) = res.d;
669                     }
670                     else {
671                         *va_arg(ap, float *) = res.f;
672                     }
673 
674                     nassigned++;
675                 }
676 
677                 break;
678             }
679 
680             default:
681                 break;
682         }
683     }
684     /* never reached */
685 }
686 
rt_vsscanf(const char * str,const char * format,va_list ap)687 int rt_vsscanf(const char *str, const char *format, va_list ap)
688 {
689     int ret, nremain;
690     char *ccltab = rt_malloc(256);
691 
692     if (ccltab == RT_NULL) {
693         return -1;
694     }
695 
696     ret = scanf_parse(ccltab, str, &nremain, format, ap);
697     rt_free(ccltab);
698 
699     return ret;
700 }
701