blob: 1914a17891723d21944c0babcc50774e8166d5a5 [file] [log] [blame]
Ulrich Drepper5d832292005-08-15 21:36:27 +00001/* Generate an index to speed access to archives.
2 Copyright (C) 2005 Red Hat, Inc.
3 Written by Ulrich Drepper <drepper@redhat.com>, 2005.
4
5 This program is Open Source software; you can redistribute it and/or
6 modify it under the terms of the Open Software License version 1.0 as
7 published by the Open Source Initiative.
8
9 You should have received a copy of the Open Software License along
10 with this program; if not, you may obtain a copy of the Open Software
11 License version 1.0 from http://www.opensource.org/licenses/osl.php or
12 by writing the Open Source Initiative c/o Lawrence Rosen, Esq.,
13 3001 King Ranch Road, Ukiah, CA 95482. */
14
15#ifdef HAVE_CONFIG_H
16# include <config.h>
17#endif
18
19#include <ar.h>
20#include <argp.h>
21#include <assert.h>
22#include <byteswap.h>
23#include <endian.h>
24#include <errno.h>
25#include <error.h>
26#include <fcntl.h>
27#include <gelf.h>
28#include <libintl.h>
29#include <locale.h>
30#include <mcheck.h>
31#include <obstack.h>
32#include <stdlib.h>
33#include <stdio.h>
34#include <stdio_ext.h>
35#include <time.h>
36#include <unistd.h>
37#include <sys/param.h>
38#include <sys/stat.h>
39
40#include <system.h>
41
42
Ulrich Drepper5d832292005-08-15 21:36:27 +000043#if __BYTE_ORDER == __LITTLE_ENDIAN
44# define le_bswap_32(val) bswap_32 (val)
45#else
46# define le_bswap_32(val) (val)
47#endif
48
49
50/* Prototypes for local functions. */
51static int handle_file (const char *fname);
52
53
54/* Name and version of program. */
55static void print_version (FILE *stream, struct argp_state *state);
56void (*argp_program_version_hook) (FILE *, struct argp_state *) = print_version;
57
58/* Bug report address. */
59const char *argp_program_bug_address = PACKAGE_BUGREPORT;
60
61
62/* Definitions of arguments for argp functions. */
63static const struct argp_option options[] =
64{
65 { NULL, 0, NULL, 0, NULL, 0 }
66};
67
68/* Short description of program. */
69static const char doc[] = N_("Generate an index to speed access to archives.");
70
71/* Strings for arguments in help texts. */
72static const char args_doc[] = N_("ARCHIVE");
73
74/* Prototype for option handler. */
75static error_t parse_opt (int key, char *arg, struct argp_state *state);
76
77/* Data structure to communicate with argp functions. */
78static struct argp argp =
79{
80 options, parse_opt, args_doc, doc, NULL, NULL, NULL
81};
82
83
84int
85main (int argc, char *argv[])
86{
87 /* Make memory leak detection possible. */
88 mtrace ();
89
90 /* We use no threads here which can interfere with handling a stream. */
91 (void) __fsetlocking (stdin, FSETLOCKING_BYCALLER);
92 (void) __fsetlocking (stdout, FSETLOCKING_BYCALLER);
93 (void) __fsetlocking (stderr, FSETLOCKING_BYCALLER);
94
95 /* Set locale. */
96 (void) setlocale (LC_ALL, "");
97
98 /* Make sure the message catalog can be found. */
99 (void) bindtextdomain (PACKAGE, LOCALEDIR);
100
101 /* Initialize the message catalog. */
102 (void) textdomain (PACKAGE);
103
104 /* Parse and process arguments. */
105 int remaining;
106 (void) argp_parse (&argp, argc, argv, ARGP_IN_ORDER, &remaining, NULL);
107
108 /* Tell the library which version we are expecting. */
109 (void) elf_version (EV_CURRENT);
110
111 /* There must at least be one more parameter specifying the archive. */
112 if (remaining == argc)
113 {
114 error (0, 0, gettext ("Archive name required"));
115 argp_help (&argp, stderr, ARGP_HELP_SEE, "ranlib");
116 exit (EXIT_FAILURE);
117 }
118
119 /* We accept the names of multiple archives. */
120 int status = 0;
121 do
122 status |= handle_file (argv[remaining]);
123 while (++remaining < argc);
124
125 return status;
126}
127
128
129/* Print the version information. */
130static void
131print_version (FILE *stream, struct argp_state *state __attribute__ ((unused)))
132{
133 fprintf (stream, "ranlib (%s) %s\n", PACKAGE_NAME, VERSION);
134 fprintf (stream, gettext ("\
135Copyright (C) %s Red Hat, Inc.\n\
136This is free software; see the source for copying conditions. There is NO\n\
137warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.\n\
138"), "2005");
139 fprintf (stream, gettext ("Written by %s.\n"), "Ulrich Drepper");
140}
141
142
143/* Handle program arguments. */
144static error_t
145parse_opt (int key, char *arg __attribute__ ((unused)),
146 struct argp_state *state __attribute__ ((unused)))
147{
148 switch (key)
149 {
150 default:
151 return ARGP_ERR_UNKNOWN;
152 }
153 return 0;
154}
155
156
157static struct obstack offsob;
158size_t offs_len;
159static struct obstack namesob;
160size_t names_len;
161
162
163/* Add all exported, defined symbols from the given section to the table. */
164static void
165add_symbols (Elf *elf, const char *fname, off_t off, Elf_Scn *scn,
166 GElf_Shdr *shdr)
167{
168 if (sizeof (off) > sizeof (uint32_t) && off > ~((uint32_t) 0))
169 /* The archive is too big. */
170 error (EXIT_FAILURE, 0, gettext ("the archive '%s' is too large"), fname);
171
172 Elf_Data *data = elf_getdata (scn, NULL);
173 assert (data->d_size == shdr->sh_size);
174
175 /* We can skip the local symbols in the table. */
176 for (int cnt = shdr->sh_info; cnt < (int) (shdr->sh_size / shdr->sh_entsize);
177 ++cnt)
178 {
179 GElf_Sym sym_mem;
180 GElf_Sym *sym = gelf_getsym (data, cnt, &sym_mem);
181 if (sym == NULL)
182 /* Should never happen. */
183 continue;
184
185 /* Ignore undefined symbols. */
186 if (sym->st_shndx == SHN_UNDEF)
187 continue;
188
189 /* For all supported platforms the following is true. */
190 assert (sizeof (uint32_t) == sizeof (int));
191 obstack_int_grow (&offsob, (int) le_bswap_32 (off));
192 offs_len += sizeof (uint32_t);
193
194 const char *symname = elf_strptr (elf, shdr->sh_link, sym->st_name);
195 size_t symname_len = strlen (symname) + 1;
196 obstack_grow (&namesob, symname, symname_len);
197 names_len += symname_len;
198 }
199}
200
201
202/* Look through ELF file and collect all symbols available for
203 linking. If available, we use the dynamic symbol section.
204 Otherwise the normal one. Relocatable files are allowed to have
205 multiple symbol tables. */
206static void
207handle_elf (Elf *elf, const char *arfname, off_t off)
208{
209 GElf_Ehdr ehdr_mem;
210 GElf_Ehdr *ehdr = gelf_getehdr (elf, &ehdr_mem);
211 assert (ehdr != NULL);
212
213 if (ehdr->e_type == ET_REL)
214 {
215 Elf_Scn *scn = NULL;
216 while ((scn = elf_nextscn (elf, scn)) != NULL)
217 {
218 GElf_Shdr shdr_mem;
219 GElf_Shdr *shdr = gelf_getshdr (scn, &shdr_mem);
220 if (shdr != NULL && shdr->sh_type == SHT_SYMTAB)
221 add_symbols (elf, arfname, off, scn, shdr);
222 }
223 }
224 else
225 {
226 Elf_Scn *symscn = NULL;
227 GElf_Shdr *symshdr = NULL;
228 Elf_Scn *scn = NULL;
229 GElf_Shdr shdr_mem;
230 while ((scn = elf_nextscn (elf, scn)) != NULL)
231 {
232 GElf_Shdr *shdr = gelf_getshdr (scn, &shdr_mem);
233 symshdr = NULL;
234 if (shdr != NULL)
235 {
236 if (shdr->sh_type == SHT_DYNSYM)
237 {
238 symscn = scn;
239 symshdr = shdr;
240 break;
241 }
242 if (shdr->sh_type == SHT_SYMTAB)
243 {
244 /* There better be only one symbol table in
245 executables in DSOs. */
246 assert (symscn == NULL);
247 symscn = scn;
248 symshdr = shdr;
249 }
250 }
251 }
252
253 add_symbols (elf, arfname, off, symscn,
254 symshdr ?: gelf_getshdr (scn, &shdr_mem));
255 }
256}
257
258
259static int
260copy_content (int oldfd, int newfd, off_t off, size_t n)
261{
262 while (n > 0)
263 {
264 char buf[32768];
265
266 ssize_t nread = pread_retry (oldfd, buf, MIN (sizeof (buf), n), off);
267 if (unlikely (nread <= 0))
268 return 1;
269
270 if (write_retry (newfd, buf, nread) != nread)
271 return 1;
272
273 n -= nread;
274 off += nread;
275 }
276
277 return 0;
278}
279
280
281/* Handle a file given on the command line. */
282static int
283handle_file (const char *fname)
284{
285 int fd = open (fname, O_RDONLY);
286 if (fd == -1)
287 {
288 error (0, errno, gettext ("cannot open '%s'"), fname);
289 return 1;
290 }
291
292 struct stat st;
293 if (fstat (fd, &st) != 0)
294 {
295 error (0, errno, gettext ("cannot stat '%s'"), fname);
296 close (fd);
297 return 1;
298 }
299
300 /* First we walk through the file, looking for all ELF files to
301 collect symbols from. */
302 Elf *arelf = elf_begin (fd, ELF_C_READ_MMAP, NULL);
303 if (arelf == NULL)
304 {
305 error (0, 0, gettext ("cannot create ELF descriptor for '%s': %s"),
306 fname, elf_errmsg (-1));
307 close (fd);
308 return 1;
309 }
310
311 if (elf_kind (arelf) != ELF_K_AR)
312 {
313 error (0, 0, gettext ("'%s' is no archive"), fname);
314 elf_end (arelf);
315 close (fd);
316 return 1;
317 }
318
319#define obstack_chunk_alloc xmalloc
320#define obstack_chunk_free free
321 obstack_init (&offsob);
322 offs_len = 0;
323 obstack_init (&namesob);
324 names_len = 0;
325
326 /* The first word in the offset table specifies the size. Create
327 such an entry now. The real value will be filled-in later. For
328 all supported platforms the following is true. */
329 assert (sizeof (uint32_t) == sizeof (int));
330 obstack_int_grow (&offsob, 0);
331 offs_len += sizeof (uint32_t);
332
333 /* Iterate over the content of the archive. */
334 off_t index_off = -1;
335 size_t index_size = 0;
336 Elf *elf;
337 Elf_Cmd cmd = ELF_C_READ_MMAP;
338 while ((elf = elf_begin (fd, cmd, arelf)) != NULL)
339 {
340 Elf_Arhdr *arhdr = elf_getarhdr (elf);
341 assert (arhdr != NULL);
342 off_t off = elf_getaroff (elf);
343
344 Elf_Kind kind = elf_kind (elf);
345 if (kind == ELF_K_ELF)
346 handle_elf (elf, fname, off);
347#if 0
348 else if (kind == ELF_K_AR)
349 {
350 // XXX Should we implement this?
351 }
352#endif
353 /* If this is the index, remember the location. */
354 else if (strcmp (arhdr->ar_name, "/") == 0)
355 {
356 index_off = off;
357 index_size = arhdr->ar_size;
358 }
359
360 /* Get next archive element. */
361 cmd = elf_next (elf);
362 if (elf_end (elf) != 0)
363 error (0, 0,
364 gettext (" error while freeing sub-ELF descriptor: %s\n"),
365 elf_errmsg (-1));
366 }
367
368 elf_end (arelf);
369 uint32_t *offs_arr = obstack_finish (&offsob);
370 assert (offs_len % sizeof (uint32_t) == 0);
371 if ((names_len & 1) != 0)
372 {
373 /* Add one more NUL byte to make length even. */
374 obstack_grow (&namesob, "", 1);
375 ++names_len;
376 }
377 const char *names_str = obstack_finish (&namesob);
378
379 /* If the file contains no symbols we need not do anything. */
380 if (names_len != 0
381 /* We have to rewrite the file also if it initially had an index
382 but now does not need one anymore. */
383 || (names_len == 0 && index_off != -1))
384 {
385 /* Create a new, temporary file in the same directory as the
386 original file. */
387 char tmpfname[strlen (fname) + 8];
388 strcpy (stpcpy (tmpfname, fname), ".XXXXXX");
389 int newfd = mkstemp (tmpfname);
390 if (newfd == -1)
391 nonew:
392 error (0, errno, gettext ("cannot create new file"));
393 else
394 {
395 /* Create the header. */
396 if (write_retry (newfd, ARMAG, SARMAG) != SARMAG)
397 {
398 // XXX Use /prof/self/fd/%d ???
399 nonew_unlink:
400 unlink (tmpfname);
401 if (newfd != -1)
402 close (newfd);
403 goto nonew;
404 }
405
406 struct ar_hdr ar_hdr;
407 memcpy (ar_hdr.ar_name, "/ ", sizeof (ar_hdr.ar_name));
408 /* Using snprintf here has a problem: the call always wants
409 to add a NUL byte. We could use a trick whereby we
410 specify the target buffer size longer than it is and this
411 would not actually fail, since all the fields are
412 consecutive and we fill them in in sequence (i.e., the
413 NUL byte gets overwritten). But _FORTIFY_SOURCE=2 would
414 not let us play these games. Therefore we play it
415 safe. */
416 char tmpbuf[MAX (sizeof (ar_hdr.ar_date), sizeof (ar_hdr.ar_size))
417 + 1];
418 memcpy (ar_hdr.ar_date, tmpbuf,
419 snprintf (tmpbuf, sizeof (tmpbuf), "%-*lld",
420 (int) sizeof (ar_hdr.ar_date),
421 (long long int) time (NULL)));
422
423 /* Note the string for the ar_uid and ar_gid cases is longer
424 than necessary. This does not matter since we copy only as
425 much as necessary but it helps the compiler to use the same
426 string for the ar_mode case. */
427 memcpy (ar_hdr.ar_uid, "0 ", sizeof (ar_hdr.ar_uid));
428 memcpy (ar_hdr.ar_gid, "0 ", sizeof (ar_hdr.ar_gid));
429 memcpy (ar_hdr.ar_mode, "0 ", sizeof (ar_hdr.ar_mode));
430
431 /* See comment for ar_date above. */
432 memcpy (ar_hdr.ar_size, tmpbuf,
433 snprintf (tmpbuf, sizeof (tmpbuf), "%-*zu",
434 (int) sizeof (ar_hdr.ar_size),
435 offs_len + names_len));
436 memcpy (ar_hdr.ar_fmag, ARFMAG, sizeof (ar_hdr.ar_fmag));
437
438 /* Fill in the number of offsets now. */
439 offs_arr[0] = le_bswap_32 (offs_len / sizeof (uint32_t) - 1);
440
441 /* Adjust the offset values for the name index size (if
442 necessary). */
443 off_t disp = (offs_len + ((names_len + 1) & ~1ul)
444 - ((index_size + 1) & ~1ul));
445 /* If there was no index so far but one is needed now we
446 have to take the archive header into account. */
447 if (index_off == -1 && names_len != 0)
448 disp += sizeof (struct ar_hdr);
449 if (disp != 0)
450 for (size_t cnt = 1; cnt < offs_len / sizeof (uint32_t); ++cnt)
451 {
452 uint32_t val;
453 val = le_bswap_32 (offs_arr[cnt]);
454
455 if (val > index_off)
456 {
457 val += disp;
458 offs_arr[cnt] = le_bswap_32 (val);
459 }
460 }
461
462 /* Create the new file. There are three parts as far we are
463 concerned: 1. original context before the index, 2. the
464 new index, 3. everything after the new index. */
465 off_t rest_off;
466 if (index_off != -1)
467 rest_off = (index_off + sizeof (struct ar_hdr)
468 + ((index_size + 1) & ~1ul));
469 else
470 rest_off = SARMAG;
471
472 if ((index_off > SARMAG
473 && copy_content (fd, newfd, SARMAG, index_off - SARMAG))
474 || (names_len != 0
475 && ((write_retry (newfd, &ar_hdr, sizeof (ar_hdr))
476 != sizeof (ar_hdr))
477 || (write_retry (newfd, offs_arr, offs_len)
478 != (ssize_t) offs_len)
479 || (write_retry (newfd, names_str, names_len)
480 != (ssize_t) names_len)))
481 || copy_content (fd, newfd, rest_off, st.st_size - rest_off)
482 /* Set the mode of the new file to the same values the
483 original file has. */
484 || fchmod (newfd, st.st_mode & ALLPERMS) != 0
485 || fchown (newfd, st.st_uid, st.st_gid) != 0
486 || close (newfd) != 0
487 || (newfd = -1, rename (tmpfname, fname) != 0))
488 goto nonew_unlink;
489 }
490 }
491
492 obstack_free (&offsob, NULL);
493 obstack_free (&namesob, NULL);
494
495 close (fd);
496
497 return 0;
498}