/*
 * Copyright (C) 2013 CERN (www.cern.ch)
 * Author: Alessandro Rubini <rubini@gnudd.com>
 *
 * Released according to the GNU GPL, version 2 or any later version.
 *
 * This work is part of the White Rabbit project, a research effort led
 * by CERN, the European Institute for Nuclear Research.
 */

#include <stdio.h>
#include <stdint.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <getopt.h>
#include <errno.h>
#include <dirent.h>
#include <fcntl.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <sys/stat.h>
#include <sys/mman.h>

#include "libsdbfs.h"
#define CFG_NAME "--SDB-CONFIG--"

/*
 * This is similar to ./sdb-read, so some code duplication is there,
 * but I'd better keep the tools separate and simple
 */

char *prgname;

static int opt_force, opt_entry;

static int create_file(struct sdbfs *fs, struct sdb_device *d, FILE *cfgf)
{
	FILE *f;
	struct sdb_product *p;
	struct sdb_component *c;
	char name[32];
	int mode = 0444;

	c = &d->sdb_component;
	p = &c->product;

	/* Remove trailing spaces from the name */
	strncpy(name, (char *)p->name, sizeof(p->name));
	name[sizeof(p->name)] = '\0';
	while (name[strlen(name) - 1] == ' ')
		name[strlen(name) - 1] = '\0';

	/* Print cfgfile information */
	fprintf(cfgf, "%s\n" "\tvendor = 0x%016lx\n" "\tdevice = 0x%08x\n",
		name, ntohll(p->vendor_id), ntohl(p->device_id));
	fprintf(cfgf, "\tposition = 0x%lx\n", ntohll(c->addr_first));
	if (ntohl(d->bus_specific) & SDB_DATA_WRITE) {
		fprintf(cfgf, "\twrite = 1\n");
		mode |= 0222;
	}
	if (ntohl(d->bus_specific) & SDB_DATA_EXEC)
		mode |= 0111;
	fprintf(cfgf, "\n");

	/* Create the actual file unless it is the root directory */
	if (!strcmp(name, "."))
		return 0;
	f = fopen(name, "w");
	if (!f) {
		fprintf(stderr, "%s: open(%s): %s\n", prgname, name,
			strerror(errno));
		return -1;
	}
	fwrite(fs->data + ntohll(c->addr_first), 1,
	       ntohll(c->addr_last) + 1 - ntohll(c->addr_first), f);
	fclose(f);
	chmod(name, mode);
	return 0;
}


/* As promised, here's the user-interface glue (and initialization, I admit) */
int main(int argc, char **argv)
{
	int n, new, c, err, cfgfd;
	FILE *f, *cfgf;
	struct sdbfs _fs;
	struct sdbfs *fs = &_fs; /* I like to type "fs->" */
	struct sdb_device *d;
	struct stat stbuf;
	void *mapaddr;
	char *fsname, *dirname;
	struct dirent **namelist;
	int i, pagesize = getpagesize();

	prgname = argv[0];

	while ( (c = getopt(argc, argv, "e:f")) != -1) {
		switch (c) {
		case 'f':
			opt_force = 1;
			break;
		case 'e':
			if (sscanf(optarg, "%i", &opt_entry) != 1) {
				fprintf(stderr, "%s: not a number \"%s\"\n",
					prgname, optarg);
				exit(1);
			}
		}
	}
	if (optind != argc - 2) {
		fprintf(stderr, "%s: Use: \"%s [-f] [-e <entry>] "
			"<output-dir> <sdb-file>\n", prgname, prgname);
		exit(1);
	}
	fsname = argv[optind + 1];
	dirname = argv[optind];

	if ( !(f = fopen(fsname, "r")) || fstat(fileno(f), &stbuf) < 0) {
		fprintf(stderr, "%s: %s: %s\n", prgname, fsname,
			strerror(errno));
		exit(1);
	}

	stbuf.st_size += pagesize - 1;
	stbuf.st_size &= ~(pagesize - 1);
	mapaddr = mmap(0, stbuf.st_size, PROT_READ, MAP_PRIVATE, fileno(f), 0);
	if (mapaddr == MAP_FAILED) {
		/* I used to complain, but sysfs doesn't allow mmapping... */
		mapaddr = malloc(stbuf.st_size);
		if (!mapaddr) {
			fprintf(stderr, "%s: out of memory reading \"%s\"\n",
				argv[0], fsname);
			exit(1);
		}
		i = fread(mapaddr, 1, stbuf.st_size, f);
		if (i < stbuf.st_size) {
			fprintf(stderr, "%s: %s: short read\n",
				argv[0], fsname);
			exit(1);
		}
	}

	/* Check output dir is empty, open config file */

	/* Open the filesystem */
	memset(fs, 0, sizeof(*fs));

	fs->name = fsname; /* not mandatory */
	fs->blocksize = 256; /* only used for writing, actually */
	fs->entrypoint = opt_entry;
	fs->data = mapaddr;

	err = sdbfs_dev_create(fs);
	if (err) {
		fprintf(stderr, "%s: sdbfs_dev_create(): %s\n", prgname,
			strerror(-err));
		fprintf(stderr, "\t(wrong entry point 0x%08lx?)\n",
			fs->entrypoint);
		exit(1);
	}

	/* We are sure the fs is good: create output dir and cfgfile */
	if (mkdir(dirname, 0777) < 0 && errno != EEXIST) {
		fprintf(stderr, "%s: %s: %s\n", prgname, dirname,
			strerror(errno));
		exit(1);
	}
	if (chdir(dirname) < 0) {
		fprintf(stderr, "%s: %s: %s\n", prgname, dirname,
			strerror(errno));
		exit(1);

	}
        n = scandir(".", &namelist, 0, 0);
	if (!opt_force && n != 2) {
		fprintf(stderr, "%s: %s: not empty\n", prgname, dirname);
		exit(1);
	}
	cfgfd = open(CFG_NAME, O_RDWR | O_CREAT | O_EXCL, 0666);
	if (cfgfd < 0) {
		fprintf(stderr, "%s: Warning: %s/%s: %s\n", prgname, dirname,
			CFG_NAME, strerror(errno));
		cfgf = fopen("/dev/null", "w");
	} else {
		cfgf = fdopen(cfgfd, "w");
	}

	/* Save the header */
	fprintf(cfgf, "# Configuration file generated by %s, reading %s\n\n",
		prgname, fsname);

	/* The root directory is a file like the other ones */
	while ( (d = sdbfs_scan(fs, new)) != NULL) {
		create_file(fs, d, cfgf);
		new = 0;
	}
	sdbfs_dev_destroy(fs);
	return 0;
}

