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