1//===----------------------------------------------------------------------===//
2//
3// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
4// See https://llvm.org/LICENSE.txt for license information.
5// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
6//
7//===----------------------------------------------------------------------===//
8
9#include <cwchar>  // mbstate_t
10#include <limits.h> // MB_LEN_MAX
11#include <stdlib.h> // MB_CUR_MAX, size_t
12#include <string.h> // memcpy
13
14// Converts `max_source_chars` from the wide character buffer pointer to by *`src`,
15// into the multi byte character sequence buffer stored at `dst`, which must be
16// `dst_size_bytes` bytes in size. Returns the number of bytes in the sequence
17// converted from *src, excluding the null terminator.
18// Returns (size_t) -1 if an error occurs and sets errno.
19// If `dst` is NULL, `dst_size_bytes` is ignored and no bytes are copied to `dst`.
20_LIBCPP_FUNC_VIS
21size_t wcsnrtombs(char *__restrict dst, const wchar_t **__restrict src,
22                   size_t max_source_chars, size_t dst_size_bytes,
23                   mbstate_t *__restrict ps) {
24
25  const size_t invalid_wchar = static_cast<size_t>(-1);
26
27  size_t source_converted;
28  size_t dest_converted;
29  size_t result = 0;
30
31  // If `dst` is null then `dst_size_bytes` should be ignored according to the
32  // standard. Setting dst_size_bytes to a large value has this effect.
33  if (dst == nullptr)
34    dst_size_bytes = static_cast<size_t>(-1);
35
36  for (dest_converted = source_converted = 0;
37       source_converted < max_source_chars && (!dst || dest_converted < dst_size_bytes);
38       ++source_converted, dest_converted += result) {
39    wchar_t c = (*src)[source_converted];
40    size_t dest_remaining = dst_size_bytes - dest_converted;
41
42    if (dst == nullptr) {
43      result = wcrtomb(NULL, c, ps);
44    } else if (dest_remaining >= static_cast<size_t>(MB_CUR_MAX)) {
45      // dst has enough space to translate in-place.
46      result = wcrtomb(dst + dest_converted, c, ps);
47    } else {
48      /*
49      * dst may not have enough space, so use a temporary buffer.
50      *
51      * We need to save a copy of the conversion state
52      * here so we can restore it if the multibyte
53      * character is too long for the buffer.
54      */
55      char buff[MB_LEN_MAX];
56      mbstate_t mbstate_tmp;
57
58      if (ps != nullptr)
59        mbstate_tmp = *ps;
60      result = wcrtomb(buff, c, ps);
61
62      if (result > dest_remaining) {
63        // Multi-byte sequence for character won't fit.
64        if (ps != nullptr)
65          *ps = mbstate_tmp;
66        if (result != invalid_wchar)
67          break;
68      } else {
69        // The buffer was used, so we need copy the translation to dst.
70        memcpy(dst, buff, result);
71      }
72    }
73
74    // result (char_size) contains the size of the multi-byte-sequence converted.
75    // Otherwise, result (char_size) is (size_t) -1 and wcrtomb() sets the errno.
76    if (result == invalid_wchar) {
77      if (dst)
78        *src = *src + source_converted;
79      return invalid_wchar;
80    }
81
82    if (c == L'\0') {
83      if (dst)
84        *src = NULL;
85      return dest_converted;
86    }
87  }
88
89  if (dst)
90    *src = *src + source_converted;
91
92  return dest_converted;
93}
94