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