|
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 |
}
|