Mercurial > emacs
comparison nt/cmdproxy.c @ 19236:9bfe8a6b9575
Initial revision
author | Geoff Voelker <voelker@cs.washington.edu> |
---|---|
date | Sat, 09 Aug 1997 02:53:48 +0000 |
parents | |
children | f645084cc96c |
comparison
equal
deleted
inserted
replaced
19235:759e45894579 | 19236:9bfe8a6b9575 |
---|---|
1 /* Proxy shell designed for use with Emacs on Windows 95 and NT. | |
2 Copyright (C) 1997 Free Software Foundation, Inc. | |
3 | |
4 Accepts subset of Unix sh(1) command-line options, for compatability | |
5 with elisp code written for Unix. When possible, executes external | |
6 programs directly (a common use of /bin/sh by Emacs), otherwise | |
7 invokes the user-specified command processor to handle built-in shell | |
8 commands, batch files and interactive mode. | |
9 | |
10 The main function is simply to process the "-c string" option in the | |
11 way /bin/sh does, since the standard Windows command shells use the | |
12 convention that everything after "/c" (the Windows equivalent of | |
13 "-c") is the input string. | |
14 | |
15 This file is part of GNU Emacs. | |
16 | |
17 GNU Emacs is free software; you can redistribute it and/or modify | |
18 it under the terms of the GNU General Public License as published by | |
19 the Free Software Foundation; either version 2, or (at your option) | |
20 any later version. | |
21 | |
22 GNU Emacs is distributed in the hope that it will be useful, | |
23 but WITHOUT ANY WARRANTY; without even the implied warranty of | |
24 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | |
25 GNU General Public License for more details. | |
26 | |
27 You should have received a copy of the GNU General Public License | |
28 along with GNU Emacs; see the file COPYING. If not, write to | |
29 the Free Software Foundation, Inc., 59 Temple Place - Suite 330, | |
30 Boston, MA 02111-1307, USA. */ | |
31 | |
32 #include <windows.h> | |
33 | |
34 #include <stdarg.h> /* va_args */ | |
35 #include <malloc.h> /* alloca */ | |
36 #include <stdlib.h> /* getenv */ | |
37 #include <string.h> /* strlen */ | |
38 | |
39 | |
40 /******* Mock C library routines *********************************/ | |
41 | |
42 /* These routines are used primarily to minimize the executable size. */ | |
43 | |
44 #define stdin GetStdHandle (STD_INPUT_HANDLE) | |
45 #define stdout GetStdHandle (STD_OUTPUT_HANDLE) | |
46 #define stderr GetStdHandle (STD_ERROR_HANDLE) | |
47 | |
48 int | |
49 vfprintf(HANDLE hnd, char * msg, va_list args) | |
50 { | |
51 DWORD bytes_written; | |
52 char buf[1024]; | |
53 | |
54 wvsprintf (buf, msg, args); | |
55 return WriteFile (hnd, buf, strlen (buf), &bytes_written, NULL); | |
56 } | |
57 | |
58 int | |
59 fprintf(HANDLE hnd, char * msg, ...) | |
60 { | |
61 va_list args; | |
62 int rc; | |
63 | |
64 va_start (args, msg); | |
65 rc = vfprintf (hnd, msg, args); | |
66 va_end (args); | |
67 | |
68 return rc; | |
69 } | |
70 | |
71 int | |
72 printf(char * msg, ...) | |
73 { | |
74 va_list args; | |
75 int rc; | |
76 | |
77 va_start (args, msg); | |
78 rc = vfprintf (stdout, msg, args); | |
79 va_end (args); | |
80 | |
81 return rc; | |
82 } | |
83 | |
84 void | |
85 fail (char * msg, ...) | |
86 { | |
87 va_list args; | |
88 | |
89 va_start (args, msg); | |
90 vfprintf (stderr, msg, args); | |
91 va_end (args); | |
92 | |
93 exit (1); | |
94 } | |
95 | |
96 void | |
97 warn (char * msg, ...) | |
98 { | |
99 va_list args; | |
100 | |
101 va_start (args, msg); | |
102 vfprintf (stderr, msg, args); | |
103 va_end (args); | |
104 } | |
105 | |
106 /******************************************************************/ | |
107 | |
108 char * | |
109 canon_filename (char *fname) | |
110 { | |
111 char *p = fname; | |
112 | |
113 while (*p) | |
114 { | |
115 if (*p == '/') | |
116 *p = '\\'; | |
117 p++; | |
118 } | |
119 | |
120 return fname; | |
121 } | |
122 | |
123 char * | |
124 skip_space (char *str) | |
125 { | |
126 while (isspace (*str)) str++; | |
127 return str; | |
128 } | |
129 | |
130 char * | |
131 skip_nonspace (char *str) | |
132 { | |
133 while (*str && !isspace (*str)) str++; | |
134 return str; | |
135 } | |
136 | |
137 int escape_char = '\\'; | |
138 | |
139 /* Get next token from input, advancing pointer. */ | |
140 int | |
141 get_next_token (char * buf, char ** pSrc) | |
142 { | |
143 char * p = *pSrc; | |
144 char * o = buf; | |
145 | |
146 p = skip_space (p); | |
147 if (*p == '"') | |
148 { | |
149 int escape_char_run = 0; | |
150 | |
151 /* Go through src until an ending quote is found, unescaping | |
152 quotes along the way. If the escape char is not quote, then do | |
153 special handling of multiple escape chars preceding a quote | |
154 char (ie. the reverse of what Emacs does to escape quotes). */ | |
155 p++; | |
156 while (1) | |
157 { | |
158 if (p[0] == escape_char && escape_char != '"') | |
159 { | |
160 escape_char_run++; | |
161 continue; | |
162 } | |
163 else if (p[0] == '"') | |
164 { | |
165 while (escape_char_run > 1) | |
166 { | |
167 *o++ = escape_char; | |
168 escape_char_run -= 2; | |
169 } | |
170 | |
171 if (escape_char_run > 0) | |
172 { | |
173 /* escaped quote */ | |
174 *o++ = *p++; | |
175 escape_char_run = 0; | |
176 } | |
177 else if (p[1] == escape_char && escape_char == '"') | |
178 { | |
179 /* quote escaped by doubling */ | |
180 *o++ = *p; | |
181 p += 2; | |
182 } | |
183 else | |
184 { | |
185 /* The ending quote. */ | |
186 *o = '\0'; | |
187 /* Leave input pointer after token. */ | |
188 p++; | |
189 break; | |
190 } | |
191 } | |
192 else if (p[0] == '\0') | |
193 { | |
194 /* End of string, but no ending quote found. We might want to | |
195 flag this as an error, but for now will consider the end as | |
196 the end of the token. */ | |
197 *o = '\0'; | |
198 break; | |
199 } | |
200 else | |
201 { | |
202 *o++ = *p++; | |
203 } | |
204 } | |
205 } | |
206 else | |
207 { | |
208 /* Next token is delimited by whitespace. */ | |
209 char * p1 = skip_nonspace (p); | |
210 memcpy (o, p, p1 - p); | |
211 o += (p1 - p); | |
212 p = p1; | |
213 } | |
214 | |
215 *pSrc = p; | |
216 | |
217 return o - buf; | |
218 } | |
219 | |
220 /* Search for EXEC file in DIR. If EXEC does not have an extension, | |
221 DIR is searched for EXEC with the standard extensions appended. */ | |
222 int | |
223 search_dir (char *dir, char *exec, int bufsize, char *buffer) | |
224 { | |
225 char *exts[] = {".bat", ".cmd", ".exe", ".com"}; | |
226 int n_exts = sizeof (exts) / sizeof (char *); | |
227 char *dummy; | |
228 int i, rc; | |
229 | |
230 /* Search the directory for the program. */ | |
231 for (i = 0; i < n_exts; i++) | |
232 { | |
233 rc = SearchPath (dir, exec, exts[i], bufsize, buffer, &dummy); | |
234 if (rc > 0) | |
235 return rc; | |
236 } | |
237 | |
238 return 0; | |
239 } | |
240 | |
241 /* Return the absolute name of executable file PROG, including | |
242 any file extensions. If an absolute name for PROG cannot be found, | |
243 return NULL. */ | |
244 char * | |
245 make_absolute (char *prog) | |
246 { | |
247 char absname[MAX_PATH]; | |
248 char dir[MAX_PATH]; | |
249 char curdir[MAX_PATH]; | |
250 char *p, *fname; | |
251 char *path; | |
252 int i; | |
253 | |
254 /* At least partial absolute path specified; search there. */ | |
255 if ((isalpha (prog[0]) && prog[1] == ':') || | |
256 (prog[0] == '\\')) | |
257 { | |
258 /* Split the directory from the filename. */ | |
259 fname = strrchr (prog, '\\'); | |
260 if (!fname) | |
261 /* Only a drive specifier is given. */ | |
262 fname = prog + 2; | |
263 strncpy (dir, prog, fname - prog); | |
264 dir[fname - prog] = '\0'; | |
265 | |
266 /* Search the directory for the program. */ | |
267 if (search_dir (dir, prog, MAX_PATH, absname) > 0) | |
268 return strdup (absname); | |
269 else | |
270 return NULL; | |
271 } | |
272 | |
273 if (GetCurrentDirectory (MAX_PATH, curdir) <= 0) | |
274 return NULL; | |
275 | |
276 /* Relative path; search in current dir. */ | |
277 if (strpbrk (prog, "\\")) | |
278 { | |
279 if (search_dir (curdir, prog, MAX_PATH, absname) > 0) | |
280 return strdup (absname); | |
281 else | |
282 return NULL; | |
283 } | |
284 | |
285 /* Just filename; search current directory then PATH. */ | |
286 path = alloca (strlen (getenv ("PATH")) + strlen (curdir) + 2); | |
287 strcpy (path, curdir); | |
288 strcat (path, ";"); | |
289 strcat (path, getenv ("PATH")); | |
290 | |
291 while (*path) | |
292 { | |
293 /* Get next directory from path. */ | |
294 p = path; | |
295 while (*p && *p != ';') p++; | |
296 strncpy (dir, path, p - path); | |
297 dir[p - path] = '\0'; | |
298 | |
299 /* Search the directory for the program. */ | |
300 if (search_dir (dir, prog, MAX_PATH, absname) > 0) | |
301 return strdup (absname); | |
302 | |
303 /* Move to the next directory. */ | |
304 path = p + 1; | |
305 } | |
306 | |
307 return NULL; | |
308 } | |
309 | |
310 /*****************************************************************/ | |
311 | |
312 #if 0 | |
313 char ** _argv; | |
314 int _argc; | |
315 | |
316 /* Parse commandline into argv array, allowing proper quoting of args. */ | |
317 void | |
318 setup_argv (void) | |
319 { | |
320 char * cmdline = GetCommandLine (); | |
321 int arg_bytes = 0; | |
322 | |
323 | |
324 } | |
325 #endif | |
326 | |
327 /* Information about child proc is global, to allow for automatic | |
328 termination when interrupted. At the moment, only one child process | |
329 can be running at any one time. */ | |
330 | |
331 PROCESS_INFORMATION child; | |
332 int interactive = TRUE; | |
333 | |
334 BOOL | |
335 console_event_handler (DWORD event) | |
336 { | |
337 switch (event) | |
338 { | |
339 case CTRL_C_EVENT: | |
340 case CTRL_BREAK_EVENT: | |
341 if (!interactive) | |
342 { | |
343 /* Both command.com and cmd.exe have the annoying behaviour of | |
344 prompting "Terminate batch job (y/n)?" when interrupted | |
345 while running a batch file, even if running in | |
346 non-interactive (-c) mode. Try to make up for this | |
347 deficiency by forcibly terminating the subprocess if | |
348 running non-interactively. */ | |
349 if (child.hProcess && | |
350 WaitForSingleObject (child.hProcess, 500) != WAIT_OBJECT_0) | |
351 TerminateProcess (child.hProcess, 0); | |
352 exit (STATUS_CONTROL_C_EXIT); | |
353 } | |
354 break; | |
355 | |
356 #if 0 | |
357 default: | |
358 /* CLOSE, LOGOFF and SHUTDOWN events - actually we don't get these | |
359 under Windows 95. */ | |
360 fail ("cmdproxy: received %d event\n", event); | |
361 if (child.hProcess) | |
362 TerminateProcess (child.hProcess, 0); | |
363 #endif | |
364 } | |
365 return TRUE; | |
366 } | |
367 | |
368 int | |
369 spawn (char * progname, char * cmdline) | |
370 { | |
371 DWORD rc = 0xff; | |
372 SECURITY_ATTRIBUTES sec_attrs; | |
373 STARTUPINFO start; | |
374 | |
375 sec_attrs.nLength = sizeof (sec_attrs); | |
376 sec_attrs.lpSecurityDescriptor = NULL; | |
377 sec_attrs.bInheritHandle = FALSE; | |
378 | |
379 memset (&start, 0, sizeof (start)); | |
380 start.cb = sizeof (start); | |
381 | |
382 if (CreateProcess (progname, cmdline, &sec_attrs, NULL, TRUE, | |
383 0, NULL, NULL, &start, &child)) | |
384 { | |
385 /* wait for completion and pass on return code */ | |
386 WaitForSingleObject (child.hProcess, INFINITE); | |
387 GetExitCodeProcess (child.hProcess, &rc); | |
388 CloseHandle (child.hThread); | |
389 CloseHandle (child.hProcess); | |
390 child.hProcess = NULL; | |
391 } | |
392 | |
393 return (int) rc; | |
394 } | |
395 | |
396 /******* Main program ********************************************/ | |
397 | |
398 int | |
399 main (int argc, char ** argv) | |
400 { | |
401 int rc; | |
402 int need_shell; | |
403 char * cmdline; | |
404 char * progname; | |
405 int envsize; | |
406 char modname[MAX_PATH]; | |
407 char path[MAX_PATH]; | |
408 | |
409 | |
410 interactive = TRUE; | |
411 | |
412 SetConsoleCtrlHandler ((PHANDLER_ROUTINE) console_event_handler, TRUE); | |
413 | |
414 /* We serve double duty: we can be called either as a proxy for the | |
415 real shell (that is, because we are defined to be the user shell), | |
416 or in our role as a helper application for running DOS programs. | |
417 In the former case, we interpret the command line options as if we | |
418 were a Unix shell, but in the latter case we simply pass our | |
419 command line to CreateProcess. We know which case we are dealing | |
420 with by whether argv[0] refers to ourself or to some other program. | |
421 (This relies on an arcane feature of CreateProcess, where we can | |
422 specify cmdproxy as the module to run, but specify a different | |
423 program in the command line - the MSVC startup code sets argv[0] | |
424 from the command line.) */ | |
425 | |
426 if (!GetModuleFileName (NULL, modname, sizeof (modname))) | |
427 fail ("GetModuleFileName failed"); | |
428 | |
429 /* Although Emacs always sets argv[0] to an absolute pathname, we | |
430 might get run in other ways as well, so convert argv[0] to an | |
431 absolute name before comparing to the module name. */ | |
432 if (!SearchPath (NULL, argv[0], ".exe", sizeof (path), path, &progname) | |
433 || stricmp (modname, path) != 0) | |
434 { | |
435 /* We are being used as a helper to run a DOS app; just pass | |
436 command line to DOS app without change. */ | |
437 /* TODO: fill in progname. */ | |
438 return spawn (NULL, GetCommandLine ()); | |
439 } | |
440 | |
441 /* Process command line. If running interactively (-c or /c not | |
442 specified) then spawn a real command shell, passing it the command | |
443 line arguments. | |
444 | |
445 If not running interactively, then attempt to execute the specified | |
446 command directly. If necessary, spawn a real shell to execute the | |
447 command. | |
448 | |
449 */ | |
450 | |
451 progname = NULL; | |
452 cmdline = NULL; | |
453 /* If no args, spawn real shell for interactive use. */ | |
454 need_shell = TRUE; | |
455 interactive = TRUE; | |
456 /* Ask for a reasonable size environment for command.com. */ | |
457 envsize = 1024; | |
458 | |
459 while (--argc > 0) | |
460 { | |
461 ++argv; | |
462 /* Only support single letter switches (except for -e); allow / as | |
463 switch char for compatability with cmd.exe. */ | |
464 if ( ((*argv)[0] == '-' || (*argv)[0] == '/') | |
465 && (*argv)[1] != '\0' && (*argv)[2] == '\0' ) | |
466 { | |
467 if ( ((*argv)[1] == 'c') && ((*argv)[2] == '\0') ) | |
468 { | |
469 if (--argc == 0) | |
470 fail ("error: expecting arg for %s", *argv); | |
471 cmdline = *(++argv); | |
472 interactive = FALSE; | |
473 } | |
474 else if ( ((*argv)[1] == 'i') && ((*argv)[2] == '\0') ) | |
475 { | |
476 if (cmdline) | |
477 warn ("warning: %s ignored because of -c", *argv); | |
478 } | |
479 else if ( ((*argv)[1] == 'e') && ((*argv)[2] == ':') ) | |
480 { | |
481 envsize = atoi (*argv + 3); | |
482 /* Enforce a reasonable minimum size. */ | |
483 if (envsize < 256) | |
484 envsize = 256; | |
485 } | |
486 else | |
487 { | |
488 warn ("warning: unknown option %s ignored", *argv); | |
489 } | |
490 } | |
491 else | |
492 break; | |
493 } | |
494 | |
495 /* If -c option, determine if we must spawn a real shell, or if we can | |
496 execute the command directly ourself. */ | |
497 if (cmdline) | |
498 { | |
499 /* If no redirection or piping, and if program can be found, then | |
500 run program directly. Otherwise invoke a real shell. */ | |
501 | |
502 static char copout_chars[] = "|<>&"; | |
503 | |
504 if (strpbrk (cmdline, copout_chars) == NULL) | |
505 { | |
506 char *args; | |
507 | |
508 /* The program name is the first token of cmdline. Since | |
509 filenames cannot legally contain embedded quotes, the value | |
510 of escape_char doesn't matter. */ | |
511 args = cmdline; | |
512 if (!get_next_token (path, &args)) | |
513 fail ("error: no program name specified.\n"); | |
514 | |
515 canon_filename (path); | |
516 progname = make_absolute (path); | |
517 | |
518 /* If we found the program, run it directly (if not found it | |
519 might be an internal shell command, so don't fail). */ | |
520 if (progname != NULL) | |
521 need_shell = FALSE; | |
522 } | |
523 } | |
524 | |
525 if (need_shell) | |
526 { | |
527 char * p; | |
528 | |
529 progname = getenv ("COMSPEC"); | |
530 if (!progname) | |
531 fail ("error: COMSPEC is not set"); | |
532 | |
533 canon_filename (progname); | |
534 progname = make_absolute (progname); | |
535 | |
536 if (progname == NULL || strchr (progname, '\\') == NULL) | |
537 fail ("make_absolute failed"); | |
538 | |
539 if (cmdline) | |
540 { | |
541 /* Convert to syntax expected by cmd.exe/command.com for | |
542 running non-interactively. Always quote program name in | |
543 case path contains spaces (fortunately it can't contain | |
544 quotes, since they are illegal in path names). */ | |
545 wsprintf (p = alloca (strlen (cmdline) + strlen (progname) + 7), | |
546 "\"%s\" /c %s", progname, cmdline); | |
547 cmdline = p; | |
548 } | |
549 else | |
550 { | |
551 /* Provide dir arg expected by command.com when first started | |
552 interactively (the "command search path"). cmd.exe does | |
553 not require it, but accepts it silently - presumably other | |
554 DOS compatible shells do the same. To avoid potential | |
555 problems with spaces in command dir (which cannot be quoted | |
556 - command.com doesn't like it), we always use the 8.3 form. */ | |
557 GetShortPathName (progname, path, sizeof (path)); | |
558 p = strrchr (path, '\\'); | |
559 /* Trailing slash is acceptable. */ | |
560 p++; | |
561 | |
562 /* Set environment size - again cmd.exe ignores this silently. */ | |
563 wsprintf (p, " /e:%d", envsize); | |
564 | |
565 /* Quote progname in case it contains spaces. */ | |
566 wsprintf (cmdline = alloca (strlen (progname) + strlen (path) + 4), | |
567 "\"%s\" %s", progname, path); | |
568 } | |
569 } | |
570 | |
571 if (!progname) | |
572 fail ("Internal error: program name not defined\n"); | |
573 | |
574 if (!cmdline) | |
575 cmdline = progname; | |
576 | |
577 rc = spawn (progname, cmdline); | |
578 | |
579 return rc; | |
580 } |