1 /**
2  * Invoke the linker as a separate process.
3  *
4  * Copyright:   Copyright (C) 1999-2020 by The D Language Foundation, All Rights Reserved
5  * Authors:     $(LINK2 http://www.digitalmars.com, Walter Bright)
6  * License:     $(LINK2 http://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.env;
23 import dmd.errors;
24 import dmd.globals;
25 import dmd.root.file;
26 import dmd.root.filename;
27 import dmd.root.outbuffer;
28 import dmd.root.rmem;
29 import dmd.root.string;
30 import dmd.utils;
31 import dmd.vsoptions;
32 
33 version (Posix) extern (C) int pipe(int*);
34 version (Windows) extern (C) int spawnlp(int, const char*, const char*, const char*, const char*);
35 version (Windows) extern (C) int spawnl(int, const char*, const char*, const char*, const char*);
36 version (Windows) extern (C) int spawnv(int, const char*, const char**);
37 version (CRuntime_Microsoft)
38 {
39   // until the new windows bindings are available when building dmd.
40   static if(!is(STARTUPINFOA))
41   {
42     alias STARTUPINFOA = STARTUPINFO;
43 
44     // dwCreationFlags for CreateProcess() and CreateProcessAsUser()
45     enum : DWORD {
46       DEBUG_PROCESS               = 0x00000001,
47       DEBUG_ONLY_THIS_PROCESS     = 0x00000002,
48       CREATE_SUSPENDED            = 0x00000004,
49       DETACHED_PROCESS            = 0x00000008,
50       CREATE_NEW_CONSOLE          = 0x00000010,
51       NORMAL_PRIORITY_CLASS       = 0x00000020,
52       IDLE_PRIORITY_CLASS         = 0x00000040,
53       HIGH_PRIORITY_CLASS         = 0x00000080,
54       REALTIME_PRIORITY_CLASS     = 0x00000100,
55       CREATE_NEW_PROCESS_GROUP    = 0x00000200,
56       CREATE_UNICODE_ENVIRONMENT  = 0x00000400,
57       CREATE_SEPARATE_WOW_VDM     = 0x00000800,
58       CREATE_SHARED_WOW_VDM       = 0x00001000,
59       CREATE_FORCEDOS             = 0x00002000,
60       BELOW_NORMAL_PRIORITY_CLASS = 0x00004000,
61       ABOVE_NORMAL_PRIORITY_CLASS = 0x00008000,
62       CREATE_BREAKAWAY_FROM_JOB   = 0x01000000,
63       CREATE_WITH_USERPROFILE     = 0x02000000,
64       CREATE_DEFAULT_ERROR_MODE   = 0x04000000,
65       CREATE_NO_WINDOW            = 0x08000000,
66       PROFILE_USER                = 0x10000000,
67       PROFILE_KERNEL              = 0x20000000,
68       PROFILE_SERVER              = 0x40000000
69     }
70   }
71 }
72 
73 /****************************************
74  * Write filename to cmdbuf, quoting if necessary.
75  */
76 private void writeFilename(OutBuffer* buf, const(char)[] filename)
77 {
78     /* Loop and see if we need to quote
79      */
80     foreach (const char c; filename)
81     {
82         if (isalnum(c) || c == '_')
83             continue;
84         /* Need to quote
85          */
86         buf.writeByte('"');
87         buf.writestring(filename);
88         buf.writeByte('"');
89         return;
90     }
91     /* No quoting necessary
92      */
93     buf.writestring(filename);
94 }
95 
96 private void writeFilename(OutBuffer* buf, const(char)* filename)
97 {
98     writeFilename(buf, filename.toDString());
99 }
100 
101 version (Posix)
102 {
103     /*****************************
104      * As it forwards the linker error message to stderr, checks for the presence
105      * of an error indicating lack of a main function (NME_ERR_MSG).
106      *
107      * Returns:
108      *      1 if there is a no main error
109      *     -1 if there is an IO error
110      *      0 otherwise
111      */
112     private int findNoMainError(int fd)
113     {
114         version (OSX)
115         {
116             static immutable(char*) nmeErrorMessage = "`__Dmain`, referenced from:";
117         }
118         else
119         {
120             static immutable(char*) nmeErrorMessage = "undefined reference to `_Dmain`";
121         }
122         FILE* stream = fdopen(fd, "r");
123         if (stream is null)
124             return -1;
125         const(size_t) len = 64 * 1024 - 1;
126         char[len + 1] buffer; // + '\0'
127         size_t beg = 0, end = len;
128         bool nmeFound = false;
129         for (;;)
130         {
131             // read linker output
132             const(size_t) n = fread(&buffer[beg], 1, len - beg, stream);
133             if (beg + n < len && ferror(stream))
134                 return -1;
135             buffer[(end = beg + n)] = '\0';
136             // search error message, stop at last complete line
137             const(char)* lastSep = strrchr(buffer.ptr, '\n');
138             if (lastSep)
139                 buffer[(end = lastSep - &buffer[0])] = '\0';
140             if (strstr(&buffer[0], nmeErrorMessage))
141                 nmeFound = true;
142             if (lastSep)
143                 buffer[end++] = '\n';
144             if (fwrite(&buffer[0], 1, end, stderr) < end)
145                 return -1;
146             if (beg + n < len && feof(stream))
147                 break;
148             // copy over truncated last line
149             memcpy(&buffer[0], &buffer[end], (beg = len - end));
150         }
151         return nmeFound ? 1 : 0;
152     }
153 }
154 
155 version (Windows)
156 {
157     private void writeQuotedArgIfNeeded(ref OutBuffer buffer, const(char)* arg)
158     {
159         bool quote = false;
160         for (size_t i = 0; arg[i]; ++i)
161         {
162             if (arg[i] == '"')
163             {
164                 quote = false;
165                 break;
166             }
167 
168             if (arg[i] == ' ')
169                 quote = true;
170         }
171 
172         if (quote)
173             buffer.writeByte('"');
174         buffer.writestring(arg);
175         if (quote)
176             buffer.writeByte('"');
177     }
178 
179     unittest
180     {
181         OutBuffer buffer;
182 
183         const(char)[] test(string arg)
184         {
185             buffer.reset();
186             buffer.writeQuotedArgIfNeeded(arg.ptr);
187             return buffer[];
188         }
189 
190         assert(test("arg") == `arg`);
191         assert(test("arg with spaces") == `"arg with spaces"`);
192         assert(test(`"/LIBPATH:dir with spaces"`) == `"/LIBPATH:dir with spaces"`);
193         assert(test(`/LIBPATH:"dir with spaces"`) == `/LIBPATH:"dir with spaces"`);
194     }
195 }
196 
197 /*****************************
198  * Run the linker.  Return status of execution.
199  */
200 public int runLINK()
201 {
202     const phobosLibname = global.finalDefaultlibname();
203 
204     void setExeFile()
205     {
206         /* Generate exe file name from first obj name.
207          * No need to add it to cmdbuf because the linker will default to it.
208          */
209         const char[] n = FileName.name(global.params.objfiles[0].toDString);
210         global.params.exefile = FileName.forceExt(n, "exe");
211     }
212 
213     const(char)[] getMapFilename()
214     {
215         const(char)[] fn = FileName.forceExt(global.params.exefile, "map");
216         const(char)[] path = FileName.path(global.params.exefile);
217         return path.length ? fn : FileName.combine(global.params.objdir, fn);
218     }
219 
220     version (Windows)
221     {
222         if (phobosLibname)
223             global.params.libfiles.push(phobosLibname.xarraydup.ptr);
224 
225         if (global.params.mscoff)
226         {
227             OutBuffer cmdbuf;
228             cmdbuf.writestring("/NOLOGO");
229             for (size_t i = 0; i < global.params.objfiles.length; i++)
230             {
231                 cmdbuf.writeByte(' ');
232                 const(char)* p = global.params.objfiles[i];
233                 writeFilename(&cmdbuf, p);
234             }
235             if (global.params.resfile)
236             {
237                 cmdbuf.writeByte(' ');
238                 writeFilename(&cmdbuf, global.params.resfile);
239             }
240             cmdbuf.writeByte(' ');
241             if (global.params.exefile)
242             {
243                 cmdbuf.writestring("/OUT:");
244                 writeFilename(&cmdbuf, global.params.exefile);
245             }
246             else
247             {
248                 setExeFile();
249             }
250             // Make sure path to exe file exists
251             ensurePathToNameExists(Loc.initial, global.params.exefile);
252             cmdbuf.writeByte(' ');
253             if (global.params.mapfile)
254             {
255                 cmdbuf.writestring("/MAP:");
256                 writeFilename(&cmdbuf, global.params.mapfile);
257             }
258             else if (global.params.map)
259             {
260                 cmdbuf.writestring("/MAP:");
261                 writeFilename(&cmdbuf, getMapFilename());
262             }
263             for (size_t i = 0; i < global.params.libfiles.length; i++)
264             {
265                 cmdbuf.writeByte(' ');
266                 cmdbuf.writestring("/DEFAULTLIB:");
267                 writeFilename(&cmdbuf, global.params.libfiles[i]);
268             }
269             if (global.params.deffile)
270             {
271                 cmdbuf.writeByte(' ');
272                 cmdbuf.writestring("/DEF:");
273                 writeFilename(&cmdbuf, global.params.deffile);
274             }
275             if (global.params.symdebug)
276             {
277                 cmdbuf.writeByte(' ');
278                 cmdbuf.writestring("/DEBUG");
279                 // in release mode we need to reactivate /OPT:REF after /DEBUG
280                 if (global.params.release)
281                     cmdbuf.writestring(" /OPT:REF");
282             }
283             if (global.params.dll)
284             {
285                 cmdbuf.writeByte(' ');
286                 cmdbuf.writestring("/DLL");
287             }
288             for (size_t i = 0; i < global.params.linkswitches.length; i++)
289             {
290                 cmdbuf.writeByte(' ');
291                 cmdbuf.writeQuotedArgIfNeeded(global.params.linkswitches[i]);
292             }
293 
294             VSOptions vsopt;
295             // if a runtime library (msvcrtNNN.lib) from the mingw folder is selected explicitly, do not detect VS and use lld
296             if (global.params.mscrtlib.length <= 6 ||
297                 global.params.mscrtlib[0..6] != "msvcrt" || !isdigit(global.params.mscrtlib[6]))
298                 vsopt.initialize();
299 
300             const(char)* lflags = vsopt.linkOptions(global.params.is64bit);
301             if (lflags)
302             {
303                 cmdbuf.writeByte(' ');
304                 cmdbuf.writestring(lflags);
305             }
306 
307             const(char)* linkcmd = getenv(global.params.is64bit ? "LINKCMD64" : "LINKCMD");
308             if (!linkcmd)
309                 linkcmd = getenv("LINKCMD"); // backward compatible
310             if (!linkcmd)
311                 linkcmd = vsopt.linkerPath(global.params.is64bit);
312 
313             // object files not SAFESEH compliant, but LLD is more picky than MS link
314             if (!global.params.is64bit)
315                 if (FileName.equals(FileName.name(linkcmd), "lld-link.exe"))
316                     cmdbuf.writestring(" /SAFESEH:NO");
317 
318             cmdbuf.writeByte(0); // null terminate the buffer
319             char[] p = cmdbuf.extractSlice()[0 .. $-1];
320             const(char)[] lnkfilename;
321             if (p.length > 7000)
322             {
323                 lnkfilename = FileName.forceExt(global.params.exefile, "lnk");
324                 writeFile(Loc.initial, lnkfilename, p);
325                 if (lnkfilename.length < p.length)
326                 {
327                     p[0] = '@';
328                     p[1 ..  lnkfilename.length +1] = lnkfilename;
329                     p[lnkfilename.length +1] = 0;
330                 }
331             }
332 
333             const int status = executecmd(linkcmd, p.ptr);
334             if (lnkfilename)
335             {
336                 lnkfilename.toCStringThen!(lf => remove(lf.ptr));
337                 FileName.free(lnkfilename.ptr);
338             }
339             return status;
340         }
341         else
342         {
343             OutBuffer cmdbuf;
344             global.params.libfiles.push("user32");
345             global.params.libfiles.push("kernel32");
346             for (size_t i = 0; i < global.params.objfiles.length; i++)
347             {
348                 if (i)
349                     cmdbuf.writeByte('+');
350                 const(char)[] p = global.params.objfiles[i].toDString();
351                 const(char)[] basename = FileName.removeExt(FileName.name(p));
352                 const(char)[] ext = FileName.ext(p);
353                 if (ext.length && !strchr(basename.ptr, '.'))
354                 {
355                     // Write name sans extension (but not if a double extension)
356                     writeFilename(&cmdbuf, p[0 .. $ - ext.length - 1]);
357                 }
358                 else
359                     writeFilename(&cmdbuf, p);
360                 FileName.free(basename.ptr);
361             }
362             cmdbuf.writeByte(',');
363             if (global.params.exefile)
364                 writeFilename(&cmdbuf, global.params.exefile);
365             else
366             {
367                 setExeFile();
368             }
369             // Make sure path to exe file exists
370             ensurePathToNameExists(Loc.initial, global.params.exefile);
371             cmdbuf.writeByte(',');
372             if (global.params.mapfile)
373                 writeFilename(&cmdbuf, global.params.mapfile);
374             else if (global.params.map)
375             {
376                 writeFilename(&cmdbuf, getMapFilename());
377             }
378             else
379                 cmdbuf.writestring("nul");
380             cmdbuf.writeByte(',');
381             for (size_t i = 0; i < global.params.libfiles.length; i++)
382             {
383                 if (i)
384                     cmdbuf.writeByte('+');
385                 writeFilename(&cmdbuf, global.params.libfiles[i]);
386             }
387             if (global.params.deffile)
388             {
389                 cmdbuf.writeByte(',');
390                 writeFilename(&cmdbuf, global.params.deffile);
391             }
392             /* Eliminate unnecessary trailing commas    */
393             while (1)
394             {
395                 const size_t i = cmdbuf.length;
396                 if (!i || cmdbuf[i - 1] != ',')
397                     break;
398                 cmdbuf.setsize(cmdbuf.length - 1);
399             }
400             if (global.params.resfile)
401             {
402                 cmdbuf.writestring("/RC:");
403                 writeFilename(&cmdbuf, global.params.resfile);
404             }
405             if (global.params.map || global.params.mapfile)
406                 cmdbuf.writestring("/m");
407             version (none)
408             {
409                 if (debuginfo)
410                     cmdbuf.writestring("/li");
411                 if (codeview)
412                 {
413                     cmdbuf.writestring("/co");
414                     if (codeview3)
415                         cmdbuf.writestring(":3");
416                 }
417             }
418             else
419             {
420                 if (global.params.symdebug)
421                     cmdbuf.writestring("/co");
422             }
423             cmdbuf.writestring("/noi");
424             for (size_t i = 0; i < global.params.linkswitches.length; i++)
425             {
426                 cmdbuf.writestring(global.params.linkswitches[i]);
427             }
428             cmdbuf.writeByte(';');
429             cmdbuf.writeByte(0); //null terminate the buffer
430             char[] p = cmdbuf.extractSlice()[0 .. $-1];
431             const(char)[] lnkfilename;
432             if (p.length > 7000)
433             {
434                 lnkfilename = FileName.forceExt(global.params.exefile, "lnk");
435                 writeFile(Loc.initial, lnkfilename, p);
436                 if (lnkfilename.length < p.length)
437                 {
438                     p[0] = '@';
439                     p[1 .. lnkfilename.length +1] = lnkfilename;
440                     p[lnkfilename.length +1] = 0;
441                 }
442             }
443             const(char)* linkcmd = getenv("LINKCMD");
444             if (!linkcmd)
445                 linkcmd = "optlink";
446             const int status = executecmd(linkcmd, p.ptr);
447             if (lnkfilename)
448             {
449                 lnkfilename.toCStringThen!(lf => remove(lf.ptr));
450                 FileName.free(lnkfilename.ptr);
451             }
452             return status;
453         }
454     }
455     else version (Posix)
456     {
457         pid_t childpid;
458         int status;
459         // Build argv[]
460         Strings argv;
461         const(char)* cc = getenv("CC");
462         if (!cc)
463         {
464             argv.push("cc");
465         }
466         else
467         {
468             // Split CC command to support link driver arguments such as -fpie or -flto.
469             char* arg = cast(char*)Mem.check(strdup(cc));
470             const(char)* tok = strtok(arg, " ");
471             while (tok)
472             {
473                 argv.push(mem.xstrdup(tok));
474                 tok = strtok(null, " ");
475             }
476             free(arg);
477         }
478         argv.append(&global.params.objfiles);
479         version (OSX)
480         {
481             // If we are on Mac OS X and linking a dynamic library,
482             // add the "-dynamiclib" flag
483             if (global.params.dll)
484                 argv.push("-dynamiclib");
485         }
486         else version (Posix)
487         {
488             if (global.params.dll)
489                 argv.push("-shared");
490         }
491         // None of that a.out stuff. Use explicit exe file name, or
492         // generate one from name of first source file.
493         argv.push("-o");
494         if (global.params.exefile)
495         {
496             argv.push(global.params.exefile.xarraydup.ptr);
497         }
498         else if (global.params.run)
499         {
500             version (all)
501             {
502                 char[L_tmpnam + 14 + 1] name;
503                 strcpy(name.ptr, P_tmpdir);
504                 strcat(name.ptr, "/dmd_runXXXXXX");
505                 int fd = mkstemp(name.ptr);
506                 if (fd == -1)
507                 {
508                     error(Loc.initial, "error creating temporary file");
509                     return 1;
510                 }
511                 else
512                     close(fd);
513                 global.params.exefile = name.arraydup;
514                 argv.push(global.params.exefile.xarraydup.ptr);
515             }
516             else
517             {
518                 /* The use of tmpnam raises the issue of "is this a security hole"?
519                  * The hole is that after tmpnam and before the file is opened,
520                  * the attacker modifies the file system to get control of the
521                  * file with that name. I do not know if this is an issue in
522                  * this context.
523                  * We cannot just replace it with mkstemp, because this name is
524                  * passed to the linker that actually opens the file and writes to it.
525                  */
526                 char[L_tmpnam + 1] s;
527                 char* n = tmpnam(s.ptr);
528                 global.params.exefile = mem.xstrdup(n);
529                 argv.push(global.params.exefile);
530             }
531         }
532         else
533         {
534             // Generate exe file name from first obj name
535             const(char)[] n = global.params.objfiles[0].toDString();
536             const(char)[] ex;
537             n = FileName.name(n);
538             if (const e = FileName.ext(n))
539             {
540                 if (global.params.dll)
541                     ex = FileName.forceExt(ex, global.dll_ext);
542                 else
543                     ex = FileName.removeExt(n);
544             }
545             else
546                 ex = "a.out"; // no extension, so give up
547             argv.push(ex.ptr);
548             global.params.exefile = ex;
549         }
550         // Make sure path to exe file exists
551         ensurePathToNameExists(Loc.initial, global.params.exefile);
552         if (global.params.symdebug)
553             argv.push("-g");
554         if (global.params.is64bit)
555             argv.push("-m64");
556         else
557             argv.push("-m32");
558         version (OSX)
559         {
560             /* Without this switch, ld generates messages of the form:
561              * ld: warning: could not create compact unwind for __Dmain: offset of saved registers too far to encode
562              * meaning they are further than 255 bytes from the frame register.
563              * ld reverts to the old method instead.
564              * See: https://ghc.haskell.org/trac/ghc/ticket/5019
565              * which gives this tidbit:
566              * "When a C++ (or x86_64 Objective-C) exception is thrown, the runtime must unwind the
567              *  stack looking for some function to catch the exception.  Traditionally, the unwind
568              *  information is stored in the __TEXT/__eh_frame section of each executable as Dwarf
569              *  CFI (call frame information).  Beginning in Mac OS X 10.6, the unwind information is
570              *  also encoded in the __TEXT/__unwind_info section using a two-level lookup table of
571              *  compact unwind encodings.
572              *  The unwinddump tool displays the content of the __TEXT/__unwind_info section."
573              *
574              * A better fix would be to save the registers next to the frame pointer.
575              */
576             argv.push("-Xlinker");
577             argv.push("-no_compact_unwind");
578         }
579         if (global.params.map || global.params.mapfile.length)
580         {
581             argv.push("-Xlinker");
582             version (OSX)
583             {
584                 argv.push("-map");
585             }
586             else
587             {
588                 argv.push("-Map");
589             }
590             if (!global.params.mapfile.length)
591             {
592                 const(char)[] fn = FileName.forceExt(global.params.exefile, "map");
593                 const(char)[] path = FileName.path(global.params.exefile);
594                 global.params.mapfile = path.length ? fn : FileName.combine(global.params.objdir, fn);
595             }
596             argv.push("-Xlinker");
597             argv.push(global.params.mapfile.xarraydup.ptr);
598         }
599         if (0 && global.params.exefile)
600         {
601             /* This switch enables what is known as 'smart linking'
602              * in the Windows world, where unreferenced sections
603              * are removed from the executable. It eliminates unreferenced
604              * functions, essentially making a 'library' out of a module.
605              * Although it is documented to work with ld version 2.13,
606              * in practice it does not, but just seems to be ignored.
607              * Thomas Kuehne has verified that it works with ld 2.16.1.
608              * BUG: disabled because it causes exception handling to fail
609              * because EH sections are "unreferenced" and elided
610              */
611             argv.push("-Xlinker");
612             argv.push("--gc-sections");
613         }
614 
615         // return true if flagp should be ordered in with the library flags
616         static bool flagIsLibraryRelated(const char* p)
617         {
618             const flag = p.toDString();
619 
620             return startsWith(p, "-l") || startsWith(p, "-L")
621                 || flag == "-(" || flag == "-)"
622                 || flag == "--start-group" || flag == "--end-group"
623                 || FileName.equalsExt(p, "a")
624             ;
625         }
626 
627         /* Add libraries. The order of libraries passed is:
628          *  1. link switches without a -L prefix,
629                e.g. --whole-archive "lib.a" --no-whole-archive     (global.params.linkswitches)
630          *  2. static libraries ending with *.a     (global.params.libfiles)
631          *  3. link switches with a -L prefix  (global.params.linkswitches)
632          *  4. libraries specified by pragma(lib), which were appended
633          *     to global.params.libfiles. These are prefixed with "-l"
634          *  5. dynamic libraries passed to the command line (global.params.dllfiles)
635          *  6. standard libraries.
636          */
637 
638         // STEP 1
639         foreach (pi, p; global.params.linkswitches)
640         {
641             if (p && p[0] && !flagIsLibraryRelated(p))
642             {
643                 if (!global.params.linkswitchIsForCC[pi])
644                     argv.push("-Xlinker");
645                 argv.push(p);
646             }
647         }
648 
649         // STEP 2
650         foreach (p; global.params.libfiles)
651         {
652             if (FileName.equalsExt(p, "a"))
653                 argv.push(p);
654         }
655 
656         // STEP 3
657         foreach (pi, p; global.params.linkswitches)
658         {
659             if (p && p[0] && flagIsLibraryRelated(p))
660             {
661                 if (!startsWith(p, "-l") && !startsWith(p, "-L") && !global.params.linkswitchIsForCC[pi])
662                 {
663                     // Don't need -Xlinker if switch starts with -l or -L.
664                     // Eliding -Xlinker is significant for -L since it allows our paths
665                     // to take precedence over gcc defaults.
666                     // All other link switches were already added in step 1.
667                     argv.push("-Xlinker");
668                 }
669                 argv.push(p);
670             }
671         }
672 
673         // STEP 4
674         foreach (p; global.params.libfiles)
675         {
676             if (!FileName.equalsExt(p, "a"))
677             {
678                 const plen = strlen(p);
679                 char* s = cast(char*)mem.xmalloc(plen + 3);
680                 s[0] = '-';
681                 s[1] = 'l';
682                 memcpy(s + 2, p, plen + 1);
683                 argv.push(s);
684             }
685         }
686 
687         // STEP 5
688         foreach (p; global.params.dllfiles)
689         {
690             argv.push(p);
691         }
692 
693         // STEP 6
694         /* D runtime libraries must go after user specified libraries
695          * passed with -l.
696          */
697         const libname = phobosLibname;
698         if (libname.length)
699         {
700             const bufsize = 2 + libname.length + 1;
701             auto buf = (cast(char*) malloc(bufsize))[0 .. bufsize];
702             if (!buf)
703                 Mem.error();
704             buf[0 .. 2] = "-l";
705 
706             char* getbuf(const(char)[] suffix)
707             {
708                 buf[2 .. 2 + suffix.length] = suffix[];
709                 buf[2 + suffix.length] = 0;
710                 return buf.ptr;
711             }
712 
713             if (libname.length > 3 + 2 && libname[0 .. 3] == "lib")
714             {
715                 if (libname[$-2 .. $] == ".a")
716                 {
717                     argv.push("-Xlinker");
718                     argv.push("-Bstatic");
719                     argv.push(getbuf(libname[3 .. $-2]));
720                     argv.push("-Xlinker");
721                     argv.push("-Bdynamic");
722                 }
723                 else if (libname[$-3 .. $] == ".so")
724                     argv.push(getbuf(libname[3 .. $-3]));
725                 else
726                     argv.push(getbuf(libname));
727             }
728             else
729             {
730                 argv.push(getbuf(libname));
731             }
732         }
733         //argv.push("-ldruntime");
734         argv.push("-lpthread");
735         argv.push("-lm");
736         version (linux)
737         {
738             // Changes in ld for Ubuntu 11.10 require this to appear after phobos2
739             argv.push("-lrt");
740             // Link against libdl for phobos usage of dlopen
741             argv.push("-ldl");
742         }
743         if (global.params.verbose)
744         {
745             // Print it
746             OutBuffer buf;
747             for (size_t i = 0; i < argv.dim; i++)
748             {
749                 buf.writestring(argv[i]);
750                 buf.writeByte(' ');
751             }
752             message(buf.peekChars());
753         }
754         argv.push(null);
755         // set up pipes
756         int[2] fds;
757         if (pipe(fds.ptr) == -1)
758         {
759             perror("unable to create pipe to linker");
760             return -1;
761         }
762         // vfork instead of fork to avoid https://issues.dlang.org/show_bug.cgi?id=21089
763         childpid = vfork();
764         if (childpid == 0)
765         {
766             // pipe linker stderr to fds[0]
767             dup2(fds[1], STDERR_FILENO);
768             close(fds[0]);
769             execvp(argv[0], argv.tdata());
770             perror(argv[0]); // failed to execute
771             _exit(-1);
772         }
773         else if (childpid == -1)
774         {
775             perror("unable to fork");
776             return -1;
777         }
778         close(fds[1]);
779         const(int) nme = findNoMainError(fds[0]);
780         waitpid(childpid, &status, 0);
781         if (WIFEXITED(status))
782         {
783             status = WEXITSTATUS(status);
784             if (status)
785             {
786                 if (nme == -1)
787                 {
788                     perror("error with the linker pipe");
789                     return -1;
790                 }
791                 else
792                 {
793                     error(Loc.initial, "linker exited with status %d", status);
794                     if (nme == 1)
795                         error(Loc.initial, "no main function specified");
796                 }
797             }
798         }
799         else if (WIFSIGNALED(status))
800         {
801             error(Loc.initial, "linker killed by signal %d", WTERMSIG(status));
802             status = 1;
803         }
804         return status;
805     }
806     else
807     {
808         error(Loc.initial, "linking is not yet supported for this version of DMD.");
809         return -1;
810     }
811 }
812 
813 
814 /******************************
815  * Execute a rule.  Return the status.
816  *      cmd     program to run
817  *      args    arguments to cmd, as a string
818  */
819 version (Windows)
820 {
821     private int executecmd(const(char)* cmd, const(char)* args)
822     {
823         int status;
824         size_t len;
825         if (global.params.verbose)
826             message("%s %s", cmd, args);
827         if (!global.params.mscoff)
828         {
829             if ((len = strlen(args)) > 255)
830             {
831                 status = putenvRestorable("_CMDLINE", args[0 .. len]);
832                 if (status == 0)
833                     args = "@_CMDLINE";
834                 else
835                     error(Loc.initial, "command line length of %llu is too long", cast(ulong) len);
836             }
837         }
838         // Normalize executable path separators
839         // https://issues.dlang.org/show_bug.cgi?id=9330
840         cmd = toWinPath(cmd);
841         version (CRuntime_Microsoft)
842         {
843             // Open scope so dmd doesn't complain about alloca + exception handling
844             {
845                 // Use process spawning through the WinAPI to avoid issues with executearg0 and spawnlp
846                 OutBuffer cmdbuf;
847                 cmdbuf.writestring("\"");
848                 cmdbuf.writestring(cmd);
849                 cmdbuf.writestring("\" ");
850                 cmdbuf.writestring(args);
851 
852                 STARTUPINFOA startInf;
853                 startInf.dwFlags = STARTF_USESTDHANDLES;
854                 startInf.hStdInput = GetStdHandle(STD_INPUT_HANDLE);
855                 startInf.hStdOutput = GetStdHandle(STD_OUTPUT_HANDLE);
856                 startInf.hStdError = GetStdHandle(STD_ERROR_HANDLE);
857                 PROCESS_INFORMATION procInf;
858 
859                 BOOL b = CreateProcessA(null, cmdbuf.peekChars(), null, null, 1, NORMAL_PRIORITY_CLASS, null, null, &startInf, &procInf);
860                 if (b)
861                 {
862                     WaitForSingleObject(procInf.hProcess, INFINITE);
863                     DWORD returnCode;
864                     GetExitCodeProcess(procInf.hProcess, &returnCode);
865                     status = returnCode;
866                     CloseHandle(procInf.hProcess);
867                 }
868                 else
869                 {
870                     status = -1;
871                 }
872             }
873         }
874         else
875         {
876             status = executearg0(cmd, args);
877             if (status == -1)
878             {
879                 status = spawnlp(0, cmd, cmd, args, null);
880             }
881         }
882         if (status)
883         {
884             if (status == -1)
885                 error(Loc.initial, "can't run '%s', check PATH", cmd);
886             else
887                 error(Loc.initial, "linker exited with status %d", status);
888         }
889         return status;
890     }
891 }
892 
893 /**************************************
894  * Attempt to find command to execute by first looking in the directory
895  * where DMD was run from.
896  * Returns:
897  *      -1      did not find command there
898  *      !=-1    exit status from command
899  */
900 version (Windows)
901 {
902     private int executearg0(const(char)* cmd, const(char)* args)
903     {
904         const argv0 = global.params.argv0;
905         //printf("argv0='%s', cmd='%s', args='%s'\n",argv0,cmd,args);
906         // If cmd is fully qualified, we don't do this
907         if (FileName.absolute(cmd.toDString()))
908             return -1;
909         const file = FileName.replaceName(argv0, cmd.toDString);
910         //printf("spawning '%s'\n",file);
911         // spawnlp returns intptr_t in some systems, not int
912         return spawnl(0, file.ptr, file.ptr, args, null);
913     }
914 }
915 
916 /***************************************
917  * Run the compiled program.
918  * Return exit status.
919  */
920 public int runProgram()
921 {
922     //printf("runProgram()\n");
923     if (global.params.verbose)
924     {
925         OutBuffer buf;
926         buf.writestring(global.params.exefile);
927         for (size_t i = 0; i < global.params.runargs.dim; ++i)
928         {
929             buf.writeByte(' ');
930             buf.writestring(global.params.runargs[i]);
931         }
932         message(buf.peekChars());
933     }
934     // Build argv[]
935     Strings argv;
936     argv.push(global.params.exefile.xarraydup.ptr);
937     for (size_t i = 0; i < global.params.runargs.dim; ++i)
938     {
939         const(char)* a = global.params.runargs[i];
940         version (Windows)
941         {
942             // BUG: what about " appearing in the string?
943             if (strchr(a, ' '))
944             {
945                 char* b = cast(char*)mem.xmalloc(3 + strlen(a));
946                 sprintf(b, "\"%s\"", a);
947                 a = b;
948             }
949         }
950         argv.push(a);
951     }
952     argv.push(null);
953     restoreEnvVars();
954     version (Windows)
955     {
956         const(char)[] ex = FileName.name(global.params.exefile);
957         if (ex == global.params.exefile)
958             ex = FileName.combine(".", ex);
959         else
960             ex = global.params.exefile;
961         // spawnlp returns intptr_t in some systems, not int
962         return spawnv(0, ex.xarraydup.ptr, argv.tdata());
963     }
964     else version (Posix)
965     {
966         pid_t childpid;
967         int status;
968         childpid = fork();
969         if (childpid == 0)
970         {
971             const(char)[] fn = argv[0].toDString();
972             // Make it "./fn" if needed
973             if (!FileName.absolute(fn))
974                 fn = FileName.combine(".", fn);
975             fn.toCStringThen!((fnp) {
976                     execv(fnp.ptr, argv.tdata());
977                     // If execv returns, it failed to execute
978                     perror(fnp.ptr);
979                 });
980             return -1;
981         }
982         waitpid(childpid, &status, 0);
983         if (WIFEXITED(status))
984         {
985             status = WEXITSTATUS(status);
986             //printf("--- errorlevel %d\n", status);
987         }
988         else if (WIFSIGNALED(status))
989         {
990             error(Loc.initial, "program killed by signal %d", WTERMSIG(status));
991             status = 1;
992         }
993         return status;
994     }
995     else
996     {
997         assert(0);
998     }
999 }