avoid negative return value from Win32::GetTickCount()
[p5sagit/p5-mst-13.2.git] / win32 / win32.c
index 8497b67..1848e9b 100644 (file)
@@ -91,11 +91,13 @@ 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 BOOL            has_shell_metachars(char *ptr);
 static long            filetime_to_clock(PFILETIME ft);
 static BOOL            filetime_from_time(PFILETIME ft, time_t t);
 static char *          get_emd_part(char *leading, char *trailing, ...);
-static void            remove_dead_process(HANDLE deceased);
+static void            remove_dead_process(long deceased);
+static long            find_pid(int pid);
+static char *          qualified_path(const char *cmd);
 
 HANDLE w32_perldll_handle = INVALID_HANDLE_VALUE;
 static DWORD   w32_platform = (DWORD)-1;
@@ -176,6 +178,7 @@ GetRegStr(const char *lpszValueName, char** ptr, DWORD* lpDataLen)
 static char *
 get_emd_part(char *prev_path, char *trailing_path, ...)
 {
+    char base[10];
     va_list ap;
     char mod_name[MAX_PATH+1];
     char *ptr;
@@ -186,9 +189,12 @@ get_emd_part(char *prev_path, char *trailing_path, ...)
     va_start(ap, trailing_path);
     strip = va_arg(ap, char *);
 
-    GetModuleFileName((w32_perldll_handle == INVALID_HANDLE_VALUE)
-                     ? GetModuleHandle(NULL)
-                     : w32_perldll_handle, mod_name, sizeof(mod_name));
+    sprintf(base, "%5.3f",
+           (double)PERL_REVISION + ((double)PERL_VERSION / (double)1000));
+
+    GetModuleFileName((HMODULE)((w32_perldll_handle == INVALID_HANDLE_VALUE)
+                               ? GetModuleHandle(NULL) : w32_perldll_handle),
+                     mod_name, sizeof(mod_name));
     ptr = strrchr(mod_name, '\\');
     while (ptr && strip) {
         /* look for directories to skip back */
@@ -196,8 +202,11 @@ get_emd_part(char *prev_path, char *trailing_path, ...)
        *ptr = '\0';
        ptr = strrchr(mod_name, '\\');
        if (!ptr || stricmp(ptr+1, strip) != 0) {
-           *optr = '\\';
-           ptr = optr;
+           if(!(*strip == '5' && *(ptr+1) == '5' && strncmp(strip, base, 5) == 0
+                   && strncmp(ptr+1, base, 5) == 0)) {
+               *optr = '\\';
+               ptr = optr;
+           }
        }
        strip = va_arg(ap, char *);
     }
@@ -209,17 +218,21 @@ get_emd_part(char *prev_path, char *trailing_path, ...)
     va_end(ap);
     strcpy(++ptr, trailing_path);
 
-    newsize = strlen(mod_name) + 1;
-    if (prev_path) {
-       oldsize = strlen(prev_path) + 1;
-       newsize += oldsize;                     /* includes plus 1 for ';' */
-       Renew(prev_path, newsize, char);
-       prev_path[oldsize-1] = ';';
-       strcpy(&prev_path[oldsize], mod_name);
-    }
-    else {
-       New(1311, prev_path, newsize, char);
-       strcpy(prev_path, mod_name);
+    /* only add directory if it exists */
+    if(GetFileAttributes(mod_name) != (DWORD) -1) {
+       /* directory exists */
+       newsize = strlen(mod_name) + 1;
+       if (prev_path) {
+           oldsize = strlen(prev_path) + 1;
+           newsize += oldsize;                 /* includes plus 1 for ';' */
+           Renew(prev_path, newsize, char);
+           prev_path[oldsize-1] = ';';
+           strcpy(&prev_path[oldsize], mod_name);
+       }
+       else {
+           New(1311, prev_path, newsize, char);
+           strcpy(prev_path, mod_name);
+       }
     }
 
     return prev_path;
@@ -289,17 +302,20 @@ win32_get_sitelib(char *pl)
 
 
 static BOOL
-has_redirection(char *ptr)
+has_shell_metachars(char *ptr)
 {
     int inquote = 0;
     char quote = '\0';
 
     /*
      * Scan string looking for redirection (< or >) or pipe
-     * characters (|) that are not in a quoted string
+     * characters (|) that are not in a quoted string.
+     * Shell variable interpolation (%VAR%) can also happen inside strings.
      */
     while (*ptr) {
        switch(*ptr) {
+       case '%':
+           return TRUE;
        case '\'':
        case '\"':
            if (inquote) {
@@ -470,7 +486,7 @@ do_aspawn(void *vreally, void **vmark, void **vsp)
     }
 
     while (++mark <= sp) {
-       if (*mark && (str = SvPV(*mark, PL_na)))
+       if (*mark && (str = SvPV_nolen(*mark)))
            argv[index++] = str;
        else
            argv[index++] = "";
@@ -478,10 +494,10 @@ do_aspawn(void *vreally, void **vmark, void **vsp)
     argv[index++] = 0;
    
     status = win32_spawnvp(flag,
-                          (const char*)(really ? SvPV(really,PL_na) : argv[0]),
+                          (const char*)(really ? SvPV_nolen(really) : argv[0]),
                           (const char* const*)argv);
 
-    if (status < 0 && errno == ENOEXEC) {
+    if (status < 0 && (errno == ENOEXEC || errno == ENOENT)) {
        /* possible shell-builtin, invoke with shell */
        int sh_items;
        sh_items = w32_perlshell_items;
@@ -491,7 +507,7 @@ do_aspawn(void *vreally, void **vmark, void **vsp)
            argv[sh_items] = w32_perlshell_vec[sh_items];
    
        status = win32_spawnvp(flag,
-                              (const char*)(really ? SvPV(really,PL_na) : argv[0]),
+                              (const char*)(really ? SvPV_nolen(really) : argv[0]),
                               (const char* const*)argv);
     }
 
@@ -521,7 +537,7 @@ do_spawn2(char *cmd, int exectype)
 
     /* Save an extra exec if possible. See if there are shell
      * metacharacters in it */
-    if (!has_redirection(cmd)) {
+    if (!has_shell_metachars(cmd)) {
        New(1301,argv, strlen(cmd) / 2 + 2, char*);
        New(1302,cmd2, strlen(cmd) + 1, char);
        strcpy(cmd2, cmd);
@@ -654,6 +670,10 @@ win32_opendir(char *filename)
     /* do the FindFirstFile call */
     fh = FindFirstFile(scanname, &FindData);
     if (fh == INVALID_HANDLE_VALUE) {
+       /* FindFirstFile() fails on empty drives! */
+       if (GetLastError() == ERROR_FILE_NOT_FOUND)
+           return p;
+       Safefree( p);
        return NULL;
     }
 
@@ -823,42 +843,40 @@ chown(const char *path, uid_t owner, gid_t group)
     return 0;
 }
 
-static void
-remove_dead_process(HANDLE deceased)
+static long
+find_pid(int pid)
 {
-#ifndef USE_RTL_WAIT
-    int child;
+    long child;
     for (child = 0 ; child < w32_num_children ; ++child) {
-       if (w32_child_pids[child] == deceased) {
-           Copy(&w32_child_pids[child+1], &w32_child_pids[child],
-                (w32_num_children-child-1), HANDLE);
-           w32_num_children--;
-           break;
-       }
+       if (w32_child_pids[child] == pid)
+           return child;
+    }
+    return -1;
+}
+
+static void
+remove_dead_process(long child)
+{
+    if (child >= 0) {
+       CloseHandle(w32_child_handles[child]);
+       Copy(&w32_child_handles[child+1], &w32_child_handles[child],
+            (w32_num_children-child-1), HANDLE);
+       Copy(&w32_child_pids[child+1], &w32_child_pids[child],
+            (w32_num_children-child-1), DWORD);
+       w32_num_children--;
     }
-#endif
 }
 
 DllExport int
 win32_kill(int pid, int sig)
 {
-#ifdef USE_RTL_WAIT
-    HANDLE hProcess= OpenProcess(PROCESS_ALL_ACCESS, TRUE, pid);
-#else
-    HANDLE hProcess = (HANDLE) pid;
-#endif
-
-    if (hProcess == NULL) {
-       croak("kill process failed!\n");
-    }
-    else {
-       if (!TerminateProcess(hProcess, sig))
-           croak("kill process failed!\n");
+    HANDLE hProcess;
+    hProcess = OpenProcess(PROCESS_ALL_ACCESS, TRUE, pid);
+    if (hProcess && TerminateProcess(hProcess, sig))
        CloseHandle(hProcess);
-
-       /* WaitForMultipleObjects() on a pid that was killed returns error
-        * so if we know the pid is gone we remove it from process list */
-       remove_dead_process(hProcess);
+    else {
+       errno = EINVAL;
+       return -1;
     }
     return 0;
 }
@@ -878,38 +896,57 @@ DllExport int
 win32_stat(const char *path, struct stat *buffer)
 {
     char       t[MAX_PATH+1]; 
-    const char *p = path;
     int                l = strlen(path);
     int                res;
 
     if (l > 1) {
        switch(path[l - 1]) {
+       /* FindFirstFile() and stat() are buggy with a trailing
+        * backslash, so change it to a forward slash :-( */
        case '\\':
-       case '/':
-           if (path[l - 2] != ':') {
-               strncpy(t, path, l - 1);
-               t[l - 1] = 0;
-               p = t;
-           };
+           strncpy(t, path, l-1);
+           t[l - 1] = '/';
+           t[l] = '\0';
+           path = t;
+           break;
+       /* FindFirstFile() is buggy with "x:", so add a slash :-( */
+       case ':':
+           if (l == 2 && isALPHA(path[0])) {
+               t[0] = path[0]; t[1] = ':'; t[2] = '/'; t[3] = '\0';
+               l = 3;
+               path = t;
+           }
+           break;
        }
     }
-    res = stat(p,buffer);
+    res = stat(path,buffer);
     if (res < 0) {
        /* CRT is buggy on sharenames, so make sure it really isn't.
         * XXX using GetFileAttributesEx() will enable us to set
         * buffer->st_*time (but note that's not available on the
         * Windows of 1995) */
-       DWORD r = GetFileAttributes(p);
+       DWORD r = GetFileAttributes(path);
        if (r != 0xffffffff && (r & FILE_ATTRIBUTE_DIRECTORY)) {
-           buffer->st_mode |= S_IFDIR | S_IREAD;
+           /* buffer may still contain old garbage since stat() failed */
+           Zero(buffer, 1, struct stat);
+           buffer->st_mode = S_IFDIR | S_IREAD;
            errno = 0;
            if (!(r & FILE_ATTRIBUTE_READONLY))
                buffer->st_mode |= S_IWRITE | S_IEXEC;
            return 0;
        }
     }
-#ifdef __BORLANDC__
     else {
+       if (l == 3 && isALPHA(path[0]) && path[1] == ':'
+           && (path[2] == '\\' || path[2] == '/'))
+       {
+           /* The drive can be inaccessible, some _stat()s are buggy */
+           if (!GetVolumeInformation(path,NULL,0,NULL,NULL,NULL,NULL,0)) {
+               errno = ENOENT;
+               return -1;
+           }
+       }
+#ifdef __BORLANDC__
        if (S_ISDIR(buffer->st_mode))
            buffer->st_mode |= S_IWRITE | S_IEXEC;
        else if (S_ISREG(buffer->st_mode)) {
@@ -926,8 +963,8 @@ win32_stat(const char *path, struct stat *buffer)
            else
                buffer->st_mode &= ~S_IEXEC;
        }
-    }
 #endif
+    }
     return res;
 }
 
@@ -973,6 +1010,40 @@ win32_getenv(const char *name)
     return curitem;
 }
 
+DllExport int
+win32_putenv(const char *name)
+{
+    char* curitem;
+    char* val;
+    int relval = -1;
+    if(name) {
+       New(1309,curitem,strlen(name)+1,char);
+       strcpy(curitem, name);
+       val = strchr(curitem, '=');
+       if(val) {
+           /* The sane way to deal with the environment.
+            * Has these advantages over putenv() & co.:
+            *  * enables us to store a truly empty value in the
+            *    environment (like in UNIX).
+            *  * we don't have to deal with RTL globals, bugs and leaks.
+            *  * Much faster.
+            * Why you may want to enable USE_WIN32_RTL_ENV:
+            *  * environ[] and RTL functions will not reflect changes,
+            *    which might be an issue if extensions want to access
+            *    the env. via RTL.  This cuts both ways, since RTL will
+            *    not see changes made by extensions that call the Win32
+            *    functions directly, either.
+            * GSAR 97-06-07
+            */
+           *val++ = '\0';
+           if(SetEnvironmentVariable(curitem, *val ? val : NULL))
+               relval = 0;
+       }
+       Safefree(curitem);
+    }
+    return relval;
+}
+
 #endif
 
 static long
@@ -1072,29 +1143,127 @@ win32_utime(const char *filename, struct utimbuf *times)
 }
 
 DllExport int
+win32_uname(struct utsname *name)
+{
+    struct hostent *hep;
+    STRLEN nodemax = sizeof(name->nodename)-1;
+    OSVERSIONINFO osver;
+
+    memset(&osver, 0, sizeof(OSVERSIONINFO));
+    osver.dwOSVersionInfoSize = sizeof(OSVERSIONINFO);
+    if (GetVersionEx(&osver)) {
+       /* sysname */
+       switch (osver.dwPlatformId) {
+       case VER_PLATFORM_WIN32_WINDOWS:
+           strcpy(name->sysname, "Windows");
+           break;
+       case VER_PLATFORM_WIN32_NT:
+           strcpy(name->sysname, "Windows NT");
+           break;
+       case VER_PLATFORM_WIN32s:
+           strcpy(name->sysname, "Win32s");
+           break;
+       default:
+           strcpy(name->sysname, "Win32 Unknown");
+           break;
+       }
+
+       /* release */
+       sprintf(name->release, "%d.%d",
+               osver.dwMajorVersion, osver.dwMinorVersion);
+
+       /* version */
+       sprintf(name->version, "Build %d",
+               osver.dwPlatformId == VER_PLATFORM_WIN32_NT
+               ? osver.dwBuildNumber : (osver.dwBuildNumber & 0xffff));
+       if (osver.szCSDVersion[0]) {
+           char *buf = name->version + strlen(name->version);
+           sprintf(buf, " (%s)", osver.szCSDVersion);
+       }
+    }
+    else {
+       *name->sysname = '\0';
+       *name->version = '\0';
+       *name->release = '\0';
+    }
+
+    /* nodename */
+    hep = win32_gethostbyname("localhost");
+    if (hep) {
+       STRLEN len = strlen(hep->h_name);
+       if (len <= nodemax) {
+           strcpy(name->nodename, hep->h_name);
+       }
+       else {
+           strncpy(name->nodename, hep->h_name, nodemax);
+           name->nodename[nodemax] = '\0';
+       }
+    }
+    else {
+       DWORD sz = nodemax;
+       if (!GetComputerName(name->nodename, &sz))
+           *name->nodename = '\0';
+    }
+
+    /* machine (architecture) */
+    {
+       SYSTEM_INFO info;
+       char *arch;
+       GetSystemInfo(&info);
+       switch (info.wProcessorArchitecture) {
+       case PROCESSOR_ARCHITECTURE_INTEL:
+           arch = "x86"; break;
+       case PROCESSOR_ARCHITECTURE_MIPS:
+           arch = "mips"; break;
+       case PROCESSOR_ARCHITECTURE_ALPHA:
+           arch = "alpha"; break;
+       case PROCESSOR_ARCHITECTURE_PPC:
+           arch = "ppc"; break;
+       default:
+           arch = "unknown"; break;
+       }
+       strcpy(name->machine, arch);
+    }
+    return 0;
+}
+
+DllExport int
 win32_waitpid(int pid, int *status, int flags)
 {
-    int rc;
+    int retval = -1;
     if (pid == -1) 
-      return win32_wait(status);
+       return win32_wait(status);
     else {
-      rc = cwait(status, pid, WAIT_CHILD);
-    /* cwait() returns differently on Borland */
-#ifdef __BORLANDC__
-    if (status)
-       *status =  (((*status >> 8) & 0xff) | ((*status << 8) & 0xff00));
+       long child = find_pid(pid);
+       if (child >= 0) {
+           HANDLE hProcess = w32_child_handles[child];
+           DWORD waitcode = WaitForSingleObject(hProcess, INFINITE);
+           if (waitcode != WAIT_FAILED) {
+               if (GetExitCodeProcess(hProcess, &waitcode)) {
+                   *status = (int)((waitcode & 0xff) << 8);
+                   retval = (int)w32_child_pids[child];
+                   remove_dead_process(child);
+                   return retval;
+               }
+           }
+           else
+               errno = ECHILD;
+       }
+       else {
+           retval = cwait(status, pid, WAIT_CHILD);
+           /* cwait() returns "correctly" on Borland */
+#ifndef __BORLANDC__
+           if (status)
+               *status *= 256;
 #endif
-      remove_dead_process((HANDLE)pid);
+       }
     }
-    return rc >= 0 ? pid : rc;                
+    return retval >= 0 ? pid : retval;                
 }
 
 DllExport int
 win32_wait(int *status)
 {
-#ifdef USE_RTL_WAIT
-    return wait(status);
-#else
     /* XXX this wait emulation only knows about processes
      * spawned via win32_spawnvp(P_NOWAIT, ...).
      */
@@ -1108,7 +1277,7 @@ win32_wait(int *status)
 
     /* if a child exists, wait for it to die */
     waitcode = WaitForMultipleObjects(w32_num_children,
-                                     w32_child_pids,
+                                     w32_child_handles,
                                      FALSE,
                                      INFINITE);
     if (waitcode != WAIT_FAILED) {
@@ -1117,13 +1286,10 @@ win32_wait(int *status)
            i = waitcode - WAIT_ABANDONED_0;
        else
            i = waitcode - WAIT_OBJECT_0;
-       if (GetExitCodeProcess(w32_child_pids[i], &exitcode) ) {
-           CloseHandle(w32_child_pids[i]);
+       if (GetExitCodeProcess(w32_child_handles[i], &exitcode) ) {
            *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--;
+           remove_dead_process(i);
            return retval;
        }
     }
@@ -1131,8 +1297,6 @@ win32_wait(int *status)
 FAILED:
     errno = GetLastError();
     return -1;
-
-#endif
 }
 
 static UINT timerid = 0;
@@ -1730,17 +1894,10 @@ win32_pclose(FILE *pf)
     win32_fclose(pf);
     SvIVX(sv) = 0;
 
-    remove_dead_process((HANDLE)childpid);
+    if (win32_waitpid(childpid, &status, 0) == -1)
+        return -1;
 
-    /* wait for the child */
-    if (cwait(&status, childpid, WAIT_CHILD) == -1)
-        return (-1);
-    /* cwait() returns differently on Borland */
-#ifdef __BORLANDC__
-    return (((status >> 8) & 0xff) | ((status << 8) & 0xff00));
-#else
-    return (status);
-#endif
+    return status;
 
 #endif /* USE_RTL_POPEN */
 }
@@ -1748,51 +1905,102 @@ win32_pclose(FILE *pf)
 DllExport int
 win32_rename(const char *oname, const char *newname)
 {
-    char szNewWorkName[MAX_PATH+1];
-    WIN32_FIND_DATA fdOldFile, fdNewFile;
-    HANDLE handle;
-    char *ptr;
-
-    if ((strchr(oname, '\\') || strchr(oname, '/'))
-       && strchr(newname, '\\') == NULL
-       && strchr(newname, '/') == NULL)
-    {
-       strcpy(szNewWorkName, oname);
-       if ((ptr = strrchr(szNewWorkName, '\\')) == NULL)
-           ptr = strrchr(szNewWorkName, '/');
-       strcpy(++ptr, newname);
+    /* XXX despite what the documentation says about MoveFileEx(),
+     * it doesn't work under Windows95!
+     */
+    if (IsWinNT()) {
+       if (!MoveFileEx(oname,newname,
+                       MOVEFILE_COPY_ALLOWED|MOVEFILE_REPLACE_EXISTING)) {
+           DWORD err = GetLastError();
+           switch (err) {
+           case ERROR_BAD_NET_NAME:
+           case ERROR_BAD_NETPATH:
+           case ERROR_BAD_PATHNAME:
+           case ERROR_FILE_NOT_FOUND:
+           case ERROR_FILENAME_EXCED_RANGE:
+           case ERROR_INVALID_DRIVE:
+           case ERROR_NO_MORE_FILES:
+           case ERROR_PATH_NOT_FOUND:
+               errno = ENOENT;
+               break;
+           default:
+               errno = EACCES;
+               break;
+           }
+           return -1;
+       }
+       return 0;
     }
-    else
-       strcpy(szNewWorkName, newname);
-
-    if (stricmp(oname, szNewWorkName) != 0) {
-       // check that we're not being fooled by relative paths
-       // and only delete the new file
-       //  1) if it exists
-       //  2) it is not the same file as the old file
-       //  3) old file exist
-       // GetFullPathName does not return the long file name on some systems
-       handle = FindFirstFile(oname, &fdOldFile);
-       if (handle != INVALID_HANDLE_VALUE) {
-           FindClose(handle);
-    
-           handle = FindFirstFile(szNewWorkName, &fdNewFile);
-    
-           if (handle != INVALID_HANDLE_VALUE)
-               FindClose(handle);
+    else {
+       int retval = 0;
+       char tmpname[MAX_PATH+1];
+       char dname[MAX_PATH+1];
+       char *endname = Nullch;
+       STRLEN tmplen = 0;
+       DWORD from_attr, to_attr;
+
+       /* if oname doesn't exist, do nothing */
+       from_attr = GetFileAttributes(oname);
+       if (from_attr == 0xFFFFFFFF) {
+           errno = ENOENT;
+           return -1;
+       }
+
+       /* if newname exists, rename it to a temporary name so that we
+        * don't delete it in case oname happens to be the same file
+        * (but perhaps accessed via a different path)
+        */
+       to_attr = GetFileAttributes(newname);
+       if (to_attr != 0xFFFFFFFF) {
+           /* if newname is a directory, we fail
+            * XXX could overcome this with yet more convoluted logic */
+           if (to_attr & FILE_ATTRIBUTE_DIRECTORY) {
+               errno = EACCES;
+               return -1;
+           }
+           tmplen = strlen(newname);
+           strcpy(tmpname,newname);
+           endname = tmpname+tmplen;
+           for (; endname > tmpname ; --endname) {
+               if (*endname == '/' || *endname == '\\') {
+                   *endname = '\0';
+                   break;
+               }
+           }
+           if (endname > tmpname)
+               endname = strcpy(dname,tmpname);
            else
-               fdNewFile.cFileName[0] = '\0';
-
-           if (strcmp(fdOldFile.cAlternateFileName,
-                      fdNewFile.cAlternateFileName) != 0
-               && strcmp(fdOldFile.cFileName, fdNewFile.cFileName) != 0)
-           {
-               // file exists and not same file
-               DeleteFile(szNewWorkName);
+               endname = ".";
+
+           /* get a temporary filename in same directory
+            * XXX is this really the best we can do? */
+           if (!GetTempFileName((LPCTSTR)endname, "plr", 0, tmpname)) {
+               errno = ENOENT;
+               return -1;
            }
+           DeleteFile(tmpname);
+
+           retval = rename(newname, tmpname);
+           if (retval != 0) {
+               errno = EACCES;
+               return retval;
+           }
+       }
+
+       /* rename oname to newname */
+       retval = rename(oname, newname);
+
+       /* if we created a temporary file before ... */
+       if (endname != Nullch) {
+           /* ...and rename succeeded, delete temporary file/directory */
+           if (retval == 0)
+               DeleteFile(tmpname);
+           /* else restore it to what it was */
+           else
+               (void)rename(tmpname, newname);
        }
+       return retval;
     }
-    return rename(oname, newname);
 }
 
 DllExport int
@@ -1882,26 +2090,209 @@ win32_chdir(const char *dir)
     return chdir(dir);
 }
 
+static char *
+create_command_line(const char* command, const char * const *args)
+{
+    int index;
+    char *cmd, *ptr, *arg;
+    STRLEN len = strlen(command) + 1;
+
+    for (index = 0; (ptr = (char*)args[index]) != NULL; ++index)
+       len += strlen(ptr) + 1;
+
+    New(1310, cmd, len, char);
+    ptr = cmd;
+    strcpy(ptr, command);
+
+    for (index = 0; (arg = (char*)args[index]) != NULL; ++index) {
+       ptr += strlen(ptr);
+       *ptr++ = ' ';
+       strcpy(ptr, arg);
+    }
+
+    return cmd;
+}
+
+static char *
+qualified_path(const char *cmd)
+{
+    char *pathstr;
+    char *fullcmd, *curfullcmd;
+    STRLEN cmdlen = 0;
+    int has_slash = 0;
+
+    if (!cmd)
+       return Nullch;
+    fullcmd = (char*)cmd;
+    while (*fullcmd) {
+       if (*fullcmd == '/' || *fullcmd == '\\')
+           has_slash++;
+       fullcmd++;
+       cmdlen++;
+    }
+
+    /* look in PATH */
+    pathstr = win32_getenv("PATH");
+    New(0, fullcmd, MAX_PATH+1, char);
+    curfullcmd = fullcmd;
+
+    while (1) {
+       DWORD res;
+
+       /* start by appending the name to the current prefix */
+       strcpy(curfullcmd, cmd);
+       curfullcmd += cmdlen;
+
+       /* if it doesn't end with '.', or has no extension, try adding
+        * a trailing .exe first */
+       if (cmd[cmdlen-1] != '.'
+           && (cmdlen < 4 || cmd[cmdlen-4] != '.'))
+       {
+           strcpy(curfullcmd, ".exe");
+           res = GetFileAttributes(fullcmd);
+           if (res != 0xFFFFFFFF && !(res & FILE_ATTRIBUTE_DIRECTORY))
+               return fullcmd;
+           *curfullcmd = '\0';
+       }
+
+       /* that failed, try the bare name */
+       res = GetFileAttributes(fullcmd);
+       if (res != 0xFFFFFFFF && !(res & FILE_ATTRIBUTE_DIRECTORY))
+           return fullcmd;
+
+       /* quit if no other path exists, or if cmd already has path */
+       if (!pathstr || !*pathstr || has_slash)
+           break;
+
+       /* skip leading semis */
+       while (*pathstr == ';')
+           pathstr++;
+
+       /* build a new prefix from scratch */
+       curfullcmd = fullcmd;
+       while (*pathstr && *pathstr != ';') {
+           if (*pathstr == '"') {      /* foo;"baz;etc";bar */
+               pathstr++;              /* skip initial '"' */
+               while (*pathstr && *pathstr != '"') {
+                   if (curfullcmd-fullcmd < MAX_PATH-cmdlen-5)
+                       *curfullcmd++ = *pathstr;
+                   pathstr++;
+               }
+               if (*pathstr)
+                   pathstr++;          /* skip trailing '"' */
+           }
+           else {
+               if (curfullcmd-fullcmd < MAX_PATH-cmdlen-5)
+                   *curfullcmd++ = *pathstr;
+               pathstr++;
+           }
+       }
+       if (*pathstr)
+           pathstr++;                  /* skip trailing semi */
+       if (curfullcmd > fullcmd        /* append a dir separator */
+           && curfullcmd[-1] != '/' && curfullcmd[-1] != '\\')
+       {
+           *curfullcmd++ = '\\';
+       }
+    }
+GIVE_UP:
+    Safefree(fullcmd);
+    return Nullch;
+}
+
+/* XXX this needs to be made more compatible with the spawnvp()
+ * provided by the various RTLs.  In particular, searching for
+ * *.{com,bat,cmd} files (as done by the RTLs) is unimplemented.
+ * This doesn't significantly affect perl itself, because we
+ * always invoke things using PERL5SHELL if a direct attempt to
+ * spawn the executable fails.
+ * 
+ * XXX splitting and rejoining the commandline between do_aspawn()
+ * and win32_spawnvp() could also be avoided.
+ */
+
 DllExport int
 win32_spawnvp(int mode, const char *cmdname, const char *const *argv)
 {
-    int status;
+#ifdef USE_RTL_SPAWNVP
+    return spawnvp(mode, cmdname, (char * const *)argv);
+#else
+    DWORD ret;
+    STARTUPINFO StartupInfo;
+    PROCESS_INFORMATION ProcessInformation;
+    DWORD create = 0;
+
+    char *cmd = create_command_line(cmdname, strcmp(cmdname, argv[0]) == 0
+                                            ? &argv[1] : argv);
+    char *fullcmd = Nullch;
+
+    switch(mode) {
+    case P_NOWAIT:     /* asynch + remember result */
+       if (w32_num_children >= MAXIMUM_WAIT_OBJECTS) {
+           errno = EAGAIN;
+           ret = -1;
+           goto RETVAL;
+       }
+       /* FALL THROUGH */
+    case P_WAIT:       /* synchronous execution */
+       break;
+    default:           /* invalid mode */
+       errno = EINVAL;
+       ret = -1;
+       goto RETVAL;
+    }
+    memset(&StartupInfo,0,sizeof(StartupInfo));
+    StartupInfo.cb = sizeof(StartupInfo);
+    StartupInfo.wShowWindow = SW_SHOWDEFAULT;
+
+RETRY:
+    if (!CreateProcess(cmdname,                /* search PATH to find executable */
+                      cmd,             /* executable, and its arguments */
+                      NULL,            /* process attributes */
+                      NULL,            /* thread attributes */
+                      TRUE,            /* inherit handles */
+                      create,          /* creation flags */
+                      NULL,            /* inherit environment */
+                      NULL,            /* inherit cwd */
+                      &StartupInfo,
+                      &ProcessInformation))
+    {
+       /* initial NULL argument to CreateProcess() does a PATH
+        * search, but it always first looks in the directory
+        * where the current process was started, which behavior
+        * is undesirable for backward compatibility.  So we
+        * jump through our own hoops by picking out the path
+        * we really want it to use. */
+       if (!fullcmd) {
+           fullcmd = qualified_path(cmdname);
+           if (fullcmd) {
+               cmdname = fullcmd;
+               goto RETRY;
+           }
+       }
+       errno = ENOENT;
+       ret = -1;
+       goto RETVAL;
+    }
 
-#ifndef USE_RTL_WAIT
-    if (mode == P_NOWAIT && w32_num_children >= MAXIMUM_WAIT_OBJECTS)
-       return -1;
-#endif
+    if (mode == P_NOWAIT) {
+       /* asynchronous spawn -- store handle, return PID */
+       w32_child_handles[w32_num_children] = ProcessInformation.hProcess;
+       ret = w32_child_pids[w32_num_children] = ProcessInformation.dwProcessId;
+       ++w32_num_children;
+    }
+    else  {
+       WaitForSingleObject(ProcessInformation.hProcess, INFINITE);
+       GetExitCodeProcess(ProcessInformation.hProcess, &ret);
+       CloseHandle(ProcessInformation.hProcess);
+    }
 
-    status = spawnvp(mode, cmdname, (char * const *) argv);
-#ifndef USE_RTL_WAIT
-    /* 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;
+    CloseHandle(ProcessInformation.hThread);
+RETVAL:
+    Safefree(cmd);
+    Safefree(fullcmd);
+    return (int)ret;
 #endif
-    return status;
 }
 
 DllExport int
@@ -2123,11 +2514,13 @@ XS(w32_GetCwd)
      *   then it worked, set PV valid, 
      *   else leave it 'undef' 
      */
-    if (SvCUR(sv))
-       SvPOK_on(sv);
     EXTEND(SP,1);
-    ST(0) = sv;
-    XSRETURN(1);
+    if (SvCUR(sv)) {
+       SvPOK_on(sv);
+       ST(0) = sv;
+       XSRETURN(1);
+    }
+    XSRETURN_UNDEF;
 }
 
 static
@@ -2136,7 +2529,7 @@ XS(w32_SetCwd)
     dXSARGS;
     if (items != 1)
        croak("usage: Win32::SetCurrentDirectory($cwd)");
-    if (SetCurrentDirectory(SvPV(ST(0),PL_na)))
+    if (SetCurrentDirectory(SvPV_nolen(ST(0))))
        XSRETURN_YES;
 
     XSRETURN_NO;
@@ -2148,6 +2541,8 @@ XS(w32_GetNextAvailDrive)
     dXSARGS;
     char ix = 'C';
     char root[] = "_:\\";
+
+    EXTEND(SP,1);
     while (ix <= 'Z') {
        root[0] = ix++;
        if (GetDriveType(root) == 1) {
@@ -2162,18 +2557,30 @@ static
 XS(w32_GetLastError)
 {
     dXSARGS;
+    EXTEND(SP,1);
     XSRETURN_IV(GetLastError());
 }
 
 static
+XS(w32_SetLastError)
+{
+    dXSARGS;
+    if (items != 1)
+       croak("usage: Win32::SetLastError($error)");
+    SetLastError(SvIV(ST(0)));
+    XSRETURN_EMPTY;
+}
+
+static
 XS(w32_LoginName)
 {
     dXSARGS;
     char *name = getlogin_buffer;
     DWORD size = sizeof(getlogin_buffer);
+    EXTEND(SP,1);
     if (GetUserName(name,&size)) {
        /* size includes NULL */
-       ST(0) = sv_2mortal(newSVpv(name,size-1));
+       ST(0) = sv_2mortal(newSVpvn(name,size-1));
        XSRETURN(1);
     }
     XSRETURN_UNDEF;
@@ -2185,9 +2592,10 @@ XS(w32_NodeName)
     dXSARGS;
     char name[MAX_COMPUTERNAME_LENGTH+1];
     DWORD size = sizeof(name);
+    EXTEND(SP,1);
     if (GetComputerName(name,&size)) {
        /* size does NOT include NULL :-( */
-       ST(0) = sv_2mortal(newSVpv(name,size));
+       ST(0) = sv_2mortal(newSVpvn(name,size));
        XSRETURN(1);
     }
     XSRETURN_UNDEF;
@@ -2202,6 +2610,7 @@ XS(w32_DomainName)
     /* mingw32 (and Win95) don't have NetWksta*(), so do it the old way */
     char name[256];
     DWORD size = sizeof(name);
+    EXTEND(SP,1);
     if (GetUserName(name,&size)) {
        char sid[1024];
        DWORD sidlen = sizeof(sid);
@@ -2220,6 +2629,7 @@ XS(w32_DomainName)
     char dname[256];
     DWORD dnamelen = sizeof(dname);
     PWKSTA_INFO_100 pwi;
+    EXTEND(SP,1);
     if (NERR_Success == NetWkstaGetInfo(NULL, 100, (LPBYTE*)&pwi)) {
        if (pwi->wki100_langroup && *(pwi->wki100_langroup)) {
            WideCharToMultiByte(CP_ACP, NULL, pwi->wki100_langroup,
@@ -2244,16 +2654,17 @@ XS(w32_FsType)
     DWORD flags, filecomplen;
     if (GetVolumeInformation(NULL, NULL, 0, NULL, &filecomplen,
                         &flags, fsname, sizeof(fsname))) {
-       if (GIMME == G_ARRAY) {
-           XPUSHs(sv_2mortal(newSVpv(fsname,0)));
+       if (GIMME_V == G_ARRAY) {
+           XPUSHs(sv_2mortal(newSVpvn(fsname,strlen(fsname))));
            XPUSHs(sv_2mortal(newSViv(flags)));
            XPUSHs(sv_2mortal(newSViv(filecomplen)));
            PUTBACK;
            return;
        }
+       EXTEND(SP,1);
        XSRETURN_PV(fsname);
     }
-    XSRETURN_UNDEF;
+    XSRETURN_EMPTY;
 }
 
 static
@@ -2264,7 +2675,7 @@ XS(w32_GetOSVersion)
 
     osver.dwOSVersionInfoSize = sizeof(OSVERSIONINFO);
     if (GetVersionEx(&osver)) {
-       XPUSHs(newSVpv(osver.szCSDVersion, 0));
+       XPUSHs(newSVpvn(osver.szCSDVersion, strlen(osver.szCSDVersion)));
        XPUSHs(newSViv(osver.dwMajorVersion));
        XPUSHs(newSViv(osver.dwMinorVersion));
        XPUSHs(newSViv(osver.dwBuildNumber));
@@ -2272,13 +2683,14 @@ XS(w32_GetOSVersion)
        PUTBACK;
        return;
     }
-    XSRETURN_UNDEF;
+    XSRETURN_EMPTY;
 }
 
 static
 XS(w32_IsWinNT)
 {
     dXSARGS;
+    EXTEND(SP,1);
     XSRETURN_IV(IsWinNT());
 }
 
@@ -2286,6 +2698,7 @@ static
 XS(w32_IsWin95)
 {
     dXSARGS;
+    EXTEND(SP,1);
     XSRETURN_IV(IsWin95());
 }
 
@@ -2319,8 +2732,8 @@ XS(w32_Spawn)
     if (items != 3)
        croak("usage: Win32::Spawn($cmdName, $args, $PID)");
 
-    cmd = SvPV(ST(0),PL_na);
-    args = SvPV(ST(1), PL_na);
+    cmd = SvPV_nolen(ST(0));
+    args = SvPV_nolen(ST(1));
 
     memset(&stStartInfo, 0, sizeof(stStartInfo));   /* Clear the block */
     stStartInfo.cb = sizeof(stStartInfo);          /* Set the structure size */
@@ -2350,7 +2763,11 @@ static
 XS(w32_GetTickCount)
 {
     dXSARGS;
-    XSRETURN_IV(GetTickCount());
+    EXTEND(SP,1);
+    DWORD msec = GetTickCount();
+    if ((IV)msec > 0)
+       XSRETURN_IV(msec);
+    XSRETURN_NV(msec);
 }
 
 static
@@ -2374,10 +2791,44 @@ XS(w32_GetShortPathName)
     if (len) {
        SvCUR_set(shortpath,len);
        ST(0) = shortpath;
+       XSRETURN(1);
     }
-    else
-       ST(0) = &PL_sv_undef;
-    XSRETURN(1);
+    XSRETURN_UNDEF;
+}
+
+static
+XS(w32_GetFullPathName)
+{
+    dXSARGS;
+    SV *filename;
+    SV *fullpath;
+    char *filepart;
+    DWORD len;
+
+    if (items != 1)
+       croak("usage: Win32::GetFullPathName($filename)");
+
+    filename = ST(0);
+    fullpath = sv_mortalcopy(filename);
+    SvUPGRADE(fullpath, SVt_PV);
+    do {
+       len = GetFullPathName(SvPVX(filename),
+                             SvLEN(fullpath),
+                             SvPVX(fullpath),
+                             &filepart);
+    } while (len >= SvLEN(fullpath) && sv_grow(fullpath,len+1));
+    if (len) {
+       if (GIMME_V == G_ARRAY) {
+           EXTEND(SP,1);
+           XST_mPV(1,filepart);
+           len = filepart - SvPVX(fullpath);
+           items = 2;
+       }
+       SvCUR_set(fullpath,len);
+       ST(0) = fullpath;
+       XSRETURN(items);
+    }
+    XSRETURN_EMPTY;
 }
 
 static
@@ -2399,15 +2850,15 @@ Perl_init_os_extras()
     w32_perlshell_tokens = Nullch;
     w32_perlshell_items = -1;
     w32_fdpid = newAV();               /* XXX needs to be in Perl_win32_init()? */
-#ifndef USE_RTL_WAIT
+    New(1313, w32_children, 1, child_tab);
     w32_num_children = 0;
-#endif
 
     /* these names are Activeware compatible */
     newXS("Win32::GetCwd", w32_GetCwd, file);
     newXS("Win32::SetCwd", w32_SetCwd, file);
     newXS("Win32::GetNextAvailDrive", w32_GetNextAvailDrive, file);
     newXS("Win32::GetLastError", w32_GetLastError, file);
+    newXS("Win32::SetLastError", w32_SetLastError, file);
     newXS("Win32::LoginName", w32_LoginName, file);
     newXS("Win32::NodeName", w32_NodeName, file);
     newXS("Win32::DomainName", w32_DomainName, file);
@@ -2419,6 +2870,7 @@ Perl_init_os_extras()
     newXS("Win32::Spawn", w32_Spawn, file);
     newXS("Win32::GetTickCount", w32_GetTickCount, file);
     newXS("Win32::GetShortPathName", w32_GetShortPathName, file);
+    newXS("Win32::GetFullPathName", w32_GetFullPathName, file);
     newXS("Win32::Sleep", w32_Sleep, file);
 
     /* XXX Bloat Alert! The following Activeware preloads really