diff -r fb5707168351 -r 4aaf78f0dd10 Lib/test/test_import.py --- a/Lib/test/test_import.py Tue Jan 03 16:23:11 2012 -0600 +++ b/Lib/test/test_import.py Mon Jan 16 12:05:11 2012 -0500 @@ -10,6 +10,7 @@ import unittest import warnings import textwrap +import shutil from test.support import ( EnvironmentVarGuard, TESTFN, forget, is_jython, @@ -491,10 +492,122 @@ importlib_util.using___import__ = self._importlib_util_flag +class TestSymbolicallyLinkedPackage(unittest.TestCase): + package_name = 'sample' + + def setUp(self): + if os.path.exists('sample-tagged'): shutil.rmtree('sample-tagged') + self.orig_sys_path = sys.path[:] + + symlink = getattr(os, 'symlink', None) or self._symlink_win32 + + # create a sample package; imagine you have a package with a tag and + # you want to symbolically link it from its untagged name. + os.mkdir(self.tagged) + init_file = os.path.join(self.tagged, '__init__.py') + open(init_file, 'w').close() + assert os.path.exists(init_file) + + # now create a symlink to the tagged package + # sample -> sample-tagged + symlink(self.tagged, self.package_name) + + assert os.path.isdir(self.package_name) + assert os.path.isfile(os.path.join(self.package_name, '__init__.py')) + + @property + def tagged(self): + return self.package_name + '-tagged' + + @classmethod + def _symlink_win32(cls, target, link, target_is_directory=False): + """ + Ctypes symlink implementation since Python doesn't support + symlinks in windows yet. Borrowed from jaraco.windows project. + """ + import ctypes.wintypes + CreateSymbolicLink = ctypes.windll.kernel32.CreateSymbolicLinkW + CreateSymbolicLink.argtypes = ( + ctypes.wintypes.LPWSTR, + ctypes.wintypes.LPWSTR, + ctypes.wintypes.DWORD, + ) + CreateSymbolicLink.restype = ctypes.wintypes.BOOLEAN + + def format_system_message(errno): + """ + Call FormatMessage with a system error number to retrieve + the descriptive error message. + """ + # first some flags used by FormatMessageW + ALLOCATE_BUFFER = 0x100 + ARGUMENT_ARRAY = 0x2000 + FROM_HMODULE = 0x800 + FROM_STRING = 0x400 + FROM_SYSTEM = 0x1000 + IGNORE_INSERTS = 0x200 + + # Let FormatMessageW allocate the buffer (we'll free it below) + # Also, let it know we want a system error message. + flags = ALLOCATE_BUFFER | FROM_SYSTEM + source = None + message_id = errno + language_id = 0 + result_buffer = ctypes.wintypes.LPWSTR() + buffer_size = 0 + arguments = None + bytes = ctypes.windll.kernel32.FormatMessageW( + flags, + source, + message_id, + language_id, + ctypes.byref(result_buffer), + buffer_size, + arguments, + ) + # note the following will cause an infinite loop if GetLastError + # repeatedly returns an error that cannot be formatted, although + # this should not happen. + handle_nonzero_success(bytes) + message = result_buffer.value + ctypes.windll.kernel32.LocalFree(result_buffer) + return message + + def handle_nonzero_success(result): + if result == 0: + value = ctypes.windll.kernel32.GetLastError() + strerror = format_system_message(value) + raise WindowsError(value, strerror) + + target_is_directory = target_is_directory or os.path.isdir(target) + handle_nonzero_success(CreateSymbolicLink(link, target, target_is_directory)) + + # regression test for issue6727 + @unittest.skipUnless( + not hasattr(sys, 'getwindowsversion') + or sys.getwindowsversion() >= (6,0), + "Windows Vista or later required") + def test_symlinked_dir_importable(self): + # make sure sample can only be imported from the current directory. + sys.path[:] = ['.'] + + # and try to import the package + pkg = __import__(self.package_name) + + def tearDown(self): + # now cleanup + if os.path.exists(self.package_name): + os.rmdir(self.package_name) + if os.path.exists(self.tagged): + shutil.rmtree(self.tagged) + sys.path[:] = self.orig_sys_path + + def test_main(verbose=None): run_unittest(ImportTests, PycRewritingTests, PathsTests, RelativeImportTests, - RelativeImportFromImportlibTests) + RelativeImportFromImportlibTests, + TestSymbolicallyLinkedPackage) if __name__ == '__main__': diff -r fb5707168351 -r 4aaf78f0dd10 Python/import.c --- a/Python/import.c Tue Jan 03 16:23:11 2012 -0600 +++ b/Python/import.c Mon Jan 16 12:05:11 2012 -0500 @@ -120,6 +120,30 @@ {0, 0} }; +#ifdef MS_WINDOWS +int isdir(char *path) { + DWORD rv; + /* see issue1293 and issue3677: + * stat() on Windows doesn't recognise paths like + * "e:\\shared\\" and "\\\\whiterab-c2znlh\\shared" as dirs. + * Also reference issue6727: + * stat() on Windows is broken and doesn't resolve symlinks properly. + */ + rv = GetFileAttributesA(path); + return rv != INVALID_FILE_ATTRIBUTES && rv & FILE_ATTRIBUTE_DIRECTORY; +} +#else +#ifdef HAVE_STAT +int isdir(char *path) { + struct stat statbuf; + return stat(path, &statbuf) == 0 && S_ISDIR(statbuf.st_mode); +} +#else +int isdir(char *path) { + return 0; +} +#endif /* HAVE_STAT */ +#endif /* MS_WINDOWS */ /* Initialize things */ @@ -1453,9 +1477,7 @@ /* Check for package import (buf holds a directory name, and there's an __init__ module in that directory */ -#ifdef HAVE_STAT - if (stat(buf, &statbuf) == 0 && /* it exists */ - S_ISDIR(statbuf.st_mode) && /* it's a directory */ + if (isdir(buf) && /* it's an existing directory */ case_ok(buf, len, namelen, name)) { /* case matches */ if (find_init_module(buf)) { /* and has __init__.py */ return &fd_package; @@ -1471,7 +1493,6 @@ } } } -#endif #if defined(PYOS_OS2) /* take a snapshot of the module spec for restoration * after the 8 character DLL hackery @@ -3290,6 +3311,7 @@ { char *path; Py_ssize_t pathlen; + int path_isdir; if (!_PyArg_NoKeywords("NullImporter()", kwds)) return -1; @@ -3304,39 +3326,13 @@ PyErr_SetString(PyExc_ImportError, "empty pathname"); return -1; } else { -#ifndef MS_WINDOWS - struct stat statbuf; - int rv; - - rv = stat(path, &statbuf); + path_isdir = isdir(path); PyMem_Free(path); - if (rv == 0) { - /* it exists */ - if (S_ISDIR(statbuf.st_mode)) { - /* it's a directory */ - PyErr_SetString(PyExc_ImportError, - "existing directory"); - return -1; - } + if (path_isdir) { + PyErr_SetString(PyExc_ImportError, + "existing directory"); + return -1; } -#else /* MS_WINDOWS */ - DWORD rv; - /* see issue1293 and issue3677: - * stat() on Windows doesn't recognise paths like - * "e:\\shared\\" and "\\\\whiterab-c2znlh\\shared" as dirs. - */ - rv = GetFileAttributesA(path); - PyMem_Free(path); - if (rv != INVALID_FILE_ATTRIBUTES) { - /* it exists */ - if (rv & FILE_ATTRIBUTE_DIRECTORY) { - /* it's a directory */ - PyErr_SetString(PyExc_ImportError, - "existing directory"); - return -1; - } - } -#endif } return 0; }