diff -r 5442a77b925c -r 8bf88b31ebb7 Lib/test/test_os.py --- a/Lib/test/test_os.py Tue Feb 26 01:02:58 2013 -0800 +++ b/Lib/test/test_os.py Tue Feb 26 08:02:26 2013 -0500 @@ -1312,6 +1312,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 +1396,7 @@ Pep383Tests, Win32KillTests, Win32SymlinkTests, + NonLocalSymlinkTests, FSEncodingTests, PidTests, LoginTests, diff -r 5442a77b925c -r 8bf88b31ebb7 Modules/posixmodule.c --- a/Modules/posixmodule.c Tue Feb 26 01:02:58 2013 -0800 +++ b/Modules/posixmodule.c Tue Feb 26 08:02:26 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),