/*
 * CDDL HEADER START
 *
 * The contents of this file are subject to the terms of the
 * Common Development and Distribution License (the "License").
 * You may not use this file except in compliance with the License.
 *
 * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
 * or http://www.opensolaris.org/os/licensing.
 * See the License for the specific language governing permissions
 * and limitations under the License.
 *
 * When distributing Covered Code, include this CDDL HEADER in each
 * file and include the License file at usr/src/OPENSOLARIS.LICENSE.
 * If applicable, add the following below this CDDL HEADER, with the
 * fields enclosed by brackets "[]" replaced with your own identifying
 * information: Portions Copyright [yyyy] [name of copyright owner]
 *
 * CDDL HEADER END
 */
/*
 * Copyright 2006 Sun Microsystems, Inc.  All rights reserved.
 * Use is subject to license terms.
 */

#pragma ident	"%Z%%M%	%I%	%E% SMI"

/*
 * Label a file system volume.
 */


#include <stdio.h>
#include <string.h>
#include <strings.h>
#include <stdlib.h>
#include <unistd.h>
#include <fcntl.h>
#include <locale.h>
#include <errno.h>
#include <sys/fcntl.h>
#include <sys/param.h>
#include <sys/types.h>
#include <sys/mntent.h>

#include <sys/fs/udf_volume.h>
#include "ud_lib.h"

static uint8_t buf[MAXBSIZE];
static uint64_t off;
#define	BUF_LEN	0x200
static int8_t	lvinfo1_buf[BUF_LEN];
static int8_t	lvinfo2_buf[BUF_LEN];
static int8_t	lvinfo3_buf[BUF_LEN];
static int8_t	fsname[BUF_LEN];
static int8_t	volname[BUF_LEN];
static int32_t fsname_len;

#define	SET_LVINFO1	0x01
#define	SET_LVINFO2	0x02
#define	SET_LVINFO3	0x04
#define	SET_FSNAME	0x08
#define	SET_VOLNAME	0x10

typedef unsigned short unicode_t;

#define	FSNAME_STR_LEN	(8 + 2)
#define	VOLNAME_STR_LEN	32
#define	INFO_STR_LEN	36

static void usage();
static void label(ud_handle_t, uint32_t);
static void print_info(struct vds *, char *, ud_handle_t);
static void label_vds(struct vds *, uint32_t, ud_handle_t);
static int32_t convert_string(int8_t *, int8_t *, int32_t, int32_t, int8_t *);
static int32_t ud_convert2unicode(int8_t *, int8_t *, int32_t);


int8_t *labelit_subopts[] = {
#define	LVINFO1	0x00
	"lvinfo1",
#define	LVINFO2	0x01
	"lvinfo2",
#define	LVINFO3	0x02
	"lvinfo3",
	NULL};


int
main(int32_t argc, char *argv[])
{
	int32_t		opt = 0;
	int32_t		flags = 0;
	int32_t		ret = 0;
	int8_t		*options = NULL;
	int8_t		*value = NULL;
	uint32_t	set_flags = 0;
	ud_handle_t	udh;

	(void) setlocale(LC_ALL, "");

#if !defined(TEXT_DOMAIN)
#define	TEXT_DOMAIN	"SYS_TEST"
#endif

	(void) textdomain(TEXT_DOMAIN);


	while ((opt = getopt(argc, argv, "F:o:")) != EOF) {
		switch (opt) {
		case 'F':
			if (strcmp(optarg, "udfs") != 0) {
				usage();
			}
			break;

		case 'o':
			/*
			 * UDFS specific options
			 */
			options = optarg;
			while (*options != '\0') {
				switch (getsubopt(&options, labelit_subopts,
						&value)) {
				case LVINFO1 :
					set_flags |= SET_LVINFO1;
					(void) convert_string(value,
						lvinfo1_buf, BUF_LEN,
						INFO_STR_LEN,
			gettext("udfs labelit: lvinfo1 should be less than "
			"36 bytes after converting to compressed unicode "
			"dstring\n"));
					break;
				case LVINFO2 :
					set_flags |= SET_LVINFO2;
					(void) convert_string(value,
						lvinfo2_buf, BUF_LEN,
						INFO_STR_LEN,
			gettext("udfs labelit: lvinfo2 should be less than "
			"36 bytes after converting to compressed unicode "
			"dstring\n"));
					break;
				case LVINFO3 :
					set_flags |= SET_LVINFO3;
					(void) convert_string(value,
						lvinfo3_buf, BUF_LEN,
						INFO_STR_LEN,
			gettext("udfs labelit: lvinfo3 should be less than "
			"36 bytes after converting to compressed unicode "
			"dstring\n"));
					break;
				default:
					(void) fprintf(stderr,
			gettext("udfs labelit: Unknown suboption %s\n"), value);
					usage();
					break;
				}
			}
			break;

		case '?':
			usage();
		}
	}

	if ((argc - optind) == 3) {

		/*
		 * There are restrictions on the
		 * length of the names
		 * fsname is 8 characters
		 * volume name is 32 characters
		 * The extra byte is for compression id
		 */
		fsname_len = convert_string(argv[optind + 1],
				fsname, BUF_LEN, FSNAME_STR_LEN,
	gettext("udfs labelit: fsname can not be longer than 8 characters\n"));

		(void) convert_string(argv[optind + 2],
				volname, BUF_LEN, VOLNAME_STR_LEN,
		gettext("udfs labelit: volname can not be longer "
			"than 32 bytes after converting to "
			"compressed unicode dstring\n"));
		set_flags |= SET_FSNAME | SET_VOLNAME;
	} else {
		if ((argc - optind) != 1) {
			usage();
		}
	}

	if (ud_init(-1, &udh) != 0) {
		(void) fprintf(stderr,
		gettext("udfs labelit: cannot initialize ud_lib\n"));
		exit(1);
	}

	/*
	 * Open special device
	 */
	if (set_flags == 0) {
		flags = O_RDONLY;
	} else {
		flags = O_RDWR;
	}
	if (ud_open_dev(udh, argv[optind], flags) != 0) {
		(void) fprintf(stderr,
		gettext("udfs labelit: cannot open <%s> errorno <%d>\n"),
					argv[optind], errno);
		exit(1);
	}

	if ((ret = ud_fill_udfs_info(udh)) != 0) {
		goto close_dev;
	}

	if ((udh->udfs.flags & VALID_UDFS) == 0) {
		ret = 1;
		goto close_dev;
	}

	label(udh, set_flags);

close_dev:
	ud_close_dev(udh);
	ud_fini(udh);

	return (ret);
}

static void
usage()
{
	(void) fprintf(stderr, gettext(
		"udfs usage: labelit [-F udfs] [generic options] "
		"[ -o specific_options ] special [fsname volume]\n"));
	(void) fprintf(stderr, gettext(
		" -o : specific_options : [lvinfo1=string],"
		"[lvinfo2=string],[lvinfo3=string]\n"));
	(void) fprintf(stderr,
		gettext("NOTE that all -o suboptions: must"
		" be separated only by commas.\n"));
	exit(1);
}

static void
label(ud_handle_t udh, uint32_t set_flags)
{
	if (set_flags == 0) {
		if (udh->udfs.flags & VALID_MVDS) {
			print_info(&udh->udfs.mvds, "mvds", udh);
		}
		if (udh->udfs.flags & VALID_RVDS) {
			print_info(&udh->udfs.rvds, "rvds", udh);
		}
		return;
	} else {

		if (udh->udfs.flags & VALID_MVDS) {
			label_vds(&udh->udfs.mvds, set_flags, udh);
		}
		if (udh->udfs.flags & VALID_RVDS) {
			label_vds(&udh->udfs.rvds, set_flags, udh);
		}
		if (((set_flags & (SET_FSNAME | SET_VOLNAME)) ==
			(SET_FSNAME | SET_VOLNAME)) &&
			(udh->udfs.fsd_len != 0)) {
			struct file_set_desc *fsd;

			off = udh->udfs.fsd_loc * udh->udfs.lbsize;
			if (ud_read_dev(udh, off, buf,
				udh->udfs.fsd_len) != 0) {
				return;
			}

			/* LINTED */
			fsd = (struct file_set_desc *)buf;

			set_dstring(fsd->fsd_lvid,
				volname, sizeof (fsd->fsd_lvid));
			set_dstring(fsd->fsd_fsi,
				volname, sizeof (fsd->fsd_fsi));

			ud_make_tag(udh, &fsd->fsd_tag, UD_FILE_SET_DESC,
				SWAP_32(fsd->fsd_tag.tag_loc),
				SWAP_16(fsd->fsd_tag.tag_crc_len));

			(void) ud_write_dev(udh, off, buf, udh->udfs.fsd_len);
		}
	}
}

static void
print_info(struct vds *v, char *name, ud_handle_t udh)
{
	uint8_t		outbuf[BUF_LEN];

	if (v->pvd_len != 0) {
		off = v->pvd_loc * udh->udfs.lbsize;
		if (ud_read_dev(udh, off, buf,
			sizeof (struct pri_vol_desc)) == 0) {

			struct pri_vol_desc *pvd;

			/* LINTED */
			pvd = (struct pri_vol_desc *)buf;

			bzero(outbuf, BUF_LEN);
			(void) ud_convert2local(
					(int8_t *)pvd->pvd_vsi,
					(int8_t *)outbuf, strlen(pvd->pvd_vsi));
			(void) fprintf(stdout,
				gettext("fsname in  %s : %s\n"),
					name, outbuf);

			bzero(outbuf, BUF_LEN);
			pvd->pvd_vol_id[31] = '\0';
			(void) ud_convert2local(
					(int8_t *)pvd->pvd_vol_id,
					(int8_t *)outbuf,
					strlen(pvd->pvd_vol_id));
			(void) fprintf(stdout,
				gettext("volume label in %s : %s\n"),
					name, outbuf);
		}
	}

	if (v->iud_len != 0) {
		off = v->iud_loc * udh->udfs.lbsize;
		if (ud_read_dev(udh, off, buf,
			sizeof (struct iuvd_desc)) == 0) {

			struct iuvd_desc *iud;

			/* LINTED */
			iud = (struct iuvd_desc *)buf;
			bzero(outbuf, BUF_LEN);
			iud->iuvd_ifo1[35] = '\0';
			(void) ud_convert2local(
					(int8_t *)iud->iuvd_ifo1,
					(int8_t *)outbuf,
					strlen(iud->iuvd_ifo1));
			(void) fprintf(stdout,
				gettext("LVInfo1 in  %s : %s\n"),
					name, outbuf);

			bzero(outbuf, BUF_LEN);
			iud->iuvd_ifo2[35] = '\0';
			(void) ud_convert2local(
					(int8_t *)iud->iuvd_ifo2,
					(int8_t *)outbuf,
					strlen(iud->iuvd_ifo2));
			(void) fprintf(stdout,
				gettext("LVInfo2 in  %s : %s\n"),
					name, outbuf);

			bzero(outbuf, BUF_LEN);
			iud->iuvd_ifo3[35] = '\0';
			(void) ud_convert2local(
					(int8_t *)iud->iuvd_ifo3,
					(int8_t *)outbuf,
					strlen(iud->iuvd_ifo3));
			(void) fprintf(stdout,
				gettext("LVInfo3 in  %s : %s\n"),
					name, outbuf);
		}
	}
}

/* ARGSUSED */
static void
label_vds(struct vds *v, uint32_t set_flags, ud_handle_t udh)
{

	if (((set_flags & (SET_FSNAME | SET_VOLNAME)) ==
		(SET_FSNAME | SET_VOLNAME)) &&
		(v->pvd_len)) {

		off = v->pvd_loc * udh->udfs.lbsize;
		if (ud_read_dev(udh, off, buf,
			sizeof (struct pri_vol_desc)) == 0) {

			struct pri_vol_desc *pvd;

			/* LINTED */
			pvd = (struct pri_vol_desc *)buf;
			bzero((int8_t *)&pvd->pvd_vsi[9], 119);
			(void) strncpy((int8_t *)&pvd->pvd_vsi[9],
					&fsname[1], fsname_len - 1);

			set_dstring(pvd->pvd_vol_id,
				volname, sizeof (pvd->pvd_vol_id));

			ud_make_tag(udh, &pvd->pvd_tag,
				SWAP_16(pvd->pvd_tag.tag_id),
				SWAP_32(pvd->pvd_tag.tag_loc),
				SWAP_16(pvd->pvd_tag.tag_crc_len));

			(void) ud_write_dev(udh, off, buf,
				sizeof (struct pri_vol_desc));
		}
	}

	if (set_flags && v->iud_len) {

		off = v->iud_loc * udh->udfs.lbsize;
		if (ud_read_dev(udh, off, buf,
			sizeof (struct iuvd_desc)) == 0) {

			struct iuvd_desc *iuvd;

			/* LINTED */
			iuvd = (struct iuvd_desc *)buf;

			if ((set_flags & SET_VOLNAME) == SET_VOLNAME) {
				set_dstring(iuvd->iuvd_lvi,
					volname, sizeof (iuvd->iuvd_lvi));
			}
			if ((set_flags & SET_LVINFO1) == SET_LVINFO1) {
				set_dstring(iuvd->iuvd_ifo1,
					lvinfo1_buf, sizeof (iuvd->iuvd_ifo1));
			}
			if ((set_flags & SET_LVINFO2) == SET_LVINFO2) {
				set_dstring(iuvd->iuvd_ifo2,
					lvinfo2_buf, sizeof (iuvd->iuvd_ifo2));
			}
			if ((set_flags & SET_LVINFO3) == SET_LVINFO3) {
				set_dstring(iuvd->iuvd_ifo3,
					lvinfo3_buf, sizeof (iuvd->iuvd_ifo3));
			}

			ud_make_tag(udh, &iuvd->iuvd_tag,
				SWAP_16(iuvd->iuvd_tag.tag_id),
				SWAP_32(iuvd->iuvd_tag.tag_loc),
				SWAP_16(iuvd->iuvd_tag.tag_crc_len));

			(void) ud_write_dev(udh, off, buf,
				sizeof (struct iuvd_desc));
		}
	}

	if (((set_flags & (SET_FSNAME | SET_VOLNAME)) ==
		(SET_FSNAME | SET_VOLNAME)) &&
		(v->lvd_len)) {

		off = v->lvd_loc * udh->udfs.lbsize;
		if (ud_read_dev(udh, off, buf,
			sizeof (struct log_vol_desc)) == 0) {

			struct log_vol_desc *lvd;

			/* LINTED */
			lvd = (struct log_vol_desc *)buf;
			set_dstring(lvd->lvd_lvid,
				volname, sizeof (lvd->lvd_lvid));

			ud_make_tag(udh, &lvd->lvd_tag,
				SWAP_16(lvd->lvd_tag.tag_id),
				SWAP_32(lvd->lvd_tag.tag_loc),
				SWAP_16(lvd->lvd_tag.tag_crc_len));

			(void) ud_write_dev(udh, off, buf,
				sizeof (struct log_vol_desc));
		}
	}
}


int32_t
convert_string(int8_t *value, int8_t *out_buf, int32_t out_len,
	int32_t len, int8_t *error_string)
{
	int32_t		out_length = 0;

	out_length = ud_convert2unicode(value, out_buf, out_len);
	if (out_length > len - 1) {
		(void) fprintf(stderr, "%s", error_string);
		exit(1);
	}

	return (out_length);
}

static int32_t
ud_convert2unicode(int8_t *mb, int8_t *comp, int32_t out_len)
{
	wchar_t		buf4c[128];
	int32_t		len = 0;
	int32_t		i = 0;
	int32_t		j = 0;
	uint8_t		c = 8;

	len = mbstowcs(buf4c, mb, 127);
	buf4c[127] = '\0';

	for (i = 0; i < len; i++) {
		if (buf4c[i] & 0xFFFFFF00) {
			c = 16;
			break;
		}
	}

	comp[0] = c;
	j = 1;
	for (i = 0; i < len && i < out_len; i++) {
		if (c == 16) {
			comp[j] = (buf4c[i] & 0xFF00) >> 8;
		}
		comp[j++] = buf4c[i] & 0xFF;
	}

	return (j);
}