Another windres snapshot. Can now read the COFF resources directory,
[external/binutils.git] / binutils / windres.c
1 /* windres.c -- a program to manipulate Windows resources
2    Copyright 1997 Free Software Foundation, Inc.
3    Written by Ian Lance Taylor, Cygnus Support.
4
5    This file is part of GNU Binutils.
6
7    This program is free software; you can redistribute it and/or modify
8    it under the terms of the GNU General Public License as published by
9    the Free Software Foundation; either version 2 of the License, or
10    (at your option) any later version.
11
12    This program is distributed in the hope that it will be useful,
13    but WITHOUT ANY WARRANTY; without even the implied warranty of
14    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
15    GNU General Public License for more details.
16
17    You should have received a copy of the GNU General Public License
18    along with this program; if not, write to the Free Software
19    Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA
20    02111-1307, USA.  */
21
22 /* This program can read and write Windows resources in various
23    formats.  In particular, it can act like the rc resource compiler
24    program, and it can act like the cvtres res to COFF conversion
25    program.
26
27    It is based on information taken from the following sources:
28
29    * Microsoft documentation.
30
31    * The rcl program, written by Gunther Ebert
32      <gunther.ebert@ixos-leipzig.de>.
33
34    * The res2coff program, written by Pedro A. Aranda <paag@tid.es>.
35
36    */
37
38 #include "bfd.h"
39 #include "getopt.h"
40 #include "bucomm.h"
41 #include "libiberty.h"
42 #include "windres.h"
43
44 #include <assert.h>
45 #include <ctype.h>
46
47 /* An enumeration of format types.  */
48
49 enum res_format
50 {
51   /* Unknown format.  */
52   RES_FORMAT_UNKNOWN,
53   /* Textual RC file.  */
54   RES_FORMAT_RC,
55   /* Binary RES file.  */
56   RES_FORMAT_RES,
57   /* COFF file.  */
58   RES_FORMAT_COFF
59 };
60
61 /* A structure used to map between format types and strings.  */
62
63 struct format_map
64 {
65   const char *name;
66   enum res_format format;
67 };
68
69 /* A mapping between names and format types.  */
70
71 static const struct format_map format_names[] =
72 {
73   { "rc", RES_FORMAT_RC },
74   { "res", RES_FORMAT_RES },
75   { "coff", RES_FORMAT_COFF },
76   { NULL, RES_FORMAT_UNKNOWN }
77 };
78
79 /* A mapping from file extensions to format types.  */
80
81 static const struct format_map format_fileexts[] =
82 {
83   { "rc", RES_FORMAT_RC },
84   { "res", RES_FORMAT_RES },
85   { "exe", RES_FORMAT_COFF },
86   { "obj", RES_FORMAT_COFF },
87   { "o", RES_FORMAT_COFF },
88   { NULL, RES_FORMAT_UNKNOWN }
89 };
90
91 /* A list of include directories.  */
92
93 struct include_dir
94 {
95   struct include_dir *next;
96   char *dir;
97 };
98
99 static struct include_dir *include_dirs;
100
101 /* Long options.  */
102
103 /* 150 isn't special; it's just an arbitrary non-ASCII char value.  */
104
105 #define OPTION_DEFINE 150
106 #define OPTION_HELP (OPTION_DEFINE + 1)
107 #define OPTION_INCLUDE_DIR (OPTION_HELP + 1)
108 #define OPTION_LANGUAGE (OPTION_INCLUDE_DIR + 1)
109 #define OPTION_PREPROCESSOR (OPTION_LANGUAGE + 1)
110 #define OPTION_VERSION (OPTION_PREPROCESSOR + 1)
111 #define OPTION_YYDEBUG (OPTION_VERSION + 1)
112
113 static const struct option long_options[] =
114 {
115   {"define", required_argument, 0, OPTION_DEFINE},
116   {"help", no_argument, 0, OPTION_HELP},
117   {"include-dir", required_argument, 0, OPTION_INCLUDE_DIR},
118   {"input-format", required_argument, 0, 'I'},
119   {"language", required_argument, 0, OPTION_LANGUAGE},
120   {"output-format", required_argument, 0, 'O'},
121   {"preprocessor", required_argument, 0, OPTION_PREPROCESSOR},
122   {"target", required_argument, 0, 'F'},
123   {"version", no_argument, 0, OPTION_VERSION},
124   {"yydebug", no_argument, 0, OPTION_YYDEBUG},
125   {0, no_argument, 0, 0}
126 };
127
128 /* Static functions.  */
129
130 static enum res_format format_from_name PARAMS ((const char *));
131 static enum res_format format_from_filename PARAMS ((const char *, int));
132 static void usage PARAMS ((FILE *, int));
133 \f
134 /* Open a file using the include directory search list.  */
135
136 FILE *
137 open_file_search (filename, mode, errmsg, real_filename)
138      const char *filename;
139      const char *mode;
140      const char *errmsg;
141      char **real_filename;
142 {
143   FILE *e;
144   struct include_dir *d;
145
146   e = fopen (filename, mode);
147   if (e != NULL)
148     {
149       *real_filename = xstrdup (filename);
150       return e;
151     }
152
153   if (errno == ENOENT)
154     {
155       for (d = include_dirs; d != NULL; d = d->next)
156         {
157           char *n;
158
159           n = (char *) xmalloc (strlen (d->dir) + strlen (filename) + 2);
160           sprintf (n, "%s/%s", d->dir, filename);
161           e = fopen (n, mode);
162           if (e != NULL)
163             {
164               *real_filename = n;
165               return e;
166             }
167
168           if (errno != ENOENT)
169             break;
170         }
171     }
172
173   fatal ("can't open %s `%s': %s", errmsg, filename, strerror (errno));
174
175   /* Return a value to avoid a compiler warning.  */
176   return NULL;
177 }
178 \f
179 /* Unicode support.  */
180
181 /* Convert an ASCII string to a unicode string.  We just copy it,
182    expanding chars to shorts, rather than doing something intelligent.  */
183
184 void
185 unicode_from_ascii (length, unicode, ascii)
186      unsigned short *length;
187      unsigned short **unicode;
188      const char *ascii;
189 {
190   int len;
191   const char *s;
192   unsigned short *w;
193
194   len = strlen (ascii);
195
196   if (length != NULL)
197     {
198       if (len > 0xffff)
199         fatal ("string too long (%d chars > 0xffff)", len);
200       *length = len;
201     }
202
203   *unicode = (unsigned short *) xmalloc ((len + 1) * sizeof (unsigned short));
204
205   for (s = ascii, w = *unicode; *s != '\0'; s++, w++)
206     *w = *s & 0xff;
207   *w = 0;
208 }
209
210 /* Print the unicode string UNICODE to the file E.  LENGTH is the
211    number of characters to print, or -1 if we should print until the
212    end of the string.  */
213
214 void
215 unicode_print (e, unicode, length)
216      FILE *e;
217      const unsigned short *unicode;
218      int length;
219 {
220   while (1)
221     {
222       unsigned short ch;
223
224       if (length == 0)
225         return;
226       if (length > 0)
227         --length;
228
229       ch = *unicode;
230
231       if (ch == 0)
232         return;
233
234       ++unicode;
235
236       if ((ch & 0x7f) == ch && isprint (ch))
237         putc (ch, e);
238       else if ((ch & 0xff) == ch)
239         fprintf (e, "\\%03o", (unsigned int) ch);
240       else
241         fprintf (e, "\\x%x", (unsigned int) ch);
242     }
243 }
244 \f
245 /* Compare two resource ID's.  We consider name entries to come before
246    numeric entries, because that is how they appear in the COFF .rsrc
247    section.  */
248
249 int
250 res_id_cmp (a, b)
251      struct res_id a;
252      struct res_id b;
253 {
254   if (! a.named)
255     {
256       if (b.named)
257         return 1;
258       if (a.u.id > b.u.id)
259         return 1;
260       else if (a.u.id < b.u.id)
261         return -1;
262       else
263         return 0;
264     }
265   else
266     {
267       unsigned short *as, *ase, *bs, *bse;
268
269       if (! b.named)
270         return -1;
271
272       as = a.u.n.name;
273       ase = as + a.u.n.length;
274       bs = b.u.n.name;
275       bse = bs + b.u.n.length;
276
277       while (as < ase)
278         {
279           int i;
280
281           if (bs >= bse)
282             return 1;
283           i = (int) *as - (int) *bs;
284           if (i != 0)
285             return i;
286           ++as;
287           ++bs;
288         }
289
290       if (bs < bse)
291         return -1;
292
293       return 0;
294     }
295 }
296
297 /* Print a resource ID.  */
298
299 void
300 res_id_print (stream, id, quote)
301      FILE *stream;
302      struct res_id id;
303      int quote;
304 {
305   if (! id.named)
306     fprintf (stream, "%lu", id.u.id);
307   else
308     {
309       if (quote)
310         putc ('"', stream);
311       unicode_print (stream, id.u.n.name, id.u.n.length);
312       if (quote)
313         putc ('"', stream);
314     }
315 }
316
317 /* Print a list of resource ID's.  */
318
319 void
320 res_ids_print (stream, cids, ids)
321      FILE *stream;
322      int cids;
323      const struct res_id *ids;
324 {
325   int i;
326
327   for (i = 0; i < cids; i++)
328     {
329       res_id_print (stream, ids[i], 1);
330       if (i + 1 < cids)
331         fprintf (stream, ": ");
332     }
333 }
334
335 /* Convert an ASCII string to a resource ID.  */
336
337 void
338 res_string_to_id (res_id, string)
339      struct res_id *res_id;
340      const char *string;
341 {
342   res_id->named = 1;
343   unicode_from_ascii (&res_id->u.n.length, &res_id->u.n.name, string);
344 }
345
346 /* Define a resource.  The arguments are the resource tree, RESOURCES,
347    and the location at which to put it in the tree, CIDS and IDS.
348    This returns a newly allocated res_resource structure, which the
349    caller is expected to initialize.  If DUPOK is non-zero, then if a
350    resource with this ID exists, it is returned.  Otherwise, a warning
351    is issued, and a new resource is created replacing the existing
352    one.  */
353
354 struct res_resource *
355 define_resource (resources, cids, ids, dupok)
356      struct res_directory **resources;
357      int cids;
358      const struct res_id *ids;
359      int dupok;
360 {
361   struct res_entry *re = NULL;
362   int i;
363
364   assert (cids > 0);
365   for (i = 0; i < cids; i++)
366     {
367       struct res_entry **pp;
368
369       if (*resources == NULL)
370         {
371           *resources = (struct res_directory *) xmalloc (sizeof **resources);
372           (*resources)->characteristics = 0;
373           (*resources)->time = 0;
374           (*resources)->major = 0;
375           (*resources)->minor = 0;
376           (*resources)->entries = NULL;
377         }
378
379       for (pp = &(*resources)->entries; *pp != NULL; pp = &(*pp)->next)
380         if (res_id_cmp ((*pp)->id, ids[i]) == 0)
381           break;
382
383       if (*pp != NULL)
384         re = *pp;
385       else
386         {
387           re = (struct res_entry *) xmalloc (sizeof *re);
388           re->next = NULL;
389           re->id = ids[i];
390           if ((i + 1) < cids)
391             {
392               re->subdir = 1;
393               re->u.dir = NULL;
394             }
395           else
396             {
397               re->subdir = 0;
398               re->u.res = NULL;
399             }
400
401           *pp = re;
402         }
403
404       if ((i + 1) < cids)
405         {
406           if (! re->subdir)
407             {
408               fprintf (stderr, "%s: ", program_name);
409               res_ids_print (stderr, i, ids);
410               fprintf (stderr, ": expected to be a directory\n");
411               xexit (1);
412             }
413
414           resources = &re->u.dir;
415         }
416     }
417
418   if (re->subdir)
419     {
420       fprintf (stderr, "%s: ", program_name);
421       res_ids_print (stderr, cids, ids);
422       fprintf (stderr, ": expected to be a leaf\n");
423       xexit (1);
424     }
425
426   if (re->u.res != NULL)
427     {
428       if (dupok)
429         return re->u.res;
430
431       fprintf (stderr, "%s: warning: ", program_name);
432       res_ids_print (stderr, cids, ids);
433       fprintf (stderr, ": duplicate value\n");
434     }
435
436   re->u.res = (struct res_resource *) xmalloc (sizeof (struct res_resource));
437
438   re->u.res->type = RES_TYPE_UNINITIALIZED;
439   memset (&re->u.res->res_info, 0, sizeof (struct res_res_info));
440   memset (&re->u.res->coff_info, 0, sizeof (struct res_coff_info));
441
442   return re->u.res;
443 }
444
445 /* Define a standard resource.  This is a version of define_resource
446    that just takes type, name, and language arguments.  */
447
448 struct res_resource *
449 define_standard_resource (resources, type, name, language, dupok)
450      struct res_directory **resources;
451      int type;
452      struct res_id name;
453      int language;
454      int dupok;
455 {
456   struct res_id a[3];
457
458   a[0].named = 0;
459   a[0].u.id = type;
460   a[1] = name;
461   a[2].named = 0;
462   a[2].u.id = language;
463   return define_resource (resources, 3, a, dupok);
464 }
465 \f
466 /* Return whether the dialog resource DIALOG is a DIALOG or a
467    DIALOGEX.  */
468
469 int
470 extended_dialog (dialog)
471      const struct dialog *dialog;
472 {
473   const struct dialog_control *c;
474
475   if (dialog->ex != NULL)
476     return 1;
477
478   for (c = dialog->controls; c != NULL; c = c->next)
479     if (c->data != NULL || c->help != 0)
480       return 1;
481
482   return 0;
483 }
484
485 /* Return whether MENUITEMS are a MENU or a MENUEX.  */
486
487 int
488 extended_menu (menuitems)
489      const struct menuitem *menuitems;
490 {
491   const struct menuitem *mi;
492
493   for (mi = menuitems; mi != NULL; mi = mi->next)
494     {
495       if (mi->help != 0 || mi->state != 0)
496         return 1;
497       if (mi->popup != NULL && mi->id != 0)
498         return 1;
499       if ((mi->type
500            & ~ (MENUITEM_CHECKED
501                 | MENUITEM_GRAYED
502                 | MENUITEM_HELP
503                 | MENUITEM_INACTIVE
504                 | MENUITEM_MENUBARBREAK
505                 | MENUITEM_MENUBREAK))
506           != 0)
507         return 1;
508       if (mi->popup != NULL)
509         {
510           if (extended_menu (mi->popup))
511             return 1;
512         }
513     }
514
515   return 0;
516 }
517 \f
518 /* Convert a string to a format type, or exit if it can't be done.  */
519
520 static enum res_format
521 format_from_name (name)
522      const char *name;
523 {
524   const struct format_map *m;
525
526   for (m = format_names; m->name != NULL; m++)
527     if (strcasecmp (m->name, name) == 0)
528       break;
529
530   if (m->name == NULL)
531     {
532       fprintf (stderr, "%s: unknown format type `%s'\n", program_name, name);
533       fprintf (stderr, "%s: supported formats:", program_name);
534       for (m = format_names; m->name != NULL; m++)
535         fprintf (stderr, " %s", m->name);
536       fprintf (stderr, "\n");
537       xexit (1);
538     }
539
540   return m->format;
541 }
542
543 /* Work out a format type given a file name.  If INPUT is non-zero,
544    it's OK to look at the file itself.  */
545
546 static enum res_format
547 format_from_filename (filename, input)
548      const char *filename;
549      int input;
550 {
551   const char *ext;
552   FILE *e;
553   unsigned char b1, b2, b3, b4, b5;
554   int magic;
555
556   /* If we have an extension, see if we recognize it as implying a
557      particular format.  */
558   ext = strrchr (filename, '.');
559   if (ext != NULL)
560     {
561       const struct format_map *m;
562
563       ++ext;
564       for (m = format_fileexts; m->name != NULL; m++)
565         if (strcasecmp (m->name, ext) == 0)
566           return m->format;
567     }
568
569   /* If we don't recognize the name of an output file, assume it's a
570      COFF file.  */
571
572   if (! input)
573     return RES_FORMAT_COFF;
574
575   /* Read the first few bytes of the file to see if we can guess what
576      it is.  */
577
578   e = fopen (filename, FOPEN_RB);
579   if (e == NULL)
580     fatal ("%s: %s", filename, strerror (errno));
581
582   b1 = getc (e);
583   b2 = getc (e);
584   b3 = getc (e);
585   b4 = getc (e);
586   b5 = getc (e);
587
588   fclose (e);
589
590   /* A PE executable starts with 0x4d 0x5a.  */
591   if (b1 == 0x4d && b2 == 0x5a)
592     return RES_FORMAT_COFF;
593
594   /* A COFF .o file starts with a COFF magic number.  */
595   magic = (b2 << 8) | b1;
596   switch (magic)
597     {
598     case 0x14c: /* i386 */
599     case 0x166: /* MIPS */
600     case 0x184: /* Alpha */
601     case 0x268: /* 68k */
602     case 0x1f0: /* PowerPC */
603     case 0x290: /* PA */
604       return RES_FORMAT_COFF;
605     }
606
607   /* A RES file starts with 0x0 0x0 0x0 0x0 0x20 0x0 0x0 0x0.  */
608   if (b1 == 0 && b2 == 0 && b3 == 0 && b4 == 0 && b5 == 0x20)
609     return RES_FORMAT_RES;
610
611   /* If every character is printable or space, assume it's an RC file.  */
612   if ((isprint (b1) || isspace (b1))
613       && (isprint (b2) || isspace (b2))
614       && (isprint (b3) || isspace (b3))
615       && (isprint (b4) || isspace (b4))
616       && (isprint (b5) || isspace (b5)))
617     return RES_FORMAT_RC;
618
619   /* Otherwise, we give up.  */
620   fatal ("can not determine type of file `%s'; use the -I option",
621          filename);
622
623   /* Return something to silence the compiler warning.  */
624   return RES_FORMAT_UNKNOWN;
625 }
626
627 /* Print a usage message and exit.  */
628
629 static void
630 usage (stream, status)
631      FILE *stream;
632      int status;
633 {
634   fprintf (stream, "Usage: %s [options] [input-file] [output-file]\n",
635            program_name);
636   fprintf (stream, "\
637 Options:\n\
638   -i FILE, --input FILE       Name input file\n\
639   -o FILE, --output FILE      Name output file\n\
640   -I FORMAT, --input-format FORMAT\n\
641                               Specify input format\n\
642   -O FORMAT, --output-format FORMAT\n\
643                               Specify output format\n\
644   -F TARGET, --target TARGET  Specify COFF target\n\
645   --preprocessor PROGRAM      Program to use to preprocess rc file\n\
646   --include-dir DIR           Include directory when preprocessing rc file\n\
647   --define SYM[=VAL]          Define SYM when preprocessing rc file\n\
648   --language VAL              Set language when reading rc file\n\
649 #ifdef YYDEBUG
650   --yydebug                   Turn on parser debugging\n\
651 #endif
652   --help                      Print this help message\n\
653   --version                   Print version information\n");
654   fprintf (stream, "\
655 FORMAT is one of rc, res, or coff, and is deduced from the file name\n\
656 extension if not specified.  A single file name is an input file.\n\
657 No input-file is stdin, default rc.  No output-file is stdout, default rc.\n");
658   list_supported_targets (program_name, stream);
659   if (status == 0)
660     fprintf (stream, "Report bugs to bug-gnu-utils@prep.ai.mit.edu\n");
661   exit (status);
662 }
663
664 /* The main function.  */
665
666 int
667 main (argc, argv)
668      int argc;
669      char **argv;
670 {
671   int c;
672   char *input_filename;
673   char *output_filename;
674   enum res_format input_format;
675   enum res_format output_format;
676   char *target;
677   char *preprocessor;
678   char *preprocargs;
679   int language;
680   struct res_directory *resources;
681
682   program_name = argv[0];
683   xmalloc_set_program_name (program_name);
684
685   bfd_init ();
686   set_default_bfd_target ();
687
688   input_filename = NULL;
689   output_filename = NULL;
690   input_format = RES_FORMAT_UNKNOWN;
691   output_format = RES_FORMAT_UNKNOWN;
692   target = NULL;
693   preprocessor = NULL;
694   preprocargs = NULL;
695   language = -1;
696
697   while ((c = getopt_long (argc, argv, "i:o:I:O:F:", long_options,
698                            (int *) 0)) != EOF)
699     {
700       switch (c)
701         {
702         case 'i':
703           input_filename = optarg;
704           break;
705
706         case 'o':
707           output_filename = optarg;
708           break;
709
710         case 'I':
711           input_format = format_from_name (optarg);
712           break;
713
714         case 'O':
715           output_format = format_from_name (optarg);
716           break;
717
718         case 'F':
719           target = optarg;
720           break;
721
722         case OPTION_PREPROCESSOR:
723           preprocessor = optarg;
724           break;
725
726         case OPTION_DEFINE:
727           if (preprocargs == NULL)
728             {
729               preprocargs = xmalloc (strlen (optarg) + 3);
730               sprintf (preprocargs, "-D%s", optarg);
731             }
732           else
733             {
734               char *n;
735
736               n = xmalloc (strlen (preprocargs) + strlen (optarg) + 4);
737               sprintf (n, "%s -D%s", preprocargs, optarg);
738               free (preprocargs);
739               preprocargs = n;
740             }
741           break;
742
743         case OPTION_INCLUDE_DIR:
744           if (preprocargs == NULL)
745             {
746               preprocargs = xmalloc (strlen (optarg) + 3);
747               sprintf (preprocargs, "-I%s", optarg);
748             }
749           else
750             {
751               char *n;
752
753               n = xmalloc (strlen (preprocargs) + strlen (optarg) + 4);
754               sprintf (n, "%s -I%s", preprocargs, optarg);
755               free (preprocargs);
756               preprocargs = n;
757             }
758
759           {
760             struct include_dir *n, **pp;
761
762             n = (struct include_dir *) xmalloc (sizeof *n);
763             n->next = NULL;
764             n->dir = optarg;
765
766             for (pp = &include_dirs; *pp != NULL; pp = &(*pp)->next)
767               ;
768             *pp = n;
769           }
770
771           break;
772
773         case OPTION_LANGUAGE:
774           language = strtol (optarg, (char **) NULL, 16);
775           break;
776
777 #ifdef YYDEBUG
778         case OPTION_YYDEBUG:
779           yydebug = 1;
780           break;
781 #endif
782
783         case OPTION_HELP:
784           usage (stdout, 0);
785           break;
786
787         case OPTION_VERSION:
788           print_version ("windres");
789           break;
790
791         default:
792           usage (stderr, 1);
793           break;
794         }
795     }
796
797   if (input_filename == NULL && optind < argc)
798     {
799       input_filename = argv[optind];
800       ++optind;
801     }
802
803   if (output_filename == NULL && optind < argc)
804     {
805       output_filename = argv[optind];
806       ++optind;
807     }
808
809   if (argc != optind)
810     usage (stderr, 1);
811
812   if (input_format == RES_FORMAT_UNKNOWN)
813     {
814       if (input_filename == NULL)
815         input_format = RES_FORMAT_RC;
816       else
817         input_format = format_from_filename (input_filename, 1);
818     }
819
820   if (output_format == RES_FORMAT_UNKNOWN)
821     {
822       if (output_filename == NULL)
823         output_format = RES_FORMAT_RC;
824       else
825         output_format = format_from_filename (output_filename, 0);
826     }
827
828   /* Read the input file.  */
829
830   switch (input_format)
831     {
832     default:
833       abort ();
834     case RES_FORMAT_RC:
835       resources = read_rc_file (input_filename, preprocessor, preprocargs,
836                                 language);
837       break;
838     case RES_FORMAT_RES:
839       resources = read_res_file (input_filename);
840       break;
841     case RES_FORMAT_COFF:
842       resources = read_coff_rsrc (input_filename, target);
843       break;
844     }
845
846   /* Write the output file.  */
847
848   switch (output_format)
849     {
850     default:
851       abort ();
852     case RES_FORMAT_RC:
853       write_rc_file (output_filename, resources);
854       break;
855     case RES_FORMAT_RES:
856       write_res_file (output_filename, resources);
857       break;
858     case RES_FORMAT_COFF:
859       write_coff_file (output_filename, target, resources);
860       break;
861     }
862
863   xexit (0);
864   return 0;
865 }
866
867 struct res_directory *
868 read_res_file (filename)
869      const char *filename;
870 {
871   fatal ("read_res_file unimplemented");
872   return NULL;
873 }
874
875 void
876 write_res_file (filename, resources)
877      const char *filename;
878      const struct res_directory *resources;
879 {
880   fatal ("write_res_file unimplemented");
881 }
882
883 void
884 write_coff_file (filename, target, resources)
885      const char *filename;
886      const char *target;
887      const struct res_directory *resources;
888 {
889   fatal ("write_coff_file unimplemented");
890 }