| // Copyright 2004-2009 Google Inc. |
| // |
| // Licensed under the Apache License, Version 2.0 (the "License"); |
| // you may not use this file except in compliance with the License. |
| // You may obtain a copy of the License at |
| // |
| // http://www.apache.org/licenses/LICENSE-2.0 |
| // |
| // Unless required by applicable law or agreed to in writing, software |
| // distributed under the License is distributed on an "AS IS" BASIS, |
| // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| // See the License for the specific language governing permissions and |
| // limitations under the License. |
| // ======================================================================== |
| // |
| // Defines class Process to incapsulate win32 |
| // functions for creation and some manipulations of |
| // processes. |
| |
| #include "omaha/common/process.h" |
| |
| #include <ntsecapi.h> |
| #include <psapi.h> |
| #include <stierr.h> |
| #include <tlhelp32.h> |
| #include <vector> |
| |
| #ifndef NT_SUCCESS |
| #define NT_SUCCESS(Status) ((NTSTATUS)(Status) >= 0) |
| #endif |
| |
| #include "omaha/common/debug.h" |
| #include "omaha/common/disk.h" |
| #include "omaha/common/error.h" |
| #include "omaha/common/logging.h" |
| #include "omaha/common/scoped_any.h" |
| #include "omaha/common/string.h" |
| #include "omaha/common/system.h" |
| #include "omaha/common/system_info.h" |
| #include "omaha/common/utils.h" |
| #include "omaha/common/user_info.h" |
| #include "omaha/common/window_utils.h" |
| |
| namespace omaha { |
| |
| const int kNumRetriesToFindProcess = 4; |
| const int kFindProcessRetryIntervalMs = 500; |
| const int kMaxCmdLineLengthBytes = 4096; |
| |
| // Constructor |
| Process::Process(const TCHAR* name, const TCHAR* window_class_name) |
| : process_id_(0), |
| exit_code_(0), |
| number_of_restarts_(static_cast<uint32>(-1)), |
| name_(name), |
| shutdown_event_(NULL) { |
| ASSERT1(name); |
| |
| command_line_ = name; |
| window_class_name_ = window_class_name; |
| } |
| |
| // Constructor |
| Process::Process(uint32 process_id) |
| : process_id_(process_id), |
| exit_code_(0), |
| number_of_restarts_(static_cast<uint32>(-1)), |
| name_(itostr(static_cast<uint32>(process_id))), |
| shutdown_event_(NULL) { |
| reset(process_, ::OpenProcess(PROCESS_QUERY_INFORMATION | SYNCHRONIZE, |
| false, |
| process_id)); |
| if (!valid(process_)) { |
| UTIL_LOG(LEVEL_ERROR, |
| (_T("[Process::Process - failed to open process][%u][0x%x]"), |
| process_id, HRESULTFromLastError())); |
| } |
| } |
| |
| // Destructor |
| Process::~Process() { |
| } |
| |
| // Start with command params |
| bool Process::Start(const TCHAR* command_line_parameters) { |
| return StartWithHwnd(command_line_parameters, NULL); |
| } |
| |
| // Start with command params and specific hwnd |
| bool Process::StartWithHwnd(const TCHAR* command_line_parameters, HWND hwnd) { |
| // command_line_parameters may be NULL |
| // hwnd may be NULL |
| |
| // Can't start the same process twice in the same |
| // containing object. |
| if (Running()) { |
| return false; |
| } |
| |
| // Add command line params if any. |
| if (command_line_parameters && *command_line_parameters) { |
| command_line_parameters_ = command_line_parameters; |
| } |
| |
| // just reuse the existing function, don't repeat the code. |
| number_of_restarts_ = static_cast<uint32>(-1); |
| time_of_start_ = GetTickCount(); |
| return Restart(hwnd); |
| } |
| |
| // Restart with the old command params |
| bool Process::Restart(HWND hwnd) { |
| // Can't start the same process twice in the same |
| // containing object. |
| if (Running()) { |
| return false; |
| } |
| |
| // start the process. |
| HRESULT hr = System::ShellExecuteProcess(command_line_, |
| command_line_parameters_, |
| hwnd, |
| address(process_)); |
| |
| if (SUCCEEDED(hr)) { |
| process_id_ = GetProcessIdFromHandle(get(process_)); |
| ASSERT1(process_id_); |
| number_of_restarts_++; |
| } else { |
| UTIL_LOG(LEVEL_ERROR, |
| (_T("System::ShellExecuteProcess '%s' failed with 0x%08x"), |
| command_line_, hr)); |
| } |
| |
| return SUCCEEDED(hr); |
| } |
| |
| // Check if the process is running. |
| bool Process::Running() const { |
| if (!get(process_)) { |
| return false; |
| } |
| |
| return (::WaitForSingleObject(get(process_), 0) == WAIT_TIMEOUT); |
| } |
| |
| // Create a job and assign the process to it |
| HANDLE Process::AssignToJob() { |
| // Make sure that the process handle is valid |
| if (!get(process_)) { |
| return false; |
| } |
| |
| // Create a job |
| scoped_job job(::CreateJobObject(NULL, NULL)); |
| if (!valid(job)) { |
| UTIL_LOG(LEVEL_ERROR, |
| (_T("[Process::AssignToJob - CreateJobObject failed][0x%x]"), |
| HRESULTFromLastError())); |
| return false; |
| } |
| |
| // Assign the process to the job |
| if (!::AssignProcessToJobObject(get(job), get(process_))) { |
| UTIL_LOG(LEVEL_ERROR, |
| (_T("[Process::AssignToJob-AssignProcessToJobObject fail][0x%x]"), |
| HRESULTFromLastError())); |
| return false; |
| } |
| |
| return release(job); |
| } |
| |
| // Wait till the process finishes |
| bool Process::WaitUntilDead(uint32 timeout_msec) { |
| ASSERT1(timeout_msec); |
| |
| if (!get(process_)) { |
| return false; |
| } |
| |
| uint32 ret = 0; |
| if (shutdown_event_) { |
| HANDLE wait_handles[2] = {0}; |
| wait_handles[0] = get(process_); |
| wait_handles[1] = shutdown_event_; |
| ret = ::WaitForMultipleObjectsEx(2, |
| wait_handles, |
| false, |
| timeout_msec, |
| true); |
| } else { |
| ret = ::WaitForSingleObjectEx(get(process_), timeout_msec, true); |
| } |
| if (ret == WAIT_OBJECT_0) { |
| UTIL_LOG(L2, (_T("[Process::WaitUntilDead - succeeded to wait process]") |
| _T("[%s]"), GetName())); |
| return true; |
| } else if (ret == WAIT_IO_COMPLETION) { |
| UTIL_LOG(LEVEL_ERROR, (_T("[Process::WaitUntilDead-recv APC][%s][%u][%u]"), |
| GetName(), process_id_)); |
| return false; |
| } else { |
| UTIL_LOG(LEVEL_ERROR, (_T("[Process::WaitUntilDead - fail to wait process,") |
| _T("possibly timeout][%s][%u][%u]"), |
| GetName(), process_id_, ret)); |
| return false; |
| } |
| } |
| |
| // Wait some time till the process and all its descendent processes finish |
| // |
| // Background: |
| // Some process might spawn another process and get itself terminated |
| // without waiting the descendant process to finish. |
| // |
| // Args: |
| // job: Job to which the process is assigned |
| // AssignToJob() will be called when NULL value is passed |
| // timeout_msec: Timeout value in msec |
| // path_to_exclude: Path of descendant process to excluded from waiting |
| // (this should be in long format) |
| // exit_code: To hold the exit code being returned |
| bool Process::WaitUntilAllDead(HANDLE job, |
| uint32 timeout_msec, |
| const TCHAR* path_to_exclude, |
| uint32* exit_code) { |
| ASSERT1(timeout_msec); |
| |
| UTIL_LOG(L2, (_T("[Process::WaitUntilAllDead][%u][%s]"), |
| timeout_msec, path_to_exclude)); |
| |
| if (exit_code) { |
| *exit_code = 0; |
| } |
| |
| scoped_job job_guard; |
| if (!job) { |
| reset(job_guard, AssignToJob()); |
| if (!valid(job_guard)) { |
| return false; |
| } |
| job = get(job_guard); |
| } |
| |
| return InternalWaitUntilAllDead(job, |
| timeout_msec, |
| path_to_exclude, |
| exit_code); |
| } |
| |
| // Helper function to wait till the process and all its descendent processes |
| // finish. |
| bool Process::InternalWaitUntilAllDead(HANDLE job, |
| uint32 timeout_msec, |
| const TCHAR* path_to_exclude, |
| uint32* exit_code) { |
| ASSERT1(job); |
| ASSERT1(timeout_msec); |
| |
| // Wait until current process finishes |
| if (!WaitUntilDead(timeout_msec)) { |
| return false; |
| } |
| |
| // Find descendant process |
| uint32 desc_process_id = GetDescendantProcess( |
| job, |
| false, // child_only |
| exit_code != NULL, // sole_descendent |
| NULL, // search_name |
| path_to_exclude); |
| |
| if (desc_process_id) { |
| // Open descendent process |
| Process desc_process(desc_process_id); |
| |
| // If descendant process dies too soon, do not need to wait for it |
| if (desc_process.Running()) { |
| // Release the parent process handle |
| // This to handle the scenario that Firefox uninstall code will wait till |
| // parent process handle becomes NULL |
| reset(process_); |
| |
| UTIL_LOG(L2, (_T("[Process::InternalWaitUntilAllDead]") |
| _T("[waiting descendant process][%u]"), desc_process_id)); |
| |
| // Propagate the shutdown event to descendent process |
| if (shutdown_event_) { |
| desc_process.SetShutdownEvent(shutdown_event_); |
| } |
| |
| // Wait till descendant process finishes |
| bool wait_ret = desc_process.InternalWaitUntilAllDead(job, |
| timeout_msec, |
| path_to_exclude, |
| exit_code); |
| |
| return wait_ret; |
| } |
| } |
| |
| // Use the exit code from parent process |
| if (exit_code) { |
| VERIFY1(GetExitCode(exit_code)); |
| } |
| |
| // Release the parent process handle |
| reset(process_); |
| |
| return true; |
| } |
| |
| // Wait until process is dead or a windows message arrives (for use in a message |
| // loop while waiting) |
| HRESULT Process::WaitUntilDeadOrInterrupt(uint32 msec) { |
| if (!get(process_)) { |
| return E_FAIL; |
| } |
| |
| HANDLE events[1] = { get(process_) }; |
| uint32 dw = ::MsgWaitForMultipleObjects(1, events, FALSE, msec, QS_ALLEVENTS); |
| switch (dw) { |
| case WAIT_OBJECT_0: |
| return CI_S_PROCESSWAIT_DEAD; |
| case WAIT_OBJECT_0 + 1: |
| return CI_S_PROCESSWAIT_MESSAGE; |
| case WAIT_TIMEOUT: |
| return CI_S_PROCESSWAIT_TIMEOUT; |
| case WAIT_FAILED: |
| default: |
| return E_FAIL; |
| } |
| } |
| |
| #if !SHIPPING |
| CString Process::GetDebugInfo() const { |
| return debug_info_; |
| } |
| #endif |
| |
| // Return the process ID |
| uint32 Process::GetId() const { |
| return process_id_; |
| } |
| |
| // Return the process name |
| const TCHAR *Process::GetName() const { |
| return name_; |
| } |
| |
| // Return win32 handle to the process. |
| HANDLE Process::GetHandle() const { |
| return get(process_); |
| } |
| |
| // Get process exit code. |
| bool Process::GetExitCode(uint32* exit_code) const { |
| ASSERT1(exit_code); |
| |
| if (!get(process_)) { |
| return false; |
| } |
| |
| if (!::GetExitCodeProcess(get(process_), |
| reinterpret_cast<DWORD*>(&exit_code_))) { |
| UTIL_LOG(LEVEL_ERROR, |
| (_T("[Process::GetExitCode - failed to get exit code][%u][0x%x]"), |
| process_id_, HRESULTFromLastError())); |
| return false; |
| } |
| if (exit_code_ == STILL_ACTIVE) { |
| return false; |
| } |
| |
| *exit_code = exit_code_; |
| return true; |
| } |
| |
| // default implementation allows termination |
| bool Process::IsTerminationAllowed() const { |
| return true; |
| } |
| |
| // Terminate the process. If wait_for_terminate_msec == 0 return value doesn't |
| // mean that the process actualy terminated. It becomes assync. operation. |
| // Check the status with Running accessor function in this case. |
| bool Process::Terminate(uint32 wait_for_terminate_msec) { |
| if (!Running()) { |
| return true; |
| } |
| |
| if (!IsTerminationAllowed()) { |
| return false; |
| } |
| |
| if (!::TerminateProcess(get(process_), 1)) { |
| return false; |
| } |
| |
| return wait_for_terminate_msec ? WaitUntilDead(wait_for_terminate_msec) : |
| true; |
| } |
| |
| // Default returns INFINITE means never restart. |
| // Return any number of msec if overwriting |
| uint32 Process::GetRestartInterval() const { |
| return INFINITE; |
| } |
| |
| // How many times the process can be restarted |
| // in case it crashes. When overriding return any |
| // number or INFINITE to restart forever. |
| uint32 Process::GetMaxNumberOfRestarts() const { |
| return 0; |
| } |
| |
| // what is the time window for number of crashes returned by |
| // GetMaxNumberOfRestarts(). If crashed more that this number of restarts |
| // in a specified time window - do not restart it anymore. |
| // Default implementation returns INFINITE which means that this is not time |
| // based at all, if the process crashed more than the value returned by |
| // GetMaxNumberOfRestarts it will not be restarted no matter how long it took. |
| uint32 Process::GetTimeWindowForCrashes() const { |
| return INFINITE; |
| } |
| |
| uint32 Process::GetMaxMemory() const { |
| return 0; |
| } |
| |
| // Have we exceeded the number of maximum restarting? |
| bool Process::AllowedToRestart() const { |
| uint32 max_number_of_restarts = GetMaxNumberOfRestarts(); |
| |
| if ((max_number_of_restarts == INFINITE) || |
| (number_of_restarts_ < max_number_of_restarts)) { |
| return true; |
| } |
| |
| // process crashed too many times. Let's look at the rate of crashes. |
| // Maybe we can "forgive" the process if it took some time for it to crash. |
| if ((::GetTickCount() - time_of_start_) < GetTimeWindowForCrashes()) { |
| return false; // not forgiven |
| } |
| |
| // Everything is forgiven. Give the process |
| // new start in life. |
| time_of_start_ = ::GetTickCount(); |
| number_of_restarts_ = static_cast<uint32>(-1); |
| |
| return true; |
| } |
| |
| // Set shutdown event using in signaling the process watch |
| void Process::SetShutdownEvent(HANDLE shutdown_event) { |
| ASSERT1(shutdown_event); |
| |
| shutdown_event_ = shutdown_event; |
| } |
| |
| // Set priority class to the process. |
| bool Process::SetPriority(uint32 priority_class) const { |
| if (!get(process_)) { |
| return false; |
| } |
| |
| VERIFY1(::SetPriorityClass(get(process_), priority_class)); |
| return true; |
| } |
| |
| // Try to get a descendant process. Return process id if found. |
| uint32 Process::GetDescendantProcess(HANDLE job, |
| bool child_only, |
| bool sole_descedent, |
| const TCHAR* search_name, |
| const TCHAR* path_to_exclude) { |
| ASSERT1(job); |
| |
| // Find all descendent processes |
| std::vector<ProcessInfo> descendant_processes; |
| if (FAILED(GetAllDescendantProcesses(job, |
| child_only, |
| search_name, |
| path_to_exclude, |
| &descendant_processes))) { |
| return 0; |
| } |
| |
| // If more than one decendent processes is found, filter out those that are |
| // not direct children. This is because it might be the case that in a very |
| // short period of time, process A spawns B and B spawns C, and we capture |
| // both B and C. |
| std::vector<ProcessInfo> child_processes; |
| typedef std::vector<ProcessInfo>::const_iterator ProcessInfoConstIterator; |
| if (descendant_processes.size() > 1) { |
| for (ProcessInfoConstIterator it(descendant_processes.begin()); |
| it != descendant_processes.end(); ++it) { |
| if (it->parent_id == process_id_) { |
| child_processes.push_back(*it); |
| } |
| } |
| if (!child_processes.empty()) { |
| descendant_processes = child_processes; |
| } |
| } |
| |
| // Save the debugging information if needed |
| #if !SHIPPING |
| if (sole_descedent && descendant_processes.size() > 1) { |
| debug_info_ = _T("More than one descendent process is found for process "); |
| debug_info_ += itostr(process_id_); |
| debug_info_ += _T("\n"); |
| for (ProcessInfoConstIterator it(descendant_processes.begin()); |
| it != descendant_processes.end(); ++it) { |
| debug_info_.AppendFormat(_T("%u %u %s\n"), |
| it->process_id, |
| it->parent_id, |
| it->exe_file); |
| } |
| } |
| #else |
| sole_descedent; // unreferenced formal parameter |
| #endif |
| |
| return descendant_processes.empty() ? 0 : descendant_processes[0].process_id; |
| } |
| |
| BOOL Process::IsProcessInJob(HANDLE process_handle, |
| HANDLE job_handle, |
| PBOOL result) { |
| typedef BOOL (WINAPI *Fun)(HANDLE process_handle, |
| HANDLE job_handle, |
| PBOOL result); |
| |
| HINSTANCE kernel_instance = ::GetModuleHandle(_T("kernel32.dll")); |
| ASSERT1(kernel_instance); |
| Fun pfn = reinterpret_cast<Fun>(::GetProcAddress(kernel_instance, |
| "IsProcessInJob")); |
| ASSERT(pfn, (_T("IsProcessInJob export not found in kernel32.dll"))); |
| return pfn ? (*pfn)(process_handle, job_handle, result) : FALSE; |
| } |
| |
| // Try to get all matching descendant processes |
| HRESULT Process::GetAllDescendantProcesses( |
| HANDLE job, |
| bool child_only, |
| const TCHAR* search_name, |
| const TCHAR* path_to_exclude, |
| std::vector<ProcessInfo>* descendant_processes) { |
| ASSERT1(job); |
| ASSERT1(descendant_processes); |
| |
| // Take a snapshot |
| // Note that we do not have a seperate scoped_* type defined to wrap the |
| // handle returned by CreateToolhelp32Snapshot. So scoped_hfile with similar |
| // behavior is used. |
| scoped_hfile process_snap(::CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0)); |
| if (!process_snap) { |
| HRESULT hr = HRESULTFromLastError(); |
| UTIL_LOG(LEVEL_ERROR, |
| (_T("[Process::GetAllDescendantProcesses - fail to get snapshot]") |
| _T("[0x%x]"), hr)); |
| return hr; |
| } |
| |
| // Eumerate all processes in the snapshot |
| PROCESSENTRY32 pe32; |
| SetZero(pe32); |
| pe32.dwSize = sizeof(PROCESSENTRY32); |
| if (!::Process32First(get(process_snap), &pe32)) { |
| HRESULT hr = HRESULTFromLastError(); |
| UTIL_LOG(LEVEL_ERROR, (_T("[Process::GetAllDescendantProcesses - failed to") |
| _T("get first process][0x%x]"), hr)); |
| return hr; |
| } |
| |
| do { |
| // Skip process 0 and current process |
| if (pe32.th32ProcessID == 0 || pe32.th32ProcessID == process_id_) { |
| continue; |
| } |
| |
| // If searching for child only, perform the check |
| if (child_only && pe32.th32ParentProcessID != process_id_) { |
| continue; |
| } |
| |
| // Open the process |
| scoped_process process(::OpenProcess(PROCESS_QUERY_INFORMATION | |
| SYNCHRONIZE, |
| false, |
| pe32.th32ProcessID)); |
| if (!valid(process)) { |
| continue; |
| } |
| |
| // Determines whether the process is running in the specified job |
| BOOL result = FALSE; |
| if (!IsProcessInJob(get(process), job, &result) || !result) { |
| continue; |
| } |
| |
| // Check whether the process is still running |
| if (::WaitForSingleObject(get(process), 0) != WAIT_TIMEOUT) { |
| continue; |
| } |
| |
| // Compare the name if needed |
| if (search_name && *search_name) { |
| if (_tcsicmp(pe32.szExeFile, search_name) != 0) { |
| continue; |
| } |
| } |
| |
| // If we need to exclude certain path, check it now |
| if (path_to_exclude && *path_to_exclude) { |
| if (IsProcessRunningWithPath(pe32.th32ProcessID, path_to_exclude)) { |
| continue; |
| } |
| } |
| |
| // Add to the list |
| ProcessInfo proc_info; |
| proc_info.process_id = pe32.th32ProcessID; |
| proc_info.parent_id = pe32.th32ParentProcessID; |
| #if !SHIPPING |
| proc_info.exe_file = pe32.szExeFile; |
| #endif |
| descendant_processes->push_back(proc_info); |
| } while (::Process32Next(get(process_snap), &pe32)); |
| |
| return S_OK; |
| } |
| |
| HRESULT Process::FindProcesses(uint32 exclude_mask, |
| const TCHAR* search_name, |
| bool search_main_executable_only, |
| std::vector<uint32>* process_ids_found) { |
| ASSERT1(process_ids_found); |
| // Remove the only include processes owned by user mask from the exclude |
| // mask. This is needed as this is the behavior expected by the method, |
| // before the addition of the user_sid. |
| exclude_mask &= (~INCLUDE_ONLY_PROCESS_OWNED_BY_USER); |
| std::vector<CString> command_lines; |
| return FindProcesses(exclude_mask, search_name, search_main_executable_only, |
| _T(""), command_lines, process_ids_found); |
| } |
| |
| bool Process::IsStringPresentInList(const CString& process_command_line, |
| const std::vector<CString>& list) { |
| std::vector<CString>::const_iterator iter = list.begin(); |
| for (; iter != list.end(); ++iter) { |
| CString value_to_find = *iter; |
| |
| // If we are able to open the process command line, then we should |
| // ensure that it does not contain the value that we are looking for. |
| if (process_command_line.Find(value_to_find) != -1) { |
| // Found a match. |
| return true; |
| } |
| } |
| |
| return false; |
| } |
| |
| // TODO(omaha): Change the implementation of this method to take in a |
| // predicate that determines whether a process should be included in the |
| // result set. |
| HRESULT Process::FindProcesses(uint32 exclude_mask, |
| const TCHAR* search_name, |
| bool search_main_executable_only, |
| const CString& user_sid, |
| const std::vector<CString>& command_lines, |
| std::vector<uint32>* process_ids_found) { |
| ASSERT1(search_name && *search_name); |
| ASSERT1(process_ids_found); |
| ASSERT1(!((exclude_mask & EXCLUDE_PROCESS_COMMAND_LINE_CONTAINING_STRING) && |
| (exclude_mask & INCLUDE_PROCESS_COMMAND_LINE_CONTAINING_STRING))); |
| |
| const TCHAR* const kLocalSystemSid = _T("S-1-5-18"); |
| |
| // Clear the output queue |
| process_ids_found->clear(); |
| |
| // Get the list of process identifiers. |
| uint32 process_ids[kMaxProcesses]; |
| SetZero(process_ids); |
| uint32 bytes_returned = 0; |
| if (!::EnumProcesses(reinterpret_cast<DWORD*>(process_ids), |
| sizeof(process_ids), |
| reinterpret_cast<DWORD*>(&bytes_returned))) { |
| HRESULT hr = HRESULTFromLastError(); |
| UTIL_LOG(LEVEL_ERROR, (_T("[Process::FindProcesses-fail to EnumProcesses]") |
| _T("[0x%x]"), hr)); |
| return hr; |
| } |
| |
| // Enumerate all processes |
| int num_processes = bytes_returned / sizeof(process_ids[0]); |
| // We have found an elevated number of crashes in 1.2.584.15114 on what |
| // we believe are Italian systems. The first step to solving this Italian job |
| // is to assert on the condition while we are further testing this. |
| ASSERT1(num_processes <= kMaxProcesses); |
| |
| // In Vista, SeDebugPrivilege is required to open the process not owned by |
| // current user. Also required for XP admins to open Local System processes |
| // with PROCESS_QUERY_INFORMATION access rights. |
| System::AdjustPrivilege(SE_DEBUG_NAME, true); |
| |
| // Get ID of current process |
| uint32 curr_process_id = ::GetCurrentProcessId(); |
| |
| // Get SID of current user |
| CString curr_user_sid; |
| HRESULT hr = omaha::user_info::GetCurrentUser(NULL, NULL, &curr_user_sid); |
| if (FAILED(hr)) { |
| return hr; |
| } |
| |
| UTIL_LOG(L4, (_T("[Process::FindProcesses][processes=%d]"), num_processes)); |
| for (int i = 0; i < num_processes; ++i) { |
| // Skip the system idle process. |
| if (process_ids[i] == 0) { |
| continue; |
| } |
| |
| // Get the owner sid. |
| // Note that we may fail to get the owner which is not current user. |
| // So if the owner_sid is empty, the process is sure not to be owned by the |
| // current user. |
| CString owner_sid; |
| Process::GetProcessOwner(process_ids[i], &owner_sid); |
| |
| if ((exclude_mask & INCLUDE_ONLY_PROCESS_OWNED_BY_USER) && |
| owner_sid != user_sid) { |
| UTIL_LOG(L4, |
| (_T("[Excluding process as not owned by user][%d]"), process_ids[i])); |
| continue; |
| } |
| |
| // Skip it if it is owned by the one specified in exclude_mask |
| if ((exclude_mask & EXCLUDE_CURRENT_PROCESS) && |
| process_ids[i] == curr_process_id) { |
| UTIL_LOG(L4, (_T("[Excluding current process %d"), process_ids[i])); |
| continue; |
| } |
| if ((exclude_mask & EXCLUDE_PROCESS_OWNED_BY_CURRENT_USER) && |
| owner_sid == curr_user_sid) { |
| UTIL_LOG(L4, |
| (_T("[Excluding process as owned by current user][%d]"), |
| process_ids[i])); |
| continue; |
| } |
| if ((exclude_mask & EXCLUDE_PROCESS_OWNED_BY_SYSTEM) && |
| owner_sid == kLocalSystemSid) { |
| UTIL_LOG(L4, |
| (_T("[Excluding process as owned by system][%d]"), process_ids[i])); |
| continue; |
| } |
| if (exclude_mask & EXCLUDE_PROCESS_COMMAND_LINE_CONTAINING_STRING || |
| exclude_mask & INCLUDE_PROCESS_COMMAND_LINE_CONTAINING_STRING) { |
| CString process_command_line; |
| HRESULT hr = GetCommandLine(process_ids[i], &process_command_line); |
| if (FAILED(hr)) { |
| UTIL_LOG(L4, |
| (_T("[Excluding process could not get command line][%d]"), |
| process_ids[i])); |
| continue; |
| } |
| |
| // If we are able to open the process command line, then we should |
| // ensure that it does not contain the value that we are looking for if |
| // we are excluding the command line or that it contains the command line |
| // that we are looking for in case the include switch is specified. |
| bool present = IsStringPresentInList(process_command_line, command_lines); |
| if ((present && |
| (exclude_mask & EXCLUDE_PROCESS_COMMAND_LINE_CONTAINING_STRING)) || |
| (!present && |
| (exclude_mask & INCLUDE_PROCESS_COMMAND_LINE_CONTAINING_STRING))) { |
| UTIL_LOG(L4, (_T("[Process command line matches criteria][%d]'[%s]'"), |
| process_ids[i], process_command_line)); |
| continue; |
| } |
| } |
| |
| // If search_name is provided, make sure it matches |
| if (Process::IsProcessUsingExeOrDll(process_ids[i], |
| search_name, |
| search_main_executable_only)) { |
| UTIL_LOG(L4, |
| (_T("[Including process][%d][%s]"), process_ids[i], search_name)); |
| process_ids_found->push_back(process_ids[i]); |
| } |
| } |
| |
| return S_OK; |
| } |
| |
| HRESULT Process::FindProcessesInSession( |
| DWORD session_id, |
| uint32 exclude_mask, |
| const TCHAR* search_name, |
| bool search_main_executable_only, |
| const CString& user_sid, |
| const std::vector<CString>& cmd_lines, |
| std::vector<uint32>* process_ids_found) { |
| HRESULT hr = FindProcesses(exclude_mask, |
| search_name, |
| search_main_executable_only, |
| user_sid, |
| cmd_lines, |
| process_ids_found); |
| if (FAILED(hr)) { |
| return hr; |
| } |
| |
| // Filter to processes running under session_id. |
| std::vector<uint32>::iterator iter = process_ids_found->begin(); |
| while (iter != process_ids_found->end()) { |
| uint32 process_pid = *iter; |
| DWORD process_session = 0; |
| hr = S_OK; |
| if (!::ProcessIdToSessionId(process_pid, &process_session)) { |
| hr = HRESULTFromLastError(); |
| UTIL_LOG(LE, (_T("[::ProcessIdToSessionId failed][0x%x]"), hr)); |
| } |
| |
| if (FAILED(hr) || process_session != session_id) { |
| // Remove from list and continue. |
| iter = process_ids_found->erase(iter); |
| continue; |
| } |
| |
| ++iter; |
| } |
| |
| return S_OK; |
| } |
| |
| bool Process::IsModuleMatchingExeOrDll(const TCHAR* module_name, |
| const TCHAR* search_name, |
| bool is_fully_qualified_name) { |
| UTIL_LOG(L4, (_T("[Process::IsModuleMatchingExeOrDll]") |
| _T("[module=%s][search=%s]"), module_name, search_name)); |
| CString module_file_name; |
| if (is_fully_qualified_name) { |
| if (FAILED(GetLongPathName(module_name, &module_file_name))) { |
| return false; |
| } |
| } else { |
| module_file_name = ::PathFindFileName(module_name); |
| ASSERT1(!module_file_name.IsEmpty()); |
| if (module_file_name.IsEmpty()) { |
| return false; |
| } |
| } |
| |
| return (module_file_name.CompareNoCase(search_name) == 0); |
| } |
| |
| DWORD Process::GetProcessImageFileName(HANDLE proc_handle, |
| LPTSTR image_file, |
| DWORD file_size) { |
| typedef DWORD (WINAPI *Fun)(HANDLE proc_handle, |
| LPWSTR image_file, |
| DWORD file_size); |
| |
| HINSTANCE psapi_instance = ::GetModuleHandle(_T("Psapi.dll")); |
| ASSERT1(psapi_instance); |
| Fun pfn = reinterpret_cast<Fun>(::GetProcAddress(psapi_instance, |
| "GetProcessImageFileNameW")); |
| if (!pfn) { |
| UTIL_LOG(L1, (_T("::GetProcessImageFileNameW() not found in Psapi.dll"))); |
| return 0; |
| } |
| return (*pfn)(proc_handle, image_file, file_size); |
| } |
| |
| bool Process::IsProcImageMatch(HANDLE proc_handle, |
| const TCHAR* search_name, |
| bool is_fully_qualified_name) { |
| TCHAR image_name[MAX_PATH] = _T(""); |
| if (!GetProcessImageFileName(proc_handle, |
| image_name, |
| arraysize(image_name))) { |
| UTIL_LOG(L4, (_T("[GetProcessImageFileName fail[0x%x]"), |
| HRESULTFromLastError())); |
| return false; |
| } |
| |
| UTIL_LOG(L4, (_T("[GetProcessImageFileName][%s]"), image_name)); |
| CString dos_name; |
| HRESULT hr(DevicePathToDosPath(image_name, &dos_name)); |
| if (FAILED(hr)) { |
| UTIL_LOG(L4, (_T("[DevicePathToDosPath fail[0x%x]"), hr)); |
| return false; |
| } |
| |
| return IsModuleMatchingExeOrDll(dos_name, |
| search_name, |
| is_fully_qualified_name); |
| } |
| |
| // Is the process using the specified exe/dll? |
| bool Process::IsProcessUsingExeOrDll(uint32 process_id, |
| const TCHAR* search_name, |
| bool search_main_executable_only) { |
| UTIL_LOG(L4, (_T("[Process::IsProcessUsingExeOrDll]") |
| _T("[pid=%d][search_name=%s]"), process_id, search_name)); |
| ASSERT1(search_name); |
| |
| // Open the process |
| scoped_process process_handle(::OpenProcess( |
| PROCESS_QUERY_INFORMATION | PROCESS_VM_READ, |
| FALSE, |
| process_id)); |
| if (!process_handle) { |
| UTIL_LOG(L4, (_T("[::OpenProcess failed][0x%x]"), HRESULTFromLastError())); |
| return false; |
| } |
| |
| // Does the name represent a fully qualified name? |
| // We only do a simple check here |
| bool is_fully_qualified_name = String_FindChar(search_name, _T('\\')) != -1; |
| CString long_search_name; |
| if (is_fully_qualified_name) { |
| HRESULT hr(GetLongPathName(search_name, &long_search_name)); |
| if (FAILED(hr)) { |
| UTIL_LOG(L4, (_T("[GetLongPathName fail][hr=x%x]"), hr)); |
| return false; |
| } |
| search_name = long_search_name; |
| } |
| |
| // Take a snapshot of all modules in the specified process |
| int num_modules_to_fetch = search_main_executable_only ? 1 : |
| kMaxProcessModules; |
| HMODULE module_handles[kMaxProcessModules]; |
| SetZero(module_handles); |
| uint32 bytes_needed = 0; |
| if (!::EnumProcessModules(get(process_handle), |
| module_handles, |
| num_modules_to_fetch * sizeof(HMODULE), |
| reinterpret_cast<DWORD*>(&bytes_needed))) { |
| HRESULT hr = HRESULTFromLastError(); |
| UTIL_LOG(LEVEL_ERROR, (_T("[EnumProcessModules failed][0x%x]"), hr)); |
| |
| if (IsWow64(::GetCurrentProcessId())) { |
| // ::EnumProcessModules from a WoW64 process fails for x64 processes. |
| // We try ::GetProcessImageFileName as a workaround here. |
| return search_main_executable_only ? |
| IsProcImageMatch(get(process_handle), |
| search_name, |
| is_fully_qualified_name) : |
| false; |
| } else { |
| return false; |
| } |
| } |
| |
| int num_modules = bytes_needed / sizeof(HMODULE); |
| if (num_modules > num_modules_to_fetch) { |
| num_modules = num_modules_to_fetch; |
| } |
| |
| for (int i = 0; i < num_modules; ++i) { |
| TCHAR module_name[MAX_PATH]; |
| SetZero(module_name); |
| if (!::GetModuleFileNameEx(get(process_handle), |
| module_handles[i], |
| module_name, |
| arraysize(module_name))) { |
| UTIL_LOG(LEVEL_ERROR, (_T("[GetModuleFileNameEx fail[x%x]"), |
| HRESULTFromLastError())); |
| continue; |
| } |
| |
| if (IsModuleMatchingExeOrDll(module_name, |
| search_name, |
| is_fully_qualified_name)) { |
| return true; |
| } |
| } |
| |
| return false; |
| } |
| |
| // Helper function to get long path name |
| HRESULT Process::GetLongPathName(const TCHAR* short_name, CString* long_name) { |
| ASSERT1(short_name); |
| ASSERT1(long_name); |
| |
| TCHAR temp_name[MAX_PATH]; |
| SetZero(temp_name); |
| |
| HRESULT hr = S_OK; |
| if (!::GetLongPathName(short_name, temp_name, arraysize(temp_name))) { |
| hr = HRESULTFromLastError(); |
| } else { |
| long_name->SetString(temp_name); |
| } |
| |
| return hr; |
| } |
| |
| // Type definitions needed for GetCommandLine() and GetProcessIdFromHandle() |
| // From MSDN document on NtQueryInformationProcess() and other sources |
| typedef struct _PROCESS_BASIC_INFORMATION { |
| PVOID Reserved1; |
| BYTE *PebBaseAddress; |
| PVOID Reserved2[2]; |
| ULONG_PTR UniqueProcessId; |
| PVOID Reserved3; |
| } PROCESS_BASIC_INFORMATION; |
| |
| typedef enum _PROCESSINFOCLASS { |
| ProcessBasicInformation = 0, |
| ProcessWow64Information = 26 |
| } PROCESSINFOCLASS; |
| |
| typedef WINBASEAPI DWORD WINAPI |
| GetProcessIdFn( |
| HANDLE Process |
| ); |
| |
| typedef LONG WINAPI |
| NtQueryInformationProcess( |
| IN HANDLE ProcessHandle, |
| IN PROCESSINFOCLASS ProcessInformationClass, |
| OUT PVOID ProcessInformation, |
| IN ULONG ProcessInformationLength, |
| OUT PULONG ReturnLength OPTIONAL |
| ); |
| |
| typedef struct _RTL_DRIVE_LETTER_CURDIR { |
| USHORT Flags; |
| USHORT Length; |
| ULONG TimeStamp; |
| UNICODE_STRING DosPath; |
| } RTL_DRIVE_LETTER_CURDIR, *PRTL_DRIVE_LETTER_CURDIR; |
| |
| typedef struct _RTL_USER_PROCESS_PARAMETERS { |
| ULONG MaximumLength; |
| ULONG Length; |
| ULONG Flags; |
| ULONG DebugFlags; |
| PVOID ConsoleHandle; |
| ULONG ConsoleFlags; |
| HANDLE StdInputHandle; |
| HANDLE StdOutputHandle; |
| HANDLE StdErrorHandle; |
| UNICODE_STRING CurrentDirectoryPath; |
| HANDLE CurrentDirectoryHandle; |
| UNICODE_STRING DllPath; |
| UNICODE_STRING ImagePathName; |
| UNICODE_STRING CommandLine; |
| PVOID Environment; |
| ULONG StartingPositionLeft; |
| ULONG StartingPositionTop; |
| ULONG Width; |
| ULONG Height; |
| ULONG CharWidth; |
| ULONG CharHeight; |
| ULONG ConsoleTextAttributes; |
| ULONG WindowFlags; |
| ULONG ShowWindowFlags; |
| UNICODE_STRING WindowTitle; |
| UNICODE_STRING DesktopName; |
| UNICODE_STRING ShellInfo; |
| UNICODE_STRING RuntimeData; |
| RTL_DRIVE_LETTER_CURDIR DLCurrentDirectory[0x20]; |
| } RTL_USER_PROCESS_PARAMETERS, *PRTL_USER_PROCESS_PARAMETERS; |
| |
| // Get the function pointer to GetProcessId in KERNEL32.DLL |
| static HRESULT EnsureGPIFunction(GetProcessIdFn** gpi_func_ptr) { |
| static GetProcessIdFn* gpi_func = NULL; |
| if (!gpi_func) { |
| HMODULE kernel32_module = ::GetModuleHandle(_T("kernel32.dll")); |
| if (!kernel32_module) { |
| return HRESULTFromLastError(); |
| } |
| gpi_func = reinterpret_cast<GetProcessIdFn*>( |
| ::GetProcAddress(kernel32_module, "GetProcessId")); |
| if (!gpi_func) { |
| return HRESULTFromLastError(); |
| } |
| } |
| |
| *gpi_func_ptr = gpi_func; |
| return S_OK; |
| } |
| |
| // Get the function pointer to NtQueryInformationProcess in NTDLL.DLL |
| static HRESULT EnsureQIPFunction(NtQueryInformationProcess** qip_func_ptr) { |
| static NtQueryInformationProcess* qip_func = NULL; |
| if (!qip_func) { |
| HMODULE ntdll_module = ::GetModuleHandle(_T("ntdll.dll")); |
| if (!ntdll_module) { |
| return HRESULTFromLastError(); |
| } |
| qip_func = reinterpret_cast<NtQueryInformationProcess*>( |
| ::GetProcAddress(ntdll_module, "NtQueryInformationProcess")); |
| if (!qip_func) { |
| return HRESULTFromLastError(); |
| } |
| } |
| |
| *qip_func_ptr = qip_func; |
| return S_OK; |
| } |
| |
| // Obtain the process ID from a hProcess HANDLE |
| ULONG Process::GetProcessIdFromHandle(HANDLE hProcess) { |
| if (SystemInfo::IsRunningOnXPSP1OrLater()) { |
| // Thunk to the documented ::GetProcessId() API |
| GetProcessIdFn* gpi_func = NULL; |
| HRESULT hr = EnsureGPIFunction(&gpi_func); |
| if (FAILED(hr)) { |
| ASSERT(FALSE, |
| (_T("Process::GetProcessIdFromHandle - EnsureGPIFunction") |
| _T(" failed[0x%x]"), hr)); |
| return 0; |
| } |
| ASSERT1(gpi_func); |
| return gpi_func(hProcess); |
| } |
| |
| // For lower versions of Windows, we use undocumented |
| // function NtQueryInformationProcess to get at the PID |
| NtQueryInformationProcess* qip_func = NULL; |
| HRESULT hr = EnsureQIPFunction(&qip_func); |
| if (FAILED(hr)) { |
| ASSERT(FALSE, |
| (_T("Process::GetProcessIdFromHandle - EnsureQIPFunction") |
| _T(" failed[0x%x]"), hr)); |
| return 0; |
| } |
| ASSERT1(qip_func); |
| |
| PROCESS_BASIC_INFORMATION info; |
| SetZero(info); |
| if (!NT_SUCCESS(qip_func(hProcess, |
| ProcessBasicInformation, |
| &info, |
| sizeof(info), |
| NULL))) { |
| ASSERT(FALSE, (_T("Process::GetProcessIdFromHandle - ") |
| _T("NtQueryInformationProcess failed!"))); |
| return 0; |
| } |
| |
| return info.UniqueProcessId; |
| } |
| |
| // Get the command line of a process |
| HRESULT Process::GetCommandLine(uint32 process_id, CString* cmd_line) { |
| ASSERT1(process_id); |
| ASSERT1(cmd_line); |
| |
| // Open the process |
| scoped_process process_handle(::OpenProcess( |
| PROCESS_QUERY_INFORMATION | PROCESS_VM_READ, |
| false, |
| process_id)); |
| if (!process_handle) { |
| return HRESULTFromLastError(); |
| } |
| |
| // Obtain Process Environment Block |
| // Note that NtQueryInformationProcess is not available in Windows 95/98/ME |
| NtQueryInformationProcess* qip_func = NULL; |
| HRESULT hr = EnsureQIPFunction(&qip_func); |
| |
| if (FAILED(hr)) { |
| return hr; |
| } |
| ASSERT1(qip_func); |
| |
| PROCESS_BASIC_INFORMATION info; |
| SetZero(info); |
| if (!NT_SUCCESS(qip_func(get(process_handle), |
| ProcessBasicInformation, |
| &info, |
| sizeof(info), |
| NULL))) { |
| return E_FAIL; |
| } |
| BYTE* peb = info.PebBaseAddress; |
| |
| // Read address of parameters (see some PEB reference) |
| // TODO(omaha): use offsetof(PEB, ProcessParameters) to replace 0x10 |
| // http://msdn.microsoft.com/en-us/library/aa813706.aspx |
| SIZE_T bytes_read = 0; |
| uint32 dw = 0; |
| if (!::ReadProcessMemory(get(process_handle), |
| peb + 0x10, |
| &dw, |
| sizeof(dw), |
| &bytes_read)) { |
| return HRESULTFromLastError(); |
| } |
| |
| // Read all the parameters |
| RTL_USER_PROCESS_PARAMETERS params; |
| SetZero(params); |
| if (!::ReadProcessMemory(get(process_handle), |
| reinterpret_cast<PVOID>(dw), |
| ¶ms, |
| sizeof(params), |
| &bytes_read)) { |
| return HRESULTFromLastError(); |
| } |
| |
| // Read the command line parameter |
| const int max_cmd_line_len = std::min( |
| static_cast<int>(params.CommandLine.MaximumLength), |
| kMaxCmdLineLengthBytes); |
| if (!::ReadProcessMemory(get(process_handle), |
| params.CommandLine.Buffer, |
| cmd_line->GetBufferSetLength(max_cmd_line_len), |
| max_cmd_line_len, |
| &bytes_read)) { |
| return HRESULTFromLastError(); |
| } |
| |
| cmd_line->ReleaseBuffer(); |
| |
| return S_OK; |
| } |
| |
| // Check if the process is running with a specified path |
| bool Process::IsProcessRunningWithPath(uint32 process_id, const TCHAR* path) { |
| ASSERT1(process_id); |
| ASSERT1(path && *path); |
| |
| const int kProcessWaitModuleFullyUpMs = 100; |
| const int kProcessWaitModuleRetries = 10; |
| |
| // Open the process |
| scoped_process process(::OpenProcess( |
| PROCESS_QUERY_INFORMATION | PROCESS_VM_READ, |
| false, |
| process_id)); |
| if (!process) { |
| UTIL_LOG(LEVEL_ERROR, |
| (_T("[Process::IsProcessRunningWithPath - OpenProcess failed]") |
| _T("[%u][0x%x]"), |
| process_id, HRESULTFromLastError())); |
| return false; |
| } |
| |
| for (int i = 0; i < kProcessWaitModuleRetries; ++i) { |
| // Get the command line path of the main module |
| // Note that we are using psapi functions which is not supported in Windows |
| // 95/98/ME |
| // |
| // Sometimes it might be the case that the process is created but the main |
| // module is not fully loaded. If so, wait a while and then try again |
| TCHAR process_path[MAX_PATH]; |
| if (::GetModuleFileNameEx(get(process), |
| NULL, |
| process_path, |
| arraysize(process_path))) { |
| // Do the check |
| if (String_StartsWith(process_path, path, true)) { |
| return true; |
| } |
| |
| // Try again with short form |
| TCHAR short_path[MAX_PATH]; |
| if (::GetShortPathName(path, short_path, arraysize(short_path)) && |
| String_StartsWith(process_path, short_path, true)) { |
| return true; |
| } |
| |
| return false; |
| } |
| |
| UTIL_LOG(LEVEL_ERROR, |
| (_T("[Process::IsProcessRunningWithPath - GetModuleFileNameEx ") |
| _T("failed][%u][0x%x]"), |
| process_id, HRESULTFromLastError())); |
| |
| ::Sleep(kProcessWaitModuleFullyUpMs); |
| } |
| |
| UTIL_LOG(LEVEL_ERROR, |
| (_T("[Process::IsProcessRunningWithPath - failed to get process ") |
| _T("path][%u][0x%x]"), |
| process_id, HRESULTFromLastError())); |
| |
| return false; |
| } |
| |
| // Get the process owner |
| // Note that we may fail to get the owner which is not current user. |
| // TODO(omaha): merge with UserInfo::GetCurrentUser |
| HRESULT Process::GetProcessOwner(uint32 pid, CString* owner_sid) { |
| ASSERT1(pid); |
| ASSERT1(owner_sid); |
| |
| scoped_process process(::OpenProcess(PROCESS_QUERY_INFORMATION, false, pid)); |
| if (!valid(process)) { |
| HRESULT hr = HRESULTFromLastError(); |
| UTIL_LOG(LEVEL_ERROR, |
| (_T("[Process::GetProcessOwner - OpenProcess failed][%u][0x%x]"), |
| pid, hr)); |
| return hr; |
| } |
| |
| scoped_handle process_token; |
| if (!::OpenProcessToken(get(process), |
| READ_CONTROL | TOKEN_QUERY, |
| address(process_token))) { |
| HRESULT hr = HRESULTFromLastError(); |
| UTIL_LOG(L4, |
| (_T("[Process::GetProcessOwner - OpenProcessToken failed][0x%x]"), |
| hr)); |
| return hr; |
| } |
| |
| DWORD size_needed = 0; |
| BOOL b = ::GetTokenInformation(get(process_token), |
| TokenUser, |
| NULL, |
| 0, |
| &size_needed); |
| ASSERT1(!b && ::GetLastError() == ERROR_INSUFFICIENT_BUFFER); |
| |
| scoped_array<byte> token_user(new byte[size_needed]); |
| DWORD size_returned = 0; |
| if (!::GetTokenInformation(get(process_token), |
| TokenUser, |
| token_user.get(), |
| size_needed, |
| &size_returned)) { |
| HRESULT hr = HRESULTFromLastError(); |
| UTIL_LOG(LEVEL_ERROR, |
| (_T("[Process::GetProcessOwner - GetTokenInformation fail][0x%x]"), |
| hr)); |
| return hr; |
| } |
| |
| PSID process_sid = (reinterpret_cast<TOKEN_USER*>( |
| token_user.get()))->User.Sid; |
| ASSERT1(process_sid); |
| if (!process_sid) { |
| return E_FAIL; |
| } |
| |
| TCHAR* process_sid_str = NULL; |
| if (!::ConvertSidToStringSid(process_sid, &process_sid_str)) { |
| HRESULT hr = HRESULTFromLastError(); |
| UTIL_LOG(LEVEL_ERROR, |
| (_T("[IsOwnedByUser - ConvertSidToStringSid failed][0x%x]"), |
| hr)); |
| return hr; |
| } |
| scoped_hlocal scoped_guard_sid_str(process_sid_str); |
| |
| *owner_sid = process_sid_str; |
| |
| return S_OK; |
| } |
| |
| // Creates an impersonation token for the user running process_id. |
| // The caller is responsible for closing the returned handle. |
| HRESULT Process::GetImpersonationToken(DWORD process_id, HANDLE* user_token) { |
| // Get a handle to the process. |
| scoped_process process(::OpenProcess( |
| PROCESS_DUP_HANDLE | PROCESS_QUERY_INFORMATION, |
| TRUE, |
| process_id)); |
| if (!valid(process)) { |
| HRESULT hr(HRESULTFromLastError()); |
| UTIL_LOG(LEVEL_ERROR, |
| (_T("[GetImpersonationToken - ::OpenProcess failed][0x%x]"), |
| hr)); |
| return hr; |
| } |
| |
| HRESULT result = S_OK; |
| scoped_handle process_token; |
| if (!::OpenProcessToken(get(process), TOKEN_DUPLICATE | TOKEN_QUERY, |
| address(process_token))) { |
| result = HRESULTFromLastError(); |
| } else { |
| if (!::DuplicateTokenEx(get(process_token), |
| TOKEN_IMPERSONATE | TOKEN_QUERY | |
| TOKEN_ASSIGN_PRIMARY | TOKEN_DUPLICATE, |
| NULL, |
| SecurityImpersonation, |
| TokenPrimary, |
| user_token)) { |
| result = HRESULTFromLastError(); |
| } |
| } |
| |
| ASSERT(SUCCEEDED(result), (_T("[GetImpersonationToken Failed][hr=0x%x]"), |
| result)); |
| return result; |
| } |
| |
| HRESULT Process::GetUsersOfProcesses(const TCHAR* task_name, |
| int maximum_users, |
| scoped_handle user_tokens[], |
| int* number_of_users) { |
| ASSERT1(task_name && *task_name); |
| ASSERT1(maximum_users); |
| ASSERT1(user_tokens); |
| ASSERT1(number_of_users); |
| |
| scoped_hfile th32cs_snapshot(::CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, |
| 0)); |
| if (!valid(th32cs_snapshot)) { |
| HRESULT hr(HRESULTFromLastError()); |
| UTIL_LOG(LEVEL_ERROR, (_T("[::CreateToolhelp32Snapshot fail][0x%x]"), hr)); |
| return hr; |
| } |
| |
| HRESULT result = S_OK; |
| *number_of_users = 0; |
| // Walk the list of processes. |
| PROCESSENTRY32 process = {0}; |
| process.dwSize = sizeof(PROCESSENTRY32); |
| for (BOOL found = ::Process32First(get(th32cs_snapshot), &process); found; |
| found = ::Process32Next(get(th32cs_snapshot), &process)) { |
| // Check if it is one of the processes we are looking for. |
| if (_tcsicmp(task_name, process.szExeFile) == 0) { |
| // We match. Get the user's token. |
| scoped_handle user_token; |
| if (FAILED(GetImpersonationToken(process.th32ProcessID, |
| address(user_token)))) |
| continue; |
| |
| // Search through the existing list to see if it's a duplicate. |
| // It's O(n^2) but we should have very few logged on users. |
| int i = 0; |
| for (; i < *number_of_users; i++) { |
| if (get(user_tokens[i]) == get(user_token)) { |
| // It's a duplicate. |
| break; |
| } |
| } |
| if (i >= *number_of_users) { |
| // It's a new one. Add it if there's room. |
| ASSERT1(i < maximum_users); |
| if (i < maximum_users) { |
| // Release the user_token, we don't want it to be closed |
| // by the user_token destructor |
| reset(user_tokens[(*number_of_users)++], release(user_token)); |
| } |
| } |
| } |
| } |
| return result; |
| } |
| |
| HRESULT Process::GetImagePath(const CString& process_name, |
| const CString& user_sid, |
| CString* path) { |
| ASSERT1(path); |
| |
| // Search for running processes with process_name. |
| uint32 mask = INCLUDE_ONLY_PROCESS_OWNED_BY_USER; |
| std::vector<CString> command_line; |
| std::vector<uint32> process_ids; |
| HRESULT hr = FindProcesses(mask, |
| process_name, |
| true, |
| user_sid, |
| command_line, |
| &process_ids); |
| if (FAILED(hr)) { |
| UTIL_LOG(LEVEL_WARNING, (_T("[FindProcesses failed][0x%08x]"), hr)); |
| return hr; |
| } |
| |
| if (process_ids.empty()) { |
| return E_FAIL; |
| } |
| |
| uint32 process_id = process_ids[0]; |
| UTIL_LOG(L4, (_T("[GetImagePath][pid=%d]"), process_id)); |
| scoped_process process_handle(::OpenProcess(PROCESS_QUERY_INFORMATION | |
| PROCESS_VM_READ, |
| FALSE, |
| process_id)); |
| if (!process_handle) { |
| HRESULT hr = HRESULTFromLastError(); |
| UTIL_LOG(L4, (_T("[OpenProcess failed][0x%08x]"), hr)); |
| return hr; |
| } |
| |
| HMODULE module_handle = NULL; |
| DWORD bytes_needed = 0; |
| if (!::EnumProcessModules(get(process_handle), |
| &module_handle, |
| sizeof(HMODULE), |
| &bytes_needed)) { |
| HRESULT hr = HRESULTFromLastError(); |
| UTIL_LOG(LEVEL_WARNING, (_T("[EnumProcessModules failed][0x%08x]"), hr)); |
| // ::EnumProcessModules from a WoW64 process fails for x64 processes. We try |
| // ::GetProcessImageFileName as a workaround here. |
| TCHAR image_name[MAX_PATH] = {0}; |
| if (!GetProcessImageFileName(get(process_handle), |
| image_name, |
| arraysize(image_name))) { |
| HRESULT hr = HRESULTFromLastError(); |
| UTIL_LOG(LE, (_T("[GetProcessImageFileName failed][0x%08x]"), hr)); |
| return hr; |
| } else { |
| *path = image_name; |
| return S_OK; |
| } |
| } |
| |
| TCHAR module_name[MAX_PATH] = {0}; |
| if (!::GetModuleFileNameEx(get(process_handle), |
| module_handle, |
| module_name, |
| arraysize(module_name))) { |
| HRESULT hr = HRESULTFromLastError(); |
| UTIL_LOG(LEVEL_ERROR, (_T("[GetModuleFileNameEx failed][0x%08x]"), hr)); |
| return hr; |
| } |
| |
| *path = module_name; |
| return S_OK; |
| } |
| |
| bool Process::IsWow64(uint32 pid) { |
| typedef BOOL (WINAPI *IsWow64Process)(HANDLE, BOOL*); |
| scoped_process handle(::OpenProcess(PROCESS_QUERY_INFORMATION | SYNCHRONIZE, |
| false, |
| pid)); |
| if (!handle) { |
| return false; |
| } |
| |
| HINSTANCE kernel_instance = ::GetModuleHandle(_T("kernel32.dll")); |
| if (kernel_instance == NULL) { |
| ASSERT1(false); |
| HRESULT hr = HRESULTFromLastError(); |
| UTIL_LOG(LW, (_T("[::GetModuleHandle kernel32.dll failed][0x%08x]"), hr)); |
| return false; |
| } |
| |
| IsWow64Process pfn = reinterpret_cast<IsWow64Process>(::GetProcAddress( |
| kernel_instance, |
| "IsWow64Process")); |
| if (!pfn) { |
| UTIL_LOG(LW, (_T("[::IsWow64Process() not found in kernel32.dll]"))); |
| return false; |
| } |
| |
| BOOL wow64 = FALSE; |
| if (!(*pfn)(get(handle), &wow64)) { |
| HRESULT hr = HRESULTFromLastError(); |
| UTIL_LOG(LW, (_T("[::IsWow64Process() failed][0x%08x]"), hr)); |
| return false; |
| } |
| |
| return (wow64 != 0); |
| } |
| |
| HRESULT Process::MakeProcessWindowForeground(const CString& executable) { |
| UTIL_LOG(L3, (_T("[MakeProcessWindowForeground]"))); |
| |
| CString sid; |
| HRESULT hr = omaha::user_info::GetCurrentUser(NULL, NULL, &sid); |
| if (FAILED(hr)) { |
| return hr; |
| } |
| |
| // This code does not handle two cases: |
| // 1. If a new process instance is starting up but there are other process |
| // instances running, then we will not wait for the new process instance. |
| // One way to fix this is to pass the number of expected processes to this |
| // method. |
| // 2. If we find multiple processes, and we are able to find the windows only |
| // for some of the processes (maybe because the rest are still starting up) |
| // then we will only set the windows of the one that we found to the |
| // foreground and ignore the rest. |
| bool found = false; |
| for (int retries = 0; retries < kNumRetriesToFindProcess && !found; |
| ++retries) { |
| std::vector<CString> command_lines; |
| std::vector<uint32> processes; |
| DWORD flags = EXCLUDE_CURRENT_PROCESS | INCLUDE_ONLY_PROCESS_OWNED_BY_USER; |
| hr = Process::FindProcesses(flags, |
| executable, |
| true, |
| sid, |
| command_lines, |
| &processes); |
| if (FAILED(hr)) { |
| UTIL_LOG(LW, (_T("[FindProcesses failed][0x%08x]"), hr)); |
| return hr; |
| } |
| |
| UTIL_LOG(L3, (_T("[Found %d processes]"), processes.size())); |
| for (size_t i = 0; i < processes.size(); ++i) { |
| CSimpleArray<HWND> windows; |
| if (!WindowUtils::FindProcessWindows(processes[i], 0, &windows)) { |
| UTIL_LOG(L3, (_T("[FindProcessWindows failed][0x%08x]"), hr)); |
| continue; |
| } |
| |
| for (int j = 0; j < windows.GetSize(); ++j) { |
| if (WindowUtils::IsMainWindow(windows[j])) { |
| UTIL_LOG(L4, (_T("[Found main window of process %d]"), processes[i])); |
| WindowUtils::MakeWindowForeground(windows[j]); |
| ::FlashWindow(windows[j], true); |
| found = true; |
| break; |
| } |
| } |
| } |
| |
| if (!found) { |
| ::Sleep(kFindProcessRetryIntervalMs); |
| } |
| } |
| |
| return S_OK; |
| } |
| |
| } // namespace omaha |
| |