diff options
Diffstat (limited to 'tools')
-rw-r--r-- | tools/debugedit.c | 1661 | ||||
-rw-r--r-- | tools/elfdeps.c | 299 | ||||
-rw-r--r-- | tools/hashtab.c | 523 | ||||
-rw-r--r-- | tools/hashtab.h | 143 | ||||
-rw-r--r-- | tools/javadeps.c | 1288 | ||||
-rw-r--r-- | tools/rpmdeps.c | 105 | ||||
-rw-r--r-- | tools/rpmgraph.c | 258 |
7 files changed, 4277 insertions, 0 deletions
diff --git a/tools/debugedit.c b/tools/debugedit.c new file mode 100644 index 0000000..d1b9335 --- /dev/null +++ b/tools/debugedit.c @@ -0,0 +1,1661 @@ +/* Copyright (C) 2001, 2002, 2003, 2005, 2007, 2009, 2010, 2011 Red Hat, Inc. + Written by Alexander Larsson <alexl@redhat.com>, 2002 + Based on code by Jakub Jelinek <jakub@redhat.com>, 2001. + + This program 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; either version 2, or (at your option) + any later version. + + This program 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 this program; if not, write to the Free Software Foundation, + Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. */ + +#include "system.h" + +/* Needed for libelf */ +#define _FILE_OFFSET_BITS 64 + +#include <assert.h> +#include <byteswap.h> +#include <endian.h> +#include <errno.h> +#include <error.h> +#include <limits.h> +#include <string.h> +#include <stdlib.h> +#include <stdint.h> +#include <unistd.h> +#include <sys/types.h> +#include <sys/stat.h> +#include <fcntl.h> +#include <popt.h> + +#include <gelf.h> +#include <dwarf.h> + +#include <rpm/rpmio.h> +#include <rpm/rpmpgp.h> +#include "tools/hashtab.h" + +#define DW_TAG_partial_unit 0x3c +#define DW_FORM_sec_offset 0x17 +#define DW_FORM_exprloc 0x18 +#define DW_FORM_flag_present 0x19 +#define DW_FORM_ref_sig8 0x20 + +char *base_dir = NULL; +char *dest_dir = NULL; +char *list_file = NULL; +int list_file_fd = -1; +int do_build_id = 0; + +typedef struct +{ + Elf *elf; + GElf_Ehdr ehdr; + Elf_Scn **scn; + const char *filename; + int lastscn; + GElf_Shdr shdr[0]; +} DSO; + +typedef struct +{ + unsigned char *ptr; + uint32_t addend; +} REL; + +#define read_uleb128(ptr) ({ \ + unsigned int ret = 0; \ + unsigned int c; \ + int shift = 0; \ + do \ + { \ + c = *ptr++; \ + ret |= (c & 0x7f) << shift; \ + shift += 7; \ + } while (c & 0x80); \ + \ + if (shift >= 35) \ + ret = UINT_MAX; \ + ret; \ +}) + +static uint16_t (*do_read_16) (unsigned char *ptr); +static uint32_t (*do_read_32) (unsigned char *ptr); +static void (*write_32) (unsigned char *ptr, GElf_Addr val); + +static int ptr_size; +static int cu_version; + +static inline uint16_t +buf_read_ule16 (unsigned char *data) +{ + return data[0] | (data[1] << 8); +} + +static inline uint16_t +buf_read_ube16 (unsigned char *data) +{ + return data[1] | (data[0] << 8); +} + +static inline uint32_t +buf_read_ule32 (unsigned char *data) +{ + return data[0] | (data[1] << 8) | (data[2] << 16) | (data[3] << 24); +} + +static inline uint32_t +buf_read_ube32 (unsigned char *data) +{ + return data[3] | (data[2] << 8) | (data[1] << 16) | (data[0] << 24); +} + +static const char * +strptr (DSO *dso, int sec, off_t offset) +{ + Elf_Scn *scn; + Elf_Data *data; + + scn = dso->scn[sec]; + if (offset >= 0 && (GElf_Addr) offset < dso->shdr[sec].sh_size) + { + data = NULL; + while ((data = elf_rawdata (scn, data)) != NULL) + { + if (data->d_buf + && offset >= data->d_off + && offset < data->d_off + data->d_size) + return (const char *) data->d_buf + (offset - data->d_off); + } + } + + return NULL; +} + + +#define read_1(ptr) *ptr++ + +#define read_16(ptr) ({ \ + uint16_t ret = do_read_16 (ptr); \ + ptr += 2; \ + ret; \ +}) + +#define read_32(ptr) ({ \ + uint32_t ret = do_read_32 (ptr); \ + ptr += 4; \ + ret; \ +}) + +REL *relptr, *relend; +int reltype; + +#define do_read_32_relocated(ptr) ({ \ + uint32_t dret = do_read_32 (ptr); \ + if (relptr) \ + { \ + while (relptr < relend && relptr->ptr < ptr) \ + ++relptr; \ + if (relptr < relend && relptr->ptr == ptr) \ + { \ + if (reltype == SHT_REL) \ + dret += relptr->addend; \ + else \ + dret = relptr->addend; \ + } \ + } \ + dret; \ +}) + +#define read_32_relocated(ptr) ({ \ + uint32_t ret = do_read_32_relocated (ptr); \ + ptr += 4; \ + ret; \ +}) + +static void +dwarf2_write_le32 (unsigned char *p, GElf_Addr val) +{ + uint32_t v = (uint32_t) val; + + p[0] = v; + p[1] = v >> 8; + p[2] = v >> 16; + p[3] = v >> 24; +} + + +static void +dwarf2_write_be32 (unsigned char *p, GElf_Addr val) +{ + uint32_t v = (uint32_t) val; + + p[3] = v; + p[2] = v >> 8; + p[1] = v >> 16; + p[0] = v >> 24; +} + +static struct + { + const char *name; + unsigned char *data; + Elf_Data *elf_data; + size_t size; + int sec, relsec; + } debug_sections[] = + { +#define DEBUG_INFO 0 +#define DEBUG_ABBREV 1 +#define DEBUG_LINE 2 +#define DEBUG_ARANGES 3 +#define DEBUG_PUBNAMES 4 +#define DEBUG_PUBTYPES 5 +#define DEBUG_MACINFO 6 +#define DEBUG_LOC 7 +#define DEBUG_STR 8 +#define DEBUG_FRAME 9 +#define DEBUG_RANGES 10 +#define DEBUG_TYPES 11 + { ".debug_info", NULL, NULL, 0, 0, 0 }, + { ".debug_abbrev", NULL, NULL, 0, 0, 0 }, + { ".debug_line", NULL, NULL, 0, 0, 0 }, + { ".debug_aranges", NULL, NULL, 0, 0, 0 }, + { ".debug_pubnames", NULL, NULL, 0, 0, 0 }, + { ".debug_pubtypes", NULL, NULL, 0, 0, 0 }, + { ".debug_macinfo", NULL, NULL, 0, 0, 0 }, + { ".debug_loc", NULL, NULL, 0, 0, 0 }, + { ".debug_str", NULL, NULL, 0, 0, 0 }, + { ".debug_frame", NULL, NULL, 0, 0, 0 }, + { ".debug_ranges", NULL, NULL, 0, 0, 0 }, + { ".debug_types", NULL, NULL, 0, 0, 0 }, + { NULL, NULL, NULL, 0, 0, 0 } + }; + +struct abbrev_attr + { + unsigned int attr; + unsigned int form; + }; + +struct abbrev_tag + { + unsigned int entry; + unsigned int tag; + int nattr; + struct abbrev_attr attr[0]; + }; + +static hashval_t +abbrev_hash (const void *p) +{ + struct abbrev_tag *t = (struct abbrev_tag *)p; + + return t->entry; +} + +static int +abbrev_eq (const void *p, const void *q) +{ + struct abbrev_tag *t1 = (struct abbrev_tag *)p; + struct abbrev_tag *t2 = (struct abbrev_tag *)q; + + return t1->entry == t2->entry; +} + +static void +abbrev_del (void *p) +{ + free (p); +} + +static htab_t +read_abbrev (DSO *dso, unsigned char *ptr) +{ + htab_t h = htab_try_create (50, abbrev_hash, abbrev_eq, abbrev_del); + unsigned int attr, form; + struct abbrev_tag *t; + int size; + void **slot; + + if (h == NULL) + { +no_memory: + error (0, ENOMEM, "%s: Could not read .debug_abbrev", dso->filename); + if (h) + htab_delete (h); + return NULL; + } + + while ((attr = read_uleb128 (ptr)) != 0) + { + size = 10; + t = malloc (sizeof (*t) + size * sizeof (struct abbrev_attr)); + if (t == NULL) + goto no_memory; + t->entry = attr; + t->nattr = 0; + slot = htab_find_slot (h, t, INSERT); + if (slot == NULL) + { + free (t); + goto no_memory; + } + if (*slot != NULL) + { + error (0, 0, "%s: Duplicate DWARF abbreviation %d", dso->filename, + t->entry); + free (t); + htab_delete (h); + return NULL; + } + t->tag = read_uleb128 (ptr); + ++ptr; /* skip children flag. */ + while ((attr = read_uleb128 (ptr)) != 0) + { + if (t->nattr == size) + { + size += 10; + t = realloc (t, sizeof (*t) + size * sizeof (struct abbrev_attr)); + if (t == NULL) + goto no_memory; + } + form = read_uleb128 (ptr); + if (form == 2 + || (form > DW_FORM_flag_present && form != DW_FORM_ref_sig8)) + { + error (0, 0, "%s: Unknown DWARF DW_FORM_%d", dso->filename, form); + htab_delete (h); + return NULL; + } + + t->attr[t->nattr].attr = attr; + t->attr[t->nattr++].form = form; + } + if (read_uleb128 (ptr) != 0) + { + error (0, 0, "%s: DWARF abbreviation does not end with 2 zeros", + dso->filename); + htab_delete (h); + return NULL; + } + *slot = t; + } + + return h; +} + +#define IS_DIR_SEPARATOR(c) ((c)=='/') + +static char * +canonicalize_path (const char *s, char *d) +{ + char *rv = d; + const char *sroot; + char *droot; + + if (IS_DIR_SEPARATOR (*s)) + { + *d++ = *s++; + if (IS_DIR_SEPARATOR (*s) && !IS_DIR_SEPARATOR (s[1])) + { + /* Special case for "//foo" meaning a Posix namespace + escape. */ + *d++ = *s++; + } + while (IS_DIR_SEPARATOR (*s)) + s++; + } + droot = d; + sroot = s; + + while (*s) + { + /* At this point, we're always at the beginning of a path + segment. */ + + if (s[0] == '.' && (s[1] == 0 || IS_DIR_SEPARATOR (s[1]))) + { + s++; + if (*s) + while (IS_DIR_SEPARATOR (*s)) + ++s; + } + + else if (s[0] == '.' && s[1] == '.' + && (s[2] == 0 || IS_DIR_SEPARATOR (s[2]))) + { + char *pre = d - 1; /* includes slash */ + while (droot < pre && IS_DIR_SEPARATOR (*pre)) + pre--; + if (droot <= pre && ! IS_DIR_SEPARATOR (*pre)) + { + while (droot < pre && ! IS_DIR_SEPARATOR (*pre)) + pre--; + /* pre now points to the slash */ + if (droot < pre) + pre++; + if (pre + 3 == d && pre[0] == '.' && pre[1] == '.') + { + *d++ = *s++; + *d++ = *s++; + } + else + { + d = pre; + s += 2; + if (*s) + while (IS_DIR_SEPARATOR (*s)) + s++; + } + } + else + { + *d++ = *s++; + *d++ = *s++; + } + } + else + { + while (*s && ! IS_DIR_SEPARATOR (*s)) + *d++ = *s++; + } + + if (IS_DIR_SEPARATOR (*s)) + { + *d++ = *s++; + while (IS_DIR_SEPARATOR (*s)) + s++; + } + } + while (droot < d && IS_DIR_SEPARATOR (d[-1])) + --d; + if (d == rv) + *d++ = '.'; + *d = 0; + + return rv; +} + +static int +has_prefix (const char *str, + const char *prefix) +{ + size_t str_len; + size_t prefix_len; + + str_len = strlen (str); + prefix_len = strlen (prefix); + + if (str_len < prefix_len) + return 0; + + return strncmp (str, prefix, prefix_len) == 0; +} + +static int dirty_elf; +static void +dirty_section (unsigned int sec) +{ + elf_flagdata (debug_sections[sec].elf_data, ELF_C_SET, ELF_F_DIRTY); + dirty_elf = 1; +} + +static int +edit_dwarf2_line (DSO *dso, uint32_t off, char *comp_dir, int phase) +{ + unsigned char *ptr = debug_sections[DEBUG_LINE].data, *dir; + unsigned char **dirt; + unsigned char *endsec = ptr + debug_sections[DEBUG_LINE].size; + unsigned char *endcu, *endprol; + unsigned char opcode_base; + uint32_t value, dirt_cnt; + size_t comp_dir_len = strlen (comp_dir); + size_t abs_file_cnt = 0, abs_dir_cnt = 0; + + if (phase != 0) + return 0; + + ptr += off; + + endcu = ptr + 4; + endcu += read_32 (ptr); + if (endcu == ptr + 0xffffffff) + { + error (0, 0, "%s: 64-bit DWARF not supported", dso->filename); + return 1; + } + + if (endcu > endsec) + { + error (0, 0, "%s: .debug_line CU does not fit into section", + dso->filename); + return 1; + } + + value = read_16 (ptr); + if (value != 2 && value != 3 && value != 4) + { + error (0, 0, "%s: DWARF version %d unhandled", dso->filename, + value); + return 1; + } + + endprol = ptr + 4; + endprol += read_32 (ptr); + if (endprol > endcu) + { + error (0, 0, "%s: .debug_line CU prologue does not fit into CU", + dso->filename); + return 1; + } + + opcode_base = ptr[4 + (value >= 4)]; + ptr = dir = ptr + 4 + (value >= 4) + opcode_base; + + /* dir table: */ + value = 1; + while (*ptr != 0) + { + ptr = (unsigned char *) strchr ((char *)ptr, 0) + 1; + ++value; + } + + dirt = (unsigned char **) alloca (value * sizeof (unsigned char *)); + dirt[0] = (unsigned char *) "."; + dirt_cnt = 1; + ptr = dir; + while (*ptr != 0) + { + dirt[dirt_cnt++] = ptr; + ptr = (unsigned char *) strchr ((char *)ptr, 0) + 1; + } + ptr++; + + /* file table: */ + while (*ptr != 0) + { + char *s, *file; + size_t file_len, dir_len; + + file = (char *) ptr; + ptr = (unsigned char *) strchr ((char *)ptr, 0) + 1; + value = read_uleb128 (ptr); + + if (value >= dirt_cnt) + { + error (0, 0, "%s: Wrong directory table index %u", + dso->filename, value); + return 1; + } + file_len = strlen (file); + dir_len = strlen ((char *)dirt[value]); + s = malloc (comp_dir_len + 1 + file_len + 1 + dir_len + 1); + if (s == NULL) + { + error (0, ENOMEM, "%s: Reading file table", dso->filename); + return 1; + } + if (*file == '/') + { + memcpy (s, file, file_len + 1); + if (dest_dir && has_prefix (file, base_dir)) + ++abs_file_cnt; + } + else if (*dirt[value] == '/') + { + memcpy (s, dirt[value], dir_len); + s[dir_len] = '/'; + memcpy (s + dir_len + 1, file, file_len + 1); + } + else + { + char *p = s; + if (comp_dir_len != 0) + { + memcpy (s, comp_dir, comp_dir_len); + s[comp_dir_len] = '/'; + p += comp_dir_len + 1; + } + memcpy (p, dirt[value], dir_len); + p[dir_len] = '/'; + memcpy (p + dir_len + 1, file, file_len + 1); + } + canonicalize_path (s, s); + if (list_file_fd != -1) + { + char *p = NULL; + if (base_dir == NULL) + p = s; + else if (has_prefix (s, base_dir)) + p = s + strlen (base_dir); + else if (has_prefix (s, dest_dir)) + p = s + strlen (dest_dir); + + if (p) + { + size_t size = strlen (p) + 1; + while (size > 0) + { + ssize_t ret = write (list_file_fd, p, size); + if (ret == -1) + break; + size -= ret; + p += ret; + } + } + } + + free (s); + + read_uleb128 (ptr); + read_uleb128 (ptr); + } + ++ptr; + + if (dest_dir) + { + unsigned char *srcptr, *buf = NULL; + size_t base_len = strlen (base_dir); + size_t dest_len = strlen (dest_dir); + size_t shrank = 0; + + if (dest_len == base_len) + abs_file_cnt = 0; + if (abs_file_cnt) + { + srcptr = buf = malloc (ptr - dir); + memcpy (srcptr, dir, ptr - dir); + ptr = dir; + } + else + ptr = srcptr = dir; + while (*srcptr != 0) + { + size_t len = strlen ((char *)srcptr) + 1; + const unsigned char *readptr = srcptr; + + char *orig = strdup ((const char *) srcptr); + + if (*srcptr == '/' && has_prefix ((char *)srcptr, base_dir)) + { + if (dest_len < base_len) + ++abs_dir_cnt; + memcpy (ptr, dest_dir, dest_len); + ptr += dest_len; + readptr += base_len; + } + srcptr += len; + + shrank += srcptr - readptr; + canonicalize_path ((char *)readptr, (char *)ptr); + len = strlen ((char *)ptr) + 1; + shrank -= len; + ptr += len; + + if (memcmp (orig, ptr - len, len)) + dirty_section (DEBUG_STR); + free (orig); + } + + if (shrank > 0) + { + if (--shrank == 0) + error (EXIT_FAILURE, 0, + "canonicalization unexpectedly shrank by one character"); + else + { + memset (ptr, 'X', shrank); + ptr += shrank; + *ptr++ = '\0'; + } + } + + if (abs_dir_cnt + abs_file_cnt != 0) + { + size_t len = (abs_dir_cnt + abs_file_cnt) * (base_len - dest_len); + + if (len == 1) + error (EXIT_FAILURE, 0, "-b arg has to be either the same length as -d arg, or more than 1 char longer"); + memset (ptr, 'X', len - 1); + ptr += len - 1; + *ptr++ = '\0'; + } + *ptr++ = '\0'; + ++srcptr; + + while (*srcptr != 0) + { + size_t len = strlen ((char *)srcptr) + 1; + + if (*srcptr == '/' && has_prefix ((char *)srcptr, base_dir)) + { + memcpy (ptr, dest_dir, dest_len); + if (dest_len < base_len) + { + memmove (ptr + dest_len, srcptr + base_len, + len - base_len); + ptr += dest_len - base_len; + } + dirty_section (DEBUG_STR); + } + else if (ptr != srcptr) + memmove (ptr, srcptr, len); + srcptr += len; + ptr += len; + dir = srcptr; + read_uleb128 (srcptr); + read_uleb128 (srcptr); + read_uleb128 (srcptr); + if (ptr != dir) + memmove (ptr, dir, srcptr - dir); + ptr += srcptr - dir; + } + *ptr = '\0'; + free (buf); + } + return 0; +} + +static unsigned char * +edit_attributes (DSO *dso, unsigned char *ptr, struct abbrev_tag *t, int phase) +{ + int i; + uint32_t list_offs; + int found_list_offs; + char *comp_dir; + + comp_dir = NULL; + list_offs = 0; + found_list_offs = 0; + for (i = 0; i < t->nattr; ++i) + { + uint32_t form = t->attr[i].form; + size_t len = 0; + size_t base_len, dest_len; + + while (1) + { + if (t->attr[i].attr == DW_AT_stmt_list) + { + if (form == DW_FORM_data4 + || form == DW_FORM_sec_offset) + { + list_offs = do_read_32_relocated (ptr); + found_list_offs = 1; + } + } + + if (t->attr[i].attr == DW_AT_comp_dir) + { + if (form == DW_FORM_string) + { + free (comp_dir); + comp_dir = strdup ((char *)ptr); + + if (phase == 1 && dest_dir && has_prefix ((char *)ptr, base_dir)) + { + base_len = strlen (base_dir); + dest_len = strlen (dest_dir); + + memcpy (ptr, dest_dir, dest_len); + if (dest_len < base_len) + { + memset(ptr + dest_len, '/', + base_len - dest_len); + + } + dirty_section (DEBUG_INFO); + } + } + else if (form == DW_FORM_strp && + debug_sections[DEBUG_STR].data) + { + char *dir; + + dir = (char *) debug_sections[DEBUG_STR].data + + do_read_32_relocated (ptr); + + free (comp_dir); + comp_dir = strdup (dir); + + if (phase == 1 && dest_dir && has_prefix (dir, base_dir)) + { + base_len = strlen (base_dir); + dest_len = strlen (dest_dir); + + memcpy (dir, dest_dir, dest_len); + if (dest_len < base_len) + { + memmove (dir + dest_len, dir + base_len, + strlen (dir + base_len) + 1); + } + dirty_section (DEBUG_STR); + } + } + } + else if ((t->tag == DW_TAG_compile_unit + || t->tag == DW_TAG_partial_unit) + && t->attr[i].attr == DW_AT_name + && form == DW_FORM_strp + && debug_sections[DEBUG_STR].data) + { + char *name; + + name = (char *) debug_sections[DEBUG_STR].data + + do_read_32_relocated (ptr); + if (*name == '/' && comp_dir == NULL) + { + char *enddir = strrchr (name, '/'); + + if (enddir != name) + { + comp_dir = malloc (enddir - name + 1); + memcpy (comp_dir, name, enddir - name); + comp_dir [enddir - name] = '\0'; + } + else + comp_dir = strdup ("/"); + } + + if (phase == 1 && dest_dir && has_prefix (name, base_dir)) + { + base_len = strlen (base_dir); + dest_len = strlen (dest_dir); + + memcpy (name, dest_dir, dest_len); + if (dest_len < base_len) + { + memmove (name + dest_len, name + base_len, + strlen (name + base_len) + 1); + } + dirty_section (DEBUG_STR); + } + } + + switch (form) + { + case DW_FORM_ref_addr: + if (cu_version == 2) + ptr += ptr_size; + else + ptr += 4; + break; + case DW_FORM_flag_present: + break; + case DW_FORM_addr: + ptr += ptr_size; + break; + case DW_FORM_ref1: + case DW_FORM_flag: + case DW_FORM_data1: + ++ptr; + break; + case DW_FORM_ref2: + case DW_FORM_data2: + ptr += 2; + break; + case DW_FORM_ref4: + case DW_FORM_data4: + case DW_FORM_sec_offset: + ptr += 4; + break; + case DW_FORM_ref8: + case DW_FORM_data8: + case DW_FORM_ref_sig8: + ptr += 8; + break; + case DW_FORM_sdata: + case DW_FORM_ref_udata: + case DW_FORM_udata: + read_uleb128 (ptr); + break; + case DW_FORM_strp: + ptr += 4; + break; + case DW_FORM_string: + ptr = (unsigned char *) strchr ((char *)ptr, '\0') + 1; + break; + case DW_FORM_indirect: + form = read_uleb128 (ptr); + continue; + case DW_FORM_block1: + len = *ptr++; + break; + case DW_FORM_block2: + len = read_16 (ptr); + form = DW_FORM_block1; + break; + case DW_FORM_block4: + len = read_32 (ptr); + form = DW_FORM_block1; + break; + case DW_FORM_block: + case DW_FORM_exprloc: + len = read_uleb128 (ptr); + form = DW_FORM_block1; + assert (len < UINT_MAX); + break; + default: + error (0, 0, "%s: Unknown DWARF DW_FORM_%d", dso->filename, + form); + return NULL; + } + + if (form == DW_FORM_block1) + ptr += len; + + break; + } + } + + /* Ensure the CU current directory will exist even if only empty. Source + filenames possibly located in its parent directories refer relatively to + it and the debugger (GDB) cannot safely optimize out the missing + CU current dir subdirectories. */ + if (comp_dir && list_file_fd != -1) + { + char *p; + size_t size; + + if (base_dir && has_prefix (comp_dir, base_dir)) + p = comp_dir + strlen (base_dir); + else if (dest_dir && has_prefix (comp_dir, dest_dir)) + p = comp_dir + strlen (dest_dir); + else + p = comp_dir; + + size = strlen (p) + 1; + while (size > 0) + { + ssize_t ret = write (list_file_fd, p, size); + if (ret == -1) + break; + size -= ret; + p += ret; + } + } + + if (found_list_offs && comp_dir) + edit_dwarf2_line (dso, list_offs, comp_dir, phase); + + free (comp_dir); + + return ptr; +} + +static int +rel_cmp (const void *a, const void *b) +{ + REL *rela = (REL *) a, *relb = (REL *) b; + + if (rela->ptr < relb->ptr) + return -1; + + if (rela->ptr > relb->ptr) + return 1; + + return 0; +} + +static int +edit_dwarf2 (DSO *dso) +{ + Elf_Data *data; + Elf_Scn *scn; + int i, j; + + for (i = 0; debug_sections[i].name; ++i) + { + debug_sections[i].data = NULL; + debug_sections[i].size = 0; + debug_sections[i].sec = 0; + debug_sections[i].relsec = 0; + } + ptr_size = 0; + + for (i = 1; i < dso->ehdr.e_shnum; ++i) + if (! (dso->shdr[i].sh_flags & (SHF_ALLOC | SHF_WRITE | SHF_EXECINSTR)) + && dso->shdr[i].sh_size) + { + const char *name = strptr (dso, dso->ehdr.e_shstrndx, + dso->shdr[i].sh_name); + + if (strncmp (name, ".debug_", sizeof (".debug_") - 1) == 0) + { + for (j = 0; debug_sections[j].name; ++j) + if (strcmp (name, debug_sections[j].name) == 0) + { + if (debug_sections[j].data) + { + error (0, 0, "%s: Found two copies of %s section", + dso->filename, name); + return 1; + } + + scn = dso->scn[i]; + data = elf_rawdata (scn, NULL); + assert (data != NULL && data->d_buf != NULL); + assert (elf_rawdata (scn, data) == NULL); + assert (data->d_off == 0); + assert (data->d_size == dso->shdr[i].sh_size); + debug_sections[j].data = data->d_buf; + debug_sections[j].elf_data = data; + debug_sections[j].size = data->d_size; + debug_sections[j].sec = i; + break; + } + + if (debug_sections[j].name == NULL) + { + error (0, 0, "%s: Unknown debugging section %s", + dso->filename, name); + } + } + else if (dso->ehdr.e_type == ET_REL + && ((dso->shdr[i].sh_type == SHT_REL + && strncmp (name, ".rel.debug_", + sizeof (".rel.debug_") - 1) == 0) + || (dso->shdr[i].sh_type == SHT_RELA + && strncmp (name, ".rela.debug_", + sizeof (".rela.debug_") - 1) == 0))) + { + for (j = 0; debug_sections[j].name; ++j) + if (strcmp (name + sizeof (".rel") - 1 + + (dso->shdr[i].sh_type == SHT_RELA), + debug_sections[j].name) == 0) + { + debug_sections[j].relsec = i; + break; + } + } + } + + if (dso->ehdr.e_ident[EI_DATA] == ELFDATA2LSB) + { + do_read_16 = buf_read_ule16; + do_read_32 = buf_read_ule32; + write_32 = dwarf2_write_le32; + } + else if (dso->ehdr.e_ident[EI_DATA] == ELFDATA2MSB) + { + do_read_16 = buf_read_ube16; + do_read_32 = buf_read_ube32; + write_32 = dwarf2_write_be32; + } + else + { + error (0, 0, "%s: Wrong ELF data enconding", dso->filename); + return 1; + } + + if (debug_sections[DEBUG_INFO].data != NULL) + { + unsigned char *ptr, *endcu, *endsec; + uint32_t value; + htab_t abbrev; + struct abbrev_tag tag, *t; + int phase; + REL *relbuf = NULL; + + if (debug_sections[DEBUG_INFO].relsec) + { + int ndx, maxndx; + GElf_Rel rel; + GElf_Rela rela; + GElf_Sym sym; + GElf_Addr base = dso->shdr[debug_sections[DEBUG_INFO].sec].sh_addr; + Elf_Data *symdata = NULL; + int rtype; + + i = debug_sections[DEBUG_INFO].relsec; + scn = dso->scn[i]; + data = elf_getdata (scn, NULL); + assert (data != NULL && data->d_buf != NULL); + assert (elf_getdata (scn, data) == NULL); + assert (data->d_off == 0); + assert (data->d_size == dso->shdr[i].sh_size); + maxndx = dso->shdr[i].sh_size / dso->shdr[i].sh_entsize; + relbuf = malloc (maxndx * sizeof (REL)); + reltype = dso->shdr[i].sh_type; + if (relbuf == NULL) + error (1, errno, "%s: Could not allocate memory", dso->filename); + + symdata = elf_getdata (dso->scn[dso->shdr[i].sh_link], NULL); + assert (symdata != NULL && symdata->d_buf != NULL); + assert (elf_getdata (dso->scn[dso->shdr[i].sh_link], symdata) + == NULL); + assert (symdata->d_off == 0); + assert (symdata->d_size + == dso->shdr[dso->shdr[i].sh_link].sh_size); + + for (ndx = 0, relend = relbuf; ndx < maxndx; ++ndx) + { + if (dso->shdr[i].sh_type == SHT_REL) + { + gelf_getrel (data, ndx, &rel); + rela.r_offset = rel.r_offset; + rela.r_info = rel.r_info; + rela.r_addend = 0; + } + else + gelf_getrela (data, ndx, &rela); + gelf_getsym (symdata, ELF64_R_SYM (rela.r_info), &sym); + /* Relocations against section symbols are uninteresting + in REL. */ + if (dso->shdr[i].sh_type == SHT_REL && sym.st_value == 0) + continue; + /* Only consider relocations against .debug_str, .debug_line + and .debug_abbrev. */ + if (sym.st_shndx != debug_sections[DEBUG_STR].sec + && sym.st_shndx != debug_sections[DEBUG_LINE].sec + && sym.st_shndx != debug_sections[DEBUG_ABBREV].sec) + continue; + rela.r_addend += sym.st_value; + rtype = ELF64_R_TYPE (rela.r_info); + switch (dso->ehdr.e_machine) + { + case EM_SPARC: + case EM_SPARC32PLUS: + case EM_SPARCV9: + if (rtype != R_SPARC_32 && rtype != R_SPARC_UA32) + goto fail; + break; + case EM_386: + if (rtype != R_386_32) + goto fail; + break; + case EM_PPC: + case EM_PPC64: + if (rtype != R_PPC_ADDR32 && rtype != R_PPC_UADDR32) + goto fail; + break; + case EM_S390: + if (rtype != R_390_32) + goto fail; + break; + case EM_IA_64: + if (rtype != R_IA64_SECREL32LSB) + goto fail; + break; + case EM_X86_64: + if (rtype != R_X86_64_32) + goto fail; + break; + case EM_ALPHA: + if (rtype != R_ALPHA_REFLONG) + goto fail; + break; + default: + fail: + error (1, 0, "%s: Unhandled relocation %d in .debug_info section", + dso->filename, rtype); + } + relend->ptr = debug_sections[DEBUG_INFO].data + + (rela.r_offset - base); + relend->addend = rela.r_addend; + ++relend; + } + if (relbuf == relend) + { + free (relbuf); + relbuf = NULL; + relend = NULL; + } + else + qsort (relbuf, relend - relbuf, sizeof (REL), rel_cmp); + } + + for (phase = 0; phase < 2; phase++) + { + ptr = debug_sections[DEBUG_INFO].data; + relptr = relbuf; + endsec = ptr + debug_sections[DEBUG_INFO].size; + while (ptr < endsec) + { + if (ptr + 11 > endsec) + { + error (0, 0, "%s: .debug_info CU header too small", + dso->filename); + return 1; + } + + endcu = ptr + 4; + endcu += read_32 (ptr); + if (endcu == ptr + 0xffffffff) + { + error (0, 0, "%s: 64-bit DWARF not supported", dso->filename); + return 1; + } + + if (endcu > endsec) + { + error (0, 0, "%s: .debug_info too small", dso->filename); + return 1; + } + + cu_version = read_16 (ptr); + if (cu_version != 2 && cu_version != 3 && cu_version != 4) + { + error (0, 0, "%s: DWARF version %d unhandled", dso->filename, + cu_version); + return 1; + } + + value = read_32_relocated (ptr); + if (value >= debug_sections[DEBUG_ABBREV].size) + { + if (debug_sections[DEBUG_ABBREV].data == NULL) + error (0, 0, "%s: .debug_abbrev not present", dso->filename); + else + error (0, 0, "%s: DWARF CU abbrev offset too large", + dso->filename); + return 1; + } + + if (ptr_size == 0) + { + ptr_size = read_1 (ptr); + if (ptr_size != 4 && ptr_size != 8) + { + error (0, 0, "%s: Invalid DWARF pointer size %d", + dso->filename, ptr_size); + return 1; + } + } + else if (read_1 (ptr) != ptr_size) + { + error (0, 0, "%s: DWARF pointer size differs between CUs", + dso->filename); + return 1; + } + + abbrev = read_abbrev (dso, + debug_sections[DEBUG_ABBREV].data + value); + if (abbrev == NULL) + return 1; + + while (ptr < endcu) + { + tag.entry = read_uleb128 (ptr); + if (tag.entry == 0) + continue; + t = htab_find_with_hash (abbrev, &tag, tag.entry); + if (t == NULL) + { + error (0, 0, "%s: Could not find DWARF abbreviation %d", + dso->filename, tag.entry); + htab_delete (abbrev); + return 1; + } + + ptr = edit_attributes (dso, ptr, t, phase); + if (ptr == NULL) + break; + } + + htab_delete (abbrev); + } + } + free (relbuf); + } + + return 0; +} + +static struct poptOption optionsTable[] = { + { "base-dir", 'b', POPT_ARG_STRING, &base_dir, 0, + "base build directory of objects", NULL }, + { "dest-dir", 'd', POPT_ARG_STRING, &dest_dir, 0, + "directory to rewrite base-dir into", NULL }, + { "list-file", 'l', POPT_ARG_STRING, &list_file, 0, + "file where to put list of source and header file names", NULL }, + { "build-id", 'i', POPT_ARG_NONE, &do_build_id, 0, + "recompute build ID note and print ID on stdout", NULL }, + POPT_AUTOHELP + { NULL, 0, 0, NULL, 0, NULL, NULL } +}; + +static DSO * +fdopen_dso (int fd, const char *name) +{ + Elf *elf = NULL; + GElf_Ehdr ehdr; + int i; + DSO *dso = NULL; + + elf = elf_begin (fd, ELF_C_RDWR_MMAP, NULL); + if (elf == NULL) + { + error (0, 0, "cannot open ELF file: %s", elf_errmsg (-1)); + goto error_out; + } + + if (elf_kind (elf) != ELF_K_ELF) + { + error (0, 0, "\"%s\" is not an ELF file", name); + goto error_out; + } + + if (gelf_getehdr (elf, &ehdr) == NULL) + { + error (0, 0, "cannot get the ELF header: %s", + elf_errmsg (-1)); + goto error_out; + } + + if (ehdr.e_type != ET_DYN && ehdr.e_type != ET_EXEC && ehdr.e_type != ET_REL) + { + error (0, 0, "\"%s\" is not a shared library", name); + goto error_out; + } + + /* Allocate DSO structure. Leave place for additional 20 new section + headers. */ + dso = (DSO *) + malloc (sizeof(DSO) + (ehdr.e_shnum + 20) * sizeof(GElf_Shdr) + + (ehdr.e_shnum + 20) * sizeof(Elf_Scn *)); + if (!dso) + { + error (0, ENOMEM, "Could not open DSO"); + goto error_out; + } + + elf_flagelf (elf, ELF_C_SET, ELF_F_LAYOUT); + + memset (dso, 0, sizeof(DSO)); + dso->elf = elf; + dso->ehdr = ehdr; + dso->scn = (Elf_Scn **) &dso->shdr[ehdr.e_shnum + 20]; + + for (i = 0; i < ehdr.e_shnum; ++i) + { + dso->scn[i] = elf_getscn (elf, i); + gelf_getshdr (dso->scn[i], dso->shdr + i); + } + + dso->filename = (const char *) strdup (name); + return dso; + +error_out: + if (dso) + { + free ((char *) dso->filename); + free (dso); + } + if (elf) + elf_end (elf); + if (fd != -1) + close (fd); + return NULL; +} + +static const pgpHashAlgo algorithms[] = { PGPHASHALGO_MD5, + PGPHASHALGO_SHA1, PGPHASHALGO_SHA256, PGPHASHALGO_SHA384, PGPHASHALGO_SHA512 }; + +/* Compute a fresh build ID bit-string from the editted file contents. */ +static void +handle_build_id (DSO *dso, Elf_Data *build_id, + size_t build_id_offset, size_t build_id_size) +{ + DIGEST_CTX ctx; + pgpHashAlgo algorithm; + int i = sizeof(algorithms)/sizeof(algorithms[0]); + void *digest = NULL; + size_t len; + + while (i-- > 0) + { + algorithm = algorithms[i]; + if (rpmDigestLength(algorithm) == build_id_size) + break; + } + if (i < 0) + { + fprintf (stderr, "Cannot handle %Zu-byte build ID\n", build_id_size); + exit (1); + } + + if (!dirty_elf) + goto print; + + if (elf_update (dso->elf, ELF_C_NULL) < 0) + { + fprintf (stderr, "Failed to update file: %s\n", + elf_errmsg (elf_errno ())); + exit (1); + } + + /* Clear the old bits so they do not affect the new hash. */ + memset ((char *) build_id->d_buf + build_id_offset, 0, build_id_size); + + ctx = rpmDigestInit(algorithm, 0); + + /* Slurp the relevant header bits and section contents and feed them + into the hash function. The only bits we ignore are the offset + fields in ehdr and shdrs, since the semantically identical ELF file + could be written differently if it doesn't change the phdr layout. + We always use the GElf (i.e. Elf64) formats for the bits to hash + since it is convenient. It doesn't matter whether this is an Elf32 + or Elf64 object, only that we are consistent in what bits feed the + hash so it comes out the same for the same file contents. */ + { + union + { + GElf_Ehdr ehdr; + GElf_Phdr phdr; + GElf_Shdr shdr; + } u; + Elf_Data x = { .d_version = EV_CURRENT, .d_buf = &u }; + + x.d_type = ELF_T_EHDR; + x.d_size = sizeof u.ehdr; + u.ehdr = dso->ehdr; + u.ehdr.e_phoff = u.ehdr.e_shoff = 0; + if (elf64_xlatetom (&x, &x, dso->ehdr.e_ident[EI_DATA]) == NULL) + { + bad: + fprintf (stderr, "Failed to compute header checksum: %s\n", + elf_errmsg (elf_errno ())); + exit (1); + } + + x.d_type = ELF_T_PHDR; + x.d_size = sizeof u.phdr; + for (i = 0; i < dso->ehdr.e_phnum; ++i) + { + if (gelf_getphdr (dso->elf, i, &u.phdr) == NULL) + goto bad; + if (elf64_xlatetom (&x, &x, dso->ehdr.e_ident[EI_DATA]) == NULL) + goto bad; + rpmDigestUpdate(ctx, x.d_buf, x.d_size); + } + + x.d_type = ELF_T_SHDR; + x.d_size = sizeof u.shdr; + for (i = 0; i < dso->ehdr.e_shnum; ++i) + if (dso->scn[i] != NULL) + { + u.shdr = dso->shdr[i]; + u.shdr.sh_offset = 0; + if (elf64_xlatetom (&x, &x, dso->ehdr.e_ident[EI_DATA]) == NULL) + goto bad; + rpmDigestUpdate(ctx, x.d_buf, x.d_size); + + if (u.shdr.sh_type != SHT_NOBITS) + { + Elf_Data *d = elf_rawdata (dso->scn[i], NULL); + if (d == NULL) + goto bad; + rpmDigestUpdate(ctx, d->d_buf, d->d_size); + } + } + } + + rpmDigestFinal(ctx, &digest, &len, 0); + memcpy((unsigned char *)build_id->d_buf + build_id_offset, digest, build_id_size); + free(digest); + + elf_flagdata (build_id, ELF_C_SET, ELF_F_DIRTY); + + print: + /* Now format the build ID bits in hex to print out. */ + { + const uint8_t * id = (uint8_t *)build_id->d_buf + build_id_offset; + char *hex = pgpHexStr(id, build_id_size); + puts (hex); + free(hex); + } +} + +int +main (int argc, char *argv[]) +{ + DSO *dso; + int fd, i; + const char *file; + poptContext optCon; /* context for parsing command-line options */ + int nextopt; + const char **args; + struct stat stat_buf; + char *p; + Elf_Data *build_id = NULL; + size_t build_id_offset = 0, build_id_size = 0; + + optCon = poptGetContext("debugedit", argc, (const char **)argv, optionsTable, 0); + + while ((nextopt = poptGetNextOpt (optCon)) > 0 || nextopt == POPT_ERROR_BADOPT) + /* do nothing */ ; + + if (nextopt != -1) + { + fprintf (stderr, "Error on option %s: %s.\nRun '%s --help' to see a full list of available command line options.\n", + poptBadOption (optCon, 0), + poptStrerror (nextopt), + argv[0]); + exit (1); + } + + args = poptGetArgs (optCon); + if (args == NULL || args[0] == NULL || args[1] != NULL) + { + poptPrintHelp(optCon, stdout, 0); + exit (1); + } + + if (dest_dir != NULL) + { + if (base_dir == NULL) + { + fprintf (stderr, "You must specify a base dir if you specify a dest dir\n"); + exit (1); + } + if (strlen (dest_dir) > strlen (base_dir)) + { + fprintf (stderr, "Dest dir longer than base dir is not supported\n"); + exit (1); + } + } + + /* Make sure there are trailing slashes in dirs */ + if (base_dir != NULL && base_dir[strlen (base_dir)-1] != '/') + { + p = malloc (strlen (base_dir) + 2); + strcpy (p, base_dir); + strcat (p, "/"); + free (base_dir); + base_dir = p; + } + if (dest_dir != NULL && dest_dir[strlen (dest_dir)-1] != '/') + { + p = malloc (strlen (dest_dir) + 2); + strcpy (p, dest_dir); + strcat (p, "/"); + free (dest_dir); + dest_dir = p; + } + + if (list_file != NULL) + { + list_file_fd = open (list_file, O_WRONLY|O_CREAT|O_APPEND, 0644); + } + + file = args[0]; + + if (elf_version(EV_CURRENT) == EV_NONE) + { + fprintf (stderr, "library out of date\n"); + exit (1); + } + + if (stat(file, &stat_buf) < 0) + { + fprintf (stderr, "Failed to open input file '%s': %s\n", file, strerror(errno)); + exit (1); + } + + /* Make sure we can read and write */ + chmod (file, stat_buf.st_mode | S_IRUSR | S_IWUSR); + + fd = open (file, O_RDWR); + if (fd < 0) + { + fprintf (stderr, "Failed to open input file '%s': %s\n", file, strerror(errno)); + exit (1); + } + + dso = fdopen_dso (fd, file); + if (dso == NULL) + exit (1); + + for (i = 1; i < dso->ehdr.e_shnum; i++) + { + const char *name; + + switch (dso->shdr[i].sh_type) + { + case SHT_PROGBITS: + name = strptr (dso, dso->ehdr.e_shstrndx, dso->shdr[i].sh_name); + /* TODO: Handle stabs */ + if (strcmp (name, ".stab") == 0) + { + fprintf (stderr, "Stabs debuginfo not supported: %s\n", file); + exit (1); + } + if (strcmp (name, ".debug_info") == 0) + edit_dwarf2 (dso); + + break; + case SHT_NOTE: + if (do_build_id + && build_id == NULL && (dso->shdr[i].sh_flags & SHF_ALLOC)) + { + /* Look for a build-ID note here. */ + Elf_Data *data = elf_rawdata (elf_getscn (dso->elf, i), NULL); + Elf32_Nhdr nh; + Elf_Data dst = + { + .d_version = EV_CURRENT, .d_type = ELF_T_NHDR, + .d_buf = &nh, .d_size = sizeof nh + }; + Elf_Data src = dst; + src.d_buf = data->d_buf; + assert (sizeof (Elf32_Nhdr) == sizeof (Elf64_Nhdr)); + while ((char *) data->d_buf + data->d_size - + (char *) src.d_buf > (int) sizeof nh + && elf32_xlatetom (&dst, &src, dso->ehdr.e_ident[EI_DATA])) + { + Elf32_Word len = sizeof nh + nh.n_namesz; + len = (len + 3) & ~3; + + if (nh.n_namesz == sizeof "GNU" && nh.n_type == 3 + && !memcmp ((char *) src.d_buf + sizeof nh, "GNU", sizeof "GNU")) + { + build_id = data; + build_id_offset = (char *) src.d_buf + len - + (char *) data->d_buf; + build_id_size = nh.n_descsz; + break; + } + + len += nh.n_descsz; + len = (len + 3) & ~3; + src.d_buf = (char *) src.d_buf + len; + } + } + break; + default: + break; + } + } + + if (do_build_id && build_id != NULL) + handle_build_id (dso, build_id, build_id_offset, build_id_size); + + if (elf_update (dso->elf, ELF_C_WRITE) < 0) + { + fprintf (stderr, "Failed to write file: %s\n", elf_errmsg (elf_errno())); + exit (1); + } + if (elf_end (dso->elf) < 0) + { + fprintf (stderr, "elf_end failed: %s\n", elf_errmsg (elf_errno())); + exit (1); + } + close (fd); + + /* Restore old access rights */ + chmod (file, stat_buf.st_mode); + + poptFreeContext (optCon); + + return 0; +} diff --git a/tools/elfdeps.c b/tools/elfdeps.c new file mode 100644 index 0000000..b523698 --- /dev/null +++ b/tools/elfdeps.c @@ -0,0 +1,299 @@ + +#include <sys/types.h> +#include <sys/stat.h> +#include <unistd.h> +#include <stdlib.h> +#include <fcntl.h> +#include <error.h> +#include <errno.h> +#include <popt.h> +#include <gelf.h> + +#include <rpm/rpmstring.h> +#include <rpm/argv.h> + +int filter_private = 0; +int soname_only = 0; + +typedef struct elfInfo_s { + Elf *elf; + + int isDSO; + int isElf64; /* is 64bit marker needed in dependencies */ + int isExec; /* requires are only added to executables */ + int gotDEBUG; + int gotHASH; + int gotGNUHASH; + int gotSONAME; + + ARGV_t requires; + ARGV_t provides; +} elfInfo; + +static int skipPrivate(const char *s) +{ + return (filter_private && rstreq(s, "GLIBC_PRIVATE")); +} + +static void processVerDef(Elf_Scn *scn, GElf_Shdr *shdr, elfInfo *ei) +{ + Elf_Data *data = NULL; + unsigned int offset, auxoffset; + char *soname = NULL; + + while ((data = elf_getdata(scn, data)) != NULL) { + offset = 0; + + for (int i = shdr->sh_info; --i >= 0; ) { + GElf_Verdef def_mem, *def; + def = gelf_getverdef (data, offset, &def_mem); + if (def == NULL) + break; + auxoffset = offset + def->vd_aux; + offset += def->vd_next; + + for (int j = def->vd_cnt; --j >= 0; ) { + GElf_Verdaux aux_mem, * aux; + const char *s; + aux = gelf_getverdaux (data, auxoffset, &aux_mem); + if (aux == NULL) + break; + s = elf_strptr(ei->elf, shdr->sh_link, aux->vda_name); + if (s == NULL) + break; + if (def->vd_flags & VER_FLG_BASE) { + rfree(soname); + soname = rstrdup(s); + auxoffset += aux->vda_next; + continue; + } else if (soname && !soname_only && !skipPrivate(s)) { + const char *marker = ei->isElf64 ? "(64bit)" : ""; + char *dep = NULL; + rasprintf(&dep, "%s(%s)%s", soname, s, marker); + argvAdd(&ei->provides, dep); + rfree(dep); + } + } + + } + } + rfree(soname); +} + +static void processVerNeed(Elf_Scn *scn, GElf_Shdr *shdr, elfInfo *ei) +{ + Elf_Data *data = NULL; + char *soname = NULL; + while ((data = elf_getdata(scn, data)) != NULL) { + unsigned int offset = 0, auxoffset; + for (int i = shdr->sh_info; --i >= 0; ) { + const char *s = NULL; + GElf_Verneed need_mem, *need; + need = gelf_getverneed (data, offset, &need_mem); + if (need == NULL) + break; + + s = elf_strptr(ei->elf, shdr->sh_link, need->vn_file); + if (s == NULL) + break; + rfree(soname); + soname = rstrdup(s); + auxoffset = offset + need->vn_aux; + + for (int j = need->vn_cnt; --j >= 0; ) { + GElf_Vernaux aux_mem, * aux; + aux = gelf_getvernaux (data, auxoffset, &aux_mem); + if (aux == NULL) + break; + s = elf_strptr(ei->elf, shdr->sh_link, aux->vna_name); + if (s == NULL) + break; + + if (ei->isExec && soname && !soname_only && !skipPrivate(s)) { + const char *marker = ei->isElf64 ? "(64bit)" : ""; + char *dep = NULL; + rasprintf(&dep, "%s(%s)%s", soname, s, marker); + argvAdd(&ei->requires, dep); + rfree(dep); + } + auxoffset += aux->vna_next; + } + offset += need->vn_next; + } + } + rfree(soname); +} + +static void processDynamic(Elf_Scn *scn, GElf_Shdr *shdr, elfInfo *ei) +{ + Elf_Data *data = NULL; + while ((data = elf_getdata(scn, data)) != NULL) { + for (int i = 0; i < (shdr->sh_size / shdr->sh_entsize); i++) { + ARGV_t *deptype = NULL; + const char *s = NULL; + GElf_Dyn dyn_mem, *dyn; + + dyn = gelf_getdyn (data, i, &dyn_mem); + if (dyn == NULL) + break; + + switch (dyn->d_tag) { + case DT_HASH: + ei->gotHASH = 1; + break; + case DT_GNU_HASH: + ei->gotGNUHASH = 1; + break; + case DT_DEBUG: + ei->gotDEBUG = 1; + break; + case DT_SONAME: + ei->gotSONAME = 1; + s = elf_strptr(ei->elf, shdr->sh_link, dyn->d_un.d_val); + deptype = &ei->provides; + break; + case DT_NEEDED: + if (ei->isExec) { + s = elf_strptr(ei->elf, shdr->sh_link, dyn->d_un.d_val); + deptype = &ei->requires; + } + break; + default: + break; + } + + if (s && deptype) { + const char *marker = ei->isElf64 ? "()(64bit)" : ""; + char *dep = rstrscat(NULL, s, marker, NULL); + argvAdd(deptype, dep); + rfree(dep); + } + } + } +} + +static void processSections(elfInfo *ei) +{ + Elf_Scn * scn = NULL; + while ((scn = elf_nextscn(ei->elf, scn)) != NULL) { + GElf_Shdr shdr_mem, *shdr; + shdr = gelf_getshdr(scn, &shdr_mem); + if (shdr == NULL) + break; + + switch (shdr->sh_type) { + case SHT_GNU_verdef: + processVerDef(scn, shdr, ei); + break; + case SHT_GNU_verneed: + processVerNeed(scn, shdr, ei); + break; + case SHT_DYNAMIC: + processDynamic(scn, shdr, ei); + break; + default: + break; + } + } +} + +static int processFile(const char *fn, int dtype) +{ + int rc = 1; + int fdno = -1; + struct stat st; + GElf_Ehdr *ehdr, ehdr_mem; + elfInfo *ei = rcalloc(1, sizeof(*ei)); + + fdno = open(fn, O_RDONLY); + if (fdno < 0 || fstat(fdno, &st) < 0) + goto exit; + + (void) elf_version(EV_CURRENT); + ei->elf = elf_begin(fdno, ELF_C_READ, NULL); + if (ei->elf == NULL || elf_kind(ei->elf) != ELF_K_ELF) + goto exit; + + ehdr = gelf_getehdr(ei->elf, &ehdr_mem); + if (ehdr == NULL) + goto exit; + + if (ehdr->e_type == ET_DYN || ehdr->e_type == ET_EXEC) { +/* on alpha, everything is 64bit but we dont want the (64bit) markers */ +#if !defined(__alpha__) + ei->isElf64 = (ehdr->e_ident[EI_CLASS] == ELFCLASS64); +#else + ei->isElf64 = 0; +#endif + ei->isDSO = (ehdr->e_type == ET_DYN); + ei->isExec = (st.st_mode & (S_IXUSR|S_IXGRP|S_IXOTH)); + + processSections(ei); + } + + /* + * For DSOs which use the .gnu_hash section and don't have a .hash + * section, we need to ensure that we have a new enough glibc. + */ + if (ei->isExec && ei->gotGNUHASH && !ei->gotHASH && !soname_only) { + argvAdd(&ei->requires, "rtld(GNU_HASH)"); + } + + /* For DSO's, provide the basename of the file if DT_SONAME not found. */ + if (ei->isDSO && !ei->gotDEBUG && !ei->gotSONAME) { + const char *marker = ei->isElf64 ? "()(64bit)" : ""; + const char *bn = strrchr(fn, '/'); + char *dep; + bn = bn ? bn + 1 : fn; + dep = rstrscat(NULL, bn, marker, NULL); + argvAdd(&ei->provides, dep); + rfree(dep); + } + + rc = 0; + /* dump the requested dependencies for this file */ + for (ARGV_t dep = dtype ? ei->requires : ei->provides; dep && *dep; dep++) { + fprintf(stdout, "%s\n", *dep); + } + +exit: + if (fdno >= 0) close(fdno); + if (ei) { + argvFree(ei->provides); + argvFree(ei->requires); + if (ei->elf) elf_end(ei->elf); + rfree(ei); + } + return rc; +} + +int main(int argc, char *argv[]) +{ + char fn[BUFSIZ]; + int provides = 0; + int requires = 0; + poptContext optCon; + + struct poptOption opts[] = { + { "provides", 'P', POPT_ARG_VAL, &provides, -1, NULL, NULL }, + { "requires", 'R', POPT_ARG_VAL, &requires, -1, NULL, NULL }, + { "filter-private", 0, POPT_ARG_VAL, &filter_private, -1, NULL, NULL }, + { "soname-only", 0, POPT_ARG_VAL, &soname_only, -1, NULL, NULL }, + POPT_AUTOHELP + POPT_TABLEEND + }; + + optCon = poptGetContext(argv[0], argc, (const char **) argv, opts, 0); + if (argc < 2 || poptGetNextOpt(optCon) == 0) { + poptPrintUsage(optCon, stderr, 0); + exit(EXIT_FAILURE); + } + + while (fgets(fn, sizeof(fn), stdin) != NULL) { + fn[strlen(fn)-1] = '\0'; + (void) processFile(fn, requires); + } + + poptFreeContext(optCon); + return 0; +} diff --git a/tools/hashtab.c b/tools/hashtab.c new file mode 100644 index 0000000..7bd576d --- /dev/null +++ b/tools/hashtab.c @@ -0,0 +1,523 @@ +/* An expandable hash tables datatype. + Copyright (C) 1999, 2000, 2001 Free Software Foundation, Inc. + Contributed by Vladimir Makarov (vmakarov@cygnus.com). + +This file is part of the libiberty library. +Libiberty is free software; you can redistribute it and/or +modify it under the terms of the GNU Library General Public +License as published by the Free Software Foundation; either +version 2 of the License, or (at your option) any later version. + +Libiberty 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 +Library General Public License for more details. + +You should have received a copy of the GNU Library General Public +License along with libiberty; see the file COPYING.LIB. If +not, write to the Free Software Foundation, Inc., 59 Temple Place - Suite 330, +Boston, MA 02111-1307, USA. */ + +/* This package implements basic hash table functionality. It is possible + to search for an entry, create an entry and destroy an entry. + + Elements in the table are generic pointers. + + The size of the table is not fixed; if the occupancy of the table + grows too high the hash table will be expanded. + + The abstract data implementation is based on generalized Algorithm D + from Knuth's book "The art of computer programming". Hash table is + expanded by creation of new hash table and transferring elements from + the old table to the new table. */ + +#include <sys/types.h> +#include <stdlib.h> +#include <string.h> +#include <stdio.h> +#include "tools/hashtab.h" + +/* This macro defines reserved value for empty table entry. */ + +#define EMPTY_ENTRY ((void *) 0) + +/* This macro defines reserved value for table entry which contained + a deleted element. */ + +#define DELETED_ENTRY ((void *) 1) + +static unsigned long higher_prime_number (unsigned long); +static hashval_t hash_pointer (const void *); +static int eq_pointer (const void *, const void *); +static int htab_expand (htab_t); +static void **find_empty_slot_for_expand (htab_t, hashval_t); + +/* At some point, we could make these be NULL, and modify the + hash-table routines to handle NULL specially; that would avoid + function-call overhead for the common case of hashing pointers. */ +htab_hash htab_hash_pointer = hash_pointer; +htab_eq htab_eq_pointer = eq_pointer; + +/* The following function returns a nearest prime number which is + greater than N, and near a power of two. */ + +static unsigned long +higher_prime_number (n) + unsigned long n; +{ + /* These are primes that are near, but slightly smaller than, a + power of two. */ + static unsigned long primes[] = { + (unsigned long) 2, + (unsigned long) 7, + (unsigned long) 13, + (unsigned long) 31, + (unsigned long) 61, + (unsigned long) 127, + (unsigned long) 251, + (unsigned long) 509, + (unsigned long) 1021, + (unsigned long) 2039, + (unsigned long) 4093, + (unsigned long) 8191, + (unsigned long) 16381, + (unsigned long) 32749, + (unsigned long) 65521, + (unsigned long) 131071, + (unsigned long) 262139, + (unsigned long) 524287, + (unsigned long) 1048573, + (unsigned long) 2097143, + (unsigned long) 4194301, + (unsigned long) 8388593, + (unsigned long) 16777213, + (unsigned long) 33554393, + (unsigned long) 67108859, + (unsigned long) 134217689, + (unsigned long) 268435399, + (unsigned long) 536870909, + (unsigned long) 1073741789, + (unsigned long) 2147483647, + /* 4294967291L */ + ((unsigned long) 2147483647) + ((unsigned long) 2147483644), + }; + + unsigned long* low = &primes[0]; + unsigned long* high = &primes[sizeof(primes) / sizeof(primes[0])]; + + while (low != high) + { + unsigned long* mid = low + (high - low) / 2; + if (n > *mid) + low = mid + 1; + else + high = mid; + } + + /* If we've run out of primes, abort. */ + if (n > *low) + { + fprintf (stderr, "Cannot find prime bigger than %lu\n", n); + abort (); + } + + return *low; +} + +/* Returns a hash code for P. */ + +static hashval_t +hash_pointer (p) + const void * p; +{ + return (hashval_t) ((long)p >> 3); +} + +/* Returns non-zero if P1 and P2 are equal. */ + +static int +eq_pointer (p1, p2) + const void * p1; + const void * p2; +{ + return p1 == p2; +} + +/* This function creates table with length slightly longer than given + source length. The created hash table is initiated as empty (all the + hash table entries are EMPTY_ENTRY). The function returns the created + hash table. Memory allocation may fail; it may return NULL. */ + +htab_t +htab_try_create (size, hash_f, eq_f, del_f) + size_t size; + htab_hash hash_f; + htab_eq eq_f; + htab_del del_f; +{ + htab_t result; + + size = higher_prime_number (size); + result = (htab_t) calloc (1, sizeof (struct htab)); + if (result == NULL) + return NULL; + + result->entries = (void **) calloc (size, sizeof (void *)); + if (result->entries == NULL) + { + free (result); + return NULL; + } + + result->size = size; + result->hash_f = hash_f; + result->eq_f = eq_f; + result->del_f = del_f; + result->return_allocation_failure = 1; + return result; +} + +/* This function frees all memory allocated for given hash table. + Naturally the hash table must already exist. */ + +void +htab_delete (htab) + htab_t htab; +{ + int i; + + if (htab->del_f) + for (i = htab->size - 1; i >= 0; i--) + if (htab->entries[i] != EMPTY_ENTRY + && htab->entries[i] != DELETED_ENTRY) + (*htab->del_f) (htab->entries[i]); + + free (htab->entries); + free (htab); +} + +/* This function clears all entries in the given hash table. */ + +void +htab_empty (htab) + htab_t htab; +{ + int i; + + if (htab->del_f) + for (i = htab->size - 1; i >= 0; i--) + if (htab->entries[i] != EMPTY_ENTRY + && htab->entries[i] != DELETED_ENTRY) + (*htab->del_f) (htab->entries[i]); + + memset (htab->entries, 0, htab->size * sizeof (void *)); +} + +/* Similar to htab_find_slot, but without several unwanted side effects: + - Does not call htab->eq_f when it finds an existing entry. + - Does not change the count of elements/searches/collisions in the + hash table. + This function also assumes there are no deleted entries in the table. + HASH is the hash value for the element to be inserted. */ + +static void ** +find_empty_slot_for_expand (htab, hash) + htab_t htab; + hashval_t hash; +{ + size_t size = htab->size; + hashval_t hash2 = 1 + hash % (size - 2); + unsigned int index = hash % size; + + for (;;) + { + void **slot = htab->entries + index; + + if (*slot == EMPTY_ENTRY) + return slot; + else if (*slot == DELETED_ENTRY) + abort (); + + index += hash2; + if (index >= size) + index -= size; + } +} + +/* The following function changes size of memory allocated for the + entries and repeatedly inserts the table elements. The occupancy + of the table after the call will be about 50%. Naturally the hash + table must already exist. Remember also that the place of the + table entries is changed. If memory allocation failures are allowed, + this function will return zero, indicating that the table could not be + expanded. If all goes well, it will return a non-zero value. */ + +static int +htab_expand (htab) + htab_t htab; +{ + void **oentries; + void **olimit; + void **p; + + oentries = htab->entries; + olimit = oentries + htab->size; + + htab->size = higher_prime_number (htab->size * 2); + + if (htab->return_allocation_failure) + { + void **nentries = (void **) calloc (htab->size, sizeof (void **)); + if (nentries == NULL) + return 0; + htab->entries = nentries; + } + + htab->n_elements -= htab->n_deleted; + htab->n_deleted = 0; + + p = oentries; + do + { + void * x = *p; + + if (x != EMPTY_ENTRY && x != DELETED_ENTRY) + { + void **q = find_empty_slot_for_expand (htab, (*htab->hash_f) (x)); + + *q = x; + } + + p++; + } + while (p < olimit); + + free (oentries); + return 1; +} + +/* This function searches for a hash table entry equal to the given + element. It cannot be used to insert or delete an element. */ + +void * +htab_find_with_hash (htab, element, hash) + htab_t htab; + const void * element; + hashval_t hash; +{ + unsigned int index; + hashval_t hash2; + size_t size; + void * entry; + + htab->searches++; + size = htab->size; + index = hash % size; + + entry = htab->entries[index]; + if (entry == EMPTY_ENTRY + || (entry != DELETED_ENTRY && (*htab->eq_f) (entry, element))) + return entry; + + hash2 = 1 + hash % (size - 2); + + for (;;) + { + htab->collisions++; + index += hash2; + if (index >= size) + index -= size; + + entry = htab->entries[index]; + if (entry == EMPTY_ENTRY + || (entry != DELETED_ENTRY && (*htab->eq_f) (entry, element))) + return entry; + } +} + +/* Like htab_find_slot_with_hash, but compute the hash value from the + element. */ + +void * +htab_find (htab, element) + htab_t htab; + const void * element; +{ + return htab_find_with_hash (htab, element, (*htab->hash_f) (element)); +} + +/* This function searches for a hash table slot containing an entry + equal to the given element. To delete an entry, call this with + INSERT = 0, then call htab_clear_slot on the slot returned (possibly + after doing some checks). To insert an entry, call this with + INSERT = 1, then write the value you want into the returned slot. + When inserting an entry, NULL may be returned if memory allocation + fails. */ + +void ** +htab_find_slot_with_hash (htab, element, hash, insert) + htab_t htab; + const void * element; + hashval_t hash; + enum insert_option insert; +{ + void **first_deleted_slot; + unsigned int index; + hashval_t hash2; + size_t size; + + if (insert == INSERT && htab->size * 3 <= htab->n_elements * 4 + && htab_expand (htab) == 0) + return NULL; + + size = htab->size; + hash2 = 1 + hash % (size - 2); + index = hash % size; + + htab->searches++; + first_deleted_slot = NULL; + + for (;;) + { + void * entry = htab->entries[index]; + if (entry == EMPTY_ENTRY) + { + if (insert == NO_INSERT) + return NULL; + + htab->n_elements++; + + if (first_deleted_slot) + { + *first_deleted_slot = EMPTY_ENTRY; + return first_deleted_slot; + } + + return &htab->entries[index]; + } + + if (entry == DELETED_ENTRY) + { + if (!first_deleted_slot) + first_deleted_slot = &htab->entries[index]; + } + else if ((*htab->eq_f) (entry, element)) + return &htab->entries[index]; + + htab->collisions++; + index += hash2; + if (index >= size) + index -= size; + } +} + +/* Like htab_find_slot_with_hash, but compute the hash value from the + element. */ + +void ** +htab_find_slot (htab, element, insert) + htab_t htab; + const void * element; + enum insert_option insert; +{ + return htab_find_slot_with_hash (htab, element, (*htab->hash_f) (element), + insert); +} + +/* This function deletes an element with the given value from hash + table. If there is no matching element in the hash table, this + function does nothing. */ + +void +htab_remove_elt (htab, element) + htab_t htab; + void * element; +{ + void **slot; + + slot = htab_find_slot (htab, element, NO_INSERT); + if (*slot == EMPTY_ENTRY) + return; + + if (htab->del_f) + (*htab->del_f) (*slot); + + *slot = DELETED_ENTRY; + htab->n_deleted++; +} + +/* This function clears a specified slot in a hash table. It is + useful when you've already done the lookup and don't want to do it + again. */ + +void +htab_clear_slot (htab, slot) + htab_t htab; + void **slot; +{ + if (slot < htab->entries || slot >= htab->entries + htab->size + || *slot == EMPTY_ENTRY || *slot == DELETED_ENTRY) + abort (); + + if (htab->del_f) + (*htab->del_f) (*slot); + + *slot = DELETED_ENTRY; + htab->n_deleted++; +} + +/* This function scans over the entire hash table calling + CALLBACK for each live entry. If CALLBACK returns false, + the iteration stops. INFO is passed as CALLBACK's second + argument. */ + +void +htab_traverse (htab, callback, info) + htab_t htab; + htab_trav callback; + void * info; +{ + void **slot = htab->entries; + void **limit = slot + htab->size; + + do + { + void * x = *slot; + + if (x != EMPTY_ENTRY && x != DELETED_ENTRY) + if (!(*callback) (slot, info)) + break; + } + while (++slot < limit); +} + +/* Return the current size of given hash table. */ + +size_t +htab_size (htab) + htab_t htab; +{ + return htab->size; +} + +/* Return the current number of elements in given hash table. */ + +size_t +htab_elements (htab) + htab_t htab; +{ + return htab->n_elements - htab->n_deleted; +} + +/* Return the fraction of fixed collisions during all work with given + hash table. */ + +double +htab_collisions (htab) + htab_t htab; +{ + if (htab->searches == 0) + return 0.0; + + return (double) htab->collisions / (double) htab->searches; +} diff --git a/tools/hashtab.h b/tools/hashtab.h new file mode 100644 index 0000000..9ed18ae --- /dev/null +++ b/tools/hashtab.h @@ -0,0 +1,143 @@ +/* An expandable hash tables datatype. + Copyright (C) 1999, 2000 Free Software Foundation, Inc. + Contributed by Vladimir Makarov (vmakarov@cygnus.com). + +This program 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; either version 2 of the License, or +(at your option) any later version. + +This program 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 this program; if not, write to the Free Software +Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. */ + +/* This package implements basic hash table functionality. It is possible + to search for an entry, create an entry and destroy an entry. + + Elements in the table are generic pointers. + + The size of the table is not fixed; if the occupancy of the table + grows too high the hash table will be expanded. + + The abstract data implementation is based on generalized Algorithm D + from Knuth's book "The art of computer programming". Hash table is + expanded by creation of new hash table and transferring elements from + the old table to the new table. */ + +#ifndef __HASHTAB_H__ +#define __HASHTAB_H__ + +#ifdef __cplusplus +extern "C" { +#endif /* __cplusplus */ + +/* The type for a hash code. */ +typedef unsigned int hashval_t; + +/* Callback function pointer types. */ + +/* Calculate hash of a table entry. */ +typedef hashval_t (*htab_hash) (const void *); + +/* Compare a table entry with a possible entry. The entry already in + the table always comes first, so the second element can be of a + different type (but in this case htab_find and htab_find_slot + cannot be used; instead the variants that accept a hash value + must be used). */ +typedef int (*htab_eq) (const void *, const void *); + +/* Cleanup function called whenever a live element is removed from + the hash table. */ +typedef void (*htab_del) (void *); + +/* Function called by htab_traverse for each live element. The first + arg is the slot of the element (which can be passed to htab_clear_slot + if desired), the second arg is the auxiliary pointer handed to + htab_traverse. Return 1 to continue scan, 0 to stop. */ +typedef int (*htab_trav) (void **, void *); + +/* Hash tables are of the following type. The structure + (implementation) of this type is not needed for using the hash + tables. All work with hash table should be executed only through + functions mentioned below. */ + +struct htab +{ + /* Pointer to hash function. */ + htab_hash hash_f; + + /* Pointer to comparison function. */ + htab_eq eq_f; + + /* Pointer to cleanup function. */ + htab_del del_f; + + /* Table itself. */ + void **entries; + + /* Current size (in entries) of the hash table */ + size_t size; + + /* Current number of elements including also deleted elements */ + size_t n_elements; + + /* Current number of deleted elements in the table */ + size_t n_deleted; + + /* The following member is used for debugging. Its value is number + of all calls of `htab_find_slot' for the hash table. */ + unsigned int searches; + + /* The following member is used for debugging. Its value is number + of collisions fixed for time of work with the hash table. */ + unsigned int collisions; + + /* This is non-zero if we are allowed to return NULL for function calls + that allocate memory. */ + int return_allocation_failure; +}; + +typedef struct htab *htab_t; + +/* An enum saying whether we insert into the hash table or not. */ +enum insert_option {NO_INSERT, INSERT}; + +/* The prototypes of the package functions. */ + +/* This function is like htab_create, but may return NULL if memory + allocation fails, and also signals that htab_find_slot_with_hash and + htab_find_slot are allowed to return NULL when inserting. */ +extern htab_t htab_try_create (size_t, htab_hash, htab_eq, htab_del); +extern void htab_delete (htab_t); +extern void htab_empty (htab_t); + +extern void *htab_find (htab_t, const void *); +extern void **htab_find_slot (htab_t, const void *, enum insert_option); +extern void *htab_find_with_hash (htab_t, const void *, hashval_t); +extern void **htab_find_slot_with_hash (htab_t, const void *, hashval_t, + enum insert_option); +extern void htab_clear_slot (htab_t, void **); +extern void htab_remove_elt (htab_t, void *); + +extern void htab_traverse (htab_t, htab_trav, void *); + +extern size_t htab_size (htab_t); +extern size_t htab_elements (htab_t); +extern double htab_collisions (htab_t); + +/* A hash function for pointers. */ +extern htab_hash htab_hash_pointer; + +/* An equality function for pointers. */ +extern htab_eq htab_eq_pointer; + +#ifdef __cplusplus +} +#endif /* __cplusplus */ + +#endif /* __HASHTAB_H */ diff --git a/tools/javadeps.c b/tools/javadeps.c new file mode 100644 index 0000000..9d15787 --- /dev/null +++ b/tools/javadeps.c @@ -0,0 +1,1288 @@ +/* +RPM and it's source code are covered under two separate licenses. + +The entire code base may be distributed under the terms of the GNU General +Public License (GPL), which appears immediately below. Alternatively, +all of the source code in the lib subdirectory of the RPM source code +distribution as well as any code derived from that code may instead be +distributed under the GNU Library General Public License (LGPL), at the +choice of the distributor. The complete text of the LGPL appears +at the bottom of this file. + +This alternatively is allowed to enable applications to be linked against +the RPM library (commonly called librpm) without forcing such applications +to be distributed under the GPL. + +Any questions regarding the licensing of RPM should be addressed to +marc@redhat.com and ewt@redhat.com. +*/ + +/* + Simple progam for pullng all the referenced java classes out of a + class file. Java files are supposed to be platform independent, so + this program should work on all platforms. This code is based on + the information found in: + + "Java Virtual Machine" by Jon Meyer & Troy Downing. + O'Reilly & Associates, INC. (First Edition, March 1997) + ISBN: 1-56592-194-1 + + Jonathan Ross, Ken Estes + Mail.com + */ + + +/* + Remember that: + + JAR consists of a zip archive, as defined by PKWARE, containing + a manifest file and potentially signature files, as defined in + the Manifest and Signature specification. So we use infozip's + 'unzip -p' found at http://www.cdrom.com/pub/infozip/. + + Additional documentation, about this fact, at: + + http://java.sun.com/products/jdk/1.1/docs/guide/jar/jarGuide.html + http://java.sun.com/products/jdk/1.2/docs/guide/jar/jarGuide.html + + http://java.sun.com/products/jdk/1.1/docs/guide/jar/manifest.html + http://java.sun.com/products/jdk/1.2/docs/guide/jar/manifest.html + +*/ + +#include <stdio.h> +#include <stdlib.h> +#include <stdarg.h> +#include <string.h> +#include <netdb.h> +#include <rpm/rpmutil.h> +#include <ctype.h> +#include "debug.h" + +/*---------typedefs---------*/ + + +/* The symbol table is a waste of memory.. + but it's easy to code! */ + +typedef struct { + short poolSize; + char **stringList; + short *classRef; + short *typeRef; +} symbolTable_t; + + +/*---------Global Variables, in all caps---------*/ + +/*name of this program*/ +const char *PROGRAM_NAME=0; + +/*name of the current class file*/ +const char *FILE_NAME=0; + +/*the name of the last class file seen*/ +char *CLASS_NAME=0; + +/*The string to put before each line, + this is a formatted version of CLASS_NAME */ +char *OUTPUT_PREFIX=0; + +/*arguments chosen*/ +int ARG_PROVIDES=0; +int ARG_REQUIRES=0; +int ARG_RPMFORMAT=0; +int ARG_DEPSFORMAT=0; +int ARG_KEYWORDS=0; +int ARG_STARPROV=0; + +/*keywords found in class file*/ +char *KEYWORD_VERSION=0; +char *KEYWORD_REVISION=0; +char *KEYWORD_EPOCH=0; + +/* + + Quantify says over 80 percent of the time of the program is spent + in printf (and the functions it calls) AND I verified that only + about a quarter of the classes found in the dependency analysis are + unique. After the change no function used more then 26% of the over + all time. + + I implement a simple mechanism to remove most duplicate + dependencies. The print_table is a table of string, with + duplicates, which are to be printed. Just before the program + exists it is sorted and all unique entries are printed. If it + fills up during then it is flushed using the same technique as + above. + + The functions which manipulate the table are: + void print_table_flush(void) + void print_table_add(char *str) + +*/ + + +#define MAX_PRINT_TABLE 10000 +char *PRINT_TABLE[MAX_PRINT_TABLE]; +int SIZE_PRINT_TABLE; + +/*--------- declare all functions ---------*/ + +void usage (void); +void outofmemory(void); +void die(const char *format, ...); +size_t my_fread(void *ptr, size_t size, size_t nitems, FILE *stream); +void check_range(short entryNum, short value, short poolSize); +char *is_lower_equal (char *string, const char *pattern); +int findJavaMagic (FILE *fileHandle); +int my_strcmp (const void *a, const void *b); +void print_table_flush(void); +void print_table_add(char *str); +char *formatClassName(char *pSomeString, char terminator, char print_star); +void dumpRefType(char *pSomeString); +void genSymbolTable (FILE *fileHandle, symbolTable_t *symbolTable); +void freeSymbolTable (symbolTable_t *symbolTable); +char *findClassName (FILE *fileHandle, symbolTable_t *symbolTable); +void dumpProvides(char *className); +void dumpRequires(symbolTable_t *symbolTable); +void processJavaFile (FILE *fileHandle); + +/*--------- functions ---------*/ + +void +usage (void) +{ + printf("NAME:\n\tjavadeps - Examine Java class files and\n" + "\t\t\treturn information about their dependencies.\n\n"); + printf("USAGE:\n"); + printf("\t javadeps { --provides | --requires } \n" + "\t\t [--rpmformat] [--depsformat] [--keywords] \n" + "\t\t [--] classfile-name ... \n\n" + "\t javadeps [--help]\n\n"); + printf("\n\n"); + printf("DESCRIPTION:\n\n"); + printf("List the dependencies or the fully qualified class names, of the \n" + "classfiles listed on the command line. \n\n"); + printf("OPTIONS:\n\n"); + printf("--requires For each class files listed in the arguments,\n" + " -r print the list of class files that would be\n" + " required to run these java programs. This does not \n" + " include anyting instantiated by reflection.\n\n"); + printf("--provides For each class files listed in the arguments, \n" + " -p Print the fully qualified java classes,\n" + " that they provide.\n\n"); + printf("--rpmformat Format the output to match that used by RPM's \n" + " -F (Red Hat Package Manager) dependency analysis \n" + " database. The default is not --rpmformat.\n\n"); + printf("--depsformat print the name of the class which \n" + " -d This is mostly used in conjunctions with --requires \n" + " to list the class file dependencies in a format " + " similar to traditional Makefile dependencies. The " + " default is not --depsformat.\n\n"); + printf("--keywords Make use of any keywords embeded in the classfile.\n" + " -k The default is not --keyword.\n\n"); + printf("--starprov Add the star notation provides to the provides list.\n" + " -s The default is not --starprov. This is only for use\n" + " with (Sun) jhtml dependencies, and since jhtml is \n" + " deprecated so is this option.\n\n"); + printf("--help Display this page and exit.\n\n"); + printf("-- This stops the processing of arguments, making it \n" + " easier for users to have filenames like '--keywords',\n" + " without the command line parser getting confused.\n\n"); + printf("\n\n"); + printf("If any of the class file names in the argument list is '-' then\n" + "<stdin> will be read instead of reading from a file. The\n" + "contents of <stdin> should be the contents of a class file and \n" + "not a list of class files to read. It is assumed that when run \n" + "with '-', this program is in a pipeline preceded by the \n" + "command 'unzip -p filename.jar' so that <stdin> may contain\n" + "the contents of several classfiles concatenated together.\n"); + printf("\n\n"); + printf("If --keywords is specified then the following strings are \n" + "searched for (case insensitive) in the class file string table\n" + "and, if a string is found with a prefix matching the keyword then \n" + "the dependencies are changed accordingly. There may be multiple \n" + "string tables entries prefixed with RPM_Provides and RPM_Requires. \n" + "This would indicate that the dependency is the union\n" + "of all entries.\n" + "\n\n" + "Keyword List:\n\n" + "'$Revision: ' This RCS/CVS compatible keyword is assumed to \n" + " contain the version number of the class file \n" + " it is found in. Care should be taken with this\n" + " option as RPM's notion of which version is later\n" + " may not corrispond with your own, especially\n" + " if you use branches. This keyword\n" + " only effects the output of --provides and only\n" + " when RPM_Version is not defined.\n\n" + "'RPM_Version: ' This is an alternative method of specifying the\n" + " version number of the class. It will override\n" + " $Revision if set. This keyword only effects\n" + " the output of --provides \n\n" + "'RPM_Epoch: ' This string contains the epoch to use with the \n" + " version number stored in Revision. If not \n" + " specified, the epoch is assumed to be zero.\n" + " This keyword only effects the output of\n " + " --provides and only when $Revision number is\n" + " used.\n\n" + "'RPM_Provides: ' This string lists additional capabilities\n" + " provided by the java class. The string should\n" + " be a white space ([\\t\\v\\n\\r\\f\\ ])\n" + " separated list of dependency strings. Each\n" + " dependency string must be of the same format as\n" + " would be valid in the Requires or Provides line\n" + " of the specfile. This keyword only effects the\n" + " output of --provides.\n\n" + "'RPM_Requires: ' This string lists additional requirements of\n" + " the java class. The string should be a white \n" + " space ([\\t \v\\n\\r\\f\\ ]) separated list of \n" + " dependency strings. Each dependency string must\n" + " be of the same format as would be valid in the \n" + " Requires or Provides line of the specfile. This\n" + " keyword only effects the output of --requires.\n " + " \n\n" + "Note that there is no means of setting the release number. This\n" + "is necessary because release numbers are incremented when the\n" + "source does not change but the package needs to be rebuilt. So\n" + "relase numbers can not be stored in the source. The release is\n" + "assumed to be zero. \n\n" + ""); + printf("EXAMPLES (Java Keywords): \n\n" + "\t public static final String REVISION = \"$Revision: 2.12 $\";\n" + "\t public static final String EPOCH = \"4\";\n" + "\t public static final String REQUIRES = \"RPM_Requires: " + "java(gnu.regexp.RE) java(com.ibm.site.util.Options)>=1.5\";\n" + ""); + printf("\n\n"); + printf("EXAMPLES (Arguments): \n\n" + "\tjavadeps --requires -- filename.class\n\n" + "\tjavadeps --provides -- filename.class\n\n" + "\tjavadeps --help\n\n" + "\n" + "\tjavadeps --requires --rpmformat --keywords -- filename.class\n\n" + "\tjavadeps --requires --depsformat -- filename.class\n\n" + "\tjavadeps --requires -- filename1.class filename2.class\n\n" + "\tcat filename2.class | javadeps --requires -- filename1.class -\n\n" + "\tunzip -p filename.jar | javadeps --requires -- - \n\n" + ""); + printf("This program is distributed with RPM the Redhat Package \n" + "Managment system. Further information about RPM can be found at \n" + "\thttp://www.rpm.org/\n\n"); + printf("\n\n"); + exit(-1); +} + +RPM_GNUC_NORETURN +void outofmemory(void) { + + /* Its doubtful we could do a printf if there is really a memory + issue but at least halt the program */ + + fprintf(stderr, "Could not allocate memory\n"); + exit(-1); +} + +RPM_GNUC_NORETURN +void die(const char *format, ...) { + /* Most errors are fatal. + This function throws a fatal error and + accepts arguments like printf does*/ + + char *newformat = NULL, *newmsg = NULL; + va_list ap; + + if ( + !(newformat = (char*) malloc(1024)) || + !(newmsg = (char*) malloc(1024)) + ) + outofmemory(); + + /* Rewrite format line, to include additional information. The + format line we chose depends on how much information is availible + at the time of the error. Display the maximum ammount of + information. */ + + /* notice the FILE_NAME is useless for jar files. We would want to + print the name of the classfile which caused the error. That + is hard since we only know that name when we are done parsing + the file, and most errors will occur before that.*/ + + if ( (!FILE_NAME) ) { + + sprintf (newformat, "\n%s: %s", + PROGRAM_NAME, format); + + } else if ( (FILE_NAME) && (!CLASS_NAME) ) { + + sprintf (newformat, "\n%s: Java classfile: %s, %s", + PROGRAM_NAME, FILE_NAME, format); + + } else if (CLASS_NAME) { + sprintf (newformat, "\n%s: Java classfile: %s, classname: %s, %s", + PROGRAM_NAME, FILE_NAME, CLASS_NAME, format); + } + + va_start(ap, format); + vsprintf (newmsg, newformat, ap); + va_end(ap); + + /* print error to where it needs to go: + stdout, stderr, or syslog + */ + + fprintf(stderr, "%s", newmsg); + + free(newformat); + free(newmsg); + + exit(-1); +} + + +/* wrap fread for safety. It is a fatal error to get an unexpected + EOF inside a class file. */ + +size_t my_fread(void *ptr, size_t size, size_t nitems, FILE *stream) { + size_t rc=0; + /*these variables are helpful in the debugger*/ + int eof=0; + int error=0; + + + rc = fread(ptr, size, nitems, stream); + if ( (size!=0) && (rc == 0) ) { + eof = feof(stream); + error = ferror(stream); + die("Error reading from file, or unexpected EOF\n"); + } + return rc; +} + + +void check_range(short entryNum, short value, short poolSize) { + + if (value > poolSize) { + die("Symbol Table Entry Number: %d, Value: %d, " + "is out of range of the constant pool. Pool Size: %d\n", + entryNum, value, poolSize); + } + return ; +} + + + +/* If lower case conversion of string is equal to pattern return a + pointer into string, just after the match. If the string does not + patch the pattern the null pointer is returned. This does not + change string. + + This is similar to strcasecmp, but I expect my patterns to be a + prefix of my strings. */ + +char +*is_lower_equal (char *string, const char *pattern) +{ + + while ( (tolower(*string) == *pattern) && + *string && *pattern ) { + string++; + pattern++; + } + + if ( *pattern == 0 ) { + return string; + } + + return NULL; +} + + +/* + Read fileHandle until we find the next instance of the Java + Classfile magic number indicating a java file or find EOF or + fileread error. Since we are reading from stdin which may contain + the concatination of many class files we can not be sure that the + magic number will be the first few bytes. + + Return 1 on success 0 on failure. */ + +#define mod4(num) ( (num) & 3 ) + + +int findJavaMagic (FILE *fileHandle) +{ + int offset=0; + int foundMagic = 0; + size_t rc; + + /* what were looking for */ + unsigned char magicInt[4] = {0xCA, 0xFE, 0xBA, 0xBE}; + /*the hex reads in decimal: 202 254 186 190 */ + + /* a circular buffer indicating the last few bytes we read */ + unsigned char buffer[4] = {0}; + + foundMagic = 0; + while( !foundMagic ) { + + rc = fread(&buffer[offset], 1, 1, fileHandle); + if ( !rc ) { + + /* Either this was not a java file or we were given a jar file + and have already found the last java file in it.*/ + + if ( feof(fileHandle) ) { + return 0; + } + + if ( ferror(fileHandle) ) { + die ("Error reading character from file.\n"); + }; + + } + + /* offset points to the most recent char we read so offest+1 + points to the oldest char we saved. */ + + foundMagic = ( + (magicInt[0] == buffer[mod4(offset+1)]) && + (magicInt[1] == buffer[mod4(offset+2)]) && + (magicInt[2] == buffer[mod4(offset+3)]) && + (magicInt[3] == buffer[mod4(offset+0)]) && + 1 + ); + + offset = mod4(offset+1); + + } /*end while*/ + + return foundMagic; +} + +#undef mod4 + + +int +my_strcmp (const void *a, const void *b) { +char **a1; char **b1; +int ret; + +a1 = (char **)a; +b1 = (char **)b; +ret = strcmp(*a1,*b1); + return ret; +} + +/* print the unique strings found in PRINT_TABLE and clear it out */ + +void +print_table_flush(void) { + int i; + char *last_string; + + if (!SIZE_PRINT_TABLE) { + return ; + } + + /* The qsort line gives a warning on some unicies who insist that + strcmp takes arguments of type pointers to void not the + traditional pointers to char. */ + + qsort( (void *) PRINT_TABLE, (size_t) SIZE_PRINT_TABLE, + sizeof(char *), &my_strcmp); + + printf("%s\n",PRINT_TABLE[0]); + last_string = PRINT_TABLE[0]; + PRINT_TABLE[0] = NULL; + + for (i = 1; i < SIZE_PRINT_TABLE; i++) { + if ( strcmp(last_string, PRINT_TABLE[i]) ){ + printf("%s\n",PRINT_TABLE[i]); + free(last_string); + last_string = PRINT_TABLE[i]; + } else { + free(PRINT_TABLE[i]); + } + PRINT_TABLE[i] = NULL; + } + + free(last_string); + SIZE_PRINT_TABLE = 0; + return ; +} + + +/* add an element to PRINT_TABLE for later printing to stdout. We do + not make a copy of the string so each string must be unique, + (different calls must pass pointers to different areas of memory) + and the string must not be freed anywhere else in the code and the + string must be from memory which can be freed.*/ + +void +print_table_add(char *str) { + + if (SIZE_PRINT_TABLE == MAX_PRINT_TABLE) { + print_table_flush(); + } + + if (OUTPUT_PREFIX) { + char *new_str; + + new_str= (char*) malloc(strlen(OUTPUT_PREFIX)+strlen(str)+1); + if (!new_str){ + outofmemory(); + } + + new_str[0]='\0'; + strcat(new_str,OUTPUT_PREFIX); + strcat(new_str,str); + free(str); + str=new_str; + } + + PRINT_TABLE[SIZE_PRINT_TABLE] = str; + SIZE_PRINT_TABLE++; + return ; +} + + +static void +print_list(char *in_string) { + + /* This function is no longer needed due to fixes in RPM's + processing of dependencies. Keep the code until I get a chance + to use RPM3.0 personally */ + + if (in_string) { + printf("%s\n", in_string); + } + +/* + Old function did: + + Given a list separated by whitespace, put each element in the print + table with an added "\n" */ + + /* + char *WhiteSpace_Set = "\t\v\n\r\f "; + char *newEnd, *out_string; + int copy_len; + + in_string += strspn(in_string, WhiteSpace_Set); + + while (*in_string) { + newEnd = strpbrk(in_string, WhiteSpace_Set); + + if (newEnd) { + copy_len = newEnd-in_string; + } else { + if (*in_string) { + copy_len = strlen(in_string); + } else { + copy_len = 0; + } + } + + out_string = malloc(copy_len+10); + if ( !out_string ) { + outofmemory(); + } + out_string[0]= '\0'; + + if (copy_len) { + strncat(out_string, in_string, copy_len); + in_string+=copy_len; + strcat(out_string, "\n"); + print_table_add(out_string); + } + + in_string += strspn(in_string+copy_len, WhiteSpace_Set); + } + + */ + return ; +} + + +/* Print a properly formatted java class name, and returns the length + of the class string . Do not print \n here as we may wish to + append the version number IFF we are printing the name of this classfile + + We also provide the class with the leaf node replaced with '*'. + This would not be necessary if we only had to worry about java + Class files. However our parsing of jhtml files depends on this + information. This is deprecated since jhtml is deprecated and + this method allows for very inaccurate dependencies. + + Also users may wish to refer to dependencies using star notation + (though this would be less accurate then explicit dependencies + since any leaf class will satify a star dependency). */ + +char +*formatClassName(char *in_string, char terminator, + char print_star) +{ + char *leaf_class=0, *out_string=0; + char *ClassName_Break_Set=0; + + + out_string = (char*) malloc(strlen(in_string) + 10); + if ( !out_string ) { + outofmemory(); + } + out_string[0]= '\0'; + + /*these characters end the current parse of the string in function + formatClassName.*/ + + ClassName_Break_Set = (char*) malloc(3); + if ( !ClassName_Break_Set ) { + outofmemory(); + } + ClassName_Break_Set[0] = '/'; + /*terminator can be '\0' this must go after '/'*/ + ClassName_Break_Set[1] = terminator; + ClassName_Break_Set[2] = '\0'; + + if(ARG_RPMFORMAT) { + strcat(out_string, "java("); + } + if (print_star) { + /* print the path to the leaf class but do not print the leaf + class, stop at the last '/' we fix this back below*/ + leaf_class = strrchr(in_string, '/'); + if (leaf_class) { + leaf_class[0] = terminator; + } + } + + while (*in_string != terminator) { + char *newEnd=0; + int copy_len; + + /* handle the break_set */ + + if (in_string[0] == '\0' ) { + die("Classname does not terminate with: '%c', '%s'\n", + terminator, in_string); + } else { + if (in_string[0] == '/' ) { + /* convert '/' to '.' */ + strcat(out_string, "."); + in_string++; + } + } + + newEnd = strpbrk(in_string, ClassName_Break_Set); + + if (newEnd) { + copy_len = newEnd-in_string; + } else { + if (terminator == '\0') { + copy_len = strlen(in_string); + } else { + copy_len = 0; + } + } + + /* handle upto but not including the break_set*/ + if (copy_len) { + strncat(out_string, in_string, copy_len); + in_string+=copy_len; + } + + } /*end while*/ + + if (leaf_class) { + /* print the star and fix the leaf class*/ + strcat(out_string, ".*"); + leaf_class[0] = '/'; + } + if(ARG_RPMFORMAT) { + strcat(out_string, ")"); + } + + free(ClassName_Break_Set); + return out_string; +} + + +/* Parse out just the class names from a java type and moves the string + pointer to the end of the string. */ + +void +dumpRefType(char *string) +{ + /* All class types start with a 'L' and and end with a ';'. We want + everyting in between. There might be more then one per string + like (params for a method call) */ + + string = strchr(string, 'L'); + while (string) { + string++; + print_table_add(formatClassName(string, ';', 0)); + string = strchr(string, ';'); + string = strchr(string, 'L'); + } + + return ; +} + + +/* Print out the classes referenced in the symbol table */ + +void +dumpRequires(symbolTable_t *symbolTable) { + int tem; + int ref = 0; + + for(tem=1; tem < symbolTable->poolSize; tem++ ) { + + /* dump all the classes in the const table. */ + ref = symbolTable->classRef[tem]; + if(ref) { + char *string = symbolTable->stringList[ref]; + if( !*string ) { + die("class num: %d, referenced string num: %d, " + "which is null.\n", + tem, ref); + } + if ( string[0] == '[' ) { + /* + This is an array. We need to ingore + strings like: + '[B' + '[[B' + '[[I' + + This hack leaves blank lines in the output + when a string not containing a class is + sent to dumpRefType. + */ + string = strchr(string, 'L'); + if (string) { + dumpRefType(string); + } + } else { + print_table_add(formatClassName(string, '\0', 0)); + } + } + + /* dump all the references */ + ref = symbolTable->typeRef[tem]; + if (ref) { + char *string = symbolTable->stringList[ref]; + if ( !*string ) { + die("type num: %d, referenced string num: %d, " + "which is null.\n", + tem, ref); + } + /* this is a java type... parse out the class names */ + dumpRefType(string); + } + + } /*end for*/ + + return ; +} + + +/* Read a java class files symbol table into memory. +*/ + + +void genSymbolTable (FILE *fileHandle, symbolTable_t *symbolTable) +{ + char ignore[10]; + int i=0; + + + /* We are called just after fileHandle saw the magic number, seek a + few bytes in to find the poolsize */ + + my_fread(&ignore, 4, 1, fileHandle); + + my_fread(&(symbolTable->poolSize), 2, 1, fileHandle); + symbolTable->poolSize=ntohs(symbolTable->poolSize); + + /* new the tables */ + + symbolTable->stringList = (char**) calloc(symbolTable->poolSize, + sizeof(char*)); + if(!symbolTable->stringList){ + outofmemory(); + } + + symbolTable->classRef = (short*) calloc(symbolTable->poolSize, + sizeof(short*)); + if(!symbolTable->classRef){ + outofmemory(); + } + + symbolTable->typeRef = (short*) calloc(symbolTable->poolSize, + sizeof(short*)); + if(!symbolTable->typeRef){ + outofmemory(); + } + + /* zero 'em all out. */ + for(i=0; i < symbolTable->poolSize; i++) { + symbolTable->stringList[i] = NULL; + symbolTable->classRef[i] = 0; + symbolTable->typeRef[i] = 0; + } + + + /* for the number of entries + (it starts at 1) + */ + + for(i=1; i < symbolTable->poolSize; i++) { + unsigned short type = 0; + unsigned short value = 0; + unsigned char tag = 0; + + /* read the type of this entry */ + + my_fread(&tag, 1, 1, fileHandle); + switch(tag) { + case 1: /* master string pool. */ + { + /* record all these strings */ + char *someString; + unsigned short length = 0; + + /* I am not sure if these strings must be null + terminated. I termiante them to be safe. */ + + my_fread(&length, 2, 1, fileHandle); + length=ntohs(length); + + someString = (char*) malloc(length+1); + if(!someString){ + outofmemory(); + } + my_fread(someString, length, 1, fileHandle); + someString[length]=0; + symbolTable->stringList[i] = someString; + + if (ARG_KEYWORDS) { + char *ptr=0; + + /* Each keyword can appear multiple times. Don't + bother with datastructures to store these strings, + if we need to print it print it now. */ + + /* it would be better if instead of printing the + strings "raw" I turn the space separated list + into a "\n" separated list*/ + + if (ARG_REQUIRES) { + ptr = is_lower_equal(someString, "rpm_requires: "); + if(ptr){ + print_list(ptr); + } + } + if (ARG_PROVIDES) { + ptr = is_lower_equal(someString, "rpm_provides: "); + if(ptr){ + print_list(ptr); + } + } + /* I wish there was a good way to handle this + ptr = is_lower_equal(someString, "rpm_conflicts: "); + */ + ptr = is_lower_equal(someString, "$revision: "); + if(ptr){ + KEYWORD_REVISION=ptr; + /* terminate the string before " $" */ + ptr = strchr(KEYWORD_REVISION, ' '); + if (ptr) { + *ptr = 0; + } + } + ptr = is_lower_equal(someString, "rpm_version: "); + if(ptr){ + KEYWORD_VERSION=ptr; + /* terminate the string at first whitespace */ + ptr = strchr(KEYWORD_VERSION, ' '); + if (ptr) { + *ptr = 0; + } + } + ptr = is_lower_equal(someString, "rpm_epoch: "); + if(ptr){ + KEYWORD_EPOCH=ptr; + /* terminate the string at first whitespace */ + ptr = strchr(KEYWORD_EPOCH, ' '); + if (ptr) { + *ptr = 0; + } + } + } + break; + } + case 2: /* unknow type!! */ + die("Unknown type in constant table. " + "Entry: %d. \n", i); + break; + case 3: /* int */ + my_fread(&ignore, 4, 1, fileHandle); + break; + case 4: /* float */ + my_fread(&ignore, 4, 1, fileHandle); + break; + case 5: /* long (counts as 2) */ + my_fread(&ignore, 8, 1, fileHandle); + i++; + break; + case 6: /* double (counts as 2) */ + my_fread(&ignore, 8, 1, fileHandle); + i++; + break; + case 7: /* Class */ + my_fread(&value, 2, 1, fileHandle); + symbolTable->classRef[i]=ntohs(value); + /* record which const it's referencing */ + check_range(i, symbolTable->classRef[i], symbolTable->poolSize); + break; + case 8: /* String */ + my_fread(&ignore, 2, 1, fileHandle); + break; + case 9: /* field reference */ + my_fread(&ignore, 4, 1, fileHandle); + break; + case 10: /* method reference */ + my_fread(&ignore, 4, 1, fileHandle); + break; + case 11: /* interface method reference */ + my_fread(&ignore, 4, 1, fileHandle); + break; + case 12: /* constant name/type */ + my_fread(&ignore, 2, 1, fileHandle); + my_fread(&type, 2, 1, fileHandle); + symbolTable->typeRef[i]=ntohs(type); + /* record the name, and the type it's referencing. */ + check_range(i, symbolTable->typeRef[i], symbolTable->poolSize); + break; + default: + die("Unknown tag type: %d.\n", + "Entry: %d. \n", tag, i); + break; + } + } + + return ; +} + + +/* Find the proper name of the current Java Class file and return a + formatted of the name. +*/ + +char* +findClassName (FILE *fileHandle, symbolTable_t *symbolTable) { + char ignore[10]; + unsigned short type = 0; + unsigned short jclass = 0; + char *className; + + /* seek a little past the end of the table */ + + my_fread(&ignore, 2, 1, fileHandle); + + /* read the name of this classfile */ + + my_fread(&type, 2, 1, fileHandle); + type=ntohs(type); + jclass = symbolTable->classRef[type]; + if( !jclass || + !symbolTable->stringList[jclass] ) { + die("Couln't find class: %d, provided by file.\n", jclass); + } + + className=symbolTable->stringList[jclass]; + + return className; + +} + + +/* Print the name of the class that we have found and whos symbol + table is loaded into memory. + + The name may need to have the version number printed after it, if + there are keywords in the symbol table. + + Additionally the class name may need to be printed twice, once with + the regular name of the class and once with the leaf class replaced + with a star. + +*/ + +void +dumpProvides(char *className) { + char *out_string; +#ifdef UNUSED + char *formattedClassName; + char *newline; +#endif + + /* Provide the star version of this class for jhtml + dependencies. This option is deprecated since jhtml is + deprecated. */ + + if (ARG_STARPROV) { + print_table_add(formatClassName(className, '\0', 1)); + } + + /* If we have information about the version number of this class + then add it to the formatted name and print this as well. */ + + out_string = formatClassName(className, '\0', 0); + + { + int len = 10; + + if (out_string) { + len += strlen(out_string); + } + if (KEYWORD_EPOCH) { + len += strlen(KEYWORD_EPOCH); + } + if (KEYWORD_VERSION) { + len += strlen(KEYWORD_VERSION); + } + if (KEYWORD_REVISION) { + len += strlen(KEYWORD_REVISION); + } + + out_string = realloc(out_string, len ); + if (!out_string){ + outofmemory(); + } + + } + + if( KEYWORD_VERSION || KEYWORD_REVISION ){ + /* It is easier to remove the extra new line here in one place + then to try and add a newline every where that formatClassName + is called */ + char *newline; + + /* I am not using rpm 3.0 yet so I need both the dependencies with + and without the version numbers, when I upgrade I will remove + this block (with copy_string) and change the "=" to " = " ten + lines down.*/ + { + char *copy_string; + copy_string = (char*) malloc(strlen(out_string)+1); + if (!copy_string){ + outofmemory(); + } + copy_string = strcpy(copy_string, out_string); + print_table_add(copy_string); + } + + newline = strrchr(out_string, '\n'); + if (newline) { + newline[0] = '\0'; + } + strcat(out_string, " = "); + if(KEYWORD_EPOCH){ + strcat(out_string, KEYWORD_EPOCH); + strcat(out_string, ":"); + } + if(KEYWORD_VERSION){ + strcat(out_string, KEYWORD_VERSION); + } else { + strcat(out_string, KEYWORD_REVISION); + } + strcat(out_string, "\n"); + } + + print_table_add(out_string); + out_string=NULL; + + return ; +} + + + + + +void freeSymbolTable (symbolTable_t *symbolTable) +{ + int i=0; + + for(i=1; i < symbolTable->poolSize; i++) { + if( symbolTable->stringList[i] ) { + free(symbolTable->stringList[i]); + symbolTable->stringList[i] = 0; + } + } + + free(symbolTable->stringList); + symbolTable->stringList=0; + + free(symbolTable->classRef); + symbolTable->classRef=0; + + free(symbolTable->typeRef); + symbolTable->typeRef=0; + + return ; +} + + +/* Process each file. + This procedure must be called directly after finding + the magic number. +*/ + +void processJavaFile (FILE *fileHandle) { + symbolTable_t symbolTable= {0}; + char *format_class_name; + + genSymbolTable(fileHandle, &symbolTable); + CLASS_NAME=findClassName(fileHandle, &symbolTable); + + if(ARG_DEPSFORMAT) { + const char *prefix_seperator = ": "; + + format_class_name = formatClassName(CLASS_NAME, '\0', 0); + + OUTPUT_PREFIX = (char*) malloc(strlen(format_class_name)+ + strlen(prefix_seperator)+1); + if (!OUTPUT_PREFIX){ + outofmemory(); + } + OUTPUT_PREFIX = strcpy(OUTPUT_PREFIX, format_class_name); + strcat(OUTPUT_PREFIX, prefix_seperator); + } + + if(ARG_PROVIDES) { + dumpProvides(CLASS_NAME); + } + if(ARG_REQUIRES) { + dumpRequires(&symbolTable); + } + + free(OUTPUT_PREFIX); + OUTPUT_PREFIX = 0; + freeSymbolTable(&symbolTable); + + return ; + +} + + +int +main(int argc, char *argv[]) +{ + FILE *fileHandle; + int i = 0; + int rc = 0; + int foundMagic=0; + + PROGRAM_NAME=argv[0]; + + if(argv[1] == NULL) { + usage(); + } + + /* parse arguments which are not filenames*/ + + for (i = 1; argv[i] != NULL; i++) { + + if (0) { + /* + First entry a dummy to get the + other entries to align correctly + */ + ; + } else if ( !strcmp("-p",argv[i]) || !strcmp("--provides",argv[i]) ) { + ARG_PROVIDES = 1; + } else if ( !strcmp("-r",argv[i]) || !strcmp("--requires",argv[i]) ) { + ARG_REQUIRES = 1; + } else if ( !strcmp("-h",argv[i]) || !strcmp("--help",argv[i]) || + !strcmp("-?",argv[i]) ) { + + usage(); + } else if ( !strcmp("-F",argv[i]) || !strcmp("--rpmformat",argv[i]) ) { + ARG_RPMFORMAT=1; + } else if ( !strcmp("-d",argv[i]) || !strcmp("--depsformat",argv[i]) ) { + ARG_DEPSFORMAT=1; + } else if ( !strcmp("-k",argv[i]) || !strcmp("--keywords",argv[i]) ) { + ARG_KEYWORDS=1; + } else if ( !strcmp("-s",argv[i]) || !strcmp("--starprov",argv[i]) ) { + ARG_STARPROV=1; + } else if ( !strcmp("--",argv[i]) ) { + i++; + break; + } else { + /* we do not recognize the argument, must be a filename*/ + break; + } + } /*end for arguments which are not filenames*/ + + /* check arguments for consistancy */ + + if ( !ARG_PROVIDES && !ARG_REQUIRES ) { + die ("Must specify either --provides or --requires.\n"); + } + + if ( ARG_PROVIDES && ARG_REQUIRES ) { + die ("Can not specify both --provides and --requires.\n"); + } + + if ( ARG_REQUIRES && ARG_STARPROV) { + die ("Can not specify both --requires and --starpov.\n"); + } + + if(argv[i] == NULL) { + die ("Must specify Java class files.\n"); + } + + /* parse arguments which are filenames. */ + + for ( /*null initializer*/; argv[i] != NULL; i++) { + + /*open the correct file and process it*/ + + if ( !strcmp("-", argv[i]) ) { + /* use stdin, might be a jar file */ + fileHandle = stdin; + FILE_NAME = "<stdin>"; + + foundMagic = findJavaMagic(fileHandle); + while (foundMagic) { + processJavaFile(fileHandle); + foundMagic = findJavaMagic(fileHandle); + } + } else { + /* Open a disk file*/ + fileHandle = fopen(argv[i], "r"); + if( fileHandle == 0 ) { + die ("Could not open file: %s.\n", argv[i]); + } + fileHandle = fileHandle; + FILE_NAME = argv[i]; + + foundMagic = findJavaMagic(fileHandle); + if (foundMagic) { + processJavaFile(fileHandle); + } + } + + rc = fclose(fileHandle); + if( rc ) { + die ("Could not close file: %s.\n", FILE_NAME); + } + CLASS_NAME=0; + } /*end parsing arguments which are filenames*/ + + print_table_flush(); + return 0; +} diff --git a/tools/rpmdeps.c b/tools/rpmdeps.c new file mode 100644 index 0000000..438209c --- /dev/null +++ b/tools/rpmdeps.c @@ -0,0 +1,105 @@ +#include "system.h" +const char *__progname; + +#include <rpm/rpmbuild.h> +#include <rpm/argv.h> +#include <rpm/rpmds.h> +#include <rpm/rpmfc.h> + +#include "debug.h" + +char *progname; + +static int print_provides; + +static int print_requires; + +static void rpmdsPrint(const char * msg, rpmds ds, FILE * fp) +{ + if (fp == NULL) fp = stderr; + + if (msg) + fprintf(fp, "===================================== %s\n", msg); + + ds = rpmdsInit(ds); + while (rpmdsNext(ds) >= 0) + fprintf(fp, "%s\n", rpmdsDNEVR(ds)+2); +} + +static struct poptOption optionsTable[] = { + + { NULL, '\0', POPT_ARG_INCLUDE_TABLE, rpmcliAllPoptTable, 0, + N_("Common options for all rpm modes and executables:"), + NULL }, + + { "provides", 'P', POPT_ARG_VAL, &print_provides, -1, + NULL, NULL }, + { "requires", 'R', POPT_ARG_VAL, &print_requires, -1, + NULL, NULL }, + + POPT_AUTOALIAS + POPT_AUTOHELP + POPT_TABLEEND +}; + +int +main(int argc, char *argv[]) +{ + poptContext optCon; + ARGV_t av = NULL; + rpmfc fc; + int ac = 0; + int ec = 1; +char buf[BUFSIZ]; + + if ((progname = strrchr(argv[0], '/')) != NULL) + progname++; + else + progname = argv[0]; + + optCon = rpmcliInit(argc, argv, optionsTable); + if (optCon == NULL) + goto exit; + + av = (ARGV_t) poptGetArgs(optCon); + ac = argvCount(av); + + if (ac == 0) { + char * b, * be; + av = NULL; + while ((b = fgets(buf, sizeof(buf), stdin)) != NULL) { + buf[sizeof(buf)-1] = '\0'; + be = b + strlen(buf) - 1; + while (strchr("\r\n", *be) != NULL) + *be-- = '\0'; + argvAdd(&av, b); + } + ac = argvCount(av); + } + + /* Make sure file names are sorted. */ + argvSort(av, NULL); + + + /* Build file/package class and dependency dictionaries. */ + fc = rpmfcCreate(getenv("RPM_BUILD_ROOT"), 0); + if (rpmfcClassify(fc, av, NULL) || rpmfcApply(fc)) + goto exit; + +if (_rpmfc_debug) { +rpmfcPrint(buf, fc, NULL); +} + + if (print_provides) + rpmdsPrint(NULL, rpmfcProvides(fc), stdout); + if (print_requires) + rpmdsPrint(NULL, rpmfcRequires(fc), stdout); + + fc = rpmfcFree(fc); + + ec = 0; + +exit: + optCon = rpmcliFini(optCon); + return ec; +} diff --git a/tools/rpmgraph.c b/tools/rpmgraph.c new file mode 100644 index 0000000..bbb9d29 --- /dev/null +++ b/tools/rpmgraph.c @@ -0,0 +1,258 @@ +#include "system.h" +const char *__progname; + +#include <rpm/rpmcli.h> +#include <rpm/rpmlib.h> /* rpmReadPackageFile */ +#include <rpm/rpmdb.h> +#include <rpm/rpmps.h> +#include <rpm/rpmte.h> +#include <rpm/rpmts.h> +#include <rpm/rpmds.h> + +#include <rpm/rpmlog.h> +#include <rpm/rpmfileutil.h> + +#include "lib/manifest.h" +#include "debug.h" + +static int noDeps = 1; + +static rpmVSFlags vsflags = 0; + +static int +rpmGraph(rpmts ts, struct rpmInstallArguments_s * ia, const char ** fileArgv) +{ + rpmps ps; + char ** pkgURL = NULL; + char * pkgState = NULL; + const char ** fnp; + char * fileURL = NULL; + int numPkgs = 0; + int numFailed = 0; + int prevx = 0; + int pkgx = 0; + char ** argv = NULL; + int argc = 0; + char ** av = NULL; + int ac = 0; + Header h; + rpmRC rpmrc; + int rc = 0; + rpmVSFlags ovsflags; + int i; + + if (fileArgv == NULL) + return 0; + + /* Build fully globbed list of arguments in argv[argc]. */ + for (fnp = fileArgv; *fnp; fnp++) { + av = _free(av); + ac = 0; + rc = rpmGlob(*fnp, &ac, &av); + if (rc || ac == 0) continue; + + argv = xrealloc(argv, (argc+2) * sizeof(*argv)); + memcpy(argv+argc, av, ac * sizeof(*av)); + argc += ac; + argv[argc] = NULL; + } + av = _free(av); ac = 0; + +restart: + /* Allocate sufficient storage for next set of args. */ + if (pkgx >= numPkgs) { + numPkgs = pkgx + argc; + pkgURL = xrealloc(pkgURL, (numPkgs + 1) * sizeof(*pkgURL)); + memset(pkgURL + pkgx, 0, ((argc + 1) * sizeof(*pkgURL))); + pkgState = xrealloc(pkgState, (numPkgs + 1) * sizeof(*pkgState)); + memset(pkgState + pkgx, 0, ((argc + 1) * sizeof(*pkgState))); + } + + /* Copy next set of args. */ + for (i = 0; i < argc; i++) { + fileURL = _free(fileURL); + fileURL = argv[i]; + argv[i] = NULL; + + pkgURL[pkgx] = fileURL; + fileURL = NULL; + pkgx++; + } + fileURL = _free(fileURL); + + /* Continue processing file arguments, building transaction set. */ + for (fnp = (const char **) pkgURL+prevx; *fnp != NULL; fnp++, prevx++) { + const char * fileName; + FD_t fd; + + (void) urlPath(*fnp, &fileName); + + /* Try to read the header from a package file. */ + fd = Fopen(*fnp, "r.ufdio"); + if (fd == NULL || Ferror(fd)) { + rpmlog(RPMLOG_ERR, _("open of %s failed: %s\n"), *fnp, + Fstrerror(fd)); + if (fd) { + Fclose(fd); + fd = NULL; + } + numFailed++; *fnp = NULL; + continue; + } + + /* Read the header, verifying signatures (if present). */ + ovsflags = rpmtsSetVSFlags(ts, vsflags); + rpmrc = rpmReadPackageFile(ts, fd, *fnp, &h); + ovsflags = rpmtsSetVSFlags(ts, ovsflags); + Fclose(fd); + fd = NULL; + + switch (rpmrc) { + case RPMRC_FAIL: + default: + rpmlog(RPMLOG_ERR, _("%s cannot be installed\n"), *fnp); + numFailed++; *fnp = NULL; + break; + case RPMRC_OK: + rc = rpmtsAddInstallElement(ts, h, (fnpyKey)fileName, 0, NULL); + break; + case RPMRC_NOTFOUND: + goto maybe_manifest; + break; + } + h = headerFree(h); + continue; + +maybe_manifest: + /* Try to read a package manifest. */ + fd = Fopen(*fnp, "r.fpio"); + if (fd == NULL || Ferror(fd)) { + rpmlog(RPMLOG_ERR, _("open of %s failed: %s\n"), *fnp, + Fstrerror(fd)); + if (fd) { + Fclose(fd); + fd = NULL; + } + numFailed++; *fnp = NULL; + break; + } + + /* Read list of packages from manifest. */ + rc = rpmReadPackageManifest(fd, &argc, &argv); + if (rc) + rpmlog(RPMLOG_NOTICE, _("%s: read manifest failed: %s\n"), + fileURL, Fstrerror(fd)); + Fclose(fd); + fd = NULL; + + /* If successful, restart the query loop. */ + if (rc == 0) { + prevx++; + goto restart; + } + + numFailed++; *fnp = NULL; + break; + } + + if (numFailed > 0) goto exit; + + if (!noDeps) { + rc = rpmtsCheck(ts); + if (rc) { + numFailed += numPkgs; + goto exit; + } + ps = rpmtsProblems(ts); + if (rpmpsNumProblems(ps) > 0) { + rpmlog(RPMLOG_ERR, _("Failed dependencies:\n")); + rpmpsPrint(NULL, ps); + numFailed += numPkgs; + } + ps = rpmpsFree(ps); + } + + rc = rpmtsOrder(ts); + if (rc) + goto exit; + + { rpmtsi pi; + rpmte p; + rpmte q; + int oType = TR_ADDED; + + fprintf(stdout, "digraph XXX {\n"); + + fprintf(stdout, " rankdir=LR\n"); + + fprintf(stdout, "//===== Packages:\n"); + pi = rpmtsiInit(ts); + while ((p = rpmtsiNext(pi, oType)) != NULL) { + q = rpmteParent(p); + if (q != NULL) + fprintf(stdout, " \"%s\" -> \"%s\"\n", rpmteN(p), rpmteN(q)); + else { + fprintf(stdout, " \"%s\"\n", rpmteN(p)); + fprintf(stdout, " { rank=max ; \"%s\" }\n", rpmteN(p)); + } + } + pi = rpmtsiFree(pi); + + fprintf(stdout, "}\n"); + } + + rc = 0; + +exit: + for (i = 0; i < numPkgs; i++) + pkgURL[i] = _free(pkgURL[i]); + pkgState = _free(pkgState); + pkgURL = _free(pkgURL); + argv = _free(argv); + + return rc; +} + +static struct poptOption optionsTable[] = { + { "check", '\0', POPT_ARG_VAL|POPT_ARGFLAG_DOC_HIDDEN, &noDeps, 0, + N_("don't verify package dependencies"), NULL }, + { "nolegacy", '\0', POPT_BIT_SET, &vsflags, RPMVSF_NEEDPAYLOAD, + N_("don't verify header+payload signature"), NULL }, + { NULL, '\0', POPT_ARG_INCLUDE_TABLE, rpmcliAllPoptTable, 0, + N_("Common options for all rpm modes and executables:"), + NULL }, + + POPT_AUTOALIAS + POPT_AUTOHELP + POPT_TABLEEND +}; + +int +main(int argc, char *argv[]) +{ + rpmts ts = NULL; + struct rpmInstallArguments_s * ia = &rpmIArgs; + poptContext optCon; + int ec = 0; + + optCon = rpmcliInit(argc, argv, optionsTable); + if (optCon == NULL) + exit(EXIT_FAILURE); + + ts = rpmtsCreate(); + if (rpmcliQueryFlags & VERIFY_DIGEST) + vsflags |= _RPMVSF_NODIGESTS; + if (rpmcliQueryFlags & VERIFY_SIGNATURE) + vsflags |= _RPMVSF_NOSIGNATURES; + if (rpmcliQueryFlags & VERIFY_HDRCHK) + vsflags |= RPMVSF_NOHDRCHK; + (void) rpmtsSetVSFlags(ts, vsflags); + + ec = rpmGraph(ts, ia, poptGetArgs(optCon)); + + ts = rpmtsFree(ts); + + optCon = rpmcliFini(optCon); + + return ec; +} |