Commit 3e86c99f authored by Brian Curtin's avatar Brian Curtin

Merge from 3.2 for Issue #12084.

parents e67b1eab d25aef55
...@@ -1562,7 +1562,7 @@ def can_symlink(): ...@@ -1562,7 +1562,7 @@ def can_symlink():
os.symlink(TESTFN, symlink_path) os.symlink(TESTFN, symlink_path)
can = True can = True
os.remove(symlink_path) os.remove(symlink_path)
except (OSError, NotImplementedError): except (OSError, NotImplementedError, AttributeError):
can = False can = False
_can_symlink = can _can_symlink = can
return can return can
......
...@@ -1248,6 +1248,51 @@ class Win32SymlinkTests(unittest.TestCase): ...@@ -1248,6 +1248,51 @@ class Win32SymlinkTests(unittest.TestCase):
self.assertEqual(os.stat(link), os.stat(target)) self.assertEqual(os.stat(link), os.stat(target))
self.assertNotEqual(os.lstat(link), os.stat(link)) 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): class FSEncodingTests(unittest.TestCase):
def test_nop(self): def test_nop(self):
......
...@@ -10,6 +10,9 @@ What's New in Python 3.3 Alpha 1? ...@@ -10,6 +10,9 @@ What's New in Python 3.3 Alpha 1?
Core and Builtins 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 - Issue #12265: Make error messages produced by passing an invalid set of
arguments to a function more informative. arguments to a function more informative.
......
...@@ -509,14 +509,11 @@ typedef struct _REPARSE_DATA_BUFFER { ...@@ -509,14 +509,11 @@ typedef struct _REPARSE_DATA_BUFFER {
#define MAXIMUM_REPARSE_DATA_BUFFER_SIZE ( 16 * 1024 ) #define MAXIMUM_REPARSE_DATA_BUFFER_SIZE ( 16 * 1024 )
static int 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]; char target_buffer[MAXIMUM_REPARSE_DATA_BUFFER_SIZE];
REPARSE_DATA_BUFFER *rdb = (REPARSE_DATA_BUFFER *)target_buffer; REPARSE_DATA_BUFFER *rdb = (REPARSE_DATA_BUFFER *)target_buffer;
DWORD n_bytes_returned; DWORD n_bytes_returned;
const wchar_t *ptr;
wchar_t *buf;
size_t len;
if (0 == DeviceIoControl( if (0 == DeviceIoControl(
reparse_point_handle, reparse_point_handle,
...@@ -525,41 +522,12 @@ win32_read_link(HANDLE reparse_point_handle, ULONG *reparse_tag, wchar_t **targe ...@@ -525,41 +522,12 @@ win32_read_link(HANDLE reparse_point_handle, ULONG *reparse_tag, wchar_t **targe
target_buffer, sizeof(target_buffer), target_buffer, sizeof(target_buffer),
&n_bytes_returned, &n_bytes_returned,
NULL)) /* we're not using OVERLAPPED_IO */ NULL)) /* we're not using OVERLAPPED_IO */
return -1; return FALSE;
if (reparse_tag) if (reparse_tag)
*reparse_tag = rdb->ReparseTag; *reparse_tag = rdb->ReparseTag;
if (target_path) { return TRUE;
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;
} }
#endif /* MS_WINDOWS */ #endif /* MS_WINDOWS */
...@@ -1125,36 +1093,97 @@ attributes_from_dir_w(LPCWSTR pszFile, BY_HANDLE_FILE_INFORMATION *info, ULONG * ...@@ -1125,36 +1093,97 @@ attributes_from_dir_w(LPCWSTR pszFile, BY_HANDLE_FILE_INFORMATION *info, ULONG *
return TRUE; return TRUE;
} }
#ifndef SYMLOOP_MAX /* Grab GetFinalPathNameByHandle dynamically from kernel32 */
#define SYMLOOP_MAX ( 88 ) static int has_GetFinalPathNameByHandle = 0;
#endif static DWORD (CALLBACK *Py_GetFinalPathNameByHandleA)(HANDLE, LPSTR, DWORD,
DWORD);
static DWORD (CALLBACK *Py_GetFinalPathNameByHandleW)(HANDLE, LPWSTR, DWORD,
DWORD);
static int 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 static int
win32_xstat_impl(const char *path, struct win32_stat *result, BOOL traverse, int depth) 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; int code;
HANDLE hFile; HANDLE hFile, hFile2;
BY_HANDLE_FILE_INFORMATION info; BY_HANDLE_FILE_INFORMATION info;
ULONG reparse_tag = 0; ULONG reparse_tag = 0;
wchar_t *target_path; wchar_t *target_path;
const char *dot; const char *dot;
if (depth > SYMLOOP_MAX) { if(!check_GetFinalPathNameByHandle()) {
SetLastError(ERROR_CANT_RESOLVE_FILENAME); /* XXX: ELOOP? */ /* If the OS doesn't have GetFinalPathNameByHandle, return a
NotImplementedError. */
PyErr_SetString(PyExc_NotImplementedError,
"GetFinalPathNameByHandle not available on this platform");
return -1; return -1;
} }
hFile = CreateFileA( hFile = CreateFileA(
path, path,
0, /* desired access */ FILE_READ_ATTRIBUTES, /* desired access */
0, /* share mode */ 0, /* share mode */
NULL, /* security attributes */ NULL, /* security attributes */
OPEN_EXISTING, OPEN_EXISTING,
/* FILE_FLAG_BACKUP_SEMANTICS is required to open a directory */ /* 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); NULL);
if (hFile == INVALID_HANDLE_VALUE) { if (hFile == INVALID_HANDLE_VALUE) {
...@@ -1178,15 +1207,32 @@ win32_xstat_impl(const char *path, struct win32_stat *result, BOOL traverse, int ...@@ -1178,15 +1207,32 @@ win32_xstat_impl(const char *path, struct win32_stat *result, BOOL traverse, int
} else { } else {
if (!GetFileInformationByHandle(hFile, &info)) { if (!GetFileInformationByHandle(hFile, &info)) {
CloseHandle(hFile); CloseHandle(hFile);
return -1;; return -1;
} }
if (info.dwFileAttributes & FILE_ATTRIBUTE_REPARSE_POINT) { if (info.dwFileAttributes & FILE_ATTRIBUTE_REPARSE_POINT) {
code = win32_read_link(hFile, &reparse_tag, traverse ? &target_path : NULL); if (!win32_get_reparse_tag(hFile, &reparse_tag))
CloseHandle(hFile); return -1;
if (code < 0)
return code; /* Close the outer open file handle now that we're about to
reopen it with different flags. */
if (!CloseHandle(hFile))
return -1;
if (traverse) { 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); free(target_path);
return code; return code;
} }
...@@ -1206,28 +1252,36 @@ win32_xstat_impl(const char *path, struct win32_stat *result, BOOL traverse, int ...@@ -1206,28 +1252,36 @@ win32_xstat_impl(const char *path, struct win32_stat *result, BOOL traverse, int
} }
static int 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; int code;
HANDLE hFile; HANDLE hFile, hFile2;
BY_HANDLE_FILE_INFORMATION info; BY_HANDLE_FILE_INFORMATION info;
ULONG reparse_tag = 0; ULONG reparse_tag = 0;
wchar_t *target_path; wchar_t *target_path;
const wchar_t *dot; const wchar_t *dot;
if (depth > SYMLOOP_MAX) { if(!check_GetFinalPathNameByHandle()) {
SetLastError(ERROR_CANT_RESOLVE_FILENAME); /* XXX: ELOOP? */ /* If the OS doesn't have GetFinalPathNameByHandle, return a
NotImplementedError. */
PyErr_SetString(PyExc_NotImplementedError,
"GetFinalPathNameByHandle not available on this platform");
return -1; return -1;
} }
hFile = CreateFileW( hFile = CreateFileW(
path, path,
0, /* desired access */ FILE_READ_ATTRIBUTES, /* desired access */
0, /* share mode */ 0, /* share mode */
NULL, /* security attributes */ NULL, /* security attributes */
OPEN_EXISTING, OPEN_EXISTING,
/* FILE_FLAG_BACKUP_SEMANTICS is required to open a directory */ /* 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); NULL);
if (hFile == INVALID_HANDLE_VALUE) { if (hFile == INVALID_HANDLE_VALUE) {
...@@ -1251,15 +1305,32 @@ win32_xstat_impl_w(const wchar_t *path, struct win32_stat *result, BOOL traverse ...@@ -1251,15 +1305,32 @@ win32_xstat_impl_w(const wchar_t *path, struct win32_stat *result, BOOL traverse
} else { } else {
if (!GetFileInformationByHandle(hFile, &info)) { if (!GetFileInformationByHandle(hFile, &info)) {
CloseHandle(hFile); CloseHandle(hFile);
return -1;; return -1;
} }
if (info.dwFileAttributes & FILE_ATTRIBUTE_REPARSE_POINT) { if (info.dwFileAttributes & FILE_ATTRIBUTE_REPARSE_POINT) {
code = win32_read_link(hFile, &reparse_tag, traverse ? &target_path : NULL); if (!win32_get_reparse_tag(hFile, &reparse_tag))
CloseHandle(hFile); return -1;
if (code < 0)
return code; /* Close the outer open file handle now that we're about to
reopen it with different flags. */
if (!CloseHandle(hFile))
return -1;
if (traverse) { 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); free(target_path);
return code; return code;
} }
...@@ -1283,7 +1354,7 @@ win32_xstat(const char *path, struct win32_stat *result, BOOL traverse) ...@@ -1283,7 +1354,7 @@ win32_xstat(const char *path, struct win32_stat *result, BOOL traverse)
{ {
/* Protocol violation: we explicitly clear errno, instead of /* Protocol violation: we explicitly clear errno, instead of
setting it to a POSIX error. Callers should use GetLastError. */ 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; errno = 0;
return code; return code;
} }
...@@ -1293,13 +1364,11 @@ win32_xstat_w(const wchar_t *path, struct win32_stat *result, BOOL traverse) ...@@ -1293,13 +1364,11 @@ win32_xstat_w(const wchar_t *path, struct win32_stat *result, BOOL traverse)
{ {
/* Protocol violation: we explicitly clear errno, instead of /* Protocol violation: we explicitly clear errno, instead of
setting it to a POSIX error. Callers should use GetLastError. */ 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; errno = 0;
return code; return code;
} }
/* About the following functions: win32_lstat_w, win32_stat, win32_stat_w
/* About the following functions: win32_lstat, win32_lstat_w, win32_stat,
win32_stat_w
In Posix, stat automatically traverses symlinks and returns the stat In Posix, stat automatically traverses symlinks and returns the stat
structure for the target. In Windows, the equivalent GetFileAttributes by structure for the target. In Windows, the equivalent GetFileAttributes by
...@@ -2848,29 +2917,7 @@ posix__getfullpathname(PyObject *self, PyObject *args) ...@@ -2848,29 +2917,7 @@ posix__getfullpathname(PyObject *self, PyObject *args)
return PyBytes_FromString(outbuf); return PyBytes_FromString(outbuf);
} /* end of posix__getfullpathname */ } /* 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 */ /* A helper function for samepath on windows */
static PyObject * static PyObject *
...@@ -5507,7 +5554,7 @@ posix_lstat(PyObject *self, PyObject *args) ...@@ -5507,7 +5554,7 @@ posix_lstat(PyObject *self, PyObject *args)
return posix_do_stat(self, args, "O&:lstat", lstat, NULL, NULL); return posix_do_stat(self, args, "O&:lstat", lstat, NULL, NULL);
#else /* !HAVE_LSTAT */ #else /* !HAVE_LSTAT */
#ifdef MS_WINDOWS #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); win32_lstat_w);
#else #else
return posix_do_stat(self, args, "O&:lstat", STAT, NULL, NULL); return posix_do_stat(self, args, "O&:lstat", STAT, NULL, NULL);
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment