* as/asranlib/asranlib.c: made it more robust
[fw/sdcc] / as / asranlib / asranlib.c
1 /* asranlib.c - ranlib for asxxxx arvhives
2    version 1.0.0, April 27th, 2008
3
4    Copyright (C) 2008-2009 Borut Razem, borut dot razem at siol dot net
5
6 This program is free software; you can redistribute it and/or modify it
7 under the terms of the GNU General Public License as published by the
8 Free Software Foundation; either version 3, or (at your option) any
9 later version.
10
11 This program is distributed in the hope that it will be useful,
12 but WITHOUT ANY WARRANTY; without even the implied warranty of
13 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
14 GNU General Public License for more details.
15
16 You should have received a copy of the GNU General Public License
17 along with this program.  If not, see <http://www.gnu.org/licenses/>. */
18
19 #include <stdio.h>
20 #include <stdlib.h>
21 #include <string.h>
22 #include <assert.h>
23 #include <time.h>
24 #include "dbuf_string.h"
25 #include "lkar.h"
26
27 #ifdef _WIN32
28 #include <io.h>
29 #else
30 #include <unistd.h>
31 #endif
32
33 #define NELEM(x)  (sizeof (x) / sizeof (*x))
34
35
36 static int verbose = 0;
37 static int list = 0;
38 static int print_index = 0;
39
40 int
41 is_ar (FILE * libfp)
42 {
43   char buf[SARMAG];
44   int ret;
45
46   if (!(ret = fread (buf, 1, sizeof (buf), libfp) == sizeof (buf) && memcmp (buf, ARMAG, SARMAG) == 0))
47     rewind (libfp);
48
49   return ret;
50 }
51
52 static char *sym_tab;
53 static int sym_tab_size;
54
55 char *
56 get_member_name (char *name, size_t *p_size, int allocate, FILE * libfp)
57 {
58   *p_size = 0;
59
60   if (0 == memcmp (name, "#1/", 3))
61     {
62       char *p;
63       size_t len = strtoul (&name [3], &p, 10);
64       if (p > &name [3])
65         {
66           /* BSD appends real file name to the file header */
67           if (p_size != NULL)
68             *p_size = len;
69
70           if (allocate)
71             {
72               char *n = (char *) malloc (len);
73               if (fread (n, 1, len, libfp) != len)
74                 {
75                   /* not an ar archive or broken ar archive */
76                   return NULL;
77                 }
78               else
79                 return n;
80             }
81           else
82             {
83               /* just advance the file pointer */
84               fseek (libfp, len, SEEK_CUR);
85               return NULL;
86             }
87         }
88       else
89         {
90           /* not an ar archive or broken ar archive */
91           return NULL;
92         }
93     }
94   else if (allocate)
95     {
96       if (name[0] == '/')
97         {
98           if (NULL != sym_tab)
99             {
100               char *p;
101
102               int name_offset = strtol (++name, &p, 0);
103               if (p != name && name_offset < sym_tab_size)
104                 {
105                   int len = p - name + 1;
106                   while (len < AR_NAME_LEN && name[len++] == ' ')
107                     ;
108                   if (len == AR_NAME_LEN)
109                     {
110                       char *n;
111
112                       /* long name: get it from the symbol table */
113                       name = &sym_tab[name_offset];
114                       for (p = name; *p != '/' && *p != '\n'; ++p)
115                         assert (p < &sym_tab[sym_tab_size]);
116
117                       if (p[0] != '/' || p[1] != '\n')
118                         while (*++p != '\n')
119                           assert (p < &sym_tab[sym_tab_size]);
120
121                       n = (char *) malloc (p - name + 1);
122                       memcpy (n, name, p - name);
123                       n[p - name] = '\0';
124                       return n;
125                     }
126                 }
127             }
128         }
129       else
130         {
131           char *p = strrchr (name, '/');
132
133           if (NULL != p)
134             {
135               int len = p - name;
136               while (name[++len] == ' ')
137                 ;
138               if (len == AR_NAME_LEN)
139                 {
140                   char *n = (char *) malloc (p - name + 1);
141                   memcpy (n, name, p - name);
142                   n[p - name] = '\0';
143                   return n;
144                 }
145             }
146           else
147             {
148               /* BSD formed member name:
149                  trim trailing spaces */
150               char *n;
151
152               p = name + AR_NAME_LEN;
153               while (*--p == ' ' && p >= name)
154                 ;
155               ++p;
156               n = (char *) malloc (p - name + 1);
157               memcpy (n, name, p - name);
158               n[p - name] = '\0';
159               return n;
160             }
161         }
162
163       /* bad formed member name:
164        just return it */
165
166       return strdup (name);
167     }
168   else
169     return NULL;
170 }
171
172 size_t
173 ar_get_header (struct ar_hdr *hdr, FILE * libfp, char **p_obj_name)
174 {
175   char header[ARHDR_LEN];
176   char buf[AR_DATE_LEN + 1];
177   char *obj_name;
178   size_t size;
179
180   if (fread (header, 1, sizeof (header), libfp) != sizeof (header)
181       || memcmp (header + AR_FMAG_OFFSET, ARFMAG, AR_FMAG_LEN) != 0)
182     {
183       /* not an ar archive */
184       return 0;
185     }
186
187   memcpy (hdr->ar_name, &header[AR_NAME_OFFSET], AR_NAME_LEN);
188   hdr->ar_name[AR_NAME_LEN] = '\0';
189
190   memcpy (buf, &header[AR_DATE_OFFSET], AR_DATE_LEN);
191   buf[AR_DATE_LEN] = '\0';
192   hdr->ar_date = strtol (buf, NULL, 0);
193
194   memcpy (buf, &header[AR_UID_OFFSET], AR_GID_LEN);
195   buf[AR_GID_LEN] = '\0';
196   hdr->ar_uid = (uid_t) strtol (buf, NULL, 0);
197
198   memcpy (buf, &header[AR_GID_OFFSET], AR_DATE_LEN);
199   buf[AR_DATE_LEN] = '\0';
200   hdr->ar_gid = (gid_t) strtol (buf, NULL, 0);
201
202   memcpy (buf, &header[AR_MODE_OFFSET], AR_MODE_LEN);
203   buf[AR_MODE_LEN] = '\0';
204   hdr->ar_mode = (mode_t) strtoul (buf, NULL, 0);
205
206   memcpy (buf, &header[AR_SIZE_OFFSET], AR_SIZE_LEN);
207   buf[AR_SIZE_LEN] = '\0';
208   hdr->ar_size = strtol (buf, NULL, 0);
209
210   if (NULL == (obj_name = get_member_name (hdr->ar_name, &size, p_obj_name != NULL, libfp)) && p_obj_name != NULL)
211     {
212       /* Malformed archive */
213       return 0;
214     }
215
216   if (p_obj_name != NULL)
217     *p_obj_name = obj_name;
218
219   /* treat BSD appended real file name as a part of the header */
220   hdr->ar_size -= size;
221
222   return size + ARHDR_LEN;
223 }
224
225 static char *
226 get_member_name_by_offset (FILE * fp, long offset)
227 {
228   struct ar_hdr hdr;
229   char *name;
230
231   fseek (fp, offset, SEEK_SET);
232   return (ar_get_header (&hdr, fp, &name) != 0) ? name : NULL;
233 }
234
235 struct symbol_s
236   {
237     const char *name;
238     size_t offset;
239     struct symbol_s *next;
240   };
241
242 struct symbol_s *symlist, *lastsym;
243 unsigned int offset, first_member_offset;
244
245 int
246 add_symbol (const char *sym, void *param)
247 {
248   struct symbol_s *s;
249
250   if ((s = (struct symbol_s *) malloc (sizeof (struct symbol_s))) == NULL)
251     return 0;
252
253   if (verbose)
254     printf ("%s\n", sym);
255
256   s->name = strdup (sym);
257   s->offset = offset - first_member_offset;
258   s->next = NULL;
259
260   if (NULL == symlist)
261     {
262       lastsym = symlist = s;
263     }
264   else
265     {
266       lastsym->next = s;
267       lastsym = s;
268     }
269
270   return 0;
271 }
272
273 int
274 is_rel (FILE * libfp)
275 {
276   int c;
277   long pos = ftell (libfp);
278   int ret = 0;
279
280   /* [XDQ][HL] */
281   if (((c = getc (libfp)) == 'X' || c == 'D' || c == 'Q') && ((c = getc (libfp)) == 'H' || c == 'L'))
282     {
283       switch (getc (libfp))
284         {
285         case '\r':
286           if (getc (libfp) == '\n')
287             ret = 1;
288           break;
289
290         case '\n':
291           ret = 1;
292         }
293     }
294   else if (c == ';')
295     {
296       char buf[6];
297
298       if (fread (buf, 1, sizeof (buf), libfp) == sizeof (buf) && memcmp (buf, "!FILE ", 6) == 0)
299         ret = 1;
300     }
301   fseek (libfp, pos, SEEK_SET);
302   return ret;
303 }
304
305 int
306 enum_symbols (FILE * fp, long size, int (*func) (const char *sym, void *param), void *param)
307 {
308   long end;
309   struct dbuf_s buf;
310   struct dbuf_s symname;
311
312   assert (func != NULL);
313
314   dbuf_init (&buf, 512);
315   dbuf_init (&symname, 32);
316
317   end = (size >= 0) ? ftell (fp) + size : -1;
318
319   /*
320    * Read in the object file.  Look for lines that
321    * begin with "S" and end with "D".  These are
322    * symbol table definitions.  If we find one, see
323    * if it is our symbol.  Make sure we only read in
324    * our object file and don't go into the next one.
325    */
326
327   while (end < 0 || ftell (fp) < end)
328     {
329       const char *p;
330
331       dbuf_set_length (&buf, 0);
332       if (dbuf_getline (&buf, fp) == 0)
333         break;
334
335       p = dbuf_c_str (&buf);
336
337       if ('T' == p[0])
338         break;
339
340       /*
341        * Skip everything that's not a symbol record.
342        */
343       if ('S' == p[0] && ' ' == p[1])
344         {
345           dbuf_set_length (&symname, 0);
346
347           for (p += 2; *p && ' ' != *p; ++p)
348             dbuf_append_char (&symname, *p);
349
350           /* If it's an actual symbol, record it */
351           if (' ' == p[0] && 'D' == p[1])
352             if (func != NULL)
353               if ((*func) (dbuf_c_str (&symname), NULL))
354                 return 1;
355         }
356     }
357
358   dbuf_destroy (&buf);
359   dbuf_destroy (&symname);
360
361   return 0;
362 }
363
364 static int
365 process_symbol_table (struct ar_hdr *hdr, FILE *fp)
366 {
367   long pos = ftell (fp);
368
369   if (print_index)
370     {
371       char *buf, *po, *ps;
372       int i;
373       long nsym;
374
375       printf ("Archive index:\n");
376
377       buf = (char *) malloc (hdr->ar_size);
378
379       if (fread (buf, 1, hdr->ar_size, fp) != hdr->ar_size)
380         {
381           free (buf);
382           return 0;
383         }
384
385       nsym = sgetl (buf);
386
387       po = buf + 4;
388       ps = po + nsym * 4;
389
390       for (i = 0; i < nsym; ++i)
391         {
392           char *obj;
393
394           offset = sgetl (po);
395           po += 4;
396
397           if (NULL == (obj = get_member_name_by_offset (fp, offset))) /* member name */
398             return 0;
399
400           printf ("%s in %s", ps, obj);
401           if (verbose)
402             printf (" at 0x%04x\n", offset);
403           else
404             putchar ('\n');
405           free (obj);
406
407           ps += strlen(ps) + 1;
408         
409         }
410       free (buf);
411
412       fseek (fp, pos, SEEK_SET);
413
414       putchar ('\n');
415     }
416
417   /* skip the symbol table */
418   fseek (fp, pos + hdr->ar_size + (hdr->ar_size & 1), SEEK_SET);
419
420   return 1;
421 }
422
423 static int
424 process_bsd_symbol_table (struct ar_hdr *hdr, FILE *fp)
425 {
426   long pos = ftell (fp);
427
428   if (print_index)
429     {
430       char *buf, *po, *ps;
431       int i;
432       long tablesize;
433       long nsym;
434
435       printf ("Archive index:\n");
436
437       buf = (char *) malloc (hdr->ar_size);
438
439       if (fread (buf, 1, hdr->ar_size, fp) != hdr->ar_size)
440         {
441           free (buf);
442           return 0;
443         }
444
445       tablesize = sgetl (buf);
446       nsym = tablesize / 8;
447
448       po = buf + 4;
449
450       ps = po + tablesize + 4;
451
452       for (i = 0; i < nsym; ++i)
453         {
454           char *obj;
455           long sym;
456
457           sym = sgetl (po);
458           po += 4;
459           offset = sgetl (po);
460           po += 4;
461
462           printf ("%s in ", ps + sym);
463
464           if (NULL == (obj = get_member_name_by_offset (fp, offset))) /* member name */
465             return 0;
466
467           printf ("%s\n", obj);
468           free (obj);
469         }
470       free (buf);
471       putchar ('\n');
472     }
473
474   /* skip the symbol table */
475   fseek (fp, pos + hdr->ar_size + (hdr->ar_size & 1), SEEK_SET);
476
477   return 1;
478 }
479
480 int
481 get_symbols (FILE * fp, const char *archive)
482 {
483   struct ar_hdr hdr;
484   size_t hdr_len;
485   char *name;
486
487   if (!is_ar (fp) || !(hdr_len = ar_get_header (&hdr, fp, &name)))
488     {
489       free (name);
490
491       return 0;
492     }
493
494   if (AR_IS_SYMBOL_TABLE (name))
495     {
496       free (name);
497
498       if (!process_symbol_table (&hdr, fp))
499         return 0;
500
501       if (feof (fp))
502         return 1;
503       else if (!(hdr_len = ar_get_header (&hdr, fp, (verbose || list) ? &name : NULL)))
504         return 0;
505     }
506   else if (AR_IS_BSD_SYMBOL_TABLE (name))
507     {
508       free (name);
509
510       if (!process_bsd_symbol_table (&hdr, fp))
511         return 0;
512
513       if (feof (fp))
514         return 1;
515       else if (!(hdr_len = ar_get_header (&hdr, fp, (verbose || list) ? &name : NULL)))
516         return 0;
517     }
518   else if (!verbose && !list)
519     free (name);
520
521   first_member_offset = ftell (fp) - hdr_len;
522
523   /* walk trough all archive members */
524   do
525     {
526       if (is_rel (fp))
527         {
528           if (verbose || list)
529             {
530               printf ("%s%s\n", name, verbose ? ":" : "");
531               free (name);
532             }
533
534           if (!list)
535             {
536               long mdule_offset = ftell (fp);
537
538               offset = mdule_offset - hdr_len;
539
540               enum_symbols (fp, hdr.ar_size, add_symbol, NULL);
541
542               fseek (fp, mdule_offset + hdr.ar_size + (hdr.ar_size & 1), SEEK_SET);
543             }
544
545           if (verbose)
546             putchar ('\n');
547         }
548       else
549         {
550           if (verbose || list)
551             {
552               fprintf (stderr, "asranlib: %s: File format not recognized\n", name);
553               free (name);
554             }
555
556           /* skip if the member is not a .REL format */
557           fseek (fp, hdr.ar_size + (hdr.ar_size & 1), SEEK_CUR);
558         }
559     }
560   while ((hdr_len = ar_get_header (&hdr, fp, (verbose || list) ? &name : NULL)));
561
562   return feof (fp) ? 1 : 0;
563 }
564
565 void
566 do_ranlib (const char *archive)
567 {
568   FILE *infp;
569
570   if (NULL == (infp = fopen (archive, "rb")))
571     {
572       fprintf (stderr, "asranlib: %s: ", archive);
573       perror (NULL);
574       exit (1);
575     }
576
577   if (!get_symbols (infp, archive))
578     {
579       fprintf (stderr, "asranlib: %s: Malformed archive\n", archive);
580       fclose (infp);
581       exit (1);
582     }
583   else if (!list && !print_index)
584     {
585       FILE *outfp;
586       struct symbol_s *symp;
587       char buf[4];
588       int str_length;
589       int pad;
590       int nsym;
591       int symtab_size;
592       char tmpfile[] = "arXXXXXX";
593
594 #ifdef _WIN32
595       if (NULL == _mktemp (tmpfile) || NULL == (outfp = fopen (tmpfile, "wb")))
596         {
597           fclose (infp);
598           fprintf (stderr, "asranlib: %s: ", tmpfile);
599           perror (NULL);
600           exit (1);
601         }
602 #else
603       if ((pad = mkstemp (tmpfile)) < 0)
604         {
605           fclose (infp);
606           fprintf (stderr, "asranlib: %s: ", tmpfile);
607           perror (NULL);
608           exit (1);
609         }
610
611       if (NULL == (outfp = fdopen (pad, "wb")))
612         {
613           close (pad);
614           fclose (infp);
615           perror ("asranlib");
616           exit (1);
617         }
618 #endif
619
620       /* calculate the size of symbol table */
621       for (str_length = 0, nsym = 0, symp = symlist; symp; ++nsym, symp = symp->next)
622         {
623           str_length += strlen (symp->name) + 1;
624         }
625
626       symtab_size = 4 + 4 * nsym + str_length;
627
628       fprintf (outfp, ARMAG AR_SYMBOL_TABLE_NAME "%-12d%-6d%-6d%-8d%-10d" ARFMAG, (int) time (NULL), 0, 0, 0, symtab_size);
629
630       if (symtab_size & 1)
631         {
632           pad = 1;
633           ++symtab_size;
634         }
635       else
636         pad = 0;
637
638       symtab_size += SARMAG + ARHDR_LEN;
639
640       sputl (nsym, buf);
641       fwrite (buf, 1, sizeof (buf), outfp);
642
643       for (symp = symlist; symp; symp = symp->next)
644         {
645           sputl (symp->offset + symtab_size, buf);
646           fwrite (buf, 1, sizeof (buf), outfp);
647         }
648
649
650       for (symp = symlist; symp; symp = symp->next)
651         {
652           fputs (symp->name, outfp);
653           putc ('\0', outfp);
654         }
655
656       if (pad)
657         putc ('\n', outfp);
658
659       fseek (infp, first_member_offset, SEEK_SET);
660
661       while (EOF != (pad = getc (infp)))
662         putc (pad, outfp);
663
664       fclose (outfp);
665       fclose (infp);
666
667       if (0 != remove (archive))
668         {
669           fprintf (stderr, "asranlib: can't remove %s to %s: ", tmpfile, archive);
670           perror (NULL);
671         }
672       else if (0 != rename (tmpfile, archive))
673         {
674           fprintf (stderr, "asranlib: can't rename %s to %s: ", tmpfile, archive);
675           perror (NULL);
676         }
677     }
678   else
679     fclose (infp);
680 }
681
682 void
683 do_verbose (void)
684 {
685   verbose = 1;
686 }
687
688 void
689 print_version (void)
690 {
691   printf ("SDCC asxxxx ranlib 1.0.0 $Revision$\n");
692   exit (0);
693 }
694
695 void
696 do_list (void)
697 {
698   list = 1;
699 }
700
701 void
702 print_armap (void)
703 {
704   print_index = 1;
705 }
706
707 void usage (void);
708
709 struct opt_s
710   {
711     char short_opt;
712     const char *long_opt;
713     void (*optfnc) (void);
714     const char *comment;
715   }
716 opts[] =
717   {
718     { 'v', "verbose", &do_verbose, "Be more verbose about the operation" },
719     { 'V', "version", &print_version, "Print this help message" },
720     { 'h', "help", &usage, "Print version information" },
721     { 't', "list", &do_list, "List the contents of an archive" },
722     { 's', "print-armap", &print_armap, "Print the archive index" },
723   };
724
725 void
726 usage (void)
727 {
728   int i;
729
730   printf ("Usage: asranlib [options] archive\n"
731     " Generate an index to speed access to archives\n"
732     " The options are:\n");
733
734   for (i = 0; i < NELEM (opts); ++i)
735     {
736       int len = 5;
737       if ('\0' != opts[i].short_opt)
738         printf ("  -%c ", opts[i].short_opt);
739       else
740         printf ("     ");
741
742       if (NULL != opts[i].long_opt)
743         {
744           printf ("--%s ", opts[i].long_opt);
745           len += strlen (opts[i].long_opt);
746         }
747
748       while (len++ < 30)
749         putchar (' ');
750       printf ("%s\n", opts[i].comment);
751     }
752
753   printf ("asranlib: supported targets: asxxxx\n");
754
755   exit (1);
756 }
757
758 int
759 main (int argc, char *argv[])
760 {
761   char **argp;
762   int noopts = 0;
763   int narch = 0;
764
765   for (argp = argv + 1; *argp; ++argp)
766     {
767       if (!noopts && (*argp)[0] == '-')
768         {
769           int i;
770
771           if ((*argp)[1] == '-')
772             {
773               if ((*argp)[2] == '\0')
774                 {
775                   /* end of options */
776                   noopts = 1;
777                   continue;
778                 }
779               else
780                 {
781                   /* long option */
782                   for (i = 0; i < NELEM (opts); ++i)
783                     {
784                       if (0 == strcmp (&(*argp)[2], opts[i].long_opt))
785                         {
786                           if (NULL != opts[i].optfnc)
787                             {
788                               (*opts[i].optfnc) ();
789                               break;
790                             }
791                         }
792                     }
793                   if (i >= NELEM (opts))
794                     {
795                       fprintf (stderr, "asranlib: unrecognized option `%s'\n", *argp);
796                       usage ();
797                     }
798                 }
799             }
800           else
801             {
802               char *optp;
803
804               /* short option */
805               for (optp = &(*argp)[1]; *optp != '\0'; ++optp)
806                 {
807                   for (i = 0; i < NELEM (opts); ++i)
808                     {
809                       if (*optp == opts[i].short_opt)
810                         {
811                           if (NULL != opts[i].optfnc)
812                             {
813                               (*opts[i].optfnc) ();
814                               break;
815                             }
816                         }
817                     }
818                   if (i >= NELEM (opts))
819                     {
820                       fprintf (stderr, "asranlib: invalid option -- %c\n", *optp);
821                       usage ();
822                     }
823                 }
824             }
825         }
826       else
827         {
828           /* not an option */
829           do_ranlib (*argp);
830           ++narch;
831         }
832     }
833
834   if (!narch)
835     usage ();
836
837   return 0;
838 }