/**
** @file	mkblob.c
**
** @author	Warren R. Carithers
**
** Create a binary blob from a collection of ELF files.
*/

#define _DEFAULT_SOURCE

#include <elf.h>
#include <fcntl.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 table 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
typedef struct header_s {
	char magic[4];
	uint32_t num;

} header_t;

// length of the file name field
#define NAMELEN 20

// program descriptor
typedef struct prog_s {
	char name[NAMELEN]; // truncated name (15 chars)
	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;
	char *fullname;
	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

/**
** Name:	process
**
** Do the initial processing for an ELF file
**
** @param name  The name of the file
*/
void process(const char *name)
{
	struct stat info;

	// check the name length
	if (strlen(name) >= NAMELEN) {
		fprintf(stderr, "%s: name exceeds length limit (%d)\n", name,
				NAMELEN - 1);
		return;
	}

	// does it exist?
	if (stat(name, &info) < 0) {
		perror(name);
		return;
	}

	// is it a regular file?
	if (!S_ISREG(info.st_mode)) {
		fprintf(stderr, "%s: not a regular file\n", name);
		return;
	}

	// open it and check the file header
	int fd = open(name, O_RDONLY);
	if (fd < 0) {
		perror(name);
		return;
	}

	// read and check the ELF header
	Elf32_Ehdr hdr;
	int n = read(fd, &hdr, sizeof(Elf32_Ehdr));
	close(fd);
	if (n != sizeof(Elf32_Ehdr)) {
		fprintf(stderr, "%s: header read was short - only %d\n", name, n);
		return;
	}
	if (hdr.e_ident[EI_MAG0] != ELFMAG0 || hdr.e_ident[EI_MAG1] != ELFMAG1 ||
		hdr.e_ident[EI_MAG2] != ELFMAG2 || hdr.e_ident[EI_MAG3] != ELFMAG3) {
		fprintf(stderr, "%s: bad ELF magic number\n", name);
		return;
	}

	// ok, it's a valid ELF file - create the prog list entry
	prog_t *new = calloc(1, sizeof(prog_t));
	if (new == NULL) {
		fprintf(stderr, "%s: calloc prog returned NULL\n", name);
		return;
	}
	node_t *node = calloc(1, sizeof(node_t));
	if (node == NULL) {
		free(new);
		fprintf(stderr, "%s: calloc node returned NULL\n", name);
		return;
	}
	node->data = new;
	node->fullname = strdup(name);

	// copy in the name

	// only want the last component
	const char *slash = strrchr(name, '/');
	if (slash == NULL) {
		// only the file name
		slash = name;

	} else {
		// skip the slash
		++slash;
	}
	strncpy(new->name, slash, sizeof(new->name) - 1);
	new->offset = offset;
	new->size = info.st_size;

	// bump our counters
	++n_progs;
	offset += info.st_size;

	// make sure it's a multiple of eight bytes long
	if ((info.st_size & FSIZE_MASK) != 0) {
		// nope, so we must round it up when we write it out
		new->flags |= FL_ROUNDUP;

		// increases the offset to the next file
		offset += 8 - (info.st_size & FSIZE_MASK);
	}

	// add to the list
	if (progs == NULL) {
		// first entry
		progs = node;

	} else {
		// add to the end
		if (last_prog == NULL) {
			fprintf(stderr, "%s: progs ! NULL, last_prog is NULL\n", name);
			free(new);
			free(node->fullname);
			free(node);
			return;
		}
		last_prog->next = node;
	}
	last_prog = node;
}

/**
** Name:	copy
**
** Copy the contents of a program list entry into the blob
**
** @param ofd   The output FILE* to be written
** @param prog  Pointer to the program list entry for the file
*/
void copy(FILE *ofd, node_t *node)
{
	prog_t *prog = node->data;

	// open it so we can copy it
	int fd = open(node->fullname, O_RDONLY);
	if (fd < 0) {
		perror(node->fullname);
		return;
	}
	uint8_t buf[512];

	// copy it block-by-block
	do {
		int n = read(fd, buf, 512);

		// no bytes --> we're done
		if (n < 1) {
			break;
		}

		// copy it, and verify the copy count
		int k = fwrite(buf, 1, n, ofd);
		if (k != n) {
			fprintf(stderr, "%s: write of %d returned %d\n", prog->name, n, k);
		}

	} while (1);
	printf("%s: copied %d", prog->name, prog->size);

	// do we need to round up?
	if ((prog->flags & FL_ROUNDUP) != 0) {
		// we'll fill with NUL bytes
		uint64_t filler = 0;

		// how many filler bytes do we need?
		int nbytes = 8 - (prog->size & FSIZE_MASK);

		// do it, and check the transfer count to be sure
		int n = fwrite(&filler, 1, nbytes, ofd);
		if (n != nbytes) {
			fprintf(stderr, "%s: fill write of %d returned %d\n", prog->name,
					nbytes, n);
		}

		// report that we added some filler bytes
		printf("(+%d)", n);
	}
	puts(" bytes");

	// all done!
	close(fd);
}
int main(int argc, char *argv[])
{
	// construct program list
	for (int i = 1; i < argc; ++i) {
		process(argv[i]);
	}
	if (n_progs < 1) {
		fputs("Nothing to do... exiting.", stderr);
		exit(0);
	}

	// create the output file
	FILE *ofd;
	ofd = fopen("user.img", "wb");
	if (ofd == NULL) {
		perror("user.img");
		exit(1);
	}
	printf("Processing %d ELF files\n", n_progs);

	// we need to adjust the offset values so they are relative to the
	// start of the blob, not relative to the start of the file area.
	// do this by adding the sum of the file header and program entries
	// to each offset field.
	uint32_t hlen = sizeof(header_t) + n_progs * sizeof(prog_t);
	node_t *curr = progs;
	while (curr != NULL) {
		curr->data->offset += hlen;
		curr = curr->next;
	}

	// write out the blob header
	header_t hdr = { "BLB", n_progs };
	if (fwrite(&hdr, sizeof(header_t), 1, ofd) != 1) {
		perror("blob header");
		fclose(ofd);
		exit(1);
	}

	// next, the program entries
	curr = progs;
	while (curr != NULL) {
		if (fwrite(curr->data, sizeof(prog_t), 1, ofd) != 1) {
			perror("blob prog entry write");
			fclose(ofd);
			exit(1);
		}
		curr = curr->next;
	}

	// finally, copy the files
	curr = progs;
	while (curr != NULL) {
		prog_t *prog = curr->data;
		copy(ofd, curr);
		node_t *tmp = curr;
		curr = curr->next;
		free(tmp->data);
		free(tmp->fullname);
		free(tmp);
	}
	fclose(ofd);
	return 0;
}