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 (c) 2017, 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 boolean_t g_req;
  42 static uint_t g_nctf;
  43 
  44 #define CTFMERGE_OK     0
  45 #define CTFMERGE_FATAL  1
  46 #define CTFMERGE_USAGE  2
  47 
  48 #define CTFMERGE_DEFAULT_NTHREADS       8
  49 #define CTFMERGE_ALTEXEC        "CTFMERGE_ALTEXEC"
  50 
  51 static void
  52 ctfmerge_fatal(const char *fmt, ...)
  53 {
  54         va_list ap;
  55 
  56         (void) fprintf(stderr, "%s: ", g_progname);
  57         va_start(ap, fmt);
  58         (void) vfprintf(stderr, fmt, ap);
  59         va_end(ap);
  60 
  61         if (g_outfile != NULL)
  62                 (void) unlink(g_outfile);
  63 
  64         exit(CTFMERGE_FATAL);
  65 }
  66 
  67 static boolean_t
  68 ctfmerge_expect_ctf(const char *name, Elf *elf)
  69 {
  70         Elf_Scn *scn, *strscn;
  71         Elf_Data *data, *strdata;
  72         GElf_Shdr shdr;
  73         ulong_t i;
  74 
  75         if (g_req == B_FALSE)
  76                 return (B_FALSE);
  77 
  78         scn = NULL;
  79         while ((scn = elf_nextscn(elf, scn)) != NULL) {
  80                 if (gelf_getshdr(scn, &shdr) == NULL) {
  81                         ctfmerge_fatal("failed to get section header for file "
  82                             "%s: %s\n", name, elf_errmsg(elf_errno()));
  83                 }
  84 
  85                 if (shdr.sh_type == SHT_SYMTAB)
  86                         break;
  87         }
  88 
  89         if (scn == NULL)
  90                 return (B_FALSE);
  91 
  92         if ((strscn = elf_getscn(elf, shdr.sh_link)) == NULL)
  93                 ctfmerge_fatal("failed to get section header for file %s: %s\n",
  94                     name, elf_errmsg(elf_errno()));
  95 
  96         if ((data = elf_getdata(scn, NULL)) == NULL)
  97                 ctfmerge_fatal("failed to read symbol table for %s: %s\n",
  98                     name, elf_errmsg(elf_errno()));
  99 
 100         if ((strdata = elf_getdata(strscn, NULL)) == NULL)
 101                 ctfmerge_fatal("failed to read string table for %s: %s\n",
 102                     name, elf_errmsg(elf_errno()));
 103 
 104         for (i = 0; i < shdr.sh_size / shdr.sh_entsize; i++) {
 105                 GElf_Sym sym;
 106                 const char *file;
 107                 size_t len;
 108 
 109                 if (gelf_getsym(data, i, &sym) == NULL)
 110                         ctfmerge_fatal("failed to read symbol table entry %lu "
 111                             "for %s: %s\n", i, name, elf_errmsg(elf_errno()));
 112 
 113                 if (GELF_ST_TYPE(sym.st_info) != STT_FILE)
 114                         continue;
 115 
 116                 file = (const char *)((uintptr_t)strdata->d_buf + sym.st_name);
 117                 len = strlen(file);
 118                 if (len < 2 || name[len - 2] != '.')
 119                         continue;
 120 
 121                 if (name[len - 1] == 'c')
 122                         return (B_TRUE);
 123         }
 124 
 125         return (B_FALSE);
 126 }
 127 
 128 /*
 129  * Go through and construct enough information for this Elf Object to try and do
 130  * a ctf_bufopen().
 131  */
 132 static void
 133 ctfmerge_elfopen(const char *name, Elf *elf, ctf_merge_t *cmh)
 134 {
 135         GElf_Ehdr ehdr;
 136         GElf_Shdr shdr;
 137         Elf_Scn *scn;
 138         Elf_Data *ctf_data, *str_data, *sym_data;
 139         ctf_sect_t ctfsect, symsect, strsect;
 140         ctf_file_t *fp;
 141         int err;
 142 
 143         if (gelf_getehdr(elf, &ehdr) == NULL)
 144                 ctfmerge_fatal("failed to get ELF header for %s: %s\n",
 145                     name, elf_errmsg(elf_errno()));
 146 
 147         bzero(&ctfsect, sizeof (ctf_sect_t));
 148         bzero(&symsect, sizeof (ctf_sect_t));
 149         bzero(&strsect, sizeof (ctf_sect_t));
 150 
 151         scn = NULL;
 152         while ((scn = elf_nextscn(elf, scn)) != NULL) {
 153                 const char *sname;
 154 
 155                 if (gelf_getshdr(scn, &shdr) == NULL)
 156                         ctfmerge_fatal("failed to get section header for "
 157                             "file %s: %s\n", name, elf_errmsg(elf_errno()));
 158 
 159                 sname = elf_strptr(elf, ehdr.e_shstrndx, shdr.sh_name);
 160                 if (shdr.sh_type == SHT_PROGBITS &&
 161                     strcmp(sname, ".SUNW_ctf") == 0) {
 162                         ctfsect.cts_name = sname;
 163                         ctfsect.cts_type = shdr.sh_type;
 164                         ctfsect.cts_flags = shdr.sh_flags;
 165                         ctfsect.cts_size = shdr.sh_size;
 166                         ctfsect.cts_entsize = shdr.sh_entsize;
 167                         ctfsect.cts_offset = (off64_t)shdr.sh_offset;
 168 
 169                         ctf_data = elf_getdata(scn, NULL);
 170                         if (ctf_data == NULL)
 171                                 ctfmerge_fatal("failed to get ELF CTF "
 172                                     "data section for %s: %s\n", name,
 173                                     elf_errmsg(elf_errno()));
 174                         ctfsect.cts_data = ctf_data->d_buf;
 175                 } else if (shdr.sh_type == SHT_SYMTAB) {
 176                         Elf_Scn *strscn;
 177                         GElf_Shdr strhdr;
 178 
 179                         symsect.cts_name = sname;
 180                         symsect.cts_type = shdr.sh_type;
 181                         symsect.cts_flags = shdr.sh_flags;
 182                         symsect.cts_size = shdr.sh_size;
 183                         symsect.cts_entsize = shdr.sh_entsize;
 184                         symsect.cts_offset = (off64_t)shdr.sh_offset;
 185 
 186                         if ((strscn = elf_getscn(elf, shdr.sh_link)) == NULL ||
 187                             gelf_getshdr(strscn, &strhdr) == NULL)
 188                                 ctfmerge_fatal("failed to get "
 189                                     "string table for file %s: %s\n", name,
 190                                     elf_errmsg(elf_errno()));
 191 
 192                         strsect.cts_name = elf_strptr(elf, ehdr.e_shstrndx,
 193                             strhdr.sh_name);
 194                         strsect.cts_type = strhdr.sh_type;
 195                         strsect.cts_flags = strhdr.sh_flags;
 196                         strsect.cts_size = strhdr.sh_size;
 197                         strsect.cts_entsize = strhdr.sh_entsize;
 198                         strsect.cts_offset = (off64_t)strhdr.sh_offset;
 199 
 200                         sym_data = elf_getdata(scn, NULL);
 201                         if (sym_data == NULL)
 202                                 ctfmerge_fatal("failed to get ELF CTF "
 203                                     "data section for %s: %s\n", name,
 204                                     elf_errmsg(elf_errno()));
 205                         symsect.cts_data = sym_data->d_buf;
 206 
 207                         str_data = elf_getdata(strscn, NULL);
 208                         if (str_data == NULL)
 209                                 ctfmerge_fatal("failed to get ELF CTF "
 210                                     "data section for %s: %s\n", name,
 211                                     elf_errmsg(elf_errno()));
 212                         strsect.cts_data = str_data->d_buf;
 213                 }
 214         }
 215 
 216         if (ctfsect.cts_type == SHT_NULL) {
 217                 if (ctfmerge_expect_ctf(name, elf) == B_FALSE)
 218                         return;
 219                 ctfmerge_fatal("failed to open %s: %s\n", name,
 220                     ctf_errmsg(ECTF_NOCTFDATA));
 221         }
 222 
 223         if (symsect.cts_type != SHT_NULL && strsect.cts_type != SHT_NULL) {
 224                 fp = ctf_bufopen(&ctfsect, &symsect, &strsect, &err);
 225         } else {
 226                 fp = ctf_bufopen(&ctfsect, NULL, NULL, &err);
 227         }
 228 
 229         if (fp == NULL) {
 230                 if (ctfmerge_expect_ctf(name, elf) == B_TRUE) {
 231                         ctfmerge_fatal("failed to open file %s: %s\n",
 232                             name, ctf_errmsg(err));
 233                 }
 234         } else {
 235                 if ((err = ctf_merge_add(cmh, fp)) != 0) {
 236                         ctfmerge_fatal("failed to add input %s: %s\n",
 237                             name, ctf_errmsg(err));
 238                 }
 239                 g_nctf++;
 240         }
 241 }
 242 
 243 static void
 244 ctfmerge_read_archive(const char *name, int fd, Elf *elf,
 245     ctf_merge_t *cmh)
 246 {
 247         Elf *aelf;
 248         Elf_Cmd cmd = ELF_C_READ;
 249         int cursec = 1;
 250         char *nname;
 251 
 252         while ((aelf = elf_begin(fd, cmd, elf)) != NULL) {
 253                 Elf_Arhdr *arhdr;
 254                 boolean_t leakelf = B_FALSE;
 255 
 256                 if ((arhdr = elf_getarhdr(aelf)) == NULL)
 257                         ctfmerge_fatal("failed to get archive header %d for "
 258                             "%s: %s\n", cursec, name, elf_errmsg(elf_errno()));
 259 
 260                 if (*(arhdr->ar_name) == '/')
 261                         goto next;
 262 
 263                 if (asprintf(&nname, "%s.%s.%d", name, arhdr->ar_name,
 264                     cursec) < 0)
 265                         ctfmerge_fatal("failed to allocate memory for archive "
 266                             "%d of file %s\n", cursec, name);
 267 
 268                 switch (elf_kind(aelf)) {
 269                 case ELF_K_AR:
 270                         ctfmerge_read_archive(nname, fd, aelf, cmh);
 271                         free(nname);
 272                         break;
 273                 case ELF_K_ELF:
 274                         ctfmerge_elfopen(nname, aelf, cmh);
 275                         free(nname);
 276                         leakelf = B_TRUE;
 277                         break;
 278                 default:
 279                         ctfmerge_fatal("unknown elf kind (%d) in archive %d "
 280                             "for %s\n", elf_kind(aelf), cursec, name);
 281                 }
 282 
 283 next:
 284                 cmd = elf_next(aelf);
 285                 if (leakelf == B_FALSE)
 286                         (void) elf_end(aelf);
 287                 cursec++;
 288         }
 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 [-t] [-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-o  file to add CTF data to\n"
 311             "\t-t  require CTF data from all inputs built from C sources\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         ctf_file_t *ofp;
 345         ctf_merge_t *cmh;
 346         long argj;
 347         char *eptr;
 348 
 349         g_progname = basename(argv[0]);
 350 
 351         ctfmerge_altexec(argv);
 352 
 353         /*
 354          * We support a subset of the old CTF merge flags, mostly for
 355          * compatability.
 356          */
 357         while ((c = getopt(argc, argv, ":d:fgj:l:L:o:t")) != -1) {
 358                 switch (c) {
 359                 case 'd':
 360                         g_unique = optarg;
 361                         break;
 362                 case 'f':
 363                         /* Silently ignored for compatibility */
 364                         break;
 365                 case 'g':
 366                         /* Silently ignored for compatibility */
 367                         break;
 368                 case 'j':
 369                         errno = 0;
 370                         argj = strtol(optarg, &eptr, 10);
 371                         if (errno != 0 || argj == LONG_MAX ||
 372                             argj > 1024 || *eptr != '\0') {
 373                                 ctfmerge_fatal("invalid argument for -j: %s\n",
 374                                     optarg);
 375                         }
 376                         nthreads = (uint_t)argj;
 377                         break;
 378                 case 'l':
 379                         label = optarg;
 380                         break;
 381                 case 'L':
 382                         label = getenv(optarg);
 383                         break;
 384                 case 'o':
 385                         g_outfile = optarg;
 386                         break;
 387                 case 't':
 388                         g_req = B_TRUE;
 389                         break;
 390                 case ':':
 391                         ctfmerge_usage("Option -%c requires an operand\n",
 392                             optopt);
 393                         return (CTFMERGE_USAGE);
 394                 case '?':
 395                         ctfmerge_usage("Unknown option: -%c\n", optopt);
 396                         return (CTFMERGE_USAGE);
 397                 }
 398         }
 399 
 400         if (g_outfile == NULL) {
 401                 ctfmerge_usage("missing required -o output file\n");
 402                 return (CTFMERGE_USAGE);
 403         }
 404 
 405         (void) elf_version(EV_CURRENT);
 406 
 407         /*
 408          * Obviously this isn't atomic, but at least gives us a good starting
 409          * point.
 410          */
 411         if ((ofd = open(g_outfile, O_RDWR)) < 0)
 412                 ctfmerge_fatal("cannot open output file %s: %s\n", g_outfile,
 413                     strerror(errno));
 414 
 415         argc -= optind;
 416         argv += optind;
 417 
 418         if (argc < 1) {
 419                 ctfmerge_usage("no input files specified");
 420                 return (CTFMERGE_USAGE);
 421         }
 422 
 423         cmh = ctf_merge_init(ofd, &err);
 424         if (cmh == NULL)
 425                 ctfmerge_fatal("failed to create merge handle: %s\n",
 426                     ctf_errmsg(err));
 427 
 428         if ((err = ctf_merge_set_nthreads(cmh, nthreads)) != 0)
 429                 ctfmerge_fatal("failed to set parallelism to %u: %s\n",
 430                     nthreads, ctf_errmsg(err));
 431 
 432         for (i = 0; i < argc; i++) {
 433                 ctf_file_t *ifp;
 434                 int fd;
 435 
 436                 if ((fd = open(argv[i], O_RDONLY)) < 0)
 437                         ctfmerge_fatal("failed to open file %s: %s\n",
 438                             argv[i], strerror(errno));
 439                 ifp = ctf_fdopen(fd, &err);
 440                 if (ifp == NULL) {
 441                         Elf *e;
 442 
 443                         if ((e = elf_begin(fd, ELF_C_READ, NULL)) == NULL) {
 444                                 (void) close(fd);
 445                                 ctfmerge_fatal("failed to open %s: %s\n",
 446                                     argv[i], ctf_errmsg(err));
 447                         }
 448 
 449                         /*
 450                          * It's an ELF file, check if we have an archive or if
 451                          * we're expecting CTF here.
 452                          */
 453                         switch (elf_kind(e)) {
 454                         case ELF_K_AR:
 455                                 break;
 456                         case ELF_K_ELF:
 457                                 if (ctfmerge_expect_ctf(argv[i], e) == B_TRUE) {
 458                                         (void) elf_end(e);
 459                                         (void) close(fd);
 460                                         ctfmerge_fatal("failed to "
 461                                             "open %s: file was built from C "
 462                                             "sources, but missing CTF\n",
 463                                             argv[i]);
 464                                 }
 465                                 (void) elf_end(e);
 466                                 (void) close(fd);
 467                                 continue;
 468                         default:
 469                                 (void) elf_end(e);
 470                                 (void) close(fd);
 471                                 ctfmerge_fatal("failed to open %s: "
 472                                     "unsupported ELF file type", argv[i]);
 473                         }
 474 
 475                         ctfmerge_read_archive(argv[i], fd, e, cmh);
 476                         (void) elf_end(e);
 477                         (void) close(fd);
 478                         continue;
 479                 }
 480                 (void) close(fd);
 481                 if ((err = ctf_merge_add(cmh, ifp)) != 0)
 482                         ctfmerge_fatal("failed to add input %s: %s\n",
 483                             argv[i], ctf_errmsg(err));
 484                 g_nctf++;
 485         }
 486 
 487         if (g_nctf == 0) {
 488                 ctf_merge_fini(cmh);
 489                 return (0);
 490         }
 491 
 492         if (g_unique != NULL) {
 493                 ctf_file_t *ufp;
 494                 char *base;
 495 
 496                 ufp = ctf_open(g_unique, &err);
 497                 if (ufp == NULL) {
 498                         ctfmerge_fatal("failed to open uniquify file %s: %s\n",
 499                             g_unique, ctf_errmsg(err));
 500                 }
 501 
 502                 base = basename(g_unique);
 503                 (void) ctf_merge_uniquify(cmh, ufp, base);
 504         }
 505 
 506         if (label != NULL) {
 507                 if ((err = ctf_merge_label(cmh, label)) != 0)
 508                         ctfmerge_fatal("failed to add label %s: %s\n", label,
 509                             ctf_errmsg(err));
 510         }
 511 
 512         err = ctf_merge_merge(cmh, &ofp);
 513         if (err != 0)
 514                 ctfmerge_fatal("failed to merge types: %s\n", ctf_errmsg(err));
 515         ctf_merge_fini(cmh);
 516 
 517         if (asprintf(&tmpfile, "%s.ctf", g_outfile) == -1)
 518                 ctfmerge_fatal("ran out of memory for temporary file name\n");
 519         err = ctf_elfwrite(ofp, g_outfile, tmpfile, wflags);
 520         if (err == CTF_ERR) {
 521                 (void) unlink(tmpfile);
 522                 free(tmpfile);
 523                 ctfmerge_fatal("encountered a libctf error: %s!\n",
 524                     ctf_errmsg(ctf_errno(ofp)));
 525         }
 526 
 527         if (rename(tmpfile, g_outfile) != 0) {
 528                 (void) unlink(tmpfile);
 529                 free(tmpfile);
 530                 ctfmerge_fatal("failed to rename temporary file: %s\n",
 531                     strerror(errno));
 532         }
 533         free(tmpfile);
 534 
 535         return (CTFMERGE_OK);
 536 }