Issue #13772: Restored directory detection of targets in os.symlink on Windows, which was temporarily removed in Python 3.2.3 due to an incomplete implementation. The implementation now works even if the symlink is created in a location other than the current directory.

This commit is contained in:
Jason R. Coombs 2013-05-27 23:21:28 -04:00
parent db4e5c53c9
commit 3a09286790
4 changed files with 173 additions and 13 deletions

View file

@ -2023,9 +2023,10 @@ features:
Create a symbolic link pointing to *source* named *link_name*.
On Windows, a symlink represents either a file or a directory, and does not
morph to the target dynamically. If *target_is_directory* is set to ``True``,
the symlink will be created as a directory symlink, otherwise as a file symlink
(the default). On non-Window platforms, *target_is_directory* is ignored.
morph to the target dynamically. If the target is present, the type of the
symlink will be created to match. Otherwise, the symlink will be created
as a directory if *target_is_directory* is ``True`` or a file symlink (the
default) otherwise. On non-Window platforms, *target_is_directory* is ignored.
Symbolic link support was introduced in Windows 6.0 (Vista). :func:`symlink`
will raise a :exc:`NotImplementedError` on Windows versions earlier than 6.0.
@ -2041,6 +2042,7 @@ features:
to the administrator level. Either obtaining the privilege or running your
application as an administrator are ways to successfully create symlinks.
:exc:`OSError` is raised when the function is called by an unprivileged
user.

View file

@ -686,13 +686,8 @@ class WalkTests(unittest.TestCase):
f.write("I'm " + path + " and proud of it. Blame test_os.\n")
f.close()
if support.can_symlink():
if os.name == 'nt':
def symlink_to_dir(src, dest):
os.symlink(src, dest, True)
else:
symlink_to_dir = os.symlink
symlink_to_dir(os.path.abspath(t2_path), link_path)
symlink_to_dir('broken', broken_link_path)
os.symlink(os.path.abspath(t2_path), link_path)
symlink_to_dir('broken', broken_link_path, True)
sub2_tree = (sub2_path, ["link"], ["broken_link", "tmp3"])
else:
sub2_tree = (sub2_path, [], ["tmp3"])
@ -1516,7 +1511,7 @@ class Win32SymlinkTests(unittest.TestCase):
os.remove(self.missing_link)
def test_directory_link(self):
os.symlink(self.dirlink_target, self.dirlink, True)
os.symlink(self.dirlink_target, self.dirlink)
self.assertTrue(os.path.exists(self.dirlink))
self.assertTrue(os.path.isdir(self.dirlink))
self.assertTrue(os.path.islink(self.dirlink))
@ -1610,6 +1605,38 @@ class Win32SymlinkTests(unittest.TestCase):
shutil.rmtree(level1)
@support.skip_unless_symlink
class NonLocalSymlinkTests(unittest.TestCase):
def setUp(self):
"""
Create this structure:
base
\___ some_dir
"""
os.makedirs('base/some_dir')
def tearDown(self):
shutil.rmtree('base')
def test_directory_link_nonlocal(self):
"""
The symlink target should resolve relative to the link, not relative
to the current directory.
Then, link base/some_link -> base/some_dir and ensure that some_link
is resolved as a directory.
In issue13772, it was discovered that directory detection failed if
the symlink target was not specified relative to the current
directory, which was a defect in the implementation.
"""
src = os.path.join('base', 'some_link')
os.symlink('some_dir', src)
assert os.path.isdir(src)
class FSEncodingTests(unittest.TestCase):
def test_nop(self):
self.assertEqual(os.fsencode(b'abc\xff'), b'abc\xff')
@ -2137,6 +2164,7 @@ def test_main():
Pep383Tests,
Win32KillTests,
Win32SymlinkTests,
NonLocalSymlinkTests,
FSEncodingTests,
DeviceEncodingTests,
PidTests,

View file

@ -24,6 +24,11 @@ Core and Builtins
Library
-------
- Issue #13772: Restored directory detection of targets in ``os.symlink`` on
Windows, which was temporarily removed in Python 3.2.3 due to an incomplete
implementation. The implementation now works even if the symlink is created
in a location other than the current directory.
- Issue #16986: ElementTree now correctly parses a string input not only when
an internal XML encoding is UTF-8 or US-ASCII.

View file

@ -7200,6 +7200,124 @@ check_CreateSymbolicLink()
return (Py_CreateSymbolicLinkW && Py_CreateSymbolicLinkA);
}
void _dirnameW(WCHAR *path) {
/* Remove the last portion of the path */
WCHAR *ptr;
/* walk the path from the end until a backslash is encountered */
for(ptr = path + wcslen(path); ptr != path; ptr--)
{
if(*ptr == *L"\\" || *ptr == *L"/") {
break;
}
}
*ptr = 0;
}
void _dirnameA(char *path) {
/* Remove the last portion of the path */
char *ptr;
/* walk the path from the end until a backslash is encountered */
for(ptr = path + strlen(path); ptr != path; ptr--)
{
if(*ptr == '\\' || *ptr == '/') {
break;
}
}
*ptr = 0;
}
int _is_absW(WCHAR *path) {
/* Is this path absolute? */
return path[0] == L'\\' || path[0] == L'/' || path[1] == L':';
}
int _is_absA(char *path) {
/* Is this path absolute? */
return path[0] == '\\' || path[0] == '/' || path[1] == ':';
}
void _joinW(WCHAR *dest_path, const WCHAR *root, const WCHAR *rest) {
/* join root and rest with a backslash */
int root_len;
if(_is_absW(rest)) {
wcscpy(dest_path, rest);
return;
}
root_len = wcslen(root);
wcscpy(dest_path, root);
if(root_len) {
dest_path[root_len] = *L"\\";
root_len += 1;
}
wcscpy(dest_path+root_len, rest);
}
void _joinA(char *dest_path, const char *root, const char *rest) {
/* join root and rest with a backslash */
int root_len;
if(_is_absA(rest)) {
strcpy(dest_path, rest);
return;
}
root_len = strlen(root);
strcpy(dest_path, root);
if(root_len) {
dest_path[root_len] = '\\';
root_len += 1;
}
strcpy(dest_path+root_len, rest);
}
int _check_dirW(WCHAR *src, WCHAR *dest)
{
/* Return True if the path at src relative to dest is a directory */
WIN32_FILE_ATTRIBUTE_DATA src_info;
WCHAR dest_parent[MAX_PATH];
WCHAR src_resolved[MAX_PATH] = L"";
/* dest_parent = os.path.dirname(dest) */
wcscpy(dest_parent, dest);
_dirnameW(dest_parent);
/* src_resolved = os.path.join(dest_parent, src) */
_joinW(src_resolved, dest_parent, src);
return (
GetFileAttributesExW(src_resolved, GetFileExInfoStandard, &src_info)
&& src_info.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY
);
}
int _check_dirA(char *src, char *dest)
{
/* Return True if the path at src relative to dest is a directory */
WIN32_FILE_ATTRIBUTE_DATA src_info;
char dest_parent[MAX_PATH];
char src_resolved[MAX_PATH] = "";
/* dest_parent = os.path.dirname(dest) */
strcpy(dest_parent, dest);
_dirnameW(dest_parent);
/* src_resolved = os.path.join(dest_parent, src) */
_joinW(src_resolved, dest_parent, src);
return (
GetFileAttributesExA(src_resolved, GetFileExInfoStandard, &src_info)
&& src_info.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY
);
}
#endif
static PyObject *
@ -7256,13 +7374,20 @@ posix_symlink(PyObject *self, PyObject *args, PyObject *kwargs)
}
#ifdef MS_WINDOWS
Py_BEGIN_ALLOW_THREADS
if (dst.wide)
if (dst.wide) {
/* if src is a directory, ensure target_is_directory==1 */
target_is_directory |= _check_dirW(src.wide, dst.wide);
result = Py_CreateSymbolicLinkW(dst.wide, src.wide,
target_is_directory);
else
}
else {
/* if src is a directory, ensure target_is_directory==1 */
target_is_directory |= _check_dirA(src.narrow, dst.narrow);
result = Py_CreateSymbolicLinkA(dst.narrow, src.narrow,
target_is_directory);
}
Py_END_ALLOW_THREADS
if (!result) {