]> granicus.if.org Git - python/commitdiff
Fix #9333. The symlink function is always available now, raising OSError
authorBrian Curtin <brian.curtin@gmail.com>
Tue, 28 Dec 2010 14:31:47 +0000 (14:31 +0000)
committerBrian Curtin <brian.curtin@gmail.com>
Tue, 28 Dec 2010 14:31:47 +0000 (14:31 +0000)
when the user doesn't hold the symbolic link privilege rather than hiding it.

Lib/test/support.py
Lib/test/test_glob.py
Lib/test/test_httpservers.py
Lib/test/test_os.py
Lib/test/test_platform.py
Lib/test/test_posixpath.py
Lib/test/test_shutil.py
Lib/test/test_sysconfig.py
Lib/test/test_tarfile.py
Misc/NEWS
Modules/posixmodule.c

index 27efd37afc79a479c1e874eb169030df49beabde..142a87ea4eb6c315a8ce27e85e35760159b36b70 100644 (file)
@@ -43,7 +43,7 @@ __all__ = [
     "run_unittest", "run_doctest", "threading_setup", "threading_cleanup",
     "reap_children", "cpython_only", "check_impl_detail", "get_attribute",
     "swap_item", "swap_attr", "requires_IEEE_754",
-    "TestHandler", "Matcher"]
+    "TestHandler", "Matcher", "can_symlink", "skip_unless_symlink"]
 
 
 class Error(Exception):
@@ -1412,3 +1412,23 @@ class Matcher(object):
         else:
             result = dv.find(v) >= 0
         return result
+
+
+_can_symlink = None
+def can_symlink():
+    global _can_symlink
+    if _can_symlink is not None:
+        return _can_symlink
+    try:
+        os.symlink(TESTFN, TESTFN + "can_symlink")
+        can = True
+    except OSError:
+        can = False
+    _can_symlink = can
+    return can
+
+def skip_unless_symlink(test):
+    """Skip decorator for tests that require functional symlink"""
+    ok = can_symlink()
+    msg = "Requires functional symlink implementation"
+    return test if ok else unittest.skip(msg)(test)
index f1e1c03624387e636f548dfc82d33943a7bc209d..1560a6bbf0b1b45895f006caf428a5ceb9a441fa 100644 (file)
@@ -1,5 +1,5 @@
 import unittest
-from test.support import run_unittest, TESTFN
+from test.support import run_unittest, TESTFN, skip_unless_symlink, can_symlink
 import glob
 import os
 import shutil
@@ -25,7 +25,7 @@ class GlobTests(unittest.TestCase):
         self.mktemp('ZZZ')
         self.mktemp('a', 'bcd', 'EF')
         self.mktemp('a', 'bcd', 'efg', 'ha')
-        if hasattr(os, "symlink"):
+        if can_symlink():
             os.symlink(self.norm('broken'), self.norm('sym1'))
             os.symlink(self.norm('broken'), self.norm('sym2'))
 
@@ -98,8 +98,7 @@ class GlobTests(unittest.TestCase):
         # either of these results are reasonable
         self.assertIn(res[0], [self.tempdir, self.tempdir + os.sep])
 
-    @unittest.skipUnless(hasattr(os, "symlink"),
-                         "Missing symlink implementation")
+    @skip_unless_symlink
     def test_glob_broken_symlinks(self):
         eq = self.assertSequencesEqual_noorder
         eq(self.glob('sym*'), [self.norm('sym1'), self.norm('sym2')])
index 19d3d176387499b8ae67b7ea50d3194b10ee855e..e42038aadc32f3aa3f165a7d649e1b09702a3eac 100644 (file)
@@ -304,7 +304,7 @@ class CGIHTTPServerTestCase(BaseTestCase):
 
         # The shebang line should be pure ASCII: use symlink if possible.
         # See issue #7668.
-        if hasattr(os, "symlink"):
+        if support.can_symlink():
             self.pythonexe = os.path.join(self.parent_dir, 'python')
             os.symlink(sys.executable, self.pythonexe)
         else:
index 9cfd7b8ffb9d3a47e0ab6301f69c477899bc32ff..497d8096c1f0da30420055b0043a976a9bcc5039 100644 (file)
@@ -541,7 +541,7 @@ class WalkTests(unittest.TestCase):
             f = open(path, "w")
             f.write("I'm " + path + " and proud of it.  Blame test_os.\n")
             f.close()
-        if hasattr(os, "symlink"):
+        if support.can_symlink():
             os.symlink(os.path.abspath(t2_path), link_path)
             sub2_tree = (sub2_path, ["link"], ["tmp3"])
         else:
@@ -585,7 +585,7 @@ class WalkTests(unittest.TestCase):
         self.assertEqual(all[flipped + 1], (sub1_path, ["SUB11"], ["tmp2"]))
         self.assertEqual(all[2 - 2 * flipped], sub2_tree)
 
-        if hasattr(os, "symlink"):
+        if support.can_symlink():
             # Walk, following symlinks.
             for root, dirs, files in os.walk(walk_path, followlinks=True):
                 if root == link_path:
@@ -1149,7 +1149,7 @@ class Win32KillTests(unittest.TestCase):
 
 
 @unittest.skipUnless(sys.platform == "win32", "Win32 specific tests")
-@unittest.skipUnless(hasattr(os, "symlink"), "Requires symlink implementation")
+@support.skip_unless_symlink
 class Win32SymlinkTests(unittest.TestCase):
     filelink = 'filelinktest'
     filelink_target = os.path.abspath(__file__)
index 7264c576e572992d2728f1dd49834d44e2bdb493..7dd7eef2fedb1d4bb96cf99a0f34c394a996d3ce 100644 (file)
@@ -10,8 +10,7 @@ class PlatformTest(unittest.TestCase):
     def test_architecture(self):
         res = platform.architecture()
 
-    @unittest.skipUnless(hasattr(os, "symlink"),
-                         "Missing symlink implementation")
+    @support.skip_unless_symlink
     def test_architecture_via_symlink(self): # issue3762
         # On Windows, the EXE needs to know where pythonXY.dll is at so we have
         # to add the directory to the path.
index 40425956616c29d05263089de6d52901e3b37406..f045b0bf4a9bb1bc84cec1794bbe3489b94978ee 100644 (file)
@@ -155,7 +155,7 @@ class PosixPathTest(unittest.TestCase):
             f.write(b"foo")
             f.close()
             self.assertIs(posixpath.islink(support.TESTFN + "1"), False)
-            if hasattr(os, "symlink"):
+            if support.can_symlink():
                 os.symlink(support.TESTFN + "1", support.TESTFN + "2")
                 self.assertIs(posixpath.islink(support.TESTFN + "2"), True)
                 os.remove(support.TESTFN + "1")
index d9e96785b2cb7c9a75ccacc65546e1ab709c6bf9..30d9e07e35d2dfc6d6d8b9b883636a2f28b5f0ad 100644 (file)
@@ -291,8 +291,7 @@ class TestShutil(unittest.TestCase):
         finally:
             shutil.rmtree(TESTFN, ignore_errors=True)
 
-    @unittest.skipUnless(hasattr(os, "symlink"),
-                         "Missing symlink implementation")
+    @support.skip_unless_symlink
     def test_dont_copy_file_onto_symlink_to_itself(self):
         # bug 851123.
         os.mkdir(TESTFN)
@@ -312,8 +311,7 @@ class TestShutil(unittest.TestCase):
         finally:
             shutil.rmtree(TESTFN, ignore_errors=True)
 
-    @unittest.skipUnless(hasattr(os, "symlink"),
-                         "Missing symlink implementation")
+    @support.skip_unless_symlink
     def test_rmtree_on_symlink(self):
         # bug 1669.
         os.mkdir(TESTFN)
@@ -338,8 +336,7 @@ class TestShutil(unittest.TestCase):
             finally:
                 os.remove(TESTFN)
 
-        @unittest.skipUnless(hasattr(os, "symlink"),
-                             "Missing symlink implementation")
+        @support.skip_unless_symlink
         def test_copytree_named_pipe(self):
             os.mkdir(TESTFN)
             try:
@@ -375,8 +372,7 @@ class TestShutil(unittest.TestCase):
         shutil.copytree(src_dir, dst_dir, copy_function=_copy)
         self.assertEqual(len(copied), 2)
 
-    @unittest.skipUnless(hasattr(os, "symlink"),
-                         "Missing symlink implementation")
+    @support.skip_unless_symlink
     def test_copytree_dangling_symlinks(self):
 
         # a dangling symlink raises an error at the end
index d532ddfe51774bc6c4ef8186f21f1cd3c7b6d6d1..193b5f00c4db091eeae72d4d6b5796116004a1b5 100644 (file)
@@ -12,7 +12,7 @@ import shutil
 from copy import copy, deepcopy
 
 from test.support import (run_unittest, TESTFN, unlink, get_attribute,
-                          captured_stdout)
+                          captured_stdout, skip_unless_symlink)
 
 import sysconfig
 from sysconfig import (get_paths, get_platform, get_config_vars,
@@ -245,8 +245,7 @@ class TestSysConfig(unittest.TestCase):
                   'posix_home', 'posix_prefix', 'posix_user')
         self.assertEqual(get_scheme_names(), wanted)
 
-    @unittest.skipUnless(hasattr(os, "symlink"),
-                         "Missing symlink implementation")
+    @skip_unless_symlink
     def test_symlink(self):
         # On Windows, the EXE needs to know where pythonXY.dll is at so we have
         # to add the directory to the path.
index 9d844640c8cc55219376ec7c507d6be2fa570bb8..ff02c693330d2c0a1d0febb3b7630cac96299101 100644 (file)
@@ -322,8 +322,7 @@ class MiscReadTest(CommonReadTest):
 
     @unittest.skipUnless(hasattr(os, "link"),
                          "Missing hardlink implementation")
-    @unittest.skipUnless(hasattr(os, "symlink"),
-                         "Missing symlink implementation")
+    @support.skip_unless_symlink
     def test_extract_hardlink(self):
         # Test hardlink extraction (e.g. bug #857297).
         tar = tarfile.open(tarname, errorlevel=1, encoding="iso8859-1")
@@ -841,8 +840,7 @@ class WriteTest(WriteTestBase):
                 os.remove(target)
                 os.remove(link)
 
-    @unittest.skipUnless(hasattr(os, "symlink"),
-                         "Missing symlink implementation")
+    @support.skip_unless_symlink
     def test_symlink_size(self):
         path = os.path.join(TEMPDIR, "symlink")
         os.symlink("link_target", path)
index e16cdb83a8b7f2916b40d9c42fafb0671ae055c5..674ae64c203d47a77d751ade0c7376fa78fb896c 100644 (file)
--- a/Misc/NEWS
+++ b/Misc/NEWS
@@ -18,6 +18,10 @@ Core and Builtins
 Library
 -------
 
+- Issue 9333: os.symlink now available regardless of user privileges.
+  The function now raises OSError on Windows >=6.0 when the user is unable
+  to create symbolic links. XP and 2003 still raise NotImplementedError.
+
 - Issue #10783: struct.pack() doesn't encode implicitly unicode to UTF-8
   anymore.
 
index 6f13776cce0588600c6ced3f2ef7e1a25964dd94..0b7f3f09bc714bf0e0be2f1ded5dee075f68105c 100644 (file)
@@ -279,6 +279,7 @@ extern int lstat(const char *, struct stat *);
 #include <lmcons.h>     /* for UNLEN */
 #ifdef SE_CREATE_SYMBOLIC_LINK_NAME /* Available starting with Vista */
 #define HAVE_SYMLINK
+static int win32_can_symlink = 0;
 #endif
 #endif /* _MSC_VER */
 
@@ -5243,6 +5244,10 @@ win_symlink(PyObject *self, PyObject *args, PyObject *kwargs)
     if (!PyArg_ParseTupleAndKeywords(args, kwargs, "OO|i:symlink",
         kwlist, &src, &dest, &target_is_directory))
         return NULL;
+
+    if (win32_can_symlink == 0)
+        return PyErr_Format(PyExc_OSError, "symbolic link privilege not held");
+
     if (!convert_to_unicode(&src)) { return NULL; }
     if (!convert_to_unicode(&dest)) {
         Py_DECREF(src);
@@ -7801,7 +7806,7 @@ static PyMethodDef posix_methods[] = {
     {"symlink",         posix_symlink, METH_VARARGS, posix_symlink__doc__},
 #endif /* HAVE_SYMLINK */
 #if defined(HAVE_SYMLINK) && defined(MS_WINDOWS)
-    {"_symlink", (PyCFunction)win_symlink, METH_VARARGS | METH_KEYWORDS,
+    {"symlink", (PyCFunction)win_symlink, METH_VARARGS | METH_KEYWORDS,
                  win_symlink__doc__},
 #endif /* defined(HAVE_SYMLINK) && defined(MS_WINDOWS) */
 #ifdef HAVE_SYSTEM
@@ -8118,7 +8123,7 @@ static int insertvalues(PyObject *module)
 #endif
 
 #if defined(HAVE_SYMLINK) && defined(MS_WINDOWS)
-void
+static int
 enable_symlink()
 {
     HANDLE tok;
@@ -8127,10 +8132,10 @@ enable_symlink()
     int meth_idx = 0;
 
     if (!OpenProcessToken(GetCurrentProcess(), TOKEN_ALL_ACCESS, &tok))
-        return;
+        return 0;
 
     if (!LookupPrivilegeValue(NULL, SE_CREATE_SYMBOLIC_LINK_NAME, &luid))
-        return;
+        return 0;
 
     tok_priv.PrivilegeCount = 1;
     tok_priv.Privileges[0].Luid = luid;
@@ -8139,21 +8144,10 @@ enable_symlink()
     if (!AdjustTokenPrivileges(tok, FALSE, &tok_priv,
                                sizeof(TOKEN_PRIVILEGES),
                                (PTOKEN_PRIVILEGES) NULL, (PDWORD) NULL))
-        return;
-
-    if(GetLastError() == ERROR_NOT_ALL_ASSIGNED) {
-        /* We couldn't acquire the necessary privilege, so leave the
-           method hidden for this user. */
-        return;
-    } else {
-        /* We've successfully acquired the symlink privilege so rename
-           the method to it's proper "os.symlink" name. */
-        while(posix_methods[meth_idx].ml_meth != (PyCFunction)win_symlink)
-            meth_idx++;
-        posix_methods[meth_idx].ml_name = "symlink";
+        return 0;
 
-        return;
-    }
+    /* ERROR_NOT_ALL_ASSIGNED returned when the privilege can't be assigned. */
+    return GetLastError() == ERROR_NOT_ALL_ASSIGNED ? 0 : 1;
 }
 #endif /* defined(HAVE_SYMLINK) && defined(MS_WINDOWS) */
 
@@ -8419,7 +8413,7 @@ INITFUNC(void)
     PyObject *m, *v;
 
 #if defined(HAVE_SYMLINK) && defined(MS_WINDOWS)
-    enable_symlink();
+    win32_can_symlink = enable_symlink();
 #endif
 
     m = PyModule_Create(&posixmodule);