/*
* 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) 2012, 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);
}
int
adr_name_nkeys(const adr_name_t *name)
{
return (name->an_count);
}
const char *
adr_name_key_n(const adr_name_t *name, int n)
{
return (name->an_keys[n]);
}
const char *
adr_name_value_n(const adr_name_t *name, int n)
{
return (name->an_values[n]);
}
/*
* 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)
goto error;
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 in an 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;
/* LINTED */
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)
{
/* domain + ":" + '\0' */
int length = 2 + 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]);
/* key + '=' + value */
length += strlen(name->an_keys[i]) + 1 +
strlen(name->an_values[i]);
if (i != 0) {
/* ',' separator */
length++;
}
}
char *str = malloc(length);
if (str == NULL)
return (NULL);
char *result = str;
str += snprintf(str, length, "%s:", name->an_domain);
for (int i = 0; i < name->an_count; i++) {
if (i != 0) {
*str++ = ',';
}
str += strquote(str, name->an_keys[i]);
*str++ = '=';
str += strquote(str, name->an_values[i]);
}
*str++ = '\0';
/* Ensure we've allocated exactly what we need */
assert(length == strlen(result) + 1);
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);
}