1 // This file has been adapted to the Win32 version of apcctrl
2 // by Kern E. Sibbald. Many thanks to ATT and James Weatherall,
3 // the original author, for providing an excellent template.
4 //
5 // Rewrite/Refactoring by Adam Kropelin
6 //
7 // Copyright (2007) Adam D. Kropelin
8 // Copyright (2000) Kern E. Sibbald
9 //
10
11 // Implementation of service-oriented functionality of apcctrl
12
13 #include "winapi.h"
14 #include "compat.h"
15 #include "winups.h"
16 #include "winservice.h"
17 #include <stdio.h>
18
19 // Error message logging
20 void LogErrorMsg(const char *msg, const char *fname, int lineno);
21 #define log_error_message(msg) LogErrorMsg((msg), __FILE__, __LINE__)
22
23 // No internationalization support
24 #define _(x) x
25
26 // Internal service state (static)
27 SERVICE_STATUS upsService::m_srvstatus;
28 SERVICE_STATUS_HANDLE upsService::m_hstatus;
29 DWORD upsService::m_servicethread = 0;
30
31 // Typedefs for dynamically loaded functions
32 typedef BOOL (WINAPI * ChangeServiceConfig2Func)(SC_HANDLE, DWORD, LPVOID);
33 typedef DWORD (* RegisterServiceProcessFunc)(DWORD, DWORD);
34
35 // Internal service name
36 static char SERVICE_NAME[] = "apcctrl";
37
38 // Displayed service name
39 static const char SERVICE_DISPLAYNAME[] = "apcctrl UPS Monitor";
40
41 // Service description
42 static char APCCTRL_SERVICE_DESCRIPTION[] =
43 "apcctrl provides shutdown of your computer "
44 "in the event of a power failure.";
45
46 // List other required serves
47 #define SERVICE_DEPENDENCIES __TEXT("tcpip\0afd\0+File System\0")
48
49 // SERVICE MAIN ROUTINE
apcctrlServiceMain()50 int upsService::apcctrlServiceMain()
51 {
52 // How to run as a service depends upon the OS being used
53 switch (g_os_version_info.dwPlatformId) {
54
55 // Windows 95/98/Me
56 case VER_PLATFORM_WIN32_WINDOWS:
57 // Obtain a handle to the kernel library
58 HINSTANCE kerneldll;
59 kerneldll = LoadLibrary("KERNEL32.DLL");
60 if (kerneldll == NULL) {
61 MessageBox(NULL,
62 "KERNEL32.DLL not found: apcctrl service not started",
63 "apcctrl Service", MB_OK);
64 break;
65 }
66
67 // And find the RegisterServiceProcess function
68 RegisterServiceProcessFunc RegisterServiceProcess;
69 RegisterServiceProcess =
70 (RegisterServiceProcessFunc)GetProcAddress(
71 kerneldll, "RegisterServiceProcess");
72 if (RegisterServiceProcess == NULL) {
73 MessageBox(NULL,
74 "Registry service not found: apcctrl service not started",
75 "apcctrl Service", MB_OK);
76 log_error_message("Registry service not found");
77 FreeLibrary(kerneldll);
78 break;
79 }
80
81 // Register this process with the OS as a service!
82 RegisterServiceProcess(0, 1);
83
84 // Run the main program as a service
85 apcctrlAppMain(1);
86
87 // Then remove the service from the system service table
88 RegisterServiceProcess(0, 0);
89
90 // Free the kernel library
91 FreeLibrary(kerneldll);
92 break;
93
94 // Windows NT, Win2K, WinXP
95 case VER_PLATFORM_WIN32_NT:
96 // Create a service entry table
97 SERVICE_TABLE_ENTRY dispatchTable[] = {
98 {SERVICE_NAME, (LPSERVICE_MAIN_FUNCTION)ServiceMain},
99 {NULL, NULL}
100 };
101
102 // Call the service control dispatcher with our entry table
103 if (!StartServiceCtrlDispatcher(dispatchTable)) {
104 log_error_message("StartServiceCtrlDispatcher failed.");
105 }
106 break;
107
108 } /* end switch */
109
110 return 0;
111 }
112
113 // SERVICE MAIN ROUTINE
114 // NT/Win2K/WinXP ONLY !!!
ServiceMain(DWORD argc,char ** argv)115 void WINAPI upsService::ServiceMain(DWORD argc, char **argv)
116 {
117 // Register the service control handler
118 m_hstatus = RegisterServiceCtrlHandler(SERVICE_NAME, ServiceCtrl);
119 if (m_hstatus == 0) {
120 log_error_message("RegisterServiceCtlHandler failed");
121 MessageBox(NULL, "Contact Register Service Handler failure",
122 "apcctrl service", MB_OK);
123 return;
124 }
125
126 // Set up some standard service state values
127 m_srvstatus.dwServiceType = SERVICE_WIN32 | SERVICE_INTERACTIVE_PROCESS;
128 m_srvstatus.dwServiceSpecificExitCode = 0;
129
130 // Give this status to the SCM
131 if (!ReportStatus(
132 SERVICE_START_PENDING, // service state
133 NO_ERROR, // exit code
134 45000)) { // wait hint
135 log_error_message("ReportStatus STOPPED failed 1");
136 return;
137 }
138
139 // Now start the service working thread
140 CreateThread(NULL, 0, ServiceWorkThread, NULL, 0, NULL);
141 }
142
143 // SERVICE START ROUTINE - thread that calls apcctrlAppMain
144 // NT/Win2K/WinXP ONLY !!!
ServiceWorkThread(LPVOID lpwThreadParam)145 DWORD WINAPI upsService::ServiceWorkThread(LPVOID lpwThreadParam)
146 {
147 // report the status to the service control manager.
148 if (!ReportStatus(
149 SERVICE_RUNNING, // service state
150 NO_ERROR, // exit code
151 0)) { // wait hint
152 MessageBox(NULL, "Report Service failure", "apcctrl Service", MB_OK);
153 log_error_message("ReportStatus RUNNING failed");
154 return 0;
155 }
156
157 // Save the current thread identifier
158 m_servicethread = GetCurrentThreadId();
159
160 /* Call apcctrl main code */
161 apcctrlAppMain(1);
162
163 /* Mark that we're no longer running */
164 m_servicethread = 0;
165
166 /* Tell the service manager that we've stopped */
167 ReportStatus(SERVICE_STOPPED, 0, 0);
168 return 0;
169 }
170
171 // SERVICE STOP ROUTINE - NT/Win2K/WinXP ONLY !!!
ServiceStop()172 void upsService::ServiceStop()
173 {
174 apcctrlTerminate();
175 }
176
177 // SERVICE INSTALL ROUTINE
InstallService(bool quiet)178 int upsService::InstallService(bool quiet)
179 {
180 const unsigned int MAXPATH = 2048;
181
182 // Get the filename of this executable
183 char path[MAXPATH];
184 if (GetModuleFileName(NULL, path, MAXPATH) == 0) {
185 if (!quiet) {
186 MessageBox(NULL,
187 "Unable to install apcctrl service", SERVICE_NAME,
188 MB_ICONEXCLAMATION | MB_OK);
189 }
190 return 0;
191 }
192
193 // Append the service-start flag to the end of the path
194 // Length is path len plus quotes, space, start flag, and NUL terminator
195 char servicecmd[MAXPATH];
196 if (strlen(path) + 4 + strlen(apcctrlRunService) < MAXPATH) {
197 sprintf(servicecmd, "\"%s\" %s", path, apcctrlRunService);
198 } else {
199 if (!quiet) {
200 MessageBox(NULL,
201 "Service command length too long. Service not registered.",
202 SERVICE_NAME, MB_ICONEXCLAMATION | MB_OK);
203 }
204 return 0;
205 }
206
207 // How to add the apcctrl service depends upon the OS
208 switch (g_os_version_info.dwPlatformId) {
209
210 // Windows 95/98/Me
211 case VER_PLATFORM_WIN32_WINDOWS:
212 // Locate the RunService registry entry
213 HKEY runservices;
214 if (RegCreateKey(HKEY_LOCAL_MACHINE,
215 "Software\\Microsoft\\Windows\\CurrentVersion\\RunServices",
216 &runservices) != ERROR_SUCCESS) {
217 log_error_message("Cannot write System Registry");
218 MessageBox(NULL, _("The System Registry could not be updated - "
219 "the apcctrl service was not installed"),
220 SERVICE_NAME, MB_ICONEXCLAMATION | MB_OK);
221 break;
222 }
223
224 // Attempt to add a apcctrl key
225 if (RegSetValueEx(runservices, SERVICE_NAME, 0, REG_SZ,
226 (unsigned char *)servicecmd, strlen(servicecmd)+1) != ERROR_SUCCESS) {
227 RegCloseKey(runservices);
228 MessageBox(NULL, "The apcctrl service could not be installed",
229 SERVICE_NAME, MB_ICONEXCLAMATION | MB_OK);
230 break;
231 }
232
233 RegCloseKey(runservices);
234
235 // Indicate that we're installed to run as a service
236 SetServiceFlag(1);
237
238 // We have successfully installed the service!
239 if (!quiet) {
240 MessageBox(NULL,
241 _("The apcctrl UPS service was successfully installed.\n"
242 "The service may be started by double clicking on the\n"
243 "apcctrl \"Start\" icon and will automatically\n"
244 "be run the next time this machine is rebooted. "),
245 SERVICE_NAME, MB_ICONINFORMATION | MB_OK);
246 }
247 break;
248
249 // Windows NT, Win2K, WinXP
250 case VER_PLATFORM_WIN32_NT:
251 // Open the default, local Service Control Manager database
252 SC_HANDLE hsrvmanager;
253 hsrvmanager = OpenSCManager(NULL, NULL, SC_MANAGER_ALL_ACCESS);
254 if (hsrvmanager == NULL) {
255 MessageBox(NULL,
256 _("The Service Control Manager could not be contacted - "
257 "the apcctrl service was not installed"),
258 SERVICE_NAME, MB_ICONEXCLAMATION | MB_OK);
259 break;
260 }
261
262 // Create an entry for the apcctrl service
263 SC_HANDLE hservice;
264 hservice = CreateService(
265 hsrvmanager, // SCManager database
266 SERVICE_NAME, // name of service
267 SERVICE_DISPLAYNAME, // name to display
268 SERVICE_ALL_ACCESS, // desired access
269 SERVICE_WIN32_OWN_PROCESS | // service type
270 SERVICE_INTERACTIVE_PROCESS,
271 SERVICE_AUTO_START, // start type
272 SERVICE_ERROR_NORMAL, // error control type
273 servicecmd, // service's binary
274 NULL, // no load ordering group
275 NULL, // no tag identifier
276 SERVICE_DEPENDENCIES, // dependencies
277 NULL, // LocalSystem account
278 NULL); // no password
279 if (hservice == NULL) {
280 if (!quiet || GetLastError() != ERROR_SERVICE_EXISTS) {
281 MessageBox(NULL,
282 "The apcctrl service could not be installed",
283 SERVICE_NAME, MB_ICONEXCLAMATION | MB_OK);
284 }
285 CloseServiceHandle(hsrvmanager);
286 break;
287 }
288
289 SetServiceDescription(hservice, APCCTRL_SERVICE_DESCRIPTION);
290
291 CloseServiceHandle(hservice);
292 CloseServiceHandle(hsrvmanager);
293
294 // Indicate that we're installed to run as a service
295 SetServiceFlag(1);
296
297 // Everything went fine
298 if (!quiet) {
299 MessageBox(NULL,
300 _("The apcctrl UPS service was successfully installed.\n"
301 "The service may be started from the Control Panel and will\n"
302 "automatically be run the next time this machine is rebooted."),
303 SERVICE_NAME,
304 MB_ICONINFORMATION | MB_OK);
305 }
306 break;
307
308 default:
309 MessageBox(NULL,
310 _("Unknown Windows operating system.\n"
311 "Cannot install apcctrl service.\n"),
312 SERVICE_NAME, MB_ICONEXCLAMATION | MB_OK);
313 break;
314 }
315
316 return 0;
317 }
318
319
320 // SERVICE REMOVE ROUTINE
RemoveService(bool quiet)321 int upsService::RemoveService(bool quiet)
322 {
323 // How to remove the apcctrl service depends upon the OS
324 switch (g_os_version_info.dwPlatformId) {
325
326 // Windows 95/98/Me
327 case VER_PLATFORM_WIN32_WINDOWS:
328 // Locate the RunService registry entry
329 HKEY runservices;
330 if (RegOpenKey(HKEY_LOCAL_MACHINE,
331 "Software\\Microsoft\\Windows\\CurrentVersion\\RunServices",
332 &runservices) != ERROR_SUCCESS) {
333 if (!quiet) {
334 MessageBox(NULL,
335 _("Could not find registry entry.\n"
336 "Service probably not registerd - "
337 "the apcctrl service was not removed"),
338 SERVICE_NAME, MB_ICONEXCLAMATION | MB_OK);
339 }
340 } else {
341 // Attempt to delete the apcctrl key
342 if (RegDeleteValue(runservices, SERVICE_NAME) != ERROR_SUCCESS) {
343 if (!quiet) {
344 MessageBox(NULL, _("Could not delete Registry key.\n"
345 "The apcctrl service could not be removed"),
346 SERVICE_NAME, MB_ICONEXCLAMATION | MB_OK);
347 }
348 }
349
350 RegCloseKey(runservices);
351 break;
352 }
353
354 // Try to kill any running copy of apcctrl
355 apcctrlTerminate();
356
357 // Indicate that we're no longer installed to run as a service
358 SetServiceFlag(0);
359
360 // We have successfully removed the service!
361 if (!quiet) {
362 MessageBox(NULL, "The apcctrl service has been removed",
363 SERVICE_NAME, MB_ICONINFORMATION | MB_OK);
364 }
365 break;
366
367 // Windows NT, Win2K, WinXP
368 case VER_PLATFORM_WIN32_NT:
369 SC_HANDLE hservice = OpenNTService();
370 if (!StopNTService(hservice)) {
371 // Service could not be stopped
372 MessageBox(NULL, "The apcctrl service could not be stopped",
373 SERVICE_NAME, MB_ICONEXCLAMATION | MB_OK);
374 }
375
376 if(DeleteService(hservice)) {
377 // Indicate that we're no longer installed to run as a service
378 SetServiceFlag(0);
379
380 // Service successfully removed
381 if (!quiet) {
382 MessageBox(NULL, "The apcctrl service has been removed",
383 SERVICE_NAME, MB_ICONINFORMATION | MB_OK);
384 }
385 } else {
386 // Failed to remove
387 MessageBox(NULL, "The apcctrl service could not be removed",
388 SERVICE_NAME, MB_ICONEXCLAMATION | MB_OK);
389 }
390
391 CloseServiceHandle(hservice);
392 break;
393 }
394
395 return 0;
396 }
397
398 // USEFUL SERVICE SUPPORT ROUTINES
399
400 // Service control routine
ServiceCtrl(DWORD ctrlcode)401 void WINAPI upsService::ServiceCtrl(DWORD ctrlcode)
402 {
403 // What control code have we been sent?
404 switch(ctrlcode) {
405 case SERVICE_CONTROL_STOP:
406 // STOP : The service must stop
407 m_srvstatus.dwCurrentState = SERVICE_STOP_PENDING;
408 ServiceStop();
409 break;
410
411 case SERVICE_CONTROL_INTERROGATE:
412 // QUERY : Service control manager just wants to know our state
413 break;
414
415 default:
416 // Control code not recognised
417 break;
418 }
419
420 // Tell the control manager what we're up to.
421 ReportStatus(m_srvstatus.dwCurrentState, NO_ERROR, 0);
422 }
423
424 // Service manager status reporting
ReportStatus(DWORD state,DWORD exitcode,DWORD waithint)425 BOOL upsService::ReportStatus(DWORD state,
426 DWORD exitcode,
427 DWORD waithint)
428 {
429 static DWORD checkpoint = 1;
430
431 // If we're in the start state then we don't want the control manager
432 // sending us control messages because they'll confuse us.
433 if (state == SERVICE_START_PENDING)
434 m_srvstatus.dwControlsAccepted = 0;
435 else
436 m_srvstatus.dwControlsAccepted = SERVICE_ACCEPT_STOP;
437
438 // Save the new status we've been given
439 m_srvstatus.dwCurrentState = state;
440 m_srvstatus.dwWin32ExitCode = exitcode;
441 m_srvstatus.dwWaitHint = waithint;
442
443 // Update the checkpoint variable to let the SCM know that we
444 // haven't died if requests take a long time
445 if ((state == SERVICE_RUNNING) || (state == SERVICE_STOPPED))
446 m_srvstatus.dwCheckPoint = 0;
447 else
448 m_srvstatus.dwCheckPoint = checkpoint++;
449
450 // Tell the SCM our new status
451 BOOL result = SetServiceStatus(m_hstatus, &m_srvstatus);
452 if (!result)
453 log_error_message("SetServiceStatus failed");
454
455 return result;
456 }
457
458 // Error reporting
LogErrorMsg(const char * message,const char * fname,int lineno)459 void LogErrorMsg(const char *message, const char *fname, int lineno)
460 {
461 // Get the error code
462 LPTSTR msg;
463 DWORD error = GetLastError();
464 FormatMessage(FORMAT_MESSAGE_ALLOCATE_BUFFER|
465 FORMAT_MESSAGE_FROM_SYSTEM,
466 NULL,
467 error,
468 0,
469 (LPTSTR)&msg,
470 0,
471 NULL);
472
473 // Use event logging to log the error
474 HANDLE heventsrc = RegisterEventSource(NULL, SERVICE_NAME);
475 if (heventsrc == NULL)
476 return;
477
478 char msgbuff[256];
479 snprintf(msgbuff, sizeof(msgbuff), "\n\n%s error: %ld at %s:%d",
480 SERVICE_NAME, error, fname, lineno);
481
482 const char *strings[3];
483 strings[0] = msgbuff;
484 strings[1] = message;
485 strings[2] = msg;
486
487 ReportEvent(heventsrc, // handle of event source
488 EVENTLOG_ERROR_TYPE, // event type
489 0, // event category
490 0, // event ID
491 NULL, // current user's SID
492 3, // strings in 'strings'
493 0, // no bytes of raw data
494 (const char **)strings, // array of error strings
495 NULL); // no raw data
496
497 DeregisterEventSource(heventsrc);
498 LocalFree(msg);
499 }
500
SetServiceDescription(SC_HANDLE hService,LPSTR lpDesc)501 void upsService::SetServiceDescription(SC_HANDLE hService, LPSTR lpDesc)
502 {
503 HINSTANCE hLib = LoadLibrary("ADVAPI32.DLL");
504 if (!hLib)
505 return;
506
507 ChangeServiceConfig2Func ChangeServiceConfig2 =
508 (ChangeServiceConfig2Func)GetProcAddress(hLib, "ChangeServiceConfig2A");
509 if (!ChangeServiceConfig2) {
510 FreeLibrary(hLib);
511 return;
512 }
513
514 SERVICE_DESCRIPTION sdBuf;
515 sdBuf.lpDescription = lpDesc;
516
517 ChangeServiceConfig2(
518 hService, // handle to service
519 SERVICE_CONFIG_DESCRIPTION, // change: description
520 &sdBuf); // value: new description
521
522 FreeLibrary(hLib);
523 }
524
SetServiceFlag(DWORD flag)525 void upsService::SetServiceFlag(DWORD flag)
526 {
527 // Create or open HKLM\Software\apcctrl key
528 HKEY apcctrl;
529 RegCreateKey(HKEY_LOCAL_MACHINE, "Software\\apcctrl", &apcctrl);
530
531 // Add InstalledService value
532 RegSetValueEx(
533 apcctrl, "InstalledService", 0, REG_DWORD, (BYTE*)&flag, sizeof(flag));
534 }
535
StopNTService(SC_HANDLE hservice)536 BOOL upsService::StopNTService(SC_HANDLE hservice)
537 {
538 // Try to stop the apcctrl service
539 SERVICE_STATUS status;
540 status.dwCurrentState = SERVICE_RUNNING;
541 if (ControlService(hservice, SERVICE_CONTROL_STOP, &status)) {
542 while(QueryServiceStatus(hservice, &status)) {
543 if (status.dwCurrentState == SERVICE_STOP_PENDING) {
544 Sleep(1000);
545 } else {
546 break;
547 }
548 }
549 }
550
551 return status.dwCurrentState == SERVICE_STOPPED;
552 }
553
OpenNTService()554 SC_HANDLE upsService::OpenNTService()
555 {
556 // Open the SCM
557 SC_HANDLE hscm = OpenSCManager(
558 NULL, // machine (NULL == local)
559 NULL, // database (NULL == default)
560 SC_MANAGER_ALL_ACCESS); // access required
561 if (hscm == NULL) {
562 return NULL;
563 }
564
565 // Open the service
566 SC_HANDLE hservice = OpenService(hscm, SERVICE_NAME, SERVICE_ALL_ACCESS);
567
568 // Close SCM and return service handle
569 CloseServiceHandle(hscm);
570 return hservice;
571 }
572