From: Paul Gofman pgofman@codeweavers.com
--- dlls/ntdll/unix/registry.c | 155 +++++++++++++++++++++++++++++++++++-- server/protocol.def | 12 +++ server/registry.c | 94 ++++++++++++++++++++-- 3 files changed, 247 insertions(+), 14 deletions(-)
diff --git a/dlls/ntdll/unix/registry.c b/dlls/ntdll/unix/registry.c index b63c7a66925..14b3ade3e6d 100644 --- a/dlls/ntdll/unix/registry.c +++ b/dlls/ntdll/unix/registry.c @@ -29,6 +29,8 @@ #include <string.h> #include <unistd.h> #include <errno.h> +#include <fcntl.h> +#include <sys/stat.h>
#include "ntstatus.h" #define WIN32_NO_STATUS @@ -282,8 +284,9 @@ static char *save_subkeys( char *data, struct saved_key *parent, struct saved_ke }
/* save a registry branch to a file */ -static void save_all_subkeys( char *data, FILE *f ) +static char *save_all_subkeys( char *data, FILE *f ) { + /* Output registry format should match server/registry.c:save_all_subkeys(). */ enum prefix_type prefix_type; int parent_count;
@@ -309,7 +312,7 @@ static void save_all_subkeys( char *data, FILE *f ) default: break; } - save_subkeys( data, NULL, NULL, f ); + return save_subkeys( data, NULL, NULL, f ); }
@@ -901,22 +904,162 @@ NTSTATUS WINAPI NtNotifyChangeKey( HANDLE key, HANDLE event, PIO_APC_ROUTINE apc io, filter, subtree, buffer, length, async ); }
+/* acquire mutex for registry flush operation */ +static HANDLE get_key_flush_mutex(void) +{ + WCHAR bufferW[256]; + UNICODE_STRING name = {.Buffer = bufferW}; + OBJECT_ATTRIBUTES attr; + char buffer[256]; + HANDLE mutex; + + snprintf( buffer, ARRAY_SIZE(buffer), "\Sessions\%u\BaseNamedObjects\__wine_regkey_flush", + (int)NtCurrentTeb()->Peb->SessionId ); + name.Length = name.MaximumLength = (strlen(buffer) + 1) * sizeof(WCHAR); + ascii_to_unicode( bufferW, buffer, name.Length / sizeof(WCHAR) ); + + InitializeObjectAttributes( &attr, &name, OBJ_OPENIF, NULL, NULL ); + if (NtCreateMutant( &mutex, MUTEX_ALL_ACCESS, &attr, FALSE ) < 0) return NULL; + NtWaitForSingleObject( mutex, FALSE, NULL ); + return mutex; +} + +/* release registry flush mutex */ +static void release_key_flush_mutex( HANDLE mutex ) +{ + NtReleaseMutant( mutex, NULL ); + NtClose( mutex ); +} + +/* save registry branch to Wine regsitry storage file */ +static NTSTATUS save_registry_branch( char **data ) +{ + static const char temp_fn[] = "savereg.tmp"; + char *file_name, *path = NULL, *tmp = NULL; + int file_name_len, path_len, fd; + struct stat st; + NTSTATUS ret; + FILE *f; + + file_name_len = *(int *)*data; + *data += sizeof(int); + file_name = *data; + *data += file_name_len; + + path_len = strlen( config_dir ) + 1 + file_name_len + 1; + if (!(path = malloc( path_len ))) return STATUS_NO_MEMORY; + sprintf( path, "%s/%s", config_dir, file_name ); + + if ((fd = open( path, O_WRONLY )) != -1) + { + /* if file is not a regular file or has multiple links or is accessed + * via symbolic links, write directly into it; otherwise use a temp file */ + if (!lstat( path, &st ) && (!S_ISREG(st.st_mode) || st.st_nlink > 1)) + { + ftruncate( fd, 0 ); + goto save; + } + close( fd ); + } + + /* create a temp file in the same directory */ + if (!(tmp = malloc( strlen( config_dir ) + 1 + strlen( temp_fn ) + 1 ))) + { + ret = STATUS_NO_MEMORY; + goto done; + } + sprintf( tmp, "%s/%s", config_dir, temp_fn ); + + if ((fd = open( tmp, O_CREAT | O_EXCL | O_WRONLY, 0666 )) == -1) + { + ret = errno_to_status( errno ); + goto done; + } + +save: + if (!(f = fdopen( fd, "w" ))) + { + ret = errno_to_status( errno ); + if (tmp) unlink( tmp ); + close( fd ); + goto done; + } + + *data = save_all_subkeys( *data, f ); + + ret = fclose( f ) ? errno_to_status( errno ) : STATUS_SUCCESS; + if (tmp) + { + if (!ret && rename( tmp, path )) ret = errno_to_status( errno ); + if (ret) unlink( tmp ); + } + +done: + free( tmp ); + free( path ); + return ret; +}
/****************************************************************************** * NtFlushKey (NTDLL.@) */ NTSTATUS WINAPI NtFlushKey( HANDLE key ) { + abstime_t timestamp_counter; + data_size_t size = 0; unsigned int ret; + char *data = NULL, *curr_data; + HANDLE mutex; + int i, branch_count, branch;
TRACE( "key=%p\n", key );
- SERVER_START_REQ( flush_key ) + mutex = get_key_flush_mutex(); + + while (1) { - req->hkey = wine_server_obj_handle( key ); - ret = wine_server_call( req ); + SERVER_START_REQ( flush_key ) + { + req->hkey = wine_server_obj_handle( key ); + if (size) wine_server_set_reply( req, data, size ); + ret = wine_server_call( req ); + size = reply->total; + branch_count = reply->branch_count; + timestamp_counter = reply->timestamp_counter; + } + SERVER_END_REQ; + + if (ret != STATUS_BUFFER_TOO_SMALL) break; + free( data ); + if (!(data = malloc( size ))) + { + ERR( "No memory.\n" ); + ret = STATUS_NO_MEMORY; + goto done; + } } - SERVER_END_REQ; + if (ret) goto done; + + curr_data = data; + for (i = 0; i < branch_count; ++i) + { + branch = *(int *)curr_data; + curr_data += sizeof(int); + if ((ret = save_registry_branch( &curr_data ))) goto done; + + SERVER_START_REQ( flush_key_done ) + { + req->branch = branch; + req->timestamp_counter = timestamp_counter; + ret = wine_server_call( req ); + } + SERVER_END_REQ; + if (ret) break; + } + +done: + release_key_flush_mutex( mutex ); + free( data ); return ret; }
diff --git a/server/protocol.def b/server/protocol.def index 2e020984a4d..955ae674841 100644 --- a/server/protocol.def +++ b/server/protocol.def @@ -1817,6 +1817,18 @@ struct process_info /* Flush a registry key */ @REQ(flush_key) obj_handle_t hkey; /* handle to the key */ +@REPLY + abstime_t timestamp_counter; /* branch last change timestamp counter */ + data_size_t total; /* total length needed for data */ + int branch_count; /* number of registry branches to flush */ + VARARG(data,bytes); /* registry data */ +@END + + +/* Clear KEY_DIRTY after key flush */ +@REQ(flush_key_done) + abstime_t timestamp_counter; /* timestamp counter returned from flush_key */ + int branch; /* saved registry branch id */ @END
diff --git a/server/registry.c b/server/registry.c index ba87e7c79c4..6a668c9146c 100644 --- a/server/registry.c +++ b/server/registry.c @@ -90,6 +90,7 @@ struct key unsigned int flags; /* flags */ timeout_t modif; /* last modification time */ struct list notify_list; /* list of notifications */ + abstime_t timestamp_counter; /* timestamp counter at last change */ };
/* key flags */ @@ -118,6 +119,8 @@ struct key_value #define MAX_NAME_LEN 256 /* max. length of a key name */ #define MAX_VALUE_LEN 16383 /* max. length of a value name */
+static abstime_t change_timestamp_counter; + /* the root of the registry tree */ static struct key *root_key;
@@ -708,6 +711,7 @@ static struct key *create_key_object( struct object *parent, const struct unicod key->last_value = -1; key->values = NULL; key->modif = modif; + key->timestamp_counter = 0; list_init( &key->notify_list );
if (options & REG_OPTION_CREATE_LINK) key->flags |= KEY_SYMLINK; @@ -728,23 +732,25 @@ static struct key *create_key_object( struct object *parent, const struct unicod /* mark a key and all its parents as dirty (modified) */ static void make_dirty( struct key *key ) { + ++change_timestamp_counter; while (key) { if (key->flags & (KEY_DIRTY|KEY_VOLATILE)) return; /* nothing to do */ key->flags |= KEY_DIRTY; + key->timestamp_counter = change_timestamp_counter; key = get_parent( key ); } }
/* mark a key and all its subkeys as clean (not modified) */ -static void make_clean( struct key *key ) +static void make_clean( struct key *key, abstime_t timestamp_counter ) { int i;
if (key->flags & KEY_VOLATILE) return; if (!(key->flags & KEY_DIRTY)) return; - key->flags &= ~KEY_DIRTY; - for (i = 0; i <= key->last_subkey; i++) make_clean( key->subkeys[i] ); + if (key->timestamp_counter <= timestamp_counter) key->flags &= ~KEY_DIRTY; + for (i = 0; i <= key->last_subkey; i++) make_clean( key->subkeys[i], timestamp_counter ); }
/* go through all the notifications and send them if necessary */ @@ -2003,6 +2009,7 @@ void init_registry(void) /* save a registry branch to a file */ static void save_all_subkeys( struct key *key, FILE *f ) { + /* Registry format in ntdll/registry.c:save_all_subkeys() should match. */ fprintf( f, "WINE REGISTRY Version 2\n" ); fprintf( f, ";; All keys relative to " ); dump_path( key, NULL, f ); @@ -2191,7 +2198,7 @@ static int save_branch( struct key *key, const char *path )
done: free( tmp ); - if (ret) make_clean( key ); + if (ret) make_clean( key, key->timestamp_counter ); return ret; }
@@ -2239,6 +2246,36 @@ static int is_wow64_thread( struct thread *thread ) return (is_machine_64bit( native_machine ) && !is_machine_64bit( thread->process->machine )); }
+/* find all the branches inside the specified key or the branch containing the key */ +static void find_branches_for_key( struct key *key, int *branches, int *branch_count ) +{ + struct key *k; + int i; + + *branch_count = 0; + for (i = 0; i < save_branch_count; i++) + { + k = save_branch_info[i].key; + while ((k = get_parent(k))) + { + if (k != key) continue; + branches[(*branch_count)++] = i; + break; + } + } + + if (*branch_count) return; + + do + { + for (i = 0; i < save_branch_count; i++) + { + if(key != save_branch_info[i].key) continue; + branches[(*branch_count)++] = i; + return; + } + } while ((key = get_parent( key ))); +}
/* create a registry key */ DECL_HANDLER(create_key) @@ -2303,15 +2340,56 @@ DECL_HANDLER(delete_key) } }
-/* flush a registry key */ +/* return registry branches snaphot data for flushing key */ DECL_HANDLER(flush_key) { struct key *key = get_hkey_obj( req->hkey, 0 ); - if (key) + int branches[3], branch_count = 0, i, path_len; + char *data; + + if (!key) return; + + reply->total = 0; + reply->branch_count = 0; + if ((key->flags & KEY_DIRTY) && !(key->flags & KEY_VOLATILE)) + find_branches_for_key( key, branches, &branch_count ); + release_object( key ); + + reply->timestamp_counter = change_timestamp_counter; + for (i = 0; i < branch_count; ++i) { - /* we don't need to do anything here with the current implementation */ - release_object( key ); + if (!(save_branch_info[branches[i]].key->flags & KEY_DIRTY)) continue; + ++reply->branch_count; + path_len = strlen( save_branch_info[branches[i]].path ) + 1; + reply->total += sizeof(int) + sizeof(int) + path_len + save_registry( save_branch_info[branches[i]].key, NULL ); + } + if (reply->total > get_reply_max_size()) + { + set_error( STATUS_BUFFER_TOO_SMALL ); + return; } + + if (!(data = set_reply_data_size( reply->total ))) return; + + for (i = 0; i < branch_count; ++i) + { + if (!(save_branch_info[branches[i]].key->flags & KEY_DIRTY)) continue; + *(int *)data = branches[i]; + data += sizeof(int); + path_len = strlen( save_branch_info[branches[i]].path ) + 1; + *(int *)data = path_len; + data += sizeof(int); + memcpy( data, save_branch_info[branches[i]].path, path_len ); + data += path_len; + data += save_registry( save_branch_info[branches[i]].key, data ); + } +} + +/* clear dirty state after successful registry branch flush */ +DECL_HANDLER(flush_key_done) +{ + if (req->branch < save_branch_count) make_clean( save_branch_info[req->branch].key, req->timestamp_counter ); + else set_error( STATUS_INVALID_PARAMETER ); }
/* enumerate registry subkeys */