/**
** @file	listblob.c
**
** @author	Warren R. Carithers
**
** Examine a binary blob of ELF files.
*/

#define _DEFAULT_SOURCE
#include <ctype.h>
#include <elf.h>
#include <fcntl.h>
#include <stdbool.h>
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/stat.h>
#include <unistd.h>

/*
** Blob file organization
**
** The file begins with a four-byte magic number and a four-byte integer
** indicating the number of ELF files contained in the blob. This is
** followed by an array of 32-byte file entries, and then the contents
** of the ELF files in the order they appear in the program file table.
**
**		Bytes        Contents
**		-----        ----------------------------
**		0 - 3        File magic number ("BLB\0")
**      4 - 7        Number of ELF files in blob ("n")
**      8 - n*32+8   Program file table
**      n*32+9 - ?   ELF file contents
**
** Each program file table entry contains the following information:
**
** 		name         File name (up to 19 characters long)
**		offset       Byte offset to the ELF header for this file
**		size         Size of this ELF file, in bytes
**		flags        Flags related to this file
*/
// blob header: 8 bytes
typedef struct header_s {
	char magic[4];
	uint32_t num;

} header_t;

// The program table entry is 32 bytes long. To accomplish this, the
// name field is 20 bytes long, which allows file names of 19 characters
// (followed by a trailing NUL byte).
//
// If that field is made longer, it should be incremented in multiples
// of four to avoid the insertion of padding bytes.
#define NAMELEN 20

// program descriptor: 32 bytes
typedef struct prog_s {
	char name[NAMELEN]; // truncated name (19 chars plus NUL)
	uint32_t offset; // offset from the beginning of the blob
	uint32_t size; // size of this ELF module
	uint32_t flags; // miscellaneous flags
} prog_t;

// modules must be written as multiples of eight bytes
#define FL_ROUNDUP 0x00000001

// mask for mod 8 checking
#define FSIZE_MASK 0x00000007

// program list entry
typedef struct node_s {
	prog_t *data;
	struct node_s *next;

} node_t;
node_t *progs, *last_prog; // list pointers
uint32_t n_progs; // number of files being copied
uint32_t offset; // current file area offset
bool defs = false; // print CPP #defines?
bool enums = false; // print C enums?

// header string for the userids.h file
const char header[] =
	"/**\n"
	"** @file	userids.h\n"
	"**\n"
	"** @author	Warren R. Carithers\n"
	"**\n"
	"** @brief	IDs for user-level programs\n"
	"**\n"
	"** NOTE: this file is automatically generated when the user.img file\n"
	"** is created. Do not edit this manually!\n"
	"*/\n"
	"\n"
	"#ifndef USERIDS_H_\n"
	"#define USERIDS_H_\n"
	"\n"
	"#ifndef ASM_SRC\n"
	"/*\n"
	"** These IDs are used to identify the various user programs.\n"
	"** Each call to exec() will provide one of these as the first\n"
	"** argument.\n"
	"**\n"
	"** This list should be updated if/when the collection of\n"
	"** user processes changes.\n"
	"*/\n"
	"enum users_e {";

// trailer string for the userids.h file
const char trailer[] = "\n\t// sentinel\n\t, N_USERS\n"
					   "};\n"
					   "#endif  /* !ASM_SRC */\n"
					   "\n"
					   "#endif";

/**
** Name:	process
**
** Process a program list entry
**
** @param num   Program list index
** @param prog  Pointer to the program list entry
*/
void process(uint32_t num, prog_t *prog)
{
	if (defs || enums) {
		char *slash = strrchr(prog->name, '/');
		if (slash == NULL) {
			slash = prog->name;

		} else {
			++slash;
		}
		slash[0] = toupper(slash[0]);
		if (defs) {
			// just printing #define statements
			printf("#define %-15s %2d\n", prog->name, num);

		} else {
			// printing a new userids.h file
			if (num == 0) {
				// first one, so print the file header
				puts(header);
				putchar('\t');

			} else {
				// second or later entry; limit to 8 per line
				fputs(((num & 0x7) == 0) ? ",\n\t" : ", ", stdout);
			}
			printf("%s", prog->name);
		}

	} else {
		// just printing information
		printf("Entry %2d:  ", num);
		printf("%-s,", prog->name);
		printf(" offset 0x%x, size 0x%x, flags %08x\n", prog->offset,
			   prog->size, prog->flags);
	}
}
void usage(char *name)
{
	fprintf(stderr, "usage: %s [-d | -e] blob_name\n", name);
}
int main(int argc, char *argv[])
{
	if (argc < 2 || argc > 3) {
		usage(argv[0]);
		exit(1);
	}
	int nameix = 1;

	// could use getopt() for this, but this is easy enough
	if (argc == 3) {
		if (strcmp(argv[1], "-d") == 0) {
			defs = true;

		} else if (strcmp(argv[1], "-e") == 0) {
			enums = true;

		} else {
			usage(argv[0]);
			exit(1);
		}
		nameix = 2;
	}
	char *name = argv[nameix];
	int fd = open(name, O_RDONLY);
	if (fd < 0) {
		perror(name);
		exit(1);
	}
	header_t hdr;
	int n = read(fd, &hdr, sizeof(header_t));
	if (n != sizeof(header_t)) {
		fprintf(stderr, "%s: header read returned only %d bytes\n", name, n);
		close(fd);
		exit(1);
	}
	if (strcmp(hdr.magic, "BLB") != 0) {
		fprintf(stderr, "%s: bad magic number\n", name);
		close(fd);
		exit(1);
	}
	if (hdr.num < 1) {
		fprintf(stderr, "%s: no programs in blob?\n", name);
		close(fd);
		exit(1);
	}
	prog_t progs[hdr.num];
	n = read(fd, progs, hdr.num * sizeof(prog_t));
	if (n != (int)(hdr.num * sizeof(prog_t))) {
		fprintf(stderr, "%s: prog table only %d bytes, expected %lu\n", name, n,
				hdr.num * sizeof(prog_t));
		close(fd);
		exit(1);
	}
	for (uint32_t i = 0; i < hdr.num; ++i) {
		process(i, &progs[i]);
	}
	if (enums) {
		// print the file trailer
		puts(trailer);
	}
	close(fd);
	return 0;
}