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