xref: /reactos/sdk/lib/ucrt/lowio/write.cpp (revision e98e9000)
1 //
2 // write.cpp
3 //
4 //      Copyright (c) Microsoft Corporation. All rights reserved.
5 //
6 // Defines _write(), which writes a buffer to a file.
7 //
8 #include <corecrt_internal_lowio.h>
9 #include <corecrt_internal_mbstring.h>
10 #include <corecrt_internal_ptd_propagation.h>
11 #include <ctype.h>
12 #include <locale.h>
13 #include <stdlib.h>
14 #include <string.h>
15 #include <wchar.h>
16 
17 
18 
19 namespace
20 {
21     struct write_result
22     {
23         DWORD error_code;
24         DWORD char_count;
25         DWORD lf_count;
26     };
27 }
28 
29 
30 
31 // This is the normal size of the LF => CRLF translation buffer.  The default
32 // buffer is 4K, plus extra room for LF characters.  Not all buffers are exactly
33 // this size, but this is used as the base size.
34 static size_t const BUF_SIZE = 5 * 1024;
35 
36 
37 
38 // Writes a buffer to a file.  The way in which the buffer is written depends on
39 // the mode in which the file was opened (e.g., if the file is a text mode file,
40 // linefeed translation will take place).
41 //
42 // On success, this function returns the number of bytes actually written (note
43 // that "bytes" here is "bytes from the original buffer;" more or fewer bytes
44 // may have actually been written, due to linefeed translation, codepage
45 // translation, and other transformations).  On failure, this function returns 0
46 // and sets errno.
_write_internal(int const fh,void const * const buffer,unsigned const size,__crt_cached_ptd_host & ptd)47 extern "C" int __cdecl _write_internal(int const fh, void const* const buffer, unsigned const size, __crt_cached_ptd_host& ptd)
48 {
49     _UCRT_CHECK_FH_CLEAR_OSSERR_RETURN(ptd, fh, EBADF, -1);
50     _UCRT_VALIDATE_CLEAR_OSSERR_RETURN(ptd, (fh >= 0 && (unsigned)fh < (unsigned)_nhandle), EBADF, -1);
51     _UCRT_VALIDATE_CLEAR_OSSERR_RETURN(ptd, (_osfile(fh) & FOPEN), EBADF, -1);
52 
53     __acrt_lowio_lock_fh(fh);
54     int result = -1;
55     __try
56     {
57         if ((_osfile(fh) & FOPEN) == 0)
58         {
59             ptd.get_errno().set(EBADF);
60             ptd.get_doserrno().set(0);
61             _ASSERTE(("Invalid file descriptor. File possibly closed by a different thread",0));
62             __leave;
63         }
64 
65         result = _write_nolock(fh, buffer, size, ptd);
66     }
67     __finally
68     {
69         __acrt_lowio_unlock_fh(fh);
70     }
71     __endtry
72     return result;
73 }
74 
_write(int const fh,void const * const buffer,unsigned const size)75 extern "C" int __cdecl _write(int const fh, void const* const buffer, unsigned const size)
76 {
77     __crt_cached_ptd_host ptd;
78     return _write_internal(fh, buffer, size, ptd);
79 }
80 
write_requires_double_translation_nolock(int const fh,__crt_cached_ptd_host & ptd)81 static bool __cdecl write_requires_double_translation_nolock(int const fh, __crt_cached_ptd_host& ptd) throw()
82 {
83     // Double translation is required if both [a] the current locale is not the C
84     // locale or the file is open in a non-ANSI mode and [b] we are writing to the
85     // console.
86 
87     // If this isn't a TTY or a text mode screen, then it isn't the console:
88     if (!_isatty(fh))
89     {
90         return false;
91     }
92 
93     if ((_osfile(fh) & FTEXT) == 0) {
94         return false;
95     }
96 
97     // Get the current locale.  If we're in the C locale and the file is open
98     // in ANSI mode, we don't need double translation:
99     bool const is_c_locale = ptd.get_locale()->locinfo->locale_name[LC_CTYPE] == nullptr;
100     if (is_c_locale && _textmode(fh) == __crt_lowio_text_mode::ansi)
101     {
102         return false;
103     }
104 
105     // If we can't get the console mode, it's not the console:
106     DWORD mode;
107     if (!GetConsoleMode(reinterpret_cast<HANDLE>(_osfhnd(fh)), &mode))
108     {
109         return false;
110     }
111 
112     // Otherwise, double translation is required:
113     return true;
114 }
115 
116 
117 
write_double_translated_ansi_nolock(int const fh,_In_reads_ (buffer_size)char const * const buffer,unsigned const buffer_size,__crt_cached_ptd_host & ptd)118 static write_result __cdecl write_double_translated_ansi_nolock(
119     int                                 const fh,
120     _In_reads_(buffer_size) char const* const buffer,
121     unsigned                            const buffer_size,
122     __crt_cached_ptd_host&                    ptd
123     ) throw()
124 {
125     HANDLE      const os_handle  = reinterpret_cast<HANDLE>(_osfhnd(fh));
126     char const* const buffer_end = buffer + buffer_size;
127     UINT        const console_cp = GetConsoleOutputCP();
128     _locale_t   const locale     = ptd.get_locale();
129     bool        const is_utf8    = locale->locinfo->_public._locale_lc_codepage == CP_UTF8;
130 
131     write_result result = { 0 };
132 
133     for (char const* source_it = buffer; source_it < buffer_end; )
134     {
135         char const c = *source_it;
136 
137         // We require double conversion, to convert from the source multibyte
138         // to Unicode, then from Unicode back to multibyte, but in the console
139         // codepage.
140         //
141         // Here, we have to take into account that _write() might be called
142         // byte-by-byte, so when we see a lead byte without a trail byte, we
143         // have to store it and return no error.  When this function is called
144         // again, that byte will be combined with the next available character.
145         wchar_t wc[2] = { 0 };
146         int wc_used = 1;
147         if (is_utf8)
148         {
149             _ASSERTE(!_dbcsBufferUsed(fh));
150             const int mb_buf_size = sizeof(_mbBuffer(fh));
151             int mb_buf_used;
152             for (mb_buf_used = 0; mb_buf_used < mb_buf_size && _mbBuffer(fh)[mb_buf_used]; ++mb_buf_used)
153             {}
154 
155             if (mb_buf_used > 0)
156             {
157                 const int mb_len = _utf8_no_of_trailbytes(_mbBuffer(fh)[0]) + 1;
158                 _ASSERTE(1 < mb_len && mb_buf_used < mb_len);
159                 const int remaining_bytes = mb_len - mb_buf_used;
160                 if (remaining_bytes <= (buffer_end - source_it))
161                 {
162                     // We now have enough bytes to complete the code point
163                     char mb_buffer[MB_LEN_MAX];
164 
165                     for (int i = 0; i < mb_buf_used; ++i)
166                     {
167                         mb_buffer[i] = _mbBuffer(fh)[i];
168                     }
169                     for (int i = 0; i < remaining_bytes; ++i)
170                     {
171                         mb_buffer[i + mb_buf_used] = source_it[i];
172                     }
173 
174                     // Clear out the temp buffer
175                     for (int i = 0; i < mb_buf_used; ++i)
176                     {
177                         _mbBuffer(fh)[i] = 0;
178                     }
179 
180                     mbstate_t state{};
181                     const char* str = mb_buffer;
182                     if (mb_len == 4)
183                     {
184                         wc_used = 2;
185                     }
186                     if (__crt_mbstring::__mbsrtowcs_utf8(wc, &str, wc_used, &state, ptd) == -1)
187                     {
188                         return result;
189                     }
190                     source_it += (remaining_bytes - 1);
191                 }
192                 else
193                 {
194                     // Need to add some more bytes to the buffer for later
195                     const auto bytes_to_add = buffer_end - source_it;
196                     _ASSERTE(mb_buf_used + bytes_to_add < mb_buf_size);
197                     for (int i = 0; i < bytes_to_add; ++i)
198                     {
199                         _mbBuffer(fh)[i + mb_buf_used] = source_it[i];
200                     }
201                     // Pretend we wrote the bytes, because this isn't an error *yet*.
202                     result.char_count += static_cast<DWORD>(bytes_to_add);
203                     return result;
204                 }
205             }
206             else
207             {
208                 const int mb_len = _utf8_no_of_trailbytes(*source_it) + 1;
209                 const auto available_bytes = buffer_end - source_it;
210                 if (mb_len <= (available_bytes))
211                 {
212                     // We have enough bytes to write the entire code point
213                     mbstate_t state{};
214                     const char* str = source_it;
215                     if (mb_len == 4)
216                     {
217                         wc_used = 2;
218                     }
219                     if (__crt_mbstring::__mbsrtowcs_utf8(wc, &str, wc_used, &state, ptd) == -1)
220                     {
221                         return result;
222                     }
223                     source_it += (mb_len - 1);
224                 }
225                 else
226                 {
227                     // Not enough bytes for this code point
228                     _ASSERTE(available_bytes <= sizeof(_mbBuffer(fh)));
229                     for (int i = 0; i < available_bytes; ++i)
230                     {
231                         _mbBuffer(fh)[i] = source_it[i];
232                     }
233                     // Pretend we wrote the bytes, because this isn't an error *yet*.
234                     result.char_count += static_cast<DWORD>(available_bytes);
235                     return result;
236                 }
237             }
238         }
239         else if (_dbcsBufferUsed(fh))
240         {
241             // We already have a DBCS lead byte buffered.  Take the current
242             // character, combine it with the lead byte, and convert:
243             _ASSERTE(_isleadbyte_fast_internal(_dbcsBuffer(fh), locale));
244 
245             char mb_buffer[MB_LEN_MAX];
246             mb_buffer[0] = _dbcsBuffer(fh);
247             mb_buffer[1] = *source_it;
248 
249             _dbcsBufferUsed(fh) = false;
250 
251             if (_mbtowc_internal(wc, mb_buffer, 2, ptd) == -1)
252             {
253                 return result;
254             }
255         }
256         else
257         {
258             if (_isleadbyte_fast_internal(*source_it, locale))
259             {
260                 if ((source_it + 1) < buffer_end)
261                 {
262                     // And we have more bytes to read, just convert...
263                     if (_mbtowc_internal(wc, source_it, 2, ptd) == -1)
264                     {
265                         return result;
266                     }
267 
268                     // Increment the source_it to accomodate the DBCS character:
269                     ++source_it;
270                 }
271                 else
272                 {
273                     // And we ran out of bytes to read, so buffer the lead byte:
274                     _dbcsBuffer(fh) = *source_it;
275                     _dbcsBufferUsed(fh) = true;
276 
277                     // We lie here that we actually wrote the last character, to
278                     // ensure we don't consider this an error:
279                     ++result.char_count;
280                     return result;
281                 }
282             }
283             else
284             {
285                 // single character conversion:
286                 if (_mbtowc_internal(wc, source_it, 1, ptd) == -1)
287                 {
288                     return result;
289                 }
290             }
291         }
292 
293         ++source_it;
294 
295         // Translate the Unicode character into Multibyte in the console codepage
296         // and write the character to the file:
297         char mb_buffer[MB_LEN_MAX];
298         DWORD const size = static_cast<DWORD>(__acrt_WideCharToMultiByte(
299             console_cp, 0, wc, wc_used, mb_buffer, sizeof(mb_buffer), nullptr, nullptr));
300 
301         if(size == 0)
302             return result;
303 
304         DWORD written;
305         if (!WriteFile(os_handle, mb_buffer, size, &written, nullptr))
306         {
307             result.error_code = GetLastError();
308             return result;
309         }
310 
311         // When we are converting, some conversions may result in:
312         //
313         //     2 MBCS characters => 1 wide character => 1 MBCS character.
314         //
315         // For example, when printing Japanese characters in the English console
316         // codepage, each source character is transformed into a single question
317         // mark.  Therefore, we want to track the number of bytes we converted,
318         // plus the linefeed count, instead of how many bytes we actually wrote.
319         result.char_count = result.lf_count + static_cast<DWORD>(source_it - buffer);
320 
321         // If the write succeeded but didn't write all of the characters, return:
322         if (written < size)
323         {
324             return result;
325         }
326 
327         // If the original character that we read was an LF, write a CR too:
328         // CRT_REFACTOR TODO Doesn't this write LFCR instead of CRLF?
329         if (c == LF)
330         {
331             wchar_t const cr = CR;
332             if (!WriteFile(os_handle, &cr, 1, &written, nullptr))
333             {
334                 result.error_code = GetLastError();
335                 return result;
336             }
337 
338             if (written < 1)
339             {
340                 return result;
341             }
342 
343             ++result.lf_count;
344             ++result.char_count;
345         }
346     }
347 
348     return result;
349 }
350 
351 
352 
353 static write_result __cdecl write_double_translated_unicode_nolock(
_In_reads_(buffer_size)354     _In_reads_(buffer_size)                      char const* const buffer,
355     _In_ _Pre_satisfies_((buffer_size % 2) == 0) unsigned    const buffer_size
356     ) throw()
357 {
358     // When writing to a Unicode file (UTF-8 or UTF-16LE) that corresponds to
359     // the console, we don't actually need double translation.  We just need to
360     // print each character to the console, one-by-one.  (This function is
361     // named what it is because its use is guarded by the double translation
362     // check, and to match the name of the corresponding ANSI function.)
363 
364     write_result result = { 0 };
365 
366     // Needed for SAL to clarify that buffer_size is even.
367     _Analysis_assume_((buffer_size/2) != ((buffer_size-1)/2));
368     char const* const buffer_end = buffer + buffer_size;
369     for (char const* pch = buffer; pch < buffer_end; pch += 2)
370     {
371         wchar_t const c = *reinterpret_cast<wchar_t const*>(pch);
372 
373         // _putwch_nolock does not depend on global state, no PTD needed to be propagated.
374         if (_putwch_nolock(c) == c)
375         {
376             result.char_count += 2;
377         }
378         else
379         {
380             result.error_code = GetLastError();
381             return result;
382         }
383 
384         // If the character was a carriage return, also emit a line feed.
385         // CRT_REFACTOR TODO Doesn't this print LFCR instead of CRLF?
386         if (c == LF)
387         {
388             // _putwch_nolock does not depend on global state, no PTD needed to be propagated.
389             if (_putwch_nolock(CR) != CR)
390             {
391                 result.error_code = GetLastError();
392                 return result;
393             }
394 
395             ++result.char_count;
396             ++result.lf_count;
397         }
398     }
399 
400     return result;
401 }
402 
403 
404 
write_text_ansi_nolock(int const fh,_In_reads_ (buffer_size)char const * const buffer,unsigned const buffer_size)405 static write_result __cdecl write_text_ansi_nolock(
406     int                                 const fh,
407     _In_reads_(buffer_size) char const* const buffer,
408     unsigned                            const buffer_size
409     ) throw()
410 {
411     HANDLE      const os_handle  = reinterpret_cast<HANDLE>(_osfhnd(fh));
412     char const* const buffer_end = buffer + buffer_size;
413 
414     write_result result = { 0 };
415 
416     for (char const* source_it = buffer; source_it < buffer_end; )
417     {
418         char lfbuf[BUF_SIZE]; // The LF => CRLF translation buffer
419 
420         // One-past-the-end of the translation buffer.  Note that we subtract
421         // one to account for the case where we're pointing to the last element
422         // in the buffer and we need to write both a CR and an LF.
423         char* const lfbuf_end = lfbuf + sizeof(lfbuf) - 1;
424 
425         // Translate the source buffer into the translation buffer.  Note that
426         // both source_it and lfbuf_it are incremented in the loop.
427         char* lfbuf_it = lfbuf;
428         while (lfbuf_it < lfbuf_end && source_it < buffer_end)
429         {
430             char const c = *source_it++;
431 
432             if (c == LF)
433             {
434                 ++result.lf_count;
435                 *lfbuf_it++ = CR;
436             }
437 
438             *lfbuf_it++ = c;
439         }
440 
441         DWORD const lfbuf_length = static_cast<DWORD>(lfbuf_it - lfbuf);
442 
443         DWORD written;
444         if (!WriteFile(os_handle, lfbuf, lfbuf_length, &written, nullptr))
445         {
446             result.error_code = GetLastError();
447             return result;
448         }
449 
450         result.char_count += written;
451         if (written < lfbuf_length)
452         {
453             return result; // The write succeeded but didn't write everything
454         }
455     }
456 
457     return result;
458 }
459 
460 
461 
write_text_utf16le_nolock(int const fh,_In_reads_ (buffer_size)char const * const buffer,unsigned const buffer_size)462 static write_result __cdecl write_text_utf16le_nolock(
463     int                                 const fh,
464     _In_reads_(buffer_size) char const* const buffer,
465     unsigned                            const buffer_size
466     ) throw()
467 {
468     HANDLE         const os_handle  = reinterpret_cast<HANDLE>(_osfhnd(fh));
469     wchar_t const* const buffer_end = reinterpret_cast<wchar_t const*>(buffer + buffer_size);
470 
471     write_result result = { 0 };
472 
473     wchar_t const* source_it = reinterpret_cast<wchar_t const*>(buffer);
474     while (source_it < buffer_end)
475     {
476         wchar_t lfbuf[BUF_SIZE / sizeof(wchar_t)]; // The translation buffer
477 
478         // One-past-the-end of the translation buffer.  Note that we subtract
479         // one to account for the case where we're pointing to the last element
480         // in the buffer and we need to write both a CR and an LF.
481         wchar_t const* lfbuf_end = lfbuf + BUF_SIZE / sizeof(wchar_t) - 1;
482 
483         // Translate the source buffer into the translation buffer.  Note that
484         // both source_it and lfbuf_it are incremented in the loop.
485         wchar_t* lfbuf_it = lfbuf;
486         while (lfbuf_it < lfbuf_end && source_it < buffer_end)
487         {
488             wchar_t const c = *source_it++;
489 
490             if (c == LF)
491             {
492                 result.lf_count += 2;
493                 *lfbuf_it++ = CR;
494             }
495 
496             *lfbuf_it++ = c;
497         }
498 
499         // Note that this length is in bytes, not wchar_t elemnts, since we need
500         // to tell WriteFile how many bytes (not characters) to write:
501         DWORD const lfbuf_length = static_cast<DWORD>(lfbuf_it - lfbuf) * sizeof(wchar_t);
502 
503 
504         // Attempt the write and return immediately if it fails:
505         DWORD written;
506         if (!WriteFile(os_handle, lfbuf, lfbuf_length, &written, nullptr))
507         {
508             result.error_code = GetLastError();
509             return result;
510         }
511 
512         result.char_count += written;
513         if (written < lfbuf_length)
514         {
515             return result; // The write succeeded, but didn't write everything
516         }
517     }
518 
519     return result;
520 }
521 
522 
523 
write_text_utf8_nolock(int const fh,_In_reads_ (buffer_size)char const * const buffer,unsigned const buffer_size)524 static write_result __cdecl write_text_utf8_nolock(
525     int                                 const fh,
526     _In_reads_(buffer_size) char const* const buffer,
527     unsigned                            const buffer_size
528     ) throw()
529 {
530     HANDLE         const os_handle  = reinterpret_cast<HANDLE>(_osfhnd(fh));
531     wchar_t const* const buffer_end = reinterpret_cast<wchar_t const*>(buffer + buffer_size);
532 
533     write_result result = { 0 };
534 
535     wchar_t const* source_it = reinterpret_cast<wchar_t const*>(buffer);
536     while (source_it < buffer_end)
537     {
538         // The translation buffer.  We use two buffers:  the first is used to
539         // store the UTF-16 LF => CRLF translation (this is that buffer here).
540         // The second is used for storing the conversion to UTF-8 (defined
541         // below).  The sizes are selected to handle the worst-case scenario
542         // where each UTF-8 character is four bytes long.
543         wchar_t utf16_buf[BUF_SIZE / 6];
544 
545         // One-past-the-end of the translation buffer.  Note that we subtract
546         // one to account for the case where we're pointing to the last element
547         // in the buffer and we need to write both a CR and an LF.
548         wchar_t const* utf16_buf_end = utf16_buf + (BUF_SIZE / 6 - 1);
549 
550         // Translate the source buffer into the translation buffer.  Note that
551         // both source_it and lfbuf_it are incremented in the loop.
552         wchar_t* utf16_buf_it = utf16_buf;
553         while (utf16_buf_it < utf16_buf_end && source_it < buffer_end)
554         {
555             wchar_t const c = *source_it++;
556 
557             if (c == LF)
558             {
559                 // No need to count the number of line-feeds translated; we
560                 // track the number of written characters by counting the total
561                 // number of characters written from the UTF8 buffer (see below
562                 // where we update the char_count).
563                 *utf16_buf_it++ = CR;
564             }
565 
566             *utf16_buf_it++ = c;
567         }
568 
569         // Note that this length is in characters, not bytes.
570         DWORD const utf16_buf_length = static_cast<DWORD>(utf16_buf_it - utf16_buf);
571 
572 
573         // This is the second translation, where we translate the UTF-16 text to
574         // UTF-8, into the UTF-8 buffer:
575         char utf8_buf[(BUF_SIZE * 2) / 3];
576         DWORD const bytes_converted = static_cast<DWORD>(__acrt_WideCharToMultiByte(
577                 CP_UTF8,
578                 0,
579                 utf16_buf,
580                 utf16_buf_length,
581                 utf8_buf,
582                 sizeof(utf8_buf),
583                 nullptr,
584                 nullptr));
585 
586         if (bytes_converted == 0)
587         {
588             result.error_code = GetLastError();
589             return result;
590         }
591 
592         // Here, we need to make every attempt to write all of the converted
593         // characters to avoid corrupting the stream.  If, for example, we write
594         // only half of the bytes of a UTF-8 character, the stream may be
595         // corrupted.
596         //
597         // This loop will ensure that we exit only if either (a) all of the
598         // bytes are written, ensuring that no partial MBCSes are written, or
599         // (b) there is an error in the stream.
600         for (DWORD bytes_written = 0; bytes_written < bytes_converted; )
601         {
602             char const* const current      = utf8_buf + bytes_written;
603             DWORD       const current_size = bytes_converted - bytes_written;
604 
605             DWORD written;
606             if (!WriteFile(os_handle, current, current_size, &written, nullptr))
607             {
608                 result.error_code = GetLastError();
609                 return result;
610             }
611 
612             bytes_written += written;
613         }
614 
615         // If this chunk was committed successfully, update the character count:
616         result.char_count = static_cast<DWORD>(reinterpret_cast<char const*>(source_it) - buffer);
617     }
618 
619     return result;
620 }
621 
622 
623 
write_binary_nolock(int const fh,_In_reads_ (buffer_size)char const * const buffer,unsigned const buffer_size)624 static write_result __cdecl write_binary_nolock(
625     int                                 const fh,
626     _In_reads_(buffer_size) char const* const buffer,
627     unsigned                            const buffer_size
628     ) throw()
629 {
630     HANDLE const os_handle = reinterpret_cast<HANDLE>(_osfhnd(fh));
631 
632     // Compared to text files, binary files are easy...
633     write_result result = { 0 };
634     if (!WriteFile(os_handle, buffer, buffer_size, &result.char_count, nullptr))
635     {
636         result.error_code = GetLastError();
637     }
638 
639     return result;
640 }
641 
642 
643 
_write_nolock(int const fh,void const * const buffer,unsigned const buffer_size,__crt_cached_ptd_host & ptd)644 extern "C" int __cdecl _write_nolock(int const fh, void const* const buffer, unsigned const buffer_size, __crt_cached_ptd_host& ptd)
645 {
646     // If the buffer is empty, there is nothing to be written:
647     if (buffer_size == 0)
648     {
649         return 0;
650     }
651 
652     // If the buffer is null, though... well, that is not allowed:
653     _UCRT_VALIDATE_CLEAR_OSSERR_RETURN(ptd, buffer != nullptr, EINVAL, -1);
654 
655     __crt_lowio_text_mode const fh_textmode = _textmode(fh);
656 
657     // If the file is open for Unicode, the buffer size must always be even:
658     if (fh_textmode == __crt_lowio_text_mode::utf16le || fh_textmode == __crt_lowio_text_mode::utf8)
659     {
660         _UCRT_VALIDATE_CLEAR_OSSERR_RETURN(ptd, buffer_size % 2 == 0, EINVAL, -1);
661     }
662 
663     // If the file is opened for appending, seek to the end of the file.  We
664     // ignore errors because the underlying file may not allow seeking.
665     if (_osfile(fh) & FAPPEND)
666     {
667         (void)_lseeki64_nolock_internal(fh, 0, FILE_END, ptd);
668     }
669 
670     char const* const char_buffer = static_cast<char const*>(buffer);
671 
672     // Dispatch the actual writing to one of the helper routines based on the
673     // text mode of the file and whether or not the file refers to the console.
674     //
675     // Note that in the event that the handle belongs to the console, WriteFile
676     // will generate garbage output.  To print to the console correctly, we need
677     // to print ANSI.  Also note that when printing to the console, we need to
678     // convert the characters to the console codepge.
679     write_result result = { 0 };
680     if (write_requires_double_translation_nolock(fh, ptd))
681     {
682         switch (fh_textmode)
683         {
684         case __crt_lowio_text_mode::ansi:
685             result = write_double_translated_ansi_nolock(fh, char_buffer, buffer_size, ptd);
686             break;
687 
688         case __crt_lowio_text_mode::utf16le:
689         case __crt_lowio_text_mode::utf8:
690             _Analysis_assume_((buffer_size % 2) == 0);
691             result = write_double_translated_unicode_nolock(char_buffer, buffer_size);
692             break;
693         }
694     }
695     else if (_osfile(fh) & FTEXT)
696     {
697         switch (fh_textmode)
698         {
699         case __crt_lowio_text_mode::ansi:
700             result = write_text_ansi_nolock(fh, char_buffer, buffer_size);
701             break;
702 
703         case __crt_lowio_text_mode::utf16le:
704             result = write_text_utf16le_nolock(fh, char_buffer, buffer_size);
705             break;
706 
707         case __crt_lowio_text_mode::utf8:
708             result = write_text_utf8_nolock(fh, char_buffer, buffer_size);
709             break;
710         }
711     }
712     else
713     {
714         result = write_binary_nolock(fh, char_buffer, buffer_size);
715     }
716 
717 
718     // Why did we not write anything?  Lettuce find out...
719     if (result.char_count == 0)
720     {
721         // If nothing was written, check to see if it was due to an OS error:
722         if (result.error_code != 0)
723         {
724             // An OS error occurred.  ERROR_ACCESS_DENIED should be mapped in
725             // this case to EBADF, not EACCES.  All other errors are mapped
726             // normally:
727             if (result.error_code == ERROR_ACCESS_DENIED)
728             {
729                 ptd.get_errno().set(EBADF);
730                 ptd.get_doserrno().set(result.error_code);
731             }
732             else
733             {
734                 __acrt_errno_map_os_error_ptd(result.error_code, ptd);
735             }
736 
737             return -1;
738         }
739 
740         // If this file is a device and the first character was Ctrl+Z, then
741         // writing nothing is the expected behavior and is not an error:
742         if ((_osfile(fh) & FDEV) && *char_buffer == CTRLZ)
743         {
744             return 0;
745         }
746 
747         // Otherwise, the error is reported as ENOSPC:
748         ptd.get_errno().set(ENOSPC);
749         ptd.get_doserrno().set(0);
750         return -1;
751     }
752 
753     // The write succeeded.  Return the adjusted number of bytes written:
754     return result.char_count - result.lf_count;
755 }
756