diff -r 153e32333aaa -r 4fdbc9f74235 Lib/test/test_import.py --- a/Lib/test/test_import.py Mon Jan 16 18:02:09 2012 +0100 +++ b/Lib/test/test_import.py Mon Jan 16 12:05:11 2012 -0500 @@ -11,6 +11,7 @@ import sys import unittest import textwrap +import shutil from test.support import ( EnvironmentVarGuard, TESTFN, check_warnings, forget, is_jython, @@ -659,11 +660,123 @@ 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, PycacheTests, PycRewritingTests, PathsTests, RelativeImportTests, OverridingImportBuiltinTests, - RelativeImportFromImportlibTests) + RelativeImportFromImportlibTests, + TestSymbolicallyLinkedPackage) if __name__ == '__main__': diff -r 153e32333aaa -r 4fdbc9f74235 Python/import.c --- a/Python/import.c Mon Jan 16 18:02:09 2012 +0100 +++ b/Python/import.c Mon Jan 16 12:05:11 2012 -0500 @@ -140,6 +140,52 @@ {0, 0} }; +/* The following definitions of isdir and Py_PathIsDir implement platform- + * specific implementations of directory detection. + * 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. + */ + +#ifndef MS_WINDOWS +int isdir(char *path) { + struct stat statbuf; + return stat(path, &statbuf) == 0 && S_ISDIR(statbuf.st_mode); +} +#else +int isdir(char *path) { + DWORD rv; + rv = GetFileAttributesA(path); + return rv != INVALID_FILE_ATTRIBUTES && rv & FILE_ATTRIBUTE_DIRECTORY; +} +#endif + +/* Py_PathIsDir: + in (PyObject *): Pointer to PyBytes on Unix and PyUnicode on Windows. + out (int *): Pointer to result (1 if path exists and is a dir). + Return 1 if succeeded; 0 if an exception occurred. + */ +#ifndef MS_WINDOWS +int Py_PathIsDir(PyObject *path, int *path_isdir) { + *path_isdir = isdir(PyBytes_AS_STRING(path)); + return 1; +} +#else +int Py_PathIsDir(PyObject *path, int *path_isdir) { + DWORD rv; + wchar_t *path_chars; + + path_chars = PyUnicode_AsWideCharString(path, NULL); + if (path_chars == NULL) + return 0; + rv = GetFileAttributesW(path_chars); + PyMem_Free(path_chars); + *path_isdir = rv != INVALID_FILE_ATTRIBUTES && rv & FILE_ATTRIBUTE_DIRECTORY; + return 1; +} +#endif /* Initialize things */ @@ -1738,9 +1784,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; @@ -1758,7 +1802,6 @@ return NULL; } } -#endif #if defined(PYOS_OS2) /* take a snapshot of the module spec for restoration * after the 8 character DLL hackery @@ -3680,69 +3723,40 @@ static int NullImporter_init(NullImporter *self, PyObject *args, PyObject *kwds) { -#ifndef MS_WINDOWS PyObject *path; - struct stat statbuf; - int rv; + int path_isdir; + int path_size; if (!_PyArg_NoKeywords("NullImporter()", kwds)) return -1; +#ifndef MS_WINDOWS if (!PyArg_ParseTuple(args, "O&:NullImporter", PyUnicode_FSConverter, &path)) return -1; - if (PyBytes_GET_SIZE(path) == 0) { - Py_DECREF(path); - PyErr_SetString(PyExc_ImportError, "empty pathname"); + path_size = PyBytes_GET_SIZE(path); +#else + if (!PyArg_ParseTuple(args, "U:NullImporter", + &path)) + return -1; + + path_size = PyUnicode_GET_SIZE(path); +#endif + + if (!Py_PathIsDir(path, &path_isdir)) { return -1; } - rv = stat(PyBytes_AS_STRING(path), &statbuf); +#ifndef MS_WINDOWS Py_DECREF(path); - if (rv == 0) { - /* it exists */ - if (S_ISDIR(statbuf.st_mode)) { - /* it's a directory */ - PyErr_SetString(PyExc_ImportError, "existing directory"); - return -1; - } - } -#else /* MS_WINDOWS */ - PyObject *pathobj; - DWORD rv; - wchar_t *path; - - if (!_PyArg_NoKeywords("NullImporter()", kwds)) - return -1; - - if (!PyArg_ParseTuple(args, "U:NullImporter", - &pathobj)) - return -1; - - if (PyUnicode_GET_SIZE(pathobj) == 0) { - PyErr_SetString(PyExc_ImportError, "empty pathname"); +#endif + + if (path_isdir) { + PyErr_SetString(PyExc_ImportError, "existing directory"); return -1; } - path = PyUnicode_AsWideCharString(pathobj, NULL); - if (path == NULL) - return -1; - /* see issue1293 and issue3677: - * stat() on Windows doesn't recognise paths like - * "e:\\shared\\" and "\\\\whiterab-c2znlh\\shared" as dirs. - */ - rv = GetFileAttributesW(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; }