service_root.cpp

Go to the documentation of this file.
00001 #ifndef SERVICE_ROOT_IMPLEMENTATION_FILE
00002 #define SERVICE_ROOT_IMPLEMENTATION_FILE
00003 
00004 /*****************************************************************************\
00005 *                                                                             *
00006 *  Name   : service_root                                                      *
00007 *  Author : Chris Koeritz                                                     *
00008 *                                                                             *
00009 *******************************************************************************
00010 * Copyright (c) 2000-$now By Author.  This program is free software; you can  *
00011 * redistribute it and/or modify it under the terms of the GNU General Public  *
00012 * License as published by the Free Software Foundation; either version 2 of   *
00013 * the License or (at your option) any later version.  This is online at:      *
00014 *     http://www.fsf.org/copyleft/gpl.html                                    *
00015 * Please send any updates to: fred@gruntose.com                               *
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 //#define DEBUG_SERVICE_ROOT
00041   // uncomment for noisier version.
00042 
00043 #ifdef __WIN32__
00044   // pull in the special win32 service junk.
00045   #include <io.h>
00046   #include <process.h>
00047   #include <tchar.h>
00048   class internal_service_status : public SERVICE_STATUS {};
00049 #else
00050   // placeholders for other platforms.
00051   class internal_service_status {};
00052 
00053   enum more_service_markers {
00054     // control codes used in control_service.
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   // we don't bother with reporting if this service is in standalone mode.
00148   if (!_normal_app) {
00149     // this is a real service so make the report.
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     // Report the status of the service to the service control manager.
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     // we don't send events to the system log when standalone.
00183     program_wide_logger().log(to_log);
00184   } else {
00185     // we're a real service probably, so send a system event out.
00186     u_int dwErr = GetLastError();
00187     HANDLE event_source = RegisterEventSource(NIL,
00188         to_unicode_temp(service_name()));
00189     if (!event_source) return;  // failed to create the event support.
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;  // "no program" is never running.
00210   }
00211   // create the lock name based on our program name.
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   // hook in some signal handlers so we do a clean shutdown.
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   // note: for the installation of the service to really "work", the person
00252   // being set up as the service's user id must have the right to run as a
00253   // service.  this is separately available in the run_as_service program.
00254 
00255   if (args.entries() > 0) {
00256     int index = 0;
00257     if (args.find("install", index, false)) {
00258       istring user, password;  // only used if there's security info.
00259       bool set_auto = false;
00260       index = 0;  // reset for second search.
00261       if (args.find("automatic", index, false))
00262         set_auto = true;  // they want automatic startup.
00263       index = 0;  // reset for third search.
00264       if (args.find("security", index, false)) {
00265         index++;  // jump to next item for the filename.
00266         if (index > args.entries() - 1) {
00267           // they've left us no string for this.
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 //hmmm: check if the ini config is happy.
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       // if they got to here, they don't know what the commands are, so
00296       // print them out.
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   // no command line parameters were passed, so this is probably being started
00305   // by the Service Control Manager.
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   // register the control request handler for the service.
00322   root._control_handle = (void *)RegisterServiceCtrlHandler
00323       (to_unicode_temp(root.service_name()), control_service);
00324 
00325   if (!root._control_handle) return;
00326     // failed to get a handle.  this is not good.
00327 
00328   // set some boilerplate values.
00329   root._service_status->dwServiceType = SERVICE_WIN32_OWN_PROCESS;
00330 
00331   // add in desktop interaction, which will really only work for the SYSTEM
00332   // account (LocalSystem).  but apparently if we don't add it here, the
00333   // registration for the service in the services browser doesn't show it as
00334   // an option.
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   // record that we have started the service.
00343   root.report_status(SERVICE_START_PENDING, 0, 0);
00344   // now actually begin.
00345   root.perform_service(argc, argv);
00346 
00347   // tell the SCM that we're shutting down now.
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         // SERVICE_STOP_PENDING is reported before the stop to avoid a race
00359         // condition between the shutdown process and the service status
00360         // reporting (might claim an error 1053 occurred otherwise).
00361       root.stop_service();
00362       return;
00363     case SERVICE_CONTROL_INTERROGATE:
00364 //do something useful here?
00365       break;
00366     default:  // unknown control codes...
00367       break;
00368   }
00369   root.report_status(root.current_status(), 0, 0);
00370 }
00371 
00372 flexichar *service_root::compute_dependency_string()
00373 {
00374   // this might be useful in general, although it's only used here so far.
00375   // this takes a string array and warps it into a single string.  strings
00376   // are separated by null characters, while the end of the list has two
00377   // null characters.  note that the returned string must be deleted after
00378   // it is done being used!
00379   int deplen = 2;  // two character minimum for double nulls.
00380   // the incoming dependencies are utf-8 strings, so we should always have
00381   // enough space by counting their length, even when going to unicode.
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   // dependency format wants two nulls at end.
00395   *(dep_copy + dep_posn++) = '\0';
00396   *(dep_copy + dep_posn++) = '\0';
00397   return dep_copy;
00398 }
00399 
00400 //hmmm: shouldn't that return a bool or something?
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   // permit the service to use the desktop if it's LocalSystem.
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     // fix the user's domain name to be local.  we are tolerating using an
00428     // nt-domain username but don't expect it to work properly.
00429     user = istring(".\\") + user_in;
00430   }
00431 
00432   SC_HANDLE manager = OpenSCManager(NIL, NIL, SC_MANAGER_ALL_ACCESS);
00433     // open the manager on this machine with the local database.
00434   if (manager) {
00435     flexichar *dep_copy = service_root::compute_dependency_string();
00436       // this is a newly allocated string which we must delete below.
00437 
00438 //hmmm: shouldn't we have a flag for when we want this to be an automatically
00439 //      started service at startup!!!???
00440 //      we need to encapsulate how services can start at the interface.
00441 
00442     // this handles when automatic startup is desired.
00443     int start_flag = SERVICE_DEMAND_START;
00444     if (set_auto) start_flag = SERVICE_AUTO_START;
00445 
00446     // prepare appropriate parms for security info, if passed.
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 //hmmm: only include the interactive if they pass --desktop?
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);  // clean up the copy we made.
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   // now try to stop the service.
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("");  // force one ending out.
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   // now remove the service
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   // clean up everything.
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     // the break event is used as a stop of the simulated service.
00553     case CTRL_C_EVENT:  // fall-through.
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;  // didn't handle anything.
00562 #endif
00563 }
00564 
00565 
00566 #endif //SERVICE_ROOT_IMPLEMENTATION_FILE
00567 

Generated on Fri Nov 21 04:29:58 2008 for HOOPLE Libraries by  doxygen 1.5.1