/* Generate an index to speed access to archives. Copyright (C) 2005, 2006 Red Hat, Inc. This file is part of Red Hat elfutils. Written by Ulrich Drepper , 2005. Red Hat elfutils is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; version 2 of the License. Red Hat elfutils is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with Red Hat elfutils; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston MA 02110-1301 USA. Red Hat elfutils is an included package of the Open Invention Network. An included package of the Open Invention Network is a package for which Open Invention Network licensees cross-license their patents. No patent license is granted, either expressly or impliedly, by designation as an included package. Should you wish to participate in the Open Invention Network licensing program, please visit www.openinventionnetwork.com . */ #ifdef HAVE_CONFIG_H # include #endif #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #if __BYTE_ORDER == __LITTLE_ENDIAN # define le_bswap_32(val) bswap_32 (val) #else # define le_bswap_32(val) (val) #endif /* Prototypes for local functions. */ static int handle_file (const char *fname); /* Name and version of program. */ static void print_version (FILE *stream, struct argp_state *state); void (*argp_program_version_hook) (FILE *, struct argp_state *) = print_version; /* Bug report address. */ const char *argp_program_bug_address = PACKAGE_BUGREPORT; /* Definitions of arguments for argp functions. */ static const struct argp_option options[] = { { NULL, 0, NULL, 0, NULL, 0 } }; /* Short description of program. */ static const char doc[] = N_("Generate an index to speed access to archives."); /* Strings for arguments in help texts. */ static const char args_doc[] = N_("ARCHIVE"); /* Prototype for option handler. */ static error_t parse_opt (int key, char *arg, struct argp_state *state); /* Data structure to communicate with argp functions. */ static struct argp argp = { options, parse_opt, args_doc, doc, NULL, NULL, NULL }; int main (int argc, char *argv[]) { /* Make memory leak detection possible. */ mtrace (); /* We use no threads here which can interfere with handling a stream. */ (void) __fsetlocking (stdin, FSETLOCKING_BYCALLER); (void) __fsetlocking (stdout, FSETLOCKING_BYCALLER); (void) __fsetlocking (stderr, FSETLOCKING_BYCALLER); /* Set locale. */ (void) setlocale (LC_ALL, ""); /* Make sure the message catalog can be found. */ (void) bindtextdomain (PACKAGE, LOCALEDIR); /* Initialize the message catalog. */ (void) textdomain (PACKAGE); /* Parse and process arguments. */ int remaining; (void) argp_parse (&argp, argc, argv, ARGP_IN_ORDER, &remaining, NULL); /* Tell the library which version we are expecting. */ (void) elf_version (EV_CURRENT); /* There must at least be one more parameter specifying the archive. */ if (remaining == argc) { error (0, 0, gettext ("Archive name required")); argp_help (&argp, stderr, ARGP_HELP_SEE, "ranlib"); exit (EXIT_FAILURE); } /* We accept the names of multiple archives. */ int status = 0; do status |= handle_file (argv[remaining]); while (++remaining < argc); return status; } /* Print the version information. */ static void print_version (FILE *stream, struct argp_state *state __attribute__ ((unused))) { fprintf (stream, "ranlib (%s) %s\n", PACKAGE_NAME, VERSION); fprintf (stream, gettext ("\ Copyright (C) %s Red Hat, Inc.\n\ This is free software; see the source for copying conditions. There is NO\n\ warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.\n\ "), "2006"); fprintf (stream, gettext ("Written by %s.\n"), "Ulrich Drepper"); } /* Handle program arguments. */ static error_t parse_opt (int key, char *arg __attribute__ ((unused)), struct argp_state *state __attribute__ ((unused))) { switch (key) { default: return ARGP_ERR_UNKNOWN; } return 0; } static struct obstack offsob; size_t offs_len; static struct obstack namesob; size_t names_len; /* Add all exported, defined symbols from the given section to the table. */ static void add_symbols (Elf *elf, const char *fname, off_t off, Elf_Scn *scn, GElf_Shdr *shdr) { if (sizeof (off) > sizeof (uint32_t) && off > ~((uint32_t) 0)) /* The archive is too big. */ error (EXIT_FAILURE, 0, gettext ("the archive '%s' is too large"), fname); Elf_Data *data = elf_getdata (scn, NULL); assert (data->d_size == shdr->sh_size); /* We can skip the local symbols in the table. */ for (int cnt = shdr->sh_info; cnt < (int) (shdr->sh_size / shdr->sh_entsize); ++cnt) { GElf_Sym sym_mem; GElf_Sym *sym = gelf_getsym (data, cnt, &sym_mem); if (sym == NULL) /* Should never happen. */ continue; /* Ignore undefined symbols. */ if (sym->st_shndx == SHN_UNDEF) continue; /* For all supported platforms the following is true. */ assert (sizeof (uint32_t) == sizeof (int)); obstack_int_grow (&offsob, (int) le_bswap_32 (off)); offs_len += sizeof (uint32_t); const char *symname = elf_strptr (elf, shdr->sh_link, sym->st_name); size_t symname_len = strlen (symname) + 1; obstack_grow (&namesob, symname, symname_len); names_len += symname_len; } } /* Look through ELF file and collect all symbols available for linking. If available, we use the dynamic symbol section. Otherwise the normal one. Relocatable files are allowed to have multiple symbol tables. */ static void handle_elf (Elf *elf, const char *arfname, off_t off) { GElf_Ehdr ehdr_mem; GElf_Ehdr *ehdr = gelf_getehdr (elf, &ehdr_mem); assert (ehdr != NULL); if (ehdr->e_type == ET_REL) { Elf_Scn *scn = NULL; while ((scn = elf_nextscn (elf, scn)) != NULL) { GElf_Shdr shdr_mem; GElf_Shdr *shdr = gelf_getshdr (scn, &shdr_mem); if (shdr != NULL && shdr->sh_type == SHT_SYMTAB) add_symbols (elf, arfname, off, scn, shdr); } } else { Elf_Scn *symscn = NULL; GElf_Shdr *symshdr = NULL; Elf_Scn *scn = NULL; GElf_Shdr shdr_mem; while ((scn = elf_nextscn (elf, scn)) != NULL) { GElf_Shdr *shdr = gelf_getshdr (scn, &shdr_mem); symshdr = NULL; if (shdr != NULL) { if (shdr->sh_type == SHT_DYNSYM) { symscn = scn; symshdr = shdr; break; } if (shdr->sh_type == SHT_SYMTAB) { /* There better be only one symbol table in executables in DSOs. */ assert (symscn == NULL); symscn = scn; symshdr = shdr; } } } add_symbols (elf, arfname, off, symscn, symshdr ?: gelf_getshdr (scn, &shdr_mem)); } } static int copy_content (int oldfd, int newfd, off_t off, size_t n) { while (n > 0) { char buf[32768]; ssize_t nread = pread_retry (oldfd, buf, MIN (sizeof (buf), n), off); if (unlikely (nread <= 0)) return 1; if (write_retry (newfd, buf, nread) != nread) return 1; n -= nread; off += nread; } return 0; } /* Handle a file given on the command line. */ static int handle_file (const char *fname) { int fd = open (fname, O_RDONLY); if (fd == -1) { error (0, errno, gettext ("cannot open '%s'"), fname); return 1; } struct stat st; if (fstat (fd, &st) != 0) { error (0, errno, gettext ("cannot stat '%s'"), fname); close (fd); return 1; } /* First we walk through the file, looking for all ELF files to collect symbols from. */ Elf *arelf = elf_begin (fd, ELF_C_READ_MMAP, NULL); if (arelf == NULL) { error (0, 0, gettext ("cannot create ELF descriptor for '%s': %s"), fname, elf_errmsg (-1)); close (fd); return 1; } if (elf_kind (arelf) != ELF_K_AR) { error (0, 0, gettext ("'%s' is no archive"), fname); elf_end (arelf); close (fd); return 1; } #define obstack_chunk_alloc xmalloc #define obstack_chunk_free free obstack_init (&offsob); offs_len = 0; obstack_init (&namesob); names_len = 0; /* The first word in the offset table specifies the size. Create such an entry now. The real value will be filled-in later. For all supported platforms the following is true. */ assert (sizeof (uint32_t) == sizeof (int)); obstack_int_grow (&offsob, 0); offs_len += sizeof (uint32_t); /* Iterate over the content of the archive. */ off_t index_off = -1; size_t index_size = 0; Elf *elf; Elf_Cmd cmd = ELF_C_READ_MMAP; while ((elf = elf_begin (fd, cmd, arelf)) != NULL) { Elf_Arhdr *arhdr = elf_getarhdr (elf); assert (arhdr != NULL); off_t off = elf_getaroff (elf); Elf_Kind kind = elf_kind (elf); if (kind == ELF_K_ELF) handle_elf (elf, fname, off); #if 0 else if (kind == ELF_K_AR) { // XXX Should we implement this? } #endif /* If this is the index, remember the location. */ else if (strcmp (arhdr->ar_name, "/") == 0) { index_off = off; index_size = arhdr->ar_size; } /* Get next archive element. */ cmd = elf_next (elf); if (elf_end (elf) != 0) error (0, 0, gettext (" error while freeing sub-ELF descriptor: %s\n"), elf_errmsg (-1)); } elf_end (arelf); uint32_t *offs_arr = obstack_finish (&offsob); assert (offs_len % sizeof (uint32_t) == 0); if ((names_len & 1) != 0) { /* Add one more NUL byte to make length even. */ obstack_grow (&namesob, "", 1); ++names_len; } const char *names_str = obstack_finish (&namesob); /* If the file contains no symbols we need not do anything. */ if (names_len != 0 /* We have to rewrite the file also if it initially had an index but now does not need one anymore. */ || (names_len == 0 && index_off != -1)) { /* Create a new, temporary file in the same directory as the original file. */ char tmpfname[strlen (fname) + 8]; strcpy (stpcpy (tmpfname, fname), ".XXXXXX"); int newfd = mkstemp (tmpfname); if (newfd == -1) nonew: error (0, errno, gettext ("cannot create new file")); else { /* Create the header. */ if (write_retry (newfd, ARMAG, SARMAG) != SARMAG) { // XXX Use /prof/self/fd/%d ??? nonew_unlink: unlink (tmpfname); if (newfd != -1) close (newfd); goto nonew; } struct ar_hdr ar_hdr; memcpy (ar_hdr.ar_name, "/ ", sizeof (ar_hdr.ar_name)); /* Using snprintf here has a problem: the call always wants to add a NUL byte. We could use a trick whereby we specify the target buffer size longer than it is and this would not actually fail, since all the fields are consecutive and we fill them in in sequence (i.e., the NUL byte gets overwritten). But _FORTIFY_SOURCE=2 would not let us play these games. Therefore we play it safe. */ char tmpbuf[MAX (sizeof (ar_hdr.ar_date), sizeof (ar_hdr.ar_size)) + 1]; memcpy (ar_hdr.ar_date, tmpbuf, snprintf (tmpbuf, sizeof (tmpbuf), "%-*lld", (int) sizeof (ar_hdr.ar_date), (long long int) time (NULL))); /* Note the string for the ar_uid and ar_gid cases is longer than necessary. This does not matter since we copy only as much as necessary but it helps the compiler to use the same string for the ar_mode case. */ memcpy (ar_hdr.ar_uid, "0 ", sizeof (ar_hdr.ar_uid)); memcpy (ar_hdr.ar_gid, "0 ", sizeof (ar_hdr.ar_gid)); memcpy (ar_hdr.ar_mode, "0 ", sizeof (ar_hdr.ar_mode)); /* See comment for ar_date above. */ memcpy (ar_hdr.ar_size, tmpbuf, snprintf (tmpbuf, sizeof (tmpbuf), "%-*zu", (int) sizeof (ar_hdr.ar_size), offs_len + names_len)); memcpy (ar_hdr.ar_fmag, ARFMAG, sizeof (ar_hdr.ar_fmag)); /* Fill in the number of offsets now. */ offs_arr[0] = le_bswap_32 (offs_len / sizeof (uint32_t) - 1); /* Adjust the offset values for the name index size (if necessary). */ off_t disp = (offs_len + ((names_len + 1) & ~1ul) - ((index_size + 1) & ~1ul)); /* If there was no index so far but one is needed now we have to take the archive header into account. */ if (index_off == -1 && names_len != 0) disp += sizeof (struct ar_hdr); if (disp != 0) for (size_t cnt = 1; cnt < offs_len / sizeof (uint32_t); ++cnt) { uint32_t val; val = le_bswap_32 (offs_arr[cnt]); if (val > index_off) { val += disp; offs_arr[cnt] = le_bswap_32 (val); } } /* Create the new file. There are three parts as far we are concerned: 1. original context before the index, 2. the new index, 3. everything after the new index. */ off_t rest_off; if (index_off != -1) rest_off = (index_off + sizeof (struct ar_hdr) + ((index_size + 1) & ~1ul)); else rest_off = SARMAG; if ((index_off > SARMAG && copy_content (fd, newfd, SARMAG, index_off - SARMAG)) || (names_len != 0 && ((write_retry (newfd, &ar_hdr, sizeof (ar_hdr)) != sizeof (ar_hdr)) || (write_retry (newfd, offs_arr, offs_len) != (ssize_t) offs_len) || (write_retry (newfd, names_str, names_len) != (ssize_t) names_len))) || copy_content (fd, newfd, rest_off, st.st_size - rest_off) /* Set the mode of the new file to the same values the original file has. */ || fchmod (newfd, st.st_mode & ALLPERMS) != 0 || fchown (newfd, st.st_uid, st.st_gid) != 0 || close (newfd) != 0 || (newfd = -1, rename (tmpfname, fname) != 0)) goto nonew_unlink; } } obstack_free (&offsob, NULL); obstack_free (&namesob, NULL); close (fd); return 0; }