/*

        DBFREP.C

        Reparador de Bases de Datos .DBF.

        (C) Angel Ortega 1992 Kaplan Development Software.



        Repara los siguientes errores:

        .- Comprueba que la marca sea v lida.
        .- Comprueba que la longitud de la cabecera coincida con
           el campo de control de la cabecera.
        .- Revisa que el n£mero de registros marcados en la cabecera
           sean los mismos que hay realmente en el fichero.
        .- Comprueba que el car cter de borrado sea un car cter
           v lido.
        .- Sustituye por espacios los c¢digos menores de 32 que
           se encuentre en los campos.

*/

#include <stdio.h>
#include <io.h>
#include <string.h>
#include <fcntl.h>
#include <sys\stat.h>

#define VERSION "1.0"

/* firma de .DBF normal */
#define DBF_NORM 0x03
/* firma de .DBF con campo MEMO */
#define DBF_MEMO 0x83
/* car cter de fin de estructura de campos */
#define DBF_FINSTRU 0x0D


/*************************
        Variables
*************************/

/* estructura de la cabecera de una .DBF */
struct dbfcab
{
        unsigned char marca;            /* marca dBASE */
        unsigned char ano;              /* ano £ltima modificaci¢n */
        unsigned char mes;              /* mes */
        unsigned char dia;              /* dia */
        unsigned long numreg;           /* n£mero de registros */
        unsigned int tamcab;            /* tama¤o de la cabecera */
        unsigned int tamreg;            /* tama¤o del registro */
        unsigned char filler[20];
};


/* estructura de un descriptor de campo en un .DBF */
struct dbfcmp
{
        char nombre[11];        /* nombre del campo */
        char tipo;              /* tipo del campo */
        char filler1[4];        /* despreciados */
        unsigned char longi;    /* longitud */
        unsigned char decim;    /* n£mero de decimales */
        char filler2[14];       /* m s despreciados */
};


/* n£mero global de errores */
int Errores=0;
/* handlers de lectura/escritura */
int i,o;
/* tama¤o de la cabecera */
unsigned int l_tamcab;
/* n£mero de campos MEMO */
int memos=0;


/***********************
        Funciones
***********************/

int VarChar(char caracter)
/* devuelve true si es un car cter v lido en nombre de campo */
{
        if(caracter==0 || caracter=='_')
                return(1);
        if(caracter>='A' && caracter<='Z')
                return(1);
        if(caracter>='a' && caracter<='z')
                return(1);
        if(caracter>='0' && caracter<='9')
                return(1);

        return(0);
}


void Go(int retorno)
/* se va! */
{
        if(Errores!=0)
                printf("*** %d errores reparados. ***\n", Errores);
        else
                printf("*** No hay errores. ***\n");

        close(i);
        close(o);

        exit(retorno);
}


void Error(char * texto)
/* contabiliza un error m s (reparable) */
{
        printf("Error: %s.\n", texto);
        Errores++;
}


void ErrorFatal(char * texto, int retorno)
/* sale con error fatal (no reparable) */
{
        printf("Error fatal: %s.\n", texto);
        Go(retorno);
}


void LeerCabDBF(int handle, struct dbfcab * cab)
/* lee la cabecera de una DBF */
{
        if(read(handle, cab, sizeof(struct dbfcab))!=sizeof(struct dbfcab))
                ErrorFatal("Imposible leer cabecera de fichero origen",1);
}


void EscrCabDBF(int handle, struct dbfcab * cab)
/* graba al disco la cabecera de una DBF */
{
        long l;

        /* coge la posici¢n actual y va al principio */
        l=tell(handle);
        lseek(handle,0,0);

        /* graba la cabecera */
        if(write(handle, cab, sizeof(struct dbfcab))!=sizeof(struct dbfcab))
                ErrorFatal("Imposible grabar cabecera en fichero destino",2);

        /* deja el puntero donde estaba */
        lseek(handle,l,0);
}


int AbrirDBF(char * origen)
/* abre una base de datos. Devuelve el handler */
{
        int i;

        if((i=open(origen, O_RDONLY|O_BINARY))==-1)
                ErrorFatal("Imposible abrir base de datos origen",1);
        return(i);
}


int CrearDBF(char * destino)
/* crea una base de datos. Devuelve el handler */
{
        int i;

        if((i=open(destino, O_CREAT|O_TRUNC|O_WRONLY|O_BINARY,
                S_IREAD|S_IWRITE))==-1)
                ErrorFatal("Imposible crear base de datos destino",2);
        return(i);
}


int LeerCmpDBF(int handle, struct dbfcmp * cmp)
/* lee una estructura de campo */
{
        int i;

        if(read(handle, cmp, sizeof(struct dbfcmp))!=sizeof(struct dbfcmp))
                ErrorFatal("Imposible leer estructura de campos",1);

        /* si es un \r, entonces se acab¢ la estructura de campos */
        if(cmp->nombre[0]==DBF_FINSTRU)
                return(0);
        else
                return(1);
}


void EscrCmpDBF(int handle, struct dbfcmp * cmp)
/* graba una estructura de campo */
{
        int i;

        if(write(handle, cmp, sizeof(struct dbfcmp))!=sizeof(struct dbfcmp))
                ErrorFatal("Imposible grabar estructura de campos",2);
}


int LeerRegistroDBF(int handle, char * buffer, unsigned int tam)
/* lee un registro */
{
        unsigned int n;

        n=read(handle, buffer, tam);

        if(n==0)
                return(0);
        else
        if(n==tam)
                return(1);
        else
        if(n==1 && buffer[0]==0x1a)
                Error("0x1A (^Z) final despreciado");
        else
                Error("Ultimo registro cortado - se desprecia");

        return(0);
}


void EscrRegistroDBF(int handle, char * buffer, unsigned int tam)
/* escribe un registro */
{
        if(write(handle, buffer, tam)!=tam)
                ErrorFatal("Imposible escribir registro en base de datos destino",2);
}


void Init(char * trayectoria, char * origen, char * destino)
/* crea los nombres para las bases de datos y prepara todo */
{
        char unidad[5];
        char dir[40];
        char nombre[10];
        char ext[5];

        /* separa la trayectoria */
        fnsplit(trayectoria, unidad, dir, nombre, ext);

        /* crea los nombres */
        sprintf(origen,"%s%s%s.REP", unidad, dir, nombre);
        sprintf(destino,"%s%s%s.DBF", unidad, dir, nombre);

        /* borra el antiguo .OLD */
        unlink(origen);
        /* renombra la base de datos origen */
        rename(destino, origen);
}


void DBFREP(char * fichero)
/* repara una dbf */
{
        char origen[50];
        char destino[50];
        struct dbfcab cab;
        struct dbfcmp cmp;
        int m,n;
        unsigned char c;
        unsigned char * registro;
        unsigned long recno;

        printf("Reparando %s...\n---------------------------------------\n",
                fichero);

        Init(fichero,origen,destino);

        /* abre los ficheros */
        i=AbrirDBF(origen);
        o=CrearDBF(destino);

        /* lee la cabecera */
        LeerCabDBF(i,&cab);
        /* graba la cabecera */
        EscrCabDBF(o,&cab);
        lseek(o,0,2);

        l_tamcab=sizeof(struct dbfcab);
        memos=0;
        m=0;

        printf("Chequeando cabecera y estructura de campos...\n");

        while(1)
        {
                /* lee una estructura de campo */
                if(!LeerCmpDBF(i,&cmp))
                        break;

                l_tamcab+=sizeof(struct dbfcmp);

                /* se cuenta un campo memo m s */
                if(cmp.tipo=='M')
                        memos++;

                /* si el tipo de campo no es v lido, error */
                if(cmp.tipo!='C' && cmp.tipo!='N' &&
                   cmp.tipo!='D' && cmp.tipo!='L' &&
                   cmp.tipo!='M')
                {
                        Error("Tipo de campo inv lido - corregido a car cter");
                        cmp.tipo='C';
                }

                /* si el nombre contiene caracteres extra¤os */
                for(n=0;n<11;n++)
                {
                        if(!VarChar(cmp.nombre[n]))
                                break;
                }
                /* si no han pasado los 11 caracteres */
                if(n!=11)
                {
                        sprintf(cmp.nombre,"CMP%05d",m);
                        Error("Caracteres inv lidos en nombre de campo");
                }

                EscrCmpDBF(o,&cmp);
                m++;
        }

        /* graba el \r en el destino */
        c=DBF_FINSTRU;
        write(o,&c,1);
        /* hay un byte m s en la cabecera */
        l_tamcab++;

        /* chequeos: firma de la cabecera */
        if(memos)
        {
                if(cab.marca==DBF_NORM)
                {
                        Error("Marca en cabecera err¢nea: hay campos memo");
                        cab.marca=DBF_MEMO;
                        EscrCabDBF(o,&cab);
                }
        }
        else
        {
                if(cab.marca==DBF_MEMO)
                {
                        Error("Marca en cabecera err¢nea: no hay campos memo");
                        cab.marca=DBF_NORM;
                        EscrCabDBF(o,&cab);
                }
        }

        /* si no coincide el tama¤o de la cabecera con lo que pone,
           otro error */
        if(l_tamcab!=cab.tamcab)
        {
                Error("Tama¤o de cabecera desajustado");
                cab.tamcab=l_tamcab;
                EscrCabDBF(o,&cab);
        }

        printf("Chequeando datos...\n");

        /** chequeo de los datos **/
        /* se posiciona en el principio de los datos */
        lseek(i,l_tamcab,0);
        /* primer registro */
        recno=1;
        /* se aloca el espacio */
        registro=(char *) malloc(cab.tamreg);

        while(1)
        {
                if(!LeerRegistroDBF(i,registro,cab.tamreg))
                        break;

                /* escribe el n£mero de registro */
                printf("\rreg: %lu ", recno);

                /* chequeos: */
                /* si el car cter de borrado no es v lido, se recupera
                   el registro */
                if(registro[0]!=' ' && registro[0]!='*')
                {
                        Error("Car cter de estado inv lido - registro recuperado");
                        registro[0]=' ';
                }

                /* chequea que no haya ning£n car cter menor que 32
                   en el registro */
                for(n=1,m=0;n<cab.tamreg;n++)
                {
                        if(registro[n]<' ')
                        {
                                m++;
                                registro[n]=' ';
                        }
                }

                if(m)
                        Error("Car cter inv lido en registro - corregido con espacios");

                EscrRegistroDBF(o, registro, cab.tamreg);
                recno++;
        }

        /* se chequea si los registros no coinciden */
        if(recno-1!=cab.numreg)
        {
                Error("N£mero de registros en base de datos no coinciden");
                cab.numreg=recno-1;
                EscrCabDBF(o,&cab);
        }

        printf("\nFin chequeo.\n");
}


void main(int numparams, char * params[])
{
        int n;

        /* logo */
        printf("\nDBFREP %s - Reparador de Bases de Datos DBF.\n", VERSION);
        printf("(C) Angel Ortega 1992 Kaplan Development Software.\n\n");

        if(numparams<2)
        {
                printf("Uso: DBFREP {fichero DBF}\n\n");
                exit(0);
        }

        DBFREP(params[1]);
        Go(0);
}

