Merge from 3.2 for Issue #12084.

This commit is contained in:
Brian Curtin 2011-06-13 16:00:35 -05:00
commit 3e86c99f90
4 changed files with 193 additions and 98 deletions

View File

@ -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

View File

@ -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):

View File

@ -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.

View File

@ -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);