root/ext/dba/libinifile/inifile.c

/* [<][>][^][v][top][bottom][index][help] */

DEFINITIONS

This source file includes following definitions.
  1. inifile_version
  2. inifile_key_free
  3. inifile_val_free
  4. inifile_line_free
  5. inifile_alloc
  6. inifile_free
  7. inifile_key_split
  8. inifile_key_string
  9. etrim
  10. inifile_read
  11. inifile_key_cmp
  12. inifile_fetch
  13. inifile_firstkey
  14. inifile_nextkey
  15. inifile_truncate
  16. inifile_find_group
  17. inifile_next_group
  18. inifile_copy_to
  19. inifile_filter
  20. inifile_delete_replace_append
  21. inifile_delete
  22. inifile_replace
  23. inifile_append

/*
   +----------------------------------------------------------------------+
   | PHP Version 5                                                        |
   +----------------------------------------------------------------------+
   | Copyright (c) 1997-2015 The PHP Group                                |
   +----------------------------------------------------------------------+
   | This source file is subject to version 3.01 of the PHP license,      |
   | that is bundled with this package in the file LICENSE, and is        |
   | available through the world-wide-web at the following url:           |
   | http://www.php.net/license/3_01.txt                                  |
   | If you did not receive a copy of the PHP license and are unable to   |
   | obtain it through the world-wide-web, please send a note to          |
   | license@php.net so we can mail you a copy immediately.               |
   +----------------------------------------------------------------------+
   | Author: Marcus Boerger <helly@php.net>                               |
   +----------------------------------------------------------------------+
 */

/* $Id: 1b985fa0072f7886f787255ae90c7e50d76f9539 $ */

#ifdef HAVE_CONFIG_H
#include "config.h"
#endif

#include "php.h"
#include "php_globals.h"

#include <stdlib.h>
#include <string.h>
#include <errno.h>
#if HAVE_UNISTD_H
#include <unistd.h>
#endif

#include "inifile.h"

/* ret = -1 means that database was opened for read-only
 * ret = 0  success
 * ret = 1  key already exists - nothing done
 */

/* {{{ inifile_version */
char *inifile_version() 
{
        return "1.0, $Id: 1b985fa0072f7886f787255ae90c7e50d76f9539 $";
}
/* }}} */ 

/* {{{ inifile_free_key */
void inifile_key_free(key_type *key)
{
        if (key->group) {
                efree(key->group);
        }
        if (key->name) {
                efree(key->name);
        }
        memset(key, 0, sizeof(key_type));
}
/* }}} */

/* {{{ inifile_free_val */
void inifile_val_free(val_type *val)
{
        if (val->value) {
                efree(val->value);
        }
        memset(val, 0, sizeof(val_type));
}
/* }}} */

/* {{{ inifile_free_val */
void inifile_line_free(line_type *ln)
{
        inifile_key_free(&ln->key);
        inifile_val_free(&ln->val);
        ln->pos = 0;
}
/* }}} */

/* {{{ inifile_alloc */
inifile * inifile_alloc(php_stream *fp, int readonly, int persistent TSRMLS_DC)
{
        inifile *dba;

        if (!readonly) {
                if (!php_stream_truncate_supported(fp)) {
                        php_error_docref(NULL TSRMLS_CC, E_WARNING, "Can't truncate this stream");
                        return NULL;
                }
        }
 
        dba = pemalloc(sizeof(inifile), persistent);
        memset(dba, 0, sizeof(inifile));
        dba->fp = fp;
        dba->readonly = readonly;
        return dba;
}
/* }}} */

/* {{{ inifile_free */
void inifile_free(inifile *dba, int persistent)
{
        if (dba) {
                inifile_line_free(&dba->curr);
                inifile_line_free(&dba->next);
                pefree(dba, persistent);
        }
}
/* }}} */

/* {{{ inifile_key_split */
key_type inifile_key_split(const char *group_name)
{
        key_type key;
        char *name;
        
        if (group_name[0] == '[' && (name = strchr(group_name, ']')) != NULL) {
                key.group = estrndup(group_name+1, name - (group_name + 1));
                key.name = estrdup(name+1);
        } else {
                key.group = estrdup("");
                key.name = estrdup(group_name);
        }
        return key;
}
/* }}} */

/* {{{ inifile_key_string */
char * inifile_key_string(const key_type *key)
{
        if (key->group && *key->group) {
                char *result;
                spprintf(&result, 0, "[%s]%s", key->group, key->name ? key->name : "");
                return result;
        } else if (key->name) {
                return estrdup(key->name);
        } else {
                return NULL;
        }
}
/* }}} */

/* {{{ etrim */
static char *etrim(const char *str)
{
        char *val;
        size_t l;
        
        if (!str) {
                return NULL;
        }
        val = (char*)str;
        while (*val && strchr(" \t\r\n", *val)) {
                val++;
        }
        l = strlen(val);
        while (l && (strchr(" \t\r\n", val[l-1]))) {
                l--;
        }
        return estrndup(val, l);
}
/* }}} */

/* {{{ inifile_findkey
 */
static int inifile_read(inifile *dba, line_type *ln TSRMLS_DC) {
        char *fline;
        char *pos;

        inifile_val_free(&ln->val);
        while ((fline = php_stream_gets(dba->fp, NULL, 0)) != NULL) {
                if (fline) {
                        if (fline[0] == '[') {
                                /* A value name cannot start with '['
                                 * So either we find a ']' or we found an error
                                 */
                                pos = strchr(fline+1, ']');
                                if (pos) {
                                        *pos = '\0';
                                        inifile_key_free(&ln->key);
                                        ln->key.group = etrim(fline+1);
                                        ln->key.name = estrdup("");
                                        ln->pos = php_stream_tell(dba->fp);
                                        efree(fline);
                                        return 1;
                                } else {
                                        efree(fline);
                                        continue;
                                }
                        } else {
                                pos = strchr(fline, '=');
                                if (pos) {
                                        *pos = '\0';
                                        /* keep group or make empty if not existent */
                                        if (!ln->key.group) {
                                                ln->key.group = estrdup("");
                                        }
                                        if (ln->key.name) {
                                                efree(ln->key.name);
                                        }
                                        ln->key.name = etrim(fline);
                                        ln->val.value = etrim(pos+1);
                                        ln->pos = php_stream_tell(dba->fp);
                                        efree(fline);
                                        return 1;
                                } else {
                                        /* simply ignore lines without '='
                                         * those should be comments
                                         */
                                         efree(fline);
                                         continue;
                                }
                        }
                }
        }
        inifile_line_free(ln);
        return 0;
}
/* }}} */

/* {{{ inifile_key_cmp */
/* 0 = EQUAL
 * 1 = GROUP-EQUAL,NAME-DIFFERENT
 * 2 = DIFFERENT
 */
static int inifile_key_cmp(const key_type *k1, const key_type *k2 TSRMLS_DC)
{
        assert(k1->group && k1->name && k2->group && k2->name);
        
        if (!strcasecmp(k1->group, k2->group)) {
                if (!strcasecmp(k1->name, k2->name)) {
                        return 0;
                } else {
                        return 1;
                }
        } else {
                return 2;
        }
}
/* }}} */

/* {{{ inifile_fetch
 */
val_type inifile_fetch(inifile *dba, const key_type *key, int skip TSRMLS_DC) {
        line_type ln = {{NULL,NULL},{NULL}};
        val_type val;
        int res, grp_eq = 0;

        if (skip == -1 && dba->next.key.group && dba->next.key.name && !inifile_key_cmp(&dba->next.key, key TSRMLS_CC)) {
                /* we got position already from last fetch */
                php_stream_seek(dba->fp, dba->next.pos, SEEK_SET);
        } else {
                /* specific instance or not same key -> restart search */
                /* the slow way: restart and seacrch */
                php_stream_rewind(dba->fp);
                inifile_line_free(&dba->next);
        }
        if (skip == -1) {
                skip = 0;
        }
        while(inifile_read(dba, &ln TSRMLS_CC)) {
                if (!(res=inifile_key_cmp(&ln.key, key TSRMLS_CC))) {
                        if (!skip) {
                                val.value = estrdup(ln.val.value ? ln.val.value : "");
                                /* allow faster access by updating key read into next */
                                inifile_line_free(&dba->next);
                                dba->next = ln;
                                dba->next.pos = php_stream_tell(dba->fp);
                                return val;
                        }
                        skip--;
                } else if (res == 1) {
                        grp_eq = 1;
                } else if (grp_eq) {
                        /* we are leaving group now: that means we cannot find the key */
                        break;
                }
        }
        inifile_line_free(&ln);
        dba->next.pos = php_stream_tell(dba->fp);
        return ln.val;
}
/* }}} */

/* {{{ inifile_firstkey
 */
int inifile_firstkey(inifile *dba TSRMLS_DC) {
        inifile_line_free(&dba->curr);
        dba->curr.pos = 0;
        return inifile_nextkey(dba TSRMLS_CC);
}
/* }}} */

/* {{{ inifile_nextkey
 */
int inifile_nextkey(inifile *dba TSRMLS_DC) {
        line_type ln = {{NULL,NULL},{NULL}};

        /*inifile_line_free(&dba->next); ??? */
        php_stream_seek(dba->fp, dba->curr.pos, SEEK_SET);
        ln.key.group = estrdup(dba->curr.key.group ? dba->curr.key.group : "");
        inifile_read(dba, &ln TSRMLS_CC);
        inifile_line_free(&dba->curr);
        dba->curr = ln;
        return ln.key.group || ln.key.name;
}       
/* }}} */

/* {{{ inifile_truncate
 */
static int inifile_truncate(inifile *dba, size_t size TSRMLS_DC)
{
        int res;

        if ((res=php_stream_truncate_set_size(dba->fp, size)) != 0) {
                php_error_docref(NULL TSRMLS_CC, E_WARNING, "Error in ftruncate: %d", res);
                return FAILURE;
        }
        php_stream_seek(dba->fp, size, SEEK_SET);
        return SUCCESS;
}
/* }}} */

/* {{{ inifile_find_group
 * if found pos_grp_start points to "[group_name]"
 */
static int inifile_find_group(inifile *dba, const key_type *key, size_t *pos_grp_start TSRMLS_DC) 
{
        int ret = FAILURE;

        php_stream_flush(dba->fp);
        php_stream_seek(dba->fp, 0, SEEK_SET);
        inifile_line_free(&dba->curr);
        inifile_line_free(&dba->next);

        if (key->group && strlen(key->group)) {
                int res;
                line_type ln = {{NULL,NULL},{NULL}};

                res = 1;
                while(inifile_read(dba, &ln TSRMLS_CC)) {
                        if ((res=inifile_key_cmp(&ln.key, key TSRMLS_CC)) < 2) {
                                ret = SUCCESS;
                                break;
                        }
                        *pos_grp_start = php_stream_tell(dba->fp);
                }
                inifile_line_free(&ln);
        } else {
                *pos_grp_start = 0;
                ret = SUCCESS;
        }
        if (ret == FAILURE) {
                *pos_grp_start = php_stream_tell(dba->fp);
        }
        return ret;
}
/* }}} */

/* {{{ inifile_next_group
 * only valid after a call to inifile_find_group
 * if any next group is found pos_grp_start points to "[group_name]" or whitespace before that
 */
static int inifile_next_group(inifile *dba, const key_type *key, size_t *pos_grp_start TSRMLS_DC) 
{
        int ret = FAILURE;
        line_type ln = {{NULL,NULL},{NULL}};

        *pos_grp_start = php_stream_tell(dba->fp);
        ln.key.group = estrdup(key->group);
        while(inifile_read(dba, &ln TSRMLS_CC)) {
                if (inifile_key_cmp(&ln.key, key TSRMLS_CC) == 2) {
                        ret = SUCCESS;
                        break;
                }
                *pos_grp_start = php_stream_tell(dba->fp);
        }
        inifile_line_free(&ln);
        return ret;
}
/* }}} */

/* {{{ inifile_copy_to
 */
static int inifile_copy_to(inifile *dba, size_t pos_start, size_t pos_end, inifile **ini_copy TSRMLS_DC)
{
        php_stream *fp;
        
        if (pos_start == pos_end) {
                *ini_copy = NULL;
                return SUCCESS;
        }
        if ((fp = php_stream_temp_create(0, 64 * 1024)) == NULL) {
                php_error_docref(NULL TSRMLS_CC, E_WARNING, "Could not create temporary stream");
                *ini_copy = NULL;
                return FAILURE;
        }

        if ((*ini_copy = inifile_alloc(fp, 1, 0 TSRMLS_CC)) == NULL) {
                /* writes error */
                return FAILURE;
        }
        php_stream_seek(dba->fp, pos_start, SEEK_SET);
        if (!php_stream_copy_to_stream_ex(dba->fp, fp, pos_end - pos_start, NULL)) {
                php_error_docref(NULL TSRMLS_CC, E_WARNING, "Could not copy group [%zu - %zu] to temporary stream", pos_start, pos_end);
                return FAILURE;
        } 
        return SUCCESS;
}
/* }}} */

/* {{{ inifile_filter
 * copy from to dba while ignoring key name (group must equal)
 */
static int inifile_filter(inifile *dba, inifile *from, const key_type *key TSRMLS_DC) 
{
        size_t pos_start = 0, pos_next = 0, pos_curr;
        int ret = SUCCESS;
        line_type ln = {{NULL,NULL},{NULL}};

        php_stream_seek(from->fp, 0, SEEK_SET);
        php_stream_seek(dba->fp, 0, SEEK_END);
        while(inifile_read(from, &ln TSRMLS_CC)) {
                switch(inifile_key_cmp(&ln.key, key TSRMLS_CC)) {
                case 0:
                        pos_curr = php_stream_tell(from->fp);
                        if (pos_start != pos_next) {
                                php_stream_seek(from->fp, pos_start, SEEK_SET);
                                if (!php_stream_copy_to_stream_ex(from->fp, dba->fp, pos_next - pos_start, NULL)) {
                                        php_error_docref(NULL TSRMLS_CC, E_WARNING, "Could not copy [%zu - %zu] from temporary stream", pos_next, pos_start);
                                        ret = FAILURE;
                                }
                                php_stream_seek(from->fp, pos_curr, SEEK_SET);
                        }
                        pos_next = pos_start = pos_curr;
                        break;
                case 1:
                        pos_next = php_stream_tell(from->fp);
                        break;
                case 2:
                        /* the function is meant to process only entries from same group */
                        assert(0);
                        break;
                }
        }
        if (pos_start != pos_next) {
                php_stream_seek(from->fp, pos_start, SEEK_SET);
                if (!php_stream_copy_to_stream_ex(from->fp, dba->fp, pos_next - pos_start, NULL)) {
                        php_error_docref(NULL TSRMLS_CC, E_WARNING, "Could not copy [%zu - %zu] from temporary stream", pos_next, pos_start);
                        ret = FAILURE;
                }
        }
        inifile_line_free(&ln);
        return ret;
}
/* }}} */

/* {{{ inifile_delete_replace_append
 */
static int inifile_delete_replace_append(inifile *dba, const key_type *key, const val_type *value, int append TSRMLS_DC) 
{
        size_t pos_grp_start=0, pos_grp_next;
        inifile *ini_tmp = NULL;
        php_stream *fp_tmp = NULL;
        int ret;

        /* 1) Search group start
         * 2) Search next group
         * 3) If not append: Copy group to ini_tmp
         * 4) Open temp_stream and copy remainder
         * 5) Truncate stream
         * 6) If not append AND key.name given: Filtered copy back from ini_tmp 
         *    to stream. Otherwise the user wanted to delete the group.
         * 7) Append value if given
         * 8) Append temporary stream
         */

        assert(!append || (key->name && value)); /* missuse */

        /* 1 - 3 */
        inifile_find_group(dba, key, &pos_grp_start TSRMLS_CC);
        inifile_next_group(dba, key, &pos_grp_next TSRMLS_CC);
        if (append) {
                ret = SUCCESS;
        } else {
                ret = inifile_copy_to(dba, pos_grp_start, pos_grp_next, &ini_tmp TSRMLS_CC);
        }

        /* 4 */
        if (ret == SUCCESS) {
                fp_tmp = php_stream_temp_create(0, 64 * 1024);
                if (!fp_tmp) {
                        php_error_docref(NULL TSRMLS_CC, E_WARNING, "Could not create temporary stream");
                        ret = FAILURE;
                } else {
                        php_stream_seek(dba->fp, 0, SEEK_END);
                        if (pos_grp_next != (size_t)php_stream_tell(dba->fp)) {
                                php_stream_seek(dba->fp, pos_grp_next, SEEK_SET);
                                if (!php_stream_copy_to_stream_ex(dba->fp, fp_tmp, PHP_STREAM_COPY_ALL, NULL)) {
                                        php_error_docref(NULL TSRMLS_CC, E_WARNING, "Could not copy remainder to temporary stream");
                                        ret = FAILURE;
                                }
                        }
                }
        }
        
        /* 5 */
        if (ret == SUCCESS) {
                if (!value || (key->name && strlen(key->name))) {
                        ret = inifile_truncate(dba, append ? pos_grp_next : pos_grp_start TSRMLS_CC); /* writes error on fail */
                }
        }

        if (ret == SUCCESS) {
                if (key->name && strlen(key->name)) {
                        /* 6 */
                        if (!append && ini_tmp) {
                                ret = inifile_filter(dba, ini_tmp, key TSRMLS_CC);
                        }

                        /* 7 */
                        /* important: do not query ret==SUCCESS again: inifile_filter might fail but
                         * however next operation must be done.
                         */
                        if (value) {
                                if (pos_grp_start == pos_grp_next && key->group && strlen(key->group)) {
                                        php_stream_printf(dba->fp TSRMLS_CC, "[%s]\n", key->group);
                                }
                                php_stream_printf(dba->fp TSRMLS_CC, "%s=%s\n", key->name, value->value ? value->value : "");
                        }
                }
                
                /* 8 */ 
                /* important: do not query ret==SUCCESS again: inifile_filter might fail but
                 * however next operation must be done.
                 */
                if (fp_tmp && php_stream_tell(fp_tmp)) {
                        php_stream_seek(fp_tmp, 0, SEEK_SET);
                        php_stream_seek(dba->fp, 0, SEEK_END);
                        if (!php_stream_copy_to_stream_ex(fp_tmp, dba->fp, PHP_STREAM_COPY_ALL, NULL)) {
                                php_error_docref(NULL TSRMLS_CC, E_RECOVERABLE_ERROR, "Could not copy from temporary stream - ini file truncated");
                                ret = FAILURE;
                        }
                }
        }

        if (ini_tmp) {
                php_stream_close(ini_tmp->fp);
                inifile_free(ini_tmp, 0);
        }
        if (fp_tmp) {
                php_stream_close(fp_tmp);
        }
        php_stream_flush(dba->fp);
        php_stream_seek(dba->fp, 0, SEEK_SET);

        return ret;
}
/* }}} */

/* {{{ inifile_delete
 */
int inifile_delete(inifile *dba, const key_type *key TSRMLS_DC) 
{
        return inifile_delete_replace_append(dba, key, NULL, 0 TSRMLS_CC);
}       
/* }}} */

/* {{{ inifile_relace
 */
int inifile_replace(inifile *dba, const key_type *key, const val_type *value TSRMLS_DC) 
{
        return inifile_delete_replace_append(dba, key, value, 0 TSRMLS_CC);
}
/* }}} */

/* {{{ inifile_append
 */
int inifile_append(inifile *dba, const key_type *key, const val_type *value TSRMLS_DC) 
{
        return inifile_delete_replace_append(dba, key, value, 1 TSRMLS_CC);
}
/* }}} */

/*
 * Local variables:
 * tab-width: 4
 * c-basic-offset: 4
 * End:
 * vim600: sw=4 ts=4 fdm=marker
 * vim<600: sw=4 ts=4
 */

/* [<][>][^][v][top][bottom][index][help] */