mirror of
https://github.com/python/cpython.git
synced 2025-08-03 00:23:06 +00:00
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
4 changed files with 173 additions and 13 deletions
|
@ -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.
|
||||
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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.
|
||||
|
||||
|
|
|
@ -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) {
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue