Merge from 3.2 for Issue #12084.
This commit is contained in:
commit
3e86c99f90
|
@ -1562,7 +1562,7 @@ def can_symlink():
|
|||
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
|
||||
|
|
|
@ -1248,6 +1248,51 @@ class Win32SymlinkTests(unittest.TestCase):
|
|||
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):
|
||||
|
|
|
@ -10,6 +10,9 @@ What's New in Python 3.3 Alpha 1?
|
|||
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.
|
||||
|
||||
|
|
|
@ -509,14 +509,11 @@ typedef struct _REPARSE_DATA_BUFFER {
|
|||
#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 @@ win32_read_link(HANDLE reparse_point_handle, ULONG *reparse_tag, wchar_t **targe
|
|||
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 @@ attributes_from_dir_w(LPCWSTR pszFile, BY_HANDLE_FILE_INFORMATION *info, ULONG *
|
|||
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);
|
||||
|
||||
static int
|
||||
win32_xstat_impl(const char *path, struct win32_stat *result, BOOL traverse, int depth)
|
||||
check_GetFinalPathNameByHandle()
|
||||
{
|
||||
int code;
|
||||
HANDLE hFile;
|
||||
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_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 @@ win32_xstat_impl(const char *path, struct win32_stat *result, BOOL traverse, int
|
|||
} 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 @@ win32_xstat_impl(const char *path, struct win32_stat *result, BOOL traverse, 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;
|
||||
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 @@ win32_xstat_impl_w(const wchar_t *path, struct win32_stat *result, BOOL traverse
|
|||
} 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 @@ win32_xstat(const char *path, struct win32_stat *result, BOOL traverse)
|
|||
{
|
||||
/* 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 @@ win32_xstat_w(const wchar_t *path, struct win32_stat *result, BOOL traverse)
|
|||
{
|
||||
/* 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 @@ posix__getfullpathname(PyObject *self, PyObject *args)
|
|||
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 @@ posix_lstat(PyObject *self, PyObject *args)
|
|||
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);
|
||||
|
|
Loading…
Reference in New Issue