process_control.cpp

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

Generated on Fri Sep 5 04:28:51 2008 for HOOPLE Libraries by  doxygen 1.5.1