/*
	Copyright(C) 2016, Red Hat, Inc., Stanislav Kozina

	This program is free software: you can redistribute it and/or modify
	it under the terms of the GNU General Public License as published by
	the Free Software Foundation, either version 3 of the License, or
	(at your option) any later version.

	This program is distributed in the hope that it will be useful,
	but WITHOUT ANY WARRANTY; without even the implied warranty of
	MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
	GNU General Public License for more details.

	You should have received a copy of the GNU General Public License
	along with this program.  If not, see <http://www.gnu.org/licenses/>.
*/

/*
 * This file contains the code which generates the kabi information for the
 * given build of the Linux kernel.
 */

#include <dwarf.h>
#include <inttypes.h>
#include <ctype.h>
#include <libelf.h>
#include <fcntl.h>
#include <stdio.h>
#include <string.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <unistd.h>
#include <stdlib.h>
#include <errno.h>
#include <assert.h>
#include <getopt.h>
#include <limits.h>

#include <elfutils/libdw.h>
#include <elfutils/libdwfl.h>
#include <elfutils/known-dwarf.h>

#include "main.h"
#include "utils.h"
#include "generate.h"
#include "ksymtab.h"
#include "stack.h"
#include "hash.h"
#include "objects.h"
#include "list.h"

#define	EMPTY_NAME	"(NULL)"
#define PROCESSED_SIZE 1024
/*
  DB size is number of hash buckets, do not have to be exact,
  but since now we have ~20K records, make it this
*/
#define DB_SIZE (20 * 1024)
#define INITIAL_RECORD_SIZE 512

/*
 * Dwarf5 spec, 7.5.4 Attribute Encodings
 * libdw doesn't support it yet
 */
#ifndef DW_AT_alignment
#define DW_AT_alignment 0x88
#endif

struct set;
struct record_db;

typedef struct {
	char *kernel_dir; /* Path to  the kernel modules to process */
	char *kabi_dir; /* Where to put the output */
	struct ksymtab *symbols; /* List of symbols to generate */
	size_t symbol_cnt;
	struct record_db *db;
	bool rhel_tree;
	bool verbose;
	bool gen_extra;
} generate_config_t;

struct cu_ctx {
	generate_config_t *conf;
	Dwarf_Die *cu_die;
	stack_t *stack; /* Current stack of symbol we're parsing */
	struct set *processed; /* Set of processed types for this CU */
};

struct file_ctx {
	generate_config_t *conf;
	struct ksymtab *ksymtab; /* ksymtab of the current kernel module */
};

struct dwarf_type {
	unsigned int dwarf_tag;
	char *prefix;
} known_dwarf_types[] = {
	{ DW_TAG_subprogram, FUNC_FILE },
	{ DW_TAG_typedef, TYPEDEF_FILE },
	{ DW_TAG_variable, VAR_FILE },
	{ DW_TAG_enumeration_type, ENUM_FILE },
	{ DW_TAG_structure_type, STRUCT_FILE },
	{ DW_TAG_union_type, UNION_FILE },
	{ 0, NULL }
};


/*
 * Structure of the database record:
 *
 * key: record key, usually includes path the file, where the type is
 *      defined (may include pseudo path, like <declaration>);
 *
 * version: type's version, used when we need to add another type of the same
 *	    name. It may happend, for example, when because of defines the same
 *          structure has changed for different compilation units.
 *
 *          It is not for the case, when the same structure defined in
 *	    different files -- it will have different keys, since it includes
 *	    the path;
 *
 * ref_count: reference counter, needed since the ownership is shared with the
 *            internal database;
 *
 * base_file: base part of the key (without version), used to generate the
 *            unique key for the new version;
 *
 * cu: compilation unit, where the type for the record defined;
 *
 * origin: "File <file>:<line>" string, describing the source, where the type
 *         for the record defined;
 *
 * stack: stack of types to reach this one.
 *         Ex.: on the toplevel
 *              struct A {
 *                        struct B fieldA;
 *              }
 *         in another file:
 *              struct B {
 *                        basetype fieldB;
 *              }
 *         the "struct B" description will contain key of the "struct A"
 *         description record in the stack;
 *
 * obj: pointer to the abstract type object, representing the toplevel type of
 *      the record.
 *
 * link: name of weak link alisas for the weak aliases.
 *
 * free: type specific function to free the record
 *       (there are normal, weak and assembly records).
 *
 * dump: type specific function for record output.
 *
 * dependents: objects that reference this record.
 */
struct record {
	char *key;
	int version;
	int ref_count;
	char *base_file;
	char *cu;
	char *origin;
	stack_t *stack;
	obj_t *obj;
	char *link;
	void (*free)(struct record *);
	void (*dump)(struct record *, FILE *);

	struct list dependents;
};

void record_update_dependents(struct record *record)
{
	struct list_node *iter;

	LIST_FOR_EACH(&record->dependents, iter) {
		obj_t *obj = list_node_data(iter);

		free(obj->base_type);
		obj->base_type = safe_strdup(record->key);
	}
}

static const bool is_builtin(Dwarf_Die *die)
{
	char *fname;
	const char *path = dwarf_decl_file(die);

	if (path == NULL)
		return true;

	fname = basename(path);
	assert (fname != NULL);
	if (strcmp(fname, "<built-in>") == 0)
		return true;

	return false;
}

static const char *get_die_name(Dwarf_Die *die)
{
	if (dwarf_hasattr(die, DW_AT_name))
		return dwarf_diename(die);
	else
		return EMPTY_NAME;
}

/*
 * Check if given DIE has DW_AT_declaration attribute.
 * That indicates that the symbol is just a declaration, not full definition.
 */
static bool is_declaration(Dwarf_Die *die)
{
	Dwarf_Attribute attr;

	if (!dwarf_hasattr(die, DW_AT_declaration))
		return false;
	(void) dwarf_attr(die, DW_AT_declaration, &attr);
	if (!dwarf_hasform(&attr, DW_FORM_flag_present))
		return false;
	return true;
}

static char *get_file_replace_path;

static char *_get_file(Dwarf_Die *cu_die, Dwarf_Die *die)
{
	const char *filename;
	char *ret;

	filename = dwarf_decl_file(die);

	if (get_file_replace_path) {
		int len = strlen(get_file_replace_path);

		if (strncmp(filename, get_file_replace_path, len) == 0) {
			filename = filename + len;
			while (*filename == '/')
				filename++;
		}
	}


	ret = safe_strdup(filename);
	path_normalize(ret);

	return ret;
}

static char *get_file(Dwarf_Die *cu_die, Dwarf_Die *die)
{
	Dwarf_Attribute attr;
	Dwarf_Die spec_die;

	/*
	 * Handle types built-in in C compiler. These are for example the
	 * variable argument list which is defined as * struct __va_list_tag.
	 */
	if (is_builtin(die))
		return safe_strdup(BUILTIN_PATH);

	if (dwarf_hasattr(die, DW_AT_decl_file))
		return _get_file(cu_die, die);

	if ((dwarf_attr(die, DW_AT_specification, &attr) == NULL) ||
	    (dwarf_formref_die(&attr, &spec_die) == NULL)) {
		fail("DIE missing file information: %s\n",
		     dwarf_diename(die));
	}

	return _get_file(cu_die, &spec_die);
}

static long get_line(Dwarf_Die *cu_die, Dwarf_Die *die)
{
	Dwarf_Attribute attr;
	Dwarf_Word line;
	Dwarf_Die spec_die;

	if (is_builtin(die))
		return 0;

	if (dwarf_attr(die, DW_AT_decl_line, &attr) != NULL) {
		dwarf_formudata(&attr, &line);
		return line;
	}

	if ((dwarf_attr(die, DW_AT_specification, &attr) == NULL) ||
	    (dwarf_formref_die(&attr, &spec_die) == NULL)) {
		fail("DIE missing line information: %s\n",
		     dwarf_diename(die));
	}

	return get_line(cu_die, &spec_die);
}

static obj_t *print_die(struct cu_ctx *, struct record *, Dwarf_Die *);

static const char *dwarf_tag_string(unsigned int tag)
{
	switch (tag) {
#define	DWARF_ONE_KNOWN_DW_TAG(NAME, CODE) case CODE: return #NAME;
		DWARF_ALL_KNOWN_DW_TAG
#undef DWARF_ONE_KNOWN_DW_TAG
	default:
		return NULL;
	}
}

static char *get_file_prefix(unsigned int dwarf_tag)
{
	struct dwarf_type *current;

	for (current = known_dwarf_types; current->prefix != NULL; current++) {
		if (dwarf_tag == current->dwarf_tag)
			break;
	}

	return current->prefix;
}

static char *get_symbol_file(Dwarf_Die *die, Dwarf_Die *cu_die)
{
	const char *name = dwarf_diename(die);
	unsigned int tag = dwarf_tag(die);
	char *file_prefix;
	char *file_name = NULL;

	file_prefix = get_file_prefix(tag);
	if (file_prefix == NULL) {
		/* No need to redirect output for this type */
		return NULL;
	}

	/*
	 * DW_AT_declaration don't have DW_AT_decl_file.
	 * Pretend like it's in other, non existent file.
	 */
	if (is_declaration(die)) {
		safe_asprintf(&file_name, DECLARATION_PATH "/%s%s.txt",
		    file_prefix, name);

		return file_name;
	}

	/*
	 * Following types can be anonymous, eg. used directly as variable type
	 * in the declaration. We don't create new file for them if that's
	 * the case, embed them directly in the current file.
	 * Note that anonymous unions can also be embedded directly in the
	 * structure!
	 */
	switch (tag) {
	case DW_TAG_enumeration_type:
	case DW_TAG_structure_type:
	case DW_TAG_union_type:
		if (name == NULL)
			return NULL;
		break;
	}

	/* We don't expect our name to be empty now */
	assert(name != NULL);

	safe_asprintf(&file_name, "%s%s.txt", file_prefix, name);

	return file_name;
}

/*
 * Check if given DIE is external.
 * It can has DW_AT_external attribute itself,
 * or in case of reference to the specification,
 * the specification DIE can be exported.
 */
static int is_external(Dwarf_Die *die)
{
	Dwarf_Attribute attr;
	Dwarf_Die spec_die;

	if (dwarf_hasattr(die, DW_AT_external)) {
		dwarf_attr(die, DW_AT_external, &attr);
		if (!dwarf_hasform(&attr, DW_FORM_flag_present))
			return false;
		return true;
	}

	if (dwarf_attr(die, DW_AT_specification, &attr) == NULL)
		return false;

	if (dwarf_formref_die(&attr, &spec_die) == NULL)
		fail("dwarf_formref_die() failed for %s\n",
		     dwarf_diename(die));
	return is_external(&spec_die);
}

static obj_t *die_read_alignment(Dwarf_Die *die, obj_t *obj)
{
	Dwarf_Attribute attr;
	Dwarf_Word value;

	if (dwarf_attr(die, DW_AT_alignment, &attr) == NULL)
		goto out;

	dwarf_formudata(&attr, &value);
	obj->alignment = value;
out:
	return obj;
}

struct set *set_init(size_t size)
{
	struct hash *h;

	h = hash_new(size, free);
	if (h == NULL)
		fail("Cannot create hash");

	return (struct set *)h;
}

static void set_add(struct set *set, const char *key)
{
	char *storage;
	struct hash *h = (struct hash *)set;

	storage = safe_strdup(key);
	hash_add(h, storage, storage);
}

static bool set_contains(struct set *set, const char *key)
{
	void *v;
	struct hash *h = (struct hash *)set;

	v = hash_find(h, key);
	return v != NULL;
}

static void set_free(struct set *set)
{
	struct hash *h = (struct hash *)set;

	hash_free(h);
}

static struct record *record_alloc(void)
{
	struct record *rec;

	rec = safe_zmalloc(sizeof(*rec));
	return rec;
}

static void record_free_regular(struct record *rec)
{
	void *data;
	struct list_node *iter;

	free(rec->base_file);
	free(rec->origin);
	if (rec->cu)
		free(rec->cu);

	while ((data = stack_pop(rec->stack)) != NULL)
		free(data);
	stack_destroy(rec->stack);

	LIST_FOR_EACH(&rec->dependents, iter) {
		obj_t *o = list_node_data(iter);

		o->depend_rec_node = NULL;
	}
	list_clear(&rec->dependents);

	obj_free(rec->obj);
}

static void record_free_weak(struct record *rec)
{
	free(rec->link);
}

static void record_free(struct record *rec)
{
	free(rec->key);

	if (rec->free)
		rec->free(rec);
	free(rec);
}

static void record_put(struct record *rec)
{
	assert(rec->ref_count > 0);

	if (--rec->ref_count == 0)
		record_free(rec);
}

static void record_get(struct record *rec)
{
	rec->ref_count++;
}

static void record_dump_regular(struct record *rec, FILE *f);

static struct record *record_new_regular(const char *key)
{
	struct record *rec;

	rec = record_alloc();
	rec->key = safe_strdup(key);
	rec->stack = stack_init();
	rec->free = record_free_regular;
	rec->dump = record_dump_regular;
	list_init(&rec->dependents, NULL);
	record_get(rec);
	return rec;
}

static void record_dump_assembly(struct record *rec, FILE *f);

static struct record *record_new_assembly(const char *key)
{
	struct record *rec;

	rec = record_alloc();
	rec->key = safe_strdup(key);

	/*
	 * The symbol not necessary belongs to an assembly function,
	 * it is actually "no definition found in the debug information",
	 * but the main goal is to find the assembly ones.
	 */

	rec->free = NULL;
	rec->dump = record_dump_assembly;

	record_get(rec);

	return rec;
}

static void record_dump_weak(struct record *rec, FILE *f);

static struct record *record_new_weak(const char *key, const char *link)
{
	struct record *rec;

	rec = record_alloc();
	rec->key = safe_strdup(key);
	rec->link = safe_strdup(link);

	rec->free = record_free_weak;
	rec->dump = record_dump_weak;

	record_get(rec);

	return rec;
}

static obj_t *record_obj(struct record *rec)
{
	return rec->obj;
}

static obj_t *record_obj_exchange(struct record *rec, obj_t *o)
{
	obj_t *old;

	old = rec->obj;
	rec->obj = o;
	return old;
}

static const char *record_origin(struct record *rec)
{
	return rec->origin;
}

static void copy_stack_cb(void *data, void *arg)
{
	char *symbol = (char *)data;
	struct record *fp = (struct record *)arg;
	char *copy;

	copy = safe_strdup(symbol);
	stack_push(fp->stack, copy);
}

static void record_add_stack(struct record *rec, stack_t *stack)
{
	walk_stack_backward(stack, copy_stack_cb, rec);
}

static void record_add_cu(struct record *rec, Dwarf_Die *cu_die)
{
	const char *name;

	if (cu_die == NULL)
		return;

	name = dwarf_diename(cu_die);
	safe_asprintf(&rec->cu, "CU: \"%s\"\n", name);
}

static void record_add_origin(struct record *rec,
			      Dwarf_Die *cu_die,
			      Dwarf_Die *die)
{
	char *dec_file;
	long dec_line;

	dec_file = get_file(cu_die, die);
	dec_line = get_line(cu_die, die);

	safe_asprintf(&rec->origin, "File: %s:%lu\n", dec_file, dec_line);
	free(dec_file);
}

static struct record *record_start(struct cu_ctx *ctx,
				   Dwarf_Die *die,
				   char *key)
{
	struct record *rec = NULL;
	generate_config_t *conf = ctx->conf;
	Dwarf_Die *cu_die = ctx->cu_die;

	/*
	 * Don't try to reenter a file that we have seen already for
	 * this CU.
	 * Note that this is just a pure optimization, the same file
	 * (type) in the same CU must be identical.
	 * But this is major optimization, without it a single
	 * re-generation of a top file would require a full regeneration
	 * of its full tree, thus the difference in speed is many orders
	 * of magnitude!
	 */
	if (set_contains(ctx->processed, key))
		goto done;

	set_add(ctx->processed, key);

	if (is_declaration(die)) {
		if (conf->verbose)
			printf("WARNING: Skipping following file as we "
			       "have only declaration: %s\n", key);
		goto done;
	}

	if (conf->verbose)
		printf("Generating %s\n", key);

	rec = record_new_regular(key);

	if (conf->gen_extra)
		record_add_cu(rec, cu_die);
	record_add_origin(rec, cu_die, die);
	record_add_stack(rec, ctx->stack);
done:
	return rec;
}

static void record_set_version(struct record *rec, int version)
{
	char *base_file = rec->base_file;
	char *key = NULL;

	if (version == 0)
		return;

	if (rec->version == 0) {
		base_file = safe_strdup(rec->key);
		/* Remove .txt ending */
		base_file[strlen(base_file) - 4] = '\0';
		rec->base_file = base_file;
	}
	rec->version = version;
	safe_asprintf(&key, "%s-%i.txt", base_file, rec->version);
	free(rec->key);
	rec->key = key;
}

static void record_close(struct record *rec, obj_t *obj)
{
	obj_fill_parent(obj);
	rec->obj = obj;
}

static void record_stack_dump_and_clear(struct record *rec, FILE *f)
{
	char *data = stack_pop(rec->stack);

	if (data == NULL)
		return;

	fprintf(f, "Stack:\n");
	do {
		fprintf(f, "-> \"%s\"\n", data);
		free(data);
	} while ((data = stack_pop(rec->stack)) != NULL);
}

static void record_dump_regular(struct record *rec, FILE *f)
{
	int rc;

	fprintf(f, FILEFMT_VERSION_STRING);
	if (rec->cu != NULL) {
		rc = fputs(rec->cu, f);
		if (rc == EOF)
			fail("Could not put CU name");
	}
	rc = fputs(rec->origin, f);
	if (rc == EOF)
		fail("Could not put origin");

	record_stack_dump_and_clear(rec, f);

	fprintf(f, "Symbol:\n");
	if (rec->obj->alignment != 0)
		fprintf(f, "Alignment %u\n", rec->obj->alignment);

	obj_dump(rec->obj, f);
}

static void record_dump_assembly(struct record *rec, FILE *f)
{
	char *name = filenametosymbol(rec->key);
	int rc;

	rc = fprintf(f, FILEFMT_VERSION_STRING "Symbol:\nassembly %s\n", name);
	free(name);
	if (rc < 0)
		fail("Could not put assembly\n");
}

static void record_dump_weak(struct record *rec, FILE *f)
{
	char *name = filenametosymbol(rec->key);
	int rc;

	rc = fprintf(f, FILEFMT_VERSION_STRING "Symbol:\nweak %s -> %s\n",
		     name, rec->link);
	free(name);
	if (rc < 0)
		fail("Could not put weak link\n");
}

static void record_dump(struct record *rec, const char *dir)
{
	char path[PATH_MAX];
	FILE *f;
	char *slash;

	snprintf(path, sizeof(path), "%s/%s", dir, rec->key);

	slash = strrchr(path, '/');
	assert(slash != NULL);
	*slash = '\0';
	rec_mkdir(path);
	*slash = '/';

	f = fopen(path, "w");
	if (f == NULL)
		fail("Cannot create record file '%s': %m", path);

	rec->dump(rec, f);

	fclose(f);
}

static void list_record_free(void *value)
{
	struct record *rec = value;

	record_put(rec);
}

/*
 * merge rec_src to the record rec_dst
 */
static bool record_merge(struct record *rec_dst,
			 struct record *rec_src,
			 bool merge_decl)
{
	const char *s1;
	const char *s2;
	obj_t *o1;
	obj_t *o2;
	obj_t *o;

	s1 = record_origin(rec_dst);
	s2 = record_origin(rec_src);

	if (!safe_streq(s1, s2))
		return false;

	o1 = record_obj(rec_dst);
	o2 = record_obj(rec_src);

	o = obj_merge(o1, o2, merge_decl);
	if (o == NULL)
		return false;

	obj_fill_parent(o);
	o = record_obj_exchange(rec_dst, o);
	obj_free(o);

	return true;
}

static char *record_db_add(struct record_db *db, struct record *rec)
{
	/*
	 * Now we need to put the new type record we've just generated
	 * to the db.
	 *
	 * The problem stopping us from merging every record possible is that
	 * multiple records can't be merged if it is not possible to create
	 * groups in which they should be merged. This is not a problem if all
	 * the records are able to be merged together, but when there is one or
	 * more incompatible merging then merging them aggressively could group
	 * them in the wrong way. And because the right way to group them can't
	 * be decided, it is better not to group and subsequently merge them at
	 * all.
	 *
	 * Meaning that at this stage, when we don't know all the records, we
	 * can't decide if all records can be merged into one, and so the only
	 * records we can merge are those that are completely same.
	 *
	 * In case they aren't same we store them as another node of the list.
	 * We also change the name of the new record, so that two reffile obj_t
	 * referencing records that we couldn't merge wouldn't get merged.
	 */

	struct hash *hash = (struct hash *)db;
	struct record *tmp_rec;
	struct list *list;
	struct list_node *iter;

	list = hash_find(hash, rec->key);
	if (list == NULL) {
		list = list_new(list_record_free);

		hash_add(hash, rec->key, list);
	}

	LIST_FOR_EACH(list, iter) {
		tmp_rec = list_node_data(iter);

		if (record_merge(tmp_rec, rec, NO_MERGE_DECL)) {
			list_concat(&tmp_rec->dependents, &rec->dependents);
			return safe_strdup(tmp_rec->key);
		}
	}

	record_get(rec);
	record_set_version(rec, list->len);
	list_add(list, rec);

	return safe_strdup(rec->key);
}

static void hash_list_free(void *value)
{
	struct list *list = value;

	list_free(list);
}

static struct record_db *record_db_init(void)
{
	struct hash *db;

	db = hash_new(DB_SIZE, hash_list_free);
	if (db == NULL)
		fail("Could not create db (hash)\n");

	return (struct record_db *)db;
}

static void record_db_dump(struct record_db *_db, char *dir)
{
	struct hash_iter iter;
	const void *v;
	struct hash *db = (struct hash *)_db;

	hash_iter_init(db, &iter);
	while (hash_iter_next(&iter, NULL, &v)) {
		struct list *list = (struct list *)v;
		struct list_node *iter;

		LIST_FOR_EACH(list, iter) {
			struct record *rec = list_node_data(iter);

			record_dump(rec, dir);
		}
	}
}

static void record_db_free(struct record_db *_db)
{
	struct hash *db = (struct hash *)_db;

	hash_free(db);
}

static obj_t *print_die_type(struct cu_ctx *ctx,
			     struct record *rec,
			     Dwarf_Die *die)
{
	Dwarf_Die type_die;
	Dwarf_Attribute attr;

	if (!dwarf_hasattr(die, DW_AT_type))
		return obj_basetype_new(safe_strdup("void"));

	(void) dwarf_attr(die, DW_AT_type, &attr);
	if (dwarf_formref_die(&attr, &type_die) == NULL)
		fail("dwarf_formref_die() failed for %s\n",
		    dwarf_diename(die));

	/* Print the type of the die */
	return print_die(ctx, rec, &type_die);
}

static obj_t *print_die_struct_member(struct cu_ctx *ctx,
				      struct record *rec,
				      Dwarf_Die *die,
				      const char *name)
{
	Dwarf_Attribute attr;
	Dwarf_Word value;
	obj_t *type;
	obj_t *obj;

	if (dwarf_attr(die, DW_AT_data_member_location, &attr) == NULL)
		fail("Offset of member %s missing!\n", name);

	(void) dwarf_formudata(&attr, &value);

	type = print_die_type(ctx, rec, die);
	obj = obj_struct_member_new_add(safe_strdup(name), type);
	obj->offset = value;

	if (dwarf_hasattr(die, DW_AT_bit_offset)) {
		Dwarf_Word offset, size;

		if (!dwarf_hasattr(die, DW_AT_bit_size))
			fail("Missing expected bit size attribute in %s!\n",
			    name);

		if (dwarf_attr(die, DW_AT_bit_offset, &attr) == NULL)
			fail("Bit offset of member %s missing!\n", name);
		(void) dwarf_formudata(&attr, &offset);
		if (dwarf_attr(die, DW_AT_bit_size, &attr) == NULL)
			fail("Bit size of member %s missing!\n", name);
		(void) dwarf_formudata(&attr, &size);

		obj->is_bitfield = 1;
		obj->first_bit = offset;
		obj->last_bit = offset + size - 1;
	}
	die_read_alignment(die, obj);
	return obj;
}

static obj_t *print_die_structure(struct cu_ctx *ctx,
				  struct record *rec,
				  Dwarf_Die *die)
{
	const char *name = get_die_name(die);
	unsigned int tag;
	obj_list_head_t *members = NULL;
	obj_t *obj;
	obj_t *member;
	Dwarf_Die child_die;

	obj = obj_struct_new(safe_strdup(name));

	if (!dwarf_haschildren(die))
		goto done;

	dwarf_child(die, &child_die);
	do {
		Dwarf_Die *die = &child_die;

		name = get_die_name(die);
		tag = dwarf_tag(die);
		if (tag != DW_TAG_member)
			fail("Unexpected tag for structure type children: "
			    "%s\n", dwarf_tag_string(tag));

		member = print_die_struct_member(ctx, rec, die, name);
		if (members == NULL)
			members = obj_list_head_new(member);
		else
			obj_list_add(members, member);

	} while (dwarf_siblingof(&child_die, &child_die) == 0);

	obj->member_list = members;
done:
	return obj;
}

static obj_t *print_die_enumerator(struct cu_ctx *ctx,
				   struct record *rec,
				   Dwarf_Die *die,
				   const char *name)
{
	Dwarf_Attribute attr;
	Dwarf_Word value;
	obj_t *obj;

	if (dwarf_attr(die, DW_AT_const_value, &attr) == NULL)
		fail("Value of enumerator %s missing!\n", name);

	(void) dwarf_formudata(&attr, &value);

	obj = obj_constant_new(safe_strdup(name));
	obj->constant = value;

	return obj;
}

static obj_t *print_die_enumeration(struct cu_ctx *ctx,
				    struct record *rec,
				    Dwarf_Die *die) {
	const char *name = get_die_name(die);
	obj_list_head_t *members = NULL;
	obj_t *member;
	obj_t *obj;
	Dwarf_Die child_die;

	obj = obj_enum_new(safe_strdup(name));

	if (!dwarf_haschildren(die))
		goto done;

	dwarf_child(die, &child_die);
	do {
		Dwarf_Die *die = &child_die;

		name = get_die_name(die);
		member = print_die_enumerator(ctx, rec, die, name);
		if (members == NULL)
			members = obj_list_head_new(member);
		else
			obj_list_add(members, member);
	} while (dwarf_siblingof(&child_die, &child_die) == 0);

	members->object = obj;
	obj->member_list = members;
done:
	return obj;
}

static obj_t *print_die_union(struct cu_ctx *ctx,
			      struct record *rec,
			      Dwarf_Die *die)
{
	const char *name = get_die_name(die);
	unsigned int tag;
	obj_list_head_t *members = NULL;
	obj_t *member;
	obj_t *type;
	obj_t *obj;
	Dwarf_Die child_die;

	obj = obj_union_new(safe_strdup(name));

	if (!dwarf_haschildren(die))
		goto done;

	dwarf_child(die, &child_die);
	do {
		Dwarf_Die *die = &child_die;

		name = get_die_name(die);
		tag = dwarf_tag(die);
		if (tag != DW_TAG_member)
			fail("Unexpected tag for union type children: %s\n",
			    dwarf_tag_string(tag));

		type = print_die_type(ctx, rec, die);
		member = obj_var_new_add(safe_strdup(name), type);

		if (members == NULL)
			members = obj_list_head_new(member);
		else
			obj_list_add(members, member);

	} while (dwarf_siblingof(&child_die, &child_die) == 0);

	members->object = obj;
	obj->member_list = members;
done:
	die_read_alignment(die, obj);
	return obj;
}

static obj_list_head_t *print_subprogram_arguments(struct cu_ctx *ctx,
						   struct record *rec,
						   Dwarf_Die *die)
{
	Dwarf_Die child_die;
	obj_t *arg_type;
	obj_t *arg;
	obj_list_head_t *arg_list = NULL;

	if (!dwarf_haschildren(die))
		return NULL;

	/* Grab the first argument */
	dwarf_child(die, &child_die);

	/* Walk all arguments until we run into the function body */
	while ((dwarf_tag(&child_die) == DW_TAG_formal_parameter) ||
	    (dwarf_tag(&child_die) == DW_TAG_unspecified_parameters)) {
		const char *name = get_die_name(&child_die);

		if (dwarf_tag(&child_die) != DW_TAG_unspecified_parameters)
			arg_type = print_die_type(ctx, rec, &child_die);
		else
			arg_type = obj_basetype_new(safe_strdup("..."));

		arg = obj_var_new_add(safe_strdup(name), arg_type);
		if (arg_list == NULL)
			arg_list = obj_list_head_new(arg);
		else
			obj_list_add(arg_list, arg);

		if (dwarf_siblingof(&child_die, &child_die) != 0)
			break;
	}
	return arg_list;
}

static obj_t *print_die_subprogram(struct cu_ctx *ctx,
				   struct record *rec,
				   Dwarf_Die *die)
{
	char *name;
	obj_list_head_t *arg_list;
	obj_t *ret_type;
	obj_t *obj;

	arg_list = print_subprogram_arguments(ctx, rec, die);
	ret_type = print_die_type(ctx, rec, die);
	name = safe_strdup(get_die_name(die));

	obj = obj_func_new_add(name, ret_type);
	if (arg_list)
		arg_list->object = obj;
	obj->member_list = arg_list;

	return obj;
}

static obj_t *_print_die_array_type(struct cu_ctx *ctx,
				    struct record *rec,
				    Dwarf_Die *child,
				    obj_t *base_type)
{
	Dwarf_Die next_child;
	Dwarf_Word value;
	Dwarf_Attribute attr;
	int rc;
	unsigned int tag;
	unsigned long arr_idx;
	obj_t *obj;
	obj_t *sub;

	if (child == NULL)
		return base_type;

	tag = dwarf_tag(child);
	if (tag != DW_TAG_subrange_type)
		fail("Unexpected tag for array type children: %s\n",
		     dwarf_tag_string(tag));

	if (dwarf_hasattr(child, DW_AT_upper_bound)) {
		(void) dwarf_attr(child, DW_AT_upper_bound, &attr);
		(void) dwarf_formudata(&attr, &value);
		/* Get the UPPER bound, so add 1 */
		arr_idx = value + 1;
	} else if (dwarf_hasattr(child, DW_AT_count)) {
		(void) dwarf_attr(child, DW_AT_count, &attr);
		(void) dwarf_formudata(&attr, &value);
		arr_idx = value;
	} else {
		arr_idx = 0;
	}

	rc = dwarf_siblingof(child, &next_child);
	child = rc == 0 ? &next_child : NULL;

	sub = _print_die_array_type(ctx, rec, child, base_type);
	obj = obj_array_new_add(sub);
	obj->index = arr_idx;

	return obj;
}

static obj_t *print_die_array_type(struct cu_ctx *ctx,
				   struct record *rec,
				   Dwarf_Die *die)
{
	Dwarf_Die child;
	obj_t *base_type;

	/* There should be one child of DW_TAG_subrange_type */
	if (!dwarf_haschildren(die))
		fail("Array type missing children!\n");

	base_type = print_die_type(ctx, rec, die);

	/* Grab the child */
	dwarf_child(die, &child);

	return _print_die_array_type(ctx, rec, &child, base_type);
}

static obj_t *print_die_tag(struct cu_ctx *ctx,
			    struct record *rec,
			    Dwarf_Die *die)
{
	unsigned int tag = dwarf_tag(die);
	const char *name = dwarf_diename(die);
	obj_t *obj = NULL;

	if (tag == DW_TAG_invalid)
		fail("DW_TAG_invalid: %s\n", name);

	switch (tag) {
	case DW_TAG_subprogram:
		obj = print_die_subprogram(ctx, rec, die);
		break;
	case DW_TAG_variable:
		obj = print_die_type(ctx, rec, die);
		obj = obj_var_new_add(safe_strdup(name), obj);
		break;
	case DW_TAG_base_type:
		obj = obj_basetype_new(safe_strdup(name));
		break;
	case DW_TAG_pointer_type:
		obj = print_die_type(ctx, rec, die);
		obj = obj_ptr_new_add(obj);
		break;
	case DW_TAG_structure_type:
		obj = print_die_structure(ctx, rec, die);
		break;
	case DW_TAG_enumeration_type:
		obj = print_die_enumeration(ctx, rec, die);
		break;
	case DW_TAG_union_type:
		obj = print_die_union(ctx, rec, die);
		break;
	case DW_TAG_typedef:
		obj = print_die_type(ctx, rec, die);
		obj = obj_typedef_new_add(safe_strdup(name), obj);
		break;
	case DW_TAG_subroutine_type:
		obj = print_die_subprogram(ctx, rec, die);
		break;
	case DW_TAG_volatile_type:
		obj = print_die_type(ctx, rec, die);
		obj = obj_qualifier_new_add(obj);
		obj->base_type = safe_strdup("volatile");
		break;
	case DW_TAG_const_type:
		obj = print_die_type(ctx, rec, die);
		obj = obj_qualifier_new_add(obj);
		obj->base_type = safe_strdup("const");
		break;
	case DW_TAG_array_type:
		obj = print_die_array_type(ctx, rec, die);
		break;
	default: {
		const char *tagname = dwarf_tag_string(tag);
		if (tagname == NULL)
			tagname = "<NO TAG>";

		fail("Unexpected tag for symbol %s: %s\n", name, tagname);
		break;
	}
	}
	obj = die_read_alignment(die, obj);
	return obj;
}

static obj_t *print_die(struct cu_ctx *ctx,
			struct record *parent_file,
			Dwarf_Die *die)
{
	char *file;
	struct record *rec;
	char *old_file;
	obj_t *obj;
	obj_t *ref_obj;
	generate_config_t *conf = ctx->conf;

	/*
	 * Sigh. The type of some fields (eg. struct member as a pointer to
	 * another struct) can be defined by a mere declaration without a full
	 * specification of the type.  In such cases we just print a remote
	 * pointer to the full type and pray it will be printed in a different
	 * occasion.
	 */

	/* Check if we need to redirect output or we have a mere declaration */
	file = get_symbol_file(die, ctx->cu_die);
	if (file == NULL) {
		/* no need for new record, output to the current one */
		assert(parent_file != NULL);
		obj = print_die_tag(ctx, parent_file, die);
		return obj;
	}

	ref_obj = obj_reffile_new();

	/* else handle new record */
	rec = record_start(ctx, die, file);
	if (rec == NULL)
		/* declaration or already processed */
		goto out;

	if (conf->gen_extra)
		stack_push(ctx->stack, safe_strdup(file));
	obj = print_die_tag(ctx, rec, die);
	if (conf->gen_extra)
		free(stack_pop(ctx->stack));

	record_close(rec, obj);

	old_file = file;
	ref_obj->depend_rec_node = list_add(&rec->dependents, ref_obj);
	/* if it creates new version, key/file name can change */
	file = record_db_add(conf->db, rec);
	record_put(rec);
	/* record_db_add() returns allocated string */
	free(old_file);

out:
	ref_obj->base_type = file;
	return ref_obj;
}

/*
 * Validate if this is the symbol we should print.
 * Returns true if should.
 */
static bool is_symbol_valid(struct file_ctx *fctx, Dwarf_Die *die)
{
	const char *name = dwarf_diename(die);
	unsigned int tag = dwarf_tag(die);
	bool result = false;
	generate_config_t *conf = fctx->conf;
	struct ksym *ksym1 = NULL;
	struct ksym *ksym2;

	/* Shortcut, unnamed die cannot be part of whitelist */
	if (name == NULL)
		goto out;

	/* If symbol file was provided, is the symbol on the list? */
	if (conf->symbols != NULL) {
		ksym1 = ksymtab_find(conf->symbols, name);
		if (ksym1 == NULL)
			goto out;
	}

	/* Is this symbol exported in this module with EXPORT_SYMBOL? */
	ksym2 = ksymtab_find(fctx->ksymtab, name);
	if (ksym2 == NULL)
		goto out;

	/* We don't care about declarations */
	if (is_declaration(die))
		goto out;
	/*
	 * Mark the symbol as not eligible to fake symbol generation.
	 * We can come till this place with an assembly function,
	 * because it may have dwarf info for its C declaration,
	 * but others in dwarf are supposed to be normal C functions.
	 */
	ksymtab_ksym_mark(ksym2);

	/* Anything EXPORT_SYMBOLed should be external */
	if (!is_external(die))
		goto out;

	/* We expect only variables or functions on whitelist */
	switch (tag) {
	case (DW_TAG_subprogram):
		/*
		 * We ignore DW_AT_prototyped. This marks functions with
		 * arguments specified in their declaration which the old
		 * pre-ANSI C didn't require. Unfortunatelly people still omit
		 * arguments instead of using foo(void) so we need to handle
		 * even functions without DW_AT_prototyped. What a pity!
		 */
		break;
	case DW_TAG_variable:
		break;
	default:
		fail("Symbol %s has unexpected tag: %s!\n", name,
		    dwarf_tag_string(tag));
	}

	result = true;

	/*
	 * Mark the symbol as fully processed,
	 * so it will not be in the subset of not found symbols.
	 * We are talking here about kabi symbols set,
	 * which is passed by -s switch.
	 *
	 * The actual processing starts later in the caller,
	 * but the decision is made here.
	 */
	if (conf->symbols != NULL)
		ksymtab_ksym_mark(ksym1);

out:
	return result;
}

/*
 * Walk all DIEs in a CU.
 * Returns true if the given symbol_name was found, otherwise false.
 */
static void process_cu_die(Dwarf_Die *cu_die, struct file_ctx *fctx)
{
	Dwarf_Die child_die;
	bool cu_printed = false;
	obj_t *ref;
	generate_config_t *conf = fctx->conf;

	if (!dwarf_haschildren(cu_die))
		return;

	/* Walk all DIEs in the CU */
	dwarf_child(cu_die, &child_die);
	do {
		void *data;
		struct cu_ctx ctx;

		if (!is_symbol_valid(fctx, &child_die))
			continue;

		if (!cu_printed && conf->verbose) {
			printf("Processing CU %s\n",
			       dwarf_diename(cu_die));
			cu_printed = true;
		}

		ctx.conf = conf;
		ctx.cu_die = cu_die;

		/* Grab a fresh stack of symbols */
		ctx.stack = stack_init();
		/* And a set of all processed symbols */
		ctx.processed = set_init(PROCESSED_SIZE);

		/* Print both the CU DIE and symbol DIE */
		ref = print_die(&ctx, NULL, &child_die);
		obj_free(ref);

		/* And clear the stack again */
		while ((data = stack_pop(ctx.stack)) != NULL)
			free(data);

		stack_destroy(ctx.stack);
		set_free(ctx.processed);
	} while (dwarf_siblingof(&child_die, &child_die) == 0);
}

static int dwflmod_generate_cb(Dwfl_Module *dwflmod, void **userdata,
		const char *name, Dwarf_Addr base, void *arg)
{
	Dwarf_Addr dwbias;
	Dwarf *dbg = dwfl_module_getdwarf(dwflmod, &dwbias);
	struct file_ctx *fctx = (struct file_ctx *)arg;

	if (*userdata != NULL)
		fail("Multiple modules found in %s!\n", name);
	*userdata = dwflmod;

	Dwarf_Off off = 0;
	Dwarf_Off old_off = 0;
	Dwarf_Off type_offset = 0;
	Dwarf_Half version;
	size_t hsize;
	Dwarf_Off abbrev;
	uint8_t addresssize;
	uint8_t offsetsize;

	while (dwarf_next_unit(dbg, off, &off, &hsize, &version, &abbrev,
	    &addresssize, &offsetsize, NULL, &type_offset) == 0) {
		if (version < 2 || version > 4)
			fail("Unsupported dwarf version: %d\n", version);

		/* CU is followed by a single DIE */
		Dwarf_Die cu_die;
		if (dwarf_offdie(dbg, old_off + hsize, &cu_die) == NULL) {
			fail("dwarf_offdie failed for cu!\n");
		}

		process_cu_die(&cu_die, fctx);

		old_off = off;
	}

	return DWARF_CB_OK;
}

static void generate_type_info(char *filepath, struct file_ctx *ctx)
{
	static const Dwfl_Callbacks callbacks = {
		.section_address = dwfl_offline_section_address,
		.find_debuginfo = dwfl_standard_find_debuginfo
	};
	Dwfl *dwfl = dwfl_begin(&callbacks);

	if (dwfl_report_offline(dwfl, filepath, filepath, -1) == NULL) {
		dwfl_report_end(dwfl, NULL, NULL);
		fail("dwfl_report_offline failed: %s\n", dwfl_errmsg(-1));
	}
	dwfl_report_end(dwfl, NULL, NULL);
	dwfl_getmodules(dwfl, &dwflmod_generate_cb, ctx, 0);

	dwfl_end(dwfl);
}

static bool is_all_done(generate_config_t *conf)
{
	if (conf->symbols == NULL)
		return false;

	return ksymtab_mark_count(conf->symbols) == conf->symbol_cnt;
}

static void generate_assembly_record(generate_config_t *conf, const char *key)
{
	struct record *rec;
	char *new_key, *name;

	if (conf->verbose)
		printf("Generating assembly record for %s\n", key);

	safe_asprintf(&name, "asm--%s.txt", key);

	rec = record_new_assembly(name);
	new_key = record_db_add(conf->db, rec);

	record_put(rec);
	free(name);
	free(new_key);
}

static bool try_generate_alias(generate_config_t *conf, struct ksym *ksym)
{
	char *link = ksymtab_ksym_get_link(ksym);
	const char *key = ksymtab_ksym_get_name(ksym);
	struct record *rec;
	char *new_key, *name;

	if (!link)
		return false;

	if (conf->verbose)
		printf("Generating weak record %s -> %s\n",
		       key, link);

	safe_asprintf(&name, "weak--%s.txt", key);

	rec = record_new_weak(name, link);
	new_key = record_db_add(conf->db, rec);

	record_put(rec);
	free(name);
	free(new_key);

	return true;
}

/*
 * process_not_found:
 *
 * Generate fake records for symbols, not found in the debug info,
 * but existed in the export list (most probably, assembly function).
 *
 * If there is a checklist, mark the symbol there (as processed).
 * If the symbol not in the checklist, ignore it completely,
 * means, do not mark and do not generate fake record for it either.
 */
static void process_not_found(struct ksym *exported, void *ctx)
{
	generate_config_t *conf = ctx;
	struct ksym *ksym;
	const char *key = ksymtab_ksym_get_name(exported);

	if (ksymtab_ksym_is_marked(exported))
		return;

	if (conf->symbols) {
		ksym = ksymtab_find(conf->symbols, key);
		if (ksym == NULL)
			return;
		ksymtab_ksym_mark(ksym);
	}

	if (!try_generate_alias(conf, exported))
		generate_assembly_record(conf, key);
}

static void ksymtab_add_alias(struct ksym *ksym, void *ctx)
{
	struct ksymtab *ksymtab = ctx;

	ksymtab_copy_sym(ksymtab, ksym);
}

static void ksymtab_add_and_link_alias(struct ksym *ksym, void *ctx)
{
	struct ksymtab *ksymtab = ctx;
	char *link;
	const char *name;
	struct ksym *link_ksym;

	link = ksymtab_ksym_get_link(ksym);
	link_ksym = ksymtab_find(ksymtab, link);

	/* if we linked, there must be the symbol in the symtab */
	assert(link_ksym != NULL);

	name = ksymtab_ksym_get_name(ksym);
	ksymtab_ksym_set_link(link_ksym, name);

	ksymtab_copy_sym(ksymtab, ksym);
}

static void merge_aliases(struct ksymtab *ksymtab,
			  struct ksymtab *symbols,
			  struct ksymtab *aliases)
{
	ksymtab_for_each(aliases, ksymtab_add_and_link_alias, ksymtab);
	if (symbols != NULL)
		ksymtab_for_each(aliases, ksymtab_add_alias, symbols);
}

static walk_rv_t process_symbol_file(char *path, void *arg)
{
	struct file_ctx fctx;
	generate_config_t *conf = (generate_config_t *)arg;
	struct ksymtab *ksymtab;
	struct ksymtab *aliases = NULL;
	walk_rv_t ret = WALK_CONT;

	/* We want to process only .ko kernel modules and vmlinux itself */
	if (!safe_strendswith(path, ".ko") &&
	    !safe_strendswith(path, "/vmlinux"))
		return ret;

	/*
	 * Don't look into RHEL build cache directories.
	 */
	if (conf->rhel_tree) {
		if (strstr(path, "redhat/rpm") != NULL)
			return WALK_SKIP;
	}

	ksymtab = ksymtab_read(path, &aliases);

	if (ksymtab_len(ksymtab) == 0) {
		if (conf->verbose)
			printf("Skip %s (no exported symbols)\n", path);
		goto out;
	}

	merge_aliases(ksymtab, conf->symbols, aliases);

	fctx.conf = conf;
	fctx.ksymtab = ksymtab;

	if (conf->verbose)
		printf("Processing %s\n", path);

	generate_type_info(path, &fctx);
	ksymtab_for_each(ksymtab, process_not_found, conf);

	if (is_all_done(conf))
		ret = WALK_STOP;
out:
	ksymtab_free(aliases);
	ksymtab_free(ksymtab);
	return ret;
}

static void print_not_found(struct ksym *ksym, void *ctx)
{
	const char *s = ksymtab_ksym_get_name(ksym);

	if (ksymtab_ksym_is_marked(ksym))
		return;
	printf("%s not found!\n", s);
}

struct record *record_copy(struct record *src)
{
	struct record *res = record_new_regular("");
	obj_t *o1 = record_obj(src);

	res->obj = obj_merge(o1, o1, MERGE_DECL);
	obj_fill_parent(res->obj);
	res->origin = safe_strdup(src->origin);
	res->base_file = NULL;

	return res;
}

bool record_list_can_merge(struct list *rec_list)
{
	bool result = true;
	struct record *merger;
	struct list_node *iter;

	if (list_len(rec_list) <= 1) {
		/* only one record -> nothing to merge */
		return false;
	}

	merger = record_copy(list_node_data(rec_list->first));
	LIST_FOR_EACH(rec_list, iter) {
		struct record *record = list_node_data(iter);

		if (!record_merge(merger, record, MERGE_DECL)) {
			result = false;
			break;
		}
	}
	record_put(merger);

	return result;
}

void record_list_merge(struct list *rec_list)
{
	struct record *first = rec_list->first->data;
	struct list_node *next = rec_list->first->next;
	struct list_node *curr;

	struct record *record;

	while (next != NULL) {
		curr = next;
		next = next->next;

		record = curr->data;

		list_concat(&first->dependents, &record->dependents);
		record_merge(first, record, MERGE_DECL);
		record_put(record);

		free(curr);
	}

	record_update_dependents(first);
	rec_list->first->next = NULL;
	rec_list->last = rec_list->first;
	rec_list->len = 1;
}

void record_db_merge(struct record_db *db)
{
	struct hash *hash = (struct hash *)db;
	bool merged;
	struct hash_iter iter;
	const char *key;
	const void *val;

	do {
		merged = false;

		hash_iter_init(hash, &iter);
		while (hash_iter_next(&iter, &key, &val)) {
			struct list *list = (struct list *)val;

			if (list != NULL && record_list_can_merge(list)) {
				record_list_merge(list);
				merged = true;
			}
		}
	} while (merged);
}

/*
 * Print symbol definition by walking all DIEs in a .debug_info section.
 * Returns true if the definition was printed, otherwise false.
 */
static void generate_symbol_defs(generate_config_t *conf)
{
	struct stat st;

	if (stat(conf->kernel_dir, &st) != 0)
		fail("Failed to stat %s: %s\n", conf->kernel_dir,
		    strerror(errno));

	/* Lets walk the normal modules */
	printf("Generating symbol defs from %s...\n", conf->kernel_dir);

	conf->db = record_db_init();

	if (S_ISDIR(st.st_mode)) {
		walk_dir(conf->kernel_dir, false, process_symbol_file, conf);
	} else if (S_ISREG(st.st_mode)) {
		char *path = conf->kernel_dir;
		conf->kernel_dir = "";
		process_symbol_file(path, conf);
	} else {
		fail("Not a file or directory: %s\n", conf->kernel_dir);
	}

	ksymtab_for_each(conf->symbols, print_not_found, NULL);

	record_db_merge(conf->db);

	record_db_dump(conf->db, conf->kabi_dir);
	record_db_free(conf->db);
}

#define	WHITESPACE	" \t\n"

/* Remove white characters from given buffer */
static void strip(char *buf)
{
	size_t i = 0, j = 0;
	while (buf[j] != '\0') {
		if (strchr(WHITESPACE, buf[j]) == NULL) {
			if (i != j)
				buf[i] = buf[j];
			i++;
		}
		j++;
	}
	buf[i] = '\0';
}

/*
 * Check if the string is valid C identifier.
 * We do this so we can easily provide the standard kabi whitelist file as the
 * symbol list.
 */
static bool is_valid_c_identifier(char *s)
{
	int i, len;

	if (s == NULL)
		return false;

	len = strlen(s);
	if (len == 0)
		return false;
	if (s[0] != '_' && !isalpha(s[0]))
		return false;

	for (i = 1; i < len; i++) {
		if (s[i] != '_' && !isalnum(s[i]))
			return false;
	}

	return true;
}

static bool is_kabi_header(char *s)
{
	const char *suffix = "_whitelist]";
	int suffixlen = strlen(suffix);
	int len;

	assert(s != NULL);

	len = strlen(s);
	if (len <= suffixlen + 1)
		return false;

	if (s[0] != '[')
		return false;

	if (strcmp(s + (len - suffixlen), suffix) != 0)
		return false;

	return true;
}

/* Get list of symbols to generate. */
static struct ksymtab *read_symbols(char *filename)
{
	FILE *fp = fopen(filename, "r");
	char *line = NULL;
	size_t len = 0;
	size_t i = 0;
	struct ksymtab *symbols;

	symbols = ksymtab_new(DEFAULT_BUFSIZE);

	if (fp == NULL)
		fail("Failed to open symbol file: %s\n", strerror(errno));

	errno = 0;
	while ((getline(&line, &len, fp)) != -1) {
		strip(line);
		if (!is_valid_c_identifier(line)) {
			if (!is_kabi_header(line)) {
				printf("WARNING: Ignoring line \'%s\' from the"
				    " symbols file as it's not a valid C "
				    "identifier.\n", line);
			}
			continue;
		}
		ksymtab_add_sym(symbols, line, len, i);
		i++;
	}

	if (errno != 0)
		fail("getline() failed for %s: %s\n", filename,
		    strerror(errno));

	if (line != NULL)
		free(line);

	fclose(fp);

	return symbols;
}

static void generate_usage()
{
	printf("Usage:\n"
	       "\tgenerate [options] kernel_dir\n"
	       "\nOptions:\n"
	       "    -h, --help:\t\tshow this message\n"
	       "    -v, --verbose:\tdisplay debug information\n"
	       "    -o, --output kabi_dir:\n\t\t\t"
	       "where to write kabi files (default: \"output\")\n"
	       "    -s, --symbols symbol_file:\n\t\t\ta file containing the"
	       " list of symbols of interest (e.g. whitelisted)\n"
	       "    -r, --rhel:\n\t\t\trun on the RHEL build tree\n"
	       "    -a, --abs-path abs_path:\n\t\t\t"
	       "replace the absolute path by a relative path\n"
	       "    -g, --generate-extra-info:\n\t\t\t"
	       "generate extra information (declaration stack, compilation unit)\n");
	exit(1);
}

static void parse_generate_opts(int argc, char **argv, generate_config_t *conf,
		char **symbol_file)
{
	*symbol_file = NULL;
	conf->rhel_tree = false;
	conf->verbose = false;
	conf->kabi_dir = DEFAULT_OUTPUT_DIR;
	int opt, opt_index;
	struct option loptions[] = {
		{"help", no_argument, 0, 'h'},
		{"verbose", no_argument, 0, 'v'},
		{"output", required_argument, 0, 'o'},
		{"symbols", required_argument, 0, 's'},
		{"rhel", no_argument, 0, 'r'},
		{"abs-path", required_argument, 0, 'a'},
		{"generate-extra-info", no_argument, 0, 'g'},
		{0, 0, 0, 0}
	};

	while ((opt = getopt_long(argc, argv, "hvo:s:ra:m:g",
				  loptions, &opt_index)) != -1) {
		switch (opt) {
		case 'h':
			generate_usage();
		case 'v':
			conf->verbose = true;
			break;
		case 'o':
			conf->kabi_dir = optarg;
			break;
		case 's':
			*symbol_file = optarg;
			break;
		case 'r':
			conf->rhel_tree = true;
			break;
		case 'a':
			get_file_replace_path = optarg;
			break;
		case 'g':
			conf->gen_extra = true;
			break;
		default:
			generate_usage();
		}
	}

	if (optind != argc - 1)
		generate_usage();

	conf->kernel_dir = argv[optind];

	rec_mkdir(conf->kabi_dir);
}

void generate(int argc, char **argv)
{
	char *symbol_file;
	generate_config_t *conf = safe_zmalloc(sizeof(*conf));

	parse_generate_opts(argc, argv, conf, &symbol_file);

	if (symbol_file != NULL) {
		conf->symbols = read_symbols(symbol_file);
		conf->symbol_cnt = ksymtab_len(conf->symbols);

		if (conf->verbose)
			printf("Loaded %ld symbols\n", conf->symbol_cnt);
	}

	generate_symbol_defs(conf);

	if (symbol_file != NULL)
		ksymtab_free(conf->symbols);

	free(conf);
}
