server: Implement registry symlinks.
diff --git a/dlls/ntdll/tests/reg.c b/dlls/ntdll/tests/reg.c
index f75194f..3521065 100644
--- a/dlls/ntdll/tests/reg.c
+++ b/dlls/ntdll/tests/reg.c
@@ -638,22 +638,18 @@
 
     /* REG_SZ is not allowed */
     status = pNtSetValueKey( link, &symlink_str, 0, REG_SZ, target, target_len );
-    todo_wine
     ok( status == STATUS_ACCESS_DENIED, "NtSetValueKey wrong status 0x%08x\n", status );
     status = pNtSetValueKey( link, &symlink_str, 0, REG_LINK, target, target_len - sizeof(WCHAR) );
     ok( status == STATUS_SUCCESS, "NtSetValueKey failed: 0x%08x\n", status );
     /* other values are not allowed */
     status = pNtSetValueKey( link, &link_str, 0, REG_LINK, target, target_len - sizeof(WCHAR) );
-    todo_wine
     ok( status == STATUS_ACCESS_DENIED, "NtSetValueKey wrong status 0x%08x\n", status );
 
     /* try opening the target through the link */
 
     attr.ObjectName = &link_str;
     status = pNtOpenKey( &key, KEY_ALL_ACCESS, &attr );
-    todo_wine
     ok( status == STATUS_OBJECT_NAME_NOT_FOUND, "NtOpenKey wrong status 0x%08x\n", status );
-    if (!status) pNtClose( key );
 
     attr.ObjectName = &target_str;
     status = pNtCreateKey( &key, KEY_ALL_ACCESS, &attr, 0, 0, 0, 0 );
@@ -670,14 +666,10 @@
 
     len = sizeof(buffer);
     status = pNtQueryValueKey( key, &value_str, KeyValuePartialInformation, info, len, &len );
-    todo_wine
-    {
     ok( status == STATUS_SUCCESS, "NtQueryValueKey failed: 0x%08x\n", status );
     ok( len == FIELD_OFFSET(KEY_VALUE_PARTIAL_INFORMATION,Data) + sizeof(DWORD), "wrong len %u\n", len );
-    }
 
     status = pNtQueryValueKey( key, &symlink_str, KeyValuePartialInformation, info, len, &len );
-    todo_wine
     ok( status == STATUS_OBJECT_NAME_NOT_FOUND, "NtQueryValueKey failed: 0x%08x\n", status );
 
     /* REG_LINK can be created in non-link keys */
@@ -699,11 +691,8 @@
 
     len = sizeof(buffer);
     status = pNtQueryValueKey( key, &value_str, KeyValuePartialInformation, info, len, &len );
-    todo_wine
-    {
     ok( status == STATUS_SUCCESS, "NtQueryValueKey failed: 0x%08x\n", status );
     ok( len == FIELD_OFFSET(KEY_VALUE_PARTIAL_INFORMATION,Data) + sizeof(DWORD), "wrong len %u\n", len );
-    }
 
     status = pNtQueryValueKey( key, &symlink_str, KeyValuePartialInformation, info, len, &len );
     ok( status == STATUS_OBJECT_NAME_NOT_FOUND, "NtQueryValueKey failed: 0x%08x\n", status );
@@ -719,24 +708,18 @@
 
     len = sizeof(buffer);
     status = pNtQueryValueKey( key, &symlink_str, KeyValuePartialInformation, info, len, &len );
-    todo_wine
-    {
     ok( status == STATUS_SUCCESS, "NtQueryValueKey failed: 0x%08x\n", status );
     ok( len == FIELD_OFFSET(KEY_VALUE_PARTIAL_INFORMATION,Data) + target_len - sizeof(WCHAR),
         "wrong len %u\n", len );
-    }
     pNtClose( key );
 
     status = pNtCreateKey( &key, KEY_ALL_ACCESS, &attr, 0, 0, 0, 0 );
     ok( status == STATUS_SUCCESS, "NtCreateKey failed: 0x%08x\n", status );
     len = sizeof(buffer);
     status = pNtQueryValueKey( key, &symlink_str, KeyValuePartialInformation, info, len, &len );
-    todo_wine
-    {
     ok( status == STATUS_SUCCESS, "NtQueryValueKey failed: 0x%08x\n", status );
     ok( len == FIELD_OFFSET(KEY_VALUE_PARTIAL_INFORMATION,Data) + target_len - sizeof(WCHAR),
         "wrong len %u\n", len );
-    }
     pNtClose( key );
 
     /* reopen the link from itself */
@@ -748,24 +731,18 @@
     ok( status == STATUS_SUCCESS, "NtOpenKey failed: 0x%08x\n", status );
     len = sizeof(buffer);
     status = pNtQueryValueKey( key, &symlink_str, KeyValuePartialInformation, info, len, &len );
-    todo_wine
-    {
     ok( status == STATUS_SUCCESS, "NtQueryValueKey failed: 0x%08x\n", status );
     ok( len == FIELD_OFFSET(KEY_VALUE_PARTIAL_INFORMATION,Data) + target_len - sizeof(WCHAR),
         "wrong len %u\n", len );
-    }
     pNtClose( key );
 
     status = pNtCreateKey( &key, KEY_ALL_ACCESS, &attr, 0, 0, 0, 0 );
     ok( status == STATUS_SUCCESS, "NtCreateKey failed: 0x%08x\n", status );
     len = sizeof(buffer);
     status = pNtQueryValueKey( key, &symlink_str, KeyValuePartialInformation, info, len, &len );
-    todo_wine
-    {
     ok( status == STATUS_SUCCESS, "NtQueryValueKey failed: 0x%08x\n", status );
     ok( len == FIELD_OFFSET(KEY_VALUE_PARTIAL_INFORMATION,Data) + target_len - sizeof(WCHAR),
         "wrong len %u\n", len );
-    }
     pNtClose( key );
 
     if (0)  /* crashes the Windows kernel in most versions */
@@ -793,9 +770,7 @@
     attr.Attributes = 0;
     attr.ObjectName = &link_str;
     status = pNtOpenKey( &key, KEY_ALL_ACCESS, &attr );
-    todo_wine
     ok( status == STATUS_OBJECT_NAME_NOT_FOUND, "NtOpenKey wrong status 0x%08x\n", status );
-    if (!status) pNtClose( key );
 
     /* relative symlink, works only on win2k */
     status = pNtSetValueKey( link, &symlink_str, 0, REG_LINK, targetW+1, sizeof(targetW)-2*sizeof(WCHAR) );
@@ -804,12 +779,9 @@
     status = pNtOpenKey( &key, KEY_ALL_ACCESS, &attr );
     ok( status == STATUS_SUCCESS || status == STATUS_OBJECT_NAME_NOT_FOUND,
         "NtOpenKey wrong status 0x%08x\n", status );
-    if (!status) pNtClose( key );
 
     status = pNtCreateKey( &key, KEY_ALL_ACCESS, &attr, 0, 0, REG_OPTION_CREATE_LINK, 0 );
-    todo_wine
     ok( status == STATUS_OBJECT_NAME_COLLISION, "NtCreateKey failed: 0x%08x\n", status );
-    if (!status) pNtClose( key );
 
     status = pNtDeleteKey( link );
     ok( status == STATUS_SUCCESS, "NtDeleteKey failed: 0x%08x\n", status );
@@ -832,10 +804,8 @@
     ok( status == STATUS_SUCCESS, "NtSetValueKey failed: 0x%08x\n", status );
 
     status = pNtOpenKey( &key, KEY_ALL_ACCESS, &attr );
-    todo_wine
     ok( status == STATUS_OBJECT_NAME_NOT_FOUND || status == STATUS_NAME_TOO_LONG,
         "NtOpenKey failed: 0x%08x\n", status );
-    if (!status) pNtClose( key );
 
     attr.Attributes = OBJ_OPENLINK;
     status = pNtOpenKey( &key, KEY_ALL_ACCESS, &attr );
diff --git a/server/registry.c b/server/registry.c
index afdffed..eb89800 100644
--- a/server/registry.c
+++ b/server/registry.c
@@ -83,6 +83,7 @@
 #define KEY_VOLATILE 0x0001  /* key is volatile (not saved to disk) */
 #define KEY_DELETED  0x0002  /* key has been deleted */
 #define KEY_DIRTY    0x0004  /* key has been modified */
+#define KEY_SYMLINK  0x0008  /* key is a symbolic link */
 
 /* a key value */
 struct key_value
@@ -107,7 +108,12 @@
 static const timeout_t save_period = 30 * -TICKS_PER_SEC;  /* delay between periodic saves */
 static struct timeout_user *save_timeout_user;  /* saving timer */
 
+static const WCHAR root_name[] = { '\\','R','e','g','i','s','t','r','y','\\' };
+static const WCHAR symlink_value[] = {'S','y','m','b','o','l','i','c','L','i','n','k','V','a','l','u','e'};
+static const struct unicode_str symlink_str = { symlink_value, sizeof(symlink_value) };
+
 static void set_periodic_save_timer(void);
+static struct key_value *find_value( const struct key *key, const struct unicode_str *name, int *index );
 
 /* information about where to save a registry branch */
 struct save_branch_info
@@ -351,8 +357,6 @@
 /* get the request vararg as registry path */
 static inline void get_req_path( struct unicode_str *str, int skip_root )
 {
-    static const WCHAR root_name[] = { '\\','R','e','g','i','s','t','r','y','\\' };
-
     str->str = get_req_data();
     str->len = (get_req_data_size() / sizeof(WCHAR)) * sizeof(WCHAR);
 
@@ -576,8 +580,38 @@
     return NULL;
 }
 
+/* follow a symlink and return the resolved key */
+static struct key *follow_symlink( struct key *key, int iteration )
+{
+    struct unicode_str path, token;
+    struct key_value *value;
+    int index;
+
+    if (iteration > 16) return NULL;
+    if (!(key->flags & KEY_SYMLINK)) return key;
+    if (!(value = find_value( key, &symlink_str, &index ))) return NULL;
+
+    path.str = value->data;
+    path.len = (value->len / sizeof(WCHAR)) * sizeof(WCHAR);
+    if (path.len <= sizeof(root_name)) return NULL;
+    if (memicmpW( path.str, root_name, sizeof(root_name)/sizeof(WCHAR) )) return NULL;
+    path.str += sizeof(root_name) / sizeof(WCHAR);
+    path.len -= sizeof(root_name);
+
+    key = root_key;
+    token.str = NULL;
+    if (!get_path_token( &path, &token )) return NULL;
+    while (token.len)
+    {
+        if (!(key = find_subkey( key, &token, &index ))) break;
+        if (!(key = follow_symlink( key, iteration + 1 ))) break;
+        get_path_token( &path, &token );
+    }
+    return key;
+}
+
 /* open a subkey */
-static struct key *open_key( struct key *key, const struct unicode_str *name )
+static struct key *open_key( struct key *key, const struct unicode_str *name, unsigned int attributes )
 {
     int index;
     struct unicode_str token;
@@ -589,19 +623,31 @@
         if (!(key = find_subkey( key, &token, &index )))
         {
             set_error( STATUS_OBJECT_NAME_NOT_FOUND );
-            break;
+            return NULL;
         }
         get_path_token( name, &token );
+        if (!token.len) break;
+        if (!(key = follow_symlink( key, 0 )))
+        {
+            set_error( STATUS_OBJECT_NAME_NOT_FOUND );
+            return NULL;
+        }
     }
 
+    if (!(attributes & OBJ_OPENLINK) && !(key = follow_symlink( key, 0 )))
+    {
+        set_error( STATUS_OBJECT_NAME_NOT_FOUND );
+        return NULL;
+    }
     if (debug_level > 1) dump_operation( key, NULL, "Open" );
-    if (key) grab_object( key );
+    grab_object( key );
     return key;
 }
 
 /* create a subkey */
 static struct key *create_key( struct key *key, const struct unicode_str *name,
-                               const struct unicode_str *class, int flags, timeout_t modif, int *created )
+                               const struct unicode_str *class, int flags, unsigned int options,
+                               unsigned int attributes, timeout_t modif, int *created )
 {
     struct key *base;
     int index;
@@ -622,12 +668,35 @@
         if (!(subkey = find_subkey( key, &token, &index ))) break;
         key = subkey;
         get_path_token( name, &token );
+        if (!token.len) break;
+        if (!(subkey = follow_symlink( subkey, 0 )))
+        {
+            set_error( STATUS_OBJECT_NAME_NOT_FOUND );
+            return NULL;
+        }
     }
 
     /* create the remaining part */
 
-    if (!token.len) goto done;
-    if (!(flags & KEY_VOLATILE) && (key->flags & KEY_VOLATILE))
+    if (!token.len)
+    {
+        if (options & REG_OPTION_CREATE_LINK)
+        {
+            set_error( STATUS_OBJECT_NAME_COLLISION );
+            return NULL;
+        }
+        if (!(attributes & OBJ_OPENLINK) && !(key = follow_symlink( key, 0 )))
+        {
+            set_error( STATUS_OBJECT_NAME_NOT_FOUND );
+            return NULL;
+        }
+        goto done;
+    }
+    if (options & REG_OPTION_VOLATILE)
+    {
+        flags = (flags & ~KEY_DIRTY) | KEY_VOLATILE;
+    }
+    else if (key->flags & KEY_VOLATILE)
     {
         set_error( STATUS_CHILD_MUST_BE_VOLATILE );
         return NULL;
@@ -648,6 +717,8 @@
             return NULL;
         }
     }
+    /* FIXME: no saving of symlinks yet */
+    if (options & REG_OPTION_CREATE_LINK) key->flags |= KEY_SYMLINK | KEY_VOLATILE;
 
  done:
     if (debug_level > 1) dump_operation( key, NULL, "Create" );
@@ -879,6 +950,16 @@
         }
     }
 
+    if (key->flags & KEY_SYMLINK)
+    {
+        if (type != REG_LINK || name->len != symlink_str.len ||
+            memicmpW( name->str, symlink_str.str, name->len / sizeof(WCHAR) ))
+        {
+            set_error( STATUS_ACCESS_DENIED );
+            return;
+        }
+    }
+
     if (len && !(ptr = memdup( data, len ))) return;
 
     if (!value)
@@ -1140,7 +1221,7 @@
     }
     name.str = p;
     name.len = len - (p - info->tmp + 1) * sizeof(WCHAR);
-    return create_key( base, &name, NULL, flags, modif, &res );
+    return create_key( base, &name, NULL, flags, 0, 0, modif, &res );
 }
 
 /* parse a comma-separated list of hex digits */
@@ -1448,7 +1529,7 @@
 
     /* load system.reg into Registry\Machine */
 
-    if (!(key = create_key( root_key, &HKLM_name, NULL, 0, current_time, &dummy )))
+    if (!(key = create_key( root_key, &HKLM_name, NULL, 0, 0, 0, current_time, &dummy )))
         fatal_error( "could not create Machine registry key\n" );
 
     load_init_registry_from_file( "system.reg", key );
@@ -1456,7 +1537,7 @@
 
     /* load userdef.reg into Registry\User\.Default */
 
-    if (!(key = create_key( root_key, &HKU_name, NULL, 0, current_time, &dummy )))
+    if (!(key = create_key( root_key, &HKU_name, NULL, 0, 0, 0, current_time, &dummy )))
         fatal_error( "could not create User\\.Default registry key\n" );
 
     load_init_registry_from_file( "userdef.reg", key );
@@ -1467,7 +1548,7 @@
     /* FIXME: match default user in token.c. should get from process token instead */
     current_user_path = format_user_registry_path( security_interactive_sid, &current_user_str );
     if (!current_user_path ||
-        !(key = create_key( root_key, &current_user_str, NULL, 0, current_time, &dummy )))
+        !(key = create_key( root_key, &current_user_str, NULL, 0, 0, 0, current_time, &dummy )))
         fatal_error( "could not create HKEY_CURRENT_USER registry key\n" );
     free( current_user_path );
     load_init_registry_from_file( "user.reg", key );
@@ -1660,9 +1741,8 @@
     /* NOTE: no access rights are required from the parent handle to create a key */
     if ((parent = get_parent_hkey_obj( req->parent )))
     {
-        int flags = (req->options & REG_OPTION_VOLATILE) ? KEY_VOLATILE : KEY_DIRTY;
-
-        if ((key = create_key( parent, &name, &class, flags, current_time, &reply->created )))
+        if ((key = create_key( parent, &name, &class, KEY_DIRTY, req->options,
+                               req->attributes, current_time, &reply->created )))
         {
             reply->hkey = alloc_handle( current->process, key, access, req->attributes );
             release_object( key );
@@ -1683,7 +1763,7 @@
     if ((parent = get_parent_hkey_obj( req->parent )))
     {
         get_req_path( &name, !req->parent );
-        if ((key = open_key( parent, &name )))
+        if ((key = open_key( parent, &name, req->attributes )))
         {
             reply->hkey = alloc_handle( current->process, key, access, req->attributes );
             release_object( key );
@@ -1817,7 +1897,7 @@
     {
         int dummy;
         get_req_path( &name, !req->hkey );
-        if ((key = create_key( parent, &name, NULL, KEY_DIRTY, current_time, &dummy )))
+        if ((key = create_key( parent, &name, NULL, KEY_DIRTY, 0, 0, current_time, &dummy )))
         {
             load_registry( key, req->file );
             release_object( key );