This is a live mirror of the Perl 5 development currently hosted at https://github.com/perl/perl5
Win32: add lstat(), fetch st_dev and st_ino and fetch st_nlink for fstat
[perl5.git] / win32 / win32.c
index 6024623..b757715 100644 (file)
 #include <tlhelp32.h>
 #include <io.h>
 #include <signal.h>
+#include <winioctl.h>
 
 /* #include "config.h" */
 
-#if !defined(PERLIO_IS_STDIO)
-#  define PerlIO FILE
-#endif
+
+#define PerlIO FILE
 
 #include <sys/stat.h>
 #include "EXTERN.h"
@@ -65,6 +65,7 @@
 #include <float.h>
 #include <time.h>
 #include <sys/utime.h>
+#include <wchar.h>
 
 #ifdef __GNUC__
 /* Mingw32 defaults to globing command line
@@ -89,12 +90,6 @@ END_EXTERN_C
 #define EXECF_SPAWN_NOWAIT 3
 
 #if defined(PERL_IMPLICIT_SYS)
-#  undef win32_get_privlib
-#  define win32_get_privlib g_win32_get_privlib
-#  undef win32_get_sitelib
-#  define win32_get_sitelib g_win32_get_sitelib
-#  undef win32_get_vendorlib
-#  define win32_get_vendorlib g_win32_get_vendorlib
 #  undef getlogin
 #  define getlogin g_getlogin
 #endif
@@ -121,12 +116,17 @@ static void       my_invalid_parameter_handler(const wchar_t* expression,
                        unsigned int line, uintptr_t pReserved);
 #endif
 
+#ifndef WIN32_NO_REGISTRY
 static char*   get_regstr_from(HKEY hkey, const char *valuename, SV **svp);
 static char*   get_regstr(const char *valuename, SV **svp);
+#endif
+
 static char*   get_emd_part(SV **prev_pathp, STRLEN *const len,
                        char *trailing, ...);
-static char*   win32_get_xlib(const char *pl, const char *xlib,
+static char*   win32_get_xlib(const char *pl,
+                       WIN32_NO_REGISTRY_M_(const char *xlib)
                        const char *libname, STRLEN *const len);
+
 static BOOL    has_shell_metachars(const char *ptr);
 static long    tokenize(const char *str, char **dest, char ***destv);
 static void    get_shell(void);
@@ -149,7 +149,7 @@ static long filetime_to_clock(PFILETIME ft);
 static BOOL    filetime_from_time(PFILETIME ft, time_t t);
 static char*   create_command_line(char *cname, STRLEN clen,
                                    const char * const *args);
-static char*   qualified_path(const char *cmd);
+static char*   qualified_path(const char *cmd, bool other_exts);
 static void    ansify_path(void);
 static LRESULT win32_process_message(HWND hwnd, UINT msg,
                        WPARAM wParam, LPARAM lParam);
@@ -174,6 +174,12 @@ END_EXTERN_C
 
 static OSVERSIONINFO g_osver = {0, 0, 0, 0, 0, ""};
 
+#ifndef WIN32_NO_REGISTRY
+/* initialized by Perl_win32_init/PERL_SYS_INIT */
+static HKEY HKCU_Perl_hnd;
+static HKEY HKLM_Perl_hnd;
+#endif
+
 #ifdef SET_INVALID_PARAMETER_HANDLER
 static BOOL silent_invalid_parameter_handler = FALSE;
 
@@ -261,36 +267,31 @@ set_w32_module_name(void)
     }
 }
 
+#ifndef WIN32_NO_REGISTRY
 /* *svp (if non-NULL) is expected to be POK (valid allocated SvPVX(*svp)) */
 static char*
-get_regstr_from(HKEY hkey, const char *valuename, SV **svp)
+get_regstr_from(HKEY handle, const char *valuename, SV **svp)
 {
     /* Retrieve a REG_SZ or REG_EXPAND_SZ from the registry */
-    HKEY handle;
     DWORD type;
-    const char *subkey = "Software\\Perl";
     char *str = NULL;
     long retval;
+    DWORD datalen;
 
-    retval = RegOpenKeyEx(hkey, subkey, 0, KEY_READ, &handle);
-    if (retval == ERROR_SUCCESS) {
-       DWORD datalen;
-       retval = RegQueryValueEx(handle, valuename, 0, &type, NULL, &datalen);
-       if (retval == ERROR_SUCCESS
-           && (type == REG_SZ || type == REG_EXPAND_SZ))
-       {
-           dTHX;
-           if (!*svp)
-               *svp = sv_2mortal(newSVpvs(""));
-           SvGROW(*svp, datalen);
-           retval = RegQueryValueEx(handle, valuename, 0, NULL,
-                                    (PBYTE)SvPVX(*svp), &datalen);
-           if (retval == ERROR_SUCCESS) {
-               str = SvPVX(*svp);
-               SvCUR_set(*svp,datalen-1);
-           }
+    retval = RegQueryValueEx(handle, valuename, 0, &type, NULL, &datalen);
+    if (retval == ERROR_SUCCESS
+       && (type == REG_SZ || type == REG_EXPAND_SZ))
+    {
+       dTHX;
+       if (!*svp)
+           *svp = sv_2mortal(newSVpvs(""));
+       SvGROW(*svp, datalen);
+       retval = RegQueryValueEx(handle, valuename, 0, NULL,
+                                (PBYTE)SvPVX(*svp), &datalen);
+       if (retval == ERROR_SUCCESS) {
+           str = SvPVX(*svp);
+           SvCUR_set(*svp,datalen-1);
        }
-       RegCloseKey(handle);
     }
     return str;
 }
@@ -299,11 +300,22 @@ get_regstr_from(HKEY hkey, const char *valuename, SV **svp)
 static char*
 get_regstr(const char *valuename, SV **svp)
 {
-    char *str = get_regstr_from(HKEY_CURRENT_USER, valuename, svp);
-    if (!str)
-       str = get_regstr_from(HKEY_LOCAL_MACHINE, valuename, svp);
+    char *str;
+    if (HKCU_Perl_hnd) {
+       str = get_regstr_from(HKCU_Perl_hnd, valuename, svp);
+       if (!str)
+           goto try_HKLM;
+    }
+    else {
+       try_HKLM:
+       if (HKLM_Perl_hnd)
+           str = get_regstr_from(HKLM_Perl_hnd, valuename, svp);
+       else
+           str = NULL;
+    }
     return str;
 }
+#endif /* ifndef WIN32_NO_REGISTRY */
 
 /* *prev_pathp (if non-NULL) is expected to be POK (valid allocated SvPVX(sv)) */
 static char *
@@ -338,8 +350,8 @@ get_emd_part(SV **prev_pathp, STRLEN *const len, char *trailing_path, ...)
        if (!ptr || stricmp(ptr+1, strip) != 0) {
            /* ... but not if component matches m|5\.$patchlevel.*| */
            if (!ptr || !(*strip == '5' && *(ptr+1) == '5'
-                         && strncmp(strip, base, baselen) == 0
-                         && strncmp(ptr+1, base, baselen) == 0))
+                         && strnEQ(strip, base, baselen)
+                         && strnEQ(ptr+1, base, baselen)))
            {
                *optr = '/';
                ptr = optr;
@@ -373,41 +385,49 @@ get_emd_part(SV **prev_pathp, STRLEN *const len, char *trailing_path, ...)
 }
 
 EXTERN_C char *
-win32_get_privlib(const char *pl, STRLEN *const len)
+win32_get_privlib(WIN32_NO_REGISTRY_M_(const char *pl) STRLEN *const len)
 {
     char *stdlib = "lib";
-    char buffer[MAX_PATH+1];
     SV *sv = NULL;
+#ifndef WIN32_NO_REGISTRY
+    char buffer[MAX_PATH+1];
 
     /* $stdlib = $HKCU{"lib-$]"} || $HKLM{"lib-$]"} || $HKCU{"lib"} || $HKLM{"lib"} || "";  */
     sprintf(buffer, "%s-%s", stdlib, pl);
     if (!get_regstr(buffer, &sv))
        (void)get_regstr(stdlib, &sv);
+#endif
 
     /* $stdlib .= ";$EMD/../../lib" */
     return get_emd_part(&sv, len, stdlib, ARCHNAME, "bin", NULL);
 }
 
 static char *
-win32_get_xlib(const char *pl, const char *xlib, const char *libname,
-              STRLEN *const len)
+win32_get_xlib(const char *pl, WIN32_NO_REGISTRY_M_(const char *xlib)
+              const char *libname, STRLEN *const len)
 {
+#ifndef WIN32_NO_REGISTRY
     char regstr[40];
+#endif
     char pathstr[MAX_PATH+1];
     SV *sv1 = NULL;
     SV *sv2 = NULL;
 
+#ifndef WIN32_NO_REGISTRY
     /* $HKCU{"$xlib-$]"} || $HKLM{"$xlib-$]"} . ---; */
     sprintf(regstr, "%s-%s", xlib, pl);
     (void)get_regstr(regstr, &sv1);
+#endif
 
     /* $xlib .=
      * ";$EMD/" . ((-d $EMD/../../../$]) ? "../../.." : "../.."). "/$libname/$]/lib";  */
     sprintf(pathstr, "%s/%s/lib", libname, pl);
     (void)get_emd_part(&sv1, NULL, pathstr, ARCHNAME, "bin", pl, NULL);
 
+#ifndef WIN32_NO_REGISTRY
     /* $HKCU{$xlib} || $HKLM{$xlib} . ---; */
     (void)get_regstr(xlib, &sv2);
+#endif
 
     /* $xlib .=
      * ";$EMD/" . ((-d $EMD/../../../$]) ? "../../.." : "../.."). "/$libname/lib";  */
@@ -420,7 +440,7 @@ win32_get_xlib(const char *pl, const char *xlib, const char *libname,
        sv1 = sv2;
     } else if (sv2) {
         dTHX;
-       sv_catpv(sv1, ";");
+       sv_catpvs(sv1, ";");
        sv_catsv(sv1, sv2);
     }
 
@@ -432,7 +452,7 @@ win32_get_xlib(const char *pl, const char *xlib, const char *libname,
 EXTERN_C char *
 win32_get_sitelib(const char *pl, STRLEN *const len)
 {
-    return win32_get_xlib(pl, "sitelib", "site", len);
+    return win32_get_xlib(pl, WIN32_NO_REGISTRY_M_("sitelib") "site", len);
 }
 
 #ifndef PERL_VENDORLIB_NAME
@@ -442,7 +462,7 @@ win32_get_sitelib(const char *pl, STRLEN *const len)
 EXTERN_C char *
 win32_get_vendorlib(const char *pl, STRLEN *const len)
 {
-    return win32_get_xlib(pl, "vendorlib", PERL_VENDORLIB_NAME, len);
+    return win32_get_xlib(pl, WIN32_NO_REGISTRY_M_("vendorlib") PERL_VENDORLIB_NAME, len);
 }
 
 static BOOL
@@ -601,6 +621,7 @@ Perl_do_aspawn(pTHX_ SV *really, SV **mark, SV **sp)
     int status;
     int flag = P_WAIT;
     int index = 0;
+    int eno;
 
     PERL_ARGS_ASSERT_DO_ASPAWN;
 
@@ -627,7 +648,7 @@ Perl_do_aspawn(pTHX_ SV *really, SV **mark, SV **sp)
                           (const char*)(really ? SvPV_nolen(really) : argv[0]),
                           (const char* const*)argv);
 
-    if (status < 0 && (errno == ENOEXEC || errno == ENOENT)) {
+    if (status < 0 && (eno = errno, (eno == ENOEXEC || eno == ENOENT))) {
        /* possible shell-builtin, invoke with shell */
        int sh_items;
        sh_items = w32_perlshell_items;
@@ -950,8 +971,8 @@ win32_readdir(DIR *dirp)
                 * new name and its null terminator */
                while (newsize > dirp->size) {
                    long curpos = dirp->curr - dirp->start;
+                   Renew(dirp->start, dirp->size * 2, char);
                    dirp->size *= 2;
-                   Renew(dirp->start, dirp->size, char);
                    dirp->curr = dirp->start + curpos;
                }
                strcpy(dirp->start + endpos, buffer);
@@ -1011,7 +1032,6 @@ win32_closedir(DIR *dirp)
 DllExport DIR *
 win32_dirp_dup(DIR *const dirp, CLONE_PARAMS *const param)
 {
-    dVAR;
     PerlInterpreter *const from = param->proto_perl;
     PerlInterpreter *const to   = (PerlInterpreter *)PERL_GET_THX;
 
@@ -1124,6 +1144,7 @@ chown(const char *path, uid_t owner, gid_t group)
  * XXX this needs strengthening  (for PerlIO)
  *   -- BKS, 11-11-200
 */
+#if !defined(__MINGW64_VERSION_MAJOR) || __MINGW64_VERSION_MAJOR < 4
 int mkstemp(const char *path)
 {
     dTHX;
@@ -1144,6 +1165,7 @@ retry:
        goto retry;
     return fd;
 }
+#endif
 
 static long
 find_pid(pTHX_ int pid)
@@ -1327,7 +1349,7 @@ get_hwnd_delay(pTHX, long child, DWORD tries)
      * caching reasons, and the child thread was attached to a different CPU
      * therefore there is no workload on that CPU and Sleep(0) returns control
      * without yielding the time slot.
-     * https://rt.perl.org/rt3/Ticket/Display.html?id=88840
+     * https://github.com/Perl/perl5/issues/11267
      */
     Sleep(0);
     win32_async_check(aTHX);
@@ -1441,11 +1463,10 @@ win32_stat(const char *path, Stat_t *sbuf)
     dTHX;
     int                res;
     int         nlink = 1;
+    unsigned __int64 ino = 0;
+    DWORD       vol = 0;
     BOOL        expect_dir = FALSE;
-
-    GV          *gv_sloppy = gv_fetchpvs("\027IN32_SLOPPY_STAT",
-                                         GV_NOTQUAL, SVt_PV);
-    BOOL        sloppy = gv_sloppy && SvTRUE(GvSV(gv_sloppy));
+    struct _stati64 st;
 
     if (l > 1) {
        switch(path[l - 1]) {
@@ -1487,26 +1508,45 @@ win32_stat(const char *path, Stat_t *sbuf)
     path = PerlDir_mapA(path);
     l = strlen(path);
 
-    if (!sloppy) {
+    if (!w32_sloppystat) {
         /* We must open & close the file once; otherwise file attribute changes  */
         /* might not yet have propagated to "other" hard links of the same file. */
         /* This also gives us an opportunity to determine the number of links.   */
-        HANDLE handle = CreateFileA(path, 0, 0, NULL, OPEN_EXISTING, 0, NULL);
+        HANDLE handle = CreateFileA(path, 0, 0, NULL, OPEN_EXISTING, FILE_FLAG_BACKUP_SEMANTICS, NULL);
         if (handle != INVALID_HANDLE_VALUE) {
             BY_HANDLE_FILE_INFORMATION bhi;
-            if (GetFileInformationByHandle(handle, &bhi))
+            if (GetFileInformationByHandle(handle, &bhi)) {
                 nlink = bhi.nNumberOfLinks;
+                ino = bhi.nFileIndexHigh;
+                ino <<= 32;
+                ino |= bhi.nFileIndexLow;
+                vol = bhi.dwVolumeSerialNumber;
+            }
             CloseHandle(handle);
         }
+       else {
+           DWORD err = GetLastError();
+           /* very common case, skip CRT stat and its also failing syscalls */
+           if(err == ERROR_FILE_NOT_FOUND) {
+               errno = ENOENT;
+               return -1;
+           }
+       }
     }
 
     /* path will be mapped correctly above */
-#if defined(WIN64) || defined(USE_LARGE_FILES)
     res = _stati64(path, sbuf);
-#else
-    res = stat(path, sbuf);
-#endif
+    sbuf->st_dev = vol;
+    sbuf->st_ino = ino;
+    sbuf->st_mode = st.st_mode;
     sbuf->st_nlink = nlink;
+    sbuf->st_uid = st.st_uid;
+    sbuf->st_gid = st.st_gid;
+    sbuf->st_rdev = st.st_rdev;
+    sbuf->st_size = st.st_size;
+    sbuf->st_atime = st.st_atime;
+    sbuf->st_mtime = st.st_mtime;
+    sbuf->st_ctime = st.st_ctime;
 
     if (res < 0) {
        /* CRT is buggy on sharenames, so make sure it really isn't.
@@ -1554,6 +1594,147 @@ win32_stat(const char *path, Stat_t *sbuf)
     return res;
 }
 
+static void
+translate_to_errno(void)
+{
+    /* This isn't perfect, eg. Win32 returns ERROR_ACCESS_DENIED for
+       both permissions errors and if the source is a directory, while
+       POSIX wants EACCES and EPERM respectively.
+
+       Determined by experimentation on Windows 7 x64 SP1, since MS
+       don't document what error codes are returned.
+    */
+    switch (GetLastError()) {
+    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_PATH_NOT_FOUND:
+      errno = ENOENT;
+      break;
+    case ERROR_ALREADY_EXISTS:
+      errno = EEXIST;
+      break;
+    case ERROR_ACCESS_DENIED:
+    case ERROR_PRIVILEGE_NOT_HELD:
+      errno = EACCES;
+      break;
+    case ERROR_NOT_SAME_DEVICE:
+      errno = EXDEV;
+      break;
+    case ERROR_DISK_FULL:
+      errno = ENOSPC;
+      break;
+    case ERROR_NOT_ENOUGH_QUOTA:
+      errno = EDQUOT;
+      break;
+    default:
+      /* ERROR_INVALID_FUNCTION - eg. symlink on a FAT volume */
+      errno = EINVAL;
+      break;
+    }
+}
+
+/* Adapted from:
+
+https://docs.microsoft.com/en-us/windows-hardware/drivers/ddi/ntifs/ns-ntifs-_reparse_data_buffer
+
+Renamed to avoid conflicts, apparently some SDKs define this
+structure.
+
+Hoisted the symlink data into a new type to allow us to make a pointer
+to it, and to avoid C++ scoping issues.
+
+*/
+
+typedef struct {
+    USHORT SubstituteNameOffset;
+    USHORT SubstituteNameLength;
+    USHORT PrintNameOffset;
+    USHORT PrintNameLength;
+    ULONG  Flags;
+    WCHAR  PathBuffer[MAX_PATH*3];
+} MY_SYMLINK_REPARSE_BUFFER, *PMY_SYMLINK_REPARSE_BUFFER;
+
+typedef struct {
+  ULONG  ReparseTag;
+  USHORT ReparseDataLength;
+  USHORT Reserved;
+  union {
+    MY_SYMLINK_REPARSE_BUFFER SymbolicLinkReparseBuffer;
+    struct {
+      USHORT SubstituteNameOffset;
+      USHORT SubstituteNameLength;
+      USHORT PrintNameOffset;
+      USHORT PrintNameLength;
+      WCHAR  PathBuffer[1];
+    } MountPointReparseBuffer;
+    struct {
+      UCHAR DataBuffer[1];
+    } GenericReparseBuffer;
+  } Data;
+} MY_REPARSE_DATA_BUFFER, *PMY_REPARSE_DATA_BUFFER;
+
+static BOOL
+is_symlink(HANDLE h) {
+    MY_REPARSE_DATA_BUFFER linkdata;
+    const MY_SYMLINK_REPARSE_BUFFER * const sd =
+        &linkdata.Data.SymbolicLinkReparseBuffer;
+    DWORD linkdata_returned;
+
+    if (!DeviceIoControl(h, FSCTL_GET_REPARSE_POINT, NULL, 0, &linkdata, sizeof(linkdata), &linkdata_returned, NULL)) {
+        return FALSE;
+    }
+
+    if (linkdata_returned < offsetof(MY_REPARSE_DATA_BUFFER, Data.SymbolicLinkReparseBuffer.PathBuffer)
+        || linkdata.ReparseTag != IO_REPARSE_TAG_SYMLINK) {
+        /* some other type of reparse point */
+        return FALSE;
+    }
+
+    return TRUE;
+}
+
+DllExport int
+win32_lstat(const char *path, Stat_t *sbuf)
+{
+    HANDLE f;
+    int fd;
+    int result;
+    DWORD attr = GetFileAttributes(path); /* doesn't follow symlinks */
+
+    if (attr == INVALID_FILE_ATTRIBUTES) {
+        translate_to_errno();
+        return -1;
+    }
+
+    if (!(attr & FILE_ATTRIBUTE_REPARSE_POINT)) {
+        return win32_stat(path, sbuf);
+    }
+
+    f = CreateFileA(path, GENERIC_READ, 0, NULL, OPEN_EXISTING,
+                           FILE_FLAG_OPEN_REPARSE_POINT|FILE_FLAG_BACKUP_SEMANTICS, 0);
+    if (f == INVALID_HANDLE_VALUE) {
+        translate_to_errno();
+        return -1;
+    }
+
+    if (!is_symlink(f)) {
+        CloseHandle(f);
+        return win32_stat(path, sbuf);
+    }
+
+    fd = win32_open_osfhandle((intptr_t)f, 0);
+    result = win32_fstat(fd, sbuf);
+    if (result != -1){
+        sbuf->st_mode = (sbuf->st_mode & ~_S_IFMT) | _S_IFLNK;
+    }
+    close(fd);
+    return result;
+}
+
 #define isSLASH(c) ((c) == '/' || (c) == '\\')
 #define SKIP_SLASHES(s) \
     STMT_START {                               \
@@ -1647,7 +1828,6 @@ win32_longpath(char *path)
        }
        else {
            /* failed a step, just return without side effects */
-           /*PerlIO_printf(Perl_debug_log, "Failed to find %s\n", path);*/
            errno = EINVAL;
            return NULL;
        }
@@ -1659,6 +1839,7 @@ win32_longpath(char *path)
 static void
 out_of_memory(void)
 {
+
     if (PL_curinterp)
        croak_no_mem();
     exit(1);
@@ -1695,7 +1876,7 @@ wstr_to_str(const wchar_t* wstr)
  * then it will convert the short name instead.
  *
  * The buffer to the ansi pathname must be freed with win32_free() when it
- * it no longer needed.
+ * is no longer needed.
  *
  * The argument to win32_ansipath() must exist before this function is
  * called; otherwise there is no way to determine the short path name.
@@ -1808,7 +1989,7 @@ win32_getenv(const char *name)
                char *end = strchr(cur,'=');
                if (end && end != cur) {
                    *end = '\0';
-                   if (!strcmp(cur,name)) {
+                   if (strEQ(cur,name)) {
                        curitem = sv_2mortal(newSVpv(end+1,0));
                        *end = '=';
                        break;
@@ -1821,12 +2002,14 @@ win32_getenv(const char *name)
            }
            FreeEnvironmentStrings(envv);
        }
+#ifndef WIN32_NO_REGISTRY
        else {
            /* last ditch: allow any environment variables that begin with 'PERL'
               to be obtained from the registry, if found there */
-           if (strncmp(name, "PERL", 4) == 0)
+           if (strBEGINs(name, "PERL"))
                (void)get_regstr(name, &curitem);
        }
+#endif
     }
     if (curitem && SvCUR(curitem))
        return SvPVX(curitem);
@@ -2142,7 +2325,7 @@ do_raise(pTHX_ int sig)
            }
        }
     }
-    /* Tell caller to exit thread/process as approriate */
+    /* Tell caller to exit thread/process as appropriate */
     return 1;
 }
 
@@ -2228,7 +2411,7 @@ win32_msgwait(pTHX_ DWORD count, LPHANDLE handles, DWORD timeout, LPDWORD result
      * This scenario can only be created if the timespan from the return of
      * MsgWaitForMultipleObjects to GetSystemTimeAsFileTime exceeds 1 ms. To
      * generate the scenario, manual breakpoints in a C debugger are required,
-     * or a context switch occured in win32_async_check in PeekMessage, or random
+     * or a context switch occurred in win32_async_check in PeekMessage, or random
      * messages are delivered to the *thread* message queue of the Perl thread
      * from another process (msctf.dll doing IPC among its instances, VS debugger
      * causes msctf.dll to be loaded into Perl by kernel), see [perl #33096].
@@ -2254,6 +2437,12 @@ win32_msgwait(pTHX_ DWORD count, LPHANDLE handles, DWORD timeout, LPDWORD result
        if (result == WAIT_OBJECT_0 + count) {
            /* Message has arrived - check it */
            (void)win32_async_check(aTHX);
+
+            /* retry */
+            if (ticks.ft_i64 > endtime)
+                endtime = ticks.ft_i64;
+
+            continue;
        }
        else {
           /* Not timeout or message - one of handles is ready */
@@ -2428,6 +2617,14 @@ win32_sleep(unsigned int t)
     return win32_msgwait(aTHX_ 0, NULL, t * 1000, NULL) / 1000;
 }
 
+DllExport int
+win32_pause(void)
+{
+    dTHX;
+    win32_msgwait(aTHX_ 0, NULL, INFINITE, NULL);
+    return -1;
+}
+
 DllExport unsigned int
 win32_alarm(unsigned int sec)
 {
@@ -2617,7 +2814,7 @@ win32_strerror(int e)
         * additionally map them to corresponding Windows (sockets) error codes
         * first to avoid getting the wrong system message.
         */
-       else if (e >= EADDRINUSE && e <= EWOULDBLOCK) {
+       else if (inRANGE(e, EADDRINUSE, EWOULDBLOCK)) {
            e = convert_errno_to_wsa_error(e);
        }
 #endif
@@ -2811,20 +3008,15 @@ win32_fflush(FILE *pf)
 DllExport Off_t
 win32_ftell(FILE *pf)
 {
-#if defined(WIN64) || defined(USE_LARGE_FILES)
     fpos_t pos;
     if (fgetpos(pf, &pos))
        return -1;
     return (Off_t)pos;
-#else
-    return ftell(pf);
-#endif
 }
 
 DllExport int
 win32_fseek(FILE *pf, Off_t offset,int origin)
 {
-#if defined(WIN64) || defined(USE_LARGE_FILES)
     fpos_t pos;
     switch (origin) {
     case SEEK_CUR:
@@ -2844,9 +3036,6 @@ win32_fseek(FILE *pf, Off_t offset,int origin)
        return -1;
     }
     return fsetpos(pf, &offset);
-#else
-    return fseek(pf, (long)offset, origin);
-#endif
 }
 
 DllExport int
@@ -2871,9 +3060,17 @@ win32_rewind(FILE *pf)
 DllExport int
 win32_tmpfd(void)
 {
+    return win32_tmpfd_mode(0);
+}
+
+DllExport int
+win32_tmpfd_mode(int mode)
+{
     char prefix[MAX_PATH+1];
     char filename[MAX_PATH+1];
     DWORD len = GetTempPath(MAX_PATH, prefix);
+    mode &= ~( O_ACCMODE | O_CREAT | O_EXCL );
+    mode |= O_RDWR;
     if (len && len < MAX_PATH) {
        if (GetTempFileName(prefix, "plx", 0, filename)) {
            HANDLE fh = CreateFile(filename,
@@ -2885,7 +3082,7 @@ win32_tmpfd(void)
                                   | FILE_FLAG_DELETE_ON_CLOSE,
                                   NULL);
            if (fh != INVALID_HANDLE_VALUE) {
-               int fd = win32_open_osfhandle((intptr_t)fh, 0);
+               int fd = win32_open_osfhandle((intptr_t)fh, mode);
                if (fd >= 0) {
                    PERL_DEB(dTHX;)
                    DEBUG_p(PerlIO_printf(Perl_debug_log,
@@ -2917,11 +3114,39 @@ win32_abort(void)
 DllExport int
 win32_fstat(int fd, Stat_t *sbufptr)
 {
-#if defined(WIN64) || defined(USE_LARGE_FILES)
-    return _fstati64(fd, sbufptr);
-#else
-    return fstat(fd, sbufptr);
-#endif
+    int result;
+    struct _stati64 st;
+    dTHX;
+    result = _fstati64(fd, &st);
+    if (result == 0) {
+        sbufptr->st_mode = st.st_mode;
+        sbufptr->st_uid = st.st_uid;
+        sbufptr->st_gid = st.st_gid;
+        sbufptr->st_rdev = st.st_rdev;
+        sbufptr->st_size = st.st_size;
+        sbufptr->st_atime = st.st_atime;
+        sbufptr->st_mtime = st.st_mtime;
+        sbufptr->st_ctime = st.st_ctime;
+
+        if (w32_sloppystat) {
+            sbufptr->st_nlink = st.st_nlink;
+            sbufptr->st_dev = st.st_dev;
+            sbufptr->st_ino = st.st_ino;
+        }
+        else {
+            HANDLE handle = (HANDLE)win32_get_osfhandle(fd);
+            BY_HANDLE_FILE_INFORMATION bhi;
+            if (GetFileInformationByHandle(handle, &bhi)) {
+                sbufptr->st_nlink = bhi.nNumberOfLinks;
+                sbufptr->st_ino = bhi.nFileIndexHigh;
+                sbufptr->st_ino <<= 32;
+                sbufptr->st_ino |= bhi.nFileIndexLow;
+                sbufptr->st_dev = bhi.dwVolumeSerialNumber;
+            }
+        }
+    }
+    
+    return result;
 }
 
 DllExport int
@@ -3009,23 +3234,20 @@ do_popen(const char *mode, const char *command, IV narg, SV **args) {
        }
        else {
            int i;
+           const char *exe_name;
 
            Newx(args_pvs, narg + 1 + w32_perlshell_items, const char *);
            SAVEFREEPV(args_pvs);
            for (i = 0; i < narg; ++i)
                args_pvs[i] = SvPV_nolen(args[i]);
            args_pvs[i] = NULL;
+           exe_name = qualified_path(args_pvs[0], TRUE);
+           if (!exe_name)
+               /* let CreateProcess() try to find it instead */
+               exe_name = args_pvs[0];
 
-           if ((childpid = do_spawnvp_handles(P_NOWAIT, args_pvs[0], args_pvs, handles)) == -1) {
-               if (errno == ENOEXEC || errno == ENOENT) {
-                   /* possible shell-builtin, invoke with shell */
-                   Move(args_pvs, args_pvs+w32_perlshell_items, narg+1, const char *);
-                   Copy(w32_perlshell_vec, args_pvs, w32_perlshell_items, const char *);
-                   if ((childpid = do_spawnvp_handles(P_NOWAIT, args_pvs[0], args_pvs, handles)) == -1)
-                       goto cleanup;
-               }
-               else
-                 goto cleanup;
+           if ((childpid = do_spawnvp_handles(P_NOWAIT, exe_name, args_pvs, handles)) == -1) {
+               goto cleanup;
            }
        }
 
@@ -3209,7 +3431,6 @@ win32_setmode(int fd, int mode)
 DllExport int
 win32_chsize(int fd, Off_t size)
 {
-#if defined(WIN64) || defined(USE_LARGE_FILES)
     int retval = 0;
     Off_t cur, end, extend;
 
@@ -3247,32 +3468,20 @@ win32_chsize(int fd, Off_t size)
            retval = -1;
        }
     }
-finish:
     win32_lseek(fd, cur, SEEK_SET);
     return retval;
-#else
-    return chsize(fd, (long)size);
-#endif
 }
 
 DllExport Off_t
 win32_lseek(int fd, Off_t offset, int origin)
 {
-#if defined(WIN64) || defined(USE_LARGE_FILES)
     return _lseeki64(fd, offset, origin);
-#else
-    return lseek(fd, (long)offset, origin);
-#endif
 }
 
 DllExport Off_t
 win32_tell(int fd)
 {
-#if defined(WIN64) || defined(USE_LARGE_FILES)
     return _telli64(fd);
-#else
-    return tell(fd);
-#endif
 }
 
 DllExport int
@@ -3374,7 +3583,7 @@ win32_rmdir(const char *dir)
 DllExport int
 win32_chdir(const char *dir)
 {
-    if (!dir) {
+    if (!dir || !*dir) {
        errno = ENOENT;
        return -1;
     }
@@ -3551,8 +3760,15 @@ create_command_line(char *cname, STRLEN clen, const char * const *args)
     return cmd;
 }
 
+static const char *exe_extensions[] =
+  {
+    ".exe", /* this must be first */
+    ".cmd",
+    ".bat"
+  };
+
 static char *
-qualified_path(const char *cmd)
+qualified_path(const char *cmd, bool other_exts)
 {
     char *pathstr;
     char *fullcmd, *curfullcmd;
@@ -3591,10 +3807,16 @@ qualified_path(const char *cmd)
        if (cmd[cmdlen-1] != '.'
            && (cmdlen < 4 || cmd[cmdlen-4] != '.'))
        {
-           strcpy(curfullcmd, ".exe");
-           res = GetFileAttributes(fullcmd);
-           if (res != 0xFFFFFFFF && !(res & FILE_ATTRIBUTE_DIRECTORY))
-               return fullcmd;
+           int i;
+           /* first extension is .exe */
+           int ext_limit = other_exts ? C_ARRAY_LENGTH(exe_extensions) : 1;
+           for (i = 0; i < ext_limit; ++i) {
+               strcpy(curfullcmd, exe_extensions[i]);
+               res = GetFileAttributes(fullcmd);
+               if (res != 0xFFFFFFFF && !(res & FILE_ATTRIBUTE_DIRECTORY))
+                   return fullcmd;
+           }
+
            *curfullcmd = '\0';
        }
 
@@ -3831,7 +4053,7 @@ RETRY:
         * jump through our own hoops by picking out the path
         * we really want it to use. */
        if (!fullcmd) {
-           fullcmd = qualified_path(cname);
+           fullcmd = qualified_path(cname, FALSE);
            if (fullcmd) {
                if (cname != cmdname)
                    Safefree(cname);
@@ -3855,7 +4077,7 @@ RETRY:
        w32_child_pids[w32_num_children] = (DWORD)ret;
        ++w32_num_children;
     }
-    else  {
+    else {
        DWORD status;
        win32_msgwait(aTHX_ 1, &ProcessInformation.hProcess, INFINITE, NULL);
        /* FIXME: if msgwait returned due to message perhaps forward the
@@ -4126,15 +4348,15 @@ win32_fdupopen(FILE *pf)
     int fileno = win32_dup(win32_fileno(pf));
 
     /* open the file in the same mode */
-    if((pf)->_flag & _IOREAD) {
+    if (PERLIO_FILE_flag(pf) & PERLIO_FILE_flag_RD) {
        mode[0] = 'r';
        mode[1] = 0;
     }
-    else if((pf)->_flag & _IOWRT) {
+    else if (PERLIO_FILE_flag(pf) & PERLIO_FILE_flag_WR) {
        mode[0] = 'a';
        mode[1] = 0;
     }
-    else if((pf)->_flag & _IORW) {
+    else if (PERLIO_FILE_flag(pf) & PERLIO_FILE_flag_RW) {
        mode[0] = 'r';
        mode[1] = '+';
        mode[2] = 0;
@@ -4205,6 +4427,35 @@ XS(w32_SetChildShowWindow)
     XSRETURN(1);
 }
 
+
+#ifdef PERL_IS_MINIPERL
+/* shelling out is much slower, full perl uses Win32.pm */
+XS(w32_GetCwd)
+{
+    dXSARGS;
+    /* Make the host for current directory */
+    char* ptr = PerlEnv_get_childdir();
+    /*
+     * If ptr != Nullch
+     *   then it worked, set PV valid,
+     *   else return 'undef'
+     */
+    if (ptr) {
+       SV *sv = sv_newmortal();
+       sv_setpv(sv, ptr);
+       PerlEnv_free_childdir(ptr);
+
+#ifndef INCOMPLETE_TAINTS
+       SvTAINTED_on(sv);
+#endif
+
+       ST(0) = sv;
+       XSRETURN(1);
+    }
+    XSRETURN_UNDEF;
+}
+#endif
+
 void
 Perl_init_os_extras(void)
 {
@@ -4226,6 +4477,9 @@ Perl_init_os_extras(void)
 #endif
 
     newXS("Win32::SetChildShowWindow", w32_SetChildShowWindow, file);
+#ifdef PERL_IS_MINIPERL
+    newXS("Win32::GetCwd", w32_GetCwd, file);
+#endif
 }
 
 void *
@@ -4439,6 +4693,20 @@ Perl_win32_init(int *argcp, char ***argvp)
 #endif
 
     ansify_path();
+
+#ifndef WIN32_NO_REGISTRY
+    {
+       LONG retval;
+       retval = RegOpenKeyExW(HKEY_CURRENT_USER, L"SOFTWARE\\Perl", 0, KEY_READ, &HKCU_Perl_hnd);
+       if (retval != ERROR_SUCCESS) {
+           HKCU_Perl_hnd = NULL;
+       }
+       retval = RegOpenKeyExW(HKEY_LOCAL_MACHINE, L"SOFTWARE\\Perl", 0, KEY_READ, &HKLM_Perl_hnd);
+       if (retval != ERROR_SUCCESS) {
+           HKLM_Perl_hnd = NULL;
+       }
+    }
+#endif
 }
 
 void
@@ -4448,6 +4716,15 @@ Perl_win32_term(void)
     OP_REFCNT_TERM;
     PERLIO_TERM;
     MALLOC_TERM;
+    LOCALE_TERM;
+    ENV_TERM;
+#ifndef WIN32_NO_REGISTRY
+    /* handles might be NULL, RegCloseKey then returns ERROR_INVALID_HANDLE
+       but no point of checking and we can't die() at this point */
+    RegCloseKey(HKLM_Perl_hnd);
+    RegCloseKey(HKCU_Perl_hnd);
+    /* the handles are in an undefined state until the next PERL_SYS_INIT3 */
+#endif
 }
 
 void
@@ -4629,6 +4906,11 @@ Perl_sys_intern_init(pTHX)
     w32_timerid                 = 0;
     w32_message_hwnd            = CAST_HWND__(INVALID_HANDLE_VALUE);
     w32_poll_count              = 0;
+#ifdef PERL_IS_MINIPERL
+    w32_sloppystat              = TRUE;
+#else
+    w32_sloppystat              = FALSE;
+#endif
     for (i=0; i < SIG_SIZE; i++) {
        w32_sighandler[i] = SIG_DFL;
     }
@@ -4657,6 +4939,7 @@ Perl_sys_intern_init(pTHX)
 void
 Perl_sys_intern_clear(pTHX)
 {
+
     Safefree(w32_perlshell_tokens);
     Safefree(w32_perlshell_vec);
     /* NOTE: w32_fdpid is freed by sv_clean_all() */
@@ -4696,6 +4979,7 @@ Perl_sys_intern_dup(pTHX_ struct interp_intern *src, struct interp_intern *dst)
     dst->timerid                = 0;
     dst->message_hwnd          = CAST_HWND__(INVALID_HANDLE_VALUE);
     dst->poll_count             = 0;
+    dst->sloppystat             = src->sloppystat;
     Copy(src->sigtable,dst->sigtable,SIG_SIZE,Sighandler_t);
 }
 #  endif /* USE_ITHREADS */