Commit 3b4499c5 authored by Brian Curtin's avatar Brian Curtin

Fix #9333. The symlink function is always available now, raising OSError

when the user doesn't hold the symbolic link privilege rather than hiding it.
parent baab9d0b
...@@ -43,7 +43,7 @@ __all__ = [ ...@@ -43,7 +43,7 @@ __all__ = [
"run_unittest", "run_doctest", "threading_setup", "threading_cleanup", "run_unittest", "run_doctest", "threading_setup", "threading_cleanup",
"reap_children", "cpython_only", "check_impl_detail", "get_attribute", "reap_children", "cpython_only", "check_impl_detail", "get_attribute",
"swap_item", "swap_attr", "requires_IEEE_754", "swap_item", "swap_attr", "requires_IEEE_754",
"TestHandler", "Matcher"] "TestHandler", "Matcher", "can_symlink", "skip_unless_symlink"]
class Error(Exception): class Error(Exception):
...@@ -1412,3 +1412,23 @@ class Matcher(object): ...@@ -1412,3 +1412,23 @@ class Matcher(object):
else: else:
result = dv.find(v) >= 0 result = dv.find(v) >= 0
return result 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)
import unittest import unittest
from test.support import run_unittest, TESTFN from test.support import run_unittest, TESTFN, skip_unless_symlink, can_symlink
import glob import glob
import os import os
import shutil import shutil
...@@ -25,7 +25,7 @@ class GlobTests(unittest.TestCase): ...@@ -25,7 +25,7 @@ class GlobTests(unittest.TestCase):
self.mktemp('ZZZ') self.mktemp('ZZZ')
self.mktemp('a', 'bcd', 'EF') self.mktemp('a', 'bcd', 'EF')
self.mktemp('a', 'bcd', 'efg', 'ha') 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('sym1'))
os.symlink(self.norm('broken'), self.norm('sym2')) os.symlink(self.norm('broken'), self.norm('sym2'))
...@@ -98,8 +98,7 @@ class GlobTests(unittest.TestCase): ...@@ -98,8 +98,7 @@ class GlobTests(unittest.TestCase):
# either of these results are reasonable # either of these results are reasonable
self.assertIn(res[0], [self.tempdir, self.tempdir + os.sep]) self.assertIn(res[0], [self.tempdir, self.tempdir + os.sep])
@unittest.skipUnless(hasattr(os, "symlink"), @skip_unless_symlink
"Missing symlink implementation")
def test_glob_broken_symlinks(self): def test_glob_broken_symlinks(self):
eq = self.assertSequencesEqual_noorder eq = self.assertSequencesEqual_noorder
eq(self.glob('sym*'), [self.norm('sym1'), self.norm('sym2')]) eq(self.glob('sym*'), [self.norm('sym1'), self.norm('sym2')])
......
...@@ -304,7 +304,7 @@ class CGIHTTPServerTestCase(BaseTestCase): ...@@ -304,7 +304,7 @@ class CGIHTTPServerTestCase(BaseTestCase):
# The shebang line should be pure ASCII: use symlink if possible. # The shebang line should be pure ASCII: use symlink if possible.
# See issue #7668. # See issue #7668.
if hasattr(os, "symlink"): if support.can_symlink():
self.pythonexe = os.path.join(self.parent_dir, 'python') self.pythonexe = os.path.join(self.parent_dir, 'python')
os.symlink(sys.executable, self.pythonexe) os.symlink(sys.executable, self.pythonexe)
else: else:
......
...@@ -541,7 +541,7 @@ class WalkTests(unittest.TestCase): ...@@ -541,7 +541,7 @@ class WalkTests(unittest.TestCase):
f = open(path, "w") f = open(path, "w")
f.write("I'm " + path + " and proud of it. Blame test_os.\n") f.write("I'm " + path + " and proud of it. Blame test_os.\n")
f.close() f.close()
if hasattr(os, "symlink"): if support.can_symlink():
os.symlink(os.path.abspath(t2_path), link_path) os.symlink(os.path.abspath(t2_path), link_path)
sub2_tree = (sub2_path, ["link"], ["tmp3"]) sub2_tree = (sub2_path, ["link"], ["tmp3"])
else: else:
...@@ -585,7 +585,7 @@ class WalkTests(unittest.TestCase): ...@@ -585,7 +585,7 @@ class WalkTests(unittest.TestCase):
self.assertEqual(all[flipped + 1], (sub1_path, ["SUB11"], ["tmp2"])) self.assertEqual(all[flipped + 1], (sub1_path, ["SUB11"], ["tmp2"]))
self.assertEqual(all[2 - 2 * flipped], sub2_tree) self.assertEqual(all[2 - 2 * flipped], sub2_tree)
if hasattr(os, "symlink"): if support.can_symlink():
# Walk, following symlinks. # Walk, following symlinks.
for root, dirs, files in os.walk(walk_path, followlinks=True): for root, dirs, files in os.walk(walk_path, followlinks=True):
if root == link_path: if root == link_path:
...@@ -1149,7 +1149,7 @@ class Win32KillTests(unittest.TestCase): ...@@ -1149,7 +1149,7 @@ class Win32KillTests(unittest.TestCase):
@unittest.skipUnless(sys.platform == "win32", "Win32 specific tests") @unittest.skipUnless(sys.platform == "win32", "Win32 specific tests")
@unittest.skipUnless(hasattr(os, "symlink"), "Requires symlink implementation") @support.skip_unless_symlink
class Win32SymlinkTests(unittest.TestCase): class Win32SymlinkTests(unittest.TestCase):
filelink = 'filelinktest' filelink = 'filelinktest'
filelink_target = os.path.abspath(__file__) filelink_target = os.path.abspath(__file__)
......
...@@ -10,8 +10,7 @@ class PlatformTest(unittest.TestCase): ...@@ -10,8 +10,7 @@ class PlatformTest(unittest.TestCase):
def test_architecture(self): def test_architecture(self):
res = platform.architecture() res = platform.architecture()
@unittest.skipUnless(hasattr(os, "symlink"), @support.skip_unless_symlink
"Missing symlink implementation")
def test_architecture_via_symlink(self): # issue3762 def test_architecture_via_symlink(self): # issue3762
# On Windows, the EXE needs to know where pythonXY.dll is at so we have # On Windows, the EXE needs to know where pythonXY.dll is at so we have
# to add the directory to the path. # to add the directory to the path.
......
...@@ -155,7 +155,7 @@ class PosixPathTest(unittest.TestCase): ...@@ -155,7 +155,7 @@ class PosixPathTest(unittest.TestCase):
f.write(b"foo") f.write(b"foo")
f.close() f.close()
self.assertIs(posixpath.islink(support.TESTFN + "1"), False) self.assertIs(posixpath.islink(support.TESTFN + "1"), False)
if hasattr(os, "symlink"): if support.can_symlink():
os.symlink(support.TESTFN + "1", support.TESTFN + "2") os.symlink(support.TESTFN + "1", support.TESTFN + "2")
self.assertIs(posixpath.islink(support.TESTFN + "2"), True) self.assertIs(posixpath.islink(support.TESTFN + "2"), True)
os.remove(support.TESTFN + "1") os.remove(support.TESTFN + "1")
......
...@@ -291,8 +291,7 @@ class TestShutil(unittest.TestCase): ...@@ -291,8 +291,7 @@ class TestShutil(unittest.TestCase):
finally: finally:
shutil.rmtree(TESTFN, ignore_errors=True) shutil.rmtree(TESTFN, ignore_errors=True)
@unittest.skipUnless(hasattr(os, "symlink"), @support.skip_unless_symlink
"Missing symlink implementation")
def test_dont_copy_file_onto_symlink_to_itself(self): def test_dont_copy_file_onto_symlink_to_itself(self):
# bug 851123. # bug 851123.
os.mkdir(TESTFN) os.mkdir(TESTFN)
...@@ -312,8 +311,7 @@ class TestShutil(unittest.TestCase): ...@@ -312,8 +311,7 @@ class TestShutil(unittest.TestCase):
finally: finally:
shutil.rmtree(TESTFN, ignore_errors=True) shutil.rmtree(TESTFN, ignore_errors=True)
@unittest.skipUnless(hasattr(os, "symlink"), @support.skip_unless_symlink
"Missing symlink implementation")
def test_rmtree_on_symlink(self): def test_rmtree_on_symlink(self):
# bug 1669. # bug 1669.
os.mkdir(TESTFN) os.mkdir(TESTFN)
...@@ -338,8 +336,7 @@ class TestShutil(unittest.TestCase): ...@@ -338,8 +336,7 @@ class TestShutil(unittest.TestCase):
finally: finally:
os.remove(TESTFN) os.remove(TESTFN)
@unittest.skipUnless(hasattr(os, "symlink"), @support.skip_unless_symlink
"Missing symlink implementation")
def test_copytree_named_pipe(self): def test_copytree_named_pipe(self):
os.mkdir(TESTFN) os.mkdir(TESTFN)
try: try:
...@@ -375,8 +372,7 @@ class TestShutil(unittest.TestCase): ...@@ -375,8 +372,7 @@ class TestShutil(unittest.TestCase):
shutil.copytree(src_dir, dst_dir, copy_function=_copy) shutil.copytree(src_dir, dst_dir, copy_function=_copy)
self.assertEqual(len(copied), 2) self.assertEqual(len(copied), 2)
@unittest.skipUnless(hasattr(os, "symlink"), @support.skip_unless_symlink
"Missing symlink implementation")
def test_copytree_dangling_symlinks(self): def test_copytree_dangling_symlinks(self):
# a dangling symlink raises an error at the end # a dangling symlink raises an error at the end
......
...@@ -12,7 +12,7 @@ import shutil ...@@ -12,7 +12,7 @@ import shutil
from copy import copy, deepcopy from copy import copy, deepcopy
from test.support import (run_unittest, TESTFN, unlink, get_attribute, from test.support import (run_unittest, TESTFN, unlink, get_attribute,
captured_stdout) captured_stdout, skip_unless_symlink)
import sysconfig import sysconfig
from sysconfig import (get_paths, get_platform, get_config_vars, from sysconfig import (get_paths, get_platform, get_config_vars,
...@@ -245,8 +245,7 @@ class TestSysConfig(unittest.TestCase): ...@@ -245,8 +245,7 @@ class TestSysConfig(unittest.TestCase):
'posix_home', 'posix_prefix', 'posix_user') 'posix_home', 'posix_prefix', 'posix_user')
self.assertEqual(get_scheme_names(), wanted) self.assertEqual(get_scheme_names(), wanted)
@unittest.skipUnless(hasattr(os, "symlink"), @skip_unless_symlink
"Missing symlink implementation")
def test_symlink(self): def test_symlink(self):
# On Windows, the EXE needs to know where pythonXY.dll is at so we have # On Windows, the EXE needs to know where pythonXY.dll is at so we have
# to add the directory to the path. # to add the directory to the path.
......
...@@ -322,8 +322,7 @@ class MiscReadTest(CommonReadTest): ...@@ -322,8 +322,7 @@ class MiscReadTest(CommonReadTest):
@unittest.skipUnless(hasattr(os, "link"), @unittest.skipUnless(hasattr(os, "link"),
"Missing hardlink implementation") "Missing hardlink implementation")
@unittest.skipUnless(hasattr(os, "symlink"), @support.skip_unless_symlink
"Missing symlink implementation")
def test_extract_hardlink(self): def test_extract_hardlink(self):
# Test hardlink extraction (e.g. bug #857297). # Test hardlink extraction (e.g. bug #857297).
tar = tarfile.open(tarname, errorlevel=1, encoding="iso8859-1") tar = tarfile.open(tarname, errorlevel=1, encoding="iso8859-1")
...@@ -841,8 +840,7 @@ class WriteTest(WriteTestBase): ...@@ -841,8 +840,7 @@ class WriteTest(WriteTestBase):
os.remove(target) os.remove(target)
os.remove(link) os.remove(link)
@unittest.skipUnless(hasattr(os, "symlink"), @support.skip_unless_symlink
"Missing symlink implementation")
def test_symlink_size(self): def test_symlink_size(self):
path = os.path.join(TEMPDIR, "symlink") path = os.path.join(TEMPDIR, "symlink")
os.symlink("link_target", path) os.symlink("link_target", path)
......
...@@ -18,6 +18,10 @@ Core and Builtins ...@@ -18,6 +18,10 @@ Core and Builtins
Library 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 - Issue #10783: struct.pack() doesn't encode implicitly unicode to UTF-8
anymore. anymore.
......
...@@ -279,6 +279,7 @@ extern int lstat(const char *, struct stat *); ...@@ -279,6 +279,7 @@ extern int lstat(const char *, struct stat *);
#include <lmcons.h> /* for UNLEN */ #include <lmcons.h> /* for UNLEN */
#ifdef SE_CREATE_SYMBOLIC_LINK_NAME /* Available starting with Vista */ #ifdef SE_CREATE_SYMBOLIC_LINK_NAME /* Available starting with Vista */
#define HAVE_SYMLINK #define HAVE_SYMLINK
static int win32_can_symlink = 0;
#endif #endif
#endif /* _MSC_VER */ #endif /* _MSC_VER */
...@@ -5243,6 +5244,10 @@ win_symlink(PyObject *self, PyObject *args, PyObject *kwargs) ...@@ -5243,6 +5244,10 @@ win_symlink(PyObject *self, PyObject *args, PyObject *kwargs)
if (!PyArg_ParseTupleAndKeywords(args, kwargs, "OO|i:symlink", if (!PyArg_ParseTupleAndKeywords(args, kwargs, "OO|i:symlink",
kwlist, &src, &dest, &target_is_directory)) kwlist, &src, &dest, &target_is_directory))
return NULL; 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(&src)) { return NULL; }
if (!convert_to_unicode(&dest)) { if (!convert_to_unicode(&dest)) {
Py_DECREF(src); Py_DECREF(src);
...@@ -7801,7 +7806,7 @@ static PyMethodDef posix_methods[] = { ...@@ -7801,7 +7806,7 @@ static PyMethodDef posix_methods[] = {
{"symlink", posix_symlink, METH_VARARGS, posix_symlink__doc__}, {"symlink", posix_symlink, METH_VARARGS, posix_symlink__doc__},
#endif /* HAVE_SYMLINK */ #endif /* HAVE_SYMLINK */
#if defined(HAVE_SYMLINK) && defined(MS_WINDOWS) #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__}, win_symlink__doc__},
#endif /* defined(HAVE_SYMLINK) && defined(MS_WINDOWS) */ #endif /* defined(HAVE_SYMLINK) && defined(MS_WINDOWS) */
#ifdef HAVE_SYSTEM #ifdef HAVE_SYSTEM
...@@ -8118,7 +8123,7 @@ static int insertvalues(PyObject *module) ...@@ -8118,7 +8123,7 @@ static int insertvalues(PyObject *module)
#endif #endif
#if defined(HAVE_SYMLINK) && defined(MS_WINDOWS) #if defined(HAVE_SYMLINK) && defined(MS_WINDOWS)
void static int
enable_symlink() enable_symlink()
{ {
HANDLE tok; HANDLE tok;
...@@ -8127,10 +8132,10 @@ enable_symlink() ...@@ -8127,10 +8132,10 @@ enable_symlink()
int meth_idx = 0; int meth_idx = 0;
if (!OpenProcessToken(GetCurrentProcess(), TOKEN_ALL_ACCESS, &tok)) if (!OpenProcessToken(GetCurrentProcess(), TOKEN_ALL_ACCESS, &tok))
return; return 0;
if (!LookupPrivilegeValue(NULL, SE_CREATE_SYMBOLIC_LINK_NAME, &luid)) if (!LookupPrivilegeValue(NULL, SE_CREATE_SYMBOLIC_LINK_NAME, &luid))
return; return 0;
tok_priv.PrivilegeCount = 1; tok_priv.PrivilegeCount = 1;
tok_priv.Privileges[0].Luid = luid; tok_priv.Privileges[0].Luid = luid;
...@@ -8139,21 +8144,10 @@ enable_symlink() ...@@ -8139,21 +8144,10 @@ enable_symlink()
if (!AdjustTokenPrivileges(tok, FALSE, &tok_priv, if (!AdjustTokenPrivileges(tok, FALSE, &tok_priv,
sizeof(TOKEN_PRIVILEGES), sizeof(TOKEN_PRIVILEGES),
(PTOKEN_PRIVILEGES) NULL, (PDWORD) NULL)) (PTOKEN_PRIVILEGES) NULL, (PDWORD) NULL))
return; return 0;
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; /* 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) */ #endif /* defined(HAVE_SYMLINK) && defined(MS_WINDOWS) */
...@@ -8419,7 +8413,7 @@ INITFUNC(void) ...@@ -8419,7 +8413,7 @@ INITFUNC(void)
PyObject *m, *v; PyObject *m, *v;
#if defined(HAVE_SYMLINK) && defined(MS_WINDOWS) #if defined(HAVE_SYMLINK) && defined(MS_WINDOWS)
enable_symlink(); win32_can_symlink = enable_symlink();
#endif #endif
m = PyModule_Create(&posixmodule); m = PyModule_Create(&posixmodule);
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment