JAMLIB A JAM subroutine library by Björn Stenberg modifications by Johan Billing Version 1.2 2000-09-17 GENERAL ======= History ------- JAMLIB 1.0 was originally released by Björn Stenberg 1996-03-06. Since the original license did not permit modification of the library, Johan Billing contacted Björn Stenberg and asked him to change the license. Björn Stenberg agreed to change the license to the GNU Lesser General Public License 1999-12-21 (see the accompanying file LICENSE). After that, some minor additions and bug fixes were made by Johan Billing and JAMLIB 1.1 was released under the new license. Changes in 1.2: * Since JAM_GetSubField() is not reentrant and cannot be used in multi-threaded applications, JAM_GetSubField_R() was added as a replacement for cases where a reentrant function is needed. Changes in 1.1: * Added support for Win32 and Linux * Added JAM_AddEmptyMessage() * Rewrote the Makefiles * Rewrote the CRC32 routine * Fixed broken JAM_FindUser() * Fixed broken JAM_GetSubfield() * Changed JAM_OpenMB so that files are opened in binary mode. This is necessary to use JAMLIB under Windows. * Improved JAM_ReadMsgHeader() to give the error JAM_NO_MESSAGE if the message no longer exists in the messagebase and JAM_CORRUPT_MSG if the subfields of the message have been corrupted. * Improved portability by changing JAMLIB so that it no longer reads and writes stuctures directly using fread() and fwrite(). * Improved ANSI-C compatibilty by no longer including the non-ANSI header file memory.h and using feof() to check for EOF instead of errno == EPASTEOF. * Added an #ifdef so that ushort and ulong are no longer defined in jam.h when compiling under Linux. These are normally already defined in the standard header files. License ------- 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 Description ----------- These are a collection of subroutines that encapsulate much of the format-specific and tedious details of the JAM message base format. The idea is that application programmers by using these routines can concentrate on the more high-level issues of their programs instead of worrying about their JAM routines. I [Björn Stenberg] wrote these routines primarily because I needed them myself. I was trying to implement JAM support in my FrexxLink BBS system and was frustrated by the poor level of documentation supplied in the JAMAPI archive distributed by the JAM authors. Finally, I dove into the JAMAPI source code in a desperate attempt at finding out how to use it. To my despair, I discovered that the JAMAPI is targeted at a very low level. I would need to implement a lot of JAM-handling code into my own program. This library is an attempt to do two things: Firstly, provide an, at least sparingly, _documented_ API, allowing application programmers to easily implement JAM into their programs. Secondly, raise the level of functionality above that of the original JAMAPI package, so that the application programmer does not have to learn and understand all the internals of the JAM message base format to implement support for it. I have not succeded completely on any of the two points, of course. Documentation can never be too good, and there are still a few things about JAM you must know in order to use it. But I think I have made it somewhat easier than perhaps was the case before. References ---------- If you are extra curious about the JAM message format, I suggest you get a hold of an archive called JAMAPI.ARJ. That archive contains a file called JAM.DOC which is the file I have used as reference for the development of these routines. Credits ------- All original code except for the CRC32 routine was written by Björn Stenberg. The CRC32 code was rewritten by Johan Billing for JAMLIB 1.1 to replace the original CRC32 code whose origin and copyright was unclear. The jam.h header file is a compilation of the best from the various header files in the JAMAPI package with some of additions by Björn Stenberg as well. Additions and modifications by Johan Billing. The JAM message base proposal is: JAM(mbp) - Copyright 1993 Joaquim Homrighausen, Andrew Milner, Mats Birch, Mats Wallin. ALL RIGHTS RESERVED Contact Information ------------------- For questions about JAMLIB, please contact: Johan Billing Fidonet: 2:205/454.77 E-mail: billing@df.lth.se If you wish to contact Björn Stenberg, his current e-mail address (as of 1999-12-21) is bjorn@haxx.nu. THE LIBRARY =========== The Source Code --------------- I made a point of making this library as system independant as I could. Only one function needs to be altered when porting this to another system: The file locking. ANSI C does not include file locking so there is not much I can do about it. The choice of C over C++ is a part of this philosophy aswell. More systems have C compilers than C++ compilers, and more people know C than C++. Also, converting this library to a C++ class should be fairly simple. If you do, send me a copy. I use some naming conventions throughout the code and in the examples. These are invented by myself as a reaction to the stunningly ugly and hard-to-read Hungarian Notation promoted by some people. The rules of my notation are simple: * All library-global identifiers are prefixed with 'JAM_'. All file-global identifiers are prefixed with 'jam_'. Local identifiers do not have prefixes. * All variables have a suffix describing their basic type. Suffixes used in this library are: _I - integer (int Example_I) _C - character (char Example_C) _S - struct (struct Example_S) _P - pointer (void* Example_P) _A - array Suffixes are then combined, to show the correct type: _PI - pointer to integer (int* Example_PI) _PC - pointer to char (char* Example_PC) _AC - array of char (char Example_AC[x]) _PPS - pointer to pointer to struct (struct** Example_PPS) * Functions do not have suffixes The whole idea is that it is quicker to read and comprehend a variable called 'Text_PC' than one called 'pszText'. We read from left to right, and thus the most important information - the name - should be the leftmost data in the word. The variable type is additional information and is therefore added to the end where it does not disturb the reader. The Functions ------------- The library is divided into five groups: * Message base functions * Message functions * Subfield functions * LastRead functions * Miscellanous functions -------------------------------------------------------------------------- Message base functions ---------------------- These functions handle JAM message bases, by opening, locking, scanning etc the contents of a message base. These are fairly straight-forward and simple routines that you should have little, if any, trouble with. A message base is identified by a message base handle, which is obtained from either JAM_OpenMB() och JAM_CreateMB(). All functions that read or write from the message base take this handle as parameter, to know which message base to use. ================================ JAM_OpenMB - Open a message base ================================ Syntax int JAM_OpenMB( uchar* Basename_PC, t_JamBase** NewBase_PPS ); Description Opens a message base. Only one message base can be open at a time. Parameters Basename_PC The path and base filename of the message base. "Base filename" means the filename without the JAM-specific extension. NewBase_PPS A pointer to a message base handle where the new message base handle will be written. On error you must free() this memory if (*NewBase_PPS) not NULL. Returns 0 if successful JAM_IO_ERROR if an I/O error occured. see JAM_Errno() JAM_BAD_PARAM if NewBas_PPS is NULL Example { int Result_I; Result_I = JAM_OpenMB( "c:\\jam\\mybase", &Base_PS ); if ( Result_I ) printf("JAM_OpenMB returned %d.\n", Result_I ); } ================================ JAM_CloseMB - Close message base ================================ Syntax int JAM_CloseMB( Base_PS ); Description Unlocks (if locked) and closes the currently open message base. Parameters Base_PS The message base to close. Note, that uou must free() this memory by yourself. Returns 0 if successful JAM_IO_ERROR if an I/O error occured. see JAM_Errno() JAM_LOCK_FAILED if the message base could not be unlocked Example { int Result_I; Result_I = JAM_CloseMB( Base_PS ); if ( Result_I ) printf("JAM_CloseMB returned %d.\n", Result_I ); } ======================================== JAM_CreateMB - Create a new message base ======================================== Syntax int JAM_CreateMB( uchar* Basename_PC, ulong BaseMsg_I, s_JamBase** NewBase_PPS ); Description Creates the necessary files for a new message base and writes a new message base header. If the message base already exists, its contents are destroyed. Parameters Basename_PC The path and base filename of the new message base. BaseMsg_I The base message number (first message #) for the new message base. This number is used when calculating new messages' unique message number. It should not be set to 0. NewBase_PPS A pointer to a message base handle where the new message base handle will be written. On error you must free() this memory if (*NewBase_PPS) not NULL. Returns 0 if successful JAM_IO_ERROR if an I/O error occured. see JAM_Errno() JAM_LOCK_FAILED if the newly created message base can not be locked see JAM_Errno() JAM_BAD_PARAM if BaseMsg_I is 0 or NewBase_PPS is NULL Example { int Result_I; Result_I = JAM_CreateMB( "c:\\jam\\mybase", 1, &Base_PS ); if ( Result_I ) printf("JAM_CreateMB returned %d.\n", Result_I ); } ==================================== JAM_RemoveMB - Remove a message base ==================================== Syntax int JAM_RemoveMB( ErrorBase_PS, uchar* Basename_PC ); Description Deletes all files associated with a message base. No checking is done as to whether the message base is currently open or not. Parameters ErrorBase_PS The message base in which to store the I/O error, if any. This parameter does *NOT* specify the message to be removed, it is only used for error tracking purposes. If an i/o error occurs when removing the message base files, this message base handler will simply hold the error code. Basename_PC The path and base filename of the message base to remove. Returns 0 if successful JAM_IO_ERROR if an I/O error occured. see JAM_Errno() JAM_BAD_PARAM if ErrorBase_PS is NULL Example { int Result_I; Result_I = JAM_RemoveMB( Base_PS, "c:\\jam\\mybase" ); if ( Result_I ) { printf("JAM_RemoveMB returned %d.\n", Result_I ); if ( Result_I == JAM_IO_ERROR ) printf( "i/o error %d\n", JAM_Errno( ErrorBase_PS ) ); } } =================================================== JAM_LockMB - Lock message base for exclusive access =================================================== Syntax int JAM_LockMB( t_JamBase* Base_PS ); Description Locks the currently open message base so that no other programs may modify it. The message base should be locked for only small periods of time, or the performance of tossers and other software may be affected. Parameters Base_PS The message base to lock Returns 0 if successful JAM_IO_ERROR if an I/O error occured. see JAM_Errno() JAM_LOCK_FAILED if the message base is currently locked by another process JAM_BAD_PARAM if Base_PS is NULL Example { int Result_I; while ( 1 ) { Result_I = JAM_LockMB( Base_PS ); if ( Result_I ) { if ( Result_I == JAM_LOCK_FAILED ) /* base locked by someone else, wait for unlock */ sleep( 1 ); else { /* error */ printf("JAM_LockMB returned %d.\n", Result_I ); return -1; } } } } ================================== JAM_UnlockMB - Unlock message base ================================== Syntax int JAM_UnlockMB( s_JamBase* Base_PS ); Description Unlocks message base, allowing other programs to modify it. Parameters Base_PS The message base to unlock Returns 0 if successful JAM_IO_ERROR if an I/O error occured. see JAM_Errno() JAM_BAD_PARAM if Base_PS is NULL Example { int Result_I; Result_I = JAM_UnlockMB( Base_PS ); if ( Result_I ) printf("JAM_UnlockMB returned %d.\n", Result_I ); } =========================================== JAM_ReadMBHeader - Read message base header =========================================== Syntax int JAM_ReadMBHeader( s_JamBase* Base_PS, s_JamBaseHeader* Header_PS ); Description Reads the message base header from the start of the JAM header file. Parameters Base_PS The message base to use Header_PS A pointer to a base header structure where the base header will be stored. Returns 0 if successful JAM_BAD_PARAM if Base_PS or Header_PS is NULL JAM_IO_ERROR if an I/O error occured. see JAM_Errno() Example { s_JamBaseHeader BaseHeader_S; int Result_I; Result_I = JAM_ReadMBHeader( Base_PS, &BaseHeader_S ); if ( Result_I ) printf("JAM_ReadMBHeader returned %d.\n", Result_I ); } ============================================= JAM_WriteMBHeader - Write message base header ============================================= Syntax int JAM_WriteMBHeader( s_JamBase* Base_PS, s_JamBaseHeader* Header_PS ); Description Increases the ModCounter field by one, resets the header signature and writes the message base header to the start of the JAM header file. Parameters Base_PS The message base to use Header_PS A pointer to the base header to be stored Returns 0 if successful JAM_BAD_PARAM if Base_PS or Header_PS is NULL JAM_IO_ERROR if an I/O error occured. see JAM_Errno() JAM_NOT_LOCKED if the message base is not locked Example { s_JamBaseHeader BaseHeader_S; int Result_I; /* modify header here */ Result_I = JAM_WriteMBHeader( &BaseHeader_S ); if ( Result_I ) printf("JAM_WriteMBHeader returned %d.\n", Result_I ); } ===================================== JAM_FindUser - Find message to a user ===================================== Syntax int JAM_FindUser( s_JamBase* Base_PS, ulong UserCrc_I, ulong StartMsg_I, ulong* MsgNo_PI ); Description Scans the message base looking for a message written to a specific user. Parameters Base_PS The message base to use UserCrc_I The CRC32 value for the searched name StartMsg_I The first message number to look at. This value is not the message's unique number, but rather the absolute position of the message in the message base. Message 0 therefore means the first message. MsgNo_PI A pointer to a variable where the message number for the found message will be stored. This number is the absolute message position in the message base. Message 0 means the first message. Returns 0 if a message was found JAM_NO_USER if no message was found JAM_IO_ERROR if an I/O error occured. see JAM_Errno() Example { uchar Name_AC[32]; int Result_I; ulong Crc_I; ulong Msg_I; strcpy( Name_AC, "Bjorn Stenberg" ); Crc_I = JAM_Crc32( Name_AC, strlen( Name_AC ) ); Result_I = JAM_FindUser( Base_PS, Crc_I, 0, &Msg_I ); switch ( Result_I ) { case JAM_NO_USER: printf("No message for me.\n"); break; case JAM_IO_ERROR: printf("IO error %d\n", JAM_Errno() ); break; } } ========================================================== JAM_GetMBSize - Get the number of messages in message base ========================================================== Syntax int JAM_GetMBSize( s_JamBase* Base_PS, ulong* Messages_PI ); Description Finds out the number of messages (deleted and undeleted) in the message base. Parameters Base_PS The message base to use Messages_PI A pointer to a variable where the number of messages will be stored. Returns 0 if successful JAM_IO_ERROR if an I/O error occured. see JAM_Errno() Example { int Result_I; ulong Size_I; Result_I = JAM_GetMBSize( Base_PS, &Size_I ); if ( Result_I ) printf("JAM_GetMBSize returned %d.\n", Result_I ); } -------------------------------------------------------------------------- Message functions ----------------- These functions handle individual JAM messages. A JAM message contains of three parts: * Message Header * Message Header Subfields * Message Text The message header is a simple C structure and the message text is a simple text buffer. The subfields, however, are a bit more tricky. These contain everything that is not covered by the header, including the TO, FROM, SUBJECT fields, origin and destination network adresses etc. There can be an unlimited number of subfields to a message. In this routine library the subfields are encapsulated by a 'subfield packet', which is handled by its own set of routines. See a later section of this document for an explanation of those. ============================================================= JAM_ReadMsgHeader - Read a message's header and its subfields ============================================================= Syntax int JAM_ReadMsgHeader( s_JamBase* Base_PS, ulong MsgNo_I, s_JamMsgHeader* Header_PS, s_JamSubPacket** Subfields_PPS ); Description Reads a message header and (optionally) the message header subfields. Parameters Base_PS The message base to use MsgNo_I The message number, i.e. the absolute position of the message in the message base. Message 0 is the first message. Header_PS A pointer to a message header structure where the message header will be stored. Subfields_PPS A pointer to a subpacket pointer, where the subfield packet handle will be stored. If this parameter is NULL, no subfields are read. Returns 0 if successful JAM_IO_ERROR if an I/O error occured. see JAM_Errno() JAM_NO_MEMORY if a memory allocation failed JAM_NO_MESSAGE if message has been removed JAM_CORRUPT_MSG if message subfields are corrupted Example { s_JamMsgHeader Header_S; s_JamSubPacket* SubPack_PS int Result_I; Result_I = JAM_ReadMsgHeader( 0, &Header_S, &SubPack_PS ); if ( Result_I ) printf("JAM_ReadMsgHeader returned %d.\n", Result_I ); } ======================================= JAM_ReadMsgText - Read a message's text ======================================= Syntax int JAM_ReadMsgText( s_JamBase* Base_PS, ulong Offset_I, ulong Length_I, uchar* Buffer_PC ); Description Reads the body text associated with a message. Parameters Base_PS The message base to use Offset_I The text position in the text file. This information is stored in the message header. Length_I The text length. This information is stored in the message header. Buffer_PC A pointer to where the text should be stored. Returns 0 if successful JAM_IO_ERROR if an I/O error occured. see JAM_Errno() Example { s_JamMsgHeader Header_S; uchar* Buffer_PC; int Result_I; /* read msg header */ Result_I = JAM_ReadMsgHeader( Base_PS, 0, &Header_S, &SubPack_PS ); if ( Result_I ) { printf("JAM_ReadMsgHeader returned %d.\n", Result_I ); return; } /* allocate buffer text */ Buffer_PC = (uchar*) malloc( Header_S.TxtLen ); if ( !Buffer_PC ) { printf("malloc failed.\n"); return; } /* read text */ Result_I = JAM_ReadMsgText( Base_PS, Header_S.TxtOffset, Header_S.TxtLen, Buffer_PC ); if ( Result_I ) printf("JAM_ReadMsgText returned %d.\n", Result_I ); free( Buffer_PC ); } ============================================== JAM_AddMessage - Add a message to message base ============================================== Syntax int JAM_AddMessage( s_JamBase* Base_PS, s_JamMsgHeader* Header_PS, s_JamSubPacket* SubPack_PS, uchar* Text_PC, ulong TextLen_I ); Description Adds a message to the message base. Fully automatic. Parameters Base_PS The message base to use Header_PS A pointer to the message header struct. The function will set the following header fields: Signature, Revision, TxtOffset, TxtLen, SubfieldLen and MsgNum. Whatever you set these fields to will be overwritten. SubPack_PS A subfield packet handler, containing all subfields for the message. Text_PC A pointer to the first byte of the message text. TextLen_I The length of the message text, excluding any zero termination characters. Returns 0 if successful JAM_IO_ERROR if an I/O error occured. see JAM_Errno() JAM_NOT_LOCKED if the message base is not locked Example { s_JamSubPacket* SubPacket_PS; s_JamSubfield Subfield_S; s_JamMsgHeader Header_S; uchar Text_AC[64]; uchar Field_AC[64]; /* ** Fix message header */ JAM_ClearMsgHeader( &Header_S ); Header_S.DateWritten = time(NULL); /* ** Create subfield packet */ SubPacket_PS = JAM_NewSubPacket(); if ( !SubPacket_PS ) { printf("JAM_NewSubPacket returned NULL.\n" ); return; } /* set up subfield 1 */ strcpy( Field_AC, "This is field #1" ); Subfield_S.LoID = JAMSFLD_SENDERNAME; Subfield_S.HiID = 0; Subfield_S.DatLen = strlen( Field_AC ); Subfield_S.Buffer = Field_AC; JAM_PutSubfield( SubPacket_PS, &Subfield_S ); /* set up subfield 2 */ strcpy( Field_AC, "This is field #2" ); Subfield_S.LoID = JAMSFLD_RECVRNAME; Subfield_S.HiID = 0; Subfield_S.DatLen = strlen( Field_AC ); Subfield_S.Buffer = Field_AC; JAM_PutSubfield( SubPacket_PS, &Subfield_S ); /* ** Add message */ strcpy( Text_AC, "Hello world!\nThis is a test."); /* [lock the message base] */ Result_I = JAM_AddMessage( Base_PS, &Header_S, SubPacket_PS, Text_AC, strlen( Text_AC ) ); if ( Result_I ) { printf("JAM_AddMessage returned %d.\n", Result_I ); return; } /* [unlock the message base] */ JAM_DelSubPacket( SubPacket_PS ); } ================================================================= JAM_AddEmptyMessage - Add a empty message entry to a message base ================================================================= Syntax int JAM_AddEmptyMessage( s_JamBase* Base_PS); Description Adds an empty message header to the message base. Useful when writing a messagebase maintenance utility. Parameters Base_PS The message base to use Returns 0 if successful JAM_IO_ERROR if an I/O error occured. see JAM_Errno() JAM_NOT_LOCKED if the message base is not locked Example none =============================================== JAM_ChangeMsgHeader - Change a message's header =============================================== Syntax int JAM_ChangeMsgHeader( s_JamBase* Base_PS, ulong MsgNo_I, s_JamMsgHeader* Header_PS ); Description Writes over an old message header with a new one. Only the header - not the subfields - can be changed due to the subfields' dynamic size. If message have MSG_DELETED attribute set, UserCRC field in index and ActiveMsgs base header also updated. NOTE! Use this function with caution. It is easy to corrupt a message by giving it an incorrect header. Parameters Base_PS The message base to use MsgNo_I The absolute message number. Message #0 is the first in the message base. Header_PS A pointer to the header structure to write. Returns 0 if successful JAM_IO_ERROR if an I/O error occured. see JAM_Errno() JAM_NOT_LOCKED if the message base is not locked Example { s_JamMsgHeader Header_S; int Result_I; /* [lock the message base] */ Result_I = JAM_ReadMsgHeader( Base_PS, 0, &Header_S, NULL ); if ( Result_I ) printf("JAM_ReadMsgHeader returned %d.\n", Result_I ); Header_S.TimesRead++; Result_I = JAM_ChangeMsgHeader( Base_PS, 0, &Header_S ); if ( Result_I ) printf("JAM_ChangeMsgHeader returned %d.\n", Result_I ); /* [unlock the message base] */ } ===================================================== JAM_ClearMsgHeader - Clear a message header structure ===================================================== Syntax int JAM_ClearMsgHeader( s_JamMsgHeader* Header_PS ); Description Clears a message header structure and prepares it for use. This includes setting the Signature field and the Revision field to their correct values, and setting the CRC fields to JAM_NO_CRC. Parameters Header_PS A pointer to the structure to prepare. Returns 0 if successful JAM_BAD_PARAM if Header_PS is NULL =================================================== JAM_DeleteMessage - Delete message from messagebase =================================================== Syntax int JAM_DeleteMessage( s_JamBase* Base_PS, ulong MsgNo_I ); Description Deletes message from messagebase by setting HdrOffset and UserCRC in index to 0xFFFFFFFF. ActiveMsgs in base header also updated. Parameters Base_PS The message base to use MsgNo_I The absolute message number. Message #0 is the first in the message base. Returns 0 if successful JAM_IO_ERROR if an I/O error occured. see JAM_Errno() JAM_NOT_LOCKED if the message base is not locked Example none -------------------------------------------------------------------------- Subfield packet functions ------------------------- As described earlier, a subfield is a part of the message header. Due to the complexity of the different network types in use, it is not feasible to try and cram all data into one header struct. Therefore, JAM uses a fairly small header struct and instead marks all additional data fields as 'subfields'. In order to make life a little more easy, I have used the concept of a container for all subfields. I call it a 'Subfield Packet'. It is identified by a struct pointer, and should be looked upon as a file or a list that you manipulate via the following four functions: =============================================== JAM_NewSubPacket - Create a new subfield packet =============================================== Syntax s_JamSubPacket* JAM_NewSubPacket( void ); Description Creates a new, empty, subfield packet. Parameters None Returns The subpacket handle, if successful, or NULL if a memory allocation failed. Example { s_JamSubPacket* SubPacket_PS; SubPacket_PS = JAM_NewSubPacket(); if ( !SubPacket_PS ) { printf("JAM_NewSubPacket returned NULL.\n" ); return; } } =========================================== JAM_DelSubPacket - Delete a subfield packet =========================================== Syntax int JAM_DelSubPacket( s_JamSubPacket* SubPack_PS ); Description Frees all memory used by a subfield packet. All subfields in the packet will be lost and the packet handle will not be valid any more. Parameters SubPack_PS The subfield packet to delete Returns 0 if successful JAM_BAD_PARAM if SubPack_PS is NULL. Example { s_JamSubPacket* SubPacket_PS; SubPacket_PS = JAM_NewSubPacket(); if ( !SubPacket_PS ) { printf("JAM_NewSubPacket returned NULL.\n" ); return; } SubPacket_PS = JAM_DelSubPacket(); if ( !SubPacket_PS ) { printf("JAM_DelSubPacket returned NULL.\n" ); return; } } ======================================================================= JAM_GetSubfield - Get a subfield from a subfield packet (not reentrant) ======================================================================= Syntax s_JamSubfield* JAM_GetSubfield( s_JamSubPacket* SubPack_PS ); Description Returns a pointer to the first/next subfield struct in the subfield packet. WARNING: This function is not reentrant and should not be used in multi-threaded applications unless you know what you are doing. Use JAM_GetSubfield_R instead when a reentrant function is needed. Parameter SubPack_PS The subfield packet to use. If this parameter is NULL, the next subfield from the subfield packet previously scanned will be returned. Returns A pointer to a subfield, if successful, or NULL if there are no more subfields in the packet. Example { s_JamSubPacket* SubPack_PS; s_JamSubfield* Subfield_PS; s_JamMsgHeader Header_S; int Result_I; Result_I = JAM_ReadMsgHeader( 0, &Header_S, &SubPack_PS ); if ( Result_I ) printf("JAM_ReadMsgHeader returned %d.\n", Result_I ); for ( Subfield_PS = JAM_GetSubfield( SubPack_PS ); Subfield_PS; Subfield_PS = JAM_GetSubfield( NULL ) ) printf("Subfield id %d\n", Subfield_PS->LoID ); JAM_DelSubPacket( SubPack_PS ); } ===================================================================== JAM_GetSubfield_R - Get a subfield from a subfield packet (reentrant) ===================================================================== Syntax s_JamSubfield* JAM_GetSubfield( s_JamSubPacket* SubPack_PS, ulong* Count_PI ); Description Returns a pointer to the first/next subfield struct in the subfield packet. This function is a reentrant replacement for JAM_GetSubfield. Parameter SubPack_PS The subfield packet to use. Count_PI Pointer to a variable that contains the number of the subfield to retrieve. The variable should be set to zero the first time the function is called and is then automatically increased by the function for any subsequent calls. Returns A pointer to a subfield, if successful, or NULL if there are no more subfields in the packet. Example { s_JamSubPacket* SubPack_PS; s_JamSubfield* Subfield_PS; s_JamMsgHeader Header_S; ulong Count_I; int Result_I; Result_I = JAM_ReadMsgHeader( 0, &Header_S, &SubPack_PS ); if ( Result_I ) printf("JAM_ReadMsgHeader returned %d.\n", Result_I ); Count_I = 0; while( ( Subfield_PS = JAM_GetSubfield_R( SubPack_PS , &Count_I ) ) ) printf("Subfield id %d\n", Subfield_PS->LoID ); JAM_DelSubPacket( SubPack_PS ); } ======================================================= JAM_PutSubfield - Put a subfield into a subfield packet ======================================================= Syntax int JAM_PutSubfield( s_JamSubPacket* SubPack_PS, s_JamSubfield* Subfield_PS ); Description Puts a subfield into a subfield packet. The subfield is copied before being put into the subfield packet. Parameters SubPack_PS The subfield packet to add to Subfield_PS The subfield to put in the packet Returns 0 if successful JAM_NO_MEMORY if a memory allocation failed Example { s_JamSubPacket* SubPacket_PS; s_JamSubfield Subfield_S; uchar Field_AC[64]; SubPacket_PS = JAM_NewSubPacket(); if ( !SubPacket_PS ) { printf("JAM_NewSubPacket returned NULL.\n" ); return; } /* set up subfield 1 */ strcpy( Field_AC, "This is field #1" ); Subfield_S.LoID = JAMSFLD_SENDERNAME; Subfield_S.HiID = 0; Subfield_S.DatLen = strlen( Field_AC ); Subfield_S.Buffer = Field_AC; JAM_PutSubfield( SubPacket_PS, &Subfield_S ); /* set up subfield 2 */ strcpy( Field_AC, "This is field #2" ); Subfield_S.LoID = JAMSFLD_RECVRNAME; Subfield_S.HiID = 0; Subfield_S.DatLen = strlen( Field_AC ); Subfield_S.Buffer = Field_AC; JAM_PutSubfield( SubPacket_PS, &Subfield_S ); JAM_DelSubPacket( SubPacket_PS ); } -------------------------------------------------------------------------- LastRead functions ------------------ JAM implements the often-used concept of high water marking for remembering which user read how many messages in each area. Personally I think this concept stinks, since it does not store *which* messages a user has read, only the number of the highest message he has read. But since it's a part of JAM and it's fairly straightforward and easy, I've implemented two support functions for it. I would, however, strongly recommend all BBS programmers to use proper message mapping systems instead, so your users can read their messages in whatever order they wish. ========================================= JAM_ReadLastRead - Read a lastread record ========================================= Syntax int JAM_ReadLastRead( s_JamBase* Base_PS, ulong User_I, s_JamLastRead* Record_PS ); Description Reads a lastread record from the lastread file. Parameter Base_PS The message base to use User_I A system-unique user number. Record_PS A pointer to the lastread struct where the record will be stored. Returns 0 if successful JAM_BAD_PARAM if Record_PS is NULL JAM_IO_ERROR if an I/O error occured. see JAM_Errno() JAM_NO_USER if the user number was not found Example { int Result_I; s_JamLastRead LastRead_S; Result_I = JAM_ReadLastRead( Base_PS, 4711, &LastRead_S ); if ( Result_I ) printf("JAM_ReadLastRead returned %d\n", Result_I ); } =========================================== JAM_WriteLastRead - Write a lastread record =========================================== Syntax int JAM_WriteLastRead( s_JamBase* Base_PS, ulong User_I, s_JamLastRead* Record_PS ); Description Writes a lastread record to the lastread file. If the user number could not be found, the record will be appended to the end of the file. Parameter Base_PS The message base to use User_I A system-unique user number Record_PS A pointer to the lastread struct to be written Returns 0 if successful JAM_BAD_PARAM if Record_PS is NULL JAM_IO_ERROR if an I/O error occured. see JAM_Errno() Example { int Result_I; s_JamLastRead LastRead_S; Result_I = JAM_WriteLastRead( Base_PS, 4711, &LastRead_S ); if ( Result_I ) printf("JAM_WriteLastRead returned %d\n", Result_I ); } -------------------------------------------------------------------------- Miscellanous functions ---------------------- ============================================== JAM_Crc32 - Calculate CRC32 on a block of data ============================================== Syntax ulong JAM_Crc32( uchar* Buffer_PC, ulong Length_I ); Description Calculates the Crc32 value for a block of data. All ASCII characters are converted to lowercase before calculating the CRC (the input data is unchanged). Parameters Buffer_PC A pointer to the first byte of the data block Length_I The number of bytes in the data block Returns The Crc32 value Example { ulong Crc_I; uchar Text_AC[32]; strcpy( Text_AC, "Hello world!\n"); Crc_I = JAM_Crc32( Text_AC, strlen( Text_AC ) ); } ============================= JAM_Errno - Specify I/O error ============================= Syntax int JAM_Errno( s_JamBase* Base_PS ); Description When any of these library routines return JAM_IO_ERROR, you can call this function to find out exactly what went wrong. Parameters Base_PS The message base to use Returns Standard 'errno' values, as the C compiler generated them, or if the I/O error was system specific, the return code is (10000 + system status code). Examples { int Result_I; uchar Text_AC[10]; /* generate an I/O error */ Result_I = JAM_ReadMsgText( 0xffffffff, 10, Text_AC ); if ( Result_I ) { errno = JAM_Errno( Base_PS ); perror("JAM i/o error"); } }