| /************************************************************** |
| * |
| * Licensed to the Apache Software Foundation (ASF) under one |
| * or more contributor license agreements. See the NOTICE file |
| * distributed with this work for additional information |
| * regarding copyright ownership. The ASF licenses this file |
| * to you 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. |
| * |
| *************************************************************/ |
| |
| |
| |
| // MARKER(update_precomp.py): autogen include statement, do not remove |
| #include "precompiled_sal.hxx" |
| |
| #define UNICODE |
| #define _UNICODE |
| |
| #ifndef WIN32_LEAN_AND_MEAN |
| # define WIN32_LEAN_AND_MEAN |
| # ifdef _MSC_VER |
| # pragma warning(push,1) /* disable warnings within system headers */ |
| # endif |
| # include <windows.h> |
| # ifdef _MSC_VER |
| # pragma warning(pop) |
| # endif |
| # include <tchar.h> |
| # undef WIN32_LEAN_AND_MEAN |
| #endif |
| #include "procimpl.h" |
| #include <rtl/ustring.hxx> |
| #include <rtl/ustrbuf.hxx> |
| #include "secimpl.h" |
| #include <osl/file.hxx> |
| |
| #include <list> |
| #include <vector> |
| #include <algorithm> |
| #include <string> |
| |
| //################################################# |
| extern "C" oslFileHandle SAL_CALL osl_createFileHandleFromOSHandle( HANDLE hFile, sal_uInt32 uFlags ); |
| |
| //################################################# |
| const sal_Unicode NAME_VALUE_SEPARATOR = TEXT('='); |
| const sal_Char* SPACE = " "; |
| const rtl::OUString ENV_COMSPEC = rtl::OUString::createFromAscii("COMSPEC"); |
| const rtl::OUString QUOTE = rtl::OUString::createFromAscii("\""); |
| |
| namespace /* private */ |
| { |
| //################################################# |
| typedef std::list<rtl::OUString> string_container_t; |
| typedef string_container_t::iterator string_container_iterator_t; |
| typedef string_container_t::const_iterator string_container_const_iterator_t; |
| typedef std::pair<string_container_iterator_t, string_container_iterator_t> iterator_pair_t; |
| typedef std::vector<sal_Unicode > environment_container_t; |
| |
| //################################################# |
| /* Function object that compares two strings that are |
| expected to be environment variables in the form |
| "name=value". Only the 'name' part will be compared. |
| The comparison is in upper case and returns true |
| if the first of both strings is less than the |
| second one. */ |
| struct less_environment_variable : |
| public std::binary_function<rtl::OUString, rtl::OUString, bool> |
| { |
| bool operator() (const rtl::OUString& lhs, const rtl::OUString& rhs) const |
| { |
| OSL_ENSURE((lhs.indexOf(NAME_VALUE_SEPARATOR) > -1) && \ |
| (rhs.indexOf(NAME_VALUE_SEPARATOR) > -1), \ |
| "Malformed environment variable"); |
| |
| // Windows compares environment variables uppercase |
| // so we do it, too |
| return (rtl_ustr_compare_WithLength( |
| lhs.toAsciiUpperCase().pData->buffer, |
| lhs.indexOf(NAME_VALUE_SEPARATOR), |
| rhs.toAsciiUpperCase().pData->buffer, |
| rhs.indexOf(NAME_VALUE_SEPARATOR)) < 0); |
| } |
| }; |
| |
| //################################################# |
| /* Function object used by for_each algorithm to |
| calculate the sum of the length of all strings |
| in a string container. */ |
| class sum_of_string_lengths |
| { |
| public: |
| //-------------------------------- |
| sum_of_string_lengths() : sum_(0) {} |
| |
| //-------------------------------- |
| void operator() (const rtl::OUString& string) |
| { |
| OSL_ASSERT(string.getLength()); |
| |
| // always include the terminating '\0' |
| if (string.getLength()) |
| sum_ += string.getLength() + 1; |
| } |
| |
| //-------------------------------- |
| operator size_t () const |
| { |
| return sum_; |
| } |
| private: |
| size_t sum_; |
| }; |
| |
| //################################################# |
| inline size_t calc_sum_of_string_lengths(const string_container_t& string_cont) |
| { |
| return std::for_each( |
| string_cont.begin(), string_cont.end(), sum_of_string_lengths()); |
| } |
| |
| //################################################# |
| void read_environment(/*out*/ string_container_t* environment) |
| { |
| // GetEnvironmentStrings returns a sorted list, Windows |
| // sorts environment variables upper case |
| LPTSTR env = reinterpret_cast<LPTSTR>(GetEnvironmentStrings()); |
| LPTSTR p = env; |
| |
| while (size_t l = _tcslen(p)) |
| { |
| environment->push_back(reinterpret_cast<const sal_Unicode*>(p)); |
| p += l + 1; |
| } |
| FreeEnvironmentStrings(env); |
| } |
| |
| //################################################# |
| /* the environment list must be sorted, new values |
| should either replace existing ones or should be |
| added to the list, environment variables will |
| be handled case-insensitive */ |
| bool create_merged_environment( |
| rtl_uString* env_vars[], |
| sal_uInt32 env_vars_count, |
| /*in|out*/ string_container_t* merged_env) |
| { |
| OSL_ASSERT(env_vars && env_vars_count > 0 && merged_env); |
| |
| read_environment(merged_env); |
| |
| for (sal_uInt32 i = 0; i < env_vars_count; i++) |
| { |
| rtl::OUString env_var = rtl::OUString(env_vars[i]); |
| |
| if (env_var.getLength() == 0) |
| return false; |
| |
| iterator_pair_t iter_pair = std::equal_range( |
| merged_env->begin(), |
| merged_env->end(), |
| env_var, |
| less_environment_variable()); |
| |
| if (env_var.indexOf(NAME_VALUE_SEPARATOR) == -1) |
| { |
| merged_env->erase(iter_pair.first, iter_pair.second); |
| } |
| else |
| { |
| if (iter_pair.first != iter_pair.second) // found |
| *iter_pair.first = env_var; |
| else // not found |
| merged_env->insert(iter_pair.first, env_var); |
| } |
| } |
| return true; |
| } |
| |
| //################################################# |
| /* Create a merged environment */ |
| bool setup_process_environment( |
| rtl_uString* environment_vars[], |
| sal_uInt32 n_environment_vars, |
| /*in|out*/ environment_container_t& environment) |
| { |
| string_container_t merged_env; |
| if (!create_merged_environment(environment_vars, n_environment_vars, &merged_env)) |
| return false; |
| |
| // allocate enough space for the '\0'-separated environment strings and |
| // a final '\0' |
| environment.resize(calc_sum_of_string_lengths(merged_env) + 1); |
| |
| string_container_const_iterator_t iter = merged_env.begin(); |
| string_container_const_iterator_t iter_end = merged_env.end(); |
| |
| sal_uInt32 pos = 0; |
| for (/**/; iter != iter_end; ++iter) |
| { |
| rtl::OUString envv = *iter; |
| |
| OSL_ASSERT(envv.getLength()); |
| |
| sal_uInt32 n = envv.getLength() + 1; // copy the final '\0', too |
| rtl_copyMemory( |
| reinterpret_cast<void*>(&environment[pos]), |
| reinterpret_cast<const void*>(envv.getStr()), |
| n * sizeof(sal_Unicode)); |
| pos += n; |
| } |
| environment[pos] = 0; // append a final '\0' |
| |
| return true; |
| } |
| |
| //########################################################## |
| /* In contrast to the Win32 API function CreatePipe with |
| this function the caller is able to determine separately |
| which handle of the pipe is inheritable. */ |
| bool create_pipe( |
| PHANDLE p_read_pipe, |
| bool b_read_pipe_inheritable, |
| PHANDLE p_write_pipe, |
| bool b_write_pipe_inheritable, |
| LPVOID p_security_descriptor = NULL, |
| DWORD pipe_size = 0) |
| { |
| SECURITY_ATTRIBUTES sa; |
| sa.nLength = sizeof(SECURITY_ATTRIBUTES); |
| sa.lpSecurityDescriptor = p_security_descriptor; |
| sa.bInheritHandle = b_read_pipe_inheritable || b_write_pipe_inheritable; |
| |
| BOOL bRet = FALSE; |
| HANDLE hTemp = NULL; |
| |
| if (!b_read_pipe_inheritable && b_write_pipe_inheritable) |
| { |
| bRet = CreatePipe(&hTemp, p_write_pipe, &sa, pipe_size); |
| |
| if (bRet && !DuplicateHandle(GetCurrentProcess(), hTemp, |
| GetCurrentProcess(), p_read_pipe, 0, FALSE, |
| DUPLICATE_CLOSE_SOURCE | DUPLICATE_SAME_ACCESS)) |
| { |
| CloseHandle(hTemp); |
| CloseHandle(*p_read_pipe); |
| return false; |
| } |
| } |
| else if (b_read_pipe_inheritable && !b_write_pipe_inheritable) |
| { |
| bRet = CreatePipe(p_read_pipe, &hTemp, &sa, pipe_size); |
| |
| if (bRet && !DuplicateHandle(GetCurrentProcess(), hTemp, |
| GetCurrentProcess(), p_write_pipe, 0, FALSE, |
| DUPLICATE_CLOSE_SOURCE | DUPLICATE_SAME_ACCESS)) |
| { |
| CloseHandle(hTemp); |
| CloseHandle(*p_write_pipe); |
| return false; |
| } |
| } |
| else |
| { |
| bRet = CreatePipe(p_read_pipe, p_write_pipe, &sa, pipe_size); |
| } |
| return bRet; |
| } |
| |
| //######################################################### |
| // Add a quote sign to the start and the end of a string |
| // if not already present |
| rtl::OUString quote_string(const rtl::OUString& string) |
| { |
| rtl::OUStringBuffer quoted; |
| if (string.indexOf(QUOTE) != 0) |
| quoted.append(QUOTE); |
| |
| quoted.append(string); |
| |
| if (string.lastIndexOf(QUOTE) != (string.getLength() - 1)) |
| quoted.append(QUOTE); |
| |
| return quoted.makeStringAndClear(); |
| } |
| |
| //The parameter path must be a system path. If it is longer than 260 characters |
| //then it is shortened using the GetShortPathName function. This function only |
| //works if the path exists. Because "path" can be the path to an executable, it |
| //may not have the file extension ".exe". However, if the file on disk has the |
| //".exe" extension, then the function will fail. In this case a second attempt |
| //is started by adding the parameter "extension" to "path". |
| rtl::OUString getShortPath(rtl::OUString const & path, rtl::OUString const & extension) |
| { |
| rtl::OUString ret(path); |
| if (path.getLength() > 260) |
| { |
| std::vector<sal_Unicode> vec(path.getLength() + 1); |
| //GetShortPathNameW only works if the file can be found! |
| const DWORD len = GetShortPathNameW( |
| reinterpret_cast<LPCWSTR>(path.getStr()), reinterpret_cast<LPWSTR>(&vec[0]), path.getLength() + 1); |
| |
| if (!len && GetLastError() == ERROR_FILE_NOT_FOUND |
| && extension.getLength()) |
| { |
| const rtl::OUString extPath(path + extension); |
| std::vector<sal_Unicode > vec2( extPath.getLength() + 1); |
| const DWORD len2 = GetShortPathNameW( |
| reinterpret_cast<LPCWSTR>(extPath.getStr()), reinterpret_cast<LPWSTR>(&vec2[0]), extPath.getLength() + 1); |
| ret = rtl::OUString(&vec2[0], len2); |
| } |
| else |
| { |
| ret = rtl::OUString(&vec[0], len); |
| } |
| } |
| return ret; |
| } |
| //########################################################## |
| // Returns the system path of the executable which can either |
| // be provided via the strImageName parameter or as first |
| // element of the strArguments list. |
| // The returned path will be quoted if it contains spaces. |
| rtl::OUString get_executable_path( |
| rtl_uString* image_name, |
| rtl_uString* cmdline_args[], |
| sal_uInt32 n_cmdline_args, |
| bool search_path) |
| { |
| rtl::OUString exe_name; |
| |
| if (image_name) |
| exe_name = image_name; |
| else if (n_cmdline_args) |
| exe_name = rtl::OUString(cmdline_args[0]); |
| |
| rtl::OUString exe_url = exe_name; |
| if (search_path) |
| osl_searchFileURL(exe_name.pData, NULL, &exe_url.pData); |
| |
| rtl::OUString exe_path; |
| if (osl_File_E_None != osl::FileBase::getSystemPathFromFileURL(exe_url, exe_path)) |
| return rtl::OUString(); |
| |
| exe_path = getShortPath(exe_path, rtl::OUString(RTL_CONSTASCII_USTRINGPARAM(".exe"))); |
| |
| if (exe_path.indexOf(' ') != -1) |
| exe_path = quote_string(exe_path); |
| |
| return exe_path; |
| } |
| |
| //########################################################## |
| rtl::OUString get_file_extension(const rtl::OUString& file_name) |
| { |
| sal_Int32 index = file_name.lastIndexOf('.'); |
| if ((index != -1) && ((index + 1) < file_name.getLength())) |
| return file_name.copy(index + 1); |
| |
| return rtl::OUString(); |
| } |
| |
| //########################################################## |
| bool is_batch_file(const rtl::OUString& file_name) |
| { |
| rtl::OUString ext = get_file_extension(file_name); |
| return (ext.equalsIgnoreAsciiCaseAscii("bat") || |
| ext.equalsIgnoreAsciiCaseAscii("cmd") || |
| ext.equalsIgnoreAsciiCaseAscii("btm")); |
| } |
| |
| //########################################################## |
| rtl::OUString get_batch_processor() |
| { |
| rtl::OUString comspec; |
| osl_getEnvironment(ENV_COMSPEC.pData, &comspec.pData); |
| |
| OSL_ASSERT(comspec.getLength()); |
| |
| /* check if comspec path contains blanks and quote it if any */ |
| if (comspec.indexOf(' ') != -1) |
| comspec = quote_string(comspec); |
| |
| return comspec; |
| } |
| |
| } // namespace private |
| |
| |
| //################################################# |
| oslProcessError SAL_CALL osl_executeProcess( |
| rtl_uString *strImageName, |
| rtl_uString *strArguments[], |
| sal_uInt32 nArguments, |
| oslProcessOption Options, |
| oslSecurity Security, |
| rtl_uString *strDirectory, |
| rtl_uString *strEnvironmentVars[], |
| sal_uInt32 nEnvironmentVars, |
| oslProcess *pProcess |
| ) |
| { |
| return osl_executeProcess_WithRedirectedIO( |
| strImageName, |
| strArguments, |
| nArguments, |
| Options, |
| Security, |
| strDirectory, |
| strEnvironmentVars, |
| nEnvironmentVars, |
| pProcess, |
| NULL, NULL, NULL ); |
| } |
| |
| //################################################# |
| oslProcessError SAL_CALL osl_executeProcess_WithRedirectedIO( |
| rtl_uString *ustrImageName, |
| rtl_uString *ustrArguments[], |
| sal_uInt32 nArguments, |
| oslProcessOption Options, |
| oslSecurity Security, |
| rtl_uString *ustrDirectory, |
| rtl_uString *ustrEnvironmentVars[], |
| sal_uInt32 nEnvironmentVars, |
| oslProcess *pProcess, |
| oslFileHandle *pProcessInputWrite, |
| oslFileHandle *pProcessOutputRead, |
| oslFileHandle *pProcessErrorRead) |
| { |
| rtl::OUString exe_path = get_executable_path( |
| ustrImageName, ustrArguments, nArguments, (Options & osl_Process_SEARCHPATH)); |
| |
| if (0 == exe_path.getLength()) |
| return osl_Process_E_NotFound; |
| |
| if (pProcess == NULL) |
| return osl_Process_E_InvalidError; |
| |
| DWORD flags = NORMAL_PRIORITY_CLASS; |
| rtl::OUStringBuffer command_line; |
| |
| if (is_batch_file(exe_path)) |
| { |
| rtl::OUString batch_processor = get_batch_processor(); |
| |
| if (batch_processor.getLength()) |
| { |
| /* cmd.exe does not work without a console window */ |
| if (!(Options & osl_Process_WAIT) || (Options & osl_Process_DETACHED)) |
| flags |= CREATE_NEW_CONSOLE; |
| |
| command_line.append(batch_processor); |
| command_line.appendAscii(" /c "); |
| } |
| else |
| // should we return here in case of error? |
| return osl_Process_E_Unknown; |
| } |
| |
| command_line.append(exe_path); |
| |
| /* Add remaining arguments to command line. If ustrImageName is NULL |
| the first parameter is the name of the executable so we have to |
| start at 1 instead of 0 */ |
| for (sal_uInt32 n = (NULL != ustrImageName) ? 0 : 1; n < nArguments; n++) |
| { |
| command_line.appendAscii(SPACE); |
| |
| /* Quote arguments containing blanks */ |
| if (rtl::OUString(ustrArguments[n]).indexOf(' ') != -1) |
| command_line.append(quote_string(ustrArguments[n])); |
| else |
| command_line.append(ustrArguments[n]); |
| } |
| |
| environment_container_t environment; |
| LPVOID p_environment = NULL; |
| |
| if (nEnvironmentVars && ustrEnvironmentVars) |
| { |
| if (!setup_process_environment( |
| ustrEnvironmentVars, nEnvironmentVars, environment)) |
| return osl_Process_E_InvalidError; |
| |
| flags |= CREATE_UNICODE_ENVIRONMENT; |
| p_environment = &environment[0]; |
| } |
| |
| rtl::OUString cwd; |
| if (ustrDirectory && ustrDirectory->length && (osl_File_E_None != osl::FileBase::getSystemPathFromFileURL(ustrDirectory, cwd))) |
| return osl_Process_E_InvalidError; |
| |
| LPCWSTR p_cwd = (cwd.getLength()) ? reinterpret_cast<LPCWSTR>(cwd.getStr()) : NULL; |
| |
| if ((Options & osl_Process_DETACHED) && !(flags & CREATE_NEW_CONSOLE)) |
| flags |= DETACHED_PROCESS; |
| |
| STARTUPINFO startup_info; |
| memset(&startup_info, 0, sizeof(STARTUPINFO)); |
| |
| startup_info.cb = sizeof(STARTUPINFO); |
| startup_info.dwFlags = STARTF_USESHOWWINDOW; |
| startup_info.lpDesktop = L""; |
| |
| /* Create pipes for redirected IO */ |
| HANDLE hInputRead = NULL; |
| HANDLE hInputWrite = NULL; |
| if (pProcessInputWrite && create_pipe(&hInputRead, true, &hInputWrite, false)) |
| startup_info.hStdInput = hInputRead; |
| |
| HANDLE hOutputRead = NULL; |
| HANDLE hOutputWrite = NULL; |
| if (pProcessOutputRead && create_pipe(&hOutputRead, false, &hOutputWrite, true)) |
| startup_info.hStdOutput = hOutputWrite; |
| |
| HANDLE hErrorRead = NULL; |
| HANDLE hErrorWrite = NULL; |
| if (pProcessErrorRead && create_pipe(&hErrorRead, false, &hErrorWrite, true)) |
| startup_info.hStdError = hErrorWrite; |
| |
| bool b_inherit_handles = false; |
| if (pProcessInputWrite || pProcessOutputRead || pProcessErrorRead) |
| { |
| startup_info.dwFlags |= STARTF_USESTDHANDLES; |
| b_inherit_handles = true; |
| } |
| |
| switch(Options & (osl_Process_NORMAL | osl_Process_HIDDEN | osl_Process_MINIMIZED | osl_Process_MAXIMIZED | osl_Process_FULLSCREEN)) |
| { |
| case osl_Process_HIDDEN: |
| startup_info.wShowWindow = SW_HIDE; |
| flags |= CREATE_NO_WINDOW; // ignored for non-console |
| // applications; ignored on |
| // Win9x |
| break; |
| |
| case osl_Process_MINIMIZED: |
| startup_info.wShowWindow = SW_MINIMIZE; |
| break; |
| |
| case osl_Process_MAXIMIZED: |
| case osl_Process_FULLSCREEN: |
| startup_info.wShowWindow = SW_MAXIMIZE; |
| break; |
| |
| default: |
| startup_info.wShowWindow = SW_NORMAL; |
| } |
| |
| rtl::OUString cmdline = command_line.makeStringAndClear(); |
| PROCESS_INFORMATION process_info; |
| BOOL bRet = FALSE; |
| |
| if ((Security != NULL) && (((oslSecurityImpl*)Security)->m_hToken != NULL)) |
| { |
| bRet = CreateProcessAsUser( |
| ((oslSecurityImpl*)Security)->m_hToken, |
| NULL, const_cast<LPTSTR>(reinterpret_cast<LPCTSTR>(cmdline.getStr())), NULL, NULL, |
| b_inherit_handles, flags, p_environment, p_cwd, |
| &startup_info, &process_info); |
| } |
| else |
| { |
| bRet = CreateProcess( |
| NULL, const_cast<LPTSTR>(reinterpret_cast<LPCTSTR>(cmdline.getStr())), NULL, NULL, |
| b_inherit_handles, flags, p_environment, p_cwd, |
| &startup_info, &process_info); |
| } |
| |
| /* Now we can close the pipe ends that are used by the child process */ |
| |
| if (hInputRead) |
| CloseHandle(hInputRead); |
| |
| if (hOutputWrite) |
| CloseHandle(hOutputWrite); |
| |
| if (hErrorWrite) |
| CloseHandle(hErrorWrite); |
| |
| if (bRet) |
| { |
| CloseHandle(process_info.hThread); |
| |
| oslProcessImpl* pProcImpl = reinterpret_cast<oslProcessImpl*>( |
| rtl_allocateMemory(sizeof(oslProcessImpl))); |
| |
| if (pProcImpl != NULL) |
| { |
| pProcImpl->m_hProcess = process_info.hProcess; |
| pProcImpl->m_IdProcess = process_info.dwProcessId; |
| |
| *pProcess = (oslProcess)pProcImpl; |
| |
| if (Options & osl_Process_WAIT) |
| WaitForSingleObject(pProcImpl->m_hProcess, INFINITE); |
| |
| if (pProcessInputWrite) |
| *pProcessInputWrite = osl_createFileHandleFromOSHandle(hInputWrite, osl_File_OpenFlag_Write); |
| |
| if (pProcessOutputRead) |
| *pProcessOutputRead = osl_createFileHandleFromOSHandle(hOutputRead, osl_File_OpenFlag_Read); |
| |
| if (pProcessErrorRead) |
| *pProcessErrorRead = osl_createFileHandleFromOSHandle(hErrorRead, osl_File_OpenFlag_Read); |
| |
| return osl_Process_E_None; |
| } |
| } |
| |
| /* if an error occured we have to close the server side pipe ends too */ |
| |
| if (hInputWrite) |
| CloseHandle(hInputWrite); |
| |
| if (hOutputRead) |
| CloseHandle(hOutputRead); |
| |
| if (hErrorRead) |
| CloseHandle(hErrorRead); |
| |
| return osl_Process_E_Unknown; |
| } |