1 tony 1.1 //%/////////////////////////////////////////////////////////////////////////////
2 //
3 // Copyright (c) 2000, 2001, 2002 BMC Software, Hewlett-Packard Company, IBM,
4 // The Open Group, Tivoli Systems
5 //
6 // Permission is hereby granted, free of charge, to any person obtaining a copy
7 // of this software and associated documentation files (the "Software"), to
8 // deal in the Software without restriction, including without limitation the
9 // rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
10 // sell copies of the Software, and to permit persons to whom the Software is
11 // furnished to do so, subject to the following conditions:
12 //
13 // THE ABOVE COPYRIGHT NOTICE AND THIS PERMISSION NOTICE SHALL BE INCLUDED IN
14 // ALL COPIES OR SUBSTANTIAL PORTIONS OF THE SOFTWARE. THE SOFTWARE IS PROVIDED
15 // "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT
16 // LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR
17 // PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
18 // HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
19 // ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
20 // WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
21 //
22 tony 1.1 //==============================================================================
23 //
24 // Author: Tony Fiorentino (fiorentino_tony@emc.com)
25 //
26 //%/////////////////////////////////////////////////////////////////////////////
27 #include <time.h>
28 #include <stdio.h>
29 #include <windows.h>
30 #include <process.h>
31
32 #include "service.h"
33
34 //-------------------------------------------------------------------------
35 // G L O B A L S
36 //-------------------------------------------------------------------------
37 int Service::g_argc = 0;
38 char **Service::g_argv = NULL;
39 char *Service::g_service_name = NULL;
40 char *Service::g_event_source = NULL;
41 DWORD Service::g_flags = 0;
42 DWORD Service::g_current_state = 0;
43 tony 1.1 SERVICE_STATUS_HANDLE Service::g_service_status_handle = 0;
44 SERVICE_MAIN_T Service::g_service_main = NULL;
45
46 //-------------------------------------------------------------------------
47 // P U B L I C
48 //-------------------------------------------------------------------------
49 Service::Service(void)
50 {
51 }
52
53 Service::Service(char *service_name)
54 {
55 g_service_name = service_name;
56 }
57
58 Service::Service(char *service_name, char *event_source)
59 {
60 g_event_source = event_source;
61 g_service_name = service_name;
62 }
63
64 tony 1.1 Service::~Service(void)
65 {
66 }
67
68 /*-------------------------------------------------------------------------*
69 * Method: Install *
70 * *
71 * Args: *
72 * display_name *
73 * The service's display name (hopefully more descriptive, will show *
74 * up in the Win32 Services MMC snapin as "Service Name"). *
75 * *
76 * exe_name: *
77 * The service's executable name (full path, please) *
78 * *
79 * flags: *
80 * Reserved. Currently unused. *
81 * *
82 * Description: *
83 * This function creates the service. *
84 * *
85 tony 1.1 * NOTE: If the process is successfully launched as a Win32 service, this *
86 * function never returns, but calls exit() instead. *
87 *-------------------------------------------------------------------------*/
88 Service::ReturnCode
89 Service::Install(
90 char *display_name,
91 char *description,
92 char *exe_name)
93 {
94 ReturnCode status = SERVICE_RETURN_SUCCESS;
95 SC_HANDLE sch;
96
97 if (g_service_name == NULL || display_name == NULL || exe_name == NULL)
98 return SERVICE_ERROR_NOT_FOUND; // SERVICE_ERROR_NOT_FOUND
99 else if ((sch = OpenSCManager(NULL, NULL, SC_MANAGER_CREATE_SERVICE)) == NULL)
100 status = get_error(GetLastError(), "open");
101 else
102 {
103 SC_HANDLE service = CreateService(
104 sch, // SCManager database
105 g_service_name, // name of service
106 tony 1.1 display_name, // service name to display
107 SERVICE_ALL_ACCESS, // desired access
108 SERVICE_WIN32_OWN_PROCESS, // service type
109 SERVICE_DEMAND_START, // start type
110 SERVICE_ERROR_NORMAL, // error control type
111 exe_name, // service's binary
112 NULL, // no load ordering group
113 NULL, // no tag identifier
114 NULL, // no dependencies
115 NULL, // LocalSystem account
116 NULL); // no password
117
118 if (service == NULL)
119 {
120 status = get_error(GetLastError(), "create");
121 return status;
122 }
123 else
124 {
125 change_service_description(service, description);
126 CloseServiceHandle(service);
127 tony 1.1 }
128
129 CloseServiceHandle(sch);
130 }
131
132 return status;
133 }
134
135 /*-------------------------------------------------------------------------*
136 * Method: Remove *
137 * *
138 * Description: *
139 * Removes the service. *
140 *-------------------------------------------------------------------------*/
141 Service::ReturnCode
142 Service::Remove(void)
143 {
144 ReturnCode status = SERVICE_RETURN_SUCCESS;
145 SC_HANDLE sch;
146
147 if (g_service_name == NULL)
148 tony 1.1 return SERVICE_ERROR_NOT_FOUND; /* SERVICE_ERROR_NOT_FOUND */
149 else if ((sch = OpenSCManager(NULL, NULL, SC_MANAGER_CREATE_SERVICE)) == NULL)
150 status = get_error(GetLastError(), "open");
151 else
152 {
153 SC_HANDLE service = OpenService(sch, g_service_name, DELETE);
154
155 if (service == NULL)
156 {
157 status = get_error(GetLastError(), "open");
158 }
159 else
160 {
161 if (!DeleteService(service))
162 {
163 status = get_error(GetLastError(), "remove");
164 }
165
166 CloseServiceHandle(service);
167 }
168
169 tony 1.1 CloseServiceHandle(sch);
170 }
171
172 return status;
173 }
174
175 /*-------------------------------------------------------------------------*
176 * Method: Start *
177 * *
178 * Args: *
179 * wait_time: *
180 * The user supplied wait (~1 second per QueryServiceStatus() attempt) *
181 * *
182 * Description: *
183 * Attempt to start the service. *
184 *-------------------------------------------------------------------------*/
185 Service::ReturnCode
186 Service::Start(int wait_time)
187 {
188 ReturnCode status = SERVICE_RETURN_SUCCESS;
189 SERVICE_STATUS service_status;
190 tony 1.1 SC_HANDLE sch;
191
192 if (g_service_name == NULL)
193 return SERVICE_ERROR_NOT_FOUND; // SERVICE_ERROR_NOT_FOUND
194 else if ((sch = OpenSCManager(NULL, NULL, GENERIC_READ)) == NULL)
195 status = get_error(GetLastError(), "open");
196 else
197 {
198 SC_HANDLE service = OpenService(sch, g_service_name, SERVICE_START | SERVICE_QUERY_STATUS);
199
200 if (service == NULL)
201 status = get_error(GetLastError(), "open");
202 else if (!StartService(service, 0, NULL))
203 status = get_error(GetLastError(), "start");
204 else
205 {
206 int i, max = (wait_time > 0) ? wait_time : 5;
207
208 // Loop up to max times waiting for the service
209 // state to change to RUNNING
210
211 tony 1.1 for (i = 0; i < max; i++)
212 {
213 if (!QueryServiceStatus(service, &service_status))
214 {
215 status = get_error(GetLastError(), "query");
216 return status; // QUERY_FAIL
217 }
218
219 if (service_status.dwCurrentState == SERVICE_RUNNING)
220 break;
221
222 Sleep(1 * CLOCKS_PER_SEC);
223 }
224
225 status = (i < max) ? SERVICE_RETURN_SUCCESS : SERVICE_ERROR_REQUEST_TIMEOUT;
226
227 CloseServiceHandle(service);
228 }
229
230 CloseServiceHandle(sch);
231 }
232 tony 1.1
233 return status;
234 }
235
236 /*-------------------------------------------------------------------------*
237 * Method: Stop *
238 * *
239 * Args: *
240 * wait_time: *
241 * The user supplied wait (~1 second per QueryServiceStatus() attempt) *
242 * *
243 * Description: *
244 * Attempt to stop the service. *
245 *-------------------------------------------------------------------------*/
246 Service::ReturnCode
247 Service::Stop(int wait_time)
248 {
249 ReturnCode status = SERVICE_RETURN_SUCCESS;
250 SERVICE_STATUS service_status;
251 SC_HANDLE sch;
252
253 tony 1.1 if (g_service_name == NULL)
254 return SERVICE_ERROR_NOT_FOUND; // SERVICE_ERROR_NOT_FOUND
255 else if ((sch = OpenSCManager(NULL, NULL, GENERIC_READ)) == NULL)
256 status = get_error(GetLastError(), "open");
257 // show_error("OpenSCMManager", "service", GetLastError());
258 else
259 {
260 SC_HANDLE service = OpenService(sch, g_service_name, SERVICE_STOP | SERVICE_QUERY_STATUS);
261
262 if (service == NULL)
263 status = get_error(GetLastError(), "open");
264 else if (!ControlService(service, SERVICE_CONTROL_STOP, &service_status))
265 status = get_error(GetLastError(), "stop");
266 else
267 {
268 int i, max = (wait_time > 0) ? wait_time : 5;
269
270 // Loop up to max times waiting for the service
271 // state to change to STOPPED
272
273 for (i = 0; i < max; i++)
274 tony 1.1 {
275 if (!QueryServiceStatus(service, &service_status))
276 {
277 status = get_error(GetLastError(), "query");
278 return status; // QUERY_FAIL
279 }
280
281 if (service_status.dwCurrentState == SERVICE_STOPPED)
282 break;
283
284 Sleep(1 * CLOCKS_PER_SEC);
285 }
286
287 status = (i < max) ? SERVICE_RETURN_SUCCESS : SERVICE_ERROR_REQUEST_TIMEOUT;
288
289 CloseServiceHandle(service);
290 }
291
292 CloseServiceHandle(sch);
293 }
294
295 tony 1.1 return status;
296 }
297
298 /*-------------------------------------------------------------------------*
299 * Method: Run *
300 * *
301 * Args: *
302 * service_main: *
303 * The user supplied service_main function (not to be confused with *
304 * real_service_main above) *
305 * *
306 * flags: *
307 * Reserved. Currently unused. *
308 * *
309 * Description: *
310 * This function interacts with the SCM to run the current process *
311 * as a Win32 service. *
312 * *
313 * NOTE: If the process is successfully launched as a Win32 service, this *
314 * function never returns, but calls exit() instead. *
315 *-------------------------------------------------------------------------*/
316 tony 1.1
317 Service::ReturnCode
318 Service::Run(SERVICE_MAIN_T service_main, DWORD flags)
319 {
320 ReturnCode status = SERVICE_RETURN_SUCCESS;
321
322 SERVICE_TABLE_ENTRY dispatchTable[] =
323 {
324 { g_service_name, real_service_main },
325 { NULL, NULL }
326 };
327
328 // Validate the arguments as best we can
329
330 if (g_service_name == NULL || service_main == NULL)
331 return SERVICE_ERROR_NOT_FOUND; // SERVICE_ERROR_NOT_FOUND
332
333 // Save parameters in global variables
334
335 g_flags = flags;
336 g_service_main = service_main;
337 tony 1.1
338 // Kick off the service
339
340 if (!StartServiceCtrlDispatcher(dispatchTable))
341 {
342 status = get_error(GetLastError(), "start");
343 return status; // FAIL
344 }
345
|
348 tony 1.1 }
349
350 /*-------------------------------------------------------------------------*
351 * Method: GetState *
352 * *
353 * Description: *
354 * Returns the state of the service into "state". *
355 *-------------------------------------------------------------------------*/
356 Service::ReturnCode
357 Service::GetState(State *state)
358 {
359 ReturnCode status = SERVICE_RETURN_SUCCESS;
360 SERVICE_STATUS service_status;
361 SC_HANDLE sch;
362
363 if (g_service_name == NULL)
364 return SERVICE_ERROR_NOT_FOUND; // SERVICE_ERROR_NOT_FOUND
365 else if ((sch = OpenSCManager(NULL, NULL, GENERIC_READ)) == NULL)
366 status = get_error(GetLastError(), "open");
367 else
368 {
369 tony 1.1 SC_HANDLE service = OpenService(sch, g_service_name, SERVICE_QUERY_STATUS);
370
371 if (service == NULL)
372 status = get_error(GetLastError(), "open");
373 else if (!QueryServiceStatus(service, &service_status))
374 status = get_error(GetLastError(), "query");
375 else
376 {
377 *state = get_state(service_status.dwCurrentState);
378 CloseServiceHandle(service);
379 }
380
381 CloseServiceHandle(sch);
382 }
383
384 return status;
385 }
386
387 /*-------------------------------------------------------------------------*
388 * Method: LogEvent *
389 * *
390 tony 1.1 * Args: *
391 * event_type: *
392 * The Win32 event type. Valid event types are: *
393 * EVENTLOG_SUCCESS : Success event *
394 * EVENTLOG_ERROR_TYPE : Error event *
395 * EVENTLOG_WARNING_TYPE : Warning event *
396 * EVENTLOG_INFORMATION_TYPE : Information event *
397 * EVENTLOG_AUDIT_SUCCESS : Success audit event *
398 * EVENTLOG_AUDIT_FAILURE : Failure audit event *
399 * *
400 * event_id: *
401 * A fancy name for error code or error number. *
402 * *
403 * string: *
404 * String to be logged or merged in with the error string in the *
405 * message DLL. *
406 * *
407 * Description: *
408 * This function provides a simple layer over the Win32 error logging *
409 * API's. *
410 * *
411 tony 1.1 * Returns: *
412 * true if event was successfully logged *
413 * false if the event could not be logged *
414 *-------------------------------------------------------------------------*/
415 bool
416 Service::LogEvent(WORD event_type, DWORD event_id, const char *string)
417 {
418 BOOL status;
419 HANDLE h_event_source = RegisterEventSource(NULL, g_event_source);
420
421 if (h_event_source == NULL)
422 FALSE;
423
424 status = ReportEvent (h_event_source,
425 event_type,
426 0,
427 event_id,
428 NULL,
429 1,
430 0,
431 &string,
432 tony 1.1 NULL);
433
434 DeregisterEventSource(h_event_source);
435
436 return (status == TRUE) ? true : false;
437 }
438
439 //-------------------------------------------------------------------------
440 // P R I V A T E
441 //-------------------------------------------------------------------------
442 /*-------------------------------------------------------------------------*
443 * Routine: real_service_main *
444 * *
445 * Args: *
446 * argc: *
447 * The number of arguments in the argv array *
448 * argv: *
449 * An array of strings representing the command line arguments *
450 * *
451 * Description: *
452 * This function is the real service main (as opposed to the user *
453 tony 1.1 * supplied service main, which is called service_main). *
454 * *
455 * Returns: *
456 * nothing *
457 *-------------------------------------------------------------------------*/
458 void __stdcall
459 Service::real_service_main(DWORD argc, LPTSTR *argv)
460 {
461 // Let the SCM know we're alive and kicking
462
463 report_status(SERVICE_START_PENDING, NO_ERROR, 0, 5000);
464
465 // If the command line arguments include the string "-debug" then
466 // invoke the debugger
467
468 if (check_args_for_string("-debug"))
469 DebugBreak();
470
471 // Save copy of argc and argc in global variables
472
473 g_argc = argc;
474 tony 1.1 g_argv = argv;
475
476 // Start service actions
477
478 g_service_status_handle = RegisterServiceCtrlHandler (g_service_name,
479 service_control_handler);
480
481 if (g_service_status_handle == 0)
482 {
483 show_error("register", "Service Control Handler", GetLastError());
484 report_status(SERVICE_STOPPED, NO_ERROR, 0, 5000);
485 return;
486 }
487
488 // Change our state to RUNNING, then invoke the user supplied
489 // service_main function. After the user's service_main exits,
490 // change the service state to STOPPED.
491
492 report_status(SERVICE_RUNNING, NO_ERROR, 0, 5000);
493 g_service_main(STARTUP_FLAG, argc, argv);
494 report_status(SERVICE_STOPPED, NO_ERROR, 0, 5000);
495 tony 1.1
496 return;
497 }
498
499 /*-------------------------------------------------------------------------*
500 * Routine: check_args_for_string *
501 * *
502 * Args: *
503 * string: *
504 * The string to match. *
505 * *
506 * Description: *
507 * This function iterates through the command line arguments searching *
508 * for the string specified by the string parameter. *
509 * *
510 * Returns: *
511 * true if the string was found *
512 * false if the string was not found *
513 *-------------------------------------------------------------------------*/
514 bool
515 Service::check_args_for_string(char *string)
516 tony 1.1 {
517 int i;
518
519 for (i = 1; i < g_argc; i++)
520 {
521 if (strcmp(g_argv[i], string) == 0)
522 return true;
523 }
524
525 return false;
526 }
527
528 /*-------------------------------------------------------------------------*
529 * Method: service_control_handler *
530 * *
531 * Args: *
532 * control: *
533 * The control sent from the SCM telling us what action to take: *
534 * start, stop, pause, continue, etc. *
535 * *
536 * Description: *
537 tony 1.1 * This function handles control messages sent from the SCM. Currently *
538 * the only message that is handled differently is the STOP message, *
539 * which invokes the user's service main function passing to it the *
540 * SHUTDOWN_FLAG. The user is then responsible to perform all shutdown *
541 * related tasks. *
542 * *
543 * Returns: *
544 * Nothing *
545 *-------------------------------------------------------------------------*/
546
547 void WINAPI
548 Service::service_control_handler(DWORD control)
549 {
550 /* Currently, only the stop contol requires special handling */
551
552 if (control == SERVICE_CONTROL_STOP)
553 {
554 report_status(SERVICE_STOP_PENDING, NO_ERROR, 0, 5000);
555 g_service_main(SHUTDOWN_FLAG, g_argc, g_argv);
556 return;
557 }
558 tony 1.1
559 /* For every other control, just send back our current state */
560
561 report_status(g_current_state, NO_ERROR, 0, 5000);
562 }
563
564 /*-------------------------------------------------------------------------*
565 * Method: report_status *
566 * *
567 * Args: *
568 * current_state: *
569 * The service's new state. May be any valid Win32 service state. *
570 * *
571 * exit_code: *
572 * This must be a Win32 exit code. If the server is exiting due to *
573 * an error returned from the Win32 API, then this is the place to *
574 * report the error status. Most of the time, this will be NO_ERROR. *
575 * *
576 * check_point: *
577 * An integer value that should start at zero and increment as each *
578 * discrete step within a phase is completed. For example, if startup *
579 tony 1.1 * consists of 3 steps, then the startup will issue its first check- *
580 * point of zero, then increment up through and including 2 as each *
581 * step is finished. *
582 * *
583 * wait_hint: *
584 * Tells the SCM how long to expect to wait for the next status *
585 * update. *
586 * *
587 * Description: *
588 * This function provides an even higher level of abstraction over *
589 * the Win32 event logging API's. *
590 * *
591 * Returns: *
592 * true if the status was successfully reported *
593 * false if the status could not be reported *
594 *-------------------------------------------------------------------------*/
595 bool
596 Service::report_status(
597 DWORD current_state,
598 DWORD exit_code,
599 DWORD check_point,
600 tony 1.1 DWORD wait_hint)
601 {
602 SERVICE_STATUS current_status =
603 {
604 SERVICE_WIN32,
605 current_state,
606 SERVICE_CONTROL_INTERROGATE,
607 exit_code,
608 0,
609 check_point,
610 wait_hint
611 };
612
613 /* Wait until we're started before we accept a stop control */
614
615 if (current_state == SERVICE_RUNNING)
616 current_status.dwControlsAccepted += SERVICE_ACCEPT_STOP;
617
618 /* Save new state */
619
620 g_current_state = current_state;
621 tony 1.1
622 return (SetServiceStatus(g_service_status_handle, ¤t_status) == TRUE) ? true : false;
623 }
624
625 /*-------------------------------------------------------------------------*
626 * Method: change_service_description *
627 * *
628 * Args: *
629 * service: *
630 * Handle to the service *
631 * *
632 * description: *
633 * The service's description *
634 * *
635 * Description: *
636 * This function sets the description for the service. The description *
637 * text shows up in the Services mmc snapin. The description is added *
638 * as a benefit to users, but does not affect the service's operation. *
639 * Therefore, if this function should fail for any reason, there is *
640 * no need to stop. That's why this function has no return value. *
641 * *
642 tony 1.1 * Returns: *
643 * Nothing *
644 * *
645 * Notes: *
646 * This function uses the ChangeServiceConfig2() API function which *
647 * first appeared in Windows2000. Therefore, this code checks the *
648 * OS version, before calling ChangeServiceConfig2(). *
649 *-------------------------------------------------------------------------*/
650 void
651 Service::change_service_description(SC_HANDLE service, char *description)
652 {
653 OSVERSIONINFO osvi;
654
655 /*
656 * Test for Windows 2000 or greater
657 */
658
659 osvi.dwOSVersionInfoSize = sizeof(OSVERSIONINFO);
660
661 if (GetVersionEx(&osvi) != 0 &&
662 osvi.dwPlatformId == VER_PLATFORM_WIN32_NT &&
663 tony 1.1 osvi.dwMajorVersion >= 5)
664 {
665 typedef BOOL (WINAPI *CHANGE_SERVICE_CONFIG2_T)(SC_HANDLE, DWORD, LPVOID);
666
667 HINSTANCE hdll = LoadLibrary("advapi32.dll");
668
669 if (hdll != NULL)
670 {
671 SERVICE_DESCRIPTION sd;
672 CHANGE_SERVICE_CONFIG2_T csc;
673
674 csc = (CHANGE_SERVICE_CONFIG2_T) GetProcAddress(hdll, "ChangeServiceConfig2A");
675
676 if (csc)
677 {
678 sd.lpDescription = description;
679 csc(service, SERVICE_CONFIG_DESCRIPTION, &sd);
680 }
681
682 FreeLibrary(hdll);
683 }
684 tony 1.1 }
685 }
686
687 /*-------------------------------------------------------------------------*
688 * Method: show_error *
689 * *
690 * Args: *
691 * action: *
692 * A single verb decribing the action going on when the error *
693 * occurred. For example, "opening", "creating", etc. *
694 * *
695 * object: *
696 * Description of the object on which was action occurred. Examples *
697 * are "file", "service", etc. *
698 * *
699 * hr: *
700 * The error status. Can be an hresult, or Win32 error status. *
701 * *
702 * Description: *
703 * This function provides an even higher level of abstraction over *
704 * the Win32 event logging API's. *
705 tony 1.1 * *
706 * Returns: *
707 * true if event was successfully logged *
708 * false if the event could not be logged *
709 *-------------------------------------------------------------------------*/
710 bool
711 Service::show_error(const char *action, const char *object, DWORD hr)
712 {
713 char console_title[_MAX_PATH] = {0};
714 char txt[_MAX_PATH] = "";
715 char msg[_MAX_PATH] = "";
716 DWORD nchars;
717
718 nchars = FormatMessage(
719 FORMAT_MESSAGE_FROM_SYSTEM,
720 NULL,
721 hr,
722 MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT),
723 msg,
724 sizeof(msg),
725 NULL);
726 tony 1.1
727 if (nchars == 0)
728 sprintf(msg, "Unknown error code - %%X%x", hr);
729 else if (nchars > 1)
730 {
731 if (msg[nchars - 1] == '\n') msg[nchars - 1] = '\0';
732 if (msg[nchars - 2] == '\r') msg[nchars - 2] = '\0';
733 }
734
735 sprintf(txt, "Failed to %s %s %s. Reason: %s", action, object, g_service_name, msg);
736
737 // Running from a console window
738 // send courtesy message txt to stderr
739 if (GetConsoleTitle(console_title, _MAX_PATH) > 0)
740 {
|