diff -r 1a1522f612b0 Lib/test/support.py --- a/Lib/test/support.py Mon Jun 06 17:11:47 2011 +0200 +++ b/Lib/test/support.py Mon Jun 13 11:38:47 2011 -0500 @@ -1490,7 +1490,7 @@ try: os.symlink(TESTFN, TESTFN + "can_symlink") can = True - except (OSError, NotImplementedError): + except (OSError, NotImplementedError, AttributeError): can = False _can_symlink = can return can diff -r 1a1522f612b0 Lib/test/test_os.py --- a/Lib/test/test_os.py Mon Jun 06 17:11:47 2011 +0200 +++ b/Lib/test/test_os.py Mon Jun 13 11:38:47 2011 -0500 @@ -1243,6 +1243,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 -r 1a1522f612b0 Modules/posixmodule.c --- a/Modules/posixmodule.c Mon Jun 06 17:11:47 2011 +0200 +++ b/Modules/posixmodule.c Mon Jun 13 11:38:47 2011 -0500 @@ -480,14 +480,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, @@ -496,41 +493,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 */ @@ -1096,36 +1064,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) { @@ -1149,15 +1178,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; } @@ -1177,28 +1223,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) { @@ -1222,15 +1276,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; } @@ -1254,7 +1325,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; } @@ -1264,13 +1335,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 @@ -1283,7 +1352,7 @@ The _w represent Unicode equivalents of the aforementioned ANSI functions. */ -static int +static int win32_lstat(const char* path, struct win32_stat *result) { return win32_xstat(path, result, FALSE); @@ -2707,29 +2776,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 * @@ -5068,7 +5115,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);