tentative fix for issue 3 (ex 53)
[mplib] / src / texk / kpathsea / lookcmd.c
1 /* libc replacement functions for win32.
2
3 Copyright (C) 1998, 99 Free Software Foundation, Inc.
4
5 This library is free software; you can redistribute it and/or
6 modify it under the terms of the GNU Library General Public
7 License as published by the Free Software Foundation; either
8 version 2 of the License, or (at your option) any later version.
9
10 This library is distributed in the hope that it will be useful,
11 but WITHOUT ANY WARRANTY; without even the implied warranty of
12 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
13 Library General Public License for more details.
14
15 You should have received a copy of the GNU Library General Public
16 License along with this library; if not, write to the Free Software
17 Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.  */
18
19 /*
20   This does make sense only under WIN32.
21   Functions:
22     - look_for_cmd() : locates an executable file
23     - parse_cmd_line() : splits a command with pipes and redirections
24     - build_cmd_line() : builds a command with pipes and redirections (useful ?)
25   */
26
27 /*
28   This part looks for the real location of the program invoked
29   by cmd. If it can find the program, that's good. Else 
30   command processor is invoked.
31 */
32
33 #ifdef __MINGW32__
34
35 #include <kpathsea/config.h>
36 #include <kpathsea/c-proto.h>
37 #include <kpathsea/win32lib.h>
38 #include <kpathsea/lib.h>
39 #include <errno.h>
40
41 BOOL 
42 look_for_cmd(const char *cmd, char **app, char **new)
43 {
44   char *env_path;
45   char *p, *q;
46   char pname[MAXPATHLEN], *fp;
47   char *suffixes[] = { ".bat", ".cmd", ".com", ".exe", NULL };
48   char **s;
49   char *app_name, *new_cmd;
50
51   BOOL go_on;
52
53   *new = *app = NULL;
54   new_cmd = app_name = NULL;
55
56   /* We should look for the application name along the PATH,
57      and decide to prepend "%COMSPEC% /c " or not to the command line.
58      Do nothing for the moment. */
59
60   /* Another way to do that would be to try CreateProcess first without
61      invoking cmd, and look at the error code. If it fails because of
62      command not found, try to prepend "cmd /c" to the cmd line.
63   */
64
65   /* Look for the application name */
66   for (p = (char *)cmd; *p && isspace(*p); p++);
67   if (*p == '"') {
68     q = ++p;
69     while(*p && *p != '"') p++;
70     if (*p == '\0') {
71       fprintf(stderr, "Look_for_cmd: malformed command (\" not terminated)\n");
72       return FALSE;
73     }
74   }
75   else
76     for (q = p; *p && !isspace(*p); p++);
77   /* q points to the beginning of appname, p to the last + 1 char */
78   if ((app_name = malloc(p - q + 1)) == NULL) {
79     fprintf(stderr, "Look_for_cmd: malloc(app_name) failed.\n");
80     return FALSE;
81   }
82   strncpy(app_name, q, p - q );
83   app_name[p - q] = '\0';
84   pname[0] = '\0';
85 #ifdef TRACE
86   fprintf(stderr, "popen: app_name = %s\n", app_name);
87 #endif
88
89   {
90     char *tmp = getenv("PATH");
91     env_path = xmalloc(strlen(tmp) + 3);
92     strcpy(env_path, ".;");
93     strcat(env_path, tmp);
94   }
95
96   /* Looking for appname on the path */
97   for (s = suffixes, go_on = TRUE; go_on; *s++) {
98     if (SearchPath(env_path,    /* Address of search path */
99                    app_name,    /* Address of filename */
100                    *s,          /* Address of extension */
101                    MAXPATHLEN,  /* Size of destination buffer */
102                    pname,       /* Address of destination buffer */
103                    &fp)         /* File part of app_name */
104         != 0) {
105 #ifdef TRACE
106       fprintf(stderr, "%s found with suffix %s\nin %s\n", app_name, *s, pname);
107 #endif
108       new_cmd = xstrdup(cmd);
109       free(app_name);
110       app_name = xstrdup(pname);
111       break;
112     }
113     go_on = (*s != NULL);
114   }
115   if (go_on == FALSE) {
116     /* the app_name was not found */
117 #ifdef TRACE
118     fprintf(stderr, "%s not found, concatenating comspec\n", app_name);
119 #endif
120     new_cmd = concatn(getenv("COMSPEC"), " /c ", cmd, NULL);
121     free(app_name);
122     app_name = NULL;
123   }
124   else {
125   }
126   if (env_path) free(env_path);
127
128   *new = new_cmd;
129   *app = app_name;
130
131   return TRUE;
132
133 }
134
135 /*
136   Command parser. Borrowed from DJGPP.
137  */
138
139 static BOOL __system_allow_multiple_cmds = FALSE;
140
141 typedef enum {
142   EMPTY,
143   WORDARG,
144   REDIR_INPUT,
145   REDIR_APPEND,
146   REDIR_OUTPUT,
147   PIPE,
148   SEMICOLON,
149   UNMATCHED_QUOTE,
150   EOL
151 } cmd_sym_t;
152
153 /* Return a copy of a word between BEG and (excluding) END with all
154    quoting characters removed from it.  */
155
156 static char *
157 __unquote (char *to, const char *beg, const char *end)
158 {
159   const char *s = beg;
160   char *d = to;
161   int quote = 0;
162
163   while (s < end)
164     {
165       switch (*s)
166         {
167         case '"':
168         case '\'':
169           if (!quote)
170             quote = *s;
171           else if (quote == *s)
172             quote = 0;
173           s++;
174           break;
175         case '\\':
176           if (s[1] == '"' || s[1] == '\''
177               || (s[1] == ';'
178                   && (__system_allow_multiple_cmds)))
179             s++;
180           /* Fall-through.  */
181         default:
182           *d++ = *s++;
183           break;
184         }
185     }
186
187   *d = 0;
188   return to;
189 }
190
191 /* A poor-man's lexical analyzer for simplified command processing.
192
193    It only knows about these:
194
195      redirection and pipe symbols
196      semi-colon `;' (that possibly ends a command)
197      argument quoting rules with quotes and `\'
198      whitespace delimiters of words (except in quoted args)
199
200    Returns the type of next symbol and pointers to its first and (one
201    after) the last characters.
202
203    Only `get_sym' and `unquote' should know about quoting rules.  */
204
205 static cmd_sym_t
206 get_sym (char *s, char **beg, char **end)
207 {
208   int in_a_word = 0;
209
210   while (isspace (*s))
211     s++;
212
213   *beg = s;
214   
215   do {
216     *end = s + 1;
217
218     if (in_a_word
219         && (!*s || strchr ("<>| \t\n", *s)
220             || ((__system_allow_multiple_cmds) && *s == ';')))
221       {
222         --*end;
223         return WORDARG;
224       }
225
226     switch (*s)
227       {
228       case '<':
229         return REDIR_INPUT;
230       case '>':
231         if (**end == '>')
232           {
233             ++*end;
234             return REDIR_APPEND;
235           }
236         return REDIR_OUTPUT;
237       case '|':
238         return PIPE;
239       case ';':
240         if (__system_allow_multiple_cmds)
241           return SEMICOLON;
242         else
243           in_a_word = 1;
244         break;
245       case '\0':
246         --*end;
247         return EOL;
248       case '\\':
249         if (s[1] == '"' || s[1] == '\''
250             || (s[1] == ';' && (__system_allow_multiple_cmds)))
251           s++;
252         in_a_word = 1;
253         break;
254       case '\'':
255       case '"':
256         {
257           char quote = *s++;
258
259           while (*s && *s != quote)
260             {
261               if (*s++ == '\\' && (*s == '"' || *s == '\''))
262                 s++;
263             }
264           *end = s;
265           if (!*s)
266             return UNMATCHED_QUOTE;
267           in_a_word = 1;
268           break;
269         }
270       default:
271         in_a_word = 1;
272         break;
273       }
274
275     s++;
276
277   } while (1);
278 }
279
280 /*
281   What we allow :
282   [cmd] [arg1] ... [argn] < [redinput] | [cmd2] | ... | [cmdn] > [redoutput]
283 */
284 void *parse_cmdline(char *line, char **input, char **output)
285 {
286   BOOL again, needcmd = TRUE, bSuccess = TRUE, append_out = FALSE;
287   char *beg = line, *end, *new_end;
288   cmd_sym_t token = EMPTY, prev_token = EMPTY;
289   int ncmd = 0, narg = 1;
290   char **fp;
291   char ***cmd;
292   char *dummy_input;                    /* So that we could pass NULL */
293   char *dummy_output;                   /* instead of a real ??put */
294
295   if (input == NULL) input = &dummy_input;
296   if (output == NULL) output = &dummy_output;
297
298   *input = NULL;
299   *output = NULL;
300   cmd = xmalloc(MAX_PIPES*sizeof(char **));
301   cmd[ncmd] = NULL;
302 #ifdef TRACE
303   fprintf(stderr, "line = %s\n", line);
304 #endif
305   do {
306     again = FALSE;
307     prev_token = token;
308     token = get_sym (beg, &beg, &end);  /* get next symbol */
309 #ifdef TRACE
310     fprintf(stderr, "token = %s\n", beg);
311 #endif  
312     switch (token) {
313     case WORDARG:
314       if (prev_token == REDIR_INPUT
315           || prev_token == REDIR_OUTPUT) {
316         fprintf(stderr, "Ambigous input/output redirect.");
317         bSuccess = FALSE;
318         goto leave;
319       }
320       /* First word we see is the program to run.  */
321       if (needcmd) {
322         narg = 1;
323         cmd[ncmd] = xmalloc(narg * sizeof(char *));
324         cmd[ncmd][narg - 1] = xmalloc(end - beg + 1);
325         __unquote (cmd[ncmd][narg - 1], beg, end); /* unquote and copy to prog */
326         if (cmd[ncmd][narg - 1][0] == '(') {
327           fprintf(stderr, "parse_cmdline(%s): Parenthesized groups not allowed.\n", line);
328           bSuccess = FALSE;
329           goto leave;
330         }
331         needcmd = FALSE;
332       }
333       else {
334         narg++;
335         cmd[ncmd] = xrealloc(cmd[ncmd], narg * sizeof(char *));
336         cmd[ncmd][narg - 1] = xmalloc(end - beg + 1);
337         __unquote (cmd[ncmd][narg - 1], beg, end); /* unquote and copy to prog */
338       }
339       beg = end; /* go forward */
340       again = TRUE;
341       break;
342
343     case REDIR_INPUT:
344     case REDIR_OUTPUT:
345     case REDIR_APPEND:
346       if (token == REDIR_INPUT) {
347         if (*input) {
348           fprintf(stderr, "Ambiguous input redirect.");
349           errno = EINVAL;
350           bSuccess = FALSE;
351           goto leave;
352         }
353         fp = input;
354       }
355       else if (token == REDIR_OUTPUT || token == REDIR_APPEND) {
356         if (*output) {
357           fprintf(stderr, "Ambiguous output redirect.");
358           errno = EINVAL;
359           bSuccess = FALSE;
360           goto leave;
361         }
362         fp = output;
363         if (token == REDIR_APPEND)
364           append_out = TRUE;
365       }
366       if (get_sym (end, &end, &new_end) != WORDARG) {
367         fprintf(stderr, "Target of redirect is not a filename.");
368         errno = EINVAL;
369         bSuccess = FALSE;
370         goto leave;
371       }
372       *fp = (char *)xmalloc (new_end - end + 1);
373       memcpy (*fp, end, new_end - end);
374       (*fp)[new_end - end] = '\0';
375       beg = new_end;
376       again = TRUE;
377       break;
378     case PIPE:
379       if (*output) {
380         fprintf(stderr, "Ambiguous output redirect.");
381         errno = EINVAL;
382         bSuccess = FALSE;
383         goto leave;
384       }
385       narg++;
386       cmd[ncmd] = xrealloc(cmd[ncmd], narg * sizeof(char *));
387       cmd[ncmd][narg - 1] = NULL;
388       ncmd++; 
389       needcmd = TRUE;
390       beg = end;
391       again = TRUE;
392       break;
393     case SEMICOLON:
394     case EOL:
395       if (needcmd) {
396         fprintf(stderr, "No command name seen.");
397         errno = EINVAL;
398         bSuccess = FALSE;
399         goto leave;
400       }
401       narg++;
402       cmd[ncmd] = xrealloc(cmd[ncmd], narg * sizeof(char *));
403       cmd[ncmd][narg - 1] = NULL;
404       ncmd++;
405       cmd[ncmd] = NULL;
406       again = FALSE;
407       break;
408           
409     case UNMATCHED_QUOTE:
410       fprintf(stderr, "Unmatched quote character.");
411       errno = EINVAL;
412       bSuccess = FALSE;
413       goto leave;
414     default:
415       fprintf(stderr, "I cannot grok this.");
416       errno = EINVAL;
417       bSuccess = FALSE;
418       goto leave;
419
420     }
421
422   }     while (again);
423
424  leave:
425   if (!bSuccess) {
426     int i;
427     char **p;
428     /* Need to free everything that was allocated */
429     for (i = 0; i < ncmd; i++) {
430       for (p = cmd[i]; *p; p++) 
431         free(*p);
432       free(cmd[i]);
433     }
434     if (cmd[ncmd]) {
435       for (i = 0; i < narg; i++)
436         free(cmd[ncmd][i]);
437       free(cmd[ncmd]);
438     }
439     free(cmd);
440     *cmd = NULL;
441   }
442   return cmd;
443 }
444
445 static char *
446 quote_elt(char *elt) 
447 {
448   char *p;
449   for (p = elt; *p; p++)
450     if (isspace(*p))
451       return concat3("\"", elt, "\"");
452
453   return xstrdup(elt);
454 }
455
456 /* static (commented out for mingw; SK) */ char *
457 quote_args(char **argv)
458 {
459   int i;
460   char *line = NULL, *new_line;
461   char *new_argv;
462
463   if (!argv)
464     return line;
465
466   line = quote_elt(argv[0]);
467   for (i = 1; argv[i]; i++) {
468     new_argv = quote_elt(argv[i]);
469     new_line = concat3(line, " ", new_argv);
470     free(line);
471     free(new_argv);
472     line = new_line;
473   }
474
475   return line;
476 }
477
478 char *
479 build_cmdline(char ***cmd, char *input, char *output)
480 {
481   int ncmd;
482   char *line = NULL, *new_line;
483
484   if (!cmd)
485     return line;
486
487   line = quote_args(cmd[0]);
488   if (input) {
489     new_line = concat3(line, " < ", quote_elt(input));
490     free(line);
491     line = new_line;
492   }
493   for(ncmd = 1; cmd[ncmd]; ncmd++) {
494     new_line = concat3(line, " | ", quote_args(cmd[ncmd]));
495     free(line);
496     line = new_line;
497   }
498
499   if (output) {
500     new_line = concat3(line, " > ", quote_elt(output));
501     free(line);
502     line = new_line;
503   }
504
505   return line;
506 }
507
508 #ifdef TEST
509 void 
510 display_cmd(char ***cmd, char *input, char *output, char *errput)
511 {
512   int ncmd = 0, narg;
513   
514   if (cmd) {
515     for (ncmd = 0; cmd[ncmd]; ncmd++) {
516       printf("cmd[%d] = %s ", ncmd, cmd[ncmd][0]);
517       for(narg = 1; cmd[ncmd][narg] != NULL; narg++)
518         printf("%s ", cmd[ncmd][narg]);
519       printf("\n");
520     }
521   }
522   printf("input = %s\n", (input ? input : "<stdin>"));
523   printf("output = %s\n", (output ? output : "<stdout>"));
524   printf("errput = %s\n", (errput ? errput : "<stderr>"));
525 }
526
527 main(int argc, char *argv[])
528 {
529   char *input;
530   char *output;
531   char ***cmd;
532   char *line;
533
534   printf("%s\n", line = "foo a b");
535   if (cmd = parse_cmdline(line, &input, &output)) {
536     display_cmd(cmd, input, output, NULL);
537   }
538   printf("%s\n", line = "foo < a > b");
539   if (cmd = parse_cmdline(line, &input, &output)) {
540     display_cmd(cmd, input, output, NULL);
541   }
542   printf("%s\n", line = "foo > a < b");
543   if (cmd = parse_cmdline(line, &input, &output)) {
544     display_cmd(cmd, input, output, NULL);
545   }
546   printf("%s\n", line = "foo | a b");
547   if (cmd = parse_cmdline(line, &input, &output)) {
548     display_cmd(cmd, input, output, NULL);
549   }
550   printf("%s\n", line = "foo a | b");
551   if (cmd = parse_cmdline(line, &input, &output)) {
552     display_cmd(cmd, input, output, NULL);
553   }
554   printf("%s\n", line = "foo | a > b");
555   if (cmd = parse_cmdline(line, &input, &output)) {
556     display_cmd(cmd, input, output, NULL);
557   }
558   printf("%s\n", line = "foo < a | b");
559   if (cmd = parse_cmdline(line, &input, &output)) {
560     display_cmd(cmd, input, output, NULL);
561   }
562   printf("%s\n", line = "foo < a | b");
563   if (cmd = parse_cmdline(line, &input, &output)) {
564     display_cmd(cmd, input, output, NULL);
565   }
566   printf("%s\n", line = "foo < a | b | c > d");
567   if (cmd = parse_cmdline(line, &input, &output)) {
568     display_cmd(cmd, input, output, NULL);
569   }
570   printf("%s\n", line = "foo > a | b < c");
571   if (cmd = parse_cmdline(line, &input, &output)) {
572     display_cmd(cmd, input, output, NULL);
573   }
574
575   printf("%s\n", line = "\"C:\\Program Files\\WinEdt\\WinEdt.exe\" -F \"[Open('%f');SelLine(%l,8)]\"");
576   if (cmd = parse_cmdline(line, &input, &output)) {
577     display_cmd(cmd, input, output, NULL);
578   }
579
580 }
581 #endif
582
583 #endif