Blame compare.c

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