]> git.sur5r.net Git - bacula/bacula/blob - bacula/src/win32/compat/compat.cpp
f0ea48a82c48b0ae2fe350ef56b91e4cfb53e1ee
[bacula/bacula] / bacula / src / win32 / compat / compat.cpp
1 //                              -*- Mode: C++ -*-
2 // compat.cpp -- compatibilty layer to make bacula-fd run
3 //               natively under windows
4 //
5 // Copyright transferred from Raider Solutions, Inc to
6 //   Kern Sibbald and John Walker by express permission.
7 //
8 //  Copyright (C) 2004-2006 Kern Sibbald
9 //
10 //  This program is free software; you can redistribute it and/or
11 //  modify it under the terms of the GNU General Public License
12 //  version 2 as amended with additional clauses defined in the
13 //  file LICENSE in the main source directory.
14 //
15 //  This program is distributed in the hope that it will be useful,
16 //  but WITHOUT ANY WARRANTY; without even the implied warranty of
17 //  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
18 //  the file LICENSE for additional details.
19 //
20 // Author          : Christopher S. Hull
21 // Created On      : Sat Jan 31 15:55:00 2004
22 // $Id$
23
24 #include "bacula.h"
25 #include "compat.h"
26 #include "jcr.h"
27 #include "vss.h"
28
29
30 #define b_errno_win32 (1<<29)
31
32
33 /* UTF-8 to UCS2 path conversion is expensive,
34    so we cache the conversion. During backup the
35    conversion is called 3 times (lstat, attribs, open),
36    by using the cache this is reduced to 1 time */
37
38 static POOLMEM *g_pWin32ConvUTF8Cache = get_pool_memory (PM_FNAME);
39 static POOLMEM *g_pWin32ConvUCS2Cache = get_pool_memory (PM_FNAME);
40 static DWORD g_dwWin32ConvUTF8strlen = 0;
41 static pthread_mutex_t Win32Convmutex = PTHREAD_MUTEX_INITIALIZER;
42
43 void Win32ConvCleanupCache()
44 {
45    if (g_pWin32ConvUTF8Cache) {
46       free_pool_memory(g_pWin32ConvUTF8Cache);
47       g_pWin32ConvUTF8Cache = NULL;
48    }
49
50    if (g_pWin32ConvUCS2Cache) {
51       free_pool_memory(g_pWin32ConvUCS2Cache);   
52       g_pWin32ConvUCS2Cache = NULL;
53    }
54
55    g_dwWin32ConvUTF8strlen = 0;
56 }
57
58
59 /* to allow the usage of the original version in this file here */
60 #undef fputs
61
62
63 #define USE_WIN32_COMPAT_IO 1
64 #define USE_WIN32_32KPATHCONVERSION 1
65
66 extern void d_msg(const char *file, int line, int level, const char *fmt,...);
67 extern DWORD   g_platform_id;
68 extern int enable_vss;
69
70 // from MicroSoft SDK (KES) is the diff between Jan 1 1601 and Jan 1 1970
71 #ifdef HAVE_MINGW
72 #define WIN32_FILETIME_ADJUST 0x19DB1DED53E8000ULL 
73 #else
74 #define WIN32_FILETIME_ADJUST 0x19DB1DED53E8000I64
75 #endif
76
77 #define WIN32_FILETIME_SCALE  10000000             // 100ns/second
78
79 void conv_unix_to_win32_path(const char *name, char *win32_name, DWORD dwSize)
80 {
81     const char *fname = name;
82     char *tname = win32_name;
83     while (*name) {
84         /* Check for Unix separator and convert to Win32 */
85         if (name[0] == '/' && name[1] == '/') {  /* double slash? */
86            name++;                               /* yes, skip first one */
87         }
88         if (*name == '/') {
89             *win32_name++ = '\\';     /* convert char */
90         /* If Win32 separated that is "quoted", remove quote */
91         } else if (*name == '\\' && name[1] == '\\') {
92             *win32_name++ = '\\';
93             name++;                   /* skip first \ */
94         } else {
95             *win32_name++ = *name;    /* copy character */
96         }
97         name++;
98     }
99     /* Strip any trailing slash, if we stored something */
100     /* but leave "c:\" with backslash (root directory case */
101     if (*fname != 0 && win32_name[-1] == '\\' && strlen (fname) != 3) {
102         win32_name[-1] = 0;
103     } else {
104         *win32_name = 0;
105     }
106
107 #ifdef WIN32_VSS
108     /* here we convert to VSS specific file name which
109        can get longer because VSS will make something like
110        \\\\?\\GLOBALROOT\\Device\\HarddiskVolumeShadowCopy1\\bacula\\uninstall.exe
111        from c:\bacula\uninstall.exe
112     */
113     if (g_pVSSClient && enable_vss && g_pVSSClient->IsInitialized()) {
114        POOLMEM *pszBuf = get_pool_memory (PM_FNAME);
115        pszBuf = check_pool_memory_size(pszBuf, dwSize);
116        bstrncpy(pszBuf, tname, strlen(tname)+1);
117        g_pVSSClient->GetShadowPath(pszBuf, tname, dwSize);
118        free_pool_memory(pszBuf);
119     }
120 #endif
121 }
122
123 POOLMEM* 
124 make_wchar_win32_path(POOLMEM *pszUCSPath, BOOL *pBIsRawPath /*= NULL*/)
125 {
126    /* created 02/27/2006 Thorsten Engel
127       
128       This function expects an UCS-encoded standard wchar_t in pszUCSPath and
129       will complete the input path to an absolue path of the form \\?\c:\path\file
130
131       With this trick, it is possible to have 32K characters long paths.
132
133       Optionally one can use pBIsRawPath to determine id pszUCSPath contains a path
134       to a raw windows partition */
135
136    if (pBIsRawPath)
137       *pBIsRawPath = FALSE;
138
139    if (!p_GetCurrentDirectoryW)
140       return pszUCSPath;
141    
142    wchar_t *name = (wchar_t *) pszUCSPath;
143
144    /* if it has already the desired form, exit without changes */
145    if (wcslen(name) > 3 && wcsncmp(name, L"\\\\?\\", 4) == 0)
146       return pszUCSPath;
147
148    POOLMEM *pwszBuf = get_pool_memory(PM_FNAME);
149    POOLMEM *pwszCurDirBuf = get_pool_memory(PM_FNAME);
150    DWORD dwCurDirPathSize = 0;
151
152    /* get buffer with enough size (name+max 6. wchars+1 null terminator */
153    DWORD dwBufCharsNeeded = (wcslen(name)+7);
154    pwszBuf = check_pool_memory_size(pwszBuf, dwBufCharsNeeded*sizeof(wchar_t));
155       
156    /* add \\?\ to support 32K long filepaths 
157       it is important to make absolute paths, so we add drive and
158       current path if necessary */
159
160    BOOL bAddDrive = TRUE;
161    BOOL bAddCurrentPath = TRUE;
162    BOOL bAddPrefix = TRUE;
163
164    /* does path begin with drive? if yes, it is absolute */
165    if (wcslen(name) >= 3 && (iswalpha (*name) && *(name+1) == ':'
166        && (*(name+2) == '\\' || *(name+2) == '/'))) {
167       bAddDrive = FALSE;
168       bAddCurrentPath = FALSE;
169    }
170
171    /* is path absolute? */
172    if (*name == '/' || *name == '\\')
173       bAddCurrentPath = FALSE; 
174
175    /* is path relative to itself?, if yes, skip ./ */
176    if (wcslen(name) > 2 && ((wcsncmp(name, L"./", 2) == 0) || (wcsncmp(name, L".\\", 2) == 0))) {
177       name+=2;
178    }
179
180    /* is path of form '//./'? */   
181    if (wcslen(name) > 3 && ((wcsncmp(name, L"//./", 4) == 0) || (wcsncmp(name, L"\\\\.\\", 4) == 0))) {
182       bAddDrive = FALSE;
183       bAddCurrentPath = FALSE;
184       bAddPrefix = FALSE;
185       if (pBIsRawPath)
186          *pBIsRawPath = TRUE;
187    }
188
189    int nParseOffset = 0;
190    
191    /* add 4 bytes header */
192    if (bAddPrefix) {
193       nParseOffset = 4;
194       wcscpy ((wchar_t *) pwszBuf,L"\\\\?\\");
195    }
196
197    /* get current path if needed */
198    if (bAddDrive || bAddCurrentPath) {
199       dwCurDirPathSize = p_GetCurrentDirectoryW(0, NULL);
200       if (dwCurDirPathSize > 0) {
201          /* get directory into own buffer as it may either return c:\... or \\?\C:\.... */         
202          pwszCurDirBuf = check_pool_memory_size(pwszCurDirBuf, (dwCurDirPathSize+1)*sizeof(wchar_t));
203          p_GetCurrentDirectoryW(dwCurDirPathSize,(wchar_t *)pwszCurDirBuf);
204       }
205       else
206       {
207          /* we have no info for doing so */
208          bAddDrive = FALSE;
209          bAddCurrentPath = FALSE;
210       }
211    }
212       
213
214    /* add drive if needed */
215    if (bAddDrive && !bAddCurrentPath) {
216       wchar_t szDrive[3];
217
218       if (dwCurDirPathSize > 3 && wcsncmp((LPCWSTR)pwszCurDirBuf, L"\\\\?\\", 4) == 0)
219          /* copy drive character */
220          wcsncpy((wchar_t *) szDrive, (LPCWSTR)pwszCurDirBuf+4,2);          
221       else
222          /* copy drive character */
223          wcsncpy((wchar_t *) szDrive, (LPCWSTR)pwszCurDirBuf,2);  
224
225       szDrive[2] = 0;
226             
227       wcscat((wchar_t *) pwszBuf, szDrive);  
228       nParseOffset +=2;
229    }
230
231    /* add path if needed */
232    if (bAddCurrentPath) {
233       /* the 1 add. character is for the eventually added backslash */
234       dwBufCharsNeeded += dwCurDirPathSize+1; 
235       pwszBuf = check_pool_memory_size(pwszBuf, dwBufCharsNeeded*sizeof(wchar_t));
236       /* get directory into own buffer as it may either return c:\... or \\?\C:\.... */
237       
238       if (dwCurDirPathSize > 3 && wcsncmp((LPCWSTR)pwszCurDirBuf, L"\\\\?\\", 4) == 0)
239          /* copy complete string */
240          wcscpy((wchar_t *) pwszBuf, (LPCWSTR)pwszCurDirBuf);          
241       else
242          /* append path  */
243          wcscat((wchar_t *) pwszBuf, (LPCWSTR)pwszCurDirBuf);       
244
245       nParseOffset = wcslen ((LPCWSTR) pwszBuf);
246
247       /* check if path ends with backslash, if not, add one */
248       if (*((wchar_t *) pwszBuf+nParseOffset-1) != L'\\') {
249          wcscat((wchar_t *) pwszBuf, L"\\");
250          nParseOffset++;
251       }      
252    }
253
254
255    wchar_t *win32_name = (wchar_t *)pwszBuf+nParseOffset;
256
257    while (*name) {
258       /* Check for Unix separator and convert to Win32 */
259       if (*name == '/') {
260          *win32_name++ = '\\';     /* convert char */
261          /* If Win32 separated that is "quoted", remove quote */
262 /* HELPME (Thorsten Engel): I don't understand the following part
263    and it removes a backslash from e.g. "\\.\c:" which I need for 
264    RAW device access. So I took it out */
265 /*      } else if (*name == '\\' && name[1] == '\\') {
266          *win32_name++ = '\\';
267          name++;  */                 /* skip first \ */ 
268       } else {
269          *win32_name++ = *name;    /* copy character */
270       }
271       name++;
272    }
273    
274    /* null terminate string */
275    *win32_name = 0;
276
277 #ifdef WIN32_VSS
278    /* here we convert to VSS specific file name which
279    can get longer because VSS will make something like
280    \\\\?\\GLOBALROOT\\Device\\HarddiskVolumeShadowCopy1\\bacula\\uninstall.exe
281    from c:\bacula\uninstall.exe
282    */ 
283    if (g_pVSSClient && enable_vss && g_pVSSClient->IsInitialized()) {
284       /* is output buffer large enough? */
285       pwszBuf = check_pool_memory_size(pwszBuf, (dwBufCharsNeeded+MAX_PATH)*sizeof(wchar_t));
286       /* create temp. buffer */
287       POOLMEM* pszBuf = get_pool_memory(PM_FNAME);
288       pszBuf = check_pool_memory_size(pszBuf, (dwBufCharsNeeded+MAX_PATH)*sizeof(wchar_t));
289       if (bAddPrefix)
290          nParseOffset = 4;
291       else
292          nParseOffset = 0; 
293       wcsncpy((wchar_t *) pszBuf, (wchar_t *) pwszBuf+nParseOffset, wcslen((wchar_t *)pwszBuf)+1-nParseOffset);
294       g_pVSSClient->GetShadowPathW((wchar_t *)pszBuf,(wchar_t *)pwszBuf,dwBufCharsNeeded+MAX_PATH);
295       free_pool_memory(pszBuf);
296    }   
297 #endif
298
299    free_pool_memory(pszUCSPath);
300    free_pool_memory(pwszCurDirBuf);
301
302    return pwszBuf;
303 }
304
305 int
306 wchar_2_UTF8(char *pszUTF, const wchar_t *pszUCS, int cchChar)
307 {
308    /* the return value is the number of bytes written to the buffer.
309       The number includes the byte for the null terminator. */
310
311    if (p_WideCharToMultiByte) {
312          int nRet = p_WideCharToMultiByte(CP_UTF8,0,pszUCS,-1,pszUTF,cchChar,NULL,NULL);
313          ASSERT (nRet > 0);
314          return nRet;
315       }
316    else
317       return 0;
318 }
319
320 int
321 UTF8_2_wchar(POOLMEM **ppszUCS, const char *pszUTF)
322 {
323    /* the return value is the number of wide characters written to the buffer. */
324    /* convert null terminated string from utf-8 to ucs2, enlarge buffer if necessary */
325
326    if (p_MultiByteToWideChar) {
327       /* strlen of UTF8 +1 is enough */
328       DWORD cchSize = (strlen(pszUTF)+1);
329       *ppszUCS = check_pool_memory_size(*ppszUCS, cchSize*sizeof (wchar_t));
330
331       int nRet = p_MultiByteToWideChar(CP_UTF8, 0, pszUTF, -1, (LPWSTR) *ppszUCS,cchSize);
332       ASSERT (nRet > 0);
333       return nRet;
334    }
335    else
336       return 0;
337 }
338
339
340 void
341 wchar_win32_path(const char *name, wchar_t *win32_name)
342 {
343     const char *fname = name;
344     while (*name) {
345         /* Check for Unix separator and convert to Win32 */
346         if (*name == '/') {
347             *win32_name++ = '\\';     /* convert char */
348         /* If Win32 separated that is "quoted", remove quote */
349         } else if (*name == '\\' && name[1] == '\\') {
350             *win32_name++ = '\\';
351             name++;                   /* skip first \ */
352         } else {
353             *win32_name++ = *name;    /* copy character */
354         }
355         name++;
356     }
357     /* Strip any trailing slash, if we stored something */
358     if (*fname != 0 && win32_name[-1] == '\\') {
359         win32_name[-1] = 0;
360     } else {
361         *win32_name = 0;
362     }
363 }
364
365 int 
366 make_win32_path_UTF8_2_wchar(POOLMEM **pszUCS, const char *pszUTF, BOOL* pBIsRawPath /*= NULL*/)
367 {
368    P(Win32Convmutex);
369    /* if we find the utf8 string in cache, we use the cached ucs2 version.
370       we compare the stringlength first (quick check) and then compare the content.            
371    */
372    if (g_dwWin32ConvUTF8strlen == strlen(pszUTF)) {
373       if (bstrcmp(pszUTF, g_pWin32ConvUTF8Cache)) {
374          int32_t nBufSize = sizeof_pool_memory(g_pWin32ConvUCS2Cache);
375          *pszUCS = check_pool_memory_size(*pszUCS, nBufSize);      
376          wcscpy((LPWSTR) *pszUCS, (LPWSTR) g_pWin32ConvUCS2Cache);
377          V(Win32Convmutex);
378          return nBufSize / sizeof (WCHAR);
379       }
380    }
381
382    /* helper to convert from utf-8 to UCS-2 and to complete a path for 32K path syntax */
383    int nRet = UTF8_2_wchar(pszUCS, pszUTF);
384
385 #ifdef USE_WIN32_32KPATHCONVERSION
386    /* add \\?\ to support 32K long filepaths */
387    *pszUCS = make_wchar_win32_path(*pszUCS, pBIsRawPath);
388 #else
389    if (pBIsRawPath)
390       *pBIsRawPath = FALSE;
391 #endif
392
393    /* populate cache */      
394    g_pWin32ConvUCS2Cache = check_pool_memory_size(g_pWin32ConvUCS2Cache, sizeof_pool_memory(*pszUCS));
395    wcscpy((LPWSTR) g_pWin32ConvUCS2Cache, (LPWSTR) *pszUCS);
396    
397    g_dwWin32ConvUTF8strlen = strlen(pszUTF);
398    g_pWin32ConvUTF8Cache = check_pool_memory_size(g_pWin32ConvUTF8Cache, g_dwWin32ConvUTF8strlen+1);
399    bstrncpy(g_pWin32ConvUTF8Cache, pszUTF, g_dwWin32ConvUTF8strlen+1);
400    V(Win32Convmutex);
401
402    return nRet;
403 }
404
405 #ifndef HAVE_VC8
406 int umask(int)
407 {
408    return 0;
409 }
410 #endif
411
412 int chmod(const char *, mode_t)
413 {
414    return 0;
415 }
416
417 int chown(const char *k, uid_t, gid_t)
418 {
419    return 0;
420 }
421
422 int lchown(const char *k, uid_t, gid_t)
423 {
424    return 0;
425 }
426
427 #ifdef needed
428 bool fstype(const char *fname, char *fs, int fslen)
429 {
430    return true;                       /* accept anything */
431 }
432 #endif
433
434
435 long int
436 random(void)
437 {
438     return rand();
439 }
440
441 void
442 srandom(unsigned int seed)
443 {
444    srand(seed);
445 }
446 // /////////////////////////////////////////////////////////////////
447 // convert from Windows concept of time to Unix concept of time
448 // /////////////////////////////////////////////////////////////////
449 void
450 cvt_utime_to_ftime(const time_t  &time, FILETIME &wintime)
451 {
452    uint64_t mstime = time;
453    mstime *= WIN32_FILETIME_SCALE;
454    mstime += WIN32_FILETIME_ADJUST;
455
456 #if defined(_MSC_VER)
457    wintime.dwLowDateTime = (DWORD)(mstime & 0xffffffffI64);
458 #else
459    wintime.dwLowDateTime = (DWORD)(mstime & 0xffffffffUL);
460 #endif
461    wintime.dwHighDateTime = (DWORD) ((mstime>>32)& 0xffffffffUL);
462 }
463
464 time_t
465 cvt_ftime_to_utime(const FILETIME &time)
466 {
467     uint64_t mstime = time.dwHighDateTime;
468     mstime <<= 32;
469     mstime |= time.dwLowDateTime;
470
471     mstime -= WIN32_FILETIME_ADJUST;
472     mstime /= WIN32_FILETIME_SCALE; // convert to seconds.
473
474     return (time_t) (mstime & 0xffffffff);
475 }
476
477 static const char *
478 errorString(void)
479 {
480    LPVOID lpMsgBuf;
481
482    FormatMessage(FORMAT_MESSAGE_ALLOCATE_BUFFER |
483                  FORMAT_MESSAGE_FROM_SYSTEM |
484                  FORMAT_MESSAGE_IGNORE_INSERTS,
485                  NULL,
486                  GetLastError(),
487                  MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), // Default lang
488                  (LPTSTR) &lpMsgBuf,
489                  0,
490                  NULL);
491
492    /* Strip any \r or \n */
493    char *rval = (char *) lpMsgBuf;
494    char *cp = strchr(rval, '\r');
495    if (cp != NULL) {
496       *cp = 0;
497    } else {
498       cp = strchr(rval, '\n');
499       if (cp != NULL)
500          *cp = 0;
501    }
502    return rval;
503 }
504
505
506 static int
507 statDir(const char *file, struct stat *sb)
508 {
509    WIN32_FIND_DATAW info_w;       // window's file info
510    WIN32_FIND_DATAA info_a;       // window's file info
511
512    // cache some common vars to make code more transparent
513    DWORD* pdwFileAttributes;
514    DWORD* pnFileSizeHigh;
515    DWORD* pnFileSizeLow;
516    FILETIME* pftLastAccessTime;
517    FILETIME* pftLastWriteTime;
518    FILETIME* pftCreationTime;
519
520    if (file[1] == ':' && file[2] == 0) {
521         d_msg(__FILE__, __LINE__, 99, "faking ROOT attrs(%s).\n", file);
522         sb->st_mode = S_IFDIR;
523         sb->st_mode |= S_IREAD|S_IEXEC|S_IWRITE;
524         time(&sb->st_ctime);
525         time(&sb->st_mtime);
526         time(&sb->st_atime);
527         return 0;
528     }
529
530    HANDLE h = INVALID_HANDLE_VALUE;
531
532    // use unicode or ascii
533    if (p_FindFirstFileW) {
534       POOLMEM* pwszBuf = get_pool_memory (PM_FNAME);
535       make_win32_path_UTF8_2_wchar(&pwszBuf, file);
536
537       h = p_FindFirstFileW((LPCWSTR) pwszBuf, &info_w);
538       free_pool_memory(pwszBuf);
539
540       pdwFileAttributes = &info_w.dwFileAttributes;
541       pnFileSizeHigh    = &info_w.nFileSizeHigh;
542       pnFileSizeLow     = &info_w.nFileSizeLow;
543       pftLastAccessTime = &info_w.ftLastAccessTime;
544       pftLastWriteTime  = &info_w.ftLastWriteTime;
545       pftCreationTime   = &info_w.ftCreationTime;
546    }
547    else if (p_FindFirstFileA) {
548       h = p_FindFirstFileA(file, &info_a);
549
550       pdwFileAttributes = &info_a.dwFileAttributes;
551       pnFileSizeHigh    = &info_a.nFileSizeHigh;
552       pnFileSizeLow     = &info_a.nFileSizeLow;
553       pftLastAccessTime = &info_a.ftLastAccessTime;
554       pftLastWriteTime  = &info_a.ftLastWriteTime;
555       pftCreationTime   = &info_a.ftCreationTime;
556    }
557
558     if (h == INVALID_HANDLE_VALUE) {
559         const char *err = errorString();
560         d_msg(__FILE__, __LINE__, 99, "FindFirstFile(%s):%s\n", file, err);
561         LocalFree((void *)err);
562         errno = b_errno_win32;
563         return -1;
564     }
565
566     sb->st_mode = 0777;               /* start with everything */
567     if (*pdwFileAttributes & FILE_ATTRIBUTE_READONLY)
568         sb->st_mode &= ~(S_IRUSR|S_IRGRP|S_IROTH);
569     if (*pdwFileAttributes & FILE_ATTRIBUTE_SYSTEM)
570         sb->st_mode &= ~S_IRWXO; /* remove everything for other */
571     if (*pdwFileAttributes & FILE_ATTRIBUTE_HIDDEN)
572         sb->st_mode |= S_ISVTX; /* use sticky bit -> hidden */
573     sb->st_mode |= S_IFDIR;
574
575     sb->st_size = *pnFileSizeHigh;
576     sb->st_size <<= 32;
577     sb->st_size |= *pnFileSizeLow;
578     sb->st_blksize = 4096;
579     sb->st_blocks = (uint32_t)(sb->st_size + 4095)/4096;
580
581     sb->st_atime = cvt_ftime_to_utime(*pftLastAccessTime);
582     sb->st_mtime = cvt_ftime_to_utime(*pftLastWriteTime);
583     sb->st_ctime = cvt_ftime_to_utime(*pftCreationTime);
584     FindClose(h);
585
586     return 0;
587 }
588
589 static int
590 stat2(const char *file, struct stat *sb)
591 {
592     BY_HANDLE_FILE_INFORMATION info;
593     HANDLE h;
594     int rval = 0;
595     char tmpbuf[1024];
596     conv_unix_to_win32_path(file, tmpbuf, 1024);
597
598     DWORD attr = (DWORD)-1;
599
600     if (p_GetFileAttributesW) {
601       POOLMEM* pwszBuf = get_pool_memory(PM_FNAME);
602       make_win32_path_UTF8_2_wchar(&pwszBuf, tmpbuf);
603
604       attr = p_GetFileAttributesW((LPCWSTR) pwszBuf);
605       free_pool_memory(pwszBuf);
606     } else if (p_GetFileAttributesA) {
607        attr = p_GetFileAttributesA(tmpbuf);
608     }
609
610     if (attr == (DWORD)-1) {
611         const char *err = errorString();
612         d_msg(__FILE__, __LINE__, 99,
613               "GetFileAttributes(%s): %s\n", tmpbuf, err);
614         LocalFree((void *)err);
615         errno = b_errno_win32;
616         return -1;
617     }
618
619     if (attr & FILE_ATTRIBUTE_DIRECTORY)
620         return statDir(tmpbuf, sb);
621
622     h = CreateFileA(tmpbuf, GENERIC_READ,
623                    FILE_SHARE_READ, NULL, OPEN_EXISTING, 0, NULL);
624
625     if (h == INVALID_HANDLE_VALUE) {
626         const char *err = errorString();
627         d_msg(__FILE__, __LINE__, 99,
628               "Cannot open file for stat (%s):%s\n", tmpbuf, err);
629         LocalFree((void *)err);
630         rval = -1;
631         errno = b_errno_win32;
632         goto error;
633     }
634
635     if (!GetFileInformationByHandle(h, &info)) {
636         const char *err = errorString();
637         d_msg(__FILE__, __LINE__, 99,
638               "GetfileInformationByHandle(%s): %s\n", tmpbuf, err);
639         LocalFree((void *)err);
640         rval = -1;
641         errno = b_errno_win32;
642         goto error;
643     }
644
645     sb->st_dev = info.dwVolumeSerialNumber;
646     sb->st_ino = info.nFileIndexHigh;
647     sb->st_ino <<= 32;
648     sb->st_ino |= info.nFileIndexLow;
649     sb->st_nlink = (short)info.nNumberOfLinks;
650     if (sb->st_nlink > 1) {
651        d_msg(__FILE__, __LINE__, 99,  "st_nlink=%d\n", sb->st_nlink);
652     }
653
654     sb->st_mode = 0777;               /* start with everything */
655     if (info.dwFileAttributes & FILE_ATTRIBUTE_READONLY)
656         sb->st_mode &= ~(S_IRUSR|S_IRGRP|S_IROTH);
657     if (info.dwFileAttributes & FILE_ATTRIBUTE_SYSTEM)
658         sb->st_mode &= ~S_IRWXO; /* remove everything for other */
659     if (info.dwFileAttributes & FILE_ATTRIBUTE_HIDDEN)
660         sb->st_mode |= S_ISVTX; /* use sticky bit -> hidden */
661     sb->st_mode |= S_IFREG;
662
663     sb->st_size = info.nFileSizeHigh;
664     sb->st_size <<= 32;
665     sb->st_size |= info.nFileSizeLow;
666     sb->st_blksize = 4096;
667     sb->st_blocks = (uint32_t)(sb->st_size + 4095)/4096;
668     sb->st_atime = cvt_ftime_to_utime(info.ftLastAccessTime);
669     sb->st_mtime = cvt_ftime_to_utime(info.ftLastWriteTime);
670     sb->st_ctime = cvt_ftime_to_utime(info.ftCreationTime);
671
672 error:
673     CloseHandle(h);
674     return rval;
675 }
676
677 int
678 stat(const char *file, struct stat *sb)
679 {
680    WIN32_FILE_ATTRIBUTE_DATA data;
681    errno = 0;
682
683
684    memset(sb, 0, sizeof(*sb));
685
686    /* why not allow win 95 to use p_GetFileAttributesExA ? 
687     * this function allows _some_ open files to be stat'ed 
688     * if (g_platform_id == VER_PLATFORM_WIN32_WINDOWS) {
689     *    return stat2(file, sb);
690     * }
691     */
692
693    if (p_GetFileAttributesExW) {
694       /* dynamically allocate enough space for UCS2 filename */
695       POOLMEM* pwszBuf = get_pool_memory (PM_FNAME);
696       make_win32_path_UTF8_2_wchar(&pwszBuf, file);
697
698       BOOL b = p_GetFileAttributesExW((LPCWSTR) pwszBuf, GetFileExInfoStandard, &data);
699       free_pool_memory(pwszBuf);
700
701       if (!b) {
702          return stat2(file, sb);
703       }
704    } else if (p_GetFileAttributesExA) {
705       if (!p_GetFileAttributesExA(file, GetFileExInfoStandard, &data)) {
706          return stat2(file, sb);
707        }
708    } else {
709       return stat2(file, sb);
710    }
711
712    sb->st_mode = 0777;               /* start with everything */
713    if (data.dwFileAttributes & FILE_ATTRIBUTE_READONLY) {
714       sb->st_mode &= ~(S_IRUSR|S_IRGRP|S_IROTH);
715    }
716    if (data.dwFileAttributes & FILE_ATTRIBUTE_SYSTEM) {
717       sb->st_mode &= ~S_IRWXO; /* remove everything for other */
718    }
719    if (data.dwFileAttributes & FILE_ATTRIBUTE_HIDDEN) {
720       sb->st_mode |= S_ISVTX; /* use sticky bit -> hidden */
721    }
722    if (data.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) {
723       sb->st_mode |= S_IFDIR;
724    } else {
725       sb->st_mode |= S_IFREG;
726    }
727
728    sb->st_nlink = 1;
729    sb->st_size = data.nFileSizeHigh;
730    sb->st_size <<= 32;
731    sb->st_size |= data.nFileSizeLow;
732    sb->st_blksize = 4096;
733    sb->st_blocks = (uint32_t)(sb->st_size + 4095)/4096;
734    sb->st_atime = cvt_ftime_to_utime(data.ftLastAccessTime);
735    sb->st_mtime = cvt_ftime_to_utime(data.ftLastWriteTime);
736    sb->st_ctime = cvt_ftime_to_utime(data.ftCreationTime);
737    return 0;
738 }
739
740 int
741 lstat(const char *file, struct stat *sb)
742 {
743    return stat(file, sb);
744 }
745
746 void
747 sleep(int sec)
748 {
749    Sleep(sec * 1000);
750 }
751
752 int
753 geteuid(void)
754 {
755    return 0;
756 }
757
758 int
759 execvp(const char *, char *[]) {
760    errno = ENOSYS;
761    return -1;
762 }
763
764
765 int
766 fork(void)
767 {
768    errno = ENOSYS;
769    return -1;
770 }
771
772 int
773 pipe(int[])
774 {
775    errno = ENOSYS;
776    return -1;
777 }
778
779 int
780 waitpid(int, int*, int)
781 {
782    errno = ENOSYS;
783    return -1;
784 }
785
786 int
787 readlink(const char *, char *, int)
788 {
789    errno = ENOSYS;
790    return -1;
791 }
792
793
794 #ifndef HAVE_MINGW
795 int
796 strcasecmp(const char *s1, const char *s2)
797 {
798    register int ch1, ch2;
799
800    if (s1==s2)
801       return 0;       /* strings are equal if same object. */
802    else if (!s1)
803       return -1;
804    else if (!s2)
805       return 1;
806    do {
807       ch1 = *s1;
808       ch2 = *s2;
809       s1++;
810       s2++;
811    } while (ch1 != 0 && tolower(ch1) == tolower(ch2));
812
813    return(ch1 - ch2);
814 }
815 #endif //HAVE_MINGW
816
817 int
818 strncasecmp(const char *s1, const char *s2, int len)
819 {
820     register int ch1, ch2;
821
822     if (s1==s2)
823         return 0;       /* strings are equal if same object. */
824     else if (!s1)
825         return -1;
826     else if (!s2)
827         return 1;
828     while (len--) {
829         ch1 = *s1;
830         ch2 = *s2;
831         s1++;
832         s2++;
833         if (ch1 == 0 || tolower(ch1) != tolower(ch2)) break;
834     }
835
836     return (ch1 - ch2);
837 }
838
839 int
840 gettimeofday(struct timeval *tv, struct timezone *)
841 {
842     SYSTEMTIME now;
843     FILETIME tmp;
844     GetSystemTime(&now);
845
846     if (tv == NULL) {
847        errno = EINVAL;
848        return -1;
849     }
850     if (!SystemTimeToFileTime(&now, &tmp)) {
851        errno = b_errno_win32;
852        return -1;
853     }
854
855     int64_t _100nsec = tmp.dwHighDateTime;
856     _100nsec <<= 32;
857     _100nsec |= tmp.dwLowDateTime;
858     _100nsec -= WIN32_FILETIME_ADJUST;
859
860     tv->tv_sec =(long) (_100nsec / 10000000);
861     tv->tv_usec = (long) ((_100nsec % 10000000)/10);
862     return 0;
863
864 }
865
866 /* For apcupsd this is in src/lib/wincompat.c */
867 #ifndef __APCUPSD__
868 extern "C" void syslog(int type, const char *fmt, ...) 
869 {
870 /*#ifndef HAVE_CONSOLE
871     MessageBox(NULL, msg, "Bacula", MB_OK);
872 #endif*/
873 }
874 #endif
875
876 struct passwd *
877 getpwuid(uid_t)
878 {
879     return NULL;
880 }
881
882 struct group *
883 getgrgid(uid_t)
884 {
885     return NULL;
886 }
887
888 // implement opendir/readdir/closedir on top of window's API
889
890 typedef struct _dir
891 {
892     WIN32_FIND_DATAA data_a;    // window's file info (ansii version)
893     WIN32_FIND_DATAW data_w;    // window's file info (wchar version)
894     const char *spec;           // the directory we're traversing
895     HANDLE      dirh;           // the search handle
896     BOOL        valid_a;        // the info in data_a field is valid
897     BOOL        valid_w;        // the info in data_w field is valid
898     UINT32      offset;         // pseudo offset for d_off
899 } _dir;
900
901 DIR *
902 opendir(const char *path)
903 {
904     /* enough space for VSS !*/
905     int max_len = strlen(path) + MAX_PATH;
906     _dir *rval = NULL;
907     if (path == NULL) {
908        errno = ENOENT;
909        return NULL;
910     }
911
912     rval = (_dir *)malloc(sizeof(_dir));
913     memset (rval, 0, sizeof (_dir));
914     if (rval == NULL) return NULL;
915     char *tspec = (char *)malloc(max_len);
916     if (tspec == NULL) return NULL;
917
918     if (g_platform_id != VER_PLATFORM_WIN32_WINDOWS) {
919 #ifdef WIN32_VSS
920        /* will append \\?\ at front itself */
921        conv_unix_to_win32_path(path, tspec, max_len-4);
922 #else
923        /* allow path to be 32767 bytes */
924        tspec[0] = '\\';
925        tspec[1] = '\\';
926        tspec[2] = '?';
927        tspec[3] = '\\';
928        tspec[4] = 0;
929        conv_unix_to_win32_path(path, tspec+4, max_len-4);
930 #endif
931     } else {
932        conv_unix_to_win32_path(path, tspec, max_len);
933     }
934
935     // add backslash only if there is none yet (think of c:\)
936     if (tspec[strlen(tspec)-1] != '\\')
937       bstrncat(tspec, "\\*", max_len);
938     else
939       bstrncat(tspec, "*", max_len);
940
941     rval->spec = tspec;
942
943     // convert to wchar_t
944     if (p_FindFirstFileW) {
945       POOLMEM* pwcBuf = get_pool_memory(PM_FNAME);;
946       make_win32_path_UTF8_2_wchar(&pwcBuf,rval->spec);
947
948       rval->dirh = p_FindFirstFileW((LPCWSTR)pwcBuf, &rval->data_w);
949
950       free_pool_memory(pwcBuf);
951
952       if (rval->dirh != INVALID_HANDLE_VALUE)
953         rval->valid_w = 1;
954     } else if (p_FindFirstFileA) {
955       rval->dirh = p_FindFirstFileA(rval->spec, &rval->data_a);
956
957       if (rval->dirh != INVALID_HANDLE_VALUE)
958         rval->valid_a = 1;
959     } else goto err;
960
961
962     d_msg(__FILE__, __LINE__,
963           99, "opendir(%s)\n\tspec=%s,\n\tFindFirstFile returns %d\n",
964           path, rval->spec, rval->dirh);
965
966     rval->offset = 0;
967     if (rval->dirh == INVALID_HANDLE_VALUE)
968         goto err;
969
970     if (rval->valid_w)
971       d_msg(__FILE__, __LINE__,
972             99, "\tFirstFile=%s\n", rval->data_w.cFileName);
973
974     if (rval->valid_a)
975       d_msg(__FILE__, __LINE__,
976             99, "\tFirstFile=%s\n", rval->data_a.cFileName);
977
978     return (DIR *)rval;
979
980 err:
981     free((void *)rval->spec);
982     free(rval);
983     errno = b_errno_win32;
984     return NULL;
985 }
986
987 int
988 closedir(DIR *dirp)
989 {
990     _dir *dp = (_dir *)dirp;
991     FindClose(dp->dirh);
992     free((void *)dp->spec);
993     free((void *)dp);
994     return 0;
995 }
996
997 /*
998   typedef struct _WIN32_FIND_DATA {
999     DWORD dwFileAttributes;
1000     FILETIME ftCreationTime;
1001     FILETIME ftLastAccessTime;
1002     FILETIME ftLastWriteTime;
1003     DWORD nFileSizeHigh;
1004     DWORD nFileSizeLow;
1005     DWORD dwReserved0;
1006     DWORD dwReserved1;
1007     TCHAR cFileName[MAX_PATH];
1008     TCHAR cAlternateFileName[14];
1009 } WIN32_FIND_DATA, *PWIN32_FIND_DATA;
1010 */
1011
1012 static int
1013 copyin(struct dirent &dp, const char *fname)
1014 {
1015     dp.d_ino = 0;
1016     dp.d_reclen = 0;
1017     char *cp = dp.d_name;
1018     while (*fname) {
1019         *cp++ = *fname++;
1020         dp.d_reclen++;
1021     }
1022         *cp = 0;
1023     return dp.d_reclen;
1024 }
1025
1026 int
1027 readdir_r(DIR *dirp, struct dirent *entry, struct dirent **result)
1028 {
1029     _dir *dp = (_dir *)dirp;
1030     if (dp->valid_w || dp->valid_a) {
1031       entry->d_off = dp->offset;
1032
1033       // copy unicode
1034       if (dp->valid_w) {
1035          char szBuf[MAX_PATH_UTF8+1];
1036          wchar_2_UTF8(szBuf,dp->data_w.cFileName);
1037          dp->offset += copyin(*entry, szBuf);
1038       } else if (dp->valid_a) { // copy ansi (only 1 will be valid)
1039          dp->offset += copyin(*entry, dp->data_a.cFileName);
1040       }
1041
1042       *result = entry;              /* return entry address */
1043       d_msg(__FILE__, __LINE__,
1044             99, "readdir_r(%p, { d_name=\"%s\", d_reclen=%d, d_off=%d\n",
1045             dirp, entry->d_name, entry->d_reclen, entry->d_off);
1046     } else {
1047 //      d_msg(__FILE__, __LINE__, 99, "readdir_r !valid\n");
1048         errno = b_errno_win32;
1049         return -1;
1050     }
1051
1052     // get next file, try unicode first
1053     if (p_FindNextFileW)
1054        dp->valid_w = p_FindNextFileW(dp->dirh, &dp->data_w);
1055     else if (p_FindNextFileA)
1056        dp->valid_a = p_FindNextFileA(dp->dirh, &dp->data_a);
1057     else {
1058        dp->valid_a = FALSE;
1059        dp->valid_w = FALSE;
1060     }
1061
1062     return 0;
1063 }
1064
1065 /*
1066  * Dotted IP address to network address
1067  *
1068  * Returns 1 if  OK
1069  *         0 on error
1070  */
1071 int
1072 inet_aton(const char *a, struct in_addr *inp)
1073 {
1074    const char *cp = a;
1075    uint32_t acc = 0, tmp = 0;
1076    int dotc = 0;
1077
1078    if (!isdigit(*cp)) {         /* first char must be digit */
1079       return 0;                 /* error */
1080    }
1081    do {
1082       if (isdigit(*cp)) {
1083          tmp = (tmp * 10) + (*cp -'0');
1084       } else if (*cp == '.' || *cp == 0) {
1085          if (tmp > 255) {
1086             return 0;           /* error */
1087          }
1088          acc = (acc << 8) + tmp;
1089          dotc++;
1090          tmp = 0;
1091       } else {
1092          return 0;              /* error */
1093       }
1094    } while (*cp++ != 0);
1095    if (dotc != 4) {              /* want 3 .'s plus EOS */
1096       return 0;                  /* error */
1097    }
1098    inp->s_addr = htonl(acc);     /* store addr in network format */
1099    return 1;
1100 }
1101
1102 int
1103 nanosleep(const struct timespec *req, struct timespec *rem)
1104 {
1105     if (rem)
1106         rem->tv_sec = rem->tv_nsec = 0;
1107     Sleep((req->tv_sec * 1000) + (req->tv_nsec/100000));
1108     return 0;
1109 }
1110
1111 void
1112 init_signals(void terminate(int sig))
1113 {
1114
1115 }
1116
1117 void
1118 init_stack_dump(void)
1119 {
1120
1121 }
1122
1123
1124 long
1125 pathconf(const char *path, int name)
1126 {
1127     switch(name) {
1128     case _PC_PATH_MAX :
1129         if (strncmp(path, "\\\\?\\", 4) == 0)
1130             return 32767;
1131     case _PC_NAME_MAX :
1132         return 255;
1133     }
1134     errno = ENOSYS;
1135     return -1;
1136 }
1137
1138 int
1139 WSA_Init(void)
1140 {
1141     WORD wVersionRequested = MAKEWORD( 1, 1);
1142     WSADATA wsaData;
1143
1144     int err = WSAStartup(wVersionRequested, &wsaData);
1145
1146
1147     if (err != 0) {
1148         printf("Can not start Windows Sockets\n");
1149         errno = ENOSYS;
1150         return -1;
1151     }
1152
1153     return 0;
1154 }
1155
1156
1157 int
1158 win32_chdir(const char *dir)
1159 {
1160    if (p_SetCurrentDirectoryW) {
1161       POOLMEM* pwszBuf = get_pool_memory(PM_FNAME);
1162       make_win32_path_UTF8_2_wchar(&pwszBuf, dir);
1163
1164       BOOL b=p_SetCurrentDirectoryW((LPCWSTR)pwszBuf);
1165       
1166       free_pool_memory(pwszBuf);
1167
1168       if (!b) {
1169          errno = b_errno_win32;
1170          return -1;
1171       }
1172    }
1173    else if (p_SetCurrentDirectoryA) {
1174       if (0 == p_SetCurrentDirectoryA(dir)) {
1175          errno = b_errno_win32;
1176          return -1;
1177       }
1178    }
1179    else return -1;
1180
1181    return 0;
1182 }
1183
1184 int
1185 win32_mkdir(const char *dir)
1186 {
1187    if (p_wmkdir){
1188       POOLMEM* pwszBuf = get_pool_memory(PM_FNAME);
1189       make_win32_path_UTF8_2_wchar(&pwszBuf, dir);
1190
1191       int n = p_wmkdir((LPCWSTR)pwszBuf);
1192       free_pool_memory(pwszBuf);
1193       return n;
1194    }
1195
1196    return _mkdir(dir);
1197 }
1198
1199
1200 char *
1201 win32_getcwd(char *buf, int maxlen)
1202 {
1203    int n=0;
1204
1205    if (p_GetCurrentDirectoryW) {
1206       POOLMEM* pwszBuf = get_pool_memory(PM_FNAME);
1207       pwszBuf = check_pool_memory_size (pwszBuf, maxlen*sizeof(wchar_t));
1208
1209       n = p_GetCurrentDirectoryW(maxlen, (LPWSTR) pwszBuf);
1210       if (n!=0)
1211          n = wchar_2_UTF8 (buf, (wchar_t *)pwszBuf, maxlen)-1;
1212       free_pool_memory(pwszBuf);
1213
1214    } else if (p_GetCurrentDirectoryA)
1215       n = p_GetCurrentDirectoryA(maxlen, buf);
1216
1217    if (n == 0 || n > maxlen) return NULL;
1218
1219    if (n+1 > maxlen) return NULL;
1220    if (n != 3) {
1221        buf[n] = '\\';
1222        buf[n+1] = 0;
1223    }
1224    return buf;
1225 }
1226
1227 int
1228 win32_fputs(const char *string, FILE *stream)
1229 {
1230    /* we use WriteConsoleA / WriteConsoleA
1231       so we can be sure that unicode support works on win32.
1232       with fallback if something fails
1233    */
1234
1235    HANDLE hOut = GetStdHandle (STD_OUTPUT_HANDLE);
1236    if (hOut && (hOut != INVALID_HANDLE_VALUE) && p_WideCharToMultiByte &&
1237        p_MultiByteToWideChar && (stream == stdout)) {
1238
1239       POOLMEM* pwszBuf = get_pool_memory(PM_MESSAGE);
1240
1241       DWORD dwCharsWritten;
1242       DWORD dwChars;
1243
1244       dwChars = UTF8_2_wchar(&pwszBuf, string);
1245
1246       /* try WriteConsoleW */
1247       if (WriteConsoleW (hOut, pwszBuf, dwChars-1, &dwCharsWritten, NULL)) {
1248          free_pool_memory(pwszBuf);
1249          return dwCharsWritten;
1250       }
1251
1252       /* convert to local codepage and try WriteConsoleA */
1253       POOLMEM* pszBuf = get_pool_memory(PM_MESSAGE);
1254       pszBuf = check_pool_memory_size(pszBuf, dwChars+1);
1255
1256       dwChars = p_WideCharToMultiByte(GetConsoleOutputCP(),0,(LPCWSTR) pwszBuf,-1,pszBuf,dwChars,NULL,NULL);
1257       free_pool_memory(pwszBuf);
1258
1259       if (WriteConsoleA (hOut, pszBuf, dwChars-1, &dwCharsWritten, NULL)) {
1260          free_pool_memory(pszBuf);
1261          return dwCharsWritten;
1262       }
1263    }
1264
1265    return fputs(string, stream);
1266 }
1267
1268 char*
1269 win32_cgets (char* buffer, int len)
1270 {
1271    /* we use console ReadConsoleA / ReadConsoleW to be able to read unicode
1272       from the win32 console and fallback if seomething fails */
1273
1274    HANDLE hIn = GetStdHandle (STD_INPUT_HANDLE);
1275    if (hIn && (hIn != INVALID_HANDLE_VALUE) && p_WideCharToMultiByte && p_MultiByteToWideChar) {
1276       DWORD dwRead;
1277       wchar_t wszBuf[1024];
1278       char  szBuf[1024];
1279
1280       /* nt and unicode conversion */
1281       if (ReadConsoleW (hIn, wszBuf, 1024, &dwRead, NULL)) {
1282
1283          /* null terminate at end */
1284          if (wszBuf[dwRead-1] == L'\n') {
1285             wszBuf[dwRead-1] = L'\0';
1286             dwRead --;
1287          }
1288
1289          if (wszBuf[dwRead-1] == L'\r') {
1290             wszBuf[dwRead-1] = L'\0';
1291             dwRead --;
1292          }
1293
1294          wchar_2_UTF8(buffer, wszBuf, len);
1295          return buffer;
1296       }
1297
1298       /* win 9x and unicode conversion */
1299       if (ReadConsoleA (hIn, szBuf, 1024, &dwRead, NULL)) {
1300
1301          /* null terminate at end */
1302          if (szBuf[dwRead-1] == L'\n') {
1303             szBuf[dwRead-1] = L'\0';
1304             dwRead --;
1305          }
1306
1307          if (szBuf[dwRead-1] == L'\r') {
1308             szBuf[dwRead-1] = L'\0';
1309             dwRead --;
1310          }
1311
1312          /* convert from ansii to wchar_t */
1313          p_MultiByteToWideChar(GetConsoleCP(), 0, szBuf, -1, wszBuf,1024);
1314          /* convert from wchar_t to UTF-8 */
1315          if (wchar_2_UTF8(buffer, wszBuf, len))
1316             return buffer;
1317       }
1318    }
1319
1320    /* fallback */
1321    if (fgets(buffer, len, stdin))
1322       return buffer;
1323    else
1324       return NULL;
1325 }
1326
1327 int
1328 win32_unlink(const char *filename)
1329 {
1330    int nRetCode;
1331    if (p_wunlink) {
1332       POOLMEM* pwszBuf = get_pool_memory(PM_FNAME);
1333       make_win32_path_UTF8_2_wchar(&pwszBuf, filename);
1334
1335       nRetCode = _wunlink((LPCWSTR) pwszBuf);
1336
1337       /* special case if file is readonly,
1338       we retry but unset attribute before */
1339       if (nRetCode == -1 && errno == EACCES && p_SetFileAttributesW && p_GetFileAttributesW) {
1340          DWORD dwAttr =  p_GetFileAttributesW((LPCWSTR)pwszBuf);
1341          if (dwAttr != INVALID_FILE_ATTRIBUTES) {
1342             if (p_SetFileAttributesW((LPCWSTR)pwszBuf, dwAttr & ~FILE_ATTRIBUTE_READONLY)) {
1343                nRetCode = _wunlink((LPCWSTR) pwszBuf);
1344                /* reset to original if it didn't help */
1345                if (nRetCode == -1)
1346                   p_SetFileAttributesW((LPCWSTR)pwszBuf, dwAttr);
1347             }
1348          }
1349       }
1350       free_pool_memory(pwszBuf);
1351    } else {
1352       nRetCode = _unlink(filename);
1353
1354       /* special case if file is readonly,
1355       we retry but unset attribute before */
1356       if (nRetCode == -1 && errno == EACCES && p_SetFileAttributesA && p_GetFileAttributesA) {
1357          DWORD dwAttr =  p_GetFileAttributesA(filename);
1358          if (dwAttr != INVALID_FILE_ATTRIBUTES) {
1359             if (p_SetFileAttributesA(filename, dwAttr & ~FILE_ATTRIBUTE_READONLY)) {
1360                nRetCode = _unlink(filename);
1361                /* reset to original if it didn't help */
1362                if (nRetCode == -1)
1363                   p_SetFileAttributesA(filename, dwAttr);
1364             }
1365          }
1366       }
1367    }
1368    return nRetCode;
1369 }
1370
1371
1372 #include "mswinver.h"
1373
1374 char WIN_VERSION_LONG[64];
1375 char WIN_VERSION[32];
1376 char WIN_RAWVERSION[32];
1377
1378 class winver {
1379 public:
1380     winver(void);
1381 };
1382
1383 static winver INIT;                     // cause constructor to be called before main()
1384
1385
1386 winver::winver(void)
1387 {
1388     const char *version = "";
1389     const char *platform = "";
1390     OSVERSIONINFO osvinfo;
1391     osvinfo.dwOSVersionInfoSize = sizeof(osvinfo);
1392
1393     // Get the current OS version
1394     if (!GetVersionEx(&osvinfo)) {
1395         version = "Unknown";
1396         platform = "Unknown";
1397     }
1398         const int ver = _mkversion(osvinfo.dwPlatformId,
1399                                    osvinfo.dwMajorVersion,
1400                                    osvinfo.dwMinorVersion);
1401         snprintf(WIN_RAWVERSION, sizeof(WIN_RAWVERSION), "Windows %#08x", ver);
1402          switch (ver)
1403         {
1404         case MS_WINDOWS_95: (version =  "Windows 95"); break;
1405         case MS_WINDOWS_98: (version =  "Windows 98"); break;
1406         case MS_WINDOWS_ME: (version =  "Windows ME"); break;
1407         case MS_WINDOWS_NT4:(version =  "Windows NT 4.0"); platform = "NT"; break;
1408         case MS_WINDOWS_2K: (version =  "Windows 2000");platform = "NT"; break;
1409         case MS_WINDOWS_XP: (version =  "Windows XP");platform = "NT"; break;
1410         case MS_WINDOWS_S2003: (version =  "Windows Server 2003");platform = "NT"; break;
1411         default: version = WIN_RAWVERSION; break;
1412         }
1413
1414     bstrncpy(WIN_VERSION_LONG, version, sizeof(WIN_VERSION_LONG));
1415     snprintf(WIN_VERSION, sizeof(WIN_VERSION), "%s %d.%d.%d",
1416              platform, osvinfo.dwMajorVersion, osvinfo.dwMinorVersion, osvinfo.dwBuildNumber);
1417
1418 #if 0
1419     HANDLE h = CreateFile("G:\\foobar", GENERIC_WRITE, 0, NULL, CREATE_ALWAYS, 0, NULL);
1420     CloseHandle(h);
1421 #endif
1422 #if 0
1423     BPIPE *b = open_bpipe("ls -l", 10, "r");
1424     char buf[1024];
1425     while (!feof(b->rfd)) {
1426         fgets(buf, sizeof(buf), b->rfd);
1427     }
1428     close_bpipe(b);
1429 #endif
1430 }
1431
1432 BOOL CreateChildProcess(VOID);
1433 VOID WriteToPipe(VOID);
1434 VOID ReadFromPipe(VOID);
1435 VOID ErrorExit(LPCSTR);
1436 VOID ErrMsg(LPTSTR, BOOL);
1437
1438 /**
1439  * Check for a quoted path,  if an absolute path name is given and it contains
1440  * spaces it will need to be quoted.  i.e.  "c:/Program Files/foo/bar.exe"
1441  * CreateProcess() says the best way to ensure proper results with executables
1442  * with spaces in path or filename is to quote the string.
1443  */
1444 const char *
1445 getArgv0(const char *cmdline)
1446 {
1447
1448     int inquote = 0;
1449     const char *cp;
1450     for (cp = cmdline; *cp; cp++)
1451     {
1452         if (*cp == '"') {
1453             inquote = !inquote;
1454         }
1455         if (!inquote && isspace(*cp))
1456             break;
1457     }
1458
1459
1460     int len = cp - cmdline;
1461     char *rval = (char *)malloc(len+1);
1462
1463     cp = cmdline;
1464     char *rp = rval;
1465
1466     while (len--)
1467         *rp++ = *cp++;
1468
1469     *rp = 0;
1470     return rval;
1471 }
1472
1473
1474 /**
1475  * OK, so it would seem CreateProcess only handles true executables:
1476  *  .com or .exe files.
1477  * So test to see whether we're getting a .bat file and if so grab
1478  * $COMSPEC value and pass batch file to it.
1479  */
1480 HANDLE
1481 CreateChildProcess(const char *cmdline, HANDLE in, HANDLE out, HANDLE err)
1482 {
1483     static const char *comspec = NULL;
1484     PROCESS_INFORMATION piProcInfo;
1485     STARTUPINFOA siStartInfo;
1486     BOOL bFuncRetn = FALSE;
1487
1488     if (comspec == NULL) {
1489        comspec = getenv("COMSPEC");
1490     }
1491     if (comspec == NULL) // should never happen
1492         return INVALID_HANDLE_VALUE;
1493
1494     // Set up members of the PROCESS_INFORMATION structure.
1495     ZeroMemory( &piProcInfo, sizeof(PROCESS_INFORMATION) );
1496
1497     // Set up members of the STARTUPINFO structure.
1498
1499     ZeroMemory( &siStartInfo, sizeof(STARTUPINFO) );
1500     siStartInfo.cb = sizeof(STARTUPINFO);
1501     // setup new process to use supplied handles for stdin,stdout,stderr
1502     // if supplied handles are not used the send a copy of our STD_HANDLE
1503     // as appropriate
1504     siStartInfo.dwFlags = STARTF_USESTDHANDLES;
1505
1506     if (in != INVALID_HANDLE_VALUE)
1507         siStartInfo.hStdInput = in;
1508     else
1509         siStartInfo.hStdInput = GetStdHandle(STD_INPUT_HANDLE);
1510
1511     if (out != INVALID_HANDLE_VALUE)
1512         siStartInfo.hStdOutput = out;
1513     else
1514         siStartInfo.hStdOutput = GetStdHandle(STD_OUTPUT_HANDLE);
1515     if (err != INVALID_HANDLE_VALUE)
1516         siStartInfo.hStdError = err;
1517     else
1518         siStartInfo.hStdError = GetStdHandle(STD_ERROR_HANDLE);
1519     // Create the child process.
1520
1521     char exeFile[256];
1522     int cmdLen = strlen(cmdline) + strlen(comspec) + 16;
1523
1524     char *cmdLine = (char *)alloca(cmdLen);
1525
1526     bstrncpy(exeFile, comspec, sizeof(exeFile));
1527     bstrncpy(cmdLine, comspec, cmdLen);
1528     bstrncat(cmdLine, " /c ", cmdLen);
1529     bstrncat(cmdLine, cmdline, cmdLen);
1530
1531     // try to execute program
1532     bFuncRetn = CreateProcessA(exeFile,
1533                               cmdLine, // command line
1534                               NULL, // process security attributes
1535                               NULL, // primary thread security attributes
1536                               TRUE, // handles are inherited
1537                               0, // creation flags
1538                               NULL, // use parent's environment
1539                               NULL, // use parent's current directory
1540                               &siStartInfo, // STARTUPINFO pointer
1541                               &piProcInfo); // receives PROCESS_INFORMATION
1542
1543     if (bFuncRetn == 0) {
1544         ErrorExit("CreateProcess failed\n");
1545         const char *err = errorString();
1546         d_msg(__FILE__, __LINE__, 99,
1547               "CreateProcess(%s, %s, ...)=%s\n", exeFile, cmdLine, err);
1548         LocalFree((void *)err);
1549         return INVALID_HANDLE_VALUE;
1550     }
1551     // we don't need a handle on the process primary thread so we close
1552     // this now.
1553     CloseHandle(piProcInfo.hThread);
1554
1555     return piProcInfo.hProcess;
1556 }
1557
1558
1559 void
1560 ErrorExit (LPCSTR lpszMessage)
1561 {
1562     d_msg(__FILE__, __LINE__, 0, "%s", lpszMessage);
1563 }
1564
1565
1566 /*
1567 typedef struct s_bpipe {
1568    pid_t worker_pid;
1569    time_t worker_stime;
1570    int wait;
1571    btimer_t *timer_id;
1572    FILE *rfd;
1573    FILE *wfd;
1574 } BPIPE;
1575 */
1576
1577 static void
1578 CloseIfValid(HANDLE handle)
1579 {
1580     if (handle != INVALID_HANDLE_VALUE)
1581         CloseHandle(handle);
1582 }
1583
1584 #ifndef HAVE_MINGW
1585 BPIPE *
1586 open_bpipe(char *prog, int wait, const char *mode)
1587 {
1588     HANDLE hChildStdinRd, hChildStdinWr, hChildStdinWrDup,
1589         hChildStdoutRd, hChildStdoutWr, hChildStdoutRdDup,
1590         hInputFile;
1591
1592     SECURITY_ATTRIBUTES saAttr;
1593
1594     BOOL fSuccess;
1595
1596     hChildStdinRd = hChildStdinWr = hChildStdinWrDup =
1597         hChildStdoutRd = hChildStdoutWr = hChildStdoutRdDup =
1598         hInputFile = INVALID_HANDLE_VALUE;
1599
1600     BPIPE *bpipe = (BPIPE *)malloc(sizeof(BPIPE));
1601     memset((void *)bpipe, 0, sizeof(BPIPE));
1602
1603     int mode_read = (mode[0] == 'r');
1604     int mode_write = (mode[0] == 'w' || mode[1] == 'w');
1605
1606
1607     // Set the bInheritHandle flag so pipe handles are inherited.
1608
1609     saAttr.nLength = sizeof(SECURITY_ATTRIBUTES);
1610     saAttr.bInheritHandle = TRUE;
1611     saAttr.lpSecurityDescriptor = NULL;
1612
1613     if (mode_read) {
1614
1615         // Create a pipe for the child process's STDOUT.
1616         if (! CreatePipe(&hChildStdoutRd, &hChildStdoutWr, &saAttr, 0)) {
1617             ErrorExit("Stdout pipe creation failed\n");
1618             goto cleanup;
1619         }
1620         // Create noninheritable read handle and close the inheritable read
1621         // handle.
1622
1623         fSuccess = DuplicateHandle(GetCurrentProcess(), hChildStdoutRd,
1624                                    GetCurrentProcess(), &hChildStdoutRdDup , 0,
1625                                    FALSE,
1626                                    DUPLICATE_SAME_ACCESS);
1627         if ( !fSuccess ) {
1628             ErrorExit("DuplicateHandle failed");
1629             goto cleanup;
1630         }
1631
1632         CloseHandle(hChildStdoutRd);
1633         hChildStdoutRd = INVALID_HANDLE_VALUE;
1634     }
1635
1636     if (mode_write) {
1637
1638         // Create a pipe for the child process's STDIN.
1639
1640         if (!CreatePipe(&hChildStdinRd, &hChildStdinWr, &saAttr, 0)) {
1641             ErrorExit("Stdin pipe creation failed\n");
1642             goto cleanup;
1643         }
1644
1645         // Duplicate the write handle to the pipe so it is not inherited.
1646         fSuccess = DuplicateHandle(GetCurrentProcess(), hChildStdinWr,
1647                                    GetCurrentProcess(), &hChildStdinWrDup,
1648                                    0,
1649                                    FALSE,                  // not inherited
1650                                    DUPLICATE_SAME_ACCESS);
1651         if (!fSuccess) {
1652             ErrorExit("DuplicateHandle failed");
1653             goto cleanup;
1654         }
1655
1656         CloseHandle(hChildStdinWr);
1657         hChildStdinWr = INVALID_HANDLE_VALUE;
1658     }
1659     // spawn program with redirected handles as appropriate
1660     bpipe->worker_pid = (pid_t)
1661         CreateChildProcess(prog,             // commandline
1662                            hChildStdinRd,    // stdin HANDLE
1663                            hChildStdoutWr,   // stdout HANDLE
1664                            hChildStdoutWr);  // stderr HANDLE
1665
1666     if ((HANDLE) bpipe->worker_pid == INVALID_HANDLE_VALUE)
1667         goto cleanup;
1668
1669     bpipe->wait = wait;
1670     bpipe->worker_stime = time(NULL);
1671
1672     if (mode_read) {
1673         CloseHandle(hChildStdoutWr); // close our write side so when
1674                                      // process terminates we can
1675                                      // detect eof.
1676         // ugly but convert WIN32 HANDLE to FILE*
1677         int rfd = _open_osfhandle((long)hChildStdoutRdDup, O_RDONLY);
1678         if (rfd >= 0) {
1679            bpipe->rfd = _fdopen(rfd, "r");
1680         }
1681     }
1682     if (mode_write) {
1683         CloseHandle(hChildStdinRd); // close our read side so as not
1684                                     // to interfre with child's copy
1685         // ugly but convert WIN32 HANDLE to FILE*
1686         int wfd = _open_osfhandle((long)hChildStdinWrDup, O_WRONLY);
1687         if (wfd >= 0) {
1688            bpipe->wfd = _fdopen(wfd, "w");
1689         }
1690     }
1691
1692     if (wait > 0) {
1693         bpipe->timer_id = start_child_timer(bpipe->worker_pid, wait);
1694     }
1695
1696     return bpipe;
1697
1698 cleanup:
1699
1700     CloseIfValid(hChildStdoutRd);
1701     CloseIfValid(hChildStdoutRdDup);
1702     CloseIfValid(hChildStdinWr);
1703     CloseIfValid(hChildStdinWrDup);
1704
1705     free((void *) bpipe);
1706     errno = b_errno_win32;            /* do GetLastError() for error code */
1707     return NULL;
1708 }
1709
1710 #endif //HAVE_MINGW
1711
1712 int
1713 kill(int pid, int signal)
1714 {
1715    int rval = 0;
1716    if (!TerminateProcess((HANDLE)pid, (UINT) signal)) {
1717       rval = -1;
1718       errno = b_errno_win32;
1719    }
1720    CloseHandle((HANDLE)pid);
1721    return rval;
1722 }
1723
1724 #ifndef HAVE_MINGW
1725
1726 int
1727 close_bpipe(BPIPE *bpipe)
1728 {
1729    int rval = 0;
1730    int32_t remaining_wait = bpipe->wait;
1731
1732    if (remaining_wait == 0) {         /* wait indefinitely */
1733       remaining_wait = INT32_MAX;
1734    }
1735    for ( ;; ) {
1736       DWORD exitCode;
1737       if (!GetExitCodeProcess((HANDLE)bpipe->worker_pid, &exitCode)) {
1738          const char *err = errorString();
1739          rval = b_errno_win32;
1740          d_msg(__FILE__, __LINE__, 0,
1741                "GetExitCode error %s\n", err);
1742          LocalFree((void *)err);
1743          break;
1744       }
1745       if (exitCode == STILL_ACTIVE) {
1746          if (remaining_wait <= 0) {
1747             rval = ETIME;             /* timed out */
1748             break;
1749          }
1750          bmicrosleep(1, 0);           /* wait one second */
1751          remaining_wait--;
1752       } else if (exitCode != 0) {
1753          /* Truncate exit code as it doesn't seem to be correct */
1754          rval = (exitCode & 0xFF) | b_errno_exit;
1755          break;
1756       } else {
1757          break;                       /* Shouldn't get here */
1758       }
1759    }
1760
1761    if (bpipe->timer_id) {
1762        stop_child_timer(bpipe->timer_id);
1763    }
1764    if (bpipe->rfd) fclose(bpipe->rfd);
1765    if (bpipe->wfd) fclose(bpipe->wfd);
1766    free((void *)bpipe);
1767    return rval;
1768 }
1769
1770 int
1771 close_wpipe(BPIPE *bpipe)
1772 {
1773     int stat = 1;
1774
1775     if (bpipe->wfd) {
1776         fflush(bpipe->wfd);
1777         if (fclose(bpipe->wfd) != 0) {
1778             stat = 0;
1779         }
1780         bpipe->wfd = NULL;
1781     }
1782     return stat;
1783 }
1784
1785 #include "findlib/find.h"
1786
1787 int
1788 utime(const char *fname, struct utimbuf *times)
1789 {
1790     FILETIME acc, mod;
1791     char tmpbuf[5000];
1792
1793     conv_unix_to_win32_path(fname, tmpbuf, 5000);
1794
1795     cvt_utime_to_ftime(times->actime, acc);
1796     cvt_utime_to_ftime(times->modtime, mod);
1797
1798     HANDLE h = INVALID_HANDLE_VALUE;
1799
1800     if (p_CreateFileW) {
1801       POOLMEM* pwszBuf = get_pool_memory(PM_FNAME);
1802       make_win32_path_UTF8_2_wchar(&pwszBuf, tmpbuf);
1803
1804       h = p_CreateFileW((LPCWSTR)pwszBuf,
1805                         FILE_WRITE_ATTRIBUTES,
1806                         FILE_SHARE_WRITE|FILE_SHARE_READ|FILE_SHARE_DELETE,
1807                         NULL,
1808                         OPEN_EXISTING,
1809                         FILE_FLAG_BACKUP_SEMANTICS, // required for directories
1810                         NULL);
1811
1812       free_pool_memory(pwszBuf);
1813     } else if (p_CreateFileA) {
1814       h = p_CreateFileA(tmpbuf,
1815                         FILE_WRITE_ATTRIBUTES,
1816                         FILE_SHARE_WRITE|FILE_SHARE_READ|FILE_SHARE_DELETE,
1817                         NULL,
1818                         OPEN_EXISTING,
1819                         FILE_FLAG_BACKUP_SEMANTICS, // required for directories
1820                         NULL);
1821     }
1822
1823     if (h == INVALID_HANDLE_VALUE) {
1824         const char *err = errorString();
1825         d_msg(__FILE__, __LINE__, 99,
1826               "Cannot open file \"%s\" for utime(): ERR=%s", tmpbuf, err);
1827         LocalFree((void *)err);
1828         errno = b_errno_win32;
1829         return -1;
1830     }
1831
1832     int rval = SetFileTime(h, NULL, &acc, &mod) ? 0 : -1;
1833     CloseHandle(h);
1834     if (rval == -1) {
1835        errno = b_errno_win32;
1836     }
1837     return rval;
1838 }
1839
1840 #if USE_WIN32_COMPAT_IO
1841
1842 int
1843 open(const char *file, int flags, int mode)
1844 {
1845    if (p_wopen) {
1846       POOLMEM* pwszBuf = get_pool_memory(PM_FNAME);
1847       make_win32_path_UTF8_2_wchar(&pwszBuf, file);
1848
1849       int nRet = p_wopen((LPCWSTR) pwszBuf, flags|_O_BINARY, mode);
1850       free_pool_memory(pwszBuf);
1851
1852       return nRet;
1853    }
1854
1855    return _open(file, flags|_O_BINARY, mode);
1856 }
1857
1858 /*
1859  * Note, this works only for a file. If you want
1860  *   to close a socket, use closesocket().
1861  *   Bacula has been modified in src/lib/bnet.c
1862  *   to use closesocket().
1863  */
1864 #ifndef HAVE_VC8
1865 int
1866 close(int fd)
1867 {
1868     return _close(fd);
1869 }
1870
1871 #ifndef HAVE_WXCONSOLE
1872 ssize_t
1873 read(int fd, void *buf, ssize_t len)
1874 {
1875     return _read(fd, buf, (size_t)len);
1876 }
1877
1878 ssize_t
1879 write(int fd, const void *buf, ssize_t len)
1880 {
1881     return _write(fd, buf, (size_t)len);
1882 }
1883 #endif
1884
1885
1886 off_t
1887 lseek(int fd, off_t offset, int whence)
1888 {
1889     return (off_t)_lseeki64(fd, offset, whence);
1890 }
1891
1892 int
1893 dup2(int fd1, int fd2)
1894 {
1895     return _dup2(fd1, fd2);
1896 }
1897 #endif
1898 #else
1899 int
1900 open(const char *file, int flags, int mode)
1901 {
1902     DWORD access = 0;
1903     DWORD shareMode = 0;
1904     DWORD create = 0;
1905     DWORD msflags = 0;
1906     HANDLE foo = INVALID_HANDLE_VALUE;
1907     const char *remap = file;
1908
1909     if (flags & O_WRONLY) access = GENERIC_WRITE;
1910     else if (flags & O_RDWR) access = GENERIC_READ|GENERIC_WRITE;
1911     else access = GENERIC_READ;
1912
1913     if (flags & O_CREAT) create = CREATE_NEW;
1914     else create = OPEN_EXISTING;
1915
1916     if (flags & O_TRUNC) create = TRUNCATE_EXISTING;
1917
1918     if (!(flags & O_EXCL))
1919         shareMode = FILE_SHARE_DELETE|FILE_SHARE_READ|FILE_SHARE_WRITE;
1920
1921     if (flags & O_APPEND) {
1922         printf("open...APPEND not implemented yet.");
1923         exit(-1);
1924     }
1925
1926     if (p_CreateFileW) {
1927        POOLMEM* pwszBuf = get_pool_memory(PM_FNAME);
1928        make_win32_path_UTF8_2_wchar(pwszBuf, file);
1929
1930        foo = p_CreateFileW((LPCWSTR) pwszBuf, access, shareMode, NULL, create, msflags, NULL);
1931        free_pool_memory(pwszBuf);
1932     } else if (p_CreateFileA)
1933        foo = CreateFile(file, access, shareMode, NULL, create, msflags, NULL);
1934
1935     if (INVALID_HANDLE_VALUE == foo) {
1936         errno = b_errno_win32;
1937         return(int) -1;
1938     }
1939     return (int)foo;
1940
1941 }
1942
1943
1944 int
1945 close(int fd)
1946 {
1947     if (!CloseHandle((HANDLE)fd)) {
1948         errno = b_errno_win32;
1949         return -1;
1950     }
1951
1952     return 0;
1953 }
1954
1955 ssize_t
1956 write(int fd, const void *data, ssize_t len)
1957 {
1958     BOOL status;
1959     DWORD bwrite;
1960     status = WriteFile((HANDLE)fd, data, len, &bwrite, NULL);
1961     if (status) return bwrite;
1962     errno = b_errno_win32;
1963     return -1;
1964 }
1965
1966
1967 ssize_t
1968 read(int fd, void *data, ssize_t len)
1969 {
1970     BOOL status;
1971     DWORD bread;
1972
1973     status = ReadFile((HANDLE)fd, data, len, &bread, NULL);
1974     if (status) return bread;
1975     errno = b_errno_win32;
1976     return -1;
1977 }
1978
1979 off_t
1980 lseek(int fd, off_t offset, int whence)
1981 {
1982     DWORD method = 0;
1983     DWORD val;
1984     switch (whence) {
1985     case SEEK_SET :
1986         method = FILE_BEGIN;
1987         break;
1988     case SEEK_CUR:
1989         method = FILE_CURRENT;
1990         break;
1991     case SEEK_END:
1992         method = FILE_END;
1993         break;
1994     default:
1995         errno = EINVAL;
1996         return -1;
1997     }
1998
1999     if ((val=SetFilePointer((HANDLE)fd, (DWORD)offset, NULL, method)) == INVALID_SET_FILE_POINTER) {
2000        errno = b_errno_win32;
2001        return -1;
2002     }
2003     /* ***FIXME*** I doubt this works right */
2004     return val;
2005 }
2006
2007 int
2008 dup2(int, int)
2009 {
2010     errno = ENOSYS;
2011     return -1;
2012 }
2013
2014
2015 #endif
2016
2017 #endif //HAVE_MINGW
2018
2019 #ifdef HAVE_MINGW
2020 /* syslog function, added by Nicolas Boichat */
2021 void closelog() {}
2022 void openlog(const char *ident, int option, int facility) {}  
2023 #endif //HAVE_MINGW
2024
2025 /* Temp kludges ***FIXME**** */
2026 #ifdef __APCUPSD__
2027 unsigned int alarm(unsigned int seconds) 
2028 {
2029    return 0;
2030 }
2031 #endif