1 /*****************************************************************************\
3 * Name : launch_manager
4 * Author : Chris Koeritz
6 *******************************************************************************
7 * Copyright (c) 2000-$now By Author. This program is free software; you can *
8 * redistribute it and/or modify it under the terms of the GNU General Public *
9 * License as published by the Free Software Foundation; either version 2 of *
10 * the License or (at your option) any later version. This is online at: *
11 * http://www.fsf.org/copyleft/gpl.html *
12 * Please send any updates to: fred@gruntose.com *
13 \*****************************************************************************/
15 #include "hoople_service.h"
16 #include "launch_manager.h"
18 #include <application/windoze_helper.h>
19 #include <basis/astring.h>
20 #include <basis/mutex.h>
21 #include <configuration/configurator.h>
22 #include <configuration/section_manager.h>
23 #include <filesystem/filename.h>
24 #include <loggers/critical_events.h>
25 #include <loggers/program_wide_logger.h>
26 #include <processes/configured_applications.h>
27 #include <processes/ethread.h>
28 #include <processes/launch_process.h>
29 #include <structures/set.h>
30 #include <structures/string_table.h>
31 #include <structures/unique_id.h>
32 #include <textual/parser_bits.h>
33 #include <timely/time_control.h>
34 #include <timely/time_stamp.h>
36 using namespace basis;
37 using namespace configuration;
38 using namespace filesystem;
39 using namespace loggers;
40 using namespace processes;
41 using namespace structures;
42 using namespace textual;
43 using namespace timely;
45 namespace application {
47 #define DEBUG_PROCESS_MANAGER
48 // uncomment for verbose diagnostics.
50 #define LOG(s) CLASS_EMERGENCY_LOG(program_wide_logger::get(), s)
52 const int CHECK_INTERVAL = 4 * SECOND_ms;
53 // this is how frequently the checking thread executes to ensure that
54 // processes are gone when they should be.
56 const int GRACEFUL_SLACK = 90 * SECOND_ms;
57 // the length of time before a graceful shutdown devolves into a forced
60 const int MAXIMUM_INITIAL_APP_WAIT = 4 * SECOND_ms;
61 // this is the longest we bother to wait for a process we just started.
62 // if it hasn't begun by then, we decide it will never do so.
64 const int STARTUP_APPS_DELAY_PERIOD = 2 * SECOND_ms;
65 // we delay for this long before the initial apps are started.
67 const int MAXIMUM_REQUEST_PAUSE = 42 * SECOND_ms;
68 // the longest we will ever wait for a response to be generated based on
71 // these are concurrency control macros for the lists managed here.
72 #define LOCK_CONFIG auto_synchronizer l(*_config_lock)
73 #define LOCK_ZOMBIES auto_synchronizer l(*_zombie_lock)
74 #define LOCK_KIDS auto_synchronizer l(*_scamp_lock)
77 #ifdef DEBUG_PROCESS_MANAGER
78 #define COMPLAIN_APPLICATION \
79 LOG(astring("the application called ") + app_name + " could not be found.")
80 #define COMPLAIN_PRODUCT \
81 LOG(astring("the section for ") + product + " could not be found.")
83 #define COMPLAIN_APPLICATION {}
84 #define COMPLAIN_PRODUCT {}
89 class launch_manager_thread : public ethread
92 launch_manager_thread(launch_manager &parent)
93 : ethread(CHECK_INTERVAL, ethread::SLACK_INTERVAL),
95 virtual ~launch_manager_thread() {}
97 virtual void perform_activity(void *)
98 { _parent.push_timed_activities(_processes); }
101 launch_manager &_parent; // the owner of the object.
102 process_entry_array _processes; // will be filled as needed.
107 class graceful_record
110 astring _product; // the product name the app is listed under.
111 astring _app_name; // the application's name.
112 time_stamp _started; // when the graceful shutdown started.
113 int _pid; // the particular process id for this app.
114 int _level; // the shutdown ordering specifier.
116 graceful_record(int pid = 0, const astring &product = "",
117 const astring &app_name = "", int level = 0)
118 : _product(product), _app_name(app_name), _pid(pid), _level(level) {}
121 class graceful_array : public array<graceful_record> {};
125 launch_manager::launch_manager(configured_applications &config)
127 _started_initial_apps(false),
128 _checker(new launch_manager_thread(*this)),
129 _config_lock(new mutex),
130 _going_down(new graceful_array),
131 _zombie_lock(new mutex),
132 _our_kids(new graceful_array),
133 _scamp_lock(new mutex),
134 _stop_launching(false),
135 _startup_time(new time_stamp(STARTUP_APPS_DELAY_PERIOD)),
136 _procs(new process_control),
137 _gag_exclusions(new string_set),
138 _tracking_exclusions(new string_set)
140 FUNCDEF("constructor");
142 // start the application checking thread.
143 _checker->start(NULL_POINTER);
145 _checker->reschedule(200); // make it start pretty quickly.
148 launch_manager::~launch_manager()
150 FUNCDEF("destructor");
159 WHACK(_startup_time);
161 WHACK(_gag_exclusions);
162 WHACK(_tracking_exclusions);
163 LOG("launch_manager is now stopped.");
166 void launch_manager::add_gag_exclusion(const astring &exclusion)
167 { *_gag_exclusions += exclusion; }
169 void launch_manager::add_tracking_exclusion(const astring &exclusion)
170 { *_tracking_exclusions += exclusion; }
172 const char *launch_manager::outcome_name(const outcome &to_name)
174 switch (to_name.value()) {
175 case FILE_NOT_FOUND: return "FILE_NOT_FOUND";
176 case NO_ANCHOR: return "NO_ANCHOR";
177 case NO_PRODUCT: return "NO_PRODUCT";
178 case NO_APPLICATION: return "NO_APPLICATION";
179 case BAD_PROGRAM: return "BAD_PROGRAM";
180 case LAUNCH_FAILED: return "LAUNCH_FAILED";
181 case NOT_RUNNING: return "NOT_RUNNING";
182 case FROZEN: return "FROZEN";
183 default: return common::outcome_name(to_name);
187 void launch_manager::stop_everything()
189 _stop_launching = true; // at least deny any connected clients.
190 stop_all_kids(); // shut down all programs that we started.
191 _checker->stop(); // stop our thread.
194 void launch_manager::stop_all_kids()
196 FUNCDEF("stop_all_kids");
197 _stop_launching = true; // set this for good measure to keep clients out.
198 LOG("zapping any active sub-processes prior to exit.");
200 // now we wait for the process closures to take effect. we are relying on
201 // the graceful shutdown devolving to a process zap and the timing is
202 // rooted around that assumption.
204 for (int lev = 100; lev >= 0; lev--) {
205 // loop from our highest level to our lowest for the shutdown.
206 #ifdef DEBUG_PROCESS_MANAGER
207 LOG(a_sprintf("level %d", lev));
209 bool zapped_any = false;
211 // this shuts down all the child processes we've started at this level.
212 LOCK_KIDS; // lock within this scope.
213 for (int i = _our_kids->length() - 1; i >= 0; i--) {
214 // now check each record and see if it's at the appropriate level.
215 graceful_record &grace = (*_our_kids)[i];
216 if (lev == grace._level) {
217 // start a graceful shutdown.
218 zap_process(grace._product, grace._app_name, true);
219 // remove it from our list.
220 _our_kids->zap(i, i);
221 zapped_any = true; // set our flag.
225 int num_dying = 1; // go into the loop once at least.
227 #ifdef DEBUG_PROCESS_MANAGER
228 time_stamp next_print(4 * SECOND_ms);
232 #ifdef DEBUG_PROCESS_MANAGER
233 if (time_stamp() >= next_print) {
235 next_print.reset(4 * SECOND_ms);
239 // while there are any pending process zaps, we will wait here. this
240 // will hose us but good if the processes aren't eventually cleared up,
241 // but that shouldn't happen.
245 num_dying = _going_down->length();
248 if (!num_dying) break; // jump out of loop.
250 _checker->reschedule(0); // make thread check as soon as possible.
252 time_control::sleep_ms(40);
254 #ifdef DEBUG_PROCESS_MANAGER
255 LOG("done waiting...");
260 void launch_manager::launch_startup_apps()
262 FUNCDEF("launch_startup_apps");
264 // read the startup section.
265 string_table startup_info;
268 if (!_configs.find_section(_configs.STARTUP_SECTION(), startup_info)) {
269 // if there's no startup section, we do nothing right now.
270 LOG("the startup section was not found!");
274 #ifdef DEBUG_PROCESS_MANAGER
275 LOG(astring("table has: ") + startup_info.text_form());
278 for (int i = 0; i < startup_info.symbols(); i++) {
279 astring app = startup_info.name(i);
280 if (app.equal_to(configured_applications::STARTUP_APP_NAME())) continue;
281 // skip bogus name that keeps the section present.
282 astring info = startup_info[i];
283 LOG(astring("launching application ") + app + "...");
284 // parse the items that are in the entry for this program.
285 astring product, parms;
287 if (!configured_applications::parse_startup_entry(info, product, parms,
289 LOG("the startup entry was not malformed; we could not parse it!");
293 LOG(app + a_sprintf(" is %ssingle shot.", one_shot? "" : "not "));
295 // now try to send the program off on its way.
296 launch_now(product, app, parms);
299 // it's only supposed to be started once, so now toss out the entry.
300 remove_from_startup(product, app);
305 outcome launch_manager::launch_now(const astring &product,
306 const astring &app_name, const astring ¶meters)
308 FUNCDEF("launch_now");
309 LOG(astring("product \"") + product + "\", application \"" + app_name
310 + (parameters.length() ? astring("\"")
311 : astring("\", parms=") + parameters));
313 if (_stop_launching) {
314 // if the application is one of the exceptions to the gag rule, then
315 // we will still launch it. otherwise, we'll ignore this request.
316 if (_gag_exclusions->member(app_name)) {
317 // this is one of the apps that can still be launched when gagged.
319 LOG(astring("application \"") + app_name + "\" cannot be launched;"
320 + parser_bits::platform_eol_to_chars() + "launching services have been "
322 return LAUNCH_FAILED;
331 // get the specific entry for the program they want.
332 entry_found = _configs.find_program(product, app_name, level);
334 if (!_configs.product_exists(product)) {
335 // return more specific error for missing product.
339 COMPLAIN_APPLICATION;
340 return NO_APPLICATION;
344 filename existence_check(entry_found);
345 if (!existence_check.exists()) {
346 LOG(astring("file or path wasn't found for ") + entry_found + ".");
347 return FILE_NOT_FOUND;
350 basis::un_int kid = 0;
351 int ret = launch_process::run(entry_found, parameters,
352 launch_process::RETURN_IMMEDIATELY | launch_process::HIDE_APP_WINDOW, kid);
354 // hey, it worked! so now make sure we track its lifetime...
356 if (_tracking_exclusions->member(app_name)) {
357 // this is one of the apps that we don't track. if it's still
358 // running when we're shutting down, it's not our problem.
363 // we were told the specific id for the process we started.
365 LOG(a_sprintf("adding given process id %d for app %s at level %d.",
366 kid, app_name.s(), level));
367 graceful_record to_add(kid, product, app_name, level);
368 *_our_kids += to_add;
371 #ifdef DEBUG_PROCESS_MANAGER
372 LOG("was not told child process id!!!");
374 // we weren't told the process id; let's see if we can search for it.
376 time_stamp give_it_up(MAXIMUM_INITIAL_APP_WAIT);
377 while (give_it_up > time_stamp()) {
378 // find the process id for the program we just started.
379 if (find_process(app_name, pids)) break;
380 time_control::sleep_ms(10); // pause to see if we can find it yet.
383 if (time_stamp() >= give_it_up) {
384 // we could not launch it for some reason, or our querier has failed.
385 // however, as far as we know, it launched successfully. if the id is
386 // missing, then that's not really our fault. we will just not be able
387 // to track the program, possibly because it's already gone.
388 LOG(astring("no process found for product \"") + product
389 + "\", application \"" + app_name + "\"; not adding "
396 // add all the processes we found under that name.
397 for (int i = 0; i < pids.elements(); i++) {
398 LOG(a_sprintf("adding process id %d for app %s at level %d.",
399 pids[i], app_name.s(), level));
400 graceful_record to_add(pids[i], product, app_name, level);
401 *_our_kids += to_add;
406 // if we reached here, things are not good. we must not have been able to
407 // start the process.
410 if (ret == NO_ERROR) return OKAY; // how would that happen?
411 else if ( (ret == ERROR_FILE_NOT_FOUND) || (ret == ERROR_PATH_NOT_FOUND) ) {
412 LOG(astring("file or path wasn't found for ") + app_name + ".");
413 return FILE_NOT_FOUND;
414 } else if (ret == ERROR_BAD_FORMAT) {
415 LOG(astring(app_name) + " was not in EXE format.");
418 LOG(astring("there was an unknown error while trying to run ")
419 + app_name + "; the error is:" + parser_bits::platform_eol_to_chars()
420 + critical_events::system_error_text(ret));
421 return LAUNCH_FAILED;
424 LOG(astring("error ") + critical_events::system_error_text(ret)
425 + " occurred attempting to run: " + app_name + " " + parameters);
426 return FILE_NOT_FOUND;
430 outcome launch_manager::launch_at_startup(const astring &product,
431 const astring &app_name, const astring ¶meters, int one_shot)
433 FUNCDEF("launch_at_startup");
434 LOCK_CONFIG; // this whole function modifies the config file.
436 #ifdef DEBUG_PROCESS_MANAGER
437 LOG(astring("product \"") + product + "\", application \"" + app_name
438 + (one_shot? astring("\", OneShot") : astring("\", MultiUse")));
441 // get the specific entry for the program they want.
443 astring entry_found = _configs.find_program(product, app_name, level);
445 if (!_configs.product_exists(product)) {
446 // return more specific error for missing product.
450 COMPLAIN_APPLICATION;
451 return NO_APPLICATION;
454 if (!_configs.add_startup_entry(product, app_name, parameters, one_shot)) {
455 // most likely problem is that it was already there.
462 outcome launch_manager::remove_from_startup(const astring &product,
463 const astring &app_name)
465 FUNCDEF("remove_from_startup");
466 LOCK_CONFIG; // this whole function modifies the config file.
468 #ifdef DEBUG_PROCESS_MANAGER
469 LOG(astring("product \"") + product + "\", application \"" + app_name + "\"");
472 // get the specific entry for the program they want.
474 astring entry_found = _configs.find_program(product, app_name, level);
476 if (!_configs.product_exists(product)) {
477 // return more specific error for missing product.
481 COMPLAIN_APPLICATION;
482 return NO_APPLICATION;
484 //hmmm: is product required for this for real?
486 if (!_configs.remove_startup_entry(product, app_name)) {
487 // the entry couldn't be removed, probably doesn't exist.
488 return NO_APPLICATION;
494 outcome launch_manager::query_application(const astring &product,
495 const astring &app_name)
497 FUNCDEF("query_application");
498 #ifdef DEBUG_PROCESS_MANAGER
499 LOG(astring("product \"") + product + "\", application \"" + app_name + "\"");
504 // get the specific entry for the program they want.
506 astring entry_found = _configs.find_program(product, app_name, level);
508 if (!_configs.product_exists(product)) {
509 // return more specific error for missing product.
513 COMPLAIN_APPLICATION;
514 return NO_APPLICATION;
518 // now seek that program name in the current list of processes.
519 astring program_name(app_name);
520 program_name.to_lower();
523 if (!find_process(app_name, pids))
528 outcome launch_manager::start_graceful_close(const astring &product,
529 const astring &app_name)
531 FUNCDEF("start_graceful_close");
532 //hmmm: record this app as one we need to watch.
535 // find that program name to make sure it's not already shutting down.
538 for (int i = _going_down->length() - 1; i >= 0; i--) {
539 graceful_record &grace = (*_going_down)[i];
540 if (grace._app_name.iequals(app_name)) {
546 if (!hoople_service::close_application(app_name))
550 if (!find_process(app_name, pids)) {
551 LOG(astring("Failed to find process id for [") + app_name
552 + astring("], assuming it has already exited."));
557 // add all the process ids, just in case there were multiple instances
558 // of the application somehow.
560 for (int i = 0; i < pids.elements(); i++) {
561 graceful_record to_add(pids[i], product, app_name);
562 *_going_down += to_add;
569 bool launch_manager::get_processes(process_entry_array &processes)
571 FUNCDEF("get_processes");
572 if (!_procs->query_processes(processes)) {
573 LOG("failed to query processes!");
579 bool launch_manager::find_process(const astring &app_name_in, int_set &pids)
581 FUNCDEF("find_process");
583 process_entry_array processes;
584 if (!get_processes(processes)) return false;
585 return process_control::find_process_in_list(processes, app_name_in, pids);
588 outcome launch_manager::zap_process(const astring &product,
589 const astring &app_name_key, bool graceful)
591 FUNCDEF("zap_process");
593 #ifdef DEBUG_PROCESS_MANAGER
594 LOG(astring("product \"") + product + "\", application \"" + app_name_key
595 + (graceful? "\", Graceful Close" : "\", Brutal Close"));
598 if (_tracking_exclusions->member(app_name_key)) {
599 // the non-tracked applications are never reported as running since they're
600 // not allowed to be zapped anyhow.
604 // use the real program name from here onward.
609 // get the specific entry for the program they want.
611 app_name = _configs.find_program(product, app_name_key, level);
613 if (!_configs.product_exists(product)) {
614 // return more specific error for missing product.
618 COMPLAIN_APPLICATION;
619 return NO_APPLICATION;
621 // truncate the directory and just use the base.
622 app_name = filename(app_name).basename();
625 // if they want a graceful close, start by trying that. if it appears to
626 // have succeeded, then we exit and let the thread take care of ensuring
627 // the app goes down.
628 outcome to_return = NOT_RUNNING;
630 to_return = start_graceful_close(product, app_name);
631 if (to_return == OKAY)
632 return OKAY; // maybe finished the close.
634 // they want a harsh close of this application or the graceful close failed.
635 astring program_name(app_name);
637 if (!find_process(program_name, pids)) {
638 #ifdef DEBUG_PROCESS_MANAGER
639 LOG(program_name + " process was not running.")
644 // search for the application in the process list.
646 for (int i = 0; i < pids.elements(); i++) {
647 bool ret = _procs->zap_process(pids[i]);
649 LOG(astring(astring::SPRINTF, "Killed process %d [",
650 pids[i]) + program_name + astring("]"));
652 LOG(astring(astring::SPRINTF, "Failed to zap process %d [",
653 pids[i]) + program_name + astring("]"));
655 if (!ret) failed = true;
657 // kind of a bizarre return, but whatever. it really should be some
658 // failure result since the zap failed.
659 return failed? ACCESS_DENIED : OKAY;
662 outcome launch_manager::shut_down_launching_services(const astring &secret_word)
664 FUNCDEF("shut_down_launching_services");
665 LOG("checking secret word...");
666 //hmmm: changing the secret word here.
667 if (secret_word.equal_to("MarblesAreRoundishYo")) {
668 LOG("it's correct; ending launch capabilities.");
670 LOG("the secret word is wrong. continuing normal operation.");
673 _stop_launching = true;
677 outcome launch_manager::reenable_launching_services(const astring &secret_word)
679 FUNCDEF("reenable_launching_services");
680 LOG("checking secret word...");
681 if (secret_word.equal_to("MarblesAreRoundishYo")) {
682 LOG("it's correct; resuming launch capabilities.");
684 LOG("the secret word is wrong. continuing with prior mode.");
687 _stop_launching = false;
691 #define GET_PROCESSES \
692 if (!retrieved_processes) { \
693 retrieved_processes = true; \
694 if (!get_processes(processes)) { \
695 LOG("failed to retrieve process list from OS!"); \
696 return; /* badness. */ \
700 void launch_manager::push_timed_activities(process_entry_array &processes)
702 FUNCDEF("push_timed_activities");
704 // make sure we started the applications that were slated for execution at
705 // system startup time. we wait on this until the first thread activation
706 // to give other processes some breathing room right at startup time.
707 // also, it then doesn't block the main service thread.
708 if (!_started_initial_apps && (*_startup_time <= time_stamp()) ) {
709 // launch all the apps that are listed for system startup.
710 LOG("starting up the tasks registered for system initiation.");
711 launch_startup_apps();
712 _started_initial_apps = true;
715 if (!_started_initial_apps) {
716 _checker->reschedule(200);
717 // keep hitting this function until we know we can relax since the
718 // startup apps have been sent off.
721 bool retrieved_processes = false;
724 // loop over the death records we've got and check on the soon to be gone.
727 for (int i = _going_down->length() - 1; i >= 0; i--) {
728 graceful_record &grace = (*_going_down)[i];
730 GET_PROCESSES; // load them if they hadn't been.
733 if (!process_control::find_process_in_list(processes, grace._app_name,
735 // the app can't be found as running, so whack the record for it.
736 #ifdef DEBUG_PROCESS_MANAGER
737 LOG(astring("cannot find app ") + grace._app_name
738 + " as running still; removing its dying record");
740 _going_down->zap(i, i);
743 if (!pids.member(grace._pid)) {
744 // that particular instance exited on its own, so whack the record.
745 #ifdef DEBUG_PROCESS_MANAGER
746 LOG(astring("app ") + grace._app_name
747 + " exited on its own; removing its dying record");
749 _going_down->zap(i, i);
753 if (grace._started <= time_stamp(-GRACEFUL_SLACK)) {
755 LOG(astring("Application ") + grace._app_name + " is unresponsive to "
756 "the graceful shutdown request. Now zapping it instead.");
757 if (!_procs->zap_process(grace._pid))
758 LOG("Devolved graceful shutdown failed as zap_process also.");
759 _going_down->zap(i, i);
763 // it's not time to dump this one yet, so keep looking at others.
768 // now loop over the list of our active kids and make sure that they are
769 // all still running. if they aren't, then toss the record out.
772 for (int i = _our_kids->length() - 1; i >= 0; i--) {
773 graceful_record &grace = (*_our_kids)[i];
775 GET_PROCESSES; // load them if they hadn't been.
778 if (!process_control::find_process_in_list(processes, grace._app_name,
780 // the app can't be found as running, so whack the record for it.
781 #ifdef DEBUG_PROCESS_MANAGER
782 LOG(astring("cannot find kid ") + grace._app_name
783 + " as still running; removing its normal record");
785 _our_kids->zap(i, i);
788 if (!pids.member(grace._pid)) {
789 // that particular instance exited on its own, so whack the record.
790 #ifdef DEBUG_PROCESS_MANAGER
791 LOG(astring("kid ") + grace._app_name
792 + " exited on its own; removing its normal record");
794 _our_kids->zap(i, i);
798 // this kid is still going, so keep its record.