1/* Licensed to the Apache Software Foundation (ASF) under one or more 2 * contributor license agreements. See the NOTICE file distributed with 3 * this work for additional information regarding copyright ownership. 4 * The ASF licenses this file to You under the Apache License, Version 2.0 5 * (the "License"); you may not use this file except in compliance with 6 * the License. You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17/* -------------------------------------------------------------------- 18 * 19 * wintty : a Apache/WinNT support utility for monitoring and 20 * reflecting user feedback from the Apache process via 21 * stdin/stdout, even as running within the service context. 22 * 23 * Originally contributed by William Rowe <wrowe covalent.net> 24 * 25 * Note: this implementation is _very_ experimental, and error handling 26 * is far from complete. Using it as a cgi or pipe process allows the 27 * programmer to discover if facilities such as reliable piped logs 28 * are working as expected, or answer operator prompts that would 29 * otherwise be discarded by the service process. 30 * 31 * Also note the isservice detection semantics, which far exceed any 32 * mechanism we have discovered thus far. 33 * 34 * -------------------------------------------------------------------- 35 */ 36 37#define WIN32_LEAN_AND_MEAN 38#include <windows.h> 39#include <stdlib.h> 40#include <stdio.h> 41 42#if defined(_MSC_VER) && _MSC_VER >= 1400 43#define _CRT_SECURE_NO_DEPRECATE 44#pragma warning(disable: 4996) 45#endif 46 47const char *options = 48"\nwintty: a utility for echoing the stdin stream to a new console window,\n" 49"\teven when invoked from within a service (such as the Apache server.)\n" 50"\tAlso reflects the console input back to the stdout stream, allowing\n" 51"\tthe operator to respond to prompts from the context of a service.\n\n" 52"Syntax: %s [opts] [-t \"Window Title\"]\n\n" 53" opts: -c{haracter} or -l{ine} input\n" 54"\t-q{uiet} or -e{cho} input\n" 55"\t-u{nprocessed} or -p{rocessed} input\n" 56"\t-n{owrap} or -w{rap} output lines\n" 57"\t-f{ormatted} or -r{aw} output lines\n" 58"\t-O{output} [number of seconds]\n" 59"\t-v{erbose} error reporting (for debugging)\n" 60"\t-? for this message\n\n"; 61 62BOOL verbose = FALSE; 63 64void printerr(char *fmt, ...) 65{ 66 char str[1024]; 67 va_list args; 68 if (!verbose) 69 return; 70 va_start(args, fmt); 71 wvsprintf(str, fmt, args); 72 OutputDebugString(str); 73} 74 75DWORD WINAPI feedback(LPVOID args); 76 77typedef struct feedback_args_t { 78 HANDLE in; 79 HANDLE out; 80} feedback_args_t; 81 82int main(int argc, char** argv) 83{ 84 char str[1024], *contitle = NULL; 85 HANDLE hproc, thread; 86 HANDLE hwinsta = NULL, hsavewinsta; 87 HANDLE hdesk = NULL, hsavedesk = NULL; 88 HANDLE conin, conout; 89 HANDLE hstdin, hstdout, hstderr, hdup; 90 feedback_args_t feed; 91 DWORD conmode; 92 DWORD newinmode = 0, notinmode = 0; 93 DWORD newoutmode = 0, notoutmode = 0; 94 DWORD tid; 95 DWORD len; 96 DWORD timeout = INFINITE; 97 BOOL isservice = FALSE; 98 char *arg0 = argv[0]; 99 100 while (--argc) { 101 ++argv; 102 if (**argv == '/' || **argv == '-') { 103 switch (tolower((*argv)[1])) { 104 case 'c': 105 notinmode |= ENABLE_LINE_INPUT; break; 106 case 'l': 107 newinmode |= ENABLE_LINE_INPUT; break; 108 case 'q': 109 notinmode |= ENABLE_ECHO_INPUT; break; 110 case 'e': 111 newinmode |= ENABLE_ECHO_INPUT; break; 112 case 'u': 113 notinmode |= ENABLE_PROCESSED_INPUT; break; 114 case 'p': 115 newinmode |= ENABLE_PROCESSED_INPUT; break; 116 case 'n': 117 notoutmode |= ENABLE_WRAP_AT_EOL_OUTPUT; break; 118 case 'w': 119 newoutmode |= ENABLE_WRAP_AT_EOL_OUTPUT; break; 120 case 'r': 121 notoutmode |= ENABLE_PROCESSED_OUTPUT; break; 122 case 'f': 123 newoutmode |= ENABLE_PROCESSED_OUTPUT; break; 124 case 'o': 125 if (*(argv + 1) && *(argv + 1)[0] != '-') { 126 *(++argv); 127 timeout = atoi(*argv) / 1000; 128 --argc; 129 } 130 else { 131 timeout = 0; 132 } 133 break; 134 case 'v': 135 verbose = TRUE; 136 break; 137 case 't': 138 contitle = *(++argv); 139 --argc; 140 break; 141 case '?': 142 printf(options, arg0); 143 exit(1); 144 default: 145 printf("wintty option %s not recognized, use -? for help.\n\n", *argv); 146 exit(1); 147 } 148 } 149 else { 150 printf("wintty argument %s not understood, use -? for help.\n\n", *argv); 151 exit(1); 152 } 153 } 154 155 hproc = GetCurrentProcess(); 156 hsavewinsta = GetProcessWindowStation(); 157 if (!hsavewinsta || hsavewinsta == INVALID_HANDLE_VALUE) { 158 printerr("GetProcessWindowStation() failed (%d)\n", GetLastError()); 159 } 160 else if (!GetUserObjectInformation(hsavewinsta, UOI_NAME, str, sizeof(str), &len)) { 161 printerr("GetUserObjectInfoformation(hWinSta) failed (%d)\n", GetLastError()); 162 } 163 else if (strnicmp(str, "Service-", 8) == 0) { 164 printerr("WindowStation Name %s is a service\n", str); 165 isservice = TRUE; 166 } 167 SetLastError(0); 168 169 hstdin = GetStdHandle(STD_INPUT_HANDLE); 170 if (!hstdin || hstdin == INVALID_HANDLE_VALUE) { 171 printerr("GetStdHandle(STD_INPUT_HANDLE) failed (%d)\n", 172 GetLastError()); 173 } 174 else if (DuplicateHandle(hproc, hstdin, hproc, &hdup, 0, 175 isservice, DUPLICATE_SAME_ACCESS)) { 176 CloseHandle(hstdin); 177 hstdin = hdup; 178 } 179 else { 180 printerr("DupHandle(stdin [%x]) failed (%d)\n", 181 hstdin, GetLastError()); 182 } 183 184 hstdout = GetStdHandle(STD_OUTPUT_HANDLE); 185 if (!hstdout || hstdout == INVALID_HANDLE_VALUE) { 186 printerr("GetStdHandle(STD_OUTPUT_HANDLE) failed (%d)\n", 187 GetLastError()); 188 } 189 else if (DuplicateHandle(hproc, hstdout, hproc, &hdup, 0, 190 isservice, DUPLICATE_SAME_ACCESS)) { 191 CloseHandle(hstdout); 192 hstdout = hdup; 193 } 194 else { 195 printerr("DupHandle(stdout [%x]) failed (%d)\n", 196 hstdout, GetLastError()); 197 } 198 199 hstderr = GetStdHandle(STD_ERROR_HANDLE); 200 if (!hstderr || hstderr == INVALID_HANDLE_VALUE) { 201 printerr("GetStdHandle(STD_ERROR_HANDLE) failed (%d)\n", 202 GetLastError()); 203 } 204 else if (DuplicateHandle(hproc, hstderr, hproc, &hdup, 0, 205 isservice, DUPLICATE_SAME_ACCESS)) { 206 CloseHandle(hstderr); 207 hstderr = hdup; 208 } 209 else { 210 printerr("DupHandle(stderr [%x]) failed (%d)\n", 211 hstderr, GetLastError()); 212 } 213 214 /* You can't close the console till all the handles above were 215 * rescued by DuplicateHandle() 216 */ 217 if (!FreeConsole()) 218 printerr("FreeConsole() failed (%d)\n", GetLastError()); 219 220 if (isservice) { 221#ifdef WE_EVER_FIGURE_OUT_WHY_THIS_DOESNT_WORK 222 hsavedesk = GetThreadDesktop(GetCurrentThreadId()); 223 if (!hsavedesk || hsavedesk == INVALID_HANDLE_VALUE) { 224 printerr("GetThreadDesktop(GetTID()) failed (%d)\n", GetLastError()); 225 } 226 CloseWindowStation(hwinsta); 227 hwinsta = OpenWindowStation("WinSta0", TRUE, MAXIMUM_ALLOWED); 228 if (!hwinsta || hwinsta == INVALID_HANDLE_VALUE) { 229 printerr("OpenWinSta(WinSta0) failed (%d)\n", GetLastError()); 230 } 231 else if (!SetProcessWindowStation(hwinsta)) { 232 printerr("SetProcWinSta(WinSta0) failed (%d)\n", GetLastError()); 233 } 234 hdesk = OpenDesktop("Default", 0, TRUE, MAXIMUM_ALLOWED); 235 if (!hdesk || hdesk == INVALID_HANDLE_VALUE) { 236 printerr("OpenDesktop(Default) failed (%d)\n", GetLastError()); 237 } 238 else if (!SetThreadDesktop(hdesk)) { 239 printerr("SetThreadDesktop(Default) failed (%d)\n", GetLastError()); 240 } 241#else 242 PROCESS_INFORMATION pi; 243 STARTUPINFO si; 244 DWORD exitcode = 1; 245 char appbuff[MAX_PATH]; 246 char *appname = NULL; 247 char *cmdline = GetCommandLine(); 248 249 if (!GetModuleFileName(NULL, appbuff, sizeof(appbuff))) { 250 appname = appbuff; 251 } 252 253 memset(&si, 0, sizeof(si)); 254 si.cb = sizeof(si); 255 si.dwFlags = STARTF_USESHOWWINDOW 256 | STARTF_USESTDHANDLES; 257 si.lpDesktop = "WinSta0\\Default"; 258 si.wShowWindow = 1; /* SW_SHOWNORMAL */ 259 si.hStdInput = hstdin; 260 si.hStdOutput = hstdout; 261 si.hStdError = hstderr; 262 263 /* Instantly, upon creating the new process, we will close our 264 * copies of the handles so our parent isn't confused when the 265 * child closes their copy of the handle. Without this action, 266 * we would hold a copy of the handle, and the parent would not 267 * receive their EOF notification. 268 */ 269 if (CreateProcess(appname, cmdline, NULL, NULL, TRUE, 270 CREATE_SUSPENDED | CREATE_NEW_CONSOLE, 271 NULL, NULL, &si, &pi)) { 272 CloseHandle(si.hStdInput); 273 CloseHandle(si.hStdOutput); 274 CloseHandle(si.hStdError); 275 ResumeThread(pi.hThread); 276 CloseHandle(pi.hThread); 277 WaitForSingleObject(pi.hProcess, INFINITE); 278 GetExitCodeProcess(pi.hProcess, &exitcode); 279 CloseHandle(pi.hProcess); 280 return exitcode; 281 } 282 return 1; 283#endif 284 } 285 286 if (!AllocConsole()) { 287 printerr("AllocConsole(Default) failed (%d)\n", GetLastError()); 288 } 289 290 if (contitle && !SetConsoleTitle(contitle)) { 291 printerr("SetConsoleTitle() failed (%d)\n", GetLastError()); 292 } 293 294 conout = CreateFile("CONOUT$", GENERIC_READ | GENERIC_WRITE, 295 FILE_SHARE_READ | FILE_SHARE_WRITE, 296 FALSE, OPEN_EXISTING, 0, NULL); 297 if (!conout || conout == INVALID_HANDLE_VALUE) { 298 printerr("CreateFile(CONOUT$) failed (%d)\n", GetLastError()); 299 } 300 else if (!GetConsoleMode(conout, &conmode)) { 301 printerr("GetConsoleMode(CONOUT) failed (%d)\n", GetLastError()); 302 } 303 else if (!SetConsoleMode(conout, conmode = ((conmode | newoutmode) 304 & ~notoutmode))) { 305 printerr("SetConsoleMode(CONOUT, 0x%x) failed (%d)\n", 306 conmode, GetLastError()); 307 } 308 309 conin = CreateFile("CONIN$", GENERIC_READ | GENERIC_WRITE, 310 FILE_SHARE_READ | FILE_SHARE_WRITE, 311 FALSE, OPEN_EXISTING, 0, NULL); 312 if (!conin || conin == INVALID_HANDLE_VALUE) { 313 printerr("CreateFile(CONIN$) failed (%d)\n", GetLastError()); 314 } 315 else if (!GetConsoleMode(conin, &conmode)) { 316 printerr("GetConsoleMode(CONIN) failed (%d)\n", GetLastError()); 317 } 318 else if (!SetConsoleMode(conin, conmode = ((conmode | newinmode) 319 & ~notinmode))) { 320 printerr("SetConsoleMode(CONIN, 0x%x) failed (%d)\n", 321 conmode, GetLastError()); 322 } 323 324 feed.in = conin; 325 feed.out = hstdout; 326 thread = CreateThread(NULL, 0, feedback, (LPVOID)&feed, 0, &tid); 327 328 while (ReadFile(hstdin, str, sizeof(str), &len, NULL)) 329 if (!len || !WriteFile(conout, str, len, &len, NULL)) 330 break; 331 332 printerr("[EOF] from stdin (%d)\n", GetLastError()); 333 334 CloseHandle(stdout); 335 if (!GetConsoleTitle(str, sizeof(str))) { 336 printerr("SetConsoleTitle() failed (%d)\n", GetLastError()); 337 } 338 else { 339 strcat(str, " - [Finished]"); 340 if (!SetConsoleTitle(str)) { 341 printerr("SetConsoleTitle() failed (%d)\n", GetLastError()); 342 } 343 } 344 345 WaitForSingleObject(thread, timeout); 346 FreeConsole(); 347 if (isservice) { 348 if (!SetProcessWindowStation(hsavewinsta)) { 349 len = GetLastError(); 350 } 351 if (!SetThreadDesktop(hsavedesk)) { 352 len = GetLastError(); 353 } 354 CloseDesktop(hdesk); 355 CloseWindowStation(hwinsta); 356 } 357 return 0; 358} 359 360 361DWORD WINAPI feedback(LPVOID arg) 362{ 363 feedback_args_t *feed = (feedback_args_t*)arg; 364 char *str[1024]; 365 DWORD len; 366 367 while (ReadFile(feed->in, str, sizeof(str), &len, NULL)) 368 if (!len || !WriteFile(feed->out, str, len, &len, NULL)) 369 break; 370 371 printerr("[EOF] from Console (%d)\n", GetLastError()); 372 373 return 0; 374} 375