/* * Server-side atom management * * Copyright (C) 1999, 2000 Alexandre Julliard * Copyright (C) 2000 Turchanov Sergei * * 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 "config.h" #include "wine/port.h" #include #include #include #include #include "ntstatus.h" #define WIN32_NO_STATUS #include "unicode.h" #include "request.h" #include "object.h" #include "process.h" #include "handle.h" #include "user.h" #include "winuser.h" #include "winternl.h" #define HASH_SIZE 37 #define MIN_HASH_SIZE 4 #define MAX_HASH_SIZE 0x200 #define MAX_ATOM_LEN 255 #define MIN_STR_ATOM 0xc000 #define MAX_ATOMS 0x4000 struct atom_entry { struct atom_entry *next; /* hash table list */ struct atom_entry *prev; /* hash table list */ int count; /* reference count */ short pinned; /* whether the atom is pinned or not */ atom_t atom; /* atom handle */ unsigned short hash; /* string hash */ unsigned short len; /* string len */ WCHAR str[1]; /* atom string */ }; struct atom_table { struct object obj; /* object header */ int count; /* count of atom handles */ int last; /* last handle in-use */ struct atom_entry **handles; /* atom handles */ int entries_count; /* humber of hash entries */ struct atom_entry **entries; /* hash table entries */ }; static void atom_table_dump( struct object *obj, int verbose ); static void atom_table_destroy( struct object *obj ); static const struct object_ops atom_table_ops = { sizeof(struct atom_table), /* size */ atom_table_dump, /* dump */ no_add_queue, /* add_queue */ NULL, /* remove_queue */ NULL, /* signaled */ NULL, /* satisfied */ no_signal, /* signal */ no_get_fd, /* get_fd */ no_lookup_name, /* lookup_name */ no_close_handle, /* close_handle */ atom_table_destroy /* destroy */ }; /* create an atom table */ static struct atom_table *create_table(int entries_count) { struct atom_table *table; if ((table = alloc_object( &atom_table_ops ))) { if ((entries_count < MIN_HASH_SIZE) || (entries_count > MAX_HASH_SIZE)) entries_count = HASH_SIZE; table->entries_count = entries_count; if (!(table->entries = malloc( sizeof(*table->entries) * table->entries_count ))) { set_error( STATUS_NO_MEMORY ); goto fail; } memset( table->entries, 0, sizeof(*table->entries) * table->entries_count ); table->count = 64; table->last = -1; if ((table->handles = mem_alloc( sizeof(*table->handles) * table->count ))) return table; fail: release_object( table ); table = NULL; } return table; } /* retrieve an entry pointer from its atom */ static struct atom_entry *get_atom_entry( struct atom_table *table, atom_t atom ) { struct atom_entry *entry = NULL; if (table && (atom >= MIN_STR_ATOM) && (atom <= MIN_STR_ATOM + table->last)) entry = table->handles[atom - MIN_STR_ATOM]; if (!entry) set_error( STATUS_INVALID_HANDLE ); return entry; } /* add an atom entry in the table and return its handle */ static atom_t add_atom_entry( struct atom_table *table, struct atom_entry *entry ) { int i; for (i = 0; i <= table->last; i++) if (!table->handles[i]) goto found; if (i == table->count) { struct atom_entry **new_table = NULL; int new_size = table->count + table->count / 2; if (new_size > MAX_ATOMS) new_size = MAX_ATOMS; if (new_size > table->count) new_table = realloc( table->handles, sizeof(*table->handles) * new_size ); if (!new_table) { set_error( STATUS_NO_MEMORY ); return 0; } table->count = new_size; table->handles = new_table; } table->last = i; found: table->handles[i] = entry; entry->atom = i + MIN_STR_ATOM; return entry->atom; } /* compute the hash code for a string */ static unsigned short atom_hash( struct atom_table *table, const WCHAR *str, size_t len ) { unsigned int i; unsigned short hash = 0; for (i = 0; i < len; i++) hash ^= toupperW(str[i]) + i; return hash % table->entries_count; } /* dump an atom table */ static void atom_table_dump( struct object *obj, int verbose ) { int i; struct atom_table *table = (struct atom_table *)obj; assert( obj->ops == &atom_table_ops ); fprintf( stderr, "Atom table size=%d entries=%d\n", table->last + 1, table->entries_count ); if (!verbose) return; for (i = 0; i <= table->last; i++) { struct atom_entry *entry = table->handles[i]; if (!entry) continue; fprintf( stderr, " %04x: ref=%d pinned=%c hash=%d \"", entry->atom, entry->count, entry->pinned ? 'Y' : 'N', entry->hash ); dump_strW( entry->str, entry->len, stderr, "\"\""); fprintf( stderr, "\"\n" ); } } /* destroy the atom table */ static void atom_table_destroy( struct object *obj ) { int i; struct atom_table *table = (struct atom_table *)obj; assert( obj->ops == &atom_table_ops ); if (table->handles) { for (i = 0; i <= table->last; i++) free( table->handles[i] ); free( table->handles ); } if (table->entries) free( table->entries ); } /* find an atom entry in its hash list */ static struct atom_entry *find_atom_entry( struct atom_table *table, const WCHAR *str, size_t len, unsigned short hash ) { struct atom_entry *entry = table->entries[hash]; while (entry) { if (entry->len == len && !memicmpW( entry->str, str, len )) break; entry = entry->next; } return entry; } /* add an atom to the table */ static atom_t add_atom( struct atom_table *table, const WCHAR *str, size_t len ) { struct atom_entry *entry; unsigned short hash = atom_hash( table, str, len ); atom_t atom = 0; if (!len) { set_error( STATUS_OBJECT_NAME_INVALID ); return 0; } if (len > MAX_ATOM_LEN) { set_error( STATUS_INVALID_PARAMETER ); return 0; } if ((entry = find_atom_entry( table, str, len, hash ))) /* exists already */ { entry->count++; return entry->atom; } if ((entry = mem_alloc( sizeof(*entry) + (len - 1) * sizeof(WCHAR) ))) { if ((atom = add_atom_entry( table, entry ))) { entry->prev = NULL; if ((entry->next = table->entries[hash])) entry->next->prev = entry; table->entries[hash] = entry; entry->count = 1; entry->pinned = 0; entry->hash = hash; entry->len = len; memcpy( entry->str, str, len * sizeof(WCHAR) ); } else free( entry ); } else set_error( STATUS_NO_MEMORY ); return atom; } /* delete an atom from the table */ static void delete_atom( struct atom_table *table, atom_t atom, int if_pinned ) { struct atom_entry *entry = get_atom_entry( table, atom ); if (!entry) return; if (entry->pinned && !if_pinned) set_error( STATUS_WAS_LOCKED ); else if (!--entry->count) { if (entry->next) entry->next->prev = entry->prev; if (entry->prev) entry->prev->next = entry->next; else table->entries[entry->hash] = entry->next; table->handles[atom - MIN_STR_ATOM] = NULL; free( entry ); } } /* find an atom in the table */ static atom_t find_atom( struct atom_table *table, const WCHAR *str, size_t len ) { struct atom_entry *entry; if (!len) { set_error( STATUS_OBJECT_NAME_INVALID ); return 0; } if (len > MAX_ATOM_LEN) { set_error( STATUS_INVALID_PARAMETER ); return 0; } if (table && (entry = find_atom_entry( table, str, len, atom_hash(table, str, len) ))) return entry->atom; set_error( STATUS_OBJECT_NAME_NOT_FOUND ); return 0; } static struct atom_table *get_global_table( struct winstation *winstation, int create ) { if (!winstation->atom_table) { if (create) winstation->atom_table = create_table( HASH_SIZE ); else set_error( STATUS_OBJECT_NAME_NOT_FOUND ); } return winstation->atom_table; } static struct atom_table *get_table( obj_handle_t h, int create ) { struct atom_table *table = NULL; if (h) { table = (struct atom_table *)get_handle_obj( current->process, h, 0, &atom_table_ops ); } else { struct winstation *winstation = get_process_winstation( current->process, WINSTA_ACCESSGLOBALATOMS ); if (winstation) { table = get_global_table( winstation, 1 ); if (table) grab_object( table ); release_object( winstation ); } } return table; } /* add an atom in the global table; used for window properties */ atom_t add_global_atom( struct winstation *winstation, const WCHAR *str, size_t len ) { struct atom_table *global_table = get_global_table( winstation, 1 ); if (!global_table) return 0; return add_atom( global_table, str, len ); } /* find an atom in the global table; used for window properties */ atom_t find_global_atom( struct winstation *winstation, const WCHAR *str, size_t len ) { struct atom_table *global_table = get_global_table( winstation, 0 ); struct atom_entry *entry; if (!len || len > MAX_ATOM_LEN || !global_table) return 0; if ((entry = find_atom_entry( global_table, str, len, atom_hash(global_table, str, len) ))) return entry->atom; return 0; } /* increment the ref count of a global atom; used for window properties */ int grab_global_atom( struct winstation *winstation, atom_t atom ) { if (atom >= MIN_STR_ATOM) { struct atom_table *global_table = get_global_table( winstation, 0 ); if (global_table) { struct atom_entry *entry = get_atom_entry( global_table, atom ); if (entry) entry->count++; return (entry != NULL); } else return 0; } else return 1; } /* decrement the ref count of a global atom; used for window properties */ void release_global_atom( struct winstation *winstation, atom_t atom ) { if (atom >= MIN_STR_ATOM) { struct atom_table *global_table = get_global_table( winstation, 0 ); if (global_table) delete_atom( global_table, atom, 1 ); } } /* add a global atom */ DECL_HANDLER(add_atom) { struct atom_table *table = get_table( req->table, 1 ); if (table) { reply->atom = add_atom( table, get_req_data(), get_req_data_size() / sizeof(WCHAR) ); release_object( table ); } } /* delete a global atom */ DECL_HANDLER(delete_atom) { struct atom_table *table = get_table( req->table, 0 ); if (table) { delete_atom( table, req->atom, 0 ); release_object( table ); } } /* find a global atom */ DECL_HANDLER(find_atom) { struct atom_table *table = get_table( req->table, 0 ); if (table) { reply->atom = find_atom( table, get_req_data(), get_req_data_size() / sizeof(WCHAR) ); release_object( table ); } } /* get global atom name */ DECL_HANDLER(get_atom_information) { struct atom_table *table = get_table( req->table, 0 ); if (table) { struct atom_entry *entry; if ((entry = get_atom_entry( table, req->atom ))) { size_t len = entry->len * sizeof(WCHAR); if (len <= get_reply_max_size()) set_reply_data( entry->str, len ); else if (get_reply_max_size()) set_error( STATUS_BUFFER_OVERFLOW ); reply->count = entry->count; reply->pinned = entry->pinned; } else reply->count = -1; release_object( table ); } } /* set global atom name */ DECL_HANDLER(set_atom_information) { struct atom_table *table = get_table( req->table, 0 ); if (table) { struct atom_entry *entry; if ((entry = get_atom_entry( table, req->atom ))) { if (req->pinned) entry->pinned = 1; } release_object( table ); } } /* init a (local) atom table */ DECL_HANDLER(init_atom_table) { struct atom_table* table = create_table( req->entries ); if (table) { reply->table = alloc_handle( current->process, table, 0, FALSE); release_object( table ); } } /* set global atom name */ DECL_HANDLER(empty_atom_table) { struct atom_table *table = get_table( req->table, 1 ); if (table) { int i; struct atom_entry *entry; for (i = 0; i <= table->last; i++) { entry = table->handles[i]; if (entry && (!entry->pinned || req->if_pinned)) { if (entry->next) entry->next->prev = entry->prev; if (entry->prev) entry->prev->next = entry->next; else table->entries[entry->hash] = entry->next; table->handles[i] = NULL; free( entry ); } } release_object( table ); } }