1 /*
   2  * This file and its contents are supplied under the terms of the
   3  * Common Development and Distribution License ("CDDL"), version 1.0.
   4  * You may only use this file in accordance with the terms of version
   5  * 1.0 of the CDDL.
   6  *
   7  * A full copy of the text of the CDDL should have accompanied this
   8  * source.  A copy of the CDDL is also available via the Internet at
   9  * http://www.illumos.org/license/CDDL.
  10  */
  11 
  12 /*
  13  * Copyright 2019 Joyent, Inc.
  14  */
  15 
  16 /*
  17  * merge CTF containers
  18  */
  19 
  20 #include <stdio.h>
  21 #include <libctf.h>
  22 #include <sys/stat.h>
  23 #include <sys/types.h>
  24 #include <fcntl.h>
  25 #include <errno.h>
  26 #include <strings.h>
  27 #include <assert.h>
  28 #include <unistd.h>
  29 #include <sys/fcntl.h>
  30 #include <stdlib.h>
  31 #include <libelf.h>
  32 #include <gelf.h>
  33 #include <sys/mman.h>
  34 #include <libgen.h>
  35 #include <stdarg.h>
  36 #include <limits.h>
  37 
  38 static char *g_progname;
  39 static char *g_unique;
  40 static char *g_outfile;
  41 static uint_t g_nctf;
  42 
  43 #define CTFMERGE_OK     0
  44 #define CTFMERGE_FATAL  1
  45 #define CTFMERGE_USAGE  2
  46 
  47 #define CTFMERGE_DEFAULT_NTHREADS       8
  48 #define CTFMERGE_ALTEXEC        "CTFMERGE_ALTEXEC"
  49 
  50 static void __attribute__((__noreturn__))
  51 ctfmerge_fatal(const char *fmt, ...)
  52 {
  53         va_list ap;
  54 
  55         (void) fprintf(stderr, "%s: ", g_progname);
  56         va_start(ap, fmt);
  57         (void) vfprintf(stderr, fmt, ap);
  58         va_end(ap);
  59 
  60         if (g_outfile != NULL)
  61                 (void) unlink(g_outfile);
  62 
  63         exit(CTFMERGE_FATAL);
  64 }
  65 
  66 /*
  67  * We failed to find CTF for this file, check if it's OK. If we're not derived
  68  * from C, or we have the -m option, we let missing CTF pass.
  69  */
  70 static void
  71 ctfmerge_check_for_c(const char *name, Elf *elf, uint_t flags)
  72 {
  73         char errmsg[1024];
  74 
  75         if (flags & CTF_ALLOW_MISSING_DEBUG)
  76                 return;
  77 
  78         switch (ctf_has_c_source(elf, errmsg, sizeof (errmsg))) {
  79         case CHR_ERROR:
  80                 ctfmerge_fatal("failed to open %s: %s\n", name, errmsg);
  81                 break;
  82 
  83         case CHR_NO_C_SOURCE:
  84                 return;
  85 
  86         default:
  87                 ctfmerge_fatal("failed to open %s: %s\n", name,
  88                     ctf_errmsg(ECTF_NOCTFDATA));
  89                 break;
  90         }
  91 }
  92 
  93 /*
  94  * Go through and construct enough information for this Elf Object to try and do
  95  * a ctf_bufopen().
  96  */
  97 static int
  98 ctfmerge_elfopen(const char *name, Elf *elf, ctf_merge_t *cmh, uint_t flags)
  99 {
 100         GElf_Ehdr ehdr;
 101         GElf_Shdr shdr;
 102         Elf_Scn *scn;
 103         Elf_Data *ctf_data, *str_data, *sym_data;
 104         ctf_sect_t ctfsect, symsect, strsect;
 105         ctf_file_t *fp;
 106         int err;
 107 
 108         if (gelf_getehdr(elf, &ehdr) == NULL)
 109                 ctfmerge_fatal("failed to get ELF header for %s: %s\n",
 110                     name, elf_errmsg(elf_errno()));
 111 
 112         bzero(&ctfsect, sizeof (ctf_sect_t));
 113         bzero(&symsect, sizeof (ctf_sect_t));
 114         bzero(&strsect, sizeof (ctf_sect_t));
 115 
 116         scn = NULL;
 117         while ((scn = elf_nextscn(elf, scn)) != NULL) {
 118                 const char *sname;
 119 
 120                 if (gelf_getshdr(scn, &shdr) == NULL)
 121                         ctfmerge_fatal("failed to get section header for "
 122                             "file %s: %s\n", name, elf_errmsg(elf_errno()));
 123 
 124                 sname = elf_strptr(elf, ehdr.e_shstrndx, shdr.sh_name);
 125                 if (shdr.sh_type == SHT_PROGBITS &&
 126                     strcmp(sname, ".SUNW_ctf") == 0) {
 127                         ctfsect.cts_name = sname;
 128                         ctfsect.cts_type = shdr.sh_type;
 129                         ctfsect.cts_flags = shdr.sh_flags;
 130                         ctfsect.cts_size = shdr.sh_size;
 131                         ctfsect.cts_entsize = shdr.sh_entsize;
 132                         ctfsect.cts_offset = (off64_t)shdr.sh_offset;
 133 
 134                         ctf_data = elf_getdata(scn, NULL);
 135                         if (ctf_data == NULL)
 136                                 ctfmerge_fatal("failed to get ELF CTF "
 137                                     "data section for %s: %s\n", name,
 138                                     elf_errmsg(elf_errno()));
 139                         ctfsect.cts_data = ctf_data->d_buf;
 140                 } else if (shdr.sh_type == SHT_SYMTAB) {
 141                         Elf_Scn *strscn;
 142                         GElf_Shdr strhdr;
 143 
 144                         symsect.cts_name = sname;
 145                         symsect.cts_type = shdr.sh_type;
 146                         symsect.cts_flags = shdr.sh_flags;
 147                         symsect.cts_size = shdr.sh_size;
 148                         symsect.cts_entsize = shdr.sh_entsize;
 149                         symsect.cts_offset = (off64_t)shdr.sh_offset;
 150 
 151                         if ((strscn = elf_getscn(elf, shdr.sh_link)) == NULL ||
 152                             gelf_getshdr(strscn, &strhdr) == NULL)
 153                                 ctfmerge_fatal("failed to get "
 154                                     "string table for file %s: %s\n", name,
 155                                     elf_errmsg(elf_errno()));
 156 
 157                         strsect.cts_name = elf_strptr(elf, ehdr.e_shstrndx,
 158                             strhdr.sh_name);
 159                         strsect.cts_type = strhdr.sh_type;
 160                         strsect.cts_flags = strhdr.sh_flags;
 161                         strsect.cts_size = strhdr.sh_size;
 162                         strsect.cts_entsize = strhdr.sh_entsize;
 163                         strsect.cts_offset = (off64_t)strhdr.sh_offset;
 164 
 165                         sym_data = elf_getdata(scn, NULL);
 166                         if (sym_data == NULL)
 167                                 ctfmerge_fatal("failed to get ELF CTF "
 168                                     "data section for %s: %s\n", name,
 169                                     elf_errmsg(elf_errno()));
 170                         symsect.cts_data = sym_data->d_buf;
 171 
 172                         str_data = elf_getdata(strscn, NULL);
 173                         if (str_data == NULL)
 174                                 ctfmerge_fatal("failed to get ELF CTF "
 175                                     "data section for %s: %s\n", name,
 176                                     elf_errmsg(elf_errno()));
 177                         strsect.cts_data = str_data->d_buf;
 178                 }
 179         }
 180 
 181         if (ctfsect.cts_type == SHT_NULL) {
 182                 ctfmerge_check_for_c(name, elf, flags);
 183                 return (ENOENT);
 184         }
 185 
 186         if (symsect.cts_type != SHT_NULL && strsect.cts_type != SHT_NULL) {
 187                 fp = ctf_bufopen(&ctfsect, &symsect, &strsect, &err);
 188         } else {
 189                 fp = ctf_bufopen(&ctfsect, NULL, NULL, &err);
 190         }
 191 
 192         if (fp == NULL) {
 193                 ctfmerge_fatal("failed to open file %s: %s\n",
 194                     name, ctf_errmsg(err));
 195         }
 196 
 197         if ((err = ctf_merge_add(cmh, fp)) != 0) {
 198                 ctfmerge_fatal("failed to add input %s: %s\n",
 199                     name, ctf_errmsg(err));
 200         }
 201 
 202         g_nctf++;
 203         return (0);
 204 }
 205 
 206 static void
 207 ctfmerge_read_archive(const char *name, int fd, Elf *elf,
 208     ctf_merge_t *cmh, uint_t flags)
 209 {
 210         Elf_Cmd cmd = ELF_C_READ;
 211         int cursec = 1;
 212         Elf *aelf;
 213 
 214         while ((aelf = elf_begin(fd, cmd, elf)) != NULL) {
 215                 char *nname = NULL;
 216                 Elf_Arhdr *arhdr;
 217 
 218                 if ((arhdr = elf_getarhdr(aelf)) == NULL)
 219                         ctfmerge_fatal("failed to get archive header %d for "
 220                             "%s: %s\n", cursec, name, elf_errmsg(elf_errno()));
 221 
 222                 cmd = elf_next(aelf);
 223 
 224                 if (*(arhdr->ar_name) == '/')
 225                         goto next;
 226 
 227                 if (asprintf(&nname, "%s.%s.%d", name, arhdr->ar_name,
 228                     cursec) < 0)
 229                         ctfmerge_fatal("failed to allocate memory for archive "
 230                             "%d of file %s\n", cursec, name);
 231 
 232                 switch (elf_kind(aelf)) {
 233                 case ELF_K_AR:
 234                         ctfmerge_read_archive(nname, fd, aelf, cmh, flags);
 235                         break;
 236                 case ELF_K_ELF:
 237                         /* ctfmerge_elfopen() takes ownership of aelf. */
 238                         if (ctfmerge_elfopen(nname, aelf, cmh, flags) == 0)
 239                                 aelf = NULL;
 240                         break;
 241                 default:
 242                         ctfmerge_fatal("unknown elf kind (%d) in archive %d "
 243                             "for %s\n", elf_kind(aelf), cursec, name);
 244                         break;
 245                 }
 246 
 247 next:
 248                 (void) elf_end(aelf);
 249                 free(nname);
 250                 cursec++;
 251         }
 252 }
 253 
 254 static void
 255 ctfmerge_file_add(ctf_merge_t *cmh, const char *file, uint_t flags)
 256 {
 257         Elf *e;
 258         int fd;
 259 
 260         if ((fd = open(file, O_RDONLY)) < 0) {
 261                 ctfmerge_fatal("failed to open file %s: %s\n",
 262                     file, strerror(errno));
 263         }
 264 
 265         if ((e = elf_begin(fd, ELF_C_READ, NULL)) == NULL) {
 266                 (void) close(fd);
 267                 ctfmerge_fatal("failed to open %s: %s\n",
 268                     file, elf_errmsg(elf_errno()));
 269         }
 270 
 271         switch (elf_kind(e)) {
 272         case ELF_K_AR:
 273                 ctfmerge_read_archive(file, fd, e, cmh, flags);
 274                 break;
 275 
 276         case ELF_K_ELF:
 277                 /* ctfmerge_elfopen() takes ownership of e. */
 278                 if (ctfmerge_elfopen(file, e, cmh, flags) == 0)
 279                         e = NULL;
 280                 break;
 281 
 282         default:
 283                 ctfmerge_fatal("unknown elf kind (%d) for %s\n",
 284                     elf_kind(e), file);
 285         }
 286 
 287         (void) elf_end(e);
 288         (void) close(fd);
 289 }
 290 
 291 static void
 292 ctfmerge_usage(const char *fmt, ...)
 293 {
 294         if (fmt != NULL) {
 295                 va_list ap;
 296 
 297                 (void) fprintf(stderr, "%s: ", g_progname);
 298                 va_start(ap, fmt);
 299                 (void) vfprintf(stderr, fmt, ap);
 300                 va_end(ap);
 301         }
 302 
 303         (void) fprintf(stderr, "Usage: %s [-m] [-d uniqfile] [-l label] "
 304             "[-L labelenv] [-j nthrs] -o outfile file ...\n"
 305             "\n"
 306             "\t-d  uniquify merged output against uniqfile\n"
 307             "\t-j  use nthrs threads to perform the merge\n"
 308             "\t-l  set output container's label to specified value\n"
 309             "\t-L  set output container's label to value from environment\n"
 310             "\t-m  allow C-based input files to not have CTF\n"
 311             "\t-o  file to add CTF data to\n",
 312             g_progname);
 313 }
 314 
 315 static void
 316 ctfmerge_altexec(char **argv)
 317 {
 318         const char *alt;
 319         char *altexec;
 320 
 321         alt = getenv(CTFMERGE_ALTEXEC);
 322         if (alt == NULL || *alt == '\0')
 323                 return;
 324 
 325         altexec = strdup(alt);
 326         if (altexec == NULL)
 327                 ctfmerge_fatal("failed to allocate memory for altexec\n");
 328         if (unsetenv(CTFMERGE_ALTEXEC) != 0)
 329                 ctfmerge_fatal("failed to unset %s from environment: %s\n",
 330                     CTFMERGE_ALTEXEC, strerror(errno));
 331 
 332         (void) execv(altexec, argv);
 333         ctfmerge_fatal("failed to execute alternate program %s: %s",
 334             altexec, strerror(errno));
 335 }
 336 
 337 int
 338 main(int argc, char *argv[])
 339 {
 340         int err, i, c, ofd;
 341         uint_t nthreads = CTFMERGE_DEFAULT_NTHREADS;
 342         char *tmpfile = NULL, *label = NULL;
 343         int wflags = CTF_ELFWRITE_F_COMPRESS;
 344         uint_t flags = 0;
 345         ctf_merge_t *cmh;
 346         ctf_file_t *ofp;
 347         long argj;
 348         char *eptr;
 349 
 350         g_progname = basename(argv[0]);
 351 
 352         ctfmerge_altexec(argv);
 353 
 354         /*
 355          * We support a subset of the old CTF merge flags, mostly for
 356          * compatibility.
 357          */
 358         while ((c = getopt(argc, argv, ":d:fgj:l:L:mo:t")) != -1) {
 359                 switch (c) {
 360                 case 'd':
 361                         g_unique = optarg;
 362                         break;
 363                 case 'f':
 364                         /* Silently ignored for compatibility */
 365                         break;
 366                 case 'g':
 367                         /* Silently ignored for compatibility */
 368                         break;
 369                 case 'j':
 370                         errno = 0;
 371                         argj = strtol(optarg, &eptr, 10);
 372                         if (errno != 0 || argj == LONG_MAX ||
 373                             argj > 1024 || *eptr != '\0') {
 374                                 ctfmerge_fatal("invalid argument for -j: %s\n",
 375                                     optarg);
 376                         }
 377                         nthreads = (uint_t)argj;
 378                         break;
 379                 case 'l':
 380                         label = optarg;
 381                         break;
 382                 case 'L':
 383                         label = getenv(optarg);
 384                         break;
 385                 case 'm':
 386                         flags |= CTF_ALLOW_MISSING_DEBUG;
 387                         break;
 388                 case 'o':
 389                         g_outfile = optarg;
 390                         break;
 391                 case 't':
 392                         /* Silently ignored for compatibility */
 393                         break;
 394                 case ':':
 395                         ctfmerge_usage("Option -%c requires an operand\n",
 396                             optopt);
 397                         return (CTFMERGE_USAGE);
 398                 case '?':
 399                         ctfmerge_usage("Unknown option: -%c\n", optopt);
 400                         return (CTFMERGE_USAGE);
 401                 }
 402         }
 403 
 404         if (g_outfile == NULL) {
 405                 ctfmerge_usage("missing required -o output file\n");
 406                 return (CTFMERGE_USAGE);
 407         }
 408 
 409         (void) elf_version(EV_CURRENT);
 410 
 411         /*
 412          * Obviously this isn't atomic, but at least gives us a good starting
 413          * point.
 414          */
 415         if ((ofd = open(g_outfile, O_RDWR)) < 0)
 416                 ctfmerge_fatal("cannot open output file %s: %s\n", g_outfile,
 417                     strerror(errno));
 418 
 419         argc -= optind;
 420         argv += optind;
 421 
 422         if (argc < 1) {
 423                 ctfmerge_usage("no input files specified");
 424                 return (CTFMERGE_USAGE);
 425         }
 426 
 427         cmh = ctf_merge_init(ofd, &err);
 428         if (cmh == NULL)
 429                 ctfmerge_fatal("failed to create merge handle: %s\n",
 430                     ctf_errmsg(err));
 431 
 432         if ((err = ctf_merge_set_nthreads(cmh, nthreads)) != 0)
 433                 ctfmerge_fatal("failed to set parallelism to %u: %s\n",
 434                     nthreads, ctf_errmsg(err));
 435 
 436         for (i = 0; i < argc; i++) {
 437                 ctfmerge_file_add(cmh, argv[i], flags);
 438         }
 439 
 440         if (g_nctf == 0) {
 441                 ctf_merge_fini(cmh);
 442                 return (0);
 443         }
 444 
 445         if (g_unique != NULL) {
 446                 ctf_file_t *ufp;
 447                 char *base;
 448 
 449                 ufp = ctf_open(g_unique, &err);
 450                 if (ufp == NULL) {
 451                         ctfmerge_fatal("failed to open uniquify file %s: %s\n",
 452                             g_unique, ctf_errmsg(err));
 453                 }
 454 
 455                 base = basename(g_unique);
 456                 (void) ctf_merge_uniquify(cmh, ufp, base);
 457         }
 458 
 459         if (label != NULL) {
 460                 if ((err = ctf_merge_label(cmh, label)) != 0)
 461                         ctfmerge_fatal("failed to add label %s: %s\n", label,
 462                             ctf_errmsg(err));
 463         }
 464 
 465         err = ctf_merge_merge(cmh, &ofp);
 466         if (err != 0)
 467                 ctfmerge_fatal("failed to merge types: %s\n", ctf_errmsg(err));
 468         ctf_merge_fini(cmh);
 469 
 470         if (asprintf(&tmpfile, "%s.ctf", g_outfile) == -1)
 471                 ctfmerge_fatal("ran out of memory for temporary file name\n");
 472         err = ctf_elfwrite(ofp, g_outfile, tmpfile, wflags);
 473         if (err == CTF_ERR) {
 474                 (void) unlink(tmpfile);
 475                 free(tmpfile);
 476                 ctfmerge_fatal("encountered a libctf error: %s!\n",
 477                     ctf_errmsg(ctf_errno(ofp)));
 478         }
 479 
 480         if (rename(tmpfile, g_outfile) != 0) {
 481                 (void) unlink(tmpfile);
 482                 free(tmpfile);
 483                 ctfmerge_fatal("failed to rename temporary file: %s\n",
 484                     strerror(errno));
 485         }
 486         free(tmpfile);
 487 
 488         return (CTFMERGE_OK);
 489 }