usr/src/lib/libadr/common/adr_name.c
author David Powell <david.e.powell@oracle.com>
Tue, 23 Nov 2010 15:54:20 -0800
changeset 604 20d9acfeb7fb
parent 573 f0add9469f92
child 650 265ab927a268
permissions -rw-r--r--
17421 name key order preservation 17422 rad dynamic namespace support 17423 rad kstat module

/*
 * 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 (c) 2010, Oracle and/or its affiliates. All rights reserved.
 */

#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <assert.h>
#include <stdarg.h>
#include <stddef.h>
#include <inttypes.h>
#include <pthread.h>

#include "adr_name.h"

/*
 * For the most part an adr_name is what is looks like, with one exception.
 * All string values are stored in a string space pointed to by an_domain
 * (the beginning of which happens to be the domain name).  This greatly
 * simplifies allocation, freeing, and especially parsing, and increases
 * locality as well.
 */
struct adr_name {
	pthread_mutex_t an_lock;
	int an_refs;
	int an_count;
	char *an_domain;
	char **an_keys;		/* Unsorted keys */
	char **an_values;	/* Unsorted values */
	char **an_skeys;	/* Sorted keys */
	char **an_svalues;	/* Sorted values */
};

#define	zalloc(x)	calloc(1, x)

static void
adr_name_free(adr_name_t *name)
{
	free(name->an_domain);
	free(name->an_keys);
	free(name);
}

static adr_name_t *
adr_name_create_common(int n)
{
	adr_name_t *result = zalloc(sizeof (adr_name_t));
	if (result == NULL)
		return (NULL);

	(void) pthread_mutex_init(&result->an_lock, NULL);
	result->an_refs = 1;
	result->an_count = n;
	result->an_keys = zalloc(4 * n * sizeof (char *));
	result->an_values = &result->an_keys[n];
	result->an_skeys = &result->an_keys[2 * n];
	result->an_svalues = &result->an_keys[3 * n];
	if (result->an_keys == NULL) {
		free(result);
		return (NULL);
	}

	return (result);
}

/*
 * Sort keys and reject duplicates.
 */
static adr_name_t *
adr_name_normalize(adr_name_t *name)
{
	int c = name->an_count;
	for (int i = 0; i < c; i++) {
		name->an_skeys[i] = name->an_keys[i];
		name->an_svalues[i] = name->an_values[i];
	}

	for (int i = 0; i < c - 1; i++) {
		for (int j = i + 1; j < c; j++) {
			int res = strcmp(name->an_skeys[i], name->an_skeys[j]);
			if (res == 0) {
				adr_name_free(name);
				return (NULL);
			}
			if (res > 0) {
				char *ktmp = name->an_skeys[i];
				char *vtmp = name->an_svalues[i];
				name->an_skeys[i] = name->an_skeys[j];
				name->an_svalues[i] = name->an_svalues[j];
				name->an_skeys[j] = ktmp;
				name->an_svalues[j] = vtmp;
			}
		}
	}
	return (name);
}

/*
 * Copy key and value string pointers from input arrays to adr_name_t arrays.
 * Tally total space required for strings.
 */
static int
adr_name_acopy(adr_name_t *tgt, int off, int n,
    const char const * const *k, const char const * const *v)
{
	int len = 2 * n;
	for (int i = 0; i < n; i++) {
		assert(off + i < tgt->an_count);
		char *key = tgt->an_keys[off + i] = (char *)k[i];
		char *value = tgt->an_values[off + i] = (char *)v[i];
		len += strlen(key) + strlen(value);
	}

	return (len);
}

/*
 * Copy key and value string pointers from va_list to adr_name_t arrays.
 * Tally total space required for strings.
 */
static int
adr_name_vcopy(adr_name_t *tgt, int off, int n, va_list l)
{
	int len = 2 * n;
	for (int i = 0; i < n; i++) {
		assert(off + i < tgt->an_count);
		char *key = tgt->an_keys[off + i] = va_arg(l, char *);
		char *value = tgt->an_values[off + i] = va_arg(l, char *);
		len += strlen(key) + strlen(value);
	}

	return (len);
}

/*
 * Copies the strings pointed to by an_keys[] and an_values[] into the
 * name's string space and update the pointers to point to the copies.
 */
static boolean_t
adr_name_strcpy(adr_name_t *name, int length, const char *domain)
{
	if ((name->an_domain = zalloc(length)) == NULL)
		return (B_FALSE);

	char *loc = name->an_domain;
	char *end = name->an_domain + length;
	int count = strlcpy(loc, domain, end - loc);
	if (count >= end - loc)
		return (B_FALSE);
	loc += count + 1;

	for (int i = 0; i < name->an_count; i++) {
		count = strlcpy(loc, name->an_keys[i], end - loc);
		if (count >= end - loc)
			return (B_FALSE);
		name->an_keys[i] = loc;
		loc += count + 1;

		count = strlcpy(loc, name->an_values[i], end - loc);
		if (count >= end - loc)
			return (B_FALSE);
		name->an_values[i] = loc;
		loc += count + 1;
	}

	return (B_TRUE);
}

adr_name_t *
adr_name_create(const char *domain, int n,
    const char * const *k, const char * const *v)
{
	adr_name_t *result = adr_name_create_common(n);
	if (result == NULL)
		return (NULL);

	int length = strlen(domain) + 1;
	length += adr_name_acopy(result, 0, n, k, v);

	if (!adr_name_strcpy(result, length, domain)) {
		adr_name_free(result);
		return (NULL);
	}

	return (adr_name_normalize(result));
}

adr_name_t *
adr_name_vcreate(const char *domain, int n, ...)
{
	adr_name_t *result = adr_name_create_common(n);
	if (result == NULL)
		return (NULL);

	int length = strlen(domain) + 1;
	va_list l;
	va_start(l, n);
	length += adr_name_vcopy(result, 0, n, l);
	va_end(l);

	if (!adr_name_strcpy(result, length, domain)) {
		adr_name_free(result);
		return (NULL);
	}

	return (adr_name_normalize(result));
}

/*
 * Constructs a new adr_name_t by combining the new key-value pairs
 * with those from the original.  Will fail if there is overlap.
 */
adr_name_t *
adr_name_compose(const adr_name_t *name, int n, const char * const *k,
    const char * const *v)
{
	int cn = name->an_count + n;
	adr_name_t *result = adr_name_create_common(cn);
	if (result == NULL)
		return (NULL);

	int length = strlen(name->an_domain) + 1;
	length += adr_name_acopy(result, 0, name->an_count,
	    (const char **)name->an_keys, (const char **)name->an_values);
	length += adr_name_acopy(result, name->an_count, n, k, v);

	if (!adr_name_strcpy(result, length, name->an_domain)) {
		adr_name_free(result);
		return (NULL);
	}

	return (adr_name_normalize(result));
}

/*
 * Constructs a new adr_name_t by combining the new key-value pairs
 * with those from the original.  Will fail if there is overlap.
 */
adr_name_t *
adr_name_vcompose(const adr_name_t *name, int n, ...)
{
	int cn = name->an_count + n;
	adr_name_t *result = adr_name_create_common(cn);
	if (result == NULL)
		return (NULL);

	int length = strlen(name->an_domain) + 1;
	length += adr_name_acopy(result, 0, name->an_count,
	    (const char **)name->an_keys, (const char **)name->an_values);

	va_list l;
	va_start(l, n);
	length += adr_name_vcopy(result, name->an_count, n, l);
	va_end(l);

	if (!adr_name_strcpy(result, length, name->an_domain)) {
		adr_name_free(result);
		return (NULL);
	}

	return (adr_name_normalize(result));
}

const char *
adr_name_domain(const adr_name_t *name)
{
	return (name->an_domain);
}

const char *
adr_name_key(const adr_name_t *name, const char *key)
{
	for (int i = 0; i < name->an_count; i++)
		if (strcmp(name->an_keys[i], key) == 0)
			return (name->an_values[i]);

	return (NULL);
}

/*
 * Construct a name given a string.
 */
adr_name_t *
adr_name_fromstr_x(const char *str, boolean_t strict)
{
	int length = strlen(str) + 1;
	char *data;
	if ((data = zalloc(length)) == NULL)
		goto error;

	char *dst = data;
	char *pos = strchr(str, ':');
	if (pos == NULL || (strict && pos == str))
		goto error;

	(void) strncpy(dst, str, pos - str);
	dst += pos - str;
	(*dst++) = '\0';

	pos++;
	int keys = 0;
	char *start = pos;
	char *kvstart = dst;
	enum state { KEY, VALUE } mode = KEY;
	for (; *pos; pos++) {
		char c = *pos;
		if (c == ',') {
			if (pos == start || mode == KEY)
				goto error;
			keys++;
			(*dst++) = '\0';
			mode = KEY;
			start = pos + 1;
			continue;
		} else if (c == '=') {
			if (pos == start || mode == VALUE)
				goto error;
			(*dst++) = '\0';
			mode = VALUE;
			start = pos + 1;
			continue;
		} else if (c == '\\') {
			if (*(++pos) == '\0')
				goto error;
			switch (c = *pos) {
			case 'C':
				c = ',';
				break;
			case 'E':
				c = '=';
				break;
			case 'S':
				c = '\\';
				break;
			}
		}
		(*dst++) = c;
	}

	if (strict && (pos == start || mode == KEY) ||
	    !strict && (pos != start && mode == KEY ||
	    pos == start && mode == VALUE))
		goto error;
	if (mode == VALUE)
		keys++;
	(*dst++) = '\0';

	adr_name_t *result = adr_name_create_common(keys);
	if (result == NULL)
		return (NULL);
	result->an_domain = data;

	for (int i = 0; i < keys; i++) {
		result->an_keys[i] = kvstart;
		kvstart += strlen(kvstart) + 1;
		result->an_values[i] = kvstart;
		kvstart += strlen(kvstart) + 1;
	}

	return (adr_name_normalize(result));

error:
	free(data);
	return (NULL);
}

adr_name_t *
adr_name_fromstr(const char *str)
{
	return (adr_name_fromstr_x(str, B_TRUE));
}

/*
 * Count the number of characters unquoted string that will need quoting.
 */
static int
count_quoted(const char *str)
{
	int count = 0;
	for (char *loc = strpbrk(str, ",=\\");
	    loc != NULL; loc = strpbrk(loc + 1, ",=\\"))
		count++;
	return (count);
}

/*
 * Quote the string pointed to src, putting the result in dst.
 * dst must be large enough to hold strlen(src) + count_quoted(src).
 */
static int
strquote(char *dst, char *src)
{
	char c, nc;
	char *start = dst;

	while (nc = c = *src++) {
		switch (c) {
		case '\\':
			nc = 'S';
			break;
		case ',':
			nc = 'C';
			break;
		case '=':
			nc = 'E';
			break;
		}

		if (nc != c)
			*dst++ = '\\';
		*dst++ = nc;

	}

	return (dst - start);
}

/*
 * Convert a name into its canonical string form:
 *   domain:key1=value1,key2=value2,etc.
 */
char *
adr_name_tostr(const adr_name_t *name)
{
	int length = 1 + strlen(name->an_domain);
	for (int i = 0; i < name->an_count; i++) {
		length += count_quoted(name->an_keys[i]);
		length += count_quoted(name->an_values[i]);
		length += 2 + strlen(name->an_keys[i]) +
		    strlen(name->an_values[i]);
	}

	char *str = malloc(length);
	if (str == NULL)
		return (NULL);
	char *result = str;

	int cnt = snprintf(str, length, "%s", name->an_domain);
	length -= cnt;
	str += cnt;

	char sep = ':';
	for (int i = 0; i < name->an_count; i++) {
		*str++ = sep;
		str += strquote(str, name->an_keys[i]);
		*str++ = '=';
		str += strquote(str, name->an_values[i]);
		sep = ',';
	}
	*str++ = '\0';

	return (result);
}

/*
 * Compares two adr_name_ts.  Two adr_name_ts are equal if their domains
 * are equals, and they contain the same set of keys with the same values.
 */
int
adr_name_cmp(const adr_name_t *n1, const adr_name_t *n2)
{
	int res;

	res = strcmp(n1->an_domain, n2->an_domain);
	if (res != 0)
		return (res);

	if (n1->an_count < n2->an_count)
		return (-1);
	if (n1->an_count > n2->an_count)
		return (1);

	/* Compare pre-sorted key/value pairs */
	for (int i = 0; i < n1->an_count; i++) {
		res = strcmp(n1->an_skeys[i], n2->an_skeys[i]);
		if (res != 0)
			return (res);
		res = strcmp(n1->an_svalues[i], n2->an_svalues[i]);
		if (res != 0)
			return (res);
	}

	return (0);
}

/*
 * Compares an adr_name_t to a pattern.  A pattern is just a partial
 * adr_name_t.  A name is considered a match if everything present in
 * the pattern is present in the name and has the same value.
 */
boolean_t
adr_name_match(const adr_name_t *name, const adr_name_t *pattern)
{
	if (pattern == NULL)
		return (B_TRUE);

	if (pattern->an_domain[0] != '\0' &&
	    strcmp(name->an_domain, pattern->an_domain) != 0)
		return (B_FALSE);

	for (int i = 0; i < pattern->an_count; i++) {
		const char *value = adr_name_key(name, pattern->an_keys[i]);
		if (value == NULL || strcmp(value, pattern->an_values[i]) != 0)
			return (B_FALSE);
	}

	return (B_TRUE);
}

adr_name_t *
adr_name_hold(adr_name_t *name)
{
	(void) pthread_mutex_lock(&name->an_lock);
	name->an_refs++;
	(void) pthread_mutex_unlock(&name->an_lock);
	return (name);
}

void
adr_name_rele(adr_name_t *name)
{
	if (name == NULL)
		return;
	(void) pthread_mutex_lock(&name->an_lock);
	if (--name->an_refs == 0) {
		adr_name_free(name);
		return;
	}
	(void) pthread_mutex_unlock(&name->an_lock);
}