1 /* win_utils.c - utility functions for Windows and CygWin */
2 #if defined(_WIN32) || defined(__CYGWIN__)
3 
4 /* Fix #138: require to use MSVCRT implementation of *printf functions */
5 #define __USE_MINGW_ANSI_STDIO 0
6 #include "win_utils.h"
7 #include <windows.h>
8 
9 /**
10  * Set process priority and affinity to use any CPU but the first one,
11  * this improves benchmark results on a multi-core systems.
12  */
set_benchmark_cpu_affinity(void)13 void set_benchmark_cpu_affinity(void)
14 {
15 	DWORD_PTR dwProcessMask, dwSysMask, dwDesired;
16 
17 	SetPriorityClass(GetCurrentProcess(), HIGH_PRIORITY_CLASS);
18 	SetThreadPriority(GetCurrentThread(), THREAD_PRIORITY_HIGHEST);
19 
20 	if ( GetProcessAffinityMask(GetCurrentProcess(), &dwProcessMask, &dwSysMask) ) {
21 		dwDesired = dwSysMask & (dwProcessMask & ~1); /* remove the first processor */
22 		dwDesired = (dwDesired ? dwDesired : dwSysMask & ~1);
23 		if (dwDesired != 0) {
24 			SetProcessAffinityMask(GetCurrentProcess(), dwDesired);
25 		}
26 	}
27 }
28 
29 #ifdef _WIN32
30 /* Windows-only (non-CygWin) functions */
31 
32 #include "file.h"
33 #include "parse_cmdline.h"
34 #include <share.h> /* for _SH_DENYWR */
35 #include <fcntl.h> /* for _O_RDONLY, _O_BINARY */
36 #include <io.h> /* for isatty */
37 #include <assert.h>
38 #include <errno.h>
39 #include <locale.h>
40 
41 struct console_data_t
42 {
43 	unsigned console_flags;
44 	unsigned saved_cursor_size;
45 	unsigned primary_codepage;
46 	unsigned secondary_codepage;
47 	wchar_t format_buffer[4096];
48 	wchar_t printf_result[65536];
49 	wchar_t program_dir[32768];
50 };
51 struct console_data_t console_data;
52 
53 /**
54  * Convert a c-string as wide character string using given codepage
55  * and print it to the given buffer.
56  *
57  * @param str the string to convert
58  * @param codepage the codepage to use
59  * @param buffer buffer to print the string to
60  * @param buf_size buffer size in bytes
61  * @return converted string on success, NULL on fail with error code stored in errno
62  */
cstr_to_wchar_buffer(const char * str,int codepage,wchar_t * buffer,size_t buf_size)63 static wchar_t* cstr_to_wchar_buffer(const char* str, int codepage, wchar_t* buffer, size_t buf_size)
64 {
65 	if (MultiByteToWideChar(codepage, 0, str, -1, buffer, buf_size / sizeof(wchar_t)) != 0)
66 		return buffer;
67 	set_errno_from_last_file_error();
68 	return NULL; /* conversion failed */
69 }
70 
71 /**
72  * Convert a c-string to wide character string using given codepage.
73  * The result is allocated with malloc and must be freed by caller.
74  *
75  * @param str the string to convert
76  * @param codepage the codepage to use
77  * @param exact_conversion non-zero to require exact encoding conversion, 0 otherwise
78  * @return converted string on success, NULL on fail with error code stored in errno
79  */
cstr_to_wchar(const char * str,int codepage,int exact_conversion)80 static wchar_t* cstr_to_wchar(const char* str, int codepage, int exact_conversion)
81 {
82 	DWORD flags = (exact_conversion ? MB_ERR_INVALID_CHARS : 0);
83 	int size = MultiByteToWideChar(codepage, flags, str, -1, NULL, 0);
84 	if (size != 0) {
85 		wchar_t* buf = (wchar_t*)rsh_malloc(size * sizeof(wchar_t));
86 		if (MultiByteToWideChar(codepage, flags, str, -1, buf, size) != 0)
87 			return buf;
88 	}
89 	set_errno_from_last_file_error();
90 	return NULL; /* conversion failed */
91 }
92 
93 /**
94  * Convert a wide character string to c-string using given codepage.
95  * The result is allocated with malloc and must be freed by caller.
96  *
97  * @param wstr the wide string to convert
98  * @param codepage the codepage to use
99  * @param exact_conversion non-zero to require exact encoding conversion, 0 otherwise
100  * @return converted string on success, NULL on fail with error code stored in errno
101  */
wchar_to_cstr(const wchar_t * wstr,int codepage,int exact_conversion)102 static char* wchar_to_cstr(const wchar_t* wstr, int codepage, int exact_conversion)
103 {
104 	int size;
105 	BOOL bUsedDefChar = FALSE;
106 	BOOL* lpUsedDefaultChar = (exact_conversion && codepage != CP_UTF8 ? &bUsedDefChar : NULL);
107 	if (codepage == -1) {
108 		codepage = (opt.flags & OPT_UTF8 ? CP_UTF8 : (opt.flags & OPT_ENC_DOS) ? CP_OEMCP : CP_ACP);
109 	}
110 	size = WideCharToMultiByte(codepage, 0, wstr, -1, NULL, 0, 0, NULL);
111 	/* size=0 is returned only on fail, cause even an empty string requires buffer size=1 */
112 	if (size != 0) {
113 		char* buf = (char*)rsh_malloc(size);
114 		if (WideCharToMultiByte(codepage, 0, wstr, -1, buf, size, 0, lpUsedDefaultChar) != 0) {
115 			if (!bUsedDefChar)
116 				return buf;
117 			free(buf);
118 			errno = EILSEQ;
119 			return NULL;
120 		}
121 		free(buf);
122 	}
123 	set_errno_from_last_file_error();
124 	return NULL;
125 }
126 
127 /**
128  * Convert c-string to wide string using encoding and transformation specified by flags.
129  * The result is allocated with malloc and must be freed by caller.
130  *
131  * @param str the C-string to convert
132  * @param flags bit-mask containing the following bits: ConvertToPrimaryEncoding, ConvertToSecondaryEncoding,
133  *              ConvertToUtf8, ConvertToNative, ConvertPath, ConvertExact
134  * @return converted wide string on success, NULL on fail with error code stored in errno
135  */
convert_str_to_wcs(const char * str,unsigned flags)136 wchar_t* convert_str_to_wcs(const char* str, unsigned flags)
137 {
138 	int is_utf8 = flags & (opt.flags & OPT_UTF8 ? (ConvertToUtf8 | ConvertToPrimaryEncoding) : (ConvertToUtf8 | ConvertToSecondaryEncoding));
139 	int codepage = (is_utf8 ? CP_UTF8 : (opt.flags & OPT_ENC_DOS) ? CP_OEMCP : CP_ACP);
140 	wchar_t* wstr = cstr_to_wchar(str, codepage, (flags & ConvertExact));
141 	if (wstr && (flags & ConvertPath)) {
142 		wchar_t* long_path = get_long_path_if_needed(wstr);
143 		if (long_path) {
144 			free(wstr);
145 			return long_path;
146 		}
147 	}
148 	return wstr;
149 }
150 
151 /**
152  * Convert wide string to c-string using encoding and transformation specified by flags.
153  *
154  * @param str the C-string to convert
155  * @param flags bit-mask containing the following bits: ConvertToPrimaryEncoding, ConvertToSecondaryEncoding,
156  *              ConvertToUtf8, ConvertToNative, ConvertPath, ConvertExact
157  * @return converted wide string on success, NULL on fail with error code stored in errno
158  */
convert_wcs_to_str(const wchar_t * wstr,unsigned flags)159 char* convert_wcs_to_str(const wchar_t* wstr, unsigned flags)
160 {
161 	int is_utf8 = flags & (opt.flags & OPT_UTF8 ? (ConvertToUtf8 | ConvertToPrimaryEncoding) : (ConvertToUtf8 | ConvertToSecondaryEncoding));
162 	int codepage = (is_utf8 ? CP_UTF8 : (opt.flags & OPT_ENC_DOS) ? CP_OEMCP : CP_ACP);
163 	/* skip path UNC prefix, if found */
164 	if ((flags & ConvertPath) && IS_UNC_PREFIX(wstr))
165 		wstr += UNC_PREFIX_SIZE;
166 	return wchar_to_cstr(wstr, codepage, (flags & ConvertExact));
167 }
168 
169 /**
170  * Convert c-string encoding, the encoding is specified by flags.
171  *
172  * @param str the C-string to convert
173  * @param flags bit-mask containing the following bits: ConvertToUtf8, ConvertToNative, ConvertExact
174  * @return converted wide string on success, NULL on fail with error code stored in errno
175  */
convert_str_encoding(const char * str,unsigned flags)176 char* convert_str_encoding(const char* str, unsigned flags)
177 {
178 	int convert_from = (flags & ConvertToUtf8 ? ConvertNativeToWcs : ConvertUtf8ToWcs) | (flags & ConvertExact);
179 	wchar_t* wstr;
180 	assert((flags & ~(ConvertToUtf8 | ConvertToNative | ConvertExact)) == 0); /* disallow invalid flags */
181 	if ((flags & (ConvertToUtf8 | ConvertToNative)) == 0) {
182 		errno = EINVAL;
183 		return NULL; /* error: no conversion needed */
184 	}
185 	wstr = convert_str_to_wcs(str, convert_from);
186 	if (wstr) {
187 		char* res = convert_wcs_to_str(wstr, flags);
188 		free(wstr);
189 		return res;
190 	}
191 	return NULL;
192 }
193 
194 /**
195  * Allocate a wide string containing long file path with UNC prefix,
196  * if it is needed to access the path, otherwise return NULL.
197  *
198  * @param wpath original file path, can be a relative one
199  * @return allocated long file path if it is needed to access
200  *         the path, NULL otherwise
201  */
get_long_path_if_needed(const wchar_t * wpath)202 wchar_t* get_long_path_if_needed(const wchar_t* wpath)
203 {
204 	if (!IS_UNC_PREFIX(wpath) && wcslen(wpath) > 200) {
205 		DWORD size = GetFullPathNameW(wpath, 0, NULL, NULL);
206 		if (size > 0) {
207 			wchar_t* result = (wchar_t*)rsh_malloc((size + UNC_PREFIX_SIZE) * sizeof(wchar_t));
208 			wcscpy(result, L"\\\\?\\");
209 			size = GetFullPathNameW(wpath, size, result + UNC_PREFIX_SIZE, NULL);
210 			if (size > 0)
211 				return result;
212 			free(result);
213 		}
214 	}
215 	return NULL;
216 }
217 
218 /* the range of error codes for access errors */
219 #define MIN_EACCES_RANGE ERROR_WRITE_PROTECT
220 #define MAX_EACCES_RANGE ERROR_SHARING_BUFFER_EXCEEDED
221 
222 /**
223  * Convert the GetLastError() value to errno-compatible code.
224  *
225  * @return errno-compatible error code
226  */
convert_last_error_to_errno(void)227 static int convert_last_error_to_errno(void)
228 {
229 	DWORD error_code = GetLastError();
230 	switch (error_code)
231 	{
232 	case NO_ERROR:
233 		return 0;
234 	case ERROR_FILE_NOT_FOUND:
235 	case ERROR_PATH_NOT_FOUND:
236 	case ERROR_INVALID_DRIVE:
237 	case ERROR_INVALID_NAME:
238 	case ERROR_BAD_NETPATH:
239 	case ERROR_BAD_PATHNAME:
240 	case ERROR_FILENAME_EXCED_RANGE:
241 		return ENOENT;
242 	case ERROR_TOO_MANY_OPEN_FILES:
243 		return EMFILE;
244 	case ERROR_ACCESS_DENIED:
245 	case ERROR_SHARING_VIOLATION:
246 		return EACCES;
247 	case ERROR_NETWORK_ACCESS_DENIED:
248 	case ERROR_FAIL_I24:
249 	case ERROR_SEEK_ON_DEVICE:
250 		return EACCES;
251 	case ERROR_LOCK_VIOLATION:
252 	case ERROR_DRIVE_LOCKED:
253 	case ERROR_NOT_LOCKED:
254 	case ERROR_LOCK_FAILED:
255 		return EACCES;
256 	case ERROR_INVALID_HANDLE:
257 		return EBADF;
258 	case ERROR_NOT_ENOUGH_MEMORY:
259 	case ERROR_INVALID_BLOCK:
260 	case ERROR_NOT_ENOUGH_QUOTA:
261 	case ERROR_INSUFFICIENT_BUFFER:
262 		return ENOMEM;
263 	case ERROR_INVALID_ACCESS:
264 	case ERROR_INVALID_DATA:
265 	case ERROR_INVALID_PARAMETER:
266 	case ERROR_INVALID_FLAGS:
267 		return EINVAL;
268 	case ERROR_BROKEN_PIPE:
269 	case ERROR_NO_DATA:
270 		return EPIPE;
271 	case ERROR_DISK_FULL:
272 		return ENOSPC;
273 	case ERROR_ALREADY_EXISTS:
274 		return EEXIST;
275 	case ERROR_NESTING_NOT_ALLOWED:
276 		return EAGAIN;
277 	case ERROR_NO_UNICODE_TRANSLATION:
278 		return EILSEQ;
279 	}
280 
281 	/* try to detect error by range */
282 	if (MIN_EACCES_RANGE <= error_code && error_code <= MAX_EACCES_RANGE) {
283 		return EACCES;
284 	} else {
285 		return EINVAL;
286 	}
287 }
288 
289 /**
290  * Assign errno to the error value converted from the GetLastError().
291  */
set_errno_from_last_file_error(void)292 void set_errno_from_last_file_error(void)
293 {
294 	errno = convert_last_error_to_errno();
295 }
296 
297 /* functions to setup/restore console */
298 
299 /**
300  * Restore console on program exit.
301  */
restore_cursor(void)302 static void restore_cursor(void)
303 {
304 	CONSOLE_CURSOR_INFO cci;
305 	HANDLE hOut = GetStdHandle(STD_ERROR_HANDLE);
306 	if (hOut != INVALID_HANDLE_VALUE && console_data.saved_cursor_size) {
307 		/* restore cursor size and visibility */
308 		cci.dwSize = console_data.saved_cursor_size;
309 		cci.bVisible = 1;
310 		SetConsoleCursorInfo(hOut, &cci);
311 	}
312 }
313 
314 /**
315  * Hide console cursor.
316  */
hide_cursor(void)317 void hide_cursor(void)
318 {
319 	CONSOLE_CURSOR_INFO cci;
320 	HANDLE hOut = GetStdHandle(STD_ERROR_HANDLE);
321 	if (hOut != INVALID_HANDLE_VALUE && GetConsoleCursorInfo(hOut, &cci))
322 	{
323 		/* store current cursor size and visibility flag */
324 		console_data.saved_cursor_size = (cci.bVisible ? cci.dwSize : 0);
325 
326 		/* now hide cursor */
327 		cci.bVisible = 0;
328 		SetConsoleCursorInfo(hOut, &cci); /* hide cursor */
329 		rsh_install_exit_handler(restore_cursor);
330 	}
331 }
332 
333 /**
334  * Prepare console on program initialization: change console font codepage
335  * according to program options and hide cursor.
336  */
setup_console(void)337 void setup_console(void)
338 {
339 	/* the default encoding is UTF-8 */
340 	if ((opt.flags & OPT_ENCODING) == 0)
341 		opt.flags |= OPT_UTF8;
342 
343 	console_data.console_flags = 0;
344 	console_data.saved_cursor_size = 0;
345 	console_data.primary_codepage = (opt.flags & OPT_UTF8 ? CP_UTF8 : (opt.flags & OPT_ENC_DOS) ? CP_OEMCP : CP_ACP);
346 	console_data.secondary_codepage = (!(opt.flags & OPT_UTF8) ? CP_UTF8 : (opt.flags & OPT_ENC_DOS) ? CP_OEMCP : CP_ACP);
347 
348 	/* note: we are using numbers 1 = _fileno(stdout), 2 = _fileno(stderr) */
349 	/* cause _fileno() is undefined, when compiling as strict ansi C. */
350 	if (isatty(1))
351 		console_data.console_flags |= 1;
352 	if (isatty(2))
353 		console_data.console_flags |= 2;
354 
355 	if ((opt.flags & OPT_UTF8) != 0)
356 	{
357 		if (console_data.console_flags & 1)
358 			_setmode(1, _O_U8TEXT);
359 		if (console_data.console_flags & 2)
360 			_setmode(2, _O_U8TEXT);
361 
362 #ifdef USE_GETTEXT
363 		bind_textdomain_codeset(TEXT_DOMAIN, "utf-8");
364 #endif /* USE_GETTEXT */
365 	}
366 	else
367 	{
368 		setlocale(LC_CTYPE, opt.flags & OPT_ENC_DOS ? ".OCP" : ".ACP");
369 	}
370 }
371 
get_program_dir(void)372 wchar_t* get_program_dir(void)
373 {
374 	return console_data.program_dir;
375 }
376 
377 /**
378  * Check if the given stream is connected to console.
379  *
380  * @param stream the stream to check
381  * @return 1 if the stream is connected to console, 0 otherwise
382  */
win_is_console_stream(FILE * stream)383 int win_is_console_stream(FILE* stream)
384 {
385 	return ((stream == stdout && (console_data.console_flags & 1))
386 		|| (stream == stderr && (console_data.console_flags & 2)));
387 }
388 
389 /**
390  * Detect the program directory.
391  */
init_program_dir(void)392 void init_program_dir(void)
393 {
394 	DWORD buf_size = sizeof(console_data.program_dir) / sizeof(console_data.program_dir[0]);
395 	DWORD length;
396 	length = GetModuleFileNameW(NULL, console_data.program_dir, buf_size);
397 	if (length == 0 || length >= buf_size) {
398 		console_data.program_dir[0] = 0;
399 		return;
400 	}
401 	/* remove trailng file name with the last path separator */
402 	for (; length > 0 && !IS_PATH_SEPARATOR_W(console_data.program_dir[length]); length--);
403 	for (; length > 0 && IS_PATH_SEPARATOR_W(console_data.program_dir[length]); length--);
404 	console_data.program_dir[length + 1] = 0;
405 }
406 
407 #ifdef USE_GETTEXT
408 /**
409  * Check that the path points to an existing directory.
410  *
411  * @param path the path to check
412  * @return 1 if the argument is a directory, 0 otherwise
413  */
is_directory(const char * path)414 static int is_directory(const char* path)
415 {
416 	DWORD res = GetFileAttributesA(path);
417 	return (res != INVALID_FILE_ATTRIBUTES &&
418 		!!(res & FILE_ATTRIBUTE_DIRECTORY));
419 }
420 
421 /**
422  * Set the locale directory relative to ${PROGRAM_DIR}/locale.
423  */
setup_locale_dir(void)424 void setup_locale_dir(void)
425 {
426 	wchar_t* short_dir;
427 	char* program_dir = NULL;
428 	char* locale_dir;
429 	DWORD buf_size;
430 	DWORD res;
431 
432 	if (!console_data.program_dir[0]) return;
433 	buf_size = GetShortPathNameW(console_data.program_dir, NULL, 0);
434 	if (!buf_size) return;
435 
436 	short_dir = (wchar_t*)rsh_malloc(sizeof(wchar_t) * buf_size);
437 	res = GetShortPathNameW(console_data.program_dir, short_dir, buf_size);
438 	if (res > 0 && res < buf_size)
439 		program_dir = convert_wcs_to_str(short_dir, ConvertToPrimaryEncoding);
440 	free(short_dir);
441 	if (!program_dir) return;
442 
443 	locale_dir = make_path(program_dir, "locale", 0);
444 	free(program_dir);
445 	if (!locale_dir) return;
446 
447 	if (is_directory(locale_dir))
448 		bindtextdomain(TEXT_DOMAIN, locale_dir);
449 	free(locale_dir);
450 }
451 #endif /* USE_GETTEXT */
452 
453 #define USE_CSTR_ARGS 0
454 #define USE_WSTR_ARGS 1
455 
456 /**
457  * Print formatted data to the specified file descriptor,
458  * handling proper printing UTF-8 strings to Windows console.
459  *
460  * @param out file descriptor
461  * @param format data format string
462  * @param str_type wide/c-string type of string arguments
463  * @param args list of arguments
464  * @return the number of characters printed, -1 on error
465  */
win_vfprintf_encoded(FILE * out,const char * format,int str_type,va_list args)466 static int win_vfprintf_encoded(FILE* out, const char* format, int str_type, va_list args)
467 {
468 	int res;
469 	if (!win_is_console_stream(out)) {
470 		return vfprintf(out, (format ? format : "%s"), args);
471 	} else if (str_type == USE_CSTR_ARGS) {
472 		/* thread-unsafe code: using a static buffer */
473 		static char buffer[8192];
474 		res = vsnprintf(buffer, sizeof(buffer) - 1, format, args);
475 		if (res >= 0) {
476 			wchar_t *wstr = cstr_to_wchar_buffer(buffer, console_data.primary_codepage, console_data.printf_result, sizeof(console_data.printf_result));
477 			res = (wstr ? fwprintf(out, L"%s", wstr) : -1);
478 		}
479 	} else {
480 		wchar_t* wformat = (!format || (format[0] == '%' && format[1] == 's' && !format[2]) ? L"%s" :
481 			cstr_to_wchar_buffer(format, console_data.primary_codepage, console_data.format_buffer, sizeof(console_data.format_buffer)));
482 		res = vfwprintf(out, (wformat ? wformat : L"[UTF8 conversion error]\n"), args);
483 		assert(str_type == USE_WSTR_ARGS);
484 	}
485 	/* fix: win7 incorrectly sets _IOERR(=0x20) flag of the stream for UTF-8 encoding, so clear it */
486 	if (res >= 0)
487 		clearerr(out);
488 	return res;
489 }
490 
491 /**
492  * Print formatted data to the specified file descriptor,
493  * handling proper printing UTF-8 strings to Windows console.
494  *
495  * @param out file descriptor
496  * @param format data format string
497  * @param args list of arguments
498  * @return the number of characters printed, -1 on error
499  */
win_vfprintf(FILE * out,const char * format,va_list args)500 int win_vfprintf(FILE* out, const char* format, va_list args)
501 {
502 	return win_vfprintf_encoded(out, format, USE_CSTR_ARGS, args);
503 }
504 
505 /**
506  * Print formatted data to the specified file descriptor,
507  * handling proper printing UTF-8 strings to Windows console.
508  *
509  * @param out file descriptor
510  * @param format data format string
511  * @return the number of characters printed, -1 on error
512  */
win_fprintf(FILE * out,const char * format,...)513 int win_fprintf(FILE* out, const char* format, ...)
514 {
515 	int res;
516 	va_list args;
517 	va_start(args, format);
518 	res = win_vfprintf_encoded(out, format, USE_CSTR_ARGS, args);
519 	va_end(args);
520 	return res;
521 }
522 
523 /**
524  * Print formatted data to the specified file descriptor,
525  * handling proper printing UTF-8 strings to Windows console.
526  *
527  * @param out file descriptor
528  * @param format data format string
529  * @return the number of characters printed, -1 on error
530  */
win_fprintf_warg(FILE * out,const char * format,...)531 int win_fprintf_warg(FILE* out, const char* format, ...)
532 {
533 	int res;
534 	va_list args;
535 	va_start(args, format);
536 	res = win_vfprintf_encoded(out, format, USE_WSTR_ARGS, args);
537 	va_end(args);
538 	return res;
539 }
540 
541 /**
542  * Write a buffer to a stream.
543  *
544  * @param ptr pointer to the buffer to write
545  * @param size size of an items in the buffer
546  * @param count the number of items to write
547  * @param out the stream to write to
548  * @return the number of items written, -1 on error
549  */
win_fwrite(const void * ptr,size_t size,size_t count,FILE * out)550 size_t win_fwrite(const void* ptr, size_t size, size_t count, FILE* out)
551 {
552 	if (!win_is_console_stream(out))
553 		return fwrite(ptr, size, count, out);
554 	{
555 		size_t i;
556 		const char* buf = (const char*)ptr;
557 		size *= count;
558 		if (!size)
559 			return 0;
560 		for (i = 0; i < size && buf[i] > 0; i++);
561 		if (i == size)
562 		{
563 			wchar_t* wstr = rsh_malloc(sizeof(wchar_t) * (size + 1));
564 			int res;
565 			for (i = 0; i < size; i++)
566 				wstr[i] = (wchar_t)buf[i];
567 			wstr[size] = L'\0';
568 			res = fwprintf(out, L"%s", wstr);
569 			free(wstr);
570 			if (res < 0)
571 				return res;
572 		}
573 		else
574 		{
575 			for (i = 0; (i + 8) <= size; i += 8)
576 				if (fwprintf(out, L"%C%C%C%C%C%C%C%C", buf[i], buf[i + 1], buf[i + 2],
577 						buf[i + 3], buf[i + 4], buf[i + 5], buf[i + 6], buf[i + 7]) < 0)
578 					return -1;
579 			for (; i < size; i++)
580 				if (fwprintf(out, L"%C", buf[i]) < 0)
581 					return -1;
582 		}
583 		/* fix: win7 incorrectly sets _IOERR(=0x20) flag of the stream for UTF-8 encoding, so clear it */
584 		clearerr(out);
585 		return count;
586 	}
587 }
588 
589 #endif /* _WIN32 */
590 #else
591 typedef int dummy_declaration_required_by_strict_iso_c;
592 #endif /* defined(_WIN32) || defined(__CYGWIN__) */
593