/*
**	Amiga support module for HYDRA protocol sample implementation.
**
**	Written by	Olaf Barthel
**			Brabeckstrasse 35
**			D-30559 Hannover
**
**			eMail: olsen@sourcery.han.de
**
**	Freely distributable.
*/

	/* System includes. */

#include <intuition/intuitionbase.h>

#include <libraries/gadtools.h>
#include <libraries/locale.h>
#include <libraries/asl.h>

#include <graphics/gfxbase.h>

#include <utility/date.h>

#include <devices/conunit.h>
#include <devices/serial.h>
#include <devices/timer.h>

#include <hardware/cia.h>

#include <dos/dosextens.h>
#include <dos/filehandler.h>
#include <dos/dostags.h>
#include <dos/dosasl.h>

#include <exec/memory.h>

	/* Correct a nasty bug in the prototypes. */

#define CheckIO foo21234

#include <clib/intuition_protos.h>
#include <clib/gadtools_protos.h>
#include <clib/graphics_protos.h>
#include <clib/utility_protos.h>
#include <clib/locale_protos.h>
#include <clib/timer_protos.h>
#include <clib/exec_protos.h>
#include <clib/dos_protos.h>
#include <clib/asl_protos.h>
#include <clib/macros.h>

#include <errno.h>

//#define DB(x)	x
#define DB(x)

void __stdargs kprintf(STRPTR,...);

	/* Get the CheckIO prototype right. */

#undef CheckIO

struct IORequest *CheckIO(struct IORequest *);

#include "Rendezvous.h"

#include "hydracom.h"

	/* Difference between UTC and Amiga time. */

#define UTC_OFFSET	252482400

	/* Minimum of lines to reserve for local input. */

#define MIN_LINES	3

	/* Serial buffer size. */

#define BUFFER_SIZE	8192

	/* A handy macro. */

#define ClrSignal(s)	SetSignal(0,s)

	/* Signal masks. */

#define SIG_SERREAD	(1UL << ReadPort -> mp_SigBit)
#define SIG_SERWRITE	(1UL << WritePort -> mp_SigBit)
#define SIG_CONREAD	(1UL << ConsoleReadPort -> mp_SigBit)
#define SIG_TIMER	(1UL << TimePort -> mp_SigBit)
#define SIG_WINDOW	(1UL << WindowPort -> mp_SigBit)
#define SIG_HANDSHAKE	SIGF_SINGLE
#define SIG_KILL	SIGBREAKF_CTRL_C

	/* A serial buffer structure. */

struct SerialBuffer
{
	struct IOExtSer	*SerialRequest;
	UBYTE		*SerialBuffer,
			*SerialIndex,
			*SerialTop;
	LONG		 SerialSize,
			 SerialFilled;
	BOOL		 IsClone,
			 IsBusy;
};

	/* Special rendezvous data. */

STATIC struct RendezvousData		*RendezvousData;
STATIC struct RendezvousSemaphore	*RendezvousSemaphore;

	/* Library bases. */

struct IntuitionBase	*IntuitionBase;
struct GfxBase		*GfxBase;
struct LocaleBase	*LocaleBase;
struct Library		*GadToolsBase,
			*UtilityBase,
			*TimerBase,
			*AslBase;

	/* Timer data. */

struct MsgPort		*TimePort;
struct timerequest	*TimeRequest;

	/* Serial data. */

struct MsgPort		*ReadPort,
			*WritePort;

struct SerialBuffer	*ThisBuffer,
			*NextBuffer,
			*ReadBuffer;

	/* Console data. */

struct MsgPort		*WindowPort;

struct Window		*FileWindow,
			*RemoteWindow,
			*LocalWindow,
			*LogWindow;

struct MsgPort		*ConsoleWritePort,
			*ConsoleReadPort;
struct IOStdReq		*ConsoleReadRequest;
UBYTE			 ConsoleChar;
BOOL			 ConsoleReady = FALSE,
			 WindowReady = FALSE;

struct IOStdReq		*FileRequest,
			*RemoteRequest,
			*LocalRequest,
			*LogRequest;

	/* DOS Data. */

struct Process		*ThisProcess;
APTR			 OldPtr;
LONG			 OldPri;

struct AnchorPath	*Anchor;
BOOL			 AnchorUsed = FALSE;

	/* File requester data. */

struct Process		*FileRequesterProcess;
struct MsgPort		*FileRequesterPort;

	/* Screen data. */

struct Screen		*PublicScreen,
			*Screen;

	/* Menu data. */

APTR			 VisualInfo;
struct Menu		*Menu;

struct NewMenu MenuTemplate[] =
{
	{ NM_TITLE, "Project",			 0 ,	0,	0,	(APTR)0},
	{  NM_ITEM, "Toggle chat",		"C",	0,	0,	(APTR)Alt_C},
	{  NM_ITEM, NM_BARLABEL,		 0 ,	0,	0,	(APTR)0},
	{  NM_ITEM, "Hang up",			"H",	0,	0,	(APTR)Alt_H},
	{  NM_ITEM, "Toggle duplex",		"E",	0,	0,	(APTR)Alt_E},
	{  NM_ITEM, "Toggle 7 bits/8 bits",	"B",	0,	0,	(APTR)Alt_B},
	{  NM_ITEM, NM_BARLABEL,		 0 ,	0,	0,	(APTR)0},
	{  NM_ITEM, "Start upload",		"U",	0,	0,	(APTR)PgUp},
	{  NM_ITEM, "Start download",		"D",	0,	0,	(APTR)PgDn},
	{  NM_ITEM, NM_BARLABEL,		 0 ,	0,	0,	(APTR)0},
	{  NM_ITEM, "Abort Hydra session",	".",	0,	0,	(APTR)Esc},
	{  NM_ITEM, NM_BARLABEL,		 0 ,	0,	0,	(APTR)0},
	{  NM_ITEM, "Exit HydraCom",		"Q",	0,	0,	(APTR)Alt_X},
	{ NM_END,   0,				 0 ,	0,	0,	(APTR)0}
};

	/* Time data. */

LONG			 GMT_Offset = UTC_OFFSET;

	/* Version ID. */

STRPTR VersionTag = "$VER: hydracom 1.0r6 (16.08.95)\r\n";

	/* CloseWindowSafely(struct Window *Window):
	 *
	 *	Close a window sharing its UserPort with other
	 *	windows.
	 */

STATIC VOID __regargs
CloseWindowSafely(struct Window *Window)
{
	struct IntuiMessage	*IntuiMessage;
	struct Node		*Successor;

	Forbid();

	if(Window -> UserPort)
	{
		IntuiMessage = (struct IntuiMessage *)Window -> UserPort -> mp_MsgList . lh_Head;

		while(Successor = IntuiMessage -> ExecMessage . mn_Node . ln_Succ)
		{
			if(IntuiMessage -> IDCMPWindow == Window)
			{
				Remove((struct Node *)IntuiMessage);

				ReplyMsg((struct Message *)IntuiMessage);
			}

			IntuiMessage = (struct IntuiMessage *)Successor;
		}

		Window -> UserPort = NULL;
	}

	ModifyIDCMP(Window,NULL);

	Permit();

	CloseWindow(Window);
}

	/* UpdateTime(struct timeval *Now):
	 *
	 *	Get the current time and/or update the current
	 *	time offset data.
	 */

STATIC VOID __regargs
UpdateTime(struct timeval *Now)
{
	if(Now)
		Now -> tv_secs = Now -> tv_micro = 0;

	if(TimePort = CreateMsgPort())
	{
		if(TimeRequest = (struct timerequest *)CreateIORequest(TimePort,sizeof(struct timerequest)))
		{
			if(!OpenDevice(TIMERNAME,UNIT_VBLANK,TimeRequest,NULL))
			{
				TimerBase = (struct Library *)TimeRequest -> tr_node . io_Device;

				if(LocaleBase = (struct LocaleBase *)OpenLibrary("locale.library",38))
				{
					struct Locale *Locale;

					if(Locale = OpenLocale(NULL))
					{
						GMT_Offset = 60 * Locale -> loc_GMTOffset + UTC_OFFSET;

						CloseLocale(Locale);
					}

					CloseLibrary(LocaleBase);

					LocaleBase = NULL;
				}

				if(Now)
					GetSysTime(Now);

				TimerBase = NULL;

				CloseDevice(TimeRequest);
			}

			DeleteIORequest(TimeRequest);

			TimeRequest = NULL;
		}

		DeleteMsgPort(TimePort);

		TimePort = NULL;
	}
}

	/* FileRequestEntry(VOID):
	 *
	 *	Asynchronous file request process entry.
	 */

STATIC VOID __saveds
FileRequestEntry(VOID)
{
	struct FileRequester *FileRequester;

	if(FileRequester = (struct FileRequester *)AllocAslRequestTags(ASL_FileRequest,
		ASLFR_TitleText,	"Select file(s) to upload",
		ASLFR_InitialPattern,	"#?",
		ASLFR_Flags1,		FRF_PRIVATEIDCMP | FRF_DOMULTISELECT | FRF_DOPATTERNS,
	TAG_DONE))
	{
		if(FileRequesterPort = CreateMsgPort())
		{
			struct Message	*Message;
			ULONG		 Signals;
			BOOL		 Done = FALSE;

			FileRequesterProcess = (struct Process *)FindTask(NULL);

			Signal(ThisProcess,SIG_HANDSHAKE);

			do
			{
				Signals = Wait((1L << FileRequesterPort -> mp_SigBit) | SIG_KILL);

				if(Signals & (1L << FileRequesterPort -> mp_SigBit))
				{
					LONG MaxLen;

					while(Message = GetMsg(FileRequesterPort))
					{
						MaxLen = (LONG)Message -> mn_Node . ln_Name;

						if(AslRequestTags(FileRequester,
							ASLFR_Window,		LocalWindow,
							ASLFR_SleepWindow,	TRUE,
						TAG_DONE))
						{
							if(FileRequester -> fr_NumArgs > 0)
							{
								UBYTE	 LocalBuffer[256];
								char	*String;
								LONG	 Len,Count,i;

								for(i = Len = 0 ; i < FileRequester -> fr_NumArgs ; i++)
								{
									if(FileRequester -> fr_ArgList[i] . wa_Lock)
									{
										if(NameFromLock(FileRequester -> fr_ArgList[i] . wa_Lock,LocalBuffer,256))
											Len += strlen(LocalBuffer) + 1;
									}
									else
										Len += strlen(FileRequester -> fr_Drawer) + 1;

									Len += strlen(FileRequester -> fr_ArgList[i] . wa_Name) + 1 + 2;
								}

								Len++;

								if(String = AllocVec(Len,MEMF_ANY))
								{
									*String = 0;

									for(i = Count = 0 ; Count < MaxLen && i < FileRequester -> fr_NumArgs ; i++)
									{
										if(FileRequester -> fr_ArgList[i] . wa_Lock)
										{
											if(NameFromLock(FileRequester -> fr_ArgList[i] . wa_Lock,LocalBuffer,256))
											{
												if(AddPart(LocalBuffer,FileRequester -> fr_ArgList[i] . wa_Name,256))
												{
													if(Count + strlen(LocalBuffer) + 2 < MaxLen)
													{
														strcat(String,"\"");
														strcat(String,LocalBuffer);
														strcat(String,"\"");

														Count += strlen(LocalBuffer) + 2;
													}
													else
														break;
												}
											}
										}
										else
										{
											strcpy(LocalBuffer,FileRequester -> fr_Drawer);

											if(AddPart(LocalBuffer,FileRequester -> fr_ArgList[i] . wa_Name,256))
											{
												if(Count + strlen(LocalBuffer) + 2 < MaxLen)
												{
													strcat(String,"\"");
													strcat(String,LocalBuffer);
													strcat(String,"\"");

													Count += strlen(LocalBuffer) + 2;
												}
												else
													break;
											}
										}

										if(i != FileRequester -> fr_NumArgs - 1)
										{
											strcat(String," ");
											Count++;
										}
									}

									Message -> mn_Node . ln_Name = String;

									ReplyMsg(Message);

									Message = NULL;
								}
							}
							else
							{
								if(FileRequester -> fr_File[0])
								{
									LONG Len;

									Len = strlen(FileRequester -> fr_Drawer) + strlen(FileRequester -> fr_File) + 2 + 2;

									if(Len <= MaxLen)
									{
										char *String;

										if(String = AllocVec(Len,MEMF_ANY))
										{
											strcpy(String + 1,FileRequester -> fr_Drawer);

											if(AddPart(String + 1,FileRequester -> fr_File,Len))
											{
												String[0] = '\"';
												strcat(String,"\"");

												Message -> mn_Node . ln_Name = String;

												ReplyMsg(Message);

												Message = NULL;
											}
											else
												FreeVec(String);
										}
									}
								}
							}
						}

						if(Message)
						{
							Message -> mn_Node . ln_Name = NULL;

							ReplyMsg(Message);
						}
					}
				}

				if(Signals & SIG_KILL)
					Done = TRUE;
			}
			while(!Done);

			while(Message = GetMsg(FileRequesterPort))
			{
				Message -> mn_Node . ln_Name = NULL;

				ReplyMsg(Message);
			}

			DeleteMsgPort(FileRequesterPort);
		}

		FreeAslRequest(FileRequester);
	}

	Forbid();

	FileRequesterProcess = NULL;

	Signal(ThisProcess,SIG_HANDSHAKE);
}

	/* GetFiles(char *Buffer,int MaxLen):
	 *
	 *	Get a list of file names, asynchronously please.
	 */

char *
GetFiles(char *Buffer,int MaxLen)
{
	struct MsgPort	*ReplyPort;
	char		*Result = NULL;

	if(ReplyPort = CreateMsgPort())
	{
		struct Message *Message;

		if(Message = AllocVec(sizeof(struct Message),MEMF_ANY | MEMF_CLEAR))
		{
			Message -> mn_Length		= sizeof(struct Message);
			Message -> mn_ReplyPort		= ReplyPort;
			Message -> mn_Node . ln_Name	= (STRPTR)MaxLen;

			PutMsg(FileRequesterPort,Message);

			FOREVER
			{
				if(SetSignal(0,(1L << ReplyPort -> mp_SigBit)) & (1L << ReplyPort -> mp_SigBit))
				{
					GetMsg(ReplyPort);

					break;
				}

				sys_idle();
			}

			if(Message -> mn_Node . ln_Name)
			{
				strcpy(Buffer,Message -> mn_Node . ln_Name);

				FreeVec(Message -> mn_Node . ln_Name);

				Result = Buffer;
			}

			FreeVec(Message);
		}

		DeleteMsgPort(ReplyPort);
	}

	return(Result);
}

	/* OpenConsole():
	 *
	 *	Open a console window.
	 */

STATIC BOOL __regargs
OpenConsole(struct Screen *Screen,LONG Top,LONG Height,STRPTR Title,BOOL Resize,struct Window **WindowPtr,struct IOStdReq **ConsolePtr)
{
	struct Window *Window;

	if(Window = OpenWindowTags(NULL,
		WA_Left,		0,
		WA_Top,			Top,
		WA_Width,		Screen -> Width,
		WA_Height,		Height,
		WA_Title,		Title,
		WA_SimpleRefresh,	TRUE,
		WA_DepthGadget,		TRUE,
		WA_DragBar,		TRUE,
		WA_SizeGadget,		Resize,
		WA_SizeBRight,		TRUE,
		WA_CustomScreen,	Screen,
		WA_NewLookMenus,	TRUE,
	TAG_DONE))
	{
		Window -> UserPort = WindowPort;

		SetMenuStrip(Window,Menu);

		if(ModifyIDCMP(Window,IDCMP_MENUPICK))
		{
			struct IOStdReq *ConsoleRequest;

			if(Window -> RPort -> Font -> tf_Flags & FPF_PROPORTIONAL)
				SetFont(Window -> RPort,GfxBase -> DefaultFont);

			if(ConsoleRequest = (struct IOStdReq *)CreateIORequest(ConsoleWritePort,sizeof(struct IOStdReq)))
			{
				ConsoleRequest -> io_Data = Window;

				if(!OpenDevice("console.device",CONU_CHARMAP,ConsoleRequest,CONFLAG_DEFAULT))
				{
					WindowLimits(Window,Window -> BorderLeft + 10 * Window -> RPort -> Font -> tf_XSize * 10 + Window -> BorderRight,Window -> BorderTop + 2 * Window -> RPort -> Font -> tf_YSize + Window -> BorderBottom,Screen -> Width,Screen -> Height);

						/* Turn off the cursor. */

					ConPrintf(ConsoleRequest,"\033[0 p");

					*WindowPtr	= Window;
					*ConsolePtr	= ConsoleRequest;

					return(TRUE);
				}

				DeleteIORequest(ConsoleRequest);
			}
		}

		ClearMenuStrip(Window);

		CloseWindowSafely(Window);
	}

	return(FALSE);
}

	/* CloseConsole():
	 *
	 *	Close a console window.
	 */

STATIC VOID __regargs
CloseConsole(struct Window **WindowPtr,struct IOStdReq **ConsolePtr)
{
	if(*ConsolePtr)
	{
		CloseDevice(*ConsolePtr);

		DeleteIORequest(*ConsolePtr);

		*ConsolePtr = NULL;
	}

	if(*WindowPtr)
	{
		ClearMenuStrip(*WindowPtr);

		CloseWindowSafely(*WindowPtr);

		*WindowPtr = NULL;
	}
}

	/* CloneSerialBuffer():
	 *
	 *	Clone a SerialBuffer structure.
	 */

STATIC struct SerialBuffer * __regargs
CloneSerialBuffer(struct SerialBuffer *Source,struct MsgPort *MsgPort)
{
	struct SerialBuffer *Buffer;

	if(Buffer = (struct SerialBuffer *)AllocVec(sizeof(struct SerialBuffer) + Source -> SerialSize,MEMF_ANY | MEMF_PUBLIC))
	{
		Buffer -> SerialBuffer	= Buffer -> SerialIndex = (UBYTE *)(Buffer + 1);
		Buffer -> SerialFilled	= 0;
		Buffer -> SerialTop	= Buffer -> SerialBuffer + Source -> SerialSize;
		Buffer -> SerialSize	= Source -> SerialSize;
		Buffer -> IsClone	= TRUE;
		Buffer -> IsBusy	= FALSE;

		if(Buffer -> SerialRequest = (struct IOExtSer *)AllocVec(sizeof(struct IOExtSer),MEMF_ANY | MEMF_PUBLIC))
		{
			CopyMem(Source -> SerialRequest,Buffer -> SerialRequest,sizeof(struct IOExtSer));

			Buffer -> SerialRequest -> IOSer . io_Message . mn_ReplyPort = MsgPort;

			return(Buffer);
		}
		else
			cprint("Could not create serial request\n");

		FreeVec(Buffer);
	}
	else
		cprint("Could not create serial buffer\n");

	return(NULL);
}

	/* DeleteSerialBuffer():
	 *
	 *	Delete a SerialBuffer structure.
	 */

STATIC VOID __regargs
DeleteSerialBuffer(struct SerialBuffer *Buffer)
{
	if(Buffer)
	{
		if(Buffer -> IsBusy)
		{
			if(!CheckIO(Buffer -> SerialRequest))
				AbortIO(Buffer -> SerialRequest);

			WaitIO(Buffer -> SerialRequest);
		}

		if(Buffer -> IsClone)
			FreeVec(Buffer -> SerialRequest);
		else
		{
			CloseDevice(Buffer -> SerialRequest);

			DeleteIORequest(Buffer -> SerialRequest);
		}

		FreeVec(Buffer);
	}
}

	/* CreateSerialBuffer():
	 *
	 *	Create a serial buffer structure.
	 */

STATIC struct SerialBuffer * __regargs
CreateSerialBuffer(STRPTR Device,LONG Unit,LONG Size,struct MsgPort *MsgPort)
{
	struct SerialBuffer *Buffer;

	if(Buffer = (struct SerialBuffer *)AllocVec(sizeof(struct SerialBuffer) + Size,MEMF_ANY | MEMF_PUBLIC))
	{
		Buffer -> SerialBuffer	= Buffer -> SerialIndex = (UBYTE *)(Buffer + 1);
		Buffer -> SerialFilled	= 0;
		Buffer -> SerialTop	= Buffer -> SerialBuffer + Size;
		Buffer -> SerialSize	= Size;
		Buffer -> IsClone	= FALSE;
		Buffer -> IsBusy	= FALSE;

		if(Buffer -> SerialRequest = (struct IOExtSer *)CreateIORequest(MsgPort,sizeof(struct IOExtSer)))
		{
			Buffer -> SerialRequest -> io_SerFlags = SERF_SHARED;

			if(!OpenDevice(Device,Unit,Buffer -> SerialRequest,NULL))
				return(Buffer);
			else
			{
				cprint("Could not open \"%s\", unit %d\n",Device,Unit);

				DeleteIORequest(Buffer -> SerialRequest);
			}
		}
		else
			cprint("Could not create serial request\n");

		FreeVec(Buffer);
	}
	else
		cprint("Could not create serial buffer\n");

	return(NULL);
}

	/* OpenAll():
	 *
	 *	Allocate all the resources required.
	 */

STATIC BOOL
OpenAll(STRPTR Device,LONG Unit)
{
	LONG Top,Lines,BorderSize,FontSize,ExtraLines,RemainingLines,TotalHeight;
	UWORD Pens = (UWORD)~0;

	ThisProcess = (struct Process *)FindTask(NULL);

	if(pri != 1000)
	{
		if(pri < -128)
			pri = 128;
		else
		{
			if(pri > 127)
				pri = 127;
		}

		OldPri = SetTaskPri(ThisProcess,pri);
	}

	if(!(IntuitionBase = (struct IntuitionBase *)OpenLibrary("intuition.library",37)))
	{
		cprint("Could not open intuition.library v37\n");

		return(FALSE);
	}

	if(!(GfxBase = (struct GfxBase *)OpenLibrary("graphics.library",37)))
	{
		cprint("Could not open graphics.library v37\n");

		return(FALSE);
	}

	if(!(GadToolsBase = OpenLibrary("gadtools.library",37)))
	{
		cprint("Could not open gadtools.library v37\n");

		return(FALSE);
	}

	if(!(UtilityBase = OpenLibrary("utility.library",37)))
	{
		cprint("Could not open utility.library v37\n");

		return(FALSE);
	}

	if(!(AslBase = OpenLibrary("asl.library",37)))
	{
		cprint("Could not open asl.library v37\n");

		return(FALSE);
	}

	if(LocaleBase = (struct LocaleBase *)OpenLibrary("locale.library",38))
	{
		struct Locale *Locale;

		if(Locale = OpenLocale(NULL))
		{
			GMT_Offset = 60 * Locale -> loc_GMTOffset + UTC_OFFSET;

			CloseLocale(Locale);
		}

		CloseLibrary(LocaleBase);
	}

	Forbid();

	if(CreateNewProcTags(
		NP_Name,	"HydraCom Filerequester Process",
		NP_WindowPtr,	-1,
		NP_Entry,	FileRequestEntry,
	TAG_DONE))
	{
		ClrSignal(SIG_HANDSHAKE);

		Wait(SIG_HANDSHAKE);
	}

	Permit();

	if(!FileRequesterProcess)
	{
		cprint("Could not create file requester process\n");

		return(FALSE);
	}

	if(!(TimePort = CreateMsgPort()))
	{
		cprint("Could not create timer port\n");

		return(FALSE);
	}

	if(!(TimeRequest = (struct timerequest *)CreateIORequest(TimePort,sizeof(struct timerequest))))
	{
		cprint("Could not create timer request\n");

		return(FALSE);
	}

	if(OpenDevice(TIMERNAME,UNIT_VBLANK,TimeRequest,NULL))
	{
		cprint("Could not open timer\n");

		return(FALSE);
	}

	TimerBase = (struct Library *)TimeRequest -> tr_node . io_Device;

	if(!(Anchor = (struct AnchorPath *)AllocVec(sizeof(struct AnchorPath) + 512,MEMF_ANY | MEMF_CLEAR)))
	{
		cprint("Could not allocate pattern matching buffe\n");

		return(FALSE);
	}

	Anchor -> ap_Strlen = 512;

	if(!(ReadPort = CreateMsgPort()))
	{
		cprint("Could not create serial read port\n");

		return(FALSE);
	}

	if(!(WritePort = CreateMsgPort()))
	{
		cprint("Could not create serial write port\n");

		return(FALSE);
	}

	Forbid();

	if(RendezvousSemaphore = (struct RendezvousSemaphore *)FindSemaphore(Device))
	{
		ObtainSemaphore(RendezvousSemaphore);

		if(!(RendezvousData = (*RendezvousSemaphore -> rs_Login)(ReadPort,WritePort,NULL)))
		{
			Permit();

			ReleaseSemaphore(RendezvousSemaphore);

			RendezvousSemaphore = NULL;

			cprint("Could not link to `term' port \"%s\"\n",Device);

			return(FALSE);
		}
	}

	Permit();

	if(!(ConsoleReadPort = CreateMsgPort()))
	{
		cprint("Could not create console read port\n");

		return(FALSE);
	}

	if(!(ConsoleWritePort = CreateMsgPort()))
	{
		cprint("Could not create console write port\n");

		return(FALSE);
	}

	if(!(ConsoleReadRequest = (struct IOStdReq *)AllocVec(sizeof(struct IOStdReq),MEMF_ANY | MEMF_PUBLIC | MEMF_CLEAR)))
	{
		cprint("Could not create console read request\n");

		return(FALSE);
	}

	if(!RendezvousData)
	{
		if(!(ReadBuffer = CreateSerialBuffer(Device,Unit,BUFFER_SIZE,ReadPort)))
			return(FALSE);
	}
	else
	{
		if(ReadBuffer = (struct SerialBuffer *)AllocVec(sizeof(struct SerialBuffer) + BUFFER_SIZE,MEMF_ANY | MEMF_PUBLIC))
		{
			ReadBuffer -> SerialBuffer	= ReadBuffer -> SerialIndex = (UBYTE *)(ReadBuffer + 1);
			ReadBuffer -> SerialFilled	= 0;
			ReadBuffer -> SerialTop		= ReadBuffer -> SerialBuffer + BUFFER_SIZE;
			ReadBuffer -> SerialSize	= BUFFER_SIZE;
			ReadBuffer -> IsClone		= TRUE;
			ReadBuffer -> IsBusy		= FALSE;
			ReadBuffer -> SerialRequest	= &RendezvousData -> rd_ReadRequest;
		}
		else
		{
			cprint("Could not create serial ReadBuffer\n");

			return(FALSE);
		}
	}

	if(!(ThisBuffer = CloneSerialBuffer(ReadBuffer,WritePort)))
		return(FALSE);

	if(!(NextBuffer = CloneSerialBuffer(ReadBuffer,WritePort)))
		return(FALSE);

	if(!(PublicScreen = LockPubScreen(NULL)))
	{
		cprint("Could not find default public screen\n");

		return(FALSE);
	}

	if(RendezvousData)
	{
		if(!(Screen = RendezvousData -> rd_Screen))
			Screen = PublicScreen;
	}
	else
	{
		if(!(Screen = OpenScreenTags(NULL,
			SA_DisplayID,	GetVPModeID(&PublicScreen -> ViewPort),
			SA_Overscan,	OSCAN_TEXT,
			SA_Depth,	2,
			SA_Title,	PRGNAME " " VERSION " " HC_OS " Amiga rev 6, ported by Olaf `Olsen' Barthel",
			SA_Behind,	TRUE,
			SA_SysFont,	1,
			SA_Pens,	&Pens,
			SA_Interleaved,	TRUE,
		TAG_DONE)))
		{
			cprint("Could not open screen\n");

			return(FALSE);
		}
	}

	if(!(VisualInfo = GetVisualInfo(Screen,TAG_DONE)))
	{
		cprint("Could not obtain screen visual info\n");

		return(FALSE);
	}

	if(!(Menu = CreateMenus(MenuTemplate,TAG_DONE)))
	{
		cprint("Could not create menus\n");

		return(FALSE);
	}

	if(!LayoutMenus(Menu,VisualInfo,
		GTMN_TextAttr,		Screen -> Font,
		GTMN_NewLookMenus,	TRUE,
	TAG_DONE))
	{
		cprint("Could not layout menus\n");

		return(FALSE);
	}

	Top		= Screen -> BarHeight + 1;
	BorderSize	= Screen -> WBorTop + Screen -> Font -> ta_YSize + 1 + Screen -> WBorBottom;
	FontSize	= GfxBase -> DefaultFont -> tf_YSize;
	TotalHeight	= Screen -> Height - Top;
	Lines		= (TotalHeight - 3 - 4 * BorderSize) / FontSize;
	ExtraLines	= Lines > MIN_LINES ? (Lines - MIN_LINES) / 3 : 0;
	RemainingLines	= Lines > MIN_LINES + ExtraLines * 3 ? Lines - (MIN_LINES + ExtraLines * 3) : 0;

	if(Lines < MIN_LINES)
	{
		cprint("Screen size too small (need at least %d text lines, can get only %d)\n",MIN_LINES,Lines);

		return(FALSE);
	}

	if(!(WindowPort = CreateMsgPort()))
	{
		cprint("Could not create window port\n");

		return(FALSE);
	}

	if(!OpenConsole(Screen,Top,BorderSize + (6 + ExtraLines) * FontSize,"Log",TRUE,&LogWindow,&LogRequest))
	{
		cprint("Could not open console window #1\n");

		return(FALSE);
	}

	TotalHeight	-= LogWindow -> Height + 1;
	Top		+= LogWindow -> Height + 1;

	if(!OpenConsole(Screen,Top,BorderSize + 2 * FontSize,"File",FALSE,&FileWindow,&FileRequest))
	{
		cprint("Could not open console window #2\n");

		return(FALSE);
	}

	ConPrintf(FileRequest,"\033[?7l");

	TotalHeight	-= FileWindow -> Height + 1;
	Top		+= FileWindow -> Height + 1;

	if(!OpenConsole(Screen,Top,BorderSize + (8 + RemainingLines) * FontSize,"Remote",TRUE,&RemoteWindow,&RemoteRequest))
	{
		cprint("Could not open console window #3\n");

		return(FALSE);
	}

	TotalHeight	-= RemoteWindow -> Height + 1;
	Top		+= RemoteWindow -> Height + 1;

	if(!OpenConsole(Screen,Top,TotalHeight,"Local (Press [Amiga+C] to start/end chat mode, [Esc] to abort Hydra session)",TRUE,&LocalWindow,&LocalRequest))
	{
		cprint("Could not open console window #4\n");

		return(FALSE);
	}

	CopyMem(LocalRequest,ConsoleReadRequest,sizeof(struct IOStdReq));

	ConsoleReadRequest -> io_Message . mn_ReplyPort = ConsoleReadPort;

		/* Turn the cursors back on. */

	ConPrintf(LocalRequest,"\33[ p");
	ConPrintf(RemoteRequest,"\33[ p");

	ConsoleReadRequest -> io_Command	= CMD_READ;
	ConsoleReadRequest -> io_Data		= &ConsoleChar;
	ConsoleReadRequest -> io_Length		= 1;

	ClrSignal(SIG_CONREAD);
	SendIO(ConsoleReadRequest);

	OldPtr = ThisProcess -> pr_WindowPtr;

	ThisProcess -> pr_WindowPtr = (APTR)LocalWindow;

	ScreenToFront(Screen);

	ActivateWindow(LocalWindow);

	UnlockPubScreen(NULL,PublicScreen);

	PublicScreen = NULL;

	if(!RendezvousData)
		com_flow(flowflags);

	return(TRUE);
}

	/* CloseAll():
	 *
	 *	Close all the resources.
	 */

STATIC VOID
CloseAll(VOID)
{
	if(FileRequesterProcess)
	{
		Forbid();

		ClrSignal(SIG_HANDSHAKE);

		Signal(FileRequesterProcess,SIG_KILL);

		Wait(SIG_HANDSHAKE);

		Permit();
	}

	if(LocalWindow)
		ClearMenuStrip(LocalWindow);

	if(Menu)
		FreeMenus(Menu);

	if(VisualInfo)
		FreeVisualInfo(VisualInfo);

	if(AnchorUsed)
		MatchEnd(Anchor);

	if(Anchor)
		FreeVec(Anchor);

	if(ThisProcess)
		ThisProcess -> pr_WindowPtr = OldPtr;

	if(ConsoleReadRequest)
	{
		if(ConsoleReadRequest -> io_Device)
		{
			if(!CheckIO(ConsoleReadRequest))
				AbortIO(ConsoleReadRequest);

			WaitIO(ConsoleReadRequest);
		}

		FreeVec(ConsoleReadRequest);
	}

	CloseConsole(&LocalWindow,&LocalRequest);
	CloseConsole(&RemoteWindow,&RemoteRequest);
	CloseConsole(&FileWindow,&FileRequest);
	CloseConsole(&LogWindow,&LogRequest);

	if(WindowPort)
		DeleteMsgPort(WindowPort);

	if(!RendezvousData && Screen)
		CloseScreen(Screen);

	if(PublicScreen)
		UnlockPubScreen(NULL,PublicScreen);

	DeleteSerialBuffer(NextBuffer);
	DeleteSerialBuffer(ThisBuffer);

	if(RendezvousData)
	{
		if(ReadBuffer -> IsBusy)
		{
			if(!CheckIO(ReadBuffer -> SerialRequest))
				AbortIO(ReadBuffer -> SerialRequest);

			WaitIO(ReadBuffer -> SerialRequest);
		}

		FreeVec(ReadBuffer);
	}
	else
		DeleteSerialBuffer(ReadBuffer);

	if(ConsoleWritePort)
		DeleteMsgPort(ConsoleWritePort);

	if(ConsoleReadPort)
		DeleteMsgPort(ConsoleReadPort);

	if(WritePort)
		DeleteMsgPort(WritePort);

	if(ReadPort)
		DeleteMsgPort(ReadPort);

	if(TimeRequest)
	{
		if(TimeRequest -> tr_node . io_Device)
			CloseDevice(TimeRequest);

		DeleteIORequest(TimeRequest);
	}

	if(TimePort)
		DeleteMsgPort(TimePort);

	if(UtilityBase)
		CloseLibrary(UtilityBase);

	if(AslBase)
		CloseLibrary(AslBase);

	if(GadToolsBase)
		CloseLibrary(GadToolsBase);

	if(GfxBase)
		CloseLibrary(GfxBase);

	if(IntuitionBase)
		CloseLibrary(IntuitionBase);

	if(RendezvousData)
	{
		(*RendezvousSemaphore -> rs_Logoff)(RendezvousData);

		RendezvousData = NULL;
	}

	if(RendezvousSemaphore)
	{
		ReleaseSemaphore(RendezvousSemaphore);

		RendezvousSemaphore = NULL;
	}

	if(pri != 1000)
		OldPri = SetTaskPri(ThisProcess,OldPri);
}

	/* ConPutc():
	 *
	 *	Output a single character.
	 */

VOID __stdargs
ConPutc(struct IOStdReq *Request,UBYTE Char)
{
	Request -> io_Command	= CMD_WRITE;
	Request -> io_Data	= &Char;
	Request -> io_Length	= 1;

	DoIO(Request);
}

	/* ConPuts():
	 *
	 *	Output a string.
	 */

VOID
ConPuts(struct IOStdReq *Request,STRPTR String)
{
	Request -> io_Command	= CMD_WRITE;
	Request -> io_Data	= String;
	Request -> io_Length	= strlen(String);

	DoIO(Request);
}

	/* ConPrintf():
	 *
	 *	Formatted console output.
	 */

VOID __stdargs
ConPrintf(struct IOStdReq *Request,STRPTR Format,...)
{
	STATIC UBYTE Buffer[512];

	va_list	VarArgs;
	LONG	Len;

	va_start(VarArgs,Format);
	vsprintf(Buffer,Format,VarArgs);
	va_end(VarArgs);

	Len = strlen(Buffer);

	if(Buffer[0] != '\033' && Request == FileRequest)
	{
		struct ConUnit *Unit = (struct ConUnit *)Request -> io_Unit;

		if(Unit -> cu_XCCP + Len > Unit -> cu_XMax)
		{
			if((Len = Unit -> cu_XMax - Unit -> cu_XCCP) < 1)
				return;
		}
	}

	Request -> io_Command	= CMD_WRITE;
	Request -> io_Data	= Buffer;
	Request -> io_Length	= Len;

	DoIO(Request);
}

	/* ConMove():
	 *
	 *	Move the cursor to a new position.
	 */

VOID
ConMove(struct IOStdReq *Request,LONG x,LONG y)
{
	ConPrintf(Request,"\33[%ld;%ldH",y,x);
}

	/* ConClear():
	 *
	 *	Clear the console window.
	 */

VOID
ConClear(struct IOStdReq *Request)
{
	struct ConUnit *ConUnit = (struct ConUnit *)Request -> io_Device;
	LONG x,y;

	x = ConUnit -> cu_XCCP;
	y = ConUnit -> cu_YCCP;

	ConPrintf(Request,"\033[2J\33[%ld;%ldH",y,x);
}

	/* ConGetKey():
	 *
	 *	Read a character from a console window.
	 */

int
ConGetKey()
{
	if(ConsoleReady)
	{
		int Result = ConsoleChar;

		ConsoleReady = FALSE;

		ConsoleReadRequest -> io_Command	= CMD_READ;
		ConsoleReadRequest -> io_Data		= &ConsoleChar;
		ConsoleReadRequest -> io_Length		= 1;

		ClrSignal(SIG_CONREAD);
		SendIO(ConsoleReadRequest);

		return(Result);
	}
	else
	{
		int Result = 0;

		if(WindowReady)
		{
			struct IntuiMessage *IntuiMessage;
			ULONG MsgClass;
			UWORD MsgCode;

			while(IntuiMessage = (struct IntuiMessage *)GetMsg(LocalWindow -> UserPort))
			{
				MsgClass	= IntuiMessage -> Class;
				MsgCode		= IntuiMessage -> Code;

				ReplyMsg(IntuiMessage);

				if(MsgClass == IDCMP_MENUPICK)
				{
					struct MenuItem *Item;

					while(MsgCode != MENUNULL)
					{
						if(Item = ItemAddress(Menu,MsgCode))
						{
							if(MENU_USERDATA(Item))
							{
								if(!Result)
									Result = (int)MENU_USERDATA(Item);
							}

							MsgCode = Item -> NextSelect;
						}
						else
							break;
					}
				}
			}

			WindowReady = FALSE;
		}

		return(Result);
	}
}

	/* ConScanKey():
	 *
	 *	Check for a keyboard event.
	 */

int
ConScanKey()
{
	if(ConsoleReady || WindowReady)
		return(1);
	else
	{
		int Result = 0;

		if(CheckSignal(SIG_WINDOW))
		{
			WindowReady = TRUE;

			Result = 1;
		}

		if(CheckIO(ConsoleReadRequest))
		{
			if(!WaitIO(ConsoleReadRequest))
			{
				ConsoleReady = TRUE;

				return(1);
			}
			else
			{
				ConsoleReadRequest -> io_Command	= CMD_READ;
				ConsoleReadRequest -> io_Data		= &ConsoleChar;
				ConsoleReadRequest -> io_Length		= 1;

				ClrSignal(SIG_CONREAD);
				SendIO(ConsoleReadRequest);
			}
		}

		return(Result);
	}

	return(0);
}

	/* dtr_out(byte flag):
	 *
	 *	If flag == 0 -> drop DTR signal, else set it.
	 */

VOID
dtr_out(byte flag)
{
	if(!flag && !RendezvousData)
	{
		if(ThisBuffer -> IsBusy)
		{
			WaitIO(ThisBuffer -> SerialRequest);

			ThisBuffer -> IsBusy		= FALSE;
			ThisBuffer -> SerialIndex	= ThisBuffer -> SerialBuffer;
		}

		if(ReadBuffer -> IsBusy)
		{
			if(!CheckIO(ReadBuffer -> SerialRequest))
				AbortIO(ReadBuffer -> SerialRequest);

			WaitIO(ReadBuffer -> SerialRequest);

			ReadBuffer -> IsBusy		= FALSE;
			ReadBuffer -> SerialFilled	= ReadBuffer -> SerialRequest -> IOSer . io_Actual;
			ReadBuffer -> SerialIndex	= ReadBuffer -> SerialBuffer;

			ReadBuffer -> SerialRequest -> IOSer . io_Command = SDCMD_QUERY;

			DoIO(ReadBuffer -> SerialRequest);

			if(ReadBuffer -> SerialRequest -> IOSer . io_Actual)
			{
				LONG Size = ReadBuffer -> SerialSize - ReadBuffer -> SerialFilled;

				if(Size > 0)
				{
					if(Size > ReadBuffer -> SerialRequest -> IOSer . io_Actual)
						Size = ReadBuffer -> SerialRequest -> IOSer . io_Actual;

					ReadBuffer -> SerialRequest -> IOSer . io_Command	= CMD_READ;
					ReadBuffer -> SerialRequest -> IOSer . io_Data		= ReadBuffer -> SerialBuffer + ReadBuffer -> SerialFilled;
					ReadBuffer -> SerialRequest -> IOSer . io_Length	= Size;

					DoIO(ReadBuffer -> SerialRequest);

					ReadBuffer -> SerialFilled += ReadBuffer -> SerialRequest -> IOSer . io_Actual;
				}
			}
		}

		CloseDevice(ReadBuffer -> SerialRequest);

		TimeRequest -> tr_node . io_Command	= TR_ADDREQUEST;
		TimeRequest -> tr_time . tv_secs	= 1;
		TimeRequest -> tr_time . tv_micro	= 0;

		DoIO(TimeRequest);

		if(OpenDevice(device,port,ReadBuffer -> SerialRequest,NULL))
		{
			CloseAll();

			exit(10);
		}
		else
		{
			ReadBuffer -> SerialRequest -> io_Baud		= ThisBuffer -> SerialRequest -> io_Baud;
			ReadBuffer -> SerialRequest -> io_ReadLen	= ThisBuffer -> SerialRequest -> io_ReadLen;
			ReadBuffer -> SerialRequest -> io_WriteLen	= ThisBuffer -> SerialRequest -> io_WriteLen;
			ReadBuffer -> SerialRequest -> io_SerFlags	= ThisBuffer -> SerialRequest -> io_SerFlags;

			ReadBuffer -> SerialRequest -> IOSer . io_Command = SDCMD_SETPARAMS;

			DoIO(ReadBuffer -> SerialRequest);

			CopyMem(ReadBuffer -> SerialRequest,ThisBuffer -> SerialRequest,sizeof(struct IOExtSer));

			ThisBuffer -> SerialRequest -> IOSer . io_Message . mn_ReplyPort = WritePort;

			CopyMem(ReadBuffer -> SerialRequest,NextBuffer -> SerialRequest,sizeof(struct IOExtSer));

			NextBuffer -> SerialRequest -> IOSer . io_Message . mn_ReplyPort = WritePort;
		}
	}
}

	/* com_flow(byte flags):
	 *
	 *	The bit mask `flags' determines the style(s) of
	 *	handshaking:
	 *
	 *	if (flags & 9) -> enable xON/xOFF software handshaking,
	 *	if (flags & 2) -> enable RTS/CTS hardware handshaking
	 */

VOID
com_flow(byte flags)
{
	if(ThisBuffer -> IsBusy)
	{
		WaitIO(ThisBuffer -> SerialRequest);

		ThisBuffer -> IsBusy		= FALSE;
		ThisBuffer -> SerialIndex	= ThisBuffer -> SerialBuffer;
	}

	if(ReadBuffer -> IsBusy)
	{
		if(!CheckIO(ReadBuffer -> SerialRequest))
			AbortIO(ReadBuffer -> SerialRequest);

		WaitIO(ReadBuffer -> SerialRequest);

		ReadBuffer -> IsBusy		= FALSE;
		ReadBuffer -> SerialFilled	= ReadBuffer -> SerialRequest -> IOSer . io_Actual;
		ReadBuffer -> SerialIndex	= ReadBuffer -> SerialBuffer;

		ReadBuffer -> SerialRequest -> IOSer . io_Command = SDCMD_QUERY;

		DoIO(ReadBuffer -> SerialRequest);

		if(ReadBuffer -> SerialRequest -> IOSer . io_Actual)
		{
			LONG Size = ReadBuffer -> SerialSize - ReadBuffer -> SerialFilled;

			if(Size > 0)
			{
				if(Size > ReadBuffer -> SerialRequest -> IOSer . io_Actual)
					Size = ReadBuffer -> SerialRequest -> IOSer . io_Actual;

				ReadBuffer -> SerialRequest -> IOSer . io_Command	= CMD_READ;
				ReadBuffer -> SerialRequest -> IOSer . io_Data		= ReadBuffer -> SerialBuffer + ReadBuffer -> SerialFilled;
				ReadBuffer -> SerialRequest -> IOSer . io_Length	= Size;

				DoIO(ReadBuffer -> SerialRequest);

				ReadBuffer -> SerialFilled += ReadBuffer -> SerialRequest -> IOSer . io_Actual;
			}
		}
	}

	if(flags & 2)
		ReadBuffer -> SerialRequest -> io_SerFlags |= SERF_7WIRE;
	else
		ReadBuffer -> SerialRequest -> io_SerFlags &= ~SERF_7WIRE;

	if(flags & 9)
		ReadBuffer -> SerialRequest -> io_SerFlags &= ~SERF_XDISABLED;
	else
		ReadBuffer -> SerialRequest -> io_SerFlags |= SERF_XDISABLED;

	ReadBuffer -> SerialRequest -> IOSer . io_Command = SDCMD_SETPARAMS;

	DoIO(ReadBuffer -> SerialRequest);

	ThisBuffer -> SerialRequest -> io_SerFlags = ReadBuffer -> SerialRequest -> io_SerFlags;
	NextBuffer -> SerialRequest -> io_SerFlags = ReadBuffer -> SerialRequest -> io_SerFlags;
}

	/* com_setspeed(word speed):
	 *
	 *	Set the transfer speed (in bits/second).
	 */

VOID
com_setspeed(word speed)
{
	if(ThisBuffer -> IsBusy)
	{
		WaitIO(ThisBuffer -> SerialRequest);

		ThisBuffer -> IsBusy		= FALSE;
		ThisBuffer -> SerialIndex	= ThisBuffer -> SerialBuffer;
	}

	if(ReadBuffer -> IsBusy)
	{
		if(!CheckIO(ReadBuffer -> SerialRequest))
			AbortIO(ReadBuffer -> SerialRequest);

		WaitIO(ReadBuffer -> SerialRequest);

		ReadBuffer -> IsBusy		= FALSE;
		ReadBuffer -> SerialFilled	= ReadBuffer -> SerialRequest -> IOSer . io_Actual;
		ReadBuffer -> SerialIndex	= ReadBuffer -> SerialBuffer;

		ReadBuffer -> SerialRequest -> IOSer . io_Command = SDCMD_QUERY;

		DoIO(ReadBuffer -> SerialRequest);

		if(ReadBuffer -> SerialRequest -> IOSer . io_Actual)
		{
			LONG Size = ReadBuffer -> SerialSize - ReadBuffer -> SerialFilled;

			if(Size > 0)
			{
				if(Size > ReadBuffer -> SerialRequest -> IOSer . io_Actual)
					Size = ReadBuffer -> SerialRequest -> IOSer . io_Actual;

				ReadBuffer -> SerialRequest -> IOSer . io_Command	= CMD_READ;
				ReadBuffer -> SerialRequest -> IOSer . io_Data		= ReadBuffer -> SerialBuffer + ReadBuffer -> SerialFilled;
				ReadBuffer -> SerialRequest -> IOSer . io_Length	= Size;

				DoIO(ReadBuffer -> SerialRequest);

				ReadBuffer -> SerialFilled += ReadBuffer -> SerialRequest -> IOSer . io_Actual;
			}
		}
	}

	ReadBuffer -> SerialRequest -> io_Baud = speed;

	if(parity)
	{
		ReadBuffer -> SerialRequest -> io_ReadLen = ReadBuffer -> SerialRequest -> io_WriteLen = 7;
		ReadBuffer -> SerialRequest -> io_SerFlags |= SERF_PARTY_ON;
	}
	else
	{
		ReadBuffer -> SerialRequest -> io_ReadLen = ReadBuffer -> SerialRequest -> io_WriteLen = 8;
		ReadBuffer -> SerialRequest -> io_SerFlags &= ~SERF_PARTY_ON;
	}

	ReadBuffer -> SerialRequest -> IOSer . io_Command = SDCMD_SETPARAMS;

	DoIO(ReadBuffer -> SerialRequest);

	ThisBuffer -> SerialRequest -> io_Baud		= ReadBuffer -> SerialRequest -> io_Baud;
	ThisBuffer -> SerialRequest -> io_ReadLen	= ReadBuffer -> SerialRequest -> io_ReadLen;
	ThisBuffer -> SerialRequest -> io_WriteLen	= ReadBuffer -> SerialRequest -> io_WriteLen;
	ThisBuffer -> SerialRequest -> io_SerFlags	= ReadBuffer -> SerialRequest -> io_SerFlags;

	NextBuffer -> SerialRequest -> io_Baud		= ReadBuffer -> SerialRequest -> io_Baud;
	NextBuffer -> SerialRequest -> io_ReadLen	= ReadBuffer -> SerialRequest -> io_ReadLen;
	NextBuffer -> SerialRequest -> io_WriteLen	= ReadBuffer -> SerialRequest -> io_WriteLen;
	NextBuffer -> SerialRequest -> io_SerFlags	= ReadBuffer -> SerialRequest -> io_SerFlags;
}

	/* com_putblock(byte *s,word len):
	 *
	 *	Send a data block asynchronously.
	 */

VOID
com_putblock(byte *s,word len)
{
	struct SerialBuffer *Swap = ThisBuffer;

	if(ThisBuffer -> IsBusy)
		WaitIO(ThisBuffer -> SerialRequest);
	else
	{
		if(ThisBuffer -> SerialIndex > ThisBuffer -> SerialBuffer)
		{
			ThisBuffer -> SerialRequest -> IOSer . io_Command	= CMD_WRITE;
			ThisBuffer -> SerialRequest -> IOSer . io_Data		= ThisBuffer -> SerialBuffer;
			ThisBuffer -> SerialRequest -> IOSer . io_Length	= ThisBuffer -> SerialIndex - ThisBuffer -> SerialBuffer;

			DoIO(ThisBuffer -> SerialRequest);
		}
	}

	ThisBuffer -> IsBusy		= FALSE;
	ThisBuffer -> SerialIndex	= ThisBuffer -> SerialBuffer;

	ThisBuffer = NextBuffer;
	NextBuffer = Swap;

	if(ThisBuffer -> SerialIndex > ThisBuffer -> SerialBuffer)
	{
		ThisBuffer -> SerialRequest -> IOSer . io_Command	= CMD_WRITE;
		ThisBuffer -> SerialRequest -> IOSer . io_Data		= ThisBuffer -> SerialBuffer;
		ThisBuffer -> SerialRequest -> IOSer . io_Length	= ThisBuffer -> SerialIndex - ThisBuffer -> SerialBuffer;

		DoIO(ThisBuffer -> SerialRequest);
	}

	ThisBuffer -> IsBusy		= FALSE;
	ThisBuffer -> SerialIndex	= ThisBuffer -> SerialBuffer;

	while(len > 2 * ThisBuffer -> SerialSize)
	{
		ThisBuffer -> SerialRequest -> IOSer . io_Command	= CMD_WRITE;
		ThisBuffer -> SerialRequest -> IOSer . io_Data		= s;
		ThisBuffer -> SerialRequest -> IOSer . io_Length	= ThisBuffer -> SerialSize;

		s	+= ThisBuffer -> SerialSize;
		len	-= ThisBuffer -> SerialSize;

		DoIO(ThisBuffer -> SerialRequest);
	}

	CopyMem(s,ThisBuffer -> SerialBuffer,MIN(len,ThisBuffer -> SerialSize));

	ThisBuffer -> IsBusy					= TRUE;
	ThisBuffer -> SerialIndex				= ThisBuffer -> SerialBuffer + MIN(len,ThisBuffer -> SerialSize);
	ThisBuffer -> SerialRequest -> IOSer . io_Command	= CMD_WRITE;
	ThisBuffer -> SerialRequest -> IOSer . io_Data		= ThisBuffer -> SerialBuffer;
	ThisBuffer -> SerialRequest -> IOSer . io_Length	= MIN(len,ThisBuffer -> SerialSize);

	len	-= ThisBuffer -> SerialRequest -> IOSer . io_Length;
	s	+= ThisBuffer -> SerialRequest -> IOSer . io_Length;

	ClrSignal(SIG_SERWRITE);
	SendIO(ThisBuffer -> SerialRequest);

	if(len > 0)
	{
		CopyMem(s,NextBuffer -> SerialBuffer,len);

		NextBuffer -> SerialIndex = NextBuffer -> SerialBuffer + len;
	}
}

	/* breakfunc():
	 *
	 *	Cleanup routine for SAS/C.
	 */

static int
breakfunc(void)
{
	CloseAll();

	return(1);
}

	/* sys_init(VOID):
	 *
	 *	Initialize this driver implementation.
	 */

VOID
sys_init(VOID)
{
	if(!OpenAll(device,port))
	{
		CloseAll();

		endprog(2);
	}

	onbreak(breakfunc);
}

	/* sys_reset(VOID):
	 *
	 *	Perform cleanup for this driver implementation.
	 */

VOID
sys_reset(VOID)
{
	CloseAll();
}

	/* sys_idle(VOID):
	 *
	 *	This routine gets called when the system is idle.
	 *	That's a nice one. We are supposed to call the
	 *	system scheduler, etc.
	 */

VOID
sys_idle(VOID)
{
	ULONG Signals;

	if(ReadBuffer -> SerialFilled <= 0 && !ReadBuffer -> IsBusy)
	{
		ReadBuffer -> IsBusy					= TRUE;
		ReadBuffer -> SerialIndex				= ReadBuffer -> SerialBuffer;
		ReadBuffer -> SerialRequest -> IOSer . io_Command	= CMD_READ;
		ReadBuffer -> SerialRequest -> IOSer . io_Data		= ReadBuffer -> SerialBuffer;
		ReadBuffer -> SerialRequest -> IOSer . io_Length	= 1;

		ClrSignal(SIG_SERREAD);
		SendIO(ReadBuffer -> SerialRequest);
	}

	TimeRequest -> tr_node . io_Command	= TR_ADDREQUEST;
	TimeRequest -> tr_time . tv_secs	= 1;
	TimeRequest -> tr_time . tv_micro	= 0;

	ClrSignal(SIG_TIMER);
	SendIO(TimeRequest);

	Signals = Wait(SIG_SERREAD | SIG_SERWRITE | SIG_CONREAD | SIG_WINDOW | SIG_TIMER);

	if(!(Signals & SIG_TIMER))
	{
		if(!CheckIO(TimeRequest))
			AbortIO(TimeRequest);
	}

	WaitIO(TimeRequest);

	if(Signals & SIG_SERREAD)
	{
		WaitIO(ReadBuffer -> SerialRequest);

		ReadBuffer -> IsBusy		= FALSE;
		ReadBuffer -> SerialFilled	= ReadBuffer -> SerialRequest -> IOSer . io_Actual;
		ReadBuffer -> SerialIndex	= ReadBuffer -> SerialBuffer;

		ReadBuffer -> SerialRequest -> IOSer . io_Command = SDCMD_QUERY;

		DoIO(ReadBuffer -> SerialRequest);

		if(ReadBuffer -> SerialRequest -> IOSer . io_Actual)
		{
			LONG Size = ReadBuffer -> SerialSize - ReadBuffer -> SerialFilled;

			if(Size > 0)
			{
				if(Size > ReadBuffer -> SerialRequest -> IOSer . io_Actual)
					Size = ReadBuffer -> SerialRequest -> IOSer . io_Actual;

				ReadBuffer -> SerialRequest -> IOSer . io_Command	= CMD_READ;
				ReadBuffer -> SerialRequest -> IOSer . io_Data		= ReadBuffer -> SerialBuffer + ReadBuffer -> SerialFilled;
				ReadBuffer -> SerialRequest -> IOSer . io_Length	= Size;

				DoIO(ReadBuffer -> SerialRequest);

				ReadBuffer -> SerialFilled += ReadBuffer -> SerialRequest -> IOSer . io_Actual;
			}
		}
	}

	if(Signals & SIG_SERWRITE)
	{
		struct SerialBuffer *Swap = ThisBuffer;

		WaitIO(ThisBuffer -> SerialRequest);

		ThisBuffer -> IsBusy		= FALSE;
		ThisBuffer -> SerialIndex	= ThisBuffer -> SerialBuffer;

		ThisBuffer = NextBuffer;
		NextBuffer = Swap;
	}

	if(Signals & SIG_CONREAD)
	{
		WaitIO(ConsoleReadRequest);

		ConsoleReady = TRUE;
	}

	if(Signals & SIG_WINDOW)
		WindowReady = TRUE;
}

	/* com_outfull(VOID):
	 *
	 *	Return number of bytes still to be transferred.
	 */

int
com_outfull(VOID)
{
	return(ThisBuffer -> SerialIndex - ThisBuffer -> SerialBuffer + NextBuffer -> SerialIndex - NextBuffer -> SerialBuffer);
}

	/* carrier(VOID):
	 *
	 *	Return current carrier status.
	 */

int
carrier(VOID)
{
	if(nocarrier)
		return(1);
	else
	{
		if(!ThisBuffer -> IsBusy)
		{
			ThisBuffer -> SerialRequest -> IOSer . io_Command = SDCMD_QUERY;

			DoIO(ThisBuffer -> SerialRequest);

			if(ThisBuffer -> SerialRequest -> io_Status & CIAF_COMCD)
				return(0);
			else
				return(1);
		}
		else
		{
			NextBuffer -> SerialRequest -> IOSer . io_Command = SDCMD_QUERY;

			DoIO(NextBuffer -> SerialRequest);

			if(NextBuffer -> SerialRequest -> io_Status & CIAF_COMCD)
				return(0);
			else
				return(1);
		}
	}
}

	/* com_flush(VOID):
	 *
	 *	Make sure all pending data gets written.
	 */

VOID
com_flush(VOID)
{
	if(ThisBuffer -> IsBusy)
	{
		WaitIO(ThisBuffer -> SerialRequest);

		ThisBuffer -> IsBusy		= FALSE;
		ThisBuffer -> SerialIndex	= ThisBuffer -> SerialBuffer;
	}

	if(NextBuffer -> SerialIndex > NextBuffer -> SerialBuffer)
	{
		NextBuffer -> SerialRequest -> IOSer . io_Command	= CMD_WRITE;
		NextBuffer -> SerialRequest -> IOSer . io_Data		= NextBuffer -> SerialBuffer;
		NextBuffer -> SerialRequest -> IOSer . io_Length	= NextBuffer -> SerialIndex - NextBuffer -> SerialBuffer;

		DoIO(NextBuffer -> SerialRequest);

		NextBuffer -> SerialIndex = NextBuffer -> SerialBuffer;
	}
}

	/* com_putbyte(byte c):
	 *
	 *	Transmit a single byte, queueing it if necessary.
	 */

VOID
com_putbyte(byte c)
{
	if(ThisBuffer -> IsBusy)
	{
		if(NextBuffer -> SerialIndex + 1 >= NextBuffer -> SerialTop)
		{
			struct SerialBuffer *Swap = ThisBuffer;

			WaitIO(ThisBuffer -> SerialRequest);

			ThisBuffer -> IsBusy		= FALSE;
			ThisBuffer -> SerialIndex	= ThisBuffer -> SerialBuffer;

			ThisBuffer = NextBuffer;
			NextBuffer = Swap;

			ThisBuffer -> IsBusy					= TRUE;
			ThisBuffer -> SerialRequest -> IOSer . io_Command	= CMD_WRITE;
			ThisBuffer -> SerialRequest -> IOSer . io_Data		= ThisBuffer -> SerialBuffer;
			ThisBuffer -> SerialRequest -> IOSer . io_Length	= ThisBuffer -> SerialIndex - ThisBuffer -> SerialBuffer;

			ClrSignal(SIG_SERWRITE);
			SendIO(ThisBuffer -> SerialRequest);
		}

		*NextBuffer -> SerialIndex++ = c;
	}
	else
	{
		if(ThisBuffer -> SerialIndex + 1 < ThisBuffer -> SerialTop)
		{
			*ThisBuffer -> SerialIndex++ = c;

			ThisBuffer -> IsBusy					= TRUE;
			ThisBuffer -> SerialRequest -> IOSer . io_Command	= CMD_WRITE;
			ThisBuffer -> SerialRequest -> IOSer . io_Data		= ThisBuffer -> SerialBuffer;
			ThisBuffer -> SerialRequest -> IOSer . io_Length	= 1;

			ClrSignal(SIG_SERWRITE);
			SendIO(ThisBuffer -> SerialRequest);
		}
		else
		{
			ThisBuffer -> IsBusy					= TRUE;
			ThisBuffer -> SerialRequest -> IOSer . io_Command	= CMD_WRITE;
			ThisBuffer -> SerialRequest -> IOSer . io_Data		= ThisBuffer -> SerialBuffer;
			ThisBuffer -> SerialRequest -> IOSer . io_Length	= ThisBuffer -> SerialIndex - ThisBuffer -> SerialBuffer;

			ClrSignal(SIG_SERWRITE);
			SendIO(ThisBuffer -> SerialRequest);

			*NextBuffer -> SerialIndex++ = c;
		}
	}
}

	/* com_purge(VOID):
	 *
	 *	Clear the read/write buffers.
	 */

VOID
com_purge(VOID)
{
	if(ThisBuffer -> IsBusy)
	{
		if(!CheckIO(ThisBuffer -> SerialRequest))
			AbortIO(ThisBuffer -> SerialRequest);

		WaitIO(ThisBuffer -> SerialRequest);
	}

	ThisBuffer -> IsBusy		= FALSE;
	ThisBuffer -> SerialIndex	= ThisBuffer -> SerialBuffer;

	NextBuffer -> IsBusy		= FALSE;
	NextBuffer -> SerialIndex	= NextBuffer -> SerialBuffer;

	if(ReadBuffer -> IsBusy)
	{
		if(!CheckIO(ReadBuffer -> SerialRequest))
			AbortIO(ReadBuffer -> SerialRequest);

		WaitIO(ReadBuffer -> SerialRequest);
	}

	ReadBuffer -> IsBusy		= FALSE;
	ReadBuffer -> SerialIndex	= ReadBuffer -> SerialBuffer;
	ReadBuffer -> SerialFilled	= 0;

	ThisBuffer -> SerialRequest -> IOSer . io_Command = CMD_CLEAR;
	DoIO(ThisBuffer -> SerialRequest);
}

	/* com_dump(VOID):
	 *
	 *	Wait for asynchronous write request to terminate.
	 */

VOID
com_dump(VOID)
{
	com_flush();
}

	/* com_getbyte(VOID):
	 *
	 *	Read a single byte from the serial line. If not available,
	 *	return EOF.
	 */

int
com_getbyte(VOID)
{
	int Result;

	if(ReadBuffer -> SerialFilled <= 0)
	{
		if(ReadBuffer -> IsBusy)
		{
			if(!CheckIO(ReadBuffer -> SerialRequest))
				return(EOF);
			else
				WaitIO(ReadBuffer -> SerialRequest);

			ReadBuffer -> IsBusy		= FALSE;
			ReadBuffer -> SerialFilled	= ReadBuffer -> SerialRequest -> IOSer . io_Actual;
			ReadBuffer -> SerialIndex	= ReadBuffer -> SerialBuffer;

			ReadBuffer -> SerialRequest -> IOSer . io_Command = SDCMD_QUERY;

			DoIO(ReadBuffer -> SerialRequest);

			if(ReadBuffer -> SerialRequest -> IOSer . io_Actual)
			{
				LONG Size = ReadBuffer -> SerialSize - ReadBuffer -> SerialFilled;

				if(Size > 0)
				{
					if(Size > ReadBuffer -> SerialRequest -> IOSer . io_Actual)
						Size = ReadBuffer -> SerialRequest -> IOSer . io_Actual;

					ReadBuffer -> SerialRequest -> IOSer . io_Command	= CMD_READ;
					ReadBuffer -> SerialRequest -> IOSer . io_Data		= ReadBuffer -> SerialBuffer + ReadBuffer -> SerialFilled;
					ReadBuffer -> SerialRequest -> IOSer . io_Length	= Size;

					DoIO(ReadBuffer -> SerialRequest);

					ReadBuffer -> SerialFilled += ReadBuffer -> SerialRequest -> IOSer . io_Actual;
				}
			}
		}
		else
		{
			ReadBuffer -> SerialRequest -> IOSer . io_Command = SDCMD_QUERY;

			DoIO(ReadBuffer -> SerialRequest);

			if(!ReadBuffer -> SerialRequest -> IOSer . io_Actual)
				return(EOF);
			else
			{
				ReadBuffer -> SerialRequest -> IOSer . io_Command	= CMD_READ;
				ReadBuffer -> SerialRequest -> IOSer . io_Data		= ReadBuffer -> SerialBuffer;
				ReadBuffer -> SerialRequest -> IOSer . io_Length	= MIN(ReadBuffer -> SerialRequest -> IOSer . io_Actual,ReadBuffer -> SerialSize);

				DoIO(ReadBuffer -> SerialRequest);

				if(!ReadBuffer -> SerialRequest -> IOSer . io_Actual)
					return(EOF);
				else
				{
					ReadBuffer -> SerialFilled	= ReadBuffer -> SerialRequest -> IOSer . io_Actual;
					ReadBuffer -> SerialIndex	= ReadBuffer -> SerialBuffer;
				}
			}
		}
	}

	Result = *ReadBuffer -> SerialIndex++;

	ReadBuffer -> SerialFilled--;

	if(ReadBuffer -> SerialFilled <= 0)
	{
		ReadBuffer -> IsBusy					= TRUE;
		ReadBuffer -> SerialIndex				= ReadBuffer -> SerialBuffer;

		ReadBuffer -> SerialRequest -> IOSer . io_Command	= CMD_READ;
		ReadBuffer -> SerialRequest -> IOSer . io_Data		= ReadBuffer -> SerialBuffer;
		ReadBuffer -> SerialRequest -> IOSer . io_Length	= 1;

		ClrSignal(SIG_SERREAD);
		SendIO(ReadBuffer -> SerialRequest);
	}

	return(Result);
}

	/* setstamp(STRPTR Name,LONG Time):
	 *
	 *	Set time/date of a file.
	 */

VOID
setstamp(char *Name,long Time)
{
	struct DateStamp	Date;
	ULONG			Seconds;

	DB(kprintf("setstamp |%s| %ld\n",Name,Time));

		/* Translate it into an Amiga date. */

	if(Time > GMT_Offset)
		Seconds = Time - GMT_Offset;
	else
		Seconds = 0;

	Date . ds_Days		= Seconds / (24 * 60 * 60);
	Date . ds_Minute	= (Seconds % (24 * 60 * 60)) / 60;
	Date . ds_Tick		= (Seconds % 60) * TICKS_PER_SECOND;

	SetFileDate(Name,&Date);
}

	/* freespace(STRPTR DrivePath):
	 *
	 *	Get free disk space for specified drive.
	 */

long
freespace(char *DrivePath)
{
	struct DevProc	*DevProc = GetDeviceProc(DrivePath,NULL);
	struct DosList	*DosList;
	BOOL		 GoodDevice = FALSE;
	LONG		 Size = (LONG)((ULONG)~0 >> 2);

	if(!DrivePath)
		DrivePath = "";

	DB(kprintf("freespace |%s|\n",DrivePath));

	if(DosList = LockDosList(LDF_DEVICES | LDF_READ))
	{
		while(DosList = NextDosEntry(DosList,LDF_DEVICES))
		{
			if(DosList -> dol_Task == DevProc -> dvp_Port)
			{
				struct FileSysStartupMsg *FSSM = (struct FileSysStartupMsg *)BADDR(DosList -> dol_misc . dol_handler . dol_Startup);

				if(TypeOfMem(FSSM))
				{
					struct DosEnvec *DosEnvec = (struct DosEnvec *)BADDR(FSSM -> fssm_Environ);
					STRPTR Name = (STRPTR)BADDR(FSSM -> fssm_Device);

					if(TypeOfMem(DosEnvec) && TypeOfMem(Name))
					{
						if(Name[0] > 0 && !Name[(WORD)Name[0] + 1])
						{
							struct IOStdReq __aligned IORequest;

							if(!OpenDevice(Name + 1,FSSM -> fssm_Unit,&IORequest,FSSM -> fssm_Unit))
							{
								CloseDevice(&IORequest);

								if(DosEnvec -> de_TableSize > 0 && DosEnvec -> de_LowCyl <= DosEnvec -> de_HighCyl)
									GoodDevice = TRUE;
							}
						}
					}
				}
			}
		}

		UnLockDosList(LDF_DEVICES | LDF_READ);
	}

	FreeDeviceProc(DevProc);

	if(GoodDevice)
	{
		struct InfoData *InfoData;

		if(InfoData = (struct InfoData *)AllocVec(sizeof(struct InfoData),MEMF_ANY | MEMF_PUBLIC))
		{
			UBYTE NewName[256],*Index;
			BPTR FileLock;

			memcpy(NewName,DrivePath,255);

			NewName[255] = 0;

			Index = PathPart(NewName);

			*Index = 0;

			FileLock = Lock(NewName,ACCESS_READ);

			if(FileLock)
			{
				if(Info(FileLock,InfoData))
					Size = InfoData -> id_BytesPerBlock * (InfoData -> id_NumBlocks - InfoData -> id_NumBlocksUsed);

				UnLock(FileLock);
			}

			FreeVec(InfoData);
		}
	}

	return(Size);
}

	/* ffirst(char *FileSpec):
	 *
	 *	Return name of first file matching the given specs.
	 */

char *
ffirst(char *filespec)
{
	AnchorUsed = TRUE;

	DB(kprintf("ffirst |%s|\n",filespec));

	if(MatchFirst(filespec,Anchor))
		return(NULL);
	else
		return((char *)Anchor -> ap_Buf);
}

	/* fnext(VOID):
	 *
	 *	Return name of next file matching the given specs.
	 */

char *
fnext(VOID)
{
	AnchorUsed = TRUE;

	DB(kprintf("fnext\n"));

	if(MatchNext(Anchor))
		return(NULL);
	else
		return((char *)Anchor -> ap_Buf);
}

	/* time(time_t *timeptr):
	 *
	 *	Get the current time.
	 */

time_t
time(time_t *timeptr)
{
	struct timeval	Now;
	time_t		CurrentTime;

	DB(kprintf("time 0x%08lx\n",timeptr));

		/* If the timer is already available,
		 * just read the time. Otherwise, open what
		 * we need to tell the time.
		 */

	if(TimerBase)
		GetSysTime(&Now);
	else
		UpdateTime(&Now);

		/* Determine the current time, taking the time
		 * zone into account.
		 */

	CurrentTime = (time_t)(Now . tv_secs + GMT_Offset);

	if(timeptr)
		*timeptr = CurrentTime;

	return(CurrentTime);
}

	/* localtime(const time_t *t):
	 *
	 *	Convert UTC into local time.
	 */

struct tm *
localtime(const time_t *t)
{
	STATIC struct tm Time;

	ULONG			Seconds,
				Delta;
	struct ClockData	ClockData;
	BOOL			CloseIt = FALSE;

	DB(kprintf("localtime 0x%08lx\n",t));

		/* We need utility.library for the date conversion. */

	if(!UtilityBase)
	{
		if(UtilityBase = OpenLibrary("utility.library",37))
			CloseIt = TRUE;
	}

		/* Any luck? */

	if(!UtilityBase)
	{
		memset(&Time,0,sizeof(struct tm));

		return(&Time);
	}

		/* Update the time data. */

	if(!TimerBase)
		UpdateTime(NULL);

		/* Add the offset. */

	if(*t < GMT_Offset)
		Seconds = 0;
	else
		Seconds = (ULONG)*t - GMT_Offset;

		/* Convert the seconds into time data. */

	Amiga2Date(Seconds,&ClockData);

		/* Convert the time data. */

	Time . tm_sec	= ClockData . sec;
	Time . tm_min	= ClockData . min;
	Time . tm_hour	= ClockData . hour;
	Time . tm_mday	= ClockData . mday;
	Time . tm_mon	= ClockData . month - 1;
	Time . tm_year	= ClockData . year - 1900;
	Time . tm_wday	= ClockData . wday;

		/* No daylight savings time info is provided. */

	Time . tm_isdst = 0;

		/* We will need to fill in the yday entry. */

	ClockData . mday	= 1;
	ClockData . month	= 1;

	Delta = Date2Amiga(&ClockData);

	Time . tm_yday = (Seconds - Delta) / (24 * 60 * 60) + 1;

		/* Clean up if necessary. */

	if(CloseIt)
	{
		CloseLibrary(UtilityBase);

		UtilityBase = NULL;
	}

	return(&Time);
}

	/* stat(const char *file,struct stat *st):
	 *
	 *	Get information on a file.
	 */

int
stat(const char *file,struct stat *st)
{
	BPTR FileLock;

	DB(kprintf("stat |%s| 0%08lx\n",file,st));

		/* Try to get a lock on the file. */

	if(FileLock = Lock((STRPTR)file,ACCESS_READ))
	{
		STATIC char	Volume[256],
				Comment[80];

		struct FileInfoBlock __aligned	FileInfo;
		struct InfoData __aligned	InfoData;

			/* Get information on file and filing system. */

		if(Examine(FileLock,&FileInfo) && Info(FileLock,&InfoData))
		{
			unsigned short mode = 0;

				/* Try to get the name of the volume. */

			if(!NameFromLock(FileLock,Volume,256))
				Volume[0] = 0;
			else
			{
				WORD i;

					/* Chop off everything after the colon. */

				for(i = 0 ; i < 256 ; i++)
				{
					if(Volume[i] == ':')
					{
						Volume[i + 1] = 0;

						break;
					}
				}
			}

			UnLock(FileLock);

				/* Build the protection bits. */

			if(!(FileInfo . fib_Protection & FIBF_EXECUTE))
				mode |= S_IEXECUTE;

			if(!(FileInfo . fib_Protection & FIBF_DELETE))
				mode |= S_IDELETE;

			if(!(FileInfo . fib_Protection & FIBF_READ))
				mode |= S_IREAD;

			if(!(FileInfo . fib_Protection & FIBF_WRITE))
				mode |= S_IWRITE;

			if(!(FileInfo . fib_Protection & FIBF_ARCHIVE))
				mode |= S_IARCHIVE;

			if(FileInfo . fib_Protection & FIBF_PURE)
				mode |= S_IPURE;

			if(FileInfo . fib_Protection & FIBF_SCRIPT)
				mode |= S_ISCRIPT;

				/* Keep the comment. */

			strcpy(Comment,FileInfo . fib_Comment);

				/* Fix the time if necessary. */

			if(!TimerBase)
				UpdateTime(NULL);

				/* Fill in the data. */

			st -> st_mode	= mode;
			st -> st_ino	= FileInfo . fib_DiskKey;
			st -> st_dev	= InfoData . id_DiskType;
			st -> st_rdev	= Volume;
			st -> st_nlink	= FileInfo . fib_DirEntryType == ST_SOFTLINK || FileInfo . fib_DirEntryType == ST_LINKDIR || FileInfo . fib_DirEntryType == ST_LINKFILE;
			st -> st_uid	= FileInfo . fib_OwnerUID;
			st -> st_gid	= FileInfo . fib_OwnerGID;
			st -> st_size	= FileInfo . fib_Size;
			st -> st_atime	= GMT_Offset + (FileInfo . fib_Date . ds_Days * 24 * 60 + FileInfo . fib_Date . ds_Minute) * 60 + FileInfo . fib_Date . ds_Tick / TICKS_PER_SECOND;
			st -> st_mtime	= st -> st_atime;
			st -> st_ctime	= st -> st_atime;
			st -> st_type	= FileInfo . fib_DirEntryType;
			st -> st_comment= Comment;

				/* Success. */

			return(0);
		}

		UnLock(FileLock);
	}

		/* What else should I choose? */

	errno = _OSERR = EIO;

	return(-1);
}
