handling space characters in executable's path
[fw/sdcc] / support / Util / MySystem.c
1 /*-------------------------------------------------------------------------
2   MySystem - SDCC Support function
3
4              Written By -  Sandeep Dutta . sandeep.dutta@usa.net (1999)
5
6    This program is free software; you can redistribute it and/or modify it
7    under the terms of the GNU General Public License as published by the
8    Free Software Foundation; either version 2, or (at your option) any
9    later version.
10
11    This program is distributed in the hope that it will be useful,
12    but WITHOUT ANY WARRANTY; without even the implied warranty of
13    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
14    GNU General Public License for more details.
15
16    You should have received a copy of the GNU General Public License
17    along with this program; if not, write to the Free Software
18    Foundation, 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
19
20    In other words, you are welcome to use, share and improve this program.
21    You are forbidden to forbid anyone else to use, share and improve
22    what you give them.   Help stamp out software-hoarding!
23 -------------------------------------------------------------------------*/
24
25 #ifdef _WIN32
26 #undef DATADIR
27 #include <windows.h>
28 /* avoid DATADIR definition clash :-( */
29 #include <io.h>
30 #else
31 #include <unistd.h>
32 #endif
33 #include <ctype.h>
34 #include "SDCCglobl.h"
35 #include "SDCCutil.h"
36 #include "MySystem.h"
37 #include "newalloc.h"
38
39
40 set *binPathSet = NULL; /* set of binary paths */
41
42
43 /*!
44  * get command and arguments from command line
45  */
46
47 static void
48 split_command(const char *cmd_line, char **command, char **params)
49 {
50   const char *p, *cmd_start;
51   char delim;
52   char *str;
53   unsigned len;
54
55   /* skip leading spaces */
56   for (p = cmd_line; isspace(*p); p++)
57     ;
58
59   /* get command */
60   switch (*p) {
61   case '\'':
62   case '"':
63     delim = *p;
64     cmd_start = ++p;
65     break;
66
67   default:
68     delim = ' ';
69     cmd_start = p;
70   }
71
72   if (delim == ' ') {
73     while (*p != '\0' && !isspace(*p))
74       p++;
75   }
76   else {
77     while (*p != '\0' && *p != delim)
78       p++;
79   }
80
81   if (command != NULL) {
82     len = p - cmd_start;
83     str = Safe_alloc(len + 1);
84     strncpy(str, cmd_start, len);
85     str[len] = '\0';
86     *command = str;
87   }
88
89   p++;
90
91   /* skip spaces before parameters */
92   while (isspace(*p))
93     p++;
94
95   /* get parameters */
96   if (params != NULL)
97     *params = Safe_strdup(p);
98 }
99
100
101 /*!
102  * find the command:
103  * 1) if the command is specified by path, try it
104  * 2) try to find the command in predefined path's
105  * 3) trust on $PATH
106  */
107
108 #ifdef _WIN32
109 /* WIN32 version */
110
111 /*
112  * I don't like this solution, but unfortunately cmd.exe and command.com
113  * don't accept something like this:
114  * "program" "argument"
115  * Cmd.exe accepts the following:
116  * ""program" "argument""
117  * but command.com doesn't.
118  * The following is accepted by both:
119  * program "argument"
120  *
121  * So the most portable WIN32 solution is to use GetShortPathName() for
122  * program to get rid of spaces, so that quotes are not needed :-(
123  * Using spawnvp() instead of system() is more portable cross platform approach,
124  * but then also a substitute for _popen() should be developed...
125  */
126
127 #define EXE_EXT ".exe"
128
129 /*!
130  * merge command and parameters to command line
131  */
132
133 static char *
134 merge_command(const char *command, const char *params)
135 {
136   /* allocate extra space for ' ' and '\0' */
137   char *cmd_line = (char *)Safe_alloc(strlen(command) + strlen(params) + 2);
138   sprintf(cmd_line, "%s %s", command, params);
139
140   return cmd_line;
141 }
142
143
144 /*!
145  * check if path/command exist by converting it to short file name
146  * if it exists, compose with args and return it
147  */
148
149 static char *
150 compose_command_line(const char *path, const char *command, const char *args)
151 {
152   unsigned len;
153   char cmdPath[PATH_MAX];
154   char shortPath[PATH_MAX];
155
156   if (path != NULL)
157     SNPRINTF(cmdPath, sizeof cmdPath,
158       "%s" DIR_SEPARATOR_STRING "%s", path, command);
159   else
160     strncpyz(cmdPath, command, sizeof cmdPath);
161
162   /* Try if cmdPath or cmdPath.exe exist by converting it to the short path name */
163   len = GetShortPathName(cmdPath, shortPath, sizeof shortPath);
164   assert(len < sizeof shortPath);
165   if (0 == len) {
166     len = GetShortPathName(strncatz(cmdPath, EXE_EXT, sizeof cmdPath), shortPath, sizeof shortPath);
167     assert(len < sizeof shortPath);
168   }
169   if (0 != len) {
170     /* compose the command line */
171     return merge_command(shortPath, args);
172   }
173   else {
174     /* path/command not found */
175     return NULL;
176   }
177 }
178
179
180 static char *
181 get_path(const char *cmd)
182 {
183   char *cmdLine;
184   char *command;
185   char *args;
186   char *path;
187
188   /* get the command */
189   split_command(cmd, &command, &args);
190
191   if (NULL == (cmdLine = compose_command_line(NULL, command, args))) {
192     /* not an absolute path: try to find the command in predefined binary paths */
193     if (NULL != (path = (char *)setFirstItem(binPathSet))) {
194       while (NULL == (cmdLine  = compose_command_line(path, command, args)) &&
195         NULL != (path = (char *)setNextItem(binPathSet)))
196         ;
197     }
198
199     if (NULL == cmdLine) {
200       /* didn't found the command in predefined binary paths: try with PATH */
201       char *envPath;
202
203       if (NULL != (envPath = getenv("PATH"))) {
204         /* make a local copy; strtok() will modify it */
205         envPath = Safe_strdup(envPath);
206
207         if (NULL != (path = strtok(envPath, ";"))) {
208           while (NULL == (cmdLine = compose_command_line(path, command, args)) &&
209            NULL != (path = strtok(NULL, ";")))
210            ;
211         }
212
213         Safe_free(envPath);
214       }
215     }
216
217     /* didn't found it; probably this won't help neither :-( */
218     if (NULL == cmdLine)
219       cmdLine = merge_command(command, args);
220   }
221
222   Safe_free(command);
223   Safe_free(args);
224
225   return cmdLine;
226 }
227
228 #else
229 /* *nix version */
230
231 /*!
232  * merge command and parameters to command line
233  */
234
235 static char *
236 merge_command(const char *command, const char *params)
237 {
238   /* allocate extra space for 2x'"', ' ' and '\0' */
239   char *cmd_line = (char *)Safe_alloc(strlen(command) + strlen(params) + 4);
240   sprintf(cmd_line, "\"%s\" %s", command, params);
241
242   return cmd_line;
243 }
244
245
246 /*!
247  * check if the path is absolute
248  */
249
250 static int
251 has_path(const char *path)
252 {
253   if (strrchr(path, DIR_SEPARATOR_CHAR) == NULL)
254 #ifdef _WIN32
255     /* try *nix dir separator on WIN32 */
256     if (strrchr(path, UNIX_DIR_SEPARATOR_CHAR) == NULL)
257 #endif
258       return 0;
259
260   return 1;
261 }
262
263
264 static char *
265 get_path(const char *cmd)
266 {
267   char *cmdLine = NULL;
268   char *command;
269   char *args;
270   char *path;
271   char cmdPath[PATH_MAX];
272
273
274   /* get the command */
275   split_command(cmd, &command, &args);
276
277   if (!has_path(command)) {
278     /* try to find the command in predefined binary paths */
279     if (NULL != (path = (char *)setFirstItem(binPathSet))) {
280       do
281       {
282         SNPRINTF(cmdPath, sizeof cmdPath,
283           "%s" DIR_SEPARATOR_STRING "%s", path, command);
284
285         /* Try if cmdPath */
286         if (0 == access(cmdPath, X_OK)) {
287           /* compose the command line */
288           cmdLine = merge_command(cmdPath, args);
289           break;
290         }
291       } while (NULL != (path = (char *)setNextItem(binPathSet)));
292     }
293     if (NULL == cmdLine)
294       cmdLine = merge_command(command, args);
295
296     Safe_free(command);
297     Safe_free(args);
298
299     return cmdLine;
300   }
301   else {
302     /*
303      * the command is defined with absolute path:
304      * just return it
305      */
306     Safe_free(command);
307     Safe_free(args);
308
309     return Safe_strdup(cmd);
310   }
311 }
312 #endif
313
314
315 /*!
316  * call an external program with arguements
317  */
318
319 int
320 my_system(const char *cmd)
321 {
322   int e;
323   char *cmdLine = get_path(cmd);
324
325   assert(NULL != cmdLine);
326
327   if (options.verboseExec) {
328       printf("+ %s\n", cmdLine);
329   }
330
331   e = system(cmdLine);
332   Safe_free(cmdLine);
333
334   return e;
335 }
336
337
338 /*!
339  * pipe an external program with arguements
340  */
341
342 #ifdef _WIN32
343 #define popen_read(cmd) _popen((cmd), "rt")
344 #else
345 #define popen_read(cmd) popen((cmd), "r")
346 #endif
347
348 FILE *
349 my_popen(const char *cmd)
350 {
351   FILE *fp;
352   char *cmdLine = get_path(cmd);
353
354   assert(NULL != cmdLine);
355
356   if (options.verboseExec) {
357       printf("+ %s\n", cmdLine);
358   }
359
360   fp = popen_read(cmdLine);
361   Safe_free(cmdLine);
362
363   return fp;
364 }