/*
 * Copyright (C) 2008 Nokia Corporation
 * Copyright (c) International Business Machines Corp., 2006
 *
 * 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 2 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, write to the Free Software
 * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
 */

/*
 * Generate UBI images.
 *
 * Authors: Artem Bityutskiy
 *          Oliver Lohmann
 */

#define PROGRAM_NAME    "ubinize"

#include <sys/stat.h>
#include <stdlib.h>
#include <getopt.h>
#include <string.h>
#include <fcntl.h>

#include <mtd/ubi-media.h>
#include <libubigen.h>
#include <libiniparser.h>
#include <libubi.h>
#include "common.h"

static const char optionsstr[] =
"-o, --output=<file name>     output file name\n"
"-p, --peb-size=<bytes>       size of the physical eraseblock of the flash\n"
"                             this UBI image is created for in bytes,\n"
"                             kilobytes (KiB), or megabytes (MiB)\n"
"                             (mandatory parameter)\n"
"-m, --min-io-size=<bytes>    minimum input/output unit size of the flash\n"
"                             in bytes\n"
"-s, --sub-page-size=<bytes>  minimum input/output unit used for UBI\n"
"                             headers, e.g. sub-page size in case of NAND\n"
"                             flash (equivalent to the minimum input/output\n"
"                             unit size by default)\n"
"-O, --vid-hdr-offset=<num>   offset if the VID header from start of the\n"
"                             physical eraseblock (default is the next\n"
"                             minimum I/O unit or sub-page after the EC\n"
"                             header)\n"
"-e, --erase-counter=<num>    the erase counter value to put to EC headers\n"
"                             (default is 0)\n"
"-x, --ubi-ver=<num>          UBI version number to put to EC headers\n"
"                             (default is 1)\n"
"-Q, --image-seq=<num>        32-bit UBI image sequence number to use\n"
"                             (by default a random number is picked)\n"
"-v, --verbose                be verbose\n"
"-h, --help                   print help message\n"
"-V, --version                print program version\n\n";

static const char usage[] =
"Usage: " PROGRAM_NAME " [options] <ini-file>\n\n"
"Generate UBI images. An UBI image may contain one or more UBI volumes which\n"
"have to be defined in the input configuration ini-file. The flash\n"
"characteristics are defined via the command-line options.\n\n";

static const struct option long_options[] = {
	{ .name = "output",         .has_arg = 1, .flag = NULL, .val = 'o' },
	{ .name = "peb-size",       .has_arg = 1, .flag = NULL, .val = 'p' },
	{ .name = "min-io-size",    .has_arg = 1, .flag = NULL, .val = 'm' },
	{ .name = "sub-page-size",  .has_arg = 1, .flag = NULL, .val = 's' },
	{ .name = "vid-hdr-offset", .has_arg = 1, .flag = NULL, .val = 'O' },
	{ .name = "erase-counter",  .has_arg = 1, .flag = NULL, .val = 'e' },
	{ .name = "ubi-ver",        .has_arg = 1, .flag = NULL, .val = 'x' },
	{ .name = "image-seq",      .has_arg = 1, .flag = NULL, .val = 'Q' },
	{ .name = "verbose",        .has_arg = 0, .flag = NULL, .val = 'v' },
	{ .name = "help",           .has_arg = 0, .flag = NULL, .val = 'h' },
	{ .name = "version",        .has_arg = 0, .flag = NULL, .val = 'V' },
	{ NULL, 0, NULL, 0}
};

struct args {
	const char *f_in;
	const char *f_out;
	int out_fd;
	int peb_size;
	int min_io_size;
	int subpage_size;
	int vid_hdr_offs;
	int ec;
	int ubi_ver;
	uint32_t image_seq;
	int verbose;
	dictionary *dict;
};

static struct args args = {
	.peb_size     = -1,
	.min_io_size  = -1,
	.subpage_size = -1,
	.ubi_ver      = 1,
};

static int parse_opt(int argc, char * const argv[])
{
	util_srand();
	args.image_seq = rand();

	while (1) {
		int key, error = 0;
		unsigned long int image_seq;

		key = getopt_long(argc, argv, "o:p:m:s:O:e:x:Q:vhV", long_options, NULL);
		if (key == -1)
			break;

		switch (key) {
		case 'o':
			args.out_fd = open(optarg, O_CREAT | O_TRUNC | O_WRONLY,
					   S_IWUSR | S_IRUSR | S_IRGRP | S_IWGRP | S_IROTH);
			if (args.out_fd == -1)
				return sys_errmsg("cannot open file \"%s\"", optarg);
			args.f_out = optarg;
			break;

		case 'p':
			args.peb_size = util_get_bytes(optarg);
			if (args.peb_size <= 0)
				return errmsg("bad physical eraseblock size: \"%s\"", optarg);
			break;

		case 'm':
			args.min_io_size = util_get_bytes(optarg);
			if (args.min_io_size <= 0)
				return errmsg("bad min. I/O unit size: \"%s\"", optarg);
			if (!is_power_of_2(args.min_io_size))
				return errmsg("min. I/O unit size should be power of 2");
			break;

		case 's':
			args.subpage_size = util_get_bytes(optarg);
			if (args.subpage_size <= 0)
				return errmsg("bad sub-page size: \"%s\"", optarg);
			if (!is_power_of_2(args.subpage_size))
				return errmsg("sub-page size should be power of 2");
			break;

		case 'O':
			args.vid_hdr_offs = simple_strtoul(optarg, &error);
			if (error || args.vid_hdr_offs < 0)
				return errmsg("bad VID header offset: \"%s\"", optarg);
			break;

		case 'e':
			args.ec = simple_strtoul(optarg, &error);
			if (error || args.ec < 0)
				return errmsg("bad erase counter value: \"%s\"", optarg);
			break;

		case 'x':
			args.ubi_ver = simple_strtoul(optarg, &error);
			if (error || args.ubi_ver < 0)
				return errmsg("bad UBI version: \"%s\"", optarg);
			break;

		case 'Q':
			image_seq = simple_strtoul(optarg, &error);
			if (error || image_seq > 0xFFFFFFFF)
				return errmsg("bad UBI image sequence number: \"%s\"", optarg);
			args.image_seq = image_seq;
			break;

		case 'v':
			args.verbose = 1;
			break;

		case 'h':
			fputs(usage, stdout);
			fputs(optionsstr, stdout);
			printf("For more information see `man 8 %s`\n\n",
				PROGRAM_NAME);
			exit(EXIT_SUCCESS);

		case 'V':
			common_print_version();
			exit(EXIT_SUCCESS);

		default:
			fputs(usage, stderr);
			fputs("Use -h for help\n\n", stderr);
			return -1;
		}
	}

	if (optind == argc)
		return errmsg("input configuration file was not specified (use -h for help)");

	if (optind != argc - 1)
		return errmsg("more then one configuration file was specified (use -h for help)");

	args.f_in = argv[optind];

	if (args.peb_size < 0)
		return errmsg("physical eraseblock size was not specified (use -h for help)");

	if (args.peb_size > UBI_MAX_PEB_SZ)
		return errmsg("too high physical eraseblock size %d", args.peb_size);

	if (args.min_io_size < 0)
		return errmsg("min. I/O unit size was not specified (use -h for help)");

	if (args.subpage_size < 0)
		args.subpage_size = args.min_io_size;

	if (args.subpage_size > args.min_io_size)
		return errmsg("sub-page cannot be larger then min. I/O unit");

	if (args.peb_size % args.min_io_size)
		return errmsg("physical eraseblock should be multiple of min. I/O units");

	if (args.min_io_size % args.subpage_size)
		return errmsg("min. I/O unit size should be multiple of sub-page size");

	if (!args.f_out)
		return errmsg("output file was not specified (use -h for help)");

	if (args.vid_hdr_offs) {
		if (args.vid_hdr_offs + (int)UBI_VID_HDR_SIZE >= args.peb_size)
			return errmsg("bad VID header position");
		if (args.vid_hdr_offs % 8)
			return errmsg("VID header offset has to be multiple of min. I/O unit size");
	}

	return 0;
}

static int read_section(const struct ubigen_info *ui, const char *sname,
			struct ubigen_vol_info *vi, const char **img,
			struct stat *st)
{
	char buf[256];
	const char *p;

	*img = NULL;

	if (strlen(sname) > 128)
		return errmsg("too long section name \"%s\"", sname);

	/* Make sure mode is UBI, otherwise ignore this section */
	sprintf(buf, "%s:mode", sname);
	p = iniparser_getstring(args.dict, buf, NULL);
	if (!p) {
		errmsg("\"mode\" key not found in section \"%s\"", sname);
		errmsg("the \"mode\" key is mandatory and has to be "
		       "\"mode=ubi\" if the section describes an UBI volume");
		return -1;
	}

	/* If mode is not UBI, skip this section */
	if (strcmp(p, "ubi")) {
		verbose(args.verbose, "skip non-ubi section \"%s\"", sname);
		return 1;
	}

	verbose(args.verbose, "mode=ubi, keep parsing");

	/* Fetch volume type */
	sprintf(buf, "%s:vol_type", sname);
	p = iniparser_getstring(args.dict, buf, NULL);
	if (!p) {
		normsg("volume type was not specified in "
		       "section \"%s\", assume \"dynamic\"\n", sname);
		vi->type = UBI_VID_DYNAMIC;
	} else {
		if (!strcmp(p, "static"))
			vi->type = UBI_VID_STATIC;
		else if (!strcmp(p, "dynamic"))
			vi->type = UBI_VID_DYNAMIC;
		else
			return errmsg("invalid volume type \"%s\" in section  \"%s\"",
				      p, sname);
	}

	verbose(args.verbose, "volume type: %s",
		vi->type == UBI_VID_DYNAMIC ? "dynamic" : "static");

	/* Fetch the name of the volume image file */
	sprintf(buf, "%s:image", sname);
	p = iniparser_getstring(args.dict, buf, NULL);
	if (p) {
		*img = p;
		if (stat(p, st))
			return sys_errmsg("cannot stat \"%s\" referred from section \"%s\"",
					  p, sname);
		if (st->st_size == 0)
			return errmsg("empty file \"%s\" referred from section \"%s\"",
				       p, sname);
	} else if (vi->type == UBI_VID_STATIC)
		return errmsg("image is not specified for static volume in section \"%s\"",
			      sname);

	/* Fetch volume id */
	sprintf(buf, "%s:vol_id", sname);
	vi->id = iniparser_getint(args.dict, buf, -1);
	if (vi->id == -1)
		return errmsg("\"vol_id\" key not found in section  \"%s\"", sname);
	if (vi->id < 0)
		return errmsg("negative volume ID %d in section \"%s\"",
			      vi->id, sname);
	if (vi->id >= ui->max_volumes)
		return errmsg("too high volume ID %d in section \"%s\", max. is %d",
			      vi->id, sname, ui->max_volumes);

	verbose(args.verbose, "volume ID: %d", vi->id);

	/* Fetch volume size */
	sprintf(buf, "%s:vol_size", sname);
	p = iniparser_getstring(args.dict, buf, NULL);
	if (p) {
		vi->bytes = util_get_bytes(p);
		if (vi->bytes <= 0)
			return errmsg("bad \"vol_size\" key value \"%s\" (section \"%s\")",
				      p, sname);

		/* Make sure the image size is not larger than volume size */
		if (*img && st->st_size > vi->bytes)
			return errmsg("error in section \"%s\": size of the image file "
				      "\"%s\" is %lld, which is larger than volume size %lld",
				      sname, *img, (long long)st->st_size, vi->bytes);
		verbose(args.verbose, "volume size: %lld bytes", vi->bytes);
	} else {
		struct stat st;

		if (!*img)
			return errmsg("neither image file (\"image=\") nor volume size "
				      "(\"vol_size=\") specified in section \"%s\"", sname);

		if (stat(*img, &st))
			return sys_errmsg("cannot stat \"%s\"", *img);

		vi->bytes = st.st_size;

		if (vi->bytes == 0)
			return errmsg("file \"%s\" referred from section \"%s\" is empty",
				      *img, sname);

		normsg_cont("volume size was not specified in section \"%s\", assume"
			    " minimum to fit image \"%s\"", sname, *img);
		util_print_bytes(vi->bytes, 1);
		printf("\n");
	}

	/* Fetch volume name */
	sprintf(buf, "%s:vol_name", sname);
	p = iniparser_getstring(args.dict, buf, NULL);
	if (!p)
		return errmsg("\"vol_name\" key not found in section \"%s\"", sname);

	vi->name = p;
	vi->name_len = strlen(p);
	if (vi->name_len > UBI_VOL_NAME_MAX)
		return errmsg("too long volume name in section \"%s\", max. is %d characters",
			      vi->name, UBI_VOL_NAME_MAX);

	verbose(args.verbose, "volume name: %s", p);

	/* Fetch volume alignment */
	sprintf(buf, "%s:vol_alignment", sname);
	vi->alignment = iniparser_getint(args.dict, buf, -1);
	if (vi->alignment == -1)
		vi->alignment = 1;
	else if (vi->id < 0)
		return errmsg("negative volume alignement %d in section \"%s\"",
			      vi->alignment, sname);

	verbose(args.verbose, "volume alignment: %d", vi->alignment);

	/* Fetch volume flags */
	sprintf(buf, "%s:vol_flags", sname);
	p = iniparser_getstring(args.dict, buf, NULL);
	if (p) {
		if (!strcmp(p, "autoresize")) {
			verbose(args.verbose, "autoresize flags found");
			vi->flags |= UBI_VTBL_AUTORESIZE_FLG;
		} else {
			return errmsg("unknown flags \"%s\" in section \"%s\"",
				      p, sname);
		}
	}

	/* Initialize the rest of the volume information */
	vi->data_pad = ui->leb_size % vi->alignment;
	vi->usable_leb_size = ui->leb_size - vi->data_pad;
	if (vi->type == UBI_VID_DYNAMIC)
		vi->used_ebs = (vi->bytes + vi->usable_leb_size - 1) / vi->usable_leb_size;
	else
		vi->used_ebs = (st->st_size + vi->usable_leb_size - 1) / vi->usable_leb_size;
	vi->compat = 0;
	return 0;
}

int main(int argc, char * const argv[])
{
	int err = -1, sects, i, autoresize_was_already = 0;
	struct ubigen_info ui;
	struct ubi_vtbl_record *vtbl;
	struct ubigen_vol_info *vi;
	off_t seek;

	err = parse_opt(argc, argv);
	if (err)
		return -1;

	ubigen_info_init(&ui, args.peb_size, args.min_io_size,
			 args.subpage_size, args.vid_hdr_offs,
			 args.ubi_ver, args.image_seq);

	verbose(args.verbose, "LEB size:                  %d", ui.leb_size);
	verbose(args.verbose, "PEB size:                  %d", ui.peb_size);
	verbose(args.verbose, "min. I/O size:             %d", ui.min_io_size);
	verbose(args.verbose, "sub-page size:             %d", args.subpage_size);
	verbose(args.verbose, "VID offset:                %d", ui.vid_hdr_offs);
	verbose(args.verbose, "data offset:               %d", ui.data_offs);
	verbose(args.verbose, "UBI image sequence number: %u", ui.image_seq);

	vtbl = ubigen_create_empty_vtbl(&ui);
	if (!vtbl)
		goto out;

	args.dict = iniparser_load(args.f_in);
	if (!args.dict) {
		errmsg("cannot load the input ini file \"%s\"", args.f_in);
		goto out_vtbl;
	}

	verbose(args.verbose, "loaded the ini-file \"%s\"", args.f_in);

	/* Each section describes one volume */
	sects = iniparser_getnsec(args.dict);
	if (sects == -1) {
		errmsg("ini-file parsing error (iniparser_getnsec)");
		goto out_dict;
	}

	verbose(args.verbose, "count of sections: %d", sects);
	if (sects == 0) {
		errmsg("no sections found the ini-file \"%s\"", args.f_in);
		goto out_dict;
	}

	if (sects > ui.max_volumes) {
		errmsg("too many sections (%d) in the ini-file \"%s\"",
		       sects, args.f_in);
		normsg("each section corresponds to an UBI volume, maximum "
		       "count of volumes is %d", ui.max_volumes);
		goto out_dict;
	}

	vi = calloc(sizeof(struct ubigen_vol_info), sects);
	if (!vi) {
		errmsg("cannot allocate memory");
		goto out_dict;
	}

	/*
	 * Skip 2 PEBs at the beginning of the file for the volume table which
	 * will be written later.
	 */
	seek = ui.peb_size * 2;
	if (lseek(args.out_fd, seek, SEEK_SET) != seek) {
		err = -1;
		sys_errmsg("cannot seek file \"%s\"", args.f_out);
		goto out_free;
	}

	for (i = 0; i < sects; i++) {
		const char *sname = iniparser_getsecname(args.dict, i);
		const char *img = NULL;
		struct stat st;
		int fd, j;

		if (!sname) {
			err = -1;
			errmsg("ini-file parsing error (iniparser_getsecname)");
			goto out_free;
		}

		if (args.verbose)
			printf("\n");
		verbose(args.verbose, "parsing section \"%s\"", sname);

		err = read_section(&ui, sname, &vi[i], &img, &st);
		if (err == -1)
			goto out_free;

		verbose(args.verbose, "adding volume %d", vi[i].id);

		/*
		 * Make sure that volume ID and name is unique and that only
		 * one volume has auto-resize flag
		 */
		for (j = 0; j < i; j++) {
			if (vi[i].id == vi[j].id) {
				err = -1;
				errmsg("volume IDs must be unique, but ID %d "
				       "in section \"%s\" is not",
				       vi[i].id, sname);
				goto out_free;
			}

			if (!strcmp(vi[i].name, vi[j].name)) {
				err = -1;
				errmsg("volume name must be unique, but name "
				       "\"%s\" in section \"%s\" is not",
				       vi[i].name, sname);
				goto out_free;
			}
		}

		if (vi[i].flags & UBI_VTBL_AUTORESIZE_FLG) {
			if (autoresize_was_already)
				return errmsg("only one volume is allowed "
					      "to have auto-resize flag");
			autoresize_was_already = 1;
		}

		err = ubigen_add_volume(&ui, &vi[i], vtbl);
		if (err) {
			errmsg("cannot add volume for section \"%s\"", sname);
			goto out_free;
		}

		if (img) {
			fd = open(img, O_RDONLY);
			if (fd == -1) {
				err = fd;
				sys_errmsg("cannot open \"%s\"", img);
				goto out_free;
			}

			verbose(args.verbose, "writing volume %d", vi[i].id);
			verbose(args.verbose, "image file: %s", img);

			err = ubigen_write_volume(&ui, &vi[i], args.ec, st.st_size, fd, args.out_fd);
			close(fd);
			if (err) {
				errmsg("cannot write volume for section \"%s\"", sname);
				goto out_free;
			}
		}

		if (args.verbose)
			printf("\n");
	}

	verbose(args.verbose, "writing layout volume");

	err = ubigen_write_layout_vol(&ui, 0, 1, args.ec, args.ec, vtbl, args.out_fd);
	if (err) {
		errmsg("cannot write layout volume");
		goto out_free;
	}

	verbose(args.verbose, "done");

	free(vi);
	iniparser_freedict(args.dict);
	free(vtbl);
	close(args.out_fd);
	return 0;

out_free:
	free(vi);
out_dict:
	iniparser_freedict(args.dict);
out_vtbl:
	free(vtbl);
out:
	close(args.out_fd);
	remove(args.f_out);
	return err;
}
