/*
 * $Id: main.c,v 1.70 2002/09/03 11:43:27 obarthel Exp $
 *
 * :ts=4
 *
 * SMB file system wrapper for AmigaOS, using the AmiTCP V3 API
 *
 * Copyright (C) 2000-2002 by Olaf `Olsen' Barthel <olsen@sourcery.han.de>
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 2 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
 *
 * smbfs arbeitsgruppe pcguest debug=2 //sourcery/all
 */

#include "system_headers.h"
#include "assert.h"

/****************************************************************************/

#include "smb_abstraction.h"

/****************************************************************************/

#include "smbfs_rev.h"
STRPTR Version = VERSTAG;

/****************************************************************************/

#define SAME (0)
#define OK (0)
#define NOT !
#define ZERO ((BPTR)NULL)

/****************************************************************************/

#if defined(__SASC)
#define FAR __far
#define ASM __asm
#define REG(r,p) register __##r p
#define INLINE __inline
#define SAVE_DS __saveds
#endif /* __SASC */

#if defined(__GNUC__)
#define FAR
#define ASM
#define REG(r,p) p __asm(#r)
#define INLINE __inline__
#define SAVE_DS
#endif /* __GNUC__ */

/****************************************************************************/

#define UNIX_TIME_OFFSET 252460800
#define MAX_FILENAME_LEN 256

/****************************************************************************/

#define SMB_ROOT_DIR_NAME	"\\"
#define SMB_PATH_SEPARATOR	'\\'

/****************************************************************************/

typedef STRPTR	KEY;
typedef LONG *	NUMBER;
typedef LONG	SWITCH;

/****************************************************************************/

struct FileNode
{
	struct MinNode		fn_MinNode;
	struct FileHandle *	fn_Handle;
	LONG				fn_Offset;
	LONG				fn_Mode;
	smba_file_t *		fn_File;
	STRPTR				fn_FullName;
};

struct LockNode
{
	struct MinNode	ln_MinNode;
	struct FileLock	ln_FileLock;
	smba_file_t *	ln_File;
	BOOL			ln_RestartExamine;
	UWORD			ln_Pad;
	STRPTR			ln_FullName;
};

/****************************************************************************/

/* The minimum operating system version we require to work. */
#define MINIMUM_OS_VERSION 39

/****************************************************************************/

#if (MINIMUM_OS_VERSION < 39)

/* These are in amiga.lib */
APTR ASM AsmCreatePool(REG(d0,ULONG memFlags),REG(d1,ULONG puddleSize),REG(d2,ULONG threshSize),REG(a6,struct Library * SysBase));
VOID ASM AsmDeletePool(REG(a0,APTR poolHeader),REG(a6,struct Library * SysBase));
APTR ASM AsmAllocPooled(REG(a0,APTR poolHeader),REG(d0,ULONG memSize),REG(a6,struct Library * SysBase));
VOID ASM AsmFreePooled(REG(a0,APTR poolHeader),REG(a1,APTR memory),REG(d0,ULONG memSize),REG(a6,struct Library * SysBase));

#define CreatePool(memFlags,puddleSize,threshSize) AsmCreatePool((memFlags),(puddleSize),(threshSize),SysBase)
#define DeletePool(poolHeader) AsmDeletePool((poolHeader),SysBase)
#define AllocPooled(poolHeader,memSize) AsmAllocPooled((poolHeader),(memSize),SysBase)
#define FreePooled(poolHeader,memory,memSize) AsmFreePooled((poolHeader),(memory),(memSize),SysBase)

#endif /* MINIMUM_OS_VERSION */

/****************************************************************************/

/* Forward declarations for local routines. */
LONG SAVE_DS Main(VOID);
LONG LocalPrintf(STRPTR format, ...);
STRPTR amitcp_strerror(int error);
STRPTR host_strerror(int error);
LONG CompareNames(STRPTR a, STRPTR b);
VOID StringToUpper(STRPTR s);
VOID ReportError(STRPTR fmt, ...);
VOID FreeVecPooled(APTR address);
APTR AllocVecPooled(ULONG size);
LONG GetTimeZoneDelta(VOID);
ULONG GetCurrentTime(VOID);
VOID LocalTime(time_t seconds, struct tm *tm);
time_t MakeTime(const struct tm *const tm);
VOID SPrintf(STRPTR buffer, STRPTR formatString, ...);
int BroadcastNameQuery(char *name, char *scope, UBYTE *address);

/****************************************************************************/

INLINE STATIC BOOL ReallyRemoveDosEntry(struct DosList *entry);
INLINE STATIC LONG BuildFullName(STRPTR parent_name, STRPTR name, STRPTR *result_ptr);
INLINE STATIC VOID TranslateCName(UBYTE *name, UBYTE *map);
INLINE STATIC VOID ConvertCString(LONG max_len, APTR bstring, STRPTR cstring);
STATIC VOID DisplayErrorList(VOID);
STATIC VOID AddError(STRPTR fmt, va_list args);
STATIC LONG CVSPrintf(STRPTR format_string, va_list args);
STATIC VOID VSPrintf(STRPTR buffer, STRPTR formatString, va_list args);
STATIC VOID SendDiskChange(ULONG class);
STATIC struct FileNode *FindFileNode(STRPTR name, struct FileNode *skip);
STATIC struct LockNode *FindLockNode(STRPTR name, struct LockNode *skip);
STATIC LONG CheckAccessModeCollision(STRPTR name, LONG mode);
STATIC LONG NameAlreadyInUse(STRPTR name);
STATIC BOOL IsReservedName(STRPTR name);
STATIC LONG MapErrnoToIoErr(int error);
STATIC VOID TranslateBName(UBYTE *name, UBYTE *map);
STATIC VOID Cleanup(VOID);
STATIC BOOL Setup(STRPTR program_name, STRPTR service, STRPTR workgroup, STRPTR username, STRPTR opt_password, BOOL opt_changecase, STRPTR opt_clientname, STRPTR opt_servername, int opt_cachesize, LONG *opt_time_zone_offset, STRPTR device_name, STRPTR volume_name, STRPTR translation_file);
STATIC VOID ConvertBString(LONG max_len, STRPTR cstring, APTR bstring);
STATIC BPTR Action_Parent(struct FileLock *parent, LONG *error_ptr);
STATIC LONG Action_DeleteObject(struct FileLock *parent, APTR bcpl_name, LONG *error_ptr);
STATIC BPTR Action_CreateDir(struct FileLock *parent, APTR bcpl_name, LONG *error_ptr);
STATIC BPTR Action_LocateObject(struct FileLock *parent, APTR bcpl_name, LONG mode, LONG *error_ptr);
STATIC BPTR Action_CopyDir(struct FileLock *lock, LONG *error_ptr);
STATIC LONG Action_FreeLock(struct FileLock *lock, LONG *error_ptr);
STATIC LONG Action_SameLock(struct FileLock *lock1, struct FileLock *lock2, LONG *error_ptr);
STATIC LONG Action_SetProtect(struct FileLock *parent, APTR bcpl_name, LONG mask, LONG *error_ptr);
STATIC LONG Action_RenameObject(struct FileLock *source_lock, APTR source_bcpl_name, struct FileLock *destination_lock, APTR destination_bcpl_name, LONG *error_ptr);
STATIC LONG Action_DiskInfo(struct InfoData *id, LONG *error_ptr);
STATIC LONG Action_Info(struct FileLock *lock, struct InfoData *id, LONG *error_ptr);
STATIC LONG Action_ExamineObject(struct FileLock *lock, struct FileInfoBlock *fib, LONG *error_ptr);
STATIC BOOL NameIsAcceptable(STRPTR name, LONG max_len);
STATIC LONG Action_ExamineNext(struct FileLock *lock, struct FileInfoBlock *fib, LONG *error_ptr);
STATIC LONG Action_ExamineAll(struct FileLock *lock, struct ExAllData *ed, ULONG size, ULONG type, struct ExAllControl *eac, LONG *error_ptr);
STATIC LONG Action_Find(LONG action, struct FileHandle *fh, struct FileLock *parent, APTR bcpl_name, LONG *error_ptr);
STATIC LONG Action_Read(struct FileNode *fn, APTR mem, LONG length, LONG *error_ptr);
STATIC LONG Action_Write(struct FileNode *fn, APTR mem, LONG length, LONG *error_ptr);
STATIC LONG Action_End(struct FileNode *fn, LONG *error_ptr);
STATIC LONG Action_Seek(struct FileNode *fn, LONG position, LONG mode, LONG *error_ptr);
STATIC LONG Action_SetFileSize(struct FileNode *fn, LONG position, LONG mode, LONG *error_ptr);
STATIC LONG Action_SetDate(struct FileLock *parent, APTR bcpl_name, struct DateStamp *ds, LONG *error_ptr);
STATIC LONG Action_ExamineFH(struct FileNode *fn, struct FileInfoBlock *fib, LONG *error_ptr);
STATIC BPTR Action_ParentFH(struct FileNode *fn, LONG *error_ptr);
STATIC BPTR Action_CopyDirFH(struct FileNode *fn, LONG *error_ptr);
STATIC LONG Action_FHFromLock(struct FileHandle *fh, struct FileLock *fl, LONG *error_ptr);
STATIC LONG Action_RenameDisk(APTR bcpl_name, LONG *error_ptr);
STATIC LONG Action_ChangeMode(LONG type, APTR object, LONG new_mode, LONG *error_ptr);
STATIC LONG Action_WriteProtect(LONG flag, ULONG key, LONG *error_ptr);
STATIC LONG Action_MoreCache(LONG buffer_delta, LONG *error_ptr);
STATIC LONG Action_SetComment(struct FileLock *parent, APTR bcpl_name, APTR bcpl_comment, LONG *error_ptr);
STATIC VOID HandleFileSystem(STRPTR device_name, STRPTR volume_name, STRPTR service_name);

/****************************************************************************/

struct Library *			SysBase;
struct Library *			DOSBase;
struct Library *			UtilityBase;
struct Library *			SocketBase;
struct Library *			LocaleBase;
struct Locale *				Locale;
struct Device *				TimerBase;
struct timerequest			TimerRequest;
struct Library *			IconBase;

/****************************************************************************/

int							errno;
int							h_errno;

/****************************************************************************/

STATIC struct DosList *		DeviceNode;
STATIC BOOL					DeviceNodeAdded;
STATIC struct DosList *		VolumeNode;
STATIC BOOL					VolumeNodeAdded;
STATIC struct MsgPort *		FileSystemPort;

STATIC smba_server_t *		ServerData;

STATIC BOOL					Quit;
STATIC BOOL					Quiet;
STATIC BOOL					CaseSensitive;
STATIC BOOL					OmitHidden;

STATIC LONG					TimeZoneOffset;
STATIC BOOL					OverrideLocaleTimeZone;

STATIC BOOL					WriteProtected;
STATIC ULONG				WriteProtectKey;

STATIC struct MinList		FileList;
STATIC struct MinList		LockList;

STATIC APTR					MemoryPool;

STATIC struct RDArgs *		Parameters;
STATIC struct DiskObject *	Icon;

STATIC struct WBStartup * 	WBStartup;

STATIC struct MinList		ErrorList;

STATIC STRPTR				NewProgramName;

STATIC BOOL					TranslateNames;
STATIC UBYTE				A2M[256];
STATIC UBYTE				M2A[256];

/****************************************************************************/

#if defined(__SASC)
extern struct Library * FAR	AbsExecBase;
#else
#define AbsExecBase (*(struct Library **)4)
#endif /* __SASC */

/****************************************************************************/

LONG SAVE_DS
Main(VOID)
{
	struct
	{
		KEY		Workgroup;
		KEY		UserName;
		KEY		Password;
		SWITCH	ChangeCase;
		SWITCH	CaseSensitive;
		SWITCH	OmitHidden;
		SWITCH	Quiet;
		KEY		ClientName;
		KEY		ServerName;
		KEY		DeviceName;
		KEY		VolumeName;
		NUMBER	CacheSize;
		NUMBER	DebugLevel;
		NUMBER	TimeZoneOffset;
		KEY		TranslationFile;
		KEY		Service;
	} args;

	STRPTR cmd_template =
		"DOMAIN=WORKGROUP/K,"
		"USER=USERNAME/K,"
		"PASSWORD/K,"
		"CHANGECASE/S,"
		"CASE=CASESENSITIVE/S,"
		"OMITHIDDEN/S,"
		"QUIET/S,"
		"CLIENT=CLIENTNAME/K,"
		"SERVER=SERVERNAME/K,"
		"DEVICE=DEVICENAME/K,"
		"VOLUME=VOLUMENAME/K,"
		"CACHE=CACHESIZE/N/K,"
		"DEBUGLEVEL=DEBUG/N/K,"
		"TZ=TIMEZONEOFFSET/N/K,"
		"TRANSLATE=TRANSLATIONFILE/K,"
		"SERVICE/A";

	struct Process * this_process;
	UBYTE program_name[MAX_FILENAME_LEN];
	LONG result;
	LONG number;
	LONG other_number;
	LONG cache_size = 0;
	char env_workgroup_name[17];
	char env_user_name[64];
	char env_password[64];

	SysBase = AbsExecBase;

	/* Pick up the Workbench startup message, if
	 * there is one.
	 */
	this_process = (struct Process *)FindTask(NULL);
	if(this_process->pr_CLI == ZERO)
	{
		WaitPort(&this_process->pr_MsgPort);
		WBStartup = (struct WBStartup *)GetMsg(&this_process->pr_MsgPort);
	}
	else
	{
		WBStartup = NULL;
	}

	/* Don't emit any debugging output before we are ready. */
	SETDEBUGLEVEL(0);

	/* Open the libraries we need and check
	 * whether we could get them.
	 */
	DOSBase = OpenLibrary("dos.library",0);
	UtilityBase = OpenLibrary("utility.library",37);

	if(UtilityBase == NULL || DOSBase == NULL || DOSBase->lib_Version < MINIMUM_OS_VERSION)
	{
		/* Complain loudly if this is not the operating
		 * system version we expected.
		 */
		if(DOSBase != NULL && this_process->pr_CLI != ZERO)
		{
			#if (MINIMUM_OS_VERSION < 39)
			STRPTR msg = "AmigaOS 2.04 or higher required.\n";
			#else
			STRPTR msg = "AmigaOS 3.0 or higher required.\n";
			#endif /* MINIMUM_OS_VERSION */

			Write(Output(),msg,strlen(msg));
		}

		Cleanup();

		return(RETURN_FAIL);
	}

	/* This needs to be set up properly for ReportError()
	 * to work.
	 */
	NewList((struct List *)&ErrorList);

	memset(&args,0,sizeof(args));

	/* If this program was launched from Workbench,
	 * parameter passing will have to be handled
	 * differently.
	 */
	if(WBStartup != NULL)
	{
		STRPTR str;
		BPTR old_dir;
		LONG n;

		if(WBStartup->sm_NumArgs > 1)
			n = 1;
		else
			n = 0;

		/* Get the name of the program, as it was launched
		 * from Workbench. We actually prefer the name of
		 * the first project file, if there is one.
		 */
		strncpy(program_name,WBStartup->sm_ArgList[n].wa_Name,sizeof(program_name)-1);
		program_name[sizeof(program_name)-1] = '\0';

		/* Now open icon.library and read that icon. */
		IconBase = OpenLibrary("icon.library",0);
		if(IconBase == NULL)
		{
			ReportError("Could not open 'icon.library'.");

			Cleanup();

			return(RETURN_FAIL);
		}

		old_dir = CurrentDir(WBStartup->sm_ArgList[n].wa_Lock);
		Icon = GetDiskObject(WBStartup->sm_ArgList[n].wa_Name);
		CurrentDir(old_dir);

		if(Icon == NULL)
		{
			ReportError("Icon not found.");

			Cleanup();

			return(RETURN_FAIL);
		}

		/* Examine the icon's tool types and use the
		 * information to fill the startup parameter
		 * data structure.
		 */
		str = FindToolType(Icon->do_ToolTypes,"DOMAIN");
		if(str == NULL)
			str = FindToolType(Icon->do_ToolTypes,"WORKGROUP");

		if(str == NULL)
		{
			if(GetVar("smbfs_domain",env_workgroup_name,sizeof(env_workgroup_name),0) > 0 ||
			   GetVar("smbfs_workgroup",env_workgroup_name,sizeof(env_workgroup_name),0) > 0)
			{
				str = env_workgroup_name;
			}
			else
			{
				ReportError("Required 'WORKGROUP' parameter was not provided.");
				Cleanup();

				return(RETURN_ERROR);
			}
		}

		args.Workgroup = str;

		str = FindToolType(Icon->do_ToolTypes,"USER");
		if(str == NULL)
			str = FindToolType(Icon->do_ToolTypes,"USERNAME");

		if(str == NULL)
		{
			if(GetVar("smbfs_user",env_user_name,sizeof(env_user_name),0) > 0 ||
			   GetVar("smbfs_username",env_user_name,sizeof(env_user_name),0) > 0)
			{
				str = env_user_name;
			}
		}

		args.UserName = str;

		str = FindToolType(Icon->do_ToolTypes,"PASSWORD");
		if(str == NULL)
		{
			if(GetVar("smbfs_password",env_password,sizeof(env_password),0) > 0)
				str = env_password;
		}

		args.Password = str;

		if(FindToolType(Icon->do_ToolTypes,"CHANGECASE") != NULL)
			args.ChangeCase = TRUE;

		if(FindToolType(Icon->do_ToolTypes,"OMITHIDDEN") != NULL)
			args.OmitHidden = TRUE;

		if(FindToolType(Icon->do_ToolTypes,"QUIET") != NULL)
			args.Quiet = TRUE;

		if(FindToolType(Icon->do_ToolTypes,"CASE") != NULL ||
		   FindToolType(Icon->do_ToolTypes,"CASESENSITIVE") != NULL)
		{
			args.CaseSensitive = TRUE;
		}

		str = FindToolType(Icon->do_ToolTypes,"CLIENT");
		if(str == NULL)
			str = FindToolType(Icon->do_ToolTypes,"CLIENTNAME");

		args.ClientName = str;

		str = FindToolType(Icon->do_ToolTypes,"SERVER");
		if(str == NULL)
			str = FindToolType(Icon->do_ToolTypes,"SERVERNAME");

		args.ServerName = str;

		str = FindToolType(Icon->do_ToolTypes,"DEVICE");
		if(str == NULL)
			str = FindToolType(Icon->do_ToolTypes,"DEVICENAME");

		args.DeviceName = str;

		str = FindToolType(Icon->do_ToolTypes,"VOLUME");
		if(str == NULL)
			str = FindToolType(Icon->do_ToolTypes,"VOLUMENAME");

		args.VolumeName = str;

		str = FindToolType(Icon->do_ToolTypes,"TRANSLATE");
		if(str == NULL)
			str = FindToolType(Icon->do_ToolTypes,"TRANSLATIONFILE");

		args.TranslationFile = str;

		str = FindToolType(Icon->do_ToolTypes,"SERVICE");
		args.Service = str;

		if(str != NULL)
		{
			/* Set up the name of the program, as it will be
			 * displayed in error requesters.
			 */
			NewProgramName = AllocVec(strlen(WBStartup->sm_ArgList[0].wa_Name) + strlen(" ''") + strlen(str)+1,MEMF_ANY|MEMF_PUBLIC);
			if(NewProgramName != NULL)
				SPrintf(NewProgramName,"%s '%s'",WBStartup->sm_ArgList[0].wa_Name,str);
		}

		str = FindToolType(Icon->do_ToolTypes,"DEBUG");
		if(str == NULL)
			str = FindToolType(Icon->do_ToolTypes,"DEBUGLEVEL");

		if(str != NULL)
		{
			if(StrToLong(str,&number) == -1)
			{
				ReportError("Invalid number '%s' for 'DEBUG' parameter.",str);
				Cleanup();

				return(RETURN_ERROR);
			}

			args.DebugLevel = &number;
		}

		str = FindToolType(Icon->do_ToolTypes,"TZ");
		if(str == NULL)
			str = FindToolType(Icon->do_ToolTypes,"TIMEZONEOFFSET");

		if(str != NULL)
		{
			if(StrToLong(str,&other_number) == -1)
			{
				ReportError("Invalid number '%s' for 'TIMEZONEOFFSET' parameter.",str);
				Cleanup();

				return(RETURN_ERROR);
			}

			args.TimeZoneOffset = &other_number;
		}

		str = FindToolType(Icon->do_ToolTypes,"CACHE");
		if(str == NULL)
			str = FindToolType(Icon->do_ToolTypes,"CACHESIZE");

		if(str != NULL)
		{
			if(StrToLong(str,&number) == -1)
			{
				ReportError("Invalid number '%s' for 'CACHE' parameter.",str);
				Cleanup();

				return(RETURN_ERROR);
			}

			cache_size = number;
		}

		if(args.Workgroup == NULL)
		{
			ReportError("Required 'WORKGROUP' parameter was not provided.");
			Cleanup();

			return(RETURN_ERROR);
		}

		if(args.Service == NULL)
		{
			ReportError("'SERVICE' parameter needs an argument.");
			Cleanup();

			return(RETURN_ERROR);
		}
	}
	else
	{
		GetProgramName(program_name,sizeof(program_name));

		Parameters = ReadArgs(cmd_template,(LONG *)&args,NULL);
		if(Parameters == NULL)
		{
			PrintFault(IoErr(),FilePart(program_name));
			Cleanup();

			return(RETURN_ERROR);
		}

		if(args.Workgroup == NULL)
		{
			if(GetVar("smbfs_domain",env_workgroup_name,sizeof(env_workgroup_name),0) > 0 ||
			   GetVar("smbfs_workgroup",env_workgroup_name,sizeof(env_workgroup_name),0) > 0)
			{
				args.Workgroup = env_workgroup_name;
			}
			else
			{
				ReportError("Required 'WORKGROUP' parameter was not provided.");
				Cleanup();

				return(RETURN_ERROR);
			}
		}

		if(args.UserName == NULL)
		{
			if(GetVar("smbfs_user",env_user_name,sizeof(env_user_name),0) > 0 ||
			   GetVar("smbfs_username",env_user_name,sizeof(env_user_name),0) > 0)
			{
				args.UserName = env_user_name;
			}
		}

		if(args.Password == NULL)
		{
			if(GetVar("smbfs_password",env_password,sizeof(env_password),0) > 0)
				args.Password = env_password;
		}

		if(args.Service != NULL)
		{
			STRPTR name = FilePart(program_name);

			/* Set up the name of the program, as it will be
			 * displayed in the proces status list.
			 */
			NewProgramName = AllocVec(strlen(name) + strlen(" ''") + strlen(args.Service)+1,MEMF_ANY|MEMF_PUBLIC);
			if(NewProgramName != NULL)
				SPrintf(NewProgramName,"%s '%s'",name,args.Service);
		}

		if(args.CacheSize != NULL)
			cache_size = (*args.CacheSize);
	}

	/* Use the default if no user name is given. */
	if(args.UserName == NULL)
		args.UserName = "GUEST";

	/* Use the default if no device or volume name is given. */
	if(args.DeviceName == NULL && args.VolumeName == NULL)
		args.DeviceName = "SMBFS";

	CaseSensitive = (BOOL)args.CaseSensitive;
	OmitHidden = (BOOL)args.OmitHidden;

	/* Configure the debugging options. */
	SETPROGRAMNAME(FilePart(program_name));

	if(args.DebugLevel != NULL)
		SETDEBUGLEVEL(*args.DebugLevel);
	else
		SETDEBUGLEVEL(0);

	D(("%s (%s)",VERS,DATE));

	if(Setup(
		FilePart(program_name),
		args.Service,
		args.Workgroup,
		args.UserName,
		args.Password,
		args.ChangeCase,
		args.ClientName,
		args.ServerName,
		cache_size,
		args.TimeZoneOffset,
		args.DeviceName,
		args.VolumeName,
		args.TranslationFile))
	{
		Quiet = args.Quiet;

		if(Locale != NULL)
			SHOWVALUE(Locale->loc_GMTOffset);

		HandleFileSystem(args.DeviceName,args.VolumeName,args.Service);

		result = RETURN_WARN;
	}
	else
	{
		result = RETURN_ERROR;
	}

	Cleanup();
	return(result);
}

/****************************************************************************/

LONG
LocalPrintf(STRPTR format, ...)
{
	va_list args;
	LONG result;

	va_start(args,format);
	result = VPrintf(format,args);
	va_end(args);

	return(result);
}

/****************************************************************************/

/* Obtain the descriptive text corresponding to an error number
 * that may have been generated by the TCP/IP stack.
 */
STRPTR
amitcp_strerror(int error)
{
	struct TagItem tags[2];
	STRPTR result;

	ENTER();

	tags[0].ti_Tag	= SBTM_GETVAL(SBTC_ERRNOSTRPTR);
	tags[0].ti_Data	= error;
	tags[1].ti_Tag	= TAG_END;

	SocketBaseTagList(tags);

	result = (STRPTR)tags[0].ti_Data;

	RETURN(result);
	return(result);
}

/****************************************************************************/

/* Return the descriptive text associated with a host lookup failure code. */
STRPTR
host_strerror(int error)
{
	struct TagItem tags[2];
	STRPTR result;

	ENTER();

	tags[0].ti_Tag	= SBTM_GETVAL(SBTC_HERRNOSTRPTR);
	tags[0].ti_Data	= error;
	tags[1].ti_Tag	= TAG_END;

	SocketBaseTagList(tags);

	result = (STRPTR)tags[0].ti_Data;

	RETURN(result);
	return(result);
}

/****************************************************************************/

/* Compare two strings, either case sensitive or not
 * sensitive to the case of the letters. How this is
 * to be done is controlled by a global option. This
 * routine is called whenever two SMB file names are
 * to be compared.
 */
LONG
CompareNames(STRPTR a,STRPTR b)
{
	LONG result;

	if(CaseSensitive)
		result = strcmp(a,b);
	else
		result = Stricmp(a,b);

	return(result);
}

/****************************************************************************/

/* Translate a string into all upper case characters. */
VOID
StringToUpper(STRPTR s)
{
	UBYTE c;

	while((c = (*s)) != '\0')
		(*s++) = ToUpper(c);
}

/****************************************************************************/

/* Prepare the accumulated list of error messages for display
 * and purge the contents of that list.
 */
STATIC VOID
DisplayErrorList(VOID)
{
	struct MinNode * last = NULL;
	struct MinNode * mn;
	STRPTR str = NULL;
	STRPTR msg;
	LONG len;

	/* Determine how much memory will have to be
	 * allocated to hold all the accumulated
	 * error messages.
	 */
	len = 0;

	for(mn = ErrorList.mlh_Head ;
	    mn->mln_Succ != NULL ;
	    mn = mn->mln_Succ)
	{
		last = mn;

		msg = (STRPTR)(mn + 1);

		len += strlen(msg)+1;
	}

	/* Allocate the memory for the messages, then
	 * copy them there.
	 */
	if(len > 0)
	{
		str = AllocVec(len,MEMF_ANY);
		if(str != NULL)
		{
			str[0] = '\0';

			for(mn = ErrorList.mlh_Head ;
			    mn->mln_Succ != NULL ;
			    mn = mn->mln_Succ)
			{
				msg = (STRPTR)(mn + 1);

				strcat(str,msg);
				if(mn != last)
					strcat(str,"\n");
			}
		}
	}

	/* Purge the list. */
	while((mn = (struct MinNode *)RemHead((struct List *)&ErrorList)) != NULL)
		FreeVec(mn);

	/* Display the error messages. */
	if(str != NULL)
	{
		struct Library * IntuitionBase;

		IntuitionBase = OpenLibrary("intuition.library",37);
		if(IntuitionBase != NULL)
		{
			struct EasyStruct es;
			STRPTR title;

			memset(&es,0,sizeof(es));

			if(NewProgramName == NULL)
				title = WBStartup->sm_ArgList[0].wa_Name;
			else
				title = NewProgramName;

			es.es_StructSize	= sizeof(es);
			es.es_Title			= title;
			es.es_TextFormat	= str;
			es.es_GadgetFormat	= "Ok";

			EasyRequestArgs(NULL,&es,NULL,NULL);

			CloseLibrary(IntuitionBase);
		}

		FreeVec(str);
	}
}

/* Add another error message to the list; the messages are
 * collected so that they may be displayed together when
 * necessary.
 */
STATIC VOID
AddError(STRPTR fmt,va_list args)
{
	LONG len;

	len = CVSPrintf(fmt,args);
	if(len > 0)
	{
		struct MinNode * mn;

		mn = AllocVec(sizeof(*mn) + len,MEMF_ANY|MEMF_PUBLIC);
		if(mn != NULL)
		{
			STRPTR msg = (STRPTR)(mn + 1);

			VSPrintf(msg,fmt,args);

			AddTail((struct List *)&ErrorList,(struct Node *)mn);
		}
	}
}

/****************************************************************************/

/* Report an error that has occured; if the program was not launched
 * from Shell, error messages will be accumulated for later display.
 */
VOID
ReportError(STRPTR fmt,...)
{
	if(NOT Quiet)
	{
		va_list args;

		if(WBStartup != NULL)
		{
			va_start(args,fmt);
			AddError(fmt,args);
			va_end(args);
		}
		else
		{
			UBYTE program_name[MAX_FILENAME_LEN];

			GetProgramName(program_name,sizeof(program_name));

			LocalPrintf("%s: ",FilePart(program_name));

			va_start(args,fmt);
			VPrintf(fmt,args);
			va_end(args);

			LocalPrintf("\n");
		}
	}
}

/****************************************************************************/

/* Release memory allocated from the global pool. */
VOID
FreeVecPooled(APTR address)
{
	if(address != NULL)
	{
		ULONG * mem = address;

		#if DEBUG
		{
			if(GETDEBUGLEVEL() > 0)
				memset(address,0xA3,mem[-1] - sizeof(*mem));
		}
		#endif /* DEBUG */

		FreePooled(MemoryPool,&mem[-1],mem[-1]);
	}
}

/* Allocate memory from the global pool. */
APTR
AllocVecPooled(ULONG size)
{
	APTR result = NULL;

	if(size > 0)
	{
		ULONG * mem;

		size = (sizeof(*mem) + size + 7) & ~7UL;

		mem = AllocPooled(MemoryPool,size);
		if(mem != NULL)
		{
			(*mem++) = size;

			#if DEBUG
			{
				if(GETDEBUGLEVEL() > 0)
					memset(mem,0xA5,mem[-1] - sizeof(*mem));
			}
			#endif /* DEBUG */

			result = mem;
		}
	}

	return(result);
}

/****************************************************************************/

/* Obtain the number of seconds to add to the current time
 * to translate local time into UTC.
 */
LONG
GetTimeZoneDelta(VOID)
{
	LONG seconds;

	if(OverrideLocaleTimeZone)
	{
		seconds = 60 * TimeZoneOffset;
	}
	else if (Locale != NULL)
	{
		/* The GMT offset actually is the number of minutes to add to
		 * the local time to yield Greenwich Mean Time. It is negative
		 * for all time zones east of the Greenwich meridian and
		 * positive for all time zones west of it.
		 */
		seconds = 60 * Locale->loc_GMTOffset;
	}
	else
	{
		seconds = 0;
	}

	return(seconds);
}

/****************************************************************************/

/* Obtain the current time, in standard Unix format, adjusted for the
 * local time zone.
 */
ULONG
GetCurrentTime(VOID)
{
	struct timeval tv;
	ULONG result;

	GetSysTime((APTR)&tv);

	result = UNIX_TIME_OFFSET + GetTimeZoneDelta() + tv.tv_secs;

	return(result);
}

/****************************************************************************/

/* Fill in a 'tm' type time specification with time information
 * corresponding to the number of seconds provided. Input is
 * in Unix format.
 */
VOID
LocalTime(time_t seconds,struct tm * tm)
{
	struct ClockData clock_data;

	seconds -= GetTimeZoneDelta();

	if(seconds < UNIX_TIME_OFFSET)
		seconds = 0;
	else
		seconds -= UNIX_TIME_OFFSET;

	Amiga2Date(seconds,&clock_data);

	memset(tm,0,sizeof(*tm));

	tm->tm_sec	= clock_data.sec;
	tm->tm_min	= clock_data.min;
	tm->tm_hour	= clock_data.hour;
	tm->tm_mday	= clock_data.mday;
	tm->tm_mon	= clock_data.month - 1;
	tm->tm_year	= clock_data.year - 1900;
}

/* Calculate the number of seconds that have passed since January 1st 1970
 * based upon the time specification provided. Output is in Unix format.
 */
time_t
MakeTime(const struct tm * const tm)
{
	struct ClockData clock_data;
	time_t seconds;

	clock_data.sec		= tm->tm_sec;
	clock_data.min		= tm->tm_min;
	clock_data.hour		= tm->tm_hour;
	clock_data.mday		= tm->tm_mday;
	clock_data.month	= tm->tm_mon + 1;
	clock_data.year		= tm->tm_year + 1900;

	seconds = Date2Amiga(&clock_data) + UNIX_TIME_OFFSET + GetTimeZoneDelta();

	return(seconds);
}

/****************************************************************************/

struct FormatContext
{
	UBYTE *	fc_Buffer;
	LONG	fc_Size;
};

/****************************************************************************/

STATIC VOID ASM
CountChar(REG(a3,struct FormatContext * fc))
{
	fc->fc_Size++;
}

/* Count the number of characters SPrintf() would put into a string. */
STATIC LONG
CVSPrintf(STRPTR format_string,va_list args)
{
	struct FormatContext fc;

	fc.fc_Size = 0;

	RawDoFmt((STRPTR)format_string,args,(VOID (*)())CountChar,&fc);

	return(fc.fc_Size);
}

/****************************************************************************/

STATIC VOID ASM
StuffChar(REG(d0,UBYTE c),REG(a3,struct FormatContext * fc))
{
	(*fc->fc_Buffer++) = c;
}

STATIC VOID
VSPrintf(STRPTR buffer, STRPTR formatString, va_list args)
{
	struct FormatContext fc;

	fc.fc_Buffer = buffer;

	RawDoFmt(formatString,args,(VOID (*)())StuffChar,&fc);
}

/****************************************************************************/

/* Format a string for output. */
VOID
SPrintf(STRPTR buffer, STRPTR formatString,...)
{
	va_list varArgs;

	va_start(varArgs,formatString);
	VSPrintf(buffer,formatString,varArgs);
	va_end(varArgs);
}

/****************************************************************************/

/* NetBIOS broadcast name query code courtesy of Christopher R. Hertel.
 * Thanks much, Chris!
 */
struct addr_entry
{
	unsigned short flags;
	unsigned char address[4];
};

struct nmb_header
{
	unsigned short name_trn_id;
	unsigned short flags;
	unsigned short qdcount;
	unsigned short ancount;
	unsigned short nscount;
	unsigned short arcount;
};

static UBYTE *
L1_Encode(UBYTE * dst, const UBYTE * name, const UBYTE pad, const UBYTE sfx)
{
	int i = 0;
	int j = 0;
	int k;

	while(('\0' != name[i]) && (i < 15))
	{
		k = ToUpper(name[i]);
		i++;
		dst[j++] = 'A' + ((k & 0xF0) >> 4);
		dst[j++] = 'A' + (k & 0x0F);
	}

	i = 'A' + ((pad & 0xF0) >> 4);
	k = 'A' + (pad & 0x0F);

	while(j < 30)
	{
		dst[j++] = i;
		dst[j++] = k;
	}

	dst[30] = 'A' + ((sfx & 0xF0) >> 4);
	dst[31] = 'A' + (sfx & 0x0F);
	dst[32] = '\0';

	return(dst);
}

static int
L2_Encode(UBYTE * dst, const UBYTE * name, const UBYTE pad, const UBYTE sfx, const UBYTE * scope)
{
	int lenpos;
	int i;
	int j;

	if(NULL == L1_Encode(&dst[1], name, pad, sfx))
		return(-1);

	dst[0] = 0x20;
	lenpos = 33;

	if('\0' != (*scope))
	{
		do
		{
			for(i = 0, j = (lenpos + 1);
			    ('.' != scope[i]) && ('\0' != scope[i]);
			    i++, j++)
			{
				dst[j] = ToUpper(scope[i]);
			}

			dst[lenpos] = (UBYTE)i;
			lenpos += i + 1;
			scope += i;
		}
		while('.' == (*scope++));

		dst[lenpos] = '\0';
	}

	return(lenpos + 1);
}

int
BroadcastNameQuery(char *name, char *scope, UBYTE *address)
{
	static const UBYTE header[12] =
	{
		0x07, 0xB0,	/* 1964 == 0x07B0. */
		0x01, 0x10,	/* Binary 0 0000 0010001 0000 */
		0x00, 0x01,	/* One name query. */
		0x00, 0x00,	/* Zero answers. */
		0x00, 0x00,	/* Zero authorities. */
		0x00, 0x00	/* Zero additional. */
	};

	static const UBYTE query_tail[4] =
	{
		0x00, 0x20,
		0x00, 0x01
	};

	struct timeval tv;
	fd_set read_fds;
	int sock_fd;
	int option_true = 1;
	struct sockaddr_in sox;
	struct nmb_header nmb_header;
	UBYTE buffer[512];
	int total_len;
	int i,n;
	int result;

	ENTER();

	sock_fd = socket(PF_INET, SOCK_DGRAM, IPPROTO_UDP);
	if(sock_fd < 0)
	{
		SHOWMSG("couldn't get the socket");
		result = (-errno);
		goto out;
	}

	if(setsockopt(sock_fd, SOL_SOCKET, SO_BROADCAST, &option_true, sizeof(option_true)) < 0)
	{
		SHOWMSG("couldn't enable the broadcast option");
		result = (-errno);
		goto out;
	}

	sox.sin_family = AF_INET;
	sox.sin_port = htons(137);
	sox.sin_addr.s_addr = htonl(0xFFFFFFFF);

	memcpy(buffer, header, (total_len = sizeof(header)));

	n = L2_Encode(&buffer[total_len], name, ' ', '\0', scope);
	if(n < 0)
	{
		SHOWMSG("name encoding failed");
		result = (-EINVAL);
		goto out;
	}

	total_len += n;
	memcpy(&buffer[total_len], query_tail, sizeof(query_tail));
	total_len += sizeof(query_tail);

	result = (-ENOENT);
	n = 0;

	/* Send the query packet; retry five times with a one second
	 * delay in between.
	 */
	for(i = 0 ; i < 5 ; i++)
	{
		if(sendto(sock_fd, (void *) buffer, total_len, 0, (struct sockaddr *)&sox, sizeof(struct sockaddr_in)) < 0)
		{
			SHOWMSG("could not send the packet");
			result = (-errno);
			goto out;
		}

		/* Wait for a response to arrive. */
		tv.tv_secs = 1;
		tv.tv_micro = 0;

		FD_ZERO(&read_fds);
		FD_SET(sock_fd,&read_fds);

		if(WaitSelect(sock_fd+1, &read_fds, NULL, NULL, &tv, NULL) > 0)
		{
			n = recvfrom(sock_fd, buffer, sizeof(buffer), 0, NULL, NULL);
			if(n < 0)
			{
				SHOWMSG("could not pick up the response packet");
				result = (-errno);
				goto out;
			}
			else if (n > 0)
			{
				break;
			}
		}
	}

	/* Did we get anything at all? */
	if(n > (int)sizeof(nmb_header))
	{
		/* Check whether the query was successful. */
		memcpy(&nmb_header, buffer, sizeof(nmb_header));
		if((nmb_header.flags & 0xF) == OK)
		{
			/* Find the NB/IP fields which directly follow
			 * the name.
			 */
			for(i = sizeof(header) + strlen(&buffer[sizeof(header)])+1 ; i < n - (int)sizeof(query_tail) ; i++)
			{
				if(memcmp(&buffer[i], query_tail, sizeof(query_tail)) == SAME)
				{
					int start;

					/* This should be the start of the interesting bits;
					 * we skip the NB/IP fields and the TTL field.
					 */
					start = i + sizeof(query_tail) + sizeof(long);
					if(start < n)
					{
						unsigned short read_len;
						struct addr_entry addr_entry;

						/* This should be the read length. */
						memcpy(&read_len, &buffer[start], 2);

						/* Is there any useful and readable data attached? */
						if(read_len >= sizeof(addr_entry) &&
						   start + (int)sizeof(read_len) + (int)sizeof(addr_entry) <= n)
						{
							/* Copy a single address entry; this should be
							 * just the one we need.
							 */
							memcpy(&addr_entry, &buffer[start + sizeof(read_len)], sizeof(addr_entry));

							/* Copy the address field (IPv4 only). */
							memcpy(address, addr_entry.address, 4);

							result = 0;
						}
					}

					break;
				}
			}
		}
	}

out:

	if(sock_fd >= 0)
		CloseSocket(sock_fd);

	RETURN(result);
	return(result);
}

/****************************************************************************/

/* Send a disk change notification message which will be picked up
 * by all applications that listen for this kind of event, e.g.
 * Workbench.
 */
STATIC VOID
SendDiskChange(ULONG class)
{
	struct IOStdReq * input_request = NULL;
	struct MsgPort * input_port;
	struct InputEvent ie;

	ENTER();

	input_port = CreateMsgPort();
	if(input_port == NULL)
		goto out;

	input_request = CreateIORequest(input_port,sizeof(*input_request));
	if(input_request == NULL)
		goto out;

	if(OpenDevice("input.device",0,(struct IORequest *)input_request,0) != OK)
		goto out;

	memset(&ie,0,sizeof(ie));

	ie.ie_Class		= class;
	ie.ie_Qualifier	= IEQUALIFIER_MULTIBROADCAST;
	GetSysTime(&ie.ie_TimeStamp);

	input_request->io_Command	= IND_WRITEEVENT;
	input_request->io_Data		= &ie;
	input_request->io_Length	= sizeof(ie);

	DoIO((struct IORequest *)input_request);

 out:

	if(input_request != NULL)
	{
		if(input_request->io_Device != NULL)
			CloseDevice((struct IORequest *)input_request);

		DeleteIORequest((struct IORequest *)input_request);
	}

	DeleteMsgPort(input_port);

	LEAVE();
}

/****************************************************************************/

/* Find the file node corresponding to a given name,
 * skipping a particular entry if necessary.
 */
STATIC struct FileNode *
FindFileNode(STRPTR name,struct FileNode * skip)
{
	struct FileNode * result = NULL;
	struct FileNode * fn;

	for(fn = (struct FileNode *)FileList.mlh_Head ;
	    fn->fn_MinNode.mln_Succ != NULL ;
	    fn = (struct FileNode *)fn->fn_MinNode.mln_Succ)
	{
		if(fn != skip && CompareNames(name,fn->fn_FullName) == SAME)
		{
			result = fn;
			break;
		}
	}

	return(result);
}

/* Find the lock node corresponding to a given name,
 * skipping a particular entry if necessary.
 */
STATIC struct LockNode *
FindLockNode(STRPTR name,struct LockNode * skip)
{
	struct LockNode * result = NULL;
	struct LockNode * ln;

	for(ln = (struct LockNode *)LockList.mlh_Head ;
	    ln->ln_MinNode.mln_Succ != NULL ;
	    ln = (struct LockNode *)ln->ln_MinNode.mln_Succ)
	{
		if(ln != skip && CompareNames(name,ln->ln_FullName) == SAME)
		{
			result = ln;
			break;
		}
	}

	return(result);
}

/* Check whether a new reference to be made to a named
 * file will cause a conflict of access modes. No two
 * files and locks may refer to the same object if
 * either of these references is made in exclusive
 * mode. This is the case which this function is
 * trying to avoid.
 */
STATIC LONG
CheckAccessModeCollision(STRPTR name,LONG mode)
{
	struct LockNode * ln;
	struct FileNode * fn;
	LONG error = OK;

	ENTER();
	SHOWSTRING(name);

	fn = FindFileNode(name,NULL);
	if(fn != NULL)
	{
		if(mode != SHARED_LOCK || fn->fn_Mode != SHARED_LOCK)
		{
			D(("collides with '%s'",fn->fn_FullName));
			error = ERROR_OBJECT_IN_USE;
			goto out;
		}
	}

	ln = FindLockNode(name,NULL);
	if(ln != NULL)
	{
		if(mode != SHARED_LOCK || ln->ln_FileLock.fl_Access != SHARED_LOCK)
		{
			D(("collides with '%s'",ln->ln_FullName));
			error = ERROR_OBJECT_IN_USE;
			goto out;
		}
	}

 out:

	RETURN(error);
	return(error);
}

/* Find out whether there already exists a reference to a
 * certain file or directory.
 */
STATIC LONG
NameAlreadyInUse(STRPTR name)
{
	LONG error = OK;

	ENTER();

	if(FindFileNode(name,NULL) != NULL)
	{
		error = ERROR_OBJECT_IN_USE;
		goto out;
	}

	if(FindLockNode(name,NULL) != NULL)
	{
		error = ERROR_OBJECT_IN_USE;
		goto out;
	}

 out:

	RETURN(error);
	return(error);
}

/* Check whether an Amiga file name uses special characters which
 * should be avoided when used with the SMB file sharing protocol.
 */
STATIC BOOL
IsReservedName(STRPTR name)
{
	BOOL result = FALSE;

	/* Disallow "." and "..". */
	if(name[0] == '.' && (name[1] == '\0' || (name[1] == '.' && name[2] == '\0')))
	{
		result = TRUE;
	}
	else
	{
		UBYTE c;

		/* Disallow the use of the backslash in file names. */
		while((c = (*name++)) != '\0')
		{
			if(c == SMB_PATH_SEPARATOR)
			{
				result = TRUE;
				break;
			}
		}
	}

	return(result);
}

/****************************************************************************/

/* Convert a POSIX error code into an AmigaDOS error code. */
STATIC LONG
MapErrnoToIoErr(int error)
{
	/* Not all of these mappings make good sense; bear in mind that
	 * POSIX covers more than a hundred different error codes
	 * whereas with AmigaDOS we're stranded with a measly 48...
	 */
	STATIC const LONG Map[][2] =
	{
		EPERM,				ERROR_OBJECT_NOT_FOUND,		/* Operation not permitted */
		ENOENT,				ERROR_OBJECT_NOT_FOUND,		/* No such file or directory */
		ESRCH,				ERROR_OBJECT_NOT_FOUND,		/* No such process */
		EINTR,				ERROR_BREAK,				/* Interrupted system call */
		EIO,				ERROR_OBJECT_IN_USE,		/* Input/output error */
		E2BIG,				ERROR_TOO_MANY_ARGS,		/* Argument list too long */
		EBADF,				ERROR_INVALID_LOCK,			/* Bad file descriptor */
		ENOMEM,				ERROR_NO_FREE_STORE,		/* Cannot allocate memory */
		EACCES,				ERROR_OBJECT_IN_USE,		/* Permission denied */
		ENOTBLK,			ERROR_OBJECT_WRONG_TYPE,	/* Block device required */
		EBUSY,				ERROR_OBJECT_IN_USE,		/* Device busy */
		EEXIST,				ERROR_OBJECT_EXISTS,		/* File exists */
		EXDEV,				ERROR_NOT_IMPLEMENTED,		/* Cross-device link */
		ENOTDIR,			ERROR_OBJECT_WRONG_TYPE,	/* Not a directory */
		EISDIR,				ERROR_OBJECT_WRONG_TYPE,	/* Is a directory */
		EINVAL,				ERROR_BAD_NUMBER,			/* Invalid argument */
		EFBIG,				ERROR_DISK_FULL,			/* File too large */
		ENOSPC,				ERROR_DISK_FULL,			/* No space left on device */
		ESPIPE,				ERROR_SEEK_ERROR,			/* Illegal seek */
		EROFS,				ERROR_WRITE_PROTECTED,		/* Read-only file system */
		EMLINK,				ERROR_TOO_MANY_LEVELS,		/* Too many links */
		ENOTSOCK,			ERROR_OBJECT_WRONG_TYPE,	/* Socket operation on non-socket */
		EDESTADDRREQ,		ERROR_REQUIRED_ARG_MISSING,	/* Destination address required */
		EMSGSIZE,			ERROR_LINE_TOO_LONG,		/* Message too long */
		EPROTOTYPE,			ERROR_BAD_TEMPLATE,			/* Protocol wrong type for socket */
		ENOPROTOOPT,		ERROR_NOT_IMPLEMENTED,		/* Protocol not available */
		EPROTONOSUPPORT,	ERROR_NOT_IMPLEMENTED,		/* Protocol not supported */
		ESOCKTNOSUPPORT,	ERROR_NOT_IMPLEMENTED,		/* Socket type not supported */
		EOPNOTSUPP,			ERROR_NOT_IMPLEMENTED,		/* Operation not supported */
		EPFNOSUPPORT,		ERROR_NOT_IMPLEMENTED,		/* Protocol family not supported */
		EAFNOSUPPORT,		ERROR_NOT_IMPLEMENTED,		/* Address family not supported by protocol family */
		EADDRINUSE,			ERROR_OBJECT_IN_USE,		/* Address already in use */
		EADDRNOTAVAIL,		ERROR_OBJECT_NOT_FOUND,		/* Can't assign requested address */
		ENETDOWN,			ERROR_OBJECT_NOT_FOUND,		/* Network is down */
		ENETUNREACH,		ERROR_OBJECT_NOT_FOUND,		/* Network is unreachable */
		ENETRESET,			ERROR_OBJECT_NOT_FOUND,		/* Network dropped connection on reset */
		ECONNABORTED,		ERROR_OBJECT_NOT_FOUND,		/* Software caused connection abort */
		ECONNRESET,			ERROR_OBJECT_NOT_FOUND,		/* Connection reset by peer */
		ENOBUFS,			ERROR_DISK_FULL,			/* No buffer space available */
		EISCONN,			ERROR_OBJECT_IN_USE,		/* Socket is already connected */
		ENOTCONN,			ERROR_OBJECT_WRONG_TYPE,	/* Socket is not connected */
		ESHUTDOWN,			ERROR_INVALID_LOCK,			/* Can't send after socket shutdown */
		ECONNREFUSED,		ERROR_OBJECT_IN_USE,		/* Connection refused */
		ELOOP,				ERROR_TOO_MANY_LEVELS,		/* Too many levels of symbolic links */
		ENAMETOOLONG,		ERROR_LINE_TOO_LONG,		/* File name too long */
		EHOSTDOWN,			ERROR_OBJECT_NOT_FOUND,		/* Host is down */
		EHOSTUNREACH,		ERROR_OBJECT_NOT_FOUND,		/* No route to host */
		ENOTEMPTY,			ERROR_DIRECTORY_NOT_EMPTY,	/* Directory not empty */
		EPROCLIM,			ERROR_TASK_TABLE_FULL,		/* Too many processes */
		EUSERS,				ERROR_TASK_TABLE_FULL,		/* Too many users */
		EDQUOT,				ERROR_DISK_FULL,			/* Disc quota exceeded */
		-1
	};

	LONG result = ERROR_ACTION_NOT_KNOWN;
	LONG i;

	ENTER();

	if(error < 0)
		error = (-error);

	for(i = 0 ; Map[i][0] != -1 ; i++)
	{
		if(Map[i][0] == error)
		{
			result = Map[i][1];
			break;
		}
	}

	RETURN(result);
	return(result);
}

/****************************************************************************/

/* Translate a BCPL style file name (i.e. length is in the first byte)
 * via a mapping table.
 */
INLINE STATIC VOID
TranslateBName(UBYTE * name,UBYTE * map)
{
	if(TranslateNames)
	{
		LONG len;
		UBYTE c;

		len = (*name++);

		while(len-- > 0)
		{
			c = (*name);

			(*name++) = map[c];
		}
	}
}

/* Translate a NUL terminated file name via a mapping table. */
INLINE STATIC VOID
TranslateCName(UBYTE * name,UBYTE * map)
{
	if(TranslateNames)
	{
		UBYTE c;

		while((c = (*name)) != '\0')
			(*name++) = map[c];
	}
}

/****************************************************************************/

/* Remove a DosList entry using the proper protocols. Note that
 * this function can fail!
 */
STATIC BOOL
ReallyRemoveDosEntry(struct DosList * entry)
{
	struct Message * mn;
	struct MsgPort * port;
	struct DosList * dl;
	BOOL result = FALSE;
	LONG kind,i;

	if(entry->dol_Type == DLT_DEVICE)
		kind = LDF_DEVICES;
	else
		kind = LDF_VOLUMES;

	port = entry->dol_Task;

	for(i = 0 ; i < 100 ; i++)
	{
		dl = AttemptLockDosList(LDF_WRITE|kind);
		if(((ULONG)dl) <= 1)
			dl = NULL;

		if(dl != NULL)
		{
			RemDosEntry(entry);

			UnLockDosList(LDF_WRITE|kind);

			result = TRUE;

			break;
		}

		while((mn = GetMsg(port)) != NULL)
			ReplyPkt((struct DosPacket *)mn->mn_Node.ln_Name,DOSFALSE,ERROR_ACTION_NOT_KNOWN);

		Delay(TICKS_PER_SECOND / 10);
	}

	return(result);
}

/****************************************************************************/

/* Release all resources allocated by the Setup() routine. */
STATIC VOID
Cleanup(VOID)
{
	BOOL send_disk_change = FALSE;

	ENTER();

	/* If any errors have cropped up, display them now before
	 * call it quits.
	 */
	DisplayErrorList();

	if(NewProgramName != NULL)
	{
		FreeVec(NewProgramName);
		NewProgramName = NULL;
	}

	if(Parameters != NULL)
	{
		FreeArgs(Parameters);
		Parameters = NULL;
	}

	if(Icon != NULL)
	{
		FreeDiskObject(Icon);
		Icon = NULL;
	}

	if(ServerData != NULL)
	{
		smba_disconnect(ServerData);
		ServerData = NULL;
	}

	if(DeviceNode != NULL)
	{
		if(DeviceNodeAdded)
		{
			if(ReallyRemoveDosEntry(DeviceNode))
				FreeDosEntry(DeviceNode);
		}
		else
		{
			FreeDosEntry(DeviceNode);
		}

		DeviceNode = NULL;
	}

	if(VolumeNode != NULL)
	{
		if(VolumeNodeAdded)
		{
			if(ReallyRemoveDosEntry(VolumeNode))
				FreeDosEntry(VolumeNode);

			send_disk_change = TRUE;
		}
		else
		{
			FreeDosEntry(VolumeNode);
		}

		VolumeNode = NULL;
	}

	if(FileSystemPort != NULL)
	{
		struct Message * mn;

		/* Return all queued packets; there should be none, though. */
		while((mn = GetMsg(FileSystemPort)) != NULL)
			ReplyPkt((struct DosPacket *)mn->mn_Node.ln_Name,DOSFALSE,ERROR_ACTION_NOT_KNOWN);

		DeleteMsgPort(FileSystemPort);
		FileSystemPort = NULL;
	}

	if(WBStartup == NULL && send_disk_change)
		SendDiskChange(IECLASS_DISKREMOVED);

	if(TimerBase != NULL)
	{
		CloseDevice((struct IORequest *)&TimerRequest);
		TimerBase = NULL;
	}

	if(SocketBase != NULL)
	{
		CloseLibrary(SocketBase);
		SocketBase = NULL;
	}

	if(UtilityBase != NULL)
	{
		CloseLibrary(UtilityBase);
		UtilityBase = NULL;
	}

	if(IconBase != NULL)
	{
		CloseLibrary(IconBase);
		IconBase = NULL;
	}

	if(Locale != NULL)
	{
		CloseLocale(Locale);
		Locale = NULL;
	}

	if(LocaleBase != NULL)
	{
		CloseLibrary(LocaleBase);
		LocaleBase = NULL;
	}

	if(MemoryPool != NULL)
	{
		DeletePool(MemoryPool);
		MemoryPool = NULL;
	}

	if(DOSBase != NULL)
	{
		CloseLibrary(DOSBase);
		DOSBase = NULL;
	}

	if(WBStartup != NULL)
	{
		Forbid();
		ReplyMsg((struct Message *)WBStartup);
	}

	LEAVE();
}

/* Allocate all the necessary resources to get going. */
STATIC BOOL
Setup(
	STRPTR	program_name,
	STRPTR	service,
	STRPTR	workgroup,
	STRPTR 	username,
	STRPTR	opt_password,
	BOOL	opt_changecase,
	STRPTR	opt_clientname,
	STRPTR	opt_servername,
	int		opt_cachesize,
	LONG *	opt_time_zone_offset,
	STRPTR	device_name,
	STRPTR	volume_name,
	STRPTR	translation_file)
{
	BOOL result = FALSE;
	struct DosList * dl;
	int error;
	STRPTR actual_volume_name;
	LONG actual_volume_name_len;
	UBYTE name[MAX_FILENAME_LEN];
	BOOL device_exists = FALSE;
	LONG len,i;

	ENTER();

	NewList((struct List *)&FileList);
	NewList((struct List *)&LockList);

	MemoryPool = CreatePool(MEMF_ANY|MEMF_PUBLIC,4096,4096);
	if(MemoryPool == NULL)
	{
		ReportError("Could not create memory pool.");
		goto out;
	}

	LocaleBase = OpenLibrary("locale.library",38);
	if(LocaleBase != NULL)
		Locale = OpenLocale(NULL);

	if(opt_time_zone_offset != NULL)
	{
		TimeZoneOffset			= (*opt_time_zone_offset);
		OverrideLocaleTimeZone	= TRUE;
	}

	memset(&TimerRequest,0,sizeof(TimerRequest));

	if(OpenDevice(TIMERNAME,UNIT_VBLANK,(struct IORequest *)&TimerRequest,0) != OK)
	{
		ReportError("Could not open 'timer.device'.");
		goto out;
	}

	TimerBase = TimerRequest.tr_node.io_Device;

	SocketBase = OpenLibrary("bsdsocket.library",3);
	if(SocketBase == NULL)
	{
		ReportError("Could not open 'bsdsocket.library' V3; TCP/IP stack not running?");
		goto out;
	}

	error = SocketBaseTags(
		SBTM_SETVAL(SBTC_ERRNOPTR(sizeof(errno))),	&errno,
		SBTM_SETVAL(SBTC_HERRNOLONGPTR),			&h_errno,
		SBTM_SETVAL(SBTC_LOGTAGPTR),				program_name,
		SBTM_SETVAL(SBTC_BREAKMASK),				SIGBREAKF_CTRL_C,
	TAG_END);
	if(error != OK)
	{
		ReportError("Could not initialize 'bsdsocket.library' (%ld, %s).",error,amitcp_strerror(error));
		goto out;
	}

	if(opt_changecase)
	{
		for(i = 0 ; i < (LONG)strlen(opt_password) ; i++)
			opt_password[i] = ToUpper(opt_password[i]);
	}

	TranslateNames = FALSE;

	/* Read the translation file, if possible. */
	if(translation_file != NULL)
	{
		STRPTR msg = NULL;
		BPTR file;

		error = OK;

		file = Open(translation_file,MODE_OLDFILE);
		if(file != ZERO)
		{
			if(Read(file,A2M,256) != 256 ||
			   Read(file,M2A,256) != 256)
			{
				msg = "Could not read translation file";
				error = IoErr();
			}

			Close(file);
		}
		else
		{
			msg = "Could not open translation file";
			error = IoErr();
		}

		if(msg == NULL)
		{
			TranslateNames = TRUE;
		}
		else
		{
			UBYTE description[100];

			Fault(error,NULL,description,sizeof(description));
			for(i = ((int)strlen(description)) - 1 ; i >= 0 ; i--)
			{
				if(description[i] == '\n')
					description[i] = '\0';
			}

			ReportError("%s '%s' (%ld, %s).",msg,translation_file,error,description);
			goto out;
		}
	}

	error = smba_start(service,workgroup,username,opt_password,opt_clientname,opt_servername,opt_cachesize,&ServerData);
	if(error < 0)
		goto out;

	FileSystemPort = CreateMsgPort();
	if(FileSystemPort == NULL)
	{
		ReportError("Could not create filesystem port.");
		goto out;
	}

	/* If a device name was provided, check whether it is
	 * well-formed.
	 */
	if(device_name != NULL)
	{
		len = strlen(device_name);
		if(len > 255)
			len = 255;

		for(i = 0 ; i < len ; i++)
		{
			if(device_name[i] == '/')
			{
				ReportError("Device name '%s' cannot be used with AmigaDOS.",device_name);
				goto out;
			}
		}

		/* Lose any trailing colon characters. */
		for(i = len-1 ; i >= 0 ; i--)
		{
			if(device_name[i] == ':')
				len = i;
		}

		if(len == 0)
		{
			ReportError("Device name '%s' cannot be used with AmigaDOS.",device_name);
			goto out;
		}

		memcpy(name,device_name,len);
		name[len] = '\0';

		dl = LockDosList(LDF_WRITE|LDF_VOLUMES|LDF_DEVICES);

		if(FindDosEntry(dl,name,LDF_DEVICES) != NULL)
			device_exists = TRUE;
	}
	else
	{
		dl = LockDosList(LDF_WRITE|LDF_VOLUMES|LDF_DEVICES);

		/* Find a unique device name. */
		for(i = 0 ; i < 100 ; i++)
		{
			SPrintf(name,"SMBFS%ld",i);

			device_exists = (BOOL)(FindDosEntry(dl,name,LDF_DEVICES) != NULL);
			if(NOT device_exists)
			{
				device_name = name;
				break;
			}
		}
	}

	if(device_exists)
	{
		UnLockDosList(LDF_WRITE|LDF_VOLUMES|LDF_DEVICES);

		ReportError("Device name '%s:' is already taken.",device_name);
		goto out;
	}

	/* Finally, create the device node. */
	DeviceNode = MakeDosEntry(name,DLT_DEVICE);
	if(DeviceNode == NULL)
	{
		UnLockDosList(LDF_WRITE|LDF_VOLUMES|LDF_DEVICES);

		ReportError("Could not create device node.");
		goto out;
	}

	DeviceNode->dol_Task = FileSystemPort;

	/* Examine the volume name; make sure that it is
	 * well-formed.
	 */
	if(volume_name == NULL)
		actual_volume_name = device_name;
	else
		actual_volume_name = volume_name;

	actual_volume_name_len = strlen(actual_volume_name);
	if(actual_volume_name_len > 255)
		actual_volume_name_len = 255;

	for(i = 0 ; i < actual_volume_name_len ; i++)
	{
		if(actual_volume_name[i] == '/')
		{
			UnLockDosList(LDF_WRITE|LDF_VOLUMES|LDF_DEVICES);

			ReportError("Volume name '%s' cannot be used with AmigaDOS.",actual_volume_name);
			goto out;
		}
	}

	/* Lose any trailing colon characters. */
	for(i = actual_volume_name_len-1 ; i >= 0 ; i--)
	{
		if(actual_volume_name[i] == ':')
			actual_volume_name_len = i;
	}

	if(actual_volume_name_len == 0)
	{
		UnLockDosList(LDF_WRITE|LDF_VOLUMES|LDF_DEVICES);

		ReportError("Volume name '%s' cannot be used with AmigaDOS.",actual_volume_name);
		goto out;
	}

	/* Now, finally, take care of the volume name. */
	memcpy(name,actual_volume_name,actual_volume_name_len);
	name[actual_volume_name_len] = '\0';

	VolumeNode = MakeDosEntry(name,DLT_VOLUME);
	if(VolumeNode == NULL)
	{
		UnLockDosList(LDF_WRITE|LDF_VOLUMES|LDF_DEVICES);

		ReportError("Could not create volume node.");
		goto out;
	}

	VolumeNode->dol_Task = FileSystemPort;
	DateStamp(&VolumeNode->dol_misc.dol_volume.dol_VolumeDate);
	VolumeNode->dol_misc.dol_volume.dol_DiskType = ID_DOS_DISK;

	if(DeviceNode != NULL)
	{
		AddDosEntry(DeviceNode);

		DeviceNodeAdded = TRUE;
	}

	/* Note: we always need the volume node to make some file
	 *       system operations safe (e.g. Lock()), but we may
	 *       not always need to make it visible.
	 */
	if(volume_name != NULL && VolumeNode != NULL)
	{
 		AddDosEntry(VolumeNode);

		VolumeNodeAdded = TRUE;
	}

	/* And that concludes the mounting operation. */
	UnLockDosList(LDF_WRITE|LDF_VOLUMES|LDF_DEVICES);

	/* Tell Workbench and friends to update their volume lists. */
	if(VolumeNodeAdded)
		SendDiskChange(IECLASS_DISKINSERTED);

	SetProgramName(NewProgramName);

	result = TRUE;

 out:

	RETURN(result);
	return(result);
}

/****************************************************************************/

/* Convert a BCPL string into a standard NUL terminated 'C' string. */
INLINE STATIC VOID
ConvertBString(LONG max_len,STRPTR cstring,APTR bstring)
{
	STRPTR from = bstring;
	LONG len = from[0];

	if(len > max_len-1)
		len = max_len-1;

	if(len > 0)
		strncpy(cstring,from+1,len);

	cstring[len] = '\0';
}

/* Convert a NUL terminated 'C' string into a BCPL string. */
INLINE STATIC VOID
ConvertCString(LONG max_len,APTR bstring,STRPTR cstring)
{
	LONG len = strlen(cstring);
	STRPTR to = bstring;

	if(len > max_len-1)
		len = max_len-1;

	(*to++) = len;
	memcpy(to,cstring,len);
}

/****************************************************************************/

/* Build the fully qualified name of a file or directory in reference
 * to the name of the parent directory. This takes care of all the
 * special cases, such as the root directory. The result will be converted
 * to be in a form suitable for use with the SMB file sharing service.
 */
STATIC LONG
BuildFullName(
	STRPTR		parent_name,
	STRPTR		name,
	STRPTR *	result_ptr)
{
	LONG error = OK;
	STRPTR buffer;
	LONG len;
	LONG i;

	ENTER();

	SHOWSTRING(parent_name);
	SHOWSTRING(name);

	(*result_ptr) = NULL;

	/* Throw everything left of the colon away. */
	if(name != NULL)
	{
		for(i = 0 ; i < (LONG)strlen(name) ; i++)
		{
			if(name[i] == ':')
			{
				name = &name[i+1];
				break;
			}
		}
	}

	/* Now, how much room is needed for the complete
	 * path to fit into a buffer?
	 */
	len = 2;

	if(parent_name != NULL)
		len += strlen(parent_name) + 1;

	if(name != NULL)
		len += strlen(name) + 1;

	buffer = AllocVecPooled(len+3);
	if(buffer == NULL)
	{
		error = ERROR_NO_FREE_STORE;
		goto out;
	}

	/* Start by filling in the path name. */
	if(parent_name != NULL)
	{
		/* Skip any excess separators. */
		while((*parent_name) == SMB_PATH_SEPARATOR)
			parent_name++;

		buffer[0] = SMB_PATH_SEPARATOR;
		strcpy(&buffer[1],parent_name);
	}
	else
	{
		strcpy(buffer,SMB_ROOT_DIR_NAME);
	}

	/* If there's a name to add, do just that. */
	if(name != NULL)
	{
		LONG segment_start;
		LONG segment_len;
		LONG buffer_len;
		LONG name_len;

		buffer_len = strlen(buffer);
		name_len = strlen(name);

		segment_start = 0;

		while(TRUE)
		{
			segment_len = 0;

			/* Extract the next path name segment. */
			for(i = segment_start ; i <= name_len ; i++)
			{
				if(i == name_len)
				{
					segment_len = i - segment_start;
					break;
				}
				else if (name[i] == '/')
				{
					segment_len = i - segment_start + 1;
					break;
				}
			}

			/* We're finished if there are no further
			 * path name segments to take care of.
			 */
			if(segment_len == 0)
			{
				buffer[buffer_len] = '\0';
				break;
			}

			/* A single slash indicates that we need to move up
			 * to the parent directory, if any.
			 */
			if(segment_len == 1 && name[segment_start] == '/')
			{
				/* Is this already the root directory name? */
				if(buffer_len <= 1)
				{
					FreeVecPooled(buffer);
					buffer = NULL;

					goto out;
				}
				else
				{
					/* Skip the last path component. */
					for(i = 1 ; i <= buffer_len ; i++)
					{
						if(i == buffer_len)
						{
							/* We just skipped the first path
							 * component following the root
							 * directory name. We preserve
							 * the first character since it
							 * refers to the root directory.
							 */
							buffer_len = 1;
							break;
						}
						else if (buffer[buffer_len-i] == SMB_PATH_SEPARATOR)
						{
							/* This removes both the path separator and
							 * the name following it.
							 */
							buffer_len -= i;
							break;
						}
					}
				}
			}
			else
			{
				/* Add a proper separator character if
				 * necessary.
				 */
				if(buffer_len > 0 && buffer[buffer_len-1] != SMB_PATH_SEPARATOR)
					buffer[buffer_len++] = SMB_PATH_SEPARATOR;

				/* Find out how many characters are in that name; this
				 * excludes the terminating slash.
				 */
				if(name[segment_start + segment_len - 1] == '/')
					len = segment_len - 1;
				else
					len = segment_len;

				memcpy(&buffer[buffer_len],&name[segment_start],len);
				buffer_len += len;
			}

			segment_start += segment_len;
		}
	}

	(*result_ptr) = buffer;

	SHOWSTRING(buffer);

 out:

	if(error != OK)
		FreeVecPooled(buffer);

	RETURN(error);
	return(error);
}

/****************************************************************************/

STATIC BPTR
Action_Parent(
	struct FileLock *	parent,
	LONG *				error_ptr)
{
	BPTR result = ZERO;
	STRPTR full_name = NULL;
	STRPTR parent_name;
	BOOL cleanup = TRUE;
	struct LockNode * ln = NULL;
	LONG error;

	ENTER();

	SHOWVALUE(parent);

	if(parent != NULL)
	{
		struct LockNode * parent_ln = (struct LockNode *)parent->fl_Key;

		parent_name = parent_ln->ln_FullName;
	}
	else
	{
		parent_name = NULL;
	}

	error = BuildFullName(parent_name,"/",&full_name);
	if(error != OK)
		goto out;

	/* Check if we ended up having to return the parent of
	 * the root directory. This is indicated by a NULL
	 * name pointer and a zero error code.
	 */
	if(full_name == NULL)
		goto out;

	ln = AllocVecPooled(sizeof(*ln));
	if(ln == NULL)
	{
		error = ERROR_NO_FREE_STORE;
		goto out;
	}

	memset(ln,0,sizeof(*ln));

	ln->ln_FileLock.fl_Key		= (LONG)ln;
	ln->ln_FileLock.fl_Access	= SHARED_LOCK;
	ln->ln_FileLock.fl_Task		= FileSystemPort;
	ln->ln_FileLock.fl_Volume	= MKBADDR(VolumeNode);
	ln->ln_FullName				= full_name;

	SHOWSTRING(full_name);

	error = smba_open(ServerData,full_name,&ln->ln_File);
	if(error < 0)
	{
		error = MapErrnoToIoErr(error);
		goto out;
	}

	AddTail((struct List *)&LockList,(struct Node *)ln);
	result = MKBADDR(&ln->ln_FileLock);
	cleanup = FALSE;
	SHOWVALUE(&ln->ln_FileLock);

 out:

	if(cleanup)
	{
		FreeVecPooled(full_name);
		FreeVecPooled(ln);
	}

	(*error_ptr) = error;

	RETURN(result);
	return(result);
}

/****************************************************************************/

STATIC LONG
Action_DeleteObject(
	struct FileLock *	parent,
	APTR				bcpl_name,
	LONG *				error_ptr)
{
	LONG result = DOSFALSE;
	STRPTR full_name = NULL;
	smba_file_t * file = NULL;
	STRPTR parent_name;
	STRPTR full_parent_name = NULL;
	UBYTE name[MAX_FILENAME_LEN];
	struct LockNode * ln;
	smba_stat_t st;
	LONG error;

	ENTER();

	if(WriteProtected)
	{
		error = ERROR_DISK_WRITE_PROTECTED;
		goto out;
	}

	SHOWVALUE(parent);

	if(parent != NULL)
	{
		ln = (struct LockNode *)parent->fl_Key;

		parent_name = ln->ln_FullName;
	}
	else
	{
		parent_name = NULL;
	}

	ConvertBString(sizeof(name),name,bcpl_name);
	TranslateCName(name,A2M);

	error = BuildFullName(parent_name,name,&full_name);
	if(error != OK)
		goto out;

	/* Trying to delete the root directory, are you kidding? */
	if(full_name == NULL)
	{
		error = ERROR_OBJECT_WRONG_TYPE;
		goto out;
	}

	/* We need to find this file's parent directory, so that
	 * in case the directory contents are currently being
	 * examined, that process is restarted.
	 */
	full_parent_name = AllocVecPooled(strlen(full_name)+3);
	if(full_parent_name == NULL)
	{
		error = ERROR_NO_FREE_STORE;
		goto out;
	}

	strcpy(full_parent_name,full_name);

	ln = FindLockNode(full_parent_name,NULL);
	if(ln != NULL)
		ln->ln_RestartExamine = TRUE;

	FreeVecPooled(full_parent_name);
	full_parent_name = NULL;

	SHOWSTRING(full_name);

	error = smba_open(ServerData,full_name,&file);
	if(error < 0)
	{
		error = MapErrnoToIoErr(error);
		goto out;
	}

	error = smba_getattr(file,&st);
	if(error < 0)
	{
		error = MapErrnoToIoErr(error);
		goto out;
	}

	smba_close(file);
	file = NULL;

	if(st.is_dir)
	{
		SHOWMSG("removing a directory");

		error = smba_rmdir(ServerData,full_name);
		if(error < 0)
		{
			SHOWVALUE(error);

			/* This is a little bit difficult to justify since
			 * the error code may indicate a different cause,
			 * but in practice 'EACCES' seems to be returned
			 * if the directory to remove is not empty.
			 */
			if(error == (-EACCES))
				error = ERROR_DIRECTORY_NOT_EMPTY;
			else
				error = MapErrnoToIoErr(error);

			goto out;
		}
	}
	else
	{
		SHOWMSG("removing a file");

		error = smba_remove(ServerData,full_name);
		if(error < 0)
		{
			SHOWVALUE(error);

			error = MapErrnoToIoErr(error);
			goto out;
		}
	}

	SHOWMSG("done.");

	result = DOSTRUE;

 out:

	FreeVecPooled(full_name);
	FreeVecPooled(full_parent_name);
	if(file != NULL)
		smba_close(file);

	(*error_ptr) = error;

	RETURN(result);
	return(result);
}

/****************************************************************************/

STATIC BPTR
Action_CreateDir(
	struct FileLock *	parent,
	APTR				bcpl_name,
	LONG *				error_ptr)
{
	BPTR result = ZERO;
	STRPTR full_name = NULL;
	struct LockNode * ln = NULL;
	STRPTR parent_name;
	STRPTR dir_name = NULL;
	smba_file_t * dir = NULL;
	STRPTR base_name;
	UBYTE name[MAX_FILENAME_LEN];
	LONG error;
	LONG i;

	ENTER();

	if(WriteProtected)
	{
		error = ERROR_DISK_WRITE_PROTECTED;
		goto out;
	}

	SHOWVALUE(parent);

	if(parent != NULL)
	{
		struct LockNode * parent_ln = (struct LockNode *)parent->fl_Key;

		parent_name = parent_ln->ln_FullName;
	}
	else
	{
		parent_name = NULL;
	}

	ConvertBString(sizeof(name),name,bcpl_name);
	TranslateCName(name,A2M);

	error = BuildFullName(parent_name,name,&full_name);
	if(error != OK)
		goto out;

	/* Trying to overwrite the root directory, are you kidding? */
	if(full_name == NULL)
	{
		error = ERROR_OBJECT_IN_USE;
		goto out;
	}

	dir_name = AllocVecPooled(strlen(full_name)+3);
	if(dir_name == NULL)
	{
		error = ERROR_NO_FREE_STORE;
		goto out;
	}

	strcpy(dir_name,full_name);
	base_name = NULL;
	for(i = strlen(dir_name)-1 ; i >= 0 ; i--)
	{
		if(dir_name[i] == SMB_PATH_SEPARATOR)
		{
			if(i == 0)
			{
				memmove(&dir_name[1],&dir_name[0],strlen(dir_name)+1);
				i++;
			}

			dir_name[i] = '\0';

			base_name = &dir_name[i+1];
			break;
		}
	}

	ln = AllocVecPooled(sizeof(*ln));
	if(ln == NULL)
	{
		error = ERROR_NO_FREE_STORE;
		goto out;
	}

	memset(ln,0,sizeof(*ln));

	ln->ln_FileLock.fl_Key		= (LONG)ln;
	ln->ln_FileLock.fl_Access	= EXCLUSIVE_LOCK;
	ln->ln_FileLock.fl_Task		= FileSystemPort;
	ln->ln_FileLock.fl_Volume	= MKBADDR(VolumeNode);
	ln->ln_FullName				= full_name;

	error = smba_open(ServerData,dir_name,&dir);
	if(error < 0)
	{
		error = MapErrnoToIoErr(error);
		goto out;
	}

	error = smba_mkdir(dir,base_name);
	if(error < 0)
	{
		error = MapErrnoToIoErr(error);
		goto out;
	}

	smba_close(dir);
	dir = NULL;

	SHOWSTRING(full_name);

	error = smba_open(ServerData,full_name,&ln->ln_File);
	if(error < 0)
	{
		error = MapErrnoToIoErr(error);
		goto out;
	}

	AddTail((struct List *)&LockList,(struct Node *)ln);
	result = MKBADDR(&ln->ln_FileLock);
	SHOWVALUE(&ln->ln_FileLock);

 out:

	if(dir != NULL)
		smba_close(dir);

	FreeVecPooled(dir_name);

	if(result == ZERO)
	{
		FreeVecPooled(full_name);
		FreeVecPooled(ln);
	}

	(*error_ptr) = error;

	RETURN(result);
	return(result);
}

/****************************************************************************/

STATIC BPTR
Action_LocateObject(
	struct FileLock *	parent,
	APTR				bcpl_name,
	LONG				mode,
	LONG *				error_ptr)
{
	BPTR result = ZERO;
	STRPTR full_name = NULL;
	struct LockNode * ln = NULL;
	STRPTR parent_name;
	UBYTE name[MAX_FILENAME_LEN];
	LONG error;

	ENTER();

	SHOWVALUE(parent);

	if(parent != NULL)
	{
		struct LockNode * parent_ln = (struct LockNode *)parent->fl_Key;

		parent_name = parent_ln->ln_FullName;
	}
	else
	{
		parent_name = NULL;
	}

	ConvertBString(sizeof(name),name,bcpl_name);
	TranslateCName(name,A2M);

	if(IsReservedName(FilePart(name)))
	{
		error = ERROR_OBJECT_NOT_FOUND;
		goto out;
	}

	error = BuildFullName(parent_name,name,&full_name);
	if(error != OK)
		goto out;

	/* Trying to get a lock on the root directory's parent?
	 * My pleasure.
	 */
	if(full_name == NULL)
		goto out;

	ln = AllocVecPooled(sizeof(*ln));
	if(ln == NULL)
	{
		error = ERROR_NO_FREE_STORE;
		goto out;
	}

	memset(ln,0,sizeof(*ln));

	ln->ln_FileLock.fl_Key		= (LONG)ln;
	ln->ln_FileLock.fl_Access	= (mode != EXCLUSIVE_LOCK) ? SHARED_LOCK : EXCLUSIVE_LOCK;
	ln->ln_FileLock.fl_Task		= FileSystemPort;
	ln->ln_FileLock.fl_Volume	= MKBADDR(VolumeNode);
	ln->ln_FullName				= full_name;

	error = CheckAccessModeCollision(full_name,ln->ln_FileLock.fl_Access);
	if(error != OK)
		goto out;

	SHOWSTRING(full_name);

	error = smba_open(ServerData,full_name,&ln->ln_File);
	if(error < 0)
	{
		error = MapErrnoToIoErr(error);
		goto out;
	}

	AddTail((struct List *)&LockList,(struct Node *)ln);
	result = MKBADDR(&ln->ln_FileLock);
	SHOWVALUE(&ln->ln_FileLock);

 out:

	if(result == ZERO)
	{
		FreeVecPooled(full_name);
		FreeVecPooled(ln);
	}

	(*error_ptr) = error;

	RETURN(result);
	return(result);
}

/****************************************************************************/

STATIC BPTR
Action_CopyDir(
	struct FileLock *	lock,
	LONG *				error_ptr)
{
	BPTR result = ZERO;
	STRPTR full_name = NULL;
	struct LockNode * ln = NULL;
	STRPTR source_name;
	LONG source_mode;
	LONG error;

	ENTER();

	SHOWVALUE(lock);

	if(lock != NULL && lock->fl_Access != SHARED_LOCK)
	{
		SHOWMSG("cannot duplicate exclusive lock");
		error = ERROR_OBJECT_IN_USE;
		goto out;
	}

	ln = AllocVecPooled(sizeof(*ln));
	if(ln == NULL)
	{
		error = ERROR_NO_FREE_STORE;
		goto out;
	}

	memset(ln,0,sizeof(*ln));

	if(lock != NULL)
	{
		struct LockNode * source = (struct LockNode *)lock->fl_Key;

		source_name = source->ln_FullName;
		source_mode = source->ln_FileLock.fl_Access;
	}
	else
	{
		source_name = SMB_ROOT_DIR_NAME;
		source_mode = SHARED_LOCK;
	}

	full_name = AllocVecPooled(strlen(source_name)+3);
	if(full_name == NULL)
	{
		error = ERROR_NO_FREE_STORE;
		goto out;
	}

	strcpy(full_name,source_name);

	ln->ln_FileLock.fl_Key		= (LONG)ln;
	ln->ln_FileLock.fl_Access	= source_mode;
	ln->ln_FileLock.fl_Task		= FileSystemPort;
	ln->ln_FileLock.fl_Volume	= MKBADDR(VolumeNode);
	ln->ln_FullName				= full_name;

	SHOWSTRING(full_name);

	error = smba_open(ServerData,full_name,&ln->ln_File);
	if(error < 0)
	{
		error = MapErrnoToIoErr(error);
		goto out;
	}

	AddTail((struct List *)&LockList,(struct Node *)ln);
	result = MKBADDR(&ln->ln_FileLock);
	SHOWVALUE(&ln->ln_FileLock);

 out:

	if(result == ZERO)
	{
		FreeVecPooled(full_name);
		FreeVecPooled(ln);
	}

	(*error_ptr) = error;

	RETURN(result);
	return(result);
}

/****************************************************************************/

STATIC LONG
Action_FreeLock(
	struct FileLock *	lock,
	LONG *				error_ptr)
{
	LONG result = DOSTRUE;
	struct LockNode * ln;
	LONG error = OK;

	ENTER();

	SHOWVALUE(lock);

	if(lock == NULL)
		goto out;

	ln = (struct LockNode *)lock->fl_Key;

	Remove((struct Node *)ln);
	smba_close(ln->ln_File);
	FreeVecPooled(ln->ln_FullName);
	FreeVecPooled(ln);

 out:

	(*error_ptr) = error;

	RETURN(result);
	return(result);
}

/****************************************************************************/

STATIC LONG
Action_SameLock(
	struct FileLock *	lock1,
	struct FileLock *	lock2,
	LONG *				error_ptr)
{
	LONG result = DOSFALSE;
	STRPTR name1;
	STRPTR name2;
	LONG error = OK;

	ENTER();

	SHOWVALUE(lock1);
	SHOWVALUE(lock2);

	if(lock1 != NULL)
	{
		struct LockNode * ln = (struct LockNode *)lock1->fl_Key;

		name1 = ln->ln_FullName;
	}
	else
	{
		name1 = SMB_ROOT_DIR_NAME;
	}

	if(lock2 != NULL)
	{
		struct LockNode * ln = (struct LockNode *)lock2->fl_Key;

		name2 = ln->ln_FullName;
	}
	else
	{
		name2 = SMB_ROOT_DIR_NAME;
	}

	SHOWSTRING(name1);
	SHOWSTRING(name2);

	if(Stricmp(name1,name2) == SAME)
		result = DOSTRUE;

	(*error_ptr) = error;

	RETURN(result);
	return(result);
}

/****************************************************************************/

STATIC LONG
Action_SetProtect(
	struct FileLock *	parent,
	APTR				bcpl_name,
	LONG				mask,
	LONG *				error_ptr)
{
	LONG result = DOSFALSE;
	STRPTR full_name = NULL;
	smba_file_t * file = NULL;
	STRPTR parent_name;
	UBYTE name[MAX_FILENAME_LEN];
	smba_stat_t st;
	LONG error;

	ENTER();

	if(WriteProtected)
	{
		error = ERROR_DISK_WRITE_PROTECTED;
		goto out;
	}

	SHOWVALUE(parent);

	if(parent != NULL)
	{
		struct LockNode * ln = (struct LockNode *)parent->fl_Key;

		parent_name = ln->ln_FullName;
	}
	else
	{
		parent_name = NULL;
	}

	ConvertBString(sizeof(name),name,bcpl_name);
	TranslateCName(name,A2M);

	error = BuildFullName(parent_name,name,&full_name);
	if(error != OK)
		goto out;

	/* Trying to change the protection bits of the root
	 * directory, are you kidding?
	 */
	if(full_name == NULL)
	{
		error = ERROR_OBJECT_WRONG_TYPE;
		goto out;
	}

	SHOWSTRING(full_name);

	error = smba_open(ServerData,full_name,&file);
	if(error < 0)
	{
		error = MapErrnoToIoErr(error);
		goto out;
	}

	memset(&st,0,sizeof(st));

	mask ^= FIBF_READ | FIBF_WRITE | FIBF_EXECUTE | FIBF_DELETE;

	st.atime = -1;
	st.ctime = -1;
	st.mtime = -1;
	st.size = -1;

	if((mask & (FIBF_WRITE|FIBF_DELETE)) != (FIBF_WRITE|FIBF_DELETE))
	{
		SHOWMSG("write protection enabled");
		st.is_wp = TRUE;
	}
	else
	{
		SHOWMSG("write protection disabled");
	}

	error = smba_setattr(file,&st);
	if(error < 0)
	{
		error = MapErrnoToIoErr(error);
		goto out;
	}

	result = DOSTRUE;

 out:

	FreeVecPooled(full_name);
	if(file != NULL)
		smba_close(file);

	(*error_ptr) = error;

	RETURN(result);
	return(result);
}

/****************************************************************************/

STATIC LONG
Action_RenameObject(
	struct FileLock *	source_lock,
	APTR				source_bcpl_name,
	struct FileLock *	destination_lock,
	APTR				destination_bcpl_name,
	LONG *				error_ptr)
{
	struct LockNode * ln;
	LONG result = DOSFALSE;
	STRPTR full_source_name = NULL;
	STRPTR full_destination_name = NULL;
	UBYTE name[MAX_FILENAME_LEN];
	STRPTR parent_name;
	LONG error;

	ENTER();

	if(WriteProtected)
	{
		error = ERROR_DISK_WRITE_PROTECTED;
		goto out;
	}

	SHOWVALUE(source_lock);
	SHOWVALUE(destination_lock);

	if(source_lock != NULL)
	{
		ln = (struct LockNode *)source_lock->fl_Key;

		parent_name = ln->ln_FullName;
	}
	else
	{
		parent_name = NULL;
	}

	ConvertBString(sizeof(name),name,source_bcpl_name);
	TranslateCName(name,A2M);

	error = BuildFullName(parent_name,name,&full_source_name);
	if(error != OK)
		goto out;

	/* Trying to rename the root directory, are you kidding? */
	if(full_source_name == NULL)
	{
		error = ERROR_OBJECT_IN_USE;
		goto out;
	}

	if(destination_lock != NULL)
	{
		ln = (struct LockNode *)destination_lock->fl_Key;

		parent_name = ln->ln_FullName;
	}
	else
	{
		parent_name = NULL;
	}

	ConvertBString(sizeof(name),name,destination_bcpl_name);
	TranslateCName(name,A2M);

	error = BuildFullName(parent_name,name,&full_destination_name);
	if(error != OK)
		goto out;

	/* Trying to rename the root directory, are you kidding? */
	if(full_destination_name == NULL)
	{
		error = ERROR_OBJECT_IN_USE;
		goto out;
	}

	error = NameAlreadyInUse(full_source_name);
	if(error != OK)
		goto out;

	error = NameAlreadyInUse(full_destination_name);
	if(error != OK)
		goto out;

	SHOWSTRING(full_source_name);
	SHOWSTRING(full_destination_name);

	error = smba_rename(ServerData,full_source_name,full_destination_name);
	if(error < 0)
	{
		error = MapErrnoToIoErr(error);
		goto out;
	}

	result = DOSTRUE;

 out:

	FreeVecPooled(full_source_name);
	FreeVecPooled(full_destination_name);

	(*error_ptr) = error;

	RETURN(result);
	return(result);
}

/****************************************************************************/

STATIC LONG
Action_DiskInfo(
	struct InfoData *	id,
	LONG *				error_ptr)
{
	LONG result = DOSTRUE;
	LONG block_size;
	LONG num_blocks;
	LONG num_blocks_free;
	LONG error;

	ENTER();

	memset(id,0,sizeof(*id));

	if(WriteProtected)
		id->id_DiskState = ID_WRITE_PROTECTED;
	else
		id->id_DiskState = ID_VALIDATED;

	error = smba_statfs(ServerData,&block_size,&num_blocks,&num_blocks_free);
	if(error >= 0)
	{
		SHOWMSG("got the disk data");
		SHOWVALUE(block_size);
		SHOWVALUE(num_blocks);
		SHOWVALUE(num_blocks_free);

		if(block_size <= 0)
			block_size = 512;

		if(block_size < 512)
		{
			num_blocks		/= (512 / block_size);
			num_blocks_free	/= (512 / block_size);
		}
		else if (block_size > 512)
		{
			num_blocks		*= (block_size / 512);
			num_blocks_free	*= (block_size / 512);
		}

		id->id_NumBlocks		= num_blocks;
		id->id_NumBlocksUsed	= num_blocks - num_blocks_free;
		id->id_BytesPerBlock	= 512;
		id->id_DiskType			= ID_DOS_DISK;
		id->id_VolumeNode		= MKBADDR(VolumeNode);
		id->id_InUse			= NOT (IsListEmpty((struct List *)&FileList) && IsListEmpty((struct List *)&LockList));

		if(id->id_NumBlocks == 0)
			id->id_NumBlocks = 1;

		if(id->id_NumBlocksUsed == 0)
			id->id_NumBlocksUsed = 1;
	}
	else
	{
		SHOWMSG("could not get any disk data");

		id->id_NumBlocks		= 1;
		id->id_NumBlocksUsed	= 1;
		id->id_BytesPerBlock	= 512;
		id->id_DiskType			= ID_NO_DISK_PRESENT;

		error = MapErrnoToIoErr(error);
		result = DOSFALSE;
	}

	SHOWVALUE(id->id_NumBlocks);
	SHOWVALUE(id->id_NumBlocksUsed);
	SHOWVALUE(id->id_BytesPerBlock);
	SHOWVALUE(id->id_DiskType);
	SHOWVALUE(id->id_VolumeNode);
	SHOWVALUE(id->id_InUse);

	(*error_ptr) = error;

	RETURN(result);
	return(result);
}

STATIC LONG
Action_Info(
	struct FileLock *	lock,
	struct InfoData *	id,
	LONG *				error_ptr)
{
	LONG result;

	ENTER();

	SHOWVALUE(lock);

	if(lock == NULL || lock->fl_Volume != MKBADDR(VolumeNode))
	{
		SHOWMSG("volume node does not match");

		result = DOSFALSE;

		(*error_ptr) = ERROR_NO_DISK;
	}
	else
	{
		result = Action_DiskInfo(id,error_ptr);
	}

	RETURN(result);
	return(result);
}

/****************************************************************************/

STATIC LONG
Action_ExamineObject(
	struct FileLock *		lock,
	struct FileInfoBlock *	fib,
	LONG *					error_ptr)
{
	LONG result = DOSFALSE;
	LONG error = OK;

	ENTER();

	SHOWVALUE(lock);

	memset(fib,0,sizeof(*fib));

	if(lock == NULL)
	{
		STRPTR volume_name = BADDR(VolumeNode->dol_Name);
		LONG len = volume_name[0];

		SHOWMSG("ZERO root lock");

		memcpy(fib->fib_FileName+1,volume_name+1,len);
		fib->fib_FileName[0] = len;

		fib->fib_DirEntryType	= ST_ROOT;
		fib->fib_EntryType		= ST_ROOT;
		fib->fib_NumBlocks		= 1;
		fib->fib_Date			= VolumeNode->dol_misc.dol_volume.dol_VolumeDate;
		fib->fib_DiskKey		= -1;
		fib->fib_Protection		= FIBF_OTR_READ|FIBF_OTR_EXECUTE|FIBF_OTR_WRITE|FIBF_OTR_DELETE|
								  FIBF_GRP_READ|FIBF_GRP_EXECUTE|FIBF_GRP_WRITE|FIBF_GRP_DELETE;
	}
	else
	{
		struct LockNode * ln = (struct LockNode *)lock->fl_Key;
		LONG seconds;
		smba_stat_t st;

		error = smba_getattr(ln->ln_File,&st);
		if(error < 0)
		{
			SHOWMSG("information not available");

			error = MapErrnoToIoErr(error);
			goto out;
		}

		seconds = st.mtime - UNIX_TIME_OFFSET - GetTimeZoneDelta();
		if(seconds < 0)
			seconds = 0;

		fib->fib_Date.ds_Days	= (seconds / (24 * 60 * 60));
		fib->fib_Date.ds_Minute	= (seconds % (24 * 60 * 60)) / 60;
		fib->fib_Date.ds_Tick	= (seconds % 60) * TICKS_PER_SECOND;

		SHOWSTRING(ln->ln_FullName);

		if(strcmp(ln->ln_FullName,SMB_ROOT_DIR_NAME) == SAME)
		{
			STRPTR volume_name = BADDR(VolumeNode->dol_Name);
			LONG len = volume_name[0];

			SHOWMSG("root lock");

			memcpy(fib->fib_FileName+1,volume_name+1,len);
			fib->fib_FileName[0] = len;

			fib->fib_DirEntryType	= ST_ROOT;
			fib->fib_EntryType		= ST_ROOT;
			fib->fib_NumBlocks		= 1;
			fib->fib_Protection		= FIBF_OTR_READ|FIBF_OTR_EXECUTE|FIBF_OTR_WRITE|FIBF_OTR_DELETE|
									  FIBF_GRP_READ|FIBF_GRP_EXECUTE|FIBF_GRP_WRITE|FIBF_GRP_DELETE;
		}
		else
		{
			STRPTR name;
			LONG i;

			name = ln->ln_FullName;
			for(i = strlen(name)-1 ; i >= 0 ; i--)
			{
				if(name[i] == SMB_PATH_SEPARATOR)
				{
					name = &name[i+1];
					break;
				}
			}

			/* Just checking: will the name fit? */
			if(strlen(name) >= sizeof(fib->fib_FileName))
			{
				error = ERROR_INVALID_COMPONENT_NAME;
				goto out;
			}

			ConvertCString(sizeof(fib->fib_FileName),fib->fib_FileName,name);
			TranslateBName(fib->fib_FileName,M2A);

			fib->fib_DirEntryType	= st.is_dir ? ST_USERDIR : ST_FILE;
			fib->fib_EntryType		= fib->fib_DirEntryType;
			fib->fib_NumBlocks		= (st.size + 511) / 512;
			fib->fib_Size			= st.size;
			fib->fib_Protection		= FIBF_OTR_READ|FIBF_OTR_EXECUTE|FIBF_OTR_WRITE|FIBF_OTR_DELETE|
									  FIBF_GRP_READ|FIBF_GRP_EXECUTE|FIBF_GRP_WRITE|FIBF_GRP_DELETE;

			if(st.is_wp)
				fib->fib_Protection ^= (FIBF_OTR_WRITE|FIBF_OTR_DELETE|FIBF_GRP_WRITE|FIBF_GRP_DELETE|FIBF_WRITE|FIBF_DELETE);

			if(NOT st.is_dir)
				fib->fib_DiskKey = -1;
		}
	}

	result = DOSTRUE;

	D(("fib->fib_FileName = \"%b\"",MKBADDR(fib->fib_FileName)));
	SHOWVALUE(fib->fib_DirEntryType);
	SHOWVALUE(fib->fib_NumBlocks);
	SHOWVALUE(fib->fib_Size);
	SHOWVALUE(fib->fib_Date.ds_Days);
	SHOWVALUE(fib->fib_Date.ds_Minute);
	SHOWVALUE(fib->fib_Date.ds_Tick);
	SHOWVALUE(fib->fib_DiskKey);

 out:

	(*error_ptr) = error;

	RETURN(result);
	return(result);
}

/****************************************************************************/

STATIC BOOL
NameIsAcceptable(STRPTR name,LONG max_len)
{
	BOOL result = FALSE;
	UBYTE c;

	/* This takes care of "." and "..". */
	if(name[0] == '.' && (name[1] == '\0' || (name[1] == '.' && name[2] == '\0')))
		goto out;

	/* Now for embedded '/', ':' and '\' characters and
	 * names that just don't want to fit.
	 */
	while((c = (*name++)) != '\0')
	{
		max_len--;
		if(max_len == 0 || c == '/' || c == ':' || c == SMB_PATH_SEPARATOR)
			goto out;
	}

	result = TRUE;

 out:

	return(result);
}

/****************************************************************************/

static int
dir_scan_callback_func_exnext(
	struct FileInfoBlock *	fib,
	int						unused_fpos,
	int						nextpos,
	char *					name,
	int						eof,
	smba_stat_t *			st)
{
	int result;

	ENTER();

	D((" '%s'",name));
	D(("   is_dir=%ld is_wp=%ld is_hidden=%ld size=%ld",
		st->is_dir,st->is_wp,st->is_hidden,st->size));
	D(("   fpos=%ld nextpos=%ld eof=%ld",fpos,nextpos,eof));

	/* Skip file and drawer names that we wouldn't be
	 * able to handle in the first place.
	 */
	if(NameIsAcceptable((STRPTR)name,sizeof(fib->fib_FileName)) && NOT (st->is_hidden && OmitHidden))
	{
		LONG seconds;

		ConvertCString(sizeof(fib->fib_FileName),fib->fib_FileName,name);
		TranslateBName(fib->fib_FileName,M2A);

		fib->fib_DirEntryType	= st->is_dir ? ST_USERDIR : ST_FILE;
		fib->fib_EntryType		= fib->fib_DirEntryType;
		fib->fib_NumBlocks		= (st->size + 511) / 512;
		fib->fib_Size			= st->size;
		fib->fib_Protection		= FIBF_OTR_READ|FIBF_OTR_EXECUTE|FIBF_OTR_WRITE|FIBF_OTR_DELETE|
								  FIBF_GRP_READ|FIBF_GRP_EXECUTE|FIBF_GRP_WRITE|FIBF_GRP_DELETE;

		if(st->is_wp)
			fib->fib_Protection ^= (FIBF_OTR_WRITE|FIBF_OTR_DELETE|FIBF_GRP_WRITE|FIBF_GRP_DELETE|FIBF_WRITE|FIBF_DELETE);

		seconds = st->mtime - UNIX_TIME_OFFSET - GetTimeZoneDelta();
		if(seconds < 0)
			seconds = 0;

		fib->fib_Date.ds_Days	= (seconds / (24 * 60 * 60));
		fib->fib_Date.ds_Minute	= (seconds % (24 * 60 * 60)) / 60;
		fib->fib_Date.ds_Tick	= (seconds % 60) * TICKS_PER_SECOND;

		result = 1;
	}
	else
	{
		result = 0;
	}

	fib->fib_DiskKey = eof ? -1 : nextpos;

	RETURN(result);
	return(result);
}

STATIC LONG
Action_ExamineNext(
	struct FileLock *		lock,
	struct FileInfoBlock *	fib,
	LONG *					error_ptr)
{
	struct LockNode * ln;
	LONG result = DOSFALSE;
	LONG error = OK;
	long offset;
	int count;

	ENTER();

	SHOWVALUE(lock);

	if(fib->fib_DiskKey == -1)
	{
		SHOWMSG("scanning finished.");
		error = ERROR_NO_MORE_ENTRIES;
		goto out;
	}

	if(lock == NULL)
	{
		SHOWMSG("invalid lock");
		error = ERROR_INVALID_LOCK;
		goto out;
	}

	offset = fib->fib_DiskKey;

	ln = (struct LockNode *)lock->fl_Key;

	/* Check if we should restart scanning the directory
	 * contents. This is tricky at best and may produce
	 * irritating results :(
	 */
	if(ln->ln_RestartExamine)
	{
		offset = 0;
		ln->ln_RestartExamine = FALSE;
	}

	memset(fib,0,sizeof(*fib));

	SHOWMSG("calling 'smba_readdir'");
	SHOWVALUE(offset);

	count = smba_readdir(ln->ln_File,offset,fib,(smba_callback_t)dir_scan_callback_func_exnext);

	SHOWVALUE(count);

	if(count == 0 || fib->fib_FileName[0] == '\0')
	{
		SHOWMSG("nothing to be read");
		fib->fib_DiskKey = -1;

		error = ERROR_NO_MORE_ENTRIES;
		goto out;
	}
	else if (count == (-EIO))
	{
		SHOWMSG("ouch! directory read error");
		fib->fib_DiskKey = -1;

		error = ERROR_NO_DEFAULT_DIR;
		goto out;
	}
	else if (count < 0)
	{
		SHOWMSG("error whilst scanning");
		SHOWVALUE(count);
		fib->fib_DiskKey = -1;

		error = MapErrnoToIoErr(count);
		goto out;
	}

	result = DOSTRUE;

 out:

	(*error_ptr) = error;

	RETURN(result);
	return(result);
}

/****************************************************************************/

struct ExAllContext
{
	struct ExAllData *		ec_Last;
	struct ExAllData *		ec_Next;
	ULONG					ec_BytesLeft;
	ULONG					ec_MinSize;
	struct ExAllControl * 	ec_Control;
	ULONG					ec_Type;
	LONG					ec_Error;
	BOOL					ec_FirstAttempt;
};

static int
dir_scan_callback_func_exall(
	struct ExAllContext *	ec,
	int						unused_fpos,
	int						nextpos,
	char *					name,
	int						eof,
	smba_stat_t *			st)
{
	int result = 0;

	ENTER();

	D((" '%s'",name));
	D(("   is_dir=%ld is_wp=%ld is_hidden=%ld size=%ld",
		st->is_dir,st->is_wp,st->is_hidden,st->size));
	D(("   fpos=%ld nextpos=%ld eof=%ld",fpos,nextpos,eof));

	/* Skip file and drawer names that we wouldn't be
	 * able to handle in the first place.
	 */
	if(NameIsAcceptable((STRPTR)name,MAX_FILENAME_LEN) && NOT (st->is_hidden && OmitHidden))
	{
		struct ExAllData * ed;
		ULONG size;
		ULONG type = ec->ec_Type;
		BOOL take_it;

		size = (ec->ec_MinSize + strlen(name)+1 + 3) & ~3UL;
		SHOWVALUE(size);
		if(size > ec->ec_BytesLeft)
		{
			D(("size %ld > ec->ec_BytesLeft %ld",size,ec->ec_BytesLeft));

			/* If this is the first directory entry,
			 * stop the entire process before it has
			 * really begun.
			 */
			if(ec->ec_FirstAttempt)
			{
				SHOWMSG("this was the first read attempt.");
				ec->ec_Control->eac_Entries = 0;
				ec->ec_Error = ERROR_NO_FREE_STORE;
			}
			else
			{
				SHOWMSG("try again");
				ec->ec_Error = 0;
			}

			result = 1;
			goto out;
		}

		ed = ec->ec_Next;

		ed->ed_Next = NULL;
		ed->ed_Name = (STRPTR)(((ULONG)ed) + ec->ec_MinSize);
		strcpy(ed->ed_Name,name);

		TranslateCName(ed->ed_Name,M2A);

		if(type >= ED_TYPE)
			ed->ed_Type = st->is_dir ? ST_USERDIR : ST_FILE;

		if(type >= ED_SIZE)
			ed->ed_Size = st->size;

		if(type >= ED_PROTECTION)
		{
			ed->ed_Prot = FIBF_OTR_READ|FIBF_OTR_EXECUTE|FIBF_OTR_WRITE|FIBF_OTR_DELETE|
			              FIBF_GRP_READ|FIBF_GRP_EXECUTE|FIBF_GRP_WRITE|FIBF_GRP_DELETE;

			if(st->is_wp)
				ed->ed_Prot ^= (FIBF_OTR_WRITE|FIBF_OTR_DELETE|FIBF_GRP_WRITE|FIBF_GRP_DELETE|FIBF_WRITE|FIBF_DELETE);
		}

		if(type >= ED_DATE)
		{
			LONG seconds;

			seconds = st->mtime - UNIX_TIME_OFFSET - GetTimeZoneDelta();
			if(seconds < 0)
				seconds = 0;

			ed->ed_Days		= (seconds / (24 * 60 * 60));
			ed->ed_Mins 	= (seconds % (24 * 60 * 60)) / 60;
			ed->ed_Ticks	= (seconds % 60) * TICKS_PER_SECOND;
		}

		if(type >= ED_COMMENT)
			ed->ed_Comment = "";

		if(type >= ED_OWNER)
			ed->ed_OwnerUID = ed->ed_OwnerGID = 0;

		take_it = TRUE;

		if(ec->ec_Control->eac_MatchString != NULL)
		{
			SHOWMSG("checking against match string");
			if(NOT MatchPatternNoCase(ec->ec_Control->eac_MatchString,ed->ed_Name))
			{
				SHOWMSG("does not match");
				take_it = FALSE;
			}
		}

		if(take_it && ec->ec_Control->eac_MatchFunc != NULL)
		{
			SHOWMSG("calling match func");

			/* NOTE: the order of the parameters passed to the match hook
			 *       function can be somewhat confusing. For standard
			 *       hook functions, the order of the parameters and the
			 *       registers they go into is hook=A0, object=A2,
			 *       message=A1. However, the documentation for the 'ExAll()'
			 *       function always lists them in ascending order, that is
			 *       hook=A0, message=A1, object=A2, which can lead to
			 *       quite some confusion and strange errors.
			 */
			if(NOT CallHookPkt(ec->ec_Control->eac_MatchFunc,&type,ed))
			{
				SHOWMSG("does not match");
				take_it = FALSE;
			}
		}

		if(take_it)
		{
			SHOWMSG("registering new entry");

			if(ec->ec_Last != NULL)
				ec->ec_Last->ed_Next = ed;

			ec->ec_Last = ed;
			ec->ec_Next = (struct ExAllData *)(((ULONG)ed) + size);
			ec->ec_BytesLeft -= size;
			ec->ec_Control->eac_Entries++;

			SHOWVALUE(ec->ec_Last->ed_Next);
			SHOWVALUE(ed->ed_Name);
			SHOWVALUE(ed->ed_Comment);
		}
	}

	ec->ec_Control->eac_LastKey = (ULONG)(eof ? -1 : nextpos);

 out:

	ec->ec_FirstAttempt = FALSE;

	RETURN(result);
	return(result);
}

STATIC LONG
Action_ExamineAll(
	struct FileLock *		lock,
	struct ExAllData *		ed,
	ULONG					size,
	ULONG					type,
	struct ExAllControl *	eac,
	LONG *					error_ptr)
{
	struct ExAllContext ec;
	struct LockNode * ln;
	LONG result = DOSFALSE;
	LONG error = OK;
	LONG offset;
	int count;

	ENTER();

	SHOWVALUE(lock);

	SHOWVALUE(eac->eac_LastKey);

	eac->eac_Entries = 0;
	ed->ed_Next = NULL;

	if(eac->eac_LastKey == (ULONG)-1)
	{
		SHOWMSG("scanning finished.");
		error = ERROR_NO_MORE_ENTRIES;
		goto out;
	}

	if(lock == NULL)
	{
		SHOWMSG("invalid lock");
		error = ERROR_INVALID_LOCK;
		goto out;
	}

	if(type < ED_NAME || type > ED_OWNER)
	{
		D(("type %ld not supported",type));
		error = ERROR_BAD_NUMBER;
		goto out;
	}

	SHOWVALUE(type);

	memset(&ec,0,sizeof(ec));

	ec.ec_Next			= ed;
	ec.ec_BytesLeft		= size;
	ec.ec_Control		= eac;
	ec.ec_Type			= type;
	ec.ec_Error			= ERROR_NO_MORE_ENTRIES;
	ec.ec_FirstAttempt	= TRUE;

	switch(type)
	{
		case ED_NAME:

			ec.ec_MinSize = offsetof(struct ExAllData,ed_Type);
			break;

		case ED_TYPE:

			ec.ec_MinSize = offsetof(struct ExAllData,ed_Size);
			break;

		case ED_SIZE:

			ec.ec_MinSize = offsetof(struct ExAllData,ed_Prot);
			break;

		case ED_PROTECTION:

			ec.ec_MinSize = offsetof(struct ExAllData,ed_Days);
			break;

		case ED_DATE:

			ec.ec_MinSize = offsetof(struct ExAllData,ed_Comment);
			break;

		case ED_COMMENT:

			ec.ec_MinSize = offsetof(struct ExAllData,ed_OwnerUID);
			break;

		case ED_OWNER:

			ec.ec_MinSize = sizeof(struct ExAllData);
			break;
	}

	SHOWVALUE(ec.ec_MinSize);

	offset = eac->eac_LastKey;

	ln = (struct LockNode *)lock->fl_Key;

	/* Check if we should restart scanning the directory
	 * contents. This is tricky at best and may produce
	 * irritating results :(
	 */
	if(ln->ln_RestartExamine)
	{
		offset = 0;

		ln->ln_RestartExamine = FALSE;
	}

	if(offset == 0)
	{
		smba_stat_t st;

		SHOWMSG("first invocation");

		SHOWMSG("getting file attributes");
		error = smba_getattr(ln->ln_File,&st);
		if(error < 0)
		{
			SHOWMSG("didn't work");
			error = MapErrnoToIoErr(error);
			eac->eac_LastKey = (ULONG)-1;
			goto out;
		}

		if(NOT st.is_dir)
		{
			SHOWMSG("lock does not refer to a directory");
			error = ERROR_OBJECT_WRONG_TYPE;
			eac->eac_LastKey = (ULONG)-1;
			goto out;
		}
	}

	SHOWMSG("calling 'smba_readdir'");
	SHOWVALUE(offset);

	count = smba_readdir(ln->ln_File,offset,&ec,(smba_callback_t)dir_scan_callback_func_exall);

	SHOWVALUE(count);

	if(count == 0 || eac->eac_Entries == 0)
	{
		SHOWMSG("nothing to be read");
		if(ec.ec_Error != OK)
		{
			SHOWMSG("flagging an error");
			SHOWVALUE(ec.ec_Error);
			eac->eac_LastKey = (ULONG)-1;
			error = ec.ec_Error;
		}

		goto out;
	}
	else if (count == (-EIO))
	{
		SHOWMSG("ouch! directory read error");
		eac->eac_LastKey = (ULONG)-1;

		error = ERROR_NO_DEFAULT_DIR;
		goto out;
	}
	else if (count < 0)
	{
		SHOWMSG("error whilst scanning");
		eac->eac_LastKey = (ULONG)-1;

		error = MapErrnoToIoErr(count);
		goto out;
	}

	SHOWMSG("ok");
	result = DOSTRUE;

 out:

	#if DEBUG
	{
		SHOWVALUE(eac->eac_Entries);

		while(ed != NULL)
		{
			SHOWSTRING(ed->ed_Name);

			ed = ed->ed_Next;
		}
	}
	#endif /* DEBUG */

	(*error_ptr) = error;

	RETURN(result);
	return(result);
}

/****************************************************************************/

STATIC LONG
Action_Find(
	LONG					action,
	struct FileHandle *		fh,
	struct FileLock *		parent,
	APTR					bcpl_name,
	LONG *					error_ptr)
{
	LONG result = DOSFALSE;
	STRPTR parent_path = NULL;
	STRPTR full_name = NULL;
	struct FileNode * fn = NULL;
	STRPTR parent_name;
	UBYTE name[MAX_FILENAME_LEN];
	BOOL create_new_file;
	LONG error;

	ENTER();

	switch(action)
	{
		case ACTION_FINDINPUT:
			D(("ACTION_FINDINPUT [Open(\"%b\",MODE_OLDFILE)]",MKBADDR(bcpl_name)));
			break;

		case ACTION_FINDOUTPUT:
			D(("ACTION_FINDOUTPUT [Open(\"%b\",MODE_NEWFILE)]",MKBADDR(bcpl_name)));
			break;

		case ACTION_FINDUPDATE:
			D(("ACTION_FINDUPDATE [Open(\"%b\",MODE_READWRITE)]",MKBADDR(bcpl_name)));
			break;
	}

	SHOWVALUE(parent);

	if(parent != NULL)
	{
		struct LockNode * ln = (struct LockNode *)parent->fl_Key;

		parent_name = ln->ln_FullName;
	}
	else
	{
		parent_name = NULL;
	}

	ConvertBString(sizeof(name),name,bcpl_name);
	TranslateCName(name,A2M);

	if(IsReservedName(FilePart(name)))
	{
		error = ERROR_OBJECT_NOT_FOUND;
		goto out;
	}

	error = BuildFullName(parent_name,name,&full_name);
	if(error != OK)
		goto out;

	/* Trying to open the root directory? */
	if(full_name == NULL)
	{
		error = ERROR_OBJECT_WRONG_TYPE;
		goto out;
	}

	fn = AllocVecPooled(sizeof(*fn));
	if(fn == NULL)
	{
		error = ERROR_NO_FREE_STORE;
		goto out;
	}

	memset(fn,0,sizeof(*fn));

	fn->fn_Handle	= fh;
	fn->fn_FullName	= full_name;
	fn->fn_Mode		= (action == ACTION_FINDOUTPUT) ? EXCLUSIVE_LOCK : SHARED_LOCK;

	error = CheckAccessModeCollision(full_name,fn->fn_Mode);
	if(error != OK)
		goto out;

	SHOWSTRING(full_name);

	if(action == ACTION_FINDOUTPUT)
	{
		/* Definitely create a new file. */
		create_new_file = TRUE;
	}
	else if (action == ACTION_FINDINPUT)
	{
		/* Open an existing file for reading. */
		create_new_file = FALSE;
	}
	else if (action == ACTION_FINDUPDATE)
	{
		smba_file_t * file = NULL;
		smba_stat_t st;

		if(smba_open(ServerData,full_name,&file) == OK &&
		   smba_getattr(file,&st) == OK)
		{
			/* File apparently opens Ok and information on it
			 * is available, don't try to replace it.
			 */
			create_new_file = FALSE;
		}
		else
		{
			/* We try to ignore the error here and assume
			 * that the remainder of the file opening
			 * procedure will produce a useful error
			 * report. In the mean time, assume that the
			 * file needs to be created.
			 */
			create_new_file = TRUE;
		}

		if(file != NULL)
			smba_close(file);
	}
	else
	{
		/* What's that? */
		error = ERROR_ACTION_NOT_KNOWN;
		goto out;
	}

	/* Create a new file? */
	if(create_new_file)
	{
		smba_stat_t st;
		smba_file_t * dir;
		STRPTR base_name;
		LONG i;

		if(WriteProtected)
		{
			error = ERROR_DISK_WRITE_PROTECTED;
			goto out;
		}

		parent_path = AllocVecPooled(strlen(full_name)+3);
		if(parent_path == NULL)
		{
			error = ERROR_NO_FREE_STORE;
			goto out;
		}

		strcpy(parent_path,full_name);
		base_name = NULL;
		for(i = strlen(parent_path)-1 ; i >= 0 ; i--)
		{
			if(parent_path[i] == SMB_PATH_SEPARATOR)
			{
				if(i == 0)
				{
					memmove(&parent_path[1],&parent_path[0],strlen(parent_path)+1);
					i++;
				}

				parent_path[i] = '\0';

				base_name = &parent_path[i+1];
				break;
			}
		}

		SHOWMSG("creating a file; finding parent path first");
		SHOWSTRING(parent_path);

		error = smba_open(ServerData,parent_path,&dir);
		if(error < 0)
		{
			error = MapErrnoToIoErr(error);
			goto out;
		}

		/* Only one attribute counts: the file should not be write protected. */
		memset(&st,0,sizeof(st));

		SHOWMSG("now trying to create the file");
		SHOWSTRING(base_name);

		error = smba_create(dir,base_name,&st);
		if(error < 0)
		{
			SHOWMSG("didn't work.");
			SHOWVALUE(error);

			smba_close(dir);
			error = MapErrnoToIoErr(error);

			SHOWVALUE(error);

			goto out;
		}

		SHOWMSG("good.");

		smba_close(dir);
	}

	/* Now for the remainder... */
	error = smba_open(ServerData,full_name,&fn->fn_File);
	if(error < 0)
	{
		error = MapErrnoToIoErr(error);
		goto out;
	}

	fh->fh_Arg1 = (LONG)fn;

	AddTail((struct List *)&FileList,(struct Node *)fn);
	result = DOSTRUE;

 out:

	if(result == DOSFALSE)
	{
		FreeVecPooled(full_name);
		FreeVecPooled(fn);
	}

	FreeVecPooled(parent_path);

	(*error_ptr) = error;

	RETURN(result);
	return(result);
}

/****************************************************************************/

STATIC LONG
Action_Read(
	struct FileNode *	fn,
	APTR				mem,
	LONG				length,
	LONG *				error_ptr)
{
	LONG result = 0;
	LONG error = OK;

	ENTER();

	if(length > 0)
	{
		result = smba_read(fn->fn_File,mem,length,fn->fn_Offset);
		if(result < 0)
		{
			error = MapErrnoToIoErr(result);
			result = -1;
			goto out;
		}

		fn->fn_Offset += result;
	}

 out:

	(*error_ptr) = error;

	RETURN(result);
	return(result);
}

/****************************************************************************/

STATIC LONG
Action_Write(
	struct FileNode *	fn,
	APTR				mem,
	LONG				length,
	LONG *				error_ptr)
{
	LONG result = DOSFALSE;
	LONG error = OK;

	ENTER();

	if(WriteProtected)
	{
		error = ERROR_DISK_WRITE_PROTECTED;
		goto out;
	}

	if(length > 0)
	{
		result = smba_write(fn->fn_File,mem,length,fn->fn_Offset);
		if(result < 0)
		{
			error = MapErrnoToIoErr(result);
			result = -1;
			goto out;
		}

		fn->fn_Offset += result;
	}

 out:

	(*error_ptr) = error;

	RETURN(result);
	return(result);
}

/****************************************************************************/

STATIC LONG
Action_End(
	struct FileNode *	fn,
	LONG *				error_ptr)
{
	Remove((struct Node *)fn);

	smba_close(fn->fn_File);
	FreeVecPooled(fn->fn_FullName);
	FreeVecPooled(fn);

	(*error_ptr) = OK;
	return(DOSTRUE);
}

/****************************************************************************/

STATIC LONG
Action_Seek(
	struct FileNode *	fn,
	LONG				position,
	LONG				mode,
	LONG *				error_ptr)
{
	smba_stat_t st;
	LONG result;
	LONG error;
	LONG offset;

	ENTER();

	error = smba_getattr(fn->fn_File,&st);
	if(error < 0)
	{
		error = MapErrnoToIoErr(error);
		result = -1;
		goto out;
	}

	result = offset = fn->fn_Offset;

	switch(mode)
	{
		case OFFSET_BEGINNING:

			offset = position;
			break;

		case OFFSET_CURRENT:

			offset += position;
			break;

		case OFFSET_END:

			offset = st.size + position;
			break;

		default:

			error = ERROR_ACTION_NOT_KNOWN;

			result = -1;
			goto out;
	}

	if(offset < 0 || offset > st.size)
	{
		error = ERROR_SEEK_ERROR;

		result = -1;
		goto out;
	}

	fn->fn_Offset = offset;

 out:

	(*error_ptr) = error;

	RETURN(result);
	return(result);
}

/****************************************************************************/

STATIC LONG
Action_SetFileSize(
	struct FileNode *	fn,
	LONG				position,
	LONG				mode,
	LONG *				error_ptr)
{
	smba_stat_t st;
	LONG result = -1;
	LONG error;
	long offset;

	ENTER();

	if(WriteProtected)
	{
		error = ERROR_DISK_WRITE_PROTECTED;
		goto out;
	}

	error = smba_getattr(fn->fn_File,&st);
	if(error < 0)
	{
		error = MapErrnoToIoErr(error);
		goto out;
	}

	offset = fn->fn_Offset;

	switch(mode)
	{
		case OFFSET_BEGINNING:

			offset = position;
			break;

		case OFFSET_CURRENT:

			offset += position;
			break;

		case OFFSET_END:

			offset = st.size + position;
			break;

		default:

			error = ERROR_ACTION_NOT_KNOWN;
			goto out;
	}

	if(offset < 0)
	{
		error = ERROR_SEEK_ERROR;
		goto out;
	}

	st.atime	= -1;
	st.ctime	= -1;
	st.mtime	= -1;
	st.size	= offset;

	error = smba_setattr(fn->fn_File,&st);
	if(error < 0)
	{
		error = MapErrnoToIoErr(error);
		goto out;
	}

	if(fn->fn_Offset > offset)
		fn->fn_Offset = offset;

	result = offset;

 out:

	(*error_ptr) = error;

	RETURN(result);
	return(result);
}

/****************************************************************************/

STATIC LONG
Action_SetDate(
	struct FileLock *	parent,
	APTR				bcpl_name,
	struct DateStamp *	ds,
	LONG *				error_ptr)
{
	LONG result = DOSFALSE;
	STRPTR full_name = NULL;
	smba_file_t * file = NULL;
	STRPTR parent_name;
	UBYTE name[MAX_FILENAME_LEN];
	smba_stat_t st;
	LONG seconds;
	LONG error;

	ENTER();

	if(WriteProtected)
	{
		error = ERROR_DISK_WRITE_PROTECTED;
		goto out;
	}

	SHOWVALUE(parent);

	if(parent != NULL)
	{
		struct LockNode * ln = (struct LockNode *)parent->fl_Key;

		parent_name = ln->ln_FullName;
	}
	else
	{
		parent_name = NULL;
	}

	ConvertBString(sizeof(name),name,bcpl_name);
	TranslateCName(name,A2M);

	error = BuildFullName(parent_name,name,&full_name);
	if(error != OK)
		goto out;

	/* Trying to change the date of the root directory? */
	if(full_name == NULL)
	{
		error = ERROR_OBJECT_IN_USE;
		goto out;
	}

	SHOWSTRING(full_name);

	error = smba_open(ServerData,full_name,&file);
	if(error < 0)
	{
		error = MapErrnoToIoErr(error);
		goto out;
	}

	error = smba_getattr(file,&st);
	if(error < 0)
	{
		error = MapErrnoToIoErr(error);
		goto out;
	}

	seconds = (ds->ds_Days * 24 * 60 + ds->ds_Minute) * 60 + (ds->ds_Tick / TICKS_PER_SECOND);

	st.atime = -1;
	st.ctime = -1;
	st.mtime = UNIX_TIME_OFFSET + seconds + GetTimeZoneDelta();
	st.size = -1;

	error = smba_setattr(file,&st);
	if(error < 0)
	{
		error = MapErrnoToIoErr(error);
		goto out;
	}

	result = DOSTRUE;

 out:

	FreeVecPooled(full_name);
	if(file != NULL)
		smba_close(file);

	(*error_ptr) = error;

	RETURN(result);
	return(result);
}

/****************************************************************************/

STATIC LONG
Action_ExamineFH(
	struct FileNode *		fn,
	struct FileInfoBlock *	fib,
	LONG *					error_ptr)
{
	LONG result = DOSFALSE;
	smba_stat_t st;
	LONG error;
	LONG seconds;
	STRPTR name;
	LONG i;

	ENTER();

	error = smba_getattr(fn->fn_File,&st);
	if(error < 0)
	{
		error = MapErrnoToIoErr(error);
		goto out;
	}

	name = fn->fn_FullName;
	for(i = strlen(name)-1 ; i >= 0 ; i--)
	{
		if(name[i] == SMB_PATH_SEPARATOR)
		{
			name = &name[i+1];
			break;
		}
	}

	/* Just checking: will the name fit? */
	if(strlen(name) >= sizeof(fib->fib_FileName))
	{
		error = ERROR_INVALID_COMPONENT_NAME;
		goto out;
	}

	memset(fib,0,sizeof(*fib));

	ConvertCString(sizeof(fib->fib_FileName),fib->fib_FileName,name);
	TranslateBName(fib->fib_FileName,M2A);

	fib->fib_DirEntryType	= ST_FILE;
	fib->fib_EntryType		= ST_FILE;
	fib->fib_NumBlocks		= (st.size + 511) / 512;
	fib->fib_Size			= st.size;
	fib->fib_DiskKey		= -1;

	seconds = st.mtime - UNIX_TIME_OFFSET - GetTimeZoneDelta();
	if(seconds < 0)
		seconds = 0;

	fib->fib_Date.ds_Days	= (seconds / (24 * 60 * 60));
	fib->fib_Date.ds_Minute	= (seconds % (24 * 60 * 60)) / 60;
	fib->fib_Date.ds_Tick	= (seconds % 60) * TICKS_PER_SECOND;

	result = DOSTRUE;

 out:

	(*error_ptr) = error;

	RETURN(result);
	return(result);
}

/****************************************************************************/

STATIC BPTR
Action_ParentFH(
	struct FileNode *	fn,
	LONG *				error_ptr)
{
	BPTR result = ZERO;
	struct LockNode * ln = NULL;
	LONG error;
	STRPTR full_name;
	LONG i;

	ENTER();

	full_name = AllocVecPooled(strlen(fn->fn_FullName)+3);
	if(full_name == NULL)
	{
		error = ERROR_NO_FREE_STORE;
		goto out;
	}

	strcpy(full_name,fn->fn_FullName);

	for(i = strlen(full_name)-1 ; i >= 0 ; i--)
	{
		if(i == 0)
		{
			strcpy(full_name,SMB_ROOT_DIR_NAME);
			break;
		}
		else if (full_name[i] == SMB_PATH_SEPARATOR)
		{
			full_name[i] = '\0';
			break;
		}
	}

	ln = AllocVecPooled(sizeof(*ln));
	if(ln == NULL)
	{
		error = ERROR_NO_FREE_STORE;
		goto out;
	}

	memset(ln,0,sizeof(*ln));

	ln->ln_FileLock.fl_Key		= (LONG)ln;
	ln->ln_FileLock.fl_Access	= SHARED_LOCK;
	ln->ln_FileLock.fl_Task		= FileSystemPort;
	ln->ln_FileLock.fl_Volume	= MKBADDR(VolumeNode);
	ln->ln_FullName				= full_name;

	SHOWSTRING(full_name);

	error = smba_open(ServerData,full_name,&ln->ln_File);
	if(error < 0)
	{
		error = MapErrnoToIoErr(error);
		goto out;
	}

	AddTail((struct List *)&LockList,(struct Node *)ln);
	result = MKBADDR(&ln->ln_FileLock);
	SHOWVALUE(&ln->ln_FileLock);

 out:

	if(result == ZERO)
	{
		FreeVecPooled(ln);
		FreeVecPooled(full_name);
	}

	(*error_ptr) = error;

	RETURN(result);
	return(result);
}

/****************************************************************************/

STATIC BPTR
Action_CopyDirFH(
	struct FileNode *	fn,
	LONG *				error_ptr)
{
	BPTR result = ZERO;
	struct LockNode * ln = NULL;
	STRPTR full_name = NULL;
	LONG error;

	ENTER();

	if(fn->fn_Mode != SHARED_LOCK)
	{
		error = ERROR_OBJECT_IN_USE;
		goto out;
	}

	full_name = AllocVecPooled(strlen(fn->fn_FullName)+3);
	if(full_name == NULL)
	{
		error = ERROR_NO_FREE_STORE;
		goto out;
	}

	strcpy(full_name,fn->fn_FullName);

	ln = AllocVecPooled(sizeof(*ln));
	if(ln == NULL)
	{
		error = ERROR_NO_FREE_STORE;
		goto out;
	}

	memset(ln,0,sizeof(*ln));

	ln->ln_FileLock.fl_Key		= (LONG)ln;
	ln->ln_FileLock.fl_Access	= SHARED_LOCK;
	ln->ln_FileLock.fl_Task		= FileSystemPort;
	ln->ln_FileLock.fl_Volume	= MKBADDR(VolumeNode);
	ln->ln_FullName				= full_name;

	SHOWSTRING(full_name);

	error = smba_open(ServerData,full_name,&ln->ln_File);
	if(error < 0)
	{
		error = MapErrnoToIoErr(error);
		goto out;
	}

	AddTail((struct List *)&LockList,(struct Node *)ln);
	result = MKBADDR(&ln->ln_FileLock);
	SHOWVALUE(&ln->ln_FileLock);

 out:

	if(result == ZERO)
	{
		FreeVecPooled(ln);
		FreeVecPooled(full_name);
	}

	(*error_ptr) = error;

	RETURN(result);
	return(result);
}

/****************************************************************************/

STATIC LONG
Action_FHFromLock(
	struct FileHandle *	fh,
	struct FileLock *	fl,
	LONG *				error_ptr)
{
	LONG result = DOSFALSE;
	struct FileNode * fn;
	struct LockNode * ln;
	LONG error = OK;

	ENTER();

	SHOWVALUE(fl);

	fn = AllocVecPooled(sizeof(*fn));
	if(fn == NULL)
	{
		error = ERROR_NO_FREE_STORE;
		goto out;
	}

	memset(fn,0,sizeof(*fn));

	ln = (struct LockNode *)fl->fl_Key;

	fn->fn_Handle	= fh;
	fn->fn_FullName	= ln->ln_FullName;
	fn->fn_File		= ln->ln_File;
	fn->fn_Mode		= fl->fl_Access;

	Remove((struct Node *)ln);
	FreeVecPooled(ln);

	fh->fh_Arg1 = (LONG)fn;

	AddTail((struct List *)&FileList,(struct Node *)fn);
	result = DOSTRUE;

 out:

	(*error_ptr) = error;

	RETURN(result);
	return(result);
}

/****************************************************************************/

STATIC LONG
Action_RenameDisk(
	APTR	bcpl_name,
	LONG *	error_ptr)
{
	LONG result = DOSFALSE;
	LONG error = OK;
	STRPTR old_name;
	STRPTR new_name;
	UBYTE * name;
	LONG len;

	ENTER();

	if(NOT VolumeNodeAdded)
	{
		error = ERROR_OBJECT_IN_USE;
		goto out;
	}

	if(WriteProtected)
	{
		error = ERROR_DISK_WRITE_PROTECTED;
		goto out;
	}

	/* Now for the really interesting part; the new name
	 * is to be a NUL-terminated BCPL string, and as such
	 * must be allocated via AllocVec().
	 */

	name = bcpl_name;

	len = name[0];

	new_name = AllocVec(1 + len + 1,MEMF_ANY|MEMF_PUBLIC);
	if(new_name == NULL)
	{
		error = ERROR_NO_FREE_STORE;
		goto out;
	}

	new_name[0] = len;
	memcpy(&new_name[1],&name[1],len);
	new_name[len+1] = '\0';

	Forbid();

	old_name = BADDR(VolumeNode->dol_Name);
	FreeVec(old_name);
	VolumeNode->dol_Name = MKBADDR(new_name);

	Permit();

	SendDiskChange(IECLASS_DISKINSERTED);

	result = DOSTRUE;

 out:

	(*error_ptr) = error;

	RETURN(result);
	return(result);
}

/****************************************************************************/

STATIC LONG
Action_ChangeMode(
	LONG				type,
	APTR				object,
	LONG				new_mode,
	LONG *				error_ptr)
{
	LONG result = DOSFALSE;
	struct FileLock * fl = NULL;
	struct FileNode * fn = NULL;
	struct LockNode * ln = NULL;
	STRPTR name;
	LONG old_mode;
	LONG error = OK;

	ENTER();

	/* Sanity check; verify parameters */
	if((type != CHANGE_LOCK && type != CHANGE_FH) ||
	   (new_mode != EXCLUSIVE_LOCK && new_mode != SHARED_LOCK))
	{
		error = ERROR_ACTION_NOT_KNOWN;
		goto out;
	}

	/* Now obtain the data structures, name and mode
	 * associated with the object in question.
	 */
	if(type == CHANGE_LOCK)
	{
		fl = object;
		ln = (struct LockNode *)fl->fl_Key;
		name = ln->ln_FullName;
		old_mode = fl->fl_Access;
	}
	else
	{
		struct FileHandle * fh = object;

		fn = (struct FileNode *)fh->fh_Arg1;
		name = fn->fn_FullName;
		old_mode = fn->fn_Mode;
	}

	/* Do we need to change anything at all? */
	if(new_mode == old_mode)
	{
		result = DOSTRUE;
		goto out;
	}

	/* This is the easiest case; change an
	 * exclusive access mode to a shared
	 * access mode. Since the original mode
	 * can be used by one object only,
	 * we get away by updating the mode
	 * value.
	 */
	if(new_mode == SHARED_LOCK)
	{
		if(type == CHANGE_LOCK)
			fl->fl_Access = new_mode;
		else
			fn->fn_Mode = new_mode;

		result = DOSTRUE;
		goto out;
	}

	/* Is there another shared access lock
	 * which refers to the same object?
	 */
	if(FindLockNode(name,ln) != NULL)
	{
		error = ERROR_OBJECT_IN_USE;
		goto out;
	}

	/* Is there another shared access file
	 * which refers to the same object?
	 */
	if(FindFileNode(name,fn) != NULL)
	{
		error = ERROR_OBJECT_IN_USE;
		goto out;
	}

	/* There is just one single reference
	 * to this object; change the mode
	 * and quit.
	 */
	if(type == CHANGE_LOCK)
		fl->fl_Access = new_mode;
	else
		fn->fn_Mode = new_mode;

	result = DOSTRUE;

 out:

	(*error_ptr) = error;

	RETURN(result);
	return(result);
}

/****************************************************************************/

STATIC LONG
Action_WriteProtect(
	LONG	flag,
	ULONG	key,
	LONG *	error_ptr)
{
	LONG result = DOSFALSE;
	LONG error = OK;

	ENTER();

	if(flag == DOSFALSE)
	{
		if(WriteProtected)
		{
			if(key != WriteProtectKey)
			{
				error = ERROR_INVALID_LOCK;
				goto out;
			}

			WriteProtected = FALSE;

			if(VolumeNodeAdded)
			{
				SendDiskChange(IECLASS_DISKREMOVED);
				SendDiskChange(IECLASS_DISKINSERTED);
			}
		}
	}
	else
	{
		if(NOT WriteProtected)
		{
			WriteProtected = TRUE;
			WriteProtectKey = key;

			if(VolumeNodeAdded)
			{
				SendDiskChange(IECLASS_DISKREMOVED);
				SendDiskChange(IECLASS_DISKINSERTED);
			}
		}
		else
		{
			error = ERROR_INVALID_LOCK;
			goto out;
		}
	}

	result = DOSTRUE;

 out:

	(*error_ptr) = error;

	RETURN(result);
	return(result);
}

/****************************************************************************/

STATIC LONG
Action_MoreCache(
	LONG	buffer_delta,
	LONG *	error_ptr)
{
	LONG result;
	int old_size;

	ENTER();

	old_size = smba_get_dircache_size(ServerData);

	result = smba_change_dircache_size(ServerData,old_size + buffer_delta);

	if(result == old_size && buffer_delta != 0)
	{
		result = DOSFALSE;
		(*error_ptr) = ERROR_NO_FREE_STORE;
	}

	RETURN(result);
	return(result);
}

/****************************************************************************/

STATIC LONG
Action_SetComment(
	struct FileLock *	parent,
	APTR				bcpl_name,
	APTR				bcpl_comment,
	LONG *				error_ptr)
{
	LONG result = DOSFALSE;
	STRPTR full_name = NULL;
	smba_file_t * file = NULL;
	STRPTR parent_name;
	UBYTE name[MAX_FILENAME_LEN];
	UBYTE comment[80];
	LONG error;

	ENTER();

	if(WriteProtected)
	{
		error = ERROR_DISK_WRITE_PROTECTED;
		goto out;
	}

	SHOWVALUE(parent);

	if(parent != NULL)
	{
		struct LockNode * ln = (struct LockNode *)parent->fl_Key;

		parent_name = ln->ln_FullName;
	}
	else
	{
		parent_name = NULL;
	}

	ConvertBString(sizeof(name),name,bcpl_name);
	TranslateCName(name,A2M);

	error = BuildFullName(parent_name,name,&full_name);
	if(error != OK)
		goto out;

	/* Trying to change the comment of the root directory? */
	if(full_name == NULL)
	{
		error = ERROR_OBJECT_IN_USE;
		goto out;
	}

	SHOWSTRING(full_name);

	error = smba_open(ServerData,full_name,&file);
	if(error < 0)
	{
		error = MapErrnoToIoErr(error);
		goto out;
	}

	ConvertBString(sizeof(comment),comment,bcpl_comment);

	SHOWSTRING(comment);

	/* All this work and we're only doing something very silly... */
	if(strlen(comment) > 0)
	{
		error = ERROR_COMMENT_TOO_BIG;
		goto out;
	}

	result = DOSTRUE;

 out:

	FreeVecPooled(full_name);
	if(file != NULL)
		smba_close(file);

	(*error_ptr) = error;

	RETURN(result);
	return(result);
}

/****************************************************************************/

STATIC VOID
HandleFileSystem(STRPTR device_name,STRPTR volume_name,STRPTR service_name)
{
	BOOL sign_off = FALSE;
	ULONG signals;
	BOOL done;

	ENTER();

	DisplayErrorList();

	if(NOT Quiet && WBStartup == NULL)
	{
		struct CommandLineInterface * cli;

		cli = Cli();
		if(NOT cli->cli_Background)
		{
			struct Process * this_process;
			UBYTE name[MAX_FILENAME_LEN];
			LONG max_cli;
			LONG which;
			LONG i;

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

			Forbid();

			which = max_cli = MaxCli();

			for(i = 1 ; i <= max_cli ; i++)
			{
				if(FindCliProc(i) == this_process)
				{
					which = i;
					break;
				}
			}

			Permit();

			if(volume_name == NULL)
				strncpy(name,device_name,sizeof(name)-1);
			else
				strncpy(name,volume_name,sizeof(name)-1);

			name[sizeof(name)-1] = '\0';

			for(i = strlen(name)-1 ; i >= 0 ; i--)
			{
				if(name[i] == ':')
					name[i] = '\0';
				else
					break;
			}

			LocalPrintf("Connected '%s' to '%s:'; \"Break %ld\" or [Ctrl-C] to stop... ",
			service_name,name,which);

			Flush(Output());

			sign_off = TRUE;
		}
	}

	Quiet = TRUE;

	done = FALSE;

	do
	{
		signals = Wait(SIGBREAKF_CTRL_C | SIGBREAKF_CTRL_F | (1UL << FileSystemPort->mp_SigBit));

		if(signals & (1UL << FileSystemPort->mp_SigBit))
		{
			struct DosPacket * dp;
			struct Message * mn;
			LONG res1,res2;

			while((mn = GetMsg(FileSystemPort)) != NULL)
			{
				dp = (struct DosPacket *)mn->mn_Node.ln_Name;

				D(("got packet; sender '%s'",((struct Node *)dp->dp_Port->mp_SigTask)->ln_Name));

				res2 = 0;

				switch(dp->dp_Action)
				{
					case ACTION_DIE:

						SHOWMSG("ACTION_DIE");
						if(IsListEmpty((struct List *)&FileList) && IsListEmpty((struct List *)&LockList))
						{
							SHOWMSG("no locks or files pending; quitting");

							res1 = DOSTRUE;
						}
						else
						{
							SHOWMSG("locks or files still pending; cannot quit yet");

							res1 = DOSFALSE;
							res2 = ERROR_OBJECT_IN_USE;
						}

						Quit = TRUE;
						break;

					case ACTION_CURRENT_VOLUME:
						/* (Ignore) -> VolumeNode */

						res1 = MKBADDR(VolumeNode);
						break;

					case ACTION_LOCATE_OBJECT:
						/* Lock,Name,Mode -> Lock */

						res1 = Action_LocateObject((struct FileLock *)BADDR(dp->dp_Arg1),(APTR)BADDR(dp->dp_Arg2),dp->dp_Arg3,&res2);
						break;

					case ACTION_RENAME_DISK:
						/* Name -> Bool */

						res1 = Action_RenameDisk((UBYTE *)BADDR(dp->dp_Arg1),&res2);
						break;

					case ACTION_FREE_LOCK:
						/* Lock -> Bool */

						res1 = Action_FreeLock((struct FileLock *)BADDR(dp->dp_Arg1),&res2);
						break;

					case ACTION_DELETE_OBJECT:
						/* Lock,Name -> Bool */

						res1 = Action_DeleteObject((struct FileLock *)BADDR(dp->dp_Arg1),(APTR)BADDR(dp->dp_Arg2),&res2);
						break;

					case ACTION_RENAME_OBJECT:
						/* Source lock,source name,destination lock,destination name -> Bool */

						res1 = Action_RenameObject((struct FileLock *)BADDR(dp->dp_Arg1),BADDR(dp->dp_Arg2),
							(struct FileLock *)BADDR(dp->dp_Arg3),BADDR(dp->dp_Arg4),&res2);

						break;

					case ACTION_MORE_CACHE:
						/* Buffer delta -> Total number of buffers */

						/* NOTE: documentation for this packet type is inconsistent;
						 *       in the 'good old' 1.x days 'res1' was documented as
						 *       the total number of buffers to be returned. In the
						 *       2.x documentation it is said that 'res1' should
						 *       return the success code, with 'res2' to hold the
						 *       total number of buffers. However, the 'AddBuffers'
						 *       shell command doesn't work that way, and the
						 *       dos.library implementation of 'AddBuffers()' doesn't
						 *       work that way either. The 1.3 'AddBuffers' command
						 *       appears to treat a zero result as failure and a
						 *       non-zero result as success, which suggests that this
						 *       is how the packet is supposed to work, contrary to
						 *       what the official documentation says.
						 */
						res1 = Action_MoreCache(dp->dp_Arg1,&res2);
						break;

					case ACTION_COPY_DIR:
						/* Lock -> Lock */

						res1 = Action_CopyDir((struct FileLock *)BADDR(dp->dp_Arg1),&res2);
						break;

					case ACTION_SET_PROTECT:
						/* (Ignore),Lock,Name,Mask -> Bool */

						res1 = Action_SetProtect((struct FileLock *)BADDR(dp->dp_Arg2),BADDR(dp->dp_Arg3),dp->dp_Arg4,&res2);
						break;

					case ACTION_CREATE_DIR:
						/* Lock,Name -> Lock */

						res1 = Action_CreateDir((struct FileLock *)BADDR(dp->dp_Arg1),(APTR)BADDR(dp->dp_Arg2),&res2);
						break;

					case ACTION_EXAMINE_OBJECT:
						/* FileLock,FileInfoBlock -> Bool */

						res1 = Action_ExamineObject((struct FileLock *)BADDR(dp->dp_Arg1),(struct FileInfoBlock *)BADDR(dp->dp_Arg2),&res2);
						break;

					case ACTION_EXAMINE_NEXT:
						/* FileLock,FileInfoBlock -> Bool */

						res1 = Action_ExamineNext((struct FileLock *)BADDR(dp->dp_Arg1),(struct FileInfoBlock *)BADDR(dp->dp_Arg2),&res2);
						break;

					case ACTION_DISK_INFO:
						/* InfoData -> Bool */

						Action_DiskInfo((struct InfoData *)BADDR(dp->dp_Arg1),&res2);
						res1 = DOSTRUE;
						res2 = 0;
						break;

					case ACTION_INFO:
						/* FileLock,InfoData -> Bool */

						res1 = Action_Info((struct FileLock *)BADDR(dp->dp_Arg1),(struct InfoData *)BADDR(dp->dp_Arg2),&res2);
						break;

					case ACTION_SET_COMMENT:
						/* (Ignore),FileLock,Name,Comment -> Bool */

						res1 = Action_SetComment((struct FileLock *)BADDR(dp->dp_Arg2),BADDR(dp->dp_Arg3),BADDR(dp->dp_Arg4),&res2);
						break;

					case ACTION_PARENT:
						/* Lock -> Lock */

						res1 = Action_Parent((struct FileLock *)BADDR(dp->dp_Arg1),&res2);
						break;

					case ACTION_INHIBIT:

						SHOWMSG("ACTION_INHIBIT");
						res1 = DOSTRUE;
						break;

					case ACTION_SET_DATE:
						/* (Ignore),FileLock,Name,DateStamp(APTR) -> Bool */

						res1 = Action_SetDate((struct FileLock *)BADDR(dp->dp_Arg2),(APTR)BADDR(dp->dp_Arg3),(struct DateStamp *)dp->dp_Arg4,&res2);
						break;

					case ACTION_SAME_LOCK:
						/* Lock,Lock -> Bool */

						res1 = Action_SameLock((struct FileLock *)BADDR(dp->dp_Arg1),(struct FileLock *)BADDR(dp->dp_Arg2),&res2);
						break;

					case ACTION_READ:
						/* FileHandle->fh_Arg1,Buffer(APTR),Length -> Length */

						res1 = Action_Read((struct FileNode *)dp->dp_Arg1,(APTR)dp->dp_Arg2,dp->dp_Arg3,&res2);
						break;

					case ACTION_WRITE:
						/* FileHandle->fh_Arg1,Buffer(APTR),Length -> Length */

						res1 = Action_Write((struct FileNode *)dp->dp_Arg1,(APTR)dp->dp_Arg2,dp->dp_Arg3,&res2);
						break;

					case ACTION_FINDUPDATE:
					case ACTION_FINDINPUT:
					case ACTION_FINDOUTPUT:
						/* FileHandle,FileLock,Name -> Bool */

						res1 = Action_Find(dp->dp_Action,(struct FileHandle *)BADDR(dp->dp_Arg1),(struct FileLock *)BADDR(dp->dp_Arg2),(APTR)BADDR(dp->dp_Arg3),&res2);
						break;

					case ACTION_END:
						/* FileHandle->fh_Arg1 -> Bool */

						res1 = Action_End((struct FileNode *)dp->dp_Arg1,&res2);
						break;

					case ACTION_SEEK:
						/* FileHandle->fh_Arg1,Position,Mode -> Position */

						res1 = Action_Seek((struct FileNode *)dp->dp_Arg1,dp->dp_Arg2,dp->dp_Arg3,&res2);
						break;

					case ACTION_SET_FILE_SIZE:
						/* FileHandle->fh_Arg1,Offset,Mode -> New file size */

						res1 = Action_SetFileSize((struct FileNode *)dp->dp_Arg1,dp->dp_Arg2,dp->dp_Arg3,&res2);
						break;

					case ACTION_WRITE_PROTECT:
						/* Flag,Key -> Bool */

						res1 = Action_WriteProtect(dp->dp_Arg1,dp->dp_Arg2,&res2);
						break;

					case ACTION_FH_FROM_LOCK:
						/* FileHandle(BPTR),FileLock -> Bool */

						res1 = Action_FHFromLock((struct FileHandle *)BADDR(dp->dp_Arg1),(struct FileLock *)BADDR(dp->dp_Arg2),&res2);
						break;

					case ACTION_IS_FILESYSTEM:

						SHOWMSG("ACTION_IS_FILESYSTEM");
						res1 = DOSTRUE;
						break;

					case ACTION_CHANGE_MODE:
						/* Type,Object,Mode -> Bool */

						res1 = Action_ChangeMode(dp->dp_Arg1,(APTR)BADDR(dp->dp_Arg2),dp->dp_Arg3,&res2);
						break;

					case ACTION_COPY_DIR_FH:
						/* FileHandle->fh_Arg1 -> Bool */

						res1 = Action_CopyDirFH((struct FileNode *)dp->dp_Arg1,&res2);
						break;

					case ACTION_PARENT_FH:
						/* FileHandle->fh_Arg1 -> Bool */

						res1 = Action_ParentFH((struct FileNode *)dp->dp_Arg1,&res2);
						break;

					case ACTION_EXAMINE_ALL:
						/* FileLock,ExAllData(APTR),Size,Type,ExAllControl(APTR) -> Bool */

						res1 = Action_ExamineAll((struct FileLock *)BADDR(dp->dp_Arg1),(struct ExAllData *)dp->dp_Arg2,
							dp->dp_Arg3,dp->dp_Arg4,(struct ExAllControl *)dp->dp_Arg5,&res2);

						break;

					case ACTION_EXAMINE_FH:
						/* FileHandle->fh_Arg1,FileInfoBlock -> Bool */

						res1 = Action_ExamineFH((struct FileNode *)dp->dp_Arg1,(struct FileInfoBlock *)BADDR(dp->dp_Arg2),&res2);
						break;

					case ACTION_EXAMINE_ALL_END:
						/* FileLock,ExAllData(APTR),Size,Type,ExAllControl(APTR) -> Bool */

						res1 = DOSTRUE;
						break;

					default:

						D(("Anything goes: dp->dp_Action=%ld (0x%lx)",dp->dp_Action,dp->dp_Action));

						res1 = DOSFALSE;
						res2 = ERROR_ACTION_NOT_KNOWN;

						break;
				}

				SHOWVALUE(res1);
				SHOWVALUE(res2);

				ReplyPkt(dp,res1,res2);

				D(("\n"));
			}
		}

		#if DEBUG
		{
			if(signals & SIGBREAKF_CTRL_F)
			{
				struct FileNode * fn;
				struct LockNode * ln;

				D(("list of open files:"));

				for(fn = (struct FileNode *)FileList.mlh_Head ;
				    fn->fn_MinNode.mln_Succ != NULL ;
				    fn = (struct FileNode *)fn->fn_MinNode.mln_Succ)
				{
					D(("  name='%s'",fn->fn_FullName));
					D(("  mode=%ld, offset=%ld",fn->fn_Mode,fn->fn_Offset));
					D((""));
				}

				D(("list of allocated locks:"));

				for(ln = (struct LockNode *)LockList.mlh_Head ;
				    ln->ln_MinNode.mln_Succ != NULL ;
				    ln = (struct LockNode *)ln->ln_MinNode.mln_Succ)
				{
					D(("  name='%s'",ln->ln_FullName));
					D(("  mode=%ld",ln->ln_FileLock.fl_Access));
					D((""));
				}
			}
		}
		#endif /* DEBUG */

		if(signals & SIGBREAKF_CTRL_C)
			Quit = TRUE;

		if(Quit)
		{
			if(IsListEmpty((struct List *)&FileList) && IsListEmpty((struct List *)&LockList))
			{
				SHOWMSG("no locks or files pending; quitting");
				done = TRUE;
			}
			else
			{
				SHOWMSG("locks or files still pending; cannot quit yet");
			}
		}
	}
	while(NOT done);

	if(sign_off)
		LocalPrintf("stopped.\n");

	LEAVE();
}
