* as/asranlib/asranlib.c: retain the original file mode
[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       struct stat stat_buf;
594       int can_stat;
595
596       /* TODO: create tmpfile in temporery directory (TMP, TMPDIR, /usr/tmp, /tmp) */
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: ", infp);
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 }