start.c 12.7 KB
Newer Older
1 2 3 4 5
/*
 * Start a program using ShellExecuteEx, optionally wait for it to finish
 * Compatible with Microsoft's "c:\windows\command\start.exe"
 *
 * Copyright 2003 Dan Kegel
6
 * Copyright 2007 Lyutin Anatoly (Etersoft)
7 8 9 10 11 12 13 14 15 16 17 18 19
 *
 * This program is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Lesser General Public
 * License as published by the Free Software Foundation; either
 * version 2.1 of the License, or (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * 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
21 22 23 24
 */

#include <stdio.h>
#include <stdlib.h>
25
#include <windows.h>
26
#include <shlobj.h>
27
#include <shellapi.h>
28

29 30 31
#include <wine/unicode.h>
#include <wine/debug.h>

32 33
#include "resources.h"

34 35
WINE_DEFAULT_DEBUG_CHANNEL(start);

36 37 38
/**
 Output given message to stdout without formatting.
*/
39
static void output(const WCHAR *message)
40 41
{
	DWORD count;
42 43
	DWORD   res;
	int    wlen = strlenW(message);
44

45
	if (!wlen) return;
46

47 48
	res = WriteConsoleW(GetStdHandle(STD_OUTPUT_HANDLE), message, wlen, &count, NULL);

49 50 51
	/* If writing to console fails, assume it's file
         * i/o so convert to OEM codepage and output
         */
52 53 54 55 56 57 58 59 60 61 62
	if (!res)
	{
		DWORD len;
		char  *mesA;
		/* Convert to OEM, then output */
		len = WideCharToMultiByte( GetConsoleOutputCP(), 0, message, wlen, NULL, 0, NULL, NULL );
		mesA = HeapAlloc(GetProcessHeap(), 0, len*sizeof(char));
		if (!mesA) return;
		WideCharToMultiByte( GetConsoleOutputCP(), 0, message, wlen, mesA, len, NULL, NULL );
		WriteFile(GetStdHandle(STD_OUTPUT_HANDLE), mesA, len, &count, FALSE);
		HeapFree(GetProcessHeap(), 0, mesA);
63 64 65 66 67 68 69 70 71 72
	}
}

/**
 Output given message from string table,
 followed by ": ",
 followed by description of given GetLastError() value to stdout,
 followed by a trailing newline,
 then terminate.
*/
73

74
static void fatal_error(const WCHAR *msg, DWORD error_code, const WCHAR *filename)
75
{
76
    DWORD_PTR args[1];
77 78 79 80 81 82 83
    LPVOID lpMsgBuf;
    int status;
    static const WCHAR colonsW[] = { ':', ' ', 0 };
    static const WCHAR newlineW[] = { '\n', 0 };

    output(msg);
    output(colonsW);
84 85 86
    args[0] = (DWORD_PTR)filename;
    status = FormatMessageW(FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_ARGUMENT_ARRAY,
                            NULL, error_code, 0, (LPWSTR)&lpMsgBuf, 0, (__ms_va_list *)args );
87 88 89 90 91 92 93 94 95 96 97 98
    if (!status)
    {
        WINE_ERR("FormatMessage failed\n");
    } else
    {
        output(lpMsgBuf);
        LocalFree((HLOCAL) lpMsgBuf);
        output(newlineW);
    }
    ExitProcess(1);
}

99
static void fatal_string_error(int which, DWORD error_code, const WCHAR *filename)
100
{
101
	WCHAR msg[2048];
102

103
	if (!LoadStringW(GetModuleHandleW(NULL), which,
104 105
					msg, sizeof(msg)/sizeof(WCHAR)))
		WINE_ERR("LoadString failed, error %d\n", GetLastError());
106

107
	fatal_error(msg, error_code, filename);
108 109 110 111
}
	
static void fatal_string(int which)
{
112
	WCHAR msg[2048];
113

114
	if (!LoadStringW(GetModuleHandleW(NULL), which,
115 116
					msg, sizeof(msg)/sizeof(WCHAR)))
		WINE_ERR("LoadString failed, error %d\n", GetLastError());
117 118 119 120 121

	output(msg);
	ExitProcess(1);
}

122
static void usage(void)
123 124 125 126
{
	fatal_string(STRING_USAGE);
}

127
static WCHAR *build_args( int argc, WCHAR **argvW )
128
{
129 130 131 132
	int i, wlen = 1;
	WCHAR *ret, *p;
	static const WCHAR FormatQuotesW[] = { ' ', '\"', '%', 's', '\"', 0 };
	static const WCHAR FormatW[] = { ' ', '%', 's', 0 };
133 134

	for (i = 0; i < argc; i++ )
135
	{
136 137 138
		wlen += strlenW(argvW[i]) + 1;
		if (strchrW(argvW[i], ' '))
			wlen += 2;
139
	}
140
	ret = HeapAlloc( GetProcessHeap(), 0, wlen*sizeof(WCHAR) );
141
	ret[0] = 0;
142 143

	for (i = 0, p = ret; i < argc; i++ )
144
	{
145 146
		if (strchrW(argvW[i], ' '))
			p += sprintfW(p, FormatQuotesW, argvW[i]);
147
		else
148
			p += sprintfW(p, FormatW, argvW[i]);
149
	}
150 151 152
	return ret;
}

153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171
static WCHAR *get_parent_dir(WCHAR* path)
{
	WCHAR *last_slash;
	WCHAR *result;
	int len;

	last_slash = strrchrW( path, '\\' );
	if (last_slash == NULL)
		len = 1;
	else
		len = last_slash - path + 1;

	result = HeapAlloc(GetProcessHeap(), 0, len * sizeof(WCHAR));
	CopyMemory(result, path, (len-1)*sizeof(WCHAR));
	result[len-1] = '\0';

	return result;
}

172 173 174 175 176 177
static BOOL is_option(const WCHAR* arg, const WCHAR* opt)
{
    return CompareStringW(LOCALE_USER_DEFAULT, NORM_IGNORECASE,
                          arg, -1, opt, -1) == CSTR_EQUAL;
}

178
int wmain (int argc, WCHAR *argv[])
179
{
180
	SHELLEXECUTEINFOW sei;
181
	DWORD creation_flags;
182
	WCHAR *args = NULL;
183
	int i;
184 185
        BOOL unix_mode = FALSE;
        BOOL progid_open = FALSE;
186
	WCHAR *title = NULL;
187 188 189
	WCHAR *dos_filename = NULL;
	WCHAR *parent_directory = NULL;
	DWORD binary_type;
190

191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207
	static const WCHAR bW[] = { '/', 'b', 0 };
	static const WCHAR minW[] = { '/', 'm', 'i', 'n', 0 };
	static const WCHAR maxW[] = { '/', 'm', 'a', 'x', 0 };
	static const WCHAR lowW[] = { '/', 'l', 'o', 'w', 0 };
	static const WCHAR normalW[] = { '/', 'n', 'o', 'r', 'm', 'a', 'l', 0 };
	static const WCHAR highW[] = { '/', 'h', 'i', 'g', 'h', 0 };
	static const WCHAR realtimeW[] = { '/', 'r', 'e', 'a', 'l', 't', 'i', 'm', 'e', 0 };
	static const WCHAR abovenormalW[] = { '/', 'a', 'b', 'o', 'v', 'e', 'n', 'o', 'r', 'm', 'a', 'l', 0 };
	static const WCHAR belownormalW[] = { '/', 'b', 'e', 'l', 'o', 'w', 'n', 'o', 'r', 'm', 'a', 'l', 0 };
	static const WCHAR separateW[] = { '/', 's', 'e', 'p', 'a', 'r', 'a', 't', 'e', 0 };
	static const WCHAR sharedW[] = { '/', 's', 'h', 'a', 'r', 'e', 'd', 0 };
	static const WCHAR nodeW[] = { '/', 'n', 'o', 'd', 'e', 0 };
	static const WCHAR affinityW[] = { '/', 'a', 'f', 'f', 'i', 'n', 'i', 't', 'y', 0 };
	static const WCHAR wW[] = { '/', 'w', 0 };
	static const WCHAR waitW[] = { '/', 'w', 'a', 'i', 't', 0 };
	static const WCHAR helpW[] = { '/', '?', 0 };
	static const WCHAR unixW[] = { '/', 'u', 'n', 'i', 'x', 0 };
208
	static const WCHAR progIDOpenW[] =
209
		{ '/', 'p', 'r', 'o', 'g', 'I', 'D', 'O', 'p', 'e', 'n', 0};
210
	static const WCHAR openW[] = { 'o', 'p', 'e', 'n', 0 };
211
	static const WCHAR cmdW[] = { 'c', 'm', 'd', '.', 'e', 'x', 'e', 0 };
212

213 214
	memset(&sei, 0, sizeof(sei));
	sei.cbSize = sizeof(sei);
215
	sei.lpVerb = openW;
216 217
	sei.nShow = SW_SHOWNORMAL;
	/* Dunno what these mean, but it looks like winMe's start uses them */
218
	sei.fMask = SEE_MASK_FLAG_DDEWAIT|
219
	            SEE_MASK_FLAG_NO_UI;
220 221
        sei.lpDirectory = NULL;
        creation_flags = CREATE_NEW_CONSOLE;
222 223

	/* Canonical Microsoft commandline flag processing:
224
	 * flags start with / and are case insensitive.
225
	 */
226
	for (i=1; i<argc; i++) {
227 228
                /* parse first quoted argument as console title */
                if (!title && argv[i][0] == '"') {
229 230 231
			title = argv[i];
			continue;
		}
232
		if (argv[i][0] != '/')
233 234
			break;

235
		/* Unix paths can start with / so we have to assume anything following /unix is not a flag */
236
		if (unix_mode || progid_open)
237 238
			break;

239 240 241 242 243 244
		if (argv[i][0] == '/' && (argv[i][1] == 'd' || argv[i][1] == 'D')) {
			if (argv[i][2])
				/* The start directory was concatenated to the option */
				sei.lpDirectory = argv[i]+2;
			else if (i+1 == argc) {
				WINE_ERR("you must specify a directory path for the /d option\n");
245
				usage();
246 247 248 249
			} else
				sei.lpDirectory = argv[++i];
		}
		else if (is_option(argv[i], bW)) {
250
			creation_flags &= ~CREATE_NEW_CONSOLE;
251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287
		}
		else if (argv[i][0] == '/' && (argv[i][1] == 'i' || argv[i][1] == 'I')) {
                    TRACE("/i is ignored\n"); /* FIXME */
		}
		else if (is_option(argv[i], minW)) {
			sei.nShow = SW_SHOWMINIMIZED;
		}
		else if (is_option(argv[i], maxW)) {
			sei.nShow = SW_SHOWMAXIMIZED;
		}
		else if (is_option(argv[i], lowW)) {
			creation_flags |= IDLE_PRIORITY_CLASS;
		}
		else if (is_option(argv[i], normalW)) {
			creation_flags |= NORMAL_PRIORITY_CLASS;
		}
		else if (is_option(argv[i], highW)) {
			creation_flags |= HIGH_PRIORITY_CLASS;
		}
		else if (is_option(argv[i], realtimeW)) {
			creation_flags |= REALTIME_PRIORITY_CLASS;
		}
		else if (is_option(argv[i], abovenormalW)) {
			creation_flags |= ABOVE_NORMAL_PRIORITY_CLASS;
		}
		else if (is_option(argv[i], belownormalW)) {
			creation_flags |= BELOW_NORMAL_PRIORITY_CLASS;
		}
		else if (is_option(argv[i], separateW)) {
			TRACE("/separate is ignored\n"); /* FIXME */
		}
		else if (is_option(argv[i], sharedW)) {
			TRACE("/shared is ignored\n"); /* FIXME */
		}
		else if (is_option(argv[i], nodeW)) {
			if (i+1 == argc) {
				WINE_ERR("you must specify a numa node for the /node option\n");
288
				usage();
289 290 291 292
			} else
			{
				TRACE("/node is ignored\n"); /* FIXME */
				i++;
293 294
			}
		}
295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311
		else if (is_option(argv[i], affinityW))
		{
			if (i+1 == argc) {
				WINE_ERR("you must specify a numa node for the /node option\n");
				usage();
			} else
			{
				TRACE("/affinity is ignored\n"); /* FIXME */
				i++;
			}
		}
		else if (is_option(argv[i], wW) || is_option(argv[i], waitW)) {
			sei.fMask |= SEE_MASK_NOCLOSEPROCESS;
		}
		else if (is_option(argv[i], helpW)) {
			usage();
		}
312

313 314 315
		/* Wine extensions */

		else if (is_option(argv[i], unixW)) {
316
                        unix_mode = TRUE;
317 318
		}
		else if (is_option(argv[i], progIDOpenW)) {
319
                        progid_open = TRUE;
320 321 322 323 324 325 326
		} else

		{
			WINE_ERR("Unknown option '%s'\n", wine_dbgstr_w(argv[i]));
			usage();
		}
	}
327

328
	if (progid_open) {
329 330
		if (i == argc)
			usage();
331 332 333 334
		sei.lpClass = argv[i++];
		sei.fMask |= SEE_MASK_CLASSNAME;
	}

335 336 337 338 339 340 341
	if (i == argc) {
		if (progid_open || unix_mode)
			usage();
		sei.lpFile = cmdW;
	}
	else
		sei.lpFile = argv[i++];
342

343 344
	args = build_args( argc - i, &argv[i] );
	sei.lpParameters = args;
345

346
	if (unix_mode || progid_open) {
347
		LPWSTR (*CDECL wine_get_dos_file_name_ptr)(LPCSTR);
348 349 350
		char* multibyte_unixpath;
		int multibyte_unixpath_len;

351
		wine_get_dos_file_name_ptr = (void*)GetProcAddress(GetModuleHandleA("KERNEL32"), "wine_get_dos_file_name");
352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368

		if (!wine_get_dos_file_name_ptr)
			fatal_string(STRING_UNIXFAIL);

		multibyte_unixpath_len = WideCharToMultiByte(CP_UNIXCP, 0, sei.lpFile, -1, NULL, 0, NULL, NULL);
		multibyte_unixpath = HeapAlloc(GetProcessHeap(), 0, multibyte_unixpath_len);

		WideCharToMultiByte(CP_UNIXCP, 0, sei.lpFile, -1, multibyte_unixpath, multibyte_unixpath_len, NULL, NULL);

		dos_filename = wine_get_dos_file_name_ptr(multibyte_unixpath);

		HeapFree(GetProcessHeap(), 0, multibyte_unixpath);

		if (!dos_filename)
			fatal_string(STRING_UNIXFAIL);

		sei.lpFile = dos_filename;
369 370
		if (!sei.lpDirectory)
			sei.lpDirectory = parent_directory = get_parent_dir(dos_filename);
371
		sei.fMask &= ~SEE_MASK_FLAG_NO_UI;
372 373 374 375 376

                if (GetBinaryTypeW(sei.lpFile, &binary_type)) {
                    WCHAR *commandline;
                    STARTUPINFOW startup_info;
                    PROCESS_INFORMATION process_information;
377
                    static const WCHAR commandlineformat[] = {'"','%','s','"','%','s',0};
378 379 380 381 382 383 384 385

                    /* 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 */

                    commandline = HeapAlloc(GetProcessHeap(), 0, (strlenW(sei.lpFile)+3+strlenW(sei.lpParameters))*sizeof(WCHAR));
                    sprintfW(commandline, commandlineformat, sei.lpFile, sei.lpParameters);

                    ZeroMemory(&startup_info, sizeof(startup_info));
                    startup_info.cb = sizeof(startup_info);
386
                    startup_info.lpTitle = title;
387 388 389 390 391 392 393

                    if (!CreateProcessW(
                            NULL, /* lpApplicationName */
                            commandline, /* lpCommandLine */
                            NULL, /* lpProcessAttributes */
                            NULL, /* lpThreadAttributes */
                            FALSE, /* bInheritHandles */
394
                            creation_flags, /* dwCreationFlags */
395 396 397 398 399
                            NULL, /* lpEnvironment */
                            sei.lpDirectory, /* lpCurrentDirectory */
                            &startup_info, /* lpStartupInfo */
                            &process_information /* lpProcessInformation */ ))
                    {
400
			fatal_string_error(STRING_EXECFAIL, GetLastError(), sei.lpFile);
401 402
                    }
                    sei.hProcess = process_information.hProcess;
403
                    goto done;
404 405
                }
	}
406

407
        if (!ShellExecuteExW(&sei))
408
            fatal_string_error(STRING_EXECFAIL, GetLastError(), sei.lpFile);
409 410

done:
411
	HeapFree( GetProcessHeap(), 0, args );
412 413
	HeapFree( GetProcessHeap(), 0, dos_filename );
	HeapFree( GetProcessHeap(), 0, parent_directory );
414

415 416
	if (sei.fMask & SEE_MASK_NOCLOSEPROCESS) {
		DWORD exitcode;
417 418
		WaitForSingleObject(sei.hProcess, INFINITE);
		GetExitCodeProcess(sei.hProcess, &exitcode);
419 420 421 422 423
		ExitProcess(exitcode);
	}

	ExitProcess(0);
}