source: trunk/emx/src/emxomf/emxomfld.c@ 2812

Last change on this file since 2812 was 2812, checked in by bird, 19 years ago

Exit with 8 instead of -1 on weakld failures. Fixes #123.

  • Property cvs2svn:cvs-rev set to 1.42
  • Property svn:eol-style set to native
  • Property svn:executable set to *
File size: 63.9 KB
Line 
1/* emxomfld.c -- Provide an ld-like interface to the IBM and M$ linkers
2 Copyright (c) 1992-1998 Eberhard Mattes
3 Copyright (c) 2003 InnoTek Systemberatung GmbH
4 Copyright (c) 2003-2004 Knut St. Osmundsen
5
6This file is part of emxomld.
7
8emxomfld is free software; you can redistribute it and/or modify
9it under the terms of the GNU General Public License as published by
10the Free Software Foundation; either version 2, or (at your option)
11any later version.
12
13emxomfld is distributed in the hope that it will be useful,
14but WITHOUT ANY WARRANTY; without even the implied warranty of
15MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
16GNU General Public License for more details.
17
18You should have received a copy of the GNU General Public License
19along with emxomfld; see the file COPYING. If not, write to
20the Free Software Foundation, 59 Temple Place - Suite 330,
21Boston, MA 02111-1307, USA. */
22
23
24#include <stdio.h>
25#include <stdlib.h>
26#include <alloca.h>
27#include <errno.h>
28#include <string.h>
29#include <process.h>
30#include <io.h>
31#include <fcntl.h>
32#include <sys/types.h>
33#include <sys/stat.h>
34#include <sys/utime.h>
35#include <sys/moddef.h>
36#include <getopt.h>
37#include <alloca.h>
38#include <sys/omflib.h>
39#include "defs.h"
40#include "weakld.h"
41
42#define FALSE 0
43#define TRUE 1
44
45/* A member of a linked list of strings such as file names. */
46typedef struct name_list
47{
48 struct name_list *next;
49 unsigned flags;
50 char *name;
51} name_list;
52
53
54/* Whether or not linker tracing is enabled. */
55static int opt_t;
56
57/* Whether or not to include .dll in the shared library searching. */
58static int opt_dll_search;
59
60/* The output file name, specified by the -o option. */
61static const char *output_fname = NULL;
62
63/* The map file name (output), set by the -Zmap option. */
64static const char *map_fname = NULL;
65static int map_flag = FALSE;
66
67/* The module definition file name (input), set if a file matching
68 *.def is given on the command line. */
69static const char *def_fname = NULL;
70
71/* The binary resource file name (input), set if a file matching *.res
72 is given on the command line. */
73static const char *res_fname = NULL;
74
75/* Base address of the excecutable file, specified by the -T
76 option. */
77static const char *base = NULL;
78
79/* List of directories searched for libraries. Each -L option adds a
80 directory to this list. add_libdirs is used to add another entry
81 at the end of the list. */
82static name_list *libdirs = NULL;
83static name_list **add_libdirs = &libdirs;
84
85/* List of object files. Each file given on the command line which
86 does not match *.def, *.lib and *.res is added to this list.
87 add_obj_fnames is used to add another entry at the end of the
88 list. */
89static name_list *obj_fnames = NULL;
90static name_list **add_obj_fnames = &obj_fnames;
91
92/* List of library files. Each file matching *.lib given on the
93 command line is added to this list. The -l option also adds an
94 entry to this list. add_lib_fnames is used to add another entry at
95 the end of the list.
96 The flags member indicates library search method. If set search for
97 static lib, if clear search for shared lib before search for static lib. */
98static name_list *lib_fnames = NULL;
99static name_list **add_lib_fnames = &lib_fnames;
100
101/* List of linker options. Linker options can be specified with the
102 -O option. add_options is used to add another entry at the end of
103 the list. */
104static name_list *options = NULL;
105static name_list **add_options = &options;
106
107/* The command line passed to the linker. */
108static char command_line[260];
109
110/* The current length of the command line. */
111static int line_len;
112
113/* Non-zero if arguments go into the response file instead of
114 command_line. */
115static int response_flag;
116
117/* The name of the response file. */
118static char response_fname[L_tmpnam] = "";
119
120/* The response file. */
121static FILE *response_file = NULL;
122
123/* Force the use of a response file from the next put_arg(). */
124static int force_response_file = FALSE;
125
126/* Weak alias object file. */
127static char weakobj_fname[_MAX_PATH + 1];
128
129/* Weak definition file (modified def_fname). */
130static char weakdef_fname[_MAX_PATH + 1];
131
132/* list of converted libraries and objects which must be removed upon exit. */
133static name_list *conv_list = NULL;
134
135/* Non-zero if debugging information is to be omitted. Set by the -s
136 and -S options. */
137static int strip_symbols = FALSE;
138
139/* Non-zero if emxomfld should create an .exe file and touch the
140 output file. Set by the -Zexe option. */
141static int exe_flag = FALSE;
142
143/* Non-zero when creating a dynamic link library. Set by the -Zdll
144 option. */
145static int dll_flag = FALSE;
146
147/* The stack size, specified by the -Zstack option, in Kbyte. If the
148 -Zstack option is not used, this variable defaults to 1024 to match
149 the defaults of emxbind. */
150static long stack_size = 1024;
151
152/* The name of the linker to use. By default, ilink is used. This
153 can be overridden with the EMXOMFLD_LINKER environment variable. */
154static const char *linker_name = "wlink.exe";
155
156/* The type of linker to use. By default we assume it's VAC365 or later
157 version of ilink. This can be overridden with the EMXOMFLD_TYPE env.
158 var. using any of the values WLINK, VAC365, VAC308 and LINK386. */
159static const char *linker_type = "WLINK";
160
161/* Non-zero if emxomfld should automatically convert a.out objects and
162 archives to the OMF equivalents during linking. */
163static int autoconvert_flag = 1;
164
165
166/* Prototypes. */
167
168static void usage (void) NORETURN2;
169extern void *xmalloc (size_t n);
170extern void *xrealloc (void *ptr, size_t n);
171extern char *xstrdup (const char *s);
172static void add_name_list (name_list ***add, const char *src, unsigned flags);
173static void conv_path (char *name);
174static void put_arg (const char *src, int path, int quotable);
175static void put_args (const name_list *list, int paths);
176static void make_env (void);
177static void cleanup (void);
178static void arg_init (int rsp);
179static void arg_end (void);
180int main (int argc, char *argv[]);
181
182/* To avoid including os2.h... */
183#ifndef _System
184#define _System
185#endif
186extern int _System DosCopy (char *, char *, int);
187
188/* Allocate N bytes of memory. Quit on failure. This function is
189 used like malloc(), but we don't have to check the return value. */
190
191void *xmalloc (size_t n)
192{
193 void *p;
194
195 p = malloc (n);
196 if (p == NULL && n)
197 {
198 fprintf (stderr, "emxomfld: out of memory\n");
199 exit (2);
200 }
201 return p;
202}
203
204
205/* Change the allocation of PTR to N bytes. Quit on failure. This
206 function is used like realloc(), but we don't have to check the
207 return value. */
208
209void *xrealloc (void *ptr, size_t n)
210{
211 void *p;
212
213 p = realloc (ptr, n);
214 if (p == NULL && n)
215 {
216 fprintf (stderr, "emxomfld: out of memory\n");
217 exit (2);
218 }
219 return p;
220}
221
222
223
224/* Create a duplicate of the string S on the heap. Quit on failure.
225 This function is used like strdup(), but we don't have to check the
226 return value. */
227
228char *xstrdup (const char *s)
229{
230 char *p;
231 int cch = strlen (s) + 1;
232
233 p = xmalloc (cch);
234 memcpy (p, s, cch);
235 return p;
236}
237
238
239/* Add the name SRC to a list. ADD is a pointer to the pointer of the
240 end of the list. We duplicate the string before adding it to the
241 list. */
242
243static void add_name_list (name_list ***add, const char *src, unsigned flags)
244{
245 name_list *node;
246
247 node = xmalloc (sizeof (name_list));
248 node->next = NULL;
249 node->name = xstrdup (src);
250 node->flags = flags;
251 *(*add) = node;
252 (*add) = &node->next;
253}
254
255/* Opens a response file. */
256
257static void open_response_file(void)
258{
259 int fd;
260
261 if (response_file)
262 return;
263
264 /* Complain if we are not allowed to use a response
265 file. */
266
267 if (!response_flag)
268 {
269 fprintf (stderr, "emxomfld: command line too long\n");
270 exit (2);
271 }
272
273 /* Choose a unique file name and create the response
274 file. */
275
276 strcpy (response_fname, "ldXXXXXX");
277 fd = mkstemp (response_fname);
278 if (fd < 0)
279 {
280 perror ("emxomfld");
281 exit (2);
282 }
283 close(fd);
284 response_file = fopen (response_fname, "wt");
285 if (response_file == NULL)
286 {
287 perror ("emxomfld");
288 exit (2);
289 }
290
291 /* Add the name of the response file to the command
292 line. */
293
294 command_line[line_len++] = ' ';
295 command_line[line_len++] = '@';
296 strcpy (command_line+line_len, response_fname);
297 if (!stricmp (linker_type, "WLINK"))
298 strcat (command_line, ".");
299
300 if (force_response_file)
301 force_response_file = FALSE;
302}
303
304/* Replace forward slashes `/' in NAME with backslashes `\'. The linkers
305 requires backslashes in path names. */
306
307static void conv_path (char *name)
308{
309 char *p;
310
311 for (p = name; *p != 0; ++p)
312 if (*p == '/')
313 *p = '\\';
314}
315
316
317/* Add the argument SRC to the command line or to the response file.
318 If PATH is non-zero, SRC is a path name and slashes are to be
319 replaced by backslashes. If the command line gets too long, a
320 response file is created.
321 If quotable is non-zero SRC will be quoted. This is required for
322 supporting files names which includes '+' and spaces. */
323
324static void put_arg (const char *src, int path, int quotable)
325{
326 int len, max_len;
327 char *tmp;
328
329 if (src != NULL)
330 {
331
332 /* Instead of a comma, we write a newline to the response
333 file. */
334
335 if (response_file != NULL && strcmp (src, ",") == 0)
336 {
337 fputc ('\n', response_file);
338 line_len = 0;
339 return;
340 }
341
342 /* Make a local copy of SRC to be able to modify it. Then,
343 translate forward slashes to backslashes if PATH is
344 non-zero. */
345
346 len = strlen (src);
347 tmp = alloca (len + (quotable ? 3 : 1));
348 if (path)
349 {
350 /* needs quoting? */
351 if (quotable)
352 {
353 *tmp = '"';
354 strcpy (tmp+1, src);
355 tmp[++len] = '"';
356 tmp[++len] = '\0';
357 }
358 else
359 strcpy (tmp, src);
360 conv_path (tmp);
361 }
362 else
363 strcpy (tmp, src);
364
365
366 /* Check if we've reached the maximum line length. If the
367 maximum command line length is exceeded, create a response
368 file and write the remaining arguments to that file instead
369 of putting them on the command line. */
370
371 max_len = (response_file == NULL ? 110 : 52);
372 if ( line_len + len + 1 > max_len
373 || (force_response_file && !response_file))
374 {
375
376 /* If SRC is a single comma or a single semicolon, copy it
377 to the output, ignoring the maximum line length. This is
378 to meet the IBM/M$ linker command syntax. The maximum line
379 length allows for enough commas and semicolons added this
380 way. */
381
382 if ((*tmp == ',' || *tmp == ';') && tmp[1] == 0)
383 {
384 if (response_file == NULL)
385 {
386 command_line[line_len+0] = *tmp;
387 command_line[line_len+1] = 0;
388 }
389 else
390 fputc (*tmp, response_file);
391 ++line_len;
392 return;
393 }
394
395 /* If a response file has not yet been opened, open it. */
396
397 if (response_file == NULL)
398 open_response_file();
399 else if (line_len != 0)
400 {
401
402 /* Start a new line in the response file. */
403
404 fputs (" +\n", response_file);
405 }
406 line_len = 0;
407 }
408
409 /* Separate command line arguments by spaces (unless the
410 argument to be added starts with a delimiter. */
411
412 if (line_len != 0 && *src != ',' && *src != ';')
413 {
414 if (response_file == NULL)
415 command_line[line_len++] = ' ';
416 else
417 fputc (' ', response_file);
418 }
419
420 /* Finally write the argument to the command line or to the
421 response file and adjust the current line length. */
422
423 if (response_file == NULL)
424 strcpy (command_line + line_len, tmp);
425 else
426 fputs (tmp, response_file);
427 line_len += len;
428 }
429}
430
431
432/* Put a list of arguments onto the command line or into the response
433 file. If PATHS is non-zero, the arguments are path names and
434 slashes are to be replaced by backslashes. */
435
436static void put_args (const name_list *list, int paths)
437{
438 while (list != NULL)
439 {
440 put_arg (list->name, paths, paths);
441 list = list->next;
442 }
443}
444
445
446/* Build the environment for the IBM/M$ Linkers: define the LIB
447 environment variable. */
448
449static void make_env (void)
450{
451 static char tmp[4096];
452 char *p;
453 int len;
454 const name_list *list;
455
456 /* Create a string for putenv(). */
457
458 strcpy (tmp, "LIB=");
459 len = strlen (tmp);
460
461 /* Add the library directories to LIB, using `;' as separator. */
462
463 for (list = libdirs; list != NULL; list = list->next)
464 {
465 if (list != libdirs && tmp[len-1] != ';')
466 tmp[len++] = ';';
467 strcpy (tmp+len, list->name);
468 conv_path (tmp+len);
469 len += strlen (list->name);
470 }
471
472 /* Append to the end the previous definition of LIB. */
473
474 p = getenv ("LIB");
475 if (p != NULL)
476 {
477 if (tmp[len-1] != ';')
478 tmp[len++] = ';';
479 strcpy (tmp+len, p);
480 }
481
482
483 /* Put the new value of LIB into the environment. */
484
485 putenv (tmp);
486
487 if (opt_t)
488 fprintf(stderr, "*** %s\n", tmp);
489}
490
491/**
492 * Checks if the stream phFile is an OMF library.
493 *
494 * @returns 1 if OMF library.
495 * @returns 0 if not OMF library.
496 * @param phFile Filestream to check.
497 */
498static int check_omf_library(FILE *phFile)
499{
500#pragma pack(1)
501 struct
502 {
503 byte rec_type;
504 word rec_len;
505 dword dict_offset;
506 word dict_blocks;
507 byte flags;
508 } libhdr;
509#pragma pack()
510
511 if ( fread(&libhdr, 1, sizeof(libhdr), phFile) == sizeof (libhdr)
512 && !fseek(phFile, 0, SEEK_SET)
513 && libhdr.rec_type == LIBHDR
514 && libhdr.flags <= 1 /* ASSUME only first bit is used... */
515 )
516 {
517 int page_size = libhdr.rec_len + 3;
518 if (page_size >= 16
519 && page_size <= 32768
520 && !(page_size & (page_size - 1)) != 0)
521 return 1;
522 }
523 return 0;
524}
525
526
527/**
528 * Checks if the stream phFile is an OMF object or library.
529 *
530 * @returns 1 if OMF.
531 * @returns 0 if not OMF.
532 * @param phFile Filestream to check.
533 */
534static int check_omf(FILE *phFile)
535{
536#pragma pack(1)
537 struct
538 {
539 byte rec_type;
540 word rec_len;
541 } omfhdr;
542#pragma pack()
543 if ( fread(&omfhdr, 1, sizeof(omfhdr), phFile) == sizeof (omfhdr)
544 && omfhdr.rec_type == THEADR
545 && omfhdr.rec_len >= sizeof(omfhdr)
546 && !fseek(phFile, 0, SEEK_SET)
547 )
548 return 1;
549
550 return !fseek(phFile, 0, SEEK_SET)
551 && check_omf_library(phFile);
552}
553
554
555/**
556 * Checks if the stream phFile is an LX DLL.
557 *
558 * @returns 1 if LX DLL.
559 * @returns 0 if not LX DLL.
560 * @param phFile File stream to check.
561 */
562static int check_lx_dll(FILE *phFile)
563{
564 unsigned long ul;
565 char achMagic[2];
566
567 if ( fseek(phFile, 0, SEEK_SET)
568 || fread(&achMagic, 1, 2, phFile) != 2)
569 goto thats_not_it;
570
571 if (!memcmp(achMagic, "MZ", 2))
572 {
573 if ( fseek(phFile, 0x3c, SEEK_SET)
574 || fread(&ul, 1, 4, phFile) != 4 /* offset of the 'new' header */
575 || ul < 0x40
576 || ul >= 0x10000000 /* 512MB stubs sure */
577 || fseek(phFile, ul, SEEK_SET)
578 || fread(&achMagic, 1, 2, phFile) != 2)
579 goto thats_not_it;
580 }
581
582 if ( memcmp(achMagic, "LX", 2)
583 || fseek(phFile, 14, SEEK_CUR)
584 || fread(&ul, 1, 4, phFile) != 4) /*e32_mflags*/
585 goto thats_not_it;
586
587#define E32MODDLL 0x08000L
588#define E32MODPROTDLL 0x18000L
589#define E32MODMASK 0x38000L
590 if ( (ul & E32MODMASK) != E32MODDLL
591 && (ul & E32MODMASK) != E32MODPROTDLL)
592 goto thats_not_it;
593
594 /* it's a LX DLL! */
595 fseek(phFile, 0, SEEK_SET);
596 return 1;
597
598
599thats_not_it:
600 fseek(phFile, 0, SEEK_SET);
601 return 0;
602}
603
604
605/**
606 * Generates an unique temporary file.
607 *
608 * @returns 0 on success.
609 * @returns -1 on failure.
610 * @param pszFile Where to put the filename.
611 * @param pszPrefix Prefix.
612 * @param pszSuffix Suffix.
613 * @param pszLooklike Filename which name is to be incorporated into the temp filename.
614 * @remark The code is nicked from the weak linker.
615 */
616static int make_tempfile(char *pszFile, const char *pszPrefix, const char *pszSuffix, const char *pszLooklike)
617{
618 struct stat s;
619 unsigned c = 0;
620 char szLooklike[32];
621 pid_t pid = getpid();
622 const char * pszTmp = getenv("TMP");
623 if (!pszTmp) pszTmp = getenv("TMPDIR");
624 if (!pszTmp) pszTmp = getenv("TEMP");
625 if (!pszTmp) pszTmp = ".";
626 if (pszLooklike)
627 {
628 int cch;
629 char *psz = (char*)pszLooklike; /* we're nice fellows. */
630 while ((psz = strpbrk(psz, ":/\\")) != NULL)
631 pszLooklike = ++psz;
632 cch = strlen(pszLooklike);
633 if (cch + 3 > sizeof(szLooklike))
634 cch = sizeof(szLooklike) - 3;
635 szLooklike[0] = '_';
636 memcpy(&szLooklike[1], pszLooklike, cch);
637 szLooklike[cch + 1] = '_';
638 szLooklike[cch + 2] = '\0';
639 pszLooklike = psz = &szLooklike[0];
640 while ((psz = strpbrk(psz, ".@%^&#()")) != NULL)
641 *psz++ = '_';
642 }
643 else
644 pszLooklike = "";
645
646 do
647 {
648 struct timeval tv = {0,0};
649 if (c++ >= 200)
650 return -1;
651 gettimeofday(&tv, NULL);
652 sprintf(pszFile, "%s\\%s%s%x%lx%d%lx%s", pszTmp, pszPrefix, pszLooklike, pid, tv.tv_sec, c, tv.tv_usec, pszSuffix);
653 } while (!stat(pszFile, &s));
654
655 return 0;
656}
657
658
659/**
660 * Converts the file indicated by phFile & pszFilename to omf closing
661 * phFile and updating pszFilename with the new (temporary filename).
662 *
663 * @returns Pointer to an filestream for the converted file and pszFilename
664 * containing the name of the converted file.
665 * @returns exit the program
666 * @param phFile Filestream of the file to convert. (close this)
667 * @param pszFilename Name of the file to convert on entry.
668 * Name of the converted file on return.
669 */
670static FILE *aout_to_omf(FILE *pf, char *pszFilename, int fLibrary)
671{
672 int rc;
673 char * pszNewFile;
674 name_list *pName;
675
676 fclose(pf); /* don't need this! */
677
678 if (opt_t)
679 fprintf(stderr, "emxomfld: info: converting %s %s to OMF.\n",
680 fLibrary ? "lib" : "obj", pszFilename);
681
682 /*
683 * Make temporary file.
684 */
685 pName = xmalloc(sizeof(name_list));
686 pName->name = pszNewFile = xmalloc(_MAX_PATH);
687 if (make_tempfile(pszNewFile, "ldconv", fLibrary ? ".lib" : ".obj", pszFilename))
688 {
689 free(pszNewFile);
690 return NULL;
691 }
692
693 /*
694 * Do the conversion.
695 */
696 rc = spawnlp(P_WAIT, "emxomf.exe", "emxomf.exe", "-o", pszNewFile, pszFilename, NULL);
697 if (!rc)
698 {
699 /* open the file */
700 pf = fopen(pszNewFile, "rb");
701 if (pf)
702 {
703 /* add to auto delete list for removal on exit(). */
704 pName->next = conv_list;
705 conv_list = pName;
706
707 strcpy(pszFilename, pszNewFile);
708
709 if (opt_t)
710 fprintf(stderr, "emxomfld: info: convert result '%s'.\n",
711 pszFilename);
712 return pf;
713 }
714 remove(pszNewFile);
715 }
716 free(pszNewFile);
717 free(pName);
718
719 fprintf(stderr, "emxomfld: a.out to omf conversion failed for '%s'.\n",
720 pszFilename);
721 exit(1);
722 return NULL;
723}
724
725
726/**
727 * Converts the file indicated by phFile & pszFilename to omf closing
728 * phFile and updating pszFilename with the new (temporary filename).
729 *
730 * @returns Pointer to an filestream for the converted file and pszFilename
731 * containing the name of the converted file.
732 * @returns exit the program
733 * @param phFile Filestream of the file to convert. (close this)
734 * @param pszFilename Name of the file to convert on entry.
735 * Name of the converted file on return.
736 */
737static FILE *lx_to_omf(FILE *pf, char *pszFilename)
738{
739 int rc;
740 char * pszNewFile;
741 name_list *pName;
742
743 fclose(pf); /* don't need this! */
744
745 if (opt_t)
746 fprintf(stderr, "emxomfld: info: converting %s %s to an OMF import lib.\n",
747 "lib", pszFilename);
748
749 /*
750 * Make temporary file.
751 */
752 pName = xmalloc(sizeof(name_list));
753 pName->name = pszNewFile = xmalloc(_MAX_PATH);
754 if (make_tempfile(pszNewFile, "ldconv", ".lib", pszFilename))
755 {
756 free(pszNewFile);
757 return NULL;
758 }
759
760 /*
761 * Do the conversion.
762 */
763 rc = spawnlp(P_WAIT, "emximp.exe", "emximp.exe", "-o", pszNewFile, pszFilename, NULL);
764 if (!rc)
765 {
766 /* open the file */
767 pf = fopen(pszNewFile, "rb");
768 if (pf)
769 {
770 /* add to auto delete list for removal on exit(). */
771 pName->next = conv_list;
772 conv_list = pName;
773
774 strcpy(pszFilename, pszNewFile);
775
776 if (opt_t)
777 fprintf(stderr, "emxomfld: info: convert result '%s'.\n",
778 pszFilename);
779 return pf;
780 }
781 remove(pszNewFile);
782 }
783 free(pszNewFile);
784 free(pName);
785
786 fprintf(stderr, "emxomfld: lx dll to omf conversion failed for '%s'.\n",
787 pszFilename);
788 exit(2);
789 return NULL;
790}
791
792
793/**
794 * Finds the full path of a OMF object file and opens the file.
795 *
796 * This function may perform conversion from a.out to omf if that feature
797 * is enabled.
798 *
799 * We choose to be UNIX compatible her, and not search the LIB env.var.
800 * for unqualified objects. Nor will we add any suffixes to the name
801 * if it's witout any extension.
802 *
803 * @returns Pointer to a file stream for the object file to use in the link.
804 * @returns NULL on failure with pszFullname containing a copy of
805 * pszName (or something like that).
806 * @param pszFullname Where to store the name of the file to be used
807 * in the linking (and which stream is returned).
808 * @param pszName Object name given to on the linker commandline.
809 */
810static FILE *find_obj(char *pszFullname, const char *pszName)
811{
812 FILE *phFile;
813 char *psz;
814
815 /*
816 * Make abspath with slashes the desired way and such.
817 */
818 if (!_realrealpath(pszName, pszFullname, _MAX_PATH + 1))
819 {
820 printf("emxomfld: _abspath failed on '%s'!!!\n", pszName);
821 exit(1);
822 }
823
824 psz = pszFullname;
825 while ((psz = strchr(psz, '/')) != NULL)
826 *psz++ = '\\';
827
828 /*
829 * Try open the file.
830 */
831 phFile = fopen(pszFullname, "rb");
832 if (!phFile)
833 return NULL;
834
835 /*
836 * If autoconversion check if such is needed.
837 */
838 if ( autoconvert_flag
839 && !check_omf(phFile))
840 phFile = aout_to_omf(phFile, pszFullname, FALSE);
841
842 return phFile;
843}
844
845
846
847/* Finds the full path of a library file and opens the file.
848 *
849 * This function may perform conversion from a.out to omf if that feature
850 * is enabled.
851 *
852 * The function assumes that LIB has been updated with all the search paths
853 * specified on the commandline.
854 *
855 * Library names with no extension are given extensions after the rules
856 * indicated by the IS_SHARED parameter. If IS_SHARED is set then libraries
857 * with suffixes indicating shared libraries will be looked for before
858 * libraries with suffixes indicated static libraries. The list is as
859 * follows for set IS_SHARED:
860 * 1. _dll.lib
861 * 2. .lib
862 * 3. .dll (optional)
863 * 4. _s.lib
864 *
865 * If IS_SHARED is clear:
866 * 1. _s.lib
867 * 2. .lib
868 *
869 * Library names with no path is searched for in the semicolon separated list
870 * of paths the env.var. LIB contains. For each directory in LIB we'll start
871 * by see if it contains a 'lib' prefixed file, if not found we'll check for
872 * the unprefixed filename. If we're appending suffixes too, we'll loop thru
873 * all the possible suffixes for each directory before advancing to the next,
874 * having the prefixing as the inner most loop.
875 *
876 * @returns Pointer to a file stream for the library file to use in the link.
877 * @returns NULL on failure with pszFullname containing a copy of
878 * pszName (or something like that).
879 * @param pszFullname Where to store the name of the file to be used
880 * in the linking (and which stream is returned).
881 * @param pszName Library name given to on the linker commandline.
882 */
883static FILE *find_lib(char *pszFullname, const char *pszName, int fShared)
884{
885 /* Suffix list for shared linking. */
886 static const char *apszSharedSuff[] = { "_dll.lib", "_dll.a", ".lib", ".a", "_s.lib", "_s.a", NULL };
887 /* Suffix list for shared linking with .dll. */
888 static const char *apszSharedDllSuff[] = { "_dll.lib", "_dll.a", ".lib", ".a", ".dll", "_s.lib", "_s.a", NULL };
889 /* Suffix list for static linking. */
890 static const char *apszStaticSuff[] = { "_s.lib", "_s.a", ".lib", ".a", NULL };
891 /* Suffix list for names with extension. */
892 static const char *apszExtensionSuff[] = { "", NULL };
893 /* Prefix list for names with path. */
894 static const char *apszWithPathPref[] = { "", NULL };
895 /* Prefix list for names with no path. */
896 static const char *apszWithoutPathPref[]= { "lib", "", NULL };
897 int fPath; /* set if the library name have a path. */
898 int fExt; /* set if the library name have an extension. */
899 const char **papszSuffs; /* Pointer to the suffix list. */
900 const char **papszPrefs; /* Pointer to the prefix list. */
901 const char *pszLibPath; /* The path we're searching. */
902 size_t cchCurPath; /* Size of the current path. */
903 size_t cchName = strlen(pszName);
904 const char *psz;
905
906 /*
907 * Check if the file name has a path.
908 * (If it has, we won't check the LIB directories.)
909 * Choose the prefix list accordingly.
910 */
911 fPath = (strpbrk(pszName, ":/\\") != NULL);
912 papszPrefs = fPath ? apszWithPathPref : apszWithoutPathPref;
913
914 /*
915 * Check if the file has a real extension.
916 * Real extension means, .lib, .dll or .a. It also implies something
917 * before the dot.
918 * Choose the suffix list accordingly.
919 */
920 fExt = ( (cchName > 4 && !stricmp(pszName + cchName - 4, ".lib"))
921 || (cchName > 4 && !stricmp(pszName + cchName - 4, ".dll"))
922 || (cchName > 2 && !stricmp(pszName + cchName - 2, ".a")) );
923
924 if (!fExt)
925 {
926 if (fShared)
927 papszSuffs = opt_dll_search ? &apszSharedDllSuff[0] : &apszSharedSuff[0];
928 else
929 papszSuffs = &apszStaticSuff[0];
930 }
931 else
932 papszSuffs = apszExtensionSuff;
933
934 /*
935 * Loop 1: LIB (with a fake .\ as the first iteration)
936 * (Looping on pszLibPath, with preinitiated cchCurPath & pszFullname.)
937 */
938 cchCurPath = 0;
939 if (!fPath)
940 {
941 cchCurPath = 2;
942 memcpy(pszFullname, ".\\", 2);
943 }
944 pszLibPath = getenv("LIB");
945 do
946 {
947 /*
948 * Loop2: Suffixes.
949 */
950 int iSuff;
951 for (iSuff = 0; papszSuffs[iSuff]; iSuff++)
952 {
953 /*
954 * Loop3: Prefixes.
955 */
956 int iPref;
957 for (iPref = 0; papszPrefs[iPref]; iPref++)
958 {
959 FILE *phFile;
960 int cch = strlen(papszPrefs[iPref]);
961
962 /*
963 * Construct name.
964 */
965 memcpy(&pszFullname[cchCurPath], papszPrefs[iPref], cch);
966 cch = cchCurPath + cch;
967 memcpy(&pszFullname[cch], pszName, cchName);
968 cch += cchName;
969 strcpy(&pszFullname[cch], papszSuffs[iSuff]);
970
971 /*
972 * Open and if necessary convert it.
973 */
974 phFile = fopen(pszFullname, "rb");
975 if (phFile)
976 {
977 char *pszTmp;
978 if (autoconvert_flag)
979 {
980 if (check_lx_dll(phFile))
981 phFile = lx_to_omf(phFile, pszFullname);
982 else if (!check_omf(phFile))
983 phFile = aout_to_omf(phFile, pszFullname, TRUE);
984 }
985
986 /* Get the real native path. */
987 pszTmp = _realrealpath(pszFullname, NULL, 0);
988 if (pszTmp)
989 {
990 strcpy(pszFullname, pszTmp);
991 free(pszTmp);
992 }
993
994 /* Replace forward slashes with backslashes (link386). */
995 while ((pszFullname = strchr(pszFullname, '/')) != NULL)
996 *pszFullname++ = '\\';
997 return phFile;
998 }
999 } /* next prefix */
1000 } /* next suffix */
1001
1002 /*
1003 * If a path was specified or no LIB we're done now.
1004 */
1005 if (fPath || !pszLibPath)
1006 break;
1007
1008 /*
1009 * Next LIB part.
1010 */
1011 for (;;)
1012 {
1013 psz = strchr(pszLibPath, ';');
1014 if (!psz)
1015 psz = strchr(pszLibPath, '\0');
1016 cchCurPath = psz - pszLibPath;
1017 if (cchCurPath)
1018 {
1019 memcpy(pszFullname, pszLibPath, cchCurPath);
1020 pszLibPath = psz + (*psz == ';');
1021 /* Append last slash if it is not there */
1022 if ( pszFullname[cchCurPath - 1] != '/'
1023 && pszFullname[cchCurPath - 1] != '\\')
1024 pszFullname[cchCurPath++] = '\\';
1025 break;
1026 }
1027 if (!*psz)
1028 break;
1029 pszLibPath = psz + 1;
1030 }
1031 } while (cchCurPath);
1032
1033 /* failure */
1034 return NULL;
1035}
1036
1037
1038/* Weak prelinking for Method 2 Weak support. */
1039
1040static void weak_prelink ()
1041{
1042 int rc = 0;
1043 name_list * pOpt;
1044 PWLD pwld;
1045 unsigned fFlags = 0;
1046
1047 /* look for ilinker options. */
1048 if (opt_t)
1049 fFlags |= WLDC_VERBOSE;
1050 if (!stricmp(linker_type, "LINK386"))
1051 fFlags |= WLDC_LINKER_LINK386;
1052 else if (!stricmp(linker_type, "WLINK"))
1053 fFlags |= WLDC_LINKER_WLINK;
1054
1055 for (pOpt = options; pOpt; pOpt = pOpt->next)
1056 if ( !strnicmp(pOpt->name, "/NOE", 4)
1057 || !strnicmp(pOpt->name, "-NOE", 4))
1058 fFlags |= WLDC_NO_EXTENDED_DICTIONARY_SEARCH;
1059 else
1060 if ( !strnicmp(pOpt->name, "/INC", 4)
1061 || !strnicmp(pOpt->name, "-INC", 4))
1062 fFlags = fFlags; /* Ignore for now. */
1063 else
1064 if ( !strnicmp(pOpt->name, "/IG", 3)
1065 || !strnicmp(pOpt->name, "-IG", 3))
1066 fFlags |= WLDC_CASE_INSENSITIVE;
1067 else
1068 if ( !strnicmp(pOpt->name, "/I", 2)
1069 || !strnicmp(pOpt->name, "-I", 2))
1070 fFlags = fFlags; /* Ignore - require opt_t. */
1071 else
1072 if ( !strnicmp(pOpt->name, "/NOIN", 5)
1073 || !strnicmp(pOpt->name, "-NOIN", 5))
1074 fFlags &= ~WLDC_VERBOSE;
1075 else
1076 if ( !strnicmp(pOpt->name, "/NOI", 4)
1077 || !strnicmp(pOpt->name, "/NOI", 4))
1078 fFlags &= ~WLDC_CASE_INSENSITIVE;
1079
1080 /* create the linker and to the linking. */
1081 if (opt_t)
1082 fprintf(stderr, "*** Invoking weak prelinker with flags %x.\n", fFlags);
1083 pwld = WLDCreate (fFlags);
1084 if (pwld)
1085 {
1086 name_list * pcur;
1087 FILE *phfile;
1088 char szname[_MAX_PATH + 1];
1089
1090 /* definition file if any */
1091 if (def_fname && def_fname[0])
1092 {
1093 phfile = fopen (def_fname, "r");
1094 rc = WLDAddDefFile (pwld, phfile, def_fname);
1095 }
1096
1097 /* objects */
1098 for (pcur = obj_fnames; !rc && pcur; pcur = pcur->next)
1099 {
1100 phfile = find_obj (szname, pcur->name);
1101 rc = WLDAddObject (pwld, phfile, szname);
1102 }
1103
1104 /* libraries */
1105 for (pcur = lib_fnames; !rc && pcur; pcur = pcur->next)
1106 {
1107 phfile = find_lib (szname, pcur->name, !pcur->flags);
1108 rc = WLDAddLibrary (pwld, phfile, szname);
1109 free(pcur->name);
1110 pcur->name = xstrdup(szname);
1111 }
1112
1113 /* complete pass 1 */
1114 if (!rc)
1115 {
1116 rc = WLDPass1 (pwld);
1117 /* ignore unresolved externals for now. */
1118 if (rc == 42)
1119 {
1120 rc = 0;
1121 fprintf(stderr, "Ignoring unresolved externals reported from weak prelinker.\n");
1122 }
1123 }
1124
1125 /* generate weak aliases. */
1126 if (!rc)
1127 rc = WLDGenerateWeakAliases (pwld, weakobj_fname, weakdef_fname);
1128 if (!rc && weakobj_fname[0])
1129 {
1130 char *pszTmp = _realrealpath(weakobj_fname, NULL, 0);
1131 if (pszTmp)
1132 {
1133 strcpy(weakobj_fname, pszTmp);
1134 free(pszTmp);
1135 }
1136 add_name_list (&add_obj_fnames, weakobj_fname, 0);
1137 }
1138 if (!rc && weakdef_fname[0])
1139 {
1140 char *pszTmp = _realrealpath(weakdef_fname, NULL, 0);
1141 if (pszTmp)
1142 {
1143 strcpy(weakdef_fname, pszTmp);
1144 free(pszTmp);
1145 }
1146 def_fname = weakdef_fname;
1147 }
1148
1149 /* cleanup the linker */
1150 WLDDestroy (pwld);
1151
1152 /* last words */
1153 if (rc)
1154 {
1155 fprintf (stderr, "emxomfld: weak prelinker failed. (rc=%d)\n", rc);
1156 rc = 8;
1157 }
1158 }
1159 else
1160 {
1161 fprintf (stderr, "emxomfld: failed to create weak prelinker.\n");
1162 rc = 8;
1163 }
1164
1165 /* die on error. */
1166 if (rc)
1167 exit(rc);
1168
1169 /* verbose */
1170 if (opt_t)
1171 fprintf(stderr, "*** Weak prelinker done\n");
1172}
1173
1174
1175/* Start a new set of command line arguments. If RSP is non-zero, we
1176 are allowed to use a response file. */
1177
1178static void arg_init (int rsp)
1179{
1180 if (response_fname[0] != '\0')
1181 {
1182 remove (response_fname);
1183 response_fname[0] = '\0';
1184 }
1185 command_line[0] = '\0';
1186 line_len = 0;
1187 response_flag = rsp;
1188 force_response_file = FALSE;
1189}
1190
1191
1192/* Call this after adding all the command line arguments. If a
1193 response file has been created, add a newline and close it. */
1194
1195static void arg_end (void)
1196{
1197 if (response_file != NULL)
1198 {
1199 fputc ('\n', response_file);
1200 if (fflush (response_file) != 0 || fclose (response_file) != 0)
1201 {
1202 perror ("emxomfld");
1203 exit (2);
1204 }
1205 response_file = NULL;
1206 }
1207}
1208
1209/* Generates a definition file for a dll which doesn't have one. */
1210static void gen_deffile(void)
1211{
1212 char * psz;
1213 name_list *pName;
1214
1215 /*
1216 * Make temporary file.
1217 */
1218 pName = (name_list *)xmalloc(sizeof(*pName));
1219 pName->name = psz = xmalloc(_MAX_PATH);
1220 if (!make_tempfile(psz, "lddef", ".def", NULL))
1221 {
1222 FILE *pFile = fopen(psz, "w");
1223 if (pFile)
1224 {
1225 const char *pszName = _getname(output_fname);
1226 size_t cchName = strlen(pszName);
1227 if (cchName > 4 && !stricmp(pszName + cchName - 4, ".dll"))
1228 cchName -= 4;
1229 fprintf(pFile,
1230 ";; Autogenerated by emxomfld\n"
1231 "LIBRARY %.*s INITINSTANCE TERMINSTANCE\n"
1232 "DATA MULTIPLE\n"
1233 "CODE SHARED\n"
1234 "\n",
1235 cchName, pszName);
1236 fclose(pFile);
1237 def_fname = psz;
1238 if (opt_t)
1239 fprintf(stderr,
1240 "--- Generated def-file %s:\n"
1241 ";; Autogenerated by emxomfld\n"
1242 "LIBRARY %.*s INITINSTANCE TERMINSTANCE\n"
1243 "DATA MULTIPLE NONSHARED\n"
1244 "CODE SINGLE SHARED\n"
1245 "---- End of generated def-file.\n",
1246 psz, cchName, pszName);
1247
1248 /* add to auto delete list for removal on exit(). */
1249 pName->next = conv_list;
1250 conv_list = pName;
1251 return;
1252 }
1253 }
1254 free(psz);
1255 free(pName);
1256}
1257
1258/* converts a def file statement to watcom responsfile lingo. */
1259
1260static def_2_watcom(struct _md *md, const _md_stmt *stmt, _md_token token, void *arg)
1261{
1262 switch (token)
1263 {
1264 case _MD_BASE:
1265 fprintf (response_file, "OPTION OFFSET=%#lx\n", stmt->base.addr);
1266 break;
1267
1268 case _MD_CODE:
1269 break;
1270
1271 case _MD_DATA:
1272 break;
1273
1274 case _MD_DESCRIPTION:
1275 fprintf (response_file, "OPTION DESCRIPTION '%s'\n", stmt->descr.string);
1276 break;
1277
1278 case _MD_EXETYPE:
1279 break;
1280
1281 case _MD_EXPORTS:
1282 fprintf (response_file, "EXPORT '%s'", stmt->export.entryname);
1283 if (stmt->export.flags & _MDEP_ORDINAL)
1284 fprintf (response_file, ".%d", stmt->export.ordinal);
1285 if (stmt->export.internalname[0])
1286 fprintf (response_file, "='%s'", stmt->export.internalname);
1287 if (stmt->export.flags & _MDEP_RESIDENTNAME)
1288 fprintf (response_file, " RESIDENT", stmt->export.internalname);
1289 /** @todo _MDEP_NONAME */
1290 fprintf (response_file, "\n");
1291
1292 /* reference the internal name. */
1293 if (stmt->export.internalname[0])
1294 fprintf (response_file, "REFERENCE '%s'\n", stmt->export.internalname);
1295 break;
1296
1297 case _MD_HEAPSIZE:
1298 fprintf (response_file, "OPTION HEAPSIZE=%#lx\n", stmt->heapsize.size);
1299 break;
1300
1301 case _MD_IMPORTS:
1302 fprintf (response_file, "IMPORT '%s' '%s'", stmt->import.entryname,
1303 stmt->import.modulename);
1304 if (stmt->import.flags & _MDEP_ORDINAL)
1305 fprintf (response_file, ".%d", stmt->import.ordinal);
1306 else if (stmt->import.internalname[0])
1307 fprintf (response_file, ".'%s'", stmt->import.internalname);
1308 fprintf (response_file, "\n");
1309 break;
1310
1311 case _MD_LIBRARY:
1312 if (stmt->library.name[0])
1313 fprintf (response_file, "OPTION MODNAME='%s'\n", stmt->library.name);
1314 break;
1315
1316 case _MD_NAME:
1317 if (stmt->name.name[0])
1318 fprintf (response_file, "OPTION MODNAME='%s'\n", stmt->name.name);
1319 break;
1320
1321 case _MD_OLD:
1322 fprintf (response_file, "OPTION OLDLIBRARY='%s'\n", stmt->old.name);
1323 break;
1324
1325 case _MD_PROTMODE:
1326 fprintf (response_file, "OPTION PROTMODE\n");
1327 break;
1328
1329 case _MD_REALMODE:
1330 fprintf (response_file, "OPTION PROTMODE\n");
1331 break;
1332
1333 case _MD_SEGMENTS:
1334 fprintf (stderr, "emxomfld: ignoring SEGMENTS directive in .def-file\n");
1335 break;
1336
1337 case _MD_STACKSIZE:
1338 fprintf (response_file, "OPTION STACK=%#ld\n", stmt->stacksize.size);
1339 break;
1340
1341 case _MD_STUB:
1342 fprintf (response_file, "OPTION STUB='%s'\n", stmt->stub.name);
1343 break;
1344
1345 case _MD_VIRTUAL:
1346 case _MD_PHYSICAL:
1347 break;
1348
1349 case _MD_parseerror:
1350 fprintf (stderr, "emxomfld: %s (line %ld of %s)",
1351 _md_errmsg (stmt->error.code), _md_get_linenumber (md), def_fname);
1352 exit (2);
1353 break;
1354
1355 default:
1356 abort ();
1357 }
1358 return 0;
1359}
1360
1361/* -t output. We dump the commandline and responsefile. */
1362static void show_spawn(const char *pszwhat)
1363{
1364 if (!opt_t)
1365 return;
1366 fprintf(stderr, "*** Invoking %s\n %s\n", pszwhat, command_line);
1367 if (response_fname[0])
1368 { /* display the responsfile content. */
1369 char sz[4096];
1370 FILE *phfile = fopen(response_fname, "r");
1371 fprintf(stderr, "--- Response file %s:\n", response_fname);
1372 sz[0] = '\0';
1373 while (fgets(sz, sizeof(sz), phfile))
1374 fprintf(stderr, "%s", sz);
1375 fclose(phfile);
1376 if (sz[strlen(sz) - 1] != '\n')
1377 fprintf(stderr, "\n");
1378 fprintf(stderr, "--- End of Response File\n");
1379 }
1380}
1381
1382
1383/* Execute commandline and returns the result.
1384 pszwhat is used for opt_t trace information. */
1385
1386static int emxomfld_spawn(char *pszcmd, const char *pszwhat)
1387{
1388 int argi;
1389 char ** argv;
1390 char * psz;
1391 int rc;
1392
1393 if (opt_t)
1394 show_spawn(pszwhat);
1395
1396 /* construct spawnvp() argument array */
1397 argi = 0;
1398 argv = NULL;
1399 psz = pszcmd;
1400 while (psz && *psz)
1401 {
1402 char *psz2 = psz;
1403
1404 /* skip blanks. */
1405 while (*psz2 == '\t' || *psz2 == ' ')
1406 psz2++;
1407
1408 /* find end of argument taking in account in arg quoting. */
1409 while (*psz2 && *psz2 != '\t' && *psz2 != ' ')
1410 {
1411 if (*psz2 == '"' || *psz2 == '\'')
1412 {
1413 char chQuote = *psz2++;
1414 while (*psz2 && *psz2 != chQuote)
1415 psz2++;
1416 }
1417 psz2++;
1418 }
1419
1420 /* terminate and set psz2 to point to next */
1421 if (*psz2)
1422 *psz2++ = '\0';
1423
1424 /* add argument to argument vector. */
1425 if (!(argi % 32))
1426 argv = xrealloc(argv, sizeof(argv[0]) * (argi + 32 + 1));
1427 argv[argi++] = psz;
1428
1429 /* next */
1430 psz = psz2;
1431 }
1432 argv[argi] = NULL;
1433
1434 /* Spawn process. */
1435 rc = spawnvp(P_WAIT, argv[0], argv);
1436 if (opt_t)
1437 fprintf(stderr, "*** Return from %s is %d\n", pszwhat, rc);
1438
1439 free(argv);
1440 return rc;
1441}
1442
1443
1444/* Cleanup by closing (if open) and deleting (if pressent) the
1445 response file. This function is used with atexit(). */
1446
1447static void cleanup (void)
1448{
1449 if (response_file != NULL)
1450 {
1451 fclose (response_file);
1452 response_file = NULL;
1453 }
1454 if (opt_t <= 1)
1455 {
1456 if (response_fname[0] != '\0')
1457 {
1458 remove (response_fname);
1459 response_fname[0] = '\0';
1460 }
1461 if (weakobj_fname[0] != '\0')
1462 {
1463 remove (weakobj_fname);
1464 weakobj_fname[0] = '\0';
1465 }
1466 if (weakdef_fname[0] != '\0')
1467 {
1468 remove (weakdef_fname);
1469 weakdef_fname[0] = '\0';
1470 }
1471 for (; conv_list; conv_list = conv_list->next)
1472 remove (conv_list->name);
1473 }
1474}
1475
1476/* Tell the user how to run this program. */
1477
1478static void usage (void)
1479{
1480 fputs ("emxomfld " VERSION INNOTEK_VERSION "\n"
1481 "Copyright (c) 1992-1996 by Eberhard Mattes\n"
1482 "Copyright (c) 2003 by InnoTek Systemberatung GmbH\n"
1483 "Copyright (c) 2003-2006 by Knut St. Osmundsen\n"
1484 "\n", stderr);
1485 fputs ("Usage: emxomfld -o <file> [-l <lib>] [-L <libdir>] [-T <base>] [-igtsS]\n"
1486 " [-Zexe] [-Zdll] [-Zstack <size>] [-Zmap[=<map_file>]]\n"
1487 " [-Z[no-]autoconv] [-Zdll-search] [-O <option>] [-static]\n"
1488 " [-non_shared] [-Bstatic] [-dn] [call_shared] [-Bshared]\n"
1489 " [-dy] <file>...\n"
1490 "\n", stderr);
1491 fputs ("Options:\n"
1492 " -Zno-autoconv / -Zautoconv:\n"
1493 " Turns off/on the automatic conversion of a.out libs and objs.\n"
1494 " default: -Zautoconv\n"
1495 " -Bstatic, -non_shared, -dn, -static:\n"
1496 " Link with static libraries.\n"
1497 " The search order is then changed to: lib<name>_s.lib, <name>_s.lib,\n"
1498 " lib<name>.lib, <name>.lib\n", stderr);
1499 fputs (" -Bshared, -call_shared, -dy:\n"
1500 " Link with shared libraries. This is default.\n"
1501 " The search order is then changed to: lib<name>_dll.lib, <name>_dll.lib,\n"
1502 " lib<name>.lib, <name>.lib, <name>.dll, lib<name>_s.lib, <name>_s.lib.\n"
1503 " -Zdll-search:\n"
1504 " Enables dlls as valid libraries from shared linking. (default disabled)\n"
1505 "\n", stderr);
1506 fputs ("Environment variables:\n"
1507 " EMXOMFLD_TYPE:\n"
1508 " The type of linker we're using. Values: WLINK, VAC365, VAC308, LINK386.\n"
1509 " WLINK wlink.exe from Open Watcom v1.5 or later.\n"
1510 " VAC365 ilink.exe from IBM C and C++ Compilers for OS/2 v3.6 or later.\n"
1511 " VAC308 ilink.exe from Visual Age for C++ v3.08.\n"
1512 " LINK386 link386 form OS/2 install or DDK.\n", stderr);
1513 fputs (" EMXOMFLD_LINKER:\n"
1514 " Name of the linker to use and optionally extra parameters. Spaces in the\n"
1515 " linker name or path is not supported. Quotes are not supported either.\n"
1516 "The default values for these two variables are WLINK and wlink.exe.\n", stderr);
1517 exit (1);
1518}
1519
1520
1521
1522static struct option longopts[] =
1523{
1524#define OPT_LIBS_STATIC 0x1000
1525 {"Bstatic", 0, 0, OPT_LIBS_STATIC},
1526 {"non_shared", 0, 0, OPT_LIBS_STATIC},
1527 {"dn", 0, 0, OPT_LIBS_STATIC},
1528 {"static", 0, 0, OPT_LIBS_STATIC},
1529#define OPT_LIBS_SHARED 0x1001
1530 {"Bshared", 0, 0, OPT_LIBS_SHARED},
1531 {"call_shared", 0, 0, OPT_LIBS_SHARED},
1532 {"dy", 0, 0, OPT_LIBS_SHARED},
1533#define OPT_ZEXE 0x1002
1534 {"Zexe", 0, 0, OPT_ZEXE}, /* Create .exe file, touch `output file' */
1535#define OPT_ZDLL 0x1003
1536 {"Zdll", 0, 0, OPT_ZDLL}, /* Create .dll file, touch `output file' */
1537#define OPT_ZSTACK 0x1004
1538 {"Zstack", 1, 0, OPT_ZSTACK}, /* Set stack size */
1539#define OPT_ZMAP 0x1005
1540 {"Zmap", 2, 0, OPT_ZMAP}, /* Create .map file */
1541 {"Zmap=", 1, 0, OPT_ZMAP},
1542#define OPT_ZAUTOCONV 0x1006
1543 {"Zautoconv",0, 0, OPT_ZAUTOCONV},
1544#define OPT_ZNO_AUTOCONV 0x1007
1545 {"Zno-autoconv",0, 0, OPT_ZNO_AUTOCONV},
1546#define OPT_ZDLL_SEARCH 0x1008
1547 {"Zdll-search",0, 0, OPT_ZDLL_SEARCH},
1548/* {"e", 1, 0, 'e'}, entry point */
1549 {"i", 0, 0, 'i'},
1550 {"o", 1, 0, 'o'},
1551 {"O", 1, 0, 'O'},
1552/* {"u", 1, 0, 'u'}, reference symbol */
1553 {"s", 0, 0, 's'},
1554 {"S", 0, 0, 'S'},
1555 {"t", 0, 0, 't'},
1556 {"T", 1, 0, 'T'},
1557 {"v", 0, 0, 'v'},
1558 {"x", 0, 0, 'x'},
1559 {"X", 0, 0, 'X'},
1560 {NULL, 0, 0, 0}
1561};
1562
1563/* Main function of emxomf. Parse the command line and call the IBM/M$
1564 linker (and optionally RC). */
1565
1566int main (int argc, char *argv[])
1567{
1568 struct stat s;
1569 int c, rc, files;
1570 const char *ext;
1571 char tmp[512], *t;
1572 char execname[512];
1573 name_list *pcur;
1574 int opt_libs_static = 0;
1575 int longind;
1576
1577 /* Get options from response files (@filename) and wildcard (*.o) on the command. */
1578
1579 _response (&argc, &argv);
1580 _wildcard (&argc, &argv);
1581
1582 /* Close and delete the response file on exit. */
1583
1584 atexit (cleanup);
1585
1586 /* Prepare parsing of the command line. */
1587
1588 files = 0;
1589 opterr = FALSE;
1590 /*optmode = GETOPT_KEEP; */
1591 if (argc < 2)
1592 usage ();
1593
1594 /* Parse the command line options and other arguments. */
1595 while ((c = getopt_long_only (argc, argv, "-l:y:L:", longopts, &longind)) != EOF)
1596 {
1597 if (c == 0)
1598 c = longopts[longind].val;
1599 switch (c)
1600 {
1601 case 1: /* Non-option argument */
1602
1603 /* Extract the extension to see what to do with this
1604 argument. */
1605
1606 ext = _getext (optarg);
1607
1608 if (ext == NULL)
1609 {
1610 /* GCC's temporary files don't have an extension. Add a
1611 dot to the end of the name to prevent the linker from
1612 adding `.obj'. */
1613
1614 sprintf (tmp, "%s.", optarg);
1615 add_name_list (&add_obj_fnames, tmp, 0);
1616 }
1617
1618 /* If it's a .def file, use it as module definition file
1619 (input). */
1620
1621 else if (stricmp (ext, ".def") == 0)
1622 {
1623 if (def_fname != NULL)
1624 {
1625 fprintf (stderr,
1626 "emxomfld: multiple module definition files\n");
1627 return 1;
1628 }
1629 def_fname = _realrealpath(optarg, NULL, 0);
1630 if (!def_fname)
1631 def_fname = optarg;
1632 }
1633
1634 /* If it's a .res file, use it as binary resource file
1635 (input). */
1636
1637 else if (stricmp (ext, ".res") == 0)
1638 {
1639 if (res_fname != NULL)
1640 {
1641 fprintf (stderr,
1642 "emxomfld: multiple binary resource files\n");
1643 return 1;
1644 }
1645 res_fname = _realrealpath(optarg, NULL, 0);
1646 if (!def_fname)
1647 res_fname = optarg;
1648 }
1649
1650 /* If it's a .lib file, use it as library file. We also
1651 accept .a files for those who use OMF files disguised as
1652 a.out files (to simplify their make files). */
1653
1654 else if (stricmp (ext, ".lib") == 0 || stricmp (ext, ".a") == 0 || stricmp (ext, ".dll") == 0)
1655 add_name_list (&add_lib_fnames, optarg, opt_libs_static);
1656
1657 /* Otherwise, assume it's an object file. */
1658
1659 else
1660 add_name_list (&add_obj_fnames, optarg, 0);
1661 ++files;
1662 break;
1663
1664 case 't':
1665 case 'i': /* Trace the linking process, sending /INFO to the IBM/M$ linker. */
1666 opt_t++;
1667 break;
1668
1669 case 'l': /* Add library */
1670 add_name_list (&add_lib_fnames, optarg, opt_libs_static);
1671 break;
1672
1673 case 'o': /* Set output file name */
1674 output_fname = optarg;
1675 break;
1676
1677 case 'L': /* Add library directory */
1678 add_name_list (&add_libdirs, optarg, 0);
1679 break;
1680
1681 case 'T': /* Set base address */
1682 base = optarg;
1683 break;
1684
1685 case 's': /* Strip all symbols */
1686 case 'S': /* Strip debugging symbols */
1687 strip_symbols = TRUE;
1688 break;
1689
1690 case 'x': /* Discard all local symbols */
1691 case 'X': /* Discard local symbols starting with L */
1692 break;
1693
1694 case 'v': /* For compatibility */
1695 break;
1696
1697 case 'O': /* Specify Linker option */
1698 add_name_list (&add_options, optarg, 0);
1699 break;
1700
1701 case OPT_ZDLL:
1702 dll_flag = TRUE;
1703 break;
1704
1705 case OPT_ZEXE:
1706 exe_flag = TRUE;
1707 break;
1708
1709 case OPT_ZMAP:
1710 map_flag = TRUE;
1711 if (optarg)
1712 {
1713 if (map_fname != NULL)
1714 {
1715 fprintf (stderr, "emxomfld: multiple map files files\n");
1716 return 1;
1717 }
1718 map_fname = optarg;
1719 }
1720 break;
1721
1722 case OPT_ZSTACK:
1723 if (!optarg)
1724 return 1;
1725 errno = 0;
1726 stack_size = strtol (optarg, &t, 0);
1727 if (errno != 0 || *t != 0 || t == optarg)
1728 return 1;
1729 break;
1730
1731 case OPT_ZAUTOCONV:
1732 autoconvert_flag = 1;
1733 break;
1734 case OPT_ZNO_AUTOCONV:
1735 autoconvert_flag = 0;
1736 break;
1737
1738 case OPT_ZDLL_SEARCH:
1739 opt_dll_search = 1;
1740 break;
1741
1742 case OPT_LIBS_STATIC:
1743 opt_libs_static = 1;
1744 break;
1745 case OPT_LIBS_SHARED:
1746 opt_libs_static = 0;
1747 break;
1748
1749 case '?':
1750 default:
1751 if (optind > 1)
1752 fprintf (stderr, "emxomfld: invalid option (%s)\n", argv[optind - 1]);
1753 else
1754 usage ();
1755 return 1;
1756 }
1757 }
1758 /* Set default value for output file. */
1759
1760 if (output_fname == NULL)
1761 {
1762 fprintf (stderr,
1763 "emxomfld: no output file, creating $$$.exe or $$$.dll\n");
1764 output_fname = "$$$";
1765 }
1766
1767 /* Check if there are any input files. */
1768
1769 if (files == 0)
1770 {
1771 fprintf (stderr, "emxomfld: no input files\n");
1772 return 1;
1773 }
1774
1775 /* Remove the output file if -Zexe is given. */
1776
1777 if (exe_flag)
1778 remove (output_fname);
1779
1780 /* If neither -Zmap nor -Zmap=file is used, pass "nul" to the linker in
1781 the map file field. If -Zmap is used, construct the name of the
1782 .map file. If -Zmap=file is used, use `file' as the name of the
1783 .map file. */
1784
1785 if (!map_flag)
1786 map_fname = "nul";
1787 else if (map_fname == NULL)
1788 {
1789 int cch = strlen (output_fname) + 1;
1790 t = xmalloc (cch + 4);
1791 memcpy (t, output_fname, cch);
1792 _remext (t);
1793 strcat (t, ".map");
1794 map_fname = t;
1795 }
1796
1797 /* Build the environment for the linker. */
1798
1799 make_env ();
1800
1801 /* EMXOMFLD_TYPE contains VAC365, VAC308 or LINK386 if set. If non of these
1802 we assume VAC365.
1803 EMXOMFLD_LINKER contains the linker name and perhaps extra arguments. If
1804 not set we'll use the default linker, ilink. */
1805
1806 t = getenv ("EMXOMFLD_TYPE");
1807 if ( t
1808 && stricmp(t, "WLINK")
1809 && stricmp(t, "VAC365")
1810 && stricmp(t, "VAC308")
1811 && stricmp(t, "LINK386")
1812 )
1813 fprintf (stderr, "emxomfld: warning: '%s' is an invalid value for EMXOMFLD_TYPE.\n", t);
1814 else if (t)
1815 linker_type = t;
1816
1817 t = getenv ("EMXOMFLD_LINKER");
1818 if (t)
1819 linker_name = t;
1820 if (opt_t)
1821 fprintf(stderr, "*** Linker : %s\n"
1822 "*** Linker type: %s\n", linker_name, linker_type);
1823
1824 /* apply object & library hacks */
1825 for (pcur = obj_fnames, rc = 0; !rc && pcur; pcur = pcur->next)
1826 {
1827 char szname[_MAX_PATH + 1];
1828 FILE *phfile = find_obj (szname, pcur->name);
1829 if (!phfile)
1830 continue;
1831 free (pcur->name);
1832 pcur->name = xstrdup(szname);
1833 fclose(phfile);
1834 }
1835
1836 for (pcur = lib_fnames, rc = 0; !rc && pcur; pcur = pcur->next)
1837 {
1838 char szname[_MAX_PATH + 1];
1839 FILE *phfile = find_lib (szname, pcur->name, !pcur->flags);
1840 if (!phfile)
1841 continue;
1842 free (pcur->name);
1843 pcur->name = xstrdup(szname);
1844 fclose(phfile);
1845 }
1846
1847 /* generate .def-file for dlls. */
1848
1849 if (!def_fname && dll_flag)
1850 gen_deffile();
1851
1852 /* Do the weak prelinking. Important that this is done after make_env(). */
1853
1854 weak_prelink ();
1855
1856 /* Start building the linker command line. We can use a response
1857 file if the command line gets too long. */
1858
1859 arg_init (TRUE);
1860
1861 /* issue commandline */
1862 put_arg (linker_name, TRUE, FALSE);
1863
1864 if (stricmp (linker_type, "WLINK"))
1865 {
1866 /*
1867 For VAC365 and VAC308 the default options are:
1868
1869 /NOFR[EEFORMAT] Use /NOFREEFORMAT to allow a LINK386-compatible
1870 command line syntax, in which different types of file
1871 are grouped and separated by commas.
1872
1873 /DBGPACK If !strip_symbols then we'll add this option, which
1874 will cause type tables to be merged into one global
1875 table and so eliminating a lot of duplicate info.
1876
1877 For VAC365 additional default option is:
1878
1879 /STUB:<emxomfld-path>\os2stub.bin
1880 Causes this MZ stub to be used when linking the
1881 executables instead of the default on for the linker.
1882
1883 For LINK386 the default options are:
1884
1885 /BATCH Run in batch mode (disable prompting, don't
1886 echo response file)
1887
1888 The default options for all linkers are:
1889
1890 /NOLOGO Don't display sign-on banner
1891
1892 /NOEXTDICTIONARY Don't use extended dictionary (redefining
1893 library symbols is quite common)
1894
1895 /NOIGNORECASE Make symbols case-sensitive
1896
1897 /PACKCODE Group neighboring code segments (this is the
1898 default unless the SEGMENTS module definition
1899 statement is used for a segment of class
1900 'CODE'). Not grouping neighboring code
1901 segments would break sets
1902
1903 For non DLLs targets:
1904
1905 /BASE:0x10000 Base the executable an so removing extra fixups.
1906
1907 */
1908
1909 /* the next part depends on the linker type. */
1910 if (!stricmp (linker_type, "LINK386"))
1911 put_arg ("/bat", FALSE, FALSE);
1912 else /* vac3xx: */
1913 {
1914 put_arg ("/nofree", FALSE, FALSE);
1915 if (!strip_symbols)
1916 put_arg ("/db", FALSE, FALSE);
1917 if (map_flag)
1918 put_arg ("/map", FALSE, FALSE);
1919 }
1920 put_arg ("/nol", FALSE, FALSE);
1921 put_arg ("/noe", FALSE, FALSE);
1922 put_arg ("/noi", FALSE, FALSE);
1923 put_arg ("/packc", FALSE, FALSE);
1924
1925
1926 /* VAC365: check if we have os2stub.bin.
1927 We must to this after the above stuff else /nol might end up in the
1928 response file and we'll get the component output. */
1929
1930 if (!stricmp (linker_type, "VAC365"))
1931 {
1932 /* gklayout show that the linker isn't capable of determining a
1933 decent value for this parameter. 32MB makes gklayout link. */
1934 put_arg ("/ocache:0x02000000", FALSE, FALSE);
1935
1936 _execname (&execname[0], sizeof(execname));
1937 strcpy (_getname (&execname[0]), "os2stub.bin");
1938 if (!stat (execname, &s))
1939 {
1940 sprintf (tmp, "/STUB:%s", &execname[0]);
1941 put_arg (tmp, FALSE, FALSE);
1942 }
1943 }
1944
1945 /* Add the /INFORMATION option if the -i or -t option was given. This is
1946 for debugging. */
1947
1948 if (opt_t)
1949 put_arg ("/i", FALSE, FALSE);
1950
1951 /* Add the /DEBUG option if the -s option was not given. Without
1952 this, the linker throws away debugging information. */
1953
1954 if (!strip_symbols)
1955 put_arg ("/de", FALSE, FALSE);
1956
1957 /* Add the /BASE:n option to set the base address. This specifies
1958 the preferred load address of object 1. The base address being
1959 used is 0x10000 unless a DLL is generated or the -T option was
1960 given. -Tno can be used to suppress the /BASE:n option. */
1961
1962 if (base == NULL && !dll_flag)
1963 {
1964 struct _md *md;
1965
1966 if (def_fname != NULL)
1967 {
1968 int token;
1969 md = _md_open (def_fname);
1970 if (md == NULL)
1971 {
1972 fprintf (stderr, "emxomfld: cannot open `%s'\n", def_fname);
1973 exit (2);
1974 }
1975 token = _md_next_token (md);
1976 if (token == _MD_LIBRARY || token == _MD_PHYSICAL || token == _MD_VIRTUAL)
1977 dll_flag = TRUE;
1978 _md_close (md);
1979 }
1980 }
1981 if (base == NULL && !dll_flag)
1982 base = "0x10000";
1983 if (base != NULL && stricmp (base, "no") != 0)
1984 {
1985 sprintf (tmp, "/bas:%s", base);
1986 put_arg (tmp, FALSE, FALSE);
1987 }
1988
1989 /* Add the /STACK:n option if the -Zstack option was given. */
1990
1991 if (!dll_flag)
1992 {
1993 sprintf (tmp, "/st:0x%lx", stack_size * 1024);
1994 put_arg (tmp, FALSE, FALSE);
1995 }
1996
1997 /* Add the linker options specified with -O. */
1998
1999 put_args (options, FALSE);
2000
2001 /* Put the object file names onto the command line. */
2002
2003 force_response_file = TRUE; /* link386 workaround. */
2004 put_args (obj_fnames, TRUE);
2005 put_arg (",", FALSE, FALSE);
2006
2007 /* Put the output file name onto the command line. */
2008
2009 put_arg (output_fname, TRUE, TRUE);
2010 put_arg (",", FALSE, FALSE);
2011
2012 /* Put the map file name onto the command line. */
2013
2014 put_arg (map_fname, TRUE, TRUE);
2015 put_arg (",", FALSE, FALSE);
2016
2017 /* Put the library file names onto the command line. */
2018
2019 put_args (lib_fnames, TRUE);
2020 put_arg (",", FALSE, FALSE);
2021
2022 /* Put the name of the module definition file onto the command line. */
2023
2024 put_arg (def_fname, TRUE, TRUE);
2025 put_arg (";", FALSE, FALSE);
2026
2027 /* Call Linker and abort on failure. */
2028 }
2029 else /* wlink */
2030 {
2031 open_response_file ();
2032
2033 /* figure out what format options we're gonna use */
2034
2035 if (!def_fname && !dll_flag)
2036 fprintf (response_file, "FORMAT OS2 LX PMCompatible\n");
2037 else if (!def_fname && dll_flag)
2038 fprintf (response_file, "FORMAT OS2 LX DLL INITINSTANCE TERMINSTANCE\n");
2039 else
2040 {
2041 int token;
2042 struct _md *pMd = _md_open (def_fname);
2043 if (!pMd)
2044 {
2045 fprintf (stderr, "emxomfld: cannot open `%s'\n", def_fname);
2046 exit (2);
2047 }
2048 token = _md_next_token (pMd);
2049 if (token == _MD_LIBRARY || token == _MD_PHYSICAL || token == _MD_VIRTUAL)
2050 dll_flag = TRUE;
2051 if (dll_flag)
2052 {
2053 int fInitInstance = 1;
2054 int fTermInstance = 1;
2055 for (;;)
2056 {
2057 switch (_md_next_token (pMd))
2058 {
2059 case _MD_INITINSTANCE: fInitInstance = 1; continue;
2060 case _MD_INITGLOBAL: fInitInstance = 0; continue;
2061 case _MD_TERMINSTANCE: fTermInstance = 1; continue;
2062 case _MD_TERMGLOBAL: fTermInstance = 0; continue;
2063 default: break;
2064 }
2065 break;
2066 }
2067 fprintf (response_file, "FORMAT OS2 LX DLL %s %s\n",
2068 fInitInstance ? "INITINSTANCE" : "INITGLOBAL",
2069 fTermInstance ? "TERMINSTANCE" : "TERMGLOBAL");
2070 }
2071 else
2072 switch (_md_next_token (pMd))
2073 {
2074 case _MD_WINDOWAPI:
2075 fprintf (response_file, "FORMAT OS2 LX PM\n");
2076 break;
2077 default:
2078 case _MD_WINDOWCOMPAT:
2079 fprintf (response_file, "FORMAT OS2 LX PMCompatible\n");
2080 break;
2081 case _MD_NOTWINDOWCOMPAT:
2082 fprintf (response_file, "FORMAT OS2 LX FullScreen\n");
2083 break;
2084 }
2085 _md_close (pMd);
2086 }
2087
2088 /* output files */
2089
2090 fprintf (response_file, "NAME '%s'\n", output_fname);
2091
2092 if (map_flag && map_fname)
2093 fprintf (response_file, "OPTION MAP='%s'\n", map_fname);
2094 else if (map_flag)
2095 fprintf (response_file, "OPTION MAP\n", map_fname);
2096
2097 /* standard stuff */
2098
2099 if (!strip_symbols)
2100 fprintf (response_file, "DEBUG HLL\n");
2101 fprintf (response_file, "OPTION QUIET\n");
2102 fprintf (response_file, "OPTION OSNAME='OS/2 EMX'\n");
2103 fprintf (response_file, "OPTION CASEEXACT\n");
2104 if (!dll_flag)
2105 fprintf (response_file, "OPTION STACK=%#lx\n", stack_size * 1024);
2106 if (!dll_flag && !base)
2107 base = "0x10000";
2108 if (base)
2109 fprintf (response_file, "OPTION OFFSET=%s\n", base);
2110
2111 /* the stub */
2112
2113 _execname(&execname[0], sizeof(execname));
2114 strcpy (_getname (&execname[0]), "os2stub.bin");
2115 if (!stat (execname, &s))
2116 fprintf (response_file, "OPTION STUB='%s'\n", execname);
2117
2118 /* Add the /INFORMATION option if the -i or -t option was given. This is
2119 for debugging. */
2120
2121// if (opt_t)
2122// put_arg ("/i", FALSE, FALSE);
2123
2124 /* Add the linker options specified with -O. */
2125
2126 for (pcur = options; pcur; pcur = pcur->next)
2127 fprintf (response_file, "%s\n", pcur->name);
2128
2129 /* Put the object file names onto the command line. */
2130
2131 for (pcur = obj_fnames; pcur; pcur = pcur->next)
2132 fprintf (response_file, "FILE '%s'\n", pcur->name);
2133
2134 /* Put the library file names onto the command line. */
2135
2136 for (pcur = lib_fnames; pcur; pcur = pcur->next)
2137 fprintf (response_file, "LIBRARY '%s'\n", pcur->name);
2138
2139 /* Translate the essentials of the module definition file into wlink lingo. */
2140 if (def_fname)
2141 {
2142 _md_token token;
2143 struct _md *pMd = _md_open (def_fname);
2144 if (!pMd)
2145 {
2146 fprintf (stderr, "emxomfld: cannot open `%s'\n", def_fname);
2147 exit (2);
2148 }
2149 _md_next_token (pMd);
2150 _md_parse (pMd, def_2_watcom, NULL);
2151 _md_close (pMd);
2152 }
2153 }
2154
2155 /* End the arguments and run the linker. */
2156
2157 arg_end ();
2158
2159 rc = emxomfld_spawn (command_line, "Linker");
2160 if (rc == 4 && !strnicmp(linker_type, "VAC3", 4)) /* Ignore iLink warnings. */
2161 rc = 0;
2162 if (rc < 0)
2163 {
2164 perror (linker_name);
2165 exit (2);
2166 }
2167
2168 /* Run RC if Linker completed successfully and a binary resource
2169 file was given on the command line. */
2170
2171 if (rc == 0 && res_fname != NULL)
2172 {
2173 arg_init (TRUE);
2174 put_arg ("rc.exe", TRUE, FALSE);
2175 put_arg ("-n", FALSE, FALSE);
2176 put_arg (res_fname, TRUE, FALSE);
2177 put_arg (output_fname, TRUE, FALSE);
2178 arg_end ();
2179 rc = emxomfld_spawn (command_line, "Resource Linker");
2180 if (rc < 0)
2181 {
2182 perror ("emxomfld: rc");
2183 exit (2);
2184 }
2185 }
2186
2187 /* If both Linker and RC completed successfully and the -Zexe option
2188 was given, touch the output file (without .exe) to keep `make'
2189 happy. */
2190
2191 if (rc == 0 && exe_flag)
2192 {
2193 /* find target and source filenames. */
2194 t = xstrdup (output_fname);
2195 _remext (t);
2196 _execname(&execname[0], sizeof(execname));
2197 strcpy(_getname(&execname[0]), "ldstub.bin");
2198
2199 /* Copy stub into file */
2200 if (opt_t)
2201 fprintf(stderr, "*** copy %s to %s (-Zexe)", execname, t);
2202 DosCopy(&execname[0], t, 4);
2203
2204 /* Now touch it */
2205 if (utime(t, NULL))
2206 {
2207 perror ("emxomfld");
2208 exit (2);
2209 }
2210 free (t);
2211 }
2212
2213 /* Return the return code of Linker or RC. */
2214
2215 return rc;
2216}
Note: See TracBrowser for help on using the repository browser.