wine/server/atom.c
Alexandre Julliard 0f273c17ff server: Added data_size_t type to represent sizes in the server protocol.
Make it an unsigned int to save some space on Win64, if we need to
transfer more than 4Gb over the server pipe something is seriously
wrong.
2006-07-26 11:11:03 +02:00

484 lines
14 KiB
C

/*
* 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., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA
*/
#include "config.h"
#include "wine/port.h"
#include <assert.h>
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#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_map_access, /* map_access */
no_lookup_name, /* lookup_name */
no_close_handle, /* close_handle */
atom_table_destroy /* destroy */
};
static struct atom_table *global_table;
/* 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, data_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,
data_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, data_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, data_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 )
{
struct atom_table *table = winstation ? winstation->atom_table : global_table;
if (!table)
{
if (create)
{
table = create_table( HASH_SIZE );
if (winstation) winstation->atom_table = table;
else
{
global_table = table;
make_object_static( &global_table->obj );
}
}
else set_error( STATUS_OBJECT_NAME_NOT_FOUND );
}
return 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
{
table = get_global_table( NULL, 1 );
if (table) grab_object( table );
}
return table;
}
/* add an atom in the global table; used for window properties */
atom_t add_global_atom( struct winstation *winstation, const WCHAR *str, data_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, data_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 )))
{
data_size_t len = entry->len * sizeof(WCHAR);
if (get_reply_max_size())
set_reply_data( entry->str, min( len, get_reply_max_size()));
reply->count = entry->count;
reply->pinned = entry->pinned;
reply->total = len;
}
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, 0 );
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 );
}
}