diff --git a/dlls/msvcr100/msvcr100.spec b/dlls/msvcr100/msvcr100.spec index 212f30e4793..ec0a1ba607e 100644 --- a/dlls/msvcr100/msvcr100.spec +++ b/dlls/msvcr100/msvcr100.spec @@ -1199,7 +1199,7 @@ @ cdecl _ui64tow(int64 ptr long) msvcrt._ui64tow @ stub _ui64tow_s @ cdecl _ultoa(long ptr long) msvcrt._ultoa -@ stub _ultoa_s +@ cdecl _ultoa_s(long ptr long long) msvcrt._ultoa_s @ cdecl _ultow(long ptr long) msvcrt._ultow @ stub _ultow_s @ cdecl _umask(long) msvcrt._umask diff --git a/dlls/msvcr80/msvcr80.spec b/dlls/msvcr80/msvcr80.spec index 43c0032e098..a5bb126bcf7 100644 --- a/dlls/msvcr80/msvcr80.spec +++ b/dlls/msvcr80/msvcr80.spec @@ -1052,7 +1052,7 @@ @ cdecl _ui64tow(int64 ptr long) msvcrt._ui64tow @ stub _ui64tow_s @ cdecl _ultoa(long ptr long) msvcrt._ultoa -@ stub _ultoa_s +@ cdecl _ultoa_s(long ptr long long) msvcrt._ultoa_s @ cdecl _ultow(long ptr long) msvcrt._ultow @ stub _ultow_s @ cdecl _umask(long) msvcrt._umask diff --git a/dlls/msvcr90/msvcr90.spec b/dlls/msvcr90/msvcr90.spec index 74480afc451..14eec1b7a30 100644 --- a/dlls/msvcr90/msvcr90.spec +++ b/dlls/msvcr90/msvcr90.spec @@ -1039,7 +1039,7 @@ @ cdecl _ui64tow(int64 ptr long) msvcrt._ui64tow @ stub _ui64tow_s @ cdecl _ultoa(long ptr long) msvcrt._ultoa -@ stub _ultoa_s +@ cdecl _ultoa_s(long ptr long long) msvcrt._ultoa_s @ cdecl _ultow(long ptr long) msvcrt._ultow @ stub _ultow_s @ cdecl _umask(long) msvcrt._umask diff --git a/dlls/msvcrt/msvcrt.spec b/dlls/msvcrt/msvcrt.spec index 1b3739f65c1..1e8f06d55d1 100644 --- a/dlls/msvcrt/msvcrt.spec +++ b/dlls/msvcrt/msvcrt.spec @@ -974,7 +974,7 @@ @ cdecl _ui64tow(int64 ptr long) ntdll._ui64tow # stub _ui64tow_s @ cdecl _ultoa(long ptr long) ntdll._ultoa -# stub _ultoa_s +@ cdecl _ultoa_s(long ptr long long) @ cdecl _ultow(long ptr long) ntdll._ultow # stub _ultow_s @ cdecl _umask(long) MSVCRT__umask diff --git a/dlls/msvcrt/string.c b/dlls/msvcrt/string.c index 02bd0a5d539..944c7e6cee8 100644 --- a/dlls/msvcrt/string.c +++ b/dlls/msvcrt/string.c @@ -784,6 +784,60 @@ int CDECL MSVCRT__ui64toa_s(unsigned __int64 value, char *str, return 0; } +/********************************************************************* + * _ultoa_s (MSVCRT.@) + */ +int CDECL _ultoa_s(MSVCRT_ulong value, char *str, MSVCRT_size_t size, int radix) +{ + MSVCRT_ulong digit; + char buffer[33], *pos; + size_t len; + + if (!str || !size || radix < 2 || radix > 36) + { + if (str && size) + str[0] = '\0'; + + *MSVCRT__errno() = MSVCRT_EINVAL; + return MSVCRT_EINVAL; + } + + pos = buffer + 32; + *pos = '\0'; + + do + { + digit = value % radix; + value /= radix; + + if (digit < 10) + *--pos = '0' + digit; + else + *--pos = 'a' + digit - 10; + } + while (value != 0); + + len = buffer + 33 - pos; + if (len > size) + { + size_t i; + char *p = str; + + /* Copy the temporary buffer backwards up to the available number of + * characters. */ + + for (pos = buffer + 31, i = 0; i < size; i++) + *p++ = *pos--; + + str[0] = '\0'; + *MSVCRT__errno() = MSVCRT_ERANGE; + return MSVCRT_ERANGE; + } + + memcpy(str, pos, len); + return 0; +} + #define I10_OUTPUT_MAX_PREC 21 /* Internal structure used by $I10_OUTPUT */ struct _I10_OUTPUT_DATA { diff --git a/dlls/msvcrt/tests/string.c b/dlls/msvcrt/tests/string.c index 9f7370ebb92..ab647db7728 100644 --- a/dlls/msvcrt/tests/string.c +++ b/dlls/msvcrt/tests/string.c @@ -63,6 +63,7 @@ static int (__cdecl *pmbstowcs_s)(size_t*,wchar_t*,size_t,const char*,size_t); static errno_t (__cdecl *p_gcvt_s)(char*,size_t,double,int); static errno_t (__cdecl *p_itoa_s)(int,char*,size_t,int); static errno_t (__cdecl *p_strlwr_s)(char*,size_t); +static errno_t (__cdecl *p_ultoa_s)(__msvcrt_ulong,char*,size_t,int); static int *p__mb_cur_max; static unsigned char *p_mbctype; @@ -1596,6 +1597,84 @@ static void test__mbsnbcat_s(void) "Expected the output buffer string to be \"\\0inosaurdu\" without ending null terminator\n"); } +static void test__ultoa_s(void) +{ + errno_t ret; + char buffer[33]; + + if (!p_ultoa_s) + { + win_skip("Skipping _ultoa_s tests\n"); + return; + } + + errno = EBADF; + ret = p_ultoa_s(0, NULL, 0, 0); + ok(ret == EINVAL, "Expected _ultoa_s to return EINVAL, got %d\n", ret); + ok(errno == EINVAL, "Expected errno to be EINVAL, got %d\n", errno); + + memset(buffer, 'X', sizeof(buffer)); + errno = EBADF; + ret = p_ultoa_s(0, buffer, 0, 0); + ok(ret == EINVAL, "Expected _ultoa_s to return EINVAL, got %d\n", ret); + ok(errno == EINVAL, "Expected errno to be EINVAL, got %d\n", errno); + ok(buffer[0] == 'X', "Expected the output buffer to be untouched\n"); + + memset(buffer, 'X', sizeof(buffer)); + errno = EBADF; + ret = p_ultoa_s(0, buffer, sizeof(buffer), 0); + ok(ret == EINVAL, "Expected _ultoa_s to return EINVAL, got %d\n", ret); + ok(errno == EINVAL, "Expected errno to be EINVAL, got %d\n", errno); + ok(buffer[0] == '\0', "Expected the output buffer to be null terminated\n"); + + memset(buffer, 'X', sizeof(buffer)); + errno = EBADF; + ret = p_ultoa_s(0, buffer, sizeof(buffer), 64); + ok(ret == EINVAL, "Expected _ultoa_s to return EINVAL, got %d\n", ret); + ok(errno == EINVAL, "Expected errno to be EINVAL, got %d\n", errno); + ok(buffer[0] == '\0', "Expected the output buffer to be null terminated\n"); + + memset(buffer, 'X', sizeof(buffer)); + errno = EBADF; + ret = p_ultoa_s(12345678, buffer, 4, 10); + ok(ret == ERANGE, "Expected _ultoa_s to return ERANGE, got %d\n", ret); + ok(errno == ERANGE, "Expected errno to be ERANGE, got %d\n", errno); + ok(!memcmp(buffer, "\000765", 4), + "Expected the output buffer to be null terminated with truncated output\n"); + + memset(buffer, 'X', sizeof(buffer)); + errno = EBADF; + ret = p_ultoa_s(12345678, buffer, 8, 10); + ok(ret == ERANGE, "Expected _ultoa_s to return ERANGE, got %d\n", ret); + ok(errno == ERANGE, "Expected errno to be ERANGE, got %d\n", errno); + ok(!memcmp(buffer, "\0007654321", 8), + "Expected the output buffer to be null terminated with truncated output\n"); + + ret = p_ultoa_s(12345678, buffer, 9, 10); + ok(ret == 0, "Expected _ultoa_s to return 0, got %d\n", ret); + ok(!strcmp(buffer, "12345678"), + "Expected output buffer string to be \"12345678\", got \"%s\"\n", + buffer); + + ret = p_ultoa_s(43690, buffer, sizeof(buffer), 2); + ok(ret == 0, "Expected _ultoa_s to return 0, got %d\n", ret); + ok(!strcmp(buffer, "1010101010101010"), + "Expected output buffer string to be \"1010101010101010\", got \"%s\"\n", + buffer); + + ret = p_ultoa_s(1092009, buffer, sizeof(buffer), 36); + ok(ret == 0, "Expected _ultoa_s to return 0, got %d\n", ret); + ok(!strcmp(buffer, "nell"), + "Expected output buffer string to be \"nell\", got \"%s\"\n", + buffer); + + ret = p_ultoa_s(5704, buffer, sizeof(buffer), 18); + ok(ret == 0, "Expected _ultoa_s to return 0, got %d\n", ret); + ok(!strcmp(buffer, "hag"), + "Expected output buffer string to be \"hag\", got \"%s\"\n", + buffer); +} + START_TEST(string) { char mem[100]; @@ -1625,6 +1704,7 @@ START_TEST(string) p_gcvt_s = (void *)GetProcAddress(hMsvcrt, "_gcvt_s"); p_itoa_s = (void *)GetProcAddress(hMsvcrt, "_itoa_s"); p_strlwr_s = (void *)GetProcAddress(hMsvcrt, "_strlwr_s"); + p_ultoa_s = (void *)GetProcAddress(hMsvcrt, "_ultoa_s"); /* MSVCRT memcpy behaves like memmove for overlapping moves, MFC42 CString::Insert seems to rely on that behaviour */ @@ -1663,4 +1743,5 @@ START_TEST(string) test__strlwr_s(); test_wcsncat_s(); test__mbsnbcat_s(); + test__ultoa_s(); } diff --git a/include/msvcrt/stdlib.h b/include/msvcrt/stdlib.h index 086c3e141fa..49783c0856e 100644 --- a/include/msvcrt/stdlib.h +++ b/include/msvcrt/stdlib.h @@ -169,6 +169,7 @@ void __cdecl _swab(char*,char*,int); char* __cdecl _ui64toa(unsigned __int64,char*,int); errno_t __cdecl _ui64toa_s(unsigned __int64,char*,size_t,int); char* __cdecl _ultoa(__msvcrt_ulong,char*,int); +errno_t __cdecl _ultoa_s(__msvcrt_ulong,char*,size_t,int); void __cdecl _exit(int); void __cdecl abort(void);