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