/*
 * This source file is Copyright 1995 by Evan Scott.
 * All rights reserved.
 * Permission is granted to distribute this file provided no
 * fees beyond distribution costs are levied.
 */

#include <exec/types.h>
#include <exec/memory.h>
#include <exec/alerts.h>

#include <devices/timer.h>

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

#include <workbench/workbench.h>

#include <proto/exec.h>
#include <proto/dos.h>
#include <proto/intuition.h>
#include <proto/icon.h>
#include <proto/gadtools.h>

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

#include "evtypes.h"
#include "verify.h"
#include "tcp.h"

#include "site.h"
#include "ftp.h"
#include "split.h"
#include "ftpinfo.h"
#include "connect.h"
#include "request.h"

#include "globals.h"
#include "strings.h"

struct MsgPort *get_site(b8 *s)
{
	site *sp;
	struct Process *child;
	struct StandardPacket *std_pkt;
	struct DiskObject *dobj;
	BPTR ocd;
	b8 *tmp;
	
	sp = sites;
	while (sp) {
		if (stricmp(sp->name, s) == 0) return sp->port;
		sp = sp->next;
	}
	
	sp = (site *)allocate(sizeof(*sp) + strlen(s) + 1, V_site);
	if (!sp) return nil;
	
	ensure(sp, V_site);
	
	/* first sort out default login information */
	
	sp->user = nil;
	sp->password = nil;
	
	sp->needs_user = false;
	sp->needs_password = false;
	
	sp->root = nil;
	sp->user = nil;
	sp->password = nil;
	sp->host = nil;
	
	sp->open_status = false;
	sp->quick = true;
	sp->case_sensitive = false;
	sp->all_messages = true;
	sp->error_messages = true;

	sp->port_number = ftp_port_number;
	
	tmp = s;
	while (*tmp && *tmp != '@') tmp++;
	
	if (*tmp == '@') {
		sp->host = (b8 *)allocate(strlen(tmp + 1) + 1, V_cstr);
		if (sp->host) {
			strcpy(sp->host, tmp + 1);
		}
		
		sp->needs_user = true;
		sp->needs_password = true;
		
		if (tmp != s) {
			sp->user = (b8 *)allocate(tmp - s + 1, V_cstr);
			if (sp->user) {
				strncpy(sp->user, s, tmp - s);
				sp->user[tmp - s] = 0;
				sp->needs_user = false;
			}
		}
	} else {
		sp->host = (b8 *)allocate(strlen(s) + 1, V_cstr);
		if (sp->host) {
			strcpy(sp->host, s);
		}
	}
	
	/* now try to get info from icon */
	
	if (IconBase) {
		ocd = CurrentDir(ftphosts_lock);
		dobj = GetDiskObject(s);
		if (!dobj)
		{
			dobj = GetDiskObject(strings[MSG_DEFAULT]);
		}
		CurrentDir(ocd);
	} else {
		dobj = nil;
	}
	
	if (dobj) {
		/*
		 * HOST overrides the "title", whereas USER doesn't ...
		 * is this inconsistent or is it valid?
		 */
		if (!sp->user) {
			tmp = FindToolType(dobj->do_ToolTypes, strings[MSG_USER_TT]);
			if (tmp) {
				sp->user = (b8 *)allocate(strlen(tmp) + 1, V_cstr);
				if (sp->user) {
					strcpy(sp->user, tmp);
					sp->needs_user = false;
				}
			}
		}
		
		tmp = FindToolType(dobj->do_ToolTypes, strings[MSG_PASSWORD_TT]);
		if (tmp) {
			sp->password = (b8 *)allocate(strlen(tmp) + 1, V_cstr);
			if (sp->password) {
				strcpy(sp->password, tmp);
				sp->needs_password = false;
			}
		}
		
		tmp = FindToolType(dobj->do_ToolTypes, strings[MSG_STATUS_TT]);
		if (tmp) {
			if (stricmp(tmp, strings[MSG_OFF]) != 0 &&
					stricmp(tmp, strings[MSG_FALSE]) != 0) {
				sp->open_status = true;
			}
		}

		tmp = FindToolType(dobj->do_ToolTypes, strings[MSG_QUICK_TT]);
		if (tmp) {
			if (stricmp(tmp, strings[MSG_OFF]) == 0 ||
					stricmp(tmp, strings[MSG_FALSE]) == 0) {
				sp->quick = false;
			} else {
				sp->quick = true;
			}
		}
		
		tmp = FindToolType(dobj->do_ToolTypes, strings[MSG_SLOW_TT]);
		if (tmp) {
			if (stricmp(tmp, strings[MSG_OFF]) != 0 &&
					stricmp(tmp, strings[MSG_FALSE]) != 0) {
				sp->quick = false;
			} else {
				sp->quick = true;
			}
		}
		
		tmp = FindToolType(dobj->do_ToolTypes, strings[MSG_CASE_TT]);
		if (tmp) {
			if (stricmp(tmp, strings[MSG_OFF]) != 0 &&
					stricmp(tmp, strings[MSG_FALSE]) != 0) {
				sp->case_sensitive = true;
			}
		}
		
		tmp = FindToolType(dobj->do_ToolTypes, strings[MSG_HOST_TT]);
		if (tmp) {
			if (sp->host) deallocate(sp->host, V_cstr);
			sp->host = allocate(strlen(tmp) + 1, V_cstr);
			if (sp->host) {
				strcpy(sp->host, tmp);
			}
		}
		
		tmp = FindToolType(dobj->do_ToolTypes, strings[MSG_ROOT_TT]);
		if (tmp)
		{
			sp->root = allocate(strlen(tmp) + 1, V_cstr);
			if (sp->root)
			{
				strcpy(sp->root, tmp);
			}
		}
		
		tmp = FindToolType(dobj->do_ToolTypes, strings[MSG_MESSAGES_TT]);
		if (tmp) {
			if (stricmp(tmp, strings[MSG_ALL]) == 0)
			{
				sp->error_messages = true;
				sp->all_messages = true;
			}
			else if (stricmp(tmp, strings[MSG_ERROR]) == 0)
			{
				sp->all_messages = false;
				sp->error_messages = true;
			}
			else if (stricmp(tmp, strings[MSG_NONE]) == 0)
			{
				sp->all_messages = false;
				sp->error_messages = false;
			}
		}

		tmp = FindToolType(dobj->do_ToolTypes, strings[MSG_PORT_TT]);
		if (tmp) {
			sp->port_number = atoi(tmp);
		}
		
		FreeDiskObject(dobj);
	}
	
	if (!sp->host) {
		if (sp->user) deallocate(sp->user, V_cstr);
		if (sp->password) deallocate(sp->password, V_cstr);
		if (sp->root) deallocate(sp->root, V_cstr);
		
		deallocate(sp, V_site);
		
		return nil;
	}
	
	std_pkt = (struct StandardPacket *)allocate(sizeof(*std_pkt), V_StandardPacket);
	if (!std_pkt) {
		deallocate(sp->host, V_cstr);
		
		if (sp->user) deallocate(sp->user, V_cstr);
		if (sp->password) deallocate(sp->password, V_cstr);
		if (sp->root) deallocate(sp->root, V_cstr);
		
		deallocate(sp, V_site);
		
		return nil;
	}
	
	std_pkt->sp_Msg.mn_Node.ln_Name = (void *)&std_pkt->sp_Pkt;
	std_pkt->sp_Pkt.dp_Link = &std_pkt->sp_Msg;
	
	std_pkt->sp_Pkt.dp_Type = ACTION_DIE;	/* ignored when used at startup */
	std_pkt->sp_Pkt.dp_Arg1 = (b32)sp;
	std_pkt->sp_Pkt.dp_Port = startup_sync;
	
	strcpy(sp->name, s);
	
	sp->lock_list = nil;
	sp->file_list = nil;
	
	sp->infos = nil;

	sp->control = nil;
	sp->intr = nil;

	sp->cwd = nil;
	
	sp->connected = false;
	sp->read_banners = false;
	sp->unix_paths = true;
	
	sp->cfile = nil;
	sp->cfile_type = 0;
	
	sp->abort_signals = sp->disconnect_signals = 0;
	sp->site_state = SS_DISCONNECTED;
	
	sp->status_window = nil;
	
	child = CreateNewProcTags(
		NP_Entry,	site_handler,
		NP_Name,	sp->name,
		NP_StackSize,	6000,
		NP_Priority,	0,
		TAG_END,	0
	);
	
	if (!child) {
		deallocate(sp->host, V_cstr);
		
		if (sp->user) deallocate(sp->user, V_cstr);
		if (sp->password) deallocate(sp->password, V_cstr);
		if (sp->root) deallocate(sp->root, V_cstr);
		
		deallocate(std_pkt, V_StandardPacket);
		deallocate(sp, V_site);
		return nil;
	}
	
	sp->port = &child->pr_MsgPort;
	
	PutMsg(sp->port, &std_pkt->sp_Msg);
	WaitPort(startup_sync); GetMsg(startup_sync);
	
	if (std_pkt->sp_Pkt.dp_Res1) {
		sp->death_packet = std_pkt;
		
		sp->next = sites;
		sites = sp;
		
		return sp->port;
	} else {
		deallocate(sp->host, V_cstr);
		
		if (sp->user) deallocate(sp->user, V_cstr);
		if (sp->password) deallocate(sp->password, V_cstr);
		if (sp->root) deallocate(sp->root, V_cstr);
		
		deallocate(std_pkt, V_StandardPacket);
		deallocate(sp, V_site);
		
		return nil;
	}
}

void remove_site(site *sp)
{
	site **sps;
	
	verify(sp, V_site);
	
	sps = &sites;
	while (*sps && *sps != sp) {
		sps = &(*sps)->next;
	}
	
	if (*sps) {
		*sps = sp->next;
		
		if (sp->host) deallocate(sp->host, V_cstr);
		if (sp->root) deallocate(sp->root, V_cstr);
		
		deallocate(sp->death_packet, V_StandardPacket);
		deallocate(sp, V_site);
	}
	
	return;
}

void shutdown_sites(void)
{
	site *sp, *spn;
	struct DosPacket *dp;
	lock *l;
	
	sp = sites;
	while (sp) {
		verify(sp, V_site);
	
		spn = sp->next;
		
		dp = &sp->death_packet->sp_Pkt;
		dp->dp_Type = ACTION_DIE;
		dp->dp_Port = startup_sync;
		
		PutMsg(sp->port, dp->dp_Link);
		WaitPort(startup_sync); GetMsg(startup_sync);
		
		l = (lock *)dp->dp_Res2;
		while (l) {
			adopt(l, V_lock);
			l = l->next;
		}
		
		if (sp->host) deallocate(sp->host, V_cstr);
		if (sp->root) deallocate(sp->root, V_cstr);
		
		deallocate(sp->death_packet, V_StandardPacket);
		deallocate(sp, V_site);
		sp = spn;
	}
	
	sites = nil;
}

void suspend_sites(void)
{
	site *sp;
	struct DosPacket *dp;
	
	sp = sites;
	while (sp) {
		verify(sp, V_site);
	
		dp = &sp->death_packet->sp_Pkt;
		dp->dp_Type = action_SUSPEND;
		dp->dp_Port = startup_sync;
		
		PutMsg(sp->port, dp->dp_Link);
		WaitPort(startup_sync); GetMsg(startup_sync);
		
		sp = sp->next;
	}
}

struct info_header *get_dir(site *sp, b8 *name)
{
	struct info_header *ih;
	
	verify(sp, V_site);
	truth(name != nil);
	
	ih = find_info_header(sp, name);
	if (ih) return ih;
	
	/* oh kay */
	
	if (sp->cfile) {
		verify(sp->file_list, V_file_info);
		
		if (sp->file_list->closed) {
			close_file(sp, true);
		} else {
			return nil;
		}
	}
	
	if (!change_dir(sp, name)) return nil;
	
	ih = new_info_header(sp, name);
	if (!ih) return nil;	/* just out of memory ... but wth */
	
	get_list(sp, ih);
	
	return ih;
}

ftpinfo *get_info(site *ftp_site, b8 *name)
{
	b8 *s;
	struct info_header *ih;
	
	verify(ftp_site, V_site);
	truth(name != nil);
	
	/* get parent dir name */
		
	s = name + strlen(name) - 1;
	while (s >= name && *s != '/') s--;
	
	if (s >= name) {
		*s = 0;
		ih = get_dir(ftp_site, name);
		*s++ = '/';
	} else {
		s = name;
		ih = get_dir(ftp_site, "");
	}
	
	if (!ih) return nil;
	
	return find_info(ih, s);
}

void flush_info(site *ftp_site, b8 *name)
{
	b8 *s;
	struct info_header *ih;
	
	verify(ftp_site, V_site);
	truth(name != nil);
	
	/* get parent dir name */
		
	s = name + strlen(name) - 1;
	while (s >= name && *s != '/') s--;
	
	if (s >= name) {
		*s = 0;
		ih = find_info_header(ftp_site, name);
		*s++ = '/';
	} else {
		s = name;
		ih = find_info_header(ftp_site, "");
	}
	
	if (ih) {
		free_info_header(ih);
	}
	
	return;
}

status_message *get_sm(site *sp)
{
	status_message *sm;
	
	verify(sp, V_site);
	
	if (sm = (status_message *)GetMsg(sp->rank)) return sm;
	
	sm = (status_message *)allocate(sizeof(*sm), V_status_message);
	if (!sm) return nil;
	
	ensure(sm, V_status_message);
	
	sm->header.mn_ReplyPort = sp->rank;
	sm->header.mn_Length = sizeof(*sm);
	sm->header.mn_Node.ln_Type = NT_MESSAGE;
	sm->header.mn_Node.ln_Name = "site status message";
	sm->header.mn_Node.ln_Pri = 0;
	
	sm->this_site = sp;
	
	return sm;
}

void state_change(site *sp, b16 state)
{
	status_message *sm;
	
	verify(sp, V_site);
	
	if (sp->site_state == state) return;

	sp->site_state = state;
	
	sm = get_sm(sp);
	if (sm) {
		sm->command = SM_STATE_CHANGE;
		PutMsg(status_port, &sm->header);
	}
}

void __saveds site_handler(void)
{
	struct Process *me;
	struct Message *msg;
	struct DosPacket *dp;
	struct MsgPort *local, *reply, *sync, *timeport, *rank;
	struct StandardPacket *idle_packet;
	struct timerequest *timer;
	status_message *sm, *tsm;
	lock *new_lock, *slock, **locks;
	site *ftp_site;
	b32 signals;
	int idlecount, n;
	split sd, sd2;
	b8 *s;
	struct info_header *ih;
	ftpinfo *fi;
	file_info *fip;
	struct FileHandle *fh;
	struct FileInfoBlock *fib;
	b32 o1, o2, o3;
	
	me = (struct Process *)FindTask(0);
	
	local = &me->pr_MsgPort;

	WaitPort(local);
	msg = GetMsg(local);
	
	dp = (struct DosPacket *)msg->mn_Node.ln_Name;
	
	reply = dp->dp_Port;
	dp->dp_Port = local;
	
	ftp_site = (site *)dp->dp_Arg1;
	
	verify(ftp_site, V_site);
	
	local->mp_Node.ln_Name = ftp_site->name;
	
	idlecount = 0;
	
	mem_tracking_on();
	
	ftp_site->IBase = (struct IntuitionBase *)OpenLibrary("intuition.library", 36);
	if (ftp_site->IBase) {
		ftp_site->GTBase = OpenLibrary("gadtools.library", 0);
		if (ftp_site->GTBase) {
			ftp_site->GBase = (struct GfxBase *)OpenLibrary("graphics.library", 36);
			if (ftp_site->GBase) {
				sync = CreatePort(0, 0);
				if (sync) {
					ftp_site->sync = sync;
				
					timeport = CreatePort(0, 0);
					if (timeport) {
						timer = (struct timerequest *)CreateExtIO(timeport, sizeof(*timer));
						if (timer) {
							idle_packet = (struct StandardPacket *)allocate(sizeof(*idle_packet), V_StandardPacket);
							if (idle_packet) {
								idle_packet->sp_Msg.mn_Node.ln_Name =
									(void *)&idle_packet->sp_Pkt;
								idle_packet->sp_Pkt.dp_Link = &idle_packet->sp_Msg;
								idle_packet->sp_Pkt.dp_Type = action_IDLE;
								idle_packet->sp_Pkt.dp_Arg1 = (b32)ftp_site;
						
								prime->header.mn_ReplyPort = sync;
								prime->command = TCP_NEWMESSAGE;
								PutMsg(tcp, &prime->header);
								WaitPort(sync); GetMsg(sync);
								if (prime->result) {
									ftp_site->control = prime->data;
								
									PutMsg(tcp, &prime->header);
									WaitPort(sync); GetMsg(sync);
									if (prime->result) {
										ftp_site->intr = prime->data;
										ftp_site->intr->interrupt = ftp_site->control;
										ftp_site->intr->header.mn_ReplyPort = sync;
										ftp_site->intr->command = TCP_INTERRUPT;

										if (OpenDevice("timer.device", UNIT_VBLANK, (struct IORequest *)timer, 0) == 0) {
											sm = (status_message *)allocate(sizeof(*sm), V_status_message);
											if (sm) {
												rank = CreatePort(0, 0);
												if (rank) {
													ftp_site->rank = rank;
													goto begin;
												} else dp->dp_Res2 = ERROR_NO_FREE_STORE;
												deallocate(sm, V_status_message);
											} else dp->dp_Res2 = ERROR_NO_FREE_STORE;
											CloseDevice((struct IORequest *)timer);
										} else dp->dp_Res2 = ERROR_DEVICE_NOT_MOUNTED;
										ftp_site->intr->command = TCP_DISPOSE;
										PutMsg(tcp, &ftp_site->intr->header);
									} else dp->dp_Res2 = ERROR_NO_FREE_STORE;
									ftp_site->control->command = TCP_DISPOSE;
									PutMsg(tcp, &ftp_site->control->header);
								} else dp->dp_Res2 = ERROR_NO_FREE_STORE;
								deallocate(idle_packet, V_StandardPacket);
							} else dp->dp_Res2 = ERROR_NO_FREE_STORE;
							DeleteExtIO((struct IORequest *)timer);
						} else dp->dp_Res2 = ERROR_NO_FREE_STORE;
						DeletePort(timeport);
					} else dp->dp_Res2 = ERROR_NO_FREE_STORE;
					DeletePort(sync);
				} else dp->dp_Res2 = ERROR_NO_FREE_STORE;
				CloseLibrary((struct Library *)ftp_site->GBase);
			} else dp->dp_Res2 = ERROR_INVALID_RESIDENT_LIBRARY;
			CloseLibrary(ftp_site->GTBase);
		} else dp->dp_Res2 = ERROR_INVALID_RESIDENT_LIBRARY;
		CloseLibrary((struct Library *)ftp_site->IBase);
	} else dp->dp_Res2 = ERROR_INVALID_RESIDENT_LIBRARY;
	
	dp->dp_Res1 = DOSFALSE;
	
	check_memory();
	
	Forbid();
	PutMsg(reply, dp->dp_Link);
	return;

begin:
	timer->tr_node.io_Command = TR_ADDREQUEST;
	timer->tr_time.tv_secs = IDLE_INTERVAL;
	timer->tr_time.tv_micro = 0;
	SendIO((struct IORequest *)timer);
	
	ensure(sm, V_status_message);
	
	sm->header.mn_ReplyPort = sync;
	sm->header.mn_Length = sizeof(*sm);
	sm->header.mn_Node.ln_Type = NT_MESSAGE;
	sm->header.mn_Node.ln_Pri = 0;
	sm->header.mn_Node.ln_Name = "site status message";
	
	sm->command = SM_NEW_SITE;
	sm->data = ftp_site->open_status;
	sm->this_site = ftp_site;
	
	PutMsg(status_port, &sm->header);
	WaitPort(sync); GetMsg(sync);
	
	ftp_site->abort_signals = (1 << AllocSignal(-1));
	ftp_site->disconnect_signals = (1 << AllocSignal(-1));
						
	dp->dp_Res1 = DOSTRUE;
	dp->dp_Res2 = 0;
	
	PutMsg(reply, dp->dp_Link);	/* send it back to signal we are away */
	
	signals = (1 << local->mp_SigBit) | (1 << timeport->mp_SigBit) | (ftp_site->disconnect_signals);
	
	while (1) {
		if (ftp_site->connected) {
			if (!ftp_site->cfile) {
				state_change(ftp_site, SS_IDLE);
			}
		} else {
			state_change(ftp_site, SS_DISCONNECTED);
		}

		if (Wait(signals) & ftp_site->disconnect_signals) {
			disconnect(ftp_site);
		}
		
		if (GetMsg(timeport)) {
			timer->tr_node.io_Command = TR_ADDREQUEST;
			timer->tr_time.tv_secs = IDLE_INTERVAL;
			timer->tr_time.tv_micro = 0;
			
			idlecount++;
			
			SendIO((struct IORequest *)timer);
			
			if (ftp_site->lock_list == nil && ftp_site->file_list == nil) {
				if (ftp_site->connected) {
					if (idlecount > NO_LOCK_CONN_IDLE) {
						idle_packet->sp_Pkt.dp_Port = sync;
						PutMsg(ftp_port, &idle_packet->sp_Msg);
						WaitPort(sync); GetMsg(sync);
					}
				} else if (!ftp_site->connected) {
					if (idlecount > NO_LOCK_NO_CONN_IDLE) {
						idle_packet->sp_Pkt.dp_Port = sync;
						PutMsg(ftp_port, &idle_packet->sp_Msg);
						WaitPort(sync); GetMsg(sync);
					}
				}
			}
		}
		
		while (msg = GetMsg(local)) {
			idlecount = 0;
			
			dp = (struct DosPacket *)msg->mn_Node.ln_Name;
			
			reply = dp->dp_Port;
			
			switch (dp->dp_Type) {
			case action_IDLE_DEATH:
				if (ftp_site->file_list && ftp_site->file_list->closed) {
					close_file(ftp_site, true);
				}
				if (ftp_site->lock_list || ftp_site->file_list) {
					dp->dp_Res1 = DOSFALSE;
					dp->dp_Res2 = 0;
					
					break;
				}
				/* fall through to DIE */
			case ACTION_DIE:
				state_change(ftp_site, SS_DISCONNECTING);
				
				slock = ftp_site->lock_list;
				while (slock) {
					new_lock = slock->next;
					disown(slock, V_lock);
					slock = new_lock;
				}
				
				deallocate(idle_packet, V_StandardPacket);
				
				AbortIO((struct IORequest *)timer);
				WaitPort(timeport); GetMsg(timeport);
				
				CloseDevice((struct IORequest *)timer);
				
				if (ftp_site->connected) {
					ftp_site->control->command = TCP_CLOSE;
					PutMsg(tcp, &ftp_site->control->header);
					WaitPort(sync); GetMsg(sync);
					
					ftp_site->connected = false;
				}
				
				ftp_site->control->command = TCP_DISPOSE;
				PutMsg(tcp, &ftp_site->control->header);
				
				ftp_site->intr->command = TCP_DISPOSE;
				PutMsg(tcp, &ftp_site->intr->header);
				
				sm->command = SM_DEAD_SITE;
				
				PutMsg(status_port, &sm->header);
				WaitPort(sync); GetMsg(sync);
				
				while (tsm = (status_message *)GetMsg(rank)) {
					verify(tsm, V_status_message);
					deallocate(tsm, V_status_message);
				}
				
				DeletePort(rank);
				deallocate(sm, V_status_message);

				DeleteExtIO((struct IORequest *)timer);
				DeletePort(timeport);
				DeletePort(sync);
				
				CloseLibrary((struct Library *)ftp_site->GBase);
				CloseLibrary(ftp_site->GTBase);
				CloseLibrary((struct Library *)ftp_site->IBase);
				
				while (ftp_site->infos) free_info_header(ftp_site->infos);
				
				if (ftp_site->cwd) deallocate(ftp_site->cwd, V_cstr);
				if (ftp_site->root) {
					/* this one might have been allocated from main task ... but */
					deallocate(ftp_site->root, V_cstr);
					ftp_site->root = nil;
				}
				
				/* again, these may have been allocated in either the main task or
				   the site task ... too complicated to work it out ... this works for now */
				
				if (ftp_site->user) deallocate(ftp_site->user, V_cstr);
				if (ftp_site->password) deallocate(ftp_site->password, V_cstr);
	
				dp->dp_Res1 = DOSTRUE;
				dp->dp_Res2 = (b32)(ftp_site->lock_list);	/* so they can adopt the locks */
				
				check_memory();
	
				Forbid();
				PutMsg(reply, dp->dp_Link);
				return;
			case ACTION_LOCATE_OBJECT:
				if (!split_data((lock *)(dp->dp_Arg1 << 2), (b8 *)(dp->dp_Arg2 << 2), &sd)) {
					dp->dp_Res1 = 0;
					dp->dp_Res2 = ERROR_NO_FREE_STORE;
					
					break;
				}
				
				if (!ftp_site->connected) {
					init_connect(ftp_site);
					
					if (!ftp_site->connected) {
						dp->dp_Res1 = 0;
						dp->dp_Res2 = ERROR_OBJECT_NOT_FOUND;
						
						end_split(&sd);
						break;
					}
				}
				
				if (!sd.path || sd.path[0] == 0) {
					/* the root ... this is ok */
					if (dp->dp_Arg3 == EXCLUSIVE_LOCK) {
						dp->dp_Res1 = 0;
						/* can't exclusive lock root */
						dp->dp_Res2 = ERROR_OBJECT_IN_USE;
						break;
					}
					
					new_lock = (lock *)allocate(sizeof(*new_lock) + 1, V_lock);
					if (!new_lock) {
						dp->dp_Res1 = 0;
						dp->dp_Res2 = ERROR_NO_FREE_STORE;
						
						break;
					}
					
					ensure(new_lock, V_lock);
					
					new_lock->next = ftp_site->lock_list;
					ftp_site->lock_list = new_lock;
					
					new_lock->port = local;
					new_lock->rfsl = 0;
					new_lock->lastkey = 0;
					new_lock->fname[0] = 0;
					
					new_lock->fl.fl_Link = 0;
					new_lock->fl.fl_Key = 0;
					new_lock->fl.fl_Access = SHARED_LOCK;
					new_lock->fl.fl_Task = ftp_port;
					new_lock->fl.fl_Volume = (b32)ftp_volume >> 2;
					
					dp->dp_Res1 = (b32)new_lock >> 2;
					dp->dp_Res2 = 0;
				} else {
					new_lock = (lock *)allocate(sizeof(*new_lock) + strlen(sd.path) + 1, V_lock);
					if (!new_lock) {
						dp->dp_Res1 = 0;
						dp->dp_Res2 = ERROR_NO_FREE_STORE;
						break;
					}
					
					ensure(new_lock, V_lock);
					
					strcpy(new_lock->fname, sd.path);

					new_lock->port = local;
					new_lock->rfsl = 0;
					new_lock->lastkey = 0;
					
					new_lock->fl.fl_Link = 0;
					new_lock->fl.fl_Key = 0;
					new_lock->fl.fl_Access = SHARED_LOCK;
					new_lock->fl.fl_Task = ftp_port;
					new_lock->fl.fl_Volume = (b32)ftp_volume >> 2;
					
					/* search for a conflicting lock */
					
					slock = ftp_site->lock_list;
					while (slock) {
						if (strcmp(sd.path, slock->fname) == 0) {
							if (dp->dp_Arg3 == EXCLUSIVE_LOCK || slock->fl.fl_Access == EXCLUSIVE_LOCK) {
								dp->dp_Res1 = 0;
								dp->dp_Res2 = ERROR_OBJECT_IN_USE;
								
								deallocate(new_lock, V_lock);
								end_split(&sd);
								
								goto reply_msg;
							}
							
							/* ok, this one is guaranteed to work */
							
							new_lock->next = ftp_site->lock_list;
							ftp_site->lock_list = new_lock;
							
							dp->dp_Res1 = (b32)new_lock >> 2;
							dp->dp_Res2 = 0;
							
							end_split(&sd);
							
							goto reply_msg;
						}
						slock = slock->next;
					}
					
					/* ok, it doesn't conflict ... check if it actually exists */
					
					fi = get_info(ftp_site, sd.path);
					if (!fi) {
						deallocate(new_lock, V_lock);
						end_split(&sd);
						
						dp->dp_Res1 = 0;
						if (ftp_site->cfile)
							dp->dp_Res2 = ERROR_OBJECT_IN_USE;
						else
							dp->dp_Res2 = ERROR_OBJECT_NOT_FOUND;
						goto reply_msg;
					}
					
					/* well, we found it! */
					
					new_lock->next = ftp_site->lock_list;
					ftp_site->lock_list = new_lock;
					
					dp->dp_Res1 = (b32)new_lock >> 2;
					dp->dp_Res2 = 0;
				}
				
				end_split(&sd);
				break;
			case ACTION_FREE_LOCK:
				slock = (lock *)(dp->dp_Arg1 << 2);				
				
				if (!slock) {
					dp->dp_Res1 = DOSTRUE;
					dp->dp_Res2 = 0;
					break;
				}
				
				verify(slock, V_lock);
				
				locks = &ftp_site->lock_list;
				while (*locks && *locks != slock) {
					locks = &(*locks)->next;
				}
				
				if (!*locks) {
					dp->dp_Res1 = DOSFALSE;
					dp->dp_Res2 = ERROR_INVALID_LOCK;
				} else {
					*locks = slock->next;
					deallocate(slock, V_lock);
					
					dp->dp_Res1 = DOSTRUE;
					dp->dp_Res2 = 0;
				}
				
				break;
			case ACTION_DELETE_OBJECT:
				if (!split_data((lock *)(dp->dp_Arg1 << 2), (b8 *)(dp->dp_Arg2 << 2), &sd)) {
					dp->dp_Res1 = DOSFALSE;
					dp->dp_Res2 = ERROR_NO_FREE_STORE;
					
					break;
				}
				
				if (ftp_site->file_list) {
					if (ftp_site->file_list->closed) {
						close_file(ftp_site, true);
					} else {
						dp->dp_Res1 = DOSFALSE;
						dp->dp_Res2 = ERROR_OBJECT_IN_USE;
						
						end_split(&sd);
						
						goto reply_msg;
					}
				}
				
				/* search for a conflicting lock */
				
				slock = ftp_site->lock_list;
				while (slock) {
					if (strcmp(sd.path, slock->fname) == 0) {
						dp->dp_Res1 = 0;
						dp->dp_Res2 = ERROR_OBJECT_IN_USE;
						
						end_split(&sd);
						
						goto reply_msg;
					}
					slock = slock->next;
				}
				
				fi = get_info(ftp_site, sd.path);
				if (fi && fi->flags & MYFLAG_DIR) {
					dp->dp_Res2 = delete_directory(ftp_site, sd.path);
				} else {
					dp->dp_Res2 = delete_file(ftp_site, sd.path);
				}
				
				if (dp->dp_Res2) {
					dp->dp_Res1 = DOSFALSE;
				} else {
					if (fi) fi->flags |= MYFLAG_DELETED;
					dp->dp_Res1 = DOSTRUE;
				}
				
				end_split(&sd);
				
				break;
			case ACTION_RENAME_OBJECT:
				if (!split_data((lock *)(dp->dp_Arg1 << 2), (b8 *)(dp->dp_Arg2 << 2), &sd)) {
					dp->dp_Res1 = DOSFALSE;
					dp->dp_Res2 = ERROR_NO_FREE_STORE;
					
					break;
				}
				
				if (!split_data((lock *)(dp->dp_Arg3 << 2), (b8 *)(dp->dp_Arg4 << 2), &sd2)) {
					dp->dp_Res1 = DOSFALSE;
					dp->dp_Res2 = ERROR_NO_FREE_STORE;
					
					end_split(&sd);
					
					break;
				}
				
				fi = get_info(ftp_site, sd.path);
				
				dp->dp_Res2 = rename_object(ftp_site, sd.path, sd2.path);
				
				if (dp->dp_Res2) {
					dp->dp_Res1 = DOSFALSE;
				} else {
					if (fi) fi->flags |= MYFLAG_DELETED;
					flush_info(ftp_site, sd2.path);
					dp->dp_Res1 = DOSTRUE;
				}
				
				end_split(&sd);
				end_split(&sd2);
				
				break;
			case ACTION_COPY_DIR:
				slock = (lock *)(dp->dp_Arg1 << 2);
				if (!slock) {
					dp->dp_Res1 = 0;
					dp->dp_Res2 = 0;
					break;
				}
				
				verify(slock, V_lock);
				
				if (slock->fl.fl_Access == EXCLUSIVE_LOCK) {
					dp->dp_Res1 = 0;
					dp->dp_Res2 = ERROR_OBJECT_IN_USE;
					break;
				}
				
				new_lock = (lock *)allocate(sizeof(*new_lock) + strlen(slock->fname) + 1, V_lock);
				if (!new_lock) {
					dp->dp_Res1 = 0;
					dp->dp_Res2 = ERROR_NO_FREE_STORE;
					break;
				}
				
				*new_lock = *slock;
				strcpy(new_lock->fname, slock->fname);
				
				new_lock->next = slock->next;
				slock->next = new_lock;
				
				dp->dp_Res1 = (b32)new_lock >> 2;
				dp->dp_Res2 = 0;
				break;
			case ACTION_CREATE_DIR:
				if (!split_data((lock *)(dp->dp_Arg1 << 2), (b8 *)(dp->dp_Arg2 << 2), &sd)) {
					dp->dp_Res1 = 0;
					dp->dp_Res2 = ERROR_NO_FREE_STORE;
					
					break;
				}
				
				if (ftp_site->file_list) {
					if (ftp_site->file_list->closed) {
						close_file(ftp_site, true);
					} else {
						dp->dp_Res1 = 0;
						dp->dp_Res2 = ERROR_OBJECT_IN_USE;
						
						end_split(&sd);
						
						goto reply_msg;
					}
				}
				
				new_lock = (lock *)allocate(sizeof(*new_lock) + strlen(sd.path) + 1, V_lock);
				if (!new_lock) {
					dp->dp_Res1 = 0;
					dp->dp_Res2 = ERROR_NO_FREE_STORE;
					
					end_split(&sd);
					
					break;
				}
				
				dp->dp_Res2 = make_directory(ftp_site, sd.path);
				flush_info(ftp_site, sd.path);
				
				if (dp->dp_Res2) {
					dp->dp_Res1 = 0;
					
					deallocate(new_lock, V_lock);
				} else {
					dp->dp_Res1 = (b32)new_lock >> 2;
					
					ensure(new_lock, V_lock);

					new_lock->next = ftp_site->lock_list;
					ftp_site->lock_list = new_lock;
					
					strcpy(new_lock->fname, sd.path);

					new_lock->port = local;
					new_lock->rfsl = 0;
					new_lock->lastkey = 0;
					
					new_lock->fl.fl_Link = 0;
					new_lock->fl.fl_Key = 0;
					new_lock->fl.fl_Access = SHARED_LOCK;
					new_lock->fl.fl_Task = ftp_port;
					new_lock->fl.fl_Volume = (b32)ftp_volume >> 2;
				}
				
				end_split(&sd);
				
				break;
			case ACTION_EXAMINE_OBJECT:
				slock = (lock *)(dp->dp_Arg1 << 2);
				fib = (struct FileInfoBlock *)(dp->dp_Arg2 << 2);
				
				verify(slock, V_lock);
				truth(fib != nil);
				
				if (slock->fname[0] == 0) {
					/* root of this site */
					fib->fib_DiskKey = 0;
					fib->fib_DirEntryType = ST_USERDIR;
					fib->fib_EntryType = ST_USERDIR;
					
					strcpy(&fib->fib_FileName[1], ftp_site->name);
					fib->fib_FileName[0] = strlen(ftp_site->name);
					
					fib->fib_Protection = 0xf;
					fib->fib_Size = 0;
					fib->fib_NumBlocks = 0;
					fib->fib_Date = ftp_volume->dol_misc.dol_volume.dol_VolumeDate;
					fib->fib_Comment[0] = 0;
					
					dp->dp_Res1 = DOSTRUE;
					dp->dp_Res2 = 0;
					
					break;
				}
				
				s = slock->fname + strlen(slock->fname) - 1;
				while (s > slock->fname && *s != '/') s--;
				
				if (s == slock->fname) {
					ih = get_dir(ftp_site, "");
				} else {
					*s = 0;
					ih = get_dir(ftp_site, slock->fname);
					*s++ = '/';
				}
				
				if (!ih) {
					dp->dp_Res1 = DOSFALSE;
					if (ftp_site->cfile)
						dp->dp_Res2 = ERROR_OBJECT_IN_USE;
					else
						dp->dp_Res2 = ERROR_OBJECT_NOT_FOUND;
						/* general "connection buggered" */
					break;
				}
				
				fi = find_info(ih, s);
				if (!fi) {
					dp->dp_Res1 = DOSFALSE;
					dp->dp_Res2 = ERROR_OBJECT_NOT_FOUND;
					break;
				}
				
				if (fi->flags & MYFLAG_DIR) {
					fib->fib_DirEntryType = ST_USERDIR;
				} else {
					fib->fib_DirEntryType = ST_FILE;
				}
				
				fib->fib_EntryType = fib->fib_DirEntryType;
				fib->fib_DiskKey = 0;
				slock->lastkey = 0;
				fib->fib_FileName[0] = strlen(fi->name);
				strcpy(&fib->fib_FileName[1], fi->name);
				
				fib->fib_Protection = fi->flags & 0xff;
				fib->fib_Size = fi->size;
				fib->fib_NumBlocks = fi->blocks;
				fib->fib_Date = fi->modified;
				fib->fib_Comment[0] = 0;
				
				dp->dp_Res1 = DOSTRUE;
				dp->dp_Res2 = 0;
				break;
			case ACTION_EXAMINE_NEXT:
				slock = (lock *)(dp->dp_Arg1 << 2);
				fib = (struct FileInfoBlock *)(dp->dp_Arg2 << 2);
				
				verify(slock, V_lock);
				truth(fib != nil);
				
				ih = get_dir(ftp_site, slock->fname);
				if (!ih) {
					dp->dp_Res1 = DOSFALSE;
					if (ftp_site->cfile)
						dp->dp_Res2 = ERROR_OBJECT_IN_USE;
					else
						dp->dp_Res2 = ERROR_OBJECT_NOT_FOUND;
					break;
				}
				
				// n = fib->fib_DiskKey;
				n = slock->lastkey;
				
				slock->lastkey++;
				
				fi = ih->infos;
				while (fi && n) {
					fi = fi->next;
					n--;
				}
				
				while (fi && fi->flags & MYFLAG_DELETED) {
					fi = fi->next;
					slock->lastkey++;
				}
				
				fib->fib_DiskKey = slock->lastkey;

				if (!fi) {
					slock->lastkey = 0;
					fib->fib_DiskKey = 0;
					dp->dp_Res1 = DOSFALSE;
					dp->dp_Res2 = ERROR_NO_MORE_ENTRIES;
					break;
				}
				
				if (fi->flags & MYFLAG_DIR) {
					fib->fib_DirEntryType = ST_USERDIR;
				} else {
					fib->fib_DirEntryType = ST_FILE;
				}
				
				fib->fib_EntryType = fib->fib_DirEntryType;
				fib->fib_FileName[0] = strlen(fi->name);
				strcpy(&fib->fib_FileName[1], fi->name);
				
				fib->fib_Protection = fi->flags & 0xff;
				fib->fib_Size = fi->size;
				fib->fib_NumBlocks = fi->blocks;
				fib->fib_Date = fi->modified;
				fib->fib_Comment[0] = 0;
				
				dp->dp_Res1 = DOSTRUE;
				dp->dp_Res2 = 0;
				
				break;
			case ACTION_PARENT:
				slock = (lock *)(dp->dp_Arg1 << 2);
				if (!slock) {
					dp->dp_Res1 = 0;
					dp->dp_Res2 = 0;
					break;
				}
				
				if (slock->fname[0] == 0) {
					/* need root of FTP: (the slimy way) */
					dp->dp_Port = ftp_site->sync;
					dp->dp_Type = ACTION_LOCATE_OBJECT;
					
					o1 = dp->dp_Arg1;
					o2 = dp->dp_Arg2;
					o3 = dp->dp_Arg3;
					dp->dp_Arg1 = 0;
					dp->dp_Arg2 = (b32)(&("\0\0\0\0"[3])) >> 2;
					dp->dp_Arg3 = SHARED_LOCK;
					
					PutMsg(local_port, dp->dp_Link);
					WaitPort(ftp_site->sync); GetMsg(ftp_site->sync);
					
					dp->dp_Arg1 = o1;
					dp->dp_Arg2 = o2;
					dp->dp_Arg3 = o3;
					dp->dp_Type = ACTION_PARENT;
					break;
				}
				
				s = slock->fname + strlen(slock->fname) - 1;
				while (s > slock->fname && *s != '/') s--;
				
				if (s == slock->fname) {
					new_lock = (lock *)allocate(sizeof(*new_lock) + 1, V_lock);
					if (!new_lock) {
						dp->dp_Res1 = 0;
						dp->dp_Res2 = ERROR_NO_FREE_STORE;
						
						break;
					}
					
					ensure(new_lock, V_lock);
					
					new_lock->next = ftp_site->lock_list;
					ftp_site->lock_list = new_lock;
					
					new_lock->port = local;
					new_lock->rfsl = 0;
					new_lock->fname[0] = 0;
					
					new_lock->fl.fl_Link = 0;
					new_lock->fl.fl_Key = 0;
					new_lock->fl.fl_Access = SHARED_LOCK;
					new_lock->fl.fl_Task = ftp_port;
					new_lock->fl.fl_Volume = (b32)ftp_volume >> 2;
					
					dp->dp_Res1 = (b32)new_lock >> 2;
					dp->dp_Res2 = 0;
				} else {
					*s = 0;
					
					new_lock = (lock *)allocate(sizeof(*new_lock) + strlen(slock->fname) + 1, V_lock);
					if (!new_lock) {
						*s = '/';
						
						dp->dp_Res1 = 0;
						dp->dp_Res2 = ERROR_NO_FREE_STORE;
						break;
					}
					
					ensure(new_lock, V_lock);
					
					strcpy(new_lock->fname, slock->fname);
					
					*s = '/';

					new_lock->port = local;
					new_lock->rfsl = 0;
					
					new_lock->fl.fl_Link = 0;
					new_lock->fl.fl_Key = 0;
					new_lock->fl.fl_Access = SHARED_LOCK;
					new_lock->fl.fl_Task = ftp_port;
					new_lock->fl.fl_Volume = (b32)ftp_volume >> 2;
					
					/* search for a conflicting lock */
					
					slock = ftp_site->lock_list;
					while (slock) {
						if (strcmp(new_lock->fname, slock->fname) == 0) {
							if (slock->fl.fl_Access == EXCLUSIVE_LOCK) {
								dp->dp_Res1 = 0;
								dp->dp_Res2 = ERROR_OBJECT_IN_USE;
								
								deallocate(new_lock, V_lock);
								goto reply_msg;
							}
							
							/* ok, this one is guaranteed to work */
							
							new_lock->next = ftp_site->lock_list;
							ftp_site->lock_list = new_lock;
							
							dp->dp_Res1 = (b32)new_lock >> 2;
							dp->dp_Res2 = 0;
							
							goto reply_msg;
						}
						slock = slock->next;
					}
					
					/* ok, it doesn't conflict ... it must exist*/
					
					new_lock->next = ftp_site->lock_list;
					ftp_site->lock_list = new_lock;
					
					dp->dp_Res1 = (b32)new_lock >> 2;
					dp->dp_Res2 = 0;
				}
				break;
			case ACTION_SAME_LOCK:
				slock = (lock *)(dp->dp_Arg1 << 2);
				new_lock = (lock *)(dp->dp_Arg2 << 2);
				
				verify(slock, V_lock);
				verify(new_lock, V_lock);
				
				if (strcmp(slock->fname, new_lock->fname) == 0) {
					dp->dp_Res1 = DOSTRUE;
					dp->dp_Res2 = 0;
				} else {
					dp->dp_Res1 = DOSFALSE;
					dp->dp_Res2 = 0;
				}
				break;
			case ACTION_READ:
				fip = (file_info *)dp->dp_Arg1;
				verify(fip, V_file_info);
				
				if (!ftp_site->connected || ftp_site->cfile == nil) {
					dp->dp_Res1 = 0;
					dp->dp_Res2 = ERROR_OBJECT_NOT_FOUND;
					break;
				}
				
				if (ftp_site->cfile_type != ACTION_FINDINPUT) {
					dp->dp_Res1 = 0;
					dp->dp_Res2 = ERROR_READ_PROTECTED;
					break;
				}
				
				if (fip->seek_end) {	/* artificially at end */
					dp->dp_Res1 = 0;
					dp->dp_Res2 = 0;
					break;
				}
				
				state_change(ftp_site, SS_READING);
				
				o1 = dp->dp_Arg3;
				s = (b8 *)dp->dp_Arg2;
				
				if (o1 == 0) {
					dp->dp_Res1 = 0;
					dp->dp_Res2 = 0;
					break;
				}
				
				if (fip->vpos < FIRST_BLOCK_SIZE && fip->vpos < fip->rpos) {
					o2 = o1;
					if (o2 > FIRST_BLOCK_SIZE - fip->vpos) o2 = FIRST_BLOCK_SIZE - fip->vpos;
					if (o2 > fip->rpos - fip->vpos) o2 = fip->rpos - fip->vpos;
					
					memcpy(s, &fip->first_block[fip->vpos], o2);
					
					fip->vpos += o2;
					
					if (o2 == o1) {
						dp->dp_Res1 = o1;
						dp->dp_Res2 = 0;
					
						break;
					} else {
						s += o2;
						o1 -= o2;
					}
				}
				
				if (fip->vpos != fip->rpos) {
					dp->dp_Res1 = -1;
					dp->dp_Res2 = ERROR_SEEK_ERROR;
					break;
				}
				
				while (o1) {
					if (o1 > MAX_READ_SIZE) {
						o2 = MAX_READ_SIZE;
						o1 -= o2;
					} else {
						o2 = o1;
						o1 = 0;
					}
					switch (read_file(ftp_site, s, &o2)) {
					case NO_ERROR:
						s += o2;
						break;
					case ERROR_EOF:
						s += o2;
						dp->dp_Res1 = s - (b8 *)dp->dp_Arg2;
						dp->dp_Res2 = 0;
						o1 = 0;
						fip->eof = true;
						break;
					default:
						dp->dp_Res1 = -1;
						dp->dp_Res2 = ERROR_OBJECT_NOT_FOUND;
						o1 = 0;
						break;
					}
					fip->rpos += o2;
					fip->vpos = fip->rpos;
					tsm = get_sm(ftp_site);
					if (tsm) {
						tsm->command = SM_PROGRESS;
						PutMsg(status_port, &tsm->header);
					}
				}
				
				dp->dp_Res1 = s - (b8 *)dp->dp_Arg2;
				dp->dp_Res2 = 0;
				
				break;
			case ACTION_WRITE:
				fip = (file_info *)dp->dp_Arg1;
				verify(fip, V_file_info);
				
				if (!ftp_site->connected || ftp_site->cfile == nil) {
					dp->dp_Res1 = 0;
					dp->dp_Res2 = ERROR_OBJECT_NOT_FOUND;
					break;
				}
				
				if (ftp_site->cfile_type != ACTION_FINDOUTPUT) {
					dp->dp_Res1 = 0;
					dp->dp_Res2 = ERROR_WRITE_PROTECTED;
					break;
				}
				
				state_change(ftp_site, SS_WRITING);

				o1 = dp->dp_Arg3;
				s = (b8 *)dp->dp_Arg2;
				
				if (o1 == 0) {
					dp->dp_Res1 = 0;
					dp->dp_Res2 = 0;
					break;
				}
				
				if (fip->vpos != fip->rpos) {
					dp->dp_Res1 = -1;
					dp->dp_Res2 = ERROR_SEEK_ERROR;
					break;
				}
				
				while (o1) {
					if (o1 > MAX_READ_SIZE) {
						o2 = MAX_READ_SIZE;
						o1 -= o2;
					} else {
						o2 = o1;
						o1 = 0;
					}
					switch (write_file(ftp_site, s, &o2)) {
					case NO_ERROR:
						s += o2;
						break;
					case ERROR_EOF:
						s += o2;
						dp->dp_Res1 = s - (b8 *)dp->dp_Arg2;
						dp->dp_Res2 = 0;
						o1 = 0;
						fip->eof = true;
						break;
					default:
						dp->dp_Res1 = -1;
						dp->dp_Res2 = ERROR_OBJECT_NOT_FOUND;
						o1 = 0;
						break;
					}
					fip->rpos += o2;
					fip->vpos = fip->rpos;
					tsm = get_sm(ftp_site);
					if (tsm) {
						tsm->command = SM_PROGRESS;
						PutMsg(status_port, &tsm->header);
					}
				}
				
				dp->dp_Res1 = s - (b8 *)dp->dp_Arg2;
				dp->dp_Res2 = 0;

				break;
			case ACTION_FINDINPUT:
			case ACTION_FINDOUTPUT:
				if (!split_data((lock *)(dp->dp_Arg2 << 2),
							(b8 *)(dp->dp_Arg3 << 2), &sd)) {
					dp->dp_Res1 = DOSFALSE;
					dp->dp_Res2 = ERROR_NO_FREE_STORE;
					break;
				}
				
				if (!ftp_site->connected)
					init_connect(ftp_site);
				
				if (!ftp_site->connected) {
					dp->dp_Res1 = DOSFALSE;
					dp->dp_Res2 = ERROR_OBJECT_NOT_FOUND;
					
					end_split(&sd);
					break;
				}
				
				if (ftp_site->cfile) {
					fip = ftp_site->file_list;
					verify(fip, V_file_info);
					
					if (fip->closed) {
						if (strcmp(sd.path, fip->fname) == 0 &&
								dp->dp_Type == ACTION_FINDINPUT) {
							/* "reopen" this file */
							fh = (struct FileHandle *)(dp->dp_Arg1 << 2);
				
							truth(fh != nil);
				
							fh->fh_Type = ftp_port;
							fh->fh_Args = (b32)fip;
				
							fip->closed = false;
							fip->vpos = 0;
							fip->seek_end = false;
							
							dp->dp_Res1 = DOSTRUE;
							dp->dp_Res2 = 0;
							
							end_split(&sd);
							break;
						}
						
						close_file(ftp_site, true);
					} else {
						/* only one file at a time!  :( */
						dp->dp_Res1 = DOSFALSE;
						dp->dp_Res2 = ERROR_OBJECT_IN_USE;
					
						end_split(&sd);
						break;
					}
				}
				
				/* search for a conflicting lock */
					
				slock = ftp_site->lock_list;
				while (slock) {
					if (strcmp(sd.path, slock->fname) == 0) {
						if (dp->dp_Type == ACTION_FINDOUTPUT || slock->fl.fl_Access == EXCLUSIVE_LOCK) {
							dp->dp_Res1 = 0;
							dp->dp_Res2 = ERROR_OBJECT_IN_USE;
							
							end_split(&sd);
							
							goto reply_msg;
						}
					}
					slock = slock->next;
				}
				
				/* make sure we have information on this file BEFORE we start */
				
				if (dp->dp_Type == ACTION_FINDINPUT) {
					fi = get_info(ftp_site, sd.path);
				//	if (!fi) {
				//		dp->dp_Res1 = 0;
				//		dp->dp_Res2 = ERROR_OBJECT_NOT_FOUND;
				//		
				//		end_split(&sd);
				//	
				//		break;
				//	}
				} else {
					fi = nil;
				}
				
				if (dp->dp_Type == ACTION_FINDINPUT) {
					if (fi) {
						dp->dp_Res2 = open_file(ftp_site, sd.path, false, fi->name);
					} else {
						dp->dp_Res2 = open_file(ftp_site, sd.path, false, nil);
					}
				} else {
					dp->dp_Res2 = open_file(ftp_site, sd.path, true, nil);
					flush_info(ftp_site, sd.path);
				}
				
				if (dp->dp_Res2 != 0) {
					end_split(&sd);
					
					dp->dp_Res1 = 0;
					break;
				}
				
				fip = ftp_site->file_list;
				
				if (fi) {
					fip->end = fi->size;
				} else {
					fip->end = 0;
				}

				fip->port = local;
				fip->type = ftp_site->cfile_type = dp->dp_Type;
				
				end_split(&sd);
				
				fh = (struct FileHandle *)(dp->dp_Arg1 << 2);
				
				truth(fh != nil);
				
				fh->fh_Type = ftp_port;
				fh->fh_Args = (b32)fip;
				
				if (dp->dp_Type == ACTION_FINDINPUT) {
					o1 = FIRST_BLOCK_SIZE;
					switch (read_file(ftp_site, fip->first_block, &o1)) {
					case ERROR_EOF:
						fip->eof = true;
					case NO_ERROR:
						fip->rpos = o1;
						
						dp->dp_Res1 = DOSTRUE;
						dp->dp_Res2 = 0;
						
						break;
					default:
						close_file(ftp_site, true);
						
						dp->dp_Res1 = DOSFALSE;
						dp->dp_Res2 = ERROR_OBJECT_NOT_FOUND;
						
						break;
					}
				} else {
					dp->dp_Res1= DOSTRUE;
					dp->dp_Res2 = 0;
				}

				break;
			case ACTION_END:
				fip = (file_info *)dp->dp_Arg1;
				
				verify(fip, V_file_info);
				
				if (fip->type == ACTION_FINDINPUT && fip->rpos <= FIRST_BLOCK_SIZE) {
					fip->closed = true;
				} else {
					close_file(ftp_site, true);
				}
				
				dp->dp_Res1 = DOSTRUE;
				dp->dp_Res2 = 0;
				
				break;
			case ACTION_SEEK:
				fip = (file_info *)dp->dp_Arg1;
				verify(fip, V_file_info);
				
				if (dp->dp_Arg3 == OFFSET_END && dp->dp_Arg2 == 0) {
					/* go to the end of the file ... really we are :) */
					
					if (fip->seek_end) {
						dp->dp_Res1 = fip->end;
						dp->dp_Res2 = 0;
						break;
					}
					
					if (fip->type == ACTION_FINDINPUT)
						fip->seek_end = true;
					
					dp->dp_Res1 = fip->vpos;
					dp->dp_Res2 = 0;
					
					break;
				}
				
				if (dp->dp_Arg3 == OFFSET_BEGINNING) {
					o1 = dp->dp_Arg2;
				} else if (dp->dp_Arg3 == OFFSET_END) {
					o1 = fip->end + dp->dp_Arg2;
				} else if (dp->dp_Arg3 == OFFSET_CURRENT) {
					o1 = fip->vpos + dp->dp_Arg2;
				}
				
				if (o1 == fip->vpos) {
					/* not actually moving */
					
					if (fip->seek_end) {
						dp->dp_Res1 = fip->end;
						dp->dp_Res2 = 0;
						
						fip->seek_end = false;
					} else {
						dp->dp_Res1 = fip->vpos;
						dp->dp_Res2 = 0;
					}
					break;
				}
				
				if (o1 == fip->rpos) {
					/* not _really_ moving */
					
					if (fip->seek_end) {
						dp->dp_Res1 = fip->end;
						dp->dp_Res2 = 0;
						
						fip->seek_end = false;
					} else {
						dp->dp_Res1 = fip->vpos;
						dp->dp_Res2 = 0;
					}
					
					fip->vpos = fip->rpos;
					break;
				}

				if (o1 < fip->rpos && o1 < FIRST_BLOCK_SIZE) {
					/* seeking into our stored first block */
					if (fip->seek_end) {
						dp->dp_Res1 = fip->end;
						dp->dp_Res2 = 0;
						
						fip->seek_end = false;
					} else {
						dp->dp_Res1 = fip->vpos;
						dp->dp_Res2 = 0;
					}
					fip->vpos = o1;
					break;
				}
				
				show_string("SEEK:");
				show_int(dp->dp_Arg2);
				show_int(dp->dp_Arg3);
				dp->dp_Res1 = DOSFALSE;
				dp->dp_Res2 = ERROR_ACTION_NOT_KNOWN;
				break;
			case ACTION_FH_FROM_LOCK:
				fh = (struct FileHandle *)(dp->dp_Arg1 << 2);
				slock = (lock *)(dp->dp_Arg2 << 2);
				
				truth(fh != nil);
				verify(slock, V_lock);
				
				if (!ftp_site->connected)
					init_connect(ftp_site);
				
				if (!ftp_site->connected) {
					dp->dp_Res1 = DOSFALSE;
					dp->dp_Res2 = ERROR_OBJECT_NOT_FOUND;
					
					break;
				}
				
				if (ftp_site->cfile) {
					fip = ftp_site->file_list;
					verify(fip, V_file_info);
					
					if (fip->closed) {
						if (strcmp(slock->fname, fip->fname) == 0 &&
								slock->fl.fl_Access == SHARED_LOCK) {
							/* "reopen" this file */
				
							fh->fh_Type = ftp_port;
							fh->fh_Args = (b32)fip;
				
							fip->closed = false;
							fip->vpos = 0;
							fip->seek_end = false;
							
							dp->dp_Res1 = DOSTRUE;
							dp->dp_Res2 = 0;

							break;
						}
						
						close_file(ftp_site, true);
					} else {
						/* only one file at a time!  :( */
						dp->dp_Res1 = DOSFALSE;
						dp->dp_Res2 = ERROR_OBJECT_IN_USE;
						break;
					}
				}
				
				if (slock->fl.fl_Access == SHARED_LOCK) {
					fi = get_info(ftp_site, slock->fname);
				} else {
					fi = nil;
				}
				
				if (slock->fl.fl_Access == SHARED_LOCK) {
					if (fi) {
						dp->dp_Res2 = open_file(ftp_site, slock->fname, false, fi->name);
					} else {
						dp->dp_Res2 = open_file(ftp_site, slock->fname, false, nil);
					}
				} else {
					dp->dp_Res2 = open_file(ftp_site, slock->fname, true, nil);
					flush_info(ftp_site, slock->fname);
				}
				
				if (dp->dp_Res2 != 0) {
					dp->dp_Res1 = 0;
					break;
				}
				
				fip = ftp_site->file_list;
				
				if (fi) {
					fip->end = fi->size;
				} else {
					fip->end = 0;
				}

				fip->port = local;
				
				if (slock->fl.fl_Access == SHARED_LOCK) {
					fip->type = ftp_site->cfile_type = ACTION_FINDINPUT;
				} else {
					fip->type = ftp_site->cfile_type = ACTION_FINDOUTPUT;
				}
				
				fh->fh_Type = ftp_port;
				fh->fh_Args = (b32)fip;
				
				if (slock->fl.fl_Access == SHARED_LOCK) {
					o1 = FIRST_BLOCK_SIZE;
					switch (read_file(ftp_site, fip->first_block, &o1)) {
					case ERROR_EOF:
						fip->eof = true;
					case NO_ERROR:
						fip->rpos = o1;
						
						dp->dp_Res1 = DOSTRUE;
						dp->dp_Res2 = 0;
						
						break;
					default:
						close_file(ftp_site, true);
						
						dp->dp_Res1 = DOSFALSE;
						dp->dp_Res2 = ERROR_OBJECT_NOT_FOUND;
						
						break;
					}
				} else {
					dp->dp_Res1= DOSTRUE;
					dp->dp_Res2 = 0;
				}
				
				if (dp->dp_Res1) {
					/* close the lock */
					ensure(slock, 0);
					
					locks = &ftp_site->lock_list;
					while (*locks && *locks != slock) {
						locks = &(*locks)->next;
					}
				
					if (*locks) {
						*locks = slock->next;
						deallocate(slock, V_lock);
					}
				}

				break;
			case ACTION_COPY_DIR_FH:
				fh = (struct FileHandle *)(dp->dp_Arg1 << 2);
				if (!fh) {
					dp->dp_Res1 = 0;
					dp->dp_Res2 = ERROR_OBJECT_NOT_FOUND;
					break;
				}
				
				fip = (file_info *)fh->fh_Args;
				
				verify(fip, V_file_info);
				
				if (fip->type == ACTION_FINDOUTPUT) {
					dp->dp_Res1 = 0;
					dp->dp_Res2 = ERROR_OBJECT_IN_USE;
					break;
				}
				
				new_lock = (lock *)allocate(sizeof(*new_lock) + strlen(fip->fname) + 1, V_lock);
				if (!new_lock) {
					dp->dp_Res1 = 0;
					dp->dp_Res2 = ERROR_NO_FREE_STORE;
					break;
				}
				
				new_lock->port = local;
				new_lock->rfsl = 0;
				new_lock->lastkey = 0;
				
				new_lock->fl.fl_Link = 0;
				new_lock->fl.fl_Key = 0;
				new_lock->fl.fl_Access = SHARED_LOCK;
				new_lock->fl.fl_Task = ftp_port;
				new_lock->fl.fl_Volume = (b32)ftp_volume >> 2;
					
				strcpy(new_lock->fname, fip->fname);
				
				new_lock->next = ftp_site->lock_list;
				ftp_site->lock_list = new_lock;
				
				dp->dp_Res1 = (b32)new_lock >> 2;
				dp->dp_Res2 = 0;
				
				break;
			case ACTION_PARENT_FH:
				fh = (struct FileHandle *)(dp->dp_Arg1 << 2);
				if (!fh) {
					dp->dp_Res1 = 0;
					dp->dp_Res2 = ERROR_OBJECT_NOT_FOUND;
					break;
				}
				
				fip = (file_info *)fh->fh_Args;
				
				verify(fip, V_file_info);
				
				s = fip->fname + strlen(fip->fname) - 1;
				while (s > fip->fname && *s != '/') s--;
				
				if (s == fip->fname) {
					new_lock = (lock *)allocate(sizeof(*new_lock) + 1, V_lock);
					if (!new_lock) {
						dp->dp_Res1 = 0;
						dp->dp_Res2 = ERROR_NO_FREE_STORE;
						
						break;
					}
					
					ensure(new_lock, V_lock);
					
					new_lock->next = ftp_site->lock_list;
					ftp_site->lock_list = new_lock;
					
					new_lock->port = local;
					new_lock->rfsl = 0;
					new_lock->fname[0] = 0;
					
					new_lock->fl.fl_Link = 0;
					new_lock->fl.fl_Key = 0;
					new_lock->fl.fl_Access = SHARED_LOCK;
					new_lock->fl.fl_Task = ftp_port;
					new_lock->fl.fl_Volume = (b32)ftp_volume >> 2;
					
					dp->dp_Res1 = (b32)new_lock >> 2;
					dp->dp_Res2 = 0;
				} else {
					*s = 0;
					
					new_lock = (lock *)allocate(sizeof(*new_lock) + strlen(fip->fname) + 1, V_lock);
					if (!new_lock) {
						*s = '/';
						
						dp->dp_Res1 = 0;
						dp->dp_Res2 = ERROR_NO_FREE_STORE;
						break;
					}
					
					ensure(new_lock, V_lock);
					
					strcpy(new_lock->fname, fip->fname);
					
					*s = '/';

					new_lock->port = local;
					new_lock->rfsl = 0;
					
					new_lock->fl.fl_Link = 0;
					new_lock->fl.fl_Key = 0;
					new_lock->fl.fl_Access = SHARED_LOCK;
					new_lock->fl.fl_Task = ftp_port;
					new_lock->fl.fl_Volume = (b32)ftp_volume >> 2;
					
					/* search for a conflicting lock */
					
					slock = ftp_site->lock_list;
					while (slock) {
						if (strcmp(new_lock->fname, slock->fname) == 0) {
							if (slock->fl.fl_Access == EXCLUSIVE_LOCK) {
								dp->dp_Res1 = 0;
								dp->dp_Res2 = ERROR_OBJECT_IN_USE;
								
								deallocate(new_lock, V_lock);
								goto reply_msg;
							}
							
							/* ok, this one is guaranteed to work */
							
							new_lock->next = ftp_site->lock_list;
							ftp_site->lock_list = new_lock;
							
							dp->dp_Res1 = (b32)new_lock >> 2;
							dp->dp_Res2 = 0;
							
							goto reply_msg;
						}
						slock = slock->next;
					}
					
					/* ok, it doesn't conflict ... it must exist*/
					
					new_lock->next = ftp_site->lock_list;
					ftp_site->lock_list = new_lock;
					
					dp->dp_Res1 = (b32)new_lock >> 2;
					dp->dp_Res2 = 0;
				}
				break;
			case ACTION_EXAMINE_FH:
				fh = (struct FileHandle *)(dp->dp_Arg1 << 2);
				fip = (file_info *)fh->fh_Args;
				
				verify(fip, V_file_info);
				
				fib = (struct FileInfoBlock *)(dp->dp_Arg2 << 2);
				
				truth(fib != nil);
				
				s = fip->fname + strlen(fip->fname) - 1;
				while (s > fip->fname && *s != '/') s--;
				
				if (s == fip->fname) {
					ih = get_dir(ftp_site, "");
				} else {
					*s = 0;
					ih = get_dir(ftp_site, fip->fname);
					*s++ = '/';
				}
				
				if (!ih) {
					dp->dp_Res1 = DOSFALSE;
					if (ftp_site->cfile)
						dp->dp_Res2 = ERROR_OBJECT_IN_USE;
					else
						dp->dp_Res2 = ERROR_OBJECT_NOT_FOUND;
						/* general "connection buggered" */
					break;
				}
				
				fi = find_info(ih, s);
				if (!fi) {
					dp->dp_Res1 = DOSFALSE;
					dp->dp_Res2 = ERROR_OBJECT_NOT_FOUND;
					break;
				}
				
				if (fi->flags & MYFLAG_DIR) {
					fib->fib_DirEntryType = ST_USERDIR;
				} else {
					fib->fib_DirEntryType = ST_FILE;
				}
				
				fib->fib_EntryType = fib->fib_DirEntryType;
				fib->fib_DiskKey = 0;
				fib->fib_FileName[0] = strlen(fi->name);
				strcpy(&fib->fib_FileName[1], fi->name);
				
				fib->fib_Protection = fi->flags & 0xff;
				fib->fib_Size = fi->size;
				fib->fib_NumBlocks = fi->blocks;
				fib->fib_Date = fi->modified;
				fib->fib_Comment[0] = 0;
				
				dp->dp_Res1 = DOSTRUE;
				dp->dp_Res2 = 0;
				break;
			case action_SUSPEND:
				if (ftp_site->connected) {
					disconnect(ftp_site);
				}
				
				dp->dp_Res1 = DOSTRUE;
				dp->dp_Res2 = 0;
				
				dp->dp_Port = ftp_port;
				PutMsg(reply, dp->dp_Link);
				
				idle_packet->sp_Pkt.dp_Port = sync;
				PutMsg(ftp_port, &idle_packet->sp_Msg);
				WaitPort(sync); GetMsg(sync);

				continue;
			default:
				show_int(dp->dp_Type);
				dp->dp_Res1 = DOSFALSE;
				dp->dp_Res2 = ERROR_ACTION_NOT_KNOWN;
				break;
			}
			
reply_msg:
			dp->dp_Port = ftp_port;
			PutMsg(reply, dp->dp_Link);
		}
	}
}
