/*
 * $Id: main.c 1.55 2000/12/21 12:18:24 olsen Exp olsen $
 *
 * :ts=4
 *
 * SMB file system wrapper for AmigaOS, using the AmiTCP V4 API
 *
 * Copyright (C) 2000 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)

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

#ifdef __SASC
#define FAR __far
#define ASM __asm
#define REG(x) register __ ## x
#define SAVEDS __saveds
#define INLINE __inline
#else
#define FAR
#define ASM
#define REG(x)
#define SAVEDS
#define INLINE
#endif /* __SASC */

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

#define UNIX_TIME_OFFSET 252482400
#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;
};

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

/* 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);

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

/* Forward declarations for local routines. */
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 INLINE TranslateBName(UBYTE *name, UBYTE *map);
STATIC VOID INLINE TranslateCName(UBYTE *name, UBYTE *map);
STATIC BOOL ReallyRemoveDosEntry(struct DosList *entry);
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, STRPTR device_name, STRPTR volume_name, STRPTR translation_file);
STATIC VOID INLINE ConvertBString(LONG max_len, STRPTR cstring, APTR bstring);
STATIC VOID INLINE ConvertCString(LONG max_len, APTR bstring, STRPTR cstring);
STATIC LONG BuildFullName(STRPTR parent_name, STRPTR name, STRPTR *result_ptr);
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);

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

VOID ReportError(STRPTR fmt,...);
VOID SPrintf(STRPTR buffer, STRPTR formatString,...);

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

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 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];

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

extern struct Library * FAR	AbsExecBase;

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

LONG SAVEDS
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;
		KEY		TranslationFile;
		KEY		Service;
	} args;

	STRPTR cmd_template =
		"DOMAIN=WORKGROUP/A,"
		"USER=USERNAME,"
		"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,"
		"TRANSLATE=TRANSLATIONFILE/K,"
		"SERVICE/A/F";

	struct Process * this_process;
	UBYTE program_name[MAX_FILENAME_LEN];
	LONG result;
	LONG number;
	LONG cache_size = 0;

	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 < 37)
	{
		/* Complain loudly if this is not the operating
		 * system version we expected.
		 */
		if(DOSBase != NULL && this_process->pr_CLI != ZERO)
		{
			STRPTR msg = "AmigaOS 2.04 or higher required.\n";

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

		Cleanup();

		return(RETURN_FAIL);
	}

	/* This needs to be set up properly for error report
	 * 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");

		args.Workgroup = str;

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

		args.UserName = str;

		str = FindToolType(Icon->do_ToolTypes,"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,"CACHE");
		if(str == NULL)
			str = FindToolType(Icon->do_ToolTypes,"CACHESIZE");

		if(str != NULL)
		{
			LONG number;

			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("'WORKGROUP' parameter needs an argument");
			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.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.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);
}

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

/* 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)
{
	const struct { int code ; char * description; } tab[] =
	{
		{ HOST_NOT_FOUND,	"Authoritative Answer Host not found" },
		{ TRY_AGAIN,		"Non-Authoritive Host not found, or SERVERFAIL" },
		{ NO_RECOVERY,		"Non recoverable errors, FORMERR, REFUSED, NOTIMP" },
		{ NO_DATA,			"Valid name, no data record of requested type" },
		{ -1 }
	};

	STRPTR result = "Host lookup failure";
	int i;

	for(i = 0 ; tab[i].code != -1 ; i++)
	{
		if(tab[i].code == error)
		{
			result = (STRPTR)tab[i].description;
			break;
		}
	}

	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));

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

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

			Printf("\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 */

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

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

	if(size > 0)
	{
		ULONG * mem;

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

		mem = AsmAllocPooled(MemoryPool,size,SysBase);
		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(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;

	seconds -= GetTimeZoneDelta();

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

	Amiga2Date(seconds,&clock);

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

	tm->tm_sec	= clock.sec;
	tm->tm_min	= clock.min;
	tm->tm_hour	= clock.hour;
	tm->tm_mday	= clock.mday;
	tm->tm_mon	= clock.month - 1;
	tm->tm_year	= clock.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;
	time_t seconds;

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

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

	return(seconds);
}

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

struct FormatContext
{
	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
VSPrintf(STRPTR buffer, STRPTR formatString, va_list args)
{
	RawDoFmt(formatString,args,(VOID (*)())"\x16\xC0\x4E\x75",buffer);
}

/* 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 = 0;

	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 = 0;

	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 > 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 - 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 + sizeof(read_len) + 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 conflic 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.
 */
STATIC VOID INLINE
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. */
STATIC VOID INLINE
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)
{
	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);

			SendDiskChange(IECLASS_DISKREMOVED);
		}
		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(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)
	{
		AsmDeletePool(MemoryPool,SysBase);
		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,
	STRPTR	device_name,
	STRPTR	volume_name,
	STRPTR	translation_file)
{
	BOOL result = FALSE;
	int error;
	UBYTE name[MAX_FILENAME_LEN];
	LONG i;

	ENTER();

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

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

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

	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",4);
	if(SocketBase == NULL)
	{
		ReportError("Could not open 'bsdsocket.library' V4; 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 < strlen(opt_password) ; i++)
			opt_password[i] = ToUpper(opt_password[i]);
	}

	TranslateNames = FALSE;

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

		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;
	}

	Forbid();

	/* If a device name was provided, check whether it is
	 * well-formed.
	 */
	if(device_name != NULL)
	{
		struct DosList * dl;
		BOOL device_exists;

		for(i = 0 ; i < strlen(device_name) ; i++)
		{
			if(device_name[i] == '/')
			{
				Permit();

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

		if(device_name[0] == '\0')
		{
			Permit();

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

		strncpy(name,device_name,255);
		name[255] = '\0';

		/* Chop off any trailing colons. */
		for(i = strlen(name)-1 ; i >= 0 ; i--)
		{
			if(name[i] == ':')
				name[i] = '\0';
		}

		if(device_name[0] == '\0')
		{
			Permit();

			ReportError("Device name '%s:' cannot be used with AmigaDOS.",device_name);
			goto out;
		}

		dl = LockDosList(LDF_READ|LDF_DEVICES);
		device_exists = (BOOL)(FindDosEntry(dl,device_name,LDF_DEVICES) != NULL);
		UnLockDosList(LDF_DEVICES);

		if(device_exists)
		{
			Permit();

			ReportError("Device name '%s:' is already taken.",device_name);
			goto out;
		}
	}
	else
	{
		struct DosList * dl;
		BOOL device_exists;

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

			dl = LockDosList(LDF_READ|LDF_DEVICES);
			device_exists = (BOOL)(FindDosEntry(dl,name,LDF_DEVICES) != NULL);
			UnLockDosList(LDF_DEVICES);

			if(NOT device_exists)
			{
				device_name = name;
				break;
			}
		}
	}

	if(device_name != NULL)
	{
		DeviceNode = MakeDosEntry(name,DLT_DEVICE);
		if(DeviceNode == NULL)
		{
			Permit();

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

		DeviceNode->dol_Task = FileSystemPort;

		AddDosEntry(DeviceNode);
		DeviceNodeAdded = TRUE;
	}

	Permit();

	/* Now take care of the volume name; make sure that it is
	 * well-formed.
	 */
	if(volume_name == NULL)
		strncpy(name,device_name,255);
	else
		strncpy(name,volume_name,255);

	name[255] = '\0';

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

	if(name[0] == '\0')
	{
		ReportError("Volume name '%s' cannot be used with AmigaDOS.",name);
		goto out;
	}

	/* Chop off any trailing colons. */
	for(i = strlen(name)-1 ; i >= 0 ; i--)
	{
		if(name[i] == ':')
			name[i] = '\0';
	}

	VolumeNode = MakeDosEntry(name,DLT_VOLUME);
	if(VolumeNode == NULL)
	{
		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(volume_name != NULL)
	{
 		AddDosEntry(VolumeNode);
		VolumeNodeAdded = TRUE;

		SendDiskChange(IECLASS_DISKINSERTED);
	}

	SetProgramName(NewProgramName);

	result = TRUE;

 out:

	RETURN(result);
	return(result);
}

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

/* Convert a BCPL string into a standard NUL terminated 'C' string. */
STATIC VOID INLINE
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. */
STATIC VOID INLINE
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;

	ENTER();

	SHOWSTRING(parent_name);
	SHOWSTRING(name);

	(*result_ptr) = NULL;

	/* parent_name == NULL means 'root directory', and
	 * so does parent_name == "\\".
	 *
	 * name == NULL means 'use parent name', and so
	 * does name == "".
	 */

	if(name != NULL && name[0] == '\0')
		name = NULL;

	/* Relative to root directory? */
	if(name != NULL && name[0] == ':')
	{
		parent_name = NULL;

		while(name[0] == ':')
			name++;
	}

	if(parent_name != NULL && strcmp(parent_name,SMB_ROOT_DIR_NAME) == SAME)
		parent_name = NULL;

	/* 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;
	}

	/* The path starts with the name of parent. */
	if(parent_name != NULL)
	{
		if(parent_name[0] == SMB_PATH_SEPARATOR)
		{
			while(parent_name[1] == SMB_PATH_SEPARATOR)
				parent_name++;

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

	/* If there's a name to add, do just that. */
	if(name != NULL)
	{
		LONG diff,i,j,start;

		if(buffer[0] == '\0')
		{
			buffer[0] = SMB_PATH_SEPARATOR;
			buffer[1] = '\0';
		}

		/* Append the name. */
		if(parent_name != NULL)
		{
			for(i = 0 ; i < strlen(buffer) ; i++)
			{
				if(buffer[i] == SMB_PATH_SEPARATOR)
					buffer[i] = '/';
			}

			AddPart(buffer,name,len);
		}
		else
		{
			strcat(buffer,name);
		}

		/* Parse the path specification, eliminating // combinations. */
		len = strlen(buffer);

		i = len;
		while(len > 1)
		{
			if((buffer[len-1] == '/') && (buffer[len-2] != '/') && (buffer[len-2] != ':'))
				buffer[--len] = '\0';

			i--;
			if((i == 0) || (buffer[i] == ':') || (buffer[i-1] == ':'))
				break;

			if((i > 1) && (buffer[i] == '/') && (buffer[i-1] == '/') && (buffer[i-2] != ':') && (buffer[i-2] != '/'))
			{
				start = i;
				i -= 2;

				while((i > 0) && (buffer[i] != ':') && (buffer[i] != '/'))
					i--;

				if((buffer[i] == ':') || (buffer[i] == '/'))
					i++;

				j = i;
				diff = start-i+1;
				len -= diff;

				while(j < len)
				{
					buffer[j] = buffer[j+diff];
					j++;
				}

				buffer[len] = '\0';
				i = len;
			}
		}

		/* Last sanity check; there shouldn't be any isolated
		 * backslashes and no double backslashes should remain
		 * either.
		 */
		len = strlen(buffer);
		if(len > 0)
		{
			LONG start;

			if(buffer[len-1] == '/')
			{
				error = ERROR_INVALID_COMPONENT_NAME;
				goto out;
			}

			if(buffer[0] == '/' && buffer[1] == '/')
			{
				for(i = 0 ; i <= len ; i++)
					buffer[i] = buffer[i+1];

				len--;
			}

			for(i = 0 ; i < len-1 ; i++)
			{
				if(buffer[i] == '/' && buffer[i+1] == '/')
				{
					error = ERROR_INVALID_COMPONENT_NAME;
					goto out;
				}
			}

			start = -1;

			for(i = 0 ; i < len ; i++)
			{
				if(buffer[i] == ':')
					start = i+1;
			}

			if(start != -1)
			{
				for(i = 0 ; i <= len - start + 1 ; i++)
					buffer[i] = buffer[start+i];
			}

			len = strlen(buffer);

			if(buffer[0] != '/')
			{
				for(i = len+1 ; i > 0 ; i--)
					buffer[i] = buffer[i-1];

				buffer[0] = '/';
				len++;
			}

			for(i = 0 ; i < len ; i++)
			{
				if(buffer[i] == '/')
					buffer[i] = SMB_PATH_SEPARATOR;
			}
		}
	}

	/* If the buffer remains empty, it means that the
	 * reference should be made to the root directory.
	 */
	if(buffer[0] == '\0')
		strcpy(buffer,SMB_ROOT_DIR_NAME);

	(*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;
	LONG i;

	ENTER();

	SHOWVALUE(parent);

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

		parent_name = ln->ln_FullName;
		if(strcmp(parent_name,SMB_ROOT_DIR_NAME) == SAME)
			parent_name = NULL;
	}
	else
	{
		parent_name = NULL;
	}

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

	/* Now for the interesting bit. Is this already the
	 * root directory we're dealing with?
	 */
	if(strcmp(full_name,SMB_ROOT_DIR_NAME) == SAME)
		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;

	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;
		}
	}

	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 stat;
	LONG error;
	int i;

	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;
		if(strcmp(parent_name,SMB_ROOT_DIR_NAME) == SAME)
			parent_name = NULL;
	}
	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;

	/* 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);

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

	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,&stat);
	if(error < 0)
	{
		error = MapErrnoToIoErr(error);
		goto out;
	}

	smba_close(file);
	file = NULL;

	if(stat.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 * ln = (struct LockNode *)parent->fl_Key;

		parent_name = ln->ln_FullName;
		if(strcmp(parent_name,SMB_ROOT_DIR_NAME) == SAME)
			parent_name = NULL;
	}
	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;

	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(i == 0)
		{
			base_name = full_name;
			strcpy(dir_name,SMB_ROOT_DIR_NAME);
			break;
		}
		else if (dir_name[i] == SMB_PATH_SEPARATOR)
		{
			base_name = &full_name[i+1];
			dir_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	= 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 * ln = (struct LockNode *)parent->fl_Key;

		parent_name = ln->ln_FullName;
		if(strcmp(parent_name,SMB_ROOT_DIR_NAME) == SAME)
			parent_name = NULL;
	}
	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;

	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 * source;
	struct LockNode * ln = NULL;
	LONG error = OK;

	ENTER();

	SHOWVALUE(lock);

	if(lock == NULL)
	{
		SHOWMSG("zero lock");
		goto out;
	}

	if(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));

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

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

	strcpy(full_name,source->ln_FullName);

	ln->ln_FileLock.fl_Key		= (LONG)ln;
	ln->ln_FileLock.fl_Access	= source->ln_FileLock.fl_Access;
	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 stat;
	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;
		if(strcmp(parent_name,SMB_ROOT_DIR_NAME) == SAME)
			parent_name = NULL;
	}
	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;

	SHOWSTRING(full_name);

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

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

	mask ^= FIBF_READ | FIBF_WRITE | FIBF_EXECUTE | FIBF_DELETE;

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

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

	error = smba_setattr(file,&stat);
	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;
		if(strcmp(parent_name,SMB_ROOT_DIR_NAME) == SAME)
			parent_name = NULL;
	}
	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;

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

		parent_name = ln->ln_FullName;
		if(strcmp(parent_name,SMB_ROOT_DIR_NAME) == SAME)
			parent_name = NULL;
	}
	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;

	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,volume_name,len+1);

		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 stat;

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

			error = MapErrnoToIoErr(error);
			goto out;
		}

		seconds = stat.mtime - UNIX_TIME_OFFSET - GetTimeZoneDelta();

		/* There seems to be a bug in there somewhere which
		 * manages to get the time out of sync by six hours and
		 * one second.
		 */
		seconds += (6 * 60 * 60 + 1);

		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,volume_name,len+1);

			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	= stat.is_dir ? ST_USERDIR : ST_FILE;
			fib->fib_EntryType		= fib->fib_DirEntryType;
			fib->fib_NumBlocks		= (stat.size + 511) / 512;
			fib->fib_Size			= stat.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(stat.is_wp)
				fib->fib_Protection ^= (FIBF_OTR_WRITE|FIBF_OTR_DELETE|FIBF_GRP_WRITE|FIBF_GRP_DELETE|FIBF_WRITE|FIBF_DELETE);

			if(NOT stat.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						fpos,
	int						nextpos,
	char *					name,
	int						eof,
	smba_stat_t *			stat)
{
	int result;

	ENTER();

	D((" '%s'",name));
	D(("   is_dir=%ld is_wp=%ld is_hidden=%ld size=%ld",
		stat->is_dir,stat->is_wp,stat->is_hidden,stat->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 (stat->is_hidden && OmitHidden))
	{
		LONG seconds;

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

		fib->fib_DirEntryType	= stat->is_dir ? ST_USERDIR : ST_FILE;
		fib->fib_EntryType		= fib->fib_DirEntryType;
		fib->fib_NumBlocks		= (stat->size + 511) / 512;
		fib->fib_Size			= stat->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(stat->is_wp)
			fib->fib_Protection ^= (FIBF_OTR_WRITE|FIBF_OTR_DELETE|FIBF_GRP_WRITE|FIBF_GRP_DELETE|FIBF_WRITE|FIBF_DELETE);

		seconds = stat->mtime - UNIX_TIME_OFFSET - GetTimeZoneDelta();

		/* There seems to be a bug in there somewhere which
		 * manages to get the time out of sync by six hours and
		 * one second.
		 */
		seconds += (6 * 60 * 60 + 1);

		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						fpos,
	int						nextpos,
	char *					name,
	int						eof,
	smba_stat_t *			stat)
{
	int result = 0;

	ENTER();

	D((" '%s'",name));
	D(("   is_dir=%ld is_wp=%ld is_hidden=%ld size=%ld",
		stat->is_dir,stat->is_wp,stat->is_hidden,stat->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 (stat->is_hidden && OmitHidden))
	{
		struct ExAllData * ed;
		ULONG size;
		ULONG type = ec->ec_Type;
		BOOL take_it;

		size = (ec->ec_MinSize + strlen(name)+1 + 3) & ~3;
		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 = stat->is_dir ? ST_USERDIR : ST_FILE;

		if(type >= ED_SIZE)
			ed->ed_Size = stat->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(stat->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 = stat->mtime - UNIX_TIME_OFFSET - GetTimeZoneDelta();

			/* There seems to be a bug in there somewhere which
			 * manages to get the time out of sync by six hours and
			 * one second.
			 */
			seconds += (6 * 60 * 60 + 1);

			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");
			if(NOT CallHookPkt(ec->ec_Control->eac_MatchFunc,ed,&type))
			{
				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++;
		}
	}

	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 stat;

		SHOWMSG("first invocation");

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

		if(NOT stat.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];
	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;
		if(strcmp(parent_name,SMB_ROOT_DIR_NAME) == SAME)
			parent_name = NULL;
	}
	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;

	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_FINDINPUT) ? SHARED_LOCK : EXCLUSIVE_LOCK;

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

	SHOWSTRING(full_name);

	/* Create a new file? */
	if(action == ACTION_FINDOUTPUT)
	{
		smba_stat_t stat;
		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(i == 0)
			{
				base_name = full_name;
				strcpy(parent_path,SMB_ROOT_DIR_NAME);
				break;
			}
			else if (parent_path[i] == SMB_PATH_SEPARATOR)
			{
				base_name = &parent_path[i+1];
				parent_path[i] = '\0';
				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(&stat,0,sizeof(stat));

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

		error = smba_create(dir,base_name,&stat);
		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 stat;
	LONG result;
	LONG error;
	LONG offset;

	ENTER();

	error = smba_getattr(fn->fn_File,&stat);
	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 = stat.size + position;
			break;

		default:

			error = ERROR_ACTION_NOT_KNOWN;

			result = -1;
			goto out;
	}

	if(offset < 0 || offset > stat.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 stat;
	LONG result = -1;
	LONG error;
	long offset;

	ENTER();

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

	error = smba_getattr(fn->fn_File,&stat);
	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 = stat.size + position;
			break;

		default:

			error = ERROR_ACTION_NOT_KNOWN;
			goto out;
	}

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

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

	error = smba_setattr(fn->fn_File,&stat);
	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 stat;
	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;
		if(strcmp(parent_name,SMB_ROOT_DIR_NAME) == SAME)
			parent_name = NULL;
	}
	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;

	SHOWSTRING(full_name);

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

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

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

	/* There seems to be a bug in there somewhere which
	 * manages to get the time out of sync by six hours and
	 * one second.
	 */
	seconds -= (6 * 60 * 60 + 1);

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

	error = smba_setattr(file,&stat);
	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 stat;
	LONG error;
	LONG seconds;
	STRPTR name;
	LONG i;

	ENTER();

	error = smba_getattr(fn->fn_File,&stat);
	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		= (stat.size + 511) / 512;
	fib->fib_Size			= stat.size;
	fib->fib_DiskKey		= -1;

	seconds = stat.mtime - UNIX_TIME_OFFSET - GetTimeZoneDelta();

	/* There seems to be a bug in there somewhere which
	 * manages to get the time out of sync by six hours and
	 * one second.
	 */
	seconds += (6 * 60 * 60 + 1);

	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;
		if(strcmp(parent_name,SMB_ROOT_DIR_NAME) == SAME)
			parent_name = NULL;
	}
	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;

	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;
			}

			Printf("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)
		Printf("stopped.\n");

	LEAVE();
}
