/*
* handles.c: Generic and internal operations on handles
*
* Author:
* Dick Porter (dick@ximian.com)
*
* (C) 2002-2006 Novell, Inc.
*/
#include <config.h>
#include <glib.h>
#include <pthread.h>
#include <errno.h>
#include <unistd.h>
#include <string.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <sys/un.h>
#include <sys/mman.h>
#include <dirent.h>
#include <sys/stat.h>
#include <mono/os/gc_wrapper.h>
#include <mono/io-layer/wapi.h>
#include <mono/io-layer/wapi-private.h>
#include <mono/io-layer/handles-private.h>
#include <mono/io-layer/mono-mutex.h>
#include <mono/io-layer/misc-private.h>
#include <mono/io-layer/shared.h>
#include <mono/io-layer/collection.h>
#include <mono/io-layer/process-private.h>
#undef DEBUG
#undef DEBUG_REFS
static void (*_wapi_handle_ops_get_close_func (WapiHandleType type))(gpointer, gpointer);
static WapiHandleCapability handle_caps[WAPI_HANDLE_COUNT]={0};
static struct _WapiHandleOps *handle_ops[WAPI_HANDLE_COUNT]={
NULL,
&_wapi_file_ops,
&_wapi_console_ops,
&_wapi_thread_ops,
&_wapi_sem_ops,
&_wapi_mutex_ops,
&_wapi_event_ops,
&_wapi_socket_ops,
&_wapi_find_ops,
&_wapi_process_ops,
&_wapi_pipe_ops,
&_wapi_namedmutex_ops,
&_wapi_namedsem_ops,
&_wapi_namedevent_ops,
};
static void _wapi_shared_details (gpointer handle_info);
static void (*handle_details[WAPI_HANDLE_COUNT])(gpointer) = {
NULL,
_wapi_file_details,
_wapi_console_details,
_wapi_shared_details, /* thread */
_wapi_sem_details,
_wapi_mutex_details,
_wapi_event_details,
NULL, /* Nothing useful to see in a socket handle */
NULL, /* Nothing useful to see in a find handle */
_wapi_shared_details, /* process */
_wapi_pipe_details,
_wapi_shared_details, /* namedmutex */
_wapi_shared_details, /* namedsem */
_wapi_shared_details, /* namedevent */
};
const char *_wapi_handle_typename[] = {
"Unused",
"File",
"Console",
"Thread",
"Sem",
"Mutex",
"Event",
"Socket",
"Find",
"Process",
"Pipe",
"N.Mutex",
"N.Sem",
"N.Event",
"Error!!"
};
/*
* We can hold _WAPI_PRIVATE_MAX_SLOTS * _WAPI_HANDLE_INITIAL_COUNT handles.
* If 4M handles are not enough... Oh, well... we will crash.
*/
#define SLOT_INDEX(x) (x / _WAPI_HANDLE_INITIAL_COUNT)
#define SLOT_OFFSET(x) (x % _WAPI_HANDLE_INITIAL_COUNT)
struct _WapiHandleUnshared *_wapi_private_handles [_WAPI_PRIVATE_MAX_SLOTS];
static guint32 _wapi_private_handle_count = 0;
struct _WapiHandleSharedLayout *_wapi_shared_layout = NULL;
struct _WapiFileShareLayout *_wapi_fileshare_layout = NULL;
guint32 _wapi_fd_reserve;
mono_mutex_t _wapi_global_signal_mutex;
pthread_cond_t _wapi_global_signal_cond;
int _wapi_sem_id;
/* Use this instead of getpid(), to cope with linuxthreads. It's a
* function rather than a variable lookup because we need to get at
* this before share_init() might have been called.
*/
static pid_t _wapi_pid;
static mono_once_t pid_init_once = MONO_ONCE_INIT;
static void pid_init (void)
{
_wapi_pid = getpid ();
}
pid_t _wapi_getpid (void)
{
mono_once (&pid_init_once, pid_init);
return(_wapi_pid);
}
static mono_mutex_t scan_mutex = MONO_MUTEX_INITIALIZER;
static void handle_cleanup (void)
{
int i, j, k;
_wapi_process_signal_self ();
/* Every shared handle we were using ought really to be closed
* by now, but to make sure just blow them all away. The
* exiting finalizer thread in particular races us to the
* program exit and doesn't always win, so it can be left
* cluttering up the shared file. Anything else left over is
* really a bug.
*/
for(i = SLOT_INDEX (0); _wapi_private_handles[i] != NULL; i++) {
for(j = SLOT_OFFSET (0); j < _WAPI_HANDLE_INITIAL_COUNT; j++) {
struct _WapiHandleUnshared *handle_data = &_wapi_private_handles[i][j];
int type = handle_data->type;
if (_WAPI_SHARED_HANDLE (type)) {
gpointer handle = GINT_TO_POINTER (i*_WAPI_HANDLE_INITIAL_COUNT+j);
if (type == WAPI_HANDLE_THREAD) {
/* Special-case thread handles
* because they need extra
* cleanup. This also avoids
* a race condition between
* the application exit and
* the finalizer thread - if
* it finishes up between now
* and actual app termination
* it will find all its handle
* details have been blown
* away, so this sets those
* anyway.
*/
_wapi_thread_set_termination_details (handle, 0);
}
for(k = handle_data->ref; k > 0; k--) {
#ifdef DEBUG
g_message ("%s: unreffing %s handle %p", __func__, _wapi_handle_typename[type], handle);
#endif
_wapi_handle_unref (handle);
}
}
}
}
_wapi_shm_semaphores_remove ();
}
static mono_once_t shared_init_once = MONO_ONCE_INIT;
static void shared_init (void)
{
int thr_ret;
int idx = 0;
g_assert ((sizeof (handle_ops) / sizeof (handle_ops[0]))
== WAPI_HANDLE_COUNT);
_wapi_fd_reserve = getdtablesize();
do {
_wapi_private_handles [idx++] = g_new0 (struct _WapiHandleUnshared,
_WAPI_HANDLE_INITIAL_COUNT);
_wapi_private_handle_count += _WAPI_HANDLE_INITIAL_COUNT;
} while(_wapi_fd_reserve > _wapi_private_handle_count);
_wapi_shm_semaphores_init ();
_wapi_shared_layout = _wapi_shm_attach (WAPI_SHM_DATA);
g_assert (_wapi_shared_layout != NULL);
_wapi_fileshare_layout = _wapi_shm_attach (WAPI_SHM_FILESHARE);
g_assert (_wapi_fileshare_layout != NULL);
_wapi_collection_init ();
thr_ret = pthread_cond_init(&_wapi_global_signal_cond, NULL);
g_assert (thr_ret == 0);
thr_ret = mono_mutex_init(&_wapi_global_signal_mutex, NULL);
g_assert (thr_ret == 0);
/* Using g_atexit here instead of an explicit function call in
* a cleanup routine lets us cope when a third-party library
* calls exit (eg if an X client loses the connection to its
* server.)
*/
g_atexit (handle_cleanup);
}
static void _wapi_handle_init_shared (struct _WapiHandleShared *handle,
WapiHandleType type,
gpointer handle_specific)
{
handle->type = type;
handle->timestamp = (guint32)(time (NULL) & 0xFFFFFFFF);
handle->signalled = FALSE;
handle->handle_refs = 1;
if (handle_specific != NULL) {
memcpy (&handle->u, handle_specific, sizeof (handle->u));
}
}
static void _wapi_handle_init (struct _WapiHandleUnshared *handle,
WapiHandleType type, gpointer handle_specific)
{
int thr_ret;
handle->type = type;
handle->signalled = FALSE;
handle->ref = 1;
if (!_WAPI_SHARED_HANDLE(type)) {
thr_ret = pthread_cond_init (&handle->signal_cond, NULL);
g_assert (thr_ret == 0);
thr_ret = mono_mutex_init (&handle->signal_mutex, NULL);
g_assert (thr_ret == 0);
if (handle_specific != NULL) {
memcpy (&handle->u, handle_specific,
sizeof (handle->u));
}
}
}
static guint32 _wapi_handle_new_shared (WapiHandleType type,
gpointer handle_specific)
{
guint32 offset;
static guint32 last = 1;
int thr_ret;
/* Leave the first slot empty as a guard */
again:
/* FIXME: expandable array */
for(offset = last; offset <_WAPI_HANDLE_INITIAL_COUNT; offset++) {
struct _WapiHandleShared *handle = &_wapi_shared_layout->handles[offset];
if(handle->type == WAPI_HANDLE_UNUSED) {
thr_ret = _wapi_handle_lock_shared_handles ();
g_assert (thr_ret == 0);
if (InterlockedCompareExchange ((gint32 *)&handle->type, type, WAPI_HANDLE_UNUSED) == WAPI_HANDLE_UNUSED) {
last = offset + 1;
_wapi_handle_init_shared (handle, type,
handle_specific);
_wapi_handle_unlock_shared_handles ();
return(offset);
} else {
/* Someone else beat us to it, just
* continue looking
*/
}
_wapi_handle_unlock_shared_handles ();
}
}
if(last > 1) {
/* Try again from the beginning */
last = 1;
goto again;
}
/* Will need to expand the array. The caller will sort it out */
return(0);
}
/*
* _wapi_handle_new_internal:
* @type: Init handle to this type
*
* Search for a free handle and initialize it. Return the handle on
* success and 0 on failure. This is only called from
* _wapi_handle_new, and scan_mutex must be held.
*/
static guint32 _wapi_handle_new_internal (WapiHandleType type,
gpointer handle_specific)
{
guint32 i, k, count;
static guint32 last = 0;
gboolean retry = FALSE;
/* A linear scan should be fast enough. Start from the last
* allocation, assuming that handles are allocated more often
* than they're freed. Leave the space reserved for file
* descriptors
*/
if (last < _wapi_fd_reserve) {
last = _wapi_fd_reserve;
} else {
retry = TRUE;
}
again:
count = last;
for(i = SLOT_INDEX (count); _wapi_private_handles [i] != NULL; i++) {
for (k = SLOT_OFFSET (count); k < _WAPI_HANDLE_INITIAL_COUNT; k++) {
struct _WapiHandleUnshared *handle = &_wapi_private_handles [i][k];
if(handle->type == WAPI_HANDLE_UNUSED) {
last = count + 1;
_wapi_handle_init (handle, type, handle_specific);
return (count);
}
count++;
}
}
if(retry && last > _wapi_fd_reserve) {
/* Try again from the beginning */
last = _wapi_fd_reserve;
goto again;
}
/* Will need to expand the array. The caller will sort it out */
return(0);
}
gpointer _wapi_handle_new (WapiHandleType type, gpointer handle_specific)
{
guint32 handle_idx = 0;
gpointer handle;
int thr_ret;
mono_once (&shared_init_once, shared_init);
#ifdef DEBUG
g_message ("%s: Creating new handle of type %s", __func__,
_wapi_handle_typename[type]);
#endif
g_assert(!_WAPI_FD_HANDLE(type));
pthread_cleanup_push ((void(*)(void *))mono_mutex_unlock_in_cleanup,
(void *)&scan_mutex);
thr_ret = mono_mutex_lock (&scan_mutex);
g_assert (thr_ret == 0);
while ((handle_idx = _wapi_handle_new_internal (type, handle_specific)) == 0) {
/* Try and expand the array, and have another go */
int idx = SLOT_INDEX (_wapi_private_handle_count);
if (idx >= _WAPI_PRIVATE_MAX_SLOTS) {
break;
}
_wapi_private_handles [idx] = g_new0 (struct _WapiHandleUnshared,
_WAPI_HANDLE_INITIAL_COUNT);
_wapi_private_handle_count += _WAPI_HANDLE_INITIAL_COUNT;
}
thr_ret = mono_mutex_unlock (&scan_mutex);
g_assert (thr_ret == 0);
pthread_cleanup_pop (0);
if (handle_idx == 0) {
/* We ran out of slots */
handle = _WAPI_HANDLE_INVALID;
goto done;
}
/* Make sure we left the space for fd mappings */
g_assert (handle_idx >= _wapi_fd_reserve);
handle = GUINT_TO_POINTER (handle_idx);
#ifdef DEBUG
g_message ("%s: Allocated new handle %p", __func__, handle);
#endif
if (_WAPI_SHARED_HANDLE(type)) {
/* Add the shared section too */
guint32 ref;
ref = _wapi_handle_new_shared (type, handle_specific);
if (ref == 0) {
_wapi_handle_collect ();
ref = _wapi_handle_new_shared (type, handle_specific);
if (ref == 0) {
/* FIXME: grow the arrays */
handle = _WAPI_HANDLE_INVALID;
goto done;
}
}
_WAPI_PRIVATE_HANDLES(handle_idx).u.shared.offset = ref;
#ifdef DEBUG
g_message ("%s: New shared handle at offset 0x%x", __func__,
ref);
#endif
}
done:
return(handle);
}
gpointer _wapi_handle_new_from_offset (WapiHandleType type, guint32 offset,
gboolean timestamp)
{
guint32 handle_idx = 0;
gpointer handle = INVALID_HANDLE_VALUE;
int thr_ret, i, k;
struct _WapiHandleShared *shared;
guint32 now = (guint32)(time (NULL) & 0xFFFFFFFF);
mono_once (&shared_init_once, shared_init);
#ifdef DEBUG
g_message ("%s: Creating new handle of type %s to offset %d", __func__,
_wapi_handle_typename[type], offset);
#endif
g_assert(!_WAPI_FD_HANDLE(type));
g_assert(_WAPI_SHARED_HANDLE(type));
g_assert(offset != 0);
shared = &_wapi_shared_layout->handles[offset];
if (timestamp) {
/* Bump up the timestamp for this offset */
InterlockedExchange ((gint32 *)&shared->timestamp, now);
}
pthread_cleanup_push ((void(*)(void *))mono_mutex_unlock_in_cleanup,
(void *)&scan_mutex);
thr_ret = mono_mutex_lock (&scan_mutex);
g_assert (thr_ret == 0);
for (i = SLOT_INDEX (0); _wapi_private_handles [i] != NULL; i++) {
for (k = SLOT_OFFSET (0); k < _WAPI_HANDLE_INITIAL_COUNT; k++) {
struct _WapiHandleUnshared *handle_data = &_wapi_private_handles [i][k];
if (handle_data->type == type &&
handle_data->u.shared.offset == offset) {
handle = GUINT