Subversion Repositories DevTools

Rev

Rev 6914 | Details | Compare with Previous | Last modification | View Log | RSS feed

Rev Author Line No. Line
6914 dpurdie 1
//==============================================================================
2
//
3
//     Copyright : (C) 2008 Videlli Limited
4
//     This file belongs to Videlli Limited
5
//
6
//==============================================================================
7
//
8
//     Purpose :
9
//     This file contains the win32 "buildtool attend" service source code.
10
//     This is a buildtool helper service, designed to "attend" to rogue processes
11
//     that insist on presenting a dialog on the console of unattended build machine
12
//     through termination.
13
//     This has the effect of holding a build thread to ransom.
14
//     The rules of engagement are:
15
//     1 a configured rogue process is the descendant of a configured ancestor process (the build daemon win32 service).
16
//     2 a configured rogue process has been running longer than a configured maximum run time.
17
//     Only when 1 and 2 are true will the rogue process be terminated.
18
//     The service is designed to be as lightweight as possible, checking for such a
19
//     scenario once every 60 seconds, or responding to a stop or shutdown request immediately.
20
//     The rogue tools are limited to a suite of borland tools at the time of writing,
21
//     though this list is configurable should the need arise to extend the list.
22
//     They are configured in the following registry key:
23
//     HKEY_LOCAL_MACHINE\\SYSTEM\\CurrentControlSet\\Services\\buildtool attend\\Parameters
24
//     AppParameters String Value.
25
//     This takes the form:
26
//     <fully qualified ancestor path> <max run time secs> <rogue child 1 process name> <rogue child 2 process name> ...
27
//     For example:
28
//     D:\\Work\\buildtool\\srvany.exe 60 bcc32.exe bpr2mak.exe brc32.exe brcc32.exe coff2omf.exe dcc32.exe ilink32.exe impdef.exe implib.exe make.exe tlib.exe tlibimp.exe
29
//     
30
//==============================================================================
31
 
32
#include <windows.h>
33
#include <iostream.h>
34
#include <vector>
35
#include <sstream>
36
#include "Psapi.h"
37
#include "Tlhelp32.h"
38
#include "Process.h"
39
 
40
void WINAPI ServiceMain( DWORD argc, LPTSTR * argv );
41
void WINAPI ControlHandler( DWORD dwControl );
42
void Start( DWORD argc, LPSTR * argv );
43
void Stop();
44
HANDLE hWaitEvent = 0L;
45
SERVICE_STATUS_HANDLE stat_hand = {0};
46
SERVICE_STATUS servstat = {0};
47
char *serviceName = "buildtool attend\0";
48
 
49
//------------------------------------------------------------------------------
50
//  log
51
//  Logs a message to the Application Event Log
52
//------------------------------------------------------------------------------
53
//  Parameters:
54
//      message IN - the message to log
55
//------------------------------------------------------------------------------
56
//  Returns:
57
//      None
58
//------------------------------------------------------------------------------
59
void log( const char * message )
60
{
61
	HANDLE hEventLog = RegisterEventSource( 0L, "Application" );
62
 
63
	if ( hEventLog != 0L )
64
	{
65
		ReportEvent(  hEventLog, EVENTLOG_INFORMATION_TYPE, 0, 0, 0L, 1, 0, &message, 0L );
66
		DeregisterEventSource( hEventLog );
67
	}
68
}
69
 
70
//------------------------------------------------------------------------------
71
//  isDescendedFrom
72
//  Determines if a child process is descended from an ancestor process
73
//------------------------------------------------------------------------------
74
//  Parameters:
75
//      ancestor IN - the ancestor process
76
//      child IN - the child process
77
//      processCollection IN - the collection of running processes
78
//------------------------------------------------------------------------------
79
//  Returns:
80
//      true if the child is descended from the ancestor
81
//------------------------------------------------------------------------------
82
bool isDescendedFrom( const std::string &ancestor, const Process &child, const std::vector< Process * > processCollection )
83
{
84
	bool retVal = false;
85
 
86
	if ( child.mPath == ancestor )
87
	{
88
		// child is descended from ancestor
89
		retVal = true;
90
	}
91
	else
92
	{
93
		// find the childs parent
94
		Process *parent = 0L;
95
		std::vector< Process * >::const_iterator processCollectionIterator = processCollection.begin();
96
 
97
		while ( processCollectionIterator != processCollection.end() )
98
		{
99
			Process *p = *processCollectionIterator;
100
			if ( child.mPpid == p->mPid  )
101
			{
102
				parent = p;
103
				break;
104
			}
105
 
106
			processCollectionIterator ++;
107
		}
108
 
109
		if ( parent != 0L )
110
		{
111
			retVal = isDescendedFrom( ancestor, *parent, processCollection );
112
		}
113
	}
114
 
115
	return retVal;
116
}
117
 
118
//------------------------------------------------------------------------------
119
//  main
120
//  This is the entry point for the process.
121
//  It sets up a service table entry and then calls StartServiceCtrlDispatcher.
122
//  This call does not return, but uses the thread which called it as a
123
//  control dispatcher for all the services implemented by this process.
124
//------------------------------------------------------------------------------
125
//  Parameters:
126
//      argc IN - the number of arguments on the command line
127
//      argv IN - the arguments on the command line
128
//------------------------------------------------------------------------------
129
//  Returns:
130
//      None
131
//------------------------------------------------------------------------------
132
void __cdecl main( DWORD argc, LPSTR * argv )
133
{
134
	// start the control dispatcher
135
	// this call gives the SCManager this thread for the entire period that this service is running,
136
	// so that it can call us back with service controls
137
	// it will spawn a new thread to run the service itself, starting at entrypoint ServiceMain
138
	SERVICE_TABLE_ENTRY stab[] = {
139
		{ serviceName, ServiceMain },
140
		{ NULL, NULL }
141
	};
142
 
143
	StartServiceCtrlDispatcher( stab );
144
}
145
 
146
//------------------------------------------------------------------------------
147
//  ServiceMain
148
//  This is the main entry point for the service.
149
//  The service control dispatcher creates a thread to start at this routine.
150
//------------------------------------------------------------------------------
151
//  Parameters:
152
//      argc IN - the number of arguments on the command line
153
//      argv IN - the arguments on the command line
154
//------------------------------------------------------------------------------
155
//  Returns:
156
//      None
157
//------------------------------------------------------------------------------
158
void WINAPI ServiceMain( DWORD argc, LPTSTR * argv )
159
{
160
	// register a service control handler for the service
161
	// this will be called by the control dispatcher when it has instructions for the service
162
	stat_hand = RegisterServiceCtrlHandler( serviceName, ControlHandler );
163
 
164
	if ( stat_hand == 0 )
165
	{
166
		std::string message = "ServiceMain RegisterServiceCrtlHandler failed with last error " + GetLastError();
167
		log( message.c_str() );
168
	}
169
 
170
	// create the handle used by the ControlHandler to signal to this method to return
171
	hWaitEvent = CreateEvent( 0L, FALSE, FALSE, 0L );
172
 
173
	if ( hWaitEvent == 0L )
174
	{
175
		std::string message = "ServiceMain CreateEvent failed with last error " + GetLastError();
176
		log( message.c_str() );
177
	}
178
 
179
	// inform the service control manager that the service is running
180
	servstat.dwServiceType = SERVICE_WIN32;
181
	servstat.dwCurrentState = SERVICE_RUNNING;
182
	servstat.dwControlsAccepted = SERVICE_ACCEPT_STOP | SERVICE_ACCEPT_SHUTDOWN;
183
	servstat.dwWin32ExitCode = NO_ERROR;
184
	servstat.dwServiceSpecificExitCode = 0;
185
	servstat.dwCheckPoint = 0;
186
	servstat.dwWaitHint = 0;
187
 
188
	if ( SetServiceStatus( stat_hand, &servstat ) == 0 )
189
	{
190
		std::string message = "ServiceMain SetServiceStatus 1 failed with last error " + GetLastError();
191
		log( message.c_str() );
192
	}
193
 
194
	HKEY hKey;
195
	char appParams[255];
196
	DWORD dwBufLen;
197
 
198
	RegOpenKeyEx( HKEY_LOCAL_MACHINE, "SYSTEM\\CurrentControlSet\\Services\\buildtool attend\\Parameters", 0, KEY_QUERY_VALUE, &hKey );
199
	RegQueryValueEx( hKey, "AppParameters", 0L, 0L, (LPBYTE) appParams, &dwBufLen);
200
	RegCloseKey( hKey );
201
 
202
	// convert appParams to args
203
	// calculate ac
204
	int ac = 0;
205
 
206
	// increment ac if a non space is read and either no previous character existed or previous character was space
207
	for ( int i = 0; i < strlen( appParams ); i++ )
208
	{
209
		if ( !isspace( appParams[ i ] ) && ( i == 0 || isspace( appParams[ i - 1] ) ) )
210
		{
211
			ac++;
212
		}
213
	}
214
 
215
	char ** args = new char * [ ac ];
216
	int arg = 0;
217
 
218
	for ( i = 0; i < strlen( appParams ); i++ )
219
	{
220
		if ( !isspace( appParams[ i ] ) && ( i == 0 || isspace( appParams[ i - 1] ) ) )
221
		{
222
			args[ arg ] = &appParams[ i ];
223
			arg++;
224
		}
225
	}
226
 
227
	int length = strlen( appParams );
228
 
229
	for ( i = 0; i < length; i++ )
230
	{
231
		if ( isspace( appParams[ i ] ) )
232
		{
233
			appParams[ i ] = '\0';
234
		}
235
	}
236
 
237
	for ( arg = 0; arg < ac; arg++ )
238
	{
239
		std::string message = "ServiceMain args[ ";
240
		std::string param;
241
		std::stringstream ss;
242
		ss << arg;
243
		ss >> param;
244
		message += param;
245
		message += "] = ";
246
		message += args[ arg ];
247
		log( message.c_str() );
248
	}
249
 
250
	// start the service
251
	Start( ac, args );
252
 
253
	// Start has returned because of a command
254
	// stop the service
255
	Stop();
256
 
257
	// inform the service control manager that the service is stopped
258
	servstat.dwCurrentState = SERVICE_STOPPED;
259
 
260
	if ( SetServiceStatus( stat_hand, &servstat ) == 0 )
261
	{
262
		std::string message = "ServiceMain SetServiceStatus 2 failed with last error " + GetLastError();
263
		log( message.c_str() );
264
	}
265
 
266
	return;
267
}
268
 
269
//------------------------------------------------------------------------------
270
//  ControlHandler
271
//  This is called by the control dispatcher when it has instructions for the service
272
//------------------------------------------------------------------------------
273
//  Parameters:
274
//      dwControl IN - the instruction
275
//------------------------------------------------------------------------------
276
//  Returns:
277
//      None
278
//------------------------------------------------------------------------------
279
void WINAPI ControlHandler( DWORD dwControl )
280
{
281
	switch( dwControl )
282
	{
283
	case SERVICE_CONTROL_PAUSE:
284
	case SERVICE_CONTROL_CONTINUE:
285
	case SERVICE_CONTROL_INTERROGATE:
286
		servstat.dwWaitHint = 0;
287
		break;
288
	case SERVICE_CONTROL_SHUTDOWN:
289
	case SERVICE_CONTROL_STOP:
290
		servstat.dwCurrentState = SERVICE_STOP_PENDING;
291
		servstat.dwWaitHint = 0;
292
		break;
293
	}
294
 
295
	// inform the service control manager of the service state
296
	if ( SetServiceStatus( stat_hand, &servstat ) == 0 )
297
	{
298
		std::string message = "ControlHandler SetServiceStatus failed with last error " + GetLastError();
299
		log( message.c_str() );
300
	}
301
 
302
	if ( ( dwControl == SERVICE_CONTROL_STOP ) || ( dwControl == SERVICE_CONTROL_SHUTDOWN ) )
303
	{
304
		log( "ControlHandler detected stop request" );
305
 
306
		// signal ServiceMain to return 
307
		if ( SetEvent( hWaitEvent ) == 0 )
308
		{
309
			std::string message = "ControlHandler SetEvent failed with last error " + GetLastError();
310
			log( message.c_str() );
311
		}
312
 
313
	}
314
}
315
 
316
//------------------------------------------------------------------------------
317
//  Start
318
//  The service logic, run when the service starts.
319
//------------------------------------------------------------------------------
320
//  Parameters:
321
//      argc IN - the number of arguments configured in the registry
322
//      argv IN - the arguments configured in the registry
323
//------------------------------------------------------------------------------
324
//  Returns:
325
//      None
326
//------------------------------------------------------------------------------
327
void Start( DWORD argc, LPSTR * argv )
328
{
329
	boolean doSomethingUseful = true;
330
 
331
	if ( argc < 3 )
332
	{
333
		log( "Start 1 configuration issue" );
334
		doSomethingUseful = false;
335
	}
336
 
337
	int maxRunTimeSecs = atoi( argv[ 1 ] );
338
 
339
	if ( maxRunTimeSecs == 0 )
340
	{
341
		log( "Start 2 configuration issue" );
342
		doSomethingUseful = false;
343
	}
344
 
345
	if ( doSomethingUseful )
346
	{
347
		std::vector< Process * > processCollection;
348
		std::string ancestor = argv[ 0 ];
349
		PROCESSENTRY32 entry;
350
		std::vector< Process * >::iterator processCollectionIterator = processCollection.begin();
351
		DWORD run = WAIT_TIMEOUT;
352
 
353
		// set the SeDebugPrivilege to prevent OpenProcess failing with access is denied
354
		LUID luid;
355
 
356
		if ( LookupPrivilegeValue( 0L, SE_DEBUG_NAME, &luid ) )
357
		{
358
			HANDLE hToken;
359
			if ( OpenProcessToken( GetCurrentProcess(), TOKEN_ADJUST_PRIVILEGES, &hToken ) )
360
			{
361
				TOKEN_PRIVILEGES tp;
362
				tp.PrivilegeCount = 1;
363
				tp.Privileges[ 0 ].Luid = luid;
364
				tp.Privileges[ 0 ].Attributes = SE_PRIVILEGE_ENABLED;
365
				AdjustTokenPrivileges( hToken, FALSE, &tp, sizeof( TOKEN_PRIVILEGES ), 0L, 0L );
366
				CloseHandle( hToken );
367
			}
368
 
369
		}
370
 
371
		do
372
		{
373
			// clear processCollection
374
			processCollectionIterator = processCollection.begin();
375
 
376
			while ( processCollectionIterator != processCollection.end() )
377
			{
378
				Process *p = *processCollectionIterator;
379
				delete p;
380
				p = 0L;
381
				processCollectionIterator ++;
382
			}
383
 
384
			processCollection.clear();
385
 
386
			HANDLE snapshot = CreateToolhelp32Snapshot( TH32CS_SNAPPROCESS, 0 );
387
 
388
			if ( snapshot )
389
			{
390
				memset( &entry, 0, sizeof( entry ) );
391
				entry.dwSize = sizeof( entry );
392
 
393
				if ( Process32First( snapshot, &entry ) == TRUE )
394
				{
395
					do
396
					{
397
						// get a handle to the process
398
						HANDLE hProcess = OpenProcess( PROCESS_ALL_ACCESS, FALSE, entry.th32ProcessID );
399
 
400
						if ( hProcess )
401
						{
402
							// get processes fully qualified path
403
							HMODULE hMod;
404
							DWORD cbNeeded = 0;
405
 
406
							char path[MAX_PATH] = "";
407
							if ( EnumProcessModules( hProcess, &hMod, sizeof( hMod ), &cbNeeded ) )
408
							{
409
								FILETIME creationTime;
410
								FILETIME exitTime;
411
								FILETIME kernelTime;
412
								FILETIME userTime;
413
								GetModuleFileNameEx( hProcess, hMod, path, sizeof( path) );
414
								GetProcessTimes( hProcess, &creationTime, &exitTime, &kernelTime, &userTime );
415
								ULARGE_INTEGER nano;
416
								nano.LowPart = creationTime.dwLowDateTime;
417
								nano.HighPart = creationTime.dwHighDateTime;
418
								Process *p = new Process( path, entry.szExeFile, entry.th32ProcessID, entry.th32ParentProcessID, nano.QuadPart );
419
								processCollection.push_back( p );
420
							}
421
 
422
							CloseHandle( hProcess );
423
						}
424
 
425
					} while ( Process32Next( snapshot, &entry ) );
426
				}
427
				CloseHandle( snapshot );
428
			}
429
 
430
			// iterate through the processCollection
431
			// if its name matches a child from argv[2] to argv[argc-1], and it is a child of ancestor, and its runtime has expired, then terminate the process
432
			processCollectionIterator = processCollection.begin();
433
 
434
			while ( processCollectionIterator != processCollection.end() )
435
			{
436
				Process *p = *processCollectionIterator;
437
				int child = 2;
438
 
439
				while( child < argc )
440
				{
441
					if ( p->mName == argv[ child ] )
442
					{
443
						std::string message = "Start found configured process running ";
444
						message += p->mName;
445
						log( message.c_str() );
446
						// its name matches a child
447
						if ( isDescendedFrom( ancestor, *p, processCollection ) )
448
						{
449
							std::string message = "Start found configured process running and descendant ";
450
							message += p->mName;
451
							log( message.c_str() );
452
							// it is a child, directly or indirectly, of ancestor
453
							SYSTEMTIME st;
454
							FILETIME ft;
455
							// get current time
456
							GetSystemTime(&st);
457
							// convert to file time format
458
							SystemTimeToFileTime(&st, &ft);
459
							ULARGE_INTEGER nano;
460
							nano.LowPart = ft.dwLowDateTime;
461
							nano.HighPart = ft.dwHighDateTime;
462
							ULARGE_INTEGER runTime;
463
							runTime.QuadPart = nano.QuadPart - p->mTime;
464
 
465
							int runTimeSecs = runTime.QuadPart/10000000;
466
 
467
							if ( runTimeSecs > maxRunTimeSecs )
468
							{
469
								std::string message = "Start found configured process running too long ";
470
								message += p->mName;
471
								log( message.c_str() );
472
								// it has been running for too long, terminate
473
								HANDLE hProcess = OpenProcess( PROCESS_QUERY_INFORMATION | PROCESS_VM_READ | PROCESS_TERMINATE, FALSE, p->mPid );
474
 
475
								if ( hProcess )
476
								{
477
									std::string message = "Start terminating ";
478
									message += p->mName;
479
									log( message.c_str() );
480
									TerminateProcess( hProcess, CONTROL_C_EXIT );
481
									CloseHandle( hProcess );
482
								}
483
 
484
							}
485
 
486
						}
487
						break;
488
					}
489
					child++;
490
				}
491
 
492
				processCollectionIterator ++;
493
			}
494
 
495
			// this is effectively the ServiceMain routine
496
			// do not exit this loop and return until signalled
497
			// wait for 60 secs and continue to run if not signalled
498
			run = WaitForSingleObject( hWaitEvent, 60000 );
499
 
500
			if ( run == WAIT_FAILED )
501
			{
502
				std::string message = "Start WaitForSingleObject 1 failed with last error " + GetLastError();
503
				log( message.c_str() );
504
			}
505
 
506
		} while ( run == WAIT_TIMEOUT );
507
 
508
		// clear processCollection
509
		processCollectionIterator = processCollection.begin();
510
 
511
		while ( processCollectionIterator != processCollection.end() )
512
		{
513
			Process *p = *processCollectionIterator;
514
			delete p;
515
			p = 0L;
516
			processCollectionIterator ++;
517
		}
518
 
519
		processCollection.clear();
520
 
521
	}
522
	else
523
	{
524
		log( "Start calling WaitForSingleObject" );
525
		// this is effectively the ServiceMain routine
526
		// do not return until signalled
527
		if ( WaitForSingleObject( hWaitEvent, INFINITE ) == WAIT_FAILED )
528
		{
529
			std::string message = "Start WaitForSingleObject 2 failed with last error " + GetLastError();
530
			log( message.c_str() );
531
		}
532
		log( "Start called WaitForSingleObject" );
533
	}
534
 
535
}
536
 
537
//------------------------------------------------------------------------------
538
//  Stop
539
//  The service clean up, run when the service is to be stopped.
540
//------------------------------------------------------------------------------
541
//  Parameters:
542
//------------------------------------------------------------------------------
543
//  Returns:
544
//      None
545
//------------------------------------------------------------------------------
546
void Stop()
547
{
548
	// nothing to do
549
}