/*
 * Copyright (C) 2001-2004 Sistina Software, Inc. All rights reserved.  
 * Copyright (C) 2004 Red Hat, Inc. All rights reserved.
 *
 * This file is part of LVM2.
 *
 * This copyrighted material is made available to anyone wishing to use,
 * modify, copy, or redistribute it subject to the terms and conditions
 * of the GNU General Public License v.2.
 *
 * 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., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
 */

#include "lib.h"
#include "uuid.h"

#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>

static unsigned char _c[] =
    "0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ!#";

static int _built_inverse;
static unsigned char _inverse_c[256];

int lvid_create(union lvid *lvid, struct id *vgid)
{
	memcpy(lvid->id, vgid, sizeof(*lvid->id));
	id_create(&lvid->id[1]);

	return 1;
}

void uuid_from_num(char *uuid, uint32_t num)
{
	unsigned i;

	for (i = ID_LEN; i; i--) {
		uuid[i - 1] = _c[num % (sizeof(_c) - 1)];
		num /= sizeof(_c) - 1;
	}

	uuid[ID_LEN] = '\0';
}

int lvid_from_lvnum(union lvid *lvid, struct id *vgid, uint32_t lv_num)
{
	int i;

	memcpy(lvid->id, vgid, sizeof(*lvid->id));

	for (i = ID_LEN; i; i--) {
		lvid->id[1].uuid[i - 1] = _c[lv_num % (sizeof(_c) - 1)];
		lv_num /= sizeof(_c) - 1;
	}

	lvid->s[sizeof(lvid->s) - 1] = '\0';

	return 1;
}

int lvnum_from_lvid(union lvid *lvid)
{
	int i, lv_num = 0;
	unsigned char *c;

	for (i = 0; i < ID_LEN; i++) {
		lv_num *= sizeof(_c) - 1;
		if ((c = strchr(_c, lvid->id[1].uuid[i])))
			lv_num += (int) (c - _c);
	}

	return lv_num;
}

int id_create(struct id *id)
{
	int randomfile, i;
	size_t len = sizeof(id->uuid);

	memset(id->uuid, 0, len);
	if ((randomfile = open("/dev/urandom", O_RDONLY)) < 0) {
		log_sys_error("open", "id_create");
		return 0;
	}

	if (read(randomfile, id->uuid, len) != len) {
		log_sys_error("read", "id_create");
		close(randomfile);
		return 0;
	}
	close(randomfile);

        /*
         * Skip out the last 2 chars in randomized creation for LVM1
         * backwards compatibility.
         */
	for (i = 0; i < len; i++)
		id->uuid[i] = _c[id->uuid[i] % (sizeof(_c) - 3)];

	return 1;
}

/*
 * The only validity check we have is that
 * the uuid just contains characters from
 * '_c'.  A checksum would have been nice :(
 */
static void _build_inverse(void)
{
	char *ptr;

	if (_built_inverse)
		return;

	memset(_inverse_c, 0, sizeof(_inverse_c));

	for (ptr = _c; *ptr; ptr++)
		_inverse_c[(int) *ptr] = (char) 0x1;
}

int id_valid(struct id *id)
{
	int i;

	_build_inverse();

	for (i = 0; i < ID_LEN; i++)
		if (!_inverse_c[id->uuid[i]]) {
			log_err("UUID contains invalid character");
			return 0;
		}

	return 1;
}

int id_equal(const struct id *lhs, const struct id *rhs)
{
	return !memcmp(lhs->uuid, rhs->uuid, sizeof(lhs->uuid));
}

#define GROUPS (ID_LEN / 4)

int id_write_format(const struct id *id, char *buffer, size_t size)
{
	int i, tot;

	static unsigned group_size[] = { 6, 4, 4, 4, 4, 4, 6 };

	assert(ID_LEN == 32);

	/* split into groups separated by dashes */
	if (size < (32 + 6 + 1)) {
		log_err("Couldn't write uuid, buffer too small.");
		return 0;
	}

	for (i = 0, tot = 0; i < 7; i++) {
		memcpy(buffer, id->uuid + tot, group_size[i]);
		buffer += group_size[i];
		tot += group_size[i];
		*buffer++ = '-';
	}

	*--buffer = '\0';
	return 1;
}

int id_read_format(struct id *id, const char *buffer)
{
	int out = 0;

	/* just strip out any dashes */
	while (*buffer) {

		if (*buffer == '-') {
			buffer++;
			continue;
		}

		if (out >= ID_LEN) {
			log_err("Too many characters to be uuid.");
			return 0;
		}

		id->uuid[out++] = *buffer++;
	}

	if (out != ID_LEN) {
		log_err("Couldn't read uuid, incorrect number of characters.");
		return 0;
	}

	return id_valid(id);
}
