4 // this thing is showing bad behavior on win32 when unicode is enabled.
5 // therefore unicode is currently disabled for win32, which is a shame.
6 // but something needs to be fixed in our unicode conversion stuff; the unicode versions
7 // of the file names were not getting correctly back-converted into the ascii counterpart,
8 // and *that* is broken.
9 // ** this may be a widespread issue in the win32 code related to unicode right now!
10 // ** needs further investigation when a reason to better support ms-windows emerges.
12 // => but don't panic... right now things work as long as you do a utf-8 / ascii build,
13 // which is how everything's configured.
17 * Name : process_control
18 * Author : Chris Koeritz
20 * Copyright (c) 2000-$now By Author. This program is free software; you can *
21 * redistribute it and/or modify it under the terms of the GNU General Public *
22 * License as published by the Free Software Foundation; either version 2 of *
23 * the License or (at your option) any later version. This is online at: *
24 * http://www.fsf.org/copyleft/gpl.html *
25 * Please send any updates to: fred@gruntose.com *
28 #include "process_entry.h"
29 #include "process_control.h"
31 #include <application/windoze_helper.h>
32 #include <basis/astring.h>
33 #include <basis/utf_conversion.h>
34 #include <configuration/application_configuration.h>
35 #include <filesystem/filename.h>
36 #include <loggers/program_wide_logger.h>
37 #include <loggers/standard_log_base.h>
38 #include <mathematics/chaos.h>
39 #include <structures/set.h>
40 #include <structures/version_record.h>
48 using namespace basis;
49 using namespace configuration;
50 using namespace filesystem;
51 using namespace loggers;
52 using namespace mathematics;
53 using namespace structures;
59 const astring NTVDM_NAME = "ntvdm.exe";
60 // the umbrella process that hangs onto 16 bit tasks for NT.
70 //#define DEBUG_PROCESS_CONTROL
71 // uncomment for noisier debugging.
74 #define LOG(s) CLASS_EMERGENCY_LOG(program_wide_logger::get(), s)
78 class process_implementation_hider
83 application_instance psapi_dll;
84 application_instance vdm_dll;
85 BOOL (WINAPI *enumerate_processes)(basis::un_int *, basis::un_int cb, basis::un_int *);
86 BOOL (WINAPI *enumerate_modules)(HANDLE, HMODULE *, basis::un_int, basis::un_int *);
87 basis::un_int (WINAPI *get_module_name)(HANDLE, HMODULE, LPTSTR, basis::un_int);
89 INT (WINAPI *tasker_16bit)(basis::un_int, TASKENUMPROCEX fp, LPARAM);
93 application_instance kernel32_dll;
94 HANDLE (WINAPI *create_snapshot)(basis::un_int,basis::un_int);
95 BOOL (WINAPI *first_process)(HANDLE,LPPROCESSENTRY32);
96 BOOL (WINAPI *next_process)(HANDLE,LPPROCESSENTRY32);
98 // get an atomic view of the process list, which rapidly becomes out of date.
101 process_implementation_hider()
102 : psapi_dll(NIL), vdm_dll(NIL), enumerate_processes(NIL),
103 enumerate_modules(NIL), get_module_name(NIL),
107 kernel32_dll(NIL), create_snapshot(NIL), first_process(NIL),
110 ~process_implementation_hider() {
111 if (psapi_dll) FreeLibrary(psapi_dll);
112 if (vdm_dll) FreeLibrary(vdm_dll);
113 if (kernel32_dll) FreeLibrary(kernel32_dll);
123 class process_info_clump
126 basis::un_int _process_id;
127 process_entry_array &_to_fill; // where to add entries.
129 process_info_clump(basis::un_int id, process_entry_array &to_fill)
130 : _process_id(id), _to_fill(to_fill) {}
135 // top-level functions...
137 process_control::process_control()
138 : _ptrs(new process_implementation_hider),
147 // Check to see if were running under Windows95 or Windows NT.
148 version osver = application_configuration::get_OS_version();
151 if (osver.v_revision() == VER_PLATFORM_WIN32_WINDOWS) {
152 // we're on Windows 95, so use the toolhelp API for the processes.
154 } else if (osver.v_major() >= 5) {
155 // w2k and onward can do the toolhelp instead of psapi.
159 _healthy = initialize_psapi_support();
161 _healthy = initialize_toolhelp_support();
168 process_control::~process_control()
176 void process_control::sort_by_name(process_entry_array &v)
179 for (int gap = v.length() / 2; gap > 0; gap /= 2)
180 for (int i = gap; i < v.length(); i++)
181 for (int j = i - gap; j >= 0
182 && (filename(v[j].path()).basename().raw()
183 > filename(v[j + gap].path()).basename().raw());
185 { temp = v[j]; v[j] = v[j + gap]; v[j + gap] = temp; }
188 void process_control::sort_by_pid(process_entry_array &v)
191 for (int gap = v.length() / 2; gap > 0; gap /= 2)
192 for (int i = gap; i < v.length(); i++)
193 for (int j = i - gap; j >= 0 && (v[j]._process_id
194 > v[j + gap]._process_id); j = j - gap)
195 { temp = v[j]; v[j] = v[j + gap]; v[j + gap] = temp; }
198 bool process_control::query_processes(process_entry_array &to_fill)
200 if (!_healthy) return false;
203 // we're on Windows 95 or something, so use the toolhelp API for the
205 return get_processes_with_toolhelp(to_fill);
207 // we're on Windows NT and so on; use the process API (PSAPI) to get info.
208 return get_processes_with_psapi(to_fill);
212 return get_processes_with_ps(to_fill);
217 bool process_control::initialize_psapi_support()
219 // create an instance of the PSAPI dll for querying 32-bit processes and
220 // an instance of the VDM dll support just in case there are also some
222 _ptrs->psapi_dll = LoadLibraryA("psapi.dll");
223 if (!_ptrs->psapi_dll) return false;
224 _ptrs->vdm_dll = LoadLibraryA("vdmdbg.dll");
225 if (!_ptrs->vdm_dll) return false;
227 // locate the functions that we want to call.
228 _ptrs->enumerate_processes = (BOOL(WINAPI *)(basis::un_int *,basis::un_int,basis::un_int*))
229 GetProcAddress(_ptrs->psapi_dll, "EnumProcesses");
230 _ptrs->enumerate_modules
231 = (BOOL(WINAPI *)(HANDLE, HMODULE *, basis::un_int, basis::un_int *))
232 GetProcAddress(_ptrs->psapi_dll, "EnumProcessModules");
233 _ptrs->get_module_name
234 = (basis::un_int (WINAPI *)(HANDLE, HMODULE, LPTSTR, basis::un_int))
235 GetProcAddress(_ptrs->psapi_dll, "GetModuleFileNameExA");
237 _ptrs->tasker_16bit = (INT(WINAPI *)(basis::un_int, TASKENUMPROCEX, LPARAM))
238 GetProcAddress(_ptrs->vdm_dll, "VDMEnumTaskWOWEx");
240 if (!_ptrs->enumerate_processes || !_ptrs->enumerate_modules
241 || !_ptrs->get_module_name
243 || !_ptrs->tasker_16bit
250 bool process_control::initialize_toolhelp_support()
252 // get hooked up with the kernel dll so we can use toolhelp functions.
253 _ptrs->kernel32_dll = LoadLibraryA("Kernel32.DLL");
254 if (!_ptrs->kernel32_dll) return false;
256 // create pointers to the functions we want to invoke.
257 _ptrs->create_snapshot = (HANDLE(WINAPI *)(basis::un_int,basis::un_int))
258 GetProcAddress(_ptrs->kernel32_dll, "CreateToolhelp32Snapshot");
259 _ptrs->first_process = (BOOL(WINAPI *)(HANDLE,LPPROCESSENTRY32))
260 GetProcAddress(_ptrs->kernel32_dll, "Process32First");
261 _ptrs->next_process = (BOOL(WINAPI *)(HANDLE,LPPROCESSENTRY32))
262 GetProcAddress(_ptrs->kernel32_dll, "Process32Next");
263 if (!_ptrs->next_process || !_ptrs->first_process
264 || !_ptrs->create_snapshot) return false;
270 bool process_control::zap_process(basis::un_int to_zap)
272 #ifdef DEBUG_PROCESS_CONTROL
273 FUNCDEF("zap_process");
275 if (!_healthy) return false;
277 int ret = kill(to_zap, 9);
278 // send the serious take-down signal to the process.
282 HANDLE h = OpenProcess(PROCESS_TERMINATE, false, to_zap);
284 #ifdef DEBUG_PROCESS_CONTROL
285 int err = critical_events::system_error();
286 LOG(a_sprintf("error zapping process %d=", to_zap)
287 + critical_events::system_error_text(err));
292 BOOL ret = TerminateProcess(h, exit_code);
298 process_entry process_control::query_process(basis::un_int to_query)
300 FUNCDEF("query_process");
301 process_entry to_return;
303 process_entry_array to_fill;
304 bool got_em = query_processes(to_fill);
305 if (!got_em) return to_return;
307 for (int i = 0; i < to_fill.length(); i++) {
308 if (to_fill[i]._process_id == to_query)
312 //hmmm: implement more specifically.
314 //put in the single process grabber deal.
317 //grab the entry from the list.
323 bool process_control::find_process_in_list(const process_entry_array &processes,
324 const astring &app_name_in, int_set &pids)
326 #ifdef DEBUG_PROCESS_CONTROL
327 FUNCDEF("find_process_in_list");
330 astring app_name = app_name_in.lower();
332 version os_ver = application_configuration::get_OS_version();
334 bool compare_prefix = (os_ver.v_major() == 5) && (os_ver.v_minor() == 0);
335 // we only compare the first 15 letters due to a recently noticed bizarre
336 // bug where w2k only shows (and reports) the first 15 letters of file
337 // names through toolhelp.
339 bool found = false; // was it seen in process list?
340 for (int i = 0; i < processes.length(); i++) {
341 filename path = processes[i].path();
342 astring base = path.basename().raw().lower();
343 // a kludge for w2k is needed--otherwise we will miss seeing names that
344 // really are running due to the toolhelp api being busted and only
345 // reporting the first 15 characters of the name.
346 if ( (compare_prefix && (base.compare(app_name, 0, 0, 15, false)))
347 || (base == app_name) ) {
349 pids.add(processes[i]._process_id);
352 #ifdef DEBUG_PROCESS_CONTROL
354 LOG(astring("failed to find the program called ") + app_name);
362 // this section is the PSAPI version of the query.
364 // called back on each 16 bit task.
365 BOOL WINAPI process_16bit(basis::un_int dwThreadId, WORD module_handle16, WORD hTask16,
366 PSZ pszModName, PSZ pszFileName, LPARAM lpUserDefined)
368 process_info_clump *to_stuff = (process_info_clump *)lpUserDefined;
369 process_entry to_add;
370 to_add._process_id = to_stuff->_process_id;
371 to_add._module16 = hTask16;
372 to_add.path(pszFileName);
374 to_stuff->_to_fill += to_add;
378 bool process_control::get_processes_with_psapi(process_entry_array &to_fill)
380 // prepare the result object.
383 // loop over the process enumeration function until we are sure that we
384 // have allocated a large enough space for all existing processes.
385 bool got_all = false;
386 basis::un_int *pid_list = NIL;
387 basis::un_int max_size = 428 * sizeof(basis::un_int);
388 basis::un_int actual_size = 0;
390 pid_list = (basis::un_int *)HeapAlloc(GetProcessHeap(), 0, max_size);
391 if (!pid_list) return false;
392 if (!_ptrs->enumerate_processes(pid_list, max_size, &actual_size)) {
393 HeapFree(GetProcessHeap(), 0, pid_list);
396 if (actual_size == max_size) {
397 // there were too many to store, so whack the partial list.
398 HeapFree(GetProcessHeap(), 0, pid_list);
399 max_size *= 2; // try with twice as much space.
400 } else got_all = true;
403 // calculate the number of process ids that got stored.
404 basis::un_int ids = actual_size / sizeof(basis::un_int);
406 // examine each process id that we found.
407 for (basis::un_int i = 0; i < ids; i++) {
408 // get process information if security permits.
409 //turn chunk below into "scan process" or something.
410 HANDLE hProcess = OpenProcess(PROCESS_QUERY_INFORMATION | PROCESS_VM_READ,
412 flexichar process_name[MAX_ABS_PATH + 1] = { '\0' };
414 // go over the modules for the process. the first will be the main
415 // application module and the others will be threads. ???
416 basis::un_int max_size = 1 * sizeof(HMODULE);
417 //hmmm: could do a rescan loop here if too many.
418 basis::un_int actual_size = 0;
419 HMODULE *module_handles = new HMODULE[max_size + 1];
420 if (!module_handles) {
421 CloseHandle(hProcess);
422 HeapFree(GetProcessHeap(), 0, pid_list);
425 if (_ptrs->enumerate_modules(hProcess, module_handles, max_size,
427 // we want the name of the first module.
428 if (!_ptrs->get_module_name(hProcess, *module_handles, process_name,
429 sizeof(process_name)))
432 WHACK(module_handles);
433 CloseHandle(hProcess);
436 // we add whatever information we were able to find about this process.
437 process_entry new_entry;
438 new_entry._process_id = pid_list[i];
439 astring converted_name = from_unicode_temp(process_name);
440 new_entry.path(converted_name);
442 //how to get? performance data helper?
443 /// new_entry._threads = threads;
444 to_fill += new_entry;
446 // if we're looking at ntvdm, then there might be 16 bit processes
448 if (new_entry.path().length() >= NTVDM_NAME.length()) {
449 astring temp = new_entry.path().substring
450 (new_entry.path().end() - NTVDM_NAME.length() + 1,
451 new_entry.path().end());
454 //hmmm: pull this back in for mingw when it seems to be supported, if ever.
455 if (temp == NTVDM_NAME) {
456 // set up a callback stampede on the 16 bit processes.
457 process_info_clump info(pid_list[i], to_fill);
458 _ptrs->tasker_16bit(pid_list[i], (TASKENUMPROCEX)process_16bit,
465 if (pid_list) HeapFree(GetProcessHeap(), 0, pid_list);
471 // this is the toolhelp version of the query.
473 bool process_control::get_processes_with_toolhelp(process_entry_array &to_fill)
475 // prepare the result object.
478 // get an atomic view of the process list, which rapidly becomes out of date.
480 hSnapShot = _ptrs->create_snapshot(TH32CS_SNAPPROCESS, 0);
481 if (hSnapShot == INVALID_HANDLE_VALUE) return false;
483 // start iterating through the snapshot by getting the first process.
484 PROCESSENTRY32 entry;
485 entry.dwSize = sizeof(PROCESSENTRY32);
486 BOOL keep_going = _ptrs->first_process(hSnapShot, &entry);
488 // while we see valid processes, iterate through them.
490 // add an entry for the current process.
491 process_entry new_entry;
492 new_entry._process_id = entry.th32ProcessID;
493 new_entry._references = entry.cntUsage;
494 new_entry._threads = entry.cntThreads;
495 new_entry._parent_process_id = entry.th32ParentProcessID;
496 astring exe_file = from_unicode_temp(entry.szExeFile);
497 new_entry.path(exe_file);
498 to_fill += new_entry;
499 entry.dwSize = sizeof(PROCESSENTRY32); // reset struct size.
500 keep_going = _ptrs->next_process(hSnapShot, &entry);
503 CloseHandle(hSnapShot);
510 #define CLOSE_TEMPORARY_FILE { \
511 /* continuable_error("process_control", "get_processes_with_ps", error); */ \
514 unlink(tmpfile.s()); \
518 bool process_control::get_processes_with_ps(process_entry_array &to_fill)
520 FUNCDEF("get_processes_with_ps");
522 // we ask the operating system to give us a list of processes.
523 a_sprintf tmpfile("/tmp/proc_list_%d_%d.txt", application_configuration::process_id(),
524 _rando->inclusive(1, 400000));
525 a_sprintf cmd("ps wax --format \"%%p %%a\" >%s", tmpfile.s());
526 //hmmm: add more info as we expand the process entry.
527 FILE *output = NIL; // initialize now to establish variable for our macro.
528 int sysret = system(cmd.s());
529 if (negative(sysret)) {
530 LOG("got negative return from system()!");
531 CLOSE_TEMPORARY_FILE;
534 output = fopen(tmpfile.s(), "r");
536 LOG("failed to open process list file!");
537 CLOSE_TEMPORARY_FILE;
540 const int max_buff = 10000;
542 size_t size_read = 1;
544 while (size_read > 0) {
545 // read bytes from the file.
546 size_read = fread(buff, 1, max_buff, output);
547 // if there was anything, store it in the string.
549 accumulator += astring(astring::UNTERMINATED, buff, size_read);
551 CLOSE_TEMPORARY_FILE;
552 // parse the string up now.
553 bool first_line = true;
554 while (accumulator.length()) {
555 // eat any spaces in front.
557 // we toss the first line since it's a header with columns.
558 int cr_indy = accumulator.find('\n');
559 accumulator.zap(0, cr_indy);
560 if (accumulator[accumulator.end()] == '\r')
561 accumulator.zap(accumulator.end(), accumulator.end());
565 while (accumulator.length() && (accumulator[0] == ' '))
566 accumulator.zap(0, 0);
567 // look for the first part of the line; the process id.
568 int num_indy = accumulator.find(' ');
569 if (negative(num_indy)) break;
570 basis::un_int p_id = accumulator.substring(0, num_indy).convert(0);
571 accumulator.zap(0, num_indy);
572 int cr_indy = accumulator.find('\n');
573 if (negative(cr_indy))
574 cr_indy = accumulator.end() + 1;
575 astring proc_name = accumulator.substring(0, cr_indy - 1);
576 if (proc_name[proc_name.end()] == '\r')
577 proc_name.zap(proc_name.end(), proc_name.end());
578 accumulator.zap(0, cr_indy);
579 int space_indy = proc_name.find(' ');
580 //hmmm: this is incorrect regarding names that do have spaces in them.
581 if (negative(space_indy))
582 space_indy = proc_name.end() + 1;
583 process_entry to_add;
584 to_add._process_id = p_id;
585 astring path = proc_name.substring(0, space_indy - 1);
586 // patch the pathname if we see any bracketed items.
588 for (int i = 0; i < path.length(); i++) {
589 if (path[i] == '[') brackets_in++;
590 else if (path[i] == ']') brackets_in--;
592 // if we see a slash inside brackets, then we patch it so it doesn't
593 // confuse the filename object's directory handling.
594 if ( (path[i] == '/') || (path[i] == '\\') )