/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ /* * This file is part of the LibreOffice project. * * This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. * * This file incorporates work covered by the following license notice: * * 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 . */ #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 # ifdef _MSC_VER # pragma warning(pop) # endif # include # undef WIN32_LEAN_AND_MEAN #endif #include "procimpl.h" #include #include #include "secimpl.h" #include "rtl/allocator.hxx" #include #include #include #include #include #include //################################################# 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 ("COMSPEC"); const rtl::OUString QUOTE("\""); namespace /* private */ { //################################################# typedef std::list 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 iterator_pair_t; typedef std::vector 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 { 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(GetEnvironmentStrings()); LPTSTR p = env; while (size_t l = _tcslen(p)) { environment->push_back(reinterpret_cast(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 memcpy( reinterpret_cast(&environment[pos]), reinterpret_cast(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 vec(path.getLength() + 1); //GetShortPathNameW only works if the file can be found! const DWORD len = GetShortPathNameW( reinterpret_cast(path.getStr()), reinterpret_cast(&vec[0]), path.getLength() + 1); if (!len && GetLastError() == ERROR_FILE_NOT_FOUND && extension.getLength()) { const rtl::OUString extPath(path + extension); std::vector vec2( extPath.getLength() + 1); const DWORD len2 = GetShortPathNameW( reinterpret_cast(extPath.getStr()), reinterpret_cast(&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::FileBase::E_None != osl::FileBase::getSystemPathFromFileURL(exe_url, exe_path)) return rtl::OUString(); exe_path = getShortPath(exe_path, rtl::OUString(".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.equalsIgnoreAsciiCaseAsciiL(RTL_CONSTASCII_STRINGPARAM("bat")) || ext.equalsIgnoreAsciiCaseAsciiL(RTL_CONSTASCII_STRINGPARAM("cmd")) || ext.equalsIgnoreAsciiCaseAsciiL(RTL_CONSTASCII_STRINGPARAM("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::FileBase::E_None != osl::FileBase::getSystemPathFromFileURL(ustrDirectory, cwd))) return osl_Process_E_InvalidError; LPCWSTR p_cwd = (cwd.getLength()) ? reinterpret_cast(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 = const_cast(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(reinterpret_cast(cmdline.getStr())), NULL, NULL, b_inherit_handles, flags, p_environment, p_cwd, &startup_info, &process_info); } else { bRet = CreateProcess( NULL, const_cast(reinterpret_cast(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( 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 occurred 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; } /* vim:set shiftwidth=4 softtabstop=4 expandtab: */