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:
parent
db4e5c53c9
commit
3a09286790
@ -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:
|
|||||||
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):
|
|||||||
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):
|
|||||||
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):
|
|||||||
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():
|
|||||||
Pep383Tests,
|
Pep383Tests,
|
||||||
Win32KillTests,
|
Win32KillTests,
|
||||||
Win32SymlinkTests,
|
Win32SymlinkTests,
|
||||||
|
NonLocalSymlinkTests,
|
||||||
FSEncodingTests,
|
FSEncodingTests,
|
||||||
DeviceEncodingTests,
|
DeviceEncodingTests,
|
||||||
PidTests,
|
PidTests,
|
||||||
|
@ -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()
|
|||||||
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)
|
|||||||
}
|
}
|
||||||
|
|
||||||
#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) {
|
||||||
|
Loading…
x
Reference in New Issue
Block a user