Commits

Jason R. Coombs  committed 138ddf1 Merge

Merge with 3.3

  • Participants
  • Parent commits c5d4c04, 29a2557

Comments (0)

Files changed (4)

File Doc/library/os.rst

    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.
       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.
 

File Lib/test/test_os.py

             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"])
             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))
             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')
         Pep383Tests,
         Win32KillTests,
         Win32SymlinkTests,
+        NonLocalSymlinkTests,
         FSEncodingTests,
         DeviceEncodingTests,
         PidTests,

File Misc/NEWS

File contents unchanged.

File Modules/posixmodule.c

     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 *
     }
 
 #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) {