1 /**
2  * Invoke the linker as a separate process.
3  *
4  * Copyright:   Copyright (C) 1999-2023 by The D Language Foundation, All Rights Reserved
5  * Authors:     $(LINK2 https://www.digitalmars.com, Walter Bright)
6  * License:     $(LINK2 https://www.boost.org/LICENSE_1_0.txt, Boost License 1.0)
7  * Source:      $(LINK2 https://github.com/dlang/dmd/blob/master/src/dmd/link.d, _link.d)
8  * Documentation:  https://dlang.org/phobos/dmd_link.html
9  * Coverage:    https://codecov.io/gh/dlang/dmd/src/master/src/dmd/link.d
10  */
11 
12 module dmd.link;
13 
14 import core.stdc.ctype;
15 import core.stdc.stdio;
16 import core.stdc.string;
17 import core.sys.posix.stdio;
18 import core.sys.posix.stdlib;
19 import core.sys.posix.unistd;
20 import core.sys.windows.winbase;
21 import core.sys.windows.windef;
22 import dmd.dmdparams;
23 import dmd.errors;
24 import dmd.globals;
25 import dmd.location;
26 import dmd.root.array;
27 import dmd.root.env;
28 import dmd.root.file;
29 import dmd.root.filename;
30 import dmd.common.outbuffer;
31 import dmd.common.string;
32 import dmd.root.rmem;
33 import dmd.root.string;
34 import dmd.utils;
35 import dmd.target;
36 import dmd.vsoptions;
37 
38 version (Posix) extern (C) int pipe(int*);
39 
40 version (Windows)
41 {
42     /* https://www.digitalmars.com/rtl/process.html#_spawn
43      * https://docs.microsoft.com/en-us/cpp/c-runtime-library/reference/spawnvp-wspawnvp?view=msvc-170
44      */
45     extern (C)
46     {
47         int spawnlp(int, const char*, const char*, const char*, const char*);
48         int spawnl(int, const char*, const char*, const char*, const char*);
49         int spawnv(int, const char*, const char**);
50         int spawnvp(int, const char*, const char**);
51         enum _P_WAIT = 0;
52     }
53 }
54 
55 // Workaround lack of 'vfork' in older druntime binding for non-Glibc
56 version (Posix) extern(C) pid_t vfork();
57 version (CRuntime_Microsoft)
58 {
59   // until the new windows bindings are available when building dmd.
60   static if(!is(STARTUPINFOA))
61   {
62     alias STARTUPINFOA = STARTUPINFO;
63 
64     // dwCreationFlags for CreateProcess() and CreateProcessAsUser()
65     enum : DWORD {
66       DEBUG_PROCESS               = 0x00000001,
67       DEBUG_ONLY_THIS_PROCESS     = 0x00000002,
68       CREATE_SUSPENDED            = 0x00000004,
69       DETACHED_PROCESS            = 0x00000008,
70       CREATE_NEW_CONSOLE          = 0x00000010,
71       NORMAL_PRIORITY_CLASS       = 0x00000020,
72       IDLE_PRIORITY_CLASS         = 0x00000040,
73       HIGH_PRIORITY_CLASS         = 0x00000080,
74       REALTIME_PRIORITY_CLASS     = 0x00000100,
75       CREATE_NEW_PROCESS_GROUP    = 0x00000200,
76       CREATE_UNICODE_ENVIRONMENT  = 0x00000400,
77       CREATE_SEPARATE_WOW_VDM     = 0x00000800,
78       CREATE_SHARED_WOW_VDM       = 0x00001000,
79       CREATE_FORCEDOS             = 0x00002000,
80       BELOW_NORMAL_PRIORITY_CLASS = 0x00004000,
81       ABOVE_NORMAL_PRIORITY_CLASS = 0x00008000,
82       CREATE_BREAKAWAY_FROM_JOB   = 0x01000000,
83       CREATE_WITH_USERPROFILE     = 0x02000000,
84       CREATE_DEFAULT_ERROR_MODE   = 0x04000000,
85       CREATE_NO_WINDOW            = 0x08000000,
86       PROFILE_USER                = 0x10000000,
87       PROFILE_KERNEL              = 0x20000000,
88       PROFILE_SERVER              = 0x40000000
89     }
90   }
91 }
92 
93 /****************************************
94  * Write filename to cmdbuf, quoting if necessary.
95  */
96 private void writeFilename(OutBuffer* buf, const(char)[] filename)
97 {
98     /* Loop and see if we need to quote
99      */
100     foreach (const char c; filename)
101     {
102         if (isalnum(c) || c == '_')
103             continue;
104         /* Need to quote
105          */
106         buf.writeByte('"');
107         buf.writestring(filename);
108         buf.writeByte('"');
109         return;
110     }
111     /* No quoting necessary
112      */
113     buf.writestring(filename);
114 }
115 
116 private void writeFilename(OutBuffer* buf, const(char)* filename)
117 {
118     writeFilename(buf, filename.toDString());
119 }
120 
121 version (Posix)
122 {
123     /*****************************
124      * As it forwards the linker error message to stderr, checks for the presence
125      * of an error indicating lack of a main function (NME_ERR_MSG).
126      *
127      * Returns:
128      *      1 if there is a no main error
129      *     -1 if there is an IO error
130      *      0 otherwise
131      */
132     private int findNoMainError(int fd)
133     {
134         version (OSX)
135         {
136             static immutable(char*) nmeErrorMessage = "`__Dmain`, referenced from:";
137         }
138         else
139         {
140             static immutable(char*) nmeErrorMessage = "undefined reference to `_Dmain`";
141         }
142         FILE* stream = fdopen(fd, "r");
143         if (stream is null)
144             return -1;
145         const(size_t) len = 64 * 1024 - 1;
146         char[len + 1] buffer; // + '\0'
147         size_t beg = 0, end = len;
148         bool nmeFound = false;
149         for (;;)
150         {
151             // read linker output
152             const(size_t) n = fread(&buffer[beg], 1, len - beg, stream);
153             if (beg + n < len && ferror(stream))
154                 return -1;
155             buffer[(end = beg + n)] = '\0';
156             // search error message, stop at last complete line
157             const(char)* lastSep = strrchr(buffer.ptr, '\n');
158             if (lastSep)
159                 buffer[(end = lastSep - &buffer[0])] = '\0';
160             if (strstr(&buffer[0], nmeErrorMessage))
161                 nmeFound = true;
162             if (lastSep)
163                 buffer[end++] = '\n';
164             if (fwrite(&buffer[0], 1, end, stderr) < end)
165                 return -1;
166             if (beg + n < len && feof(stream))
167                 break;
168             // copy over truncated last line
169             memcpy(&buffer[0], &buffer[end], (beg = len - end));
170         }
171         return nmeFound ? 1 : 0;
172     }
173 }
174 
175 version (Windows)
176 {
177     private void writeQuotedArgIfNeeded(ref OutBuffer buffer, const(char)* arg)
178     {
179         bool quote = false;
180         for (size_t i = 0; arg[i]; ++i)
181         {
182             if (arg[i] == '"')
183             {
184                 quote = false;
185                 break;
186             }
187 
188             if (arg[i] == ' ')
189                 quote = true;
190         }
191 
192         if (quote)
193             buffer.writeByte('"');
194         buffer.writestring(arg);
195         if (quote)
196             buffer.writeByte('"');
197     }
198 
199     unittest
200     {
201         OutBuffer buffer;
202 
203         const(char)[] test(string arg)
204         {
205             buffer.reset();
206             buffer.writeQuotedArgIfNeeded(arg.ptr);
207             return buffer[];
208         }
209 
210         assert(test("arg") == `arg`);
211         assert(test("arg with spaces") == `"arg with spaces"`);
212         assert(test(`"/LIBPATH:dir with spaces"`) == `"/LIBPATH:dir with spaces"`);
213         assert(test(`/LIBPATH:"dir with spaces"`) == `/LIBPATH:"dir with spaces"`);
214     }
215 }
216 
217 /*****************************
218  * Run the linker.  Return status of execution.
219  */
220 public int runLINK()
221 {
222     const phobosLibname = finalDefaultlibname();
223 
224     void setExeFile()
225     {
226         /* Generate exe file name from first obj name.
227          * No need to add it to cmdbuf because the linker will default to it.
228          */
229         const char[] n = FileName.name(global.params.objfiles[0].toDString);
230         global.params.exefile = FileName.forceExt(n, "exe");
231     }
232 
233     const(char)[] getMapFilename()
234     {
235         const(char)[] fn = FileName.forceExt(global.params.exefile, map_ext);
236         const(char)[] path = FileName.path(global.params.exefile);
237         return path.length ? fn : FileName.combine(global.params.objdir, fn);
238     }
239 
240     version (Windows)
241     {
242         if (phobosLibname)
243             global.params.libfiles.push(phobosLibname.xarraydup.ptr);
244 
245         if (target.objectFormat() == Target.ObjectFormat.coff)
246         {
247             OutBuffer cmdbuf;
248             cmdbuf.writestring("/NOLOGO");
249             for (size_t i = 0; i < global.params.objfiles.length; i++)
250             {
251                 cmdbuf.writeByte(' ');
252                 const(char)* p = global.params.objfiles[i];
253                 writeFilename(&cmdbuf, p);
254             }
255             if (global.params.resfile)
256             {
257                 cmdbuf.writeByte(' ');
258                 writeFilename(&cmdbuf, global.params.resfile);
259             }
260             cmdbuf.writeByte(' ');
261             if (global.params.exefile)
262             {
263                 cmdbuf.writestring("/OUT:");
264                 writeFilename(&cmdbuf, global.params.exefile);
265             }
266             else
267             {
268                 setExeFile();
269             }
270             // Make sure path to exe file exists
271             ensurePathToNameExists(Loc.initial, global.params.exefile);
272             cmdbuf.writeByte(' ');
273             if (global.params.mapfile)
274             {
275                 cmdbuf.writestring("/MAP:");
276                 writeFilename(&cmdbuf, global.params.mapfile);
277             }
278             else if (driverParams.map)
279             {
280                 cmdbuf.writestring("/MAP:");
281                 writeFilename(&cmdbuf, getMapFilename());
282             }
283             for (size_t i = 0; i < global.params.libfiles.length; i++)
284             {
285                 cmdbuf.writeByte(' ');
286                 cmdbuf.writestring("/DEFAULTLIB:");
287                 writeFilename(&cmdbuf, global.params.libfiles[i]);
288             }
289             if (global.params.deffile)
290             {
291                 cmdbuf.writeByte(' ');
292                 cmdbuf.writestring("/DEF:");
293                 writeFilename(&cmdbuf, global.params.deffile);
294             }
295             if (driverParams.symdebug)
296             {
297                 cmdbuf.writeByte(' ');
298                 cmdbuf.writestring("/DEBUG");
299                 // in release mode we need to reactivate /OPT:REF after /DEBUG
300                 if (global.params.release)
301                     cmdbuf.writestring(" /OPT:REF");
302             }
303             if (driverParams.dll)
304             {
305                 cmdbuf.writeByte(' ');
306                 cmdbuf.writestring("/DLL");
307             }
308             for (size_t i = 0; i < global.params.linkswitches.length; i++)
309             {
310                 cmdbuf.writeByte(' ');
311                 cmdbuf.writeQuotedArgIfNeeded(global.params.linkswitches[i]);
312             }
313 
314             VSOptions vsopt;
315             // if a runtime library (msvcrtNNN.lib) from the mingw folder is selected explicitly, do not detect VS and use lld
316             if (driverParams.mscrtlib.length <= 6 ||
317                 driverParams.mscrtlib[0..6] != "msvcrt" || !isdigit(driverParams.mscrtlib[6]))
318                 vsopt.initialize();
319 
320             const(char)* linkcmd = getenv(target.is64bit ? "LINKCMD64" : "LINKCMD");
321             if (!linkcmd)
322                 linkcmd = getenv("LINKCMD"); // backward compatible
323             if (!linkcmd)
324                 linkcmd = vsopt.linkerPath(target.is64bit);
325 
326             if (!target.is64bit && FileName.equals(FileName.name(linkcmd), "lld-link.exe"))
327             {
328                 // object files not SAFESEH compliant, but LLD is more picky than MS link
329                 cmdbuf.writestring(" /SAFESEH:NO");
330                 // if we are using LLD as a fallback, don't link to any VS libs even if
331                 // we detected a VS installation and they are present
332                 vsopt.uninitialize();
333             }
334 
335             if (const(char)* lflags = vsopt.linkOptions(target.is64bit))
336             {
337                 cmdbuf.writeByte(' ');
338                 cmdbuf.writestring(lflags);
339             }
340 
341             cmdbuf.writeByte(0); // null terminate the buffer
342             char[] p = cmdbuf.extractSlice()[0 .. $-1];
343             const(char)[] lnkfilename;
344             if (p.length > 7000)
345             {
346                 lnkfilename = FileName.forceExt(global.params.exefile, "lnk");
347                 writeFile(Loc.initial, lnkfilename, p);
348                 if (lnkfilename.length < p.length)
349                 {
350                     p[0] = '@';
351                     p[1 ..  lnkfilename.length +1] = lnkfilename;
352                     p[lnkfilename.length +1] = 0;
353                 }
354             }
355 
356             const int status = executecmd(linkcmd, p.ptr);
357             if (lnkfilename)
358             {
359                 lnkfilename.toCStringThen!(lf => remove(lf.ptr));
360                 FileName.free(lnkfilename.ptr);
361             }
362             return status;
363         }
364         else if (target.objectFormat() == Target.ObjectFormat.omf)
365         {
366             OutBuffer cmdbuf;
367             global.params.libfiles.push("user32");
368             global.params.libfiles.push("kernel32");
369             for (size_t i = 0; i < global.params.objfiles.length; i++)
370             {
371                 if (i)
372                     cmdbuf.writeByte('+');
373                 const(char)[] p = global.params.objfiles[i].toDString();
374                 const(char)[] basename = FileName.removeExt(FileName.name(p));
375                 const(char)[] ext = FileName.ext(p);
376                 if (ext.length && !strchr(basename.ptr, '.'))
377                 {
378                     // Write name sans extension (but not if a double extension)
379                     writeFilename(&cmdbuf, p[0 .. $ - ext.length - 1]);
380                 }
381                 else
382                     writeFilename(&cmdbuf, p);
383                 FileName.free(basename.ptr);
384             }
385             cmdbuf.writeByte(',');
386             if (global.params.exefile)
387                 writeFilename(&cmdbuf, global.params.exefile);
388             else
389             {
390                 setExeFile();
391             }
392             // Make sure path to exe file exists
393             ensurePathToNameExists(Loc.initial, global.params.exefile);
394             cmdbuf.writeByte(',');
395             if (global.params.mapfile)
396                 writeFilename(&cmdbuf, global.params.mapfile);
397             else if (driverParams.map)
398             {
399                 writeFilename(&cmdbuf, getMapFilename());
400             }
401             else
402                 cmdbuf.writestring("nul");
403             cmdbuf.writeByte(',');
404             for (size_t i = 0; i < global.params.libfiles.length; i++)
405             {
406                 if (i)
407                     cmdbuf.writeByte('+');
408                 writeFilename(&cmdbuf, global.params.libfiles[i]);
409             }
410             if (global.params.deffile)
411             {
412                 cmdbuf.writeByte(',');
413                 writeFilename(&cmdbuf, global.params.deffile);
414             }
415             /* Eliminate unnecessary trailing commas    */
416             while (1)
417             {
418                 const size_t i = cmdbuf.length;
419                 if (!i || cmdbuf[i - 1] != ',')
420                     break;
421                 cmdbuf.setsize(cmdbuf.length - 1);
422             }
423             if (global.params.resfile)
424             {
425                 cmdbuf.writestring("/RC:");
426                 writeFilename(&cmdbuf, global.params.resfile);
427             }
428             if (driverParams.map || global.params.mapfile)
429                 cmdbuf.writestring("/m");
430             version (none)
431             {
432                 if (debuginfo)
433                     cmdbuf.writestring("/li");
434                 if (codeview)
435                 {
436                     cmdbuf.writestring("/co");
437                     if (codeview3)
438                         cmdbuf.writestring(":3");
439                 }
440             }
441             else
442             {
443                 if (driverParams.symdebug)
444                     cmdbuf.writestring("/co");
445             }
446             cmdbuf.writestring("/noi");
447             for (size_t i = 0; i < global.params.linkswitches.length; i++)
448             {
449                 cmdbuf.writestring(global.params.linkswitches[i]);
450             }
451             cmdbuf.writeByte(';');
452             cmdbuf.writeByte(0); //null terminate the buffer
453             char[] p = cmdbuf.extractSlice()[0 .. $-1];
454             const(char)[] lnkfilename;
455             if (p.length > 7000)
456             {
457                 lnkfilename = FileName.forceExt(global.params.exefile, "lnk");
458                 writeFile(Loc.initial, lnkfilename, p);
459                 if (lnkfilename.length < p.length)
460                 {
461                     p[0] = '@';
462                     p[1 .. lnkfilename.length +1] = lnkfilename;
463                     p[lnkfilename.length +1] = 0;
464                 }
465             }
466             const(char)* linkcmd = getenv("LINKCMD");
467             if (!linkcmd)
468                 linkcmd = "optlink";
469             const int status = executecmd(linkcmd, p.ptr);
470             if (lnkfilename)
471             {
472                 lnkfilename.toCStringThen!(lf => remove(lf.ptr));
473                 FileName.free(lnkfilename.ptr);
474             }
475             return status;
476         }
477         else
478         {
479             assert(0);
480         }
481     }
482     else version (Posix)
483     {
484         pid_t childpid;
485         int status;
486         // Build argv[]
487         Strings argv;
488         const(char)* cc = getenv("CC");
489         if (!cc)
490         {
491             argv.push("cc");
492         }
493         else
494         {
495             // Split CC command to support link driver arguments such as -fpie or -flto.
496             char* arg = cast(char*)Mem.check(strdup(cc));
497             const(char)* tok = strtok(arg, " ");
498             while (tok)
499             {
500                 argv.push(mem.xstrdup(tok));
501                 tok = strtok(null, " ");
502             }
503             free(arg);
504         }
505         argv.append(&global.params.objfiles);
506         version (OSX)
507         {
508             // If we are on Mac OS X and linking a dynamic library,
509             // add the "-dynamiclib" flag
510             if (driverParams.dll)
511                 argv.push("-dynamiclib");
512         }
513         else version (Posix)
514         {
515             if (driverParams.dll)
516                 argv.push("-shared");
517         }
518         // None of that a.out stuff. Use explicit exe file name, or
519         // generate one from name of first source file.
520         argv.push("-o");
521         if (global.params.exefile)
522         {
523             argv.push(global.params.exefile.xarraydup.ptr);
524         }
525         else if (global.params.run)
526         {
527             version (all)
528             {
529                 char[L_tmpnam + 14 + 1] name;
530                 strcpy(name.ptr, P_tmpdir);
531                 strcat(name.ptr, "/dmd_runXXXXXX");
532                 int fd = mkstemp(name.ptr);
533                 if (fd == -1)
534                 {
535                     error(Loc.initial, "error creating temporary file");
536                     return 1;
537                 }
538                 else
539                     close(fd);
540                 global.params.exefile = name.arraydup;
541                 argv.push(global.params.exefile.xarraydup.ptr);
542             }
543             else
544             {
545                 /* The use of tmpnam raises the issue of "is this a security hole"?
546                  * The hole is that after tmpnam and before the file is opened,
547                  * the attacker modifies the file system to get control of the
548                  * file with that name. I do not know if this is an issue in
549                  * this context.
550                  * We cannot just replace it with mkstemp, because this name is
551                  * passed to the linker that actually opens the file and writes to it.
552                  */
553                 char[L_tmpnam + 1] s;
554                 char* n = tmpnam(s.ptr);
555                 global.params.exefile = mem.xstrdup(n);
556                 argv.push(global.params.exefile);
557             }
558         }
559         else
560         {
561             // Generate exe file name from first obj name
562             const(char)[] n = global.params.objfiles[0].toDString();
563             const(char)[] ex;
564             n = FileName.name(n);
565             if (const e = FileName.ext(n))
566             {
567                 if (driverParams.dll)
568                     ex = FileName.forceExt(ex, target.dll_ext);
569                 else
570                     ex = FileName.removeExt(n);
571             }
572             else
573                 ex = "a.out"; // no extension, so give up
574             argv.push(ex.ptr);
575             global.params.exefile = ex;
576         }
577         // Make sure path to exe file exists
578         ensurePathToNameExists(Loc.initial, global.params.exefile);
579         if (driverParams.symdebug)
580             argv.push("-g");
581         if (target.is64bit)
582             argv.push("-m64");
583         else
584             argv.push("-m32");
585         version (OSX)
586         {
587             /* Without this switch, ld generates messages of the form:
588              * ld: warning: could not create compact unwind for __Dmain: offset of saved registers too far to encode
589              * meaning they are further than 255 bytes from the frame register.
590              * ld reverts to the old method instead.
591              * See: https://ghc.haskell.org/trac/ghc/ticket/5019
592              * which gives this tidbit:
593              * "When a C++ (or x86_64 Objective-C) exception is thrown, the runtime must unwind the
594              *  stack looking for some function to catch the exception.  Traditionally, the unwind
595              *  information is stored in the __TEXT/__eh_frame section of each executable as Dwarf
596              *  CFI (call frame information).  Beginning in Mac OS X 10.6, the unwind information is
597              *  also encoded in the __TEXT/__unwind_info section using a two-level lookup table of
598              *  compact unwind encodings.
599              *  The unwinddump tool displays the content of the __TEXT/__unwind_info section."
600              *
601              * A better fix would be to save the registers next to the frame pointer.
602              */
603             argv.push("-Xlinker");
604             argv.push("-no_compact_unwind");
605         }
606         if (driverParams.map || global.params.mapfile.length)
607         {
608             argv.push("-Xlinker");
609             version (OSX)
610             {
611                 argv.push("-map");
612             }
613             else
614             {
615                 argv.push("-Map");
616             }
617             if (!global.params.mapfile.length)
618             {
619                 const(char)[] fn = FileName.forceExt(global.params.exefile, map_ext);
620                 const(char)[] path = FileName.path(global.params.exefile);
621                 global.params.mapfile = path.length ? fn : FileName.combine(global.params.objdir, fn);
622             }
623             argv.push("-Xlinker");
624             argv.push(global.params.mapfile.xarraydup.ptr);
625         }
626         if (0 && global.params.exefile)
627         {
628             /* This switch enables what is known as 'smart linking'
629              * in the Windows world, where unreferenced sections
630              * are removed from the executable. It eliminates unreferenced
631              * functions, essentially making a 'library' out of a module.
632              * Although it is documented to work with ld version 2.13,
633              * in practice it does not, but just seems to be ignored.
634              * Thomas Kuehne has verified that it works with ld 2.16.1.
635              * BUG: disabled because it causes exception handling to fail
636              * because EH sections are "unreferenced" and elided
637              */
638             argv.push("-Xlinker");
639             argv.push("--gc-sections");
640         }
641 
642         // return true if flagp should be ordered in with the library flags
643         static bool flagIsLibraryRelated(const char* p)
644         {
645             const flag = p.toDString();
646 
647             return startsWith(p, "-l") || startsWith(p, "-L")
648                 || flag == "-(" || flag == "-)"
649                 || flag == "--start-group" || flag == "--end-group"
650                 || FileName.equalsExt(p, "a")
651             ;
652         }
653 
654         /* Add libraries. The order of libraries passed is:
655          *  1. link switches without a -L prefix,
656                e.g. --whole-archive "lib.a" --no-whole-archive     (global.params.linkswitches)
657          *  2. static libraries ending with *.a     (global.params.libfiles)
658          *  3. link switches with a -L prefix  (global.params.linkswitches)
659          *  4. libraries specified by pragma(lib), which were appended
660          *     to global.params.libfiles. These are prefixed with "-l"
661          *  5. dynamic libraries passed to the command line (global.params.dllfiles)
662          *  6. standard libraries.
663          */
664 
665         // STEP 1
666         foreach (pi, p; global.params.linkswitches)
667         {
668             if (p && p[0] && !flagIsLibraryRelated(p))
669             {
670                 if (!global.params.linkswitchIsForCC[pi])
671                     argv.push("-Xlinker");
672                 argv.push(p);
673             }
674         }
675 
676         // STEP 2
677         foreach (p; global.params.libfiles)
678         {
679             if (FileName.equalsExt(p, "a"))
680                 argv.push(p);
681         }
682 
683         // STEP 3
684         foreach (pi, p; global.params.linkswitches)
685         {
686             if (p && p[0] && flagIsLibraryRelated(p))
687             {
688                 if (!startsWith(p, "-l") && !startsWith(p, "-L") && !global.params.linkswitchIsForCC[pi])
689                 {
690                     // Don't need -Xlinker if switch starts with -l or -L.
691                     // Eliding -Xlinker is significant for -L since it allows our paths
692                     // to take precedence over gcc defaults.
693                     // All other link switches were already added in step 1.
694                     argv.push("-Xlinker");
695                 }
696                 argv.push(p);
697             }
698         }
699 
700         // STEP 4
701         foreach (p; global.params.libfiles)
702         {
703             if (!FileName.equalsExt(p, "a"))
704             {
705                 const plen = strlen(p);
706                 char* s = cast(char*)mem.xmalloc(plen + 3);
707                 s[0] = '-';
708                 s[1] = 'l';
709                 memcpy(s + 2, p, plen + 1);
710                 argv.push(s);
711             }
712         }
713 
714         // STEP 5
715         foreach (p; global.params.dllfiles)
716         {
717             argv.push(p);
718         }
719 
720         // STEP 6
721         /* D runtime libraries must go after user specified libraries
722          * passed with -l.
723          */
724         const libname = phobosLibname;
725         if (libname.length)
726         {
727             const bufsize = 2 + libname.length + 1;
728             auto buf = (cast(char*) malloc(bufsize))[0 .. bufsize];
729             Mem.check(buf.ptr);
730             buf[0 .. 2] = "-l";
731 
732             char* getbuf(const(char)[] suffix)
733             {
734                 buf[2 .. 2 + suffix.length] = suffix[];
735                 buf[2 + suffix.length] = 0;
736                 return buf.ptr;
737             }
738 
739             if (libname.length > 3 + 2 && libname[0 .. 3] == "lib")
740             {
741                 if (libname[$-2 .. $] == ".a")
742                 {
743                     argv.push("-Xlinker");
744                     argv.push("-Bstatic");
745                     argv.push(getbuf(libname[3 .. $-2]));
746                     argv.push("-Xlinker");
747                     argv.push("-Bdynamic");
748                 }
749                 else if (libname[$-3 .. $] == ".so")
750                     argv.push(getbuf(libname[3 .. $-3]));
751                 else
752                     argv.push(getbuf(libname));
753             }
754             else
755             {
756                 argv.push(getbuf(libname));
757             }
758         }
759         //argv.push("-ldruntime");
760         argv.push("-lpthread");
761         argv.push("-lm");
762         version (linux)
763         {
764             // Changes in ld for Ubuntu 11.10 require this to appear after phobos2
765             argv.push("-lrt");
766             // Link against libdl for phobos usage of dlopen
767             argv.push("-ldl");
768         }
769         else version (OpenBSD)
770         {
771             // Link against -lc++abi for Unwind symbols
772             argv.push("-lc++abi");
773             // Link against -lexecinfo for backtrace symbols
774             argv.push("-lexecinfo");
775         }
776         if (global.params.verbose)
777         {
778             // Print it
779             OutBuffer buf;
780             for (size_t i = 0; i < argv.length; i++)
781             {
782                 buf.writestring(argv[i]);
783                 buf.writeByte(' ');
784             }
785             message(buf.peekChars());
786         }
787         argv.push(null);
788         // set up pipes
789         int[2] fds;
790         if (pipe(fds.ptr) == -1)
791         {
792             perror("unable to create pipe to linker");
793             return -1;
794         }
795         // vfork instead of fork to avoid https://issues.dlang.org/show_bug.cgi?id=21089
796         childpid = vfork();
797         if (childpid == 0)
798         {
799             // pipe linker stderr to fds[0]
800             dup2(fds[1], STDERR_FILENO);
801             close(fds[0]);
802             execvp(argv[0], argv.tdata());
803             perror(argv[0]); // failed to execute
804             _exit(-1);
805         }
806         else if (childpid == -1)
807         {
808             perror("unable to fork");
809             return -1;
810         }
811         close(fds[1]);
812         const(int) nme = findNoMainError(fds[0]);
813         waitpid(childpid, &status, 0);
814         if (WIFEXITED(status))
815         {
816             status = WEXITSTATUS(status);
817             if (status)
818             {
819                 if (nme == -1)
820                 {
821                     perror("error with the linker pipe");
822                     return -1;
823                 }
824                 else
825                 {
826                     error(Loc.initial, "linker exited with status %d", status);
827                     if (nme == 1)
828                         error(Loc.initial, "no main function specified");
829                 }
830             }
831         }
832         else if (WIFSIGNALED(status))
833         {
834             error(Loc.initial, "linker killed by signal %d", WTERMSIG(status));
835             status = 1;
836         }
837         return status;
838     }
839     else
840     {
841         error(Loc.initial, "linking is not yet supported for this version of DMD.");
842         return -1;
843     }
844 }
845 
846 
847 /******************************
848  * Execute a rule.  Return the status.
849  *      cmd     program to run
850  *      args    arguments to cmd, as a string
851  */
852 version (Windows)
853 {
854     private int executecmd(const(char)* cmd, const(char)* args)
855     {
856         int status;
857         size_t len;
858         if (global.params.verbose)
859             message("%s %s", cmd, args);
860         if (target.objectFormat() == Target.ObjectFormat.omf)
861         {
862             if ((len = strlen(args)) > 255)
863             {
864                 status = putenvRestorable("_CMDLINE", args[0 .. len]);
865                 if (status == 0)
866                     args = "@_CMDLINE";
867                 else
868                     error(Loc.initial, "command line length of %llu is too long", cast(ulong) len);
869             }
870         }
871         // Normalize executable path separators
872         // https://issues.dlang.org/show_bug.cgi?id=9330
873         cmd = toWinPath(cmd);
874         version (CRuntime_Microsoft)
875         {
876             // Open scope so dmd doesn't complain about alloca + exception handling
877             {
878                 // Use process spawning through the WinAPI to avoid issues with executearg0 and spawnlp
879                 OutBuffer cmdbuf;
880                 cmdbuf.writestring("\"");
881                 cmdbuf.writestring(cmd);
882                 cmdbuf.writestring("\" ");
883                 cmdbuf.writestring(args);
884 
885                 STARTUPINFOA startInf;
886                 startInf.dwFlags = STARTF_USESTDHANDLES;
887                 startInf.hStdInput = GetStdHandle(STD_INPUT_HANDLE);
888                 startInf.hStdOutput = GetStdHandle(STD_OUTPUT_HANDLE);
889                 startInf.hStdError = GetStdHandle(STD_ERROR_HANDLE);
890                 PROCESS_INFORMATION procInf;
891 
892                 BOOL b = CreateProcessA(null, cmdbuf.peekChars(), null, null, 1, NORMAL_PRIORITY_CLASS, null, null, &startInf, &procInf);
893                 if (b)
894                 {
895                     WaitForSingleObject(procInf.hProcess, INFINITE);
896                     DWORD returnCode;
897                     GetExitCodeProcess(procInf.hProcess, &returnCode);
898                     status = returnCode;
899                     CloseHandle(procInf.hProcess);
900                 }
901                 else
902                 {
903                     status = -1;
904                 }
905             }
906         }
907         else
908         {
909             status = executearg0(cmd, args);
910             if (status == -1)
911             {
912                 status = spawnlp(0, cmd, cmd, args, null);
913             }
914         }
915         if (status)
916         {
917             if (status == -1)
918                 error(Loc.initial, "can't run '%s', check PATH", cmd);
919             else
920                 error(Loc.initial, "linker exited with status %d", status);
921         }
922         return status;
923     }
924 }
925 
926 /**************************************
927  * Attempt to find command to execute by first looking in the directory
928  * where DMD was run from.
929  * Returns:
930  *      -1      did not find command there
931  *      !=-1    exit status from command
932  */
933 version (Windows)
934 {
935     private int executearg0(const(char)* cmd, const(char)* args)
936     {
937         const argv0 = global.params.argv0;
938         //printf("argv0='%s', cmd='%s', args='%s'\n",argv0,cmd,args);
939         // If cmd is fully qualified, we don't do this
940         if (FileName.absolute(cmd.toDString()))
941             return -1;
942         const file = FileName.replaceName(argv0, cmd.toDString);
943         //printf("spawning '%s'\n",file);
944         // spawnlp returns intptr_t in some systems, not int
945         return spawnl(0, file.ptr, file.ptr, args, null);
946     }
947 }
948 
949 /***************************************
950  * Run the compiled program.
951  * Return exit status.
952  */
953 public int runProgram()
954 {
955     //printf("runProgram()\n");
956     if (global.params.verbose)
957     {
958         OutBuffer buf;
959         buf.writestring(global.params.exefile);
960         for (size_t i = 0; i < global.params.runargs.length; ++i)
961         {
962             buf.writeByte(' ');
963             buf.writestring(global.params.runargs[i]);
964         }
965         message(buf.peekChars());
966     }
967     // Build argv[]
968     Strings argv;
969     argv.push(global.params.exefile.xarraydup.ptr);
970     for (size_t i = 0; i < global.params.runargs.length; ++i)
971     {
972         const(char)* a = global.params.runargs[i];
973         version (Windows)
974         {
975             // BUG: what about " appearing in the string?
976             if (strchr(a, ' '))
977             {
978                 const blen = 3 + strlen(a);
979                 char* b = cast(char*)mem.xmalloc(blen);
980                 snprintf(b, blen, "\"%s\"", a);
981                 a = b;
982             }
983         }
984         argv.push(a);
985     }
986     argv.push(null);
987     restoreEnvVars();
988     version (Windows)
989     {
990         const(char)[] ex = FileName.name(global.params.exefile);
991         if (ex == global.params.exefile)
992             ex = FileName.combine(".", ex);
993         else
994             ex = global.params.exefile;
995         // spawnlp returns intptr_t in some systems, not int
996         return spawnv(0, ex.xarraydup.ptr, argv.tdata());
997     }
998     else version (Posix)
999     {
1000         pid_t childpid;
1001         int status;
1002         childpid = fork();
1003         if (childpid == 0)
1004         {
1005             const(char)[] fn = argv[0].toDString();
1006             // Make it "./fn" if needed
1007             if (!FileName.absolute(fn))
1008                 fn = FileName.combine(".", fn);
1009             fn.toCStringThen!((fnp) {
1010                     execv(fnp.ptr, argv.tdata());
1011                     // If execv returns, it failed to execute
1012                     perror(fnp.ptr);
1013                 });
1014             return -1;
1015         }
1016         waitpid(childpid, &status, 0);
1017         if (WIFEXITED(status))
1018         {
1019             status = WEXITSTATUS(status);
1020             //printf("--- errorlevel %d\n", status);
1021         }
1022         else if (WIFSIGNALED(status))
1023         {
1024             error(Loc.initial, "program killed by signal %d", WTERMSIG(status));
1025             status = 1;
1026         }
1027         return status;
1028     }
1029     else
1030     {
1031         assert(0);
1032     }
1033 }
1034 
1035 /***************************************
1036  * Run the C preprocessor.
1037  * Params:
1038  *    cpp = name of C preprocessor program
1039  *    filename = C source file name
1040  *    importc_h = filename of importc.h
1041  *    cppswitches = array of switches to pass to C preprocessor
1042  *    output = preprocessed output file name
1043  *    defines = buffer to append any `#define` and `#undef` lines encountered to
1044  * Returns:
1045  *    exit status.
1046  */
1047 public int runPreprocessor(const(char)[] cpp, const(char)[] filename, const(char)* importc_h, ref Array!(const(char)*) cppswitches,
1048     const(char)[] output, OutBuffer* defines)
1049 {
1050     //printf("runPreprocessor() cpp: %.*s filename: %.*s\n", cast(int)cpp.length, cpp.ptr, cast(int)filename.length, filename.ptr);
1051     version (Windows)
1052     {
1053         // Build argv[]
1054         Strings argv;
1055         if (target.objectFormat() == Target.ObjectFormat.coff)
1056         {
1057             static if (1)
1058             {
1059                 /* Run command, intercept stdout, remove first line that CL insists on emitting
1060                  */
1061                 OutBuffer buf;
1062                 buf.writestring(cpp);
1063                 buf.printf(" /P /Zc:preprocessor /PD /nologo %.*s /FI%s /Fi%.*s",
1064                     cast(int)filename.length, filename.ptr, importc_h, cast(int)output.length, output.ptr);
1065 
1066                 /* Append preprocessor switches to command line
1067                  */
1068                 foreach (a; cppswitches)
1069                 {
1070                     if (a && a[0])
1071                     {
1072                         buf.writeByte(' ');
1073                         buf.writestring(a);
1074                     }
1075                 }
1076 
1077                 if (global.params.verbose)
1078                     message(buf.peekChars());
1079 
1080                 ubyte[2048] buffer = void;
1081 
1082                 OutBuffer linebuf;      // each line from stdout
1083                 bool print = false;     // print line collected from stdout
1084 
1085                 /* Collect text captured from stdout to linebuf[].
1086                  * Then decide to print or discard the contents.
1087                  * Discarding lines that consist only of a filename is necessary to pass
1088                  * the D test suite which diffs the output. CL's emission of filenames cannot
1089                  * be turned off.
1090                  */
1091                 void sink(ubyte[] data)
1092                 {
1093                     foreach (c; data)
1094                     {
1095                         switch (c)
1096                         {
1097                             case '\r':
1098                                 break;
1099 
1100                             case '\n':
1101                                 if (print)
1102                                     printf("%s\n", linebuf.peekChars());
1103 
1104                                 // set up for next line
1105                                 linebuf.setsize(0);
1106                                 print = false;
1107                                 break;
1108 
1109                             case '\t':
1110                             case ';':
1111                             case '(':
1112                             case '\'':
1113                             case '"':   // various non-filename characters
1114                                 print = true; // mean it's not a filename
1115                                 goto default;
1116 
1117                             default:
1118                                 linebuf.writeByte(c);
1119                                 break;
1120                         }
1121                     }
1122                 }
1123 
1124                 // Convert command to wchar
1125                 wchar[1024] scratch = void;
1126                 auto smbuf = SmallBuffer!wchar(scratch.length, scratch[]);
1127                 auto szCommand = toWStringz(buf.peekChars()[0 .. buf.length], smbuf);
1128 
1129                 int exitCode = runProcessCollectStdout(szCommand.ptr, buffer[], &sink);
1130 
1131                 if (linebuf.length && print)  // anything leftover from stdout collection
1132                     printf("%s\n", defines.peekChars());
1133 
1134                 return exitCode;
1135             }
1136             else
1137             {
1138                 argv.push("cl".ptr);            // null terminated copy
1139                 argv.push("/P".ptr);            // preprocess only
1140                 argv.push("/nologo".ptr);       // don't print logo
1141                 argv.push(filename.xarraydup.ptr);   // and the input
1142 
1143                 OutBuffer buf;
1144                 buf.writestring("/Fi");       // https://docs.microsoft.com/en-us/cpp/build/reference/fi-preprocess-output-file-name?view=msvc-170
1145                 buf.writeStringz(output);
1146                 argv.push(buf.extractData()); // output file
1147 
1148                 argv.push(null);                     // argv[] always ends with a null
1149                 // spawnlp returns intptr_t in some systems, not int
1150                 return spawnvp(_P_WAIT, "cl".ptr, argv.tdata());
1151             }
1152         }
1153         else if (target.objectFormat() == Target.ObjectFormat.omf)
1154         {
1155             /* Digital Mars Win32 target
1156              * sppn filename -oooutput
1157              * https://www.digitalmars.com/ctg/sc.html
1158              */
1159 
1160             static if (1)
1161             {
1162                 /* Run command
1163                  */
1164                 OutBuffer buf;
1165                 buf.writestring(cpp);
1166                 buf.printf(" %.*s -HI%s -ED -o%.*s",
1167                     cast(int)filename.length, filename.ptr, importc_h, cast(int)output.length, output.ptr);
1168 
1169                 /* Append preprocessor switches to command line
1170                  */
1171                 foreach (a; cppswitches)
1172                 {
1173                     if (a && a[0])
1174                     {
1175                         buf.writeByte(' ');
1176                         buf.writestring(a);
1177                     }
1178                 }
1179 
1180                 if (global.params.verbose)
1181                     message(buf.peekChars());
1182 
1183                 ubyte[2048] buffer = void;
1184 
1185                 /* Write lines captured from stdout to either defines[] or stdout
1186                  */
1187                 enum S
1188                 {
1189                     start, // start of line
1190                     hash,  // write to defines[]
1191                     other, // write to stdout
1192                 }
1193 
1194                 S state = S.start;
1195 
1196                 void sinkomf(ubyte[] data)
1197                 {
1198                     foreach (c; data)
1199                     {
1200                         final switch (state)
1201                         {
1202                             case S.start:
1203                                 if (c == '#')
1204                                 {
1205                                     defines.writeByte(c);
1206                                     state = S.hash;
1207                                 }
1208                                 else
1209                                 {
1210                                     fputc(c, stdout);
1211                                     state = S.other;
1212                                 }
1213                                 break;
1214 
1215                             case S.hash:
1216                                 defines.writeByte(c);
1217                                 if (c == '\n')
1218                                     state = S.start;
1219                                 break;
1220 
1221                             case S.other:
1222                                 fputc(c, stdout);
1223                                 if (c == '\n')
1224                                     state = S.start;
1225                                 break;
1226                         }
1227                     }
1228                     //printf("%.*s", cast(int)data.length, data.ptr);
1229                 }
1230 
1231                 // Convert command to wchar
1232                 wchar[1024] scratch = void;
1233                 auto smbuf = SmallBuffer!wchar(scratch.length, scratch[]);
1234                 auto szCommand = toWStringz(buf.peekChars()[0 .. buf.length], smbuf);
1235 
1236                 //printf("szCommand: %ls\n", szCommand.ptr);
1237                 int exitCode = runProcessCollectStdout(szCommand.ptr, buffer[], &sinkomf);
1238                 printf("\n");
1239                 return exitCode;
1240             }
1241             else
1242             {
1243                 auto cmd = cpp.xarraydup.ptr;
1244                 argv.push(cmd);                      // Digita; Mars C preprocessor
1245                 argv.push(filename.xarraydup.ptr);   // and the input file
1246 
1247                 OutBuffer buf;
1248                 buf.writestring("-o");        // https://www.digitalmars.com/ctg/sc.html#dashofilename
1249                 buf.writeString(output);
1250                 argv.push(buf.extractData()); // output file
1251 
1252                 argv.push(null);              // argv[] always ends with a null
1253                 // spawnlp returns intptr_t in some systems, not int
1254                 return spawnvp(_P_WAIT, cmd, argv.tdata());
1255             }
1256         }
1257         else
1258         {
1259             assert(0);
1260         }
1261     }
1262     else version (Posix)
1263     {
1264         // Build argv[]
1265         Strings argv;
1266         argv.push(cpp.xarraydup.ptr);       // null terminated copy
1267 
1268         foreach (p; cppswitches)
1269         {
1270             if (p && p[0])
1271                 argv.push(p);
1272         }
1273 
1274         // Set memory model
1275         argv.push(target.is64bit ? "-m64" : "-m32");
1276 
1277         // merge #define's with output
1278         argv.push("-dD");       // https://gcc.gnu.org/onlinedocs/cpp/Invocation.html#index-dD
1279 
1280         // need to redefine some macros in importc.h
1281         argv.push("-Wno-builtin-macro-redefined");
1282 
1283         if (target.os == Target.OS.OSX)
1284         {
1285             argv.push("-fno-blocks");       // disable clang blocks extension
1286             argv.push("-E");                // run preprocessor only for clang
1287             argv.push("-include");          // OSX cpp has switch order dependencies
1288             argv.push(importc_h);
1289             argv.push(filename.xarraydup.ptr);  // and the input
1290             argv.push("-o");                // specify output file
1291         }
1292         else
1293         {
1294             argv.push(filename.xarraydup.ptr);  // and the input
1295             argv.push("-include");
1296             argv.push(importc_h);
1297         }
1298         if (target.os == Target.OS.FreeBSD || target.os == Target.OS.OpenBSD)
1299             argv.push("-o");                // specify output file
1300         argv.push(output.xarraydup.ptr);    // and the output
1301         argv.push(null);                    // argv[] always ends with a null
1302 
1303         if (global.params.verbose)
1304         {
1305             OutBuffer buf;
1306 
1307             foreach (i, a; argv[])
1308             {
1309                 if (a)
1310                 {
1311                     if (i)
1312                         buf.writeByte(' ');
1313                     buf.writestring(a);
1314                 }
1315             }
1316             message(buf.peekChars());
1317         }
1318 
1319         pid_t childpid = fork();
1320         if (childpid == 0)
1321         {
1322             const(char)[] fn = argv[0].toDString();
1323             fn.toCStringThen!((fnp) {
1324                     execvp(fnp.ptr, argv.tdata());
1325                     // If execv returns, it failed to execute
1326                     perror(fnp.ptr);
1327                 });
1328             return -1;
1329         }
1330         int status;
1331         waitpid(childpid, &status, 0);
1332         if (WIFEXITED(status))
1333         {
1334             status = WEXITSTATUS(status);
1335             //printf("--- errorlevel %d\n", status);
1336         }
1337         else if (WIFSIGNALED(status))
1338         {
1339             error(Loc.initial, "program killed by signal %d", WTERMSIG(status));
1340             status = 1;
1341         }
1342         return status;
1343     }
1344     else
1345     {
1346         assert(0);
1347     }
1348 }
1349 
1350 /*********************************
1351  * Run a command and intercept its stdout, which is redirected
1352  * to sink().
1353  * Params:
1354  *      szCommand = command to run
1355  *      buffer = buffer to collect stdout data to
1356  *      sink = stdout data is sent to sink()
1357  * Returns:
1358  *      0 on success
1359  * Reference:
1360  *      Based on
1361  *      https://github.com/dlang/visuald/blob/master/tools/pipedmd.d#L252
1362  */
1363 version (Windows)
1364 int runProcessCollectStdout(const(wchar)* szCommand, ubyte[] buffer, void delegate(ubyte[]) sink)
1365 {
1366     import core.sys.windows.windows;
1367     import core.sys.windows.wtypes;
1368     import core.sys.windows.psapi;
1369 
1370     //printf("runProcess() command: %ls\n", szCommand);
1371     // Set the bInheritHandle flag so pipe handles are inherited.
1372     SECURITY_ATTRIBUTES saAttr;
1373     saAttr.nLength = SECURITY_ATTRIBUTES.sizeof;
1374     saAttr.bInheritHandle = TRUE;
1375     saAttr.lpSecurityDescriptor = null;
1376 
1377     // Create a pipe for the child process's STDOUT.
1378     HANDLE hStdOutRead;
1379     HANDLE hStdOutWrite;
1380     if ( !CreatePipe(&hStdOutRead, &hStdOutWrite, &saAttr, 0) )
1381             assert(0);
1382     // Ensure the read handle to the pipe for STDOUT is not inherited.
1383     if ( !SetHandleInformation(hStdOutRead, HANDLE_FLAG_INHERIT, 0) )
1384             assert(0);
1385 
1386     // Another pipe
1387     HANDLE hStdInRead;
1388     HANDLE hStdInWrite;
1389     if ( !CreatePipe(&hStdInRead, &hStdInWrite, &saAttr, 0) )
1390             assert(0);
1391     if ( !SetHandleInformation(hStdInWrite, HANDLE_FLAG_INHERIT, 0) )
1392             assert(0);
1393 
1394     // Set up members of the PROCESS_INFORMATION structure.
1395     PROCESS_INFORMATION piProcInfo;
1396     memset( &piProcInfo, 0, PROCESS_INFORMATION.sizeof );
1397 
1398     // Set up members of the STARTUPINFO structure.
1399     // This structure specifies the STDIN and STDOUT handles for redirection.
1400     STARTUPINFOW siStartInfo;
1401     memset( &siStartInfo, 0, STARTUPINFOW.sizeof );
1402     siStartInfo.cb = STARTUPINFOW.sizeof;
1403     siStartInfo.hStdError = hStdOutWrite;
1404     siStartInfo.hStdOutput = hStdOutWrite;
1405     siStartInfo.hStdInput = hStdInRead;
1406     siStartInfo.dwFlags |= STARTF_USESTDHANDLES;
1407 
1408     // https://docs.microsoft.com/en-us/windows/win32/api/processthreadsapi/nf-processthreadsapi-createprocessw
1409     BOOL bSuccess = CreateProcessW(null,
1410                           cast(wchar*)szCommand,     // command line
1411                           null,          // process security attributes
1412                           null,          // primary thread security attributes
1413                           TRUE,          // handles are inherited
1414                           CREATE_SUSPENDED,             // creation flags
1415                           null,          // use parent's environment
1416                           null,          // use parent's current directory
1417                           &siStartInfo,  // STARTUPINFO pointer
1418                           &piProcInfo);  // receives PROCESS_INFORMATION
1419 
1420     if (!bSuccess)
1421     {
1422         printf("failed launching %ls\n", cast(wchar*)szCommand); // https://issues.dlang.org/show_bug.cgi?id=21958
1423         return 1;
1424     }
1425 
1426     ResumeThread(piProcInfo.hThread);
1427 
1428     DWORD bytesFilled = 0;
1429     DWORD bytesAvailable = 0;
1430     DWORD bytesRead = 0;
1431     DWORD exitCode = 0;
1432 
1433     while (true)
1434     {
1435         DWORD dwlen = cast(DWORD)buffer.length;
1436         bSuccess = PeekNamedPipe(hStdOutRead, buffer.ptr + bytesFilled, dwlen - bytesFilled, &bytesRead, &bytesAvailable, null);
1437         if (bSuccess && bytesRead > 0)
1438             bSuccess = ReadFile(hStdOutRead, buffer.ptr + bytesFilled, dwlen - bytesFilled, &bytesRead, null);
1439         if (bSuccess && bytesRead > 0)
1440         {
1441             sink(buffer[0 .. bytesRead]);
1442         }
1443 
1444         bSuccess = GetExitCodeProcess(piProcInfo.hProcess, &exitCode);
1445         if (!bSuccess || exitCode != 259) //259 == STILL_ACTIVE
1446         {
1447             break;
1448         }
1449         Sleep(1);
1450     }
1451 
1452     // close the handles to the process
1453     CloseHandle(hStdInWrite);
1454     CloseHandle(hStdOutRead);
1455     CloseHandle(piProcInfo.hProcess);
1456     CloseHandle(piProcInfo.hThread);
1457 
1458     return exitCode;
1459 }