lok: vcl: fix multiple floatwin removal case more robustly.
[LibreOffice.git] / sal / osl / w32 / procimpl.cxx
blob49465bb6f1630ff0d425b3690a775c35b54e968b
1 /* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
2 /*
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
22 # include <windows.h>
23 # undef WIN32_LEAN_AND_MEAN
24 #endif
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>
35 #include <vector>
36 #include <algorithm>
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
45 second one. */
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
55 // so we do it, too
56 return (rtl_ustr_compare_WithLength(
57 lhs.toAsciiUpperCase().pData->buffer,
58 lhs.indexOf(L'='),
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
69 public:
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
84 return sum_;
86 private:
87 size_t sum_;
90 inline size_t calc_sum_of_string_lengths(const std::vector<OUString>& string_cont)
92 return std::for_each(
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();
101 LPWSTR p = env;
103 while (size_t l = wcslen(p))
105 environment->push_back(o3tl::toU(p));
106 p += l + 1;
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 = OUString(env_vars[i]);
133 if (env_var.getLength() == 0)
134 return false;
136 auto iter_pair = std::equal_range(
137 merged_env->begin(),
138 merged_env->end(),
139 env_var,
140 less_environment_variable());
142 if (env_var.indexOf(L'=') == -1)
144 merged_env->erase(iter_pair.first, iter_pair.second);
146 else
148 if (iter_pair.first != iter_pair.second) // found
149 *iter_pair.first = env_var;
150 else // not found
151 merged_env->insert(iter_pair.first, env_var);
154 return true;
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))
165 return false;
167 // allocate enough space for the '\0'-separated environment strings and
168 // a final '\0'
169 environment.resize(calc_sum_of_string_lengths(merged_env) + 1);
171 sal_uInt32 pos = 0;
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));
178 pos += n;
180 environment[pos] = 0; // append a final '\0'
182 return true;
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. */
188 bool create_pipe(
189 PHANDLE p_read_pipe,
190 bool b_read_pipe_inheritable,
191 PHANDLE p_write_pipe,
192 bool b_write_pipe_inheritable,
193 LPVOID p_security_descriptor = nullptr,
194 DWORD pipe_size = 0)
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;
201 BOOL bRet = FALSE;
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))
212 CloseHandle(hTemp);
213 CloseHandle(*p_read_pipe);
214 return false;
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))
225 CloseHandle(hTemp);
226 CloseHandle(*p_write_pipe);
227 return false;
230 else
232 bRet = CreatePipe(p_read_pipe, p_write_pipe, &sa, pipe_size);
234 return bRet;
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)
243 quoted.append('"');
245 quoted.append(string);
247 if (string.lastIndexOf(L'"') != (string.getLength() - 1))
248 quoted.append('"');
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)
261 OUString ret(path);
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[0]), 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[0]), extPath.getLength() + 1);
277 ret = OUString(&vec2[0], len2);
279 else
281 ret = OUString(&vec[0], len);
284 return ret;
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,
295 bool search_path)
297 OUString exe_name;
299 if (image_name)
300 exe_name = image_name;
301 else if (n_cmdline_args)
302 exe_name = OUString(cmdline_args[0]);
304 OUString exe_url = exe_name;
305 if (search_path)
306 osl_searchFileURL(exe_name.pData, nullptr, &exe_url.pData);
308 OUString exe_path;
309 if (osl::FileBase::E_None != osl::FileBase::getSystemPathFromFileURL(exe_url, exe_path))
310 return OUString();
312 exe_path = getShortPath(exe_path, ".exe");
314 if (exe_path.indexOf(' ') != -1)
315 exe_path = quote_string(exe_path);
317 return exe_path;
320 OUString get_file_extension(const OUString& file_name)
322 // Quoted 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
330 else
332 sal_Int32 index = file_name.lastIndexOf('.');
333 if ((index != -1) && ((index + 1) < file_name.getLength()))
334 return file_name.copy(index + 1);
336 return OUString();
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()
350 OUString comspec;
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);
359 return 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,
373 oslProcess *pProcess
376 return osl_executeProcess_WithRedirectedIO(
377 strImageName,
378 strArguments,
379 nArguments,
380 Options,
381 Security,
382 strDirectory,
383 strEnvironmentVars,
384 nEnvironmentVars,
385 pProcess,
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);
426 command_line.append(" /c ");
428 else
429 // should we return here in case of error?
430 return osl_Process_E_Unknown;
433 command_line.append(exe_path);
435 /* Add remaining arguments to command line. If ustrImageName is nullptr
436 the first parameter is the name of the executable so we have to
437 start at 1 instead of 0 */
438 for (sal_uInt32 n = (nullptr != ustrImageName) ? 0 : 1; n < nArguments; n++)
440 command_line.append(" ");
442 /* Quote arguments containing blanks */
443 if (OUString(ustrArguments[n]).indexOf(' ') != -1)
444 command_line.append(quote_string(ustrArguments[n]));
445 else
446 command_line.append(ustrArguments[n]);
449 std::vector<sal_Unicode> environment;
450 LPVOID p_environment = nullptr;
452 if (nEnvironmentVars && ustrEnvironmentVars)
454 if (!setup_process_environment(
455 ustrEnvironmentVars, nEnvironmentVars, environment))
456 return osl_Process_E_InvalidError;
458 flags |= CREATE_UNICODE_ENVIRONMENT;
459 p_environment = &environment[0];
462 OUString cwd;
463 if (ustrDirectory && ustrDirectory->length && (osl::FileBase::E_None != osl::FileBase::getSystemPathFromFileURL(ustrDirectory, cwd)))
464 return osl_Process_E_InvalidError;
466 LPCWSTR p_cwd = (cwd.getLength()) ? o3tl::toW(cwd.getStr()) : nullptr;
468 if ((Options & osl_Process_DETACHED) && !(flags & CREATE_NEW_CONSOLE))
469 flags |= DETACHED_PROCESS;
471 STARTUPINFOW startup_info;
472 memset(&startup_info, 0, sizeof(startup_info));
474 startup_info.cb = sizeof(startup_info);
475 startup_info.dwFlags = STARTF_USESHOWWINDOW;
476 startup_info.lpDesktop = const_cast<LPWSTR>(L"");
478 /* Create pipes for redirected IO */
479 HANDLE hInputRead = nullptr;
480 HANDLE hInputWrite = nullptr;
481 if (pProcessInputWrite && create_pipe(&hInputRead, true, &hInputWrite, false))
482 startup_info.hStdInput = hInputRead;
484 HANDLE hOutputRead = nullptr;
485 HANDLE hOutputWrite = nullptr;
486 if (pProcessOutputRead && create_pipe(&hOutputRead, false, &hOutputWrite, true))
487 startup_info.hStdOutput = hOutputWrite;
489 HANDLE hErrorRead = nullptr;
490 HANDLE hErrorWrite = nullptr;
491 if (pProcessErrorRead && create_pipe(&hErrorRead, false, &hErrorWrite, true))
492 startup_info.hStdError = hErrorWrite;
494 bool b_inherit_handles = false;
495 if (pProcessInputWrite || pProcessOutputRead || pProcessErrorRead)
497 startup_info.dwFlags |= STARTF_USESTDHANDLES;
498 b_inherit_handles = true;
501 switch(Options & (osl_Process_NORMAL | osl_Process_HIDDEN | osl_Process_MINIMIZED | osl_Process_MAXIMIZED | osl_Process_FULLSCREEN))
503 case osl_Process_HIDDEN:
504 startup_info.wShowWindow = SW_HIDE;
505 flags |= CREATE_NO_WINDOW; // ignored for non-console
506 // applications; ignored on
507 // Win9x
508 break;
510 case osl_Process_MINIMIZED:
511 startup_info.wShowWindow = SW_MINIMIZE;
512 break;
514 case osl_Process_MAXIMIZED:
515 case osl_Process_FULLSCREEN:
516 startup_info.wShowWindow = SW_MAXIMIZE;
517 break;
519 default:
520 startup_info.wShowWindow = SW_NORMAL;
523 OUString cmdline = command_line.makeStringAndClear();
524 PROCESS_INFORMATION process_info;
525 BOOL bRet = FALSE;
527 if ((Security != nullptr) && (static_cast<oslSecurityImpl*>(Security)->m_hToken != nullptr))
529 bRet = CreateProcessAsUserW(
530 static_cast<oslSecurityImpl*>(Security)->m_hToken,
531 nullptr, const_cast<LPWSTR>(o3tl::toW(cmdline.getStr())), nullptr, nullptr,
532 b_inherit_handles, flags, p_environment, p_cwd,
533 &startup_info, &process_info);
535 else
537 bRet = CreateProcessW(
538 nullptr, const_cast<LPWSTR>(o3tl::toW(cmdline.getStr())), nullptr, nullptr,
539 b_inherit_handles, flags, p_environment, p_cwd,
540 &startup_info, &process_info);
543 /* Now we can close the pipe ends that are used by the child process */
545 if (hInputRead)
546 CloseHandle(hInputRead);
548 if (hOutputWrite)
549 CloseHandle(hOutputWrite);
551 if (hErrorWrite)
552 CloseHandle(hErrorWrite);
554 if (bRet)
556 CloseHandle(process_info.hThread);
558 oslProcessImpl* pProcImpl = static_cast<oslProcessImpl*>(
559 malloc(sizeof(oslProcessImpl)));
561 if (pProcImpl != nullptr)
563 pProcImpl->m_hProcess = process_info.hProcess;
564 pProcImpl->m_IdProcess = process_info.dwProcessId;
566 *pProcess = static_cast<oslProcess>(pProcImpl);
568 if (Options & osl_Process_WAIT)
569 WaitForSingleObject(pProcImpl->m_hProcess, INFINITE);
571 if (pProcessInputWrite)
572 *pProcessInputWrite = osl_createFileHandleFromOSHandle(hInputWrite, osl_File_OpenFlag_Write);
574 if (pProcessOutputRead)
575 *pProcessOutputRead = osl_createFileHandleFromOSHandle(hOutputRead, osl_File_OpenFlag_Read);
577 if (pProcessErrorRead)
578 *pProcessErrorRead = osl_createFileHandleFromOSHandle(hErrorRead, osl_File_OpenFlag_Read);
580 return osl_Process_E_None;
584 /* if an error occurred we have to close the server side pipe ends too */
586 if (hInputWrite)
587 CloseHandle(hInputWrite);
589 if (hOutputRead)
590 CloseHandle(hOutputRead);
592 if (hErrorRead)
593 CloseHandle(hErrorRead);
595 return osl_Process_E_Unknown;
598 /* vim:set shiftwidth=4 softtabstop=4 expandtab: */