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) {