summaryrefslogtreecommitdiff
path: root/utils/mcpu-config.c
diff options
context:
space:
mode:
authorkx <kx@radix-linux.su>2026-10-09 18:00:15 +0300
committerkx <kx@radix-linux.su>2026-10-09 18:00:15 +0300
commit065da3509019b15dd0e678072e6be629e5330bef (patch)
tree69c934c9e786ac048b4fbe8f4c4c5571661521f4 /utils/mcpu-config.c
parentd130fe27fa753be02ed76fd9b277aa3c3d928eb9 (diff)
downloadlibmcpu-0.4.0.tar.xz
Version 0.4.00.4.0
Diffstat (limited to 'utils/mcpu-config.c')
-rw-r--r--utils/mcpu-config.c704
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 );
+}