VirtualBox

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

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

redirect.c: MSC fix.

  • Property svn:eol-style set to native
  • Property svn:keywords set to Author Date Id Revision
File size: 21.1 KB
Line 
1/* $Id: redirect.c 2684 2013-06-19 11:01:48Z 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 if (putenv(psz))
314 {
315 fprintf(pStdErr, "%s: error: putenv(\"%s\"): %s\n", name(argv[0]), psz, strerror(errno));
316 return 1;
317 }
318 continue;
319 }
320
321 /*
322 * Change directory switch?
323 */
324 if (*psz == 'C')
325 {
326 psz++;
327 if (*psz == ':' || *psz == '=')
328 psz++;
329 else
330 {
331 if (i + 1 >= argc)
332 {
333 fprintf(pStdErr, "%s: syntax error: no argument for %s\n", name(argv[0]), argv[i]);
334 return 1;
335 }
336 psz = argv[++i];
337 }
338 if (!chdir(psz))
339 continue;
340#ifdef _MSC_VER
341 {
342 /* drop trailing slash if any. */
343 size_t cch = strlen(psz);
344 if ( cch > 2
345 && (psz[cch - 1] == '/' || psz[cch - 1] == '\\')
346 && psz[cch - 1] != ':')
347 {
348 int rc2;
349 char *pszCopy = strdup(psz);
350 do pszCopy[--cch] = '\0';
351 while ( cch > 2
352 && (pszCopy[cch - 1] == '/' || pszCopy[cch - 1] == '\\')
353 && pszCopy[cch - 1] != ':');
354 rc2 = chdir(pszCopy);
355 free(pszCopy);
356 if (!rc2)
357 continue;
358 }
359 }
360#endif
361 fprintf(pStdErr, "%s: error: chdir(\"%s\"): %s\n", name(argv[0]), psz, strerror(errno));
362 return 1;
363 }
364
365 /*
366 * Zap environment switch?
367 * This is a bit of a hack.
368 */
369 if (*psz == 'Z')
370 {
371 unsigned j = 0;
372 while (envp[j] != NULL)
373 j++;
374 while (j-- > 0)
375 {
376 char *pszEqual = strchr(envp[j], '=');
377 char *pszCopy;
378
379 if (pszEqual)
380 *pszEqual = '\0';
381 pszCopy = strdup(envp[j]);
382 if (pszEqual)
383 *pszEqual = '=';
384
385#if defined(_MSC_VER) || defined(__OS2__)
386 putenv(pszCopy);
387#else
388 unsetenv(pszCopy);
389#endif
390 free(pszCopy);
391 }
392 continue;
393 }
394
395 /*
396 * Close the specified file descriptor (no stderr/out/in aliases).
397 */
398 if (*psz == 'c')
399 {
400 psz++;
401 if (!*psz)
402 {
403 i++;
404 if (i >= argc)
405 {
406 fprintf(pStdErr, "%s: syntax error: missing filename argument.\n", name(argv[0]));
407 return 1;
408 }
409 psz = argv[i];
410 }
411
412 fd = (int)strtol(psz, &psz, 0);
413 if (!fd || *psz)
414 {
415 fprintf(pStdErr, "%s: error: failed to convert '%s' to a number\n", name(argv[0]), argv[i]);
416 return 1;
417
418 }
419 if (fd < 0)
420 {
421 fprintf(pStdErr, "%s: error: negative fd %d (%s)\n", name(argv[0]), fd, argv[i]);
422 return 1;
423 }
424 /** @todo deal with stderr */
425 safeCloseFd(fd);
426 continue;
427 }
428
429 /*
430 * Parse a file descriptor argument.
431 */
432
433 /* mode */
434 switch (*psz)
435 {
436 case 'r':
437 psz++;
438 if (*psz == '+')
439 {
440 fOpen = O_RDWR;
441 psz++;
442 }
443 else
444 fOpen = O_RDONLY;
445 break;
446
447 case 'w':
448 psz++;
449 if (*psz == '+')
450 {
451 psz++;
452 fOpen = O_RDWR | O_CREAT | O_TRUNC;
453 }
454 else
455 fOpen = O_WRONLY | O_CREAT | O_TRUNC;
456 break;
457
458 case 'a':
459 psz++;
460 if (*psz == '+')
461 {
462 psz++;
463 fOpen = O_RDWR | O_CREAT | O_APPEND;
464 }
465 else
466 fOpen = O_WRONLY | O_CREAT | O_APPEND;
467 break;
468
469 case 'i': /* make sure stdin is read-only. */
470 fOpen = O_RDONLY;
471 break;
472
473 case '+':
474 fprintf(pStdErr, "%s: syntax error: Unexpected '+' in '%s'\n", name(argv[0]), argv[i]);
475 return 1;
476
477 default:
478 fOpen = O_RDWR | O_CREAT | O_TRUNC;
479 break;
480 }
481
482 /* binary / text modifiers */
483 switch (*psz)
484 {
485 case 'b':
486#ifdef O_BINARY
487 fOpen |= O_BINARY;
488#endif
489 psz++;
490 break;
491
492 case 't':
493#ifdef O_TEXT
494 fOpen |= O_TEXT;
495#endif
496 psz++;
497 break;
498
499 default:
500#ifdef O_BINARY
501 fOpen |= O_BINARY;
502#endif
503 break;
504
505 }
506
507 /* convert to file descriptor number */
508 switch (*psz)
509 {
510 case 'i':
511 fd = 0;
512 psz++;
513 break;
514
515 case 'o':
516 fd = 1;
517 psz++;
518 break;
519
520 case 'e':
521 fd = 2;
522 psz++;
523 break;
524
525 case '0':
526 if (!psz[1])
527 {
528 fd = 0;
529 psz++;
530 break;
531 }
532 case '1':
533 case '2':
534 case '3':
535 case '4':
536 case '5':
537 case '6':
538 case '7':
539 case '8':
540 case '9':
541 fd = (int)strtol(psz, &psz, 0);
542 if (!fd)
543 {
544 fprintf(pStdErr, "%s: error: failed to convert '%s' to a number\n", name(argv[0]), argv[i]);
545 return 1;
546
547 }
548 if (fd < 0)
549 {
550 fprintf(pStdErr, "%s: error: negative fd %d (%s)\n", name(argv[0]), fd, argv[i]);
551 return 1;
552 }
553 break;
554
555 /*
556 * Invalid argument.
557 */
558 default:
559 fprintf(pStdErr, "%s: error: failed to convert '%s' ('%s') to a file descriptor\n", name(argv[0]), psz, argv[i]);
560 return 1;
561 }
562
563 /*
564 * Check for the filename.
565 */
566 if (*psz)
567 {
568 if (*psz != ':' && *psz != '=')
569 {
570 fprintf(pStdErr, "%s: syntax error: characters following the file descriptor: '%s' ('%s')\n", name(argv[0]), psz, argv[i]);
571 return 1;
572 }
573 psz++;
574 }
575 else
576 {
577 i++;
578 if (i >= argc)
579 {
580 fprintf(pStdErr, "%s: syntax error: missing filename argument.\n", name(argv[0]));
581 return 1;
582 }
583 psz = argv[i];
584 }
585
586 /*
587 * Setup the redirection.
588 */
589 if (fd == fileno(pStdErr))
590 {
591 /*
592 * Move stderr to a new location, making it close on exec.
593 * If pStdOut has already teamed up with pStdErr, update it too.
594 */
595 FILE *pNew;
596 fdOpened = dup(fileno(pStdErr));
597 if (fdOpened == -1)
598 {
599 fprintf(pStdErr, "%s: error: failed to dup stderr (%d): %s\n", name(argv[0]), fileno(pStdErr), strerror(errno));
600 return 1;
601 }
602#ifdef _MSC_VER
603 /** @todo figure out how to make the handle close-on-exec. We'll simply close it for now.
604 * SetHandleInformation + set FNOINHERIT in CRT.
605 */
606#else
607 if (fcntl(fdOpened, F_SETFD, FD_CLOEXEC) == -1)
608 {
609 fprintf(pStdErr, "%s: error: failed to make stderr (%d) close-on-exec: %s\n", name(argv[0]), fdOpened, strerror(errno));
610 return 1;
611 }
612#endif
613
614 pNew = fdopen(fdOpened, "w");
615 if (!pNew)
616 {
617 fprintf(pStdErr, "%s: error: failed to fdopen the new stderr (%d): %s\n", name(argv[0]), fdOpened, strerror(errno));
618 return 1;
619 }
620 if (pStdOut == pStdErr)
621 pStdOut = pNew;
622 pStdErr = pNew;
623 }
624 else if (fd == 1 && pStdOut != pStdErr)
625 pStdOut = pStdErr;
626
627 /*
628 * Close and open the new file descriptor.
629 */
630 safeCloseFd(fd);
631#if defined(_MSC_VER)
632 if (!strcmp(psz, "/dev/null"))
633 psz = (char *)"nul";
634#endif
635 fdOpened = open(psz, fOpen, 0666);
636 if (fdOpened == -1)
637 {
638 fprintf(pStdErr, "%s: error: failed to open '%s' as %d: %s\n", name(argv[0]), psz, fd, strerror(errno));
639 return 1;
640 }
641 if (fdOpened != fd)
642 {
643 /* move it (dup2 returns 0 on MSC). */
644 if (dup2(fdOpened, fd) == -1)
645 {
646 fprintf(pStdErr, "%s: error: failed to dup '%s' as %d: %s\n", name(argv[0]), psz, fd, strerror(errno));
647 return 1;
648 }
649 close(fdOpened);
650 }
651 }
652 else
653 {
654 fprintf(pStdErr, "%s: syntax error: Invalid argument '%s'.\n", name(argv[0]), argv[i]);
655 return usage(pStdErr, name(argv[0]));
656 }
657 }
658
659 /*
660 * Make sure there's something to execute.
661 */
662 if (i >= argc)
663 {
664 fprintf(pStdErr, "%s: syntax error: nothing to execute!\n", name(argv[0]));
665 return usage(pStdErr, name(argv[0]));
666 }
667
668#if defined(_MSC_VER)
669 if (fileno(pStdErr) != 2) /* no close-on-exec flag on windows */
670 {
671 fclose(pStdErr);
672 pStdErr = NULL;
673 }
674
675 /* MSC is a PITA since it refuses to quote the arguments... */
676 quoteArguments(argc - i, &argv[i]);
677 rc = _spawnvp(_P_WAIT, argv[i], &argv[i]);
678 if (rc == -1 && pStdErr)
679 {
680 fprintf(pStdErr, "%s: error: _spawnvp(_P_WAIT, \"%s\", ...) failed: %s\n", name(argv[0]), argv[i], strerror(errno));
681 rc = 1;
682 }
683 return rc;
684#else
685 execvp(argv[i], &argv[i]);
686 fprintf(pStdErr, "%s: error: _execvp(_P_WAIT, \"%s\", ...) failed: %s\n", name(argv[0]), argv[i], strerror(errno));
687 return 1;
688#endif
689}
690
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