1 /* Copyright (c) 2008, 2010, Oracle and/or its affiliates. All rights reserved.
2
3 This program is free software; you can redistribute it and/or modify
4 it under the terms of the GNU General Public License, version 2.0,
5 as published by the Free Software Foundation.
6
7 This program is also distributed with certain software (including
8 but not limited to OpenSSL) that is licensed under separate terms,
9 as designated in a particular file or component or in included license
10 documentation. The authors of MySQL hereby grant you an additional
11 permission to link the program and your derivative works with the
12 separately licensed software that they have included with MySQL.
13
14 This program is distributed in the hope that it will be useful,
15 but WITHOUT ANY WARRANTY; without even the implied warranty of
16 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
17 GNU General Public License, version 2.0, for more details.
18
19 You should have received a copy of the GNU General Public License
20 along with this program; if not, write to the Free Software
21 Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA */
22
23 /*
24 The purpose of this file is to provide implementation of file IO routines on
25 Windows that can be thought as drop-in replacement for corresponding C runtime
26 functionality.
27
28 Compared to Windows CRT, this one
29 - does not have the same file descriptor
30 limitation (default is 16384 and can be increased further, whereas CRT poses
31 a hard limit of 2048 file descriptors)
32 - the file operations are not serialized
33 - positional IO pread/pwrite is ported here.
34 - no text mode for files, all IO is "binary"
35
36 Naming convention:
37 All routines are prefixed with my_win_, e.g Posix open() is implemented with
38 my_win_open()
39
40 Implemented are
41 - POSIX routines(e.g open, read, lseek ...)
42 - Some ANSI C stream routines (fopen, fdopen, fileno, fclose)
43 - Windows CRT equvalients (my_get_osfhandle, open_osfhandle)
44
45 Worth to note:
46 - File descriptors used here are located in a range that is not compatible
47 with CRT on purpose. Attempt to use a file descriptor from Windows CRT library
48 range in my_win_* function will be punished with DBUG_ASSERT()
49
50 - File streams (FILE *) are actually from the C runtime. The routines provided
51 here are useful only in scernarios that use low-level IO with my_win_fileno()
52 */
53
54 #ifdef _WIN32
55
56 #include "mysys_priv.h"
57 #include <share.h>
58 #include <sys/stat.h>
59
60 /* Associates a file descriptor with an existing operating-system file handle.*/
my_open_osfhandle(HANDLE handle,int oflag)61 File my_open_osfhandle(HANDLE handle, int oflag)
62 {
63 int offset= -1;
64 uint i;
65 DBUG_ENTER("my_open_osfhandle");
66
67 mysql_mutex_lock(&THR_LOCK_open);
68 for(i= MY_FILE_MIN; i < my_file_limit;i++)
69 {
70 if(my_file_info[i].fhandle == 0)
71 {
72 struct st_my_file_info *finfo= &(my_file_info[i]);
73 finfo->type= FILE_BY_OPEN;
74 finfo->fhandle= handle;
75 finfo->oflag= oflag;
76 offset= i;
77 break;
78 }
79 }
80 mysql_mutex_unlock(&THR_LOCK_open);
81 if(offset == -1)
82 errno= EMFILE; /* to many file handles open */
83 DBUG_RETURN(offset);
84 }
85
86
invalidate_fd(File fd)87 static void invalidate_fd(File fd)
88 {
89 DBUG_ENTER("invalidate_fd");
90 DBUG_ASSERT(fd >= MY_FILE_MIN && fd < (int)my_file_limit);
91 my_file_info[fd].fhandle= 0;
92 DBUG_VOID_RETURN;
93 }
94
95
96 /* Get Windows handle for a file descriptor */
my_get_osfhandle(File fd)97 HANDLE my_get_osfhandle(File fd)
98 {
99 DBUG_ENTER("my_get_osfhandle");
100 DBUG_ASSERT(fd >= MY_FILE_MIN && fd < (int)my_file_limit);
101 DBUG_RETURN(my_file_info[fd].fhandle);
102 }
103
104
my_get_open_flags(File fd)105 static int my_get_open_flags(File fd)
106 {
107 DBUG_ENTER("my_get_open_flags");
108 DBUG_ASSERT(fd >= MY_FILE_MIN && fd < (int)my_file_limit);
109 DBUG_RETURN(my_file_info[fd].oflag);
110 }
111
112
113 /*
114 Open a file with sharing. Similar to _sopen() from libc, but allows managing
115 share delete on win32
116
117 SYNOPSIS
118 my_win_sopen()
119 path file name
120 oflag operation flags
121 shflag share flag
122 pmode permission flags
123
124 RETURN VALUE
125 File descriptor of opened file if success
126 -1 and sets errno if fails.
127 */
128
my_win_sopen(const char * path,int oflag,int shflag,int pmode)129 File my_win_sopen(const char *path, int oflag, int shflag, int pmode)
130 {
131 int fh; /* handle of opened file */
132 int mask;
133 HANDLE osfh; /* OS handle of opened file */
134 DWORD fileaccess; /* OS file access (requested) */
135 DWORD fileshare; /* OS file sharing mode */
136 DWORD filecreate; /* OS method of opening/creating */
137 DWORD fileattrib; /* OS file attribute flags */
138 SECURITY_ATTRIBUTES SecurityAttributes;
139
140 DBUG_ENTER("my_win_sopen");
141
142 if (check_if_legal_filename(path))
143 {
144 errno= EACCES;
145 DBUG_RETURN(-1);
146 }
147 SecurityAttributes.nLength= sizeof(SecurityAttributes);
148 SecurityAttributes.lpSecurityDescriptor= NULL;
149 SecurityAttributes.bInheritHandle= !(oflag & _O_NOINHERIT);
150
151 /* decode the access flags */
152 switch (oflag & (_O_RDONLY | _O_WRONLY | _O_RDWR)) {
153 case _O_RDONLY: /* read access */
154 fileaccess= GENERIC_READ;
155 break;
156 case _O_WRONLY: /* write access */
157 fileaccess= GENERIC_WRITE;
158 break;
159 case _O_RDWR: /* read and write access */
160 fileaccess= GENERIC_READ | GENERIC_WRITE;
161 break;
162 default: /* error, bad oflag */
163 errno= EINVAL;
164 DBUG_RETURN(-1);
165 }
166
167 /* decode sharing flags */
168 switch (shflag) {
169 case _SH_DENYRW: /* exclusive access except delete */
170 fileshare= FILE_SHARE_DELETE;
171 break;
172 case _SH_DENYWR: /* share read and delete access */
173 fileshare= FILE_SHARE_READ | FILE_SHARE_DELETE;
174 break;
175 case _SH_DENYRD: /* share write and delete access */
176 fileshare= FILE_SHARE_WRITE | FILE_SHARE_DELETE;
177 break;
178 case _SH_DENYNO: /* share read, write and delete access */
179 fileshare= FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE;
180 break;
181 case _SH_DENYRWD: /* exclusive access */
182 fileshare= 0L;
183 break;
184 case _SH_DENYWRD: /* share read access */
185 fileshare= FILE_SHARE_READ;
186 break;
187 case _SH_DENYRDD: /* share write access */
188 fileshare= FILE_SHARE_WRITE;
189 break;
190 case _SH_DENYDEL: /* share read and write access */
191 fileshare= FILE_SHARE_READ | FILE_SHARE_WRITE;
192 break;
193 default: /* error, bad shflag */
194 errno= EINVAL;
195 DBUG_RETURN(-1);
196 }
197
198 /* decode open/create method flags */
199 switch (oflag & (_O_CREAT | _O_EXCL | _O_TRUNC)) {
200 case 0:
201 case _O_EXCL: /* ignore EXCL w/o CREAT */
202 filecreate= OPEN_EXISTING;
203 break;
204
205 case _O_CREAT:
206 filecreate= OPEN_ALWAYS;
207 break;
208
209 case _O_CREAT | _O_EXCL:
210 case _O_CREAT | _O_TRUNC | _O_EXCL:
211 filecreate= CREATE_NEW;
212 break;
213
214 case _O_TRUNC:
215 case _O_TRUNC | _O_EXCL: /* ignore EXCL w/o CREAT */
216 filecreate= TRUNCATE_EXISTING;
217 break;
218
219 case _O_CREAT | _O_TRUNC:
220 filecreate= CREATE_ALWAYS;
221 break;
222
223 default:
224 /* this can't happen ... all cases are covered */
225 errno= EINVAL;
226 DBUG_RETURN(-1);
227 }
228
229 /* decode file attribute flags if _O_CREAT was specified */
230 fileattrib= FILE_ATTRIBUTE_NORMAL; /* default */
231 if (oflag & _O_CREAT)
232 {
233 _umask((mask= _umask(0)));
234
235 if (!((pmode & ~mask) & _S_IWRITE))
236 fileattrib= FILE_ATTRIBUTE_READONLY;
237 }
238
239 /* Set temporary file (delete-on-close) attribute if requested. */
240 if (oflag & _O_TEMPORARY)
241 {
242 fileattrib|= FILE_FLAG_DELETE_ON_CLOSE;
243 fileaccess|= DELETE;
244 }
245
246 /* Set temporary file (delay-flush-to-disk) attribute if requested.*/
247 if (oflag & _O_SHORT_LIVED)
248 fileattrib|= FILE_ATTRIBUTE_TEMPORARY;
249
250 /* Set sequential or random access attribute if requested. */
251 if (oflag & _O_SEQUENTIAL)
252 fileattrib|= FILE_FLAG_SEQUENTIAL_SCAN;
253 else if (oflag & _O_RANDOM)
254 fileattrib|= FILE_FLAG_RANDOM_ACCESS;
255
256 /* try to open/create the file */
257 if ((osfh= CreateFile(path, fileaccess, fileshare, &SecurityAttributes,
258 filecreate, fileattrib, NULL)) == INVALID_HANDLE_VALUE)
259 {
260 /*
261 OS call to open/create file failed! map the error, release
262 the lock, and return -1. note that it's not necessary to
263 call _free_osfhnd (it hasn't been used yet).
264 */
265 my_osmaperr(GetLastError()); /* map error */
266 DBUG_RETURN(-1); /* return error to caller */
267 }
268
269 if ((fh= my_open_osfhandle(osfh,
270 oflag & (_O_APPEND | _O_RDONLY | _O_TEXT))) == -1)
271 {
272 CloseHandle(osfh);
273 }
274
275 DBUG_RETURN(fh); /* return handle */
276 }
277
278
my_win_open(const char * path,int flags)279 File my_win_open(const char *path, int flags)
280 {
281 DBUG_ENTER("my_win_open");
282 DBUG_RETURN(my_win_sopen((char *) path, flags | _O_BINARY, _SH_DENYNO,
283 _S_IREAD | S_IWRITE));
284 }
285
286
my_win_close(File fd)287 int my_win_close(File fd)
288 {
289 DBUG_ENTER("my_win_close");
290 if(CloseHandle(my_get_osfhandle(fd)))
291 {
292 invalidate_fd(fd);
293 DBUG_RETURN(0);
294 }
295 my_osmaperr(GetLastError());
296 DBUG_RETURN(-1);
297 }
298
299
my_win_pread(File Filedes,uchar * Buffer,size_t Count,my_off_t offset)300 size_t my_win_pread(File Filedes, uchar *Buffer, size_t Count, my_off_t offset)
301 {
302 DWORD nBytesRead;
303 HANDLE hFile;
304 OVERLAPPED ov= {0};
305 LARGE_INTEGER li;
306
307 DBUG_ENTER("my_win_pread");
308
309 if(!Count)
310 DBUG_RETURN(0);
311 #ifdef _WIN64
312 if(Count > UINT_MAX)
313 Count= UINT_MAX;
314 #endif
315
316 hFile= (HANDLE)my_get_osfhandle(Filedes);
317 li.QuadPart= offset;
318 ov.Offset= li.LowPart;
319 ov.OffsetHigh= li.HighPart;
320
321 if(!ReadFile(hFile, Buffer, (DWORD)Count, &nBytesRead, &ov))
322 {
323 DWORD lastError= GetLastError();
324 /*
325 ERROR_BROKEN_PIPE is returned when no more data coming
326 through e.g. a command pipe in windows : see MSDN on ReadFile.
327 */
328 if(lastError == ERROR_HANDLE_EOF || lastError == ERROR_BROKEN_PIPE)
329 DBUG_RETURN(0); /*return 0 at EOF*/
330 my_osmaperr(lastError);
331 DBUG_RETURN((size_t)-1);
332 }
333 DBUG_RETURN(nBytesRead);
334 }
335
336
my_win_read(File Filedes,uchar * Buffer,size_t Count)337 size_t my_win_read(File Filedes, uchar *Buffer, size_t Count)
338 {
339 DWORD nBytesRead;
340 HANDLE hFile;
341
342 DBUG_ENTER("my_win_read");
343 if(!Count)
344 DBUG_RETURN(0);
345 #ifdef _WIN64
346 if(Count > UINT_MAX)
347 Count= UINT_MAX;
348 #endif
349
350 hFile= (HANDLE)my_get_osfhandle(Filedes);
351
352 if(!ReadFile(hFile, Buffer, (DWORD)Count, &nBytesRead, NULL))
353 {
354 DWORD lastError= GetLastError();
355 /*
356 ERROR_BROKEN_PIPE is returned when no more data coming
357 through e.g. a command pipe in windows : see MSDN on ReadFile.
358 */
359 if(lastError == ERROR_HANDLE_EOF || lastError == ERROR_BROKEN_PIPE)
360 DBUG_RETURN(0); /*return 0 at EOF*/
361 my_osmaperr(lastError);
362 DBUG_RETURN((size_t)-1);
363 }
364 DBUG_RETURN(nBytesRead);
365 }
366
367
my_win_pwrite(File Filedes,const uchar * Buffer,size_t Count,my_off_t offset)368 size_t my_win_pwrite(File Filedes, const uchar *Buffer, size_t Count,
369 my_off_t offset)
370 {
371 DWORD nBytesWritten;
372 HANDLE hFile;
373 OVERLAPPED ov= {0};
374 LARGE_INTEGER li;
375
376 DBUG_ENTER("my_win_pwrite");
377 DBUG_PRINT("my",("Filedes: %d, Buffer: %p, Count: %llu, offset: %llu",
378 Filedes, Buffer, (ulonglong)Count, (ulonglong)offset));
379
380 if(!Count)
381 DBUG_RETURN(0);
382
383 #ifdef _WIN64
384 if(Count > UINT_MAX)
385 Count= UINT_MAX;
386 #endif
387
388 hFile= (HANDLE)my_get_osfhandle(Filedes);
389 li.QuadPart= offset;
390 ov.Offset= li.LowPart;
391 ov.OffsetHigh= li.HighPart;
392
393 if(!WriteFile(hFile, Buffer, (DWORD)Count, &nBytesWritten, &ov))
394 {
395 my_osmaperr(GetLastError());
396 DBUG_RETURN((size_t)-1);
397 }
398 else
399 DBUG_RETURN(nBytesWritten);
400 }
401
402
my_win_lseek(File fd,my_off_t pos,int whence)403 my_off_t my_win_lseek(File fd, my_off_t pos, int whence)
404 {
405 LARGE_INTEGER offset;
406 LARGE_INTEGER newpos;
407
408 DBUG_ENTER("my_win_lseek");
409
410 /* Check compatibility of Windows and Posix seek constants */
411 compile_time_assert(FILE_BEGIN == SEEK_SET && FILE_CURRENT == SEEK_CUR
412 && FILE_END == SEEK_END);
413
414 offset.QuadPart= pos;
415 if(!SetFilePointerEx(my_get_osfhandle(fd), offset, &newpos, whence))
416 {
417 my_osmaperr(GetLastError());
418 newpos.QuadPart= -1;
419 }
420 DBUG_RETURN(newpos.QuadPart);
421 }
422
423
424 #ifndef FILE_WRITE_TO_END_OF_FILE
425 #define FILE_WRITE_TO_END_OF_FILE 0xffffffff
426 #endif
my_win_write(File fd,const uchar * Buffer,size_t Count)427 size_t my_win_write(File fd, const uchar *Buffer, size_t Count)
428 {
429 DWORD nWritten;
430 OVERLAPPED ov;
431 OVERLAPPED *pov= NULL;
432 HANDLE hFile;
433
434 DBUG_ENTER("my_win_write");
435 DBUG_PRINT("my",("Filedes: %d, Buffer: %p, Count %llu", fd, Buffer,
436 (ulonglong)Count));
437
438 if(!Count)
439 DBUG_RETURN(0);
440
441 #ifdef _WIN64
442 if(Count > UINT_MAX)
443 Count= UINT_MAX;
444 #endif
445
446 if(my_get_open_flags(fd) & _O_APPEND)
447 {
448 /*
449 Atomic append to the end of file is is done by special initialization of
450 the OVERLAPPED structure. See MSDN WriteFile documentation for more info.
451 */
452 memset(&ov, 0, sizeof(ov));
453 ov.Offset= FILE_WRITE_TO_END_OF_FILE;
454 ov.OffsetHigh= -1;
455 pov= &ov;
456 }
457
458 hFile= my_get_osfhandle(fd);
459 if(!WriteFile(hFile, Buffer, (DWORD)Count, &nWritten, pov))
460 {
461 my_osmaperr(GetLastError());
462 DBUG_RETURN((size_t)-1);
463 }
464 DBUG_RETURN(nWritten);
465 }
466
467
my_win_chsize(File fd,my_off_t newlength)468 int my_win_chsize(File fd, my_off_t newlength)
469 {
470 HANDLE hFile;
471 LARGE_INTEGER length;
472 DBUG_ENTER("my_win_chsize");
473
474 hFile= (HANDLE) my_get_osfhandle(fd);
475 length.QuadPart= newlength;
476 if (!SetFilePointerEx(hFile, length , NULL , FILE_BEGIN))
477 goto err;
478 if (!SetEndOfFile(hFile))
479 goto err;
480 DBUG_RETURN(0);
481 err:
482 my_osmaperr(GetLastError());
483 my_errno= errno;
484 DBUG_RETURN(-1);
485 }
486
487
488 /* Get the file descriptor for stdin,stdout or stderr */
my_get_stdfile_descriptor(FILE * stream)489 static File my_get_stdfile_descriptor(FILE *stream)
490 {
491 HANDLE hFile;
492 DWORD nStdHandle;
493 DBUG_ENTER("my_get_stdfile_descriptor");
494
495 if(stream == stdin)
496 nStdHandle= STD_INPUT_HANDLE;
497 else if(stream == stdout)
498 nStdHandle= STD_OUTPUT_HANDLE;
499 else if(stream == stderr)
500 nStdHandle= STD_ERROR_HANDLE;
501 else
502 DBUG_RETURN(-1);
503
504 hFile= GetStdHandle(nStdHandle);
505 if(hFile != INVALID_HANDLE_VALUE)
506 DBUG_RETURN(my_open_osfhandle(hFile, 0));
507 DBUG_RETURN(-1);
508 }
509
510
my_win_fileno(FILE * file)511 File my_win_fileno(FILE *file)
512 {
513 HANDLE hFile= (HANDLE)_get_osfhandle(fileno(file));
514 int retval= -1;
515 uint i;
516
517 DBUG_ENTER("my_win_fileno");
518
519 for(i= MY_FILE_MIN; i < my_file_limit; i++)
520 {
521 if(my_file_info[i].fhandle == hFile)
522 {
523 retval= i;
524 break;
525 }
526 }
527 if(retval == -1)
528 /* try std stream */
529 DBUG_RETURN(my_get_stdfile_descriptor(file));
530 DBUG_RETURN(retval);
531 }
532
533
my_win_fopen(const char * filename,const char * type)534 FILE *my_win_fopen(const char *filename, const char *type)
535 {
536 FILE *file;
537 int flags= 0;
538 DBUG_ENTER("my_win_open");
539
540 /*
541 If we are not creating, then we need to use my_access to make sure
542 the file exists since Windows doesn't handle files like "com1.sym"
543 very well
544 */
545 if (check_if_legal_filename(filename))
546 {
547 errno= EACCES;
548 DBUG_RETURN(NULL);
549 }
550
551 file= fopen(filename, type);
552 if(!file)
553 DBUG_RETURN(NULL);
554
555 if(strchr(type,'a') != NULL)
556 flags= O_APPEND;
557
558 /*
559 Register file handle in my_table_info.
560 Necessary for my_fileno()
561 */
562 if(my_open_osfhandle((HANDLE)_get_osfhandle(fileno(file)), flags) < 0)
563 {
564 fclose(file);
565 DBUG_RETURN(NULL);
566 }
567 DBUG_RETURN(file);
568 }
569
570
my_win_fdopen(File fd,const char * type)571 FILE * my_win_fdopen(File fd, const char *type)
572 {
573 FILE *file;
574 int crt_fd;
575 int flags= 0;
576
577 DBUG_ENTER("my_win_fdopen");
578
579 if(strchr(type,'a') != NULL)
580 flags= O_APPEND;
581 /* Convert OS file handle to CRT file descriptor and then call fdopen*/
582 crt_fd= _open_osfhandle((intptr_t)my_get_osfhandle(fd), flags);
583 if(crt_fd < 0)
584 file= NULL;
585 else
586 file= fdopen(crt_fd, type);
587 DBUG_RETURN(file);
588 }
589
590
my_win_fclose(FILE * file)591 int my_win_fclose(FILE *file)
592 {
593 File fd;
594
595 DBUG_ENTER("my_win_close");
596 fd= my_fileno(file);
597 if(fd < 0)
598 DBUG_RETURN(-1);
599 if(fclose(file) < 0)
600 DBUG_RETURN(-1);
601 invalidate_fd(fd);
602 DBUG_RETURN(0);
603 }
604
605
606
607 /*
608 Quick and dirty my_fstat() implementation for Windows.
609 Use CRT fstat on temporarily allocated file descriptor.
610 Patch file size, because size that fstat returns is not
611 reliable (may be outdated)
612 */
my_win_fstat(File fd,struct _stati64 * buf)613 int my_win_fstat(File fd, struct _stati64 *buf)
614 {
615 int crt_fd;
616 int retval;
617 HANDLE hFile, hDup;
618
619 DBUG_ENTER("my_win_fstat");
620
621 hFile= my_get_osfhandle(fd);
622 if(!DuplicateHandle( GetCurrentProcess(), hFile, GetCurrentProcess(),
623 &hDup ,0,FALSE,DUPLICATE_SAME_ACCESS))
624 {
625 my_osmaperr(GetLastError());
626 DBUG_RETURN(-1);
627 }
628 if ((crt_fd= _open_osfhandle((intptr_t)hDup,0)) < 0)
629 DBUG_RETURN(-1);
630
631 retval= _fstati64(crt_fd, buf);
632 if(retval == 0)
633 {
634 /* File size returned by stat is not accurate (may be outdated), fix it*/
635 GetFileSizeEx(hDup, (PLARGE_INTEGER) (&(buf->st_size)));
636 }
637 _close(crt_fd);
638 DBUG_RETURN(retval);
639 }
640
641
642
my_win_stat(const char * path,struct _stati64 * buf)643 int my_win_stat( const char *path, struct _stati64 *buf)
644 {
645 DBUG_ENTER("my_win_stat");
646 if(_stati64( path, buf) == 0)
647 {
648 /* File size returned by stat is not accurate (may be outdated), fix it*/
649 WIN32_FILE_ATTRIBUTE_DATA data;
650 if (GetFileAttributesEx(path, GetFileExInfoStandard, &data))
651 {
652 LARGE_INTEGER li;
653 li.LowPart= data.nFileSizeLow;
654 li.HighPart= data.nFileSizeHigh;
655 buf->st_size= li.QuadPart;
656 }
657 DBUG_RETURN(0);
658 }
659 DBUG_RETURN(-1);
660 }
661
662
663
my_win_fsync(File fd)664 int my_win_fsync(File fd)
665 {
666 DBUG_ENTER("my_win_fsync");
667 if(FlushFileBuffers(my_get_osfhandle(fd)))
668 DBUG_RETURN(0);
669 my_osmaperr(GetLastError());
670 DBUG_RETURN(-1);
671 }
672
673
674
my_win_dup(File fd)675 int my_win_dup(File fd)
676 {
677 HANDLE hDup;
678 DBUG_ENTER("my_win_dup");
679 if (DuplicateHandle(GetCurrentProcess(), my_get_osfhandle(fd),
680 GetCurrentProcess(), &hDup, 0, FALSE, DUPLICATE_SAME_ACCESS))
681 {
682 DBUG_RETURN(my_open_osfhandle(hDup, my_get_open_flags(fd)));
683 }
684 my_osmaperr(GetLastError());
685 DBUG_RETURN(-1);
686 }
687
688 #endif /*_WIN32*/
689