#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <stdarg.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <errno.h>
#include <libelf.h>
#include <libctf.h>
#include <string.h>
#include <libgen.h>
#include <limits.h>
#include <strings.h>
#include <sys/debug.h>
#define CTFCONVERT_OK 0
#define CTFCONVERT_FATAL 1
#define CTFCONVERT_USAGE 2
static char *ctfconvert_progname;
static void
ctfconvert_fatal(const char *fmt, ...)
{
va_list ap;
(void) fprintf(stderr, "%s: ", ctfconvert_progname);
va_start(ap, fmt);
(void) vfprintf(stderr, fmt, ap);
va_end(ap);
exit(CTFCONVERT_FATAL);
}
static void
ctfconvert_warning(void *arg, const char *fmt, ...)
{
va_list ap;
char *buf;
va_start(ap, fmt);
if (vasprintf(&buf, fmt, ap) != -1) {
(void) fprintf(stderr, "%s: WARNING: %s", ctfconvert_progname,
buf);
free(buf);
}
va_end(ap);
}
static void
ctfconvert_usage(const char *fmt, ...)
{
if (fmt != NULL) {
va_list ap;
(void) fprintf(stderr, "%s: ", ctfconvert_progname);
va_start(ap, fmt);
(void) vfprintf(stderr, fmt, ap);
va_end(ap);
}
(void) fprintf(stderr, "Usage: %s [-fikms] [-j nthrs] [-l label | "
"-L labelenv] [-b batchsize]\n"
" [-o outfile] [-M ignorefile] input\n"
"\n"
"\t-b batch process this many dies at a time (default %d)\n"
"\t-f always attempt to convert files\n"
"\t-i ignore files not built partially from C sources\n"
"\t-j use nthrs threads to perform the merge (default %d)\n"
"\t-k keep around original input file on failure\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 input to have missing debug info\n"
"\t-M allow files listed in ignorefile to have missing debug\n"
"\t-o copy input to outfile and add CTF\n"
"\t-s allow truncation of data that cannot be fully converted\n",
ctfconvert_progname,
CTF_CONVERT_DEFAULT_BATCHSIZE,
CTF_CONVERT_DEFAULT_NTHREADS);
}
typedef struct ctfconvert_fixup {
boolean_t cf_cyclic;
boolean_t cf_mcpu;
boolean_t cf_lastpad;
ulong_t cf_padoff;
} ctfconvert_fixup_t;
static int
ctfconvert_fixup_genunix_cb(const char *name, ctf_id_t tid, ulong_t off,
void *arg)
{
ctfconvert_fixup_t *cfp = arg;
cfp->cf_lastpad = B_FALSE;
if (strcmp(name, "cpu_cyclic") == 0) {
cfp->cf_cyclic = B_TRUE;
return (0);
}
if (strcmp(name, "cpu_m") == 0) {
cfp->cf_mcpu = B_TRUE;
return (0);
}
if (strcmp(name, "cpu_m_pad") == 0) {
cfp->cf_lastpad = B_TRUE;
cfp->cf_padoff = off;
return (0);
}
return (0);
}
static void
ctfconvert_fixup_genunix(ctf_file_t *fp)
{
ctf_id_t cpuid, mcpu;
ssize_t sz;
ctfconvert_fixup_t cf;
int model, ptrsz;
cpuid = ctf_lookup_by_name(fp, "struct cpu");
if (cpuid == CTF_ERR)
return;
if (ctf_type_kind(fp, cpuid) != CTF_K_STRUCT)
return;
if ((sz = ctf_type_size(fp, cpuid)) == CTF_ERR)
return;
model = ctf_getmodel(fp);
VERIFY(model == CTF_MODEL_ILP32 || model == CTF_MODEL_LP64);
ptrsz = model == CTF_MODEL_ILP32 ? 4 : 8;
bzero(&cf, sizeof (ctfconvert_fixup_t));
if (ctf_member_iter(fp, cpuid, ctfconvert_fixup_genunix_cb, &cf) ==
CTF_ERR)
return;
if (cf.cf_cyclic == B_FALSE || cf.cf_mcpu == B_TRUE ||
cf.cf_lastpad == B_FALSE) {
return;
}
if (cf.cf_padoff + ptrsz * NBBY != sz * NBBY) {
return;
}
mcpu = ctf_lookup_by_name(fp, "struct machcpu");
if (mcpu == CTF_ERR) {
mcpu = ctf_add_forward(fp, CTF_ADD_NONROOT, "machcpu",
CTF_K_STRUCT);
if (mcpu == CTF_ERR) {
ctfconvert_fatal("failed to add 'struct machcpu' "
"forward: %s\n", ctf_errmsg(ctf_errno(fp)));
}
} else {
int kind;
if ((kind = ctf_type_kind(fp, mcpu)) == CTF_ERR) {
ctfconvert_fatal("failed to get the type kind for "
"the struct machcpu: %s\n",
ctf_errmsg(ctf_errno(fp)));
}
if (kind != CTF_K_STRUCT && kind != CTF_K_FORWARD)
ctfconvert_fatal("encountered a struct machcpu of the "
"wrong type, found type kind %d\n", kind);
}
if (ctf_update(fp) == CTF_ERR) {
ctfconvert_fatal("failed to update output file: %s\n",
ctf_errmsg(ctf_errno(fp)));
}
if (ctf_add_member(fp, cpuid, "cpu_m", mcpu, sz * NBBY) == CTF_ERR) {
ctfconvert_fatal("failed to add the m_cpu member: %s\n",
ctf_errmsg(ctf_errno(fp)));
}
if (ctf_update(fp) == CTF_ERR) {
ctfconvert_fatal("failed to update output file: %s\n",
ctf_errmsg(ctf_errno(fp)));
}
VERIFY(ctf_type_size(fp, cpuid) == sz);
}
int
main(int argc, char *argv[])
{
int c, ifd, err;
boolean_t keep = B_FALSE;
ctf_convert_flag_t flags = 0;
uint_t bsize = CTF_CONVERT_DEFAULT_BATCHSIZE;
uint_t nthreads = CTF_CONVERT_DEFAULT_NTHREADS;
const char *outfile = NULL;
const char *label = NULL;
const char *infile = NULL;
const char *ignorefile = NULL;
char *tmpfile;
ctf_file_t *ofp;
char buf[4096] = "";
boolean_t optx = B_FALSE;
boolean_t ignore_non_c = B_FALSE;
ctf_convert_t *cch;
ctfconvert_progname = basename(argv[0]);
while ((c = getopt(argc, argv, ":b:fij:kl:L:mM:o:sX")) != -1) {
switch (c) {
case 'b': {
long argno;
const char *errstr;
argno = strtonum(optarg, 1, UINT_MAX, &errstr);
if (errstr != NULL) {
ctfconvert_fatal("invalid argument for -b: "
"%s - %s\n", optarg, errstr);
}
bsize = (uint_t)argno;
break;
}
case 'f':
flags |= CTF_FORCE_CONVERSION;
break;
case 'i':
ignore_non_c = B_TRUE;
break;
case 'j': {
long argno;
const char *errstr;
argno = strtonum(optarg, 1, 1024, &errstr);
if (errstr != NULL) {
ctfconvert_fatal("invalid argument for -j: "
"%s - %s\n", optarg, errstr);
}
nthreads = (uint_t)argno;
break;
}
case 'k':
keep = B_TRUE;
break;
case 'l':
label = optarg;
break;
case 'L':
label = getenv(optarg);
break;
case 'm':
flags |= CTF_ALLOW_MISSING_DEBUG;
break;
case 'M':
ignorefile = optarg;
break;
case 'o':
outfile = optarg;
break;
case 's':
flags |= CTF_ALLOW_TRUNCATION;
break;
case 'X':
optx = B_TRUE;
break;
case ':':
ctfconvert_usage("Option -%c requires an operand\n",
optopt);
return (CTFCONVERT_USAGE);
case '?':
ctfconvert_usage("Unknown option: -%c\n", optopt);
return (CTFCONVERT_USAGE);
}
}
argv += optind;
argc -= optind;
if (argc != 1) {
ctfconvert_usage("Exactly one input file is required\n");
return (CTFCONVERT_USAGE);
}
infile = argv[0];
if (elf_version(EV_CURRENT) == EV_NONE)
ctfconvert_fatal("failed to initialize libelf: library is "
"out of date\n");
ifd = open(infile, O_RDONLY);
if (ifd < 0) {
ctfconvert_fatal("failed to open input file %s: %s\n", infile,
strerror(errno));
}
if (outfile != NULL && strcmp(infile, outfile) != 0)
keep = B_TRUE;
cch = ctf_convert_init(&err);
if (cch == NULL) {
ctfconvert_fatal(
"failed to create libctf conversion handle: %s\n",
strerror(err));
}
if ((err = ctf_convert_set_nthreads(cch, nthreads)) != 0)
ctfconvert_fatal("Could not set number of threads: %s\n",
strerror(err));
if ((err = ctf_convert_set_batchsize(cch, bsize)) != 0)
ctfconvert_fatal("Could not set batch size: %s\n",
strerror(err));
if ((err = ctf_convert_set_flags(cch, flags)) != 0)
ctfconvert_fatal("Could not set conversion flags: %s\n",
strerror(err));
if (label != NULL && (err = ctf_convert_set_label(cch, label)) != 0)
ctfconvert_fatal("Could not set label: %s\n",
strerror(err));
if ((err = ctf_convert_set_warncb(cch, ctfconvert_warning, NULL)) != 0)
ctfconvert_fatal("Could not set warning callback: %s\n",
strerror(err));
if (ignorefile != NULL) {
char *buf = NULL;
ssize_t cnt;
size_t len = 0;
FILE *fp;
if ((fp = fopen(ignorefile, "r")) == NULL) {
ctfconvert_fatal("Could not open ignorefile '%s': %s\n",
ignorefile, strerror(errno));
}
while ((cnt = getline(&buf, &len, fp)) != -1) {
char *p = buf;
if (cnt == 0 || *p == '#')
continue;
(void) strsep(&p, "\n");
if ((err = ctf_convert_add_ignore(cch, buf)) != 0) {
ctfconvert_fatal(
"Failed to add '%s' to ignore list: %s\n",
buf, strerror(err));
}
}
free(buf);
if (cnt == -1 && ferror(fp) != 0) {
ctfconvert_fatal(
"Error reading from ignorefile '%s': %s\n",
ignorefile, strerror(errno));
}
(void) fclose(fp);
}
ofp = ctf_fdconvert(cch, ifd, &err, buf, sizeof (buf));
ctf_convert_fini(cch);
if (ofp == NULL) {
if (err == ECTF_CONVNOCSRC && ignore_non_c) {
exit(CTFCONVERT_OK);
}
if (err == ECTF_CONVNODEBUG &&
(flags & CTF_ALLOW_MISSING_DEBUG) != 0) {
exit(CTFCONVERT_OK);
}
if (keep == B_FALSE)
(void) unlink(infile);
switch (err) {
case ECTF_CONVBKERR:
ctfconvert_fatal("CTF conversion failed: %s", buf);
break;
case ECTF_CONVNODEBUG:
ctfconvert_fatal("CTF conversion failed due to "
"missing debug data; use -m to override\n");
break;
default:
if (*buf != '\0') {
(void) fprintf(stderr, "%s: %s",
ctfconvert_progname, buf);
}
ctfconvert_fatal("CTF conversion failed: %s\n",
ctf_errmsg(err));
}
}
if (optx == B_TRUE)
ctfconvert_fixup_genunix(ofp);
tmpfile = NULL;
if (outfile == NULL || strcmp(infile, outfile) == 0) {
if (asprintf(&tmpfile, "%s.ctf", infile) == -1) {
if (keep == B_FALSE)
(void) unlink(infile);
ctfconvert_fatal("failed to allocate memory for "
"temporary file: %s\n", strerror(errno));
}
outfile = tmpfile;
}
err = ctf_elfwrite(ofp, infile, outfile, CTF_ELFWRITE_F_COMPRESS);
if (err == CTF_ERR) {
(void) unlink(outfile);
if (keep == B_FALSE)
(void) unlink(infile);
ctfconvert_fatal("failed to write CTF section to output file: "
"%s\n", ctf_errmsg(ctf_errno(ofp)));
}
ctf_close(ofp);
if (tmpfile != NULL) {
if (rename(tmpfile, infile) != 0) {
int e = errno;
(void) unlink(outfile);
if (keep == B_FALSE)
(void) unlink(infile);
ctfconvert_fatal("failed to rename temporary file: "
"%s\n", strerror(e));
}
}
free(tmpfile);
return (CTFCONVERT_OK);
}