diff -r 5fae31006724 -r b744517b90bc Doc/library/os.rst --- a/Doc/library/os.rst Tue Feb 26 12:39:57 2013 +0000 +++ b/Doc/library/os.rst Tue Feb 26 08:44:41 2013 -0500 @@ -1440,9 +1440,11 @@ *target_is_directory*, which defaults to ``False``. On Windows, a symlink represents 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). + the target dynamically. For this reason, when creating a symlink on Windows, + if the target is not already present, the symlink will default to being a + file symlink. If *target_is_directory* is set to ``True``, the symlink will + be created as a directory symlink. This parameter is ignored if the target + exists (and the symlink is created with the same type as the target). Symbolic link support was introduced in Windows 6.0 (Vista). :func:`symlink` will raise a :exc:`NotImplementedError` on Windows versions earlier than 6.0. @@ -1455,6 +1457,7 @@ 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 -r 5fae31006724 -r b744517b90bc Lib/test/test_os.py --- a/Lib/test/test_os.py Tue Feb 26 12:39:57 2013 +0000 +++ b/Lib/test/test_os.py Tue Feb 26 08:44:41 2013 -0500 @@ -478,12 +478,7 @@ 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) + os.symlink(os.path.abspath(t2_path), link_path) sub2_tree = (sub2_path, ["link"], ["tmp3"]) else: sub2_tree = (sub2_path, [], ["tmp3"]) @@ -1220,7 +1215,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)) @@ -1312,6 +1307,38 @@ 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') @@ -1364,6 +1391,7 @@ Pep383Tests, Win32KillTests, Win32SymlinkTests, + NonLocalSymlinkTests, FSEncodingTests, PidTests, LoginTests, diff -r 5fae31006724 -r b744517b90bc Modules/posixmodule.c --- a/Modules/posixmodule.c Tue Feb 26 12:39:57 2013 +0000 +++ b/Modules/posixmodule.c Tue Feb 26 08:44:41 2013 -0500 @@ -5411,6 +5411,47 @@ return has_CreateSymbolicLinkW; } +void _dirname(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; +} + +int _is_abs(WCHAR *path) { + /* Is this path absolute? */ + + return path[0] == L'\\' || path[0] == L'/' || path[1] == ':'; + +} + +void _join(WCHAR *dest_path, const WCHAR *root, const WCHAR *rest) { + /* join root and rest with a backslash */ + int root_len; + + if(_is_abs(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); +} + PyDoc_STRVAR(win_symlink__doc__, "symlink(src, dst, target_is_directory=False)\n\n\ Create a symbolic link pointing to src named dst.\n\ @@ -5426,6 +5467,9 @@ PyObject *src, *dest; int target_is_directory = 0; DWORD res; + WIN32_FILE_ATTRIBUTE_DATA src_info; + WCHAR dest_parent[MAX_PATH]; + WCHAR src_resolved[MAX_PATH] = L""; if (!check_CreateSymbolicLinkW()) { @@ -5446,6 +5490,18 @@ return NULL; } + /* if src is a directory, ensure target_is_directory==1 */ + /* dest_parent = os.path.dirname(dest) */ + wcscpy(dest_parent, PyUnicode_AsUnicode(dest)); + _dirname(dest_parent); + /* src_resolved = os.path.join(dest_parent, src) */ + _join(src_resolved, dest_parent, PyUnicode_AsUnicode(src)); + if(GetFileAttributesExW(src_resolved, GetFileExInfoStandard, &src_info)) + { + target_is_directory = target_is_directory || + (src_info.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY); + } + Py_BEGIN_ALLOW_THREADS res = Py_CreateSymbolicLinkW( PyUnicode_AsUnicode(dest),