#include 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 ); }