diff options
author | Jason R. Coombs <jaraco@jaraco.com> | 2013-05-27 23:26:36 -0400 |
---|---|---|
committer | Jason R. Coombs <jaraco@jaraco.com> | 2013-05-27 23:26:36 -0400 |
commit | efabcb5e49292378f1cc84fc942759a0c4009cc9 (patch) | |
tree | 056e455ac5a725d456a32e4a6d27cb395364a083 | |
parent | 1bc59b81c94ad2f80f5b03ed1d6547badaae7e5c (diff) | |
parent | fb6e0872dd363de26d0ebee7ff5d34956a0b58a2 (diff) | |
download | cpython-efabcb5e49292378f1cc84fc942759a0c4009cc9.tar.gz |
Merge with 3.3
-rw-r--r-- | Doc/library/os.rst | 8 | ||||
-rw-r--r-- | Lib/test/test_os.py | 44 | ||||
-rw-r--r-- | Modules/posixmodule.c | 129 |
3 files changed, 168 insertions, 13 deletions
diff --git a/Doc/library/os.rst b/Doc/library/os.rst index 137fd42986..16bec77940 100644 --- a/Doc/library/os.rst +++ b/Doc/library/os.rst @@ -2024,9 +2024,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. @@ -2042,6 +2043,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. diff --git a/Lib/test/test_os.py b/Lib/test/test_os.py index 65d3c3bf6e..8b445b23b1 100644 --- a/Lib/test/test_os.py +++ b/Lib/test/test_os.py @@ -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') @@ -2243,6 +2270,7 @@ def test_main(): Pep383Tests, Win32KillTests, Win32SymlinkTests, + NonLocalSymlinkTests, FSEncodingTests, DeviceEncodingTests, PidTests, diff --git a/Modules/posixmodule.c b/Modules/posixmodule.c index bedbc1c000..90bbb137a5 100644 --- a/Modules/posixmodule.c +++ b/Modules/posixmodule.c @@ -6735,6 +6735,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 * @@ -6793,13 +6911,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) { |