root/ext/dbase/dbase.c

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

DEFINITIONS

This source file includes following definitions.
  1. _close_dbase
  2. PHP_MINIT_FUNCTION
  3. PHP_MSHUTDOWN_FUNCTION
  4. PHP_FUNCTION
  5. PHP_FUNCTION
  6. PHP_FUNCTION
  7. PHP_FUNCTION
  8. PHP_FUNCTION
  9. PHP_FUNCTION
  10. PHP_FUNCTION
  11. PHP_FUNCTION
  12. php_dbase_get_record
  13. PHP_FUNCTION
  14. PHP_FUNCTION
  15. PHP_FUNCTION
  16. PHP_FUNCTION
  17. ZEND_GET_MODULE

/*
   +----------------------------------------------------------------------+
   | PHP Version 5                                                        |
   +----------------------------------------------------------------------+
   | Copyright (c) 1997-2008 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: Jim Winstead <jimw@php.net>                                  |
   +----------------------------------------------------------------------+
 */

/* $Id: dbase.c,v 1.74.2.2.2.10 2007/12/31 07:20:05 sebastian Exp $ */

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

#include "php.h"
#include "safe_mode.h"
#include "fopen_wrappers.h"
#include "php_globals.h"

#include <stdlib.h>

#ifdef HAVE_SYS_TYPES_H
#include <sys/types.h>
#endif

#if DBASE
#include "php_dbase.h"
#include "dbf.h"
#if defined(THREAD_SAFE)
DWORD DbaseTls;
static int numthreads=0;
void *dbase_mutex;

typedef struct dbase_global_struct{
        int le_dbhead;
}dbase_global_struct;

#define DBase_GLOBAL(a) dbase_globals->a

#define DBase_TLS_VARS \
        dbase_global_struct *dbase_globals; \
        dbase_globals=TlsGetValue(DbaseTls); 

#else
static int le_dbhead;
#define DBase_GLOBAL(a) a
#define DBase_TLS_VARS
#endif

#include <fcntl.h>
#include <errno.h>


static void _close_dbase(zend_rsrc_list_entry *rsrc TSRMLS_DC)
{
        dbhead_t *dbhead = (dbhead_t *)rsrc->ptr;

        close(dbhead->db_fd);
        free_dbf_head(dbhead);
}


PHP_MINIT_FUNCTION(dbase)
{
#if defined(THREAD_SAFE)
        dbase_global_struct *dbase_globals;
#ifdef COMPILE_DL_DBASE
        CREATE_MUTEX(dbase_mutex, "DBase_TLS");
        SET_MUTEX(dbase_mutex);
        numthreads++;
        if (numthreads==1){
        if ((DbaseTls=TlsAlloc())==0xFFFFFFFF){
                FREE_MUTEX(dbase_mutex);
                return 0;
        }}
        FREE_MUTEX(dbase_mutex);
#endif
        dbase_globals = (dbase_global_struct *) LocalAlloc(LPTR, sizeof(dbase_global_struct)); 
        TlsSetValue(DbaseTls, (void *) dbase_globals);
#endif
        DBase_GLOBAL(le_dbhead) =
                zend_register_list_destructors_ex(_close_dbase, NULL, "dbase", module_number);
        return SUCCESS;
}

static PHP_MSHUTDOWN_FUNCTION(dbase)
{
#if defined(THREAD_SAFE)
        dbase_global_struct *dbase_globals;
        dbase_globals = TlsGetValue(DbaseTls); 
        if (dbase_globals != 0) 
                LocalFree((HLOCAL) dbase_globals); 
#ifdef COMPILE_DL_DBASE
        SET_MUTEX(dbase_mutex);
        numthreads--;
        if (!numthreads){
        if (!TlsFree(DbaseTls)){
                FREE_MUTEX(dbase_mutex);
                return 0;
        }}
        FREE_MUTEX(dbase_mutex);
#endif
#endif
        return SUCCESS;
}

/* {{{ proto int dbase_open(string name, int mode)
   Opens a dBase-format database file */
PHP_FUNCTION(dbase_open)
{
        zval **dbf_name, **options;
        dbhead_t *dbh;
        int handle;
        DBase_TLS_VARS;

        if (ZEND_NUM_ARGS() != 2 || zend_get_parameters_ex(2, &dbf_name, &options) == FAILURE) {
                WRONG_PARAM_COUNT;
        }
        convert_to_string_ex(dbf_name);
        convert_to_long_ex(options);

        if (!Z_STRLEN_PP(dbf_name)) {
                php_error_docref(NULL TSRMLS_CC, E_WARNING, "The filename cannot be empty.");
                RETURN_FALSE;
        }

        if (Z_LVAL_PP(options) == 1) {
                php_error_docref(NULL TSRMLS_CC, E_WARNING, "Cannot open %s in write-only mode", Z_STRVAL_PP(dbf_name));
                RETURN_FALSE;
        } else if (Z_LVAL_PP(options) < 0 || Z_LVAL_PP(options) > 3) {
                php_error_docref(NULL TSRMLS_CC, E_WARNING, "Invalid access mode %ld", Z_LVAL_PP(options));
                RETURN_FALSE;
        }

        if (PG(safe_mode) && (!php_checkuid(Z_STRVAL_PP(dbf_name), NULL, CHECKUID_CHECK_FILE_AND_DIR))) {
                RETURN_FALSE;
        }
        
        if (php_check_open_basedir(Z_STRVAL_PP(dbf_name) TSRMLS_CC)) {
                RETURN_FALSE;
        }

        dbh = dbf_open(Z_STRVAL_PP(dbf_name), Z_LVAL_PP(options) TSRMLS_CC);
        if (dbh == NULL) {
                php_error_docref(NULL TSRMLS_CC, E_WARNING, "unable to open database %s", Z_STRVAL_PP(dbf_name));
                RETURN_FALSE;
        }

        handle = zend_list_insert(dbh, DBase_GLOBAL(le_dbhead));
        RETURN_LONG(handle);
}
/* }}} */

/* {{{ proto bool dbase_close(int identifier)
   Closes an open dBase-format database file */
PHP_FUNCTION(dbase_close)
{
        zval **dbh_id;
        dbhead_t *dbh;
        int dbh_type;
        DBase_TLS_VARS;

        if (ZEND_NUM_ARGS() != 1 || (zend_get_parameters_ex(1, &dbh_id) == FAILURE)) {
                WRONG_PARAM_COUNT;
        }
        convert_to_long_ex(dbh_id);
        dbh = zend_list_find(Z_LVAL_PP(dbh_id), &dbh_type);
        if (!dbh || dbh_type != DBase_GLOBAL(le_dbhead)) {
                php_error_docref(NULL TSRMLS_CC, E_WARNING, "Unable to find database for identifier %ld", Z_LVAL_PP(dbh_id));
                RETURN_FALSE;
        }

        zend_list_delete(Z_LVAL_PP(dbh_id));
        RETURN_TRUE;
}
/* }}} */

/* {{{ proto int dbase_numrecords(int identifier)
   Returns the number of records in the database */
PHP_FUNCTION(dbase_numrecords)
{
        zval **dbh_id;
        dbhead_t *dbh;
        int dbh_type;
        DBase_TLS_VARS;

        if (ZEND_NUM_ARGS() != 1 || (zend_get_parameters_ex(1, &dbh_id) == FAILURE)) {
                WRONG_PARAM_COUNT;
        }
        convert_to_long_ex(dbh_id);
        dbh = zend_list_find(Z_LVAL_PP(dbh_id), &dbh_type);
        if (!dbh || dbh_type != DBase_GLOBAL(le_dbhead)) {
                php_error_docref(NULL TSRMLS_CC, E_WARNING, "Unable to find database for identifier %ld", Z_LVAL_PP(dbh_id));
                RETURN_FALSE;
        }

        RETURN_LONG(dbh->db_records);
}
/* }}} */

/* {{{ proto int dbase_numfields(int identifier)
   Returns the number of fields (columns) in the database */
PHP_FUNCTION(dbase_numfields)
{
        zval **dbh_id;
        dbhead_t *dbh;
        int dbh_type;
        DBase_TLS_VARS;

        if (ZEND_NUM_ARGS() != 1 || zend_get_parameters_ex(1, &dbh_id) == FAILURE) {
                WRONG_PARAM_COUNT;
        }
        convert_to_long_ex(dbh_id);
        dbh = zend_list_find(Z_LVAL_PP(dbh_id), &dbh_type);
        if (!dbh || dbh_type != DBase_GLOBAL(le_dbhead)) {
                php_error_docref(NULL TSRMLS_CC, E_WARNING, "Unable to find database for identifier %ld", Z_LVAL_PP(dbh_id));
                RETURN_FALSE;
        }

        RETURN_LONG(dbh->db_nfields);
}
/* }}} */

/* {{{ proto bool dbase_pack(int identifier)
   Packs the database (deletes records marked for deletion) */
PHP_FUNCTION(dbase_pack)
{
        zval **dbh_id;
        dbhead_t *dbh;
        int dbh_type;
        DBase_TLS_VARS;

        if (ZEND_NUM_ARGS() != 1 || zend_get_parameters_ex(1, &dbh_id) == FAILURE) {
                WRONG_PARAM_COUNT;
        }
        convert_to_long_ex(dbh_id);
        dbh = zend_list_find(Z_LVAL_PP(dbh_id), &dbh_type);
        if (!dbh || dbh_type != DBase_GLOBAL(le_dbhead)) {
                php_error_docref(NULL TSRMLS_CC, E_WARNING, "Unable to find database for identifier %ld", Z_LVAL_PP(dbh_id));
                RETURN_FALSE;
        }

        pack_dbf(dbh);
        put_dbf_info(dbh);
        RETURN_TRUE;
}
/* }}} */

/* {{{ proto bool dbase_add_record(int identifier, array data)
   Adds a record to the database */
PHP_FUNCTION(dbase_add_record)
{
        zval **dbh_id, **fields, **field;
        dbhead_t *dbh;
        int dbh_type;

        int num_fields;
        dbfield_t *dbf, *cur_f;
        char *cp, *t_cp;
        int i;
        DBase_TLS_VARS;

        if (ZEND_NUM_ARGS() != 2 || zend_get_parameters_ex(2, &dbh_id, &fields) == FAILURE) {
                WRONG_PARAM_COUNT;
        }
        convert_to_long_ex(dbh_id);
        if (Z_TYPE_PP(fields) != IS_ARRAY) {
                php_error_docref(NULL TSRMLS_CC, E_WARNING, "Expected array as second parameter");
                RETURN_FALSE;
        }

        dbh = zend_list_find(Z_LVAL_PP(dbh_id), &dbh_type);
        if (!dbh || dbh_type != DBase_GLOBAL(le_dbhead)) {
                php_error_docref(NULL TSRMLS_CC, E_WARNING, "Unable to find database for identifier %ld", Z_LVAL_PP(dbh_id));
                RETURN_FALSE;
        }

        num_fields = zend_hash_num_elements(Z_ARRVAL_PP(fields));

        if (num_fields != dbh->db_nfields) {
                php_error_docref(NULL TSRMLS_CC, E_WARNING, "Wrong number of fields specified");
                RETURN_FALSE;
        }

        cp = t_cp = (char *)emalloc(dbh->db_rlen + 1);
        *t_cp++ = VALID_RECORD;

        dbf = dbh->db_fields;
        for (i = 0, cur_f = dbf; cur_f < &dbf[num_fields]; i++, cur_f++) {
                zval tmp;
                if (zend_hash_index_find(Z_ARRVAL_PP(fields), i, (void **)&field) == FAILURE) {
                        php_error_docref(NULL TSRMLS_CC, E_WARNING, "unexpected error");
                        efree(cp);
                        RETURN_FALSE;
                }
                
                tmp = **field;
                zval_copy_ctor(&tmp);
                convert_to_string(&tmp);
                snprintf(t_cp, cur_f->db_flen+1, cur_f->db_format, Z_STRVAL(tmp));
                zval_dtor(&tmp); 
                t_cp += cur_f->db_flen;
        }

        dbh->db_records++;
        if (put_dbf_record(dbh, dbh->db_records, cp) < 0) {
                php_error_docref(NULL TSRMLS_CC, E_WARNING, "unable to put record at %ld", dbh->db_records);
                efree(cp);
                RETURN_FALSE;
        }

        put_dbf_info(dbh);
        efree(cp);

        RETURN_TRUE;
}
/* }}} */

/* {{{ proto bool dbase_replace_record(int identifier, array data, int recnum)
   Replaces a record to the database */
PHP_FUNCTION(dbase_replace_record)
{
        zval **dbh_id, **fields, **field, **recnum;
        dbhead_t *dbh;
        int dbh_type;

        int num_fields;
        dbfield_t *dbf, *cur_f;
        char *cp, *t_cp;
        int i;
        DBase_TLS_VARS;

        if (ZEND_NUM_ARGS() != 3 || zend_get_parameters_ex(3, &dbh_id, &fields, &recnum) == FAILURE) {
                WRONG_PARAM_COUNT;
        }
        convert_to_long_ex(dbh_id);
        convert_to_long_ex(recnum);
        if (Z_TYPE_PP(fields) != IS_ARRAY) {
                php_error_docref(NULL TSRMLS_CC, E_WARNING, "Expected array as second parameter");
                RETURN_FALSE;
        }

        dbh = zend_list_find(Z_LVAL_PP(dbh_id), &dbh_type);
        if (!dbh || dbh_type != DBase_GLOBAL(le_dbhead)) {
                php_error_docref(NULL TSRMLS_CC, E_WARNING, "Unable to find database for identifier %ld", Z_LVAL_PP(dbh_id));
                RETURN_FALSE;
        }

        num_fields = zend_hash_num_elements(Z_ARRVAL_PP(fields));

        if (num_fields != dbh->db_nfields) {
                php_error_docref(NULL TSRMLS_CC, E_WARNING, "Wrong number of fields specified");
                RETURN_FALSE;
        }

        cp = t_cp = (char *)emalloc(dbh->db_rlen + 1);
        *t_cp++ = VALID_RECORD;

        dbf = dbh->db_fields;
        for (i = 0, cur_f = dbf; cur_f < &dbf[num_fields]; i++, cur_f++) {
                if (zend_hash_index_find(Z_ARRVAL_PP(fields), i, (void **)&field) == FAILURE) {
                        php_error_docref(NULL TSRMLS_CC, E_WARNING, "unexpected error");
                        efree(cp);
                        RETURN_FALSE;
                }
                convert_to_string_ex(field);
                snprintf(t_cp, cur_f->db_flen+1, cur_f->db_format, Z_STRVAL_PP(field)); 
                t_cp += cur_f->db_flen;
        }

        if (put_dbf_record(dbh, Z_LVAL_PP(recnum), cp) < 0) {
                php_error_docref(NULL TSRMLS_CC, E_WARNING, "unable to put record at %ld", dbh->db_records);
                efree(cp);
                RETURN_FALSE;
        }

        put_dbf_info(dbh);
        efree(cp);

        RETURN_TRUE;
}
/* }}} */

/* {{{ proto bool dbase_delete_record(int identifier, int record)
   Marks a record to be deleted */
PHP_FUNCTION(dbase_delete_record)
{
        zval **dbh_id, **record;
        dbhead_t *dbh;
        int dbh_type;
        DBase_TLS_VARS;

        if (ZEND_NUM_ARGS() != 2 || (zend_get_parameters_ex(2, &dbh_id, &record) == FAILURE)) {
                WRONG_PARAM_COUNT;
        }
        convert_to_long_ex(dbh_id);
        convert_to_long_ex(record);

        dbh = zend_list_find(Z_LVAL_PP(dbh_id), &dbh_type);
        if (!dbh || dbh_type != DBase_GLOBAL(le_dbhead)) {
                php_error_docref(NULL TSRMLS_CC, E_WARNING, "Unable to find database for identifier %ld", Z_LVAL_PP(dbh_id));
                RETURN_FALSE;
        }

        if (del_dbf_record(dbh, Z_LVAL_PP(record)) < 0) {
                if (Z_LVAL_PP(record) > dbh->db_records) {
                        php_error_docref(NULL TSRMLS_CC, E_WARNING, "record %ld out of bounds", Z_LVAL_PP(record));
                } else {
                        php_error_docref(NULL TSRMLS_CC, E_WARNING, "unable to delete record %ld", Z_LVAL_PP(record));
                }
                RETURN_FALSE;
        }

        put_dbf_info(dbh);
        RETURN_TRUE;
}
/* }}} */

/* {{{ php_dbase_get_record
 */  
static void php_dbase_get_record(INTERNAL_FUNCTION_PARAMETERS, int assoc)
{
        zval **dbh_id, **record;
        dbhead_t *dbh;
        int dbh_type;
        dbfield_t *dbf, *cur_f;
        char *data, *fnp, *str_value;
        size_t cursize = 0;
        long overflow_test;
        int errno_save;
        DBase_TLS_VARS;

        if (ZEND_NUM_ARGS() != 2 || (zend_get_parameters_ex(2, &dbh_id, &record) == FAILURE)) {
                WRONG_PARAM_COUNT;
        }
        convert_to_long_ex(dbh_id);
        convert_to_long_ex(record);

        dbh = zend_list_find(Z_LVAL_PP(dbh_id), &dbh_type);
        if (!dbh || dbh_type != DBase_GLOBAL(le_dbhead)) {
                php_error_docref(NULL TSRMLS_CC, E_WARNING, "Unable to find database for identifier %ld", Z_LVAL_PP(dbh_id));
                RETURN_FALSE;
        }

        if ((data = get_dbf_record(dbh, Z_LVAL_PP(record))) == NULL) {
                php_error_docref(NULL TSRMLS_CC, E_WARNING, "Tried to read bad record %ld", Z_LVAL_PP(record));
                RETURN_FALSE;
        }

        dbf = dbh->db_fields;

        array_init(return_value);

        fnp = NULL;
        for (cur_f = dbf; cur_f < &dbf[dbh->db_nfields]; cur_f++) {
                /* get the value */
                str_value = (char *)emalloc(cur_f->db_flen + 1);

                if(cursize <= (unsigned)cur_f->db_flen) {
                        cursize = cur_f->db_flen + 1;
                        fnp = erealloc(fnp, cursize);
                }
                snprintf(str_value, cursize, cur_f->db_format, get_field_val(data, cur_f, fnp));

                /* now convert it to the right php internal type */
                switch (cur_f->db_type) {
                        case 'C':
                        case 'D':
                                if (!assoc) {
                                        add_next_index_string(return_value, str_value, 1);
                                } else {
                                        add_assoc_string(return_value, cur_f->db_fname, str_value, 1);
                                }
                                break;
                        case 'I':       /* FALLS THROUGH */
                        case 'N':
                                if (cur_f->db_fdc == 0) {
                                        /* Large integers in dbase can be larger than long */
                                        errno_save = errno;
                                        overflow_test = strtol(str_value, NULL, 10);
                                        if (errno == ERANGE) {
                                            /* If the integer is too large, keep it as string */
                                                if (!assoc) {
                                                    add_next_index_string(return_value, str_value, 1);
                                                } else {
                                                    add_assoc_string(return_value, cur_f->db_fname, str_value, 1);
                                                }
                                        } else {
                                                if (!assoc) {
                                                    add_next_index_long(return_value, overflow_test);
                                                } else {
                                                    add_assoc_long(return_value, cur_f->db_fname, overflow_test);
                                                }
                                        }
                                        errno = errno_save;
                                } else {
                                        if (!assoc) {
                                                add_next_index_double(return_value, atof(str_value));
                                        } else {
                                                add_assoc_double(return_value, cur_f->db_fname, atof(str_value));
                                        }
                                }
                                break;
                        case 'F':
                                if (!assoc) {
                                        add_next_index_double(return_value, atof(str_value));
                                } else {
                                        add_assoc_double(return_value, cur_f->db_fname, atof(str_value));
                                }
                                break;
                        case 'L':       /* we used to FALL THROUGH, but now we check for T/Y and F/N
                                                   and insert 1 or 0, respectively.  db_fdc is the number of
                                                   decimals, which we don't care about.      3/14/2001 LEW */
                                if ((*str_value == 'T') || (*str_value == 'Y')) {
                                        if (!assoc) {
                                                add_next_index_long(return_value, strtol("1", NULL, 10));
                                        } else {
                                                add_assoc_long(return_value, cur_f->db_fname,strtol("1", NULL, 10));
                                        }
                                } else {
                                        if ((*str_value == 'F') || (*str_value == 'N')) {
                                                if (!assoc) {
                                                        add_next_index_long(return_value, strtol("0", NULL, 10));
                                                } else {
                                                        add_assoc_long(return_value, cur_f->db_fname,strtol("0", NULL, 10));
                                                }
                                        } else {
                                                if (!assoc) {
                                                        add_next_index_long(return_value, strtol(" ", NULL, 10));
                                                } else {
                                                        add_assoc_long(return_value, cur_f->db_fname,strtol(" ", NULL, 10));
                                                }
                                        }
                                }
                                break;
                        case 'M':
                                /* this is a memo field. don't know how to deal with this yet */
                                break;
                        default:
                                /* should deal with this in some way */
                                break;
                }
                efree(str_value);
        }

        efree(fnp);

        /* mark whether this record was deleted */
        if (data[0] == '*') {
                add_assoc_long(return_value, "deleted", 1);
        } else {
                add_assoc_long(return_value, "deleted", 0);
        }

        free(data);
}
/* }}} */
 
/* {{{ proto array dbase_get_record(int identifier, int record)
   Returns an array representing a record from the database */
PHP_FUNCTION(dbase_get_record)
{
        php_dbase_get_record(INTERNAL_FUNCTION_PARAM_PASSTHRU, 0);
}
/* }}} */

/* From Martin Kuba <makub@aida.inet.cz> */
/* {{{ proto array dbase_get_record_with_names(int identifier, int record)
   Returns an associative array representing a record from the database */
PHP_FUNCTION(dbase_get_record_with_names)
{
        php_dbase_get_record(INTERNAL_FUNCTION_PARAM_PASSTHRU, 1);
}
/* }}} */

/* {{{ proto bool dbase_create(string filename, array fields)
   Creates a new dBase-format database file */
PHP_FUNCTION(dbase_create)
{
        zval **filename, **fields, **field, **value;
        int fd;
        dbhead_t *dbh;

        int num_fields;
        dbfield_t *dbf, *cur_f;
        int i, rlen, handle;
        DBase_TLS_VARS;

        if (ZEND_NUM_ARGS() != 2 || (zend_get_parameters_ex(2, &filename, &fields) == FAILURE)) {
                WRONG_PARAM_COUNT;
        }
        convert_to_string_ex(filename);

        if (Z_TYPE_PP(fields) != IS_ARRAY) {
                php_error_docref(NULL TSRMLS_CC, E_WARNING, "Expected array as second parameter");
                RETURN_FALSE;
        }

        if (PG(safe_mode) && (!php_checkuid(Z_STRVAL_PP(filename), NULL, CHECKUID_CHECK_FILE_AND_DIR))) {
                RETURN_FALSE;
        }
        
        if (php_check_open_basedir(Z_STRVAL_PP(filename) TSRMLS_CC)) {
                RETURN_FALSE;
        }

        if ((fd = VCWD_OPEN_MODE(Z_STRVAL_PP(filename), O_BINARY|O_RDWR|O_CREAT, 0644)) < 0) {
                php_error_docref(NULL TSRMLS_CC, E_WARNING, "Unable to create database (%d): %s", errno, strerror(errno));
                RETURN_FALSE;
        }

        num_fields = zend_hash_num_elements(Z_ARRVAL_PP(fields));

        if (num_fields <= 0) {
                php_error_docref(NULL TSRMLS_CC, E_WARNING, "Unable to create database without fields");
                RETURN_FALSE;
        }

        /* have to use regular malloc() because this gets free()d by
           code in the dbase library */
        dbh = (dbhead_t *)malloc(sizeof(dbhead_t));
        dbf = (dbfield_t *)malloc(sizeof(dbfield_t) * num_fields);
        if (!dbh || !dbf) {
                php_error_docref(NULL TSRMLS_CC, E_WARNING, "Unable to allocate memory for header info");
                RETURN_FALSE;
        }
        
        /* initialize the header structure */
        dbh->db_fields = dbf;
        dbh->db_fd = fd;
        dbh->db_dbt = DBH_TYPE_NORMAL;
        strcpy(dbh->db_date, "19930818");
        dbh->db_records = 0;
        dbh->db_nfields = num_fields;
        dbh->db_hlen = sizeof(struct dbf_dhead) + 1 + num_fields * sizeof(struct dbf_dfield);

        rlen = 1;
        /**
         * Patch by greg@darkphoton.com
         **/
        /* make sure that the db_format entries for all fields are set to NULL to ensure we
       don't seg fault if there's an error and we need to call free_dbf_head() before all
       fields have been defined. */
        for (i = 0, cur_f = dbf; i < num_fields; i++, cur_f++) {
                cur_f->db_format = NULL;
        }
        /**
         * end patch
         */


        for (i = 0, cur_f = dbf; i < num_fields; i++, cur_f++) {
                /* look up the first field */
                if (zend_hash_index_find(Z_ARRVAL_PP(fields), i, (void **)&field) == FAILURE) {
                        php_error_docref(NULL TSRMLS_CC, E_WARNING, "unable to find field %d", i);
                        free_dbf_head(dbh);
                        RETURN_FALSE;
                }

                if (Z_TYPE_PP (field) != IS_ARRAY) {
                        php_error_docref(NULL TSRMLS_CC, E_WARNING, "second parameter must be array of arrays");
                        free_dbf_head(dbh);
                        RETURN_FALSE;
                }

                /* field name */
                if (zend_hash_index_find(Z_ARRVAL_PP(field), 0, (void **)&value) == FAILURE) {
                        php_error_docref(NULL TSRMLS_CC, E_WARNING, "expected field name as first element of list in field %d", i);
                        free_dbf_head(dbh);
                        RETURN_FALSE;
                }
                convert_to_string_ex(value);
                if (Z_STRLEN_PP(value) > 10 || Z_STRLEN_PP(value) == 0) {
                        php_error_docref(NULL TSRMLS_CC, E_WARNING, "invalid field name '%s' (must be non-empty and less than or equal to 10 characters)", Z_STRVAL_PP(value));
                        free_dbf_head(dbh);
                        RETURN_FALSE;
                }
                copy_crimp(cur_f->db_fname, Z_STRVAL_PP(value), Z_STRLEN_PP(value));

                /* field type */
                if (zend_hash_index_find(Z_ARRVAL_PP (field), 1, (void **)&value) == FAILURE) {
                        php_error_docref(NULL TSRMLS_CC, E_WARNING, "expected field type as second element of list in field %d", i);
                        RETURN_FALSE;
                }
                convert_to_string_ex(value);
                cur_f->db_type = toupper(*Z_STRVAL_PP(value));

                cur_f->db_fdc = 0;

                /* verify the field length */
                switch (cur_f->db_type) {
                case 'L':
                        cur_f->db_flen = 1;
                        break;
                case 'M':
                        cur_f->db_flen = 10;
                        dbh->db_dbt = DBH_TYPE_MEMO;
                        /* should create the memo file here, probably */
                        break;
                case 'D':
                        cur_f->db_flen = 8;
                        break;
                case 'F':
                        cur_f->db_flen = 20;
                        break;
                case 'N':
                case 'C':
                        /* field length */
                        if (zend_hash_index_find(Z_ARRVAL_PP (field), 2, (void **)&value) == FAILURE) {
                                php_error_docref(NULL TSRMLS_CC, E_WARNING, "expected field length as third element of list in field %d", i);
                                free_dbf_head(dbh);
                                RETURN_FALSE;
                        }
                        convert_to_long_ex(value);
                        cur_f->db_flen = Z_LVAL_PP(value);

                        if (cur_f->db_type == 'N') {
                                if (zend_hash_index_find(Z_ARRVAL_PP (field), 3, (void **)&value) == FAILURE) {
                                        php_error_docref(NULL TSRMLS_CC, E_WARNING, "expected field precision as fourth element of list in field %d", i);
                                        free_dbf_head(dbh);
                                        RETURN_FALSE;
                                }
                                convert_to_long_ex(value);
                                cur_f->db_fdc = Z_LVAL_PP(value);
                        }
                        break;
                default:
                        php_error_docref(NULL TSRMLS_CC, E_WARNING, "unknown field type '%c'", cur_f->db_type);
                        free_dbf_head(dbh);
                        RETURN_FALSE;
                }
                cur_f->db_foffset = rlen;
                rlen += cur_f->db_flen;
        
                cur_f->db_format = get_dbf_f_fmt(cur_f);
        }

        dbh->db_rlen = rlen;
        put_dbf_info(dbh);

        handle = zend_list_insert(dbh, DBase_GLOBAL(le_dbhead));
        RETURN_LONG(handle);
}
/* }}} */

/* {{{ arginfo */
static
ZEND_BEGIN_ARG_INFO(arginfo_dbase_open, 0)
        ZEND_ARG_INFO(0, name)
        ZEND_ARG_INFO(0, mode)
ZEND_END_ARG_INFO()

static
ZEND_BEGIN_ARG_INFO(arginfo_dbase_close, 0)
        ZEND_ARG_INFO(0, identifier)
ZEND_END_ARG_INFO()

static
ZEND_BEGIN_ARG_INFO(arginfo_dbase_numrecords, 0)
        ZEND_ARG_INFO(0, identifier)
ZEND_END_ARG_INFO()

static
ZEND_BEGIN_ARG_INFO(arginfo_dbase_numfields, 0)
        ZEND_ARG_INFO(0, identifier)
ZEND_END_ARG_INFO()

static
ZEND_BEGIN_ARG_INFO(arginfo_dbase_pack, 0)
        ZEND_ARG_INFO(0, identifier)
ZEND_END_ARG_INFO()

static
ZEND_BEGIN_ARG_INFO(arginfo_dbase_add_record, 0)
        ZEND_ARG_INFO(0, identifier)
        ZEND_ARG_INFO(0, data) /* ARRAY_INFO(0, data, 0) */
ZEND_END_ARG_INFO()

static
ZEND_BEGIN_ARG_INFO(arginfo_dbase_replace_record, 0)
        ZEND_ARG_INFO(0, identifier)
        ZEND_ARG_INFO(0, data) /* ARRAY_INFO(0, data, 0) */
        ZEND_ARG_INFO(0, recnum)
ZEND_END_ARG_INFO()

static
ZEND_BEGIN_ARG_INFO(arginfo_dbase_delete_record, 0)
        ZEND_ARG_INFO(0, identifier)
        ZEND_ARG_INFO(0, record)
ZEND_END_ARG_INFO()

static
ZEND_BEGIN_ARG_INFO(arginfo_dbase_get_record, 0)
        ZEND_ARG_INFO(0, identifier)
        ZEND_ARG_INFO(0, record)
ZEND_END_ARG_INFO()

static
ZEND_BEGIN_ARG_INFO(arginfo_dbase_get_record_with_names, 0)
        ZEND_ARG_INFO(0, identifier)
        ZEND_ARG_INFO(0, record)
ZEND_END_ARG_INFO()

static
ZEND_BEGIN_ARG_INFO(arginfo_dbase_create, 0)
        ZEND_ARG_INFO(0, filename)
        ZEND_ARG_INFO(0, fields) /* ARRAY_INFO(0, fields, 0) */
ZEND_END_ARG_INFO()

static
ZEND_BEGIN_ARG_INFO(arginfo_dbase_get_header_info, 0)
        ZEND_ARG_INFO(0, database_handle)
ZEND_END_ARG_INFO()

/* }}} */

/* {{{ dbase_functions[]
 */
zend_function_entry dbase_functions[] = {
        PHP_FE(dbase_open,                                                              arginfo_dbase_open)
        PHP_FE(dbase_create,                                                    arginfo_dbase_create)
        PHP_FE(dbase_close,                                                             arginfo_dbase_close)
        PHP_FE(dbase_numrecords,                                                arginfo_dbase_numrecords)
        PHP_FE(dbase_numfields,                                                 arginfo_dbase_numfields)
        PHP_FE(dbase_add_record,                                                arginfo_dbase_add_record)
        PHP_FE(dbase_replace_record,                                    arginfo_dbase_replace_record)
        PHP_FE(dbase_get_record,                                                arginfo_dbase_get_record)
        PHP_FE(dbase_get_record_with_names,                             arginfo_dbase_get_record_with_names)
        PHP_FE(dbase_delete_record,                                             arginfo_dbase_delete_record)
        PHP_FE(dbase_pack,                                                              arginfo_dbase_pack)
        PHP_FE(dbase_get_header_info,                                   arginfo_dbase_get_header_info)
        {NULL, NULL, NULL}
};
/* }}} */

/* Added by Zak Greant <zak@php.net> */
/* {{{ proto array dbase_get_header_info(int database_handle)
 */
PHP_FUNCTION(dbase_get_header_info)
{
        zval            **dbh_id, *row;
        dbfield_t       *dbf, *cur_f;
        dbhead_t        *dbh;
        int             dbh_type;
        DBase_TLS_VARS; 

        if (ZEND_NUM_ARGS() != 1 || (zend_get_parameters_ex(1, &dbh_id) == FAILURE)) {
                WRONG_PARAM_COUNT;
        }
        convert_to_long_ex(dbh_id);

        dbh = zend_list_find(Z_LVAL_PP(dbh_id), &dbh_type);
        if (!dbh || dbh_type != DBase_GLOBAL(le_dbhead)) {
                php_error_docref(NULL TSRMLS_CC, E_WARNING, "Unable to find database for identifier %ld", Z_LVAL_PP(dbh_id));
                RETURN_FALSE;
        }

        array_init(return_value);

        dbf = dbh->db_fields;
        for (cur_f = dbf; cur_f < &dbh->db_fields[dbh->db_nfields]; ++cur_f) {
                MAKE_STD_ZVAL(row);
                array_init(row);
                
                add_next_index_zval(return_value, row);
                
                /* field name */
                add_assoc_string(row, "name", cur_f->db_fname, 1);
                
                /* field type */
                switch (cur_f->db_type) {
                        case 'C': add_assoc_string(row, "type", "character", 1);        break;
                        case 'D': add_assoc_string(row, "type", "date", 1);             break;
                        case 'I': add_assoc_string(row, "type", "integer", 1);          break;
                        case 'N': add_assoc_string(row, "type", "number", 1);           break;
                        case 'L': add_assoc_string(row, "type", "boolean", 1);          break;
                        case 'M': add_assoc_string(row, "type", "memo", 1);                     break;
                        case 'F': add_assoc_string(row, "type", "float", 1);     break;
                        default:  add_assoc_string(row, "type", "unknown", 1);          break;
                }
                
                /* length of field */
                add_assoc_long(row, "length", cur_f->db_flen);
                
                /* number of decimals in field */
                switch (cur_f->db_type) {
                        case 'N':
                        case 'I':
                                add_assoc_long(row, "precision", cur_f->db_fdc);
                                break;
                        default:
                                add_assoc_long(row, "precision", 0);
                }

                /* format for printing %s etc */
                add_assoc_string(row, "format", cur_f->db_format, 1);
                
                /* offset within record */
                add_assoc_long(row, "offset", cur_f->db_foffset);
        }
}
/* }}} */

zend_module_entry dbase_module_entry = {
        STANDARD_MODULE_HEADER,
        "dbase", dbase_functions, PHP_MINIT(dbase), PHP_MSHUTDOWN(dbase), NULL, NULL, NULL, NO_VERSION_YET, STANDARD_MODULE_PROPERTIES
};


#ifdef COMPILE_DL_DBASE
ZEND_GET_MODULE(dbase)

#if defined(PHP_WIN32) && defined(THREAD_SAFE)

/*NOTE: You should have an odbc.def file where you
export DllMain*/
BOOL WINAPI DllMain(HANDLE hModule, 
                      DWORD  ul_reason_for_call, 
                      LPVOID lpReserved)
{
    return 1;
}
#endif
#endif

#endif

/*
 * 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] */