mf/session: Reset per-node end of stream flags when stopped.
[wine.git] / programs / start / start.c
blobe78968e2052cb0b51029c1cf8d5f278f8d35aaee
1 /*
2 * Start a program using ShellExecuteEx, optionally wait for it to finish
3 * Compatible with Microsoft's "c:\windows\command\start.exe"
5 * Copyright 2003 Dan Kegel
6 * Copyright 2007 Lyutin Anatoly (Etersoft)
8 * This program is free software; you can redistribute it and/or
9 * modify it under the terms of the GNU Lesser General Public
10 * License as published by the Free Software Foundation; either
11 * version 2.1 of the License, or (at your option) any later version.
13 * This program is distributed in the hope that it will be useful,
14 * but WITHOUT ANY WARRANTY; without even the implied warranty of
15 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
16 * Lesser General Public License for more details.
18 * You should have received a copy of the GNU Lesser General Public
19 * License along with this program; if not, write to the Free Software
20 * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA
23 #include <stdio.h>
24 #include <stdlib.h>
25 #include <windows.h>
26 #include <shlobj.h>
27 #include <shellapi.h>
29 #include <wine/debug.h>
31 #include "resources.h"
33 WINE_DEFAULT_DEBUG_CHANNEL(start);
35 /**
36 Output given message to stdout without formatting.
38 static void output(const WCHAR *message)
40 DWORD count;
41 DWORD res;
42 int wlen = lstrlenW(message);
44 if (!wlen) return;
46 res = WriteConsoleW(GetStdHandle(STD_OUTPUT_HANDLE), message, wlen, &count, NULL);
48 /* If writing to console fails, assume it's file
49 * i/o so convert to OEM codepage and output
51 if (!res)
53 DWORD len;
54 char *mesA;
55 /* Convert to OEM, then output */
56 len = WideCharToMultiByte( GetConsoleOutputCP(), 0, message, wlen, NULL, 0, NULL, NULL );
57 mesA = HeapAlloc(GetProcessHeap(), 0, len*sizeof(char));
58 if (!mesA) return;
59 WideCharToMultiByte( GetConsoleOutputCP(), 0, message, wlen, mesA, len, NULL, NULL );
60 WriteFile(GetStdHandle(STD_OUTPUT_HANDLE), mesA, len, &count, FALSE);
61 HeapFree(GetProcessHeap(), 0, mesA);
65 /**
66 Output given message from string table,
67 followed by ": ",
68 followed by description of given GetLastError() value to stdout,
69 followed by a trailing newline,
70 then terminate.
73 static void fatal_error(const WCHAR *msg, DWORD error_code, const WCHAR *filename)
75 DWORD_PTR args[1];
76 LPVOID lpMsgBuf;
77 int status;
79 output(msg);
80 output(L": ");
81 args[0] = (DWORD_PTR)filename;
82 status = FormatMessageW(FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_ARGUMENT_ARRAY,
83 NULL, error_code, 0, (LPWSTR)&lpMsgBuf, 0, (__ms_va_list *)args );
84 if (!status)
86 WINE_ERR("FormatMessage failed\n");
87 } else
89 output(lpMsgBuf);
90 LocalFree((HLOCAL) lpMsgBuf);
91 output(L"\n");
93 ExitProcess(1);
96 static void fatal_string_error(int which, DWORD error_code, const WCHAR *filename)
98 WCHAR msg[2048];
100 if (!LoadStringW(GetModuleHandleW(NULL), which, msg, ARRAY_SIZE(msg)))
101 WINE_ERR("LoadString failed, error %d\n", GetLastError());
103 fatal_error(msg, error_code, filename);
106 static void fatal_string(int which)
108 WCHAR msg[2048];
110 if (!LoadStringW(GetModuleHandleW(NULL), which, msg, ARRAY_SIZE(msg)))
111 WINE_ERR("LoadString failed, error %d\n", GetLastError());
113 output(msg);
114 ExitProcess(1);
117 static void usage(void)
119 fatal_string(STRING_USAGE);
122 /***********************************************************************
123 * build_command_line
125 * Build the command line of a process from the argv array.
127 * We must quote and escape characters so that the argv array can be rebuilt
128 * from the command line:
129 * - spaces and tabs must be quoted
130 * 'a b' -> '"a b"'
131 * - quotes must be escaped
132 * '"' -> '\"'
133 * - if '\'s are followed by a '"', they must be doubled and followed by '\"',
134 * resulting in an odd number of '\' followed by a '"'
135 * '\"' -> '\\\"'
136 * '\\"' -> '\\\\\"'
137 * - '\'s are followed by the closing '"' must be doubled,
138 * resulting in an even number of '\' followed by a '"'
139 * ' \' -> '" \\"'
140 * ' \\' -> '" \\\\"'
141 * - '\'s that are not followed by a '"' can be left as is
142 * 'a\b' == 'a\b'
143 * 'a\\b' == 'a\\b'
145 static WCHAR *build_command_line( WCHAR **wargv )
147 int len;
148 WCHAR **arg, *ret;
149 LPWSTR p;
151 len = 1;
152 for (arg = wargv; *arg; arg++) len += 3 + 2 * wcslen( *arg );
153 if (!(ret = malloc( len * sizeof(WCHAR) ))) return NULL;
155 p = ret;
156 for (arg = wargv; *arg; arg++)
158 BOOL has_space, has_quote;
159 int i, bcount;
160 WCHAR *a;
162 /* check for quotes and spaces in this argument */
163 has_space = !**arg || wcschr( *arg, ' ' ) || wcschr( *arg, '\t' );
164 has_quote = wcschr( *arg, '"' ) != NULL;
166 /* now transfer it to the command line */
167 if (has_space) *p++ = '"';
168 if (has_quote || has_space)
170 bcount = 0;
171 for (a = *arg; *a; a++)
173 if (*a == '\\') bcount++;
174 else
176 if (*a == '"') /* double all the '\\' preceding this '"', plus one */
177 for (i = 0; i <= bcount; i++) *p++ = '\\';
178 bcount = 0;
180 *p++ = *a;
183 else
185 wcscpy( p, *arg );
186 p += wcslen( p );
188 if (has_space)
190 /* Double all the '\' preceding the closing quote */
191 for (i = 0; i < bcount; i++) *p++ = '\\';
192 *p++ = '"';
194 *p++ = ' ';
196 if (p > ret) p--; /* remove last space */
197 *p = 0;
198 return ret;
201 static WCHAR *get_parent_dir(WCHAR* path)
203 WCHAR *last_slash;
204 WCHAR *result;
205 int len;
207 last_slash = wcsrchr( path, '\\' );
208 if (last_slash == NULL)
209 len = 1;
210 else
211 len = last_slash - path + 1;
213 result = HeapAlloc(GetProcessHeap(), 0, len * sizeof(WCHAR));
214 CopyMemory(result, path, (len-1)*sizeof(WCHAR));
215 result[len-1] = '\0';
217 return result;
220 static BOOL is_option(const WCHAR* arg, const WCHAR* opt)
222 return CompareStringW(LOCALE_USER_DEFAULT, NORM_IGNORECASE,
223 arg, -1, opt, -1) == CSTR_EQUAL;
226 static void parse_title(const WCHAR *arg, WCHAR *title, int size)
228 /* See:
229 * WCMD_start() in programs/cmd/builtins.c
230 * WCMD_parameter_with_delims() in programs/cmd/batch.c
231 * The shell has already tokenized the command line for us.
232 * All we need to do is filter out all the quotes.
235 int next;
236 const WCHAR *p = arg;
238 for (next = 0; next < (size-1) && *p; p++) {
239 if (*p != '"')
240 title[next++] = *p;
242 title[next] = '\0';
245 static BOOL search_path(const WCHAR *firstParam, WCHAR **full_path)
247 /* Copied from WCMD_run_program() in programs/cmd/wcmdmain.c */
249 #define MAXSTRING 8192
251 WCHAR temp[MAX_PATH];
252 WCHAR pathtosearch[MAXSTRING];
253 WCHAR *pathposn;
254 WCHAR stemofsearch[MAX_PATH]; /* maximum allowed executable name is
255 MAX_PATH, including null character */
256 WCHAR *lastSlash;
257 WCHAR pathext[MAXSTRING];
258 BOOL extensionsupplied = FALSE;
259 DWORD len;
261 /* Calculate the search path and stem to search for */
262 if (wcspbrk (firstParam, L"/\\:") == NULL) { /* No explicit path given, search path */
263 lstrcpyW(pathtosearch, L".;");
264 len = GetEnvironmentVariableW(L"PATH", &pathtosearch[2], ARRAY_SIZE(pathtosearch)-2);
265 if ((len == 0) || (len >= ARRAY_SIZE(pathtosearch) - 2)) {
266 lstrcpyW (pathtosearch, L".");
268 if (wcschr(firstParam, '.') != NULL) extensionsupplied = TRUE;
269 if (lstrlenW(firstParam) >= MAX_PATH) {
270 return FALSE;
273 lstrcpyW(stemofsearch, firstParam);
275 } else {
277 /* Convert eg. ..\fred to include a directory by removing file part */
278 GetFullPathNameW(firstParam, ARRAY_SIZE(pathtosearch), pathtosearch, NULL);
279 lastSlash = wcsrchr(pathtosearch, '\\');
280 if (lastSlash && wcschr(lastSlash, '.') != NULL) extensionsupplied = TRUE;
281 lstrcpyW(stemofsearch, lastSlash+1);
283 /* Reduce pathtosearch to a path with trailing '\' to support c:\a.bat and
284 c:\windows\a.bat syntax */
285 if (lastSlash) *(lastSlash + 1) = 0x00;
288 /* Now extract PATHEXT */
289 len = GetEnvironmentVariableW(L"PATHEXT", pathext, ARRAY_SIZE(pathext));
290 if ((len == 0) || (len >= ARRAY_SIZE(pathext))) {
291 lstrcpyW (pathext, L".bat;.com;.cmd;.exe");
294 /* Loop through the search path, dir by dir */
295 pathposn = pathtosearch;
296 WINE_TRACE("Searching in '%s' for '%s'\n", wine_dbgstr_w(pathtosearch),
297 wine_dbgstr_w(stemofsearch));
298 while (pathposn) {
299 WCHAR thisDir[MAX_PATH] = {'\0'};
300 int length = 0;
301 WCHAR *pos = NULL;
302 BOOL found = FALSE;
303 BOOL inside_quotes = FALSE;
305 /* Work on the first directory on the search path */
306 pos = pathposn;
307 while ((inside_quotes || *pos != ';') && *pos != 0)
309 if (*pos == '"')
310 inside_quotes = !inside_quotes;
311 pos++;
314 if (*pos) { /* Reached semicolon */
315 memcpy(thisDir, pathposn, (pos-pathposn) * sizeof(WCHAR));
316 thisDir[(pos-pathposn)] = 0x00;
317 pathposn = pos+1;
318 } else { /* Reached string end */
319 lstrcpyW(thisDir, pathposn);
320 pathposn = NULL;
323 /* Remove quotes */
324 length = lstrlenW(thisDir);
325 if (thisDir[length - 1] == '"')
326 thisDir[length - 1] = 0;
328 if (*thisDir != '"')
329 lstrcpyW(temp, thisDir);
330 else
331 lstrcpyW(temp, thisDir + 1);
333 /* Since you can have eg. ..\.. on the path, need to expand
334 to full information */
335 GetFullPathNameW(temp, MAX_PATH, thisDir, NULL);
337 /* 1. If extension supplied, see if that file exists */
338 if (thisDir[lstrlenW(thisDir) - 1] != '\\') lstrcatW(thisDir, L"\\");
339 lstrcatW(thisDir, stemofsearch);
340 pos = &thisDir[lstrlenW(thisDir)]; /* Pos = end of name */
342 /* 1. If extension supplied, see if that file exists */
343 if (extensionsupplied) {
344 if (GetFileAttributesW(thisDir) != INVALID_FILE_ATTRIBUTES) {
345 found = TRUE;
349 /* 2. Any .* matches? */
350 if (!found) {
351 HANDLE h;
352 WIN32_FIND_DATAW finddata;
354 lstrcatW(thisDir, L".*");
355 h = FindFirstFileW(thisDir, &finddata);
356 FindClose(h);
357 if (h != INVALID_HANDLE_VALUE) {
359 WCHAR *thisExt = pathext;
361 /* 3. Yes - Try each path ext */
362 while (thisExt) {
363 WCHAR *nextExt = wcschr(thisExt, ';');
365 if (nextExt) {
366 memcpy(pos, thisExt, (nextExt-thisExt) * sizeof(WCHAR));
367 pos[(nextExt-thisExt)] = 0x00;
368 thisExt = nextExt+1;
369 } else {
370 lstrcpyW(pos, thisExt);
371 thisExt = NULL;
374 if (GetFileAttributesW(thisDir) != INVALID_FILE_ATTRIBUTES) {
375 found = TRUE;
376 thisExt = NULL;
382 if (found) {
383 int needed_size = lstrlenW(thisDir) + 1;
384 *full_path = HeapAlloc(GetProcessHeap(), 0, needed_size * sizeof(WCHAR));
385 if (*full_path)
386 lstrcpyW(*full_path, thisDir);
387 return TRUE;
390 return FALSE;
393 int __cdecl wmain (int argc, WCHAR *argv[])
395 SHELLEXECUTEINFOW sei;
396 DWORD creation_flags;
397 int i;
398 BOOL unix_mode = FALSE;
399 BOOL progid_open = FALSE;
400 WCHAR *title = NULL;
401 const WCHAR *file;
402 WCHAR *dos_filename = NULL;
403 WCHAR *fullpath = NULL;
404 WCHAR *parent_directory = NULL;
405 DWORD binary_type;
407 memset(&sei, 0, sizeof(sei));
408 sei.cbSize = sizeof(sei);
409 sei.lpVerb = L"open";
410 sei.nShow = SW_SHOWNORMAL;
411 /* Dunno what these mean, but it looks like winMe's start uses them */
412 sei.fMask = SEE_MASK_FLAG_DDEWAIT|
413 SEE_MASK_FLAG_NO_UI;
414 sei.lpDirectory = NULL;
415 creation_flags = CREATE_NEW_CONSOLE;
417 /* Canonical Microsoft commandline flag processing:
418 * flags start with / and are case insensitive.
420 for (i=1; i<argc; i++) {
421 /* parse first quoted argument as console title */
422 if (!title && argv[i][0] == '"') {
423 /* it will remove at least 1 quote */
424 int maxChars = lstrlenW(argv[1]);
425 title = HeapAlloc(GetProcessHeap(), 0, maxChars*sizeof(WCHAR));
426 if (title)
427 parse_title(argv[i], title, maxChars);
428 else {
429 WINE_ERR("out of memory\n");
430 ExitProcess(1);
432 continue;
434 if (argv[i][0] != '/')
435 break;
437 if (argv[i][1] == 'd' || argv[i][1] == 'D') {
438 if (argv[i][2])
439 /* The start directory was concatenated to the option */
440 sei.lpDirectory = argv[i]+2;
441 else if (i+1 == argc) {
442 WINE_ERR("you must specify a directory path for the /d option\n");
443 usage();
444 } else
445 sei.lpDirectory = argv[++i];
447 else if (is_option(argv[i], L"/b")) {
448 creation_flags &= ~CREATE_NEW_CONSOLE;
450 else if (argv[i][0] == '/' && (argv[i][1] == 'i' || argv[i][1] == 'I')) {
451 TRACE("/i is ignored\n"); /* FIXME */
453 else if (is_option(argv[i], L"/min")) {
454 sei.nShow = SW_SHOWMINIMIZED;
456 else if (is_option(argv[i], L"/max")) {
457 sei.nShow = SW_SHOWMAXIMIZED;
459 else if (is_option(argv[i], L"/low")) {
460 creation_flags |= IDLE_PRIORITY_CLASS;
462 else if (is_option(argv[i], L"/normal")) {
463 creation_flags |= NORMAL_PRIORITY_CLASS;
465 else if (is_option(argv[i], L"/high")) {
466 creation_flags |= HIGH_PRIORITY_CLASS;
468 else if (is_option(argv[i], L"/realtime")) {
469 creation_flags |= REALTIME_PRIORITY_CLASS;
471 else if (is_option(argv[i], L"/abovenormal")) {
472 creation_flags |= ABOVE_NORMAL_PRIORITY_CLASS;
474 else if (is_option(argv[i], L"/belownormal")) {
475 creation_flags |= BELOW_NORMAL_PRIORITY_CLASS;
477 else if (is_option(argv[i], L"/separate")) {
478 TRACE("/separate is ignored\n"); /* FIXME */
480 else if (is_option(argv[i], L"/shared")) {
481 TRACE("/shared is ignored\n"); /* FIXME */
483 else if (is_option(argv[i], L"/node")) {
484 if (i+1 == argc) {
485 WINE_ERR("you must specify a numa node for the /node option\n");
486 usage();
487 } else
489 TRACE("/node is ignored\n"); /* FIXME */
490 i++;
493 else if (is_option(argv[i], L"/affinity"))
495 if (i+1 == argc) {
496 WINE_ERR("you must specify a numa node for the /node option\n");
497 usage();
498 } else
500 TRACE("/affinity is ignored\n"); /* FIXME */
501 i++;
504 else if (is_option(argv[i], L"/w") || is_option(argv[i], L"/wait")) {
505 sei.fMask |= SEE_MASK_NOCLOSEPROCESS;
507 else if (is_option(argv[i], L"/?")) {
508 usage();
511 /* Wine extensions */
513 else if (is_option(argv[i], L"/unix")) {
514 unix_mode = TRUE;
515 i++;
516 break;
518 else if (is_option(argv[i], L"/exec")) {
519 creation_flags = 0;
520 sei.fMask = SEE_MASK_NOCLOSEPROCESS | SEE_MASK_NO_CONSOLE | SEE_MASK_FLAG_NO_UI;
521 i++;
522 break;
524 else if (is_option(argv[i], L"/progIDOpen")) {
525 progid_open = TRUE;
526 if (++i == argc) usage();
527 sei.lpClass = argv[i++];
528 sei.fMask |= SEE_MASK_CLASSNAME;
529 break;
530 } else
533 WINE_ERR("Unknown option '%s'\n", wine_dbgstr_w(argv[i]));
534 usage();
538 if (i == argc) {
539 if (progid_open || unix_mode)
540 usage();
541 file = L"cmd.exe";
543 else
544 file = argv[i++];
546 sei.lpParameters = build_command_line( &argv[i] );
548 if (unix_mode || progid_open) {
549 LPWSTR (*CDECL wine_get_dos_file_name_ptr)(LPCSTR);
550 char* multibyte_unixpath;
551 int multibyte_unixpath_len;
553 wine_get_dos_file_name_ptr = (void*)GetProcAddress(GetModuleHandleA("KERNEL32"), "wine_get_dos_file_name");
555 if (!wine_get_dos_file_name_ptr)
556 fatal_string(STRING_UNIXFAIL);
558 multibyte_unixpath_len = WideCharToMultiByte(CP_UNIXCP, 0, file, -1, NULL, 0, NULL, NULL);
559 multibyte_unixpath = HeapAlloc(GetProcessHeap(), 0, multibyte_unixpath_len);
561 WideCharToMultiByte(CP_UNIXCP, 0, file, -1, multibyte_unixpath, multibyte_unixpath_len, NULL, NULL);
563 dos_filename = wine_get_dos_file_name_ptr(multibyte_unixpath);
565 HeapFree(GetProcessHeap(), 0, multibyte_unixpath);
567 if (!dos_filename)
568 fatal_string(STRING_UNIXFAIL);
570 sei.lpFile = dos_filename;
571 if (!sei.lpDirectory)
572 sei.lpDirectory = parent_directory = get_parent_dir(dos_filename);
573 sei.fMask &= ~SEE_MASK_FLAG_NO_UI;
574 } else {
575 if (search_path(file, &fullpath)) {
576 if (fullpath != NULL) {
577 sei.lpFile = fullpath;
578 } else {
579 fatal_string_error(STRING_EXECFAIL, ERROR_OUTOFMEMORY, file);
581 } else {
582 sei.lpFile = file;
586 if (GetBinaryTypeW(sei.lpFile, &binary_type)) {
587 WCHAR *commandline;
588 STARTUPINFOW startup_info;
589 PROCESS_INFORMATION process_information;
591 /* explorer on windows always quotes the filename when running a binary on windows (see bug 5224) so we have to use CreateProcessW in this case */
593 commandline = HeapAlloc(GetProcessHeap(), 0, (lstrlenW(sei.lpFile)+4+lstrlenW(sei.lpParameters))*sizeof(WCHAR));
594 swprintf(commandline, lstrlenW(sei.lpFile) + 4 + lstrlenW(sei.lpParameters),
595 L"\"%s\" %s", sei.lpFile, sei.lpParameters);
597 ZeroMemory(&startup_info, sizeof(startup_info));
598 startup_info.cb = sizeof(startup_info);
599 startup_info.wShowWindow = sei.nShow;
600 startup_info.dwFlags |= STARTF_USESHOWWINDOW;
601 startup_info.lpTitle = title;
603 if (!CreateProcessW(
604 sei.lpFile, /* lpApplicationName */
605 commandline, /* lpCommandLine */
606 NULL, /* lpProcessAttributes */
607 NULL, /* lpThreadAttributes */
608 FALSE, /* bInheritHandles */
609 creation_flags, /* dwCreationFlags */
610 NULL, /* lpEnvironment */
611 sei.lpDirectory, /* lpCurrentDirectory */
612 &startup_info, /* lpStartupInfo */
613 &process_information /* lpProcessInformation */ ))
615 fatal_string_error(STRING_EXECFAIL, GetLastError(), sei.lpFile);
617 sei.hProcess = process_information.hProcess;
618 goto done;
621 if (!ShellExecuteExW(&sei))
623 const WCHAR *filename = sei.lpFile;
624 DWORD size, filename_len;
625 WCHAR *name, *env;
627 size = GetEnvironmentVariableW(L"PATHEXT", NULL, 0);
628 if (size)
630 WCHAR *start, *ptr;
632 env = HeapAlloc(GetProcessHeap(), 0, size * sizeof(WCHAR));
633 if (!env)
634 fatal_string_error(STRING_EXECFAIL, ERROR_OUTOFMEMORY, sei.lpFile);
635 GetEnvironmentVariableW(L"PATHEXT", env, size);
637 filename_len = lstrlenW(filename);
638 name = HeapAlloc(GetProcessHeap(), 0, (filename_len + size) * sizeof(WCHAR));
639 if (!name)
640 fatal_string_error(STRING_EXECFAIL, ERROR_OUTOFMEMORY, sei.lpFile);
642 sei.lpFile = name;
643 start = env;
644 while ((ptr = wcschr(start, ';')))
646 if (start == ptr)
648 start = ptr + 1;
649 continue;
652 lstrcpyW(name, filename);
653 memcpy(&name[filename_len], start, (ptr - start) * sizeof(WCHAR));
654 name[filename_len + (ptr - start)] = 0;
656 if (ShellExecuteExW(&sei))
658 HeapFree(GetProcessHeap(), 0, name);
659 HeapFree(GetProcessHeap(), 0, env);
660 goto done;
663 start = ptr + 1;
668 fatal_string_error(STRING_EXECFAIL, GetLastError(), filename);
671 done:
672 HeapFree( GetProcessHeap(), 0, dos_filename );
673 HeapFree( GetProcessHeap(), 0, fullpath );
674 HeapFree( GetProcessHeap(), 0, parent_directory );
675 HeapFree( GetProcessHeap(), 0, title );
677 if (sei.fMask & SEE_MASK_NOCLOSEPROCESS) {
678 DWORD exitcode;
679 WaitForSingleObject(sei.hProcess, INFINITE);
680 GetExitCodeProcess(sei.hProcess, &exitcode);
681 ExitProcess(exitcode);
684 ExitProcess(0);