[Python-checkins] cpython (merge 3.2 -> default): Merge from 3.2 for Issue #12084.

brian.curtin python-checkins at python.org
Mon Jun 13 23:10:52 CEST 2011


http://hg.python.org/cpython/rev/c04c55afbf81
changeset:   70812:c04c55afbf81
parent:      70688:d8502fee4638
parent:      70811:1a3e8db28d49
user:        Brian Curtin <brian at python.org>
date:        Mon Jun 13 16:00:35 2011 -0500
summary:
  Merge from 3.2 for Issue #12084.

files:
  Lib/test/support.py   |    2 +-
  Lib/test/test_os.py   |   45 +++++
  Misc/NEWS             |    3 +
  Modules/posixmodule.c |  239 +++++++++++++++++------------
  4 files changed, 192 insertions(+), 97 deletions(-)


diff --git a/Lib/test/support.py b/Lib/test/support.py
--- a/Lib/test/support.py
+++ b/Lib/test/support.py
@@ -1562,7 +1562,7 @@
         os.symlink(TESTFN, symlink_path)
         can = True
         os.remove(symlink_path)
-    except (OSError, NotImplementedError):
+    except (OSError, NotImplementedError, AttributeError):
         can = False
     _can_symlink = can
     return can
diff --git a/Lib/test/test_os.py b/Lib/test/test_os.py
--- a/Lib/test/test_os.py
+++ b/Lib/test/test_os.py
@@ -1248,6 +1248,51 @@
         self.assertEqual(os.stat(link), os.stat(target))
         self.assertNotEqual(os.lstat(link), os.stat(link))
 
+        bytes_link = os.fsencode(link)
+        self.assertEqual(os.stat(bytes_link), os.stat(target))
+        self.assertNotEqual(os.lstat(bytes_link), os.stat(bytes_link))
+
+    def test_12084(self):
+        level1 = os.path.abspath(support.TESTFN)
+        level2 = os.path.join(level1, "level2")
+        level3 = os.path.join(level2, "level3")
+        try:
+            os.mkdir(level1)
+            os.mkdir(level2)
+            os.mkdir(level3)
+
+            file1 = os.path.abspath(os.path.join(level1, "file1"))
+
+            with open(file1, "w") as f:
+                f.write("file1")
+
+            orig_dir = os.getcwd()
+            try:
+                os.chdir(level2)
+                link = os.path.join(level2, "link")
+                os.symlink(os.path.relpath(file1), "link")
+                self.assertIn("link", os.listdir(os.getcwd()))
+
+                # Check os.stat calls from the same dir as the link
+                self.assertEqual(os.stat(file1), os.stat("link"))
+
+                # Check os.stat calls from a dir below the link
+                os.chdir(level1)
+                self.assertEqual(os.stat(file1),
+                                 os.stat(os.path.relpath(link)))
+
+                # Check os.stat calls from a dir above the link
+                os.chdir(level3)
+                self.assertEqual(os.stat(file1),
+                                 os.stat(os.path.relpath(link)))
+            finally:
+                os.chdir(orig_dir)
+        except OSError as err:
+            self.fail(err)
+        finally:
+            os.remove(file1)
+            shutil.rmtree(level1)
+
 
 class FSEncodingTests(unittest.TestCase):
     def test_nop(self):
diff --git a/Misc/NEWS b/Misc/NEWS
--- a/Misc/NEWS
+++ b/Misc/NEWS
@@ -10,6 +10,9 @@
 Core and Builtins
 -----------------
 
+- Issue #12084: os.stat on Windows now works properly with relative symbolic
+  links when called from any directory.
+
 - Issue #12265: Make error messages produced by passing an invalid set of
   arguments to a function more informative.
 
diff --git a/Modules/posixmodule.c b/Modules/posixmodule.c
--- a/Modules/posixmodule.c
+++ b/Modules/posixmodule.c
@@ -509,14 +509,11 @@
 #define MAXIMUM_REPARSE_DATA_BUFFER_SIZE  ( 16 * 1024 )
 
 static int
-win32_read_link(HANDLE reparse_point_handle, ULONG *reparse_tag, wchar_t **target_path)
+win32_get_reparse_tag(HANDLE reparse_point_handle, ULONG *reparse_tag)
 {
     char target_buffer[MAXIMUM_REPARSE_DATA_BUFFER_SIZE];
     REPARSE_DATA_BUFFER *rdb = (REPARSE_DATA_BUFFER *)target_buffer;
     DWORD n_bytes_returned;
-    const wchar_t *ptr;
-    wchar_t *buf;
-    size_t len;
 
     if (0 == DeviceIoControl(
         reparse_point_handle,
@@ -525,41 +522,12 @@
         target_buffer, sizeof(target_buffer),
         &n_bytes_returned,
         NULL)) /* we're not using OVERLAPPED_IO */
-        return -1;
+        return FALSE;
 
     if (reparse_tag)
         *reparse_tag = rdb->ReparseTag;
 
-    if (target_path) {
-        switch (rdb->ReparseTag) {
-        case IO_REPARSE_TAG_SYMLINK:
-            /* XXX: Maybe should use SubstituteName? */
-            ptr = rdb->SymbolicLinkReparseBuffer.PathBuffer +
-                  rdb->SymbolicLinkReparseBuffer.PrintNameOffset/sizeof(WCHAR);
-            len = rdb->SymbolicLinkReparseBuffer.PrintNameLength/sizeof(WCHAR);
-            break;
-        case IO_REPARSE_TAG_MOUNT_POINT:
-            ptr = rdb->MountPointReparseBuffer.PathBuffer +
-                  rdb->MountPointReparseBuffer.SubstituteNameOffset/sizeof(WCHAR);
-            len = rdb->MountPointReparseBuffer.SubstituteNameLength/sizeof(WCHAR);
-            break;
-        default:
-            SetLastError(ERROR_REPARSE_TAG_MISMATCH); /* XXX: Proper error code? */
-            return -1;
-        }
-        buf = (wchar_t *)malloc(sizeof(wchar_t)*(len+1));
-        if (!buf) {
-            SetLastError(ERROR_OUTOFMEMORY);
-            return -1;
-        }
-        wcsncpy(buf, ptr, len);
-        buf[len] = L'\0';
-        if (wcsncmp(buf, L"\\??\\", 4) == 0)
-            buf[1] = L'\\';
-        *target_path = buf;
-    }
-
-    return 0;
+    return TRUE;
 }
 #endif /* MS_WINDOWS */
 
@@ -1125,36 +1093,97 @@
     return TRUE;
 }
 
-#ifndef SYMLOOP_MAX
-#define SYMLOOP_MAX ( 88 )
-#endif
-
+/* Grab GetFinalPathNameByHandle dynamically from kernel32 */
+static int has_GetFinalPathNameByHandle = 0;
+static DWORD (CALLBACK *Py_GetFinalPathNameByHandleA)(HANDLE, LPSTR, DWORD,
+                                                      DWORD);
+static DWORD (CALLBACK *Py_GetFinalPathNameByHandleW)(HANDLE, LPWSTR, DWORD,
+                                                      DWORD);
 static int
-win32_xstat_impl_w(const wchar_t *path, struct win32_stat *result, BOOL traverse, int depth);
+check_GetFinalPathNameByHandle()
+{
+    HINSTANCE hKernel32;
+    /* only recheck */
+    if (!has_GetFinalPathNameByHandle)
+    {
+        hKernel32 = GetModuleHandle("KERNEL32");
+        *(FARPROC*)&Py_GetFinalPathNameByHandleA = GetProcAddress(hKernel32,
+                                                "GetFinalPathNameByHandleA");
+        *(FARPROC*)&Py_GetFinalPathNameByHandleW = GetProcAddress(hKernel32,
+                                                "GetFinalPathNameByHandleW");
+        has_GetFinalPathNameByHandle = Py_GetFinalPathNameByHandleA &&
+                                       Py_GetFinalPathNameByHandleW;
+    }
+    return has_GetFinalPathNameByHandle;
+}
+
+static BOOL
+get_target_path(HANDLE hdl, wchar_t **target_path)
+{
+    int buf_size, result_length;
+    wchar_t *buf;
+
+    /* We have a good handle to the target, use it to determine
+       the target path name (then we'll call lstat on it). */
+    buf_size = Py_GetFinalPathNameByHandleW(hdl, 0, 0,
+                                            VOLUME_NAME_DOS);
+    if(!buf_size)
+        return FALSE;
+
+    buf = (wchar_t *)malloc((buf_size+1)*sizeof(wchar_t));
+    result_length = Py_GetFinalPathNameByHandleW(hdl,
+                       buf, buf_size, VOLUME_NAME_DOS);
+
+    if(!result_length) {
+        free(buf);
+        return FALSE;
+    }
+
+    if(!CloseHandle(hdl)) {
+        free(buf);
+        return FALSE;
+    }
+
+    buf[result_length] = 0;
+
+    *target_path = buf;
+    return TRUE;
+}
 
 static int
-win32_xstat_impl(const char *path, struct win32_stat *result, BOOL traverse, int depth)
-{
-    int code;
-    HANDLE hFile;
+win32_xstat_impl_w(const wchar_t *path, struct win32_stat *result,
+                   BOOL traverse);
+static int
+win32_xstat_impl(const char *path, struct win32_stat *result,
+                 BOOL traverse)
+{
+    int code; 
+    HANDLE hFile, hFile2;
     BY_HANDLE_FILE_INFORMATION info;
     ULONG reparse_tag = 0;
-    wchar_t *target_path;
+	wchar_t *target_path;
     const char *dot;
 
-    if (depth > SYMLOOP_MAX) {
-        SetLastError(ERROR_CANT_RESOLVE_FILENAME); /* XXX: ELOOP? */
+    if(!check_GetFinalPathNameByHandle()) {
+        /* If the OS doesn't have GetFinalPathNameByHandle, return a
+           NotImplementedError. */
+        PyErr_SetString(PyExc_NotImplementedError,
+            "GetFinalPathNameByHandle not available on this platform");
         return -1;
     }
 
     hFile = CreateFileA(
         path,
-        0, /* desired access */
+        FILE_READ_ATTRIBUTES, /* desired access */
         0, /* share mode */
         NULL, /* security attributes */
         OPEN_EXISTING,
         /* FILE_FLAG_BACKUP_SEMANTICS is required to open a directory */
-        FILE_ATTRIBUTE_NORMAL|FILE_FLAG_BACKUP_SEMANTICS|FILE_FLAG_OPEN_REPARSE_POINT,
+        /* FILE_FLAG_OPEN_REPARSE_POINT does not follow the symlink.
+           Because of this, calls like GetFinalPathNameByHandle will return
+           the symlink path agin and not the actual final path. */
+        FILE_ATTRIBUTE_NORMAL|FILE_FLAG_BACKUP_SEMANTICS|
+            FILE_FLAG_OPEN_REPARSE_POINT,
         NULL);
 
     if (hFile == INVALID_HANDLE_VALUE) {
@@ -1178,15 +1207,32 @@
     } else {
         if (!GetFileInformationByHandle(hFile, &info)) {
             CloseHandle(hFile);
-            return -1;;
+            return -1;
         }
         if (info.dwFileAttributes & FILE_ATTRIBUTE_REPARSE_POINT) {
-            code = win32_read_link(hFile, &reparse_tag, traverse ? &target_path : NULL);
-            CloseHandle(hFile);
-            if (code < 0)
-                return code;
+            if (!win32_get_reparse_tag(hFile, &reparse_tag))
+                return -1;
+
+            /* Close the outer open file handle now that we're about to
+               reopen it with different flags. */
+            if (!CloseHandle(hFile))
+                return -1;
+
             if (traverse) {
-                code = win32_xstat_impl_w(target_path, result, traverse, depth + 1);
+                /* In order to call GetFinalPathNameByHandle we need to open
+                   the file without the reparse handling flag set. */
+                hFile2 = CreateFileA(
+                           path, FILE_READ_ATTRIBUTES, FILE_SHARE_READ,
+                           NULL, OPEN_EXISTING,
+                           FILE_ATTRIBUTE_NORMAL|FILE_FLAG_BACKUP_SEMANTICS,
+                           NULL);
+                if (hFile2 == INVALID_HANDLE_VALUE)
+                    return -1;
+
+                if (!get_target_path(hFile2, &target_path))
+                    return -1;
+
+                code = win32_xstat_impl_w(target_path, result, FALSE);
                 free(target_path);
                 return code;
             }
@@ -1206,28 +1252,36 @@
 }
 
 static int
-win32_xstat_impl_w(const wchar_t *path, struct win32_stat *result, BOOL traverse, int depth)
+win32_xstat_impl_w(const wchar_t *path, struct win32_stat *result,
+                   BOOL traverse)
 {
     int code;
-    HANDLE hFile;
+    HANDLE hFile, hFile2;
     BY_HANDLE_FILE_INFORMATION info;
     ULONG reparse_tag = 0;
 	wchar_t *target_path;
     const wchar_t *dot;
 
-    if (depth > SYMLOOP_MAX) {
-        SetLastError(ERROR_CANT_RESOLVE_FILENAME); /* XXX: ELOOP? */
+    if(!check_GetFinalPathNameByHandle()) {
+        /* If the OS doesn't have GetFinalPathNameByHandle, return a
+           NotImplementedError. */
+        PyErr_SetString(PyExc_NotImplementedError,
+            "GetFinalPathNameByHandle not available on this platform");
         return -1;
     }
 
     hFile = CreateFileW(
         path,
-        0, /* desired access */
+        FILE_READ_ATTRIBUTES, /* desired access */
         0, /* share mode */
         NULL, /* security attributes */
         OPEN_EXISTING,
         /* FILE_FLAG_BACKUP_SEMANTICS is required to open a directory */
-        FILE_ATTRIBUTE_NORMAL|FILE_FLAG_BACKUP_SEMANTICS|FILE_FLAG_OPEN_REPARSE_POINT,
+        /* FILE_FLAG_OPEN_REPARSE_POINT does not follow the symlink.
+           Because of this, calls like GetFinalPathNameByHandle will return
+           the symlink path agin and not the actual final path. */
+        FILE_ATTRIBUTE_NORMAL|FILE_FLAG_BACKUP_SEMANTICS| 
+            FILE_FLAG_OPEN_REPARSE_POINT,
         NULL);
 
     if (hFile == INVALID_HANDLE_VALUE) {
@@ -1251,15 +1305,32 @@
     } else {
         if (!GetFileInformationByHandle(hFile, &info)) {
             CloseHandle(hFile);
-            return -1;;
+            return -1;
         }
         if (info.dwFileAttributes & FILE_ATTRIBUTE_REPARSE_POINT) {
-            code = win32_read_link(hFile, &reparse_tag, traverse ? &target_path : NULL);
-            CloseHandle(hFile);
-            if (code < 0)
-                return code;
+            if (!win32_get_reparse_tag(hFile, &reparse_tag))
+                return -1;
+
+            /* Close the outer open file handle now that we're about to
+               reopen it with different flags. */
+            if (!CloseHandle(hFile))
+                return -1;
+
             if (traverse) {
-                code = win32_xstat_impl_w(target_path, result, traverse, depth + 1);
+                /* In order to call GetFinalPathNameByHandle we need to open
+                   the file without the reparse handling flag set. */
+                hFile2 = CreateFileW(
+                           path, FILE_READ_ATTRIBUTES, FILE_SHARE_READ,
+                           NULL, OPEN_EXISTING,
+                           FILE_ATTRIBUTE_NORMAL|FILE_FLAG_BACKUP_SEMANTICS,
+                           NULL);
+                if (hFile2 == INVALID_HANDLE_VALUE)
+                    return -1;
+
+                if (!get_target_path(hFile2, &target_path))
+                    return -1;
+
+                code = win32_xstat_impl_w(target_path, result, FALSE);
                 free(target_path);
                 return code;
             }
@@ -1283,7 +1354,7 @@
 {
     /* Protocol violation: we explicitly clear errno, instead of
        setting it to a POSIX error. Callers should use GetLastError. */
-    int code = win32_xstat_impl(path, result, traverse, 0);
+    int code = win32_xstat_impl(path, result, traverse);
     errno = 0;
     return code;
 }
@@ -1293,13 +1364,11 @@
 {
     /* Protocol violation: we explicitly clear errno, instead of
        setting it to a POSIX error. Callers should use GetLastError. */
-    int code = win32_xstat_impl_w(path, result, traverse, 0);
+    int code = win32_xstat_impl_w(path, result, traverse);
     errno = 0;
     return code;
 }
-
-/* About the following functions: win32_lstat, win32_lstat_w, win32_stat,
-   win32_stat_w
+/* About the following functions: win32_lstat_w, win32_stat, win32_stat_w
 
    In Posix, stat automatically traverses symlinks and returns the stat
    structure for the target.  In Windows, the equivalent GetFileAttributes by
@@ -2848,29 +2917,7 @@
     return PyBytes_FromString(outbuf);
 } /* end of posix__getfullpathname */
 
-/* Grab GetFinalPathNameByHandle dynamically from kernel32 */
-static int has_GetFinalPathNameByHandle = 0;
-static DWORD (CALLBACK *Py_GetFinalPathNameByHandleA)(HANDLE, LPSTR, DWORD,
-                                                      DWORD);
-static DWORD (CALLBACK *Py_GetFinalPathNameByHandleW)(HANDLE, LPWSTR, DWORD,
-                                                      DWORD);
-static int
-check_GetFinalPathNameByHandle()
-{
-    HINSTANCE hKernel32;
-    /* only recheck */
-    if (!has_GetFinalPathNameByHandle)
-    {
-        hKernel32 = GetModuleHandle("KERNEL32");
-        *(FARPROC*)&Py_GetFinalPathNameByHandleA = GetProcAddress(hKernel32,
-                                                "GetFinalPathNameByHandleA");
-        *(FARPROC*)&Py_GetFinalPathNameByHandleW = GetProcAddress(hKernel32,
-                                                "GetFinalPathNameByHandleW");
-        has_GetFinalPathNameByHandle = Py_GetFinalPathNameByHandleA &&
-                                       Py_GetFinalPathNameByHandleW;
-    }
-    return has_GetFinalPathNameByHandle;
-}
+
 
 /* A helper function for samepath on windows */
 static PyObject *
@@ -5507,7 +5554,7 @@
     return posix_do_stat(self, args, "O&:lstat", lstat, NULL, NULL);
 #else /* !HAVE_LSTAT */
 #ifdef MS_WINDOWS
-    return posix_do_stat(self, args, "O&:lstat", STAT, "U:lstat",
+    return posix_do_stat(self, args, "O&:lstat", win32_lstat, "U:lstat",
                          win32_lstat_w);
 #else
     return posix_do_stat(self, args, "O&:lstat", STAT, NULL, NULL);

-- 
Repository URL: http://hg.python.org/cpython


More information about the Python-checkins mailing list