diff -r 4813498eda65 Doc/library/winreg.rst --- a/Doc/library/winreg.rst Mon Jul 07 18:08:57 2014 +0200 +++ b/Doc/library/winreg.rst Mon Jul 07 21:02:44 2014 +0300 @@ -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 unload. + + 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 4813498eda65 Lib/test/test_winreg.py --- a/Lib/test/test_winreg.py Mon Jul 07 18:08:57 2014 +0200 +++ b/Lib/test/test_winreg.py Mon Jul 07 21:02:44 2014 +0300 @@ -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,43 @@ finally: DeleteKey(HKEY_CURRENT_USER, test_key_name) + @skip_unless_privilege("SeRestorePrivilege") + @skip_unless_privilege("SeBackupPrivilege") + def test_unload_key(self): + self.addCleanup(support.unlink, support.TESTFN) + # 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'") + + 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") + + # test scaffolding + for key in (HKEY_USERS, HKEY_LOCAL_MACHINE): + with acquired_privilege("SeBackupPrivilege"): + with acquired_privilege("SeRestorePrivilege"): + SaveKey(HKEY_CURRENT_USER, support.TESTFN) + LoadKey(key, test_key_base, support.TESTFN) + + # now test that UnloadKey works + UnloadKey(key, test_key_base) + support.unlink(support.TESTFN) @unittest.skipUnless(REMOTE_NAME, "Skipping remote registry tests") diff -r 4813498eda65 PC/winreg.c --- a/PC/winreg.c Mon Jul 07 18:08:57 2014 +0200 +++ b/PC/winreg.c Mon Jul 07 21:02:44 2014 +0300 @@ -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,