/************************************************************************
*** Statusfiles intentionally look like the old Windows ini file ***
*** format. The right thing to do if this functionality is ported ***
*** to Windows is to replace most of this code with a wrapper to the ***
*** appropriate calls to use the registry. ***
*** ***
*** The files contain sections delimited by '[section name]' on a ***
*** line by itself. Individual lines are of the form ***
*** 'variable=value' Strings are quoted in double quotes. We're ***
*** allowing quote characters to be escaped by the backslash ***
*** character. A semicolon outside of a string is considered a ***
*** comment until EOL. An implicit strtrim is done on every read. ***
*** ***
*** Comments are NOT preserved, nor is line ordering. This is ***
*** different from the windows ini routines. There isn't enough ***
*** reason to add in this functionality at the moment. (If we don't ***
*** tell the users, they wont try to use comments...) ***
*** ***
*** All public functions return 1 on success, 0 on failure. The ***
*** global string STATUSFILE_ERROR will contain error text, if any. ***
*** ***
*** All variables are stored in hashes. Essentially we have a hash ***
*** of hashes, the first hash containing the sections hashes, the ***
*** second being standard var/value pairs. ***
************************************************************************/
#include <stdio.h>
#include <ctype.h>
#ifndef NT
#include <unistd.h>
#endif /* NT */
#include <mrt.h>
#include "irrd.h"
#ifdef HAVE_LIBGEN_H
#include <libgen.h>
#endif /* HAVE_LIBGEN_H */
/**
** Constants
**/
#define HASHSIZE_SECTIONS 101
#define HASHSIZE_VARS 47
#ifdef TESTJMH
trace_t *default_trace;
irr_t IRR;
#else
extern trace_t *default_trace;
extern irr_t IRR;
#endif
/**********************************************************************
**
** Private Functions
**
**********************************************************************/
/**
** String functions
**/
static int
StrTrim (char *buf)
{
char *p;
/* Strip trailing whitespace */
p = buf + strlen(buf) - 1;
while ((p >= buf) && isspace(*p)) *p-- = '\0';
/* Remove any leading white space */
p = buf;
while (isspace(*p)) p++;
if (p != buf)
memmove(buf, p, strlen(p)+1);
if (strlen(buf) > 0)
return (1);
return (0);
}
/*
* Strip characters after the ; character, but only if the ; character
* doesn't have a " character in the buffer before it or the " character
* isn't present.
*/
static int
CmtTrim (char *buf)
{
char *p, *q;
/* String leading comments before " (if any) */
if ((p = strchr(buf, '"')) != NULL) {
q = strchr(buf, ';');
if (q != NULL && q < p) *q = '\0';
}
else {
if ((p = strchr(buf, ';')) != NULL)
*p = '\0';
}
if (strlen(buf) > 0)
return (1);
return (0);
}
/*
* Discards comments, (delimited by ; and to the left of " if present)
* Throws out initial whitespace, trailing whitespace and whitespace
* between the = lines
* Whitespace includes CR and LF characters.
*/
static int
VarTrim (char *buf)
{
char *p, *q;
/* Make sure we have an =, otherwise we might as well bail */
if ((p = strchr(buf, '=')) == NULL)
return (0);
if (!CmtTrim(buf)) return (0);
if (!StrTrim(buf)) return (0);
/* Remove white space after = */
if ((p = strchr(buf, '=')) != NULL) {
p++; q = p;
while (isspace(*q)) q++;
if (p != q)
memmove (p, q, strlen(q)+1);
}
/* Remove white space before = */
if ((p = q = strchr(buf, '=')) != NULL) {
if (q != buf) {
while (((p-1) >= buf) && isspace(*(p-1))) p--;
if (p != q)
memmove(p, q, strlen(q)+1);
}
}
if (strlen(buf) > 0) return (1);
return (0);
}
/**
** Hash maintenance functions
**/
static int
HashItemDestroy (hash_item_t *h)
{
if (h == NULL) return (1);
if (h->key) Delete (h->key);
if (h->value) Delete (h->value);
Delete(h);
return (1);
}
static int
HashSectionDestroy (section_hash_t *h)
{
if (h == NULL) return (1);
if (h->key) Delete (h->key);
if (h->hash_vars) HASH_Destroy (h->hash_vars);
Delete(h);
return (1);
}
static int
InsertVar (statusfile_t *sf, char *section, hash_item_t *h)
{
section_hash_t *h_sect;
/* Find the proper section */
if ((h_sect = HASH_Lookup(sf->hash_sections, section)) == NULL) {
h_sect = New(section_hash_t);
h_sect->key = strdup(section);
h_sect->hash_vars = HASH_Create(HASHSIZE_VARS,
HASH_KeyOffset, HASH_Offset(h, &(h->key)),
HASH_DestroyFunction, HashItemDestroy,
0);
HASH_Insert(sf->hash_sections, h_sect);
}
HASH_Insert(h_sect->hash_vars, h);
return (1);
}
static hash_item_t *
LookupVar (statusfile_t *sf, char *section, char *variable)
{
hash_item_t *p_hi;
section_hash_t *p_sh;
if ((p_sh = HASH_Lookup(sf->hash_sections, section)) != NULL) {
if ((p_hi = HASH_Lookup(p_sh->hash_vars, variable)) != NULL) {
return (p_hi);
}
}
return (NULL);
}
static int
DeleteVar (statusfile_t *sf, char *section, char *variable)
{
hash_item_t *p_hi;
section_hash_t *p_sh;
if ((p_sh = HASH_Lookup(sf->hash_sections, section)) != NULL) {
if ((p_hi = HASH_Lookup(p_sh->hash_vars, variable)) != NULL) {
HASH_Remove(p_sh->hash_vars, p_hi);
return (1);
}
}
return (0);
}
static int
ProcessLine (statusfile_t *sf, char *section, char *line)
{
char tmp[BUFSIZE], *p, *q, *last;
hash_item_t *h_var;
h_var = New(hash_item_t);
tmp[BUFSIZE-1] = '\0';
strncpy(tmp, line, BUFSIZE);
if (VarTrim(tmp)) {
/* Sanity check: no " before = */
p = strchr(tmp, '"');
q = strchr(tmp, '=');
if (p && (p < q)) return (0);
if ((p = strtok_r(tmp, "=", &last)) != NULL) {
/* No " in the key */
if ((q = strchr(p, '"')) != NULL) return (0);
h_var->key = strdup(p);
if ((p = strtok_r(NULL, "=", &last)) != NULL) {
/* The " must follow the = if any */
if ((q = strchr(p, '"')) != NULL) {
if (q != p) return (0);
/* Truncate the string at the next " */
p = q + 1;
if ((q = strchr(p, '"')) != NULL) *q = '\0';
else return (0);
}
}
h_var->value = strdup(p);
return (InsertVar(sf, section, h_var));
}
}
/* Failure cases */
if (h_var->key) Delete(h_var->key);
Delete(h_var);
return (0);
}
static char *
IsSection (char *buf)
{
char *p, *q;
char tmp[BUFSIZE];
strncpy(tmp, buf, BUFSIZE-1);
CmtTrim(tmp);
p = tmp;
while (isspace(*p)) p++;
if (*p == '[') {
p++;
p = strdup(p);
if ((q = strchr (p, ']')) != NULL) {
*q = '\0';
return (p);
}
else {
free(p);
}
}
return (NULL);
}
/**
** File operations
**/
static int
ReadStatusFile (statusfile_t *sf)
{
FILE *fp;
char *section = NULL, *new_section = NULL;
char buf[BUFSIZE];
int length;
int ret = 0;
int c;
if ((fp = fopen (sf->filename, "r")) != NULL) {
while (fgets (buf, BUFSIZE, fp) != NULL) {
length = strlen (buf);
if (((length >= 1) && (buf[length - 1] == '\n')) ||
((length >= 2) && (buf[length - 1] == '\r') && (buf[length - 2] == '\n'))) {
/* Canonicalize on Unix style EOL */
if ((new_section = IsSection(buf)) != NULL) {
if (section) free(section);
section = new_section;
}
else {
if (!ProcessLine(sf, section, buf)) {
/* Ignore blank lines */
if (StrTrim(buf))
trace(INFO, default_trace, "ERROR: Failed to process line [%s]\n", buf);
}
}
continue;
}
else {
StrTrim(buf);
trace (INFO, default_trace, "ERROR: Overflowed line for statusfile %s [%s]\n", sf->filename, buf);
do {
c = fgetc(fp);
} while ((c != '\n') && (c != EOF));
if (c == EOF) break;
continue;
}
}
ret = 1;
}
return (ret);
}
/*
* We're going to let this also serve as a copy function. If filename
* is NULL, then simply use the filename from the statusfile_t. Otherwise,
* write to the new file.
*
* In the case of a copy, we just write the file in place to the new file.
* Its up to the caller to verify that we're not going to clobber something.
*
* Otherwise we write the file to <handle filename>.tmp and then rotate
* it in by moving the old one to <handle filename>.old and moving
* <handle filename>.tmp to <handle filename>.
*
* We make an attempt to re-move the .old back if one of the moves fail,
* but one can only do so much.
*/
static int
WriteStatusFile (statusfile_t *sf, char *filename)
{
char *base, *file, *fn;
char tmp[BUFSIZE], tmp_old[BUFSIZE];
FILE *fp;
hash_item_t *p_hi;
section_hash_t *p_sh;
int ret = 0;
tmp[BUFSIZE-1] = '\0';
if (filename == NULL) fn = sf->filename;
else {
if (!strcmp(filename, sf->filename)) {
fn = sf->filename;
}
else {
fn = filename;
}
}
if (fn == sf->filename) {
strcpy(tmp, fn);
base = strdup(dirname(tmp));
strcpy(tmp, fn);
file = strdup(basename(tmp));
strncpy(tmp, base, BUFSIZE - 1);
strncat(tmp, "/.", BUFSIZE - 1 - strlen(tmp));
strncat(tmp, file, BUFSIZE - 1 - strlen(tmp));
strncat(tmp, ".tmp", BUFSIZE - 1 - strlen(tmp));
strncpy(tmp_old, base, BUFSIZE - 1);
strncat(tmp_old, "/.", BUFSIZE - 1 - strlen(tmp));
strncat(tmp_old, file, BUFSIZE - 1 - strlen(tmp));
strncat(tmp_old, ".old", BUFSIZE - 1 - strlen(tmp));
free(base);
free(file);
}
else {
strcpy(tmp, fn);
}
if ((fp = fopen(tmp, "w")) != NULL) {
/* Print the file */
HASH_Iterate(sf->hash_sections, p_sh) {
fprintf (fp, "[%s]\n", p_sh->key);
HASH_Iterate(p_sh->hash_vars, p_hi) {
/* If the data contains anything other than what is useful for
numbers, print it with quotes. We somewhat sloppily assume
that numbers will contain no whitespace. */
char *p = p_hi->value;
int is_num = 1;
int decimal_count = 0;
if (*p && (*p == '-')) p++;
while (*p && is_num && decimal_count <= 1) {
if (!isdigit(*p) && (*p == '.')) decimal_count++;
else if (!isdigit(*p)) is_num = 0;
p++;
}
if ((decimal_count <= 1) && is_num) {
fprintf (fp, "%s=%s\n", p_hi->key, p_hi->value);
}
else {
fprintf (fp, "%s=\"%s\"\n", p_hi->key, p_hi->value);
}
}
fprintf(fp, "\n");
}
fclose (fp);
/* Rotate the files */
if (fn == sf->filename) {
FILE *fp_tmp = NULL;
unsigned char b_file_dne = 1;
if ((fp_tmp = fopen(sf->filename, "r")) != NULL) {
fclose(fp_tmp);
b_file_dne = 0;
}
if (!b_file_dne)
if (rename(sf->filename, tmp_old) != 0) {
trace (INFO, default_trace, "ERROR: Unable to move new status file %s to %s\n", sf->filename, tmp_old);
goto FAIL;
}
if (rename(tmp, sf->filename) != 0) {
trace (INFO, default_trace, "ERROR: Failed to rotate in new statusfile: %s->%s\n", tmp, sf->filename);
/* Attempt to put file back */
if (b_file_dne || (rename(tmp_old, sf->filename) != 0)) {
trace (INFO, default_trace, "ERROR: Failed to put back original statusfile: %s->%s\n", tmp_old, sf->filename);
goto FAIL;
}
goto FAIL;
}
if (!b_file_dne)
if (unlink(tmp_old) != 0) {
/* This isn't really fatal since the new file really is in place,
however, the next rotate may fail */
trace (INFO, default_trace, "WARNING: Failed to unlink old statusfile %s\n", tmp_old);
}
}
ret = 1;
}
else {
trace (INFO, default_trace, "ERROR: Failed to write new statusfile %s\n", tmp);
}
FAIL:
return (ret);
}
/**********************************************************************
**
** Public Functions
**
**********************************************************************/
/*
* InitStatusFile
*
* Arguments: The filename that you want to write to.
* A valid trace handle.
*
* Returns: statusfile_t * or NULL if failed.
*
*/
statusfile_t *
InitStatusFile (char *filename)
{
statusfile_t *sf;
section_hash_t sh;
if ((sf = New(statusfile_t)) != NULL) {
sf->filename = strdup(filename);
if ((sf->hash_sections = HASH_Create (HASHSIZE_SECTIONS,
HASH_KeyOffset, HASH_Offset(&sh, &(sh.key)),
HASH_DestroyFunction, HashSectionDestroy,
0)) == NULL)
goto FAIL;
ReadStatusFile(sf);
pthread_mutex_init (&(sf->mutex_lock), NULL);
}
FAIL:
return (sf);
}
/*
* CloseStatusFile
*
* Pretty obvious what this does. :-)
*/
int
CloseStatusFile (statusfile_t *sf)
{
Delete(sf->filename);
sf->filename = NULL;
if (!pthread_mutex_lock(&(sf->mutex_lock))) {
if (sf->hash_sections) {
HASH_Destroy(sf->hash_sections);
sf->hash_sections = NULL;
}
pthread_mutex_unlock(&(sf->mutex_lock));
}
/* There's not much we can do about failures in the above routines */
return (1);
}
/*
* GetStatusString
*
* For a given statusfile handle, returns the value associated with a
* given variable in a particular section.
*
* Returns pointer to the live data value.
* Thou Shalt Not Alter The String Returned Without Calling Strdup
* Lest Thou Corrupt Thine Data.
*
* (It was decided to not return a strdup'ed variable by default
* because we often just want to ref it and we may do this a LOT.
* This would lead to hemorraghed memory.)
*/
char *
GetStatusString (statusfile_t *sf, char *section, char *variable)
{
hash_item_t *p_hi;
char *ret = NULL;
if (sf->filename == NULL) return (NULL);
if (!pthread_mutex_lock(&(sf->mutex_lock))) {
if ((p_hi = LookupVar(sf, section, variable)) != NULL) {
ret = p_hi->value;
}
pthread_mutex_unlock(&(sf->mutex_lock));
}
return (ret);
}
/*
* SetStatusString
*
* For a given statusfile handle, sets the value of a variable in a
* particular section.
*
* If value == NULL, delete the variable
*
* Returns: 0 on failure, 1 on success.
*/
int
SetStatusString (statusfile_t *sf, char *section, char *variable, char *value)
{
hash_item_t *p_hi;
int ret = 0;
if (sf->filename == NULL) return (0);
if (!pthread_mutex_lock(&(sf->mutex_lock))) {
/* Do we delete the value? */
if (value == NULL) {
DeleteVar(sf, section, variable);
ret = 1;
}
/* See if its already in there. If it is, then replace the current val */
else if ((p_hi = LookupVar(sf, section, variable)) != NULL) {
if ((p_hi->value != NULL) && strcmp(p_hi->value, value)) {
free(p_hi->value);
p_hi->value = strdup(value);
}
ret = 1;
}
/* If its not, add a new value */
else {
p_hi = New(hash_item_t);
p_hi->key = strdup(variable);
p_hi->value = strdup(value);
if (InsertVar(sf, section, p_hi)) ret = 1;
}
ret = WriteStatusFile(sf, NULL);
pthread_mutex_unlock(&(sf->mutex_lock));
}
return (ret);
}
void
uii_show_statusfile (uii_connection_t *uii)
{
if (!pthread_mutex_lock(&(IRR.statusfile->mutex_lock))) {
if (IRR.statusfile->filename)
uii_add_bulk_output (uii, "Statusfile location: %s\r\n", IRR.statusfile->filename);
else
uii_add_bulk_output (uii, "No statusfile configured.\r\n");
pthread_mutex_unlock(&(IRR.statusfile->mutex_lock));
}
}
int
config_statusfile (uii_connection_t *uii, char *filename)
{
if (IRR.statusfile) {
CloseStatusFile(IRR.statusfile);
Delete(IRR.statusfile);
}
IRR.statusfile = InitStatusFile(filename);
return ((IRR.statusfile) ? 1 : -1);
}
syntax highlighted by Code2HTML, v. 0.9.1