Blame compare.c

Packit Service 80a84b
/*
Packit Service 80a84b
	Copyright(C) 2017, Red Hat, Inc.
Packit Service 80a84b
Packit Service 80a84b
	This program is free software: you can redistribute it and/or modify
Packit Service 80a84b
	it under the terms of the GNU General Public License as published by
Packit Service 80a84b
	the Free Software Foundation, either version 3 of the License, or
Packit Service 80a84b
	(at your option) any later version.
Packit Service 80a84b
Packit Service 80a84b
	This program is distributed in the hope that it will be useful,
Packit Service 80a84b
	but WITHOUT ANY WARRANTY; without even the implied warranty of
Packit Service 80a84b
	MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
Packit Service 80a84b
	GNU General Public License for more details.
Packit Service 80a84b
Packit Service 80a84b
	You should have received a copy of the GNU General Public License
Packit Service 80a84b
	along with this program.  If not, see <http://www.gnu.org/licenses/>.
Packit Service 80a84b
*/
Packit Service 80a84b
Packit Service 80a84b
#ifndef	_GNU_SOURCE /* We use GNU basename() that doesn't modify the arg */
Packit Service 80a84b
#error "We need GNU version of basename()!"
Packit Service 80a84b
#endif
Packit Service 80a84b
Packit Service 80a84b
#include <errno.h>
Packit Service 80a84b
#include <getopt.h>
Packit Service 80a84b
#include <stdbool.h>
Packit Service 80a84b
#include <stdio.h>
Packit Service 80a84b
#include <stdlib.h>
Packit Service 80a84b
#include <string.h>
Packit Service 80a84b
#include <sys/stat.h>
Packit Service 80a84b
#include <libgen.h>
Packit Service 80a84b
Packit Service 80a84b
#include "main.h"
Packit Service 80a84b
#include "objects.h"
Packit Service 80a84b
#include "utils.h"
Packit Service 80a84b
#include "compare.h"
Packit Service 80a84b
Packit Service 80a84b
/* diff -u style prefix for tree comparison */
Packit Service 80a84b
#define ADD_PREFIX "+"
Packit Service 80a84b
#define DEL_PREFIX "-"
Packit Service 80a84b
Packit Service 80a84b
/* Return values for the (_)compare_tree functions */
Packit Service 80a84b
enum {
Packit Service 80a84b
	COMP_SAME = 0,	/* Subtree are equal */
Packit Service 80a84b
	COMP_DIFF,	/* Subtree differs, stop the scanning */
Packit Service 80a84b
	COMP_CONT,	/* Only offset or alignment change, continue */
Packit Service 80a84b
};
Packit Service 80a84b
Packit Service 80a84b
int comp_return_value(int old, int new) {
Packit Service 80a84b
	switch (new) {
Packit Service 80a84b
	case COMP_DIFF:
Packit Service 80a84b
		return COMP_DIFF;
Packit Service 80a84b
	case COMP_CONT:
Packit Service 80a84b
		if (old != COMP_DIFF)
Packit Service 80a84b
			return COMP_CONT;
Packit Service 80a84b
	case COMP_SAME:
Packit Service 80a84b
		;
Packit Service 80a84b
	}
Packit Service 80a84b
	return old;
Packit Service 80a84b
}
Packit Service 80a84b
Packit Service 80a84b
/*
Packit Service 80a84b
 * Is this symbol a duplicate, i.e. is not the first version of this symbol.
Packit Service 80a84b
 */
Packit Service 80a84b
static bool is_duplicate(char *filename)
Packit Service 80a84b
{
Packit Service 80a84b
	char *base = basename(filename);
Packit Service 80a84b
	char *prefix = NULL, *name = NULL;
Packit Service 80a84b
	int version = 0;
Packit Service 80a84b
	bool ret = (sscanf(base, "%m[a-z]--%m[^.-]-%i.txt",
Packit Service 80a84b
			   &prefix, &name, &version) == 3);
Packit Service 80a84b
Packit Service 80a84b
	free(prefix);
Packit Service 80a84b
	free(name);
Packit Service 80a84b
Packit Service 80a84b
	return ret;
Packit Service 80a84b
}
Packit Service 80a84b
Packit Service 80a84b
static void _print_node_list(const char *s, const char *prefix,
Packit Service 80a84b
			     obj_list_t *list, obj_list_t *last, FILE *stream) {
Packit Service 80a84b
	obj_list_t *l = list;
Packit Service 80a84b
Packit Service 80a84b
	fprintf(stream, "%s:\n", s);
Packit Service 80a84b
	while (l && l != last) {
Packit Service 80a84b
		obj_print_tree__prefix(l->member, prefix, stream);
Packit Service 80a84b
		l = l->next;
Packit Service 80a84b
	}
Packit Service 80a84b
}
Packit Service 80a84b
Packit Service 80a84b
static void print_node_list(const char *s, const char *prefix,
Packit Service 80a84b
			    obj_list_t *list, FILE *stream) {
Packit Service 80a84b
	_print_node_list(s, prefix, list, NULL, stream);
Packit Service 80a84b
}
Packit Service 80a84b
Packit Service 80a84b
Packit Service 80a84b
/*
Packit Service 80a84b
 * There is some ambiguity here that need to be cleared and a
Packit Service 80a84b
 * hierarchy that need to be explicitly established. The current
Packit Service 80a84b
 * situation is: if there is a real change to the object
Packit Service 80a84b
 * (different name, type...) we return CMP_DIFF; If that's not
Packit Service 80a84b
 * the case, but a referred symbol has changed, we return
Packit Service 80a84b
 * CMP_REFFILE; If that's not the case, but the offset has
Packit Service 80a84b
 * changed, we return CMP_OFFSET. So the current order is
Packit Service 80a84b
 * CMP_DIFF > CMP_REFFILE > CMP_OFFSET > CMP_ALIGNMENT > CMP_BYTE_SIZE
Packit Service 80a84b
 * In case of alignment, if the structure alignment has changed,
Packit Service 80a84b
 * only that is reported. If not, then the fields are checked and
Packit Service 80a84b
 * the all the different fields are reported.
Packit Service 80a84b
 * The same is true of byte size changes.
Packit Service 80a84b
 */
Packit Service 80a84b
Packit Service 80a84b
typedef enum {
Packit Service 80a84b
	CMP_SAME = 0,
Packit Service 80a84b
	CMP_OFFSET,	/* Only the offset has changed */
Packit Service 80a84b
	CMP_DIFF,	/* Nodes are differents */
Packit Service 80a84b
	CMP_REFFILE,	/* A refered symbol has changed */
Packit Service 80a84b
	CMP_ALIGNMENT,  /* An alignment has changed */
Packit Service 80a84b
	CMP_BYTE_SIZE,  /* Byte size has changed */
Packit Service 80a84b
} cmp_ret_t;
Packit Service 80a84b
Packit Service 80a84b
static int compare_two_files(const char *filename, const char *newfile,
Packit Service 80a84b
			     bool follow);
Packit Service 80a84b
Packit Service 80a84b
static int cmp_node_reffile(obj_t *o1, obj_t *o2)
Packit Service 80a84b
{
Packit Service 80a84b
	char *s1 = filenametotype(o1->base_type);
Packit Service 80a84b
	char *s2 = filenametotype(o2->base_type);
Packit Service 80a84b
	int len;
Packit Service 80a84b
	bool ret;
Packit Service 80a84b
Packit Service 80a84b
	ret = safe_streq(s1, s2);
Packit Service 80a84b
	free(s1);
Packit Service 80a84b
	free(s2);
Packit Service 80a84b
Packit Service 80a84b
	if (!ret)
Packit Service 80a84b
		return CMP_DIFF;
Packit Service 80a84b
Packit Service 80a84b
	/*
Packit Service 80a84b
	 * Compare the symbol referenced by file, but be careful not
Packit Service 80a84b
	 * to follow imaginary declaration path.
Packit Service 80a84b
	 *
Packit Service 80a84b
	 * TODO: This is quite wasteful. We reopen files and parse
Packit Service 80a84b
	 * them again many times.
Packit Service 80a84b
	 */
Packit Service 80a84b
	len = strlen(DECLARATION_PATH);
Packit Service 80a84b
	if (strncmp(o1->base_type, DECLARATION_PATH, len) &&
Packit Service 80a84b
	    strncmp(o2->base_type, DECLARATION_PATH, len) &&
Packit Service 80a84b
	    compare_two_files(o1->base_type, o2->base_type, true))
Packit Service 80a84b
		return CMP_REFFILE;
Packit Service 80a84b
Packit Service 80a84b
	return CMP_SAME;
Packit Service 80a84b
}
Packit Service 80a84b
Packit Service 80a84b
static int _cmp_nodes(obj_t *o1, obj_t *o2, bool search)
Packit Service 80a84b
{
Packit Service 80a84b
	if ((o1->type != o2->type) ||
Packit Service 80a84b
	    !safe_streq(o1->name, o2->name) ||
Packit Service 80a84b
	    (is_weak(o1) != is_weak(o2)) ||
Packit Service 80a84b
	    (is_weak(o1) && is_weak(o2) && !safe_streq(o1->link, o2->link)) ||
Packit Service 80a84b
	    ((o1->ptr == NULL) != (o2->ptr == NULL)) ||
Packit Service 80a84b
	    (has_constant(o1) && (o1->constant != o2->constant)) ||
Packit Service 80a84b
	    (has_index(o1) && (o1->index != o2->index)) ||
Packit Service 80a84b
	    (is_bitfield(o1) != is_bitfield(o2)) ||
Packit Service 80a84b
	    (is_bitfield(o1) && ((o1->last_bit - o1->first_bit) !=
Packit Service 80a84b
				 (o2->last_bit - o2->first_bit))))
Packit Service 80a84b
		return CMP_DIFF;
Packit Service 80a84b
Packit Service 80a84b
	if (o1->type == __type_reffile) {
Packit Service 80a84b
		int ret;
Packit Service 80a84b
Packit Service 80a84b
		ret = cmp_node_reffile(o1, o2);
Packit Service 80a84b
		if (ret)
Packit Service 80a84b
			return ret;
Packit Service 80a84b
	} else if (!safe_streq(o1->base_type, o2->base_type))
Packit Service 80a84b
		return CMP_DIFF;
Packit Service 80a84b
Packit Service 80a84b
	if (has_offset(o1) &&
Packit Service 80a84b
	    ((o1->offset != o2->offset) ||
Packit Service 80a84b
	     (is_bitfield(o1) && (o1->first_bit != o2->first_bit)))) {
Packit Service 80a84b
		if (search && o1->name == NULL)
Packit Service 80a84b
			/*
Packit Service 80a84b
			 * This field is an unnamed struct or union. When
Packit Service 80a84b
			 * searching for a node, avoid to consider the next
Packit Service 80a84b
			 * unnamed struct or union to be the same one.
Packit Service 80a84b
			 */
Packit Service 80a84b
			return CMP_DIFF;
Packit Service 80a84b
		return CMP_OFFSET;
Packit Service 80a84b
	}
Packit Service 80a84b
Packit Service 80a84b
	if (o1->alignment != o2->alignment)
Packit Service 80a84b
		return CMP_ALIGNMENT;
Packit Service 80a84b
Packit Service 80a84b
	if (o1->byte_size != o2->byte_size)
Packit Service 80a84b
		return CMP_BYTE_SIZE;
Packit Service 80a84b
Packit Service 80a84b
	return CMP_SAME;
Packit Service 80a84b
}
Packit Service 80a84b
Packit Service 80a84b
static int cmp_nodes(obj_t *o1, obj_t *o2)
Packit Service 80a84b
{
Packit Service 80a84b
	return _cmp_nodes(o1, o2, false);
Packit Service 80a84b
}
Packit Service 80a84b
Packit Service 80a84b
typedef enum {
Packit Service 80a84b
	DIFF_INSERT,
Packit Service 80a84b
	DIFF_DELETE,
Packit Service 80a84b
	DIFF_REPLACE,
Packit Service 80a84b
	DIFF_CONT,
Packit Service 80a84b
} diff_ret_t;
Packit Service 80a84b
Packit Service 80a84b
/*
Packit Service 80a84b
 * When field are changed or moved around, there can be several diff
Packit Service 80a84b
 * representations for the change.  We are trying to keep the diff as
Packit Service 80a84b
 * small as possible, while keeping most significant changes in term
Packit Service 80a84b
 * of kABI (mainly shifted fields, which most likely indicate that
Packit Service 80a84b
 * some change to the ABI have been overlooked).
Packit Service 80a84b
 *
Packit Service 80a84b
 * This function compare two lists whose first member diverge. We're
Packit Service 80a84b
 * looking at four different scenarios:
Packit Service 80a84b
 * - N fields appears only in list2, then the lists rejoined (insertion)
Packit Service 80a84b
 * - P fields appears only in list1, then the lists rejoined (deletion)
Packit Service 80a84b
 * - Q fields diverges, then the lists rejoined (replacement)
Packit Service 80a84b
 * - the lists never rejoined
Packit Service 80a84b
 *
Packit Service 80a84b
 * Since for the same change, several of the scenarios above might
Packit Service 80a84b
 * represent the change, we choose the one that minimize the diff
Packit Service 80a84b
 * (min(N,P,Q)). So we're looking for the first element of list1 in
Packit Service 80a84b
 * list2, the first element of list2 in list1 or the first line where
Packit Service 80a84b
 * list1 and list2 do not differ, whichever comes first.
Packit Service 80a84b
 */
Packit Service 80a84b
static diff_ret_t list_diff(obj_list_t *list1, obj_list_t **next1,
Packit Service 80a84b
			    obj_list_t *list2, obj_list_t **next2)
Packit Service 80a84b
{
Packit Service 80a84b
	obj_t *o1 = list2->member, *o2 = list1->member, *o = o1;
Packit Service 80a84b
	int d1 = 0, d2 = 0, ret;
Packit Service 80a84b
	obj_list_t *next;
Packit Service 80a84b
Packit Service 80a84b
	next = *next1 = list1;
Packit Service 80a84b
	*next2 = list2;
Packit Service 80a84b
Packit Service 80a84b
	while (next) {
Packit Service 80a84b
		ret = _cmp_nodes(o, next->member, true);
Packit Service 80a84b
		if (ret == CMP_SAME || ret == CMP_OFFSET
Packit Service 80a84b
		    || ret == CMP_ALIGNMENT) {
Packit Service 80a84b
			if (o == o1)
Packit Service 80a84b
				/* We find the first element of list2
Packit Service 80a84b
				   on list1, that is d1 elements have
Packit Service 80a84b
				   been removed from list1 */
Packit Service 80a84b
				return DIFF_DELETE;
Packit Service 80a84b
			else
Packit Service 80a84b
				return DIFF_INSERT;
Packit Service 80a84b
		}
Packit Service 80a84b
Packit Service 80a84b
		if (d1 == d2)  {
Packit Service 80a84b
			ret = _cmp_nodes((*next1)->member, (*next2)->member,
Packit Service 80a84b
					 true);
Packit Service 80a84b
			if (ret == CMP_SAME || ret == CMP_OFFSET
Packit Service 80a84b
			    || ret == CMP_ALIGNMENT) {
Packit Service 80a84b
				/* d1 fields have been replaced */
Packit Service 80a84b
				return DIFF_REPLACE;
Packit Service 80a84b
			}
Packit Service 80a84b
Packit Service 80a84b
		}
Packit Service 80a84b
Packit Service 80a84b
		if (!(*next1) || !((*next1)->next) || (d2  < d1)) {
Packit Service 80a84b
			next = *next2 = (*next2)->next;
Packit Service 80a84b
			o = o2;
Packit Service 80a84b
			d2++;
Packit Service 80a84b
		} else {
Packit Service 80a84b
			next = *next1 = (*next1)->next;
Packit Service 80a84b
			o = o1;
Packit Service 80a84b
			d1++;
Packit Service 80a84b
		}
Packit Service 80a84b
	}
Packit Service 80a84b
	return DIFF_CONT;
Packit Service 80a84b
}
Packit Service 80a84b
Packit Service 80a84b
/*
Packit Service 80a84b
 * We want to show practical output to the user.  For instance if a
Packit Service 80a84b
 * struct member type change, we want to show which struct member
Packit Service 80a84b
 * changed type, not that somewhere a "signed int" has been changed
Packit Service 80a84b
 * into a "unsigned bin".
Packit Service 80a84b
 *
Packit Service 80a84b
 * For now, we consider that a useful output should start at a named
Packit Service 80a84b
 * object or at a struct field or var (the field/var itself may be
Packit Service 80a84b
 * unamed, typically when it's an union or struct of alternative
Packit Service 80a84b
 * elements but it most likely contains named element).
Packit Service 80a84b
 */
Packit Service 80a84b
static bool worthy_of_print(obj_t *o)
Packit Service 80a84b
{
Packit Service 80a84b
	return (o->name != NULL) ||
Packit Service 80a84b
		(o->type == __type_struct_member) ||
Packit Service 80a84b
		(o->type == __type_var);
Packit Service 80a84b
}
Packit Service 80a84b
Packit Service 80a84b
static void print_two_nodes(const char *s, obj_t *o1, obj_t *o2, FILE *stream)
Packit Service 80a84b
{
Packit Service 80a84b
Packit Service 80a84b
	while (!worthy_of_print(o1)) {
Packit Service 80a84b
		o1 = o1->parent;
Packit Service 80a84b
		o2 = o2->parent;
Packit Service 80a84b
		if ((o1 == NULL) || (o2 == NULL))
Packit Service 80a84b
			fail("No ancestor worthy of print\n");
Packit Service 80a84b
	}
Packit Service 80a84b
	fprintf(stream, "%s:\n", s);
Packit Service 80a84b
	obj_print_tree__prefix(o1, DEL_PREFIX, stream);
Packit Service 80a84b
	obj_print_tree__prefix(o2, ADD_PREFIX, stream);
Packit Service 80a84b
}
Packit Service 80a84b
Packit Service 80a84b
typedef struct compare_config_s {
Packit Service 80a84b
	bool debug;
Packit Service 80a84b
	bool hide_kabi;
Packit Service 80a84b
	bool hide_kabi_new;
Packit Service 80a84b
	bool skip_duplicate; /* Don't show multiple version of a symbol */
Packit Service 80a84b
	int follow;
Packit Service 80a84b
	char *old_dir;
Packit Service 80a84b
	char *new_dir;
Packit Service 80a84b
	char *filename;
Packit Service 80a84b
	char **flist;
Packit Service 80a84b
	int flistsz;
Packit Service 80a84b
	int flistcnt;
Packit Service 80a84b
	int ret;
Packit Service 80a84b
	/*
Packit Service 80a84b
	 * The following options allow to hide some symbol changes in
Packit Service 80a84b
	 * kABI comparison. Hides...
Packit Service 80a84b
	 */
Packit Service 80a84b
	int no_replaced; /* replaced symbols */
Packit Service 80a84b
	int no_shifted;  /* symbols whose offset shifted */
Packit Service 80a84b
	int no_inserted; /* symbols inserted in the middle of a struct/union */
Packit Service 80a84b
	int no_deleted;  /* symbols removed in the middle (poke a hole) */
Packit Service 80a84b
	int no_added;    /* symbols added at the end of a struct/union... */
Packit Service 80a84b
	int no_removed;  /* symbols removed at the end of a struct/union... */
Packit Service 80a84b
	int no_moved_files; /* file that has been moved (or removed) */
Packit Service 80a84b
} compare_config_t;
Packit Service 80a84b
Packit Service 80a84b
compare_config_t compare_config = {false, false, false, false, 0,
Packit Service 80a84b
				   NULL, NULL, NULL, NULL,
Packit Service 80a84b
				   0, 0, 0, 0, 0, 0, 0, 0, 0, 0};
Packit Service 80a84b
Packit Service 80a84b
static void message_alignment_value(unsigned v, FILE *stream)
Packit Service 80a84b
{
Packit Service 80a84b
	if (v == 0)
Packit Service 80a84b
		fprintf(stream, "<undefined>");
Packit Service 80a84b
	else
Packit Service 80a84b
		fprintf(stream, "%u", v);
Packit Service 80a84b
}
Packit Service 80a84b
Packit Service 80a84b
static void message_byte_size_value(unsigned int v, FILE *stream)
Packit Service 80a84b
{
Packit Service 80a84b
	if (v == 0)
Packit Service 80a84b
		fprintf(stream, "<undefined>");
Packit Service 80a84b
	else
Packit Service 80a84b
		fprintf(stream, "%u", v);
Packit Service 80a84b
}
Packit Service 80a84b
Packit Service 80a84b
static void message_alignment(obj_t *o1, obj_t *o2, FILE *stream)
Packit Service 80a84b
{
Packit Service 80a84b
	char *part_str;
Packit Service 80a84b
Packit Service 80a84b
	if (o1->type == __type_struct_member) {
Packit Service 80a84b
		part_str = "field";
Packit Service 80a84b
	} else {
Packit Service 80a84b
		part_str = "symbol";
Packit Service 80a84b
	}
Packit Service 80a84b
Packit Service 80a84b
	fprintf(stream, "The alignment of %s '%s' has changed from ",
Packit Service 80a84b
		part_str, o1->name);
Packit Service 80a84b
Packit Service 80a84b
	message_alignment_value(o1->alignment, stream);
Packit Service 80a84b
	fprintf(stream, " to ");
Packit Service 80a84b
	message_alignment_value(o2->alignment, stream);
Packit Service 80a84b
	fprintf(stream, "\n");
Packit Service 80a84b
}
Packit Service 80a84b
Packit Service 80a84b
static void message_byte_size(obj_t *o1, obj_t *o2, FILE *stream)
Packit Service 80a84b
{
Packit Service 80a84b
	fprintf(stream, "The byte size of symbol '%s' has changed from ",
Packit Service 80a84b
		o1->name);
Packit Service 80a84b
Packit Service 80a84b
	message_byte_size_value(o1->byte_size, stream);
Packit Service 80a84b
	fprintf(stream, " to ");
Packit Service 80a84b
	message_byte_size_value(o2->byte_size, stream);
Packit Service 80a84b
	fprintf(stream, "\n");
Packit Service 80a84b
}
Packit Service 80a84b
Packit Service 80a84b
static int _compare_tree(obj_t *o1, obj_t *o2, FILE *stream)
Packit Service 80a84b
{
Packit Service 80a84b
	obj_list_t *list1 = NULL, *list2 = NULL;
Packit Service 80a84b
	int ret = COMP_SAME, tmp;
Packit Service 80a84b
Packit Service 80a84b
	tmp = cmp_nodes(o1, o2);
Packit Service 80a84b
	if (tmp) {
Packit Service 80a84b
		if (tmp == CMP_REFFILE) {
Packit Service 80a84b
			fprintf(stream, "symbol %s has changed\n",
Packit Service 80a84b
				o1->base_type);
Packit Service 80a84b
			ret = COMP_DIFF;
Packit Service 80a84b
		} else if ((tmp == CMP_OFFSET && !compare_config.no_shifted) ||
Packit Service 80a84b
			   (tmp == CMP_DIFF && !compare_config.no_replaced)) {
Packit Service 80a84b
			const char *s =	(tmp == CMP_OFFSET) ?
Packit Service 80a84b
				"Shifted" : "Replaced";
Packit Service 80a84b
			print_two_nodes(s, o1, o2, stream);
Packit Service 80a84b
			ret = COMP_CONT;
Packit Service 80a84b
		} else if (tmp == CMP_ALIGNMENT) {
Packit Service 80a84b
			message_alignment(o1, o2, stream);
Packit Service 80a84b
			ret = COMP_CONT;
Packit Service 80a84b
		} else if (tmp == CMP_BYTE_SIZE) {
Packit Service 80a84b
			message_byte_size(o1, o2, stream);
Packit Service 80a84b
			ret = COMP_CONT;
Packit Service 80a84b
		}
Packit Service 80a84b
Packit Service 80a84b
		if (ret == COMP_DIFF)
Packit Service 80a84b
			return ret;
Packit Service 80a84b
	}
Packit Service 80a84b
Packit Service 80a84b
	if (o1->member_list)
Packit Service 80a84b
		list1 = o1->member_list->first;
Packit Service 80a84b
	if (o2->member_list)
Packit Service 80a84b
		list2 = o2->member_list->first;
Packit Service 80a84b
Packit Service 80a84b
	while (list1 && list2) {
Packit Service 80a84b
		if (cmp_nodes(list1->member, list2->member) == CMP_DIFF) {
Packit Service 80a84b
			int index;
Packit Service 80a84b
			obj_list_t *next1, *next2;
Packit Service 80a84b
Packit Service 80a84b
			index = list_diff(list1, &next1, list2, &next2);
Packit Service 80a84b
Packit Service 80a84b
			switch (index) {
Packit Service 80a84b
			case DIFF_INSERT:
Packit Service 80a84b
				/* Insertion */
Packit Service 80a84b
				if (!compare_config.no_inserted) {
Packit Service 80a84b
					_print_node_list("Inserted", ADD_PREFIX,
Packit Service 80a84b
							 list2, next2, stream);
Packit Service 80a84b
					ret = COMP_DIFF;
Packit Service 80a84b
				}
Packit Service 80a84b
				list2 = next2;
Packit Service 80a84b
				break;
Packit Service 80a84b
			case DIFF_DELETE:
Packit Service 80a84b
				/* Removal */
Packit Service 80a84b
				if (!compare_config.no_deleted) {
Packit Service 80a84b
					_print_node_list("Deleted", DEL_PREFIX,
Packit Service 80a84b
							 list1, next1, stream);
Packit Service 80a84b
					ret = COMP_DIFF;
Packit Service 80a84b
				}
Packit Service 80a84b
				list1 = next1;
Packit Service 80a84b
				break;
Packit Service 80a84b
			case DIFF_REPLACE:
Packit Service 80a84b
				/*
Packit Service 80a84b
				 * We could print the diff here, but relying on
Packit Service 80a84b
				 * the next calls to _compare_tree() to display
Packit Service 80a84b
				 * the replaced fields individually works too.
Packit Service 80a84b
				 */
Packit Service 80a84b
			case DIFF_CONT:
Packit Service 80a84b
				/* Nothing to do */
Packit Service 80a84b
				;
Packit Service 80a84b
			}
Packit Service 80a84b
		}
Packit Service 80a84b
Packit Service 80a84b
		tmp =_compare_tree(list1->member, list2->member, stream);
Packit Service 80a84b
		ret = comp_return_value(ret, tmp);
Packit Service 80a84b
Packit Service 80a84b
		list1 = list1->next;
Packit Service 80a84b
		list2 = list2->next;
Packit Service 80a84b
		if (!list1 && list2) {
Packit Service 80a84b
			if (!compare_config.no_added) {
Packit Service 80a84b
				print_node_list("Added", ADD_PREFIX,
Packit Service 80a84b
						list2, stream);
Packit Service 80a84b
				ret = COMP_DIFF;
Packit Service 80a84b
			}
Packit Service 80a84b
			return ret;
Packit Service 80a84b
		}
Packit Service 80a84b
		if (list1 && !list2) {
Packit Service 80a84b
			if (!compare_config.no_removed) {
Packit Service 80a84b
				print_node_list("Removed", DEL_PREFIX,
Packit Service 80a84b
						list1, stream);
Packit Service 80a84b
				ret = COMP_DIFF;
Packit Service 80a84b
			}
Packit Service 80a84b
			return ret;
Packit Service 80a84b
		}
Packit Service 80a84b
	}
Packit Service 80a84b
Packit Service 80a84b
	if (o1->ptr && o2->ptr) {
Packit Service 80a84b
		tmp = _compare_tree(o1->ptr, o2->ptr, stream);
Packit Service 80a84b
		ret = comp_return_value(ret, tmp);
Packit Service 80a84b
	}
Packit Service 80a84b
Packit Service 80a84b
	return ret;
Packit Service 80a84b
}
Packit Service 80a84b
Packit Service 80a84b
/*
Packit Service 80a84b
 * Compare two symbols and show the difference in a c-like format
Packit Service 80a84b
 */
Packit Service 80a84b
static int compare_tree(obj_t *o1, obj_t *o2, FILE *stream)
Packit Service 80a84b
{
Packit Service 80a84b
	return _compare_tree(o1, o2, stream);
Packit Service 80a84b
}
Packit Service 80a84b
Packit Service 80a84b
static bool push_file(const char *filename)
Packit Service 80a84b
{
Packit Service 80a84b
	int i, sz = compare_config.flistsz;
Packit Service 80a84b
	int cnt = compare_config.flistcnt;
Packit Service 80a84b
	char **flist = compare_config.flist;
Packit Service 80a84b
Packit Service 80a84b
	for (i = 0; i < cnt; i++)
Packit Service 80a84b
		if (!strcmp(flist[i], filename))
Packit Service 80a84b
			return false;
Packit Service 80a84b
Packit Service 80a84b
	if (!sz) {
Packit Service 80a84b
		compare_config.flistsz = sz = 16;
Packit Service 80a84b
		compare_config.flist = flist =
Packit Service 80a84b
			safe_zmalloc(16 * sizeof(char *));
Packit Service 80a84b
	}
Packit Service 80a84b
	if (cnt >= sz) {
Packit Service 80a84b
		sz *= 2;
Packit Service 80a84b
		compare_config.flistsz = sz;
Packit Service 80a84b
		compare_config.flist = flist =
Packit Service 80a84b
			safe_realloc(flist, sz * sizeof(char *));
Packit Service 80a84b
	}
Packit Service 80a84b
Packit Service 80a84b
	flist[cnt] = strdup(filename);
Packit Service 80a84b
	compare_config.flistcnt++;
Packit Service 80a84b
Packit Service 80a84b
	return true;
Packit Service 80a84b
}
Packit Service 80a84b
Packit Service 80a84b
static void free_files()
Packit Service 80a84b
{
Packit Service 80a84b
	int i;
Packit Service 80a84b
Packit Service 80a84b
	for (i = 0; i < compare_config.flistcnt; i++)
Packit Service 80a84b
		free(compare_config.flist[i]);
Packit Service 80a84b
	free(compare_config.flist);
Packit Service 80a84b
	compare_config.flistcnt = compare_config.flistsz = 0;
Packit Service 80a84b
}
Packit Service 80a84b
Packit Service 80a84b
static void compare_usage()
Packit Service 80a84b
{
Packit Service 80a84b
	printf("Usage:\n"
Packit Service 80a84b
	       "\tcompare [options] kabi_dir kabi_dir [kabi_file...]\n"
Packit Service 80a84b
	       "\tcompare [options] kabi_file kabi_file\n"
Packit Service 80a84b
	       "\nOptions:\n"
Packit Service 80a84b
	       "    -h, --help:\t\tshow this message\n"
Packit Service 80a84b
	       "    -k, --hide-kabi:\thide changes made by RH_KABI_REPLACE()\n"
Packit Service 80a84b
	       "    -n, --hide-kabi-new:\n\t\t\thide the kabi trickery made by"
Packit Service 80a84b
	       " RH_KABI_REPLACE, but show the new field\n"
Packit Service 80a84b
	       "    -d, --debug:\tprint the raw tree\n"
Packit Service 80a84b
	       "    --follow:\t\tfollow referenced symbols\n"
Packit Service 80a84b
	       "    --no-offset:\tdon't display the offset of struct fields\n"
Packit Service 80a84b
	       "    --no-replaced:\thide replaced symbols"
Packit Service 80a84b
	       " (symbols that changed, but hasn't moved)\n"
Packit Service 80a84b
	       "    --no-shifted:\thide shifted symbols"
Packit Service 80a84b
	       " (symbol that hasn't changed, but whose offset changed)\n"
Packit Service 80a84b
	       "    --no-inserted:\t"
Packit Service 80a84b
	       "hide symbols inserted in the middle of a struct, union...\n"
Packit Service 80a84b
	       "    --no-deleted:\t"
Packit Service 80a84b
	       "hide symbols removed from the middle of a struct, union...\n"
Packit Service 80a84b
	       "    --no-added:\t\t"
Packit Service 80a84b
	       "hide symbols added at the end of a struct, union...\n"
Packit Service 80a84b
	       "    --no-removed:\t"
Packit Service 80a84b
	       "hide symbols removed from the end of a struct, union...\n"
Packit Service 80a84b
	       "    --no-moved-files:\thide changes caused by symbols "
Packit Service 80a84b
	       "definition moving to another file\n\t\t\t"
Packit Service 80a84b
	       "Warning: it also hides symbols that are removed entirely\n"
Packit Service 80a84b
	       "    -s, --skip-duplicate:\tshow only the first version of a "
Packit Service 80a84b
	       "symbol when several exist\n");
Packit Service 80a84b
Packit Service 80a84b
	exit(1);
Packit Service 80a84b
}
Packit Service 80a84b
Packit Service 80a84b
/*
Packit Service 80a84b
 * Parse two files and compare the resulting tree.
Packit Service 80a84b
 *
Packit Service 80a84b
 * filename: file to compare (relative to compare_config.*_dir)
Packit Service 80a84b
 * newfile:  if not NULL, the file to use in compare_config.new_dir,
Packit Service 80a84b
 *           otherwise, filename is used for both.
Packit Service 80a84b
 * follow:   Are we here because we followed a reference file? If so,
Packit Service 80a84b
 *           don't print anything and exit immediately if follow
Packit Service 80a84b
 *           option isn't set.
Packit Service 80a84b
 */
Packit Service 80a84b
static int compare_two_files(const char *filename, const char *newfile,
Packit Service 80a84b
			     bool follow)
Packit Service 80a84b
{
Packit Service 80a84b
	obj_t *root1, *root2;
Packit Service 80a84b
	char *old_dir = compare_config.old_dir;
Packit Service 80a84b
	char *new_dir = compare_config.new_dir;
Packit Service 80a84b
	char *path1, *path2, *s = NULL;
Packit Service 80a84b
	const char *filename2;
Packit Service 80a84b
	FILE *file1, *file2, *stream;
Packit Service 80a84b
	struct stat fstat;
Packit Service 80a84b
	size_t sz;
Packit Service 80a84b
	int ret = 0, tmp;
Packit Service 80a84b
Packit Service 80a84b
	if (follow && !compare_config.follow)
Packit Service 80a84b
		return 0;
Packit Service 80a84b
Packit Service 80a84b
	/* Avoid infinite loop */
Packit Service 80a84b
	if (!push_file(filename))
Packit Service 80a84b
		return 0;
Packit Service 80a84b
Packit Service 80a84b
	safe_asprintf(&path1, "%s/%s", old_dir, filename);
Packit Service 80a84b
	filename2 = newfile ? newfile : filename;
Packit Service 80a84b
	safe_asprintf(&path2, "%s/%s", new_dir, filename2);
Packit Service 80a84b
Packit Service 80a84b
	if (stat(path2, &fstat) != 0) {
Packit Service 80a84b
		if (errno == ENOENT) {
Packit Service 80a84b
			/* Don't consider an incomplete definition a change */
Packit Service 80a84b
			if (strncmp(filename2, DECLARATION_PATH,
Packit Service 80a84b
				    strlen(DECLARATION_PATH)) &&
Packit Service 80a84b
			    !compare_config.no_moved_files) {
Packit Service 80a84b
				ret = EXIT_KABI_CHANGE;
Packit Service 80a84b
				printf("Symbol removed or moved: %s\n",
Packit Service 80a84b
				       filename);
Packit Service 80a84b
			}
Packit Service 80a84b
Packit Service 80a84b
			free(path1);
Packit Service 80a84b
			free(path2);
Packit Service 80a84b
Packit Service 80a84b
			return ret;
Packit Service 80a84b
		} else {
Packit Service 80a84b
			fail("Failed to stat() file%s: %s\n",
Packit Service 80a84b
			     path2, strerror(errno));
Packit Service 80a84b
		}
Packit Service 80a84b
	}
Packit Service 80a84b
Packit Service 80a84b
	file1 = safe_fopen(path1);
Packit Service 80a84b
	file2 = safe_fopen(path2);
Packit Service 80a84b
Packit Service 80a84b
	root1 = obj_parse(file1, path1);
Packit Service 80a84b
	root2 = obj_parse(file2, path2);
Packit Service 80a84b
Packit Service 80a84b
	free(path1);
Packit Service 80a84b
	free(path2);
Packit Service 80a84b
Packit Service 80a84b
	if (compare_config.hide_kabi) {
Packit Service 80a84b
		obj_hide_kabi(root1, compare_config.hide_kabi_new);
Packit Service 80a84b
		obj_hide_kabi(root2, compare_config.hide_kabi_new);
Packit Service 80a84b
	}
Packit Service 80a84b
Packit Service 80a84b
	if (compare_config.debug && !follow) {
Packit Service 80a84b
		obj_debug_tree(root1);
Packit Service 80a84b
		obj_debug_tree(root2);
Packit Service 80a84b
	}
Packit Service 80a84b
Packit Service 80a84b
	if (follow) {
Packit Service 80a84b
		stream = fopen("/dev/null", "w");
Packit Service 80a84b
		if (stream == NULL)
Packit Service 80a84b
			fail("Unable to open /dev/null: %s\n", strerror(errno));
Packit Service 80a84b
	} else {
Packit Service 80a84b
		stream = open_memstream(&s, &sz);
Packit Service 80a84b
	}
Packit Service 80a84b
	tmp = compare_tree(root1, root2, stream);
Packit Service 80a84b
Packit Service 80a84b
	if (tmp != COMP_SAME) {
Packit Service 80a84b
		if (!follow) {
Packit Service 80a84b
			printf("Changes detected in: %s\n", filename);
Packit Service 80a84b
			fflush(stream);
Packit Service 80a84b
			fputs(s, stdout);
Packit Service 80a84b
			putchar('\n');
Packit Service 80a84b
		}
Packit Service 80a84b
		ret = EXIT_KABI_CHANGE;
Packit Service 80a84b
	}
Packit Service 80a84b
Packit Service 80a84b
	obj_free(root1);
Packit Service 80a84b
	obj_free(root2);
Packit Service 80a84b
	fclose(file1);
Packit Service 80a84b
	fclose(file2);
Packit Service 80a84b
	fclose(stream);
Packit Service 80a84b
	free(s);
Packit Service 80a84b
Packit Service 80a84b
	return ret;
Packit Service 80a84b
Packit Service 80a84b
}
Packit Service 80a84b
Packit Service 80a84b
static walk_rv_t compare_files_cb(char *kabi_path, void *arg)
Packit Service 80a84b
{
Packit Service 80a84b
	compare_config_t *conf = (compare_config_t *)arg;
Packit Service 80a84b
	char *filename;
Packit Service 80a84b
Packit Service 80a84b
	if (compare_config.skip_duplicate && is_duplicate(kabi_path))
Packit Service 80a84b
		return WALK_CONT;
Packit Service 80a84b
Packit Service 80a84b
	/* If conf->*_dir contains slashes, skip them */
Packit Service 80a84b
	filename = kabi_path + strlen(conf->old_dir);
Packit Service 80a84b
	while (*filename == '/')
Packit Service 80a84b
		filename++;
Packit Service 80a84b
Packit Service 80a84b
	free_files();
Packit Service 80a84b
	if (compare_two_files(filename, NULL, false))
Packit Service 80a84b
		conf->ret = EXIT_KABI_CHANGE;
Packit Service 80a84b
Packit Service 80a84b
	return WALK_CONT;
Packit Service 80a84b
}
Packit Service 80a84b
Packit Service 80a84b
#define COMPARE_NO_OPT(name) \
Packit Service 80a84b
	{"no-"#name, no_argument, &compare_config.no_##name, 1}
Packit Service 80a84b
Packit Service 80a84b
/*
Packit Service 80a84b
 * Performs the compare command
Packit Service 80a84b
 */
Packit Service 80a84b
int compare(int argc, char **argv)
Packit Service 80a84b
{
Packit Service 80a84b
	int opt, opt_index;
Packit Service 80a84b
	char *old_dir, *new_dir;
Packit Service 80a84b
	struct stat sb1, sb2;
Packit Service 80a84b
	struct option loptions[] = {
Packit Service 80a84b
		{"debug", no_argument, 0, 'd'},
Packit Service 80a84b
		{"hide-kabi", no_argument, 0, 'k'},
Packit Service 80a84b
		{"hide-kabi-new", no_argument, 0, 'n'},
Packit Service 80a84b
		{"help", no_argument, 0, 'h'},
Packit Service 80a84b
		{"skip-duplicate", no_argument, 0, 's'},
Packit Service 80a84b
		{"follow", no_argument, &compare_config.follow, 1},
Packit Service 80a84b
		{"no-offset", no_argument, &display_options.no_offset, 1},
Packit Service 80a84b
		COMPARE_NO_OPT(replaced),
Packit Service 80a84b
		COMPARE_NO_OPT(shifted),
Packit Service 80a84b
		COMPARE_NO_OPT(inserted),
Packit Service 80a84b
		COMPARE_NO_OPT(deleted),
Packit Service 80a84b
		COMPARE_NO_OPT(added),
Packit Service 80a84b
		COMPARE_NO_OPT(removed),
Packit Service 80a84b
		{"no-moved-files", no_argument,
Packit Service 80a84b
		 &compare_config.no_moved_files, 1},
Packit Service 80a84b
		{0, 0, 0, 0}
Packit Service 80a84b
	};
Packit Service 80a84b
Packit Service 80a84b
	memset(&display_options, 0, sizeof(display_options));
Packit Service 80a84b
Packit Service 80a84b
	while ((opt = getopt_long(argc, argv, "dknhs",
Packit Service 80a84b
				  loptions, &opt_index)) != -1) {
Packit Service 80a84b
		switch (opt) {
Packit Service 80a84b
		case 0:
Packit Service 80a84b
			break;
Packit Service 80a84b
		case 'd':
Packit Service 80a84b
			compare_config.debug = true;
Packit Service 80a84b
			break;
Packit Service 80a84b
		case 'n':
Packit Service 80a84b
			compare_config.hide_kabi_new = true;
Packit Service 80a84b
			/* fall through */
Packit Service 80a84b
		case 'k':
Packit Service 80a84b
			compare_config.hide_kabi = true;
Packit Service 80a84b
			break;
Packit Service 80a84b
		case 's':
Packit Service 80a84b
			compare_config.skip_duplicate = true;
Packit Service 80a84b
			break;
Packit Service 80a84b
		case 'h':
Packit Service 80a84b
		default:
Packit Service 80a84b
			compare_usage();
Packit Service 80a84b
		}
Packit Service 80a84b
	}
Packit Service 80a84b
Packit Service 80a84b
	if (argc < optind + 2) {
Packit Service 80a84b
		printf("Wrong number of argument\n");
Packit Service 80a84b
		compare_usage();
Packit Service 80a84b
	}
Packit Service 80a84b
Packit Service 80a84b
	old_dir = compare_config.old_dir = argv[optind++];
Packit Service 80a84b
	new_dir = compare_config.new_dir = argv[optind++];
Packit Service 80a84b
Packit Service 80a84b
	if ((stat(old_dir, &sb1) == -1) || (stat(new_dir, &sb2) == -1))
Packit Service 80a84b
		fail("stat failed: %s\n", strerror(errno));
Packit Service 80a84b
Packit Service 80a84b
	if (S_ISREG(sb1.st_mode) && S_ISREG(sb2.st_mode)) {
Packit Service 80a84b
		char *oldname = basename(old_dir);
Packit Service 80a84b
		char *newname = basename(new_dir);
Packit Service 80a84b
Packit Service 80a84b
		if (optind != argc) {
Packit Service 80a84b
			printf("Too many arguments\n");
Packit Service 80a84b
			compare_usage();
Packit Service 80a84b
		}
Packit Service 80a84b
		compare_config.old_dir = dirname(old_dir);
Packit Service 80a84b
		compare_config.new_dir = dirname(new_dir);
Packit Service 80a84b
Packit Service 80a84b
		return compare_two_files(oldname, newname, false);
Packit Service 80a84b
	}
Packit Service 80a84b
Packit Service 80a84b
	if (!S_ISDIR(sb1.st_mode) || !S_ISDIR(sb2.st_mode)) {
Packit Service 80a84b
		printf("Compare takes two directories or two regular"
Packit Service 80a84b
		       " files as arguments\n");
Packit Service 80a84b
		compare_usage();
Packit Service 80a84b
	}
Packit Service 80a84b
Packit Service 80a84b
	if (optind == argc) {
Packit Service 80a84b
		walk_dir(old_dir, false, compare_files_cb, &compare_config);
Packit Service 80a84b
Packit Service 80a84b
		return compare_config.ret;
Packit Service 80a84b
	}
Packit Service 80a84b
Packit Service 80a84b
	while (optind < argc) {
Packit Service 80a84b
		char *path, *filename;
Packit Service 80a84b
Packit Service 80a84b
		filename = compare_config.filename =  argv[optind++];
Packit Service 80a84b
		safe_asprintf(&path, "%s/%s", old_dir, filename);
Packit Service 80a84b
Packit Service 80a84b
		if (stat(path, &sb1) == -1) {
Packit Service 80a84b
			if (errno == ENOENT)
Packit Service 80a84b
				fail("file does not exist: %s\n", path);
Packit Service 80a84b
			fail("stat failed: %s\n", strerror(errno));
Packit Service 80a84b
		}
Packit Service 80a84b
Packit Service 80a84b
		if (!S_ISREG(sb1.st_mode)) {
Packit Service 80a84b
			printf("Compare third argument must be a regular file");
Packit Service 80a84b
			compare_usage();
Packit Service 80a84b
		}
Packit Service 80a84b
		free(path);
Packit Service 80a84b
Packit Service 80a84b
		if (compare_two_files(filename, NULL, false))
Packit Service 80a84b
			compare_config.ret = EXIT_KABI_CHANGE;
Packit Service 80a84b
	}
Packit Service 80a84b
Packit Service 80a84b
	return compare_config.ret;
Packit Service 80a84b
}