root/usr/src/cmd/ctfdiff/ctfdiff.c
/*
 * This file and its contents are supplied under the terms of the
 * Common Development and Distribution License ("CDDL"), version 1.0.
 * You may only use this file in accordance with the terms of version
 * 1.0 of the CDDL.
 *
 * A full copy of the text of the CDDL should have accompanied this
 * source.  A copy of the CDDL is also available via the Internet at
 * http://www.illumos.org/license/CDDL.
 */

/*
 * Copyright (c) 2015, Joyent, Inc.
 */

/*
 * diff two CTF containers
 */

#include <stdio.h>
#include <stdlib.h>
#include <errno.h>
#include <strings.h>
#include <libctf.h>
#include <libgen.h>
#include <stdarg.h>

#define CTFDIFF_NAMELEN 256

#define CTFDIFF_EXIT_SIMILAR    0
#define CTFDIFF_EXIT_DIFFERENT  1
#define CTFDIFF_EXIT_USAGE      2
#define CTFDIFF_EXIT_ERROR      3

typedef enum ctf_diff_cmd {
        CTF_DIFF_TYPES =        0x01,
        CTF_DIFF_FUNCS =        0x02,
        CTF_DIFF_OBJS =         0x04,
        CTF_DIFF_DEFAULT =      0x07,
        CTF_DIFF_LABEL =        0x08,
        CTF_DIFF_ALL =          0x0f
} ctf_diff_cmd_t;

typedef struct {
        int             dil_next;
        const char      **dil_labels;
} ctfdiff_label_t;

static char *g_progname;
static const char *g_iname;
static ctf_file_t *g_ifp;
static const char *g_oname;
static ctf_file_t *g_ofp;
static char **g_typelist = NULL;
static int g_nexttype = 0;
static int g_ntypes = 0;
static char **g_objlist = NULL;
static int g_nextfunc = 0;
static int g_nfuncs = 0;
static char **g_funclist = NULL;
static int g_nextobj = 0;
static int g_nobjs = 0;
static boolean_t g_onlydiff = B_FALSE;
static boolean_t g_different = B_FALSE;
static ctf_diff_cmd_t g_flag = 0;

static void
ctfdiff_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);

        exit(CTFDIFF_EXIT_ERROR);
}

static const char *
ctfdiff_fp_to_name(ctf_file_t *fp)
{
        if (fp == g_ifp)
                return (g_iname);
        if (fp == g_ofp)
                return (g_oname);
        return (NULL);
}

/* ARGSUSED */
static void
ctfdiff_func_cb(ctf_file_t *ifp, ulong_t iidx, boolean_t similar,
    ctf_file_t *ofp, ulong_t oidx, void *arg)
{
        char namebuf[CTFDIFF_NAMELEN];

        if (similar == B_TRUE)
                return;

        if (ctf_symbol_name(ifp, iidx, namebuf, sizeof (namebuf)) == NULL) {
                if (g_nextfunc != 0)
                        return;
                (void) printf("ctf container %s function %lu is different\n",
                    ctfdiff_fp_to_name(ifp), iidx);
        } else {
                if (g_nextfunc != 0) {
                        int i;
                        for (i = 0; i < g_nextfunc; i++) {
                                if (strcmp(g_funclist[i], namebuf) == 0)
                                        break;
                        }
                        if (i == g_nextfunc)
                                return;
                }
                (void) printf("ctf container %s function %s (%lu) is "
                    "different\n", ctfdiff_fp_to_name(ifp), namebuf, iidx);
        }

        g_different = B_TRUE;
}

/* ARGSUSED */
static void
ctfdiff_obj_cb(ctf_file_t *ifp, ulong_t iidx, ctf_id_t iid, boolean_t similar,
    ctf_file_t *ofp, ulong_t oidx, ctf_id_t oid, void *arg)
{
        char namebuf[CTFDIFF_NAMELEN];

        if (similar == B_TRUE)
                return;

        if (ctf_symbol_name(ifp, iidx, namebuf, sizeof (namebuf)) == NULL) {
                if (g_nextobj != 0)
                        return;
                (void) printf("ctf container %s object %lu is different\n",
                    ctfdiff_fp_to_name(ifp), iidx);
        } else {
                if (g_nextobj != 0) {
                        int i;
                        for (i = 0; i < g_nextobj; i++) {
                                if (strcmp(g_objlist[i], namebuf) == 0)
                                        break;
                        }
                        if (i == g_nextobj)
                                return;
                }
                (void) printf("ctf container %s object %s (%lu) is different\n",
                    ctfdiff_fp_to_name(ifp), namebuf, iidx);
        }

        g_different = B_TRUE;
}

/* ARGSUSED */
static void
ctfdiff_cb(ctf_file_t *ifp, ctf_id_t iid, boolean_t similar, ctf_file_t *ofp,
    ctf_id_t oid, void *arg)
{
        if (similar == B_TRUE)
                return;

        if (ctf_type_kind(ifp, iid) == CTF_K_UNKNOWN)
                return;

        /*
         * Check if it's the type the user cares about.
         */
        if (g_nexttype != 0) {
                int i;
                char namebuf[CTFDIFF_NAMELEN];

                if (ctf_type_name(ifp, iid, namebuf, sizeof (namebuf)) ==
                    NULL) {
                        ctfdiff_fatal("failed to obtain the name "
                            "of type %ld from %s: %s\n",
                            iid, ctfdiff_fp_to_name(ifp),
                            ctf_errmsg(ctf_errno(ifp)));
                }

                for (i = 0; i < g_nexttype; i++) {
                        if (strcmp(g_typelist[i], namebuf) == 0)
                                break;
                }

                if (i == g_nexttype)
                        return;
        }

        g_different = B_TRUE;

        if (g_onlydiff == B_TRUE)
                return;

        (void) printf("ctf container %s type %ld is different\n",
            ctfdiff_fp_to_name(ifp), iid);
}

/* ARGSUSED */
static int
ctfdiff_labels_count(const char *name, const ctf_lblinfo_t *li, void *arg)
{
        uint32_t *count = arg;
        *count = *count + 1;

        return (0);
}

/* ARGSUSED */
static int
ctfdiff_labels_fill(const char *name, const ctf_lblinfo_t *li, void *arg)
{
        ctfdiff_label_t *dil = arg;

        dil->dil_labels[dil->dil_next] = name;
        dil->dil_next++;

        return (0);
}

static int
ctfdiff_labels(ctf_file_t *ifp, ctf_file_t *ofp)
{
        int ret;
        uint32_t nilabel, nolabel, i, j;
        ctfdiff_label_t idl, odl;
        const char **ilptr, **olptr;

        nilabel = nolabel = 0;
        ret = ctf_label_iter(ifp, ctfdiff_labels_count, &nilabel);
        if (ret == CTF_ERR)
                return (ret);
        ret = ctf_label_iter(ofp, ctfdiff_labels_count, &nolabel);
        if (ret == CTF_ERR)
                return (ret);

        if (nilabel != nolabel) {
                (void) printf("ctf container %s labels differ from ctf "
                    "container %s\n", ctfdiff_fp_to_name(ifp),
                    ctfdiff_fp_to_name(ofp));
                g_different = B_TRUE;
                return (0);
        }

        if (nilabel == 0)
                return (0);

        ilptr = malloc(sizeof (char *) * nilabel);
        olptr = malloc(sizeof (char *) * nolabel);
        if (ilptr == NULL || olptr == NULL) {
                ctfdiff_fatal("failed to allocate memory for label "
                    "comparison\n");
        }

        idl.dil_next = 0;
        idl.dil_labels = ilptr;
        odl.dil_next = 0;
        odl.dil_labels = olptr;

        if ((ret = ctf_label_iter(ifp, ctfdiff_labels_fill, &idl)) != 0)
                goto out;
        if ((ret = ctf_label_iter(ofp, ctfdiff_labels_fill, &odl)) != 0)
                goto out;

        for (i = 0; i < nilabel; i++) {
                for (j = 0; j < nolabel; j++) {
                        if (strcmp(ilptr[i], olptr[j]) == 0)
                                break;
                }

                if (j == nolabel) {
                        (void) printf("ctf container %s labels differ from ctf "
                            "container %s\n", ctfdiff_fp_to_name(ifp),
                            ctfdiff_fp_to_name(ofp));
                        g_different = B_TRUE;
                        break;
                }
        }

        ret = 0;
out:
        free(ilptr);
        free(olptr);
        return (ret);
}

static void
ctfdiff_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 [-afIloqt] [-F function] [-O object]"
            "[-p parent] [-P parent]\n"
            "\t[-T type] file1 file2\n"
            "\n"
            "\t-a diff label, types, objects, and functions\n"
            "\t-f diff function type information\n"
            "\t-F when diffing functions, only consider those named\n"
            "\t-I ignore the names of integral types\n"
            "\t-l diff CTF labels\n"
            "\t-o diff global object type information\n"
            "\t-O when diffing objects, only consider those named\n"
            "\t-p set the CTF parent for file1\n"
            "\t-P set the CTF parent for file2\n"
            "\t-q set quiet mode (no diff information sent to stdout)\n"
            "\t-t diff CTF type information\n"
            "\t-T when diffing types, only consider those named\n",
            g_progname);
}

int
main(int argc, char *argv[])
{
        ctf_diff_flag_t flags = 0;
        int err, c;
        ctf_file_t *ifp, *ofp;
        ctf_diff_t *cdp;
        ctf_file_t *pifp = NULL;
        ctf_file_t *pofp = NULL;

        g_progname = basename(argv[0]);

        while ((c = getopt(argc, argv, ":aqtfolIp:F:O:P:T:")) != -1) {
                switch (c) {
                case 'a':
                        g_flag |= CTF_DIFF_ALL;
                        break;
                case 't':
                        g_flag |= CTF_DIFF_TYPES;
                        break;
                case 'f':
                        g_flag |= CTF_DIFF_FUNCS;
                        break;
                case 'o':
                        g_flag |= CTF_DIFF_OBJS;
                        break;
                case 'l':
                        g_flag |= CTF_DIFF_LABEL;
                        break;
                case 'q':
                        g_onlydiff = B_TRUE;
                        break;
                case 'p':
                        pifp = ctf_open(optarg, &err);
                        if (pifp == NULL) {
                                ctfdiff_fatal("failed to open parent input "
                                    "container %s: %s\n", optarg,
                                    ctf_errmsg(err));
                        }
                        break;
                case 'F':
                        if (g_nextfunc == g_nfuncs) {
                                if (g_nfuncs == 0)
                                        g_nfuncs = 16;
                                else
                                        g_nfuncs *= 2;
                                g_funclist = realloc(g_funclist,
                                    sizeof (char *) * g_nfuncs);
                                if (g_funclist == NULL) {
                                        ctfdiff_fatal("failed to allocate "
                                            "memory for the %dth -F option: "
                                            "%s\n", g_nexttype + 1,
                                            strerror(errno));
                                }
                        }
                        g_funclist[g_nextfunc] = optarg;
                        g_nextfunc++;
                        break;
                case 'O':
                        if (g_nextobj == g_nobjs) {
                                if (g_nobjs == 0)
                                        g_nobjs = 16;
                                else
                                        g_nobjs *= 2;
                                g_objlist = realloc(g_objlist,
                                    sizeof (char *) * g_nobjs);
                                if (g_objlist == NULL) {
                                        ctfdiff_fatal("failed to allocate "
                                            "memory for the %dth -F option: "
                                            "%s\n", g_nexttype + 1,
                                            strerror(errno));
                                        return (CTFDIFF_EXIT_ERROR);
                                }
                        }
                        g_objlist[g_nextobj] = optarg;
                        g_nextobj++;
                        break;
                case 'I':
                        flags |= CTF_DIFF_F_IGNORE_INTNAMES;
                        break;
                case 'P':
                        pofp = ctf_open(optarg, &err);
                        if (pofp == NULL) {
                                ctfdiff_fatal("failed to open parent output "
                                    "container %s: %s\n", optarg,
                                    ctf_errmsg(err));
                        }
                        break;
                case 'T':
                        if (g_nexttype == g_ntypes) {
                                if (g_ntypes == 0)
                                        g_ntypes = 16;
                                else
                                        g_ntypes *= 2;
                                g_typelist = realloc(g_typelist,
                                    sizeof (char *) * g_ntypes);
                                if (g_typelist == NULL) {
                                        ctfdiff_fatal("failed to allocate "
                                            "memory for the %dth -T option: "
                                            "%s\n", g_nexttype + 1,
                                            strerror(errno));
                                }
                        }
                        g_typelist[g_nexttype] = optarg;
                        g_nexttype++;
                        break;
                case ':':
                        ctfdiff_usage("Option -%c requires an operand\n",
                            optopt);
                        return (CTFDIFF_EXIT_USAGE);
                case '?':
                        ctfdiff_usage("Unknown option: -%c\n", optopt);
                        return (CTFDIFF_EXIT_USAGE);
                }
        }

        argc -= optind - 1;
        argv += optind - 1;

        if (g_flag == 0)
                g_flag = CTF_DIFF_DEFAULT;

        if (argc != 3) {
                ctfdiff_usage(NULL);
                return (CTFDIFF_EXIT_USAGE);
        }

        if (g_nexttype != 0 && !(g_flag & CTF_DIFF_TYPES)) {
                ctfdiff_usage("-T cannot be used if not diffing types\n");
                return (CTFDIFF_EXIT_USAGE);
        }

        if (g_nextfunc != 0 && !(g_flag & CTF_DIFF_FUNCS)) {
                ctfdiff_usage("-F cannot be used if not diffing functions\n");
                return (CTFDIFF_EXIT_USAGE);
        }

        if (g_nextobj != 0 && !(g_flag & CTF_DIFF_OBJS)) {
                ctfdiff_usage("-O cannot be used if not diffing objects\n");
                return (CTFDIFF_EXIT_USAGE);
        }

        ifp = ctf_open(argv[1], &err);
        if (ifp == NULL) {
                ctfdiff_fatal("failed to open %s: %s\n", argv[1],
                    ctf_errmsg(err));
        }
        if (pifp != NULL) {
                err = ctf_import(ifp, pifp);
                if (err != 0) {
                        ctfdiff_fatal("failed to set parent container: %s\n",
                            ctf_errmsg(ctf_errno(pifp)));
                }
        }
        g_iname = argv[1];
        g_ifp = ifp;

        ofp = ctf_open(argv[2], &err);
        if (ofp == NULL) {
                ctfdiff_fatal("failed to open %s: %s\n", argv[2],
                    ctf_errmsg(err));
        }

        if (pofp != NULL) {
                err = ctf_import(ofp, pofp);
                if (err != 0) {
                        ctfdiff_fatal("failed to set parent container: %s\n",
                            ctf_errmsg(ctf_errno(pofp)));
                }
        }
        g_oname = argv[2];
        g_ofp = ofp;

        if (ctf_diff_init(ifp, ofp, &cdp) != 0) {
                ctfdiff_fatal("failed to initialize libctf diff engine: %s\n",
                    ctf_errmsg(ctf_errno(ifp)));
        }

        if (ctf_diff_setflags(cdp, flags) != 0) {
                ctfdiff_fatal("failed to set ctfdiff flags: %s\n",
                    ctf_errmsg(ctf_errno(ifp)));
        }

        err = 0;
        if ((g_flag & CTF_DIFF_TYPES) && err != CTF_ERR)
                err = ctf_diff_types(cdp, ctfdiff_cb, NULL);
        if ((g_flag & CTF_DIFF_FUNCS) && err != CTF_ERR)
                err = ctf_diff_functions(cdp, ctfdiff_func_cb, NULL);
        if ((g_flag & CTF_DIFF_OBJS) && err != CTF_ERR)
                err = ctf_diff_objects(cdp, ctfdiff_obj_cb, NULL);
        if ((g_flag & CTF_DIFF_LABEL) && err != CTF_ERR)
                err = ctfdiff_labels(ifp, ofp);

        ctf_diff_fini(cdp);
        if (err == CTF_ERR) {
                ctfdiff_fatal("encountered a libctf error: %s!\n",
                    ctf_errmsg(ctf_errno(ifp)));
        }

        return (g_different == B_TRUE ? CTFDIFF_EXIT_DIFFERENT :
            CTFDIFF_EXIT_SIMILAR);
}