Implement registry change notification and test

Mike McCormack mike at codeweavers.com
Sun Nov 17 00:43:22 CST 2002


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

-------------- next part --------------
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
 
 
+ at 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 */
+ at 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;
 }
+
-------------- next part --------------
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
+}


More information about the wine-patches mailing list