1 /* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
3 * This file is part of the LibreOffice project.
5 * This Source Code Form is subject to the terms of the Mozilla Public
6 * License, v. 2.0. If a copy of the MPL was not distributed with this
7 * file, You can obtain one at http://mozilla.org/MPL/2.0/.
9 * This file incorporates work covered by the following license notice:
11 * Licensed to the Apache Software Foundation (ASF) under one or more
12 * contributor license agreements. See the NOTICE file distributed
13 * with this work for additional information regarding copyright
14 * ownership. The ASF licenses this file to you under the Apache
15 * License, Version 2.0 (the "License"); you may not use this file
16 * except in compliance with the License. You may obtain a copy of
17 * the License at http://www.apache.org/licenses/LICENSE-2.0 .
20 #ifndef WIN32_LEAN_AND_MEAN
21 # define WIN32_LEAN_AND_MEAN
23 # undef WIN32_LEAN_AND_MEAN
26 #include "file-impl.hxx"
27 #include "procimpl.hxx"
28 #include <rtl/ustring.hxx>
29 #include <rtl/ustrbuf.hxx>
30 #include "secimpl.hxx"
31 #include <osl/file.hxx>
32 #include <o3tl/char16_t2wchar_t.hxx>
37 namespace /* private */
39 typedef std::vector
<rtl::OUString
> string_container_t
;
40 typedef string_container_t::iterator string_container_iterator_t
;
41 typedef string_container_t::const_iterator string_container_const_iterator_t
;
42 typedef std::vector
<sal_Unicode
> environment_container_t
;
44 /* Function object that compares two strings that are
45 expected to be environment variables in the form
46 "name=value". Only the 'name' part will be compared.
47 The comparison is in upper case and returns true
48 if the first of both strings is less than the
50 struct less_environment_variable
52 bool operator() (const rtl::OUString
& lhs
, const rtl::OUString
& rhs
) const
54 OSL_ENSURE((lhs
.indexOf(L
'=') > -1) &&
55 (rhs
.indexOf(L
'=') > -1),
56 "Malformed environment variable");
58 // Windows compares environment variables uppercase
60 return (rtl_ustr_compare_WithLength(
61 lhs
.toAsciiUpperCase().pData
->buffer
,
63 rhs
.toAsciiUpperCase().pData
->buffer
,
64 rhs
.indexOf(L
'=')) < 0);
68 /* Function object used by for_each algorithm to
69 calculate the sum of the length of all strings
70 in a string container. */
71 class sum_of_string_lengths
75 sum_of_string_lengths() : sum_(0) {}
77 void operator() (const rtl::OUString
& string
)
79 OSL_ASSERT(string
.getLength());
81 // always include the terminating '\0'
82 if (string
.getLength())
83 sum_
+= string
.getLength() + 1;
86 operator size_t () const
94 inline size_t calc_sum_of_string_lengths(const string_container_t
& string_cont
)
97 string_cont
.begin(), string_cont
.end(), sum_of_string_lengths());
100 void read_environment(/*out*/ string_container_t
* environment
)
102 // GetEnvironmentStrings returns a sorted list, Windows
103 // sorts environment variables upper case
104 LPWSTR env
= GetEnvironmentStringsW();
107 while (size_t l
= wcslen(p
))
109 environment
->push_back(o3tl::toU(p
));
112 FreeEnvironmentStringsW(env
);
114 // it is apparently possible that the environment is not completely
115 // sorted; Cygwin may append entries, which breaks the equal_range
116 std::stable_sort(environment
->begin(), environment
->end(),
117 less_environment_variable());
120 /* the environment list must be sorted, new values
121 should either replace existing ones or should be
122 added to the list, environment variables will
123 be handled case-insensitive */
124 bool create_merged_environment(
125 rtl_uString
* env_vars
[],
126 sal_uInt32 env_vars_count
,
127 /*in|out*/ string_container_t
* merged_env
)
129 OSL_ASSERT(env_vars
&& env_vars_count
> 0 && merged_env
);
131 read_environment(merged_env
);
133 for (sal_uInt32 i
= 0; i
< env_vars_count
; i
++)
135 rtl::OUString env_var
= rtl::OUString(env_vars
[i
]);
137 if (env_var
.getLength() == 0)
140 auto iter_pair
= std::equal_range(
144 less_environment_variable());
146 if (env_var
.indexOf(L
'=') == -1)
148 merged_env
->erase(iter_pair
.first
, iter_pair
.second
);
152 if (iter_pair
.first
!= iter_pair
.second
) // found
153 *iter_pair
.first
= env_var
;
155 merged_env
->insert(iter_pair
.first
, env_var
);
161 /* Create a merged environment */
162 bool setup_process_environment(
163 rtl_uString
* environment_vars
[],
164 sal_uInt32 n_environment_vars
,
165 /*in|out*/ environment_container_t
& environment
)
167 string_container_t merged_env
;
168 if (!create_merged_environment(environment_vars
, n_environment_vars
, &merged_env
))
171 // allocate enough space for the '\0'-separated environment strings and
173 environment
.resize(calc_sum_of_string_lengths(merged_env
) + 1);
175 string_container_const_iterator_t iter
= merged_env
.begin();
176 string_container_const_iterator_t iter_end
= merged_env
.end();
179 for (/**/; iter
!= iter_end
; ++iter
)
181 rtl::OUString envv
= *iter
;
183 OSL_ASSERT(envv
.getLength());
185 sal_uInt32 n
= envv
.getLength() + 1; // copy the final '\0', too
186 memcpy(&environment
[pos
], envv
.getStr(), n
* sizeof(sal_Unicode
));
189 environment
[pos
] = 0; // append a final '\0'
194 /* In contrast to the Win32 API function CreatePipe with
195 this function the caller is able to determine separately
196 which handle of the pipe is inheritable. */
199 bool b_read_pipe_inheritable
,
200 PHANDLE p_write_pipe
,
201 bool b_write_pipe_inheritable
,
202 LPVOID p_security_descriptor
= nullptr,
205 SECURITY_ATTRIBUTES sa
;
206 sa
.nLength
= sizeof(SECURITY_ATTRIBUTES
);
207 sa
.lpSecurityDescriptor
= p_security_descriptor
;
208 sa
.bInheritHandle
= b_read_pipe_inheritable
|| b_write_pipe_inheritable
;
211 HANDLE hTemp
= nullptr;
213 if (!b_read_pipe_inheritable
&& b_write_pipe_inheritable
)
215 bRet
= CreatePipe(&hTemp
, p_write_pipe
, &sa
, pipe_size
);
217 if (bRet
&& !DuplicateHandle(GetCurrentProcess(), hTemp
,
218 GetCurrentProcess(), p_read_pipe
, 0, FALSE
,
219 DUPLICATE_CLOSE_SOURCE
| DUPLICATE_SAME_ACCESS
))
222 CloseHandle(*p_read_pipe
);
226 else if (b_read_pipe_inheritable
&& !b_write_pipe_inheritable
)
228 bRet
= CreatePipe(p_read_pipe
, &hTemp
, &sa
, pipe_size
);
230 if (bRet
&& !DuplicateHandle(GetCurrentProcess(), hTemp
,
231 GetCurrentProcess(), p_write_pipe
, 0, FALSE
,
232 DUPLICATE_CLOSE_SOURCE
| DUPLICATE_SAME_ACCESS
))
235 CloseHandle(*p_write_pipe
);
241 bRet
= CreatePipe(p_read_pipe
, p_write_pipe
, &sa
, pipe_size
);
246 // Add a quote sign to the start and the end of a string
247 // if not already present
248 rtl::OUString
quote_string(const rtl::OUString
& string
)
250 rtl::OUStringBuffer quoted
;
251 if (string
.indexOf(L
'"') != 0)
254 quoted
.append(string
);
256 if (string
.lastIndexOf(L
'"') != (string
.getLength() - 1))
259 return quoted
.makeStringAndClear();
262 // The parameter path must be a system path. If it is longer than 260 characters
263 // then it is shortened using the GetShortPathName function. This function only
264 // works if the path exists. Because "path" can be the path to an executable, it
265 // may not have the file extension ".exe". However, if the file on disk has the
266 // ".exe" extension, then the function will fail. In this case a second attempt
267 // is started by adding the parameter "extension" to "path".
268 rtl::OUString
getShortPath(rtl::OUString
const & path
, rtl::OUString
const & extension
)
270 rtl::OUString
ret(path
);
271 if (path
.getLength() > 260)
273 std::vector
<sal_Unicode
> vec(path
.getLength() + 1);
274 //GetShortPathNameW only works if the file can be found!
275 const DWORD len
= GetShortPathNameW(
276 o3tl::toW(path
.getStr()), o3tl::toW(&vec
[0]), path
.getLength() + 1);
278 if (!len
&& GetLastError() == ERROR_FILE_NOT_FOUND
279 && extension
.getLength())
281 const rtl::OUString
extPath(path
+ extension
);
282 std::vector
<sal_Unicode
> vec2(
283 extPath
.getLength() + 1);
284 const DWORD len2
= GetShortPathNameW(
285 o3tl::toW(extPath
.getStr()), o3tl::toW(&vec2
[0]), extPath
.getLength() + 1);
286 ret
= rtl::OUString(&vec2
[0], len2
);
290 ret
= rtl::OUString(&vec
[0], len
);
296 // Returns the system path of the executable which can either
297 // be provided via the strImageName parameter or as first
298 // element of the strArguments list.
299 // The returned path will be quoted if it contains spaces.
300 rtl::OUString
get_executable_path(
301 rtl_uString
* image_name
,
302 rtl_uString
* cmdline_args
[],
303 sal_uInt32 n_cmdline_args
,
306 rtl::OUString exe_name
;
309 exe_name
= image_name
;
310 else if (n_cmdline_args
)
311 exe_name
= rtl::OUString(cmdline_args
[0]);
313 rtl::OUString exe_url
= exe_name
;
315 osl_searchFileURL(exe_name
.pData
, nullptr, &exe_url
.pData
);
317 rtl::OUString exe_path
;
318 if (osl::FileBase::E_None
!= osl::FileBase::getSystemPathFromFileURL(exe_url
, exe_path
))
319 return rtl::OUString();
321 exe_path
= getShortPath(exe_path
, ".exe");
323 if (exe_path
.indexOf(' ') != -1)
324 exe_path
= quote_string(exe_path
);
329 rtl::OUString
get_file_extension(const rtl::OUString
& file_name
)
331 sal_Int32 index
= file_name
.lastIndexOf('.');
332 if ((index
!= -1) && ((index
+ 1) < file_name
.getLength()))
333 return file_name
.copy(index
+ 1);
335 return rtl::OUString();
338 bool is_batch_file(const rtl::OUString
& file_name
)
340 rtl::OUString ext
= get_file_extension(file_name
);
341 return (ext
.equalsIgnoreAsciiCase("bat") ||
342 ext
.equalsIgnoreAsciiCase("cmd") ||
343 ext
.equalsIgnoreAsciiCase("btm"));
346 const rtl::OUString
ENV_COMSPEC ("COMSPEC");
347 rtl::OUString
get_batch_processor()
349 rtl::OUString comspec
;
350 osl_getEnvironment(ENV_COMSPEC
.pData
, &comspec
.pData
);
352 OSL_ASSERT(comspec
.getLength());
354 /* check if comspec path contains blanks and quote it if any */
355 if (comspec
.indexOf(' ') != -1)
356 comspec
= quote_string(comspec
);
361 } // namespace private
363 oslProcessError SAL_CALL
osl_executeProcess(
364 rtl_uString
*strImageName
,
365 rtl_uString
*strArguments
[],
366 sal_uInt32 nArguments
,
367 oslProcessOption Options
,
368 oslSecurity Security
,
369 rtl_uString
*strDirectory
,
370 rtl_uString
*strEnvironmentVars
[],
371 sal_uInt32 nEnvironmentVars
,
375 return osl_executeProcess_WithRedirectedIO(
385 nullptr, nullptr, nullptr );
388 oslProcessError SAL_CALL
osl_executeProcess_WithRedirectedIO(
389 rtl_uString
*ustrImageName
,
390 rtl_uString
*ustrArguments
[],
391 sal_uInt32 nArguments
,
392 oslProcessOption Options
,
393 oslSecurity Security
,
394 rtl_uString
*ustrDirectory
,
395 rtl_uString
*ustrEnvironmentVars
[],
396 sal_uInt32 nEnvironmentVars
,
397 oslProcess
*pProcess
,
398 oslFileHandle
*pProcessInputWrite
,
399 oslFileHandle
*pProcessOutputRead
,
400 oslFileHandle
*pProcessErrorRead
)
402 rtl::OUString exe_path
= get_executable_path(
403 ustrImageName
, ustrArguments
, nArguments
, (Options
& osl_Process_SEARCHPATH
) != 0);
405 if (0 == exe_path
.getLength())
406 return osl_Process_E_NotFound
;
408 if (pProcess
== nullptr)
409 return osl_Process_E_InvalidError
;
411 DWORD flags
= NORMAL_PRIORITY_CLASS
;
412 rtl::OUStringBuffer command_line
;
414 if (is_batch_file(exe_path
))
416 rtl::OUString batch_processor
= get_batch_processor();
418 if (batch_processor
.getLength())
420 /* cmd.exe does not work without a console window */
421 if (!(Options
& osl_Process_WAIT
) || (Options
& osl_Process_DETACHED
))
422 flags
|= CREATE_NEW_CONSOLE
;
424 command_line
.append(batch_processor
);
425 command_line
.append(" /c ");
428 // should we return here in case of error?
429 return osl_Process_E_Unknown
;
432 command_line
.append(exe_path
);
434 /* Add remaining arguments to command line. If ustrImageName is nullptr
435 the first parameter is the name of the executable so we have to
436 start at 1 instead of 0 */
437 for (sal_uInt32 n
= (nullptr != ustrImageName
) ? 0 : 1; n
< nArguments
; n
++)
439 command_line
.append(" ");
441 /* Quote arguments containing blanks */
442 if (rtl::OUString(ustrArguments
[n
]).indexOf(' ') != -1)
443 command_line
.append(quote_string(ustrArguments
[n
]));
445 command_line
.append(ustrArguments
[n
]);
448 environment_container_t environment
;
449 LPVOID p_environment
= nullptr;
451 if (nEnvironmentVars
&& ustrEnvironmentVars
)
453 if (!setup_process_environment(
454 ustrEnvironmentVars
, nEnvironmentVars
, environment
))
455 return osl_Process_E_InvalidError
;
457 flags
|= CREATE_UNICODE_ENVIRONMENT
;
458 p_environment
= &environment
[0];
462 if (ustrDirectory
&& ustrDirectory
->length
&& (osl::FileBase::E_None
!= osl::FileBase::getSystemPathFromFileURL(ustrDirectory
, cwd
)))
463 return osl_Process_E_InvalidError
;
465 LPCWSTR p_cwd
= (cwd
.getLength()) ? o3tl::toW(cwd
.getStr()) : nullptr;
467 if ((Options
& osl_Process_DETACHED
) && !(flags
& CREATE_NEW_CONSOLE
))
468 flags
|= DETACHED_PROCESS
;
470 STARTUPINFOW startup_info
;
471 memset(&startup_info
, 0, sizeof(startup_info
));
473 startup_info
.cb
= sizeof(startup_info
);
474 startup_info
.dwFlags
= STARTF_USESHOWWINDOW
;
475 startup_info
.lpDesktop
= const_cast<LPWSTR
>(L
"");
477 /* Create pipes for redirected IO */
478 HANDLE hInputRead
= nullptr;
479 HANDLE hInputWrite
= nullptr;
480 if (pProcessInputWrite
&& create_pipe(&hInputRead
, true, &hInputWrite
, false))
481 startup_info
.hStdInput
= hInputRead
;
483 HANDLE hOutputRead
= nullptr;
484 HANDLE hOutputWrite
= nullptr;
485 if (pProcessOutputRead
&& create_pipe(&hOutputRead
, false, &hOutputWrite
, true))
486 startup_info
.hStdOutput
= hOutputWrite
;
488 HANDLE hErrorRead
= nullptr;
489 HANDLE hErrorWrite
= nullptr;
490 if (pProcessErrorRead
&& create_pipe(&hErrorRead
, false, &hErrorWrite
, true))
491 startup_info
.hStdError
= hErrorWrite
;
493 bool b_inherit_handles
= false;
494 if (pProcessInputWrite
|| pProcessOutputRead
|| pProcessErrorRead
)
496 startup_info
.dwFlags
|= STARTF_USESTDHANDLES
;
497 b_inherit_handles
= true;
500 switch(Options
& (osl_Process_NORMAL
| osl_Process_HIDDEN
| osl_Process_MINIMIZED
| osl_Process_MAXIMIZED
| osl_Process_FULLSCREEN
))
502 case osl_Process_HIDDEN
:
503 startup_info
.wShowWindow
= SW_HIDE
;
504 flags
|= CREATE_NO_WINDOW
; // ignored for non-console
505 // applications; ignored on
509 case osl_Process_MINIMIZED
:
510 startup_info
.wShowWindow
= SW_MINIMIZE
;
513 case osl_Process_MAXIMIZED
:
514 case osl_Process_FULLSCREEN
:
515 startup_info
.wShowWindow
= SW_MAXIMIZE
;
519 startup_info
.wShowWindow
= SW_NORMAL
;
522 rtl::OUString cmdline
= command_line
.makeStringAndClear();
523 PROCESS_INFORMATION process_info
;
526 if ((Security
!= nullptr) && (static_cast<oslSecurityImpl
*>(Security
)->m_hToken
!= nullptr))
528 bRet
= CreateProcessAsUserW(
529 static_cast<oslSecurityImpl
*>(Security
)->m_hToken
,
530 nullptr, const_cast<LPWSTR
>(o3tl::toW(cmdline
.getStr())), nullptr, nullptr,
531 b_inherit_handles
, flags
, p_environment
, p_cwd
,
532 &startup_info
, &process_info
);
536 bRet
= CreateProcessW(
537 nullptr, const_cast<LPWSTR
>(o3tl::toW(cmdline
.getStr())), nullptr, nullptr,
538 b_inherit_handles
, flags
, p_environment
, p_cwd
,
539 &startup_info
, &process_info
);
542 /* Now we can close the pipe ends that are used by the child process */
545 CloseHandle(hInputRead
);
548 CloseHandle(hOutputWrite
);
551 CloseHandle(hErrorWrite
);
555 CloseHandle(process_info
.hThread
);
557 oslProcessImpl
* pProcImpl
= static_cast<oslProcessImpl
*>(
558 rtl_allocateMemory(sizeof(oslProcessImpl
)));
560 if (pProcImpl
!= nullptr)
562 pProcImpl
->m_hProcess
= process_info
.hProcess
;
563 pProcImpl
->m_IdProcess
= process_info
.dwProcessId
;
565 *pProcess
= static_cast<oslProcess
>(pProcImpl
);
567 if (Options
& osl_Process_WAIT
)
568 WaitForSingleObject(pProcImpl
->m_hProcess
, INFINITE
);
570 if (pProcessInputWrite
)
571 *pProcessInputWrite
= osl_createFileHandleFromOSHandle(hInputWrite
, osl_File_OpenFlag_Write
);
573 if (pProcessOutputRead
)
574 *pProcessOutputRead
= osl_createFileHandleFromOSHandle(hOutputRead
, osl_File_OpenFlag_Read
);
576 if (pProcessErrorRead
)
577 *pProcessErrorRead
= osl_createFileHandleFromOSHandle(hErrorRead
, osl_File_OpenFlag_Read
);
579 return osl_Process_E_None
;
583 /* if an error occurred we have to close the server side pipe ends too */
586 CloseHandle(hInputWrite
);
589 CloseHandle(hOutputRead
);
592 CloseHandle(hErrorRead
);
594 return osl_Process_E_Unknown
;
597 /* vim:set shiftwidth=4 softtabstop=4 expandtab: */