VirtualBox

source: kBuild/trunk/src/kmk/kmkbuiltin/redirect.c@ 2667

Last change on this file since 2667 was 2667, checked in by bird, 12 years ago

kmk_redirect: Quote arguments on windows and support single quotation input args. Fixes #114.

  • Property svn:eol-style set to native
  • Property svn:keywords set to Author Date Id Revision
File size: 20.6 KB
Line 
1/* $Id: redirect.c 2667 2012-11-25 19:52:26Z bird $ */
2/** @file
3 * kmk_redirect - Do simple program <-> file redirection (++).
4 */
5
6/*
7 * Copyright (c) 2007-2012 knut st. osmundsen <bird-kBuild-spamx@anduin.net>
8 *
9 * This file is part of kBuild.
10 *
11 * kBuild is free software; you can redistribute it and/or modify
12 * it under the terms of the GNU General Public License as published by
13 * the Free Software Foundation; either version 3 of the License, or
14 * (at your option) any later version.
15 *
16 * kBuild is distributed in the hope that it will be useful,
17 * but WITHOUT ANY WARRANTY; without even the implied warranty of
18 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
19 * GNU General Public License for more details.
20 *
21 * You should have received a copy of the GNU General Public License
22 * along with kBuild. If not, see <http://www.gnu.org/licenses/>
23 *
24 */
25
26/*******************************************************************************
27* Header Files *
28*******************************************************************************/
29#include "config.h"
30#include <stdio.h>
31#include <stdlib.h>
32#include <string.h>
33#include <errno.h>
34#include <fcntl.h>
35#if defined(_MSC_VER)
36# include <io.h>
37# include <direct.h>
38# include <process.h>
39#else
40# include <unistd.h>
41#endif
42
43#ifdef __OS2__
44# define INCL_BASE
45# include <os2.h>
46# ifndef LIBPATHSTRICT
47# define LIBPATHSTRICT 3
48# endif
49#endif
50
51
52#if defined(_MSC_VER)
53/**
54 * Replaces arguments in need of quoting.
55 *
56 * This will "leak" the original and/or the replacement string, depending on
57 * how you look at it.
58 *
59 * For details on how MSC parses the command line, see "Parsing C Command-Line
60 * Arguments": http://msdn.microsoft.com/en-us/library/a1y7w461.aspx
61 *
62 * @param argc The argument count.
63 * @param argv The argument vector.
64 */
65static void quoteArguments(int argc, char **argv)
66{
67 int i;
68 for (i = 0; i < argc; i++)
69 {
70 const char *pszOrg = argv[i];
71 size_t cchOrg = strlen(pszOrg);
72 const char *pszQuotes = (const char *)memchr(pszOrg, '"', cchOrg);
73 if ( pszQuotes
74 || cchOrg == 0
75 || memchr(pszOrg, '&', cchOrg)
76 || memchr(pszOrg, '>', cchOrg)
77 || memchr(pszOrg, '<', cchOrg)
78 || memchr(pszOrg, '|', cchOrg)
79 || memchr(pszOrg, '%', cchOrg)
80 )
81 {
82 char ch;
83 int fComplicated = pszQuotes || (cchOrg > 0 && pszOrg[cchOrg - 1] == '\\');
84 size_t cchNew = fComplicated ? cchOrg * 2 + 2 : cchOrg + 2;
85 char *pszNew = (char *)malloc(cchNew + 1);
86
87 argv[i] = pszNew;
88
89 *pszNew++ = '"';
90 if (fComplicated)
91 {
92 while ((ch = *pszOrg++) != '\0')
93 {
94 if (ch == '"')
95 {
96 *pszNew++ = '\\';
97 *pszNew++ = '"';
98 }
99 else if (ch == '\\')
100 {
101 /* Backslashes are a bit complicated, they depends on
102 whether a quotation mark follows them or not. They
103 only require escaping if one does. */
104 unsigned cSlashes = 1;
105 while ((ch = *pszOrg) == '\\')
106 {
107 pszOrg++;
108 cSlashes++;
109 }
110 if (ch == '"' || ch == '\0') /* We put a " at the EOS. */
111 {
112 while (cSlashes-- > 0)
113 {
114 *pszNew++ = '\\';
115 *pszNew++ = '\\';
116 }
117 }
118 else
119 while (cSlashes-- > 0)
120 *pszNew++ = '\\';
121 }
122 else
123 *pszNew++ = ch;
124 }
125 }
126 else
127 {
128 memcpy(pszNew, pszOrg, cchOrg);
129 pszNew += cchOrg;
130 }
131 *pszNew++ = '"';
132 *pszNew = '\0';
133 }
134 }
135
136 /*for (i = 0; i < argc; i++) printf("argv[%u]=%s;;\n", i, argv[i]); */
137}
138#endif /* _MSC_VER */
139
140
141static const char *name(const char *pszName)
142{
143 const char *psz = strrchr(pszName, '/');
144#if defined(_MSC_VER) || defined(__OS2__)
145 const char *psz2 = strrchr(pszName, '\\');
146 if (!psz2)
147 psz2 = strrchr(pszName, ':');
148 if (psz2 && (!psz || psz2 > psz))
149 psz = psz2;
150#endif
151 return psz ? psz + 1 : pszName;
152}
153
154
155static int usage(FILE *pOut, const char *argv0)
156{
157 fprintf(pOut,
158 "usage: %s [-[rwa+tb]<fd> <file>] [-c<fd>] [-Z] [-E <var=val>] [-C <dir>] -- <program> [args]\n"
159 " or: %s --help\n"
160 " or: %s --version\n"
161 "\n"
162 "The rwa+tb is like for fopen, if not specified it defaults to w+.\n"
163 "The <fd> is either a number or an alias for the standard handles:\n"
164 " i = stdin\n"
165 " o = stdout\n"
166 " e = stderr\n"
167 "\n"
168 "The -c switch will close the specified file descriptor.\n"
169 "\n"
170 "The -Z switch zaps the environment.\n"
171 "\n"
172 "The -E switch is for making changes to the environment in a putenv\n"
173 "fashion.\n"
174 "\n"
175 "The -C switch is for changing the current directory. This takes immediate\n"
176 "effect, so be careful where you put it.\n"
177 "\n"
178 "This command was originally just a quick hack to avoid invoking the shell\n"
179 "on Windows (cygwin) where forking is very expensive and has exhibited\n"
180 "stability issues on SMP machines. It has since grown into something like\n"
181 "/usr/bin/env on steroids.\n"
182 ,
183 argv0, argv0, argv0);
184 return 1;
185}
186
187
188int main(int argc, char **argv, char **envp)
189{
190 int i;
191#if defined(_MSC_VER)
192 intptr_t rc;
193#endif
194 FILE *pStdErr = stderr;
195 FILE *pStdOut = stdout;
196
197 /*
198 * Parse arguments.
199 */
200 if (argc <= 1)
201 return usage(pStdErr, name(argv[0]));
202 for (i = 1; i < argc; i++)
203 {
204 if (argv[i][0] == '-')
205 {
206 int fd;
207 int fdOpened;
208 int fOpen;
209 char *psz = &argv[i][1];
210 if (*psz == '-')
211 {
212 /* '--' ? */
213 if (!psz[1])
214 {
215 i++;
216 break;
217 }
218
219 /* convert to short. */
220 if (!strcmp(psz, "-help"))
221 psz = "h";
222 else if (!strcmp(psz, "-version"))
223 psz = "V";
224 else if (!strcmp(psz, "-env"))
225 psz = "E";
226 else if (!strcmp(psz, "-chdir"))
227 psz = "C";
228 else if (!strcmp(psz, "-zap-env"))
229 psz = "Z";
230 else if (!strcmp(psz, "-close"))
231 psz = "c";
232 }
233
234 /*
235 * Deal with the obligatory help and version switches first.
236 */
237 if (*psz == 'h')
238 {
239 usage(pStdOut, name(argv[0]));
240 return 0;
241 }
242 if (*psz == 'V')
243 {
244 printf("kmk_redirect - kBuild version %d.%d.%d (r%u)\n"
245 "Copyright (C) 2007-2012 knut st. osmundsen\n",
246 KBUILD_VERSION_MAJOR, KBUILD_VERSION_MINOR, KBUILD_VERSION_PATCH,
247 KBUILD_SVN_REV);
248 return 0;
249 }
250
251 /*
252 * Environment switch?
253 */
254 if (*psz == 'E')
255 {
256 psz++;
257 if (*psz == ':' || *psz == '=')
258 psz++;
259 else
260 {
261 if (i + 1 >= argc)
262 {
263 fprintf(pStdErr, "%s: syntax error: no argument for %s\n", name(argv[0]), argv[i]);
264 return 1;
265 }
266 psz = argv[++i];
267 }
268#ifdef __OS2__
269 if ( !strncmp(psz, "BEGINLIBPATH=", sizeof("BEGINLIBPATH=") - 1)
270 || !strncmp(psz, "ENDLIBPATH=", sizeof("ENDLIBPATH=") - 1)
271 || !strncmp(psz, "LIBPATHSTRICT=", sizeof("LIBPATHSTRICT=") - 1))
272 {
273 ULONG ulVar = *psz == 'B' ? BEGIN_LIBPATH
274 : *psz == 'E' ? END_LIBPATH
275 : LIBPATHSTRICT;
276 const char *pszVal = strchr(psz, '=') + 1;
277 APIRET rc = DosSetExtLIBPATH(pszVal, ulVar);
278 if (rc)
279 {
280 fprintf(pStdErr, "%s: error: DosSetExtLibPath(\"%s\", %.*s (%lu)): %lu\n",
281 name(argv[0]), pszVal, pszVal - psz - 1, psz, ulVar, rc);
282 return 1;
283 }
284 }
285 else
286#endif /* __OS2__ */
287 if (putenv(psz))
288 {
289 fprintf(pStdErr, "%s: error: putenv(\"%s\"): %s\n", name(argv[0]), psz, strerror(errno));
290 return 1;
291 }
292 continue;
293 }
294
295 /*
296 * Change directory switch?
297 */
298 if (*psz == 'C')
299 {
300 psz++;
301 if (*psz == ':' || *psz == '=')
302 psz++;
303 else
304 {
305 if (i + 1 >= argc)
306 {
307 fprintf(pStdErr, "%s: syntax error: no argument for %s\n", name(argv[0]), argv[i]);
308 return 1;
309 }
310 psz = argv[++i];
311 }
312 if (!chdir(psz))
313 continue;
314#ifdef _MSC_VER
315 {
316 /* drop trailing slash if any. */
317 size_t cch = strlen(psz);
318 if ( cch > 2
319 && (psz[cch - 1] == '/' || psz[cch - 1] == '\\')
320 && psz[cch - 1] != ':')
321 {
322 int rc2;
323 char *pszCopy = strdup(psz);
324 do pszCopy[--cch] = '\0';
325 while ( cch > 2
326 && (pszCopy[cch - 1] == '/' || pszCopy[cch - 1] == '\\')
327 && pszCopy[cch - 1] != ':');
328 rc2 = chdir(pszCopy);
329 free(pszCopy);
330 if (!rc2)
331 continue;
332 }
333 }
334#endif
335 fprintf(pStdErr, "%s: error: chdir(\"%s\"): %s\n", name(argv[0]), psz, strerror(errno));
336 return 1;
337 }
338
339 /*
340 * Zap environment switch?
341 * This is a bit of a hack.
342 */
343 if (*psz == 'Z')
344 {
345 unsigned j = 0;
346 while (envp[j] != NULL)
347 j++;
348 while (j-- > 0)
349 {
350 char *pszEqual = strchr(envp[j], '=');
351 char *pszCopy;
352
353 if (pszEqual)
354 *pszEqual = '\0';
355 pszCopy = strdup(envp[j]);
356 if (pszEqual)
357 *pszEqual = '=';
358
359#if defined(_MSC_VER) || defined(__OS2__)
360 putenv(pszCopy);
361#else
362 unsetenv(pszCopy);
363#endif
364 free(pszCopy);
365 }
366 continue;
367 }
368
369 /*
370 * Close the specified file descriptor (no stderr/out/in aliases).
371 */
372 if (*psz == 'c')
373 {
374 psz++;
375 if (!*psz)
376 {
377 i++;
378 if (i >= argc)
379 {
380 fprintf(pStdErr, "%s: syntax error: missing filename argument.\n", name(argv[0]));
381 return 1;
382 }
383 psz = argv[i];
384 }
385
386 fd = (int)strtol(psz, &psz, 0);
387 if (!fd || *psz)
388 {
389 fprintf(pStdErr, "%s: error: failed to convert '%s' to a number\n", name(argv[0]), argv[i]);
390 return 1;
391
392 }
393 if (fd < 0)
394 {
395 fprintf(pStdErr, "%s: error: negative fd %d (%s)\n", name(argv[0]), fd, argv[i]);
396 return 1;
397 }
398 /** @todo deal with stderr */
399 close(fd);
400 continue;
401 }
402
403 /*
404 * Parse a file descriptor argument.
405 */
406
407 /* mode */
408 switch (*psz)
409 {
410 case 'r':
411 psz++;
412 if (*psz == '+')
413 {
414 fOpen = O_RDWR;
415 psz++;
416 }
417 else
418 fOpen = O_RDONLY;
419 break;
420
421 case 'w':
422 psz++;
423 if (*psz == '+')
424 {
425 psz++;
426 fOpen = O_RDWR | O_CREAT | O_TRUNC;
427 }
428 else
429 fOpen = O_WRONLY | O_CREAT | O_TRUNC;
430 break;
431
432 case 'a':
433 psz++;
434 if (*psz == '+')
435 {
436 psz++;
437 fOpen = O_RDWR | O_CREAT | O_APPEND;
438 }
439 else
440 fOpen = O_WRONLY | O_CREAT | O_APPEND;
441 break;
442
443 case 'i': /* make sure stdin is read-only. */
444 fOpen = O_RDONLY;
445 break;
446
447 case '+':
448 fprintf(pStdErr, "%s: syntax error: Unexpected '+' in '%s'\n", name(argv[0]), argv[i]);
449 return 1;
450
451 default:
452 fOpen = O_RDWR | O_CREAT | O_TRUNC;
453 break;
454 }
455
456 /* binary / text modifiers */
457 switch (*psz)
458 {
459 case 'b':
460#ifdef O_BINARY
461 fOpen |= O_BINARY;
462#endif
463 psz++;
464 break;
465
466 case 't':
467#ifdef O_TEXT
468 fOpen |= O_TEXT;
469#endif
470 psz++;
471 break;
472
473 default:
474#ifdef O_BINARY
475 fOpen |= O_BINARY;
476#endif
477 break;
478
479 }
480
481 /* convert to file descriptor number */
482 switch (*psz)
483 {
484 case 'i':
485 fd = 0;
486 psz++;
487 break;
488
489 case 'o':
490 fd = 1;
491 psz++;
492 break;
493
494 case 'e':
495 fd = 2;
496 psz++;
497 break;
498
499 case '0':
500 if (!psz[1])
501 {
502 fd = 0;
503 psz++;
504 break;
505 }
506 case '1':
507 case '2':
508 case '3':
509 case '4':
510 case '5':
511 case '6':
512 case '7':
513 case '8':
514 case '9':
515 fd = (int)strtol(psz, &psz, 0);
516 if (!fd)
517 {
518 fprintf(pStdErr, "%s: error: failed to convert '%s' to a number\n", name(argv[0]), argv[i]);
519 return 1;
520
521 }
522 if (fd < 0)
523 {
524 fprintf(pStdErr, "%s: error: negative fd %d (%s)\n", name(argv[0]), fd, argv[i]);
525 return 1;
526 }
527 break;
528
529 /*
530 * Invalid argument.
531 */
532 default:
533 fprintf(pStdErr, "%s: error: failed to convert '%s' ('%s') to a file descriptor\n", name(argv[0]), psz, argv[i]);
534 return 1;
535 }
536
537 /*
538 * Check for the filename.
539 */
540 if (*psz)
541 {
542 if (*psz != ':' && *psz != '=')
543 {
544 fprintf(pStdErr, "%s: syntax error: characters following the file descriptor: '%s' ('%s')\n", name(argv[0]), psz, argv[i]);
545 return 1;
546 }
547 psz++;
548 }
549 else
550 {
551 i++;
552 if (i >= argc)
553 {
554 fprintf(pStdErr, "%s: syntax error: missing filename argument.\n", name(argv[0]));
555 return 1;
556 }
557 psz = argv[i];
558 }
559
560 /*
561 * Setup the redirection.
562 */
563 if (fd == fileno(pStdErr))
564 {
565 /*
566 * Move stderr to a new location, making it close on exec.
567 * If pStdOut has already teamed up with pStdErr, update it too.
568 */
569 FILE *pNew;
570 fdOpened = dup(fileno(pStdErr));
571 if (fdOpened == -1)
572 {
573 fprintf(pStdErr, "%s: error: failed to dup stderr (%d): %s\n", name(argv[0]), fileno(pStdErr), strerror(errno));
574 return 1;
575 }
576#ifdef _MSC_VER
577 /** @todo figure out how to make the handle close-on-exec. We'll simply close it for now.
578 * SetHandleInformation + set FNOINHERIT in CRT.
579 */
580#else
581 if (fcntl(fdOpened, F_SETFD, FD_CLOEXEC) == -1)
582 {
583 fprintf(pStdErr, "%s: error: failed to make stderr (%d) close-on-exec: %s\n", name(argv[0]), fdOpened, strerror(errno));
584 return 1;
585 }
586#endif
587
588 pNew = fdopen(fdOpened, "w");
589 if (!pNew)
590 {
591 fprintf(pStdErr, "%s: error: failed to fdopen the new stderr (%d): %s\n", name(argv[0]), fdOpened, strerror(errno));
592 return 1;
593 }
594 if (pStdOut == pStdErr)
595 pStdOut = pNew;
596 pStdErr = pNew;
597 }
598 else if (fd == 1 && pStdOut != pStdErr)
599 pStdOut = pStdErr;
600
601 /*
602 * Close and open the new file descriptor.
603 */
604 close(fd);
605#if defined(_MSC_VER)
606 if (!strcmp(psz, "/dev/null"))
607 psz = (char *)"nul";
608#endif
609 fdOpened = open(psz, fOpen, 0666);
610 if (fdOpened == -1)
611 {
612 fprintf(pStdErr, "%s: error: failed to open '%s' as %d: %s\n", name(argv[0]), psz, fd, strerror(errno));
613 return 1;
614 }
615 if (fdOpened != fd)
616 {
617 /* move it (dup2 returns 0 on MSC). */
618 if (dup2(fdOpened, fd) == -1)
619 {
620 fprintf(pStdErr, "%s: error: failed to dup '%s' as %d: %s\n", name(argv[0]), psz, fd, strerror(errno));
621 return 1;
622 }
623 close(fdOpened);
624 }
625 }
626 else
627 {
628 fprintf(pStdErr, "%s: syntax error: Invalid argument '%s'.\n", name(argv[0]), argv[i]);
629 return usage(pStdErr, name(argv[0]));
630 }
631 }
632
633 /*
634 * Make sure there's something to execute.
635 */
636 if (i >= argc)
637 {
638 fprintf(pStdErr, "%s: syntax error: nothing to execute!\n", name(argv[0]));
639 return usage(pStdErr, name(argv[0]));
640 }
641
642#if defined(_MSC_VER)
643 if (fileno(pStdErr) != 2) /* no close-on-exec flag on windows */
644 {
645 fclose(pStdErr);
646 pStdErr = NULL;
647 }
648
649 /* MSC is a PITA since it refuses to quote the arguments... */
650 quoteArguments(argc - i, &argv[i]);
651 rc = _spawnvp(_P_WAIT, argv[i], &argv[i]);
652 if (rc == -1 && pStdErr)
653 {
654 fprintf(pStdErr, "%s: error: _spawnvp(_P_WAIT, \"%s\", ...) failed: %s\n", name(argv[0]), argv[i], strerror(errno));
655 rc = 1;
656 }
657 return rc;
658#else
659 execvp(argv[i], &argv[i]);
660 fprintf(pStdErr, "%s: error: _execvp(_P_WAIT, \"%s\", ...) failed: %s\n", name(argv[0]), argv[i], strerror(errno));
661 return 1;
662#endif
663}
664
Note: See TracBrowser for help on using the repository browser.

© 2024 Oracle Support Privacy / Do Not Sell My Info Terms of Use Trademark Policy Automated Access Etiquette