diff options
Diffstat (limited to 'utils/mcpu-config.c')
| -rw-r--r-- | utils/mcpu-config.c | 704 |
1 files changed, 704 insertions, 0 deletions
diff --git a/utils/mcpu-config.c b/utils/mcpu-config.c new file mode 100644 index 0000000..2d44d27 --- /dev/null +++ b/utils/mcpu-config.c @@ -0,0 +1,704 @@ +#ifdef HAVE_CONFIG_H +#include <config.h> +#endif + +#include <errno.h> +#include <stdint.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> + +#include <libmpuio.h> + +#include "mcpu-config.h" + +typedef struct mcpu_text mcpu_text; +struct mcpu_text +{ + __mpu_char16_t *data; + size_t length; + size_t capacity; +}; + +static void +mcpu_text_init( mcpu_text *text ) +{ + if( text == NULL ) + return; + + text->data = NULL; + text->length = 0; + text->capacity = 0; +} + +static void +mcpu_text_free( mcpu_text *text ) +{ + if( text == NULL ) + return; + + free( text->data ); + text->data = NULL; + text->length = 0; + text->capacity = 0; +} + +static int +mcpu_text_reserve( mcpu_text *text, size_t need ) +{ + __mpu_char16_t *p; + size_t capacity; + + if( text == NULL ) + return( -1 ); + + if( need <= text->capacity ) + return( 0 ); + + capacity = text->capacity ? text->capacity : 256; + while( capacity < need ) + { + if( capacity > SIZE_MAX / 2 ) + { + errno = EOVERFLOW; + return( -1 ); + } + capacity *= 2; + } + + if( capacity > SIZE_MAX / sizeof(__mpu_char16_t) ) + { + errno = EOVERFLOW; + return( -1 ); + } + + p = (__mpu_char16_t *)realloc( text->data, + capacity * sizeof(__mpu_char16_t) ); + if( p == NULL ) + return( -1 ); + + text->data = p; + text->capacity = capacity; + return( 0 ); +} + +static int +mcpu_text_append( mcpu_text *text, + const __mpu_char16_t *data, size_t length ) +{ + if( text == NULL || (data == NULL && length != 0) ) + { + errno = EINVAL; + return( -1 ); + } + + if( length > SIZE_MAX - text->length - 1 ) + { + errno = EOVERFLOW; + return( -1 ); + } + + if( mcpu_text_reserve(text, text->length + length + 1) != 0 ) + return( -1 ); + + if( length != 0 ) + memcpy( text->data + text->length, data, + length * sizeof(__mpu_char16_t) ); + + text->length += length; + text->data[text->length] = 0; + return( 0 ); +} + +static int +mcpu_text_from_utf8( mcpu_text *text, const char *s ) +{ + __mpu_char16_t *u; + __mpu_size_t n; + int rc; + + if( text == NULL || s == NULL ) + { + errno = EINVAL; + return( -1 ); + } + + mcpu_text_free( text ); + mcpu_text_init( text ); + + n = mpu_utf8_to_ucs2( NULL, (const __mpu_char8_t *)s, 0 ); + if( n == (__mpu_size_t)-1 ) + return( -1 ); + + u = (__mpu_char16_t *)calloc( (size_t)n + 1, sizeof(__mpu_char16_t) ); + if( u == NULL ) + return( -1 ); + + if( mpu_utf8_to_ucs2(u, (const __mpu_char8_t *)s, + (size_t)n + 1) == (__mpu_size_t)-1 ) + { + free( u ); + return( -1 ); + } + + rc = mcpu_text_append( text, u, (size_t)n ); + free( u ); + return( rc ); +} + +static char * +mcpu_text_to_utf8( const __mpu_char16_t *data, size_t length ) +{ + __mpu_char16_t *tmp; + __mpu_char8_t *out; + __mpu_size_t n; + + if( data == NULL && length != 0 ) + { + errno = EINVAL; + return( NULL ); + } + + tmp = (__mpu_char16_t *)calloc( length + 1, sizeof(__mpu_char16_t) ); + if( tmp == NULL ) + return( NULL ); + + if( length != 0 ) + memcpy( tmp, data, length * sizeof(__mpu_char16_t) ); + + n = mpu_ucs2_to_utf8( NULL, tmp, 0 ); + if( n == (__mpu_size_t)-1 ) + { + free( tmp ); + return( NULL ); + } + + out = (__mpu_char8_t *)calloc( (size_t)n + 1, 1 ); + if( out == NULL ) + { + free( tmp ); + return( NULL ); + } + + if( mpu_ucs2_to_utf8(out, tmp, (size_t)n + 1) == (__mpu_size_t)-1 ) + { + free( out ); + free( tmp ); + return( NULL ); + } + + free( tmp ); + return( (char *)out ); +} + + +static char * +read_bytes( const char *filename, size_t *file_length ) +{ + FILE *fp; + char *data; + long size; + size_t n; + + if( file_length == NULL ) + { + errno = EINVAL; + return( NULL ); + } + + *file_length = 0; + + fp = fopen( filename, "rb" ); + if( fp == NULL ) + return( NULL ); + + if( fseek( fp, 0, SEEK_END ) != 0 ) + { + fclose( fp ); + return( NULL ); + } + + size = ftell( fp ); + if( size < 0 || fseek( fp, 0, SEEK_SET ) != 0 ) + { + fclose( fp ); + return( NULL ); + } + + data = (char *)calloc( (size_t)size + 1, 1 ); + if( data == NULL ) + { + fclose( fp ); + return( NULL ); + } + + n = fread( data, 1, (size_t)size, fp ); + if( n != (size_t)size && ferror(fp) ) + { + free( data ); + fclose( fp ); + return( NULL ); + } + + data[n] = 0; + fclose( fp ); + *file_length = n; + + return( data ); +} + +static int +is_space16( __mpu_char16_t c ) +{ + return( c == ' ' || c == '\t' || c == '\r' || c == '\n' || + c == '\f' || c == '\v' ); +} + +static int +is_name16( __mpu_char16_t c ) +{ + return( (c >= 'a' && c <= 'z') || + (c >= 'A' && c <= 'Z') || + (c >= '0' && c <= '9') || c == '_' ); +} + +static void +skip_space_and_comments( const __mpu_char16_t *s, size_t n, size_t *pos ) +{ + size_t p = *pos; + + for( ;; ) + { + while( p < n && is_space16(s[p]) ) + ++p; + + if( p + 1 < n && s[p] == '/' && s[p + 1] == '*' ) + { + p += 2; + while( p + 1 < n && !(s[p] == '*' && s[p + 1] == '/') ) + ++p; + if( p + 1 < n ) + p += 2; + continue; + } + + if( p + 1 < n && s[p] == '/' && s[p + 1] == '/' ) + { + p += 2; + while( p < n && s[p] != '\n' ) + ++p; + continue; + } + + if( p < n && s[p] == '#' ) + { + while( p < n && s[p] != '\n' ) + ++p; + continue; + } + + break; + } + + *pos = p; +} + +static char * +expand_value( const mcpu_config *config, const char *value ) +{ + size_t length = strlen( value ); + size_t capacity = length + 64; + char *out = (char *)calloc( capacity, 1 ); + size_t i = 0; + size_t o = 0; + + if( out == NULL ) + return( NULL ); + + while( i < length ) + { + if( value[i] == '$' ) + { + char name[256]; + size_t ni = 0; + size_t j = i + 1; + int braced = 0; + const char *replacement; + + if( j < length && value[j] == '{' ) + { + braced = 1; + ++j; + } + + while( j < length && ni + 1 < sizeof(name) && + ((value[j] >= 'a' && value[j] <= 'z') || + (value[j] >= 'A' && value[j] <= 'Z') || + (value[j] >= '0' && value[j] <= '9') || value[j] == '_') ) + name[ni++] = value[j++]; + + if( braced ) + { + if( j >= length || value[j] != '}' ) + { + free( out ); + errno = EINVAL; + return( NULL ); + } + ++j; + } + + if( ni == 0 ) + { + out[o++] = value[i++]; + continue; + } + + name[ni] = 0; + replacement = mcpu_config_get( config, name ); + if( replacement == NULL ) + replacement = getenv( name ); + if( replacement == NULL ) + replacement = ""; + + { + size_t rl = strlen( replacement ); + if( o + rl + 1 > capacity ) + { + char *p; + while( o + rl + 1 > capacity ) + capacity *= 2; + p = (char *)realloc( out, capacity ); + if( p == NULL ) + { + free( out ); + return( NULL ); + } + out = p; + } + memcpy( out + o, replacement, rl ); + o += rl; + } + + i = j; + continue; + } + + if( o + 2 > capacity ) + { + char *p; + capacity *= 2; + p = (char *)realloc( out, capacity ); + if( p == NULL ) + { + free( out ); + return( NULL ); + } + out = p; + } + + out[o++] = value[i++]; + } + + out[o] = 0; + return( out ); +} + +void +mcpu_config_init( mcpu_config *config ) +{ + if( config == NULL ) + return; + + config->first = NULL; + config->verbose = 0; +} + +void +mcpu_config_free( mcpu_config *config ) +{ + mcpu_config_entry *p; + + if( config == NULL ) + return; + + p = config->first; + while( p ) + { + mcpu_config_entry *next = p->next; + free( p->name ); + free( p->value ); + free( p ); + p = next; + } + + config->first = NULL; +} + +const char * +mcpu_config_get( const mcpu_config *config, const char *name ) +{ + mcpu_config_entry *p; + + if( config == NULL || name == NULL ) + return( NULL ); + + for( p = config->first; p; p = p->next ) + if( strcmp( p->name, name ) == 0 ) + return( p->value ); + + return( NULL ); +} + +int +mcpu_config_set( mcpu_config *config, const char *name, + const char *value ) +{ + mcpu_config_entry *p; + char *new_value; + + if( config == NULL || name == NULL || value == NULL ) + { + errno = EINVAL; + return( -1 ); + } + + new_value = expand_value( config, value ); + if( new_value == NULL ) + return( -1 ); + + for( p = config->first; p; p = p->next ) + { + if( strcmp( p->name, name ) == 0 ) + { + free( p->value ); + p->value = new_value; + return( 0 ); + } + } + + p = (mcpu_config_entry *)calloc( 1, sizeof(*p) ); + if( p == NULL ) + { + free( new_value ); + return( -1 ); + } + + p->name = strdup( name ); + if( p->name == NULL ) + { + free( new_value ); + free( p ); + return( -1 ); + } + + p->value = new_value; + p->next = config->first; + config->first = p; + + return( 0 ); +} + + +static int +config_entry_compare( const void *a, const void *b ) +{ + const mcpu_config_entry *ea = *(const mcpu_config_entry * const *)a; + const mcpu_config_entry *eb = *(const mcpu_config_entry * const *)b; + return( strcmp(ea->name, eb->name) ); +} + + +int +mcpu_config_dump( const mcpu_config *config, FILE *stream ) +{ + const mcpu_config_entry *p; + mcpu_config_entry **list; + size_t count = 0; + size_t i = 0; + + if( config == NULL || stream == NULL ) + { + errno = EINVAL; + return( -1 ); + } + + for( p = config->first; p; p = p->next ) + ++count; + + list = count ? (mcpu_config_entry **)calloc( count, sizeof(*list) ) : NULL; + if( count != 0 && list == NULL ) + return( -1 ); + + for( p = config->first; p; p = p->next ) + list[i++] = (mcpu_config_entry *)p; + + if( count > 1 ) + qsort( list, count, sizeof(*list), config_entry_compare ); + + for( i = 0; i < count; ++i ) + { + if( fprintf(stream, "%s = %s;\n", list[i]->name, list[i]->value) < 0 ) + { + free( list ); + return( -1 ); + } + } + + free( list ); + return( ferror(stream) ? -1 : 0 ); +} + + +int +mcpu_config_read( mcpu_config *config, const char *filename, + int missing_is_error ) +{ + char *bytes; + size_t byte_length; + mcpu_text text; + size_t p = 0; + int rc = -1; + + if( config == NULL || filename == NULL ) + { + errno = EINVAL; + return( -1 ); + } + + bytes = read_bytes( filename, &byte_length ); + if( bytes == NULL ) + { + if( !missing_is_error && errno == ENOENT ) + return( 0 ); + return( -1 ); + } + + mcpu_text_init( &text ); + { + const char *input = bytes; + + if( memchr( bytes, 0, byte_length ) != NULL ) + { + fprintf( stderr, "%s: NUL character is not allowed in configuration text\n", + filename ); + free( bytes ); + return( -1 ); + } + + if( byte_length >= 3 && + (unsigned char)bytes[0] == 0xef && + (unsigned char)bytes[1] == 0xbb && + (unsigned char)bytes[2] == 0xbf ) + input += 3; + + if( mcpu_text_from_utf8( &text, input ) != 0 ) + { + fprintf( stderr, "%s: invalid UTF-8 or non-UCS-2 character\n", + filename ); + free( bytes ); + return( -1 ); + } + } + free( bytes ); + + while( p < text.length ) + { + size_t name_start; + size_t name_end; + size_t value_start; + size_t value_end; + char *name = NULL; + char *value = NULL; + + skip_space_and_comments( text.data, text.length, &p ); + if( p >= text.length ) + break; + + name_start = p; + while( p < text.length && is_name16(text.data[p]) ) + ++p; + name_end = p; + + if( name_end == name_start ) + { + fprintf( stderr, "%s: malformed configuration variable\n", filename ); + goto done; + } + + skip_space_and_comments( text.data, text.length, &p ); + if( p >= text.length || text.data[p] != '=' ) + { + fprintf( stderr, "%s: '=' expected after configuration variable\n", filename ); + goto done; + } + ++p; + + while( p < text.length && is_space16(text.data[p]) ) + ++p; + + value_start = p; + if( p < text.length && (text.data[p] == '"' || text.data[p] == '\'') ) + { + __mpu_char16_t quote = text.data[p++]; + value_start = p; + while( p < text.length && text.data[p] != quote ) + { + if( text.data[p] == '\\' && p + 1 < text.length ) + p += 2; + else + ++p; + } + if( p >= text.length ) + { + fprintf( stderr, "%s: unterminated quoted configuration value\n", filename ); + goto done; + } + value_end = p++; + while( p < text.length && is_space16(text.data[p]) ) + ++p; + } + else + { + while( p < text.length && text.data[p] != ';' && text.data[p] != '\n' ) + ++p; + value_end = p; + while( value_end > value_start && is_space16(text.data[value_end - 1]) ) + --value_end; + } + + if( p >= text.length || text.data[p] != ';' ) + { + fprintf( stderr, "%s: ';' expected after configuration value\n", filename ); + goto done; + } + ++p; + + name = mcpu_text_to_utf8( text.data + name_start, name_end - name_start ); + value = mcpu_text_to_utf8( text.data + value_start, value_end - value_start ); + if( name == NULL || value == NULL ) + { + free( name ); + free( value ); + goto done; + } + + if( mcpu_config_set( config, name, value ) != 0 ) + { + free( name ); + free( value ); + goto done; + } + + free( name ); + free( value ); + name = NULL; + value = NULL; + } + + rc = 0; + +done: + mcpu_text_free( &text ); + return( rc ); +} |
