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