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