diff -r b6ea3dc89a78 Doc/library/winreg.rst --- a/Doc/library/winreg.rst Sat Jan 17 18:46:10 2015 +0200 +++ b/Doc/library/winreg.rst Thu Jan 22 11:37:10 2015 +0200 @@ -274,6 +274,24 @@ If *key* is a handle returned by :func:`ConnectRegistry`, then the path specified in *file_name* is relative to the remote computer. +.. function:: UnloadKey(key, sub_key) + + Unloads the specified registry key and its subkeys from the registry. + It's the counterpart of :func:`LoadKey`. + + *key* is a handle returned by :func:`ConnectRegistry` or one of the constants + :const:`HKEY_USERS` or :const:`HKEY_LOCAL_MACHINE`. + + *sub_key* is a string that identifies the subkey to load. + + A call to :func:`UnloadKey` fails if the calling process does not have the + :const:`SE_RESTORE_PRIVILEGE` privilege. Note that privileges are different + from permissions -- see the `RegUnLoadKey documentation + `__ for + more details. + + .. versionadded:: 3.5 + .. function:: OpenKey(key, sub_key, reserved=0, access=KEY_READ) OpenKeyEx(key, sub_key, reserved=0, access=KEY_READ) diff -r b6ea3dc89a78 Lib/test/test_winreg.py --- a/Lib/test/test_winreg.py Sat Jan 17 18:46:10 2015 +0200 +++ b/Lib/test/test_winreg.py Thu Jan 22 11:37:10 2015 +0200 @@ -9,8 +9,11 @@ # Do this first so test will be skipped if module doesn't exist support.import_module('winreg', required_on=['win']) +support.import_module('ctypes', required_on=['win']) # Now import everything +import winreg from winreg import * +from test.windows_helper import acquired_privilege, skip_unless_privilege try: REMOTE_NAME = sys.argv[sys.argv.index("--remote")+1] @@ -34,6 +37,8 @@ test_key_name = "SOFTWARE\\" + test_key_base # On OS'es that support reflection we should test with a reflected key test_reflect_key_name = "SOFTWARE\\Classes\\" + test_key_base +ERROR_PRIVILEGE_NOT_HELD = 1314 +ERROR_INVALID_PARAMETER = 87 test_data = [ ("Int Value", 45, REG_DWORD), @@ -367,6 +372,48 @@ finally: DeleteKey(HKEY_CURRENT_USER, test_key_name) + def test_unload_key_no_privilege(self): + # Use this idiom in order to avoid failures in refleak mode + # where the process already has the privileges, thus + # leading to the next test failure. + try: + winreg.UnloadKey(winreg.HKEY_USERS, test_key_base) + except OSError as exc: + found = (exc.winerror in + (ERROR_PRIVILEGE_NOT_HELD, ERROR_INVALID_PARAMETER)) + if not found: + self.fail("Unexpected failure: should have failed with: " + "'A required privilege is not held by the client'") + + @skip_unless_privilege("SeRestorePrivilege") + @skip_unless_privilege("SeBackupPrivilege") + def test_unload_key(self): + self.addCleanup(support.unlink, support.TESTFN) + with acquired_privilege("SeRestorePrivilege"): + # only HKEY_USERS and HKEY_LOCAL_MACHINE are allowed + for key in (HKEY_CURRENT_USER, HKEY_CLASSES_ROOT): + with self.assertRaisesRegex(OSError, + "The parameter is incorrect"): + UnloadKey(key, test_key_base) + # the key does not exist + with self.assertRaisesRegex(OSError, + "The parameter is incorrect"): + UnloadKey(HKEY_USERS, "nanana") + + with acquired_privilege("SeBackupPrivilege"): + SaveKey(HKEY_CURRENT_USER, support.TESTFN) + for key in (HKEY_USERS, HKEY_LOCAL_MACHINE): + LoadKey(key, test_key_base, support.TESTFN) + try: + UnloadKey(key, test_key_base) + finally: + # If test_key_base was mounted in HKEY_USERS, + # the reflected key will appear in HKEY_CURRENT_USER + # as well. The only problem is that we can't use + # UnloadKey to detach it, so we'll just remove it. + if key == HKEY_USERS: + DeleteKey(HKEY_CURRENT_USER, test_key_base) + @unittest.skipUnless(REMOTE_NAME, "Skipping remote registry tests") diff -r b6ea3dc89a78 PC/winreg.c --- a/PC/winreg.c Sat Jan 17 18:46:10 2015 +0200 +++ b/PC/winreg.c Thu Jan 22 11:37:10 2015 +0200 @@ -50,6 +50,8 @@ "FlushKey() - Writes all the attributes of the specified key to the registry.\n" "LoadKey() - Creates a subkey under HKEY_USER or HKEY_LOCAL_MACHINE and stores\n" " registration information from a specified file into that subkey.\n" +"UnloadKey() - Unloads the specified registry key and its subkeys \n" +" from the registry.\n" "OpenKey() - Opens the specified key.\n" "OpenKeyEx() - Alias of OpenKey().\n" "QueryValue() - Retrieves the value associated with the unnamed value for a\n" @@ -229,6 +231,20 @@ "\n" "The docs imply key must be in the HKEY_USER or HKEY_LOCAL_MACHINE tree"); +PyDoc_STRVAR(UnloadKey_doc, +"UnloadKey(key, sub_key)\n" +"Unloads the specified registry key and its subkeys from the registry.\n" +"\n" +"key is an already open key, or any one of the predefined HKEY_* constants.\n" +"sub_key is a string that identifies the sub_key to unload.\n" +"\n" +"A call to UnloadKey() fails if the calling process does not have the\n" +"SE_RESTORE_PRIVILEGE privilege.\n" +"If key is a handle returned by ConnectRegistry(), then the path specified\n" +"in fileName is relative to the remote computer.\n" +"\n" +"The docs imply key must be in the HKEY_USER or HKEY_LOCAL_MACHINE tree"); + PyDoc_STRVAR(OpenKey_doc, "OpenKey(key, sub_key, reserved=0, access=KEY_READ) -> key\n" "Opens the specified key.\n" @@ -1334,6 +1350,27 @@ } static PyObject * +PyUnloadKey(PyObject *self, PyObject *args) +{ + HKEY hKey; + PyObject *obKey; + wchar_t *subKey; + + long rc; + if (!PyArg_ParseTuple(args, "Ou:UnloadKey", &obKey, &subKey)) + return NULL; + if (!PyHKEY_AsHKEY(obKey, &hKey, FALSE)) + return NULL; + Py_BEGIN_ALLOW_THREADS + rc = RegUnLoadKeyW(hKey, subKey); + Py_END_ALLOW_THREADS + if (rc != ERROR_SUCCESS) + return PyErr_SetFromWindowsErrWithFunction(rc, "RegUnloadKey"); + Py_INCREF(Py_None); + return Py_None; +} + +static PyObject * PyOpenKey(PyObject *self, PyObject *args, PyObject *kwargs) { HKEY hKey; @@ -1735,6 +1772,7 @@ ExpandEnvironmentStrings_doc }, {"FlushKey", PyFlushKey, METH_VARARGS, FlushKey_doc}, {"LoadKey", PyLoadKey, METH_VARARGS, LoadKey_doc}, + {"UnloadKey", PyUnloadKey, METH_VARARGS, UnloadKey_doc}, {"OpenKey", (PyCFunction)PyOpenKey, METH_VARARGS | METH_KEYWORDS, OpenKey_doc}, {"OpenKeyEx", (PyCFunction)PyOpenKey, METH_VARARGS | METH_KEYWORDS,