/*
 * isosize.c - Andries Brouwer, 000608
 *
 * use header info to find size of iso9660 file system
 * output a number - useful in scripts
 *
 * Synopsis:
 *    isosize [-x] [-d <num>] <filename>
 *        where "-x" gives length in sectors and sector size while
 *              without this argument the size is given in bytes
 *        without "-x" gives length in bytes unless "-d <num>" is
 *		given. In the latter case the length in bytes divided
 *		by <num> is given
 *
 *  Version 2.03 2000/12/21
 *     - add "-d <num>" option and use long long to fix things > 2 GB
 *  Version 2.02 2000/10/11
 *     - error messages on IO failures [D. Gilbert]
 *
 */
#include <stdio.h>
#include <stdlib.h>
#include <getopt.h>
#include <fcntl.h>
#include <unistd.h>
#include <errno.h>

#include "nls.h"
#include "c.h"
#include "strutils.h"
#include "closestream.h"

#define ISODCL(from, to) (to - from + 1)

static int is_iso(int fd)
{
	char label[8];
	if (pread(fd, &label, 8, 0x8000) == -1)
		return 1;
	return memcmp(&label, &"\1CD001\1", 8);
}

static int isonum_721(unsigned char *p)
{
	return ((p[0] & 0xff)
		| ((p[1] & 0xff) << 8));
}

static int isonum_722(unsigned char *p)
{
	return ((p[1] & 0xff)
		| ((p[0] & 0xff) << 8));
}

static int isonum_723(unsigned char *p, int xflag)
{
	int le = isonum_721(p);
	int be = isonum_722(p + 2);
	if (xflag && le != be)
		/* translation is useless */
		warnx("723error: le=%d be=%d", le, be);
	return (le);
}

static int isonum_731(unsigned char *p)
{
	return ((p[0] & 0xff)
		| ((p[1] & 0xff) << 8)
		| ((p[2] & 0xff) << 16)
		| ((p[3] & 0xff) << 24));
}

static int isonum_732(unsigned char *p)
{
	return ((p[3] & 0xff)
		| ((p[2] & 0xff) << 8)
		| ((p[1] & 0xff) << 16)
		| ((p[0] & 0xff) << 24));
}

static int isonum_733(unsigned char *p, int xflag)
{
	int le = isonum_731(p);
	int be = isonum_732(p + 4);
	if (xflag && le != be)
		/* translation is useless */
		warnx("733error: le=%d be=%d", le, be);
	return (le);
}

struct iso_primary_descriptor
{
	unsigned char type			[ISODCL (   1,	  1)]; /* 711 */
	unsigned char id			[ISODCL (   2,	  6)];
	unsigned char version			[ISODCL (   7,	  7)]; /* 711 */
	unsigned char unused1			[ISODCL (   8,	  8)];
	unsigned char system_id			[ISODCL (   9,	 40)]; /* auchars */
	unsigned char volume_id			[ISODCL (  41,	 72)]; /* duchars */
	unsigned char unused2			[ISODCL (  73,	 80)];
	unsigned char volume_space_size		[ISODCL (  81,	 88)]; /* 733 */
	unsigned char unused3			[ISODCL (  89,	120)];
	unsigned char volume_set_size		[ISODCL ( 121,	124)]; /* 723 */
	unsigned char volume_sequence_number	[ISODCL ( 125,	128)]; /* 723 */
	unsigned char logical_block_size	[ISODCL ( 129,	132)]; /* 723 */
	unsigned char path_table_size		[ISODCL ( 133,	140)]; /* 733 */
	unsigned char type_l_path_table		[ISODCL ( 141,	144)]; /* 731 */
	unsigned char opt_type_l_path_table	[ISODCL ( 145,	148)]; /* 731 */
	unsigned char type_m_path_table		[ISODCL ( 149,	152)]; /* 732 */
	unsigned char opt_type_m_path_table	[ISODCL ( 153,	156)]; /* 732 */
	unsigned char root_directory_record	[ISODCL ( 157,	190)]; /* 9.1 */
	unsigned char volume_set_id		[ISODCL ( 191,	318)]; /* duchars */
	unsigned char publisher_id		[ISODCL ( 319,	446)]; /* achars */
	unsigned char preparer_id		[ISODCL ( 447,	574)]; /* achars */
	unsigned char application_id		[ISODCL ( 575,	702)]; /* achars */
	unsigned char copyright_file_id		[ISODCL ( 703,	739)]; /* 7.5 dchars */
	unsigned char abstract_file_id		[ISODCL ( 740,	776)]; /* 7.5 dchars */
	unsigned char bibliographic_file_id	[ISODCL ( 777,	813)]; /* 7.5 dchars */
	unsigned char creation_date		[ISODCL ( 814,	830)]; /* 8.4.26.1 */
	unsigned char modification_date		[ISODCL ( 831,	847)]; /* 8.4.26.1 */
	unsigned char expiration_date		[ISODCL ( 848,	864)]; /* 8.4.26.1 */
	unsigned char effective_date		[ISODCL ( 865,	881)]; /* 8.4.26.1 */
	unsigned char file_structure_version	[ISODCL ( 882,	882)]; /* 711 */
	unsigned char unused4			[ISODCL ( 883,	883)];
	unsigned char application_data		[ISODCL ( 884, 1395)];
	unsigned char unused5			[ISODCL (1396, 2048)];
};

static void isosize(int argc, char *filenamep, int xflag, long divisor)
{
	int fd, nsecs, ssize;
	struct iso_primary_descriptor ipd;

	if ((fd = open(filenamep, O_RDONLY)) < 0)
		err(EXIT_FAILURE, _("cannot open %s"), filenamep);
	if (is_iso(fd))
		warnx(_("%s: might not be an ISO filesystem"), filenamep);

	if (lseek(fd, 16 << 11, 0) == (off_t) - 1)
		err(EXIT_FAILURE, _("seek error on %s"), filenamep);

	if (read(fd, &ipd, sizeof(ipd)) < 0)
		err(EXIT_FAILURE, _("read error on %s"), filenamep);

	nsecs = isonum_733(ipd.volume_space_size, xflag);
	/* isonum_723 returns nowadays always 2048 */
	ssize = isonum_723(ipd.logical_block_size, xflag);

	if (1 < argc)
		printf("%s: ", filenamep);
	if (xflag) {
		printf(_("sector count: %d, sector size: %d\n"), nsecs, ssize);
	} else {
		long long product = nsecs;

		if (divisor == 0)
			printf("%lld\n", product * ssize);
		else if (divisor == ssize)
			printf("%d\n", nsecs);
		else
			printf("%lld\n", (product * ssize) / divisor);
	}

	close(fd);
}

static void __attribute__((__noreturn__)) usage(FILE *out)
{
	fputs(USAGE_HEADER, out);
	fprintf(out,
		_(" %s [options] <iso9660_image_file>\n"),
		program_invocation_short_name);

	fputs(USAGE_SEPARATOR, out);
	fputs(_("Show the length of an ISO-9660 filesystem.\n"), out);

	fputs(USAGE_OPTIONS, out);
	fputs(_(" -d, --divisor=<number>  divide the amount of bytes by <number>\n"), out);
	fputs(_(" -x, --sectors           show sector count and size\n"), out);
	fputs(USAGE_SEPARATOR, out);
	fputs(USAGE_HELP, out);
	fputs(USAGE_VERSION, out);
	fprintf(out, USAGE_MAN_TAIL("isosize(8)"));

	exit(out == stderr ? EXIT_FAILURE : EXIT_SUCCESS);
}

int main(int argc, char **argv)
{
	int j, ct, opt, xflag = 0;
	long divisor = 0;

	static const struct option longopts[] = {
		{"divisor", required_argument, 0, 'd'},
		{"sectors", no_argument, 0, 'x'},
		{"version", no_argument, 0, 'V'},
		{"help", no_argument, 0, 'h'},
		{NULL, 0, 0, 0}
	};

	setlocale(LC_ALL, "");
	bindtextdomain(PACKAGE, LOCALEDIR);
	textdomain(PACKAGE);
	atexit(close_stdout);

	while ((opt = getopt_long(argc, argv, "d:xVh", longopts, NULL)) != -1)
		switch (opt) {
		case 'd':
			divisor =
			    strtol_or_err(optarg,
					  _("invalid divisor argument"));
			break;
		case 'x':
			xflag = 1;
			break;
		case 'V':
			printf(UTIL_LINUX_VERSION);
			return EXIT_SUCCESS;
		case 'h':
			usage(stdout);
		default:
			usage(stderr);
		}

	ct = argc - optind;

	if (ct <= 0)
		usage(stderr);

	for (j = optind; j < argc; j++)
		isosize(ct, argv[j], xflag, divisor);

	return EXIT_SUCCESS;
}
