#include <stdio.h>
#include <libctf.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <fcntl.h>
#include <errno.h>
#include <strings.h>
#include <assert.h>
#include <unistd.h>
#include <sys/fcntl.h>
#include <stdlib.h>
#include <libelf.h>
#include <gelf.h>
#include <sys/mman.h>
#include <libgen.h>
#include <stdarg.h>
#include <limits.h>
static char *g_progname;
static char *g_unique;
static char *g_outfile;
static uint_t g_nctf;
#define CTFMERGE_OK 0
#define CTFMERGE_FATAL 1
#define CTFMERGE_USAGE 2
#define CTFMERGE_DEFAULT_NTHREADS 8
static void __attribute__((__noreturn__))
ctfmerge_fatal(const char *fmt, ...)
{
va_list ap;
(void) fprintf(stderr, "%s: ", g_progname);
va_start(ap, fmt);
(void) vfprintf(stderr, fmt, ap);
va_end(ap);
if (g_outfile != NULL)
(void) unlink(g_outfile);
exit(CTFMERGE_FATAL);
}
static void
ctfmerge_check_for_c(const char *name, Elf *elf, uint_t flags)
{
char errmsg[1024];
if (flags & CTF_ALLOW_MISSING_DEBUG)
return;
switch (ctf_has_c_source(elf, errmsg, sizeof (errmsg))) {
case CHR_ERROR:
ctfmerge_fatal("failed to open %s: %s\n", name, errmsg);
break;
case CHR_NO_C_SOURCE:
return;
default:
ctfmerge_fatal("failed to open %s: %s\n", name,
ctf_errmsg(ECTF_NOCTFDATA));
break;
}
}
static int
ctfmerge_elfopen(const char *name, Elf *elf, ctf_merge_t *cmh, uint_t flags)
{
GElf_Ehdr ehdr;
GElf_Shdr shdr;
Elf_Scn *scn;
Elf_Data *ctf_data, *str_data, *sym_data;
ctf_sect_t ctfsect, symsect, strsect;
ctf_file_t *fp;
int err;
if (gelf_getehdr(elf, &ehdr) == NULL)
ctfmerge_fatal("failed to get ELF header for %s: %s\n",
name, elf_errmsg(elf_errno()));
bzero(&ctfsect, sizeof (ctf_sect_t));
bzero(&symsect, sizeof (ctf_sect_t));
bzero(&strsect, sizeof (ctf_sect_t));
scn = NULL;
while ((scn = elf_nextscn(elf, scn)) != NULL) {
const char *sname;
if (gelf_getshdr(scn, &shdr) == NULL)
ctfmerge_fatal("failed to get section header for "
"file %s: %s\n", name, elf_errmsg(elf_errno()));
sname = elf_strptr(elf, ehdr.e_shstrndx, shdr.sh_name);
if (shdr.sh_type == SHT_PROGBITS &&
strcmp(sname, ".SUNW_ctf") == 0) {
ctfsect.cts_name = sname;
ctfsect.cts_type = shdr.sh_type;
ctfsect.cts_flags = shdr.sh_flags;
ctfsect.cts_size = shdr.sh_size;
ctfsect.cts_entsize = shdr.sh_entsize;
ctfsect.cts_offset = (off64_t)shdr.sh_offset;
ctf_data = elf_getdata(scn, NULL);
if (ctf_data == NULL)
ctfmerge_fatal("failed to get ELF CTF "
"data section for %s: %s\n", name,
elf_errmsg(elf_errno()));
ctfsect.cts_data = ctf_data->d_buf;
} else if (shdr.sh_type == SHT_SYMTAB) {
Elf_Scn *strscn;
GElf_Shdr strhdr;
symsect.cts_name = sname;
symsect.cts_type = shdr.sh_type;
symsect.cts_flags = shdr.sh_flags;
symsect.cts_size = shdr.sh_size;
symsect.cts_entsize = shdr.sh_entsize;
symsect.cts_offset = (off64_t)shdr.sh_offset;
if ((strscn = elf_getscn(elf, shdr.sh_link)) == NULL ||
gelf_getshdr(strscn, &strhdr) == NULL)
ctfmerge_fatal("failed to get "
"string table for file %s: %s\n", name,
elf_errmsg(elf_errno()));
strsect.cts_name = elf_strptr(elf, ehdr.e_shstrndx,
strhdr.sh_name);
strsect.cts_type = strhdr.sh_type;
strsect.cts_flags = strhdr.sh_flags;
strsect.cts_size = strhdr.sh_size;
strsect.cts_entsize = strhdr.sh_entsize;
strsect.cts_offset = (off64_t)strhdr.sh_offset;
sym_data = elf_getdata(scn, NULL);
if (sym_data == NULL)
ctfmerge_fatal("failed to get ELF CTF "
"data section for %s: %s\n", name,
elf_errmsg(elf_errno()));
symsect.cts_data = sym_data->d_buf;
str_data = elf_getdata(strscn, NULL);
if (str_data == NULL)
ctfmerge_fatal("failed to get ELF CTF "
"data section for %s: %s\n", name,
elf_errmsg(elf_errno()));
strsect.cts_data = str_data->d_buf;
}
}
if (ctfsect.cts_type == SHT_NULL) {
ctfmerge_check_for_c(name, elf, flags);
return (ENOENT);
}
if (symsect.cts_type != SHT_NULL && strsect.cts_type != SHT_NULL) {
fp = ctf_bufopen(&ctfsect, &symsect, &strsect, &err);
} else {
fp = ctf_bufopen(&ctfsect, NULL, NULL, &err);
}
if (fp == NULL) {
ctfmerge_fatal("failed to open file %s: %s\n",
name, ctf_errmsg(err));
}
if ((err = ctf_merge_add(cmh, fp)) != 0) {
ctfmerge_fatal("failed to add input %s: %s\n",
name, ctf_errmsg(err));
}
g_nctf++;
return (0);
}
static void
ctfmerge_read_archive(const char *name, int fd, Elf *elf,
ctf_merge_t *cmh, uint_t flags)
{
Elf_Cmd cmd = ELF_C_READ;
int cursec = 1;
Elf *aelf;
while ((aelf = elf_begin(fd, cmd, elf)) != NULL) {
char *nname = NULL;
Elf_Arhdr *arhdr;
if ((arhdr = elf_getarhdr(aelf)) == NULL)
ctfmerge_fatal("failed to get archive header %d for "
"%s: %s\n", cursec, name, elf_errmsg(elf_errno()));
cmd = elf_next(aelf);
if (*(arhdr->ar_name) == '/')
goto next;
if (asprintf(&nname, "%s(%s)", name, arhdr->ar_name) < 0)
ctfmerge_fatal("failed to allocate memory for archive "
"%d of file %s\n", cursec, name);
switch (elf_kind(aelf)) {
case ELF_K_AR:
ctfmerge_read_archive(nname, fd, aelf, cmh, flags);
break;
case ELF_K_ELF:
if (ctfmerge_elfopen(nname, aelf, cmh, flags) == 0)
aelf = NULL;
break;
default:
ctfmerge_fatal("unknown elf kind (%d) in archive %d "
"for %s\n", elf_kind(aelf), cursec, name);
break;
}
next:
(void) elf_end(aelf);
free(nname);
cursec++;
}
}
static void
ctfmerge_file_add(ctf_merge_t *cmh, const char *file, uint_t flags)
{
Elf *e;
int fd;
if ((fd = open(file, O_RDONLY)) < 0) {
ctfmerge_fatal("failed to open file %s: %s\n",
file, strerror(errno));
}
if ((e = elf_begin(fd, ELF_C_READ, NULL)) == NULL) {
(void) close(fd);
ctfmerge_fatal("failed to open %s: %s\n",
file, elf_errmsg(elf_errno()));
}
switch (elf_kind(e)) {
case ELF_K_AR:
ctfmerge_read_archive(file, fd, e, cmh, flags);
break;
case ELF_K_ELF:
if (ctfmerge_elfopen(file, e, cmh, flags) == 0)
e = NULL;
break;
default:
ctfmerge_fatal("unknown elf kind (%d) for %s\n",
elf_kind(e), file);
}
(void) elf_end(e);
(void) close(fd);
}
static void
ctfmerge_usage(const char *fmt, ...)
{
if (fmt != NULL) {
va_list ap;
(void) fprintf(stderr, "%s: ", g_progname);
va_start(ap, fmt);
(void) vfprintf(stderr, fmt, ap);
va_end(ap);
}
(void) fprintf(stderr, "Usage: %s [-m] [-d uniqfile] [-l label] "
"[-L labelenv] [-j nthrs] -o outfile file ...\n"
"\n"
"\t-d uniquify merged output against uniqfile\n"
"\t-j use nthrs threads to perform the merge\n"
"\t-l set output container's label to specified value\n"
"\t-L set output container's label to value from environment\n"
"\t-m allow C-based input files to not have CTF\n"
"\t-o file to add CTF data to\n",
g_progname);
}
int
main(int argc, char *argv[])
{
int err, i, c, ofd;
uint_t nthreads = CTFMERGE_DEFAULT_NTHREADS;
char *tmpfile = NULL, *label = NULL;
int wflags = CTF_ELFWRITE_F_COMPRESS;
uint_t flags = 0;
ctf_merge_t *cmh;
ctf_file_t *ofp;
long argj;
char *eptr;
g_progname = basename(argv[0]);
while ((c = getopt(argc, argv, ":d:fgj:l:L:mo:t")) != -1) {
switch (c) {
case 'd':
g_unique = optarg;
break;
case 'f':
break;
case 'g':
break;
case 'j':
errno = 0;
argj = strtol(optarg, &eptr, 10);
if (errno != 0 || argj == LONG_MAX ||
argj > 1024 || *eptr != '\0') {
ctfmerge_fatal("invalid argument for -j: %s\n",
optarg);
}
nthreads = (uint_t)argj;
break;
case 'l':
label = optarg;
break;
case 'L':
label = getenv(optarg);
break;
case 'm':
flags |= CTF_ALLOW_MISSING_DEBUG;
break;
case 'o':
g_outfile = optarg;
break;
case 't':
break;
case ':':
ctfmerge_usage("Option -%c requires an operand\n",
optopt);
return (CTFMERGE_USAGE);
case '?':
ctfmerge_usage("Unknown option: -%c\n", optopt);
return (CTFMERGE_USAGE);
}
}
if (g_outfile == NULL) {
ctfmerge_usage("missing required -o output file\n");
return (CTFMERGE_USAGE);
}
(void) elf_version(EV_CURRENT);
if ((ofd = open(g_outfile, O_RDWR)) < 0)
ctfmerge_fatal("cannot open output file %s: %s\n", g_outfile,
strerror(errno));
argc -= optind;
argv += optind;
if (argc < 1) {
ctfmerge_usage("no input files specified\n");
return (CTFMERGE_USAGE);
}
cmh = ctf_merge_init(ofd, &err);
if (cmh == NULL)
ctfmerge_fatal("failed to create merge handle: %s\n",
ctf_errmsg(err));
if ((err = ctf_merge_set_nthreads(cmh, nthreads)) != 0)
ctfmerge_fatal("failed to set parallelism to %u: %s\n",
nthreads, ctf_errmsg(err));
for (i = 0; i < argc; i++) {
ctfmerge_file_add(cmh, argv[i], flags);
}
if (g_nctf == 0) {
ctf_merge_fini(cmh);
return (0);
}
if (g_unique != NULL) {
ctf_file_t *ufp;
char *base;
ufp = ctf_open(g_unique, &err);
if (ufp == NULL) {
ctfmerge_fatal("failed to open uniquify file %s: %s\n",
g_unique, ctf_errmsg(err));
}
base = basename(g_unique);
(void) ctf_merge_uniquify(cmh, ufp, base);
}
if (label != NULL) {
if ((err = ctf_merge_label(cmh, label)) != 0)
ctfmerge_fatal("failed to add label %s: %s\n", label,
ctf_errmsg(err));
}
err = ctf_merge_merge(cmh, &ofp);
if (err != 0)
ctfmerge_fatal("failed to merge types: %s\n", ctf_errmsg(err));
ctf_merge_fini(cmh);
if (asprintf(&tmpfile, "%s.ctf", g_outfile) == -1)
ctfmerge_fatal("ran out of memory for temporary file name\n");
err = ctf_elfwrite(ofp, g_outfile, tmpfile, wflags);
if (err == CTF_ERR) {
(void) unlink(tmpfile);
free(tmpfile);
ctfmerge_fatal("encountered a libctf error: %s!\n",
ctf_errmsg(ctf_errno(ofp)));
}
if (rename(tmpfile, g_outfile) != 0) {
(void) unlink(tmpfile);
free(tmpfile);
ctfmerge_fatal("failed to rename temporary file: %s\n",
strerror(errno));
}
free(tmpfile);
return (CTFMERGE_OK);
}