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

    Backported changes from JAMLIB 1.4.7 made by Johan Billing 2003-10-26

    - Now uses calloc instead of malloc/memset
    - (*NewArea_PPS) will be set to zero even if calloc() failed in
      JAM_OpenMB() and JAM_CreateMB()
    - JAM_CreateMB() no longer attempts to forever to lock the newly created
      messagebase. If the first attempt fails, it will return an error.
    - jam_Lock() now sets Base_PS->Errno under Linux

    Other changes made by Johan Billing 2003-10-26

    - Fixed comparison between signed and unsigned variable in JAM_GetMBSize()

    - JAM_CreateMB() would not unlock and close the newly created messagebase
      upon failure.

    Changes made by Johan Billing 2004-07-10

    - Updated the Win32-specific parts of the code to make it compatible with
      newer versions of MinGW (tested with 3.1.0-1):

      * Now uses Sleep() instead of sleep()
      * Changed _LK_UNLOCK to _LK_UNLCK in jam_Lock()
      
*/

/***********************************************************************
**
**  MBASE.C -- Message base handling
**
**  Author: Bj”rn Stenberg (bjorn.stenberg@sth.frontec.se)
**
***********************************************************************/
#include <stdio.h>
#include <errno.h>
#include <time.h>
#include <stdlib.h>
#include <string.h>

#include "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>
#include <windows.h>

#if !defined( _LK_UNLCK ) && defined ( _LK_UNLOCK )
#define _LK_UNLCK _LK_UNLOCK /* For backwards compatibility */
#endif

#endif

#if defined( __LINUX__ )
#include <sys/file.h>
#include <unistd.h>
#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( __LINUX__ )
#define JAM_Sleep(x) sleep(x)
#endif

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


/***********************************************************************
**
**  JAM_OpenMB - Open message base
**
***********************************************************************/
int JAM_OpenMB( char* 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( char* 	Basename_PC,
		  uint32_t 	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 the new base cannot be locked directly, something is seriously wrong */
    if ( Status_I ) {
        JAM_CloseMB(Base_PS);
	return Status_I;
    }

    Status_I = JAM_WriteMBHeader( Base_PS, &Base_S );
    if ( Status_I ) {
        JAM_UnlockMB( Base_PS );
        JAM_CloseMB(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;
	fclose( Base_PS->TxtFile_PS ); Base_PS->TxtFile_PS = NULL;
	fclose( Base_PS->IdxFile_PS ); Base_PS->IdxFile_PS = NULL;
	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, char* Basename_PC )
{
    char 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, uint32_t* Messages_PI )
{
    long 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 ) 
	{
		/* unlimited timeout */
      case -1:
			while ( jam_Lock( Base_PS, 1 ) == JAM_LOCK_FAILED )
	   	   JAM_Sleep( 1 );
			return 0;

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

		/* X seconds timeout */
      default: 
			{
		   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,
		  uint32_t 	UserCrc_I,
		  uint32_t 	StartMsg_I,
		  uint32_t* 	MsgNo_PI )
{
    uint32_t 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(__LINUX__)
    int      Handle_I,Status_I;
    struct flock fl;

    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) 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);

    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, char* Basename_PC, char* Mode_PC )
{
    char 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;
}