summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorJason R. Coombs <jaraco@jaraco.com>2013-05-27 23:26:36 -0400
committerJason R. Coombs <jaraco@jaraco.com>2013-05-27 23:26:36 -0400
commitefabcb5e49292378f1cc84fc942759a0c4009cc9 (patch)
tree056e455ac5a725d456a32e4a6d27cb395364a083
parent1bc59b81c94ad2f80f5b03ed1d6547badaae7e5c (diff)
parentfb6e0872dd363de26d0ebee7ff5d34956a0b58a2 (diff)
downloadcpython-efabcb5e49292378f1cc84fc942759a0c4009cc9.tar.gz
Merge with 3.3
-rw-r--r--Doc/library/os.rst8
-rw-r--r--Lib/test/test_os.py44
-rw-r--r--Modules/posixmodule.c129
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) {