It seems that registry handles are somehow treated differently from normal handles, so to make this work I needed to add a hack so that we could trigger a registry change event when a registry key was closed.
Windows also remembers the change filter on a per hkey basis.
Mike
ChangeLog:
* Implement registry change notification
* add test for registry change notification
Index: server/handle.c =================================================================== RCS file: /home/wine/wine/server/handle.c,v retrieving revision 1.22 diff -u -r1.22 handle.c --- server/handle.c 2 Oct 2002 23:49:30 -0000 1.22 +++ server/handle.c 17 Nov 2002 06:35:38 -0000 @@ -332,6 +332,8 @@ table = handle_is_global(handle) ? global_table : process->handles; if (entry < table->entries + table->free) table->free = entry - table->entries; if (entry == table->entries + table->last) shrink_handle_table( table ); + /* hack: windows seems to treat registry handles differently */ + registry_close_handle( obj, handle ); release_object( obj ); return 1; } Index: server/protocol.def =================================================================== RCS file: /home/wine/wine/server/protocol.def,v retrieving revision 1.49 diff -u -r1.49 protocol.def --- server/protocol.def 29 Oct 2002 00:41:42 -0000 1.49 +++ server/protocol.def 17 Nov 2002 06:35:40 -0000 @@ -1362,6 +1362,14 @@ @END +@REQ(set_registry_notification) + obj_handle_t hkey; /* key to watch for changes */ + obj_handle_t event; /* event to set */ + int subtree; /* should we watch the whole subtree? */ + unsigned int filter; /* things to watch */ +@END + + /* Create a waitable timer */ @REQ(create_timer) int inherit; /* inherit flag */ Index: server/registry.c =================================================================== RCS file: /home/wine/wine/server/registry.c,v retrieving revision 1.43 diff -u -r1.43 registry.c --- server/registry.c 12 Sep 2002 22:07:06 -0000 1.43 +++ server/registry.c 17 Nov 2002 06:35:42 -0000 @@ -48,6 +48,16 @@ #include "winternl.h" #include "wine/library.h" +struct notify +{ + struct event *event; /* event to set when changing this key */ + int subtree; /* true if subtree notification */ + unsigned int filter; /* which events to notify on */ + obj_handle_t hkey; /* hkey associated with this notification */ + struct notify *next; /* list of notifications */ + struct notify *prev; /* list of notifications */ +}; + /* a registry key */ struct key { @@ -64,6 +74,8 @@ short flags; /* flags */ short level; /* saving level */ time_t modif; /* last modification time */ + struct notify *first_notify; /* list of notifications */ + struct notify *last_notify; /* list of notifications */ }; /* key flags */ @@ -284,6 +296,53 @@ fprintf( stderr, "\n" ); } +/* notify waiter and maybe delete the notification */ +static void do_notification( struct key *key, struct notify *notify, int del ) +{ + if( notify->event ) + { + set_event( notify->event ); + release_object( notify->event ); + notify->event = NULL; + } + + if ( !del ) + return; + if( notify->next ) + notify->next->prev = notify->prev; + else + key->last_notify = notify->prev; + if( notify->prev ) + notify->prev->next = notify->next; + else + key->first_notify = notify->next; + free( notify ); +} + +static struct notify *find_notify( struct key *key, obj_handle_t hkey) +{ + struct notify *n; + + for( n=key->first_notify; n; n = n->next) + if( n->hkey == hkey ) + break; + return n; +} + +/* close the notification associated with a handle */ +void registry_close_handle( struct object *obj, obj_handle_t hkey ) +{ + struct key * key = (struct key *) obj; + struct notify *notify; + + if( obj->ops != &key_ops ) + return; + notify = find_notify( key, hkey ); + if( !notify ) + return; + do_notification( key, notify, 1 ); +} + static void key_destroy( struct object *obj ) { int i; @@ -302,6 +361,9 @@ key->subkeys[i]->parent = NULL; release_object( key->subkeys[i] ); } + /* unconditionally notify everything waiting on this key */ + while ( key->first_notify ) + do_notification( key, key->first_notify, 1 ); } /* duplicate a key path */ @@ -389,6 +451,8 @@ key->level = current_level; key->modif = modif; key->parent = NULL; + key->first_notify = NULL; + key->last_notify = NULL; if (!(key->name = strdupW( name ))) { release_object( key ); @@ -420,12 +484,32 @@ for (i = 0; i <= key->last_subkey; i++) make_clean( key->subkeys[i] ); } +/* go through all the notifications and send them if necessary */ +void check_notify( struct key *key, unsigned int change, int not_subtree ) +{ + struct notify *n = key->first_notify; + while (n) + { + struct notify *next = n->next; + if ( ( not_subtree || n->subtree ) && ( change & n->filter ) ) + do_notification( key, n, 0 ); + n = next; + } +} + /* update key modification time */ -static void touch_key( struct key *key ) +static void touch_key( struct key *key, unsigned int change ) { + struct key *k; + key->modif = time(NULL); key->level = max( key->level, current_level ); make_dirty( key ); + + /* do notifications */ + check_notify( key, change, 1 ); + for ( k = key->parent; k; k = k->parent ) + check_notify( k, change & ~REG_NOTIFY_CHANGE_LAST_SET, 0 ); } /* try to grow the array of subkeys; return 1 if OK, 0 on error */ @@ -583,6 +667,7 @@ if (!*path) goto done; *created = 1; + touch_key( key, REG_NOTIFY_CHANGE_NAME ); /* FIXME: is this right? */ if (flags & KEY_DIRTY) make_dirty( key ); base = key; base_idx = index; @@ -718,7 +803,7 @@ } if (debug_level > 1) dump_operation( key, NULL, "Delete" ); free_subkey( parent, index ); - touch_key( parent ); + touch_key( parent, REG_NOTIFY_CHANGE_NAME ); } /* try to grow the array of values; return 1 if OK, 0 on error */ @@ -821,7 +906,7 @@ value->type = type; value->len = len; value->data = ptr; - touch_key( key ); + touch_key( key, REG_NOTIFY_CHANGE_LAST_SET ); if (debug_level > 1) dump_operation( key, value, "Set" ); } @@ -912,7 +997,7 @@ if (value->data) free( value->data ); for (i = index; i < key->last_value; i++) key->values[i] = key->values[i + 1]; key->last_value--; - touch_key( key ); + touch_key( key, REG_NOTIFY_CHANGE_LAST_SET ); /* try to shrink the array */ nb_values = key->nb_values; @@ -1814,6 +1899,55 @@ if ((key = get_hkey_obj( req->hkey, KEY_QUERY_VALUE | KEY_ENUMERATE_SUB_KEYS ))) { register_branch_for_saving( key, get_req_data(), get_req_data_size() ); + release_object( key ); + } +} + +/* add a registry key change notification */ +DECL_HANDLER(set_registry_notification) +{ + struct key *key; + struct event *event; + struct notify *notify; + + key = get_hkey_obj( req->hkey, KEY_NOTIFY ); + if( key ) + { + event = get_event_obj( current->process, req->event, SYNCHRONIZE ); + if( event ) + { + notify = find_notify( key, req->hkey ); + if( notify ) + { + release_object( notify->event ); + grab_object( event ); + notify->event = event; + } + else + { + notify = (struct notify *) malloc (sizeof *notify); + if( notify ) + { + grab_object( event ); + notify->event = event; + notify->subtree = req->subtree; + notify->filter = req->filter; + notify->hkey = req->hkey; + + /* add to linked list */ + notify->prev = NULL; + notify->next = key->first_notify; + if ( notify->next ) + notify->next->prev = notify; + else + key->last_notify = notify; + key->first_notify = notify; + } + else + set_error( STATUS_NO_MEMORY ); + } + release_object( event ); + } release_object( key ); } } Index: include/winnt.h =================================================================== RCS file: /home/wine/wine/include/winnt.h,v retrieving revision 1.138 diff -u -r1.138 winnt.h --- include/winnt.h 4 Nov 2002 22:43:24 -0000 1.138 +++ include/winnt.h 17 Nov 2002 06:35:46 -0000 @@ -3386,7 +3386,10 @@ #define REG_OPENED_EXISTING_KEY 0x00000002 /* For RegNotifyChangeKeyValue */ -#define REG_NOTIFY_CHANGE_NAME 0x1 +#define REG_NOTIFY_CHANGE_NAME 0x01 +#define REG_NOTIFY_CHANGE_ATTRIBUTES 0x02 +#define REG_NOTIFY_CHANGE_LAST_SET 0x04 +#define REG_NOTIFY_CHANGE_SECURITY 0x08 #define KEY_QUERY_VALUE 0x00000001 #define KEY_SET_VALUE 0x00000002 Index: dlls/advapi32/registry.c =================================================================== RCS file: /home/wine/wine/dlls/advapi32/registry.c,v retrieving revision 1.48 diff -u -r1.48 registry.c --- dlls/advapi32/registry.c 13 Nov 2002 19:45:27 -0000 1.48 +++ dlls/advapi32/registry.c 17 Nov 2002 06:35:49 -0000 @@ -1841,7 +1841,31 @@ DWORD fdwNotifyFilter, HANDLE hEvent, BOOL fAsync ) { - FIXME("(%p,%i,%ld,%p,%i): stub\n",hkey,fWatchSubTree,fdwNotifyFilter, + LONG ret; + + TRACE("(%p,%i,%ld,%p,%i)\n",hkey,fWatchSubTree,fdwNotifyFilter, hEvent,fAsync); - return ERROR_SUCCESS; + + if( !fAsync ) + hEvent = CreateEventA(NULL, 0, 0, NULL); + + SERVER_START_REQ( set_registry_notification ) + { + req->hkey = hkey; + req->event = hEvent; + req->subtree = fWatchSubTree; + req->filter = fdwNotifyFilter; + ret = RtlNtStatusToDosError( wine_server_call(req) ); + } + SERVER_END_REQ; + + if( !fAsync ) + { + if( ret == ERROR_SUCCESS ) + WaitForSingleObject( hEvent, INFINITE ); + CloseHandle( hEvent ); + } + + return ret; } +
Index: dlls/advapi32/tests/Makefile.in =================================================================== RCS file: /home/wine/wine/dlls/advapi32/tests/Makefile.in,v retrieving revision 1.1 diff -u -r1.1 Makefile.in --- dlls/advapi32/tests/Makefile.in 9 Aug 2002 01:22:40 -0000 1.1 +++ dlls/advapi32/tests/Makefile.in 17 Nov 2002 06:36:24 -0000 @@ -6,6 +6,7 @@ IMPORTS = advapi32 kernel32 ntdll CTESTS = \ + rchange.c \ registry.c @MAKE_TEST_RULES@ --- /dev/null Mon Jul 18 08:46:18 1994 +++ dlls/advapi32/tests/rchange.c Sun Nov 17 15:17:27 2002 @@ -0,0 +1,200 @@ +/* + * Unit tests for registry change notifications + * + * Copyright (c) 2002 Mike McCormack + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +#include <assert.h> +#include "wine/test.h" +#include "winbase.h" +#include "winreg.h" +#include "winerror.h" + +#if 0 +#include<windows.h> +#include<stdio.h> + +#define ok(cond, str) if(!(cond)) printf("%d %s\n",__LINE__,str) +#define START_TEXT(x) int main(int argc, char **argv) +#endif + +START_TEST(rchange) +/* int main(int argc, char **argv) */ +{ + HKEY hkey = 0, hkeynew = 0; + DWORD r, val = 1234; + HANDLE hEvent; + + // cleanup from previous run + RegDeleteKey(HKEY_CURRENT_USER, "Software\\test\\new"); + RegDeleteKey(HKEY_CURRENT_USER, "Software\\test"); + + // create an event to play with + hEvent = CreateEvent(NULL, FALSE, FALSE, NULL); + ok(hEvent!=INVALID_HANDLE_VALUE, "couldn't create event"); + + // create a key and a change notification... shouldn't be notified yet + r = RegCreateKeyEx(HKEY_CURRENT_USER, "Software\\test", 0, + NULL, 0, KEY_ALL_ACCESS, NULL, &hkey, NULL); + ok(!r, "reg open key failed(1)"); + r = RegNotifyChangeKeyValue(hkey, FALSE, REG_NOTIFY_CHANGE_NAME, hEvent, TRUE); + ok(!r, "reg notify change failed (1)"); + r = WaitForSingleObject(hEvent, 0); + ok(r!=WAIT_OBJECT_0, "wait succeeded but nothing has happened(1)"); + + // create a subkey on the key we're watching... we should be notified + r = RegCreateKeyEx(hkey, "new", 0, + NULL, 0, KEY_ALL_ACCESS, NULL, &hkeynew, NULL); + ok(!r, "failed to create subkey"); + r = WaitForSingleObject(hEvent, 0); + ok(r==WAIT_OBJECT_0, "wait failed but shouldn't have(1)"); + // close the key so we can reopen it with a different notification + RegCloseKey(hkeynew); + RegCloseKey(hkey); + hkeynew = hkey = 0; + + + // condition: the key HKEY_CURRENT_USER\\Software\\test\\new exists + // but all key handles are closed + + // create a value in the subkey... check for a notification + r = RegOpenKeyEx(HKEY_CURRENT_USER, "Software\\test\\new", 0, KEY_ALL_ACCESS, &hkeynew); + ok(!r, "key does not exist!"); + r = RegNotifyChangeKeyValue(hkeynew, TRUE, REG_NOTIFY_CHANGE_LAST_SET, hEvent, TRUE); + ok(!r, "reg notify change failed (1a)"); + r = RegSetValueEx(hkeynew, "blah", 0, REG_DWORD, (LPBYTE) &val, sizeof val); + ok(!r, "couldn't set value(1a)\n"); + r = WaitForSingleObject(hEvent, 0); + ok(r==WAIT_OBJECT_0, "wait failed (1a)"); + + // delete a value ... check for a notification + r = RegNotifyChangeKeyValue(hkeynew, TRUE, REG_NOTIFY_CHANGE_LAST_SET, hEvent, TRUE); + ok(!r, "reg notify change failed (1b)"); + r = RegDeleteValue(hkeynew, "blah"); + ok(!r, "couldn't delete value(1b)\n"); + r = WaitForSingleObject(hEvent, 0); + ok(r==WAIT_OBJECT_0, "wait failed (1c)"); + + // re-establish the wait, then close the key .. check for another notification + r = RegNotifyChangeKeyValue(hkeynew, FALSE, REG_NOTIFY_CHANGE_NAME, hEvent, TRUE); + ok(!r, "reg notify change failed (2)"); + r = CloseHandle(hkeynew); + ok(r, "close failed!(2)"); + r = WaitForSingleObject(hEvent, 0); + ok(r==WAIT_OBJECT_0, "wait failed but shouldn't have(2)"); + hkeynew = 0; + + + // condition: the key HKEY_CURRENT_USER\\Software\\test\\new exists + // but all key handles are closed + + // delete the subkey ...check for a notification + r = RegOpenKeyEx(HKEY_CURRENT_USER, "Software\\test", 0, KEY_ALL_ACCESS, &hkey); + ok(!r, "key does not exist!"); + r = RegNotifyChangeKeyValue(hkey, FALSE, REG_NOTIFY_CHANGE_NAME, hEvent, TRUE); + ok(!r, "reg notify change failed (3)"); + r = RegDeleteKey(hkey,"new"); + ok(!r, "delete key failed"); + r = WaitForSingleObject(hEvent, 0); + ok(r==WAIT_OBJECT_0, "wait failed but shouldn't have(3)"); + + // create a value ... but wait on the wrong thing + r = RegNotifyChangeKeyValue(hkey, FALSE, REG_NOTIFY_CHANGE_NAME, hEvent, TRUE); + ok(!r, "reg notify change failed (3a)"); + r = RegSetValueEx(hkey, "blah", 0, REG_DWORD, (LPBYTE) &val, sizeof val); + ok(!r, "couldn't set value\n"); + r = WaitForSingleObject(hEvent, 0); + ok(r!=WAIT_OBJECT_0, "wait succeeded but shouldn't have(3a)"); + + // condition: the key HKEY_CURRENT_USER\\Software\\test exists + // only hkey is open with a handle to it + + // get rid of the notification from above by creating a key + r = RegCreateKeyEx(hkey, "new", 0, + NULL, 0, KEY_ALL_ACCESS, NULL, &hkeynew, NULL); + ok(!r, "failed to create subkey"); + r = WaitForSingleObject(hEvent, 0); + ok(r==WAIT_OBJECT_0, "wait failed but shouldn't have(1)"); + + // condition: the key HKEY_CURRENT_USER\\Software\\test\\new exists + // only hkey is open with a handle to it's parent + + // delete a value ... but wait on the wrong thing + // ALERT: changing the notification filter does not work until we close the key + // so this does *not* work as expected on windows + r = RegNotifyChangeKeyValue(hkey, FALSE, REG_NOTIFY_CHANGE_LAST_SET, hEvent, TRUE); + ok(!r, "reg notify change failed"); + r = RegDeleteValue(hkey, "blah"); + ok(!r, "couldn't delete value\n"); + r = WaitForSingleObject(hEvent, 0); + ok(r!=WAIT_OBJECT_0, "doesn't succeed on (on winnt)"); + + // condition: the key HKEY_CURRENT_USER\\Software\\test\\new exists + // only hkey is open with a handle to it's parent + + // delete the key Softare\\test\\new to get rid of the notification + r = RegDeleteKey(hkey,"new"); + ok(!r, "delete key failed"); + r = WaitForSingleObject(hEvent, 0); + ok(r==WAIT_OBJECT_0, "wait should have succeeded"); + + r = RegCloseKey(hkey); + ok(!r, "close key failed"); + + // condition: the key HKEY_CURRENT_USER\\Software\\test exists + // no keys open + + + // create a value ... check for a notification + r = RegOpenKeyEx(HKEY_CURRENT_USER, "Software\\test", 0, KEY_ALL_ACCESS, &hkey); + ok(!r, "key does not exist!"); + r = RegNotifyChangeKeyValue(hkey, FALSE, REG_NOTIFY_CHANGE_LAST_SET, hEvent, TRUE); + ok(!r, "reg notify change failed (3c)"); + r = RegSetValueEx(hkey, "blah", 0, REG_DWORD, (LPBYTE) &val, sizeof val); + ok(!r, "couldn't set value\n"); + r = WaitForSingleObject(hEvent, 0); + ok(r==WAIT_OBJECT_0, "wait should have succeeded(3c)"); + + // delete a value ... check for a notification + // again, our change to the filter should be ignored + r = RegNotifyChangeKeyValue(hkey, FALSE, REG_NOTIFY_CHANGE_NAME, hEvent, TRUE); + ok(!r, "reg notify change failed (3d)"); + r = RegDeleteValue(hkey, "blah"); + ok(!r, "couldn't delete value\n"); + r = WaitForSingleObject(hEvent, 0); + ok(r==WAIT_OBJECT_0, "wait should have succeeded(3d)"); + + // condition: the key HKEY_CURRENT_USER\\Software\\test exists + // hkey is a handle to it + + // now close the key ... check for a notification + r = RegNotifyChangeKeyValue(hkey, FALSE, REG_NOTIFY_CHANGE_NAME, hEvent, TRUE); + ok(!r, "reg notify change failed (4)"); + r = CloseHandle(hkey); + ok(r, "close failed(4)"); + r = WaitForSingleObject(hEvent, 0); + ok(r==WAIT_OBJECT_0, "wait failed but shouldn't have(4)"); + + // clean up + r = RegDeleteKey(HKEY_CURRENT_USER, "Software\\test"); + ok(!r, "delete key failed(5)"); + r = CloseHandle(hEvent); + ok(r, "close failed(5)"); + + // post condition: Software\\test does not exist any longer + // no keys open +}