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/alloc.h>
29 #include <rtl/ustring.hxx>
30 #include <rtl/ustrbuf.hxx>
31 #include "secimpl.hxx"
32 #include <osl/file.hxx>
33 #include <o3tl/char16_t2wchar_t.hxx>
38 namespace /* private */
40 /* Function object that compares two strings that are
41 expected to be environment variables in the form
42 "name=value". Only the 'name' part will be compared.
43 The comparison is in upper case and returns true
44 if the first of both strings is less than the
46 struct less_environment_variable
48 bool operator() (const OUString
& lhs
, const OUString
& rhs
) const
50 OSL_ENSURE((lhs
.indexOf(L
'=') > -1) &&
51 (rhs
.indexOf(L
'=') > -1),
52 "Malformed environment variable");
54 // Windows compares environment variables uppercase
56 return (rtl_ustr_compare_WithLength(
57 lhs
.toAsciiUpperCase().pData
->buffer
,
59 rhs
.toAsciiUpperCase().pData
->buffer
,
60 rhs
.indexOf(L
'=')) < 0);
64 /* Function object used by for_each algorithm to
65 calculate the sum of the length of all strings
66 in a string container. */
67 class sum_of_string_lengths
71 sum_of_string_lengths() : sum_(0) {}
73 void operator() (const OUString
& string
)
75 OSL_ASSERT(string
.getLength());
77 // always include the terminating '\0'
78 if (string
.getLength())
79 sum_
+= string
.getLength() + 1;
82 operator size_t () const
90 size_t calc_sum_of_string_lengths(const std::vector
<OUString
>& string_cont
)
93 string_cont
.begin(), string_cont
.end(), sum_of_string_lengths());
96 void read_environment(/*out*/ std::vector
<OUString
>* environment
)
98 // GetEnvironmentStrings returns a sorted list, Windows
99 // sorts environment variables upper case
100 LPWSTR env
= GetEnvironmentStringsW();
103 while (size_t l
= wcslen(p
))
105 environment
->push_back(OUString(o3tl::toU(p
)));
108 FreeEnvironmentStringsW(env
);
110 // it is apparently possible that the environment is not completely
111 // sorted; Cygwin may append entries, which breaks the equal_range
112 std::stable_sort(environment
->begin(), environment
->end(),
113 less_environment_variable());
116 /* the environment list must be sorted, new values
117 should either replace existing ones or should be
118 added to the list, environment variables will
119 be handled case-insensitive */
120 bool create_merged_environment(
121 rtl_uString
* env_vars
[],
122 sal_uInt32 env_vars_count
,
123 /*in|out*/ std::vector
<OUString
>* merged_env
)
125 OSL_ASSERT(env_vars
&& env_vars_count
> 0 && merged_env
);
127 read_environment(merged_env
);
129 for (sal_uInt32 i
= 0; i
< env_vars_count
; i
++)
131 OUString
env_var(env_vars
[i
]);
133 if (env_var
.getLength() == 0)
136 auto iter_pair
= std::equal_range(
140 less_environment_variable());
142 if (env_var
.indexOf(L
'=') == -1)
144 merged_env
->erase(iter_pair
.first
, iter_pair
.second
);
148 if (iter_pair
.first
!= iter_pair
.second
) // found
149 *iter_pair
.first
= env_var
;
151 merged_env
->insert(iter_pair
.first
, env_var
);
157 /* Create a merged environment */
158 bool setup_process_environment(
159 rtl_uString
* environment_vars
[],
160 sal_uInt32 n_environment_vars
,
161 /*in|out*/ std::vector
<sal_Unicode
>& environment
)
163 std::vector
<OUString
> merged_env
;
164 if (!create_merged_environment(environment_vars
, n_environment_vars
, &merged_env
))
167 // allocate enough space for the '\0'-separated environment strings and
169 environment
.resize(calc_sum_of_string_lengths(merged_env
) + 1);
172 for (auto& envv
: merged_env
)
174 OSL_ASSERT(envv
.getLength());
176 sal_uInt32 n
= envv
.getLength() + 1; // copy the final '\0', too
177 memcpy(&environment
[pos
], envv
.getStr(), n
* sizeof(sal_Unicode
));
180 environment
[pos
] = 0; // append a final '\0'
185 /* In contrast to the Win32 API function CreatePipe with
186 this function the caller is able to determine separately
187 which handle of the pipe is inheritable. */
190 bool b_read_pipe_inheritable
,
191 PHANDLE p_write_pipe
,
192 bool b_write_pipe_inheritable
,
193 LPVOID p_security_descriptor
= nullptr,
196 SECURITY_ATTRIBUTES sa
;
197 sa
.nLength
= sizeof(SECURITY_ATTRIBUTES
);
198 sa
.lpSecurityDescriptor
= p_security_descriptor
;
199 sa
.bInheritHandle
= b_read_pipe_inheritable
|| b_write_pipe_inheritable
;
202 HANDLE hTemp
= nullptr;
204 if (!b_read_pipe_inheritable
&& b_write_pipe_inheritable
)
206 bRet
= CreatePipe(&hTemp
, p_write_pipe
, &sa
, pipe_size
);
208 if (bRet
&& !DuplicateHandle(GetCurrentProcess(), hTemp
,
209 GetCurrentProcess(), p_read_pipe
, 0, FALSE
,
210 DUPLICATE_CLOSE_SOURCE
| DUPLICATE_SAME_ACCESS
))
213 CloseHandle(*p_read_pipe
);
217 else if (b_read_pipe_inheritable
&& !b_write_pipe_inheritable
)
219 bRet
= CreatePipe(p_read_pipe
, &hTemp
, &sa
, pipe_size
);
221 if (bRet
&& !DuplicateHandle(GetCurrentProcess(), hTemp
,
222 GetCurrentProcess(), p_write_pipe
, 0, FALSE
,
223 DUPLICATE_CLOSE_SOURCE
| DUPLICATE_SAME_ACCESS
))
226 CloseHandle(*p_write_pipe
);
232 bRet
= CreatePipe(p_read_pipe
, p_write_pipe
, &sa
, pipe_size
);
237 // Add a quote sign to the start and the end of a string
238 // if not already present
239 OUString
quote_string(const OUString
& string
)
241 OUStringBuffer quoted
;
242 if (string
.indexOf(L
'"') != 0)
245 quoted
.append(string
);
247 if (string
.lastIndexOf(L
'"') != (string
.getLength() - 1))
250 return quoted
.makeStringAndClear();
253 // The parameter path must be a system path. If it is longer than 260 characters
254 // then it is shortened using the GetShortPathName function. This function only
255 // works if the path exists. Because "path" can be the path to an executable, it
256 // may not have the file extension ".exe". However, if the file on disk has the
257 // ".exe" extension, then the function will fail. In this case a second attempt
258 // is started by adding the parameter "extension" to "path".
259 OUString
getShortPath(OUString
const & path
, OUString
const & extension
)
262 if (path
.getLength() > 260)
264 std::vector
<sal_Unicode
> vec(path
.getLength() + 1);
265 //GetShortPathNameW only works if the file can be found!
266 const DWORD len
= GetShortPathNameW(
267 o3tl::toW(path
.getStr()), o3tl::toW(vec
.data()), path
.getLength() + 1);
269 if (!len
&& GetLastError() == ERROR_FILE_NOT_FOUND
270 && extension
.getLength())
272 const OUString
extPath(path
+ extension
);
273 std::vector
<sal_Unicode
> vec2(
274 extPath
.getLength() + 1);
275 const DWORD len2
= GetShortPathNameW(
276 o3tl::toW(extPath
.getStr()), o3tl::toW(vec2
.data()), extPath
.getLength() + 1);
277 ret
= OUString(vec2
.data(), len2
);
281 ret
= OUString(vec
.data(), len
);
287 // Returns the system path of the executable which can either
288 // be provided via the strImageName parameter or as first
289 // element of the strArguments list.
290 // The returned path will be quoted if it contains spaces.
291 OUString
get_executable_path(
292 rtl_uString
* image_name
,
293 rtl_uString
* cmdline_args
[],
294 sal_uInt32 n_cmdline_args
,
300 exe_name
= image_name
;
301 else if (n_cmdline_args
)
302 exe_name
= OUString(cmdline_args
[0]);
304 OUString exe_url
= exe_name
;
306 osl_searchFileURL(exe_name
.pData
, nullptr, &exe_url
.pData
);
309 if (osl::FileBase::E_None
!= osl::FileBase::getSystemPathFromFileURL(exe_url
, exe_path
))
312 exe_path
= getShortPath(exe_path
, ".exe");
314 if (exe_path
.indexOf(' ') != -1)
315 exe_path
= quote_string(exe_path
);
320 OUString
get_file_extension(const OUString
& file_name
)
323 if ((file_name
.indexOf(L
'"') == 0) && (file_name
.lastIndexOf(L
'"') == (file_name
.getLength() - 1)))
325 sal_Int32 index
= file_name
.lastIndexOf('.');
326 if ((index
!= -1) && ((index
+ 2) < file_name
.getLength()))
327 return file_name
.copy(index
+ 1, file_name
.getLength() - (index
+ 2));
329 // Unquoted file name
332 sal_Int32 index
= file_name
.lastIndexOf('.');
333 if ((index
!= -1) && ((index
+ 1) < file_name
.getLength()))
334 return file_name
.copy(index
+ 1);
339 bool is_batch_file(const OUString
& file_name
)
341 OUString ext
= get_file_extension(file_name
);
342 return (ext
.equalsIgnoreAsciiCase("bat") ||
343 ext
.equalsIgnoreAsciiCase("cmd") ||
344 ext
.equalsIgnoreAsciiCase("btm"));
347 const OUString
ENV_COMSPEC ("COMSPEC");
348 OUString
get_batch_processor()
351 osl_getEnvironment(ENV_COMSPEC
.pData
, &comspec
.pData
);
353 OSL_ASSERT(comspec
.getLength());
355 /* check if comspec path contains blanks and quote it if any */
356 if (comspec
.indexOf(' ') != -1)
357 comspec
= quote_string(comspec
);
362 } // namespace private
364 oslProcessError SAL_CALL
osl_executeProcess(
365 rtl_uString
*strImageName
,
366 rtl_uString
*strArguments
[],
367 sal_uInt32 nArguments
,
368 oslProcessOption Options
,
369 oslSecurity Security
,
370 rtl_uString
*strDirectory
,
371 rtl_uString
*strEnvironmentVars
[],
372 sal_uInt32 nEnvironmentVars
,
376 return osl_executeProcess_WithRedirectedIO(
386 nullptr, nullptr, nullptr );
389 oslProcessError SAL_CALL
osl_executeProcess_WithRedirectedIO(
390 rtl_uString
*ustrImageName
,
391 rtl_uString
*ustrArguments
[],
392 sal_uInt32 nArguments
,
393 oslProcessOption Options
,
394 oslSecurity Security
,
395 rtl_uString
*ustrDirectory
,
396 rtl_uString
*ustrEnvironmentVars
[],
397 sal_uInt32 nEnvironmentVars
,
398 oslProcess
*pProcess
,
399 oslFileHandle
*pProcessInputWrite
,
400 oslFileHandle
*pProcessOutputRead
,
401 oslFileHandle
*pProcessErrorRead
)
403 OUString exe_path
= get_executable_path(
404 ustrImageName
, ustrArguments
, nArguments
, (Options
& osl_Process_SEARCHPATH
) != 0);
406 if (0 == exe_path
.getLength())
407 return osl_Process_E_NotFound
;
409 if (pProcess
== nullptr)
410 return osl_Process_E_InvalidError
;
412 DWORD flags
= NORMAL_PRIORITY_CLASS
;
413 OUStringBuffer command_line
;
415 if (is_batch_file(exe_path
))
417 OUString batch_processor
= get_batch_processor();
419 if (batch_processor
.getLength())
421 /* cmd.exe does not work without a console window */
422 if (!(Options
& osl_Process_WAIT
) || (Options
& osl_Process_DETACHED
))
423 flags
|= CREATE_NEW_CONSOLE
;
425 command_line
.append(batch_processor
+ " /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 (OUString(ustrArguments
[n
]).indexOf(' ') != -1)
443 command_line
.append(quote_string(ustrArguments
[n
]));
445 command_line
.append(ustrArguments
[n
]);
448 std::vector
<sal_Unicode
> 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
.data();
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 startup_info
.cb
= sizeof(startup_info
);
472 startup_info
.dwFlags
= STARTF_USESHOWWINDOW
;
473 startup_info
.lpDesktop
= const_cast<LPWSTR
>(L
"");
475 /* Create pipes for redirected IO */
476 HANDLE hInputRead
= nullptr;
477 HANDLE hInputWrite
= nullptr;
478 if (pProcessInputWrite
&& create_pipe(&hInputRead
, true, &hInputWrite
, false))
479 startup_info
.hStdInput
= hInputRead
;
481 HANDLE hOutputRead
= nullptr;
482 HANDLE hOutputWrite
= nullptr;
483 if (pProcessOutputRead
&& create_pipe(&hOutputRead
, false, &hOutputWrite
, true))
484 startup_info
.hStdOutput
= hOutputWrite
;
486 HANDLE hErrorRead
= nullptr;
487 HANDLE hErrorWrite
= nullptr;
488 if (pProcessErrorRead
&& create_pipe(&hErrorRead
, false, &hErrorWrite
, true))
489 startup_info
.hStdError
= hErrorWrite
;
491 bool b_inherit_handles
= false;
492 if (pProcessInputWrite
|| pProcessOutputRead
|| pProcessErrorRead
)
494 startup_info
.dwFlags
|= STARTF_USESTDHANDLES
;
495 b_inherit_handles
= true;
498 switch(Options
& (osl_Process_NORMAL
| osl_Process_HIDDEN
| osl_Process_MINIMIZED
| osl_Process_MAXIMIZED
| osl_Process_FULLSCREEN
))
500 case osl_Process_HIDDEN
:
501 startup_info
.wShowWindow
= SW_HIDE
;
502 flags
|= CREATE_NO_WINDOW
; // ignored for non-console
503 // applications; ignored on
507 case osl_Process_MINIMIZED
:
508 startup_info
.wShowWindow
= SW_MINIMIZE
;
511 case osl_Process_MAXIMIZED
:
512 case osl_Process_FULLSCREEN
:
513 startup_info
.wShowWindow
= SW_MAXIMIZE
;
517 startup_info
.wShowWindow
= SW_NORMAL
;
520 OUString cmdline
= command_line
.makeStringAndClear();
521 PROCESS_INFORMATION process_info
;
524 if ((Security
!= nullptr) && (static_cast<oslSecurityImpl
*>(Security
)->m_hToken
!= nullptr))
526 bRet
= CreateProcessAsUserW(
527 static_cast<oslSecurityImpl
*>(Security
)->m_hToken
,
528 nullptr, const_cast<LPWSTR
>(o3tl::toW(cmdline
.getStr())), nullptr, nullptr,
529 b_inherit_handles
, flags
, p_environment
, p_cwd
,
530 &startup_info
, &process_info
);
534 bRet
= CreateProcessW(
535 nullptr, const_cast<LPWSTR
>(o3tl::toW(cmdline
.getStr())), nullptr, nullptr,
536 b_inherit_handles
, flags
, p_environment
, p_cwd
,
537 &startup_info
, &process_info
);
540 /* Now we can close the pipe ends that are used by the child process */
543 CloseHandle(hInputRead
);
546 CloseHandle(hOutputWrite
);
549 CloseHandle(hErrorWrite
);
553 CloseHandle(process_info
.hThread
);
555 oslProcessImpl
* pProcImpl
= static_cast<oslProcessImpl
*>(
556 malloc(sizeof(oslProcessImpl
)));
558 if (pProcImpl
!= nullptr)
560 pProcImpl
->m_hProcess
= process_info
.hProcess
;
561 pProcImpl
->m_IdProcess
= process_info
.dwProcessId
;
563 *pProcess
= static_cast<oslProcess
>(pProcImpl
);
565 if (Options
& osl_Process_WAIT
)
566 WaitForSingleObject(pProcImpl
->m_hProcess
, INFINITE
);
568 if (pProcessInputWrite
)
569 *pProcessInputWrite
= osl_createFileHandleFromOSHandle(hInputWrite
, osl_File_OpenFlag_Write
);
571 if (pProcessOutputRead
)
572 *pProcessOutputRead
= osl_createFileHandleFromOSHandle(hOutputRead
, osl_File_OpenFlag_Read
);
574 if (pProcessErrorRead
)
575 *pProcessErrorRead
= osl_createFileHandleFromOSHandle(hErrorRead
, osl_File_OpenFlag_Read
);
577 return osl_Process_E_None
;
581 /* if an error occurred we have to close the server side pipe ends too */
584 CloseHandle(hInputWrite
);
587 CloseHandle(hOutputRead
);
590 CloseHandle(hErrorRead
);
592 return osl_Process_E_Unknown
;
595 /* vim:set shiftwidth=4 softtabstop=4 expandtab: */