Thanks to visit codestin.com
Credit goes to github.com

Skip to content

Commit 52173d4

Browse files
committed
Fix #9333. Expose os.symlink on Windows only when usable.
In order to create symlinks on Windows, SeCreateSymbolicLinkPrivilege is an account privilege that is required to be held by the user. Not only must the privilege be enabled for the account, the activated privileges for the currently running application must be adjusted to enable the requested privilege. Rather than exposing an additional function to be called prior to the user's first os.symlink call, we handle the AdjustTokenPrivileges Windows API call internally and only expose os.symlink when the privilege escalation was successful. Due to the change of only exposing os.symlink when it's available, we can go back to the original test skipping methods of checking via `hasattr`.
1 parent 0252462 commit 52173d4

12 files changed

Lines changed: 126 additions & 79 deletions

File tree

Doc/library/os.rst

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1392,7 +1392,15 @@ Files and Directories
13921392

13931393
Symbolic link support was introduced in Windows 6.0 (Vista). :func:`symlink`
13941394
will raise a :exc:`NotImplementedError` on Windows versions earlier than 6.0.
1395-
The *SeCreateSymbolicLinkPrivilege* is required in order to create symlinks.
1395+
1396+
.. note::
1397+
1398+
The *SeCreateSymbolicLinkPrivilege* is required in order to create
1399+
symlinks, so the function is only available when the privilege is held.
1400+
This privilege is not typically granted to regular users but is available
1401+
to accounts which can escalate privileges to the administrator level.
1402+
Either obtaining the privilege or running your application as an
1403+
administrator are ways to successfully create symlinks.
13961404

13971405
Availability: Unix, Windows.
13981406

Lib/test/support.py

Lines changed: 1 addition & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -42,7 +42,7 @@
4242
"set_memlimit", "bigmemtest", "bigaddrspacetest", "BasicTestRunner",
4343
"run_unittest", "run_doctest", "threading_setup", "threading_cleanup",
4444
"reap_children", "cpython_only", "check_impl_detail", "get_attribute",
45-
"swap_item", "swap_attr", "can_symlink", "skip_unless_symlink"]
45+
"swap_item", "swap_attr"]
4646

4747

4848
class Error(Exception):
@@ -1256,27 +1256,6 @@ def reap_children():
12561256
except:
12571257
break
12581258

1259-
try:
1260-
from .symlink_support import enable_symlink_privilege
1261-
except:
1262-
enable_symlink_privilege = lambda: True
1263-
1264-
def can_symlink():
1265-
"""It's no longer sufficient to test for the presence of symlink in the
1266-
os module - on Windows XP and earlier, os.symlink exists but a
1267-
NotImplementedError is thrown.
1268-
"""
1269-
has_symlink = hasattr(os, 'symlink')
1270-
is_old_windows = sys.platform == "win32" and sys.getwindowsversion().major < 6
1271-
has_privilege = False if is_old_windows else enable_symlink_privilege()
1272-
return has_symlink and (not is_old_windows) and has_privilege
1273-
1274-
def skip_unless_symlink(test):
1275-
"""Skip decorator for tests that require functional symlink"""
1276-
selector = can_symlink()
1277-
msg = "Requires functional symlink implementation"
1278-
return [unittest.skip(msg)(test), test][selector]
1279-
12801259
@contextlib.contextmanager
12811260
def swap_attr(obj, attr, new_val):
12821261
"""Temporary swap out an attribute with a new object.

Lib/test/test_glob.py

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import unittest
2-
from test.support import run_unittest, TESTFN, skip_unless_symlink, can_symlink
2+
from test.support import run_unittest, TESTFN
33
import glob
44
import os
55
import shutil
@@ -25,7 +25,7 @@ def setUp(self):
2525
self.mktemp('ZZZ')
2626
self.mktemp('a', 'bcd', 'EF')
2727
self.mktemp('a', 'bcd', 'efg', 'ha')
28-
if can_symlink():
28+
if hasattr(os, "symlink"):
2929
os.symlink(self.norm('broken'), self.norm('sym1'))
3030
os.symlink(self.norm('broken'), self.norm('sym2'))
3131

@@ -98,7 +98,8 @@ def test_glob_directory_with_trailing_slash(self):
9898
# either of these results are reasonable
9999
self.assertIn(res[0], [self.tempdir, self.tempdir + os.sep])
100100

101-
@skip_unless_symlink
101+
@unittest.skipUnless(hasattr(os, "symlink"),
102+
"Missing symlink implementation")
102103
def test_glob_broken_symlinks(self):
103104
eq = self.assertSequencesEqual_noorder
104105
eq(self.glob('sym*'), [self.norm('sym1'), self.norm('sym2')])

Lib/test/test_httpservers.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -304,7 +304,7 @@ def setUp(self):
304304

305305
# The shebang line should be pure ASCII: use symlink if possible.
306306
# See issue #7668.
307-
if support.can_symlink():
307+
if hasattr(os, "symlink"):
308308
self.pythonexe = os.path.join(self.parent_dir, 'python')
309309
os.symlink(sys.executable, self.pythonexe)
310310
else:

Lib/test/test_os.py

Lines changed: 3 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -541,7 +541,7 @@ def test_traversal(self):
541541
f = open(path, "w")
542542
f.write("I'm " + path + " and proud of it. Blame test_os.\n")
543543
f.close()
544-
if support.can_symlink():
544+
if hasattr(os, "symlink"):
545545
os.symlink(os.path.abspath(t2_path), link_path)
546546
sub2_tree = (sub2_path, ["link"], ["tmp3"])
547547
else:
@@ -585,7 +585,7 @@ def test_traversal(self):
585585
self.assertEqual(all[flipped + 1], (sub1_path, ["SUB11"], ["tmp2"]))
586586
self.assertEqual(all[2 - 2 * flipped], sub2_tree)
587587

588-
if support.can_symlink():
588+
if hasattr(os, "symlink"):
589589
# Walk, following symlinks.
590590
for root, dirs, files in os.walk(walk_path, followlinks=True):
591591
if root == link_path:
@@ -1146,14 +1146,8 @@ def test_CTRL_BREAK_EVENT(self):
11461146
self._kill_with_event(signal.CTRL_BREAK_EVENT, "CTRL_BREAK_EVENT")
11471147

11481148

1149-
def skipUnlessWindows6(test):
1150-
if (hasattr(sys, 'getwindowsversion')
1151-
and sys.getwindowsversion().major >= 6):
1152-
return test
1153-
return unittest.skip("Requires Windows Vista or later")(test)
1154-
11551149
@unittest.skipUnless(sys.platform == "win32", "Win32 specific tests")
1156-
@support.skip_unless_symlink
1150+
@unittest.skipUnless(hasattr(os, "symlink"), "Requires symlink implementation")
11571151
class Win32SymlinkTests(unittest.TestCase):
11581152
filelink = 'filelinktest'
11591153
filelink_target = os.path.abspath(__file__)

Lib/test/test_platform.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,8 @@ class PlatformTest(unittest.TestCase):
1010
def test_architecture(self):
1111
res = platform.architecture()
1212

13-
@support.skip_unless_symlink
13+
@unittest.skipUnless(hasattr(os, "symlink"),
14+
"Missing symlink implementation")
1415
def test_architecture_via_symlink(self): # issue3762
1516
# On Windows, the EXE needs to know where pythonXY.dll is at so we have
1617
# to add the directory to the path.

Lib/test/test_posixpath.py

Lines changed: 15 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -155,7 +155,7 @@ def test_islink(self):
155155
f.write(b"foo")
156156
f.close()
157157
self.assertIs(posixpath.islink(support.TESTFN + "1"), False)
158-
if support.can_symlink():
158+
if hasattr(os, "symlink"):
159159
os.symlink(support.TESTFN + "1", support.TESTFN + "2")
160160
self.assertIs(posixpath.islink(support.TESTFN + "2"), True)
161161
os.remove(support.TESTFN + "1")
@@ -180,7 +180,8 @@ def test_samefile(self):
180180
@unittest.skipIf(
181181
sys.platform.startswith('win'),
182182
"posixpath.samefile does not work on links in Windows")
183-
@support.skip_unless_symlink
183+
@unittest.skipUnless(hasattr(os, "symlink"),
184+
"Missing symlink implementation")
184185
def test_samefile_on_links(self):
185186
test_fn1 = support.TESTFN + "1"
186187
test_fn2 = support.TESTFN + "2"
@@ -204,7 +205,8 @@ def test_samestat(self):
204205
@unittest.skipIf(
205206
sys.platform.startswith('win'),
206207
"posixpath.samestat does not work on links in Windows")
207-
@support.skip_unless_symlink
208+
@unittest.skipUnless(hasattr(os, "symlink"),
209+
"Missing symlink implementation")
208210
def test_samestat_on_links(self):
209211
test_fn1 = support.TESTFN + "1"
210212
test_fn2 = support.TESTFN + "2"
@@ -273,7 +275,8 @@ def test_normpath(self):
273275
self.assertEqual(posixpath.normpath(b"///..//./foo/.//bar"),
274276
b"/foo/bar")
275277

276-
@support.skip_unless_symlink
278+
@unittest.skipUnless(hasattr(os, "symlink"),
279+
"Missing symlink implementation")
277280
@skip_if_ABSTFN_contains_backslash
278281
def test_realpath_basic(self):
279282
# Basic operation.
@@ -283,7 +286,8 @@ def test_realpath_basic(self):
283286
finally:
284287
support.unlink(ABSTFN)
285288

286-
@support.skip_unless_symlink
289+
@unittest.skipUnless(hasattr(os, "symlink"),
290+
"Missing symlink implementation")
287291
@skip_if_ABSTFN_contains_backslash
288292
def test_realpath_symlink_loops(self):
289293
# Bug #930024, return the path unchanged if we get into an infinite
@@ -307,7 +311,8 @@ def test_realpath_symlink_loops(self):
307311
support.unlink(ABSTFN+"1")
308312
support.unlink(ABSTFN+"2")
309313

310-
@support.skip_unless_symlink
314+
@unittest.skipUnless(hasattr(os, "symlink"),
315+
"Missing symlink implementation")
311316
@skip_if_ABSTFN_contains_backslash
312317
def test_realpath_resolve_parents(self):
313318
# We also need to resolve any symlinks in the parents of a relative
@@ -328,7 +333,8 @@ def test_realpath_resolve_parents(self):
328333
safe_rmdir(ABSTFN + "/y")
329334
safe_rmdir(ABSTFN)
330335

331-
@support.skip_unless_symlink
336+
@unittest.skipUnless(hasattr(os, "symlink"),
337+
"Missing symlink implementation")
332338
@skip_if_ABSTFN_contains_backslash
333339
def test_realpath_resolve_before_normalizing(self):
334340
# Bug #990669: Symbolic links should be resolved before we
@@ -358,7 +364,8 @@ def test_realpath_resolve_before_normalizing(self):
358364
safe_rmdir(ABSTFN + "/k")
359365
safe_rmdir(ABSTFN)
360366

361-
@support.skip_unless_symlink
367+
@unittest.skipUnless(hasattr(os, "symlink"),
368+
"Missing symlink implementation")
362369
@skip_if_ABSTFN_contains_backslash
363370
def test_realpath_resolve_first(self):
364371
# Bug #1213894: The first component of the path, if not absolute,

Lib/test/test_shutil.py

Lines changed: 26 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -271,7 +271,8 @@ def _filter(src, names):
271271
shutil.rmtree(src_dir)
272272
shutil.rmtree(os.path.dirname(dst_dir))
273273

274-
@support.skip_unless_symlink
274+
@unittest.skipUnless(hasattr(os, "symlink"),
275+
"Missing symlink implementation")
275276
def test_dont_copy_file_onto_link_to_itself(self):
276277
# bug 851123.
277278
os.mkdir(TESTFN)
@@ -303,7 +304,8 @@ def test_dont_copy_file_onto_link_to_itself(self):
303304
except OSError:
304305
pass
305306

306-
@support.skip_unless_symlink
307+
@unittest.skipUnless(hasattr(os, "symlink"),
308+
"Missing symlink implementation")
307309
def test_rmtree_on_symlink(self):
308310
# bug 1669.
309311
os.mkdir(TESTFN)
@@ -328,26 +330,27 @@ def test_copyfile_named_pipe(self):
328330
finally:
329331
os.remove(TESTFN)
330332

331-
@unittest.skipUnless(hasattr(os, 'mkfifo'), 'requires os.mkfifo')
332-
def test_copytree_named_pipe(self):
333-
os.mkdir(TESTFN)
334-
try:
335-
subdir = os.path.join(TESTFN, "subdir")
336-
os.mkdir(subdir)
337-
pipe = os.path.join(subdir, "mypipe")
338-
os.mkfifo(pipe)
333+
@unittest.skipUnless(hasattr(os, "symlink"),
334+
"Missing symlink implementation")
335+
def test_copytree_named_pipe(self):
336+
os.mkdir(TESTFN)
339337
try:
340-
shutil.copytree(TESTFN, TESTFN2)
341-
except shutil.Error as e:
342-
errors = e.args[0]
343-
self.assertEqual(len(errors), 1)
344-
src, dst, error_msg = errors[0]
345-
self.assertEqual("`%s` is a named pipe" % pipe, error_msg)
346-
else:
347-
self.fail("shutil.Error should have been raised")
348-
finally:
349-
shutil.rmtree(TESTFN, ignore_errors=True)
350-
shutil.rmtree(TESTFN2, ignore_errors=True)
338+
subdir = os.path.join(TESTFN, "subdir")
339+
os.mkdir(subdir)
340+
pipe = os.path.join(subdir, "mypipe")
341+
os.mkfifo(pipe)
342+
try:
343+
shutil.copytree(TESTFN, TESTFN2)
344+
except shutil.Error as e:
345+
errors = e.args[0]
346+
self.assertEqual(len(errors), 1)
347+
src, dst, error_msg = errors[0]
348+
self.assertEqual("`%s` is a named pipe" % pipe, error_msg)
349+
else:
350+
self.fail("shutil.Error should have been raised")
351+
finally:
352+
shutil.rmtree(TESTFN, ignore_errors=True)
353+
shutil.rmtree(TESTFN2, ignore_errors=True)
351354

352355
def test_copytree_special_func(self):
353356

@@ -364,7 +367,8 @@ def _copy(src, dst):
364367
shutil.copytree(src_dir, dst_dir, copy_function=_copy)
365368
self.assertEqual(len(copied), 2)
366369

367-
@support.skip_unless_symlink
370+
@unittest.skipUnless(hasattr(os, "symlink"),
371+
"Missing symlink implementation")
368372
def test_copytree_dangling_symlinks(self):
369373

370374
# a dangling symlink raises an error at the end

Lib/test/test_sysconfig.py

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@
1212
from copy import copy, deepcopy
1313

1414
from test.support import (run_unittest, TESTFN, unlink, get_attribute,
15-
captured_stdout, skip_unless_symlink)
15+
captured_stdout)
1616

1717
import sysconfig
1818
from sysconfig import (get_paths, get_platform, get_config_vars,
@@ -245,7 +245,8 @@ def test_get_scheme_names(self):
245245
'posix_home', 'posix_prefix', 'posix_user')
246246
self.assertEqual(get_scheme_names(), wanted)
247247

248-
@skip_unless_symlink
248+
@unittest.skipUnless(hasattr(os, "symlink"),
249+
"Missing symlink implementation")
249250
def test_symlink(self):
250251
# On Windows, the EXE needs to know where pythonXY.dll is at so we have
251252
# to add the directory to the path.

Lib/test/test_tarfile.py

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -322,7 +322,8 @@ def test_find_members(self):
322322

323323
@unittest.skipUnless(hasattr(os, "link"),
324324
"Missing hardlink implementation")
325-
@support.skip_unless_symlink
325+
@unittest.skipUnless(hasattr(os, "symlink"),
326+
"Missing symlink implementation")
326327
def test_extract_hardlink(self):
327328
# Test hardlink extraction (e.g. bug #857297).
328329
tar = tarfile.open(tarname, errorlevel=1, encoding="iso8859-1")
@@ -840,7 +841,8 @@ def test_link_size(self):
840841
os.remove(target)
841842
os.remove(link)
842843

843-
@support.skip_unless_symlink
844+
@unittest.skipUnless(hasattr(os, "symlink"),
845+
"Missing symlink implementation")
844846
def test_symlink_size(self):
845847
path = os.path.join(TEMPDIR, "symlink")
846848
os.symlink("link_target", path)

0 commit comments

Comments
 (0)