process_control.cpp

Go to the documentation of this file.
00001 
00002 //NOTE:
00003 //
00004 //  this thing is showing bad behavior on win32 when unicode is enabled.
00005 //  therefore unicode is currently disabled for win32, which is a shame.
00006 //  but something needs to be fixed in our unicode conversion stuff; the unicode versions
00007 //  of the file names were not getting correctly back-converted into the ascii counterpart,
00008 //  and *that* is broken.
00009 //  ** this may be a widespread issue in the win32 code related to unicode right now!
00010 //  ** needs further investigation when a reason to better support ms-windows emerges.
00011 //
00012 //  => but don't panic...  right now things work as long as you do a utf-8 / ascii build,
00013 //     which is how everything's configured.
00014 
00015 
00016 /*
00017 *  Name   : process_control
00018 *  Author : Chris Koeritz
00019 **
00020 * Copyright (c) 2000-$now By Author.  This program is free software; you can  *
00021 * redistribute it and/or modify it under the terms of the GNU General Public  *
00022 * License as published by the Free Software Foundation; either version 2 of   *
00023 * the License or (at your option) any later version.  This is online at:      *
00024 *     http://www.fsf.org/copyleft/gpl.html                                    *
00025 * Please send any updates to: fred@gruntose.com                               *
00026 */
00027 
00028 #include "process_entry.h"
00029 #include "process_control.h"
00030 
00031 #include <application/windoze_helper.h>
00032 #include <basis/astring.h>
00033 #include <basis/utf_conversion.h>
00034 #include <configuration/application_configuration.h>
00035 #include <filesystem/filename.h>
00036 #include <loggers/program_wide_logger.h>
00037 #include <loggers/standard_log_base.h>
00038 #include <mathematics/chaos.h>
00039 #include <structures/set.h>
00040 #include <structures/version_record.h>
00041 
00042 #include <stdio.h>
00043 #include <stdlib.h>
00044 #ifdef __UNIX__
00045   #include <unistd.h>
00046 #endif
00047 
00048 using namespace basis;
00049 using namespace configuration;
00050 using namespace filesystem;
00051 using namespace loggers;
00052 using namespace mathematics;
00053 using namespace structures;
00054 
00055 namespace processes {
00056 
00057 #ifdef __WIN32__
00058   #include <tlhelp32.h>
00059   const astring NTVDM_NAME = "ntvdm.exe";
00060     // the umbrella process that hangs onto 16 bit tasks for NT.
00061   #ifdef _MSCVER
00062     #include <vdmdbg.h>
00063   #endif
00064 #endif
00065 #ifdef __UNIX__
00066   #include <signal.h>
00067   #include <stdio.h>
00068 #endif
00069 
00070 //#define DEBUG_PROCESS_CONTROL
00071   // uncomment for noisier debugging.
00072 
00073 #undef LOG
00074 #define LOG(s) CLASS_EMERGENCY_LOG(program_wide_logger::get(), s)
00075 
00077 
00078 class process_implementation_hider
00079 {
00080 public:
00081 #ifdef __WIN32__
00082   // psapi members:
00083   application_instance psapi_dll;
00084   application_instance vdm_dll;
00085   BOOL (WINAPI *enumerate_processes)(basis::un_int *, basis::un_int cb, basis::un_int *);
00086   BOOL (WINAPI *enumerate_modules)(HANDLE, HMODULE *, basis::un_int, basis::un_int *);
00087   basis::un_int (WINAPI *get_module_name)(HANDLE, HMODULE, LPTSTR, basis::un_int);
00088 #ifdef _MSCVER
00089   INT (WINAPI *tasker_16bit)(basis::un_int, TASKENUMPROCEX  fp, LPARAM);
00090 #endif
00091 
00092   // toolhelp members:
00093   application_instance kernel32_dll;
00094   HANDLE (WINAPI *create_snapshot)(basis::un_int,basis::un_int);
00095   BOOL (WINAPI *first_process)(HANDLE,LPPROCESSENTRY32);
00096   BOOL (WINAPI *next_process)(HANDLE,LPPROCESSENTRY32);
00097 
00098   // get an atomic view of the process list, which rapidly becomes out of date.
00100 
00101   process_implementation_hider()
00102     : psapi_dll(NIL), vdm_dll(NIL), enumerate_processes(NIL),
00103       enumerate_modules(NIL), get_module_name(NIL),
00104 #ifdef _MSCVER
00105       tasker_16bit(NIL),
00106 #endif
00107       kernel32_dll(NIL), create_snapshot(NIL), first_process(NIL),
00108       next_process(NIL) {}
00109 
00110   ~process_implementation_hider() {
00111     if (psapi_dll) FreeLibrary(psapi_dll);
00112     if (vdm_dll) FreeLibrary(vdm_dll);
00113     if (kernel32_dll) FreeLibrary(kernel32_dll);
00114     psapi_dll = NIL;
00115     vdm_dll = NIL;
00116     kernel32_dll = NIL;
00117   }
00118 #endif
00119 };
00120 
00122 
00123 class process_info_clump
00124 {
00125 public:
00126   basis::un_int _process_id;
00127   process_entry_array &_to_fill;  // where to add entries.
00128 
00129   process_info_clump(basis::un_int id, process_entry_array &to_fill)
00130       : _process_id(id), _to_fill(to_fill) {}
00131 };
00132 
00134 
00135 // top-level functions...
00136 
00137 process_control::process_control()
00138 : _ptrs(new process_implementation_hider),
00139 #ifdef __WIN32__
00140   _use_psapi(true),
00141 #endif
00142 #ifdef __UNIX__
00143   _rando(new chaos),
00144 #endif
00145   _healthy(false)
00146 {
00147   // Check to see if were running under Windows95 or Windows NT.
00148   version osver = application_configuration::get_OS_version();
00149 
00150 #ifdef __WIN32__
00151   if (osver.v_revision() == VER_PLATFORM_WIN32_WINDOWS) {
00152     // we're on Windows 95, so use the toolhelp API for the processes.
00153     _use_psapi = false;
00154   } else if (osver.v_major() >= 5) {
00155     // w2k and onward can do the toolhelp instead of psapi.
00156     _use_psapi = false;
00157   }
00158   if (_use_psapi)
00159     _healthy = initialize_psapi_support();
00160   else
00161     _healthy = initialize_toolhelp_support();
00162 #endif
00163 #ifdef __UNIX__
00164   _healthy = true;
00165 #endif
00166 }
00167 
00168 process_control::~process_control()
00169 {
00170   WHACK(_ptrs);
00171 #ifdef __UNIX__
00172   WHACK(_rando);
00173 #endif
00174 }
00175 
00176 void process_control::sort_by_name(process_entry_array &v)
00177 {
00178   process_entry temp;
00179   for (int gap = v.length() / 2; gap > 0; gap /= 2)
00180     for (int i = gap; i < v.length(); i++)
00181       for (int j = i - gap; j >= 0
00182           && (filename(v[j].path()).basename().raw()
00183              > filename(v[j + gap].path()).basename().raw());
00184           j = j - gap)
00185       { temp = v[j]; v[j] = v[j + gap]; v[j + gap] = temp; }
00186 }
00187 
00188 void process_control::sort_by_pid(process_entry_array &v)
00189 {
00190   process_entry temp;
00191   for (int gap = v.length() / 2; gap > 0; gap /= 2)
00192     for (int i = gap; i < v.length(); i++)
00193       for (int j = i - gap; j >= 0 && (v[j]._process_id
00194           > v[j + gap]._process_id); j = j - gap)
00195       { temp = v[j]; v[j] = v[j + gap]; v[j + gap] = temp; }
00196 }
00197 
00198 bool process_control::query_processes(process_entry_array &to_fill)
00199 {
00200   if (!_healthy) return false;
00201 #ifdef __WIN32__
00202   if (!_use_psapi) {
00203     // we're on Windows 95 or something, so use the toolhelp API for the
00204     // processes.
00205     return get_processes_with_toolhelp(to_fill);
00206   } else {
00207     // we're on Windows NT and so on; use the process API (PSAPI) to get info.
00208     return get_processes_with_psapi(to_fill);
00209   }
00210 #endif
00211 #ifdef __UNIX__
00212   return get_processes_with_ps(to_fill);
00213 #endif
00214 }
00215 
00216 #ifdef __WIN32__
00217 bool process_control::initialize_psapi_support()
00218 {
00219   // create an instance of the PSAPI dll for querying 32-bit processes and
00220   // an instance of the VDM dll support just in case there are also some
00221   // 16-bit processes.
00222   _ptrs->psapi_dll = LoadLibraryA("psapi.dll");
00223   if (!_ptrs->psapi_dll) return false; 
00224   _ptrs->vdm_dll = LoadLibraryA("vdmdbg.dll");
00225   if (!_ptrs->vdm_dll) return false;
00226 
00227   // locate the functions that we want to call.
00228   _ptrs->enumerate_processes = (BOOL(WINAPI *)(basis::un_int *,basis::un_int,basis::un_int*))
00229         GetProcAddress(_ptrs->psapi_dll, "EnumProcesses");
00230   _ptrs->enumerate_modules
00231       = (BOOL(WINAPI *)(HANDLE, HMODULE *, basis::un_int, basis::un_int *))
00232         GetProcAddress(_ptrs->psapi_dll, "EnumProcessModules");
00233   _ptrs->get_module_name
00234       = (basis::un_int (WINAPI *)(HANDLE, HMODULE, LPTSTR, basis::un_int))
00235         GetProcAddress(_ptrs->psapi_dll, "GetModuleFileNameExA");
00236 #ifdef _MSCVER
00237   _ptrs->tasker_16bit = (INT(WINAPI *)(basis::un_int, TASKENUMPROCEX, LPARAM))
00238         GetProcAddress(_ptrs->vdm_dll, "VDMEnumTaskWOWEx");
00239 #endif
00240   if (!_ptrs->enumerate_processes || !_ptrs->enumerate_modules
00241       || !_ptrs->get_module_name
00242 #ifdef _MSCVER
00243       || !_ptrs->tasker_16bit
00244 #endif
00245       ) return false;
00246 
00247   return true;
00248 }
00249 
00250 bool process_control::initialize_toolhelp_support()
00251 {
00252   // get hooked up with the kernel dll so we can use toolhelp functions.
00253   _ptrs->kernel32_dll = LoadLibraryA("Kernel32.DLL");
00254   if (!_ptrs->kernel32_dll) return false;
00255 
00256   // create pointers to the functions we want to invoke.
00257   _ptrs->create_snapshot = (HANDLE(WINAPI *)(basis::un_int,basis::un_int))
00258         GetProcAddress(_ptrs->kernel32_dll, "CreateToolhelp32Snapshot");
00259   _ptrs->first_process = (BOOL(WINAPI *)(HANDLE,LPPROCESSENTRY32))
00260         GetProcAddress(_ptrs->kernel32_dll, "Process32First");
00261   _ptrs->next_process = (BOOL(WINAPI *)(HANDLE,LPPROCESSENTRY32))
00262         GetProcAddress(_ptrs->kernel32_dll, "Process32Next");
00263   if (!_ptrs->next_process || !_ptrs->first_process
00264       || !_ptrs->create_snapshot) return false;
00265   return true;
00266 }
00267 
00268 #endif
00269 
00270 bool process_control::zap_process(basis::un_int to_zap)
00271 {
00272 #ifdef DEBUG_PROCESS_CONTROL
00273   FUNCDEF("zap_process");
00274 #endif
00275   if (!_healthy) return false;
00276 #ifdef __UNIX__
00277   int ret = kill(to_zap, 9);
00278     // send the serious take-down signal to the process.
00279   return !ret;
00280 #endif
00281 #ifdef __WIN32__
00282   HANDLE h = OpenProcess(PROCESS_TERMINATE, false, to_zap);
00283   if (!h) {
00284 #ifdef DEBUG_PROCESS_CONTROL
00285     int err = critical_events::system_error();
00286     LOG(a_sprintf("error zapping process %d=", to_zap)
00287         + critical_events::system_error_text(err));
00288 #endif
00289     return false;
00290   }
00291   int exit_code = 0;
00292   BOOL ret = TerminateProcess(h, exit_code);
00293   CloseHandle(h);
00294   return !!ret;
00295 #endif
00296 }
00297 
00298 process_entry process_control::query_process(basis::un_int to_query)
00299 {
00300 //  FUNCDEF("query_process");
00301   process_entry to_return;
00302 
00303   process_entry_array to_fill;
00304   bool got_em = query_processes(to_fill);
00305   if (!got_em) return to_return;
00306 
00307   for (int i = 0; i < to_fill.length(); i++) {
00308     if (to_fill[i]._process_id == to_query)
00309       return to_fill[i];
00310   }
00311 
00312 //hmmm: implement more specifically.
00313 #ifdef __UNIX__
00314 //put in the single process grabber deal.
00315 #endif
00316 #ifdef __WIN32__
00317 //grab the entry from the list.
00318 #endif
00319 
00320   return to_return;
00321 }
00322 
00323 bool process_control::find_process_in_list(const process_entry_array &processes,
00324     const astring &app_name_in, int_set &pids)
00325 {
00326 #ifdef DEBUG_PROCESS_CONTROL
00327   FUNCDEF("find_process_in_list");
00328 #endif
00329   pids.clear();
00330   astring app_name = app_name_in.lower();
00331 
00332   version os_ver = application_configuration::get_OS_version();
00333 
00334   bool compare_prefix = (os_ver.v_major() == 5) && (os_ver.v_minor() == 0);
00335     // we only compare the first 15 letters due to a recently noticed bizarre
00336     // bug where w2k only shows (and reports) the first 15 letters of file
00337     // names through toolhelp.
00338 
00339   bool found = false;  // was it seen in process list?
00340   for (int i = 0; i < processes.length(); i++) {
00341     filename path = processes[i].path();
00342     astring base = path.basename().raw().lower();
00343     // a kludge for w2k is needed--otherwise we will miss seeing names that
00344     // really are running due to the toolhelp api being busted and only
00345     // reporting the first 15 characters of the name.
00346     if ( (compare_prefix && (base.compare(app_name, 0, 0, 15, false)))
00347         || (base == app_name) ) {
00348       found = true;
00349       pids.add(processes[i]._process_id);
00350     }
00351   }
00352 #ifdef DEBUG_PROCESS_CONTROL
00353   if (!found)
00354     LOG(astring("failed to find the program called ") + app_name);
00355 #endif
00356   return found;
00357 }
00358 
00360 
00361 #ifdef __WIN32__
00362 // this section is the PSAPI version of the query.
00363 
00364 // called back on each 16 bit task.
00365 BOOL WINAPI process_16bit(basis::un_int dwThreadId, WORD module_handle16, WORD hTask16,
00366     PSZ pszModName, PSZ pszFileName, LPARAM lpUserDefined)
00367 {
00368   process_info_clump *to_stuff = (process_info_clump *)lpUserDefined;
00369   process_entry to_add;
00370   to_add._process_id = to_stuff->_process_id;
00371   to_add._module16 = hTask16;
00372   to_add.path(pszFileName);
00373 //threads, etc?
00374   to_stuff->_to_fill += to_add;
00375   return true;
00376 }
00377 
00378 bool process_control::get_processes_with_psapi(process_entry_array &to_fill)
00379 {
00380   // prepare the result object.
00381   to_fill.reset();
00382 
00383   // loop over the process enumeration function until we are sure that we
00384   // have allocated a large enough space for all existing processes.
00385   bool got_all = false;
00386   basis::un_int *pid_list = NIL;
00387   basis::un_int max_size = 428 * sizeof(basis::un_int);
00388   basis::un_int actual_size = 0;
00389   while (!got_all) {
00390     pid_list = (basis::un_int *)HeapAlloc(GetProcessHeap(), 0, max_size);
00391     if (!pid_list) return false;
00392     if (!_ptrs->enumerate_processes(pid_list, max_size, &actual_size)) {
00393       HeapFree(GetProcessHeap(), 0, pid_list);
00394       return false;
00395     }
00396     if (actual_size == max_size) {
00397       // there were too many to store, so whack the partial list.
00398       HeapFree(GetProcessHeap(), 0, pid_list);
00399       max_size *= 2;  // try with twice as much space.
00400     } else got_all = true;
00401   }
00402 
00403   // calculate the number of process ids that got stored.
00404   basis::un_int ids = actual_size / sizeof(basis::un_int);
00405 
00406   // examine each process id that we found.
00407   for (basis::un_int i = 0; i < ids; i++) {
00408     // get process information if security permits.
00409 //turn chunk below into "scan process" or something.
00410     HANDLE hProcess = OpenProcess(PROCESS_QUERY_INFORMATION | PROCESS_VM_READ,
00411         false, pid_list[i]);
00412     flexichar process_name[MAX_ABS_PATH + 1] = { '\0' };
00413     if (hProcess) {
00414       // go over the modules for the process.  the first will be the main
00415       // application module and the others will be threads.  ???
00416       basis::un_int max_size = 1 * sizeof(HMODULE);
00417 //hmmm: could do a rescan loop here if too many.
00418       basis::un_int actual_size = 0;
00419       HMODULE *module_handles = new HMODULE[max_size + 1];
00420       if (!module_handles) {
00421         CloseHandle(hProcess);
00422         HeapFree(GetProcessHeap(), 0, pid_list);
00423         return false;
00424       }
00425       if (_ptrs->enumerate_modules(hProcess, module_handles, max_size,
00426           &actual_size)) {
00427         // we want the name of the first module.
00428         if (!_ptrs->get_module_name(hProcess, *module_handles, process_name,
00429             sizeof(process_name)))
00430           process_name[0] = 0;
00431       }
00432       WHACK(module_handles);
00433       CloseHandle(hProcess);
00434     }
00435 
00436     // we add whatever information we were able to find about this process.
00437     process_entry new_entry;
00438     new_entry._process_id = pid_list[i];
00439     astring converted_name = from_unicode_temp(process_name);
00440     new_entry.path(converted_name);
00441 
00442 //how to get?  performance data helper?
00444     to_fill += new_entry;
00445 
00446     // if we're looking at ntvdm, then there might be 16 bit processes
00447     // attached to it.
00448     if (new_entry.path().length() >= NTVDM_NAME.length()) {
00449       astring temp = new_entry.path().substring
00450           (new_entry.path().end() - NTVDM_NAME.length() + 1,
00451           new_entry.path().end());
00452       temp.to_lower();
00453 #ifdef _MSCVER
00454 //hmmm: pull this back in for mingw when it seems to be supported, if ever.
00455       if (temp == NTVDM_NAME) {
00456         // set up a callback stampede on the 16 bit processes.
00457         process_info_clump info(pid_list[i], to_fill);
00458         _ptrs->tasker_16bit(pid_list[i], (TASKENUMPROCEX)process_16bit,
00459              (LPARAM)&info);
00460       }
00461 #endif
00462     }
00463   }
00464 
00465   if (pid_list) HeapFree(GetProcessHeap(), 0, pid_list);
00466   return true;
00467 }
00468 
00470 
00471 // this is the toolhelp version of the query.
00472 
00473 bool process_control::get_processes_with_toolhelp(process_entry_array &to_fill)
00474 {
00475   // prepare the result object.
00476   to_fill.reset();
00477 
00478   // get an atomic view of the process list, which rapidly becomes out of date.
00479   HANDLE hSnapShot;
00480   hSnapShot = _ptrs->create_snapshot(TH32CS_SNAPPROCESS, 0);
00481   if (hSnapShot == INVALID_HANDLE_VALUE) return false;
00482 
00483   // start iterating through the snapshot by getting the first process.
00484   PROCESSENTRY32 entry;
00485   entry.dwSize = sizeof(PROCESSENTRY32);
00486   BOOL keep_going = _ptrs->first_process(hSnapShot, &entry);
00487 
00488   // while we see valid processes, iterate through them.
00489   while (keep_going) {
00490     // add an entry for the current process.
00491     process_entry new_entry;
00492     new_entry._process_id = entry.th32ProcessID;
00493     new_entry._references = entry.cntUsage;
00494     new_entry._threads = entry.cntThreads;
00495     new_entry._parent_process_id = entry.th32ParentProcessID;
00496     astring exe_file = from_unicode_temp(entry.szExeFile);
00497     new_entry.path(exe_file);
00498     to_fill += new_entry;
00499     entry.dwSize = sizeof(PROCESSENTRY32);  // reset struct size.
00500     keep_going = _ptrs->next_process(hSnapShot, &entry);
00501   }
00502 
00503   CloseHandle(hSnapShot);
00504   return true;
00505 }
00506 #endif  // __WIN32__
00507 
00508 #ifdef __UNIX__
00509 
00510 #define CLOSE_TMP_FILE { \
00511 /*  continuable_error("process_control", "get_processes_with_ps", error); */ \
00512   if (output) { \
00513     fclose(output); \
00514     unlink(tmpfile.s()); \
00515   } \
00516 }
00517 
00518 bool process_control::get_processes_with_ps(process_entry_array &to_fill)
00519 {
00520   FUNCDEF("get_processes_with_ps");
00521   to_fill.reset();
00522   // we ask the operating system to give us a list of processes.
00523   a_sprintf tmpfile("/tmp/proc_list_%d_%d.txt", application_configuration::process_id(),
00524       _rando->inclusive(1, 400000));
00525   a_sprintf cmd("ps wax --format \"%%p %%a\" >%s", tmpfile.s());
00526 //hmmm: add more info as we expand the process entry.
00527   FILE *output = NIL;  // initialize now to establish variable for our macro.
00528   int sysret = system(cmd.s());
00529   if (negative(sysret)) {
00530 LOG("got negative return from system()!");
00531     CLOSE_TMP_FILE;
00532     return false;
00533   }
00534   output = fopen(tmpfile.s(), "r");
00535   if (!output) {
00536 LOG("failed to open process list file!");
00537     CLOSE_TMP_FILE;
00538     return false;
00539   }
00540   const int max_buff = 10000;
00541   char buff[max_buff];
00542   size_t size_read = 1;
00543   astring accumulator;
00544   while (size_read > 0) {
00545     // read bytes from the file.
00546     size_read = fread(buff, 1, max_buff, output);
00547     // if there was anything, store it in the string.
00548     if (size_read > 0)
00549       accumulator += astring(astring::UNTERMINATED, buff, size_read);
00550   }
00551   CLOSE_TMP_FILE;
00552   // parse the string up now.
00553   bool first_line = true;
00554   while (accumulator.length()) {
00555     // eat any spaces in front.
00556     if (first_line) {
00557       // we toss the first line since it's a header with columns.
00558       int cr_indy = accumulator.find('\n');
00559       accumulator.zap(0, cr_indy);
00560       if (accumulator[accumulator.end()] == '\r')
00561         accumulator.zap(accumulator.end(), accumulator.end());
00562       first_line = false;
00563       continue;
00564     }
00565     while (accumulator.length() && (accumulator[0] == ' '))
00566       accumulator.zap(0, 0);
00567     // look for the first part of the line; the process id.
00568     int num_indy = accumulator.find(' ');
00569     if (negative(num_indy)) break;
00570     basis::un_int p_id = accumulator.substring(0, num_indy).convert(0);
00571     accumulator.zap(0, num_indy);
00572     int cr_indy = accumulator.find('\n');
00573     if (negative(cr_indy))
00574       cr_indy = accumulator.end() + 1;
00575     astring proc_name = accumulator.substring(0, cr_indy - 1);
00576     if (proc_name[proc_name.end()] == '\r')
00577       proc_name.zap(proc_name.end(), proc_name.end());
00578     accumulator.zap(0, cr_indy);
00579     int space_indy = proc_name.find(' ');
00580 //hmmm: this is incorrect regarding names that do have spaces in them.
00581     if (negative(space_indy))
00582       space_indy = proc_name.end() + 1;
00583     process_entry to_add;
00584     to_add._process_id = p_id;
00585     astring path = proc_name.substring(0, space_indy - 1);
00586     // patch the pathname if we see any bracketed items.
00587     int brackets_in = 0;
00588     for (int i = 0; i < path.length(); i++) {
00589       if (path[i] == '[') brackets_in++;
00590       else if (path[i] == ']') brackets_in--;
00591       if (brackets_in) {
00592         // if we see a slash inside brackets, then we patch it so it doesn't
00593         // confuse the filename object's directory handling.
00594         if ( (path[i] == '/') || (path[i] == '\\') )
00595           path[i] = '#';
00596       }
00597     }
00598     to_add.path(path);
00599     to_fill += to_add;
00600   }
00601   return true;
00602 }
00603 #endif  // __UNIX__
00604 
00605 } //namespace.
00606 
Generated on Sat Jan 28 04:22:24 2012 for hoople2 project by  doxygen 1.6.3