Commit 3a092867 authored by Jason R. Coombs's avatar Jason R. Coombs

Issue #13772: Restored directory detection of targets in `os.symlink` on...

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.
parent db4e5c53
...@@ -2023,9 +2023,10 @@ features: ...@@ -2023,9 +2023,10 @@ features:
Create a symbolic link pointing to *source* named *link_name*. Create a symbolic link pointing to *source* named *link_name*.
On Windows, a symlink represents either a file or a directory, and does not 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``, morph to the target dynamically. If the target is present, the type of the
the symlink will be created as a directory symlink, otherwise as a file symlink symlink will be created to match. Otherwise, the symlink will be created
(the default). On non-Window platforms, *target_is_directory* is ignored. 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` Symbolic link support was introduced in Windows 6.0 (Vista). :func:`symlink`
will raise a :exc:`NotImplementedError` on Windows versions earlier than 6.0. will raise a :exc:`NotImplementedError` on Windows versions earlier than 6.0.
...@@ -2041,6 +2042,7 @@ features: ...@@ -2041,6 +2042,7 @@ features:
to the administrator level. Either obtaining the privilege or running your to the administrator level. Either obtaining the privilege or running your
application as an administrator are ways to successfully create symlinks. application as an administrator are ways to successfully create symlinks.
:exc:`OSError` is raised when the function is called by an unprivileged :exc:`OSError` is raised when the function is called by an unprivileged
user. user.
......
...@@ -686,13 +686,8 @@ class WalkTests(unittest.TestCase): ...@@ -686,13 +686,8 @@ class WalkTests(unittest.TestCase):
f.write("I'm " + path + " and proud of it. Blame test_os.\n") f.write("I'm " + path + " and proud of it. Blame test_os.\n")
f.close() f.close()
if support.can_symlink(): if support.can_symlink():
if os.name == 'nt': os.symlink(os.path.abspath(t2_path), link_path)
def symlink_to_dir(src, dest): symlink_to_dir('broken', broken_link_path, True)
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)
sub2_tree = (sub2_path, ["link"], ["broken_link", "tmp3"]) sub2_tree = (sub2_path, ["link"], ["broken_link", "tmp3"])
else: else:
sub2_tree = (sub2_path, [], ["tmp3"]) sub2_tree = (sub2_path, [], ["tmp3"])
...@@ -1516,7 +1511,7 @@ class Win32SymlinkTests(unittest.TestCase): ...@@ -1516,7 +1511,7 @@ class Win32SymlinkTests(unittest.TestCase):
os.remove(self.missing_link) os.remove(self.missing_link)
def test_directory_link(self): 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.exists(self.dirlink))
self.assertTrue(os.path.isdir(self.dirlink)) self.assertTrue(os.path.isdir(self.dirlink))
self.assertTrue(os.path.islink(self.dirlink)) self.assertTrue(os.path.islink(self.dirlink))
...@@ -1610,6 +1605,38 @@ class Win32SymlinkTests(unittest.TestCase): ...@@ -1610,6 +1605,38 @@ class Win32SymlinkTests(unittest.TestCase):
shutil.rmtree(level1) 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): class FSEncodingTests(unittest.TestCase):
def test_nop(self): def test_nop(self):
self.assertEqual(os.fsencode(b'abc\xff'), b'abc\xff') self.assertEqual(os.fsencode(b'abc\xff'), b'abc\xff')
...@@ -2137,6 +2164,7 @@ def test_main(): ...@@ -2137,6 +2164,7 @@ def test_main():
Pep383Tests, Pep383Tests,
Win32KillTests, Win32KillTests,
Win32SymlinkTests, Win32SymlinkTests,
NonLocalSymlinkTests,
FSEncodingTests, FSEncodingTests,
DeviceEncodingTests, DeviceEncodingTests,
PidTests, PidTests,
......
...@@ -24,6 +24,11 @@ Core and Builtins ...@@ -24,6 +24,11 @@ Core and Builtins
Library 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 - Issue #16986: ElementTree now correctly parses a string input not only when
an internal XML encoding is UTF-8 or US-ASCII. an internal XML encoding is UTF-8 or US-ASCII.
......
...@@ -7200,6 +7200,124 @@ check_CreateSymbolicLink() ...@@ -7200,6 +7200,124 @@ check_CreateSymbolicLink()
return (Py_CreateSymbolicLinkW && Py_CreateSymbolicLinkA); 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 #endif
static PyObject * static PyObject *
...@@ -7256,13 +7374,20 @@ posix_symlink(PyObject *self, PyObject *args, PyObject *kwargs) ...@@ -7256,13 +7374,20 @@ posix_symlink(PyObject *self, PyObject *args, PyObject *kwargs)
} }
#ifdef MS_WINDOWS #ifdef MS_WINDOWS
Py_BEGIN_ALLOW_THREADS 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, result = Py_CreateSymbolicLinkW(dst.wide, src.wide,
target_is_directory); 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, result = Py_CreateSymbolicLinkA(dst.narrow, src.narrow,
target_is_directory); target_is_directory);
}
Py_END_ALLOW_THREADS Py_END_ALLOW_THREADS
if (!result) { if (!result) {
......
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