| /* |
| * Kernel Services Thread |
| * |
| * Copyright 1999 Ulrich Weigand |
| */ |
| |
| #include <sys/time.h> |
| #include <unistd.h> |
| |
| #include "services.h" |
| #include "process.h" |
| #include "debugtools.h" |
| |
| DEFAULT_DEBUG_CHANNEL(timer) |
| |
| #define SERVICE_USE_OBJECT 0x0001 |
| #define SERVICE_USE_TIMEOUT 0x0002 |
| #define SERVICE_DISABLED 0x8000 |
| |
| typedef struct _SERVICE |
| { |
| struct _SERVICE *next; |
| HANDLE self; |
| |
| PAPCFUNC callback; |
| ULONG_PTR callback_arg; |
| |
| int flags; |
| |
| HANDLE object; |
| long rate; |
| |
| struct timeval expire; |
| |
| } SERVICE; |
| |
| typedef struct _SERVICETABLE |
| { |
| HANDLE thread; |
| |
| SERVICE *first; |
| DWORD counter; |
| |
| } SERVICETABLE; |
| |
| /*********************************************************************** |
| * SERVICE_AddTimeval |
| */ |
| static void SERVICE_AddTimeval( struct timeval *time, long delta ) |
| { |
| delta += time->tv_usec; |
| time->tv_sec += delta / 1000000L; |
| time->tv_usec = delta % 1000000L; |
| } |
| |
| /*********************************************************************** |
| * SERVICE_DiffTimeval |
| */ |
| static long SERVICE_DiffTimeval( struct timeval *time1, struct timeval *time2 ) |
| { |
| return ( time1->tv_sec - time2->tv_sec ) * 1000000L |
| + ( time1->tv_usec - time2->tv_usec ); |
| } |
| |
| /*********************************************************************** |
| * SERVICE_Loop |
| */ |
| static DWORD CALLBACK SERVICE_Loop( SERVICETABLE *service ) |
| { |
| HANDLE handles[MAXIMUM_WAIT_OBJECTS]; |
| int count = 0; |
| DWORD timeout = INFINITE; |
| DWORD retval = WAIT_FAILED; |
| |
| while ( TRUE ) |
| { |
| PAPCFUNC callback; |
| ULONG_PTR callback_arg; |
| SERVICE *s; |
| |
| /* Check whether some condition is fulfilled */ |
| |
| struct timeval curTime; |
| gettimeofday( &curTime, NULL ); |
| |
| HeapLock( GetProcessHeap() ); |
| |
| callback = NULL; |
| callback_arg = 0L; |
| for ( s = service->first; s; s = s->next ) |
| { |
| if ( s->flags & SERVICE_DISABLED ) |
| continue; |
| |
| if ( s->flags & SERVICE_USE_OBJECT ) |
| { |
| if ( retval >= WAIT_OBJECT_0 && retval < WAIT_OBJECT_0 + count ) |
| { |
| if ( handles[retval - WAIT_OBJECT_0] == s->object ) |
| { |
| retval = WAIT_TIMEOUT; |
| callback = s->callback; |
| callback_arg = s->callback_arg; |
| break; |
| } |
| } |
| } |
| |
| if ( s->flags & SERVICE_USE_TIMEOUT ) |
| { |
| if ((s->expire.tv_sec < curTime.tv_sec) || |
| ((s->expire.tv_sec == curTime.tv_sec) && |
| (s->expire.tv_usec <= curTime.tv_usec))) |
| { |
| SERVICE_AddTimeval( &s->expire, s->rate ); |
| callback = s->callback; |
| callback_arg = s->callback_arg; |
| break; |
| } |
| } |
| } |
| |
| HeapUnlock( GetProcessHeap() ); |
| |
| /* If found, call callback routine */ |
| |
| if ( callback ) |
| { |
| callback( callback_arg ); |
| continue; |
| } |
| |
| /* If not found, determine wait condition */ |
| |
| HeapLock( GetProcessHeap() ); |
| |
| count = 0; |
| timeout = INFINITE; |
| for ( s = service->first; s; s = s->next ) |
| { |
| if ( s->flags & SERVICE_DISABLED ) |
| continue; |
| |
| if ( s->flags & SERVICE_USE_OBJECT ) |
| if ( count < MAXIMUM_WAIT_OBJECTS ) |
| handles[count++] = s->object; |
| |
| if ( s->flags & SERVICE_USE_TIMEOUT ) |
| { |
| long delta = SERVICE_DiffTimeval( &s->expire, &curTime ); |
| long time = (delta + 999L) / 1000L; |
| if ( time < 1 ) time = 1; |
| if ( time < timeout ) timeout = time; |
| } |
| } |
| |
| HeapUnlock( GetProcessHeap() ); |
| |
| |
| /* Wait until some condition satisfied */ |
| |
| TRACE("Waiting for %d objects with timeout %ld\n", |
| count, timeout ); |
| |
| retval = WaitForMultipleObjectsEx( count, handles, |
| FALSE, timeout, TRUE ); |
| |
| TRACE("Wait returned: %ld\n", retval ); |
| } |
| |
| return 0L; |
| } |
| |
| /*********************************************************************** |
| * SERVICE_CreateServiceTable |
| */ |
| static BOOL SERVICE_CreateServiceTable( void ) |
| { |
| HANDLE thread; |
| SERVICETABLE *service_table; |
| PDB *pdb = PROCESS_Current(); |
| |
| service_table = HeapAlloc( GetProcessHeap(), HEAP_ZERO_MEMORY, sizeof(SERVICETABLE) ); |
| if ( !service_table ) |
| { |
| return FALSE; |
| } |
| |
| /* service_table field in PDB must be set *BEFORE* calling CreateThread |
| * otherwise the thread cleanup service will cause an infinite recursion |
| * when installed |
| */ |
| pdb->service_table = service_table; |
| |
| thread = CreateThread( NULL, 0, (LPTHREAD_START_ROUTINE)SERVICE_Loop, |
| service_table, 0, NULL ); |
| if ( thread == INVALID_HANDLE_VALUE ) |
| { |
| pdb->service_table = 0; |
| HeapFree( GetProcessHeap(), 0, service_table ); |
| return FALSE; |
| } |
| |
| service_table->thread = thread; |
| |
| return TRUE; |
| } |
| |
| /*********************************************************************** |
| * SERVICE_AddObject |
| * |
| * Warning: the object supplied by the caller must not be closed. It'll |
| * be destroyed when the service is deleted. It's up to the caller |
| * to ensure that object will not be destroyed in between. |
| */ |
| HANDLE SERVICE_AddObject( HANDLE object, |
| PAPCFUNC callback, ULONG_PTR callback_arg ) |
| { |
| SERVICE *s; |
| SERVICETABLE *service_table; |
| HANDLE handle; |
| |
| if ( object == INVALID_HANDLE_VALUE || !callback ) |
| return INVALID_HANDLE_VALUE; |
| |
| if (PROCESS_Current()->service_table == 0 && !SERVICE_CreateServiceTable()) |
| return INVALID_HANDLE_VALUE; |
| service_table = PROCESS_Current()->service_table; |
| |
| s = HeapAlloc( GetProcessHeap(), HEAP_ZERO_MEMORY, sizeof(SERVICE) ); |
| if ( !s ) return INVALID_HANDLE_VALUE; |
| |
| s->callback = callback; |
| s->callback_arg = callback_arg; |
| s->object = object; |
| s->flags = SERVICE_USE_OBJECT; |
| |
| HeapLock( GetProcessHeap() ); |
| |
| s->self = handle = (HANDLE)++service_table->counter; |
| s->next = service_table->first; |
| service_table->first = s; |
| |
| HeapUnlock( GetProcessHeap() ); |
| |
| QueueUserAPC( NULL, service_table->thread, 0L ); |
| |
| return handle; |
| } |
| |
| /*********************************************************************** |
| * SERVICE_AddTimer |
| */ |
| HANDLE SERVICE_AddTimer( LONG rate, |
| PAPCFUNC callback, ULONG_PTR callback_arg ) |
| { |
| SERVICE *s; |
| SERVICETABLE *service_table; |
| HANDLE handle; |
| |
| if ( !rate || !callback ) |
| return INVALID_HANDLE_VALUE; |
| |
| if (PROCESS_Current()->service_table == 0 && !SERVICE_CreateServiceTable()) |
| return INVALID_HANDLE_VALUE; |
| service_table = PROCESS_Current()->service_table; |
| |
| s = HeapAlloc( GetProcessHeap(), HEAP_ZERO_MEMORY, sizeof(SERVICE) ); |
| if ( !s ) return INVALID_HANDLE_VALUE; |
| |
| s->callback = callback; |
| s->callback_arg = callback_arg; |
| s->rate = rate; |
| s->flags = SERVICE_USE_TIMEOUT; |
| |
| gettimeofday( &s->expire, NULL ); |
| SERVICE_AddTimeval( &s->expire, s->rate ); |
| |
| HeapLock( GetProcessHeap() ); |
| |
| s->self = handle = (HANDLE)++service_table->counter; |
| s->next = service_table->first; |
| service_table->first = s; |
| |
| HeapUnlock( GetProcessHeap() ); |
| |
| QueueUserAPC( NULL, service_table->thread, 0L ); |
| |
| return handle; |
| } |
| |
| /*********************************************************************** |
| * SERVICE_Delete |
| */ |
| BOOL SERVICE_Delete( HANDLE service ) |
| { |
| HANDLE handle = INVALID_HANDLE_VALUE; |
| BOOL retv = FALSE; |
| SERVICE **s, *next; |
| SERVICETABLE *service_table; |
| |
| /* service table must have been created on previous SERVICE_Add??? call */ |
| if ((service_table = PROCESS_Current()->service_table) == 0) |
| return retv; |
| |
| HeapLock( GetProcessHeap() ); |
| |
| for ( s = &service_table->first; *s; s = &(*s)->next ) |
| { |
| if ( (*s)->self == service ) |
| { |
| if ( (*s)->flags & SERVICE_USE_OBJECT ) |
| handle = (*s)->object; |
| |
| next = (*s)->next; |
| HeapFree( GetProcessHeap(), 0, *s ); |
| *s = next; |
| retv = TRUE; |
| break; |
| } |
| } |
| |
| HeapUnlock( GetProcessHeap() ); |
| |
| if ( handle != INVALID_HANDLE_VALUE ) |
| CloseHandle( handle ); |
| |
| QueueUserAPC( NULL, service_table->thread, 0L ); |
| |
| return retv; |
| } |
| |
| /*********************************************************************** |
| * SERVICE_Enable |
| */ |
| BOOL SERVICE_Enable( HANDLE service ) |
| { |
| BOOL retv = FALSE; |
| SERVICE *s; |
| SERVICETABLE *service_table; |
| |
| /* service table must have been created on previous SERVICE_Add??? call */ |
| if ((service_table = PROCESS_Current()->service_table) == 0) |
| return retv; |
| |
| HeapLock( GetProcessHeap() ); |
| |
| for ( s = service_table->first; s; s = s->next ) |
| { |
| if ( s->self == service ) |
| { |
| if ( s->flags & SERVICE_DISABLED ) |
| { |
| s->flags &= ~SERVICE_DISABLED; |
| |
| if ( s->flags & SERVICE_USE_TIMEOUT ) |
| { |
| long delta; |
| struct timeval curTime; |
| gettimeofday( &curTime, NULL ); |
| |
| delta = SERVICE_DiffTimeval( &s->expire, &curTime ); |
| if ( delta > 0 ) |
| SERVICE_AddTimeval( &s->expire, |
| (delta / s->rate) * s->rate ); |
| } |
| } |
| retv = TRUE; |
| break; |
| } |
| } |
| |
| HeapUnlock( GetProcessHeap() ); |
| |
| QueueUserAPC( NULL, service_table->thread, 0L ); |
| |
| return retv; |
| } |
| |
| /*********************************************************************** |
| * SERVICE_Disable |
| */ |
| BOOL SERVICE_Disable( HANDLE service ) |
| { |
| BOOL retv = TRUE; |
| SERVICE *s; |
| SERVICETABLE *service_table; |
| |
| /* service table must have been created on previous SERVICE_Add??? call */ |
| if ((service_table = PROCESS_Current()->service_table) == 0) |
| return retv; |
| |
| HeapLock( GetProcessHeap() ); |
| |
| for ( s = service_table->first; s; s = s->next ) |
| { |
| if ( s->self == service ) |
| { |
| s->flags |= SERVICE_DISABLED; |
| retv = TRUE; |
| break; |
| } |
| } |
| |
| HeapUnlock( GetProcessHeap() ); |
| |
| QueueUserAPC( NULL, service_table->thread, 0L ); |
| |
| return retv; |
| } |
| |
| |