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
346 // After the service shuts down, don't return.
347
348 exit(status);
349 }
350
351 /*-------------------------------------------------------------------------*
352 * Method: GetState *
353 * *
354 * Description: *
355 * Returns the state of the service into "state". *
356 *-------------------------------------------------------------------------*/
357 Service::ReturnCode
358 tony 1.1 Service::GetState(State *state)
359 {
360 ReturnCode status = SERVICE_RETURN_SUCCESS;
361 SERVICE_STATUS service_status;
362 SC_HANDLE sch;
363
364 if (g_service_name == NULL)
365 return SERVICE_ERROR_NOT_FOUND; // SERVICE_ERROR_NOT_FOUND
366 else if ((sch = OpenSCManager(NULL, NULL, GENERIC_READ)) == NULL)
367 status = get_error(GetLastError(), "open");
368 else
369 {
370 SC_HANDLE service = OpenService(sch, g_service_name, SERVICE_QUERY_STATUS);
371
372 if (service == NULL)
373 status = get_error(GetLastError(), "open");
374 else if (!QueryServiceStatus(service, &service_status))
375 status = get_error(GetLastError(), "query");
376 else
377 {
378 *state = get_state(service_status.dwCurrentState);
379 tony 1.1 CloseServiceHandle(service);
380 }
381
382 CloseServiceHandle(sch);
383 }
384
385 return status;
386 }
387
388 /*-------------------------------------------------------------------------*
389 * Method: LogEvent *
390 * *
391 * Args: *
392 * event_type: *
393 * The Win32 event type. Valid event types are: *
394 * EVENTLOG_SUCCESS : Success event *
395 * EVENTLOG_ERROR_TYPE : Error event *
396 * EVENTLOG_WARNING_TYPE : Warning event *
397 * EVENTLOG_INFORMATION_TYPE : Information event *
398 * EVENTLOG_AUDIT_SUCCESS : Success audit event *
399 * EVENTLOG_AUDIT_FAILURE : Failure audit event *
400 tony 1.1 * *
401 * event_id: *
402 * A fancy name for error code or error number. *
403 * *
404 * string: *
405 * String to be logged or merged in with the error string in the *
406 * message DLL. *
407 * *
408 * Description: *
409 * This function provides a simple layer over the Win32 error logging *
410 * API's. *
411 * *
412 * Returns: *
413 * true if event was successfully logged *
414 * false if the event could not be logged *
415 *-------------------------------------------------------------------------*/
416 bool
417 Service::LogEvent(WORD event_type, DWORD event_id, const char *string)
418 {
419 BOOL status;
420 HANDLE h_event_source = RegisterEventSource(NULL, g_event_source);
421 tony 1.1
422 if (h_event_source == NULL)
423 FALSE;
424
425 status = ReportEvent (h_event_source,
426 event_type,
427 0,
428 event_id,
429 NULL,
430 1,
431 0,
432 &string,
433 NULL);
434
435 DeregisterEventSource(h_event_source);
436
437 return (status == TRUE) ? true : false;
438 }
439
440 //-------------------------------------------------------------------------
441 // P R I V A T E
442 tony 1.1 //-------------------------------------------------------------------------
443 /*-------------------------------------------------------------------------*
444 * Routine: real_service_main *
445 * *
446 * Args: *
447 * argc: *
448 * The number of arguments in the argv array *
449 * argv: *
450 * An array of strings representing the command line arguments *
451 * *
452 * Description: *
453 * This function is the real service main (as opposed to the user *
454 * supplied service main, which is called service_main). *
455 * *
456 * Returns: *
457 * nothing *
458 *-------------------------------------------------------------------------*/
459 void __stdcall
460 Service::real_service_main(DWORD argc, LPTSTR *argv)
461 {
462 // Let the SCM know we're alive and kicking
463 tony 1.1
464 report_status(SERVICE_START_PENDING, NO_ERROR, 0, 5000);
465
466 // If the command line arguments include the string "-debug" then
467 // invoke the debugger
468
469 if (check_args_for_string("-debug"))
470 DebugBreak();
471
472 // Save copy of argc and argc in global variables
473
474 g_argc = argc;
475 g_argv = argv;
476
477 // Start service actions
478
479 g_service_status_handle = RegisterServiceCtrlHandler (g_service_name,
480 service_control_handler);
481
482 if (g_service_status_handle == 0)
483 {
484 tony 1.1 show_error("register", "Service Control Handler", GetLastError());
485 report_status(SERVICE_STOPPED, NO_ERROR, 0, 5000);
486 return;
487 }
488
489 // Change our state to RUNNING, then invoke the user supplied
490 // service_main function. After the user's service_main exits,
491 // change the service state to STOPPED.
492
493 report_status(SERVICE_RUNNING, NO_ERROR, 0, 5000);
494 g_service_main(STARTUP_FLAG, argc, argv);
495 report_status(SERVICE_STOPPED, NO_ERROR, 0, 5000);
496
497 return;
498 }
499
500 /*-------------------------------------------------------------------------*
501 * Routine: check_args_for_string *
502 * *
503 * Args: *
504 * string: *
505 tony 1.1 * The string to match. *
506 * *
507 * Description: *
508 * This function iterates through the command line arguments searching *
509 * for the string specified by the string parameter. *
510 * *
511 * Returns: *
512 * true if the string was found *
513 * false if the string was not found *
514 *-------------------------------------------------------------------------*/
515 bool
516 Service::check_args_for_string(char *string)
517 {
518 int i;
519
520 for (i = 1; i < g_argc; i++)
521 {
522 if (strcmp(g_argv[i], string) == 0)
523 return true;
524 }
525
526 tony 1.1 return false;
527 }
528
529 /*-------------------------------------------------------------------------*
530 * Method: service_control_handler *
531 * *
532 * Args: *
533 * control: *
534 * The control sent from the SCM telling us what action to take: *
535 * start, stop, pause, continue, etc. *
536 * *
537 * Description: *
538 * This function handles control messages sent from the SCM. Currently *
539 * the only message that is handled differently is the STOP message, *
540 * which invokes the user's service main function passing to it the *
541 * SHUTDOWN_FLAG. The user is then responsible to perform all shutdown *
542 * related tasks. *
543 * *
544 * Returns: *
545 * Nothing *
546 *-------------------------------------------------------------------------*/
547 tony 1.1
548 void WINAPI
549 Service::service_control_handler(DWORD control)
550 {
551 /* Currently, only the stop contol requires special handling */
552
553 if (control == SERVICE_CONTROL_STOP)
554 {
555 report_status(SERVICE_STOP_PENDING, NO_ERROR, 0, 5000);
556 g_service_main(SHUTDOWN_FLAG, g_argc, g_argv);
557 return;
558 }
559
560 /* For every other control, just send back our current state */
561
562 report_status(g_current_state, NO_ERROR, 0, 5000);
563 }
564
565 /*-------------------------------------------------------------------------*
566 * Method: report_status *
567 * *
568 tony 1.1 * Args: *
569 * current_state: *
570 * The service's new state. May be any valid Win32 service state. *
571 * *
572 * exit_code: *
573 * This must be a Win32 exit code. If the server is exiting due to *
574 * an error returned from the Win32 API, then this is the place to *
575 * report the error status. Most of the time, this will be NO_ERROR. *
576 * *
577 * check_point: *
578 * An integer value that should start at zero and increment as each *
579 * discrete step within a phase is completed. For example, if startup *
580 * consists of 3 steps, then the startup will issue its first check- *
581 * point of zero, then increment up through and including 2 as each *
582 * step is finished. *
583 * *
584 * wait_hint: *
585 * Tells the SCM how long to expect to wait for the next status *
586 * update. *
587 * *
588 * Description: *
589 tony 1.1 * This function provides an even higher level of abstraction over *
590 * the Win32 event logging API's. *
591 * *
592 * Returns: *
593 * true if the status was successfully reported *
594 * false if the status could not be reported *
595 *-------------------------------------------------------------------------*/
596 bool
597 Service::report_status(
598 DWORD current_state,
599 DWORD exit_code,
600 DWORD check_point,
601 DWORD wait_hint)
602 {
603 SERVICE_STATUS current_status =
604 {
605 SERVICE_WIN32,
606 current_state,
607 SERVICE_CONTROL_INTERROGATE,
608 exit_code,
609 0,
610 tony 1.1 check_point,
611 wait_hint
612 };
613
614 /* Wait until we're started before we accept a stop control */
615
616 if (current_state == SERVICE_RUNNING)
617 current_status.dwControlsAccepted += SERVICE_ACCEPT_STOP;
618
619 /* Save new state */
620
621 g_current_state = current_state;
622
623 return (SetServiceStatus(g_service_status_handle, ¤t_status) == TRUE) ? true : false;
624 }
625
626 /*-------------------------------------------------------------------------*
627 * Method: change_service_description *
628 * *
629 * Args: *
630 * service: *
631 tony 1.1 * Handle to the service *
632 * *
633 * description: *
634 * The service's description *
635 * *
636 * Description: *
637 * This function sets the description for the service. The description *
638 * text shows up in the Services mmc snapin. The description is added *
639 * as a benefit to users, but does not affect the service's operation. *
640 * Therefore, if this function should fail for any reason, there is *
641 * no need to stop. That's why this function has no return value. *
642 * *
643 * Returns: *
644 * Nothing *
645 * *
646 * Notes: *
647 * This function uses the ChangeServiceConfig2() API function which *
648 * first appeared in Windows2000. Therefore, this code checks the *
649 * OS version, before calling ChangeServiceConfig2(). *
650 *-------------------------------------------------------------------------*/
651 void
652 tony 1.1 Service::change_service_description(SC_HANDLE service, char *description)
653 {
654 OSVERSIONINFO osvi;
655
656 /*
657 * Test for Windows 2000 or greater
658 */
659
660 osvi.dwOSVersionInfoSize = sizeof(OSVERSIONINFO);
661
662 if (GetVersionEx(&osvi) != 0 &&
663 osvi.dwPlatformId == VER_PLATFORM_WIN32_NT &&
664 osvi.dwMajorVersion >= 5)
665 {
666 typedef BOOL (WINAPI *CHANGE_SERVICE_CONFIG2_T)(SC_HANDLE, DWORD, LPVOID);
667
668 HINSTANCE hdll = LoadLibrary("advapi32.dll");
669
670 if (hdll != NULL)
671 {
672 SERVICE_DESCRIPTION sd;
673 tony 1.1 CHANGE_SERVICE_CONFIG2_T csc;
674
675 csc = (CHANGE_SERVICE_CONFIG2_T) GetProcAddress(hdll, "ChangeServiceConfig2A");
676
677 if (csc)
678 {
679 sd.lpDescription = description;
680 csc(service, SERVICE_CONFIG_DESCRIPTION, &sd);
681 }
682
683 FreeLibrary(hdll);
684 }
685 }
686 }
687
688 /*-------------------------------------------------------------------------*
689 * Method: show_error *
690 * *
691 * Args: *
692 * action: *
693 * A single verb decribing the action going on when the error *
694 tony 1.1 * occurred. For example, "opening", "creating", etc. *
695 * *
696 * object: *
697 * Description of the object on which was action occurred. Examples *
698 * are "file", "service", etc. *
699 * *
700 * hr: *
701 * The error status. Can be an hresult, or Win32 error status. *
702 * *
703 * Description: *
704 * This function provides an even higher level of abstraction over *
705 * the Win32 event logging API's. *
706 * *
707 * Returns: *
708 * true if event was successfully logged *
709 * false if the event could not be logged *
710 *-------------------------------------------------------------------------*/
711 bool
712 Service::show_error(const char *action, const char *object, DWORD hr)
713 {
714 char console_title[_MAX_PATH] = {0};
715 tony 1.1 char txt[_MAX_PATH] = "";
716 char msg[_MAX_PATH] = "";
717 DWORD nchars;
718
719 nchars = FormatMessage(
720 FORMAT_MESSAGE_FROM_SYSTEM,
721 NULL,
722 hr,
723 MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT),
724 msg,
725 sizeof(msg),
726 NULL);
727
728 if (nchars == 0)
729 sprintf(msg, "Unknown error code - %%X%x", hr);
730 else if (nchars > 1)
731 {
732 if (msg[nchars - 1] == '\n') msg[nchars - 1] = '\0';
733 if (msg[nchars - 2] == '\r') msg[nchars - 2] = '\0';
734 }
735
736 tony 1.1 sprintf(txt, "Failed to %s %s %s. Reason: %s", action, object, g_service_name, msg);
737
738 // Running from a console window
739 // send courtesy message txt to stderr
740 if (GetConsoleTitle(console_title, _MAX_PATH) > 0)
741 {
742 cerr << txt << endl;
743 }
744
745 return LogEvent(EVENTLOG_ERROR_TYPE, 1, txt);
746 }
747
748 Service::State
749 Service::get_state(DWORD scm_state)
750 {
751 return (State)scm_state;
752 }
753
754 Service::ReturnCode
755 Service::get_error(DWORD error_status, const char *action)
756 {
757 tony 1.1 switch (error_status)
758 {
759 /*
760 // INFO: Could add cases to suppress error message.
761 case ERROR_SERVICE_DOES_NOT_EXIST:
762 case ERROR_SERVICE_CANNOT_ACCEPT_CTRL:
763 break;
764 */
765 case SERVICE_RETURN_SUCCESS:
766 break;
767
768 default:
769 show_error(action, "service", error_status);
770 break;
771 }
772 return (ReturnCode)error_status;
773 }
774
|