[Python-checkins] cpython (3.3): Issue #13772: Restored directory detection of targets in `os.symlink` on

jason.coombs python-checkins at python.org
Tue May 28 05:28:40 CEST 2013


http://hg.python.org/cpython/rev/29a2557d693e
changeset:   83943:29a2557d693e
branch:      3.3
parent:      83941:24c3e7e08168
user:        Jason R. Coombs <jaraco at jaraco.com>
date:        Mon May 27 23:21:28 2013 -0400
summary:
  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.

files:
  Doc/library/os.rst    |    8 +-
  Lib/test/test_os.py   |   44 ++++++++-
  Misc/NEWS             |    5 +
  Modules/posixmodule.c |  129 +++++++++++++++++++++++++++++-
  4 files changed, 173 insertions(+), 13 deletions(-)


diff --git a/Doc/library/os.rst b/Doc/library/os.rst
--- a/Doc/library/os.rst
+++ b/Doc/library/os.rst
@@ -2023,9 +2023,10 @@
    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 @@
       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.
 
diff --git a/Lib/test/test_os.py b/Lib/test/test_os.py
--- a/Lib/test/test_os.py
+++ b/Lib/test/test_os.py
@@ -686,13 +686,8 @@
             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 @@
             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 @@
             shutil.rmtree(level1)
 
 
+ at 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 @@
         Pep383Tests,
         Win32KillTests,
         Win32SymlinkTests,
+        NonLocalSymlinkTests,
         FSEncodingTests,
         DeviceEncodingTests,
         PidTests,
diff --git a/Misc/NEWS b/Misc/NEWS
--- a/Misc/NEWS
+++ b/Misc/NEWS
@@ -24,6 +24,11 @@
 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.
 
diff --git a/Modules/posixmodule.c b/Modules/posixmodule.c
--- a/Modules/posixmodule.c
+++ b/Modules/posixmodule.c
@@ -7200,6 +7200,124 @@
     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 @@
     }
 
 #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) {

-- 
Repository URL: http://hg.python.org/cpython


More information about the Python-checkins mailing list