diff --git a/Lib/test/test_os.py b/Lib/test/test_os.py index 2bc5fc0ab7b..5bb45f5d9b6 100644 --- a/Lib/test/test_os.py +++ b/Lib/test/test_os.py @@ -5,6 +5,7 @@ import os import unittest import warnings +import sys from test import test_support warnings.filterwarnings("ignore", "tempnam", RuntimeWarning, __name__) @@ -364,6 +365,20 @@ class URandomTests (unittest.TestCase): except NotImplementedError: pass +class Win32ErrorTests(unittest.TestCase): + def test_rename(self): + self.assertRaises(WindowsError, os.rename, test_support.TESTFN, test_support.TESTFN+".bak") + + def test_remove(self): + self.assertRaises(WindowsError, os.remove, test_support.TESTFN) + + def test_chdir(self): + self.assertRaises(WindowsError, os.chdir, test_support.TESTFN) + +if sys.platform != 'win32': + class Win32ErrorTests(unittest.TestCase): + pass + def test_main(): test_support.run_unittest( TemporaryFileTests, @@ -372,7 +387,8 @@ def test_main(): WalkTests, MakedirTests, DevNullTests, - URandomTests + URandomTests, + Win32ErrorTests ) if __name__ == "__main__": diff --git a/Lib/test/test_shutil.py b/Lib/test/test_shutil.py index 7c2860272a7..6ab5a35ef96 100644 --- a/Lib/test/test_shutil.py +++ b/Lib/test/test_shutil.py @@ -48,12 +48,12 @@ class TestShutil(unittest.TestCase): if self.errorState == 0: self.assertEqual(func, os.remove) self.assertEqual(arg, self.childpath) - self.assertEqual(exc[0], OSError) + self.failUnless(issubclass(exc[0], OSError)) self.errorState = 1 else: self.assertEqual(func, os.rmdir) self.assertEqual(arg, TESTFN) - self.assertEqual(exc[0], OSError) + self.failUnless(issubclass(exc[0], OSError)) self.errorState = 2 def test_rmtree_dont_delete_file(self): diff --git a/Misc/NEWS b/Misc/NEWS index 8c758b00ffb..444b335d73d 100644 --- a/Misc/NEWS +++ b/Misc/NEWS @@ -67,6 +67,9 @@ Core and builtins Extension Modules ----------------- +- Use Win32 API to implement os.{chdir,rename,rmdir,remove}. As a result, + these functions now raise WindowsError instead of OSError. + - Calling Tk_Init twice is refused if the first call failed as that may deadlock. diff --git a/Modules/posixmodule.c b/Modules/posixmodule.c index 7f2356c0670..9e898dc16a7 100644 --- a/Modules/posixmodule.c +++ b/Modules/posixmodule.c @@ -458,21 +458,29 @@ win32_error_unicode(char* function, Py_UNICODE* filename) static PyObject *_PyUnicode_FromFileSystemEncodedObject(register PyObject *obj) { - /* XXX Perhaps we should make this API an alias of - PyObject_Unicode() instead ?! */ - if (PyUnicode_CheckExact(obj)) { - Py_INCREF(obj); - return obj; - } - if (PyUnicode_Check(obj)) { +} + +/* Function suitable for O& conversion */ +static int +convert_to_unicode(PyObject *arg, void* _param) +{ + PyObject **param = (PyObject**)_param; + if (PyUnicode_CheckExact(arg)) { + Py_INCREF(arg); + *param = arg; + } + else if (PyUnicode_Check(arg)) { /* For a Unicode subtype that's not a Unicode object, return a true Unicode object with the same data. */ - return PyUnicode_FromUnicode(PyUnicode_AS_UNICODE(obj), - PyUnicode_GET_SIZE(obj)); + *param = PyUnicode_FromUnicode(PyUnicode_AS_UNICODE(arg), + PyUnicode_GET_SIZE(arg)); + return *param != NULL; } - return PyUnicode_FromEncodedObject(obj, - Py_FileSystemDefaultEncoding, - "strict"); + else + *param = PyUnicode_FromEncodedObject(arg, + Py_FileSystemDefaultEncoding, + "strict"); + return (*param) != NULL; } #endif /* Py_WIN_WIDE_FILENAMES */ @@ -589,35 +597,10 @@ unicode_file_names(void) #endif static PyObject * -posix_1str(PyObject *args, char *format, int (*func)(const char*), - char *wformat, int (*wfunc)(const Py_UNICODE*)) +posix_1str(PyObject *args, char *format, int (*func)(const char*)) { char *path1 = NULL; int res; -#ifdef Py_WIN_WIDE_FILENAMES - if (unicode_file_names()) { - PyUnicodeObject *po; - if (PyArg_ParseTuple(args, wformat, &po)) { - Py_BEGIN_ALLOW_THREADS - /* PyUnicode_AS_UNICODE OK without thread - lock as it is a simple dereference. */ - res = (*wfunc)(PyUnicode_AS_UNICODE(po)); - Py_END_ALLOW_THREADS - if (res < 0) - return posix_error_with_unicode_filename(PyUnicode_AS_UNICODE(po)); - Py_INCREF(Py_None); - return Py_None; - } - /* Drop the argument parsing error as narrow - strings are also valid. */ - PyErr_Clear(); - } -#else - /* Platforms that don't support Unicode filenames - shouldn't be passing these extra params */ - assert(wformat==NULL && wfunc == NULL); -#endif - if (!PyArg_ParseTuple(args, format, Py_FileSystemDefaultEncoding, &path1)) return NULL; @@ -634,52 +617,10 @@ posix_1str(PyObject *args, char *format, int (*func)(const char*), static PyObject * posix_2str(PyObject *args, char *format, - int (*func)(const char *, const char *), - char *wformat, - int (*wfunc)(const Py_UNICODE *, const Py_UNICODE *)) + int (*func)(const char *, const char *)) { char *path1 = NULL, *path2 = NULL; int res; -#ifdef Py_WIN_WIDE_FILENAMES - if (unicode_file_names()) { - PyObject *po1; - PyObject *po2; - if (PyArg_ParseTuple(args, wformat, &po1, &po2)) { - if (PyUnicode_Check(po1) || PyUnicode_Check(po2)) { - PyObject *wpath1; - PyObject *wpath2; - wpath1 = _PyUnicode_FromFileSystemEncodedObject(po1); - wpath2 = _PyUnicode_FromFileSystemEncodedObject(po2); - if (!wpath1 || !wpath2) { - Py_XDECREF(wpath1); - Py_XDECREF(wpath2); - return NULL; - } - Py_BEGIN_ALLOW_THREADS - /* PyUnicode_AS_UNICODE OK without thread - lock as it is a simple dereference. */ - res = (*wfunc)(PyUnicode_AS_UNICODE(wpath1), - PyUnicode_AS_UNICODE(wpath2)); - Py_END_ALLOW_THREADS - Py_XDECREF(wpath1); - Py_XDECREF(wpath2); - if (res != 0) - return posix_error(); - Py_INCREF(Py_None); - return Py_None; - } - /* Else flow through as neither is Unicode. */ - } - /* Drop the argument parsing error as narrow - strings are also valid. */ - PyErr_Clear(); - } -#else - /* Platforms that don't support Unicode filenames - shouldn't be passing these extra params */ - assert(wformat==NULL && wfunc == NULL); -#endif - if (!PyArg_ParseTuple(args, format, Py_FileSystemDefaultEncoding, &path1, Py_FileSystemDefaultEncoding, &path2)) @@ -696,6 +637,101 @@ posix_2str(PyObject *args, return Py_None; } +#ifdef Py_WIN_WIDE_FILENAMES +static PyObject* +win32_1str(PyObject* args, char* func, + char* format, BOOL (__stdcall *funcA)(LPCSTR), + char* wformat, BOOL (__stdcall *funcW)(LPWSTR)) +{ + PyObject *uni; + char *ansi; + BOOL result; + if (unicode_file_names()) { + if (!PyArg_ParseTuple(args, wformat, &uni)) + PyErr_Clear(); + else { + Py_BEGIN_ALLOW_THREADS + result = funcW(PyUnicode_AsUnicode(uni)); + Py_END_ALLOW_THREADS + if (!result) + return win32_error_unicode(func, PyUnicode_AsUnicode(uni)); + Py_INCREF(Py_None); + return Py_None; + } + } + if (!PyArg_ParseTuple(args, format, &ansi)) + return NULL; + Py_BEGIN_ALLOW_THREADS + result = funcA(ansi); + Py_END_ALLOW_THREADS + if (!result) + return win32_error(func, ansi); + Py_INCREF(Py_None); + return Py_None; + +} + +/* This is a reimplementation of the C library's chdir function, + but one that produces Win32 errors instead of DOS error codes. + chdir is essentially a wrapper around SetCurrentDirectory; however, + it also needs to set "magic" environment variables indicating + the per-drive current directory, which are of the form =: */ +BOOL __stdcall +win32_chdir(LPCSTR path) +{ + char new_path[MAX_PATH+1]; + int result; + char env[4] = "=x:"; + + if(!SetCurrentDirectoryA(path)) + return FALSE; + result = GetCurrentDirectoryA(MAX_PATH+1, new_path); + if (!result) + return FALSE; + /* In the ANSI API, there should not be any paths longer + than MAX_PATH. */ + assert(result <= MAX_PATH+1); + if (strncmp(new_path, "\\\\", 2) == 0 || + strncmp(new_path, "//", 2) == 0) + /* UNC path, nothing to do. */ + return TRUE; + env[1] = new_path[0]; + return SetEnvironmentVariableA(env, new_path); +} + +/* The Unicode version differs from the ANSI version + since the current directory might exceed MAX_PATH characters */ +BOOL __stdcall +win32_wchdir(LPCWSTR path) +{ + wchar_t _new_path[MAX_PATH+1], *new_path = _new_path; + int result; + wchar_t env[4] = L"=x:"; + + if(!SetCurrentDirectoryW(path)) + return FALSE; + result = GetCurrentDirectoryW(MAX_PATH+1, new_path); + if (!result) + return FALSE; + if (result > MAX_PATH+1) { + new_path = malloc(result); + if (!new_path) { + SetLastError(ERROR_OUTOFMEMORY); + return FALSE; + } + } + if (wcsncmp(new_path, L"\\\\", 2) == 0 || + wcsncmp(new_path, L"//", 2) == 0) + /* UNC path, nothing to do. */ + return TRUE; + env[1] = new_path[0]; + result = SetEnvironmentVariableW(env, new_path); + if (new_path != _new_path) + free(new_path); + return result; +} +#endif + #ifdef MS_WINDOWS /* The CRT of Windows has a number of flaws wrt. its stat() implementation: - time stamps are restricted to second resolution @@ -1410,14 +1446,13 @@ static PyObject * posix_chdir(PyObject *self, PyObject *args) { #ifdef MS_WINDOWS - return posix_1str(args, "et:chdir", chdir, "U:chdir", _wchdir); + return win32_1str(args, "chdir", "s:chdir", win32_chdir, "U:chdir", win32_wchdir); #elif defined(PYOS_OS2) && defined(PYCC_GCC) - return posix_1str(args, "et:chdir", _chdir2, NULL, NULL); + return posix_1str(args, "et:chdir", _chdir2); #elif defined(__VMS) - return posix_1str(args, "et:chdir", (int (*)(const char *))chdir, - NULL, NULL); + return posix_1str(args, "et:chdir", (int (*)(const char *))chdir); #else - return posix_1str(args, "et:chdir", chdir, NULL, NULL); + return posix_1str(args, "et:chdir", chdir); #endif } @@ -1485,7 +1520,7 @@ Change root directory to path."); static PyObject * posix_chroot(PyObject *self, PyObject *args) { - return posix_1str(args, "et:chroot", chroot, NULL, NULL); + return posix_1str(args, "et:chroot", chroot); } #endif @@ -2071,7 +2106,6 @@ posix_nice(PyObject *self, PyObject *args) } #endif /* HAVE_NICE */ - PyDoc_STRVAR(posix_rename__doc__, "rename(old, new)\n\n\ Rename a file or directory."); @@ -2080,7 +2114,36 @@ static PyObject * posix_rename(PyObject *self, PyObject *args) { #ifdef MS_WINDOWS - return posix_2str(args, "etet:rename", rename, "OO:rename", _wrename); + PyObject *o1, *o2; + char *p1, *p2; + BOOL result; + if (unicode_file_names()) { + if (!PyArg_ParseTuple(args, "O&O&:rename", + convert_to_unicode, &o1, + convert_to_unicode, &o2)) + PyErr_Clear(); + else { + Py_BEGIN_ALLOW_THREADS + result = MoveFileW(PyUnicode_AsUnicode(o1), + PyUnicode_AsUnicode(o2)); + Py_END_ALLOW_THREADS + Py_DECREF(o1); + Py_DECREF(o2); + if (!result) + return win32_error("rename", NULL); + Py_INCREF(Py_None); + return Py_None; + } + } + if (!PyArg_ParseTuple(args, "ss:rename", &p1, &p2)) + return NULL; + Py_BEGIN_ALLOW_THREADS + result = MoveFileA(p1, p2); + Py_END_ALLOW_THREADS + if (!result) + return win32_error("rename", NULL); + Py_INCREF(Py_None); + return Py_None; #else return posix_2str(args, "etet:rename", rename, NULL, NULL); #endif @@ -2095,9 +2158,9 @@ static PyObject * posix_rmdir(PyObject *self, PyObject *args) { #ifdef MS_WINDOWS - return posix_1str(args, "et:rmdir", rmdir, "U:rmdir", _wrmdir); + return win32_1str(args, "rmdir", "s:rmdir", RemoveDirectoryA, "U:rmdir", RemoveDirectoryW); #else - return posix_1str(args, "et:rmdir", rmdir, NULL, NULL); + return posix_1str(args, "et:rmdir", rmdir); #endif } @@ -2166,9 +2229,9 @@ static PyObject * posix_unlink(PyObject *self, PyObject *args) { #ifdef MS_WINDOWS - return posix_1str(args, "et:remove", unlink, "U:remove", _wunlink); + return win32_1str(args, "remove", "s:remove", DeleteFileA, "U:remove", DeleteFileW); #else - return posix_1str(args, "et:remove", unlink, NULL, NULL); + return posix_1str(args, "et:remove", unlink); #endif }