X-Git-Url: http://git.shadowcat.co.uk/gitweb/gitweb.cgi?a=blobdiff_plain;f=win32%2Fwin32.c;h=54ce3990ab7f4b0683d1d59ede42b22dd734cc38;hb=26b3385cfa7a4193b7fdcd1e1e62a8894e9d9198;hp=28454e80c6f8d8b24185f006a1275b1edc27e50c;hpb=a868473fb9213692497e27ae968094b32a41c501;p=p5sagit%2Fp5-mst-13.2.git diff --git a/win32/win32.c b/win32/win32.c index 28454e8..54ce399 100644 --- a/win32/win32.c +++ b/win32/win32.c @@ -11,6 +11,9 @@ #define WIN32_LEAN_AND_MEAN #define WIN32IO_IS_STDIO #include +#ifdef __GNUC__ +#define Win32_Winsock +#endif #include /* #include "config.h" */ @@ -22,66 +25,144 @@ #include "EXTERN.h" #include "perl.h" + +#define NO_XSLOCKS +#ifdef PERL_OBJECT +extern CPerlObj* pPerl; +#endif #include "XSUB.h" + +#include "Win32iop.h" #include #include +#ifndef __GNUC__ +/* assert.h conflicts with #define of assert in perl.h */ #include +#endif #include #include #include +#include +#if defined(_MSC_VER) || defined(__MINGW32__) +#include +#else +#include +#endif + +#ifdef __GNUC__ +/* Mingw32 defaults to globing command line + * So we turn it off like this: + */ +int _CRT_glob = 0; +#endif #define EXECF_EXEC 1 #define EXECF_SPAWN 2 #define EXECF_SPAWN_NOWAIT 3 -static DWORD IdOS(void); +#if defined(PERL_OBJECT) +#undef win32_perllib_path +#define win32_perllib_path g_win32_perllib_path +#undef do_aspawn +#define do_aspawn g_do_aspawn +#undef do_spawn +#define do_spawn g_do_spawn +#undef do_exec +#define do_exec g_do_exec +#undef opendir +#define opendir g_opendir +#undef readdir +#define readdir g_readdir +#undef telldir +#define telldir g_telldir +#undef seekdir +#define seekdir g_seekdir +#undef rewinddir +#define rewinddir g_rewinddir +#undef closedir +#define closedir g_closedir +#undef getlogin +#define getlogin g_getlogin +#endif -BOOL ProbeEnv = FALSE; -DWORD Win32System = (DWORD)-1; -char szShellPath[MAX_PATH+1]; -char szPerlLibRoot[MAX_PATH+1]; -HANDLE PerlDllHandle = INVALID_HANDLE_VALUE; +static DWORD os_id(void); +static void get_shell(void); +static long tokenize(char *str, char **dest, char ***destv); + int do_spawn2(char *cmd, int exectype); +static BOOL has_redirection(char *ptr); +static long filetime_to_clock(PFILETIME ft); +static BOOL filetime_from_time(PFILETIME ft, time_t t); + + +char * w32_perlshell_tokens = Nullch; +char ** w32_perlshell_vec; +long w32_perlshell_items = -1; +DWORD w32_platform = (DWORD)-1; +char w32_perllib_root[MAX_PATH+1]; +HANDLE w32_perldll_handle = INVALID_HANDLE_VALUE; +#ifndef __BORLANDC__ +long w32_num_children = 0; +HANDLE w32_child_pids[MAXIMUM_WAIT_OBJECTS]; +#endif -static int do_spawn2(char *cmd, int exectype); +#ifdef USE_THREADS +# ifdef USE_DECLSPEC_THREAD +__declspec(thread) char strerror_buffer[512]; +__declspec(thread) char getlogin_buffer[128]; +# ifdef HAVE_DES_FCRYPT +__declspec(thread) char crypt_buffer[30]; +# endif +# else +# define strerror_buffer (thr->i.Wstrerror_buffer) +# define getlogin_buffer (thr->i.Wgetlogin_buffer) +# define crypt_buffer (thr->i.Wcrypt_buffer) +# endif +#else +char strerror_buffer[512]; +char getlogin_buffer[128]; +# ifdef HAVE_DES_FCRYPT +char crypt_buffer[30]; +# endif +#endif int IsWin95(void) { - return (IdOS() == VER_PLATFORM_WIN32_WINDOWS); + return (os_id() == VER_PLATFORM_WIN32_WINDOWS); } int IsWinNT(void) { - return (IdOS() == VER_PLATFORM_WIN32_NT); + return (os_id() == VER_PLATFORM_WIN32_NT); } char * -win32PerlLibPath(void) +win32_perllib_path(char *sfx,...) { + va_list ap; char *end; - GetModuleFileName((PerlDllHandle == INVALID_HANDLE_VALUE) + va_start(ap,sfx); + GetModuleFileName((w32_perldll_handle == INVALID_HANDLE_VALUE) ? GetModuleHandle(NULL) - : PerlDllHandle, - szPerlLibRoot, - sizeof(szPerlLibRoot)); - - *(end = strrchr(szPerlLibRoot, '\\')) = '\0'; + : w32_perldll_handle, + w32_perllib_root, + sizeof(w32_perllib_root)); + *(end = strrchr(w32_perllib_root, '\\')) = '\0'; if (stricmp(end-4,"\\bin") == 0) end -= 4; strcpy(end,"\\lib"); - return (szPerlLibRoot); + while (sfx) + { + strcat(end,"\\"); + strcat(end,sfx); + sfx = va_arg(ap,char *); + } + va_end(ap); + return (w32_perllib_root); } -char * -win32SiteLibPath(void) -{ - static char szPerlSiteLib[MAX_PATH+1]; - strcpy(szPerlSiteLib, win32PerlLibPath()); - strcat(szPerlSiteLib, "\\site"); - return (szPerlSiteLib); -} -BOOL -HasRedirection(char *ptr) +static BOOL +has_redirection(char *ptr) { int inquote = 0; char quote = '\0'; @@ -118,6 +199,7 @@ HasRedirection(char *ptr) return FALSE; } +#if !defined(PERL_OBJECT) /* since the current process environment is being updated in util.c * the library functions will get the correct environment */ @@ -152,26 +234,78 @@ my_pclose(PerlIO *fp) { return win32_pclose(fp); } +#endif static DWORD -IdOS(void) +os_id(void) { static OSVERSIONINFO osver; - if (osver.dwPlatformId != Win32System) { + if (osver.dwPlatformId != w32_platform) { memset(&osver, 0, sizeof(OSVERSIONINFO)); osver.dwOSVersionInfoSize = sizeof(OSVERSIONINFO); GetVersionEx(&osver); - Win32System = osver.dwPlatformId; + w32_platform = osver.dwPlatformId; + } + return (w32_platform); +} + +/* Tokenize a string. Words are null-separated, and the list + * ends with a doubled null. Any character (except null and + * including backslash) may be escaped by preceding it with a + * backslash (the backslash will be stripped). + * Returns number of words in result buffer. + */ +static long +tokenize(char *str, char **dest, char ***destv) +{ + char *retstart = Nullch; + char **retvstart = 0; + int items = -1; + if (str) { + int slen = strlen(str); + register char *ret; + register char **retv; + New(1307, ret, slen+2, char); + New(1308, retv, (slen+3)/2, char*); + + retstart = ret; + retvstart = retv; + *retv = ret; + items = 0; + while (*str) { + *ret = *str++; + if (*ret == '\\' && *str) + *ret = *str++; + else if (*ret == ' ') { + while (*str == ' ') + str++; + if (ret == retstart) + ret--; + else { + *ret = '\0'; + ++items; + if (*str) + *++retv = ret+1; + } + } + else if (!*str) + ++items; + ret++; + } + retvstart[items] = Nullch; + *ret++ = '\0'; + *ret = '\0'; } - return (Win32System); + *dest = retstart; + *destv = retvstart; + return items; } -static char * -GetShell(void) +static void +get_shell(void) { - if (!ProbeEnv) { - char* defaultshell = (IsWinNT() ? "cmd.exe" : "command.com"); + if (!w32_perlshell_tokens) { /* we don't use COMSPEC here for two reasons: * 1. the same reason perl on UNIX doesn't use SHELL--rampant and * uncontrolled unportability of the ensuing scripts. @@ -179,56 +313,72 @@ GetShell(void) * interactive use (which is what most programs look in COMSPEC * for). */ - char *usershell = getenv("PERL5SHELL"); - - ProbeEnv = TRUE; - strcpy(szShellPath, usershell ? usershell : defaultshell); + char* defaultshell = (IsWinNT() ? "cmd.exe /x/c" : "command.com /c"); + char *usershell = getenv("PERL5SHELL"); + w32_perlshell_items = tokenize(usershell ? usershell : defaultshell, + &w32_perlshell_tokens, + &w32_perlshell_vec); } - return szShellPath; } int -do_aspawn(void* really, void ** mark, void ** arglast) +do_aspawn(void *vreally, void **vmark, void **vsp) { + SV *really = (SV*)vreally; + SV **mark = (SV**)vmark; + SV **sp = (SV**)vsp; char **argv; - char *strPtr; - char *cmd; + char *str; int status; - unsigned int length; + int flag = P_WAIT; int index = 0; - SV *sv = (SV*)really; - SV** pSv = (SV**)mark; - New(1310, argv, (arglast - mark) + 4, char*); + if (sp <= mark) + return -1; - if(sv != Nullsv) { - cmd = SvPV(sv, length); - } - else { - argv[index++] = cmd = GetShell(); - if (IsWinNT()) - argv[index++] = "/x"; /* always enable command extensions */ - argv[index++] = "/c"; + get_shell(); + New(1306, argv, (sp - mark) + w32_perlshell_items + 2, char*); + + if (SvNIOKp(*(mark+1)) && !SvPOKp(*(mark+1))) { + ++mark; + flag = SvIVx(*mark); } - while(++pSv <= (SV**)arglast) { - sv = *pSv; - strPtr = SvPV(sv, length); - if(strPtr != NULL && *strPtr != '\0') - argv[index++] = strPtr; + while(++mark <= sp) { + if (*mark && (str = SvPV(*mark, na))) + argv[index++] = str; + else + argv[index++] = ""; } argv[index++] = 0; - status = win32_spawnvp(P_WAIT, cmd, (const char* const*)argv); - - Safefree(argv); + status = win32_spawnvp(flag, + (const char*)(really ? SvPV(really,na) : argv[0]), + (const char* const*)argv); + + if (status < 0 && errno == ENOEXEC) { + /* possible shell-builtin, invoke with shell */ + int sh_items; + sh_items = w32_perlshell_items; + while (--index >= 0) + argv[index+sh_items] = argv[index]; + while (--sh_items >= 0) + argv[sh_items] = w32_perlshell_vec[sh_items]; + + status = win32_spawnvp(flag, + (const char*)(really ? SvPV(really,na) : argv[0]), + (const char* const*)argv); + } if (status < 0) { if (dowarn) - warn("Can't spawn \"%s\": %s", cmd, strerror(errno)); - status = 255 << 8; + warn("Can't spawn \"%s\": %s", argv[0], strerror(errno)); + status = 255 * 256; } - return (status); + else if (flag != P_NOWAIT) + status *= 256; + Safefree(argv); + return (statusvalue = status); } int @@ -239,13 +389,11 @@ do_spawn2(char *cmd, int exectype) char **argv; int status = -1; BOOL needToTry = TRUE; - char *shell, *cmd2; + char *cmd2; - /* save an extra exec if possible */ - shell = GetShell(); - - /* see if there are shell metacharacters in it */ - if(!HasRedirection(cmd)) { + /* Save an extra exec if possible. See if there are shell + * metacharacters in it */ + if(!has_redirection(cmd)) { New(1301,argv, strlen(cmd) / 2 + 2, char*); New(1302,cmd2, strlen(cmd) + 1, char); strcpy(cmd2, cmd); @@ -261,7 +409,7 @@ do_spawn2(char *cmd, int exectype) *s++ = '\0'; } *a = Nullch; - if(argv[0]) { + if (argv[0]) { switch (exectype) { case EXECF_SPAWN: status = win32_spawnvp(P_WAIT, argv[0], @@ -275,19 +423,21 @@ do_spawn2(char *cmd, int exectype) status = win32_execvp(argv[0], (const char* const*)argv); break; } - if(status != -1 || errno == 0) + if (status != -1 || errno == 0) needToTry = FALSE; } Safefree(argv); Safefree(cmd2); } - if(needToTry) { - char *argv[5]; - int i = 0; - argv[i++] = shell; - if (IsWinNT()) - argv[i++] = "/x"; - argv[i++] = "/c"; argv[i++] = cmd; argv[i] = Nullch; + if (needToTry) { + char **argv; + int i = -1; + get_shell(); + New(1306, argv, w32_perlshell_items + 2, char*); + while (++i < w32_perlshell_items) + argv[i] = w32_perlshell_vec[i]; + argv[i++] = cmd; + argv[i] = Nullch; switch (exectype) { case EXECF_SPAWN: status = win32_spawnvp(P_WAIT, argv[0], @@ -301,16 +451,19 @@ do_spawn2(char *cmd, int exectype) status = win32_execvp(argv[0], (const char* const*)argv); break; } + cmd = argv[0]; + Safefree(argv); } if (status < 0) { if (dowarn) warn("Can't %s \"%s\": %s", (exectype == EXECF_EXEC ? "exec" : "spawn"), - needToTry ? shell : argv[0], - strerror(errno)); - status = 255 << 8; + cmd, strerror(errno)); + status = 255 * 256; } - return (status); + else if (exectype != EXECF_SPAWN_NOWAIT) + status *= 256; + return (statusvalue = status); } int @@ -319,6 +472,12 @@ do_spawn(char *cmd) return do_spawn2(cmd, EXECF_SPAWN); } +int +do_spawn_nowait(char *cmd) +{ + return do_spawn2(cmd, EXECF_SPAWN_NOWAIT); +} + bool do_exec(char *cmd) { @@ -352,7 +511,10 @@ opendir(char *filename) /* check to see if filename is a directory */ if (win32_stat(filename, &sbuf) < 0 || (sbuf.st_mode & S_IFDIR) == 0) { - return NULL; + /* CRT is buggy on sharenames, so make sure it really isn't */ + DWORD r = GetFileAttributes(filename); + if (r == 0xffffffff || !(r & FILE_ATTRIBUTE_DIRECTORY)) + return NULL; } /* get the file system characteristics */ @@ -532,20 +694,35 @@ getegid(void) } int -setuid(uid_t uid) +setuid(uid_t auid) { - return (uid == ROOT_UID ? 0 : -1); + return (auid == ROOT_UID ? 0 : -1); } int -setgid(gid_t gid) +setgid(gid_t agid) { - return (gid == ROOT_GID ? 0 : -1); + return (agid == ROOT_GID ? 0 : -1); +} + +char * +getlogin(void) +{ + dTHR; + char *buf = getlogin_buffer; + DWORD size = sizeof(getlogin_buffer); + if (GetUserName(buf,&size)) + return buf; + return (char*)NULL; +} + +int +chown(const char *path, uid_t owner, gid_t group) +{ + /* XXX noop */ + return 0; } -/* - * pretended kill - */ int kill(int pid, int sig) { @@ -561,20 +738,11 @@ kill(int pid, int sig) } return 0; } - + /* * File system stuff */ -#if 0 -int -ioctl(int i, unsigned int u, char *data) -{ - croak("ioctl not implemented!\n"); - return -1; -} -#endif - DllExport unsigned int win32_sleep(unsigned int t) { @@ -627,6 +795,51 @@ win32_stat(const char *path, struct stat *buffer) #ifndef USE_WIN32_RTL_ENV +BOOL GetRegStr(HKEY hkey, const char *lpszValueName, char *lpszDefault, char *lpszData, unsigned long *lpdwDataLen) +{ // Retrieve a REG_SZ or REG_EXPAND_SZ from the registry + HKEY handle; + DWORD type, dwDataLen = *lpdwDataLen; + const char *subkey = "Software\\Perl"; + char szBuffer[MAX_PATH+1]; + long retval; + + retval = RegOpenKeyEx(hkey, subkey, 0, KEY_READ, &handle); + if(retval == ERROR_SUCCESS) + { + retval = RegQueryValueEx(handle, lpszValueName, 0, &type, (LPBYTE)lpszData, &dwDataLen); + RegCloseKey(handle); + if(retval == ERROR_SUCCESS && (type == REG_SZ || type == REG_EXPAND_SZ)) + { + if(type != REG_EXPAND_SZ) + { + *lpdwDataLen = dwDataLen; + return TRUE; + } + strcpy(szBuffer, lpszData); + dwDataLen = ExpandEnvironmentStrings(szBuffer, lpszData, *lpdwDataLen); + if(dwDataLen < *lpdwDataLen) + { + *lpdwDataLen = dwDataLen; + return TRUE; + } + } + } + + strcpy(lpszData, lpszDefault); + return FALSE; +} + +char* GetRegStr(const char *lpszValueName, char *lpszDefault, char *lpszData, unsigned long *lpdwDataLen) +{ + if(!GetRegStr(HKEY_CURRENT_USER, lpszValueName, lpszDefault, lpszData, lpdwDataLen)) + { + GetRegStr(HKEY_LOCAL_MACHINE, lpszValueName, lpszDefault, lpszData, lpdwDataLen); + } + if(*lpszData == '\0') + lpszData = NULL; + return lpszData; +} + DllExport char * win32_getenv(const char *name) { @@ -642,13 +855,19 @@ win32_getenv(const char *name) curlen = needlen; needlen = GetEnvironmentVariable(name,curitem,curlen); } + if(curitem == NULL) + { + unsigned long dwDataLen = curlen; + if(strcmp("PERL5DB", name) == 0) + curitem = GetRegStr(name, "", curitem, &dwDataLen); + } return curitem; } #endif static long -FileTimeToClock(PFILETIME ft) +filetime_to_clock(PFILETIME ft) { __int64 qw = ft->dwHighDateTime; qw <<= 32; @@ -665,8 +884,8 @@ win32_times(struct tms *timebuf) FILETIME dummy; if (GetProcessTimes(GetCurrentProcess(), &dummy, &dummy, &kernel,&user)) { - timebuf->tms_utime = FileTimeToClock(&user); - timebuf->tms_stime = FileTimeToClock(&kernel); + timebuf->tms_utime = filetime_to_clock(&user); + timebuf->tms_stime = filetime_to_clock(&kernel); timebuf->tms_cutime = 0; timebuf->tms_cstime = 0; @@ -681,8 +900,115 @@ win32_times(struct tms *timebuf) return 0; } -static UINT timerid = 0; +/* fix utime() so it works on directories in NT + * thanks to Jan Dubois + */ +static BOOL +filetime_from_time(PFILETIME pFileTime, time_t Time) +{ + struct tm *pTM = gmtime(&Time); + SYSTEMTIME SystemTime; + + if (pTM == NULL) + return FALSE; + + SystemTime.wYear = pTM->tm_year + 1900; + SystemTime.wMonth = pTM->tm_mon + 1; + SystemTime.wDay = pTM->tm_mday; + SystemTime.wHour = pTM->tm_hour; + SystemTime.wMinute = pTM->tm_min; + SystemTime.wSecond = pTM->tm_sec; + SystemTime.wMilliseconds = 0; + + return SystemTimeToFileTime(&SystemTime, pFileTime); +} +DllExport int +win32_utime(const char *filename, struct utimbuf *times) +{ + HANDLE handle; + FILETIME ftCreate; + FILETIME ftAccess; + FILETIME ftWrite; + struct utimbuf TimeBuffer; + + int rc = utime(filename,times); + /* EACCES: path specifies directory or readonly file */ + if (rc == 0 || errno != EACCES /* || !IsWinNT() */) + return rc; + + if (times == NULL) { + times = &TimeBuffer; + time(×->actime); + times->modtime = times->actime; + } + + /* This will (and should) still fail on readonly files */ + handle = CreateFile(filename, GENERIC_READ | GENERIC_WRITE, + FILE_SHARE_READ | FILE_SHARE_DELETE, NULL, + OPEN_EXISTING, FILE_FLAG_BACKUP_SEMANTICS, NULL); + if (handle == INVALID_HANDLE_VALUE) + return rc; + + if (GetFileTime(handle, &ftCreate, &ftAccess, &ftWrite) && + filetime_from_time(&ftAccess, times->actime) && + filetime_from_time(&ftWrite, times->modtime) && + SetFileTime(handle, &ftCreate, &ftAccess, &ftWrite)) + { + rc = 0; + } + + CloseHandle(handle); + return rc; +} + +DllExport int +win32_wait(int *status) +{ +#ifdef __BORLANDC__ + return wait(status); +#else + /* XXX this wait emulation only knows about processes + * spawned via win32_spawnvp(P_NOWAIT, ...). + */ + int i, retval; + DWORD exitcode, waitcode; + + if (!w32_num_children) { + errno = ECHILD; + return -1; + } + + /* if a child exists, wait for it to die */ + waitcode = WaitForMultipleObjects(w32_num_children, + w32_child_pids, + FALSE, + INFINITE); + if (waitcode != WAIT_FAILED) { + if (waitcode >= WAIT_ABANDONED_0 + && waitcode < WAIT_ABANDONED_0 + w32_num_children) + i = waitcode - WAIT_ABANDONED_0; + else + i = waitcode - WAIT_OBJECT_0; + if (GetExitCodeProcess(w32_child_pids[i], &exitcode) ) { + CloseHandle(w32_child_pids[i]); + *status = (int)((exitcode & 0xff) << 8); + retval = (int)w32_child_pids[i]; + Copy(&w32_child_pids[i+1], &w32_child_pids[i], + (w32_num_children-i-1), HANDLE); + w32_num_children--; + return retval; + } + } + +FAILED: + errno = GetLastError(); + return -1; + +#endif +} + +static UINT timerid = 0; static VOID CALLBACK TimerProc(HWND win, UINT msg, UINT id, DWORD time) { @@ -720,6 +1046,17 @@ win32_alarm(unsigned int sec) return 0; } +#ifdef HAVE_DES_FCRYPT +extern char * des_fcrypt(char *cbuf, const char *txt, const char *salt); + +DllExport char * +win32_crypt(const char *txt, const char *salt) +{ + dTHR; + return des_fcrypt(crypt_buffer, txt, salt); +} +#endif + #ifdef USE_FIXED_OSFHANDLE EXTERN_C int __cdecl _alloc_osfhnd(void); @@ -923,8 +1260,6 @@ win32_feof(FILE *fp) * we have to roll our own. */ -__declspec(thread) char strerror_buffer[512]; - DllExport char * win32_strerror(int e) { @@ -934,6 +1269,7 @@ win32_strerror(int e) DWORD source = 0; if(e < 0 || e > sys_nerr) { + dTHR; if(e < 0) e = GetLastError(); @@ -946,6 +1282,33 @@ win32_strerror(int e) return strerror(e); } +DllExport void +win32_str_os_error(void *sv, DWORD dwErr) +{ + DWORD dwLen; + char *sMsg; + dwLen = FormatMessageA(FORMAT_MESSAGE_ALLOCATE_BUFFER + |FORMAT_MESSAGE_IGNORE_INSERTS + |FORMAT_MESSAGE_FROM_SYSTEM, NULL, + dwErr, 0, (char *)&sMsg, 1, NULL); + if (0 < dwLen) { + while (0 < dwLen && isspace(sMsg[--dwLen])) + ; + if ('.' != sMsg[dwLen]) + dwLen++; + sMsg[dwLen]= '\0'; + } + if (0 == dwLen) { + sMsg = (char*)LocalAlloc(0, 64/**sizeof(TCHAR)*/); + dwLen = sprintf(sMsg, + "Unknown error #0x%lX (lookup 0x%lX)", + dwErr, GetLastError()); + } + sv_setpvn((SV*)sv, sMsg, dwLen); + LocalFree(sMsg); +} + + DllExport int win32_fprintf(FILE *fp, const char *format, ...) { @@ -1109,9 +1472,9 @@ win32_abort(void) } DllExport int -win32_fstat(int fd,struct stat *bufptr) +win32_fstat(int fd,struct stat *sbufptr) { - return fstat(fd,bufptr); + return fstat(fd,sbufptr); } DllExport int @@ -1222,7 +1585,18 @@ win32_chdir(const char *dir) DllExport int win32_spawnvp(int mode, const char *cmdname, const char *const *argv) { - return spawnvp(mode, cmdname, (char * const *) argv); + int status; + + status = spawnvp(mode, cmdname, (char * const *) argv); +#ifndef __BORLANDC__ + /* XXX For the P_NOWAIT case, Borland RTL returns pinfo.dwProcessId + * while VC RTL returns pinfo.hProcess. For purposes of the custom + * implementation of win32_wait(), we assume the latter. + */ + if (mode == P_NOWAIT && status >= 0) + w32_child_pids[w32_num_children++] = (HANDLE)status; +#endif + return status; } DllExport int @@ -1338,7 +1712,7 @@ sbrk(int need) if (committed && reserved && committed < reserved) { /* Commit last of previous chunk cannot span allocations */ - addr = VirtualAlloc(committed,reserved-committed,MEM_COMMIT,PAGE_READWRITE); + addr = (char *) VirtualAlloc(committed,reserved-committed,MEM_COMMIT,PAGE_READWRITE); if (addr) committed = reserved; } @@ -1347,7 +1721,7 @@ sbrk(int need) * so lets system choose where we start, subsequent calls pass * the old end address so ask for a contiguous block */ - addr = VirtualAlloc(reserved,size,MEM_RESERVE,PAGE_NOACCESS); + addr = (char *) VirtualAlloc(reserved,size,MEM_RESERVE,PAGE_NOACCESS); if (addr) { reserved = addr+size; @@ -1368,7 +1742,7 @@ sbrk(int need) if (brk > committed) { DWORD size = ((brk-committed + pagesize -1)/pagesize) * pagesize; - char *addr = VirtualAlloc(committed,size,MEM_COMMIT,PAGE_READWRITE); + char *addr = (char *) VirtualAlloc(committed,size,MEM_COMMIT,PAGE_READWRITE); if (addr) { committed += size; @@ -1484,8 +1858,8 @@ static XS(w32_LoginName) { dXSARGS; - char name[256]; - DWORD size = sizeof(name); + char *name = getlogin_buffer; + DWORD size = sizeof(getlogin_buffer); if (GetUserName(name,&size)) { /* size includes NULL */ ST(0) = sv_2mortal(newSVpv(name,size-1)); @@ -1673,15 +2047,729 @@ XS(w32_GetShortPathName) XSRETURN(1); } +static +XS(w32_Sleep) +{ + dXSARGS; + if (items != 1) + croak("usage: Win32::Sleep($milliseconds)"); + Sleep(SvIV(ST(0))); + XSRETURN_YES; +} + +#define TMPBUFSZ 1024 +#define MAX_LENGTH 2048 +#define SUCCESSRETURNED(x) (x == ERROR_SUCCESS) +#define REGRETURN(x) XSRETURN_IV(SUCCESSRETURNED(x)) +#define SvHKEY(index) (HKEY)((unsigned long)SvIV(index)) +#define SETIV(index,value) sv_setiv(ST(index), value) +#define SETNV(index,value) sv_setnv(ST(index), value) +#define SETPV(index,string) sv_setpv(ST(index), string) +#define SETPVN(index, buffer, length) sv_setpvn(ST(index), (char*)buffer, length) +#define SETHKEY(index, hkey) SETIV(index,(long)hkey) + +static time_t ft2timet(FILETIME *ft) +{ + SYSTEMTIME st; + struct tm tm; + + FileTimeToSystemTime(ft, &st); + tm.tm_sec = st.wSecond; + tm.tm_min = st.wMinute; + tm.tm_hour = st.wHour; + tm.tm_mday = st.wDay; + tm.tm_mon = st.wMonth - 1; + tm.tm_year = st.wYear - 1900; + tm.tm_wday = st.wDayOfWeek; + tm.tm_yday = -1; + tm.tm_isdst = -1; + return mktime (&tm); +} + +static +XS(w32_RegCloseKey) +{ + dXSARGS; + + if(items != 1) + { + croak("usage: Win32::RegCloseKey($hkey);\n"); + } + + REGRETURN(RegCloseKey(SvHKEY(ST(0)))); +} + +static +XS(w32_RegConnectRegistry) +{ + dXSARGS; + HKEY handle; + + if(items != 3) + { + croak("usage: Win32::RegConnectRegistry($machine, $hkey, $handle);\n"); + } + + if(SUCCESSRETURNED(RegConnectRegistry((char *)SvPV(ST(0), na), SvHKEY(ST(1)), &handle))) + { + SETHKEY(2,handle); + XSRETURN_YES; + } + XSRETURN_NO; +} + +static +XS(w32_RegCreateKey) +{ + dXSARGS; + HKEY handle; + DWORD disposition; + long retval; + + if(items != 3) + { + croak("usage: Win32::RegCreateKey($hkey, $subkey, $handle);\n"); + } + + retval = RegCreateKeyEx(SvHKEY(ST(0)), (char *)SvPV(ST(1), na), 0, NULL, REG_OPTION_NON_VOLATILE, KEY_ALL_ACCESS, + NULL, &handle, &disposition); + + if(SUCCESSRETURNED(retval)) + { + SETHKEY(2,handle); + XSRETURN_YES; + } + XSRETURN_NO; +} + +static +XS(w32_RegCreateKeyEx) +{ + dXSARGS; + + unsigned int length; + long retval; + HKEY hkey, handle; + char *subkey; + char *keyclass; + DWORD options, disposition; + REGSAM sam; + SECURITY_ATTRIBUTES sa, *psa; + + if(items != 9) + { + croak("usage: Win32::RegCreateKeyEx($hkey, $subkey, $reserved, $class, $options, $sam, " + "$security, $handle, $disposition);\n"); + } + + hkey = SvHKEY(ST(0)); + subkey = (char *)SvPV(ST(1), na); + keyclass = (char *)SvPV(ST(3), na); + options = (DWORD) ((unsigned long)SvIV(ST(4))); + sam = (REGSAM) ((unsigned long)SvIV(ST(5))); + psa = (SECURITY_ATTRIBUTES*)SvPV(ST(6), length); + if(length != sizeof(SECURITY_ATTRIBUTES)) + { + psa = &sa; + memset(&sa, 0, sizeof(SECURITY_ATTRIBUTES)); + sa.nLength = sizeof(SECURITY_ATTRIBUTES); + } + + retval = RegCreateKeyEx(hkey, subkey, 0, keyclass, options, sam, + psa, &handle, &disposition); + + if(SUCCESSRETURNED(retval)) + { + if(psa == &sa) + SETPVN(6, &sa, sizeof(sa)); + + SETHKEY(7,handle); + SETIV(8,disposition); + XSRETURN_YES; + } + XSRETURN_NO; +} + +static +XS(w32_RegDeleteKey) +{ + dXSARGS; + + if(items != 2) + { + croak("usage: Win32::RegDeleteKey($hkey, $subkey);\n"); + } + + REGRETURN(RegDeleteKey(SvHKEY(ST(0)), (char *)SvPV(ST(1), na))); +} + +static +XS(w32_RegDeleteValue) +{ + dXSARGS; + + if(items != 2) + { + croak("usage: Win32::RegDeleteValue($hkey, $valname);\n"); + } + + REGRETURN(RegDeleteValue(SvHKEY(ST(0)), (char *)SvPV(ST(1), na))); +} + +static +XS(w32_RegEnumKey) +{ + dXSARGS; + + char keybuffer[TMPBUFSZ]; + + if(items != 3) + { + croak("usage: Win32::RegEnumKey($hkey, $idx, $subkeyname);\n"); + } + + if(SUCCESSRETURNED(RegEnumKey(SvHKEY(ST(0)), SvIV(ST(1)), keybuffer, sizeof(keybuffer)))) + { + SETPV(2, keybuffer); + XSRETURN_YES; + } + XSRETURN_NO; +} + +static +XS(w32_RegEnumKeyEx) +{ + dXSARGS; + int length; + + DWORD keysz, classsz; + char keybuffer[TMPBUFSZ]; + char classbuffer[TMPBUFSZ]; + long retval; + FILETIME filetime; + + if(items != 6) + { + croak("usage: Win32::RegEnumKeyEx($hkey, $idx, $subkeyname, $reserved, $class, $time);\n"); + } + + keysz = sizeof(keybuffer); + classsz = sizeof(classbuffer); + retval = RegEnumKeyEx(SvHKEY(ST(0)), SvIV(ST(1)), keybuffer, &keysz, 0, + classbuffer, &classsz, &filetime); + if(SUCCESSRETURNED(retval)) + { + SETPV(2, keybuffer); + SETPV(4, classbuffer); + SETIV(5, ft2timet(&filetime)); + XSRETURN_YES; + } + XSRETURN_NO; +} + +static +XS(w32_RegEnumValue) +{ + dXSARGS; + HKEY hkey; + DWORD type, namesz, valsz; + long retval; + static HKEY last_hkey; + char myvalbuf[MAX_LENGTH]; + char mynambuf[MAX_LENGTH]; + + if(items != 6) + { + croak("usage: Win32::RegEnumValue($hkey, $i, $name, $reserved, $type, $value);\n"); + } + + hkey = SvHKEY(ST(0)); + + // If this is a new key, find out how big the maximum name and value sizes are and + // allocate space for them. Free any old storage and set the old key value to the + // current key. + + if(hkey != (HKEY)last_hkey) + { + char keyclass[TMPBUFSZ]; + DWORD classsz, subkeys, maxsubkey, maxclass, values, salen, maxnamesz, maxvalsz; + FILETIME ft; + classsz = sizeof(keyclass); + retval = RegQueryInfoKey(hkey, keyclass, &classsz, 0, &subkeys, &maxsubkey, &maxclass, + &values, &maxnamesz, &maxvalsz, &salen, &ft); + + if(!SUCCESSRETURNED(retval)) + { + XSRETURN_NO; + } + memset(myvalbuf, 0, MAX_LENGTH); + memset(mynambuf, 0, MAX_LENGTH); + last_hkey = hkey; + } + + namesz = MAX_LENGTH; + valsz = MAX_LENGTH; + retval = RegEnumValue(hkey, SvIV(ST(1)), mynambuf, &namesz, 0, &type, (LPBYTE) myvalbuf, &valsz); + if(!SUCCESSRETURNED(retval)) + { + XSRETURN_NO; + } + else + { + SETPV(2, mynambuf); + SETIV(4, type); + + // return includes the null terminator so delete it if REG_SZ, REG_MULTI_SZ or REG_EXPAND_SZ + switch(type) + { + case REG_SZ: + case REG_MULTI_SZ: + case REG_EXPAND_SZ: + if(valsz) + --valsz; + case REG_BINARY: + SETPVN(5, myvalbuf, valsz); + break; + + case REG_DWORD_BIG_ENDIAN: + { + BYTE tmp = myvalbuf[0]; + myvalbuf[0] = myvalbuf[3]; + myvalbuf[3] = tmp; + tmp = myvalbuf[1]; + myvalbuf[1] = myvalbuf[2]; + myvalbuf[2] = tmp; + } + case REG_DWORD_LITTLE_ENDIAN: // same as REG_DWORD + SETNV(5, (double)*((DWORD*)myvalbuf)); + break; + + default: + break; + } + + XSRETURN_YES; + } +} + +static +XS(w32_RegFlushKey) +{ + dXSARGS; + + if(items != 1) + { + croak("usage: Win32::RegFlushKey($hkey);\n"); + } + + REGRETURN(RegFlushKey(SvHKEY(ST(0)))); +} + +static +XS(w32_RegGetKeySecurity) +{ + dXSARGS; + SECURITY_DESCRIPTOR sd; + DWORD sdsz; + + if(items != 3) + { + croak("usage: Win32::RegGetKeySecurity($hkey, $security_info, $security_descriptor);\n"); + } + + if(SUCCESSRETURNED(RegGetKeySecurity(SvHKEY(ST(0)), SvIV(ST(1)), &sd, &sdsz))) + { + SETPVN(2, &sd, sdsz); + XSRETURN_YES; + } + XSRETURN_NO; +} + +static +XS(w32_RegLoadKey) +{ + dXSARGS; + + if(items != 3) + { + croak("usage: Win32::RegLoadKey($hkey, $subkey, $filename);\n"); + } + + REGRETURN(RegLoadKey(SvHKEY(ST(0)), (char *)SvPV(ST(1), na), (char *)SvPV(ST(2), na))); +} + +static +XS(w32_RegNotifyChangeKeyValue) +{ + croak("Win32::RegNotifyChangeKeyValue not yet implemented!\n"); +} + +static +XS(w32_RegOpenKey) +{ + dXSARGS; + HKEY handle; + + if(items != 3) + { + croak("usage: Win32::RegOpenKey($hkey, $subkey, $handle);\n"); + } + + if(SUCCESSRETURNED(RegOpenKey(SvHKEY(ST(0)), (char *)SvPV(ST(1), na), &handle))) + { + SETHKEY(2,handle); + XSRETURN_YES; + } + XSRETURN_NO; +} + +static +XS(w32_RegOpenKeyEx) +{ + dXSARGS; + HKEY handle; + + if(items != 5) + { + croak("usage: Win32::RegOpenKeyEx($hkey, $subkey, $reserved, $sam, $handle);\n"); + } + + if(SUCCESSRETURNED(RegOpenKeyEx(SvHKEY(ST(0)), (char *)SvPV(ST(1), na), + 0, (REGSAM) ((unsigned long)SvIV(ST(3))), &handle))) + { + SETHKEY(4,handle); + XSRETURN_YES; + } + XSRETURN_NO; +} + +#pragma optimize("", off) +static +XS(w32_RegQueryInfoKey) +{ + dXSARGS; + int length; + + char keyclass[TMPBUFSZ]; + DWORD subkeys, maxsubkey, maxclass, values, maxvalname, maxvaldata; + DWORD seclen, classsz; + FILETIME ft; + long retval; + + if(items != 10) + { + croak("usage: Win32::RegQueryInfoKey($hkey, $class, $numsubkeys, $maxsubkey," + "$maxclass, $values, $maxvalname, $maxvaldata, $secdesclen," + "$lastwritetime);\n"); + } + + classsz = sizeof(keyclass); + retval = RegQueryInfoKey(SvHKEY(ST(0)), keyclass, &classsz, 0, &subkeys, &maxsubkey, + &maxclass, &values, &maxvalname, &maxvaldata, + &seclen, &ft); + if(SUCCESSRETURNED(retval)) + { + SETPV(1, keyclass); + SETIV(2, subkeys); + SETIV(3, maxsubkey); + SETIV(4, maxclass); + SETIV(5, values); + SETIV(6, maxvalname); + SETIV(7, maxvaldata); + SETIV(8, seclen); + SETIV(9, ft2timet(&ft)); + XSRETURN_YES; + } + XSRETURN_NO; +} +#pragma optimize("", on) + +static +XS(w32_RegQueryValue) +{ + dXSARGS; + + unsigned char databuffer[TMPBUFSZ*2]; + long datasz = sizeof(databuffer); + + if(items != 3) + { + croak("usage: Win32::RegQueryValue($hkey, $valuename, $data);\n"); + } + + if(SUCCESSRETURNED(RegQueryValue(SvHKEY(ST(0)), SvPV(ST(1), na), (char*)databuffer, &datasz))) + { + // return includes the null terminator so delete it + SETPVN(2, databuffer, --datasz); + XSRETURN_YES; + } + XSRETURN_NO; +} + +static +XS(w32_RegQueryValueEx) +{ + dXSARGS; + + unsigned char databuffer[TMPBUFSZ*2]; + DWORD datasz = sizeof(databuffer); + DWORD type; + LONG result; + LPBYTE ptr = databuffer; + + if(items != 5) + { + croak("usage: Win32::RegQueryValueEx($hkey, $valuename, $reserved, $type, $data);\n"); + } + + result = RegQueryValueEx(SvHKEY(ST(0)), SvPV(ST(1), na), 0, &type, ptr, &datasz); + if(result == ERROR_MORE_DATA) + { + New(0, ptr, datasz+1, BYTE); + result = RegQueryValueEx(SvHKEY(ST(0)), SvPV(ST(1), na), 0, &type, ptr, &datasz); + } + if(SUCCESSRETURNED(result)) + { + SETIV(3, type); + + // return includes the null terminator so delete it if REG_SZ, REG_MULTI_SZ or REG_EXPAND_SZ + switch(type) + { + case REG_SZ: + case REG_MULTI_SZ: + case REG_EXPAND_SZ: + --datasz; + case REG_BINARY: + SETPVN(4, ptr, datasz); + break; + + case REG_DWORD_BIG_ENDIAN: + { + BYTE tmp = ptr[0]; + ptr[0] = ptr[3]; + ptr[3] = tmp; + tmp = ptr[1]; + ptr[1] = ptr[2]; + ptr[2] = tmp; + } + case REG_DWORD_LITTLE_ENDIAN: // same as REG_DWORD + SETNV(4, (double)*((DWORD*)ptr)); + break; + + default: + break; + } + + if(ptr != databuffer) + safefree(ptr); + + XSRETURN_YES; + } + if(ptr != databuffer) + safefree(ptr); + + XSRETURN_NO; +} + +static +XS(w32_RegReplaceKey) +{ + dXSARGS; + + if(items != 4) + { + croak("usage: Win32::RegReplaceKey($hkey, $subkey, $newfile, $oldfile);\n"); + } + + REGRETURN(RegReplaceKey(SvHKEY(ST(0)), (char *)SvPV(ST(1), na), (char *)SvPV(ST(2), na), (char *)SvPV(ST(3), na))); +} + +static +XS(w32_RegRestoreKey) +{ + dXSARGS; + + if(items < 2 || items > 3) + { + croak("usage: Win32::RegRestoreKey($hkey, $filename [, $flags]);\n"); + } + + REGRETURN(RegRestoreKey(SvHKEY(ST(0)), (char*)SvPV(ST(1), na), (DWORD)((items == 3) ? SvIV(ST(2)) : 0))); +} + +static +XS(w32_RegSaveKey) +{ + dXSARGS; + + if(items != 2) + { + croak("usage: Win32::RegSaveKey($hkey, $filename);\n"); + } + + REGRETURN(RegSaveKey(SvHKEY(ST(0)), (char *)SvPV(ST(1), na), NULL)); +} + +static +XS(w32_RegSetKeySecurity) +{ + dXSARGS; + + if(items != 3) + { + croak("usage: Win32::RegSetKeySecurity($hkey, $security_info, $security_descriptor);\n"); + } + + REGRETURN(RegSetKeySecurity(SvHKEY(ST(0)), SvIV(ST(1)), (SECURITY_DESCRIPTOR*)SvPV(ST(2), na))); +} + +static +XS(w32_RegSetValue) +{ + dXSARGS; + + unsigned int size; + char *buffer; + + if(items != 4) + { + croak("usage: Win32::RegSetValue($hkey, $subKey, $type, $data);\n"); + } + + DWORD type = SvIV(ST(2)); + if(type != REG_SZ && type != REG_EXPAND_SZ) + { + croak("Win32::RegSetValue: Type was not REG_SZ or REG_EXPAND_SZ, cannot set %s\n", (char *)SvPV(ST(1), na)); + } + + buffer = (char *)SvPV(ST(3), size); + REGRETURN(RegSetValue(SvHKEY(ST(0)), SvPV(ST(1), na), REG_SZ, buffer, size)); +} + +static +XS(w32_RegSetValueEx) +{ + dXSARGS; + + DWORD type; + DWORD val; + unsigned int size; + char *buffer; + + if(items != 5) + { + croak("usage: Win32::RegSetValueEx($hkey, $valname, $reserved, $type, $data);\n"); + } + + type = (DWORD)SvIV(ST(3)); + switch(type) + { + case REG_SZ: + case REG_BINARY: + case REG_MULTI_SZ: + case REG_EXPAND_SZ: + buffer = (char *)SvPV(ST(4), size); + if(type != REG_BINARY) + size++; // include null terminator in size + + REGRETURN(RegSetValueEx(SvHKEY(ST(0)), (char *)SvPV(ST(1), na), 0, type, (PBYTE) buffer, size)); + break; + + case REG_DWORD_BIG_ENDIAN: + case REG_DWORD_LITTLE_ENDIAN: // Same as REG_DWORD + val = (DWORD)SvIV(ST(4)); + REGRETURN(RegSetValueEx(SvHKEY(ST(0)), (char *)SvPV(ST(1), na), 0, type, (PBYTE) &val, sizeof(DWORD))); + break; + + default: + croak("Win32::RegSetValueEx: Type not specified, cannot set %s\n", (char *)SvPV(ST(1), na)); + } +} + +static +XS(w32_RegUnloadKey) +{ + dXSARGS; + + if(items != 2) + { + croak("usage: Win32::RegUnLoadKey($hkey, $subkey);\n"); + } + + REGRETURN(RegUnLoadKey(SvHKEY(ST(0)), (char *)SvPV(ST(1), na))); +} + +static +XS(w32_RegisterServer) +{ + dXSARGS; + BOOL bSuccess = FALSE; + HINSTANCE hInstance; + unsigned int length; + FARPROC sFunc; + + if(items != 1) + { + croak("usage: Win32::RegisterServer($LibraryName)\n"); + } + + hInstance = LoadLibrary((char *)SvPV(ST(0), length)); + if(hInstance != NULL) + { + sFunc = GetProcAddress(hInstance, "DllRegisterServer"); + if(sFunc != NULL) + { + bSuccess = (sFunc() == 0); + } + FreeLibrary(hInstance); + } + + if(bSuccess) + { + XSRETURN_YES; + } + XSRETURN_NO; +} + +static +XS(w32_UnregisterServer) +{ + dXSARGS; + BOOL bSuccess = FALSE; + HINSTANCE hInstance; + unsigned int length; + FARPROC sFunc; + + if(items != 1) + { + croak("usage: Win32::UnregisterServer($LibraryName)\n"); + } + + hInstance = LoadLibrary((char *)SvPV(ST(0), length)); + if(hInstance != NULL) + { + sFunc = GetProcAddress(hInstance, "DllUnregisterServer"); + if(sFunc != NULL) + { + bSuccess = (sFunc() == 0); + } + FreeLibrary(hInstance); + } + + if(bSuccess) + { + XSRETURN_YES; + } + XSRETURN_NO; +} + + void Perl_init_os_extras() { char *file = __FILE__; dXSUB_SYS; - /* XXX should be removed after checking with Nick */ - newXS("Win32::GetCurrentDirectory", w32_GetCwd, file); - /* these names are Activeware compatible */ newXS("Win32::GetCwd", w32_GetCwd, file); newXS("Win32::SetCwd", w32_SetCwd, file); @@ -1698,6 +2786,41 @@ Perl_init_os_extras() newXS("Win32::Spawn", w32_Spawn, file); newXS("Win32::GetTickCount", w32_GetTickCount, file); newXS("Win32::GetShortPathName", w32_GetShortPathName, file); + newXS("Win32::Sleep", w32_Sleep, file); + + /* the following extensions are used interally and may be changed at any time */ + /* therefore no documentation is provided */ + newXS("Win32::RegCloseKey", w32_RegCloseKey, file); + newXS("Win32::RegConnectRegistry", w32_RegConnectRegistry, file); + newXS("Win32::RegCreateKey", w32_RegCreateKey, file); + newXS("Win32::RegCreateKeyEx", w32_RegCreateKeyEx, file); + newXS("Win32::RegDeleteKey", w32_RegDeleteKey, file); + newXS("Win32::RegDeleteValue", w32_RegDeleteValue, file); + + newXS("Win32::RegEnumKey", w32_RegEnumKey, file); + newXS("Win32::RegEnumKeyEx", w32_RegEnumKeyEx, file); + newXS("Win32::RegEnumValue", w32_RegEnumValue, file); + + newXS("Win32::RegFlushKey", w32_RegFlushKey, file); + newXS("Win32::RegGetKeySecurity", w32_RegGetKeySecurity, file); + + newXS("Win32::RegLoadKey", w32_RegLoadKey, file); + newXS("Win32::RegOpenKey", w32_RegOpenKey, file); + newXS("Win32::RegOpenKeyEx", w32_RegOpenKeyEx, file); + newXS("Win32::RegQueryInfoKey", w32_RegQueryInfoKey, file); + newXS("Win32::RegQueryValue", w32_RegQueryValue, file); + newXS("Win32::RegQueryValueEx", w32_RegQueryValueEx, file); + + newXS("Win32::RegReplaceKey", w32_RegReplaceKey, file); + newXS("Win32::RegRestoreKey", w32_RegRestoreKey, file); + newXS("Win32::RegSaveKey", w32_RegSaveKey, file); + newXS("Win32::RegSetKeySecurity", w32_RegSetKeySecurity, file); + newXS("Win32::RegSetValue", w32_RegSetValue, file); + newXS("Win32::RegSetValueEx", w32_RegSetValueEx, file); + newXS("Win32::RegUnloadKey", w32_RegUnloadKey, file); + + newXS("Win32::RegisterServer", w32_RegisterServer, file); + newXS("Win32::UnregisterServer", w32_UnregisterServer, file); /* XXX Bloat Alert! The following Activeware preloads really * ought to be part of Win32::Sys::*, so they're not included @@ -1720,9 +2843,10 @@ Perl_win32_init(int *argcp, char ***argvp) * want to be at the vendor's whim on the default, we set * it explicitly here. */ -#if !defined(_ALPHA_) +#if !defined(_ALPHA_) && !defined(__GNUC__) _control87(MCW_EM, MCW_EM); #endif + MALLOC_INIT; } #ifdef USE_BINMODE_SCRIPTS @@ -1749,8 +2873,3 @@ win32_strip_return(SV *sv) } #endif - - - - -