blob: 29421b1cd193be23418e1c7e46d5127066fbfd5a [file] [log] [blame]
/**************************************************************
*
* 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;
}