469 lines
12 KiB
C
469 lines
12 KiB
C
/*
|
|
* Hatari - cfgopts.c
|
|
*
|
|
* The functions in this file are used to load and save the ASCII
|
|
* configuration file.
|
|
* Original information text follows:
|
|
*/
|
|
/*<<---------------[ cfgopts.c ]------------------------/
|
|
/ /
|
|
/ Functional /
|
|
/ Description: Configuration file I/O /
|
|
/ /
|
|
/ Input : Configuration file name /
|
|
/ Configuration parameters in a structure /
|
|
/ /
|
|
/ Process : Interpret information by parameter and read or /
|
|
/ write back to the configuration file. /
|
|
/ /
|
|
/ Ouput : updated configuration file or updated structure. /
|
|
/ /
|
|
/ Programmer : Jeffry J. Brickley /
|
|
/ /
|
|
/ /
|
|
/---------------------------------------------------------------------*/
|
|
|
|
/*---------------------------------------------------------------------/
|
|
/
|
|
/ Description: CfgOpts is based on GETOPTS by Bob Stout. It will
|
|
/ process a configuration file based one words and
|
|
/ store it in a structure pointing to physical data
|
|
/ area for each storage item.
|
|
/ i.e. ???.CFG:
|
|
/ Port=1
|
|
/ work_space=C:\temp
|
|
/ menus=TRUE
|
|
/ user=Jeffry Brickley
|
|
/ will write to the following structure:
|
|
/ struct Config_Tag configs[] = {
|
|
/ {"port", Int_Tag, &port_number},
|
|
/ {"work_space", String_Tag, &work_space},
|
|
/ {"menus", Bool_Tag, &menu_flag},
|
|
/ {"user", String_Tag, &User_name},
|
|
/ {NULL, Error_Tag, NULL}
|
|
/ };
|
|
/ Note that the structure must always be terminated by a NULL row as
|
|
/ was the same with GETOPTS. This however is slightly more
|
|
/ complicated than scaning the command line (but not by much) for
|
|
/ data as there can be more variety in words than letters and an
|
|
/ number of data items limited only by memory.
|
|
/
|
|
/ Like the original code from which this was taken, this is released
|
|
/ to the Public Domain. I cannot make any guarantees other than these
|
|
/ work for me and I find them useful. Feel free to pass these on to
|
|
/ a friend, but please do not charge him....
|
|
/
|
|
/---------------------------------------------------------------------*/
|
|
const char CfgOpts_fileid[] = "Hatari cfgopts.c : " __DATE__ " " __TIME__;
|
|
|
|
#include <stdio.h>
|
|
#include <stdlib.h>
|
|
#include <unistd.h>
|
|
|
|
#include "main.h"
|
|
#include "cfgopts.h"
|
|
#include "str.h"
|
|
|
|
|
|
static int parse_input_config_entry(const struct Config_Tag *ptr)
|
|
{
|
|
const char *next;
|
|
int type = ptr->type;
|
|
|
|
/* get actual config value */
|
|
next = Str_Trim(strtok(NULL, "="));
|
|
if (next == NULL)
|
|
{
|
|
if (type == String_Tag)
|
|
next = ""; /* field with empty string */
|
|
else
|
|
type = Error_Tag;
|
|
}
|
|
|
|
switch (type) /* check type */
|
|
{
|
|
case Bool_Tag:
|
|
if (!strcasecmp(next,"FALSE"))
|
|
*(bool *)ptr->buf = false;
|
|
else if (!strcasecmp(next,"TRUE"))
|
|
*(bool *)ptr->buf = true;
|
|
break;
|
|
|
|
case Char_Tag:
|
|
sscanf(next, "%c", (char *)ptr->buf);
|
|
break;
|
|
|
|
case Short_Tag:
|
|
sscanf(next, "%hd", (short *)ptr->buf);
|
|
break;
|
|
|
|
case Int_Tag:
|
|
sscanf(next, "%d", (int *)ptr->buf);
|
|
break;
|
|
|
|
case Long_Tag:
|
|
sscanf(next, "%ld", (long *)ptr->buf);
|
|
break;
|
|
|
|
case Float_Tag:
|
|
sscanf(next, "%g", (float *)ptr->buf);
|
|
break;
|
|
|
|
case Double_Tag:
|
|
sscanf(next, "%lg", (double *)ptr->buf);
|
|
break;
|
|
|
|
case String_Tag:
|
|
strcpy((char *)ptr->buf, next);
|
|
break;
|
|
|
|
case Error_Tag:
|
|
default:
|
|
return -1;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
/**
|
|
* ---------------------------------------------------------------------/
|
|
* / reads from an input configuration (INI) file.
|
|
* /---------------------------------------------------------------------
|
|
* >>------[ input_config() ]-------------[ 08-02-95 14:02PM ]------/
|
|
* / return value:
|
|
* / int ; number of records read or -1 on error
|
|
* / parameters:
|
|
* / char *filename ; filename of INI style file
|
|
* / struct Config_Tag configs[]; Configuration structure
|
|
* / char *header ; INI header name (i.e. "[TEST]")
|
|
* /-------------------------------------------------------------------<<
|
|
*/
|
|
int input_config(const char *filename, const struct Config_Tag configs[], const char *header)
|
|
{
|
|
const struct Config_Tag *ptr;
|
|
int count = 0, lineno = 0;
|
|
FILE *file;
|
|
char *fptr,*tok;
|
|
char line[1024];
|
|
|
|
file = fopen(filename,"r");
|
|
if (file == NULL)
|
|
return -1; /* return error designation. */
|
|
|
|
if (header != NULL)
|
|
{
|
|
do
|
|
{
|
|
fptr = Str_Trim(fgets(line, sizeof(line), file)); /* get input line */
|
|
if (fptr == NULL)
|
|
break;
|
|
}
|
|
while (memcmp(fptr,header,strlen(header)));
|
|
}
|
|
|
|
if ( !feof(file) )
|
|
do
|
|
{
|
|
fptr = Str_Trim(fgets(line, sizeof(line), file)); /* get input line */
|
|
if (fptr == NULL)
|
|
continue;
|
|
lineno++;
|
|
if (fptr[0] == '#')
|
|
continue; /* skip comments */
|
|
if (fptr[0] == '[')
|
|
continue; /* skip next header */
|
|
tok = Str_Trim(strtok(fptr, "=")); /* get first token */
|
|
if (tok == NULL)
|
|
continue;
|
|
for (ptr = configs; ptr->buf; ++ptr) /* scan for token */
|
|
{
|
|
if (!strcmp(tok, ptr->code)) /* got a match? */
|
|
{
|
|
if (parse_input_config_entry(ptr) == 0)
|
|
count++;
|
|
else
|
|
printf("Error in Config file %s on line %d\n",
|
|
filename, lineno);
|
|
}
|
|
}
|
|
}
|
|
while (fptr != NULL && fptr[0] != '[');
|
|
|
|
fclose(file);
|
|
return count;
|
|
}
|
|
|
|
|
|
/**
|
|
* Write out an settings line
|
|
*/
|
|
static int write_token(FILE *outfile, const struct Config_Tag *ptr)
|
|
{
|
|
fprintf(outfile,"%s = ",ptr->code);
|
|
|
|
switch (ptr->type) /* check type */
|
|
{
|
|
case Bool_Tag:
|
|
fprintf(outfile,"%s\n", *((bool *)(ptr->buf)) ? "TRUE" : "FALSE");
|
|
break;
|
|
|
|
case Char_Tag:
|
|
fprintf(outfile, "%c\n", *((char *)(ptr->buf)));
|
|
break;
|
|
|
|
case Short_Tag:
|
|
fprintf(outfile, "%hd\n", *((short *)(ptr->buf)));
|
|
break;
|
|
|
|
case Int_Tag:
|
|
fprintf(outfile, "%d\n", *((int *)(ptr->buf)));
|
|
break;
|
|
|
|
case Long_Tag:
|
|
fprintf(outfile, "%ld\n", *((long *)(ptr->buf)));
|
|
break;
|
|
|
|
case Float_Tag:
|
|
fprintf(outfile, "%g\n", *((float *)ptr->buf));
|
|
break;
|
|
|
|
case Double_Tag:
|
|
fprintf(outfile, "%g\n", *((double *)ptr->buf));
|
|
break;
|
|
|
|
case String_Tag:
|
|
fprintf(outfile, "%s\n",(char *)ptr->buf);
|
|
break;
|
|
|
|
case Error_Tag:
|
|
default:
|
|
fprintf(stderr, "Error in Config structure (Contact author).\n");
|
|
return -1;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
|
|
/**
|
|
* Write given section header and tokens for that
|
|
* Return number of written tokens
|
|
*/
|
|
static int write_header_tokens(FILE *fp, const struct Config_Tag *ptr, const char *header)
|
|
{
|
|
int count = 0;
|
|
|
|
if (header != NULL)
|
|
{
|
|
fprintf(fp, "%s\n", header);
|
|
}
|
|
|
|
for (; ptr->buf; ++ptr) /* scan for token */
|
|
{
|
|
if (write_token(fp, ptr) == 0)
|
|
++count;
|
|
}
|
|
|
|
fprintf(fp, "\n");
|
|
|
|
return count;
|
|
}
|
|
|
|
|
|
/**
|
|
* ---------------------------------------------------------------------/
|
|
* / updates an input configuration (INI) file from a structure.
|
|
* /---------------------------------------------------------------------
|
|
* >>------[ update_config() ]-------------[ 08-02-95 14:02PM ]------/
|
|
* / return value:
|
|
* / int ; Number of records read & updated
|
|
* / parameters:
|
|
* / char *filename ; filename of INI file
|
|
* / struct Config_Tag configs[]; Configuration structure
|
|
* / char *header ; INI header name (i.e. "[TEST]")
|
|
* /-------------------------------------------------------------------<<
|
|
*/
|
|
int update_config(const char *filename, const struct Config_Tag configs[], const char *header)
|
|
{
|
|
const struct Config_Tag *ptr;
|
|
int count=0, lineno=0, retval;
|
|
FILE *cfgfile, *tempfile;
|
|
char *fptr, *tok;
|
|
char line[1024];
|
|
bool bUseTempCfg = false;
|
|
const char *sTempCfgName = "_temp_.cfg";
|
|
|
|
cfgfile = fopen(filename, "r");
|
|
|
|
/* If the cfg file does not yet exists, we can create it directly: */
|
|
if (cfgfile == NULL)
|
|
{
|
|
cfgfile = fopen(filename, "w");
|
|
if (cfgfile == NULL)
|
|
return -1; /* return error designation. */
|
|
count = write_header_tokens(cfgfile, configs, header);
|
|
fclose(cfgfile);
|
|
return count;
|
|
}
|
|
|
|
tempfile = tmpfile(); /* Open a temporary file for output */
|
|
if (tempfile == NULL)
|
|
{
|
|
/* tmpfile() failed, let's try a normal open */
|
|
tempfile = fopen(sTempCfgName, "w+");
|
|
bUseTempCfg = true;
|
|
}
|
|
if (tempfile == NULL)
|
|
{
|
|
perror("update_config");
|
|
fclose(cfgfile);
|
|
return -1; /* return error designation. */
|
|
}
|
|
|
|
if (header != NULL)
|
|
{
|
|
int headerlen = strlen(header);
|
|
do
|
|
{
|
|
fptr = Str_Trim(fgets(line, sizeof(line), cfgfile)); /* get input line */
|
|
if (fptr == NULL)
|
|
break;
|
|
fprintf(tempfile, "%s\n", fptr);
|
|
}
|
|
while(memcmp(fptr, header, headerlen));
|
|
}
|
|
|
|
if (feof(cfgfile))
|
|
{
|
|
count += write_header_tokens(tempfile, configs, header);
|
|
}
|
|
else
|
|
{
|
|
char *savedtokenflags = NULL; /* Array to log the saved tokens */
|
|
int numtokens = 0; /* Total number of tokens to save */
|
|
|
|
/* Find total number of tokens: */
|
|
for (ptr=configs; ptr->buf; ++ptr)
|
|
{
|
|
numtokens += 1;
|
|
}
|
|
if (numtokens)
|
|
{
|
|
savedtokenflags = malloc(numtokens * sizeof(char));
|
|
if (savedtokenflags)
|
|
memset(savedtokenflags, 0, numtokens * sizeof(char));
|
|
}
|
|
|
|
for(;;)
|
|
{
|
|
fptr = Str_Trim(fgets(line, sizeof(line), cfgfile)); /* get input line */
|
|
/* error or eof? */
|
|
if (fptr == NULL)
|
|
break;
|
|
lineno++;
|
|
if (fptr[0] == '#')
|
|
{
|
|
fprintf(tempfile, "%s\n", fptr);
|
|
continue; /* skip comments */
|
|
}
|
|
if (fptr[0] == '[')
|
|
{
|
|
break;
|
|
}
|
|
|
|
tok = Str_Trim(strtok(fptr, "=")); /* get first token */
|
|
if (tok != NULL)
|
|
{
|
|
int i = 0;
|
|
for (ptr = configs; ptr->buf; ++ptr, i++) /* scan for token */
|
|
{
|
|
if (!strcmp(tok, ptr->code)) /* got a match? */
|
|
{
|
|
if (write_token(tempfile, ptr) == 0)
|
|
{
|
|
if (savedtokenflags)
|
|
savedtokenflags[i] = true;
|
|
count += 1;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
/* Write remaining (new?) tokens that were not in the configuration file, yet */
|
|
if (count != numtokens && savedtokenflags != NULL)
|
|
{
|
|
int i;
|
|
for (ptr = configs, i = 0; ptr->buf; ++ptr, i++)
|
|
{
|
|
if (!savedtokenflags[i])
|
|
{
|
|
if (write_token(tempfile, ptr) == 0)
|
|
{
|
|
count += 1;
|
|
fprintf(stderr, "Wrote new token %s -> %s \n", header, ptr->code);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
if (savedtokenflags)
|
|
{
|
|
free(savedtokenflags);
|
|
savedtokenflags = NULL;
|
|
}
|
|
|
|
if (!feof(cfgfile) && fptr != NULL)
|
|
fprintf(tempfile, "\n%s\n", line);
|
|
|
|
for(;;)
|
|
{
|
|
fptr = Str_Trim(fgets(line, sizeof(line), cfgfile)); /* get input line */
|
|
if (fptr == NULL)
|
|
break;
|
|
fprintf(tempfile, "%s\n", fptr);
|
|
}
|
|
}
|
|
|
|
|
|
/* Re-open the config file for writing: */
|
|
fclose(cfgfile);
|
|
cfgfile = fopen(filename, "wb");
|
|
if (cfgfile == NULL || fseek(tempfile, 0, SEEK_SET) != 0)
|
|
{
|
|
retval = -1;
|
|
goto cleanup;
|
|
}
|
|
|
|
/* Now copy the temporary file to the configuration file: */
|
|
retval = count;
|
|
while(!(feof(tempfile) || ferror(cfgfile)))
|
|
{
|
|
size_t copycount;
|
|
copycount = fread(line, sizeof(char), sizeof(line), tempfile);
|
|
if (copycount == 0)
|
|
break;
|
|
if (fwrite(line, sizeof(char), copycount, cfgfile) != copycount)
|
|
{
|
|
retval = -1;
|
|
break;
|
|
}
|
|
}
|
|
cleanup:
|
|
if (cfgfile)
|
|
{
|
|
if (ferror(cfgfile))
|
|
perror("update_config");
|
|
fclose(cfgfile);
|
|
}
|
|
if (tempfile)
|
|
{
|
|
/* tmpfile() is removed automatically on close */
|
|
fclose(tempfile);
|
|
if (bUseTempCfg)
|
|
unlink(sTempCfgName);
|
|
}
|
|
return retval;
|
|
}
|
|
|