VirtualBox

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

Last change on this file since 2733 was 2728, checked in by bird, 11 years ago

kmk_redirect.c: More stuff needs quoting on windows.

  • Property svn:eol-style set to native
  • Property svn:keywords set to Author Date Id Revision
File size: 22.6 KB
Line 
1/* $Id: redirect.c 2728 2014-03-05 13:09:47Z bird $ */
2/** @file
3 * kmk_redirect - Do simple program <-> file redirection (++).
4 */
5
6/*
7 * Copyright (c) 2007-2014 knut st. osmundsen <[email protected]>
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, '\t', cchOrg)
77 || memchr(pszOrg, '\n', cchOrg)
78 || memchr(pszOrg, '\r', cchOrg)
79 || memchr(pszOrg, '&', cchOrg)
80 || memchr(pszOrg, '>', cchOrg)
81 || memchr(pszOrg, '<', cchOrg)
82 || memchr(pszOrg, '|', cchOrg)
83 || memchr(pszOrg, '%', cchOrg)
84 || memchr(pszOrg, '\'', cchOrg)
85 || memchr(pszOrg, '=', cchOrg)
86 )
87 {
88 char ch;
89 int fComplicated = pszQuotes || (cchOrg > 0 && pszOrg[cchOrg - 1] == '\\');
90 size_t cchNew = fComplicated ? cchOrg * 2 + 2 : cchOrg + 2;
91 char *pszNew = (char *)malloc(cchNew + 1);
92
93 argv[i] = pszNew;
94
95 *pszNew++ = '"';
96 if (fComplicated)
97 {
98 while ((ch = *pszOrg++) != '\0')
99 {
100 if (ch == '"')
101 {
102 *pszNew++ = '\\';
103 *pszNew++ = '"';
104 }
105 else if (ch == '\\')
106 {
107 /* Backslashes are a bit complicated, they depends on
108 whether a quotation mark follows them or not. They
109 only require escaping if one does. */
110 unsigned cSlashes = 1;
111 while ((ch = *pszOrg) == '\\')
112 {
113 pszOrg++;
114 cSlashes++;
115 }
116 if (ch == '"' || ch == '\0') /* We put a " at the EOS. */
117 {
118 while (cSlashes-- > 0)
119 {
120 *pszNew++ = '\\';
121 *pszNew++ = '\\';
122 }
123 }
124 else
125 while (cSlashes-- > 0)
126 *pszNew++ = '\\';
127 }
128 else
129 *pszNew++ = ch;
130 }
131 }
132 else
133 {
134 memcpy(pszNew, pszOrg, cchOrg);
135 pszNew += cchOrg;
136 }
137 *pszNew++ = '"';
138 *pszNew = '\0';
139 }
140 }
141
142 /*for (i = 0; i < argc; i++) fprintf(stderr, "argv[%u]=%s;;\n", i, argv[i]);*/
143}
144#endif /* _MSC_VER */
145
146
147#ifdef _MSC_VER
148/** Used by safeCloseFd. */
149static void __cdecl ignore_invalid_parameter(const wchar_t *a, const wchar_t *b, const wchar_t *c, unsigned d, uintptr_t e)
150{
151}
152#endif
153
154
155/**
156 * Safely works around MS CRT's pedantic close() function.
157 *
158 * @param fd The file handle.
159 */
160static void safeCloseFd(int fd)
161{
162#ifdef _MSC_VER
163 _invalid_parameter_handler pfnOld = _get_invalid_parameter_handler();
164 _set_invalid_parameter_handler(ignore_invalid_parameter);
165 close(fd);
166 _set_invalid_parameter_handler(pfnOld);
167#else
168 close(fd);
169#endif
170}
171
172
173static const char *name(const char *pszName)
174{
175 const char *psz = strrchr(pszName, '/');
176#if defined(_MSC_VER) || defined(__OS2__)
177 const char *psz2 = strrchr(pszName, '\\');
178 if (!psz2)
179 psz2 = strrchr(pszName, ':');
180 if (psz2 && (!psz || psz2 > psz))
181 psz = psz2;
182#endif
183 return psz ? psz + 1 : pszName;
184}
185
186
187static int usage(FILE *pOut, const char *argv0)
188{
189 fprintf(pOut,
190 "usage: %s [-[rwa+tb]<fd> <file>] [-c<fd>] [-Z] [-E <var=val>] [-C <dir>] -- <program> [args]\n"
191 " or: %s --help\n"
192 " or: %s --version\n"
193 "\n"
194 "The rwa+tb is like for fopen, if not specified it defaults to w+.\n"
195 "The <fd> is either a number or an alias for the standard handles:\n"
196 " i = stdin\n"
197 " o = stdout\n"
198 " e = stderr\n"
199 "\n"
200 "The -c switch will close the specified file descriptor.\n"
201 "\n"
202 "The -Z switch zaps the environment.\n"
203 "\n"
204 "The -E switch is for making changes to the environment in a putenv\n"
205 "fashion.\n"
206 "\n"
207 "The -C switch is for changing the current directory. This takes immediate\n"
208 "effect, so be careful where you put it.\n"
209 "\n"
210 "This command was originally just a quick hack to avoid invoking the shell\n"
211 "on Windows (cygwin) where forking is very expensive and has exhibited\n"
212 "stability issues on SMP machines. It has since grown into something like\n"
213 "/usr/bin/env on steroids.\n"
214 ,
215 argv0, argv0, argv0);
216 return 1;
217}
218
219
220int main(int argc, char **argv, char **envp)
221{
222 int i;
223#if defined(_MSC_VER)
224 intptr_t rc;
225#endif
226 FILE *pStdErr = stderr;
227 FILE *pStdOut = stdout;
228
229 /*
230 * Parse arguments.
231 */
232 if (argc <= 1)
233 return usage(pStdErr, name(argv[0]));
234 for (i = 1; i < argc; i++)
235 {
236 if (argv[i][0] == '-')
237 {
238 int fd;
239 int fdOpened;
240 int fOpen;
241 char *psz = &argv[i][1];
242 if (*psz == '-')
243 {
244 /* '--' ? */
245 if (!psz[1])
246 {
247 i++;
248 break;
249 }
250
251 /* convert to short. */
252 if (!strcmp(psz, "-help"))
253 psz = "h";
254 else if (!strcmp(psz, "-version"))
255 psz = "V";
256 else if (!strcmp(psz, "-env"))
257 psz = "E";
258 else if (!strcmp(psz, "-chdir"))
259 psz = "C";
260 else if (!strcmp(psz, "-zap-env"))
261 psz = "Z";
262 else if (!strcmp(psz, "-close"))
263 psz = "c";
264 }
265
266 /*
267 * Deal with the obligatory help and version switches first.
268 */
269 if (*psz == 'h')
270 {
271 usage(pStdOut, name(argv[0]));
272 return 0;
273 }
274 if (*psz == 'V')
275 {
276 printf("kmk_redirect - kBuild version %d.%d.%d (r%u)\n"
277 "Copyright (C) 2007-2012 knut st. osmundsen\n",
278 KBUILD_VERSION_MAJOR, KBUILD_VERSION_MINOR, KBUILD_VERSION_PATCH,
279 KBUILD_SVN_REV);
280 return 0;
281 }
282
283 /*
284 * Environment switch?
285 */
286 if (*psz == 'E')
287 {
288 psz++;
289 if (*psz == ':' || *psz == '=')
290 psz++;
291 else
292 {
293 if (i + 1 >= argc)
294 {
295 fprintf(pStdErr, "%s: syntax error: no argument for %s\n", name(argv[0]), argv[i]);
296 return 1;
297 }
298 psz = argv[++i];
299 }
300#ifdef __OS2__
301 if ( !strncmp(psz, "BEGINLIBPATH=", sizeof("BEGINLIBPATH=") - 1)
302 || !strncmp(psz, "ENDLIBPATH=", sizeof("ENDLIBPATH=") - 1)
303 || !strncmp(psz, "LIBPATHSTRICT=", sizeof("LIBPATHSTRICT=") - 1))
304 {
305 ULONG ulVar = *psz == 'B' ? BEGIN_LIBPATH
306 : *psz == 'E' ? END_LIBPATH
307 : LIBPATHSTRICT;
308 const char *pszVal = strchr(psz, '=') + 1;
309 APIRET rc = DosSetExtLIBPATH(pszVal, ulVar);
310 if (rc)
311 {
312 fprintf(pStdErr, "%s: error: DosSetExtLibPath(\"%s\", %.*s (%lu)): %lu\n",
313 name(argv[0]), pszVal, pszVal - psz - 1, psz, ulVar, rc);
314 return 1;
315 }
316 }
317 else
318#endif /* __OS2__ */
319 {
320 const char *pchEqual = strchr(psz, '=');
321 if (pchEqual && pchEqual[1] != '\0')
322 {
323 if (putenv(psz))
324 {
325 fprintf(pStdErr, "%s: error: putenv(\"%s\"): %s\n", name(argv[0]), psz, strerror(errno));
326 return 1;
327 }
328 }
329 else
330 {
331 size_t cchVar = pchEqual ? (size_t)(pchEqual - psz) : strlen(psz);
332 char *pszCopy = (char *)malloc(cchVar + 2);
333 memcpy(pszCopy, psz, cchVar);
334
335#if defined(_MSC_VER) || defined(__OS2__)
336 pszCopy[cchVar] = '=';
337 pszCopy[cchVar + 1] = '\0';
338 if (putenv(pszCopy))
339 {
340 fprintf(pStdErr, "%s: error: putenv(\"%s\"): %s\n", name(argv[0]), pszCopy, strerror(errno));
341 return 1;
342 }
343#else
344 pszCopy[cchVar] = '\0';
345 if (unsetenv(pszCopy))
346 {
347 fprintf(pStdErr, "%s: error: unsetenv(\"%s\"): %s\n", name(argv[0]), pszCopy, strerror(errno));
348 return 1;
349 }
350#endif
351 free(pszCopy);
352 }
353 }
354 continue;
355 }
356
357 /*
358 * Change directory switch?
359 */
360 if (*psz == 'C')
361 {
362 psz++;
363 if (*psz == ':' || *psz == '=')
364 psz++;
365 else
366 {
367 if (i + 1 >= argc)
368 {
369 fprintf(pStdErr, "%s: syntax error: no argument for %s\n", name(argv[0]), argv[i]);
370 return 1;
371 }
372 psz = argv[++i];
373 }
374 if (!chdir(psz))
375 continue;
376#ifdef _MSC_VER
377 {
378 /* drop trailing slash if any. */
379 size_t cch = strlen(psz);
380 if ( cch > 2
381 && (psz[cch - 1] == '/' || psz[cch - 1] == '\\')
382 && psz[cch - 1] != ':')
383 {
384 int rc2;
385 char *pszCopy = strdup(psz);
386 do pszCopy[--cch] = '\0';
387 while ( cch > 2
388 && (pszCopy[cch - 1] == '/' || pszCopy[cch - 1] == '\\')
389 && pszCopy[cch - 1] != ':');
390 rc2 = chdir(pszCopy);
391 free(pszCopy);
392 if (!rc2)
393 continue;
394 }
395 }
396#endif
397 fprintf(pStdErr, "%s: error: chdir(\"%s\"): %s\n", name(argv[0]), psz, strerror(errno));
398 return 1;
399 }
400
401 /*
402 * Zap environment switch?
403 * This is a bit of a hack.
404 */
405 if (*psz == 'Z')
406 {
407 unsigned j = 0;
408 while (envp[j] != NULL)
409 j++;
410 while (j-- > 0)
411 {
412 char *pszEqual = strchr(envp[j], '=');
413 char *pszCopy;
414
415 if (pszEqual)
416 *pszEqual = '\0';
417 pszCopy = strdup(envp[j]);
418 if (pszEqual)
419 *pszEqual = '=';
420
421#if defined(_MSC_VER) || defined(__OS2__)
422 putenv(pszCopy);
423#else
424 unsetenv(pszCopy);
425#endif
426 free(pszCopy);
427 }
428 continue;
429 }
430
431 /*
432 * Close the specified file descriptor (no stderr/out/in aliases).
433 */
434 if (*psz == 'c')
435 {
436 psz++;
437 if (!*psz)
438 {
439 i++;
440 if (i >= argc)
441 {
442 fprintf(pStdErr, "%s: syntax error: missing filename argument.\n", name(argv[0]));
443 return 1;
444 }
445 psz = argv[i];
446 }
447
448 fd = (int)strtol(psz, &psz, 0);
449 if (!fd || *psz)
450 {
451 fprintf(pStdErr, "%s: error: failed to convert '%s' to a number\n", name(argv[0]), argv[i]);
452 return 1;
453
454 }
455 if (fd < 0)
456 {
457 fprintf(pStdErr, "%s: error: negative fd %d (%s)\n", name(argv[0]), fd, argv[i]);
458 return 1;
459 }
460 /** @todo deal with stderr */
461 safeCloseFd(fd);
462 continue;
463 }
464
465 /*
466 * Parse a file descriptor argument.
467 */
468
469 /* mode */
470 switch (*psz)
471 {
472 case 'r':
473 psz++;
474 if (*psz == '+')
475 {
476 fOpen = O_RDWR;
477 psz++;
478 }
479 else
480 fOpen = O_RDONLY;
481 break;
482
483 case 'w':
484 psz++;
485 if (*psz == '+')
486 {
487 psz++;
488 fOpen = O_RDWR | O_CREAT | O_TRUNC;
489 }
490 else
491 fOpen = O_WRONLY | O_CREAT | O_TRUNC;
492 break;
493
494 case 'a':
495 psz++;
496 if (*psz == '+')
497 {
498 psz++;
499 fOpen = O_RDWR | O_CREAT | O_APPEND;
500 }
501 else
502 fOpen = O_WRONLY | O_CREAT | O_APPEND;
503 break;
504
505 case 'i': /* make sure stdin is read-only. */
506 fOpen = O_RDONLY;
507 break;
508
509 case '+':
510 fprintf(pStdErr, "%s: syntax error: Unexpected '+' in '%s'\n", name(argv[0]), argv[i]);
511 return 1;
512
513 default:
514 fOpen = O_RDWR | O_CREAT | O_TRUNC;
515 break;
516 }
517
518 /* binary / text modifiers */
519 switch (*psz)
520 {
521 case 'b':
522#ifdef O_BINARY
523 fOpen |= O_BINARY;
524#endif
525 psz++;
526 break;
527
528 case 't':
529#ifdef O_TEXT
530 fOpen |= O_TEXT;
531#endif
532 psz++;
533 break;
534
535 default:
536#ifdef O_BINARY
537 fOpen |= O_BINARY;
538#endif
539 break;
540
541 }
542
543 /* convert to file descriptor number */
544 switch (*psz)
545 {
546 case 'i':
547 fd = 0;
548 psz++;
549 break;
550
551 case 'o':
552 fd = 1;
553 psz++;
554 break;
555
556 case 'e':
557 fd = 2;
558 psz++;
559 break;
560
561 case '0':
562 if (!psz[1])
563 {
564 fd = 0;
565 psz++;
566 break;
567 }
568 case '1':
569 case '2':
570 case '3':
571 case '4':
572 case '5':
573 case '6':
574 case '7':
575 case '8':
576 case '9':
577 fd = (int)strtol(psz, &psz, 0);
578 if (!fd)
579 {
580 fprintf(pStdErr, "%s: error: failed to convert '%s' to a number\n", name(argv[0]), argv[i]);
581 return 1;
582
583 }
584 if (fd < 0)
585 {
586 fprintf(pStdErr, "%s: error: negative fd %d (%s)\n", name(argv[0]), fd, argv[i]);
587 return 1;
588 }
589 break;
590
591 /*
592 * Invalid argument.
593 */
594 default:
595 fprintf(pStdErr, "%s: error: failed to convert '%s' ('%s') to a file descriptor\n", name(argv[0]), psz, argv[i]);
596 return 1;
597 }
598
599 /*
600 * Check for the filename.
601 */
602 if (*psz)
603 {
604 if (*psz != ':' && *psz != '=')
605 {
606 fprintf(pStdErr, "%s: syntax error: characters following the file descriptor: '%s' ('%s')\n", name(argv[0]), psz, argv[i]);
607 return 1;
608 }
609 psz++;
610 }
611 else
612 {
613 i++;
614 if (i >= argc)
615 {
616 fprintf(pStdErr, "%s: syntax error: missing filename argument.\n", name(argv[0]));
617 return 1;
618 }
619 psz = argv[i];
620 }
621
622 /*
623 * Setup the redirection.
624 */
625 if (fd == fileno(pStdErr))
626 {
627 /*
628 * Move stderr to a new location, making it close on exec.
629 * If pStdOut has already teamed up with pStdErr, update it too.
630 */
631 FILE *pNew;
632 fdOpened = dup(fileno(pStdErr));
633 if (fdOpened == -1)
634 {
635 fprintf(pStdErr, "%s: error: failed to dup stderr (%d): %s\n", name(argv[0]), fileno(pStdErr), strerror(errno));
636 return 1;
637 }
638#ifdef _MSC_VER
639 /** @todo figure out how to make the handle close-on-exec. We'll simply close it for now.
640 * SetHandleInformation + set FNOINHERIT in CRT.
641 */
642#else
643 if (fcntl(fdOpened, F_SETFD, FD_CLOEXEC) == -1)
644 {
645 fprintf(pStdErr, "%s: error: failed to make stderr (%d) close-on-exec: %s\n", name(argv[0]), fdOpened, strerror(errno));
646 return 1;
647 }
648#endif
649
650 pNew = fdopen(fdOpened, "w");
651 if (!pNew)
652 {
653 fprintf(pStdErr, "%s: error: failed to fdopen the new stderr (%d): %s\n", name(argv[0]), fdOpened, strerror(errno));
654 return 1;
655 }
656 if (pStdOut == pStdErr)
657 pStdOut = pNew;
658 pStdErr = pNew;
659 }
660 else if (fd == 1 && pStdOut != pStdErr)
661 pStdOut = pStdErr;
662
663 /*
664 * Close and open the new file descriptor.
665 */
666 safeCloseFd(fd);
667#if defined(_MSC_VER)
668 if (!strcmp(psz, "/dev/null"))
669 psz = (char *)"nul";
670#endif
671 fdOpened = open(psz, fOpen, 0666);
672 if (fdOpened == -1)
673 {
674 fprintf(pStdErr, "%s: error: failed to open '%s' as %d: %s\n", name(argv[0]), psz, fd, strerror(errno));
675 return 1;
676 }
677 if (fdOpened != fd)
678 {
679 /* move it (dup2 returns 0 on MSC). */
680 if (dup2(fdOpened, fd) == -1)
681 {
682 fprintf(pStdErr, "%s: error: failed to dup '%s' as %d: %s\n", name(argv[0]), psz, fd, strerror(errno));
683 return 1;
684 }
685 close(fdOpened);
686 }
687 }
688 else
689 {
690 fprintf(pStdErr, "%s: syntax error: Invalid argument '%s'.\n", name(argv[0]), argv[i]);
691 return usage(pStdErr, name(argv[0]));
692 }
693 }
694
695 /*
696 * Make sure there's something to execute.
697 */
698 if (i >= argc)
699 {
700 fprintf(pStdErr, "%s: syntax error: nothing to execute!\n", name(argv[0]));
701 return usage(pStdErr, name(argv[0]));
702 }
703
704#if defined(_MSC_VER)
705 if (fileno(pStdErr) != 2) /* no close-on-exec flag on windows */
706 {
707 fclose(pStdErr);
708 pStdErr = NULL;
709 }
710
711 /* MSC is a PITA since it refuses to quote the arguments... */
712 quoteArguments(argc - i, &argv[i]);
713 rc = _spawnvp(_P_WAIT, argv[i], &argv[i]);
714 if (rc == -1 && pStdErr)
715 {
716 fprintf(pStdErr, "%s: error: _spawnvp(_P_WAIT, \"%s\", ...) failed: %s\n", name(argv[0]), argv[i], strerror(errno));
717 rc = 1;
718 }
719 return rc;
720#else
721 execvp(argv[i], &argv[i]);
722 fprintf(pStdErr, "%s: error: _execvp(_P_WAIT, \"%s\", ...) failed: %s\n", name(argv[0]), argv[i], strerror(errno));
723 return 1;
724#endif
725}
726
Note: See TracBrowser for help on using the repository browser.

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