Menu

[2d617b]: / src / snprintf.c  Maximize  Restore  History

Download this file

1026 lines (968 with data), 41.9 kB

   1
   2
   3
   4
   5
   6
   7
   8
   9
  10
  11
  12
  13
  14
  15
  16
  17
  18
  19
  20
  21
  22
  23
  24
  25
  26
  27
  28
  29
  30
  31
  32
  33
  34
  35
  36
  37
  38
  39
  40
  41
  42
  43
  44
  45
  46
  47
  48
  49
  50
  51
  52
  53
  54
  55
  56
  57
  58
  59
  60
  61
  62
  63
  64
  65
  66
  67
  68
  69
  70
  71
  72
  73
  74
  75
  76
  77
  78
  79
  80
  81
  82
  83
  84
  85
  86
  87
  88
  89
  90
  91
  92
  93
  94
  95
  96
  97
  98
  99
 100
 101
 102
 103
 104
 105
 106
 107
 108
 109
 110
 111
 112
 113
 114
 115
 116
 117
 118
 119
 120
 121
 122
 123
 124
 125
 126
 127
 128
 129
 130
 131
 132
 133
 134
 135
 136
 137
 138
 139
 140
 141
 142
 143
 144
 145
 146
 147
 148
 149
 150
 151
 152
 153
 154
 155
 156
 157
 158
 159
 160
 161
 162
 163
 164
 165
 166
 167
 168
 169
 170
 171
 172
 173
 174
 175
 176
 177
 178
 179
 180
 181
 182
 183
 184
 185
 186
 187
 188
 189
 190
 191
 192
 193
 194
 195
 196
 197
 198
 199
 200
 201
 202
 203
 204
 205
 206
 207
 208
 209
 210
 211
 212
 213
 214
 215
 216
 217
 218
 219
 220
 221
 222
 223
 224
 225
 226
 227
 228
 229
 230
 231
 232
 233
 234
 235
 236
 237
 238
 239
 240
 241
 242
 243
 244
 245
 246
 247
 248
 249
 250
 251
 252
 253
 254
 255
 256
 257
 258
 259
 260
 261
 262
 263
 264
 265
 266
 267
 268
 269
 270
 271
 272
 273
 274
 275
 276
 277
 278
 279
 280
 281
 282
 283
 284
 285
 286
 287
 288
 289
 290
 291
 292
 293
 294
 295
 296
 297
 298
 299
 300
 301
 302
 303
 304
 305
 306
 307
 308
 309
 310
 311
 312
 313
 314
 315
 316
 317
 318
 319
 320
 321
 322
 323
 324
 325
 326
 327
 328
 329
 330
 331
 332
 333
 334
 335
 336
 337
 338
 339
 340
 341
 342
 343
 344
 345
 346
 347
 348
 349
 350
 351
 352
 353
 354
 355
 356
 357
 358
 359
 360
 361
 362
 363
 364
 365
 366
 367
 368
 369
 370
 371
 372
 373
 374
 375
 376
 377
 378
 379
 380
 381
 382
 383
 384
 385
 386
 387
 388
 389
 390
 391
 392
 393
 394
 395
 396
 397
 398
 399
 400
 401
 402
 403
 404
 405
 406
 407
 408
 409
 410
 411
 412
 413
 414
 415
 416
 417
 418
 419
 420
 421
 422
 423
 424
 425
 426
 427
 428
 429
 430
 431
 432
 433
 434
 435
 436
 437
 438
 439
 440
 441
 442
 443
 444
 445
 446
 447
 448
 449
 450
 451
 452
 453
 454
 455
 456
 457
 458
 459
 460
 461
 462
 463
 464
 465
 466
 467
 468
 469
 470
 471
 472
 473
 474
 475
 476
 477
 478
 479
 480
 481
 482
 483
 484
 485
 486
 487
 488
 489
 490
 491
 492
 493
 494
 495
 496
 497
 498
 499
 500
 501
 502
 503
 504
 505
 506
 507
 508
 509
 510
 511
 512
 513
 514
 515
 516
 517
 518
 519
 520
 521
 522
 523
 524
 525
 526
 527
 528
 529
 530
 531
 532
 533
 534
 535
 536
 537
 538
 539
 540
 541
 542
 543
 544
 545
 546
 547
 548
 549
 550
 551
 552
 553
 554
 555
 556
 557
 558
 559
 560
 561
 562
 563
 564
 565
 566
 567
 568
 569
 570
 571
 572
 573
 574
 575
 576
 577
 578
 579
 580
 581
 582
 583
 584
 585
 586
 587
 588
 589
 590
 591
 592
 593
 594
 595
 596
 597
 598
 599
 600
 601
 602
 603
 604
 605
 606
 607
 608
 609
 610
 611
 612
 613
 614
 615
 616
 617
 618
 619
 620
 621
 622
 623
 624
 625
 626
 627
 628
 629
 630
 631
 632
 633
 634
 635
 636
 637
 638
 639
 640
 641
 642
 643
 644
 645
 646
 647
 648
 649
 650
 651
 652
 653
 654
 655
 656
 657
 658
 659
 660
 661
 662
 663
 664
 665
 666
 667
 668
 669
 670
 671
 672
 673
 674
 675
 676
 677
 678
 679
 680
 681
 682
 683
 684
 685
 686
 687
 688
 689
 690
 691
 692
 693
 694
 695
 696
 697
 698
 699
 700
 701
 702
 703
 704
 705
 706
 707
 708
 709
 710
 711
 712
 713
 714
 715
 716
 717
 718
 719
 720
 721
 722
 723
 724
 725
 726
 727
 728
 729
 730
 731
 732
 733
 734
 735
 736
 737
 738
 739
 740
 741
 742
 743
 744
 745
 746
 747
 748
 749
 750
 751
 752
 753
 754
 755
 756
 757
 758
 759
 760
 761
 762
 763
 764
 765
 766
 767
 768
 769
 770
 771
 772
 773
 774
 775
 776
 777
 778
 779
 780
 781
 782
 783
 784
 785
 786
 787
 788
 789
 790
 791
 792
 793
 794
 795
 796
 797
 798
 799
 800
 801
 802
 803
 804
 805
 806
 807
 808
 809
 810
 811
 812
 813
 814
 815
 816
 817
 818
 819
 820
 821
 822
 823
 824
 825
 826
 827
 828
 829
 830
 831
 832
 833
 834
 835
 836
 837
 838
 839
 840
 841
 842
 843
 844
 845
 846
 847
 848
 849
 850
 851
 852
 853
 854
 855
 856
 857
 858
 859
 860
 861
 862
 863
 864
 865
 866
 867
 868
 869
 870
 871
 872
 873
 874
 875
 876
 877
 878
 879
 880
 881
 882
 883
 884
 885
 886
 887
 888
 889
 890
 891
 892
 893
 894
 895
 896
 897
 898
 899
 900
 901
 902
 903
 904
 905
 906
 907
 908
 909
 910
 911
 912
 913
 914
 915
 916
 917
 918
 919
 920
 921
 922
 923
 924
 925
 926
 927
 928
 929
 930
 931
 932
 933
 934
 935
 936
 937
 938
 939
 940
 941
 942
 943
 944
 945
 946
 947
 948
 949
 950
 951
 952
 953
 954
 955
 956
 957
 958
 959
 960
 961
 962
 963
 964
 965
 966
 967
 968
 969
 970
 971
 972
 973
 974
 975
 976
 977
 978
 979
 980
 981
 982
 983
 984
 985
 986
 987
 988
 989
 990
 991
 992
 993
 994
 995
 996
 997
 998
 999
1000
1001
1002
1003
1004
1005
1006
1007
1008
1009
1010
1011
1012
1013
1014
1015
1016
1017
1018
1019
1020
1021
1022
1023
1024
1025
/*
* snprintf.c - a portable implementation of snprintf
*
* AUTHOR
* Mark Martinec <mark.martinec@ijs.si>, April 1999.
*
* Copyright 1999, Mark Martinec. All rights reserved.
*
* TERMS AND CONDITIONS
* This program is free software; you can redistribute it and/or modify
* it under the terms of the "Frontier Artistic License" which comes
* with this Kit.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty
* of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
* See the Frontier Artistic License for more details.
*
* You should have received a copy of the Frontier Artistic License
* with this Kit in the file named LICENSE.txt .
* If not, I'll be glad to provide one.
*
* FEATURES
* - careful adherence to specs regarding flags, field width and precision;
* - good performance for large string handling (large format, large
* argument or large paddings). Performance is similar to system's sprintf
* and in several cases significantly better (make sure you compile with
* optimizations turned on, tell the compiler the code is strict ANSI
* if necessary to give it more freedom for optimizations);
* - return value semantics per ISO/IEC 9899:1999 ("ISO C99");
* - written in standard ISO/ANSI C - requires an ANSI C compiler.
*
* SUPPORTED CONVERSION SPECIFIERS AND DATA TYPES
*
* This snprintf only supports the following conversion specifiers:
* s, c, d, u, o, x, X, p (and synonyms: i, D, U, O - see below)
* with flags: '-', '+', ' ', '0' and '#'.
* An asterisk is supported for field width as well as precision.
*
* Length modifiers 'h' (short int), 'l' (long int),
* and 'll' (long long int) are supported.
* NOTE:
* If macro SNPRINTF_LONGLONG_SUPPORT is not defined (default) the
* length modifier 'll' is recognized but treated the same as 'l',
* which may cause argument value truncation! Defining
* SNPRINTF_LONGLONG_SUPPORT requires that your system's sprintf also
* handles length modifier 'll'. long long int is a language extension
* which may not be portable.
*
* Conversion of numeric data (conversion specifiers d, u, o, x, X, p)
* with length modifiers (none or h, l, ll) is left to the system routine
* sprintf, but all handling of flags, field width and precision as well as
* c and s conversions is done very carefully by this portable routine.
* If a string precision (truncation) is specified (e.g. %.8s) it is
* guaranteed the string beyond the specified precision will not be referenced.
*
* Length modifiers h, l and ll are ignored for c and s conversions (data
* types wint_t and wchar_t are not supported).
*
* The following common synonyms for conversion characters are supported:
* - i is a synonym for d
* - D is a synonym for ld, explicit length modifiers are ignored
* - U is a synonym for lu, explicit length modifiers are ignored
* - O is a synonym for lo, explicit length modifiers are ignored
* The D, O and U conversion characters are nonstandard, they are supported
* for backward compatibility only, and should not be used for new code.
*
* The following is specifically NOT supported:
* - flag ' (thousands' grouping character) is recognized but ignored
* - numeric conversion specifiers: f, e, E, g, G and synonym F,
* as well as the new a and A conversion specifiers
* - length modifier 'L' (long double) and 'q' (quad - use 'll' instead)
* - wide character/string conversions: lc, ls, and nonstandard
* synonyms C and S
* - writeback of converted string length: conversion character n
* - the n$ specification for direct reference to n-th argument
* - locales
*
* It is permitted for str_m to be zero, and it is permitted to specify NULL
* pointer for resulting string argument if str_m is zero (as per ISO C99).
*
* The return value is the number of characters which would be generated
* for the given input, excluding the trailing null. If this value
* is greater or equal to str_m, not all characters from the result
* have been stored in str, output bytes beyond the (str_m-1) -th character
* are discarded. If str_m is greater than zero it is guaranteed
* the resulting string will be null-terminated.
*
* NOTE that this matches the ISO C99, OpenBSD, and GNU C library 2.1,
* but is different from some older and vendor implementations,
* and is also different from XPG, XSH5, SUSv2 specifications.
* For historical discussion on changes in the semantics and standards
* of snprintf see printf(3) man page in the Linux programmers manual.
*
* Routines asprintf and vasprintf return a pointer (in the ptr argument)
* to a buffer sufficiently large to hold the resulting string. This pointer
* should be passed to free(3) to release the allocated storage when it is
* no longer needed. If sufficient space cannot be allocated, these functions
* will return -1 and set ptr to be a NULL pointer. These two routines are a
* GNU C library extensions (glibc).
*
* Routines asnprintf and vasnprintf are similar to asprintf and vasprintf,
* yet, like snprintf and vsnprintf counterparts, will write at most str_m-1
* characters into the allocated output string, the last character in the
* allocated buffer then gets the terminating null. If the formatted string
* length (the return value) is greater than or equal to the str_m argument,
* the resulting string was truncated and some of the formatted characters
* were discarded. These routines present a handy way to limit the amount
* of allocated memory to some sane value.
*
* AVAILABILITY
* http://www.ijs.si/software/snprintf/
*
* REVISION HISTORY
* 1999-04 V0.9 Mark Martinec
* - initial version, some modifications after comparing printf
* man pages for Digital Unix 4.0, Solaris 2.6 and HPUX 10,
* and checking how Perl handles sprintf (differently!);
* 1999-04-09 V1.0 Mark Martinec <mark.martinec@ijs.si>
* - added main test program, fixed remaining inconsistencies,
* added optional (long long int) support;
* 1999-04-12 V1.1 Mark Martinec <mark.martinec@ijs.si>
* - support the 'p' conversion (pointer to void);
* - if a string precision is specified
* make sure the string beyond the specified precision
* will not be referenced (e.g. by strlen);
* 1999-04-13 V1.2 Mark Martinec <mark.martinec@ijs.si>
* - support synonyms %D=%ld, %U=%lu, %O=%lo;
* - speed up the case of long format string with few conversions;
* 1999-06-30 V1.3 Mark Martinec <mark.martinec@ijs.si>
* - fixed runaway loop (eventually crashing when str_l wraps
* beyond 2^31) while copying format string without
* conversion specifiers to a buffer that is too short
* (thanks to Edwin Young <edwiny@autonomy.com> for
* spotting the problem);
* - added macros PORTABLE_SNPRINTF_VERSION_(MAJOR|MINOR)
* to snprintf.h
* 2000-02-14 V2.0 (never released) Mark Martinec <mark.martinec@ijs.si>
* - relaxed license terms: The Artistic License now applies.
* You may still apply the GNU GENERAL PUBLIC LICENSE
* as was distributed with previous versions, if you prefer;
* - changed REVISION HISTORY dates to use ISO 8601 date format;
* - added vsnprintf (patch also independently proposed by
* Caolan McNamara 2000-05-04, and Keith M Willenson 2000-06-01)
* 2000-06-27 V2.1 Mark Martinec <mark.martinec@ijs.si>
* - removed POSIX check for str_m<1; value 0 for str_m is
* allowed by ISO C99 (and GNU C library 2.1) - (pointed out
* on 2000-05-04 by Caolan McNamara, caolan@ csn dot ul dot ie).
* Besides relaxed license this change in standards adherence
* is the main reason to bump up the major version number;
* - added nonstandard routines asnprintf, vasnprintf, asprintf,
* vasprintf that dynamically allocate storage for the
* resulting string; these routines are not compiled by default,
* see comments where NEED_V?ASN?PRINTF macros are defined;
* - autoconf contributed by Caolan McNamara
* 2000-10-06 V2.2 Mark Martinec <mark.martinec@ijs.si>
* - BUG FIX: the %c conversion used a temporary variable
* that was no longer in scope when referenced,
* possibly causing incorrect resulting character;
* - BUG FIX: make precision and minimal field width unsigned
* to handle huge values (2^31 <= n < 2^32) correctly;
* also be more careful in the use of signed/unsigned/size_t
* internal variables - probably more careful than many
* vendor implementations, but there may still be a case
* where huge values of str_m, precision or minimal field
* could cause incorrect behaviour;
* - use separate variables for signed/unsigned arguments,
* and for short/int, long, and long long argument lengths
* to avoid possible incompatibilities on certain
* computer architectures. Also use separate variable
* arg_sign to hold sign of a numeric argument,
* to make code more transparent;
* - some fiddling with zero padding and "0x" to make it
* Linux compatible;
* - systematically use macros fast_memcpy and fast_memset
* instead of case-by-case hand optimization; determine some
* breakeven string lengths for different architectures;
* - terminology change: 'format' -> 'conversion specifier',
* 'C9x' -> 'ISO/IEC 9899:1999 ("ISO C99")',
* 'alternative form' -> 'alternate form',
* 'data type modifier' -> 'length modifier';
* - several comments rephrased and new ones added;
* - make compiler not complain about 'credits' defined but
* not used;
*/
/* Define HAVE_SNPRINTF if your system already has snprintf and vsnprintf.
*
* If HAVE_SNPRINTF is defined this module will not produce code for
* snprintf and vsnprintf, unless PREFER_PORTABLE_SNPRINTF is defined as well,
* causing this portable version of snprintf to be called portable_snprintf
* (and portable_vsnprintf).
*/
/* #define HAVE_SNPRINTF */
/* Define PREFER_PORTABLE_SNPRINTF if your system does have snprintf and
* vsnprintf but you would prefer to use the portable routine(s) instead.
* In this case the portable routine is declared as portable_snprintf
* (and portable_vsnprintf) and a macro 'snprintf' (and 'vsnprintf')
* is defined to expand to 'portable_v?snprintf' - see file snprintf.h .
* Defining this macro is only useful if HAVE_SNPRINTF is also defined,
* but does does no harm if defined nevertheless.
*/
/* #define PREFER_PORTABLE_SNPRINTF */
/* Define SNPRINTF_LONGLONG_SUPPORT if you want to support
* data type (long long int) and length modifier 'll' (e.g. %lld).
* If undefined, 'll' is recognized but treated as a single 'l'.
*
* If the system's sprintf does not handle 'll'
* the SNPRINTF_LONGLONG_SUPPORT must not be defined!
*
* This is off by default as (long long int) is a language extension.
*/
/* #define SNPRINTF_LONGLONG_SUPPORT */
/* Define NEED_SNPRINTF_ONLY if you only need snprintf, and not vsnprintf.
* If NEED_SNPRINTF_ONLY is defined, the snprintf will be defined directly,
* otherwise both snprintf and vsnprintf routines will be defined
* and snprintf will be a simple wrapper around vsnprintf, at the expense
* of an extra procedure call.
*/
/* #define NEED_SNPRINTF_ONLY */
/* Define NEED_V?ASN?PRINTF macros if you need library extension
* routines asprintf, vasprintf, asnprintf, vasnprintf respectively,
* and your system library does not provide them. They are all small
* wrapper routines around portable_vsnprintf. Defining any of the four
* NEED_V?ASN?PRINTF macros automatically turns off NEED_SNPRINTF_ONLY
* and turns on PREFER_PORTABLE_SNPRINTF.
*
* Watch for name conflicts with the system library if these routines
* are already present there.
*
* NOTE: vasprintf and vasnprintf routines need va_copy() from stdarg.h, as
* specified by C99, to be able to traverse the same list of arguments twice.
* I don't know of any other standard and portable way of achieving the same.
* With some versions of gcc you may use __va_copy(). You might even get away
* with "ap2 = ap", in this case you must not call va_end(ap2) !
* #define va_copy(ap2,ap) ap2 = ap
*/
/* #define NEED_ASPRINTF */
/* #define NEED_ASNPRINTF */
/* #define NEED_VASPRINTF */
/* #define NEED_VASNPRINTF */
/* Define the following macros if desired:
* SOLARIS_COMPATIBLE, SOLARIS_BUG_COMPATIBLE,
* HPUX_COMPATIBLE, HPUX_BUG_COMPATIBLE, LINUX_COMPATIBLE,
* DIGITAL_UNIX_COMPATIBLE, DIGITAL_UNIX_BUG_COMPATIBLE,
* PERL_COMPATIBLE, PERL_BUG_COMPATIBLE,
*
* - For portable applications it is best not to rely on peculiarities
* of a given implementation so it may be best not to define any
* of the macros that select compatibility and to avoid features
* that vary among the systems.
*
* - Selecting compatibility with more than one operating system
* is not strictly forbidden but is not recommended.
*
* - 'x'_BUG_COMPATIBLE implies 'x'_COMPATIBLE .
*
* - 'x'_COMPATIBLE refers to (and enables) a behaviour that is
* documented in a sprintf man page on a given operating system
* and actually adhered to by the system's sprintf (but not on
* most other operating systems). It may also refer to and enable
* a behaviour that is declared 'undefined' or 'implementation specific'
* in the man page but a given implementation behaves predictably
* in a certain way.
*
* - 'x'_BUG_COMPATIBLE refers to (and enables) a behaviour of system's sprintf
* that contradicts the sprintf man page on the same operating system.
*
* - I do not claim that the 'x'_COMPATIBLE and 'x'_BUG_COMPATIBLE
* conditionals take into account all idiosyncrasies of a particular
* implementation, there may be other incompatibilities.
*/
/* ============================================= */
/* NO USER SERVICABLE PARTS FOLLOWING THIS POINT */
/* ============================================= */
#define PORTABLE_SNPRINTF_VERSION_MAJOR 2
#define PORTABLE_SNPRINTF_VERSION_MINOR 2
#if defined(NEED_ASPRINTF) || defined(NEED_ASNPRINTF) || defined(NEED_VASPRINTF) || defined(NEED_VASNPRINTF)
# if defined(NEED_SNPRINTF_ONLY)
# undef NEED_SNPRINTF_ONLY
# endif
# if !defined(PREFER_PORTABLE_SNPRINTF)
# define PREFER_PORTABLE_SNPRINTF
# endif
#endif
#if defined(SOLARIS_BUG_COMPATIBLE) && !defined(SOLARIS_COMPATIBLE)
#define SOLARIS_COMPATIBLE
#endif
#if defined(HPUX_BUG_COMPATIBLE) && !defined(HPUX_COMPATIBLE)
#define HPUX_COMPATIBLE
#endif
#if defined(DIGITAL_UNIX_BUG_COMPATIBLE) && !defined(DIGITAL_UNIX_COMPATIBLE)
#define DIGITAL_UNIX_COMPATIBLE
#endif
#if defined(PERL_BUG_COMPATIBLE) && !defined(PERL_COMPATIBLE)
#define PERL_COMPATIBLE
#endif
#if defined(LINUX_BUG_COMPATIBLE) && !defined(LINUX_COMPATIBLE)
#define LINUX_COMPATIBLE
#endif
#include <sys/types.h>
#include <string.h>
#include <stdlib.h>
#include <stdio.h>
#include <stdarg.h>
#include <assert.h>
#include <errno.h>
#ifdef isdigit
#undef isdigit
#endif
#define isdigit(c) ((c) >= '0' && (c) <= '9')
/* For copying strings longer or equal to 'breakeven_point'
* it is more efficient to call memcpy() than to do it inline.
* The value depends mostly on the processor architecture,
* but also on the compiler and its optimization capabilities.
* The value is not critical, some small value greater than zero
* will be just fine if you don't care to squeeze every drop
* of performance out of the code.
*
* Small values favor memcpy, large values favor inline code.
*/
#if defined(__alpha__) || defined(__alpha)
# define breakeven_point 2 /* AXP (DEC Alpha) - gcc or cc or egcs */
#endif
#if defined(__i386__) || defined(__i386)
# define breakeven_point 12 /* Intel Pentium/Linux - gcc 2.96 */
#endif
#if defined(__hppa)
# define breakeven_point 10 /* HP-PA - gcc */
#endif
#if defined(__sparc__) || defined(__sparc)
# define breakeven_point 33 /* Sun Sparc 5 - gcc 2.8.1 */
#endif
/* some other values of possible interest: */
/* #define breakeven_point 8 */ /* VAX 4000 - vaxc */
/* #define breakeven_point 19 */ /* VAX 4000 - gcc 2.7.0 */
#ifndef breakeven_point
# define breakeven_point 6 /* some reasonable one-size-fits-all value */
#endif
#define fast_memcpy(d,s,n) \
{ register size_t nn = (size_t)(n); \
if (nn >= breakeven_point) memcpy((d), (s), nn); \
else if (nn > 0) { /* proc call overhead is worth only for large strings*/\
register char *dd; register const char *ss; \
for (ss=(s), dd=(d); nn>0; nn--) *dd++ = *ss++; } }
#define fast_memset(d,c,n) \
{ register size_t nn = (size_t)(n); \
if (nn >= breakeven_point) memset((d), (int)(c), nn); \
else if (nn > 0) { /* proc call overhead is worth only for large strings*/\
register char *dd; register const int cc=(int)(c); \
for (dd=(d); nn>0; nn--) *dd++ = cc; } }
/* prototypes */
#if defined(NEED_ASPRINTF)
int asprintf (char **ptr, const char *fmt, /*args*/ ...);
#endif
#if defined(NEED_VASPRINTF)
int vasprintf (char **ptr, const char *fmt, va_list ap);
#endif
#if defined(NEED_ASNPRINTF)
int asnprintf (char **ptr, size_t str_m, const char *fmt, /*args*/ ...);
#endif
#if defined(NEED_VASNPRINTF)
int vasnprintf (char **ptr, size_t str_m, const char *fmt, va_list ap);
#endif
#if defined(HAVE_SNPRINTF)
/* declare our portable snprintf routine under name portable_snprintf */
/* declare our portable vsnprintf routine under name portable_vsnprintf */
#else
/* declare our portable routines under names snprintf and vsnprintf */
#define portable_snprintf snprintf
#if !defined(NEED_SNPRINTF_ONLY)
#define portable_vsnprintf vsnprintf
#endif
#endif
#if !defined(HAVE_SNPRINTF) || defined(PREFER_PORTABLE_SNPRINTF)
int portable_snprintf(char *str, size_t str_m, const char *fmt, /*args*/ ...);
#if !defined(NEED_SNPRINTF_ONLY)
int portable_vsnprintf(char *str, size_t str_m, const char *fmt, va_list ap);
#endif
#endif
/* declarations */
static char credits[] = "\n\
@(#)snprintf.c, v2.2: Mark Martinec, <mark.martinec@ijs.si>\n\
@(#)snprintf.c, v2.2: Copyright 1999, Mark Martinec. Frontier Artistic License applies.\n\
@(#)snprintf.c, v2.2: http://www.ijs.si/software/snprintf/\n";
#if defined(NEED_ASPRINTF)
int asprintf(char **ptr, const char *fmt, /*args*/ ...) {
va_list ap;
size_t str_m;
int str_l;
*ptr = NULL;
va_start(ap, fmt); /* measure the required size */
str_l = portable_vsnprintf(NULL, (size_t)0, fmt, ap);
va_end(ap);
assert(str_l >= 0); /* possible integer overflow if str_m > INT_MAX */
*ptr = (char *) malloc(str_m = (size_t)str_l + 1);
if (*ptr == NULL) { errno = ENOMEM; str_l = -1; }
else {
int str_l2;
va_start(ap, fmt);
str_l2 = portable_vsnprintf(*ptr, str_m, fmt, ap);
va_end(ap);
assert(str_l2 == str_l);
}
return str_l;
}
#endif
#if defined(NEED_VASPRINTF)
int vasprintf(char **ptr, const char *fmt, va_list ap) {
size_t str_m;
int str_l;
*ptr = NULL;
{ va_list ap2;
va_copy(ap2, ap); /* don't consume the original ap, we'll need it again */
str_l = portable_vsnprintf(NULL, (size_t)0, fmt, ap2);/*get required size*/
va_end(ap2);
}
assert(str_l >= 0); /* possible integer overflow if str_m > INT_MAX */
*ptr = (char *) malloc(str_m = (size_t)str_l + 1);
if (*ptr == NULL) { errno = ENOMEM; str_l = -1; }
else {
int str_l2 = portable_vsnprintf(*ptr, str_m, fmt, ap);
assert(str_l2 == str_l);
}
return str_l;
}
#endif
#if defined(NEED_ASNPRINTF)
int asnprintf (char **ptr, size_t str_m, const char *fmt, /*args*/ ...) {
va_list ap;
int str_l;
*ptr = NULL;
va_start(ap, fmt); /* measure the required size */
str_l = portable_vsnprintf(NULL, (size_t)0, fmt, ap);
va_end(ap);
assert(str_l >= 0); /* possible integer overflow if str_m > INT_MAX */
if ((size_t)str_l + 1 < str_m) str_m = (size_t)str_l + 1; /* truncate */
/* if str_m is 0, no buffer is allocated, just set *ptr to NULL */
if (str_m == 0) { /* not interested in resulting string, just return size */
} else {
*ptr = (char *) malloc(str_m);
if (*ptr == NULL) { errno = ENOMEM; str_l = -1; }
else {
int str_l2;
va_start(ap, fmt);
str_l2 = portable_vsnprintf(*ptr, str_m, fmt, ap);
va_end(ap);
assert(str_l2 == str_l);
}
}
return str_l;
}
#endif
#if defined(NEED_VASNPRINTF)
int vasnprintf (char **ptr, size_t str_m, const char *fmt, va_list ap) {
int str_l;
*ptr = NULL;
{ va_list ap2;
va_copy(ap2, ap); /* don't consume the original ap, we'll need it again */
str_l = portable_vsnprintf(NULL, (size_t)0, fmt, ap2);/*get required size*/
va_end(ap2);
}
assert(str_l >= 0); /* possible integer overflow if str_m > INT_MAX */
if ((size_t)str_l + 1 < str_m) str_m = (size_t)str_l + 1; /* truncate */
/* if str_m is 0, no buffer is allocated, just set *ptr to NULL */
if (str_m == 0) { /* not interested in resulting string, just return size */
} else {
*ptr = (char *) malloc(str_m);
if (*ptr == NULL) { errno = ENOMEM; str_l = -1; }
else {
int str_l2 = portable_vsnprintf(*ptr, str_m, fmt, ap);
assert(str_l2 == str_l);
}
}
return str_l;
}
#endif
/*
* If the system does have snprintf and the portable routine is not
* specifically required, this module produces no code for snprintf/vsnprintf.
*/
#if !defined(HAVE_SNPRINTF) || defined(PREFER_PORTABLE_SNPRINTF)
#if !defined(NEED_SNPRINTF_ONLY)
int portable_snprintf(char *str, size_t str_m, const char *fmt, /*args*/ ...) {
va_list ap;
int str_l;
va_start(ap, fmt);
str_l = portable_vsnprintf(str, str_m, fmt, ap);
va_end(ap);
return str_l;
}
#endif
#if defined(NEED_SNPRINTF_ONLY)
int portable_snprintf(char *str, size_t str_m, const char *fmt, /*args*/ ...) {
#else
int portable_vsnprintf(char *str, size_t str_m, const char *fmt, va_list ap) {
#endif
#if defined(NEED_SNPRINTF_ONLY)
va_list ap;
#endif
size_t str_l = 0;
const char *p = fmt;
/* In contrast with POSIX, the ISO C99 now says
* that str can be NULL and str_m can be 0.
* This is more useful than the old: if (str_m < 1) return -1; */
#if defined(NEED_SNPRINTF_ONLY)
va_start(ap, fmt);
#endif
if (!p) p = "";
while (*p) {
if (*p != '%') {
/* if (str_l < str_m) str[str_l++] = *p++; -- this would be sufficient */
/* but the following code achieves better performance for cases
* where format string is long and contains few conversions */
const char *q = strchr(p+1,'%');
size_t n = !q ? strlen(p) : (q-p);
if (str_l < str_m) {
size_t avail = str_m-str_l;
fast_memcpy(str+str_l, p, (n>avail?avail:n));
}
p += n; str_l += n;
} else {
const char *starting_p;
size_t min_field_width = 0, precision = 0;
int zero_padding = 0, precision_specified = 0, justify_left = 0;
int alternate_form = 0, force_sign = 0;
int space_for_positive = 1; /* If both the ' ' and '+' flags appear,
the ' ' flag should be ignored. */
char length_modifier = '\0'; /* allowed values: \0, h, l, L */
char tmp[32];/* temporary buffer for simple numeric->string conversion */
const char *str_arg; /* string address in case of string argument */
size_t str_arg_l; /* natural field width of arg without padding
and sign */
unsigned char uchar_arg;
/* unsigned char argument value - only defined for c conversion.
N.B. standard explicitly states the char argument for
the c conversion is unsigned */
size_t number_of_zeros_to_pad = 0;
/* number of zeros to be inserted for numeric conversions
as required by the precision or minimal field width */
size_t zero_padding_insertion_ind = 0;
/* index into tmp where zero padding is to be inserted */
char fmt_spec = '\0';
/* current conversion specifier character */
str_arg = credits;/* just to make compiler happy (defined but not used)*/
str_arg = NULL;
starting_p = p; p++; /* skip '%' */
/* parse flags */
while (*p == '0' || *p == '-' || *p == '+' ||
*p == ' ' || *p == '#' || *p == '\'') {
switch (*p) {
case '0': zero_padding = 1; break;
case '-': justify_left = 1; break;
case '+': force_sign = 1; space_for_positive = 0; break;
case ' ': force_sign = 1;
/* If both the ' ' and '+' flags appear, the ' ' flag should be ignored */
#ifdef PERL_COMPATIBLE
/* ... but in Perl the last of ' ' and '+' applies */
space_for_positive = 1;
#endif
break;
case '#': alternate_form = 1; break;
case '\'': break;
}
p++;
}
/* If the '0' and '-' flags both appear, the '0' flag should be ignored. */
/* parse field width */
if (*p == '*') {
int j;
p++; j = va_arg(ap, int);
if (j >= 0) min_field_width = j;
else { min_field_width = -j; justify_left = 1; }
} else if (isdigit((int)(*p))) {
/* size_t could be wider than unsigned int;
make sure we treat argument like common implementations do */
unsigned int uj = *p++ - '0';
while (isdigit((int)(*p))) uj = 10*uj + (unsigned int)(*p++ - '0');
min_field_width = uj;
}
/* parse precision */
if (*p == '.') {
p++; precision_specified = 1;
if (*p == '*') {
int j = va_arg(ap, int);
p++;
if (j >= 0) precision = j;
else {
precision_specified = 0; precision = 0;
/* NOTE:
* Solaris 2.6 man page claims that in this case the precision
* should be set to 0. Digital Unix 4.0, HPUX 10 and BSD man page
* claim that this case should be treated as unspecified precision,
* which is what we do here.
*/
}
} else if (isdigit((int)(*p))) {
/* size_t could be wider than unsigned int;
make sure we treat argument like common implementations do */
unsigned int uj = *p++ - '0';
while (isdigit((int)(*p))) uj = 10*uj + (unsigned int)(*p++ - '0');
precision = uj;
}
}
/* parse 'h', 'l' and 'll' length modifiers */
if (*p == 'h' || *p == 'l') {
length_modifier = *p; p++;
if (length_modifier == 'l' && *p == 'l') { /* double l = long long */
#ifdef SNPRINTF_LONGLONG_SUPPORT
length_modifier = '2'; /* double l encoded as '2' */
#else
length_modifier = 'l'; /* treat it as a single 'l' */
#endif
p++;
}
}
fmt_spec = *p;
/* common synonyms: */
switch (fmt_spec) {
case 'i': fmt_spec = 'd'; break;
case 'D': fmt_spec = 'd'; length_modifier = 'l'; break;
case 'U': fmt_spec = 'u'; length_modifier = 'l'; break;
case 'O': fmt_spec = 'o'; length_modifier = 'l'; break;
default: break;
}
/* get parameter value, do initial processing */
switch (fmt_spec) {
case '%': /* % behaves similar to 's' regarding flags and field widths */
case 'c': /* c behaves similar to 's' regarding flags and field widths */
case 's':
length_modifier = '\0'; /* wint_t and wchar_t not supported */
/* the result of zero padding flag with non-numeric conversion specifier*/
/* is undefined. Solaris and HPUX 10 does zero padding in this case, */
/* Digital Unix and Linux does not. */
#if !defined(SOLARIS_COMPATIBLE) && !defined(HPUX_COMPATIBLE)
zero_padding = 0; /* turn zero padding off for string conversions */
#endif
str_arg_l = 1;
switch (fmt_spec) {
case '%':
str_arg = p; break;
case 'c': {
int j = va_arg(ap, int);
uchar_arg = (unsigned char) j; /* standard demands unsigned char */
str_arg = (const char *) &uchar_arg;
break;
}
case 's':
str_arg = va_arg(ap, const char *);
if (!str_arg) str_arg_l = 0;
/* make sure not to address string beyond the specified precision !!! */
else if (!precision_specified) str_arg_l = strlen(str_arg);
/* truncate string if necessary as requested by precision */
else if (precision == 0) str_arg_l = 0;
else {
/* memchr on HP does not like n > 2^31 !!! */
const char *q = (const char*)(memchr(str_arg, '\0',
precision <= 0x7fffffff ? precision : 0x7fffffff));
str_arg_l = !q ? precision : (q-str_arg);
}
break;
default: break;
}
break;
case 'd': case 'u': case 'o': case 'x': case 'X': case 'p': {
/* NOTE: the u, o, x, X and p conversion specifiers imply
the value is unsigned; d implies a signed value */
int arg_sign = 0;
/* 0 if numeric argument is zero (or if pointer is NULL for 'p'),
+1 if greater than zero (or nonzero for unsigned arguments),
-1 if negative (unsigned argument is never negative) */
int int_arg = 0; unsigned int uint_arg = 0;
/* only defined for length modifier h, or for no length modifiers */
long int long_arg = 0; unsigned long int ulong_arg = 0;
/* only defined for length modifier l */
void *ptr_arg = NULL;
/* pointer argument value -only defined for p conversion */
#ifdef SNPRINTF_LONGLONG_SUPPORT
long long int long_long_arg = 0;
unsigned long long int ulong_long_arg = 0;
/* only defined for length modifier ll */
#endif
if (fmt_spec == 'p') {
/* HPUX 10: An l, h, ll or L before any other conversion character
* (other than d, i, u, o, x, or X) is ignored.
* Digital Unix:
* not specified, but seems to behave as HPUX does.
* Solaris: If an h, l, or L appears before any other conversion
* specifier (other than d, i, u, o, x, or X), the behavior
* is undefined. (Actually %hp converts only 16-bits of address
* and %llp treats address as 64-bit data which is incompatible
* with (void *) argument on a 32-bit system).
*/
#ifdef SOLARIS_COMPATIBLE
# ifdef SOLARIS_BUG_COMPATIBLE
/* keep length modifiers even if it represents 'll' */
# else
if (length_modifier == '2') length_modifier = '\0';
# endif
#else
length_modifier = '\0';
#endif
ptr_arg = va_arg(ap, void *);
if (ptr_arg != NULL) arg_sign = 1;
} else if (fmt_spec == 'd') { /* signed */
switch (length_modifier) {
case '\0':
case 'h':
/* It is non-portable to specify a second argument of char or short
* to va_arg, because arguments seen by the called function
* are not char or short. C converts char and short arguments
* to int before passing them to a function.
*/
int_arg = va_arg(ap, int);
if (int_arg > 0) arg_sign = 1;
else if (int_arg < 0) arg_sign = -1;
break;
case 'l':
long_arg = va_arg(ap, long int);
if (long_arg > 0) arg_sign = 1;
else if (long_arg < 0) arg_sign = -1;
break;
#ifdef SNPRINTF_LONGLONG_SUPPORT
case '2':
long_long_arg = va_arg(ap, long long int);
if (long_long_arg > 0) arg_sign = 1;
else if (long_long_arg < 0) arg_sign = -1;
break;
#endif
}
} else { /* unsigned */
switch (length_modifier) {
case '\0':
case 'h':
uint_arg = va_arg(ap, unsigned int);
if (uint_arg) arg_sign = 1;
break;
case 'l':
ulong_arg = va_arg(ap, unsigned long int);
if (ulong_arg) arg_sign = 1;
break;
#ifdef SNPRINTF_LONGLONG_SUPPORT
case '2':
ulong_long_arg = va_arg(ap, unsigned long long int);
if (ulong_long_arg) arg_sign = 1;
break;
#endif
}
}
str_arg = tmp; str_arg_l = 0;
/* NOTE:
* For d, i, u, o, x, and X conversions, if precision is specified,
* the '0' flag should be ignored. This is so with Solaris 2.6,
* Digital UNIX 4.0, HPUX 10, Linux, FreeBSD, NetBSD; but not with Perl.
*/
#ifndef PERL_COMPATIBLE
if (precision_specified) zero_padding = 0;
#endif
if (fmt_spec == 'd') {
if (force_sign && arg_sign >= 0)
tmp[str_arg_l++] = space_for_positive ? ' ' : '+';
/* leave negative numbers for sprintf to handle,
to avoid handling tricky cases like (short int)(-32768) */
#ifdef LINUX_COMPATIBLE
} else if (fmt_spec == 'p' && force_sign && arg_sign > 0) {
tmp[str_arg_l++] = space_for_positive ? ' ' : '+';
#endif
} else if (alternate_form) {
if (arg_sign != 0 && (fmt_spec == 'x' || fmt_spec == 'X') )
{ tmp[str_arg_l++] = '0'; tmp[str_arg_l++] = fmt_spec; }
/* alternate form should have no effect for p conversion, but ... */
#ifdef HPUX_COMPATIBLE
else if (fmt_spec == 'p'
/* HPUX 10: for an alternate form of p conversion,
* a nonzero result is prefixed by 0x. */
#ifndef HPUX_BUG_COMPATIBLE
/* Actually it uses 0x prefix even for a zero value. */
&& arg_sign != 0
#endif
) { tmp[str_arg_l++] = '0'; tmp[str_arg_l++] = 'x'; }
#endif
}
zero_padding_insertion_ind = str_arg_l;
if (!precision_specified) precision = 1; /* default precision is 1 */
if (precision == 0 && arg_sign == 0
#if defined(HPUX_BUG_COMPATIBLE) || defined(LINUX_COMPATIBLE)
&& fmt_spec != 'p'
/* HPUX 10 man page claims: With conversion character p the result of
* converting a zero value with a precision of zero is a null string.
* Actually HP returns all zeroes, and Linux returns "(nil)". */
#endif
) {
/* converted to null string */
/* When zero value is formatted with an explicit precision 0,
the resulting formatted string is empty (d, i, u, o, x, X, p). */
} else {
char f[5]; int f_l = 0;
f[f_l++] = '%'; /* construct a simple format string for sprintf */
if (!length_modifier) { }
else if (length_modifier=='2') { f[f_l++] = 'l'; f[f_l++] = 'l'; }
else f[f_l++] = length_modifier;
f[f_l++] = fmt_spec; f[f_l++] = '\0';
if (fmt_spec == 'p') str_arg_l += sprintf(tmp+str_arg_l, f, ptr_arg);
else if (fmt_spec == 'd') { /* signed */
switch (length_modifier) {
case '\0':
case 'h': str_arg_l+=sprintf(tmp+str_arg_l, f, int_arg); break;
case 'l': str_arg_l+=sprintf(tmp+str_arg_l, f, long_arg); break;
#ifdef SNPRINTF_LONGLONG_SUPPORT
case '2': str_arg_l+=sprintf(tmp+str_arg_l,f,long_long_arg); break;
#endif
}
} else { /* unsigned */
switch (length_modifier) {
case '\0':
case 'h': str_arg_l+=sprintf(tmp+str_arg_l, f, uint_arg); break;
case 'l': str_arg_l+=sprintf(tmp+str_arg_l, f, ulong_arg); break;
#ifdef SNPRINTF_LONGLONG_SUPPORT
case '2': str_arg_l+=sprintf(tmp+str_arg_l,f,ulong_long_arg);break;
#endif
}
}
/* include the optional minus sign and possible "0x"
in the region before the zero padding insertion point */
if (zero_padding_insertion_ind < str_arg_l &&
tmp[zero_padding_insertion_ind] == '-') {
zero_padding_insertion_ind++;
}
if (zero_padding_insertion_ind+1 < str_arg_l &&
tmp[zero_padding_insertion_ind] == '0' &&
(tmp[zero_padding_insertion_ind+1] == 'x' ||
tmp[zero_padding_insertion_ind+1] == 'X') ) {
zero_padding_insertion_ind += 2;
}
}
{ size_t num_of_digits = str_arg_l - zero_padding_insertion_ind;
if (alternate_form && fmt_spec == 'o'
#ifdef HPUX_COMPATIBLE /* ("%#.o",0) -> "" */
&& (str_arg_l > 0)
#endif
#ifdef DIGITAL_UNIX_BUG_COMPATIBLE /* ("%#o",0) -> "00" */
#else
/* unless zero is already the first character */
&& !(zero_padding_insertion_ind < str_arg_l
&& tmp[zero_padding_insertion_ind] == '0')
#endif
) { /* assure leading zero for alternate-form octal numbers */
if (!precision_specified || precision < num_of_digits+1) {
/* precision is increased to force the first character to be zero,
except if a zero value is formatted with an explicit precision
of zero */
precision = num_of_digits+1; precision_specified = 1;
}
}
/* zero padding to specified precision? */
if (num_of_digits < precision)
number_of_zeros_to_pad = precision - num_of_digits;
}
/* zero padding to specified minimal field width? */
if (!justify_left && zero_padding) {
size_t n = min_field_width - (str_arg_l+number_of_zeros_to_pad);
if (n > 0) number_of_zeros_to_pad += n;
}
break;
}
default: /* unrecognized conversion specifier, keep format string as-is*/
zero_padding = 0; /* turn zero padding off for non-numeric convers. */
#ifndef DIGITAL_UNIX_COMPATIBLE
justify_left = 1; min_field_width = 0; /* reset flags */
#endif
#if defined(PERL_COMPATIBLE) || defined(LINUX_COMPATIBLE)
/* keep the entire format string unchanged */
str_arg = starting_p; str_arg_l = p - starting_p;
/* well, not exactly so for Linux, which does something inbetween,
* and I don't feel an urge to imitate it: "%+++++hy" -> "%+y" */
#else
/* discard the unrecognized conversion, just keep *
* the unrecognized conversion character */
str_arg = p; str_arg_l = 0;
#endif
if (*p) str_arg_l++; /* include invalid conversion specifier unchanged
if not at end-of-string */
break;
}
if (*p) p++; /* step over the just processed conversion specifier */
/* insert padding to the left as requested by min_field_width;
this does not include the zero padding in case of numerical conversions*/
if (!justify_left) { /* left padding with blank or zero */
size_t n = min_field_width - (str_arg_l+number_of_zeros_to_pad);
if (n > 0) {
if (str_l < str_m) {
size_t avail = str_m-str_l;
fast_memset(str+str_l, (zero_padding?'0':' '), (n>avail?avail:n));
}
str_l += n;
}
}
/* zero padding as requested by the precision or by the minimal field width
* for numeric conversions required? */
if (number_of_zeros_to_pad <= 0) {
/* will not copy first part of numeric right now, *
* force it to be copied later in its entirety */
zero_padding_insertion_ind = 0;
} else {
/* insert first part of numerics (sign or '0x') before zero padding */
size_t n = zero_padding_insertion_ind;
if (n > 0) {
if (str_l < str_m) {
size_t avail = str_m-str_l;
fast_memcpy(str+str_l, str_arg, (n>avail?avail:n));
}
str_l += n;
}
/* insert zero padding as requested by the precision or min field width */
n = number_of_zeros_to_pad;
if (n > 0) {
if (str_l < str_m) {
size_t avail = str_m-str_l;
fast_memset(str+str_l, '0', (n>avail?avail:n));
}
str_l += n;
}
}
/* insert formatted string
* (or as-is conversion specifier for unknown conversions) */
{ size_t n = str_arg_l - zero_padding_insertion_ind;
if (n > 0) {
if (str_l < str_m) {
size_t avail = str_m-str_l;
fast_memcpy(str+str_l, str_arg+zero_padding_insertion_ind,
(n>avail?avail:n));
}
str_l += n;
}
}
/* insert right padding */
if (justify_left) { /* right blank padding to the field width */
size_t n = min_field_width - (str_arg_l+number_of_zeros_to_pad);
if (n > 0) {
if (str_l < str_m) {
size_t avail = str_m-str_l;
fast_memset(str+str_l, ' ', (n>avail?avail:n));
}
str_l += n;
}
}
}
}
#if defined(NEED_SNPRINTF_ONLY)
va_end(ap);
#endif
if (str_m > 0) { /* make sure the string is null-terminated
even at the expense of overwriting the last character
(shouldn't happen, but just in case) */
str[str_l <= str_m-1 ? str_l : str_m-1] = '\0';
}
/* Return the number of characters formatted (excluding trailing null
* character), that is, the number of characters that would have been
* written to the buffer if it were large enough.
*
* The value of str_l should be returned, but str_l is of unsigned type
* size_t, and snprintf is int, possibly leading to an undetected
* integer overflow, resulting in a negative return value, which is illegal.
* Both XSH5 and ISO C99 (at least the draft) are silent on this issue.
* Should errno be set to EOVERFLOW and EOF returned in this case???
*/
return (int) str_l;
}
#endif
Want the latest updates on software, tech news, and AI?
Get latest updates about software, tech news, and AI from SourceForge directly in your inbox once a month.