/*
	FS1541

	packet.c


	This is the packet processor.

*/

#include <string.h>

#include <exec/types.h>
#include <exec/execbase.h>
#include <exec/memory.h>
#include <dos/dosextens.h>
#include <dos/filehandler.h>

#include <proto/exec.h>
#include <proto/utility.h>

#include "main.h"
#include "packet.h"
#include "support.h"
#include "volume.h"
#include "disk.h"

extern UBYTE diskinfo_image[840];

static BOOL getfiledata(UBYTE entry, struct FHArg1 *fharg1);
static BPTR trylockfile(LONG *error, LONG *err2, BSTR name, LONG access, struct FHArg1 *fharg1);

int inhibited = FALSE;

/*-------------------------------------------------------------------------*/

void DoPackets(void)
{
	struct DosPacket *packet;

	while((packet = GetPacket(ourport)))
	{
		LONG error = DOSFALSE;
		LONG err2 = 0;
		BOOL f = TRUE;

		dpsender = packet->dp_Port;

		if(inhibited)
		{
			switch(packet->dp_Type)
			{
				case ACTION_DISK_INFO:
				case ACTION_INHIBIT:
					break;

				default:
					f = FALSE;
					err2 = ERROR_NOT_A_DOS_DISK;
					break;
			}
		}

		if(f) switch(packet->dp_Type)
		{
			case ACTION_LOCATE_OBJECT:
			{
				struct FileLock *fl = BTOC(packet->dp_Arg1);
				struct FHArg1 fharg1;

				if(fl)
				{
					if(fl->fl_Volume != CTOB(curdoslist))
					{
						/* The reference disk is currently not inserted. */
						err2 = ERROR_DEVICE_NOT_MOUNTED;
						break;
					}
					if(fl->fl_Key != 0)
					{
						/* An invalid lock has been passed. */
						err2 = ERROR_OBJECT_WRONG_TYPE;
						break;
					}
				}
				else if(!curvolumenode)
				{
					err2 = disk_inserted ? ERROR_NOT_A_DOS_DISK : ERROR_NO_DISK;
					break;
				}

				/* This FileSystem is easy since we don't have to take
				   care of any FileLock path tracking and resolving */

				trylockfile(&error, &err2, packet->dp_Arg2, packet->dp_Arg3, &fharg1);
				MotorOff();
				break;
			}

			case ACTION_FREE_LOCK:
			{
				struct FileLock *fl = BTOC(packet->dp_Arg1);

				if(fl)
					freelock(fl);

				MotorOff();
				error = DOSTRUE;
				break;
			}

			case ACTION_COPY_DIR:
			{
				struct FileLock *fl = BTOC(packet->dp_Arg1);

				if(fl)
				{
					if(fl->fl_Access == EXCLUSIVE_LOCK)
					{
						/* Don't duplicate exclusive locks */
						err2 = ERROR_OBJECT_IN_USE;
					}
					else
					{
						if(fl->fl_Volume == CTOB(curdoslist))
						{
							/* Try to duplicate it! */
							err2 = ERROR_NO_FREE_STORE;
							error = makelock(fl->fl_Key, SHARED_LOCK);
						}
						else
						{
							/* The reference disk is currently not inserted. */
							err2 = ERROR_DEVICE_NOT_MOUNTED;
						}
					}
				}
				else
				{
					if(curvolumenode)
					{
						/* Return shared lock on root directory */
						err2 = ERROR_NO_FREE_STORE;
						error = makelock(0, SHARED_LOCK);
					}
					else
					{
						err2 = ERROR_NO_DISK;
					}
				}

				break;
			}

			case ACTION_SAME_LOCK:
			{
				struct FileLock *fl1 = BTOC(packet->dp_Arg1);
				struct FileLock *fl2 = BTOC(packet->dp_Arg2);

				err2 = 0;

				if(fl1 == fl2 || ((fl1->fl_Volume == fl2->fl_Volume) && (fl1->fl_Key == fl2->fl_Key)))
					error = DOSTRUE;

				break;
			}

			case ACTION_EXAMINE_OBJECT:
			{
				struct FileLock *fl = BTOC(packet->dp_Arg1);
				struct FileInfoBlock *fib = BTOC(packet->dp_Arg2);

				if(fl && fl->fl_Key != 0)
				{
					/* Examine file */
					struct FHArg1 fharg1;
					int entry;
					int special;

					fib->fib_DiskKey = 0;
					fib->fib_DirEntryType = fib->fib_EntryType = ST_FILE;
					fib->fib_Protection = 0;
					fib->fib_Comment[0] = '\0';
					fib->fib_Date.ds_Days = 0;
					fib->fib_Date.ds_Minute = 0;
					fib->fib_Date.ds_Tick = 0;

					if(fl->fl_Volume != CTOB(curdoslist))
					{
						/* The reference disk is currently not inserted. */
						err2 = ERROR_DEVICE_NOT_MOUNTED;
						break;
					}

					if((special = fl->fl_Key & FLKEY_SPECIALMASK))
					{
						/* Examine virtual files */
						switch(special)
						{
							case FLKEY_DISKINFO:
								strcpy(&fib->fib_FileName[1], VIRTUAL_FILE_DISKINFO);
								fib->fib_FileName[0] = strlen(VIRTUAL_FILE_DISKINFO);
								fib->fib_Size = 840;
								error = DOSTRUE;
								break;

							case FLKEY_D64:
								strcpy(&fib->fib_FileName[1], VIRTUAL_FILE_D64);
								fib->fib_FileName[0] = strlen(VIRTUAL_FILE_D64);
								fib->fib_Size = D64_SIZE;
								error = DOSTRUE;
								break;

							default:
								err2 = ERROR_OBJECT_NOT_FOUND;
								break;
						}
					}
					else
					{
						for(entry=0;entry<dirsize;entry++)
							if(fl->fl_Key == ((directory[entry].datat<<8)|directory[entry].datas))
								break;
	
						if(entry<dirsize)
						{
							if(getfiledata(entry, &fharg1))
							{
								copy64name(&fib->fib_FileName[1], directory[entry].name, 16);
								fib->fib_FileName[0] = strlen(&fib->fib_FileName[1]);
								fib->fib_Size = fharg1.len;
								error = DOSTRUE;
							}
							else err2 = ERROR_OBJECT_NOT_FOUND;
						}
						else err2 = ERROR_OBJECT_NOT_FOUND;
					}
				}
				else
				{
					/* Prepare for EXAMINE_NEXT, return volume name in
					   fib_FileName */
					fib->fib_DiskKey = 0;
					fib->fib_DirEntryType = fib->fib_EntryType = ST_ROOT;
					copy64name(&fib->fib_FileName[1], bam->name, 18);
					fib->fib_FileName[0] = strlen(&fib->fib_FileName[1]);
					fib->fib_Protection = 0;
					fib->fib_Comment[0] = '\0';
					fib->fib_Date.ds_Days = 0;
					fib->fib_Date.ds_Minute = 0;
					fib->fib_Date.ds_Tick = 0;
					error = DOSTRUE;
				}

				break;
			}

			case ACTION_EXAMINE_NEXT:
			{
				struct FileLock *fl = BTOC(packet->dp_Arg1);
				struct FileInfoBlock *fib = BTOC(packet->dp_Arg2);

				if(fl)
				{
					if(fl->fl_Volume != CTOB(curdoslist))
					{
						/* The reference disk is currently not inserted. */
						err2 = ERROR_DEVICE_NOT_MOUNTED;
						break;
					}
					if(fl->fl_Key != 0)
					{
						/* An invalid lock has been passed. */
						err2 = ERROR_OBJECT_WRONG_TYPE;
						break;
					}
				}

				fib->fib_DirEntryType = fib->fib_EntryType = ST_FILE;

				err2 = ERROR_NO_MORE_ENTRIES;

				while(fib->fib_DiskKey < dirsize)
				{
					int entry;
					struct FHArg1 fharg1;

					entry = fib->fib_DiskKey++;

					if(getfiledata(entry, &fharg1))
					{
						copy64name(&fib->fib_FileName[1], directory[entry].name, 16);
						fib->fib_FileName[0] = strlen(&fib->fib_FileName[1]);
						fib->fib_Size = fharg1.len;
						fib->fib_NumBlocks = fharg1.numblocks;
						error = DOSTRUE;
						break;
					}
				}

				if(error == DOSFALSE)
					MotorOff();

				break;
			}

			case ACTION_FINDINPUT:
			{
				struct FileHandle *fh = BTOC(packet->dp_Arg1);
				struct FileLock *fl = BTOC(packet->dp_Arg2);
				struct FHArg1 *fharg1;

				if(fl)
				{
					if(fl->fl_Volume != CTOB(curdoslist))
					{
						/* The reference disk is currently not inserted. */
						err2 = ERROR_DEVICE_NOT_MOUNTED;
						break;
					}
					if(fl->fl_Key != 0)
					{
						/* An invalid lock has been passed. */
						err2 = ERROR_OBJECT_WRONG_TYPE;
						break;
					}
				}

				if((fharg1 = AllocVec(sizeof(struct FHArg1), MEMF_ANY|MEMF_CLEAR)))
				{
					if(trylockfile(&error, &err2, packet->dp_Arg3, SHARED_LOCK, fharg1))
					{
						fh->fh_Arg1 = (LONG)fharg1;
						fh->fh_Port = DOSFALSE;
						error = DOSTRUE;
					}

					MotorOff();
				}
				else err2 = ERROR_NO_FREE_STORE;

				break;
			}

			case ACTION_READ:
			{
				struct FHArg1 *fharg1 = (struct FHArg1 *)packet->dp_Arg1;
				APTR buffer = (APTR)packet->dp_Arg2;
				ULONG togo = packet->dp_Arg3;
				int special;
				struct DataBlock *block = NULL;

				if(((struct FileLock*)BTOC(fharg1->lock))->fl_Volume != CTOB(curdoslist))
				{
					/* The reference disk is currently not inserted. */
					err2 = ERROR_DEVICE_NOT_MOUNTED;
					break;
				}

				if(fharg1->pos + togo >= fharg1->len)
					togo = fharg1->len - fharg1->pos;

				error = 0;

				if((special = ((struct FileLock*)BTOC(fharg1->lock))->fl_Key & FLKEY_SPECIALMASK))
				{
					/* Handle virtual files */
					switch(special)
					{
						case FLKEY_DISKINFO:
							CopyMem(&diskinfo_image[fharg1->pos], buffer, togo);
							fharg1->pos += togo;
							error += togo;
							break;

						case FLKEY_D64:
							while(togo)
							{
								ULONG size, offset;

								if(!(block = getputblock((fharg1->pos)>>8, FALSE)))
									break;

								offset = (fharg1->pos) & 255;

								size = 256 - offset;
								if(togo < size)
									size = togo;

								CopyMem(&((UBYTE*)block)[offset], buffer, size);

								buffer += size;
								fharg1->pos += size;
								error += size;
								togo -= size;
							}
							break;

						default:
							break;
					}
				}
				else
				{
					/* Handle real files */
					while(togo)
					{
						ULONG size;
	
						if(fharg1->p == 254)
						{
							if(!block)
								if(!(block = getblock_ts(fharg1->t, fharg1->s)))
									break;
							fharg1->p = 0;
							fharg1->t = block->t;
							fharg1->s = block->s;
						}
	
						size = 254 - fharg1->p;
						if(togo < size)
							size = togo;
	
						if(!(block = getblock_ts(fharg1->t, fharg1->s)))
							break;
	
						CopyMem(&block->data[fharg1->p], buffer, size);
						buffer += size;
						fharg1->p += size;
						fharg1->pos += size;
						error += size;
						togo -= size;
					}
				}

				break;
			}

			case ACTION_WRITE:
			case ACTION_SET_FILE_SIZE:
				error = -1;
				err2 = ERROR_DISK_WRITE_PROTECTED;
				break;

			case ACTION_SEEK:
			{
				struct FHArg1 fharg1bak, *fharg1 = (struct FHArg1 *)packet->dp_Arg1;
				LONG move = packet->dp_Arg2;

				if(((struct FileLock*)BTOC(fharg1->lock))->fl_Volume != CTOB(curdoslist))
				{
					/* The reference disk is currently not inserted. */
					err2 = ERROR_DEVICE_NOT_MOUNTED;
					break;
				}

				error = fharg1->pos;
				err2 = ERROR_SEEK_ERROR;

				switch(packet->dp_Arg3)
				{
					case OFFSET_BEGINNING:
						move = move - error;
						break;

					case OFFSET_CURRENT:
						break;

					case OFFSET_END:
						move = move + fharg1->len - error;
						break;

					default:
						error = -1;
						break;
				}

				if(error != -1)
				{
					CopyMem(fharg1, &fharg1bak, sizeof(struct FHArg1));

					if((error+move)<0 || (error+move)>fharg1->len)
					{
						error = -1;
					}
					else
					{
						int special;

						if((special = ((struct FileLock*)BTOC(fharg1->lock))->fl_Key & FLKEY_SPECIALMASK))
						{
							fharg1->pos += move;
						}
						else
						{
							if(move<0)
							{
								/* When moving back, we have to rescan the whole file */
								move = move + error;
								fharg1->p = 0;
								fharg1->pos = 0;
								fharg1->t = fharg1->t0;
								fharg1->s = fharg1->s0;
							}
	
							while(move)
							{
								ULONG size;
								struct DataBlock *block;
	
								if(fharg1->p == 254)
								{
									block = getblock_ts(fharg1->t, fharg1->s);

									if(!block)
									{
										CopyMem(&fharg1bak, fharg1, sizeof(struct FHArg1));
										error = -1;
										err2 = ABORT_DISK_ERROR;
										break;
									}

									fharg1->p = 0;
									fharg1->t = block->t;
									fharg1->s = block->s;
								}
	
								size = 254 - fharg1->p;
			
								if(move < size)
									size = move;
			
								fharg1->p += size;
								fharg1->pos += size;
								move -= size;
							}
						}
					}
				}

				break;
			}

			case ACTION_END:
			{
				struct FHArg1 *fharg1 = (struct FHArg1 *)packet->dp_Arg1;

				freelock(BTOC(fharg1->lock));
				FreeVec(fharg1);

				MotorOff();
				error = DOSTRUE;
				break;
			}

			case ACTION_PARENT:
			{
				struct FileLock *fl = BTOC(packet->dp_Arg1);

				if(fl && fl->fl_Volume != CTOB(curdoslist))
				{
					/* The reference disk is currently not inserted. */
					err2 = ERROR_DEVICE_NOT_MOUNTED;
					break;
				}

				if(fl && fl->fl_Key != 0)
				{
					err2 = ERROR_OBJECT_WRONG_TYPE;
					break;
				}

				err2 = 0;

				break;
			}

			case ACTION_IS_FILESYSTEM:
				/* Yes, we are. */
				error = DOSTRUE;
				break;

			case ACTION_CURRENT_VOLUME:
			{
				struct FHArg1 *fharg1 = (struct FHArg1 *)packet->dp_Arg1;

				/* The packet name is somewhat misleading... */
				if(fharg1)
				{
					error = ((struct FileLock*)BTOC(fharg1->lock))->fl_Volume;
				}
				else
				{
					if(curvolumenode)
						error = (LONG)CTOB(curdoslist);
					else
						error = NULL;
				}

				break;
			}

			case ACTION_INFO:
			case ACTION_DISK_INFO:
			{
				struct InfoData *id;

				if(packet->dp_Type == ACTION_INFO)
				{
					struct FileLock *fl = BTOC(packet->dp_Arg1);

					if(fl && fl->fl_Volume != CTOB(curdoslist))
					{
						/* The reference disk is currently not inserted. */
						err2 = ERROR_DEVICE_NOT_MOUNTED;
						break;
					}

					id = BTOC(packet->dp_Arg2);
				}
				else
				{
					id = BTOC(packet->dp_Arg1);
				}

				id->id_NumSoftErrors = numsofterrors;
				id->id_NumBlocks = 683;
				id->id_NumBlocksUsed = UsedBlocks();
				id->id_BytesPerBlock = 254;
				id->id_DiskState = wprotected ? ID_WRITE_PROTECTED : ID_VALIDATED;
				id->id_UnitNumber = fssm->fssm_Unit;

				if(curvolumenode)
				{
					id->id_DiskType = ID_DOS_DISK;
					id->id_VolumeNode = CTOB(curdoslist);
					id->id_InUse = curvolumenode->locklist ? DOSTRUE : DOSFALSE;
				}
				else
				{
					#ifndef ID_BUSY /* My v40 header files don't */
					#define ID_BUSY 0x42555359
					#endif
					id->id_DiskType = inhibited ? ID_BUSY : disk_inserted ? ID_UNREADABLE_DISK : ID_NO_DISK_PRESENT;
				}

				error = DOSTRUE;

				break;
			}

			case ACTION_INHIBIT:
			{
				/* We could have trouble here if the DosList is locked,
				   but then again, one shouldn't lock it for INHIBIT. */

				if(packet->dp_Arg1 == DOSTRUE)
				{
					inhibited = TRUE;

					diskreq->iotd_Req.io_Command = ETD_UPDATE;
					diskreq->iotd_Req.io_Flags = 0;
					DoIO((struct IORequest*)diskreq);

					DoDiskRemove();
				}
				else
				{
					DoDiskInsert();

					inhibited = FALSE;
				}

				error = DOSTRUE;

				break;
			}

			case ACTION_CREATE_DIR:
				err2 = ERROR_INVALID_COMPONENT_NAME;
				break;

			case ACTION_DELETE_OBJECT:
			{
				struct FileLock *fl = BTOC(packet->dp_Arg1);
				struct FHArg1 fharg1;

				if(fl)
				{
					if(fl->fl_Volume != CTOB(curdoslist))
					{
						/* The reference disk is currently not inserted. */
						err2 = ERROR_DEVICE_NOT_MOUNTED;
						break;
					}
					if(fl->fl_Key != 0)
					{
						/* An invalid lock has been passed. */
						err2 = ERROR_OBJECT_WRONG_TYPE;
						break;
					}
				}

				if(trylockfile(&error, &err2, packet->dp_Arg2, EXCLUSIVE_LOCK, &fharg1))
				{
					struct DataBlock *block;
					UBYTE t,s;

					freelock((struct FileLock*)BTOC(fharg1.lock));

					if(fharg1.number != NUM_VIRTUAL)
					{
						/* We do this only for non-virtual files. */
						for(t=fharg1.t,s=fharg1.s; t; t=block->t,s=block->s)
						{
							block = getblock_ts(t,s);
							FreeBlock(t,s);
						}
	
						directory[fharg1.number].type = 0x00;
						UpdateDiskStructure();
					}
					else err2 = ERROR_OBJECT_NOT_FOUND;
				}
				else err2 = ERROR_OBJECT_NOT_FOUND;

				MotorOff();
				break;
			}

			case ACTION_FINDOUTPUT:
			case ACTION_FINDUPDATE:
			case ACTION_RENAME_OBJECT:
			case ACTION_RENAME_DISK:
			case ACTION_FORMAT:
				err2 = ERROR_DISK_WRITE_PROTECTED;
				break;
	
			default:
				err2 = ERROR_ACTION_NOT_KNOWN;
				break;
		}
	
		ReturnPacket(packet, error, err2);
	}
}

static BOOL getfiledata(UBYTE entry, struct FHArg1 *fharg1)
{
	if(directory[entry].name[0] != 0xa0)
	{
		UWORD type = (directory[entry].type) & 0x7;

		if(type!=0x00)	/* anything but DEL-Files */
		{
			struct DataBlock *block;
			int i,t,s,size;

			for(i=0,t=directory[entry].datat,s=directory[entry].datas,size=0;
				i<683; /* a primitive solution to overcome circular links */
				t=block->t,s=block->s,i++)
			{
				if(!(block = getblock_ts(t,s)))
					return(FALSE);

				if(block->t)
					size+=254;
				else
				{
					size+=block->s-1;
					break;
				}
			}

			if(i<683)
			{
				fharg1->len = size;
				fharg1->numblocks = i;
				fharg1->t=directory[entry].datat;
				fharg1->s=directory[entry].datas;
				fharg1->t0 = fharg1->t;
				fharg1->s0 = fharg1->s;
				fharg1->number = entry;
				return(TRUE);
			}
		}
	}

	return(FALSE);
}

/*-------------------------------------------------------------------------*/

static BPTR trylockfile(LONG *error, LONG *err2, BSTR name, LONG access, struct FHArg1 *fharg1)
{
	UBYTE buf[256], fname1[20];
	STRPTR a;

	*error = DOSFALSE;

	/* Convert BSTR argument into C string */
	if((a = BTOC(name)))
	{
		CopyMem(&a[1], buf, a[0]);
		buf[a[0]] = '\0';

		if(strchr(buf, '/'))
		{
			/* User tries to access subdirectories */
			*err2 = ERROR_INVALID_COMPONENT_NAME;
			return(*error);
		}

		a = strchr(buf, ':');
		strncpy(fname1, a ? a+1 : buf, 18);
		fname1[18] = '\0';
	}
	else
		fname1[0] = '\0';

	if(fname1[0] == '\0')
	{
		/* Return lock on root directory */
		if(access == EXCLUSIVE_LOCK)
		{
			*err2 = ERROR_OBJECT_IN_USE;
		}
		else
		{
			*err2 = ERROR_NO_FREE_STORE;
			*error = makelock(0, access);
		}
	}
	else
	{
		char buf[18];
		int entry;

		*err2 = ERROR_OBJECT_NOT_FOUND;

		/* Look up file in directory and lock it */
		for(entry=0;entry<dirsize;entry++)
		{
			copy64name(buf, directory[entry].name, 16);
			if(!Stricmp(buf,fname1))
				break;;
		}

		if(entry == dirsize)
		{
			/* Perhaps we can provide a virtual file */
			fharg1->number = NUM_VIRTUAL;

			if(!Stricmp(VIRTUAL_FILE_DOLLAR,fname1))
			{
				*err2 = ERROR_NO_FREE_STORE;
				*error = fharg1->lock = makelock(FLKEY_DOLLAR, access);
				fharg1->len = 0;
			}
			else if(!Stricmp(VIRTUAL_FILE_D64,fname1))
			{
				*err2 = ERROR_NO_FREE_STORE;
				*error = fharg1->lock = makelock(FLKEY_D64, access);
				fharg1->len = D64_SIZE;
			}
			else if(!Stricmp(VIRTUAL_FILE_DISKINFO,fname1))
			{
				*err2 = ERROR_NO_FREE_STORE;
				*error = fharg1->lock = makelock(FLKEY_DISKINFO, access);
				fharg1->len = 840;
			}
		}
		else
		{
			if(getfiledata(entry, fharg1))
			{
				if(lockable(fharg1->t, fharg1->s, access))
				{
					*err2 = ERROR_NO_FREE_STORE;
					*error = fharg1->lock = makelock((fharg1->t<<8)|fharg1->s, access);
				}
				else *err2 = ERROR_OBJECT_IN_USE;
			}
		}
	}

	return(*error);
}
