VirtualBox

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

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

kmk_redirect: Cross platform environment variable unsetting.

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