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