user32/tests: Fix a typo.
[wine] / server / atom.c
1 /*
2  * Server-side atom management
3  *
4  * Copyright (C) 1999, 2000 Alexandre Julliard
5  * Copyright (C) 2000 Turchanov Sergei
6  *
7  * This library is free software; you can redistribute it and/or
8  * modify it under the terms of the GNU Lesser General Public
9  * License as published by the Free Software Foundation; either
10  * version 2.1 of the License, or (at your option) any later version.
11  *
12  * This library is distributed in the hope that it will be useful,
13  * but WITHOUT ANY WARRANTY; without even the implied warranty of
14  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
15  * Lesser General Public License for more details.
16  *
17  * You should have received a copy of the GNU Lesser General Public
18  * License along with this library; if not, write to the Free Software
19  * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA
20  */
21
22 #include "config.h"
23 #include "wine/port.h"
24
25 #include <assert.h>
26 #include <stdlib.h>
27 #include <stdio.h>
28 #include <string.h>
29
30 #include "ntstatus.h"
31 #define WIN32_NO_STATUS
32
33 #include "unicode.h"
34 #include "request.h"
35 #include "object.h"
36 #include "process.h"
37 #include "handle.h"
38 #include "user.h"
39 #include "winuser.h"
40 #include "winternl.h"
41
42 #define HASH_SIZE     37
43 #define MIN_HASH_SIZE 4
44 #define MAX_HASH_SIZE 0x200
45
46 #define MAX_ATOM_LEN  (255 * sizeof(WCHAR))
47 #define MIN_STR_ATOM  0xc000
48 #define MAX_ATOMS     0x4000
49
50 struct atom_entry
51 {
52     struct atom_entry *next;   /* hash table list */
53     struct atom_entry *prev;   /* hash table list */
54     int                count;  /* reference count */
55     short              pinned; /* whether the atom is pinned or not */
56     atom_t             atom;   /* atom handle */
57     unsigned short     hash;   /* string hash */
58     unsigned short     len;    /* string len */
59     WCHAR              str[1]; /* atom string */
60 };
61
62 struct atom_table
63 {
64     struct object       obj;                 /* object header */
65     int                 count;               /* count of atom handles */
66     int                 last;                /* last handle in-use */
67     struct atom_entry **handles;             /* atom handles */
68     int                 entries_count;       /* number of hash entries */
69     struct atom_entry **entries;             /* hash table entries */
70 };
71
72 static void atom_table_dump( struct object *obj, int verbose );
73 static void atom_table_destroy( struct object *obj );
74
75 static const struct object_ops atom_table_ops =
76 {
77     sizeof(struct atom_table),    /* size */
78     atom_table_dump,              /* dump */
79     no_get_type,                  /* get_type */
80     no_add_queue,                 /* add_queue */
81     NULL,                         /* remove_queue */
82     NULL,                         /* signaled */
83     NULL,                         /* satisfied */
84     no_signal,                    /* signal */
85     no_get_fd,                    /* get_fd */
86     no_map_access,                /* map_access */
87     default_get_sd,               /* get_sd */
88     default_set_sd,               /* set_sd */
89     no_lookup_name,               /* lookup_name */
90     no_open_file,                 /* open_file */
91     no_close_handle,              /* close_handle */
92     atom_table_destroy            /* destroy */
93 };
94
95 static struct atom_table *global_table;
96
97 /* create an atom table */
98 static struct atom_table *create_table(int entries_count)
99 {
100     struct atom_table *table;
101
102     if ((table = alloc_object( &atom_table_ops )))
103     {
104         if ((entries_count < MIN_HASH_SIZE) ||
105             (entries_count > MAX_HASH_SIZE)) entries_count = HASH_SIZE;
106         table->entries_count = entries_count;
107         if (!(table->entries = malloc( sizeof(*table->entries) * table->entries_count )))
108         {
109             set_error( STATUS_NO_MEMORY );
110             goto fail;
111         }
112         memset( table->entries, 0, sizeof(*table->entries) * table->entries_count );
113         table->count = 64;
114         table->last  = -1;
115         if ((table->handles = mem_alloc( sizeof(*table->handles) * table->count )))
116             return table;
117 fail:
118         release_object( table );
119         table = NULL;
120     }
121     return table;
122 }
123
124 /* retrieve an entry pointer from its atom */
125 static struct atom_entry *get_atom_entry( struct atom_table *table, atom_t atom )
126 {
127     struct atom_entry *entry = NULL;
128     if (table && (atom >= MIN_STR_ATOM) && (atom <= MIN_STR_ATOM + table->last))
129         entry = table->handles[atom - MIN_STR_ATOM];
130     if (!entry) set_error( STATUS_INVALID_HANDLE );
131     return entry;
132 }
133
134 /* add an atom entry in the table and return its handle */
135 static atom_t add_atom_entry( struct atom_table *table, struct atom_entry *entry )
136 {
137     int i;
138     for (i = 0; i <= table->last; i++)
139         if (!table->handles[i]) goto found;
140     if (i == table->count)
141     {
142         struct atom_entry **new_table = NULL;
143         int new_size = table->count + table->count / 2;
144         if (new_size > MAX_ATOMS) new_size = MAX_ATOMS;
145         if (new_size > table->count)
146             new_table = realloc( table->handles, sizeof(*table->handles) * new_size );
147         if (!new_table)
148         {
149             set_error( STATUS_NO_MEMORY );
150             return 0;
151         }
152         table->count = new_size;
153         table->handles = new_table;
154     }
155     table->last = i;
156  found:
157     table->handles[i] = entry;
158     entry->atom = i + MIN_STR_ATOM;
159     return entry->atom;
160 }
161
162 /* compute the hash code for a string */
163 static unsigned short atom_hash( struct atom_table *table, const struct unicode_str *str )
164 {
165     unsigned int i;
166     unsigned short hash = 0;
167     for (i = 0; i < str->len / sizeof(WCHAR); i++) hash ^= toupperW(str->str[i]) + i;
168     return hash % table->entries_count;
169 }
170
171 /* dump an atom table */
172 static void atom_table_dump( struct object *obj, int verbose )
173 {
174     int i;
175     struct atom_table *table = (struct atom_table *)obj;
176     assert( obj->ops == &atom_table_ops );
177
178     fprintf( stderr, "Atom table size=%d entries=%d\n",
179              table->last + 1, table->entries_count );
180     if (!verbose) return;
181     for (i = 0; i <= table->last; i++)
182     {
183         struct atom_entry *entry = table->handles[i];
184         if (!entry) continue;
185         fprintf( stderr, "  %04x: ref=%d pinned=%c hash=%d \"",
186                  entry->atom, entry->count, entry->pinned ? 'Y' : 'N', entry->hash );
187         dump_strW( entry->str, entry->len / sizeof(WCHAR), stderr, "\"\"");
188         fprintf( stderr, "\"\n" );
189     }
190 }
191
192 /* destroy the atom table */
193 static void atom_table_destroy( struct object *obj )
194 {
195     int i;
196     struct atom_table *table = (struct atom_table *)obj;
197     assert( obj->ops == &atom_table_ops );
198     if (table->handles)
199     {
200         for (i = 0; i <= table->last; i++) free( table->handles[i] );
201         free( table->handles );
202     }
203     free( table->entries );
204 }
205
206 /* find an atom entry in its hash list */
207 static struct atom_entry *find_atom_entry( struct atom_table *table, const struct unicode_str *str,
208                                            unsigned short hash )
209 {
210     struct atom_entry *entry = table->entries[hash];
211     while (entry)
212     {
213         if (entry->len == str->len && !memicmpW( entry->str, str->str, str->len/sizeof(WCHAR) )) break;
214         entry = entry->next;
215     }
216     return entry;
217 }
218
219 /* add an atom to the table */
220 static atom_t add_atom( struct atom_table *table, const struct unicode_str *str )
221 {
222     struct atom_entry *entry;
223     unsigned short hash = atom_hash( table, str );
224     atom_t atom = 0;
225
226     if (!str->len)
227     {
228         set_error( STATUS_OBJECT_NAME_INVALID );
229         return 0;
230     }
231     if (str->len > MAX_ATOM_LEN)
232     {
233         set_error( STATUS_INVALID_PARAMETER );
234         return 0;
235     }
236     if ((entry = find_atom_entry( table, str, hash )))  /* exists already */
237     {
238         entry->count++;
239         return entry->atom;
240     }
241
242     if ((entry = mem_alloc( FIELD_OFFSET( struct atom_entry, str[str->len / sizeof(WCHAR)] ) )))
243     {
244         if ((atom = add_atom_entry( table, entry )))
245         {
246             entry->prev  = NULL;
247             if ((entry->next = table->entries[hash])) entry->next->prev = entry;
248             table->entries[hash] = entry;
249             entry->count  = 1;
250             entry->pinned = 0;
251             entry->hash   = hash;
252             entry->len    = str->len;
253             memcpy( entry->str, str->str, str->len );
254         }
255         else free( entry );
256     }
257     else set_error( STATUS_NO_MEMORY );
258     return atom;
259 }
260
261 /* delete an atom from the table */
262 static void delete_atom( struct atom_table *table, atom_t atom, int if_pinned )
263 {
264     struct atom_entry *entry = get_atom_entry( table, atom );
265     if (!entry) return;
266     if (entry->pinned && !if_pinned) set_error( STATUS_WAS_LOCKED );
267     else if (!--entry->count)
268     {
269         if (entry->next) entry->next->prev = entry->prev;
270         if (entry->prev) entry->prev->next = entry->next;
271         else table->entries[entry->hash] = entry->next;
272         table->handles[atom - MIN_STR_ATOM] = NULL;
273         free( entry );
274     }
275 }
276
277 /* find an atom in the table */
278 static atom_t find_atom( struct atom_table *table, const struct unicode_str *str )
279 {
280     struct atom_entry *entry;
281
282     if (!str->len)
283     {
284         set_error( STATUS_OBJECT_NAME_INVALID );
285         return 0;
286     }
287     if (str->len > MAX_ATOM_LEN)
288     {
289         set_error( STATUS_INVALID_PARAMETER );
290         return 0;
291     }
292     if (table && (entry = find_atom_entry( table, str, atom_hash(table, str) )))
293         return entry->atom;
294     set_error( STATUS_OBJECT_NAME_NOT_FOUND );
295     return 0;
296 }
297
298 static struct atom_table *get_global_table( struct winstation *winstation, int create )
299 {
300     struct atom_table *table = winstation ? winstation->atom_table : global_table;
301     if (!table)
302     {
303         if (create)
304         {
305             table = create_table( HASH_SIZE );
306             if (winstation) winstation->atom_table = table;
307             else
308             {
309                 global_table = table;
310                 make_object_static( &global_table->obj );
311             }
312         }
313         else set_error( STATUS_OBJECT_NAME_NOT_FOUND );
314     }
315     return table;
316 }
317
318 static struct atom_table *get_table( obj_handle_t h, int create )
319 {
320     struct atom_table *table = NULL;
321
322     if (h)
323     {
324         table = (struct atom_table *)get_handle_obj( current->process, h, 0, &atom_table_ops );
325     }
326     else
327     {
328         table = get_global_table( NULL, 1 );
329         if (table) grab_object( table );
330     }
331     return table;
332 }
333
334 /* add an atom in the global table; used for window properties */
335 atom_t add_global_atom( struct winstation *winstation, const struct unicode_str *str )
336 {
337     struct atom_table *global_table = get_global_table( winstation, 1 );
338     if (!global_table) return 0;
339     return add_atom( global_table, str );
340 }
341
342 /* find an atom in the global table; used for window properties */
343 atom_t find_global_atom( struct winstation *winstation, const struct unicode_str *str )
344 {
345     struct atom_table *global_table = get_global_table( winstation, 0 );
346     struct atom_entry *entry;
347
348     if (!str->len || str->len > MAX_ATOM_LEN || !global_table) return 0;
349     if ((entry = find_atom_entry( global_table, str, atom_hash(global_table, str) )))
350         return entry->atom;
351     return 0;
352 }
353
354 /* increment the ref count of a global atom; used for window properties */
355 int grab_global_atom( struct winstation *winstation, atom_t atom )
356 {
357     if (atom >= MIN_STR_ATOM)
358     {
359         struct atom_table *global_table = get_global_table( winstation, 0 );
360         if (global_table)
361         {
362             struct atom_entry *entry = get_atom_entry( global_table, atom );
363             if (entry) entry->count++;
364             return (entry != NULL);
365         }
366         else return 0;
367     }
368     else return 1;
369 }
370
371 /* decrement the ref count of a global atom; used for window properties */
372 void release_global_atom( struct winstation *winstation, atom_t atom )
373 {
374     if (atom >= MIN_STR_ATOM)
375     {
376         struct atom_table *global_table = get_global_table( winstation, 0 );
377         if (global_table) delete_atom( global_table, atom, 1 );
378     }
379 }
380
381 /* add a global atom */
382 DECL_HANDLER(add_atom)
383 {
384     struct unicode_str name;
385     struct atom_table *table = get_table( req->table, 1 );
386
387     if (table)
388     {
389         get_req_unicode_str( &name );
390         reply->atom = add_atom( table, &name );
391         release_object( table );
392     }
393 }
394
395 /* delete a global atom */
396 DECL_HANDLER(delete_atom)
397 {
398     struct atom_table *table = get_table( req->table, 0 );
399     if (table)
400     {
401         delete_atom( table, req->atom, 0 );
402         release_object( table );
403     }
404 }
405
406 /* find a global atom */
407 DECL_HANDLER(find_atom)
408 {
409     struct unicode_str name;
410     struct atom_table *table = get_table( req->table, 0 );
411
412     if (table)
413     {
414         get_req_unicode_str( &name );
415         reply->atom = find_atom( table, &name );
416         release_object( table );
417     }
418 }
419
420 /* get global atom name */
421 DECL_HANDLER(get_atom_information)
422 {
423     struct atom_table *table = get_table( req->table, 0 );
424     if (table)
425     {
426         struct atom_entry *entry;
427
428         if ((entry = get_atom_entry( table, req->atom )))
429         {
430             set_reply_data( entry->str, min( entry->len, get_reply_max_size() ));
431             reply->count = entry->count;
432             reply->pinned = entry->pinned;
433             reply->total = entry->len;
434         }
435         else reply->count = -1;
436         release_object( table );
437     }
438 }
439
440 /* set global atom name */
441 DECL_HANDLER(set_atom_information)
442 {
443     struct atom_table *table = get_table( req->table, 0 );
444     if (table)
445     {
446         struct atom_entry *entry;
447
448         if ((entry = get_atom_entry( table, req->atom )))
449         {
450             if (req->pinned) entry->pinned = 1;
451         }
452         release_object( table );
453     }
454 }
455
456 /* init a (local) atom table */
457 DECL_HANDLER(init_atom_table)
458 {
459     struct atom_table* table = create_table( req->entries );
460
461     if (table)
462     {
463         reply->table = alloc_handle( current->process, table, 0, 0 );
464         release_object( table );
465     }
466 }
467
468 /* set global atom name */
469 DECL_HANDLER(empty_atom_table)
470 {
471     struct atom_table *table = get_table( req->table, 1 );
472     if (table)
473     {
474         int i;
475         struct atom_entry *entry;
476
477         for (i = 0; i <= table->last; i++)
478         {
479             entry = table->handles[i];
480             if (entry && (!entry->pinned || req->if_pinned))
481             {
482                 if (entry->next) entry->next->prev = entry->prev;
483                 if (entry->prev) entry->prev->next = entry->next;
484                 else table->entries[entry->hash] = entry->next;
485                 table->handles[i] = NULL;
486                 free( entry );
487             }
488         }
489         release_object( table );
490     }
491 }