/*
   JAMLIB - A JAM subroutine library
   Copyright (C) 1999 Björn Stenberg

   This library is free software; you can redistribute it and/or
   modify it under the terms of the GNU Lesser General Public
   License as published by the Free Software Foundation; either
   version 2.1 of the License, or (at your option) any later version.

   This library is distributed in the hope that it 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 this library; if not, write to the Free Software
   Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA

   Changes made by Johan Billing 2000-04-16:

   - Added support for Win32 and Linux
   - Changed JAM_OpenMB to open files in binary mode
   - Changed source to use feof() instead of errno == EPASTEOF
   - Changed source to use structrw to read and write structures
   - Fixed broken JAM_FindUser()
   - #includes string.h and stdlib.h instead of memory.h

*/

/***********************************************************************
 **
 **  MBASE.C -- Message base handling
 **
 **  Author: Bj”rn Stenberg (bjorn.stenberg@sth.frontec.se)
 **
 ***********************************************************************/
#ifdef HAVE_CONFIG_H
# include <config.h>
#endif

#include <stdio.h>
#include <errno.h>
#include <time.h>
#include <stdlib.h>
#include <string.h>

#include <jamlib/jam.h>
#include "structrw.h"

#if defined( __OS2__ )
# include <os2.h>	/* ANSI C does not include file locking :-( */
#endif

#if defined( __WIN32__ )
# include <sys/locking.h>
# include <io.h>
#endif

#if defined( __UNIX__ )
# if HAVE_SYS_FILE_H
#  include <sys/file.h>
# endif
# if HAVE_UNISTD_H
#  include <unistd.h>
# endif
# ifdef HAVE_FCNTL_H
#  include <fcntl.h>
# endif
# ifdef USE_LOCKF
#  include <sys/file.h>
# endif
#endif

#define OS_ERROR_OFFSET 10000

#if defined( __OS2__ )
# define JAM_Sleep( _x_ )	DosSleep( (_x_)*1000 )
#endif

#if defined( __WIN32__ )
# define JAM_Sleep(x) _sleep((x)*1000)
#endif

#if defined( __UNIX__ )
# define JAM_Sleep(x) sleep(x)
#endif

/***********************************************************************
 **
 **  File-global functions {{{
 **/
int jam_Open( s_JamBase* Base_PS, uchar* Filename_PC, char* Mode_PC );
int jam_Lock( s_JamBase* Base_PS, int DoLock_I );
/** }}}
 **
 ***********************************************************************/

/***********************************************************************
 **
 **  JAM_OpenMB - Open message base {{{
 **/
int JAM_OpenMB( uchar* Basename_PC, s_JamBase** NewArea_PPS )
{
    s_JamBase*  Base_PS;
    int		Status_I;

    if ( !NewArea_PPS )
	return JAM_BAD_PARAM;

    *NewArea_PPS = NULL;

    Base_PS = (s_JamBase*) calloc( 1, sizeof( s_JamBase ) );
    if (!Base_PS)
	return JAM_NO_MEMORY;

    *NewArea_PPS = Base_PS;

    Status_I = jam_Open( Base_PS, Basename_PC, "r+b" );
    if ( Status_I )
	return Status_I;

    return 0;
}
/** }}}
 **
 ***********************************************************************/

/***********************************************************************
 **
 **  JAM_CreateMB - Create a new message base {{{
 **/
int JAM_CreateMB( uchar*	Basename_PC,
		  ulong		BaseMsg_I,
		  s_JamBase**	NewArea_PPS )
{
    s_JamBaseHeader	Base_S;
    int			Status_I;
    s_JamBase*		Base_PS;

    if ( !NewArea_PPS || !BaseMsg_I )
	return JAM_BAD_PARAM;

    *NewArea_PPS = NULL;

    Base_PS = (s_JamBase*) calloc( 1, sizeof( s_JamBase ) );
    if (!Base_PS)
	return JAM_NO_MEMORY;

    *NewArea_PPS = Base_PS;

    Status_I = jam_Open( Base_PS, Basename_PC, "w+b" );
    if ( Status_I )
	return Status_I;

    Base_S.DateCreated = time(NULL);
    Base_S.ModCounter  = 0;
    Base_S.ActiveMsgs  = 0;
    Base_S.PasswordCRC = 0xffffffff;
    Base_S.BaseMsgNum  = BaseMsg_I;
    memset( &Base_S.RSRVD, 0, sizeof( Base_S.RSRVD ) );

    Status_I = JAM_LockMB( Base_PS, 0 );
    if ( Status_I )
	/* FIXME: Base was created, but we can not lock it
         * (normaly this should not happen)
         * Shouldn't we remove recently created files? */
	return Status_I;

    Status_I = JAM_WriteMBHeader( Base_PS, &Base_S );
    if ( Status_I ) {
	JAM_UnlockMB( Base_PS );
	return Status_I;
    }

    JAM_UnlockMB( Base_PS );

    return 0;
}
/** }}}
 **
 ***********************************************************************/

/***********************************************************************
 **
 **  JAM_CloseMB - Close message base {{{
 **/
int JAM_CloseMB( s_JamBase* Base_PS )
{
    if ( Base_PS->Locked_I ) {
	int Status_I = JAM_UnlockMB( Base_PS );
	if ( Status_I )
	    return Status_I;
    }

    if ( Base_PS->HdrFile_PS ) {
	fclose( Base_PS->HdrFile_PS ); Base_PS->HdrFile_PS = NULL;
    }
    if ( Base_PS->TxtFile_PS ) {
	fclose( Base_PS->TxtFile_PS ); Base_PS->TxtFile_PS = NULL;
    }
    if ( Base_PS->IdxFile_PS ) {
	fclose( Base_PS->IdxFile_PS ); Base_PS->IdxFile_PS = NULL;
    }
    if ( Base_PS->LrdFile_PS ) {
	fclose( Base_PS->LrdFile_PS ); Base_PS->LrdFile_PS = NULL;
    }

    Base_PS->Locked_I = 0;
    return 0;
}
/** }}}
 **
 ***********************************************************************/

/***********************************************************************
 **
 **  JAM_RemoveMB - Remove a message base {{{
 **/
int JAM_RemoveMB( s_JamBase* Base_PS, uchar* Basename_PC )
{
    uchar Filename_AC[250];
    int   Status_AI[4];

    /* .JHR file */
    sprintf( Filename_AC, "%s%s", Basename_PC, EXT_HDRFILE );
    Status_AI[0] = remove( Filename_AC );
    if ( Status_AI[0] )
	Base_PS->Errno_I = errno;

    /* .JDT file */
    sprintf( Filename_AC, "%s%s", Basename_PC, EXT_TXTFILE );
    Status_AI[1] = remove( Filename_AC );
    if ( Status_AI[1] )
	Base_PS->Errno_I = errno;

    /* .JDX file */
    sprintf( Filename_AC, "%s%s", Basename_PC, EXT_IDXFILE );
    Status_AI[2] = remove( Filename_AC );
    if ( Status_AI[2] )
	Base_PS->Errno_I = errno;

    /* .JLR file */
    sprintf( Filename_AC, "%s%s", Basename_PC, EXT_LRDFILE );
    Status_AI[3] = remove( Filename_AC );
    if ( Status_AI[3] )
	Base_PS->Errno_I = errno;

    if (Status_AI[0] || Status_AI[1] || Status_AI[2] || Status_AI[3])
	return JAM_IO_ERROR;

    return 0;
}
/** }}}
 **
 ***********************************************************************/

/***********************************************************************
 **
 **  JAM_GetMBSize - Get the number of messages in message base {{{
 **/
int JAM_GetMBSize( s_JamBase* Base_PS, ulong* Messages_PI )
{
    ulong Offset_I;

    /* go to end of index file */
    if ( fseek( Base_PS->IdxFile_PS, 0, SEEK_END ) ) {
	Base_PS->Errno_I = errno;
	return JAM_IO_ERROR;
    }

    Offset_I = ftell( Base_PS->IdxFile_PS );
    if ( Offset_I == -1 ) {
	Base_PS->Errno_I = errno;
	return JAM_IO_ERROR;
    }

    *Messages_PI = Offset_I / sizeof( s_JamIndex );

    return 0;
}
/** }}}
 **
 ***********************************************************************/

/***********************************************************************
 **
 **  JAM_LockMB - Lock message base {{{
 **/
int JAM_LockMB( s_JamBase* Base_PS, int Timeout_I )
{
    if ( Base_PS->Locked_I )
	return 0;

    switch ( Timeout_I ) {
    case -1:
	/* unlimited timeout */
	while ( jam_Lock( Base_PS, 1 ) == JAM_LOCK_FAILED )
	    JAM_Sleep( 1 );
	return 0;

    case 0:
	/* no timeout */
	return jam_Lock( Base_PS, 1 );

    default: 
	/* X seconds timeout */
	{
	    time_t Time_I = time(NULL) + Timeout_I;

	    while ( time(NULL) < Time_I )
	    {
		int Result_I;

		Result_I = jam_Lock( Base_PS, 1 );

		if ( Result_I == JAM_LOCK_FAILED )
		    JAM_Sleep( 1 );
		else
		    return Result_I;
	    }
	    return JAM_LOCK_FAILED;
	}
    }
}
/** }}}
 **
 ***********************************************************************/

/***********************************************************************
 **
 **  JAM_UnlockMB - Flush all writes and unlock message base {{{
 **/
int JAM_UnlockMB( s_JamBase* Base_PS )
{
    fflush( Base_PS->HdrFile_PS );
    fflush( Base_PS->TxtFile_PS );
    fflush( Base_PS->IdxFile_PS );
    fflush( Base_PS->LrdFile_PS );

    return jam_Lock( Base_PS, 0 );
}
/** }}}
 **
 ***********************************************************************/

/***********************************************************************
 **
 **  JAM_ReadMBHeader - Read message base header {{{
 **/
int JAM_ReadMBHeader( s_JamBase* Base_PS, s_JamBaseHeader* Header_PS )
{
    if ( !Header_PS || !Base_PS )
	return JAM_BAD_PARAM;

    if ( fseek( Base_PS->HdrFile_PS, 0, SEEK_SET ) ) {
	Base_PS->Errno_I = errno;
	return JAM_IO_ERROR;
    }

    if ( 1 > freadjambaseheader(Base_PS->HdrFile_PS,Header_PS) ) {
	Base_PS->Errno_I = errno;
	return JAM_IO_ERROR;
    }

    return 0;
}
/** }}}
 **
 ***********************************************************************/

/***********************************************************************
 **
 **  JAM_WriteMBHeader - Write message base header {{{
 **/
int JAM_WriteMBHeader( s_JamBase* Base_PS, s_JamBaseHeader* Header_PS )
{
    if ( !Header_PS || !Base_PS )
	return JAM_BAD_PARAM;

    if ( !Base_PS->Locked_I )
	return JAM_NOT_LOCKED;

    if ( fseek( Base_PS->HdrFile_PS, 0, SEEK_SET ) ) {
	Base_PS->Errno_I = errno;
	return JAM_IO_ERROR;
    }

    /* ensure header looks right */
    memcpy( Header_PS->Signature, HEADERSIGNATURE, 4 );
    Header_PS->ModCounter++;

    if ( 1 > fwritejambaseheader(Base_PS->HdrFile_PS,Header_PS) ) {
	Base_PS->Errno_I = errno;
	return JAM_IO_ERROR;
    }

    fflush( Base_PS->HdrFile_PS );

    return 0;
}
/** }}}
 **
 ***********************************************************************/

/***********************************************************************
 **
 **  JAM_FindUser - Scan scan file for messages to a user {{{
 **/
int JAM_FindUser( s_JamBase*	Base_PS,
		  ulong		UserCrc_I,
		  ulong		StartMsg_I,
		  ulong*	MsgNo_PI )
{
    ulong MsgNo_I;

    /* go to start message */
    if ( fseek( Base_PS->IdxFile_PS, StartMsg_I * sizeof( s_JamIndex ), SEEK_SET ) ) {
	Base_PS->Errno_I = errno;
	return JAM_IO_ERROR;
    }

    /* scan file */
    for ( MsgNo_I = StartMsg_I; ; MsgNo_I++ ) {

	s_JamIndex Index_S;

	if ( 1 > freadjamindex(Base_PS->IdxFile_PS,&Index_S) ) {

	    if ( feof(Base_PS->IdxFile_PS) )
		return JAM_NO_USER;

	    Base_PS->Errno_I = errno;
	    return JAM_IO_ERROR;
	}

	if ( Index_S.UserCRC == UserCrc_I )
	    break;
    }

    *MsgNo_PI = MsgNo_I;
    return 0;
}
/** }}}
 **
 ***********************************************************************/

/***********************************************************************
 **
 **  jam_Lock - Lock/unlock a message base {{{
 **/
int jam_Lock( s_JamBase* Base_PS, int DoLock_I )
{
#if defined(__OS2__)
    FILELOCK Area_S;
    APIRET   Status_I;
    ULONG    Timeout_I = 0;
    int      Handle_I;

    Handle_I = fileno( Base_PS->HdrFile_PS );
    if ( Handle_I == -1 ) {
	Base_PS->Errno_I = errno;
	return JAM_IO_ERROR;
    }

    Area_S.lOffset = 0;
    Area_S.lRange  = 1;

    if ( DoLock_I )
	Status_I = DosSetFileLocks( Handle_I, NULL, &Area_S, Timeout_I, 0 );
    else
	Status_I = DosSetFileLocks( Handle_I, &Area_S, NULL, Timeout_I, 0 );

    if ( Status_I ) {
	if ( 232 == Status_I )
	    return JAM_LOCK_FAILED;

	Base_PS->Errno_I = Status_I + OS_ERROR_OFFSET;
	return JAM_IO_ERROR;
    }

    if ( DoLock_I )
	Base_PS->Locked_I = 1;
    else
	Base_PS->Locked_I = 0;

    return 0;
#elif defined(__WIN32__)
    int      Handle_I,Status_I;

    fseek(Base_PS->HdrFile_PS,0,SEEK_SET); /* Lock from start of file */

    Handle_I = fileno( Base_PS->HdrFile_PS );
    if ( Handle_I == -1 ) {
	Base_PS->Errno_I = errno;
	return JAM_IO_ERROR;
    }

    if ( DoLock_I )
	Status_I = _locking(Handle_I,_LK_NBLCK,1);
    else
	Status_I = _locking(Handle_I,_LK_UNLCK,1);

    if ( Status_I )
	return JAM_LOCK_FAILED;

    if ( DoLock_I )
	Base_PS->Locked_I = 1;
    else
	Base_PS->Locked_I = 0;

    return 0;
#elif defined(__UNIX__)
    int      Handle_I,Status_I;

    fseek(Base_PS->HdrFile_PS,0,SEEK_SET); /* Lock from start of file */

    Handle_I = fileno( Base_PS->HdrFile_PS );
    if ( Handle_I == -1 ) {
	Base_PS->Errno_I = errno;
	return JAM_IO_ERROR;
    }

#ifdef USE_LOCKF
    if(DoLock_I) Status_I = lockf(Handle_I, F_TLOCK, 1);
    else         Status_I = lockf(Handle_I, F_ULOCK, 1);
#else
    {
	struct flock fl;

	if(DoLock_I) fl.l_type=F_WRLCK;
	else         fl.l_type=F_UNLCK;

	fl.l_whence=SEEK_SET;
	fl.l_start=0;
	fl.l_len=1;
	fl.l_pid=getpid();

	Status_I=fcntl(Handle_I,F_SETLK,&fl);
    }
#endif
    if ( Status_I ) {
	Base_PS->Errno_I = errno;
	return JAM_LOCK_FAILED;
    }

    if ( DoLock_I )
	Base_PS->Locked_I = 1;
    else
	Base_PS->Locked_I = 0;

    return 0;
#else
#error Unsupported platform
#endif
}
/** }}}
 **
 ***********************************************************************/

/***********************************************************************
 **
 **  jam_Open - Open/create message base files {{{
 **/
int jam_Open( s_JamBase* Base_PS, uchar* Basename_PC, char* Mode_PC )
{
    uchar Filename_AC[250];

    /* .JHR file */
    sprintf( Filename_AC, "%s%s", Basename_PC, EXT_HDRFILE );
    Base_PS->HdrFile_PS = fopen( Filename_AC, Mode_PC );
    if (!Base_PS->HdrFile_PS) {
	Base_PS->Errno_I = errno;
	return JAM_IO_ERROR;
    }

    /* .JDT file */
    sprintf( Filename_AC, "%s%s", Basename_PC, EXT_TXTFILE );
    Base_PS->TxtFile_PS = fopen( Filename_AC, Mode_PC );
    if (!Base_PS->TxtFile_PS) {
	Base_PS->Errno_I = errno;
	fclose( Base_PS->HdrFile_PS ); Base_PS->HdrFile_PS = NULL;
	return JAM_IO_ERROR;
    }

    /* .JDX file */
    sprintf( Filename_AC, "%s%s", Basename_PC, EXT_IDXFILE );
    Base_PS->IdxFile_PS = fopen( Filename_AC, Mode_PC );
    if (!Base_PS->IdxFile_PS) {
	Base_PS->Errno_I = errno;
	fclose( Base_PS->HdrFile_PS ); Base_PS->HdrFile_PS = NULL;
	fclose( Base_PS->TxtFile_PS ); Base_PS->TxtFile_PS = NULL;
	return JAM_IO_ERROR;
    }

    /* .JLR file */
    sprintf( Filename_AC, "%s%s", Basename_PC, EXT_LRDFILE );
    Base_PS->LrdFile_PS = fopen( Filename_AC, Mode_PC );
    if (!Base_PS->LrdFile_PS) {
	Base_PS->Errno_I = errno;
	fclose( Base_PS->HdrFile_PS ); Base_PS->HdrFile_PS = NULL;
	fclose( Base_PS->TxtFile_PS ); Base_PS->TxtFile_PS = NULL;
	fclose( Base_PS->IdxFile_PS ); Base_PS->IdxFile_PS = NULL;
	return JAM_IO_ERROR;
    }

    return 0;
}
/** }}}
 **
 ***********************************************************************/
/* Local variables:
 * vim:set ts=8 sts=4 sw=4:
 * vim600:fdm=marker:
 */


syntax highlighted by Code2HTML, v. 0.9.1