00001 #ifndef SERVICE_ROOT_IMPLEMENTATION_FILE
00002 #define SERVICE_ROOT_IMPLEMENTATION_FILE
00003
00004
00005
00006
00007
00008
00009
00010
00011
00012
00013
00014
00015
00016
00017
00018 #include "service_root.h"
00019
00020 #include <basis/convert_utf.h>
00021 #include <basis/function.h>
00022 #include <basis/istring.h>
00023 #include <basis/log_base.h>
00024 #include <basis/mutex.h>
00025 #include <basis/portable.h>
00026 #include <basis/string_array.h>
00027 #include <opsystem/command_line.h>
00028 #include <loggers/file_logger.h>
00029 #include <opsystem/filename.h>
00030 #include <opsystem/ini_config.h>
00031 #include <opsystem/path_configuration.h>
00032 #include <opsystem/rendezvous.h>
00033
00034 #include <signal.h>
00035 #include <stdio.h>
00036 #include <stdlib.h>
00037
00038 using namespace portable;
00039
00040
00041
00042
00043 #ifdef __WIN32__
00044
00045 #include <io.h>
00046 #include <process.h>
00047 #include <tchar.h>
00048 class internal_service_status : public SERVICE_STATUS {};
00049 #else
00050
00051 class internal_service_status {};
00052
00053 enum more_service_markers {
00054
00055 SERVICE_CONTROL_STOP = 999,
00056 SERVICE_CONTROL_INTERROGATE
00057 };
00058 #endif
00059
00061
00062 #undef LOG
00063 #define LOG(s) CLASS_EMERGENCY_LOG(program_wide_logger(), s)
00064
00066
00067 service_root *service_root::_last_root = NIL;
00068
00069 service_root::service_root(const istring &service_name,
00070 const istring &display_name, const string_array &dependencies)
00071 : _leave_now(false),
00072 _service_name(new istring(service_name)),
00073 _display_name(new istring(display_name)),
00074 _dependencies(new string_array(dependencies)),
00075 _error_code(0),
00076 _normal_app(false),
00077 _checkpoint(1),
00078 _service_status(new internal_service_status),
00079 _control_handle(0),
00080 _app_lock(NIL),
00081 _got_lock(false)
00082 {
00083 _last_root = this;
00084 SET_DEFAULT_COMBO_LOGGER;
00085 }
00086
00087 service_root::~service_root()
00088 {
00089 _last_root = NIL;
00090 if (_got_lock) _app_lock->unlock();
00091 WHACK(_service_name);
00092 WHACK(_display_name);
00093 WHACK(_dependencies);
00094 WHACK(_service_status);
00095 WHACK(_app_lock);
00096 }
00097
00098 const istring &service_root::service_name() const { return *_service_name; }
00099
00100 const istring &service_root::display_name() const { return *_display_name; }
00101
00102 u_int service_root::current_status()
00103 {
00104 #ifdef __WIN32__
00105 return _service_status->dwCurrentState;
00106 #else
00107 return 0;
00108 #endif
00109 }
00110
00111 service_root &service_root::get_root()
00112 {
00113 if (!_last_root) throw "_last_root not set for service_root class!";
00114 return *_last_root;
00115 }
00116
00117 void service_root::stop_service() { _leave_now = true; }
00118
00119 const string_array &service_root::dependencies() const
00120 { return *_dependencies; }
00121
00122 int service_root::instruct(int argc, char **argv)
00123 {
00124 command_line cmd(argc, argv);
00125 istring app = cmd.program_name().rootname();
00126 program_wide_logger().log(app + istring(" --install"));
00127 program_wide_logger().log(" -> installs the service.");
00128 program_wide_logger().log(" additional install flags include:");
00129 program_wide_logger().log(" --automatic -> marks the service as automatically launched.");
00130 program_wide_logger().log(" --security inifile -> sets service login using inifile.");
00131 program_wide_logger().log(" (ini file must have a [security] section and two entries");
00132 program_wide_logger().log(" named user and password)");
00133 program_wide_logger().log(app + istring(" --remove"));
00134 program_wide_logger().log(" -> removes the service.");
00135 program_wide_logger().log(app + istring(" --standalone"));
00136 program_wide_logger().log(" -> runs as a normal application (not as a service).");
00137 return 1;
00138 }
00139
00140 bool service_root::report_status(u_int dwCurrentState, u_int dwWin32ExitCode,
00141 u_int dwWaitHint)
00142 {
00143 #ifndef __WIN32__
00144 if (dwCurrentState || dwWin32ExitCode || dwWaitHint) {}
00145 return true;
00146 #else
00147
00148 if (!_normal_app) {
00149
00150 if (dwCurrentState == SERVICE_START_PENDING)
00151 _service_status->dwControlsAccepted = 0;
00152 else
00153 _service_status->dwControlsAccepted = SERVICE_ACCEPT_STOP;
00154
00155 _service_status->dwCurrentState = dwCurrentState;
00156 _service_status->dwWin32ExitCode = dwWin32ExitCode;
00157 _service_status->dwWaitHint = dwWaitHint;
00158
00159 if ( (dwCurrentState == SERVICE_RUNNING)
00160 || (dwCurrentState == SERVICE_STOPPED) )
00161 _service_status->dwCheckPoint = 0;
00162 else
00163 _service_status->dwCheckPoint = _checkpoint++;
00164
00165
00166 if (!SetServiceStatus((SERVICE_STATUS_HANDLE)_control_handle,
00167 _service_status)) {
00168 log_event("failed in call to SetServiceStatus");
00169 return false;
00170 }
00171 }
00172 return true;
00173 #endif
00174 }
00175
00176 void service_root::log_event(const istring &to_log) const
00177 {
00178 #ifndef __WIN32__
00179 program_wide_logger().log(to_log);
00180 #else
00181 if (_normal_app) {
00182
00183 program_wide_logger().log(to_log);
00184 } else {
00185
00186 u_int dwErr = GetLastError();
00187 HANDLE event_source = RegisterEventSource(NIL,
00188 to_unicode_temp(service_name()));
00189 if (!event_source) return;
00190 istring message(istring::SPRINTF, "%s had an error %d.",
00191 service_name().s(), dwErr);
00192 LPCTSTR string_list[2];
00193 to_unicode_persist(temp_msg, message);
00194 string_list[0] = temp_msg;
00195 to_unicode_persist(temp_log, to_log);
00196 string_list[1] = temp_log;
00197 ReportEvent(event_source, EVENTLOG_ERROR_TYPE, 0, 0, NIL, 2, 0,
00198 string_list, NIL);
00199 DeregisterEventSource(event_source);
00200 }
00201 #endif
00202 }
00203
00204 bool service_root::already_running(const istring &program_name)
00205 {
00206 FUNCDEF("already_running");
00207 if (!program_name) {
00208 LOG("no program name for service, so we're not making it a singleton.");
00209 return false;
00210 }
00211
00212 _app_lock = new rendezvous(program_name + "_service_");
00213 _got_lock = _app_lock->lock(rendezvous::IMMEDIATE_RETURN);
00214 #ifdef DEBUG_SERVICE_ROOT
00215 if (_got_lock)
00216 LOG("program was not running yet.")
00217 else
00218 LOG("program was already running!");
00219 #endif
00220 return !_got_lock;
00221 }
00222
00223 void service_root::stop_service_handler(int formal(sig_num))
00224 { get_root().stop_service(); }
00225
00226 int service_root::initialize(int argc, char **argv, const istring &program_name)
00227 {
00228 FUNCDEF("initialize");
00229 #ifndef __WIN32__
00230 if (already_running(program_name)) {
00231 LOG("service is already running, so this instance is leaving.");
00232 return 0;
00233 }
00234
00235
00236 signal(SIGHUP, stop_service_handler);
00237 signal(SIGINT, stop_service_handler);
00238
00239 standalone(argc, argv);
00240 return 0;
00241 #else
00242 to_unicode_persist(temp_serv, service_name());
00243
00244 SERVICE_TABLE_ENTRY dispatchTable[] = {
00245 { temp_serv, (LPSERVICE_MAIN_FUNCTION)setup },
00246 { NIL, NIL }
00247 };
00248
00249 command_line args(argc, argv);
00250
00251
00252
00253
00254
00255 if (args.entries() > 0) {
00256 int index = 0;
00257 if (args.find("install", index, false)) {
00258 istring user, password;
00259 bool set_auto = false;
00260 index = 0;
00261 if (args.find("automatic", index, false))
00262 set_auto = true;
00263 index = 0;
00264 if (args.find("security", index, false)) {
00265 index++;
00266 if (index > args.entries() - 1) {
00267
00268 LOG("Failed to find the filename for the \"security\""
00269 "parameter!\n");
00270 return instruct(argc, argv);
00271 }
00272 command_parameter parm = args.get(index);
00273 istring ini_file = parm.text();
00274 ini_configurator conf(ini_file, ini_configurator::RETURN_ONLY,
00275 ini_configurator::APPLICATION_DIRECTORY);
00276
00277 user = conf.load("security", "user", "");
00278 password = conf.load("security", "password", "");
00279 unlink(ini_file.s());
00280 }
00281
00282 install(set_auto, user, password);
00283 return 0;
00284 } else if (args.find("remove", index, false)) {
00285 remove();
00286 return 0;
00287 } else if (args.find("standalone", index, false)) {
00288 if (already_running(program_name)) {
00289 LOG("service is already running, so this instance is leaving.");
00290 return 0;
00291 }
00292 standalone(argc, argv);
00293 return 0;
00294 } else return instruct(argc, argv);
00295
00296
00297 }
00298
00299 if (already_running(program_name)) {
00300 LOG("service is already running, so this instance is leaving.");
00301 return 0;
00302 }
00303
00304
00305
00306
00307 LOG("starting service control dispatcher.");
00308
00309 if (!StartServiceCtrlDispatcher(dispatchTable)) {
00310 log_event("StartServiceCtrlDispatcher failed.");
00311 return 1;
00312 }
00313 return 0;
00314 #endif
00315 }
00316
00317 void service_root::setup(int argc, char **argv)
00318 {
00319 service_root &root = get_root();
00320 #ifdef __WIN32__
00321
00322 root._control_handle = (void *)RegisterServiceCtrlHandler
00323 (to_unicode_temp(root.service_name()), control_service);
00324
00325 if (!root._control_handle) return;
00326
00327
00328
00329 root._service_status->dwServiceType = SERVICE_WIN32_OWN_PROCESS;
00330
00331
00332
00333
00334
00335 root._service_status->dwServiceType |= SERVICE_INTERACTIVE_PROCESS;
00336
00337 root._service_status->dwServiceSpecificExitCode = 0;
00338 #else
00339 root._control_handle = 0;
00340 #endif
00341
00342
00343 root.report_status(SERVICE_START_PENDING, 0, 0);
00344
00345 root.perform_service(argc, argv);
00346
00347
00348 if (root._control_handle)
00349 root.report_status(SERVICE_STOPPED, root.error_code(), 0);
00350 }
00351
00352 void service_root::control_service(u_long control_code)
00353 {
00354 service_root &root = get_root();
00355 switch(control_code) {
00356 case SERVICE_CONTROL_STOP:
00357 root.report_status(SERVICE_STOP_PENDING, 0, 0);
00358
00359
00360
00361 root.stop_service();
00362 return;
00363 case SERVICE_CONTROL_INTERROGATE:
00364
00365 break;
00366 default:
00367 break;
00368 }
00369 root.report_status(root.current_status(), 0, 0);
00370 }
00371
00372 flexichar *service_root::compute_dependency_string()
00373 {
00374
00375
00376
00377
00378
00379 int deplen = 2;
00380
00381
00382 for (int i = 0; i < _dependencies->length(); i++)
00383 deplen += (*_dependencies)[i].length() + 1;
00384 flexichar *dep_copy = new flexichar[deplen];
00385 int dep_posn = 0;
00386 for (int j = 0; j < _dependencies->length(); j++) {
00387 to_unicode_persist(current_dep, (*_dependencies)[j]);
00388 const int len = current_dep.length();
00389 for (int k = 0; k < len + 1; k++) {
00390 *(dep_copy + dep_posn + k) = ((flexichar *)current_dep)[k];
00391 }
00392 dep_posn += len + 1;
00393 }
00394
00395 *(dep_copy + dep_posn++) = '\0';
00396 *(dep_copy + dep_posn++) = '\0';
00397 return dep_copy;
00398 }
00399
00400
00401 void service_root::install(bool set_auto, const istring &user_in,
00402 const istring &password)
00403 {
00404 FUNCDEF("install");
00405 #ifndef __WIN32__
00406 if (set_auto || !user_in || !password) {}
00407 return;
00408 #else
00409 istring path = ::module_name();
00410 if (!path) {
00411 LOG(istring("Unable to install ") + display_name().s()
00412 + istring(" - ") + system_error_text(GetLastError()));
00413 return;
00414 }
00415
00416
00417 bool can_use_desktop = false;
00418 if ( !user_in
00419 || user_in.iequals("LocalSystem")
00420 || user_in.iequals(".\\LocalSystem") ) {
00421 LOG("setting the service to be able to interact with the desktop.");
00422 can_use_desktop = true;
00423 }
00424
00425 istring user = user_in;
00426 if (user.t() && negative(user.find('\\')) ) {
00427
00428
00429 user = istring(".\\") + user_in;
00430 }
00431
00432 SC_HANDLE manager = OpenSCManager(NIL, NIL, SC_MANAGER_ALL_ACCESS);
00433
00434 if (manager) {
00435 flexichar *dep_copy = service_root::compute_dependency_string();
00436
00437
00438
00439
00440
00441
00442
00443 int start_flag = SERVICE_DEMAND_START;
00444 if (set_auto) start_flag = SERVICE_AUTO_START;
00445
00446
00447 const char *user_p = user.t()? user.s() : NIL;
00448 const char *pass_p = password.t()? password.s() : NIL;
00449
00450 int service_mode = SERVICE_WIN32_OWN_PROCESS;
00451 if (can_use_desktop) service_mode |= SERVICE_INTERACTIVE_PROCESS;
00452
00453
00454 SC_HANDLE service = CreateService(manager, to_unicode_temp(service_name()),
00455 to_unicode_temp(display_name()), SERVICE_ALL_ACCESS, service_mode,
00456 start_flag, SERVICE_ERROR_NORMAL, to_unicode_temp(path), NIL, NIL,
00457 dep_copy, to_unicode_temp(user_p), to_unicode_temp(pass_p));
00458 WHACK(dep_copy);
00459
00460 if ( service ) {
00461 istring sec_info;
00462 if (user.t())
00463 sec_info = istring(" under user id ") + user;
00464 program_wide_logger().log(display_name() + istring(" is now installed") + sec_info + ".");
00465 CloseServiceHandle(service);
00466 } else
00467 program_wide_logger().log(istring("CreateService failed: ")
00468 + system_error_text(GetLastError()));
00469
00470 CloseServiceHandle(manager);
00471 } else
00472 program_wide_logger().log(istring("OpenSCManager failed: ")
00473 + system_error_text(GetLastError()));
00474 #endif
00475 }
00476
00477 void service_root::remove()
00478 {
00479 FUNCDEF("remove");
00480 #ifndef __WIN32__
00481 return;
00482 #else
00483 SC_HANDLE manager = OpenSCManager(NIL, NIL, SC_MANAGER_ALL_ACCESS);
00484 if (!manager) {
00485 program_wide_logger().log(istring("OpenSCManager failed: ")
00486 + system_error_text(GetLastError()));
00487 return;
00488 }
00489 SC_HANDLE service = OpenService(manager, to_unicode_temp(service_name()),
00490 SERVICE_ALL_ACCESS);
00491 if (!service) {
00492 program_wide_logger().log(istring("OpenService failed: ")
00493 + system_error_text(GetLastError()));
00494 CloseServiceHandle(manager);
00495 return;
00496 }
00497
00498 if (ControlService(service, SERVICE_CONTROL_STOP, _service_status)) {
00499 #ifndef OMIT_PROGRAM_WIDE_LOGGER
00500 log_base::line_ending eol = program_wide_logger().eol();
00501 program_wide_logger().eol(log_base::NO_ENDING);
00502 #else
00503 log_base::line_ending eol = log_base::CRLF_AT_END;
00504 #endif
00505 program_wide_logger().log(istring("Stopping ") + display_name() + ".");
00506 portable::sleep_ms(200);
00507 while (QueryServiceStatus( service, _service_status ) ) {
00508 if (current_status() == SERVICE_STOP_PENDING) {
00509 program_wide_logger().log(".");
00510 portable::sleep_ms(100);
00511 } else break;
00512 }
00513 program_wide_logger().eol(eol);
00514 program_wide_logger().log("");
00515
00516 if (current_status() == SERVICE_STOPPED)
00517 program_wide_logger().log(display_name() + istring(" stopped."));
00518 else
00519 program_wide_logger().log(display_name() + istring(" failed to stop."));
00520 }
00521
00522
00523 if (DeleteService(service))
00524 program_wide_logger().log(display_name() + istring(" removed."));
00525 else
00526 program_wide_logger().log(display_name() + istring(" could not be "
00527 "removed: ") + system_error_text(GetLastError()));
00528
00529 CloseServiceHandle(service);
00530 CloseServiceHandle(manager);
00531 #endif
00532 }
00533
00534 void service_root::standalone(int argc, char **argv)
00535 {
00536 _normal_app = true;
00537 program_wide_logger().log(istring("running ") + display_name() + " as a normal application.");
00538 #ifdef __WIN32__
00539 SetConsoleCtrlHandler(standalone_controller, true);
00540 #endif
00541 perform_service(argc, argv);
00542 }
00543
00544 int service_root::standalone_controller(u_long control_type)
00545 {
00546 #ifndef __WIN32__
00547 if (control_type) {}
00548 return false;
00549 #else
00550 service_root &root = get_root();
00551 switch (control_type) {
00552
00553 case CTRL_C_EVENT:
00554 case CTRL_BREAK_EVENT:
00555 program_wide_logger().log(isprintf("stopping stand-alone run "
00556 "of %s.", root.display_name().s()));
00557 root.stop_service();
00558 return true;
00559 break;
00560 }
00561 return false;
00562 #endif
00563 }
00564
00565
00566 #endif //SERVICE_ROOT_IMPLEMENTATION_FILE
00567