/*
 * CDE - Common Desktop Environment
 *
 * Copyright (c) 1993-2012, The Open Group. All rights reserved.
 *
 * These libraries and programs are free software; you can
 * redistribute them and/or modify them under the terms of the GNU
 * Lesser General Public License as published by the Free Software
 * Foundation; either version 2 of the License, or (at your option)
 * any later version.
 *
 * These libraries and programs are distributed in the hope that
 * they will be useful, but WITHOUT ANY WARRANTY; without even the
 * implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR
 * PURPOSE. See the GNU Lesser General Public License for more
 * details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with these libraries and programs; if not, write
 * to the Free Software Foundation, Inc., 51 Franklin Street, Fifth
 * Floor, Boston, MA 02110-1301 USA
 */
/*%%  (c) Copyright 1993, 1994 Hewlett-Packard Company			 */
/*%%  (c) Copyright 1993, 1994 International Business Machines Corp.	 */
/*%%  (c) Copyright 1993, 1994 Sun Microsystems, Inc.			 */
/*%%  (c) Copyright 1993, 1994 Novell, Inc. 				 */
/*%%  $XConsortium: isfixrec.c /main/3 1995/10/23 11:39:42 rswiston $ 			 				 */
#ifndef lint
static char sccsid[] = "@(#)isfixrec.c 1.5 89/07/17 Copyr 1988 Sun Micro";
#endif
/*
 * Copyright (c) 1988 by Sun Microsystems, Inc.
 */

/*
 * isfixrec.c
 *
 * Description:
 *	Fixed length record access (FLRA) module.
 */

#include "isam_impl.h"

/* Local functions */
long _fl_getpos();			     /* Get offset in .rec file */
int  _fl_deleted();			     /* 0/1 returns 1 if record is deleted */
static void remove_from_chain();	     /* used by _flrec_wrrec() */

/*
 * _flrec_write(fcb, record, recnum, reclen)
 *
 * Write a record.
 *
 * Input params:
 *	FCB	File Control Block
 *	record	record buffer
 *	reclen	record length (NOT USED)
 *
 * Output params:
 *	recnum	record number of the new record
 *
 * Returns 0 if record was written successfully, or -1 if any error.
 */

/*ARGSUSED*/
int
_flrec_write(fcb, record, recnum, reclen)
    register Fcb	*fcb;
    char		*record;
    Recno		*recnum;
    int			reclen;
{
    Recno		recnum2;
    long		rec_position;
    char		delflag = FL_RECEXISTS;
    char		recnobuf [RECNOSIZE];

    /*
     * Reuse a deleted record if one exits.
     * Otherwise, extend .rec file by a record.
     */
    if (fcb->freerecno != NULL_RECNO) {
	recnum2 = fcb->freerecno;

	/*
	 * Remove record from the chain of deleted records.
	 */
	rec_position = _fl_getpos(fcb, recnum2); /* Offset in .rec file */
	_cp_fromfile(fcb, fcb->datfd, recnobuf, rec_position + 1, RECNOSIZE);
	fcb->freerecno = ldrecno(recnobuf);
    }
    else {
	recnum2 = ++(fcb->lastrecno);

	/* 
	 * Extend .rec file size if necessary.
	 */
	while (_fl_getpos(fcb, recnum2 + 1) > fcb->datsize * ISPAGESIZE) {
	    fcb->datsize = _extend_file(fcb, fcb->datfd, fcb->datsize);
	}
	rec_position = _fl_getpos(fcb, recnum2); /* Offset in .rec file */
    }


    /*
     * Copy record to the .at file. Mark record as undeleted.
     */
    _cp_tofile(fcb, fcb->datfd, &delflag, rec_position, 1); 
    _cp_tofile(fcb, fcb->datfd, record, rec_position + 1, fcb->minreclen); 

    *recnum = recnum2;

    return (ISOK);
}

/*
 * _flrec_read(fcb, record, recnum, reclen)
 *
 * Read a record.
 *
 * Input params:
 *	FCB	File Control Block
 *	recnum	record number of the record
 *	reclen  filled with the record size for compatibilty with
 *		variable length records
 *
 * Output params:
 *	record	record buffer is filled with data
 *
 * Returns 0 if record was read successfully, or error code if any error.
 */

int
_flrec_read(fcb, record, recnum, reclen)
    register Fcb	*fcb;
    char		*record;
    Recno		recnum;
    int			*reclen;
{
    long		rec_position;
    char		delflag;

    /*
     * Check that recnum is within the range of existing record numbers.
     */
    if (recnum < 1 || recnum > fcb->lastrecno)
	return (EENDFILE);

    rec_position = _fl_getpos(fcb, recnum); /* Offset in .rec file */

    /*
     * Check that the record is not marked as deleted.
     */
    _cp_fromfile(fcb, fcb->datfd, &delflag, rec_position, 1); 
    if (delflag == FL_RECDELETED) {
	return (ENOREC);
    }

    /*
     * Copy record from the .at file. 
     */
    _cp_fromfile(fcb, fcb->datfd, record, rec_position + 1, fcb->minreclen); 

    *reclen = fcb->minreclen;

    return (ISOK);
}

/*
 * pos = _fl_getpos(fcb, recnum)
 *
 * Calculate the position of record in .rec file.
 */

long
_fl_getpos(fcb, recnum)
    Fcb			*fcb;
    Recno		recnum;
{
    return ((long)(ISCNTLSIZE + (fcb->minreclen + 1) * (recnum -1)));
}

/*
 * _flrec_rewrite(fcb, record, recnum, reclen)
 *
 * Rewrite a record.
 *
 * Input params:
 *	FCB	File Control Block
 *	recnum	record number of the record
 *	record	new record
 *	int	reclen (NOT USED)
 *
 * Returns 0 if record was rewritten successfully, or error code if any error.
 */

/*ARGSUSED*/
int
_flrec_rewrite(fcb, record, recnum, reclen)
    register Fcb	*fcb;
    char		*record;
    Recno		recnum;
    int			reclen;
{
    long		rec_position;
    char		delflag;

    /*
     * Check that recnum is within the range of existing record numbers.
     */
    if (recnum < 1 || recnum > fcb->lastrecno)
	return (EENDFILE);

    rec_position = _fl_getpos(fcb, recnum); /* Offset in .rec file */

    /*
     * Check that the record is not marked as deleted.
     */
    _cp_fromfile(fcb, fcb->datfd, &delflag, rec_position, 1); 
    if (delflag == FL_RECDELETED) {
	return (ENOREC);
    }

    /*
     * Copy new record to the .rec file. 
     */
    _cp_tofile(fcb, fcb->datfd, record, rec_position + 1, fcb->minreclen); 

    return (ISOK);
}

/*
 * _flrec_delete(fcb, recnum)
 *
 * Rewrite a record.
 *
 * Input params:
 *	FCB	File Control Block
 *	recnum	record number of the record
 *
 * Returns 0 if record was rewritten successfully, or error code if any error.
 */

int
_flrec_delete(fcb, recnum)
    register Fcb	*fcb;
    Recno		recnum;
{
    long		rec_position;
    char		delflag;
    char		recnobuf [RECNOSIZE];

    /*
     * Check that recnum is within the range of existing record numbers.
     */
    if (recnum < 1 || recnum > fcb->lastrecno)
	return (EENDFILE);

    rec_position = _fl_getpos(fcb, recnum); /* Offset in .rec file */

    /*
     * Check that the record is not marked as deleted.
     */
    _cp_fromfile(fcb, fcb->datfd, &delflag, rec_position, 1); 
    if (delflag == FL_RECDELETED) {
	return (ENOREC);
    }

    /*
     * Set the delete flag to FL_RECDELETED.
     */
    delflag = FL_RECDELETED;
    _cp_tofile(fcb, fcb->datfd, &delflag, rec_position, 1);

    /*
     * Insert record into chain of deleted records.
     */
    strecno(fcb->freerecno, recnobuf);
    _cp_tofile(fcb, fcb->datfd, recnobuf, rec_position + 1, RECNOSIZE);
    fcb->freerecno = recnum;

    return (ISOK);
}

/*
 * _flrec_wrrec(fcb, record, recnum, reclen)
 *
 * Write a record by record number.
 *
 * Input params:
 *	FCB	File Control Block
 *	recnum	record number of the record
 *	record	record buffer
 *	int	reclen (NOT USED)
 *
 * Returns 0 if record was written successfully, or error code if any error.
 *
 * Note that _flrec_wrrec() commits updates and syncs the FCB to avoid
 * 	buffer pool overflow.
 */

/*ARGSUSED*/
int
_flrec_wrrec(fcb, record, recnum, reclen)
    register Fcb	*fcb;
    char		*record;
    Recno		recnum;
    int			reclen;
{
    long		rec_position;
    char		delflag;
    Recno		recnum2;
    char		recnumbuf [RECNOSIZE];

    /*
     * Check that recnum is not negative.
     */
    if (recnum < 1)
	return (EBADARG);

    rec_position = _fl_getpos(fcb, recnum); /* Offset in .rec file */

    if (recnum > fcb->lastrecno)  {

	/*
	 * If the recnum is bigger than the highest record number in the .rec
	 * file, extend the .rec file.
	 */
	while (_fl_getpos(fcb, recnum + 1) > fcb->datsize * ISPAGESIZE) {
	    fcb->datsize = _extend_file(fcb, fcb->datfd, fcb->datsize);
	    
	    /* Sync the updates to avoid buffer pool overflow. */
	    _isdisk_commit();
	    _isdisk_sync();
	    (void)_isfcb_cntlpg_w2(fcb);
	}

	/*
	 * Mark all records in the range <fcb->lastrecno+1, recnum>  as
	 * deleted.
	 */
	delflag = FL_RECDELETED;
	for (recnum2 = fcb->lastrecno + 1; recnum2 <= recnum; recnum2++) {	
	    _cp_tofile(fcb, fcb->datfd, &delflag, _fl_getpos(fcb, recnum2), 1); 
	    strecno(fcb->freerecno, recnumbuf);
	    _cp_tofile(fcb, fcb->datfd, recnumbuf,
		       _fl_getpos(fcb, recnum2) + 1, RECNOSIZE); 
	    fcb->freerecno = recnum2;
	    
	    /* Sync the updates to avoid buffer pool overflow. */
	    _isdisk_commit();
	    _isdisk_sync();
	    fcb->lastrecno = recnum;
	    (void)_isfcb_cntlpg_w2(fcb);
	}
	
	/*
	 * Note that the disk structures are in a consistent state now,
	 * the .rec was extended by a few records marked as 'deleted'.
	 * This is important for subsequent rollbacks.
	 */
    } 

    /*
     * If recnum specifies a record that has existed, check whether it
     * has been deleted. _flrec_wrrec() does not override existing record.
     */
    _cp_fromfile(fcb, fcb->datfd, &delflag, rec_position, 1); 
    if (delflag == FL_RECEXISTS) {
	return (EDUPL);
    }

    /*
     * Remove the record from the chain of deleted records.
     */
    remove_from_chain(fcb, recnum);

    /*
     * Copy new record to the .rec file. 
     */
    delflag = FL_RECEXISTS;
    _cp_tofile(fcb, fcb->datfd, &delflag, rec_position, 1); 
    _cp_tofile(fcb, fcb->datfd, record, rec_position + 1, fcb->minreclen); 

    return (ISOK);
}

/*
 * remvoe_from_chain(fcb, recnum)
 *
 * Remove record from the chain of deleted records.
 */

static void
remove_from_chain(fcb, recnum)
    Fcb			*fcb;
    Recno		recnum;
{
    char		recnobuf1 [RECNOSIZE] , recnobuf2 [RECNOSIZE];
    long		pos1, pos2;
    Recno		recnum2;

    pos1 = _fl_getpos(fcb, recnum);
    _cp_fromfile(fcb, fcb->datfd, recnobuf1, pos1 + 1, RECNOSIZE); 

    if (fcb->freerecno == recnum) {
	fcb->freerecno = ldrecno(recnobuf1);
    }
    else {
	recnum2 = fcb->freerecno;
	do {
	    pos2 = _fl_getpos(fcb, recnum2);
	    _cp_fromfile(fcb, fcb->datfd, recnobuf2, pos2 + 1, RECNOSIZE); 
	    recnum2 = ldrecno(recnobuf2);
	} while (recnum2 != recnum && recnum2 != NULL_RECNO);

	_cp_tofile(fcb, fcb->datfd, recnobuf1, pos2 + 1, RECNOSIZE); 
    }
}
