mfreadwrite/reader: Enable DXGI device manager path.
[wine/zf.git] / programs / start / start.c
blobe3071a3a09a8eb169c4b75302dbdf8bdc27ea387
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 static WCHAR *build_args( int argc, WCHAR **argvW )
124 int i, wlen = 1;
125 WCHAR *ret, *p;
127 for (i = 0; i < argc; i++ )
129 wlen += lstrlenW(argvW[i]) + 1;
130 if (wcschr(argvW[i], ' '))
131 wlen += 2;
133 ret = HeapAlloc( GetProcessHeap(), 0, wlen*sizeof(WCHAR) );
134 ret[0] = 0;
136 for (i = 0, p = ret; i < argc; i++ )
138 if (wcschr(argvW[i], ' '))
139 p += swprintf(p, wlen - (p - ret), L" \"%s\"", argvW[i]);
140 else
141 p += swprintf(p, wlen - (p - ret), L" %s", argvW[i]);
143 return ret;
146 static WCHAR *get_parent_dir(WCHAR* path)
148 WCHAR *last_slash;
149 WCHAR *result;
150 int len;
152 last_slash = wcsrchr( path, '\\' );
153 if (last_slash == NULL)
154 len = 1;
155 else
156 len = last_slash - path + 1;
158 result = HeapAlloc(GetProcessHeap(), 0, len * sizeof(WCHAR));
159 CopyMemory(result, path, (len-1)*sizeof(WCHAR));
160 result[len-1] = '\0';
162 return result;
165 static BOOL is_option(const WCHAR* arg, const WCHAR* opt)
167 return CompareStringW(LOCALE_USER_DEFAULT, NORM_IGNORECASE,
168 arg, -1, opt, -1) == CSTR_EQUAL;
171 static void parse_title(const WCHAR *arg, WCHAR *title, int size)
173 /* See:
174 * WCMD_start() in programs/cmd/builtins.c
175 * WCMD_parameter_with_delims() in programs/cmd/batch.c
176 * The shell has already tokenized the command line for us.
177 * All we need to do is filter out all the quotes.
180 int next;
181 const WCHAR *p = arg;
183 for (next = 0; next < (size-1) && *p; p++) {
184 if (*p != '"')
185 title[next++] = *p;
187 title[next] = '\0';
190 static BOOL search_path(const WCHAR *firstParam, WCHAR **full_path)
192 /* Copied from WCMD_run_program() in programs/cmd/wcmdmain.c */
194 #define MAXSTRING 8192
196 WCHAR temp[MAX_PATH];
197 WCHAR pathtosearch[MAXSTRING];
198 WCHAR *pathposn;
199 WCHAR stemofsearch[MAX_PATH]; /* maximum allowed executable name is
200 MAX_PATH, including null character */
201 WCHAR *lastSlash;
202 WCHAR pathext[MAXSTRING];
203 BOOL extensionsupplied = FALSE;
204 DWORD len;
206 /* Calculate the search path and stem to search for */
207 if (wcspbrk (firstParam, L"/\\:") == NULL) { /* No explicit path given, search path */
208 lstrcpyW(pathtosearch, L".;");
209 len = GetEnvironmentVariableW(L"PATH", &pathtosearch[2], ARRAY_SIZE(pathtosearch)-2);
210 if ((len == 0) || (len >= ARRAY_SIZE(pathtosearch) - 2)) {
211 lstrcpyW (pathtosearch, L".");
213 if (wcschr(firstParam, '.') != NULL) extensionsupplied = TRUE;
214 if (lstrlenW(firstParam) >= MAX_PATH) {
215 return FALSE;
218 lstrcpyW(stemofsearch, firstParam);
220 } else {
222 /* Convert eg. ..\fred to include a directory by removing file part */
223 GetFullPathNameW(firstParam, ARRAY_SIZE(pathtosearch), pathtosearch, NULL);
224 lastSlash = wcsrchr(pathtosearch, '\\');
225 if (lastSlash && wcschr(lastSlash, '.') != NULL) extensionsupplied = TRUE;
226 lstrcpyW(stemofsearch, lastSlash+1);
228 /* Reduce pathtosearch to a path with trailing '\' to support c:\a.bat and
229 c:\windows\a.bat syntax */
230 if (lastSlash) *(lastSlash + 1) = 0x00;
233 /* Now extract PATHEXT */
234 len = GetEnvironmentVariableW(L"PATHEXT", pathext, ARRAY_SIZE(pathext));
235 if ((len == 0) || (len >= ARRAY_SIZE(pathext))) {
236 lstrcpyW (pathext, L".bat;.com;.cmd;.exe");
239 /* Loop through the search path, dir by dir */
240 pathposn = pathtosearch;
241 WINE_TRACE("Searching in '%s' for '%s'\n", wine_dbgstr_w(pathtosearch),
242 wine_dbgstr_w(stemofsearch));
243 while (pathposn) {
244 WCHAR thisDir[MAX_PATH] = {'\0'};
245 int length = 0;
246 WCHAR *pos = NULL;
247 BOOL found = FALSE;
248 BOOL inside_quotes = FALSE;
250 /* Work on the first directory on the search path */
251 pos = pathposn;
252 while ((inside_quotes || *pos != ';') && *pos != 0)
254 if (*pos == '"')
255 inside_quotes = !inside_quotes;
256 pos++;
259 if (*pos) { /* Reached semicolon */
260 memcpy(thisDir, pathposn, (pos-pathposn) * sizeof(WCHAR));
261 thisDir[(pos-pathposn)] = 0x00;
262 pathposn = pos+1;
263 } else { /* Reached string end */
264 lstrcpyW(thisDir, pathposn);
265 pathposn = NULL;
268 /* Remove quotes */
269 length = lstrlenW(thisDir);
270 if (thisDir[length - 1] == '"')
271 thisDir[length - 1] = 0;
273 if (*thisDir != '"')
274 lstrcpyW(temp, thisDir);
275 else
276 lstrcpyW(temp, thisDir + 1);
278 /* Since you can have eg. ..\.. on the path, need to expand
279 to full information */
280 GetFullPathNameW(temp, MAX_PATH, thisDir, NULL);
282 /* 1. If extension supplied, see if that file exists */
283 lstrcatW(thisDir, L"\\");
284 lstrcatW(thisDir, stemofsearch);
285 pos = &thisDir[lstrlenW(thisDir)]; /* Pos = end of name */
287 /* 1. If extension supplied, see if that file exists */
288 if (extensionsupplied) {
289 if (GetFileAttributesW(thisDir) != INVALID_FILE_ATTRIBUTES) {
290 found = TRUE;
294 /* 2. Any .* matches? */
295 if (!found) {
296 HANDLE h;
297 WIN32_FIND_DATAW finddata;
299 lstrcatW(thisDir, L".*");
300 h = FindFirstFileW(thisDir, &finddata);
301 FindClose(h);
302 if (h != INVALID_HANDLE_VALUE) {
304 WCHAR *thisExt = pathext;
306 /* 3. Yes - Try each path ext */
307 while (thisExt) {
308 WCHAR *nextExt = wcschr(thisExt, ';');
310 if (nextExt) {
311 memcpy(pos, thisExt, (nextExt-thisExt) * sizeof(WCHAR));
312 pos[(nextExt-thisExt)] = 0x00;
313 thisExt = nextExt+1;
314 } else {
315 lstrcpyW(pos, thisExt);
316 thisExt = NULL;
319 if (GetFileAttributesW(thisDir) != INVALID_FILE_ATTRIBUTES) {
320 found = TRUE;
321 thisExt = NULL;
327 if (found) {
328 int needed_size = lstrlenW(thisDir) + 1;
329 *full_path = HeapAlloc(GetProcessHeap(), 0, needed_size * sizeof(WCHAR));
330 if (*full_path)
331 lstrcpyW(*full_path, thisDir);
332 return TRUE;
335 return FALSE;
338 int __cdecl wmain (int argc, WCHAR *argv[])
340 SHELLEXECUTEINFOW sei;
341 DWORD creation_flags;
342 WCHAR *args = NULL;
343 int i;
344 BOOL unix_mode = FALSE;
345 BOOL progid_open = FALSE;
346 WCHAR *title = NULL;
347 const WCHAR *file;
348 WCHAR *dos_filename = NULL;
349 WCHAR *fullpath = NULL;
350 WCHAR *parent_directory = NULL;
351 DWORD binary_type;
353 memset(&sei, 0, sizeof(sei));
354 sei.cbSize = sizeof(sei);
355 sei.lpVerb = L"open";
356 sei.nShow = SW_SHOWNORMAL;
357 /* Dunno what these mean, but it looks like winMe's start uses them */
358 sei.fMask = SEE_MASK_FLAG_DDEWAIT|
359 SEE_MASK_FLAG_NO_UI;
360 sei.lpDirectory = NULL;
361 creation_flags = CREATE_NEW_CONSOLE;
363 /* Canonical Microsoft commandline flag processing:
364 * flags start with / and are case insensitive.
366 for (i=1; i<argc; i++) {
367 /* parse first quoted argument as console title */
368 if (!title && argv[i][0] == '"') {
369 /* it will remove at least 1 quote */
370 int maxChars = lstrlenW(argv[1]);
371 title = HeapAlloc(GetProcessHeap(), 0, maxChars*sizeof(WCHAR));
372 if (title)
373 parse_title(argv[i], title, maxChars);
374 else {
375 WINE_ERR("out of memory\n");
376 ExitProcess(1);
378 continue;
380 if (argv[i][0] != '/')
381 break;
383 /* Unix paths can start with / so we have to assume anything following /unix is not a flag */
384 if (unix_mode || progid_open)
385 break;
387 if (argv[i][0] == '/' && (argv[i][1] == 'd' || argv[i][1] == 'D')) {
388 if (argv[i][2])
389 /* The start directory was concatenated to the option */
390 sei.lpDirectory = argv[i]+2;
391 else if (i+1 == argc) {
392 WINE_ERR("you must specify a directory path for the /d option\n");
393 usage();
394 } else
395 sei.lpDirectory = argv[++i];
397 else if (is_option(argv[i], L"/b")) {
398 creation_flags &= ~CREATE_NEW_CONSOLE;
400 else if (argv[i][0] == '/' && (argv[i][1] == 'i' || argv[i][1] == 'I')) {
401 TRACE("/i is ignored\n"); /* FIXME */
403 else if (is_option(argv[i], L"/min")) {
404 sei.nShow = SW_SHOWMINIMIZED;
406 else if (is_option(argv[i], L"/max")) {
407 sei.nShow = SW_SHOWMAXIMIZED;
409 else if (is_option(argv[i], L"/low")) {
410 creation_flags |= IDLE_PRIORITY_CLASS;
412 else if (is_option(argv[i], L"/normal")) {
413 creation_flags |= NORMAL_PRIORITY_CLASS;
415 else if (is_option(argv[i], L"/high")) {
416 creation_flags |= HIGH_PRIORITY_CLASS;
418 else if (is_option(argv[i], L"/realtime")) {
419 creation_flags |= REALTIME_PRIORITY_CLASS;
421 else if (is_option(argv[i], L"/abovenormal")) {
422 creation_flags |= ABOVE_NORMAL_PRIORITY_CLASS;
424 else if (is_option(argv[i], L"/belownormal")) {
425 creation_flags |= BELOW_NORMAL_PRIORITY_CLASS;
427 else if (is_option(argv[i], L"/separate")) {
428 TRACE("/separate is ignored\n"); /* FIXME */
430 else if (is_option(argv[i], L"/shared")) {
431 TRACE("/shared is ignored\n"); /* FIXME */
433 else if (is_option(argv[i], L"/node")) {
434 if (i+1 == argc) {
435 WINE_ERR("you must specify a numa node for the /node option\n");
436 usage();
437 } else
439 TRACE("/node is ignored\n"); /* FIXME */
440 i++;
443 else if (is_option(argv[i], L"/affinity"))
445 if (i+1 == argc) {
446 WINE_ERR("you must specify a numa node for the /node option\n");
447 usage();
448 } else
450 TRACE("/affinity is ignored\n"); /* FIXME */
451 i++;
454 else if (is_option(argv[i], L"/w") || is_option(argv[i], L"/wait")) {
455 sei.fMask |= SEE_MASK_NOCLOSEPROCESS;
457 else if (is_option(argv[i], L"/?")) {
458 usage();
461 /* Wine extensions */
463 else if (is_option(argv[i], L"/unix")) {
464 unix_mode = TRUE;
466 else if (is_option(argv[i], L"/progIDOpen")) {
467 progid_open = TRUE;
468 } else
471 WINE_ERR("Unknown option '%s'\n", wine_dbgstr_w(argv[i]));
472 usage();
476 if (progid_open) {
477 if (i == argc)
478 usage();
479 sei.lpClass = argv[i++];
480 sei.fMask |= SEE_MASK_CLASSNAME;
483 if (i == argc) {
484 if (progid_open || unix_mode)
485 usage();
486 file = L"cmd.exe";
488 else
489 file = argv[i++];
491 args = build_args( argc - i, &argv[i] );
492 sei.lpParameters = args;
494 if (unix_mode || progid_open) {
495 LPWSTR (*CDECL wine_get_dos_file_name_ptr)(LPCSTR);
496 char* multibyte_unixpath;
497 int multibyte_unixpath_len;
499 wine_get_dos_file_name_ptr = (void*)GetProcAddress(GetModuleHandleA("KERNEL32"), "wine_get_dos_file_name");
501 if (!wine_get_dos_file_name_ptr)
502 fatal_string(STRING_UNIXFAIL);
504 multibyte_unixpath_len = WideCharToMultiByte(CP_UNIXCP, 0, file, -1, NULL, 0, NULL, NULL);
505 multibyte_unixpath = HeapAlloc(GetProcessHeap(), 0, multibyte_unixpath_len);
507 WideCharToMultiByte(CP_UNIXCP, 0, file, -1, multibyte_unixpath, multibyte_unixpath_len, NULL, NULL);
509 dos_filename = wine_get_dos_file_name_ptr(multibyte_unixpath);
511 HeapFree(GetProcessHeap(), 0, multibyte_unixpath);
513 if (!dos_filename)
514 fatal_string(STRING_UNIXFAIL);
516 sei.lpFile = dos_filename;
517 if (!sei.lpDirectory)
518 sei.lpDirectory = parent_directory = get_parent_dir(dos_filename);
519 sei.fMask &= ~SEE_MASK_FLAG_NO_UI;
520 } else {
521 if (search_path(file, &fullpath)) {
522 if (fullpath != NULL) {
523 sei.lpFile = fullpath;
524 } else {
525 fatal_string_error(STRING_EXECFAIL, ERROR_OUTOFMEMORY, file);
527 } else {
528 sei.lpFile = file;
532 if (GetBinaryTypeW(sei.lpFile, &binary_type)) {
533 WCHAR *commandline;
534 STARTUPINFOW startup_info;
535 PROCESS_INFORMATION process_information;
537 /* 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 */
539 commandline = HeapAlloc(GetProcessHeap(), 0, (lstrlenW(sei.lpFile)+3+lstrlenW(sei.lpParameters))*sizeof(WCHAR));
540 swprintf(commandline, lstrlenW(sei.lpFile) + 3 + lstrlenW(sei.lpParameters),
541 L"\"%s\"%s", sei.lpFile, sei.lpParameters);
543 ZeroMemory(&startup_info, sizeof(startup_info));
544 startup_info.cb = sizeof(startup_info);
545 startup_info.wShowWindow = sei.nShow;
546 startup_info.dwFlags |= STARTF_USESHOWWINDOW;
547 startup_info.lpTitle = title;
549 if (!CreateProcessW(
550 sei.lpFile, /* lpApplicationName */
551 commandline, /* lpCommandLine */
552 NULL, /* lpProcessAttributes */
553 NULL, /* lpThreadAttributes */
554 FALSE, /* bInheritHandles */
555 creation_flags, /* dwCreationFlags */
556 NULL, /* lpEnvironment */
557 sei.lpDirectory, /* lpCurrentDirectory */
558 &startup_info, /* lpStartupInfo */
559 &process_information /* lpProcessInformation */ ))
561 fatal_string_error(STRING_EXECFAIL, GetLastError(), sei.lpFile);
563 sei.hProcess = process_information.hProcess;
564 goto done;
567 if (!ShellExecuteExW(&sei))
569 const WCHAR *filename = sei.lpFile;
570 DWORD size, filename_len;
571 WCHAR *name, *env;
573 size = GetEnvironmentVariableW(L"PATHEXT", NULL, 0);
574 if (size)
576 WCHAR *start, *ptr;
578 env = HeapAlloc(GetProcessHeap(), 0, size * sizeof(WCHAR));
579 if (!env)
580 fatal_string_error(STRING_EXECFAIL, ERROR_OUTOFMEMORY, sei.lpFile);
581 GetEnvironmentVariableW(L"PATHEXT", env, size);
583 filename_len = lstrlenW(filename);
584 name = HeapAlloc(GetProcessHeap(), 0, (filename_len + size) * sizeof(WCHAR));
585 if (!name)
586 fatal_string_error(STRING_EXECFAIL, ERROR_OUTOFMEMORY, sei.lpFile);
588 sei.lpFile = name;
589 start = env;
590 while ((ptr = wcschr(start, ';')))
592 if (start == ptr)
594 start = ptr + 1;
595 continue;
598 lstrcpyW(name, filename);
599 memcpy(&name[filename_len], start, (ptr - start) * sizeof(WCHAR));
600 name[filename_len + (ptr - start)] = 0;
602 if (ShellExecuteExW(&sei))
604 HeapFree(GetProcessHeap(), 0, name);
605 HeapFree(GetProcessHeap(), 0, env);
606 goto done;
609 start = ptr + 1;
614 fatal_string_error(STRING_EXECFAIL, GetLastError(), filename);
617 done:
618 HeapFree( GetProcessHeap(), 0, args );
619 HeapFree( GetProcessHeap(), 0, dos_filename );
620 HeapFree( GetProcessHeap(), 0, fullpath );
621 HeapFree( GetProcessHeap(), 0, parent_directory );
622 HeapFree( GetProcessHeap(), 0, title );
624 if (sei.fMask & SEE_MASK_NOCLOSEPROCESS) {
625 DWORD exitcode;
626 WaitForSingleObject(sei.hProcess, INFINITE);
627 GetExitCodeProcess(sei.hProcess, &exitcode);
628 ExitProcess(exitcode);
631 ExitProcess(0);