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