/*	$OpenBSD: udf_subr.c,v 1.25 2014/12/16 18:30:03 tedu Exp $	*/

/*
 * Copyright (c) 2006, Miodrag Vallat
 * Copyright (c) 2006, Pedro Martelletto
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 * 1. Redistributions of source code must retain the above copyright
 *    notice, this list of conditions and the following disclaimer.
 * 2. Redistributions in binary form must reproduce the above copyright
 *    notice, this list of conditions and the following disclaimer in the
 *    documentation and/or other materials provided with the distribution.
 *
 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
 * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
 * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
 * DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT,
 * INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
 * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
 * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
 * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
 * ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
 * POSSIBILITY OF SUCH DAMAGE.
 */

#include <sys/param.h>
#include <sys/systm.h>
#include <sys/buf.h>
#include <sys/kernel.h>
#include <sys/malloc.h>
#include <sys/mutex.h>
#include <sys/stat.h>
#include <sys/mount.h>
#include <sys/vnode.h>
#include <sys/lock.h>
#include <sys/dirent.h>
#include <sys/disklabel.h>

#include <crypto/siphash.h>

#include <isofs/udf/ecma167-udf.h>
#include <isofs/udf/udf.h>
#include <isofs/udf/udf_extern.h>

int udf_vat_read(struct umount *, uint32_t *);

/*
 * Convert a CS0 dstring to a 16-bit Unicode string.
 * Returns the length of the Unicode string, in unicode characters (not
 * bytes!), or -1 if an error arises.
 * Note that the transname destination buffer is expected to be large
 * enough to hold the result, and will not be terminated in any way.
 */
int
udf_rawnametounicode(u_int len, char *cs0string, unicode_t *transname)
{
	unicode_t *origname = transname;

	if (len-- == 0)
		return (-1);

	switch (*cs0string++) {
	case 8:		/* bytes string */
		while (len-- != 0)
			*transname++ = (unicode_t)*cs0string++;
		break;
	case 16:	/* 16 bit unicode string */
		if (len & 1)
			return (-1);
		len >>= 1;
		while (len-- != 0) {
			unicode_t tmpchar;

			tmpchar = (unicode_t)*cs0string++;
			tmpchar = (tmpchar << 8) | (unicode_t)*cs0string++;
			*transname++ = tmpchar;
		}
		break;
	default:
		return (-1);
	}

	return (transname - origname);
}

/*
 * Do a lazy probe on the underlying media to check if it's a UDF volume, in
 * which case we fake a disk label for it.
 */
int
udf_disklabelspoof(dev_t dev, void (*strat)(struct buf *),
    struct disklabel *lp)
{
	char vid[32];
	int i, bsize = 2048, error = EINVAL;
	uint32_t sector = 256, mvds_start, mvds_end;
	struct buf *bp;
	struct anchor_vdp avdp;
	struct pri_vol_desc *pvd;

	/*
	 * Get a buffer to work with.
	 */
	bp = geteblk(bsize);
	bp->b_dev = dev;

	/*
	 * Look for an Anchor Volume Descriptor at sector 256.
	 */
	bp->b_blkno = sector * btodb(bsize);
	bp->b_bcount = bsize;
	CLR(bp->b_flags, B_READ | B_WRITE | B_DONE);
	SET(bp->b_flags, B_BUSY | B_READ | B_RAW);
	bp->b_resid = bp->b_blkno / lp->d_secpercyl;

	(*strat)(bp);
	if (biowait(bp))
		goto out;

	if (udf_checktag((struct desc_tag *)bp->b_data, TAGID_ANCHOR))
		goto out;

	bcopy(bp->b_data, &avdp, sizeof(avdp));
	mvds_start = letoh32(avdp.main_vds_ex.loc);
	mvds_end = mvds_start + (letoh32(avdp.main_vds_ex.len) - 1) / bsize;

	/*
	 * Then try to find a reference to a Primary Volume Descriptor.
	 */
	for (sector = mvds_start; sector < mvds_end; sector++) {
		bp->b_blkno = sector * btodb(bsize);
		bp->b_bcount = bsize;
		CLR(bp->b_flags, B_READ | B_WRITE | B_DONE);
		SET(bp->b_flags, B_BUSY | B_READ | B_RAW);
		bp->b_resid = bp->b_blkno / lp->d_secpercyl;

		(*strat)(bp);
		if (biowait(bp))
			goto out;

		pvd = (struct pri_vol_desc *)bp->b_data;
		if (!udf_checktag(&pvd->tag, TAGID_PRI_VOL))
			break;
	}

	/*
	 * If we couldn't find a reference, bail out.
	 */
	if (sector == mvds_end)
		goto out;

	/*
	 * Okay, it's a UDF volume. Spoof a disk label for it.
	 */
	if (udf_transname(pvd->vol_id, vid, sizeof(pvd->vol_id) - 1, NULL))
		strlcpy(lp->d_typename, vid, sizeof(lp->d_typename));

	for (i = 0; i < MAXPARTITIONS; i++) {
		DL_SETPSIZE(&lp->d_partitions[i], 0);
		DL_SETPOFFSET(&lp->d_partitions[i], 0);
	}

	/*
	 * Fake two partitions, 'a' and 'c'.
	 */
	DL_SETPSIZE(&lp->d_partitions[0], DL_GETDSIZE(lp));
	lp->d_partitions[0].p_fstype = FS_UDF;
	DL_SETPSIZE(&lp->d_partitions[RAW_PART], DL_GETDSIZE(lp));
	lp->d_partitions[RAW_PART].p_fstype = FS_UDF;
	lp->d_npartitions = MAXPARTITIONS;
	lp->d_version = 1;

	lp->d_bbsize = 8192;	/* Fake. */
	lp->d_sbsize = 64*1024;	/* Fake. */
	lp->d_magic = DISKMAGIC;
	lp->d_magic2 = DISKMAGIC;
	lp->d_checksum = dkcksum(lp);

	error = 0;
out:
	bp->b_flags |= B_INVAL;
	brelse(bp);

	return (error);
}

/* Get a vnode for the Virtual Allocation Table (VAT) */
int
udf_vat_get(struct umount *ump, uint32_t lb)
{
	struct vnode *vp;
	struct unode *up;
	int error;

	error = udf_vget(ump->um_mountp, lb - ump->um_start - 3, &vp);
	if (error)
		return (error);

	up = VTOU(vp);
	up->u_vatlen = (letoh64(up->u_fentry->inf_len) - 36) >> 2;

	ump->um_vat = malloc(sizeof(struct unode), M_UDFMOUNT, M_WAITOK);
       *ump->um_vat = *up;

	ump->um_flags &= ~UDF_MNT_FIND_VAT;
	ump->um_flags |=  UDF_MNT_USES_VAT;

	vput(vp);

	return (0);
}

/* Look up a sector in the VAT */
int
udf_vat_map(struct umount *ump, uint32_t *sector)
{
	/* If there's no VAT, then it's easy */
	if (!(ump->um_flags & UDF_MNT_USES_VAT)) {
		*sector += ump->um_start;
		return (0);
	}

	/* Sanity check the given sector */
	if (*sector >= ump->um_vat->u_vatlen)
		return (EINVAL);

	return (udf_vat_read(ump, sector));
}

/* Read from the VAT */
int
udf_vat_read(struct umount *ump, uint32_t *sector)
{
	struct buf *bp;
	uint8_t *data;
	int error, size;

	size = 4;

	/*
	 * Note that we rely on the buffer cache to keep frequently accessed
	 * buffers around to avoid reading them from the disk all the time.
	 */
	error = udf_readatoffset(ump->um_vat, &size, *sector << 2, &bp, &data);
	if (error) {
		if (bp != NULL)
			brelse(bp);

		return (error);
	}

	/* Make sure we read at least a whole entry */
	if (size < 4) {
		if (bp != NULL)
			brelse(bp);

		return (EINVAL);
	}

	/* Map the sector */
	*sector = letoh32(*(uint32_t *)data) + ump->um_start;

	brelse(bp);

	return (0);
}
