Signed-off-by: Eric Pouech eric.pouech@gmail.com
--- programs/wineconsole/Makefile.in | 3 - programs/wineconsole/wineconsole.c | 95 ++++++++++++++++++++++++++++++++++-- 2 files changed, 91 insertions(+), 7 deletions(-)
diff --git a/programs/wineconsole/Makefile.in b/programs/wineconsole/Makefile.in index b859ef8a27a..08ad51d2b6a 100644 --- a/programs/wineconsole/Makefile.in +++ b/programs/wineconsole/Makefile.in @@ -1,6 +1,5 @@ MODULE = wineconsole.exe -IMPORTS = advapi32 -DELAYIMPORTS = comctl32 user32 gdi32 +DELAYIMPORTS = user32
EXTRADLLFLAGS = -mwindows -municode
diff --git a/programs/wineconsole/wineconsole.c b/programs/wineconsole/wineconsole.c index 1924bf791e6..c703127c238 100644 --- a/programs/wineconsole/wineconsole.c +++ b/programs/wineconsole/wineconsole.c @@ -32,14 +32,101 @@
WINE_DEFAULT_DEBUG_CHANNEL(console);
-int WINAPI wWinMain( HINSTANCE inst, HINSTANCE prev, WCHAR *cmdline, INT show ) +static const unsigned int EC_INTERNAL = 255; /* value of exit_code for internal errors */ + +/*********************************************************************** + * build_command_line + * + * Build the command line of a process from the argv array. + * (copied from dlls/ntdll/unix/env.c) + * + * We must quote and escape characters so that the argv array can be rebuilt + * from the command line: + * - spaces and tabs must be quoted + * 'a b' -> '"a b"' + * - quotes must be escaped + * '"' -> '"' + * - if ''s are followed by a '"', they must be doubled and followed by '"', + * resulting in an odd number of '' followed by a '"' + * '"' -> '\"' + * '\"' -> '\\"' + * - ''s are followed by the closing '"' must be doubled, + * resulting in an even number of '' followed by a '"' + * ' ' -> '" \"' + * ' \' -> '" \\"' + * - ''s that are not followed by a '"' can be left as is + * 'a\b' == 'a\b' + * 'a\b' == 'a\b' + */ +static WCHAR *build_command_line( WCHAR **wargv ) +{ + int len; + WCHAR **arg, *ret; + LPWSTR p; + + len = 1; + for (arg = wargv; *arg; arg++) len += 3 + 2 * wcslen( *arg ); + if (!(ret = malloc( len * sizeof(WCHAR) ))) return NULL; + + p = ret; + for (arg = wargv; *arg; arg++) + { + BOOL has_space, has_quote; + int i, bcount; + WCHAR *a; + + /* check for quotes and spaces in this argument (first arg is always quoted) */ + has_space = (arg == wargv) || !**arg || wcschr( *arg, ' ' ) || wcschr( *arg, '\t' ); + has_quote = wcschr( *arg, '"' ) != NULL; + + /* now transfer it to the command line */ + if (has_space) *p++ = '"'; + if (has_quote || has_space) + { + bcount = 0; + for (a = *arg; *a; a++) + { + if (*a == '\') bcount++; + else + { + if (*a == '"') /* double all the '\' preceding this '"', plus one */ + for (i = 0; i <= bcount; i++) *p++ = '\'; + bcount = 0; + } + *p++ = *a; + } + } + else + { + wcscpy( p, *arg ); + p += wcslen( p ); + } + if (has_space) + { + /* Double all the '' preceding the closing quote */ + for (i = 0; i < bcount; i++) *p++ = '\'; + *p++ = '"'; + } + *p++ = ' '; + } + if (p > ret) p--; /* remove last space */ + *p = 0; + if (p - ret >= 32767) + { + ERR( "command line too long (%Iu)\n", p - ret ); + exit( EC_INTERNAL ); + } + return ret; +} + +int wmain( int argc, WCHAR *argv[] ) { STARTUPINFOW startup = { sizeof(startup) }; PROCESS_INFORMATION info; - WCHAR *cmd = cmdline; + WCHAR *cmd; DWORD exit_code;
- static WCHAR default_cmd[] = L"cmd"; + cmd = argc > 1 ? build_command_line( &argv[1] ) : wcsdup( L"cmd.exe" );
FreeConsole(); /* make sure we're not connected to inherited console */ if (!AllocConsole()) @@ -48,8 +135,6 @@ int WINAPI wWinMain( HINSTANCE inst, HINSTANCE prev, WCHAR *cmdline, INT show ) return 1; }
- if (!*cmd) cmd = default_cmd; - startup.dwFlags = STARTF_USESTDHANDLES; startup.hStdInput = CreateFileW( L"CONIN$", GENERIC_READ | GENERIC_WRITE, 0, NULL, OPEN_EXISTING, 0, 0 );
with modifications on CreateProcess and management of CUI subsystem processes, it's handy to provide fine control to users when launching their CUI applications: - kind of windows (console) - which std handles - wait for child termination or not...
Signed-off-by: Eric Pouech eric.pouech@gmail.com
--- programs/wineconsole/wineconsole.c | 118 ++++++++++++++++++++++--------- programs/wineconsole/wineconsole.man.in | 65 ++++++++++++++++- programs/wineconsole/wineconsole.rc | 20 +++++ programs/wineconsole/wineconsole_res.h | 3 + 4 files changed, 167 insertions(+), 39 deletions(-)
diff --git a/programs/wineconsole/wineconsole.c b/programs/wineconsole/wineconsole.c index c703127c238..71fcb1a50d8 100644 --- a/programs/wineconsole/wineconsole.c +++ b/programs/wineconsole/wineconsole.c @@ -33,6 +33,28 @@ WINE_DEFAULT_DEBUG_CHANNEL(console);
static const unsigned int EC_INTERNAL = 255; /* value of exit_code for internal errors */ +static FILE* freport; + +static void message( unsigned id, const WCHAR* pmt ) +{ + WCHAR format[1024], msg[1024]; + DWORD_PTR args[1]; + args[0] = (DWORD_PTR)pmt; + + if (LoadStringW( GetModuleHandleW( NULL ), id, format, ARRAY_SIZE(format) ) && + FormatMessageW( FORMAT_MESSAGE_ARGUMENT_ARRAY | FORMAT_MESSAGE_FROM_STRING, + format, 0, 0, msg, ARRAY_SIZE(msg), (va_list*)args )) + fwprintf( freport, L"%s\n", msg ); + else + ERR("Failed to format\n"); +} + +static void usage( LPCWSTR option ) +{ + if (option) message( IDS_CMD_UNKNOWN_OPTION, option ); + message( IDS_CMD_USAGE, NULL ); + exit( EC_INTERNAL ); +}
/*********************************************************************** * build_command_line @@ -123,44 +145,72 @@ int wmain( int argc, WCHAR *argv[] ) { STARTUPINFOW startup = { sizeof(startup) }; PROCESS_INFORMATION info; - WCHAR *cmd; - DWORD exit_code; - - cmd = argc > 1 ? build_command_line( &argv[1] ) : wcsdup( L"cmd.exe" ); - - FreeConsole(); /* make sure we're not connected to inherited console */ - if (!AllocConsole()) - { - ERR( "failed to allocate console: %lu\n", GetLastError() ); - return 1; - } + DWORD cpflags = CREATE_NEW_CONSOLE; + BOOL inherit = FALSE, dowait = TRUE; + WCHAR *cmdline; + int i;
- startup.dwFlags = STARTF_USESTDHANDLES; - startup.hStdInput = CreateFileW( L"CONIN$", GENERIC_READ | GENERIC_WRITE, 0, NULL, - OPEN_EXISTING, 0, 0 ); - startup.hStdOutput = CreateFileW( L"CONOUT$", GENERIC_READ | GENERIC_WRITE, 0, NULL, - OPEN_EXISTING, 0, 0 ); - startup.hStdError = startup.hStdOutput; - - if (!CreateProcessW( NULL, cmd, NULL, NULL, FALSE, 0, NULL, NULL, &startup, &info )) + freport = stderr; + for (i = 1; i < argc; i++) { - WCHAR format[256], *buf; - INPUT_RECORD ir; - DWORD len; - exit_code = GetLastError(); - WARN( "CreateProcess failed: %lu\n", exit_code ); - LoadStringW( GetModuleHandleW( NULL ), IDS_CMD_LAUNCH_FAILED, format, ARRAY_SIZE(format) ); - len = wcslen( format ) + wcslen( cmd ); - if ((buf = malloc( len * sizeof(WCHAR) ))) + if (argv[i][0] != '-') break; + if (argv[i][1] == '-' && !argv[i][2]) {i++; break;} + if ( !wcscmp(argv[i], L"--detached")) cpflags = DETACHED_PROCESS; + else if (!wcscmp(argv[i], L"--console")) cpflags = CREATE_NEW_CONSOLE; + else if (!wcscmp(argv[i], L"--headless")) cpflags = CREATE_NO_WINDOW; + else if (!wcscmp(argv[i], L"--console-std")) inherit = FALSE; + else if (!wcscmp(argv[i], L"--inherit-std")) inherit = TRUE; + else if (!wcscmp(argv[i], L"--dontwait")) dowait = FALSE; + else if (!wcscmp(argv[i], L"--wait")) dowait = TRUE; + else if (!wcsncmp(argv[i], L"--report=", 9) && argv[i][9]) { - swprintf( buf, len, format, cmd ); - WriteConsoleW( startup.hStdOutput, buf, wcslen(buf), &len, NULL); - while (ReadConsoleInputW( startup.hStdInput, &ir, 1, &len ) && ir.EventType == MOUSE_EVENT); + FILE* f = _wfopen(argv[i] + 9, L"w+"); + if (!f) + { + message( IDS_CMD_REPORT_OPEN, argv[i] + 9 ); + exit( EC_INTERNAL ); + } + freport = f; } - return exit_code; + else if (!wcscmp(argv[i], L"--help") || !wcscmp(argv[i], L"-?")) + usage( NULL ); + else usage( argv[i] ); + } + cmdline = i < argc ? build_command_line( &argv[i] ) : wcsdup( L"cmd.exe" ); + if (inherit) + { + startup.dwFlags |= STARTF_USESTDHANDLES; + startup.hStdInput = GetStdHandle( STD_INPUT_HANDLE ); + startup.hStdOutput = GetStdHandle( STD_OUTPUT_HANDLE ); + startup.hStdError = GetStdHandle( STD_ERROR_HANDLE ); + } + if (!CreateProcessW( NULL, cmdline, NULL, NULL, TRUE, cpflags, NULL, NULL, &startup, &info )) + { + WARN( "CreateProcess failed (%ls): %lu\n", cmdline, GetLastError() ); + message( IDS_CMD_LAUNCH_FAILED, cmdline ); + return EC_INTERNAL; } - CloseHandle( info.hThread ); - WaitForSingleObject( info.hProcess, INFINITE ); - return GetExitCodeProcess( info.hProcess, &exit_code ) ? exit_code : GetLastError(); + if (dowait) + { + DWORD exit_code; + HANDLE hJob; + JOBOBJECT_EXTENDED_LIMIT_INFORMATION jobinfo; + + /* set parent and child into the same job: if parent gets killed (unix kill), + * child will be terminated as well. + */ + SetConsoleCtrlHandler( NULL, TRUE ); + hJob = CreateJobObjectW( NULL, NULL ); + memset( &jobinfo, 0, sizeof(jobinfo) ); + jobinfo.BasicLimitInformation.LimitFlags = + JOB_OBJECT_LIMIT_KILL_ON_JOB_CLOSE | + JOB_OBJECT_LIMIT_SILENT_BREAKAWAY_OK; + SetInformationJobObject( hJob, JobObjectExtendedLimitInformation, &jobinfo, sizeof(jobinfo) ); + AssignProcessToJobObject( hJob, info.hProcess ); + + WaitForSingleObject( info.hProcess, INFINITE ); + return GetExitCodeProcess( info.hProcess, &exit_code ) ? exit_code : EC_INTERNAL; + } + return 0; } diff --git a/programs/wineconsole/wineconsole.man.in b/programs/wineconsole/wineconsole.man.in index dac4e62f321..8a71be60564 100644 --- a/programs/wineconsole/wineconsole.man.in +++ b/programs/wineconsole/wineconsole.man.in @@ -1,13 +1,70 @@ .TH WINECONSOLE 1 "November 2010" "@PACKAGE_STRING@" "Wine Programs" .SH NAME -wineconsole - The Wine console +wineconsole - A Wine helper for managing the console used for running applications .SH SYNOPSIS .B wineconsole -.RI [ command "] " +.RI "[ " options " ] [ " command " ] " .SH DESCRIPTION .B wineconsole -is the Wine console manager, used to run console commands and applications. It allows running the -console in a newly made window. + +allows to have fine grain management over console and standard I/O streams used when running an application under Wine. +.SH OPTIONS + +.IP \fB--console\fR +\fBwineconsole\fR will execute the command in a newly created window. + +This is the default when none of the \fB--detached\fR, \fB--headless\fR or \fB--console\fR options is provided. +.IP \fB--detached\fR +\fBwineconsole\fR will execute the \fIcommand\fR without being attached to any console. +.IP \fB--headless\fR +\fBwineconsole\fR will execute the \fIcommand\fR attached to an invisible console. + +.IP \fB--console-std\fR +The \fIcommand\fR's standard I/O streams will be mapped to the console ones. + +This is the default when neither \fB--console-std\fR nor \fB--inherit-std\fR is provided. +.IP \fB--inherit-std\fR +The \fIcommand\fR's standard I/O streams will be mapped to the standard streams \fBwineconsole\fR is run with. + +.IP \fB--wait\fR +Wait for \fIcommand\fR to terminate before \fBwineconsole\fR terminates itself. + +.IP \fB--dontwait\fR +Terminate after starting \fIcommand\fR without waiting for it to terminate. + +.IP \fB--report=filename\fR +Write detailed error information into filename. Can be useful either when \fBwineconsole\fR isn't run attached to a console, or when its output stream is already redirected. + +.IP \fIcommand\fR +The name of the executable to run, potentially followed by its arguments, with same meaning and syntax than using \fBwine\fR. + +If this part is omitted, than \fBcmd.exe\fR is run without arguments. + +When an error occurs inside \fBwineconsole\fR, 255 is returned as exit status. +Upon success, in \fB--dontwait\fR mode the exit status is 0, while in \fB--wait\fR mode, the exit status of the \fBwineconsole\fR is the exit status for the \fIcommand\fR. + +.SH NOTES +Consoles are only of interest when the \fIcommand\fR executable belongs to the CUI subsystem. + +Using \fBwineconsole\fR overrides default Wine console creation when invoked from regular shell or script. + +.SH EXAMPLES + +.IP ./wine +wineconsole [ \fIcommand\fR ] + +Run \fIcommand\fR in a newly created standalone console. + +.IP ./wine +wineconsole --headless --inherit-std [ \fIcommand\fR ] >& log + +Run \fIcommand\fR, in an invisible console, having its standard output and error streams redirected into file 'log'. + +.IP ./wine +wineconsole --detached [ \fIcommand\fR ] + +Run \fIcommand\fR, without any console (meaning no possible input, and all \fIcommand\fR's information sent to standard output and error streams are lost). + .SH BUGS Bugs can be reported on the .UR https://bugs.winehq.org diff --git a/programs/wineconsole/wineconsole.rc b/programs/wineconsole/wineconsole.rc index 6a61dd31ede..8229cc1fdf8 100644 --- a/programs/wineconsole/wineconsole.rc +++ b/programs/wineconsole/wineconsole.rc @@ -25,6 +25,24 @@ LANGUAGE LANG_ENGLISH, SUBLANG_DEFAULT STRINGTABLE BEGIN
-IDS_CMD_LAUNCH_FAILED "wineconsole: Starting program %s failed.\nThe command is invalid.\n" +IDS_CMD_UNKNOWN_OPTION "Unknown option %1" +IDS_CMD_USAGE "Usage: wineconsole [options] [command]\n\ +\n\ +options:\n\ + --detached start [command] not being attached to any console\n\ + --console start [command] being attached to a newly created console (this is the default)\n\ + --headless start [command] being attached to a newly created yet not visible console\n\ +\n\ + --console-std ensures standard I/O streams of command are mapped to the console (this is the default)\n\ + --inherit-std ensures standard I/O streams of command are mapped to the ones which wineconsole is run with\n\ +\n\ + --report=filename write error reports into filename instead of stderr\n\ +\n\ + --wait wait for [command] to terminate before returning\n\ + --dontwait terminate as soon as [command] has been started\n\ +\n\ + [command]: executable (and optional arguments) to run" +IDS_CMD_REPORT_OPEN "Couldn't open file '%1' for reporting." +IDS_CMD_LAUNCH_FAILED "wineconsole: Starting program %1 failed.\nThe command is invalid."
END diff --git a/programs/wineconsole/wineconsole_res.h b/programs/wineconsole/wineconsole_res.h index cca60ac5857..213b353cc5e 100644 --- a/programs/wineconsole/wineconsole_res.h +++ b/programs/wineconsole/wineconsole_res.h @@ -22,4 +22,7 @@ #include <winuser.h> #include <commctrl.h>
+#define IDS_CMD_UNKNOWN_OPTION 0x302 +#define IDS_CMD_USAGE 0x303 #define IDS_CMD_LAUNCH_FAILED 0x304 +#define IDS_CMD_REPORT_OPEN 0x305