/* Copyright (C) 2001, 2002, 2003, 2004, 2006, 2013 Red Hat, Inc. Written by Jakub Jelinek , 2001. * SPDX-License-Identifier: GPL-2.0-or-later */ #include #include #include #include #include #include #include #include #include #include #include #include "prelinktab.h" htab_t prelink_devino_htab, prelink_filename_htab; int prelink_entry_count; static hashval_t devino_hash (const void *p) { struct prelink_entry *e = (struct prelink_entry *)p; return (e->dev << 2) ^ (e->ino) ^ (e->ino >> 20); } static int devino_eq (const void *p, const void *q) { struct prelink_entry *e = (struct prelink_entry *)p; struct prelink_entry *f = (struct prelink_entry *)q; return e->ino == f->ino && e->dev == f->dev; } static hashval_t filename_hash (const void *p) { struct prelink_entry *e = (struct prelink_entry *)p; const unsigned char *s = (const unsigned char *)e->filename; hashval_t h = 0; unsigned char c; size_t len = 0; while ((c = *s++) != '\0') { h += c + (c << 17); h ^= h >> 2; ++len; } return h + len + (len << 17); } static int filename_eq (const void *p, const void *q) { struct prelink_entry *e = (struct prelink_entry *)p; struct prelink_entry *f = (struct prelink_entry *)q; return strcmp (e->filename, f->filename) == 0; } int prelink_init_cache (void) { prelink_devino_htab = htab_try_create (100, devino_hash, devino_eq, NULL); prelink_filename_htab = htab_try_create (100, filename_hash, filename_eq, NULL); if (prelink_devino_htab == NULL || prelink_filename_htab == NULL) error (EXIT_FAILURE, ENOMEM, "Could not create hash table"); return 0; } struct prelink_entry * prelink_find_entry (const char *filename, const struct stat64 *stp, int insert) { struct prelink_entry e, *ent = NULL; void **filename_slot, *dummy = NULL; void **devino_slot = NULL; struct stat64 st; char *canon_filename = NULL; e.filename = filename; filename_slot = htab_find_slot (prelink_filename_htab, &e, insert ? INSERT : NO_INSERT); if (filename_slot == NULL) { if (insert) goto error_out; filename_slot = &dummy; } if (*filename_slot != NULL) return (struct prelink_entry *) *filename_slot; if (! stp) { canon_filename = prelink_canonicalize (filename, &st); if (canon_filename == NULL && stat64 (filename, &st) < 0) { error (0, errno, "Could not stat %s", filename); if (insert) { *filename_slot = &dummy; htab_clear_slot (prelink_filename_htab, filename_slot); } return NULL; } stp = &st; } e.dev = stp->st_dev; e.ino = stp->st_ino; devino_slot = htab_find_slot (prelink_devino_htab, &e, insert ? INSERT : NO_INSERT); if (devino_slot == NULL) { if (insert) goto error_out; free (canon_filename); return NULL; } if (*devino_slot != NULL) { ent = (struct prelink_entry *) *devino_slot; if (canon_filename == NULL) canon_filename = prelink_canonicalize (filename, NULL); if (canon_filename == NULL) { error (0, 0, "Could not canonicalize filename %s", filename); goto error_out2; } if (strcmp (canon_filename, ent->canon_filename) != 0) { struct prelink_link *hardlink; hardlink = (struct prelink_link *) malloc (sizeof (struct prelink_link)); if (hardlink == NULL) { error (0, ENOMEM, "Could not record hardlink %s to %s", canon_filename, ent->canon_filename); goto error_out2; } hardlink->canon_filename = canon_filename; hardlink->next = ent->hardlink; ent->hardlink = hardlink; } else free (canon_filename); return ent; } if (! insert) { if (canon_filename != NULL) free (canon_filename); return NULL; } ent = (struct prelink_entry *) calloc (sizeof (struct prelink_entry), 1); if (ent == NULL) goto error_out; ent->filename = strdup (filename); if (ent->filename == NULL) goto error_out; if (canon_filename != NULL) ent->canon_filename = canon_filename; else ent->canon_filename = prelink_canonicalize (filename, NULL); if (ent->canon_filename == NULL) { error (0, 0, "Could not canonicalize filename %s", filename); free ((char *) ent->filename); free (ent); goto error_out2; } ent->dev = stp->st_dev; ent->ino = stp->st_ino; ent->ctime = stp->st_ctime; ent->mtime = stp->st_mtime; *filename_slot = ent; *devino_slot = ent; ++prelink_entry_count; return ent; error_out: free (ent); error (0, ENOMEM, "Could not insert %s into hash table", filename); error_out2: if (insert) { if (filename_slot != NULL) { assert (*filename_slot == NULL); *filename_slot = &dummy; htab_clear_slot (prelink_filename_htab, filename_slot); } if (devino_slot != NULL && *devino_slot == NULL) { *devino_slot = &dummy; htab_clear_slot (prelink_devino_htab, devino_slot); } } free (canon_filename); return NULL; } static struct prelink_entry * prelink_load_entry (const char *filename) { struct prelink_entry e, *ent = NULL; void **filename_slot, *dummy = NULL; void **devino_slot = &dummy; struct stat64 st; uint32_t ctime = 0, mtime = 0; char *canon_filename = NULL; e.filename = filename; filename_slot = htab_find_slot (prelink_filename_htab, &e, INSERT); if (filename_slot == NULL) goto error_out; if (*filename_slot != NULL) return (struct prelink_entry *) *filename_slot; canon_filename = prelink_canonicalize (filename, &st); if (canon_filename == NULL) goto error_out2; if (strcmp (canon_filename, filename) != 0) { *filename_slot = &dummy; htab_clear_slot (prelink_filename_htab, filename_slot); e.filename = canon_filename; filename_slot = htab_find_slot (prelink_filename_htab, &e, INSERT); if (filename_slot == NULL) goto error_out; if (*filename_slot != NULL) { free (canon_filename); return (struct prelink_entry *) *filename_slot; } } if (! S_ISREG (st.st_mode)) { free (canon_filename); *filename_slot = &dummy; htab_clear_slot (prelink_filename_htab, filename_slot); return NULL; } else { e.dev = st.st_dev; e.ino = st.st_ino; ctime = (uint32_t) st.st_ctime; mtime = (uint32_t) st.st_mtime; devino_slot = htab_find_slot (prelink_devino_htab, &e, INSERT); if (devino_slot == NULL) goto error_out; } if (*devino_slot != NULL) { free (canon_filename); *filename_slot = &dummy; htab_clear_slot (prelink_filename_htab, filename_slot); return (struct prelink_entry *) *devino_slot; } ent = (struct prelink_entry *) calloc (sizeof (struct prelink_entry), 1); if (ent == NULL) goto error_out; ent->filename = strdup (filename); if (ent->filename == NULL) goto error_out; ent->canon_filename = canon_filename; ent->dev = e.dev; ent->ino = e.ino; ent->ctime = ctime; ent->mtime = mtime; *filename_slot = ent; *devino_slot = ent; ++prelink_entry_count; return ent; error_out: free (ent); error (0, ENOMEM, "Could not insert %s into hash table", filename); error_out2: if (filename_slot != NULL) { *filename_slot = &dummy; htab_clear_slot (prelink_filename_htab, filename_slot); } if (devino_slot != NULL && devino_slot != &dummy) { *devino_slot = &dummy; htab_clear_slot (prelink_devino_htab, devino_slot); } free (canon_filename); return NULL; } static int deps_cmp (const void *A, const void *B) { struct prelink_entry *a = * (struct prelink_entry **) A; struct prelink_entry *b = * (struct prelink_entry **) B; if (a == NULL) return (b != NULL); if (a != NULL && b == NULL) return -1; if (a->type == ET_NONE && b->type != ET_NONE) return 1; if (a->type != ET_NONE && b->type == ET_NONE) return -1; /* Libraries with fewest dependencies first. */ if (a->ndepends < b->ndepends) return -1; if (a->ndepends > b->ndepends) return 1; return 0; } int prelink_load_cache (void) { int fd, i, j; struct stat64 st; struct prelink_cache *cache; struct prelink_entry **ents; size_t cache_size; uint32_t string_start, *dep; fd = open (prelink_cache, O_RDONLY); if (fd < 0) return 0; /* The cache does not exist yet. */ if (fstat64 (fd, &st) < 0 || st.st_size == 0) { close (fd); return 0; } cache = mmap (0, st.st_size, PROT_READ, MAP_SHARED, fd, 0); if (cache == MAP_FAILED) error (EXIT_FAILURE, errno, "mmap of prelink cache file failed."); cache_size = st.st_size; if (memcmp (cache->magic, PRELINK_CACHE_MAGIC, sizeof (PRELINK_CACHE_MAGIC) - 1)) { if (memcmp (cache->magic, PRELINK_CACHE_NAME, sizeof (PRELINK_CACHE_NAME) - 1)) error (EXIT_FAILURE, 0, "%s: is not prelink cache file", prelink_cache); munmap (cache, cache_size); return 0; } dep = (uint32_t *) & cache->entry[cache->nlibs]; string_start = ((long) dep) - ((long) cache) + cache->ndeps * sizeof (uint32_t); ents = (struct prelink_entry **) alloca (cache->nlibs * sizeof (struct prelink_entry *)); memset (ents, 0, cache->nlibs * sizeof (struct prelink_entry *)); for (i = 0; i < cache->nlibs; i++) { /* Sanity checks. */ if (cache->entry[i].filename < string_start || cache->entry[i].filename >= string_start + cache->len_strings || cache->entry[i].depends >= cache->ndeps) error (EXIT_FAILURE, 0, "%s: bogus prelink cache file", prelink_cache); ents[i] = prelink_load_entry (((char *) cache) + cache->entry[i].filename); } for (i = 0; i < cache->nlibs; i++) { if (ents[i] == NULL) continue; if (ents[i]->type != ET_NONE) continue; ents[i]->checksum = cache->entry[i].checksum; ents[i]->base = cache->entry[i].base; ents[i]->end = cache->entry[i].end; ents[i]->type = (ents[i]->base == 0 && ents[i]->end == 0) ? ET_CACHE_EXEC : ET_CACHE_DYN; ents[i]->flags = cache->entry[i].flags; if (ents[i]->flags == PCF_UNPRELINKABLE) ents[i]->type = (quick || print_cache) ? ET_UNPRELINKABLE : ET_NONE; /* If mtime is equal to ctime, assume the filesystem does not store ctime. */ if (quick && ((ents[i]->ctime == ents[i]->mtime && ents[i]->type != ET_UNPRELINKABLE) || ents[i]->ctime != cache->entry[i].ctime || ents[i]->mtime != cache->entry[i].mtime)) ents[i]->type = ET_NONE; for (j = cache->entry[i].depends; dep[j] != i; ++j) if (dep[j] >= cache->nlibs) error (EXIT_FAILURE, 0, "%s: bogus prelink cache file", prelink_cache); else if (ents[dep[j]] == NULL) ents[i]->type = ET_NONE; if (ents[i]->type == ET_NONE) continue; ents[i]->ndepends = j - cache->entry[i].depends; if (ents[i]->ndepends) { ents[i]->depends = (struct prelink_entry **) malloc (ents[i]->ndepends * sizeof (struct prelink_entry *)); if (ents[i]->depends == NULL) error (EXIT_FAILURE, ENOMEM, "Cannot read cache file %s", prelink_cache); for (j = 0; j < ents[i]->ndepends; ++j) ents[i]->depends[j] = ents[dep[cache->entry[i].depends + j]]; } } if (quick) { qsort (ents, cache->nlibs, sizeof (struct prelink_entry *), deps_cmp); for (i = 0; i < cache->nlibs; ++i) { if (ents[i] == NULL || ents[i]->type == ET_NONE) continue; for (j = 0; j < ents[i]->ndepends; ++j) if (ents[i]->depends[j]->type == ET_NONE) { ents[i]->type = ET_NONE; free (ents[i]->depends); ents[i]->depends = NULL; ents[i]->ndepends = 0; break; } } } munmap (cache, cache_size); close (fd); return 0; } static int prelink_print_cache_size (void **p, void *info) { struct prelink_entry *e = * (struct prelink_entry **) p; int *psize = (int *) info; if ((e->base & 0xffffffff) != e->base || (e->end & 0xffffffff) != e->end) { *psize = 16; return 0; } return 1; } static int prelink_print_cache_object (void **p, void *info) { struct prelink_entry *e = * (struct prelink_entry **) p; int *psize = (int *) info, i; if (e->type == ET_UNPRELINKABLE) { printf ("%s (not prelinkable)%s\n", e->filename, e->ndepends ? ":" : ""); for (i = 0; i < e->ndepends; i++) if (e->depends[i]->type == ET_UNPRELINKABLE) printf (" %s (not prelinkable)\n", e->depends[i]->filename); else printf (" %s [0x%08x]\n", e->depends[i]->filename, e->depends[i]->checksum); return 1; } if (e->type == ET_CACHE_DYN) printf ("%s [0x%08x] 0x%0*llx-0x%0*llx%s\n", e->filename, e->checksum, *psize, (long long) e->base, *psize, (long long) e->end, e->ndepends ? ":" : ""); else printf ("%s%s\n", e->filename, e->ndepends ? ":" : ""); for (i = 0; i < e->ndepends; i++) printf (" %s [0x%08x]\n", e->depends[i]->filename, e->depends[i]->checksum); return 1; } int prelink_print_cache (void) { int size = 8; printf ("%d objects found in prelink cache `%s'\n", prelink_entry_count, prelink_cache); htab_traverse (prelink_filename_htab, prelink_print_cache_size, &size); htab_traverse (prelink_filename_htab, prelink_print_cache_object, &size); return 0; } struct collect_ents { struct prelink_entry **ents; size_t len_strings; int nents; int ndeps; }; static int prelink_save_cache_check (struct prelink_entry *ent) { int i; for (i = 0; i < ent->ndepends; ++i) switch (ent->depends[i]->type) { case ET_DYN: if (ent->depends[i]->done < 2 || (quick && (ent->depends[i]->flags & PCF_PRELINKED))) return 1; break; case ET_CACHE_DYN: if (prelink_save_cache_check (ent->depends[i])) return 1; break; case ET_UNPRELINKABLE: if (ent->type != ET_UNPRELINKABLE) return 1; if (prelink_save_cache_check (ent->depends[i])) return 1; break; default: return 1; } return 0; } static int find_ents (void **p, void *info) { struct collect_ents *l = (struct collect_ents *) info; struct prelink_entry *e = * (struct prelink_entry **) p; if (((e->type == ET_DYN || e->type == ET_EXEC) && e->done == 2) || ((e->type == ET_CACHE_DYN || e->type == ET_CACHE_EXEC || e->type == ET_UNPRELINKABLE) && ! prelink_save_cache_check (e))) { l->ents[l->nents++] = e; l->ndeps += e->ndepends + 1; l->len_strings += strlen (e->canon_filename) + 1; } return 1; } int prelink_save_cache (int do_warn) { struct prelink_cache cache; struct collect_ents l; struct prelink_cache_entry *data; uint32_t *deps, ndeps = 0, i, j, k; char *strings; int fd, len; struct prelink_entry *ents_array[prelink_entry_count]; memset (&cache, 0, sizeof (cache)); memcpy ((char *) & cache, PRELINK_CACHE_MAGIC, sizeof (PRELINK_CACHE_MAGIC) - 1); l.ents = ents_array; l.nents = 0; l.ndeps = 0; l.len_strings = 0; htab_traverse (prelink_filename_htab, find_ents, &l); cache.nlibs = l.nents; cache.ndeps = l.ndeps; cache.len_strings = l.len_strings; len = cache.nlibs * sizeof (struct prelink_cache_entry) + cache.ndeps * sizeof (uint32_t) + cache.len_strings; char data_buf[len]; data = (struct prelink_cache_entry *) data_buf; deps = (uint32_t *) & data[cache.nlibs]; strings = (char *) & deps[cache.ndeps]; for (i = 0; i < l.nents; ++i) { data[i].filename = (strings - (char *) data) + sizeof (cache); strings = stpcpy (strings, l.ents[i]->canon_filename) + 1; data[i].checksum = l.ents[i]->checksum; data[i].flags = l.ents[i]->flags & ~PCF_PRELINKED; data[i].ctime = l.ents[i]->ctime; data[i].mtime = l.ents[i]->mtime; if (l.ents[i]->type == ET_EXEC || l.ents[i]->type == ET_CACHE_EXEC) { data[i].base = 0; data[i].end = 0; } else if (l.ents[i]->type == ET_UNPRELINKABLE) { data[i].base = 0; data[i].end = 0; data[i].checksum = 0; data[i].flags = PCF_UNPRELINKABLE; } else { data[i].base = l.ents[i]->base; data[i].end = l.ents[i]->end; } } for (i = 0; i < cache.nlibs; i++) { data[i].depends = ndeps; for (j = 0; j < l.ents[i]->ndepends; j++) { for (k = 0; k < cache.nlibs; k++) if (l.ents[k] == l.ents[i]->depends[j]) break; if (k == cache.nlibs) abort (); deps[ndeps++] = k; } deps[ndeps++] = i; } size_t prelink_cache_len = strlen (prelink_cache); char prelink_cache_tmp [prelink_cache_len + sizeof (".XXXXXX")]; memcpy (mempcpy (prelink_cache_tmp, prelink_cache, prelink_cache_len), ".XXXXXX", sizeof (".XXXXXX")); fd = mkstemp (prelink_cache_tmp); if (fd < 0) { error (0, errno, "Could not write prelink cache"); return 1; } if (write (fd, &cache, sizeof (cache)) != sizeof (cache) || write (fd, data, len) != len || fchmod (fd, 0644) || fsync (fd) || close (fd) || rename (prelink_cache_tmp, prelink_cache)) { error (0, errno, "Could not write prelink cache"); unlink (prelink_cache_tmp); return 1; } return 0; } #ifndef NDEBUG static void prelink_entry_dumpfn (FILE *f, const void *ptr) { struct prelink_entry *e = (struct prelink_entry *) ptr; struct prelink_link *l; int i; fprintf (f, "%s|%s|%s|%x|%x|%llx|%llx|%llx|%llx|%llx|%d|%d|%d|%d|%d|%d|%d|", e->filename, strcmp (e->canon_filename, e->filename) ? e->canon_filename : "", e->soname && strcmp (e->soname, e->filename) ? e->soname : "", e->timestamp, e->checksum, (long long) e->base, (long long) e->end, (long long) e->pltgot, (long long) e->dev, (long long) e->ino, e->type, e->done, e->ndepends, e->refs, e->flags, e->prev ? e->prev->u.tmp : -1, e->next ? e->next->u.tmp : -1); for (i = 0; i < e->ndepends; ++i) fprintf (f, "%d-", e->depends [i]->u.tmp); fputc ('|', f); for (l = e->hardlink; l; l = l->next) fprintf (f, "%s|", l->canon_filename); fputs ("\n", f); } void prelink_entry_dump (htab_t htab, const char *filename) { size_t i; for (i = 0; i < htab->size; ++i) if (htab->entries [i] && htab->entries [i] != (void *) 1) ((struct prelink_entry *) htab->entries [i])->u.tmp = i; htab_dump (htab, filename, prelink_entry_dumpfn); } static char *restore_line; static size_t restore_size; static void * prelink_entry_restorefn (FILE *f) { struct prelink_entry *e; struct prelink_link **plink; char *p, *q, *s; long long ll[5]; int ii[5]; int i; if (getline (&restore_line, &restore_size, f) < 0) abort (); e = (struct prelink_entry *) calloc (1, sizeof (struct prelink_entry)); if (e == NULL) abort (); p = restore_line; q = strchr (p, '|'); s = malloc (q - p + 1); memcpy (s, p, q - p); s [q - p] = '\0'; e->filename = s; ++q; p = q; if (*p == '|') e->canon_filename = strdup (e->filename); else { q = strchr (p, '|'); s = malloc (q - p + 1); memcpy (s, p, q - p); s [q - p] = '\0'; e->canon_filename = s; } ++q; p = q; if (*p == '|') e->soname = strdup (e->filename); else { q = strchr (p, '|'); s = malloc (q - p + 1); memcpy (s, p, q - p); s [q - p] = '\0'; e->soname = s; } p = q + 1; if (sscanf (p, "%x|%x|%llx|%llx|%llx|%llx|%llx|%d|%d|%d|%d|%d|%d|%d|%n", ii, ii + 1, ll, ll + 1, ll + 2, ll + 3, ll + 4, &e->type, &e->done, &e->ndepends, &e->refs, &e->flags, ii + 2, ii + 3, ii + 4) < 14) abort (); e->timestamp = ii[0]; e->checksum = ii[1]; e->base = ll[0]; e->end = ll[1]; e->pltgot = ll[2]; e->dev = ll[3]; e->ino = ll[4]; e->prev = (void *) (long) ii[2]; e->next = (void *) (long) ii[3]; e->depends = (struct prelink_entry **) malloc (e->ndepends * sizeof (struct prelink_entry *)); p += ii[4]; for (i = 0; i < e->ndepends; ++i) { e->depends [i] = (void *) strtol (p, &q, 0); if (p == q || *q != '-') abort (); p = q + 1; } if (*p++ != '|') abort (); plink = &e->hardlink; while (*p != '\n') { struct prelink_link *link = (struct prelink_link *) malloc (sizeof (struct prelink_link)); q = strchr (p, '|'); *plink = link; plink = &link->next; s = malloc (q - p + 1); memcpy (s, p, q - p); s [q - p] = '\0'; e->soname = s; link->canon_filename = s; p = q + 1; } *plink = NULL; ++prelink_entry_count; return e; } void prelink_entry_restore (htab_t htab, const char *filename) { size_t i, j; struct prelink_entry *e; prelink_entry_count = 0; htab_restore (htab, filename, prelink_entry_restorefn); free (restore_line); for (i = 0; i < htab->size; ++i) if (htab->entries [i] && htab->entries [i] != (void *) 1) { e = (struct prelink_entry *) htab->entries [i]; if (e->prev == (void *) -1) e->prev = NULL; else e->prev = (struct prelink_entry *) htab->entries [(long) e->prev]; if (e->next == (void *) -1) e->next = NULL; else e->next = (struct prelink_entry *) htab->entries [(long) e->next]; for (j = 0; j < e->ndepends; ++j) { e->depends [j] = (struct prelink_entry *) htab->entries [(long) e->depends [j]]; } } } #endif