/*
 * $Id: amiga.c 1.13 1999/12/25 11:31:10 olsen Exp olsen $
 *
 * :ts=4
 *
 * AmigaOS wrapper routines for Samba 2.0.0, using the AmiTCP V4 API
 * and the SAS/C V6.58 compiler.
 *
 * Copyright (C) 1999 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.
 */

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

#include <bsdsocket/socketbasetags.h>
#include <libraries/usergroup.h>

#include <intuition/intuition.h>
#include <libraries/locale.h>
#include <exec/execbase.h>
#include <exec/memory.h>
#include <dos/dosextens.h>
#include <dos/dostags.h>
#include <devices/timer.h>
#include <utility/date.h>

#include <clib/usergroup_protos.h>
#include <clib/socket_protos.h>

#include <clib/intuition_protos.h>
#include <clib/utility_protos.h>
#include <clib/locale_protos.h>
#include <clib/timer_protos.h>
#include <clib/alib_protos.h>
#include <clib/exec_protos.h>
#include <clib/dos_protos.h>

#include <sys/socket.h>
#include <sys/ioctl.h>
#include <sys/time.h>
#include <sys/dir.h>

#include <net/if.h>

#include <constructor.h>
#include <stdarg.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <signal.h>
#include <utime.h>
#include <errno.h>
#include <stdio.h>
#include <fcntl.h>
#include <inetd.h>
#include <stat.h>
#include <ios1.h>
#include <dos.h>

#include <pragmas/usergroup_pragmas.h>
#include <pragmas/socket_pragmas.h>

#include <pragmas/exec_sysbase_pragmas.h>
#include <pragmas/intuition_pragmas.h>
#include <pragmas/utility_pragmas.h>
#include <pragmas/locale_pragmas.h>
#include <pragmas/timer_pragmas.h>
#include <pragmas/dos_pragmas.h>

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

/* This is for the "iface_struct" definition. */
#include "interfaces.h"

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

/*#define DEBUG*/
#include "Assert.h"

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

STATIC VOID ReportProblem(const char *fmt, ...);
STATIC VOID ForbidDOSCleanup(VOID);
STATIC VOID PermitDOS(VOID);
STATIC VOID ForbidDOS(VOID);
STATIC VOID UnblockDescriptorCleanup(VOID);
STATIC BOOL IsDescriptorNonblocking(int fd);
STATIC VOID BlockDescriptor(int fd);
STATIC VOID UnblockDescriptor(int fd);
STATIC VOID SaveDescriptorCleanup(VOID);
STATIC VOID RestoreDescriptor(struct UFB *ufb);
STATIC BOOL SaveDescriptor(struct UFB *ufb);
STATIC VOID UnmangleName(char **namePtr, struct MangleInfo *mi);
STATIC int MangleName(char ** namePtr,struct MangleInfo * mi);
STATIC VOID CloseUnlinkUnlockCleanup(VOID);
STATIC VOID OpenDirCleanup(VOID);
STATIC int TranslateRelativePath(char **namePtr, char *replace, int maxReplaceLen);
STATIC VOID MapDescriptorSets(const fd_set *input_fds, int num_input_fds, fd_set *socket_fds, int *max_socket_fd_ptr, fd_set *file_fds, int *max_file_fd_ptr);
STATIC VOID RemapDescriptorSets(const fd_set *socket_fds, int max_socket_fd, const fd_set *file_fds, int max_file_fd, fd_set *output_fds, int num_output_fds);
STATIC VOID ConvertFileInfoToStat(struct MsgPort *port, struct FileInfoBlock *fib, struct stat *st);
STATIC BOOL do_match(STRPTR str, STRPTR regexp);
STATIC VOID nstrcpy_blank(const size_t maxSize, char *to, const char *from);
STATIC BOOL SetFileSocket(FILE *stream, int sockfd);
STATIC VOID DaemonInit(VOID);
STATIC struct tm *ConvertTime(ULONG seconds);
STATIC VOID MapIoErrToErrno(VOID);
STATIC int MapFileNameAmigaToUnix(const char *amiga, char *unix, int maxUnixLen);
STATIC VOID FlushSTDOUT(VOID);
STATIC VOID CleanupSambaSemaphore(VOID);
STATIC BOOL SetupSambaSemaphore(VOID);
STATIC VOID RemoveLockedRegionNode(struct UFB *ufb, LONG start, LONG stop);
STATIC VOID DeleteLockedRegionNode(struct LockedRegionNode *lrn);
STATIC LONG CreateLockedRegionNode(struct LockedRegionNode **resultPtr);
STATIC VOID DeleteFileLockNode(struct FileLockNode *fln);
STATIC LONG CreateFileLockNode(struct UFB *ufb, struct FileLockNode **resultPtr);
STATIC LONG FindFileLockNodeByFileHandle(BPTR fileHandle, struct FileLockNode **resultPtr);
STATIC VOID FindFileLockNodeByDrawerAndName(BPTR parentDir, STRPTR fileName, struct FileLockNode **resultPtr);
STATIC VOID CleanupFileLocks(int fd);
STATIC int HandleFileLocking(int cmd, struct flock *l, struct UFB *ufb);
STATIC struct LockedRegionNode *FindCollidingRegion(struct FileLockNode *fln, LONG start, LONG stop, BOOL shared);

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

int amiga_sigmask(int signum);
int amiga_sigblock(int sigmask);
int amiga_sigsetmask(int sigmask);
int amiga_unlink(char *name);
int amiga_open(char *name, int mode, int prot);
int amiga_chdir(char *path);
DIR *amiga_opendir(char *dirName);
VOID amiga_closedir(DIR *dir);
struct dirent *amiga_readdir(DIR *dir);
int amiga_mkdir(char *name, int mode);
int amiga_rmdir(char *name);
int amiga_creat(char *name, int prot);
FILE *amiga_fopen(char *name, char *mode);
int amiga_rename(char *old, char *new);
char *amiga_getcwd(char *buf, size_t size);
int amiga_ftruncate(int fd, off_t size);
int amiga_accept(int sockfd, struct sockaddr *cliaddr, int *addrlen);
int amiga_bind(int sockfd, struct sockaddr *name, int namelen);
int amiga_close(int fd);
int amiga_connect(int sockfd, struct sockaddr *name, int namelen);
int amiga_getpeername(int sockfd, struct sockaddr *name, int *namelen);
int amiga_getsockopt(int sockfd, int level, int optname, VOID *optval, int *optlen);
int amiga_ioctl(int fd, unsigned long request, char *arg);
int amiga_listen(int sockfd, int backlog);
int amiga_read(int fd, VOID *data, unsigned int size);
int amiga_recvfrom(int sockfd, VOID *buff, int len, int flags, struct sockaddr *from, int *fromlen);
int amiga_select(int num_fds, fd_set *read_fds, fd_set *write_fds, fd_set *except_fds, struct timeval *timeout);
int amiga_sendto(int sockfd, VOID *buff, int len, int flags, struct sockaddr *to, int tolen);
int amiga_setsockopt(int sockfd, int level, int optname, VOID *optval, int optlen);
int amiga_socket(int domain, int type, int protocol);
int amiga_write(int fd, VOID *data, unsigned int size);
int amiga_stat(char *name, struct stat *st);
int amiga_lstat(char *name, struct stat *statstruct);
int amiga_fstat(int fd, struct stat *st);
int amiga_chmod(char *name, int mode);
int amiga_dup(int fd);
int amiga_dup2(int old_fd, int new_fd);
int amiga_chown(char *name, uid_t uid, gid_t gid);
int amiga_setegid(gid_t g);
int amiga_seteuid(uid_t u);
int amiga_gettimeofday(struct timeval *tv);
int amiga_utime(char *name, struct utimbuf *time);
VOID amiga_sleep(unsigned int seconds);
char *amiga_crypt(char *key, char *salt);
char *amiga_getpass(char *prompt);
int amiga_setgid(gid_t id);
int amiga_setgroups(int ngroups, gid_t *groups);
gid_t amiga_getgid(VOID);
struct group *amiga_getgrgid(gid_t gid);
struct group *amiga_getgrnam(char *name);
int amiga_getgroups(int ngroups, gid_t *groups);
struct hostent *amiga_gethostbyaddr(char *addr, int len, int type);
struct hostent *amiga_gethostbyname(char *name);
struct netent *amiga_getnetbyname(char *name);
int amiga_gethostname(char *hostname, int size);
struct passwd *amiga_getpwnam(char *name);
struct passwd *amiga_getpwuid(uid_t uid);
uid_t amiga_getuid(VOID);
gid_t amiga_getegid(VOID);
uid_t amiga_geteuid(VOID);
int amiga_initgroups(char *name, gid_t basegroup);
int amiga_setuid(uid_t id);
int amiga_umask(int mask);
unsigned long amiga_inet_addr(char *addr);
char *amiga_inet_ntoa(struct in_addr in);
int amiga_getopt(int argc, char *argv[], char *opts);
int amiga_system(char *cmd);
int amiga_fork(VOID);
VOID __tzset(VOID);
time_t time(time_t *timeptr);
struct tm *gmtime(const time_t *t);
struct tm *localtime(const time_t *t);
int amiga_strcasecmp(char *a, char *b);
int amiga_strncasecmp(char *a, char *b, int len);
VOID (*amiga_signal(int which,VOID (* action)(int)))(int);
VOID amiga_alarm(int seconds);
int amiga_waitpid(pid_t pid, int *status, int options);
long amiga_setsid(VOID);
int amiga_setreuid(uid_t real, uid_t eff);
int amiga_setregid(gid_t real, gid_t eff);
int amiga_fcntl(int fd, int cmd, unsigned long arg);
int amiga_getsockname(int sockfd, struct sockaddr *name, int *namelen);
int amiga_statfs(char *name, struct statfs *f);
int amiga_execl(char *path, char *arg, ...);
char *amiga_strerror(int error);
int amiga_access(char *name, int modes);
off_t amiga_lseek(int fd, off_t offset, int mode);
int amiga_chroot(char *name);
int amiga_kill(pid_t pid, int sig);
pid_t amiga_getpid(VOID);
int amiga_fgetc(FILE *in);
char *amiga_fgets(char *str, int n, FILE *in);
int amiga_fputs(const char *str, FILE *out);
int amiga_puts(const char *str);
int amiga_vfprintf(FILE *out, const char *fmt, va_list args);
int amiga_fprintf(FILE *out, const char *fmt, ...);
int amiga_printf(const char *fmt, ...);
size_t amiga_fwrite(const void *data, size_t blockSize, size_t numBlocks, FILE *out);
size_t amiga_fread(void *data, size_t blockSize, size_t numBlocks, FILE *in);
int amiga_fclose(FILE *stream);
int amiga_fflush(FILE *stream);
int amiga_fseek(FILE *stream, long int offset, int mode);
long int amiga_ftell(FILE *stream);
int amiga_setvbuf(FILE *fp, char *buff, int type, size_t size);
int amiga_fputc(int c,FILE *stream);
VOID amiga_setbuf(FILE *stream,char *buffer);

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

VOID __regargs __chkabort(VOID);
VOID _CXOVF(VOID);
VOID __regargs _CXBRK(VOID);

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

/* These are in the Samba runtime library. */
extern int vsnprintf (char *str, size_t count, const char *fmt, va_list arg);
extern int snprintf (char *str, size_t count, const char *fmt, ...);

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

#define ZERO	((BPTR)0L)
#define ERROR	(-1)
#define OK		(0)
#define SAME	(0)
#define NO		!
#define NOT		!
#define DONT	!
#define CANNOT	!
#define BUSY	(NULL)
#define NO_FLAG	(0)

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

#define FLAG_IS_SET(v,f)	(((v) & (f)) == (f))
#define FLAG_IS_CLEAR(v,f)	(((v) & (f)) == 0)

#define SET_FLAG(v,f)		(v |=  (f))
#define CLEAR_FLAG(v,f)		(v &= ~(f))

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

#define STRING_IS_EMPTY(s)	(s[0] == '\0')
#define NUM_ENTRIES(t)		(sizeof(t) / sizeof(t[0]))

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

#define FIB_IS_FILE(fib)	((fib)->fib_DirEntryType < 0)
#define FIB_IS_DRAWER(fib)	((fib)->fib_DirEntryType >= 0 && (fib)->fib_DirEntryType != ST_SOFTLINK)

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

#define UFB_IS_SOCKET		0x0800
#define UFB_IS_NON_BLOCKING	0x1000
#define UFB_UNLINK			0x2000
#define UFB_LOCKED			0x4000

#define UNIX_TIME_OFFSET	252482400

#define MAX_BSTR_LEN		256
#define MAX_FILENAME_LEN	512
#define MAX_FFS_NAME_LEN	30

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

long __oslibversion = 37;

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

extern struct Library * SysBase;
extern struct Library * DOSBase;
extern struct Library * UtilityBase;

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

STATIC struct Device * TimerBase;
STATIC struct MsgPort * TimerPort;
STATIC struct timerequest * TimerRequest;
STATIC LONG MinutesWest;

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

STATIC struct Library * SocketBase;
STATIC struct Library * UserGroupBase;

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

#if defined(_M68020) && defined(_M68881)
#define MACHINE_TYPE "['020+FPU]"
#elif defined(_M68020)
#define MACHINE_TYPE "['020]"
#else
#define MACHINE_TYPE "[68k]"
#endif

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

#include "Amiga_Samba_rev.h"
char * VersionTag = VERSTAG " " MACHINE_TYPE " Samba version 2.0.0 ported by Olaf `Olsen' Barthel <olsen@sourcery.han.de>";

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

/* This is for backwards compatibility with Kickstart 2.04 and
 * avoids a deadlock when trying to get a shared lock on
 * a semaphore already held in exclusive mode by the same Task.
 */
STATIC VOID
SafeObtainSemaphoreShared(struct SignalSemaphore * semaphore)
{
	/* Do it right with Kickstart 3.x. */
	if(SysBase->lib_Version >= 39)
	{
		ObtainSemaphoreShared(semaphore);
	}
	else
	{
		/* Try to get the shared semaphore */
		if(CANNOT AttemptSemaphoreShared(semaphore))
		{
			/* Check if we can get the exclusive version */
			if(CANNOT AttemptSemaphore(semaphore))
			{
				/* Oh well, wait for the shared lock */
				ObtainSemaphoreShared(semaphore);
			}
		}
	}
}

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

STATIC BOOL AllowBreak = TRUE;

STATIC BOOL
CheckAbort(VOID)
{
	BOOL result;

	result = (BOOL)(AllowBreak && FLAG_IS_SET(SetSignal(0,SIGBREAKF_CTRL_C),SIGBREAKF_CTRL_C));

	return(result);
}

VOID __regargs
__chkabort(VOID)
{
	if(CheckAbort())
		raise(SIGINT);
}

int
amiga_sigmask(int signum)
{
	int result = 0;

	ENTER();

	SHOWVALUE(signum);

	if(signum == SIGTERM || signum == SIGINT)
		result = (1<<SIGINT);

	RETURN(result);
	return(result);
}

int
amiga_sigblock(int sigmask)
{
	BOOL oldAllowBreak = AllowBreak;
	int result = 0;

	ENTER();

	SHOWVALUE(sigmask);

	if(DONT AllowBreak)
		result = (1<<SIGINT);

	if(FLAG_IS_SET(sigmask,(1<<SIGINT)))
	{
		AllowBreak = FALSE;

		if(oldAllowBreak != AllowBreak)
		{
			SocketBaseTags(
				SBTM_SETVAL(SBTC_BREAKMASK),NO_FLAG,
			TAG_END);
		}
	}

	RETURN(result);
	return(result);
}

int
amiga_sigsetmask(int sigmask)
{
	BOOL oldAllowBreak = AllowBreak;
	int result = 0;

	ENTER();

	SHOWVALUE(sigmask);

	AllowBreak = FLAG_IS_CLEAR(sigmask,(1<<SIGINT));
	if(oldAllowBreak != AllowBreak)
	{
		ULONG mask;

		if(AllowBreak)
			mask = SIGBREAKF_CTRL_C;
		else
			mask = NO_FLAG;

		SocketBaseTags(
			SBTM_SETVAL(SBTC_BREAKMASK),mask,
		TAG_END);
	}

	RETURN(result);
	return(result);
}

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

STATIC VOID
ReportProblem(const char *fmt,...)
{
	extern const STRPTR _ProgramName;
	BOOL useRequester = TRUE;
	va_list varArgs;

	ASSERT(fmt != NULL);

	va_start(varArgs,fmt);

	/* Launched from Workbench? */
	if(WBenchMsg == NULL)
	{
		if(DOSBase->lib_Version >= 37)
		{
			BPTR stream;

			/* Make a copy of the current terminal
			 * output stream. This avoids sending
			 * an error message through a redirected
			 * standard output stream, the output
			 * will go straight to the terminal.
			 */
			stream = Open("CONSOLE:",MODE_NEWFILE);
			if(stream != ZERO)
			{
				struct FileHandle * fh = BADDR(stream);

				/* Check if the output was redirected
				 * to "NIL:"; if not, print the error
				 * error message.
				 */
				if(fh->fh_Type != NULL)
				{
					FPrintf(stream,"%s: ",_ProgramName);
					VFPrintf(stream,(STRPTR)fmt,(APTR)varArgs);
					FPrintf(stream,"\a\n");

					useRequester = FALSE;
				}

				Close(stream);
			}
		}
	}

	/* Oh well, don't use the Shell for output. Put up a
	 * requester.
	 */
	if(useRequester)
	{
		struct Library * IntuitionBase;

		IntuitionBase = OpenLibrary("intuition.library",37);
		if(IntuitionBase != NULL)
		{
			struct Window * reqWindow;
			struct EasyStruct es;
			char title[100];

			snprintf(title,sizeof(title)-1,"Amiga Samba Error (%s)",FilePart(_ProgramName));
			title[sizeof(title)-1] = '\0';

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

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

			reqWindow = BuildEasyRequestArgs(NULL,&es,0,(APTR)varArgs);
			if(reqWindow != NULL)
			{
				struct timerequest * timeRequest = NULL;
				struct MsgPort * timePort;
				BOOL timerOpen = FALSE;
				ULONG windowSignal;
				ULONG timerSignal;
				ULONG signals;
				BOOL done;

				timePort = CreateMsgPort();
				if(timePort != NULL)
				{
					timeRequest = CreateIORequest(timePort,sizeof(*timeRequest));
					if(timeRequest != NULL)
					{
						if(OpenDevice(TIMERNAME,UNIT_VBLANK,(struct IORequest *)timeRequest,0) == OK)
							timerOpen = TRUE;
					}
				}

				windowSignal = (1UL << reqWindow->UserPort->mp_SigBit);

				if(timerOpen)
				{
					timerSignal = (1UL << timePort->mp_SigBit);

					timeRequest->tr_node.io_Command	= TR_ADDREQUEST;
					timeRequest->tr_time.tv_secs	= 30;
					timeRequest->tr_time.tv_micro	= 0;

					SendIO((struct IORequest *)timeRequest);
				}
				else
				{
					timerSignal = 0;
				}

				DisplayBeep(reqWindow->WScreen);

				done = FALSE;
				do
				{
					signals = Wait(windowSignal | timerSignal);

					if(FLAG_IS_SET(signals,windowSignal))
					{
						if(SysReqHandler(reqWindow,NULL,FALSE) >= 0)
							done = TRUE;
					}

					if(FLAG_IS_SET(signals,timerSignal))
					{
						done = TRUE;
					}
				}
				while(NOT done);

				if(timerOpen)
				{
					if(CheckIO((struct IORequest *)timeRequest) == BUSY)
						AbortIO((struct IORequest *)timeRequest);

					WaitIO((struct IORequest *)timeRequest);

					CloseDevice((struct IORequest *)timeRequest);
				}

				DeleteIORequest((struct IORequest *)timeRequest);
				DeleteMsgPort(timePort);

				FreeSysRequest(reqWindow);
			}

			CloseLibrary(IntuitionBase);
		}
	}

	va_end(varArgs);
}

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

STATIC APTR OldWindowPtr;
STATIC LONG ForbidCount;

STATIC VOID
ForbidDOSCleanup(VOID)
{
	if(ForbidCount > 0)
	{
		struct Process * pr = (struct Process *)FindTask(NULL);

		pr->pr_WindowPtr = OldWindowPtr;

		ForbidCount = 0;
	}
}

STATIC VOID
PermitDOS(VOID)
{
	ASSERT(ForbidCount > 0);

	if(--ForbidCount == 0)
	{
		struct Process * pr = (struct Process *)FindTask(NULL);

		pr->pr_WindowPtr = OldWindowPtr;
	}
}

STATIC VOID
ForbidDOS(VOID)
{
	if(ForbidCount++ == 0)
	{
		struct Process * pr = (struct Process *)FindTask(NULL);

		OldWindowPtr = pr->pr_WindowPtr;
		pr->pr_WindowPtr = (APTR)-1;
	}
}

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

#define SINGLE_CHARACTER_MODE	(1)
#define BUFFERED_MODE			(0)

STATIC int MaxNonblockingDescriptor = -1;

STATIC VOID
UnblockDescriptorCleanup(VOID)
{
	struct UFB * ufb;
	int fd;

	/* We look for descriptors we switched into
	 * non-blocking mode and reset them back
	 * to blocking mode.
	 */
	for(fd = 0 ; fd <= MaxNonblockingDescriptor ; fd++)
	{
		ufb = chkufb(fd);
		if(ufb != NULL && FLAG_IS_SET(ufb->ufbflg,UFB_IS_NON_BLOCKING))
		{
			SetMode((BPTR)ufb->ufbfh,BUFFERED_MODE);

			CLEAR_FLAG(ufb->ufbflg,UFB_IS_NON_BLOCKING);
		}
	}
}

STATIC BOOL
IsDescriptorNonblocking(int fd)
{
	struct UFB * ufb;
	BOOL result = FALSE;

	/* Verify if a descriptor was switched into non-blocking mode. */
	ufb = chkufb(fd);
	if(ufb != NULL)
		result = FLAG_IS_SET(ufb->ufbflg,UFB_IS_NON_BLOCKING);

	return(result);
}

STATIC VOID
BlockDescriptor(int fd)
{
	struct UFB * ufb;

	/* This resets a file descriptor to blocking mode,
	 * once it has been set to blocking mode.
	 */
	ufb = chkufb(fd);
	if(ufb != NULL)
	{
		if(FLAG_IS_SET(ufb->ufbflg,UFB_IS_NON_BLOCKING))
		{
			SetMode((BPTR)ufb->ufbfh,BUFFERED_MODE);

			CLEAR_FLAG(ufb->ufbflg,UFB_IS_NON_BLOCKING);
		}
	}
}

STATIC VOID
UnblockDescriptor(int fd)
{
	struct UFB * ufb;

	ufb = chkufb(fd);
	if(ufb != NULL)
	{
		/* Make sure we got a file and it's not already
		 * in non-blocking mode.
		 */
		if(FLAG_IS_CLEAR(ufb->ufbflg,UFB_IS_SOCKET) && FLAG_IS_CLEAR(ufb->ufbflg,UFB_IS_NON_BLOCKING))
		{
			/* Try to flip it into single character mode,
			 * which we treat as "non-blocking" mode.
			 */
			if(SetMode((BPTR)ufb->ufbfh,SINGLE_CHARACTER_MODE))
			{
				SET_FLAG(ufb->ufbflg,UFB_IS_NON_BLOCKING);

				/* Remember which one we switched. */
				if(MaxNonblockingDescriptor < fd)
					MaxNonblockingDescriptor = fd;
			}
		}
	}
}

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

struct SavedDescriptorNode
{
	struct MinNode	sdn_MinNode;
	struct UFB *	sdn_UFB;
	BPTR			sdn_FileHandle;
};

STATIC BOOL DescriptorListInitialized = FALSE;
STATIC struct List DescriptorList;

STATIC VOID
SaveDescriptorCleanup(VOID)
{
	/* This routine restores all file descriptors
	 * we used to map to sockets to use proper file
	 * handles.
	 */
	if(DescriptorListInitialized)
	{
		struct SavedDescriptorNode * sdn;

		for(sdn = (struct SavedDescriptorNode *)DescriptorList.lh_Head ;
		    sdn->sdn_MinNode.mln_Succ != NULL ;
		    sdn = (struct SavedDescriptorNode *)sdn->sdn_MinNode.mln_Succ)
		{
			/* Make sure that if this file is bound to
			 * a socket, the socket is closed.
			 */
			if(FLAG_IS_SET(sdn->sdn_UFB->ufbflg,UFB_IS_SOCKET))
			{
				CloseSocket(sdn->sdn_UFB->ufbfh);

				CLEAR_FLAG(sdn->sdn_UFB->ufbflg,UFB_IS_SOCKET);
			}

			sdn->sdn_UFB->ufbfh = sdn->sdn_FileHandle;
		}
	}
}

STATIC VOID
RestoreDescriptor(struct UFB * ufb)
{
	ASSERT(ufb != NULL);

	/* This routine restores one particular file descriptor
	 * which was used as a socket to refer to a proper
	 * file handle.
	 */
	if(DescriptorListInitialized)
	{
		struct SavedDescriptorNode * sdn;

		for(sdn = (struct SavedDescriptorNode *)DescriptorList.lh_Head ;
		    sdn->sdn_MinNode.mln_Succ != NULL ;
		    sdn = (struct SavedDescriptorNode *)sdn->sdn_MinNode.mln_Succ)
		{
			if(sdn->sdn_UFB == ufb)
			{
				sdn->sdn_UFB->ufbfh = sdn->sdn_FileHandle;

				Remove((struct Node *)sdn);
				free(sdn);

				break;
			}
		}
	}
}

STATIC BOOL
SaveDescriptor(struct UFB * ufb)
{
	struct SavedDescriptorNode * sdn;
	BOOL result = FALSE;

	ASSERT(ufb != NULL);

	/* Set up the descriptor list, unless it's already
	 * initialized.
	 */
	if(NOT DescriptorListInitialized)
	{
		NewList(&DescriptorList);
		DescriptorListInitialized = TRUE;
	}

	sdn = malloc(sizeof(*sdn));
	if(sdn != NULL)
	{
		memset(sdn,0,sizeof(*sdn));

		/* Remember the file buffer and the file
		 * handle attached to the descriptor.
		 */
		sdn->sdn_UFB		= ufb;
		sdn->sdn_FileHandle	= ufb->ufbfh;

		AddHead(&DescriptorList,(struct Node *)sdn);

		result = TRUE;
	}

	return(result);
}

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

STATIC BOOL InitialCurrentDirInitialized = FALSE;
STATIC BPTR InitialCurrentDir;
STATIC BPTR ChangedCurrentDir;

CBMLIB_DESTRUCTOR(CloseLibsAndDevs)
{
	ENTER();

	/* Return to the directory we were in
	 * when we started.
	 */
	if(InitialCurrentDirInitialized)
	{
		CurrentDir(InitialCurrentDir);
		InitialCurrentDirInitialized = FALSE;
	}

	/* If the current directory was changed, unlock it. */
	if(ChangedCurrentDir != ZERO)
	{
		UnLock(ChangedCurrentDir);
		ChangedCurrentDir = ZERO;
	}

	CleanupSambaSemaphore();

	if(TimerRequest != NULL)
	{
		if(TimerRequest->tr_node.io_Device != NULL)
			CloseDevice((struct IORequest *)TimerRequest);

		DeleteIORequest((struct IORequest *)TimerRequest);
		TimerRequest = NULL;
	}

	if(TimerPort != NULL)
	{
		DeleteMsgPort(TimerPort);
		TimerPort = NULL;
	}

	TimerBase = NULL;

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

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

	LEAVE();
}

CBMLIB_CONSTRUCTOR(OpenLibsAndDevs)
{
	extern STRPTR _ProgramName;
	BOOL sambaSemaphoreCreated;
	struct LocaleBase * LocaleBase;
	char *timerError = "";
	BPTR sambaLock;
	int result = ERROR;

	SETPROGRAMNAME(_ProgramName);
	SETDEBUGLEVEL(0);

	ENTER();

	SocketBase = OpenLibrary("bsdsocket.library",4);
	UserGroupBase = OpenLibrary("usergroup.library",1);

	if(SysBase->lib_Version >= 37)
	{
		TimerPort = CreateMsgPort();
		if(TimerPort != NULL)
		{
			TimerRequest = (struct timerequest *)CreateIORequest(TimerPort,sizeof(*TimerRequest));
			if(TimerRequest != NULL)
			{
				if(OpenDevice(TIMERNAME,UNIT_VBLANK,(struct IORequest *)TimerRequest,NULL) == OK)
					TimerBase = TimerRequest->tr_node.io_Device;
				else
					timerError = "opening \"timer.device\"";
			}
			else
			{
				timerError = "creating timer I/O request";
			}
		}
		else
		{
			timerError = "creating timer message port";
		}
	}

	/* Try to determine this machine's time zone. */
	LocaleBase = (struct LocaleBase *)OpenLibrary("locale.library",38);
	if(LocaleBase != NULL)
	{
		struct Locale * locale;

		locale = OpenLocale(NULL);
		MinutesWest = locale->loc_GMTOffset;
		CloseLocale(locale);

		CloseLibrary((struct Library *)LocaleBase);
	}

	ForbidDOS();

	/* Try to get a lock on the "Samba:" directory,
	 * just to make sure the assignment is in place.
	 */
	sambaLock = Lock("Samba:",SHARED_LOCK);
	if(sambaLock != ZERO)
		UnLock(sambaLock);

	PermitDOS();

	/* Initialize the global process ID database. */
	sambaSemaphoreCreated = SetupSambaSemaphore();

	if(SocketBase != NULL && UserGroupBase != NULL && TimerBase != NULL &&
	   sambaLock != ZERO && sambaSemaphoreCreated)
	{
		STATIC long h_errno = 0;
		struct timeval now;
		int error;

		error = SocketBaseTags(
			SBTM_SETVAL(SBTC_ERRNOPTR(sizeof(errno))),	&errno,
			SBTM_SETVAL(SBTC_HERRNOLONGPTR),			&h_errno,
			SBTM_SETVAL(SBTC_LOGTAGPTR),				_ProgramName,
			SBTM_SETVAL(SBTC_BREAKMASK),				SIGBREAKF_CTRL_C,
		TAG_END);
		if(error != OK)
		{
			ReportProblem("Error initializing socket data (error=%ld).",error);
			goto out;
		}

		error = ug_SetupContextTags(_ProgramName,
			UGT_ERRNOPTR(sizeof(errno)),&errno,
		TAG_END);
		if(error != OK)
		{
			ReportProblem("Error initializing user group data (error=%ld).",error);
			goto out;
		}

		/* Use a new random number seed. */
		GetSysTime((APTR)&now);
		srand(now.tv_secs);
	}
	else
	{
		if(SocketBase == NULL)
			ReportProblem("Error opening \"bsdsocket.library\" V4.");
		else if (UserGroupBase == NULL)
			ReportProblem("Error opening \"usergroup.library\" V1.");
		else if (TimerBase == NULL)
			ReportProblem("Error %s.",timerError);
		else if (sambaLock == ZERO)
			ReportProblem("Error finding \"Samba:\" assignment.");
		else if (NOT sambaSemaphoreCreated)
			ReportProblem("Error creating Samba semaphore.");

		goto out;
	}

	/* Initialize the current time zone variable. */
	__tzset();

	result = OK;

out:

	RETURN(result);
	return(result);
}

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

DESTRUCTOR_P(SocketExit,501)
{
	ENTER();

	SaveDescriptorCleanup();
	UnblockDescriptorCleanup();
	CloseUnlinkUnlockCleanup();
	ForbidDOSCleanup();

	LEAVE();
}

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

CONSTRUCTOR_P(DaemonInit,501)
{
	ENTER();

	DaemonInit();

	RETURN(0);
	return(0);
}

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

struct MangleInfo
{
	char	mi_Substitute[MAX_FILENAME_LEN];
	char *	mi_OldName;
};

STATIC VOID
UnmangleName(char ** namePtr,struct MangleInfo * mi)
{
	/* Reset the name pointer to its previous position. */
	(*namePtr) = mi->mi_OldName;
}

STATIC int
MangleName(char ** namePtr,struct MangleInfo * mi)
{
	char * name = (*namePtr);
	int result = ERROR;

	ENTER();

	if(TranslateRelativePath(&name,mi->mi_Substitute,sizeof(mi->mi_Substitute)) == OK)
	{
		char * replace = mi->mi_Substitute;
		int len,i;

		len = strlen(name);

		/* If there is one, strip the trailing slash. */
		if(len > 1 && name[len-1] == '/')
		{
			if(name != replace)
			{
				strcpy(replace,name);
				name = replace;
			}

			name[--len] = '\0';
		}

		if(strcmp(name,".") == SAME)
		{
			/* Convert to current directory. */
			name = "";
		}
		else if (strcmp(name,"..") == SAME)
		{
			/* Convert to parent directory. */
			name = "/";
		}
		else if (strncmp(name,"./",2) == SAME)
		{
			/* Retain just the name. */
			name += 2;
		}
		else if (strncmp(name,"../",3) == SAME)
		{
			/* Convert to parent directory. */
			name += 2;
		}
		else if (strncmp(name,"/",1) == SAME)
		{
			/* Ok, so this is an absolute path. We first
			 * check for a few special cases, the first
			 * being a reference to "/tmp".
			 */
			if(Strnicmp(name,"/tmp",4) == SAME && (name[4] == '/' || name[4] == '\0'))
			{
				if(name[4] == '/')
				{
					/* Convert "/tmp/foo" to "T:foo". */
					strcpy(replace,"T:");
					memmove(&replace[2],&name[5],strlen(&name[5])+1);
				}
				else
				{
					/* Convert "/tmp" to "T:". */
					strcpy(replace,"T:");
				}

				name = replace;
			}
			else if(Strnicmp(name,"/dev/",5) == SAME)
			{
				/* Except for "/dev/null" all references to
				 * files in "/dev" are redirected to nonexistant
				 * files. Note that this relies upon the fact
				 * that "NIL:" is not a true device.
				 */
				if(Stricmp(name,"/dev/null") == SAME)
					name = "NIL:";
				else
					name = "NIL:this_file_never_opens";
			}
			else
			{
				int i,rest = 0,len = 0;

				/* Find out how long the first component
				 * of the absolute path is.
				 */
				for(i = 1 ; i <= strlen(name) ; i++)
				{
					if(name[i] == '/' || name[i] == '\0')
					{
						len = i-1;

						/* Is there anything following
						 * the path name?
						 */
						if(name[i] == '/')
							rest = i+1;

						break;
					}
				}

				/* Copy the first component and
				 * attach a colon. "/foo" becomes
				 * "foo:" (without the trailing NUL
				 * byte, this will get attached
				 * later).
				 */
				memmove(replace,&name[1],len);
				replace[len++] = ':';

				/* Now add the finishing touches. "/foo/bar" finally
				 * becomes "foo:bar" and "/foo" becomes "foo:" with the
				 * trailing NUL byte attached.
				 */
				if(rest > 0)
					memmove(&replace[len],&name[rest],strlen(&name[rest])+1);
				else
					replace[len] = '\0';

				name = replace;
			}
		}

		/* Convert any "./" or "../" embedded in the name
		 * if necessary.
		 */
		for(i = 0 ; i < strlen(name) ; i++)
		{
			if(strncmp(&name[i],"./",2) == SAME ||
			   strncmp(&name[i],"../",3) == SAME)
			{
				char * from	= name;
				char * to	= replace;

				name = replace;

				while((*from) != '\0')
				{
					if((*from) == '.')
					{
						if(from[1] == '/' || from[1] == '\0')
						{
							/* "."  -> "" (done)
							 * "./" -> "" (continue)
							 */
							if(from[1] == '\0')
								break;
							else
								from += 2;
						}
						else if (from[1] == '.' && (from[2] == '/' || from[2] == '\0'))
						{
							/* ".."  -> "/" (done)
							 * "../" -> "/" (continue)
							 */
							(*to++) = '/';

							if(from[2] == '\0')
								break;
							else
								from += 3;
						}
						else
						{
							(*to++) = (*from++);
						}
					}
					else
					{
						(*to++) = (*from++);
					}
				}

				(*to) = '\0';

				break;
			}
		}

		/* Reduce any "//" embedded in the name if
		 * necessary.
		 */
		for(i = 0 ; i < strlen(name)-1 ; i++)
		{
			if(name[i] == '/' && name[i+1] == '/')
			{
				int position,len;

				if(name != replace)
				{
					strcpy(replace,name);
					name = replace;
				}

				len = strlen(name);

				position = len - 1;
				while(position > 1 && name[position] != ':')
				{
					/* "foo/bar//baz" -> "foo/baz" */
					if(name[position] == '/' && name[position-1] == '/')
					{
						int componentLen;

						/* Move in front of the name component preceding the "//". */
						componentLen = 0;
						for(i = position - 2 ; i > 0 && name[i] != ':' && name[i] != '/' ; i--)
							componentLen++;

						if(componentLen > 0)
						{
							memmove(&name[position - (componentLen + 1)],&name[position + 1],len - (position + 1));

							len -= componentLen + 2;
							name[len] = '\0';

							position -= componentLen + 1;
						}
					}

					position--;
				}

				break;
			}
		}

		D(("original name |%s|",(*namePtr)));
		D((" mangled name |%s|",name));

		/* Look for extra colon characters
		 * embedded in the name (as in "foo:bar:baz"
		 * or "foo/bar:baz") which really don't
		 * belong here.
		 */
		len = strlen(name);
		for(i = 0 ; i < len ; i++)
		{
			if(name[i] == ':' || name[i] == '/')
			{
				int j;

				for(j = i+1 ; j < len ; j++)
				{
					if(name[j] == ':')
					{
						errno = EINVAL; /* invalid name */
						goto out;
					}
				}

				break;
			}
		}

		/* Now check if the file name is longer than the
		 * maximum supported by the ROM file system. This
		 * is to avoid name space clashes.
		 */
		if(strlen(FilePart((STRPTR)name)) > MAX_FFS_NAME_LEN)
		{
			LONG error = OK;
			BPTR fileLock;

			/* This is a tricky issue: the filing system we are talking
			 * to may be able to handle more than 30 characters, but then
			 * it may be not, it's impossible to tell. We adopt the following
			 * strategy: we try to access the named file and compare its
			 * name against the one reported by the file system. If the name
			 * reported by the file system is shorter than the one provided,
			 * but matches it otherwise, we will assume that trouble is underway
			 * and back out backwards.
			 */

			ForbidDOS();

			fileLock = Lock((STRPTR)name,SHARED_LOCK);
			if(fileLock != ZERO)
			{
				struct FileInfoBlock __aligned fib;

				if(Examine(fileLock,&fib))
				{
					int len;

					/* Now check if the name reported by the
					 * filing system is shorter than the
					 * one we asked for, but matches otherwise.
					 */
					len = strlen(fib.fib_FileName);
					if(strlen((STRPTR)name) > len && Strnicmp((STRPTR)name,fib.fib_FileName,len) == SAME)
					{
						/* The name is too long to handle. */
						error = ERROR_LINE_TOO_LONG;
					}
				}
				else
				{
					error = IoErr();
				}

				UnLock(fileLock);
			}
			else
			{
				error = IoErr();
			}

			PermitDOS();

			/* We don't complain if the file does
			 * not exist, but if it is currently in
			 * use, on a volume that's not currently
			 * mounted, etc. we will complain.
			 */
			if(error != OK && error != ERROR_OBJECT_NOT_FOUND)
			{
				SetIoErr(error);
				MapIoErrToErrno();

				goto out;
			}
		}

		mi->mi_OldName = (*namePtr);
		(*namePtr) = name;

		result = OK;
	}

out:

	RETURN(result);
	return(result);
}

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

STATIC int MaxOpenDescriptor = -1;

STATIC VOID
CloseUnlinkUnlockCleanup(VOID)
{
	struct UFB * ufb;
	int fd;

	ForbidDOS();

	/* Don't let anybody stop us. */
	signal(SIGINT,SIG_IGN);
	signal(SIGTERM,SIG_IGN);

	/* We look for descriptors we marked for
	 * deletion.
	 */
	for(fd = 0 ; fd <= MaxOpenDescriptor ; fd++)
	{
		CleanupFileLocks(fd);

		ufb = chkufb(fd);
		if(ufb != NULL && FLAG_IS_SET(ufb->ufbflg,UFB_UNLINK))
		{
			char fileName[MAX_FILENAME_LEN];
			BOOL removeIt;

			removeIt = NameFromFH((BPTR)ufb->ufbfh,fileName,sizeof(fileName));

			CLEAR_FLAG(ufb->ufbflg,UFB_UNLINK);

			close(fd);

			if(removeIt)
				DeleteFile(fileName);
		}
	}

	PermitDOS();
}

int
amiga_unlink(char *name)
{
	struct MangleInfo mi;
	BPTR fileLock;
	int result = ERROR;

	chkabort();

	ASSERT(name != NULL);

	ENTER();
	SHOWSTRING(name);

	if(MangleName(&name,&mi) == OK)
	{
		LONG error = OK;

		ForbidDOS();

		fileLock = Lock(name,SHARED_LOCK);
		if(fileLock != ZERO)
		{
			struct FileInfoBlock __aligned fib;

			if(Examine(fileLock,&fib))
			{
				UnLock(fileLock);
				fileLock = ZERO;

				/* Make sure that we get to remove
				 * a file, as the name implies.
				 */
				if(FIB_IS_FILE(&fib))
				{
					if(DeleteFile(name))
						result = OK;
					else
						error = IoErr();
				}
				else
				{
					errno = EISDIR;
				}
			}
			else
			{
				error = IoErr();
			}

			UnLock(fileLock);
		}
		else
		{
			error = IoErr();
		}

		PermitDOS();

		/* Check if we couldn't delete the file in
		 * question because there still is a file
		 * handle attached to it. If we can find that
		 * file, we will mark it for deletion lateron
		 * when the file is closed.
		 */
		if(result != OK && error == ERROR_OBJECT_IN_USE)
		{
			char parentDirName[MAX_FILENAME_LEN];
			BOOL foundParentDirName;
			BPTR fileParentDir;
			int i;

			ASSERT(strlen(name) < sizeof(parentDirName));

			strcpy(parentDirName,name);
			foundParentDirName = FALSE;

			for(i = strlen(parentDirName)-1 ; i >= 0 ; i--)
			{
				if(parentDirName[i] == ':')
				{
					if(parentDirName[i+1] != '\0')
					{
						parentDirName[i+1] = '\0';
						foundParentDirName = TRUE;
					}

					break;
				}
				else if (parentDirName[i] == '/')
				{
					parentDirName[i] = '\0';
					foundParentDirName = TRUE;
					break;
				}
			}

			/* Did we find this file's parent directory name? */
			if(foundParentDirName)
			{
				D(("locking |%s|",parentDirName));

				/* Get a lock on the file's parent directory. */
				fileParentDir = Lock(parentDirName,SHARED_LOCK);
				if(fileParentDir != ZERO)
				{
					BPTR descriptorParentDir;
					BOOL gotIt = FALSE;
					struct UFB * ufb;
					int fd;

					for(fd = 0 ; fd <= MaxOpenDescriptor ; fd++)
					{
						ufb = chkufb(fd);
						if(ufb != NULL && FLAG_IS_CLEAR(ufb->ufbflg,UFB_IS_SOCKET))
						{
							/* And also get a lock on the file
							 * descriptor's parent directory.
							 */
							descriptorParentDir = ParentOfFH((BPTR)ufb->ufbfh);
							if(descriptorParentDir != ZERO)
							{
								/* Are the two pointing to the same drawer? */
								if(SameLock(fileParentDir,descriptorParentDir) == LOCK_SAME)
								{
									struct FileInfoBlock __aligned fib;

									if(ExamineFH((BPTR)ufb->ufbfh,&fib))
									{
										/* Check if the two share the same name. */
										if(Stricmp(fib.fib_FileName,FilePart(name)) == SAME)
										{
											/* Mark this file for deletion. */
											SET_FLAG(ufb->ufbflg,UFB_UNLINK);

											gotIt = TRUE;
											error = OK;
											result = OK;
										}
									}
									else
									{
										SHOWMSG("couldn't examine the descriptor");
									}
								}
								else
								{
									SHOWMSG("file and descriptor don't live in the same drawer");
								}

								UnLock(descriptorParentDir);
							}
							else
							{
								SHOWMSG("the descriptor doesn't have a parent directory (huh?!?)");
							}
						}

						if(gotIt)
							break;
					}

					UnLock(fileParentDir);
				}
				else
				{
					D(("couldn't get a lock on |%s|",parentDirName));
				}
			}
			else
			{
				D(("didn't find parent dir name of |%s|",name));
			}
		}

		/* Take care of the AmigaDOS error
		 * code, if there is any.
		 */
		if(result != OK && error != OK)
		{
			SetIoErr(error);
			MapIoErrToErrno();
		}

		UnmangleName(&name,&mi);
	}

	RETURN(result);
	return(result);
}

int
amiga_open(char *name,int mode,int prot)
{
	struct MangleInfo mi;
	int result = ERROR;

	chkabort();

	ASSERT(name != NULL);

	ENTER();
	SHOWSTRING(name);
	SHOWVALUE(mode);
	SHOWVALUE(prot);

	if(MangleName(&name,&mi) == OK)
	{
		/* Clear the "no delay" flag since the SAS/C
		 * runtime library does not support it.
		 */
		CLEAR_FLAG(mode,O_NONBLOCK);

		ForbidDOS();

		result = open(name,mode,prot);
		if(result != ERROR && MaxOpenDescriptor < result)
			MaxOpenDescriptor = result;

		PermitDOS();

		UnmangleName(&name,&mi);
	}

	RETURN(result);
	return(result);
}

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

STATIC char CurrentDirName[MAX_FILENAME_LEN] = "";

int
amiga_chdir(char *path)
{
	BOOL isAbsolutePath;
	struct MangleInfo mi;
	int result = ERROR;

	chkabort();

	ASSERT(path != NULL);

	ENTER();
	SHOWSTRING(path);

	/* Is this an absolute path? */
	isAbsolutePath = (BOOL)(path[0] == '/');

	if(MangleName(&path,&mi) == OK)
	{
		BPTR drawerLock;

		D(("chdir(\"%s\")",path));

		ForbidDOS();

		drawerLock = Lock(path,SHARED_LOCK);
		if(drawerLock != ZERO)
		{
			struct FileInfoBlock __aligned fib;

			if(Examine(drawerLock,&fib))
			{
				/* We can only move into drawers. */
				if(FIB_IS_DRAWER(&fib))
				{
					/* If necessary, get the name of
					 * the drawer to move into.
					 */
					if(NOT isAbsolutePath)
					{
						UBYTE localName[MAX_FILENAME_LEN];

						if(NameFromLock(drawerLock,localName,sizeof(localName)))
							result = MapFileNameAmigaToUnix(localName,CurrentDirName,sizeof(CurrentDirName));
						else
							MapIoErrToErrno();
					}
					else
					{
						result = OK;
					}

					/* If everything went well,
					 * move into the drawer.
					 */
					if(result == OK)
					{
						BPTR oldDrawer;

						oldDrawer = CurrentDir(drawerLock);

						/* Unlock the old drawer we came from
						 * unless we want to return to it
						 * when the program exits.
						 */
						if(InitialCurrentDirInitialized)
						{
							UnLock(oldDrawer);
						}
						else
						{
							InitialCurrentDir = oldDrawer;
							InitialCurrentDirInitialized = TRUE;
						}

						/* Make sure that this drawer is going
						 * to be unlocked when the program exits.
						 */
						ChangedCurrentDir = drawerLock;
					}
				}
				else
				{
					errno = ENOTDIR;
				}
			}
			else
			{
				MapIoErrToErrno();
			}

			if(result != OK)
				UnLock(drawerLock);
		}
		else
		{
			MapIoErrToErrno();
		}

		PermitDOS();

		UnmangleName(&path,&mi);

		if(result == OK && isAbsolutePath)
			strcpy(CurrentDirName,path);
	}

	RETURN(result);
	return(result);
}

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

struct OpenDirNode
{
	struct MinNode			odn_MinNode;
	BPTR					odn_FileLock;
	struct FileInfoBlock	odn_FIB;
	struct List				odn_VolumeList;
	DIR						odn_DIR;
	BOOL					odn_ReadingVolumes;
	struct dirent			odn_DirectoryEntry;
	struct Node *			odn_NextNode;
	ULONG					odn_NextDirEntryIndex;
	LONG					odn_ParentDirKey;
};

STATIC struct List OpenDirList;
STATIC BOOL OpenDirListInitialized = FALSE;

STATIC ULONG RootBlocks = 0;
STATIC ULONG RootBlocksUsed = 0;

STATIC VOID
OpenDirCleanup(VOID)
{
	struct OpenDirNode * odn;

	/* Unlock all directories still being scanned
	 * when exit() was called.
	 */
	for(odn = (struct OpenDirNode *)OpenDirList.lh_Head ;
	    odn->odn_MinNode.mln_Succ != NULL ;
	    odn = (struct OpenDirNode *)odn->odn_MinNode.mln_Succ)
	{
		UnLock(odn->odn_FileLock);
	}
}

DIR *
amiga_opendir(char *dirName)
{
	char localDirName[MAX_FILENAME_LEN];
	DIR *result = NULL;

	chkabort();

	ASSERT(dirName != NULL);

	ENTER();

	SHOWSTRING(dirName);

	/* Make sure that the directory list is
	 * set up properly.
	 */
	if(NOT OpenDirListInitialized)
	{
		NewList(&OpenDirList);
		OpenDirListInitialized = TRUE;
		atexit(OpenDirCleanup);
	}

	if(TranslateRelativePath(&dirName,localDirName,sizeof(localDirName)) == OK)
	{
		/* Check if we are to scan the virtual root directory. */
		if(strcmp(dirName,"/") == SAME)
		{
			struct OpenDirNode * odn;

			odn = malloc(sizeof(*odn));
			if(odn != NULL)
			{
				struct DosList * dol;

				RootBlocks = RootBlocksUsed = 0;

				memset(odn,0,sizeof(*odn));
				odn->odn_ReadingVolumes = TRUE;
				odn->odn_DIR.dd_buf = (char *)odn;
				NewList(&odn->odn_VolumeList);

				AddHead(&OpenDirList,(struct Node *)odn);

				result = &odn->odn_DIR;

				/* Now collect all volumes in the system. */
				dol = NextDosEntry(LockDosList(LDF_VOLUMES|LDF_READ),
			                                   LDF_VOLUMES|LDF_READ);
				while(dol != NULL)
				{
					/* Does the volume refer to a medium that is right
					 * now present in the drive?
					 */
					if(dol->dol_Task != NULL)
					{
						struct InfoData __aligned id;

						/* Is there a disk present? */
						if(DoPkt(dol->dol_Task,ACTION_DISK_INFO,MKBADDR(&id),	0,0,0,0))
						{
							STRPTR name = BADDR(dol->dol_Name);
							struct Node * node;

							/* Collect the number of blocks used and
							 * available in our fake root directory.
							 * Not that it matters much...
							 */
							if(id.id_BytesPerBlock == 512)
							{
								RootBlocks		+= id.id_NumBlocks;
								RootBlocksUsed	+= id.id_NumBlocksUsed;
							}
							else
							{
								RootBlocks		+= (id.id_NumBlocks * id.id_BytesPerBlock) / 512;
								RootBlocksUsed	+= (id.id_NumBlocksUsed * id.id_BytesPerBlock) / 512;
							}

							node = malloc(sizeof(*node) + ((int)name[0])+1);
							if(node != NULL)
							{
								/* Copy the name of the volume. */
								node->ln_Name = (char *)(node + 1);
								strncpy(node->ln_Name,&name[1],name[0]);
								node->ln_Name[ name[0] ] = '\0';

								AddTail(&odn->odn_VolumeList,node);

								if(odn->odn_NextNode == NULL)
									odn->odn_NextNode = node;
							}
						}
					}

					dol = NextDosEntry(dol,LDF_VOLUMES|LDF_READ);
				}

				UnLockDosList(LDF_VOLUMES|LDF_READ);
			}
			else
			{
				errno = ENOMEM;
			}
		}
		else
		{
			struct MangleInfo mi;

			if(MangleName(&dirName,&mi) == OK)
			{
				BPTR fileLock;

				ForbidDOS();

				fileLock = Lock(dirName,SHARED_LOCK);
				if(fileLock != ZERO)
				{
					struct OpenDirNode * odn;

					odn = malloc(sizeof(*odn));
					if(odn != NULL)
					{
						BPTR parentDir;

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

						parentDir = ParentDir(fileLock);
						if(parentDir != ZERO)
						{
							if(Examine(parentDir,&odn->odn_FIB))
								odn->odn_ParentDirKey = odn->odn_FIB.fib_DiskKey;

							UnLock(parentDir);
						}

						if(Examine(fileLock,&odn->odn_FIB))
						{
							/* Make sure that we are
							 * trying to read a drawer
							 * and not a file.
							 */
							if(FIB_IS_DRAWER(&odn->odn_FIB))
							{
								odn->odn_DIR.dd_buf = (char *)odn;
								odn->odn_FileLock = fileLock;
								NewList(&odn->odn_VolumeList);

								/* The lock has been "swallowed"
								 * by the dir node; make sure that
								 * we don't unlock it.
								 */
								fileLock = ZERO;

								AddHead(&OpenDirList,(struct Node *)odn);
								result = &odn->odn_DIR;
							}
							else
							{
								errno = ENOTDIR;
							}
						}
						else
						{
							MapIoErrToErrno();
						}

						if(result == NULL)
							free(odn);
					}
					else
					{
						errno = ENOMEM;
					}

					UnLock(fileLock);
				}
				else
				{
					MapIoErrToErrno();
				}

				PermitDOS();

				UnmangleName(&dirName,&mi);
			}
		}
	}

	RETURN(result);
	return(result);
}

VOID
amiga_closedir(DIR *dir)
{
	if(dir != NULL)
	{
		struct OpenDirNode * odn = (struct OpenDirNode *)dir->dd_buf;

		if(odn != NULL)
		{
			struct Node * node;

			Remove((struct Node *)odn);

			while((node = RemHead(&odn->odn_VolumeList)) != NULL)
				free(node);

			UnLock(odn->odn_FileLock);
			free(odn);
		}
	}

	chkabort();
}

struct dirent *
amiga_readdir(DIR *dir)
{
	struct dirent * result = NULL;

	chkabort();

	if(dir != NULL)
	{
		struct OpenDirNode * odn = (struct OpenDirNode *)dir->dd_buf;

		if(odn != NULL)
		{
			struct dirent * d = &odn->odn_DirectoryEntry;

			if(odn->odn_ReadingVolumes)
			{
				/* The first directory entry points
				 * back to the directory itself.
				 */
				if(odn->odn_NextDirEntryIndex == 0)
				{
					strcpy(d->d_name,".");

					d->d_ino	= ++odn->odn_NextDirEntryIndex;
					d->d_namlen	= strlen(d->d_name);

					result = d;
				}
				else
				{
					/* Return the next volume in the list. */
					if(odn->odn_NextNode != NULL && odn->odn_NextNode->ln_Succ != NULL)
					{
						ASSERT(sizeof(d->d_name) >= strlen(odn->odn_NextNode->ln_Name));

						strcpy(d->d_name,odn->odn_NextNode->ln_Name);

						odn->odn_NextNode = odn->odn_NextNode->ln_Succ;

						d->d_ino	= odn->odn_NextDirEntryIndex++;
						d->d_namlen	= strlen(d->d_name);

						result = d;
					}
					else
					{
						errno = 0;
					}
				}
			}
			else
			{
				if(odn->odn_NextDirEntryIndex == 0)
				{
					/* The first directory entry points
					 * back to the directory itself.
					 */
					strcpy(d->d_name,".");

					d->d_ino	= odn->odn_FIB.fib_DiskKey;
					d->d_namlen	= strlen(d->d_name);

					odn->odn_NextDirEntryIndex++;
					result = d;
				}
				else if(odn->odn_NextDirEntryIndex == 1)
				{
					/* The second directory entry points
					 * to the parent directory.
					 */
					strcpy(d->d_name,"..");

					d->d_ino	= odn->odn_ParentDirKey;
					d->d_namlen	= strlen(d->d_name);

					odn->odn_NextDirEntryIndex++;
					result = d;
				}
				else
				{
					ForbidDOS();

					/* All other iterations pick up the
					 * next following directory entry.
					 */
					if(ExNext(odn->odn_FileLock,&odn->odn_FIB))
					{
						ASSERT(sizeof(d->d_name) >= strlen(odn->odn_FIB.fib_FileName));

						strcpy(d->d_name,odn->odn_FIB.fib_FileName);

						d->d_ino	= odn->odn_FIB.fib_DiskKey;
						d->d_namlen	= strlen(d->d_name);

						result = d;
					}
					else
					{
						LONG error = IoErr();

						if(error == ERROR_NO_MORE_ENTRIES)
						{
							errno = 0;
						}
						else
						{
							SetIoErr(error);
							MapIoErrToErrno();
						}
					}

					PermitDOS();
				}
			}
		}
	}

	if(result != NULL)
		SHOWSTRING(result->d_name);

	return(result);
}

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

STATIC int
TranslateRelativePath(char **namePtr,char *replace,int maxReplaceLen)
{
	int result = ERROR;
	char * name;

	ASSERT(namePtr != NULL && (*namePtr) != NULL && replace != NULL);

	/* If we have a current directory all references should
	 * be made relative to, do just that. Absolute paths
	 * are not modified, though.
	 */
	name = (*namePtr);
	if(NOT STRING_IS_EMPTY(CurrentDirName) && name[0] != '/')
	{
		int totalLen;

		SHOWMSG("Changing the directory name");

		/* Skip current dir modifiers, we just want the name. */
		if(strncmp(name,"./",2) == SAME)
			name += 2;
		else if (strcmp(name,".") == SAME)
			name = "";

		/* Get the current directory name and get
		 * ready to attach the file name at the end.
		 */
		totalLen = strlen(CurrentDirName);

		if(CurrentDirName[strlen(CurrentDirName)-1] != '/' &&
		   CurrentDirName[strlen(CurrentDirName)-1] != ':' &&
		   NOT STRING_IS_EMPTY(name))
		{
			totalLen++;
		}

		totalLen += strlen(name);

		/* Check if the complete string will fit. */
		if(totalLen < maxReplaceLen)
		{
			/* Put the file name together. */
			strcpy(replace,CurrentDirName);
			if(CurrentDirName[strlen(CurrentDirName)-1] != '/' && NOT STRING_IS_EMPTY(name))
				strcat(replace,"/");

			strcat(replace,name);

			(*namePtr) = replace;
			SHOWSTRING(*namePtr);

			result = OK;
		}
		else
		{
			errno = ENAMETOOLONG;
		}
	}
	else
	{
		result = OK;
	}

	return(result);
}

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

int
amiga_mkdir(char *name,int mode)
{
	struct MangleInfo mi;
	BPTR dirLock;
	int result = ERROR;

	chkabort();

	ASSERT(name != NULL);

	ENTER();
	SHOWSTRING(name);
	SHOWVALUE(mode);

	if(MangleName(&name,&mi) == OK)
	{
		ForbidDOS();

		dirLock = CreateDir((STRPTR)name);
		if(dirLock != ZERO)
		{
			UnLock(dirLock);
			result = OK;
		}
		else
		{
			MapIoErrToErrno();
		}

		PermitDOS();

		UnmangleName(&name,&mi);
	}

	if(result == OK)
		result = amiga_chmod(name,mode);

	RETURN(result);
	return(result);
}

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

int
amiga_rmdir(char *name)
{
	struct MangleInfo mi;
	BPTR fileLock;
	int result = ERROR;

	chkabort();

	ENTER();
	SHOWSTRING(name);

	if(MangleName(&name,&mi) == OK)
	{
		ForbidDOS();

		fileLock = Lock(name,SHARED_LOCK);
		if(fileLock != ZERO)
		{
			struct FileInfoBlock __aligned fib;

			if(Examine(fileLock,&fib))
			{
				UnLock(fileLock);
				fileLock = ZERO;

				/* Make sure that we get to remove a drawer,
				 * as the function name implies.
				 */
				if(FIB_IS_DRAWER(&fib))
				{
					if(DeleteFile(name))
						result = OK;
					else
						MapIoErrToErrno();
				}
				else
				{
					errno = ENOTDIR;
				}
			}
			else
			{
				MapIoErrToErrno();
			}

			UnLock(fileLock);
		}
		else
		{
			MapIoErrToErrno();
		}

		PermitDOS();

		UnmangleName(&name,&mi);
	}

	RETURN(result);
	return(result);
}

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

int
amiga_creat(char *name,int prot)
{
	struct MangleInfo mi;
	int result = ERROR;

	chkabort();

	ASSERT(name != NULL);

	ENTER();
	SHOWSTRING(name);
	SHOWVALUE(prot);

	if(MangleName(&name,&mi) == OK)
	{
		ForbidDOS();
		result = creat(name,prot);
		PermitDOS();

		UnmangleName(&name,&mi);
	}

	RETURN(result);
	return(result);
}

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

FILE *
amiga_fopen(char *name,char *mode)
{
	struct MangleInfo mi;
	FILE *result = NULL;

	chkabort();

	ASSERT(name != NULL && mode != NULL);

	ENTER();
	SHOWSTRING(name);
	SHOWSTRING(mode);

	if(MangleName(&name,&mi) == OK)
	{
		ForbidDOS();
		result = fopen(name,mode);
		PermitDOS();

		UnmangleName(&name,&mi);
	}

	RETURN(result);
	return(result);
}

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

int
amiga_rename(char *old,char *new)
{
	struct MangleInfo old_mi;
	struct MangleInfo new_mi;
	int result = ERROR;

	chkabort();

	ASSERT(old != NULL && new != NULL);

	ENTER();
	SHOWSTRING(old);
	SHOWSTRING(new);

	/* rename() causes the link named <from> to be renamed as <to>. If <to> exists,
	 * it is first removed. Both <from> and <to> must be of the same type (that is,
	 * both directories or both non-directories), and must reside on the same
	 * file system.
	 */

	if(MangleName(&old,&old_mi) == OK)
	{
		if(MangleName(&new,&new_mi) == OK)
		{
			ForbidDOS();

			D(("rename |%s| to |%s|",old,new));

			if(CANNOT Rename(old,new))
			{
				LONG error = IoErr();

				SHOWVALUE(error);

				if(error == ERROR_OBJECT_EXISTS)
				{
					BPTR oldLock = Lock(old,SHARED_LOCK);
					BPTR newLock = Lock(new,SHARED_LOCK);

					if(oldLock != ZERO && newLock != ZERO && SameLock(oldLock,newLock) == LOCK_SAME)
					{
						result = OK;
						error = OK;

						SHOWMSG("Ok; same name");

						UnLock(oldLock);
						UnLock(newLock);
					}
					else
					{
						UnLock(oldLock);
						UnLock(newLock);

						if(DeleteFile(new))
						{
							if(Rename(old,new))
							{
								result = OK;
								error = OK;

								SHOWMSG("Ok; after removing");
							}
							else
							{
								error = IoErr();
							}
						}
						else
						{
							error = IoErr();
						}
					}
				}

				if(error != OK)
				{
					SHOWVALUE(error);

					SetIoErr(error);
					MapIoErrToErrno();
				}
			}
			else
			{
				SHOWMSG("Ok");

				result = OK;
			}

			PermitDOS();

			UnmangleName(&new,&new_mi);
		}
		else
		{
			SHOWMSG("MangleName new_mi failed");
		}

		UnmangleName(&old,&old_mi);
	}
	else
	{
		SHOWMSG("MangleName old_mi failed");
	}

	RETURN(result);
	return(result);
}

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

char *
amiga_getcwd(char *buf, size_t size)
{
	char *result = NULL;

	chkabort();

	ASSERT(buf != NULL);

	ENTER();
	SHOWVALUE(buf);
	SHOWVALUE(size);

	if(CurrentDirName[0] == '/')
	{
		strncpy(buf,CurrentDirName,size-1);
		buf[size-1] = '\0';
		result = buf;
	}
	else
	{
		BPTR oldDir;

		ForbidDOS();

		oldDir = CurrentDir(ZERO);

		if(NameFromLock(oldDir,buf,size))
		{
			if(MapFileNameAmigaToUnix(buf,buf,size) == OK)
				result = buf;
		}
		else
		{
			MapIoErrToErrno();
		}

		CurrentDir(oldDir);

		PermitDOS();
	}

	SHOWSTRING(result);

	RETURN(result);
	return(result);
}

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

#define SET_FILESIZE_ERROR (-1)

int
amiga_ftruncate(int fd,off_t size)
{
	struct UFB * ufb;
	int result = ERROR;

	chkabort();

	ENTER();
	SHOWVALUE(fd);
	SHOWVALUE(size);

	ufb = chkufb(fd);
	if(ufb != NULL)
	{
		ForbidDOS();

		if(SetFileSize((BPTR)ufb->ufbfh,size,OFFSET_BEGINNING) != SET_FILESIZE_ERROR)
			result = OK;
		else
			MapIoErrToErrno();

		PermitDOS();
	}
	else
	{
		errno = EBADF;
	}

	RETURN(result);
	return(result);
}

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

int
amiga_accept(int sockfd,struct sockaddr *cliaddr,int *addrlen)
{
	struct UFB * ufb;
	int result = ERROR;

	chkabort();

	ASSERT(cliaddr != NULL && addrlen != NULL);

	ENTER();
	SHOWVALUE(sockfd);
	SHOWVALUE(cliaddr);
	SHOWVALUE(addrlen);

	ufb = chkufb(sockfd);
	if(ufb != NULL && FLAG_IS_SET(ufb->ufbflg,UFB_IS_SOCKET))
		result = accept(ufb->ufbfh,cliaddr,(LONG *)addrlen);
	else
		errno = ENOTSOCK;

	RETURN(result);
	return(result);
}

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

int
amiga_bind(int sockfd,struct sockaddr *name,int namelen)
{
	struct UFB * ufb;
	int result = ERROR;

	chkabort();

	ASSERT(name != NULL);

	ENTER();
	SHOWVALUE(sockfd);
	SHOWVALUE(name);
	SHOWVALUE(namelen);

	ufb = chkufb(sockfd);
	if(ufb != NULL && FLAG_IS_SET(ufb->ufbflg,UFB_IS_SOCKET))
		result = bind(ufb->ufbfh,name,namelen);
	else
		errno = ENOTSOCK;

	RETURN(result);
	return(result);
}

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

int
amiga_close(int fd)
{
	struct UFB * ufb;
	int result = ERROR;

	chkabort();

	ENTER();
	SHOWVALUE(fd);

	ufb = chkufb(fd);
	if(ufb != NULL && FLAG_IS_SET(ufb->ufbflg,UFB_IS_SOCKET))
	{
		result = CloseSocket(ufb->ufbfh);
		CLEAR_FLAG(ufb->ufbflg,UFB_IS_SOCKET);

		/* Make sure that the descriptor looks like a file again. */
		RestoreDescriptor(ufb);

		close(fd);
	}
	else
	{
		if(ufb != NULL)
		{
			/* Don't close the stdio streams! */
			if(fd == 0 || fd == 1 || fd == 2)
			{
				D(("Attempt to muck with fd #%ld!",fd));

				errno = EBADF;
			}
			else
			{
				char fileName[MAX_FILENAME_LEN];
				BOOL removeIt = FALSE;

				if(FLAG_IS_SET(ufb->ufbflg,UFB_UNLINK))
				{
					removeIt = NameFromFH((BPTR)ufb->ufbfh,fileName,sizeof(fileName));

					CLEAR_FLAG(ufb->ufbflg,UFB_UNLINK);
				}

				CleanupFileLocks(fd);

				result = close(fd);

				if(removeIt)
					DeleteFile(fileName);
			}
		}
		else
		{
			errno = EBADF;
		}
	}

	RETURN(result);
	return(result);
}

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

int
amiga_connect(int sockfd,struct sockaddr *name,int namelen)
{
	struct UFB * ufb;
	int result = ERROR;

	chkabort();

	ASSERT(name != NULL && namelen > 0);

	ENTER();
	SHOWVALUE(sockfd);
	SHOWVALUE(name);
	SHOWVALUE(namelen);

	ufb = chkufb(sockfd);
	if(ufb != NULL && FLAG_IS_SET(ufb->ufbflg,UFB_IS_SOCKET))
		result = connect(ufb->ufbfh,name,namelen);
	else
		errno = ENOTSOCK;

	RETURN(result);
	return(result);
}

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

int
amiga_getpeername(int sockfd,struct sockaddr *name,int *namelen)
{
	struct UFB * ufb;
	int result = ERROR;

	chkabort();

	ASSERT(name != NULL && namelen != NULL);

	ENTER();
	SHOWVALUE(sockfd);
	SHOWVALUE(name);
	SHOWVALUE(namelen);

	ufb = chkufb(sockfd);
	if(ufb != NULL && FLAG_IS_SET(ufb->ufbflg,UFB_IS_SOCKET))
		result = getpeername(ufb->ufbfh,name,(LONG *)namelen);
	else
		errno = ENOTSOCK;

	RETURN(result);
	return(result);
}

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

int
amiga_getsockopt(int sockfd,int level,int optname,VOID *optval,int *optlen)
{
	struct UFB * ufb;
	int result = ERROR;

	chkabort();

	ASSERT(optval != NULL && optlen != NULL);

	ENTER();
	SHOWVALUE(sockfd);
	SHOWVALUE(level);
	SHOWVALUE(optname);
	SHOWVALUE(optval);
	SHOWVALUE(optlen);

	ufb = chkufb(sockfd);
	if(ufb != NULL && FLAG_IS_SET(ufb->ufbflg,UFB_IS_SOCKET))
		result = getsockopt(ufb->ufbfh,level,optname,optval,(LONG *)optlen);
	else
		errno = ENOTSOCK;

	RETURN(result);
	return(result);
}

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

int
amiga_ioctl(int fd,unsigned long request,char *arg)
{
	struct UFB * ufb;
	int result = ERROR;

	chkabort();

	ASSERT(arg != NULL);

	ENTER();
	SHOWVALUE(fd);
	SHOWVALUE(request);
	SHOWVALUE(arg);

	ufb = chkufb(fd);
	if(ufb != NULL && FLAG_IS_SET(ufb->ufbflg,UFB_IS_SOCKET))
		result = IoctlSocket(ufb->ufbfh,request,arg);
	else
		errno = ENOTSOCK;

	RETURN(result);
	return(result);
}

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

int
amiga_listen(int sockfd,int backlog)
{
	struct UFB * ufb;
	int result = ERROR;

	chkabort();

	ENTER();
	SHOWVALUE(sockfd);
	SHOWVALUE(backlog);

	ufb = chkufb(sockfd);
	if(ufb != NULL && FLAG_IS_SET(ufb->ufbflg,UFB_IS_SOCKET))
		result = listen(ufb->ufbfh,backlog);
	else
		errno = ENOTSOCK;

	RETURN(result);
	return(result);
}

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

int
amiga_read(int fd,VOID *data,unsigned int size)
{
	struct UFB * ufb;
	int result;

	chkabort();

	ASSERT(data != NULL);

	ENTER();
	SHOWVALUE(fd);
	SHOWVALUE(data);
	SHOWVALUE(size);

	ufb = chkufb(fd);
	if(ufb != NULL && FLAG_IS_SET(ufb->ufbflg,UFB_IS_SOCKET))
	{
		SHOWMSG("input from socket");
		result = recv(ufb->ufbfh,data,size,0);
	}
	else
	{
		SHOWMSG("input from file");
		ForbidDOS();
		result = read(fd,data,size);
		PermitDOS();
	}

	RETURN(result);
	return(result);
}

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

int
amiga_recvfrom(int sockfd,VOID *buff,int len,int flags,struct sockaddr *from,int *fromlen)
{
	struct UFB * ufb;
	int result = ERROR;

	chkabort();

	ASSERT(buff != NULL && from != NULL && fromlen != NULL);

	ENTER();
	SHOWVALUE(sockfd);
	SHOWVALUE(buff);
	SHOWVALUE(len);
	SHOWVALUE(flags);
	SHOWVALUE(from);
	SHOWVALUE(fromlen);

	ufb = chkufb(sockfd);
	if(ufb != NULL && FLAG_IS_SET(ufb->ufbflg,UFB_IS_SOCKET))
		result = recvfrom(ufb->ufbfh,buff,len,flags,from,(LONG *)fromlen);
	else
		errno = ENOTSOCK;

	RETURN(result);
	return(result);
}

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

STATIC VOID
MapDescriptorSets(
	const fd_set *	input_fds,
	int				num_input_fds,
	fd_set *		socket_fds,
	int *			max_socket_fd_ptr,
	fd_set *		file_fds,
	int *			max_file_fd_ptr)
{
	FD_ZERO(socket_fds);
	FD_ZERO(file_fds);

	/* This routine maps file descriptor sets
	 * from one format to another. We map
	 * socket descriptors and regular file
	 * descriptor sets.
	 */
	if(input_fds != NULL && num_input_fds > 0)
	{
		int max_socket_fd = (*max_socket_fd_ptr);
		int max_file_fd = (*max_file_fd_ptr);
		struct UFB * ufb;
		int i;

		for(i = 0 ; i < num_input_fds ; i++)
		{
			if(FD_ISSET(i,input_fds))
			{
				D(("fd to wait on #%ld",i));

				ufb = chkufb(i);
				if(ufb != NULL)
				{
					/* Is this a socket descriptor? */
					if(FLAG_IS_SET(ufb->ufbflg,UFB_IS_SOCKET))
					{
						D(("fd #%ld is a socket.\n",i));

						FD_SET(ufb->ufbfh,socket_fds);

						if(max_socket_fd < ufb->ufbfh)
							max_socket_fd = ufb->ufbfh;
					}
					else
					{
						D(("fd #%ld is a file.\n",i));

						/* We only watch files bound to
						 * console streams.
						 */
						if(IsInteractive((BPTR)ufb->ufbfh))
						{
							FD_SET(i,file_fds);

							if(max_file_fd < i)
								max_file_fd = i;
						}
					}
				}
			}
		}

		(*max_socket_fd_ptr)	= max_socket_fd;
		(*max_file_fd_ptr)		= max_file_fd;
	}
}

STATIC VOID
RemapDescriptorSets(
	const fd_set *	socket_fds,
	int				max_socket_fd,
	const fd_set *	file_fds,
	int				max_file_fd,
	fd_set *		output_fds,
	int				num_output_fds)
{
	/* This routine reverses the mapping established
	 * above. We map the file and socket descriptor
	 * sets back into the original set.
	 */
	if(output_fds != NULL)
	{
		int fd;

		FD_ZERO(output_fds);

		for(fd = 0 ; fd <= max_socket_fd ; fd++)
		{
			if(FD_ISSET(fd,socket_fds))
			{
				struct UFB * ufb;
				int output_fd;

				for(output_fd = 0 ; output_fd < num_output_fds ; output_fd++)
				{
					ufb = chkufb(output_fd);
					if(ufb != NULL && FLAG_IS_SET(ufb->ufbflg,UFB_IS_SOCKET) && ufb->ufbfh == fd)
					{
						D(("fd #%ld has data",output_fd));
						FD_SET(output_fd,output_fds);
						break;
					}
				}
			}
		}

		for(fd = 0 ; fd <= max_file_fd ; fd++)
		{
			if(FD_ISSET(fd,file_fds))
			{
				D(("fd #%ld has data",fd));
				FD_SET(fd,output_fds);
			}
		}
	}
}

int
amiga_select(int num_fds,fd_set *read_fds,fd_set *write_fds,fd_set *except_fds,struct timeval *timeout)
{
	fd_set socket_read_fds;
	fd_set socket_write_fds;
	fd_set socket_except_fds;
	int max_socket_fd;
	fd_set file_read_fds;
	fd_set file_write_fds;
	fd_set file_except_fds;
	struct UFB * ufb;
	int max_file_fd;
	int result = 0;

	chkabort();

	ENTER();

	if(num_fds > FD_SETSIZE)
	{
		D(("Warning: %ld descriptor sets to wait on requested; only %ld available.",num_fds,FD_SETSIZE));
		num_fds = FD_SETSIZE;
	}

	max_socket_fd = -1;
	max_file_fd = -1;

	MapDescriptorSets(read_fds,		num_fds,	&socket_read_fds,	&max_socket_fd,	&file_read_fds,		&max_file_fd);
	MapDescriptorSets(write_fds,	num_fds,	&socket_write_fds,	&max_socket_fd,	&file_write_fds,	&max_file_fd);
	MapDescriptorSets(except_fds,	num_fds,	&socket_except_fds,	&max_socket_fd,	&file_except_fds,	&max_file_fd);

	D(("number of socket fds to work on == %ld",max_socket_fd+1));
	D(("number of file   fds to work on == %ld",max_file_fd+1));

	/* Wait for socket input? */
	if(max_socket_fd != -1)
	{
		/* Wait for file input, too? */
		if(max_file_fd != -1 && (timeout == NULL || timeout->tv_secs > 0 || timeout->tv_micro > 0))
		{
			struct timeval stopWhen;
			struct timeval zero;
			BOOL gotSomething;
			ULONG breakMask;
			int i;

			/* We are going to poll all streams; for the timeout
			 * feature to work, we absolutely must know when to
			 * stop polling.
			 *
			 * Why aren't we using asynchronous DOS packets?
			 * The answer is that once a packet is sent, you
			 * cannot easily abort it. Polling is safer in
			 * that respect. Yes, I know that ACTION_STACK
			 * can be used to fake input to a console stream,
			 * but I'd rather not rely upon it.
			 */
			if(timeout != NULL)
			{
				GetSysTime((APTR)&stopWhen);
				AddTime((APTR)&stopWhen,(APTR)timeout);
			}
			else
			{
				/* No timeout, poll until we are interrupted
				 * or get input from any of the files. It's
				 * not really necessary to initialize this
				 * timeval, but it keeps the compiler happy.
				 */
				memset(&stopWhen,0,sizeof(stopWhen));
			}

			while(TRUE)
			{
				/* Check for break signal. */
				chkabort();

				/* Delay for a tick to avoid busy-waiting. */
				Delay(1);

				/* This tells WaitSelect() to poll the sockets for input. */
				zero.tv_secs	= 0;
				zero.tv_micro	= 0;

				/* Signals to stop on; we want to stop when a break signal arrives. */
				if(AllowBreak)
					breakMask = SIGBREAKF_CTRL_C;
				else
					breakMask = 0;

				/* Check for socket input. */
				result = WaitSelect(max_socket_fd+1,&socket_read_fds,&socket_write_fds,&socket_except_fds,&zero,&breakMask);

				/* Stop if a break signal arrives. */
				if((result < 0 && errno == EINTR) || FLAG_IS_SET(breakMask,SIGBREAKF_CTRL_C))
					raise(SIGINT);

				/* Stop if the return value from WaitSelect is negative. */
				if(result < 0)
					break;

				/* Did we get any socket input? */
				gotSomething = (BOOL)(result > 0);
				if(NOT gotSomething)
				{
					/* Check all files for input. We also poll
					 * them for input, but each with a little
					 * delay of about 1/50 of a second. We stop
					 * as soon as we find one file that has
					 * input in it.
					 */
					for(i = 0 ; i <= max_file_fd ; i++)
					{
						if(FD_ISSET(i,&file_read_fds) ||
						   FD_ISSET(i,&file_write_fds) ||
						   FD_ISSET(i,&file_except_fds))
						{
							ufb = chkufb(i);
							if(ufb != NULL)
							{
								if(WaitForChar((BPTR)ufb->ufbfh,1))
								{
									gotSomething = TRUE;
									break;
								}
							}
						}
					}
				}

				/* Did we get any input at all? */
				if(gotSomething)
				{
					BOOL gotInput;

					/* Now retest all files and remember
					 * those that had input.
					 */
					for(i = 0 ; i <= max_file_fd ; i++)
					{
						gotInput = FALSE;

						if(FD_ISSET(i,&file_read_fds) ||
						   FD_ISSET(i,&file_write_fds) ||
						   FD_ISSET(i,&file_except_fds))
						{
							ufb = chkufb(i);
							if(ufb != NULL)
							{
								/* Does this one have input? */
								gotInput = WaitForChar((BPTR)ufb->ufbfh,1);
							}
						}

						if(gotInput)
						{
							/* Mark one more descriptor as
							 * having input.
							 */
							result++;
						}
						else
						{
							/* Mark this descriptor as
							 * not having any input.
							 */
							FD_CLR(i,&file_read_fds);
							FD_CLR(i,&file_write_fds);
							FD_CLR(i,&file_except_fds);
						}
					}
				}

				/* Did we get any input? If so, stop polling. */
				if(result > 0)
					break;

				/* If a timeout was set, check if we are already
				 * beyond the point of time when we should have
				 * stopped polling.
				 */
				if(timeout != NULL)
				{
					struct timeval now;

					GetSysTime((APTR)&now);

					if((-CmpTime((APTR)&now,(APTR)&stopWhen)) >= 0)
						break;
				}
			}
		}
		else
		{
			ULONG breakMask;

			if(AllowBreak)
				breakMask = SIGBREAKF_CTRL_C;
			else
				breakMask = 0;

			result = WaitSelect(max_socket_fd+1,&socket_read_fds,&socket_write_fds,&socket_except_fds,timeout,&breakMask);
			if((result < 0 && errno == EINTR) || FLAG_IS_SET(breakMask,SIGBREAKF_CTRL_C))
				raise(SIGINT);
		}
	}
	else
	{
		/* Wait for file input? */
		if(max_file_fd != -1 && (timeout == NULL || timeout->tv_secs > 0 || timeout->tv_micro > 0))
		{
			struct timeval stopWhen;
			BOOL gotSomething;
			int i;

			if(timeout != NULL)
			{
				GetSysTime((APTR)&stopWhen);
				AddTime((APTR)&stopWhen,(APTR)timeout);
			}
			else
			{
				memset(&stopWhen,0,sizeof(stopWhen));
			}

			while(TRUE)
			{
				chkabort();

				Delay(1);

				gotSomething = FALSE;
				for(i = 0 ; i <= max_file_fd ; i++)
				{
					if(FD_ISSET(i,&file_read_fds) ||
					   FD_ISSET(i,&file_write_fds) ||
					   FD_ISSET(i,&file_except_fds))
					{
						ufb = chkufb(i);
						if(ufb != NULL)
						{
							if(WaitForChar((BPTR)ufb->ufbfh,1))
							{
								gotSomething = TRUE;
								break;
							}
						}
					}
				}

				if(gotSomething)
				{
					BOOL gotInput;

					for(i = 0 ; i <= max_file_fd ; i++)
					{
						gotInput = FALSE;

						if(FD_ISSET(i,&file_read_fds) ||
						   FD_ISSET(i,&file_write_fds) ||
						   FD_ISSET(i,&file_except_fds))
						{
							ufb = chkufb(i);
							if(ufb != NULL)
							{
								/* Does this one have input? */
								gotInput = WaitForChar((BPTR)ufb->ufbfh,1);
							}
						}

						if(gotInput)
						{
							result++;
						}
						else
						{
							FD_CLR(i,&file_read_fds);
							FD_CLR(i,&file_write_fds);
							FD_CLR(i,&file_except_fds);
						}
					}
				}

				if(result > 0)
					break;

				if(timeout != NULL)
				{
					struct timeval now;

					GetSysTime((APTR)&now);

					if((-CmpTime((APTR)&now,(APTR)&stopWhen)) >= 0)
						break;
				}
			}
		}
	}

	/* The descriptor sets remain unchanged in
	 * case of error.
	 */
	if(result >= 0)
	{
		RemapDescriptorSets(&socket_read_fds,	max_socket_fd,	&file_read_fds,		max_file_fd,	read_fds,	num_fds);
		RemapDescriptorSets(&socket_write_fds,	max_socket_fd,	&file_write_fds,	max_file_fd,	write_fds,	num_fds);
		RemapDescriptorSets(&socket_except_fds,	max_socket_fd,	&file_except_fds,	max_file_fd,	except_fds,	num_fds);
	}

	RETURN(result);
	return(result);
}

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

int
amiga_sendto(int sockfd,VOID *buff,int len,int flags,struct sockaddr *to,int tolen)
{
	struct UFB * ufb;
	int result = ERROR;

	chkabort();

	ASSERT(buff != NULL && to != NULL);

	ENTER();
	SHOWVALUE(sockfd);
	SHOWVALUE(buff);
	SHOWVALUE(len);
	SHOWVALUE(flags);
	SHOWVALUE(to);
	SHOWVALUE(tolen);

	ufb = chkufb(sockfd);
	if(ufb != NULL && FLAG_IS_SET(ufb->ufbflg,UFB_IS_SOCKET))
		result = sendto(ufb->ufbfh,buff,len,flags,to,tolen);
	else
		errno = ENOTSOCK;

	RETURN(result);
	return(result);
}

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

int
amiga_setsockopt(int sockfd,int level,int optname,VOID *optval,int optlen)
{
	struct UFB * ufb;
	int result = ERROR;

	chkabort();

	ASSERT(optval != NULL);

	ENTER();
	SHOWVALUE(sockfd);
	SHOWVALUE(level);
	SHOWVALUE(optname);
	SHOWVALUE(optval);
	SHOWVALUE(optlen);

	ufb = chkufb(sockfd);
	if(ufb != NULL && FLAG_IS_SET(ufb->ufbflg,UFB_IS_SOCKET))
		result = setsockopt(ufb->ufbfh,level,optname,optval,optlen);
	else
		errno = ENOTSOCK;

	RETURN(result);
	return(result);
}

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

int
amiga_socket(int domain,int type,int protocol)
{
	int result = ERROR;
	int fd;

	chkabort();

	ENTER();
	SHOWVALUE(domain);
	SHOWVALUE(type);
	SHOWVALUE(protocol);

	/* We open a regular file that is guaranteed to
	 * open in any case and then attach a socket
	 * in place of the original file handle.
	 */
	fd = open("NIL:",O_RDWR,0777);
	if(fd != -1)
	{
		struct UFB * ufb;

		ufb = chkufb(fd);
		if(ufb != NULL)
		{
			/* Save the original file handle value. */
			if(SaveDescriptor(ufb))
			{
				int sockfd;

				/* Now create the real socket. */
				sockfd = socket(domain,type,protocol);
				if(sockfd != -1)
				{
					/* Put the socket in place of the file handle. */
					SET_FLAG(ufb->ufbflg,UFB_IS_SOCKET);
					ufb->ufbfh = sockfd;

					result = fd;
				}
				else
				{
					close(fd);
				}
			}
			else
			{
				close(fd);
				errno = ENOMEM;
			}
		}
		else
		{
			close(fd);
			errno = EBADF;
		}
	}

	RETURN(result);
	return(result);
}

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

int
amiga_write(int fd,VOID *data,unsigned int size)
{
	struct UFB * ufb;
	int result;

	chkabort();

	ASSERT(data != NULL);

	ENTER();
	SHOWVALUE(fd);
	SHOWVALUE(data);
	SHOWVALUE(size);

	ufb = chkufb(fd);
	if(ufb != NULL && FLAG_IS_SET(ufb->ufbflg,UFB_IS_SOCKET))
	{
		result = send(ufb->ufbfh,data,size,0);
	}
	else
	{
		ForbidDOS();
		result = write(fd,data,size);
		PermitDOS();
	}

	RETURN(result);
	return(result);
}

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

STATIC VOID
ConvertFileInfoToStat(
	struct MsgPort * port,
	struct FileInfoBlock * fib,
	struct stat * st)
{
	ULONG flags;
	int mode;
	long time;

	/* This routine converts the contents of a FileInfoBlock
	 * into information to fill a Unix-like stat data structure
	 * with.
	 */
	flags = fib->fib_Protection ^ (FIBF_READ|FIBF_WRITE|FIBF_EXECUTE|FIBF_DELETE);

	if(FIB_IS_DRAWER(fib))
		mode = S_IFDIR;
	else
		mode = S_IFREG;

	if(FLAG_IS_SET(flags,FIBF_READ))
		SET_FLAG(mode,S_IRUSR);

	if(FLAG_IS_SET(flags,FIBF_WRITE) && FLAG_IS_SET(flags,FIBF_DELETE))
		SET_FLAG(mode,S_IWUSR);

	if(FLAG_IS_SET(flags,FIBF_EXECUTE))
		SET_FLAG(mode,S_IXUSR);


	if(FLAG_IS_SET(flags,FIBF_GRP_READ))
		SET_FLAG(mode,S_IRGRP);

	if(FLAG_IS_SET(flags,FIBF_GRP_WRITE) && FLAG_IS_SET(flags,FIBF_GRP_DELETE))
		SET_FLAG(mode,S_IWGRP);

	if(FLAG_IS_SET(flags,FIBF_GRP_EXECUTE))
		SET_FLAG(mode,S_IXGRP);


	if(FLAG_IS_SET(flags,FIBF_OTR_READ))
		SET_FLAG(mode,S_IROTH);

	if(FLAG_IS_SET(flags,FIBF_OTR_WRITE) && FLAG_IS_SET(flags,FIBF_OTR_DELETE))
		SET_FLAG(mode,S_IWOTH);

	if(FLAG_IS_SET(flags,FIBF_OTR_EXECUTE))
		SET_FLAG(mode,S_IXOTH);

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

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

	st->st_dev		= (u_long)port;
	st->st_ino		= fib->fib_DiskKey;
	st->st_mode		= mode;
	st->st_mtime	= UNIX_TIME_OFFSET + time + 60*MinutesWest;	/* translate from local time to UTC */
	st->st_atime	= st->st_mtime;
	st->st_ctime	= st->st_mtime;
	st->st_uid		= fib->fib_OwnerUID;
	st->st_gid		= fib->fib_OwnerGID;

	if(FIB_IS_FILE(fib))
	{
		st->st_nlink = 1;
		st->st_size  = fib->fib_Size;
	}
	else
	{
		st->st_nlink = 2;
	}
}

int
amiga_stat(char *name, struct stat *st)
{
	char localName[MAX_FILENAME_LEN];
	struct MangleInfo mi;
	int result = ERROR;

	chkabort();

	ASSERT(name != NULL && st != NULL);

	ENTER();
	SHOWSTRING(name);
	SHOWVALUE(st);

	if(TranslateRelativePath(&name,localName,sizeof(localName)) == OK)
	{
		char * originalName = name;

		if(MangleName(&name,&mi) == OK)
		{
			int len;

			SHOWSTRING(name);
			SHOWSTRING(originalName);

			len = strlen(name);
			if((strcmp(originalName,"/") == SAME) || (len > 1 && name[len-2] == ':' && name[len-1] == '/'))
			{
				struct timeval now;

				SHOWMSG("this is the virtual root directory");

				/* This must be our virtual root directory.
				 * Make something up.
				 */
				memset(st,0,sizeof(*st));
				GetSysTime((APTR)&now);

				/* Nobody may write to this "directory". */
				st->st_dev		= (u_long)"Virtual Root Directory";
				st->st_ino		= 1;
				st->st_mode		= S_IFDIR|S_IRUSR|S_IXUSR|S_IRGRP|S_IXGRP|S_IROTH|S_IXOTH;
				st->st_nlink	= 2;
				st->st_mtime	= UNIX_TIME_OFFSET + now.tv_secs + 60*MinutesWest;	/* translate from local time to UTC */
				st->st_atime	= st->st_mtime;
				st->st_ctime	= st->st_mtime;

				result = OK;
			}
			else
			{
				BPTR fileLock;

				SHOWMSG("this is a drawer or a file");

				ForbidDOS();

				fileLock = Lock((STRPTR)name,SHARED_LOCK);
				if(fileLock != ZERO)
				{
					struct FileInfoBlock __aligned fib;

					if(Examine(fileLock,&fib))
					{
						BPTR parentDir;

						/* Check if this is the root directory. */
						parentDir = ParentDir(fileLock);
						if(parentDir != ZERO)
						{
							/* This is not the root directory. */
							UnLock(parentDir);
						}
						else
						{
							/* So this is the root directory. Make sure
							 * that we return proper protection bits for
							 * it, i.e. that the directory is always
							 * readable, writable, etc. This may be
							 * necessary since on the Amiga, root
							 * directories cannot have any protection
							 * bits set. Note that the "deletable"
							 * bits don't make much sense, but then
							 * these bits work together with the
							 * writable bits. The lowest four bits
							 * remain zero, which enables them all.
							 */
							fib.fib_Protection = FIBF_OTR_READ |
							                     FIBF_OTR_WRITE |
							                     FIBF_OTR_EXECUTE |
							                     FIBF_OTR_DELETE |
							                     FIBF_GRP_READ |
							                     FIBF_GRP_WRITE |
							                     FIBF_GRP_EXECUTE |
							                     FIBF_GRP_DELETE;
						}

						ConvertFileInfoToStat(((struct FileLock *)BADDR(fileLock))->fl_Task,&fib,st);

						result = OK;
					}
					else
					{
						MapIoErrToErrno();
					}

					UnLock(fileLock);
				}
				else
				{
					LONG error = IoErr();

					if(error == ERROR_OBJECT_IN_USE)
					{
						char parentName[MAX_FILENAME_LEN];
						BPTR parentLock;

						strcpy(parentName,name);
						(*PathPart(parentName) = '\0');

						parentLock = Lock(parentName,SHARED_LOCK);
						if(parentLock != ZERO)
						{
							struct FileInfoBlock __aligned fib;

							if(Examine(parentLock,&fib))
							{
								STRPTR onlyFileName = FilePart(name);

								while(ExNext(parentLock,&fib))
								{
									if(Stricmp(fib.fib_FileName,onlyFileName) == SAME)
									{
										ConvertFileInfoToStat(((struct FileLock *)BADDR(parentLock))->fl_Task,&fib,st);

										result = OK;
										error = OK;
										break;
									}
								}
							}

							UnLock(parentLock);
						}
					}

					if(error != OK)
					{
						SetIoErr(error);

						MapIoErrToErrno();
					}
				}

				PermitDOS();
			}

			if(result == OK)
			{
				SHOWVALUE(st->st_dev);
				SHOWVALUE(st->st_ino);
				SHOWVALUE(st->st_size);
				SHOWVALUE(st->st_nlink);
				SHOWVALUE(st->st_mode);
				SHOWVALUE(st->st_mtime);
				SHOWVALUE(st->st_atime);
				SHOWVALUE(st->st_ctime);
				SHOWVALUE(st->st_uid);
				SHOWVALUE(st->st_gid);
			}
			else
			{
				SHOWVALUE(errno);
			}

			UnmangleName(&name,&mi);
		}
	}

	RETURN(result);
	return(result);
}

int
amiga_lstat(char *name, struct stat *statstruct)
{
	int result;

	chkabort();

	ENTER();
	SHOWSTRING(name);
	SHOWVALUE(statstruct);

	result = amiga_stat(name,statstruct);

	RETURN(result);
	return(result);
}

int
amiga_fstat(int fd,struct stat * st)
{
	struct UFB * ufb;
	int result = ERROR;

	chkabort();

	ASSERT(st != NULL);

	ENTER();
	SHOWVALUE(fd);
	SHOWVALUE(st);

	ufb = chkufb(fd);
	if(ufb != NULL)
	{
		if(FLAG_IS_SET(ufb->ufbflg,UFB_IS_SOCKET))
		{
			long value;
			long size = sizeof(value);

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

			st->st_dev	= (u_long)SocketBase;
			st->st_mode	= S_IFSOCK | S_IRUSR | S_IWUSR;
			st->st_uid	= geteuid();
			st->st_gid	= getegid();

			if(getsockopt(fd,SOL_SOCKET,SO_SNDBUF,&value,&size) == 0)
				st->st_blksize = value;

			result = OK;
		}
		else
		{
			struct FileHandle * fileHandle = BADDR(ufb->ufbfh);

			/* Make sure that this stream doesn't
			 * really refer to "NIL:".
			 */
			if(fileHandle->fh_Type != NULL)
			{
				struct FileInfoBlock __aligned fib;

				ForbidDOS();

				if(ExamineFH(ufb->ufbfh,&fib))
				{
					ConvertFileInfoToStat(fileHandle->fh_Type,&fib,st);

					result = OK;
				}
				else
				{
					MapIoErrToErrno();
				}

				PermitDOS();
			}
			else
			{
				struct timeval tv;

				/* Make up some phony data for a NIL: file handle. */
				memset(st,0,sizeof(*st));

				GetSysTime((APTR)&tv);

				st->st_dev		= (u_long)0;
				st->st_mode		= S_IFREG | S_IRUSR | S_IWUSR;
				st->st_mtime	= UNIX_TIME_OFFSET + tv.tv_secs + 60*MinutesWest;	/* translate from local time to UTC */
				st->st_atime	= st->st_mtime;
				st->st_ctime	= st->st_mtime;

				result = OK;
			}
		}
	}
	else
	{
		errno = EBADF;
	}

	RETURN(result);
	return(result);
}

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

int
amiga_chmod(char *name,int mode)
{
	struct MangleInfo mi;
	ULONG flags = 0;
	int result = ERROR;

	chkabort();

	ASSERT(name != NULL);

	ENTER();
	SHOWSTRING(name);
	SHOWVALUE(mode);

	/* Convert the file access modes into
	 * Amiga typical protection bits.
	 */
	if(FLAG_IS_SET(mode,S_IRUSR))
		SET_FLAG(flags,FIBF_READ);

	if(FLAG_IS_SET(mode,S_IWUSR))
	{
		SET_FLAG(flags,FIBF_WRITE);
		SET_FLAG(flags,FIBF_DELETE);
	}

	if(FLAG_IS_SET(mode,S_IXUSR))
		SET_FLAG(flags,FIBF_EXECUTE);


	if(FLAG_IS_SET(mode,S_IRGRP))
		SET_FLAG(flags,FIBF_GRP_READ);

	if(FLAG_IS_SET(mode,S_IWGRP))
	{
		SET_FLAG(flags,FIBF_GRP_WRITE);
		SET_FLAG(flags,FIBF_GRP_DELETE);
	}

	if(FLAG_IS_SET(mode,S_IXGRP))
		SET_FLAG(flags,FIBF_GRP_EXECUTE);


	if(FLAG_IS_SET(mode,S_IROTH))
		SET_FLAG(flags,FIBF_OTR_READ);

	if(FLAG_IS_SET(mode,S_IWOTH))
	{
		SET_FLAG(flags,FIBF_OTR_WRITE);
		SET_FLAG(flags,FIBF_OTR_DELETE);
	}

	if(FLAG_IS_SET(mode,S_IXOTH))
		SET_FLAG(flags,FIBF_OTR_EXECUTE);

	flags ^= (FIBF_READ|FIBF_WRITE|FIBF_EXECUTE|FIBF_DELETE);

	if(MangleName(&name,&mi) == OK)
	{
		ForbidDOS();

		if(SetProtection(name,flags))
		{
			result = OK;
		}
		else
		{
			LONG error = IoErr();

			/* Note sure about this one; is it really that important
			 * that the file attribute change succeeds? This is a definite
			 * FIXME.
			 */
			if(error == ERROR_OBJECT_IN_USE)
			{
				result = OK;
			}
			else
			{
				SetIoErr(error);

				MapIoErrToErrno();
			}
		}

		PermitDOS();

		UnmangleName(&name,&mi);
	}

	RETURN(result);
	return(result);
}

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

int
amiga_dup(int fd)
{
	struct UFB * ufb;
	int result = ERROR;

	chkabort();

	ENTER();
	SHOWVALUE(fd);

	ufb = chkufb(fd);
	if(ufb != NULL && FLAG_IS_SET(ufb->ufbflg,UFB_IS_SOCKET))
	{
		fd = open("NIL:",O_RDWR,0777);
		if(fd != -1)
		{
			struct UFB * ufb2;

			ufb2 = chkufb(fd);
			if(ufb2 != NULL)
			{
				if(SaveDescriptor(ufb2))
				{
					int sockfd;

					sockfd = Dup2Socket(ufb->ufbfh,-1);
					if(sockfd != -1)
					{
						SET_FLAG(ufb2->ufbflg,UFB_IS_SOCKET);
						ufb2->ufbfh = sockfd;

						result = fd;
					}
					else
					{
						close(fd);
					}
				}
				else
				{
					close(fd);
					errno = ENOMEM;
				}
			}
			else
			{
				close(fd);
				errno = EBADF;
			}
		}
	}
	else
	{
		errno = ENOTSOCK;
	}

	RETURN(result);
	return(result);
}

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

int
amiga_dup2(int old_fd,int new_fd)
{
	struct UFB * old_ufb;
	struct UFB * new_ufb;
	int result = ERROR;

	chkabort();

	ENTER();
	SHOWVALUE(old_fd);
	SHOWVALUE(new_fd);

	old_ufb = chkufb(old_fd);
	new_ufb = chkufb(new_fd);

	if(old_ufb != NULL && FLAG_IS_SET(old_ufb->ufbflg,UFB_IS_SOCKET) && new_ufb != NULL)
	{
		int sockfd;

		if(FLAG_IS_SET(new_ufb->ufbflg,UFB_IS_SOCKET))
		{
			CloseSocket(new_ufb->ufbfh);
			sockfd = Dup2Socket(old_ufb->ufbfh,new_ufb->ufbfh);
		}
		else
		{
			if(SaveDescriptor(new_ufb))
			{
				sockfd = Dup2Socket(old_ufb->ufbfh,-1);
			}
			else
			{
				sockfd = -1;
				errno = ENOMEM;
			}
		}

		if(sockfd != -1)
		{
			SET_FLAG(new_ufb->ufbflg,UFB_IS_SOCKET);
			new_ufb->ufbfh = sockfd;

			result = new_fd;
		}
	}
	else
	{
		errno = ENOTSOCK;
	}

	RETURN(result);
	return(result);
}

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

int
amiga_chown(char *name,uid_t uid,gid_t gid)
{
	struct MangleInfo mi;
	int result = ERROR;

	chkabort();

	ASSERT(name != NULL);

	ENTER();
	SHOWSTRING(name);
	SHOWVALUE(uid);
	SHOWVALUE(gid);

	if(MangleName(&name,&mi) == OK)
	{
		/* AmigaDOS 3.0 and up have a SetOwner() call. */
		if(DOSBase->lib_Version >= 39)
		{
			if(SetOwner(name,(((LONG)uid) << 16) | gid))
				result = OK;
			else
				MapIoErrToErrno();
		}
		else
		{
			struct DevProc * dvp;

			/* For 2.04 and 2.1 we'll have to do this
			 * manually...
			 */
			dvp = GetDeviceProc(name,NULL);
			if(dvp != NULL)
			{
				char __aligned newName[MAX_BSTR_LEN];

				newName[0] = strlen(name);
				strncpy(&newName[1],name,newName[0]);

				/* This is almost identical in operation to ACTION_SET_PROTECTION. */
				if(DoPkt(dvp->dvp_Port,ACTION_SET_OWNER,dvp->dvp_Lock,MKBADDR(newName),(((LONG)uid) << 16) | gid,0,0))
					result = OK;
				else
					MapIoErrToErrno();

				FreeDeviceProc(dvp);
			}
			else
			{
				MapIoErrToErrno();
			}
		}

		UnmangleName(&name,&mi);
	}

	RETURN(result);
	return(result);
}

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

int
amiga_setegid(gid_t g)
{
	int result;

	chkabort();

	ENTER();
	SHOWVALUE(g);

	result = setregid(-1,g);

	RETURN(result);
	return(result);
}

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

int
amiga_seteuid(uid_t u)
{
	int result;

	chkabort();

	ENTER();
	SHOWVALUE(u);
	result = setreuid(-1,u);

	RETURN(result);
	return(result);
}

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

int
amiga_gettimeofday(struct timeval *tv)
{
	chkabort();

	ENTER();
	SHOWVALUE(tv);

	if(tv != NULL)
	{
		GetSysTime((APTR)tv);
		tv->tv_secs += UNIX_TIME_OFFSET + 60*MinutesWest;	/* translate from local time to UTC */

		SHOWVALUE(tv->tv_secs);
		SHOWVALUE(tv->tv_micro);
	}

	RETURN(0);
	return(0);
}

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

int
amiga_utime(char *name,struct utimbuf *time)
{
	struct MangleInfo mi;
	struct DateStamp ds;
	int result = ERROR;

	chkabort();

	ASSERT(name != NULL);

	ENTER();
	SHOWSTRING(name);
	SHOWVALUE(time);

	/* Use the current time? */
	if(time == NULL)
	{
		DateStamp(&ds);
	}
	else
	{
		ULONG seconds;

		/* Convert the time given. */
		if(time->modtime < (UNIX_TIME_OFFSET + 60*MinutesWest))
			seconds = 0;
		else
			seconds = time->modtime - (UNIX_TIME_OFFSET + 60*MinutesWest);	/* translate from UTC to local time */

		ds.ds_Days		= (seconds / (24*60*60));
		ds.ds_Minute	= (seconds % (24*60*60)) / 60;
		ds.ds_Tick		= (seconds               % 60) * TICKS_PER_SECOND;
	}

	if(MangleName(&name,&mi) == OK)
	{
		ForbidDOS();

		if(SetFileDate((STRPTR)name,&ds))
		{
			result = OK;
		}
		else
		{
			LONG error = IoErr();

			/* Note sure about this one; is it really that important
			 * that the file date change succeeds? This is a definite
			 * FIXME.
			 */
			if(error == ERROR_OBJECT_IN_USE)
			{
				result = OK;
			}
			else
			{
				SetIoErr(error);

				MapIoErrToErrno();
			}
		}

		PermitDOS();

		UnmangleName(&name,&mi);
	}

	RETURN(result);
	return(result);
}

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

VOID
amiga_sleep(unsigned int seconds)
{
	chkabort();

	ENTER();
	SHOWVALUE(seconds);

	if(seconds > 0)
	{
		ULONG timerSignal = (1UL << TimerPort->mp_SigBit);
		ULONG signalsReceived;
		ULONG signalsToWaitFor;

		TimerRequest->tr_node.io_Command	= TR_ADDREQUEST;
		TimerRequest->tr_time.tv_secs		= seconds;
		TimerRequest->tr_time.tv_micro		= 0;

		SetSignal(0,timerSignal);
		SendIO((struct IORequest *)TimerRequest);

		signalsToWaitFor = timerSignal;
		if(AllowBreak)
			signalsToWaitFor |= SIGBREAKF_CTRL_C;

		signalsReceived = Wait(signalsToWaitFor);

		/* Did we get a break signal while we were sleeping? */
		if(FLAG_IS_SET(signalsReceived,SIGBREAKF_CTRL_C))
		{
			if(CheckIO((struct IORequest *)TimerRequest) == BUSY)
				AbortIO((struct IORequest *)TimerRequest);

			WaitIO((struct IORequest *)TimerRequest);

			/* And pull the brakes... */
			raise(SIGINT);
		}

		/* Proper termination. */
		if(FLAG_IS_SET(signalsReceived,timerSignal))
		{
			WaitIO((struct IORequest *)TimerRequest);
		}
	}

	LEAVE();
}

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

char *
amiga_crypt(char *key,char *salt)
{
	char *result;

	chkabort();

	ASSERT(key != NULL && salt != NULL);

	ENTER();
	SHOWSTRING(key);
	SHOWVALUE(salt);
	result = crypt(key,salt);

	RETURN(result);
	return(result);
}

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

char *
amiga_getpass(char *prompt)
{
	char *result;

	chkabort();

	ENTER();
	SHOWSTRING(prompt);
	result = getpass(prompt);

	RETURN(result);
	return(result);
}

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

int
amiga_setgid(gid_t id)
{
	int result;

	chkabort();

	ENTER();
	SHOWVALUE(id);
	result = setgid(id);

	RETURN(result);
	return(result);
}

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

int
amiga_setgroups(int ngroups,gid_t *groups)
{
	int result;

	chkabort();

	ASSERT(groups != NULL);

	ENTER();
	SHOWVALUE(ngroups);
	SHOWVALUE(groups);

	result = setgroups(ngroups,groups);

	RETURN(result);
	return(result);
}

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

gid_t
amiga_getgid(VOID)
{
	int result;

	chkabort();

	ENTER();
	result = getgid();

	RETURN(result);
	return(result);
}

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

struct group *
amiga_getgrgid(gid_t gid)
{
	struct group *result;

	chkabort();

	ENTER();
	SHOWVALUE(gid);

	result = getgrgid(gid);

	RETURN(result);
	return(result);
}

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

struct group *
amiga_getgrnam(char * name)
{
	struct group *result;

	chkabort();

	ASSERT(name != NULL);

	ENTER();
	SHOWSTRING(name);

	result = getgrnam(name);

	RETURN(result);
	return(result);
}

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

int
amiga_getgroups(int ngroups, gid_t *groups)
{
	int result;

	chkabort();

	ASSERT(groups != NULL);

	ENTER();
	SHOWVALUE(ngroups);
	SHOWVALUE(groups);

	result = getgroups(ngroups,groups);

	RETURN(result);
	return(result);
}

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

struct hostent *
amiga_gethostbyaddr(char *addr, int len, int type)
{
	struct hostent *result;

	chkabort();

	ASSERT(addr != NULL);

	ENTER();
	SHOWVALUE(addr);
	SHOWVALUE(len);
	SHOWVALUE(type);

	result = gethostbyaddr(addr,len,type);

	RETURN(result);
	return(result);
}

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

struct hostent *
amiga_gethostbyname(char *name)
{
	struct hostent *result;

	chkabort();

	ASSERT(name != NULL);

	ENTER();
	SHOWSTRING(name);

	result = gethostbyname(name);

	RETURN(result);
	return(result);
}

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

struct netent *
amiga_getnetbyname(char *name)
{
	struct netent *result;

	chkabort();

	ASSERT(name != NULL);

	ENTER();
	SHOWSTRING(name);

	result = getnetbyname(name);

	RETURN(result);
	return(result);
}

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

int
amiga_gethostname(char *hostname,int size)
{
	int result;

	chkabort();

	ASSERT(hostname != NULL);

	ENTER();
	SHOWVALUE(hostname);
	SHOWVALUE(size);

	result = gethostname(hostname,size);

	if(result == 0)
		SHOWSTRING(hostname);

	RETURN(result);
	return(result);
}

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

struct passwd *
amiga_getpwnam(char *name)
{
	struct passwd *result;

	chkabort();

	ASSERT(name != NULL);

	ENTER();
	SHOWSTRING(name);

	result = getpwnam(name);

	RETURN(result);
	return(result);
}

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

struct passwd *
amiga_getpwuid(uid_t uid)
{
	struct passwd *result;

	chkabort();

	ENTER();
	SHOWVALUE(uid);

	result = getpwuid(uid);

	RETURN(result);
	return(result);
}

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

uid_t
amiga_getuid(VOID)
{
	uid_t result;

	chkabort();

	ENTER();

	result = getuid();

	RETURN(result);
	return(result);
}

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

gid_t
amiga_getegid(VOID)
{
	gid_t result;

	chkabort();

	ENTER();
	result = getegid();

	RETURN(result);
	return(result);
}

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

uid_t
amiga_geteuid(VOID)
{
	uid_t result;

	chkabort();

	ENTER();

	result = geteuid();

	RETURN(result);
	return(result);
}

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

int
amiga_initgroups(char *name, gid_t basegroup)
{
	int result;

	chkabort();

	ASSERT(name != NULL);

	ENTER();
	SHOWSTRING(name);
	SHOWVALUE(basegroup);

	result = initgroups(name,basegroup);

	RETURN(result);
	return(result);
}

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

int
amiga_setuid(uid_t id)
{
	int result;

	chkabort();

	ENTER();
	SHOWVALUE(id);

	result = setuid(id);

	RETURN(result);
	return(result);
}

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

int
amiga_umask(int mask)
{
	int result;

	chkabort();

	ENTER();
	SHOWVALUE(mask);

	result = umask(mask);

	RETURN(result);
	return(result);
}

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

unsigned long
amiga_inet_addr(char *addr)
{
	unsigned long result;

	chkabort();

	ASSERT(addr != NULL);

	ENTER();
	SHOWSTRING(addr);

	result = inet_addr(addr);

	RETURN(result);
	return(result);
}

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

char *
amiga_inet_ntoa(struct in_addr in)
{
	char *result;

	chkabort();

	ENTER();
	SHOWVALUE(in.s_addr);

	result = Inet_NtoA(in.s_addr);
	SHOWSTRING(result);

	RETURN(result);
	return(result);
}

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

int	opterr = 1;
int	optind = 1;
int	optopt;
char * optarg;

int
amiga_getopt(int argc, char * argv[], char *opts)
{
	STATIC int sp = 1;
	int c;
	char *cp;

	ASSERT(argc > 0 && argv != NULL && opts != NULL);

	if(sp == 1)
	{
		if(optind >= argc || argv[optind][0] != '-' || argv[optind][1] == '\0')
		{
			return(EOF);
		}
		else if(strcmp(argv[optind], "--") == NULL)
		{
			optind++;
			return(EOF);
		}
	}

	optopt = c = argv[optind][sp];
	if(c == ':' || (cp=index(opts, c)) == NULL)
	{
		if(opterr)
			fprintf(stderr, "%s%s%c\n", argv[0], ": illegal option -- ", c);

		if(argv[optind][++sp] == '\0')
		{
			optind++;
			sp = 1;
		}

		return('?');
	}

	if(*++cp == ':')
	{
		if(argv[optind][sp+1] != '\0')
		{
			optarg = &argv[optind++][sp+1];
		}
		else if(++optind >= argc)
		{
			if(opterr)
				fprintf(stderr, "%s%s%c\n", argv[0], ": option requires an argument -- ", c);

			sp = 1;
			return('?');
		}
		else
		{
			optarg = argv[optind++];
		}

		sp = 1;
	}
	else
	{
		if(argv[optind][++sp] == '\0')
		{
			sp = 1;
			optind++;
		}

		optarg = NULL;
	}

	return(c);
}

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

/* This comes from util/lib.c */
STATIC BOOL
do_match(STRPTR str, STRPTR regexp)
{
	STRPTR p;

	for(p = regexp; (*p) != '\0' && (*str) != '\0' ; NULL)
	{
		switch(*p)
		{
			case '?':

				str++;
				p++;

				break;

			/* Look for a character matching the one after the '*' */
			case '*':

				p++;

				if((*p) == '\0')
					return(TRUE); /* Automatic match */

				while((*str) != '\0')
				{
					while((*str) != '\0' && ToUpper(*p) != ToUpper(*str))
						str++;

					if(do_match(str,p))
						return(TRUE);

					if((*str) == '\0')
						return(FALSE);
					else
						str++;
				}

				return(FALSE);

			default:

				if(ToUpper(*str++) != ToUpper(*p++))
					return(FALSE);

				break;
		}
	}

	if((*p) == '\0' && (*str) == '\0')
		return(TRUE);

	if((*p) == '\0' && str[0] == '.' && str[1] == '\0')
		return(TRUE);

	if((*str) == '\0' && (*p) == '?')
	{
		while((*p) == '?')
			p++;

		return((BOOL)((*p) == '\0'));
	}

	if((*str) == '\0' && ((*p) == '*' && p[1] == '\0'))
		return(TRUE);

	return(FALSE);
}

STATIC VOID
nstrcpy_blank(const size_t maxSize,char *to,const char *from)
{
	size_t len = 0;

	while((*from) != '\0' && (*from) != ' ' && len < maxSize-1)
	{
		(*to++) = (*from++);
		len++;
	}

	(*to) = '\0';
}

STATIC VOID
fill_volume_list(struct List * list)
{
	struct DosList * dol;

	dol = NextDosEntry(LockDosList(LDF_VOLUMES|LDF_READ),
	                               LDF_VOLUMES|LDF_READ);
	while(dol != NULL)
	{
		/* Does the volume refer to a medium that is right
		 * now present in the drive?
		 */
		if(dol->dol_Task != NULL)
		{
			struct InfoData __aligned id;

			/* Is there a disk present? */
			if(DoPkt(dol->dol_Task,ACTION_DISK_INFO,MKBADDR(&id),	0,0,0,0))
			{
				STRPTR name = BADDR(dol->dol_Name);
				struct Node * node;

				node = malloc(sizeof(*node) + ((int)name[0])+1);
				if(node != NULL)
				{
					/* Copy the name of the volume. */
					node->ln_Name = (char *)(node + 1);
					strncpy(node->ln_Name,&name[1],name[0]);
					node->ln_Name[ name[0] ] = '\0';

					AddTail(list,node);
				}
			}
		}

		dol = NextDosEntry(dol,LDF_VOLUMES|LDF_READ);
	}

	UnLockDosList(LDF_VOLUMES|LDF_READ);
}

STATIC int
deep_scan_drawer(char * drawerName,char * whichPattern,FILE * out)
{
	char * originalDrawerName;
	struct MangleInfo mi;
	int result = ERROR;

	originalDrawerName = drawerName;
	if(MangleName(&drawerName,&mi) == OK)
	{
		BPTR dirLock;

		dirLock = Lock(drawerName,SHARED_LOCK);
		if(dirLock != ZERO)
		{
			struct FileInfoBlock __aligned fib;

			if(Examine(dirLock,&fib))
			{
				struct AnchorPath * ap;

				ap = malloc(sizeof(*ap) + MAX_FILENAME_LEN);
				if(ap != NULL)
				{
					BOOL stopped = FALSE;
					BPTR oldDir;
					LONG error;

					oldDir = CurrentDir(dirLock);

					memset(ap,0,sizeof(*ap) + MAX_FILENAME_LEN);
					ap->ap_Strlen		= MAX_FILENAME_LEN;
					ap->ap_BreakBits	= SIGBREAKF_CTRL_C;

					if((error = MatchFirst("",ap)) == OK)
					{
						BOOL check;

						do
						{
							D(("checking |%s|",ap->ap_Buf));

							check = TRUE;

							if(FIB_IS_FILE(&ap->ap_Info))
							{
								SHOWMSG("this is a file");
							}
							else
							{
								SHOWMSG("this is a drawer");

								if(FLAG_IS_CLEAR(ap->ap_Flags,APF_DIDDIR))
								{
									SET_FLAG(ap->ap_Flags,APF_DODIR);
								}
								else
								{
									CLEAR_FLAG(ap->ap_Flags,APF_DIDDIR);
									check = FALSE;
								}
							}

							if(check)
							{
								SHOWSTRING(ap->ap_Info.fib_FileName);

								if(do_match(ap->ap_Info.fib_FileName,whichPattern))
								{
									D(("Output |%s/%s|",originalDrawerName,ap->ap_Buf));
									if(fprintf(out,"%s/%s\n",originalDrawerName,ap->ap_Buf) < 0)
									{
										stopped = TRUE;
										break;
									}
								}
								else
								{
									SHOWMSG("but its name does not match");
								}
							}
						}
						while((error = MatchNext(ap)) == OK);
					}

					MatchEnd(ap);

					CurrentDir(oldDir);

					if(NOT stopped)
					{
						if(error == ERROR_NO_MORE_ENTRIES)
						{
							result = OK;
						}
						else if(error == ERROR_BREAK)
						{
							/* Don't do anything; we are going
							 * to delete the file anyway.
							 */
						}
						else
						{
							SetIoErr(error);
							MapIoErrToErrno();
						}
					}

					free(ap);
				}
				else
				{
					errno = ENOMEM;
				}
			}
			else
			{
				MapIoErrToErrno();
			}

			UnLock(dirLock);
		}
		else
		{
			MapIoErrToErrno();
		}

		UnmangleName(&drawerName,&mi);
	}

	return(result);
}

int
amiga_system(char *cmd)
{
	char redirectionFileBuffer[MAX_FILENAME_LEN];
	char *redirectionFile = redirectionFileBuffer;
	struct MangleInfo mi;
	int result = ERROR;
	char *stop;
	char *options;
	char *cmdName;
	int cmdLen,i;
	int quoteCount;

	chkabort();

	ASSERT(cmd != NULL);

	ENTER();
	SHOWSTRING(cmd);

	/* Handle two special cases for the smb client program,
	 * which for reasons unknown to me resorts to calling
	 * the Unix "ls" and "find" programs to collect lists
	 * of files.
	 *
	 * The commands to watch for take the following forms:
	 *
	 *    /bin/ls file_to_list > output_filename
	 *    find directory_to_list -name "pattern_to_list" -print > output_filename
	 */
	stop = cmd;
	while((*stop) != ' ' && (*stop) != '\0')
		stop++;

	/* This will point behind the command name,
	 * to the beginning of the list of options,
	 * if any.
	 */
	options = stop;
	while((*options) == ' ')
		options++;

	/* Now go and isolate the command name. */
	cmdName = cmd;
	cmdLen = stop-cmd;
	for(i = cmdLen - 1 ; i >= 0 ; i--)
	{
		if(cmd[i] == '/' || cmd[i] == ':')
		{
			cmdName = &cmd[i+1];

			stop = cmdName;
			while((*stop) != ' ' && (*stop) != '\0')
				stop++;

			cmdLen = stop-cmdName;

			break;
		}
	}

	/* We have isolated the command name, now
	 * find the redirection file name.
	 */
	quoteCount = 0;
	strcpy(redirectionFile,"");
	for(i = 0 ; i < strlen(cmd) ; i++)
	{
		if(cmd[i] == '\"')
			quoteCount = (1-quoteCount);

		if(cmd[i] == '>' && quoteCount == 0)
		{
			char * from;

			from = &cmd[i+1];
			while((*from) == ' ')
				from++;

			/* Note that we rely upon the
			 * redirection file name not to contain
			 * quote or escape characters.
			 */
			nstrcpy_blank(sizeof(redirectionFileBuffer),redirectionFile,from);
			break;
		}
	}

	/* Now check which command we got. */
	if(cmdLen == 2 && Strnicmp(cmdName,"ls",2) == SAME)
	{
		char localDirName[MAX_FILENAME_LEN];
		char * dirName;

		dirName = ".";

		if(TranslateRelativePath(&dirName,localDirName,sizeof(localDirName)) == OK)
		{
			char whichPatternBuffer[MAX_FILENAME_LEN];
			char *whichPattern = whichPatternBuffer;
			BOOL oldAllowBreak = AllowBreak;

			/* We don't want to be interrupted. */
			AllowBreak = FALSE;

			/* Now find the pattern to use. */
			nstrcpy_blank(sizeof(whichPatternBuffer),whichPattern,options);

			SHOWMSG("doing `ls'");
			SHOWSTRING(whichPattern);
			SHOWSTRING(redirectionFile);

			/* What happens next is that the contents of the
			 * current directory are scanned. All names
			 * that match the given pattern are stored in the
			 * redirection file.
			 */

			if(MangleName(&redirectionFile,&mi) == OK)
			{
				FILE * out;

				ForbidDOS();

				out = fopen(redirectionFile,"wb");
				if(out != NULL)
				{
					SHOWSTRING(localDirName);

					if(strcmp(localDirName,"/") == SAME)
					{
						struct List list;
						struct Node * node;

						SHOWMSG("listing volumes");

						NewList(&list);

						fill_volume_list(&list);

						while((node = RemHead(&list)) != NULL)
						{
							D(("checking '%s'",node->ln_Name));

							if(do_match(node->ln_Name,whichPattern))
							{
								SHOWMSG(">>> matches");

								if(fprintf(out,"%s\n",node->ln_Name) < 0)
									break;
							}
							else
							{
								SHOWMSG(">>> does not match");
							}

							free(node);
						}

						while((node = RemHead(&list)) != NULL)
							free(node);
					}
					else
					{
						BPTR dirLock;

						SHOWMSG("listing drawers");

						dirLock = Lock("",SHARED_LOCK);
						if(dirLock != ZERO)
						{
							struct FileInfoBlock __aligned fib;

							if(Examine(dirLock,&fib))
							{
								BOOL stopped = FALSE;

								while(ExNext(dirLock,&fib))
								{
									D(("checking '%s'",fib.fib_FileName));

									if(do_match(fib.fib_FileName,whichPattern))
									{
										SHOWMSG(">>> matches");

										if(fprintf(out,"%s\n",fib.fib_FileName) < 0)
										{
											stopped = TRUE;
											break;
										}
									}
									else
									{
										SHOWMSG(">>> does not match");
									}
								}

								if(NOT stopped)
								{
									LONG error = IoErr();

									if(error == ERROR_NO_MORE_ENTRIES)
									{
										result = OK;
									}
									else
									{
										SetIoErr(error);
										MapIoErrToErrno();
									}
								}
							}
							else
							{
								MapIoErrToErrno();
							}

							UnLock(dirLock);
						}
						else
						{
							MapIoErrToErrno();
						}
					}

					fclose(out);

					if(result != OK)
						DeleteFile(redirectionFile);
				}

				PermitDOS();

				UnmangleName(&cmd,&mi);
			}

			AllowBreak = oldAllowBreak;
		}
	}
	else if (cmdLen == 4 && Strnicmp(cmdName,"find",4) == SAME)
	{
		char whichDrawerBuffer[MAX_FILENAME_LEN];
		char * whichDrawer = whichDrawerBuffer;
		char whichPattern[MAX_FILENAME_LEN];
		const int _nameLen = strlen("-name");
		BOOL oldAllowBreak = AllowBreak;
		struct List list;
		struct Node * node;

		NewList(&list);

		/* We don't want to be interrupted. */
		AllowBreak = FALSE;

		/* Now find the drawer to examine. */
		nstrcpy_blank(sizeof(whichDrawerBuffer),whichDrawer,options);

		/* Find the pattern to search for. */
		strcpy(whichPattern,"");
		for(i = 0 ; i < strlen(options) ; i++)
		{
			if(Strnicmp(&options[i],"-name",_nameLen) == SAME)
			{
				const int maxLen = sizeof(whichPattern)-1;
				int whichPatternLen;
				int escapeCount;
				int quoteCount;
				char * from;
				char * to;

				/* `from' should now point straight at the
				 * search pattern, which is probably enclosed
				 * in quote characters.
				 */
				from = &options[i+_nameLen];
				while((*from) == ' ')
					from++;

				to = whichPattern;
				whichPatternLen = 0;
				quoteCount = 0;
				escapeCount = 0;
				while((*from) != '\0' && whichPatternLen < maxLen)
				{
					if(escapeCount != 0)
					{
						(*to++) = (*from++);
						whichPatternLen++;
						escapeCount = 0;
					}
					else if((*from) == '\\')
					{
						escapeCount = (1-escapeCount);
						from++;
					}
					else if ((*from) == '\"')
					{
						quoteCount = (1-quoteCount);
						from++;
					}
					else
					{
						if((*from) == ' ' && quoteCount == 0)
						{
							break;
						}
						else
						{
							(*to++) = (*from++);
							whichPatternLen++;
						}
					}
				}

				(*to) = '\0';
			}
		}

		SHOWMSG("doing `find'");
		SHOWSTRING(whichDrawer);
		SHOWSTRING(whichPattern);
		SHOWSTRING(redirectionFile);

		/* Things are a little bit more complicated here. The
		 * "find" program scans recursively through a directory
		 * tree. We emulate this behaviour by having the MatchFirst/MatchNext
		 * routines iterate through the directory tree. The
		 * name of every file found is matched against the given
		 * pattern and, if necessary, written to the redirection
		 * file. Note that matching is performed only on the
		 * file name, not on the entire path.
		 */

		if(MangleName(&redirectionFile,&mi) == OK)
		{
			FILE * out;

			ForbidDOS();

			out = fopen(redirectionFile,"wb");
			if(out != NULL)
			{
				char localDirName[MAX_FILENAME_LEN];
				char * dirName;

				dirName = whichDrawer;

				if(TranslateRelativePath(&dirName,localDirName,sizeof(localDirName)) == OK)
				{
					D(("listing path '%s'",dirName));

					if(strcmp(dirName,"/") == SAME)
					{
						SHOWMSG("checking all 'drawers' in the fake root");

						fill_volume_list(&list);

						while((node = RemHead(&list)) != NULL)
						{
							strcpy(localDirName,"/");
							strcat(localDirName,node->ln_Name);
							free(node);

							D(("checking '%s'",localDirName));

							result = deep_scan_drawer(localDirName,whichPattern,out);
							if(result != OK)
								break;
						}

						while((node = RemHead(&list)) != NULL)
							free(node);
					}
					else
					{
						SHOWMSG("checking only a local drawer");

						result = deep_scan_drawer(dirName,whichPattern,out);
					}
				}

				fclose(out);

				if(result != OK)
					DeleteFile(redirectionFile);
			}

			PermitDOS();

			UnmangleName(&cmd,&mi);
		}

		AllowBreak = oldAllowBreak;
	}
	else
	{
		if(MangleName(&cmd,&mi) == OK)
		{
			ForbidDOS();
			result = system(cmd);
			PermitDOS();

			UnmangleName(&cmd,&mi);
		}
	}

	RETURN(result);
	return(result);
}

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

int
amiga_fork(VOID)	/* dummy */
{
	int result;

	chkabort();

	ENTER();

	result = ERROR;
	errno = ENOSYS;

	RETURN(result);
	return(result);
}

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

STATIC BOOL
SetFileSocket(FILE *stream,int sockfd)
{
	struct UFB * ufb;
	BOOL success = FALSE;

	ASSERT(stream != NULL);

	ENTER();

	/* Check which buffer the file
	 * is attached to.
	 */
	ufb = chkufb(fileno(stream));
	if(ufb != NULL)
	{
		SHOWVALUE(ufb);

		/* Save the file handle. */
		if(SaveDescriptor(ufb))
		{
			/* And put the socket in its place. */
			ufb->ufbfh = sockfd;
			SET_FLAG(ufb->ufbflg,UFB_IS_SOCKET);

			success = TRUE;
		}
	}
	else
	{
		SHOWMSG("no ufb");
	}

	RETURN(success);
	return(success);
}

STATIC VOID
DaemonInit(VOID)
{
	struct DaemonMessage * dm;
	int sock;

	ENTER();

	/* This routine is called when the program starts up.
	 * If it was launched by the INet super server, this
	 * program will run as a daemon. We pick up its
	 * socket input stream and attach it to our stdio
	 * streams.
	 */
	dm = (struct DaemonMessage *)((struct Process *)FindTask(NULL))->pr_ExitData;
	if(dm != NULL)
	{
		sock = ObtainSocket(dm->dm_ID,dm->dm_Family,dm->dm_Type,0);
		if(sock != -1)
		{
			BOOL success = FALSE;
			int sock_stdin;
			int sock_stdout;
			int sock_stderr;

			sock_stdin	= Dup2Socket(sock,-1);
			sock_stdout	= Dup2Socket(sock,-1);
			sock_stderr	= Dup2Socket(sock,-1);

			if(sock_stdin != -1 && sock_stdout != -1 && sock_stderr != -1)
			{
				SHOWMSG("got all sockets");

				D(("stdin  = sock %ld",sock_stdin));
				D(("stdout = sock %ld",sock_stdout));
				D(("stderr = sock %ld",sock_stderr));

				if(SetFileSocket(stdin,sock_stdin) &&
				   SetFileSocket(stdout,sock_stdout) &&
				   SetFileSocket(stderr,sock_stderr))
				{
					SHOWMSG("and all streams are initialized");

					success = TRUE;
				}
				else
				{
					SHOWMSG("failed to initialize streams");
				}
			}
			else
			{
				SHOWMSG("didn't get the sockets");
			}

			if(NO success)
			{
				if(sock_stdin != -1)
					CloseSocket(sock_stdin);

				if(sock_stdout != -1)
					CloseSocket(sock_stdout);

				if(sock_stderr != -1)
					CloseSocket(sock_stderr);
			}
		}
		else
		{
			SHOWMSG("no socket");
		}
	}
	else
	{
		SHOWMSG("No daemon message");
	}

	LEAVE();
}

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

VOID
__tzset(VOID)
{
	STATIC char TimeZoneName[20] = "";

	/* This routine sets up the internal
	 * time zone variable according to
	 * the local settings.
	 */
	if(STRING_IS_EMPTY(TimeZoneName))
	{
		int hoursWest = MinutesWest / 60;

		if(hoursWest >= 0)
			sprintf(TimeZoneName,"GMT+%02d", hoursWest);
		else
			sprintf(TimeZoneName,"GMT-%02d",-hoursWest);
	}

	_TZ = TimeZoneName;
}

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

time_t
time(time_t *timeptr)
{
	time_t currentTime;
	struct timeval tv;

	chkabort();

	GetSysTime((APTR)&tv);

	currentTime = UNIX_TIME_OFFSET + tv.tv_secs + 60*MinutesWest;	/* translate from local time to UTC */

	if(timeptr != NULL)
		(*timeptr) = currentTime;

	return(currentTime);
}

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

STATIC struct tm *
ConvertTime(ULONG seconds)
{
	STATIC struct tm tm;
	struct ClockData clockData;
	ULONG delta;

	chkabort();

	Amiga2Date(seconds,&clockData);

	tm.tm_sec	= clockData.sec;
	tm.tm_min	= clockData.min;
	tm.tm_hour	= clockData.hour;
	tm.tm_mday	= clockData.mday;
	tm.tm_mon	= clockData.month - 1;
	tm.tm_year	= clockData.year - 1900;
	tm.tm_wday	= clockData.wday;
	tm.tm_isdst	= 0;

	clockData.mday	= 1;
	clockData.month	= 1;

	delta = Date2Amiga(&clockData);

	tm.tm_yday = (seconds - delta) / (24*60*60);

	return(&tm);
}

struct tm *
gmtime(const time_t *t)
{
	struct tm * result;
	ULONG seconds;

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

	result = ConvertTime(seconds);

	return(result);
}

struct tm *
localtime(const time_t *t)
{
	struct tm * result;
	ULONG seconds;

	if((*t) < (UNIX_TIME_OFFSET + 60*MinutesWest))
		seconds = 0;
	else
		seconds = (*t) - (UNIX_TIME_OFFSET + 60*MinutesWest);	/* translate from UTC to local time */

	result = ConvertTime(seconds);

	return(result);
}

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

int
amiga_strcasecmp(char *a,char *b)
{
	int result;

	ASSERT(a != NULL && b != NULL);

	result = Stricmp((STRPTR)a,b);

	return(result);
}

int
amiga_strncasecmp(char *a,char *b,int len)
{
	int result;

	ASSERT(a != NULL && b != NULL);

	result = Strnicmp(a,b,len);

	return(result);
}

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

VOID
(*amiga_signal(int which,VOID (* action)(int)))(int)
{
	VOID (* result)(int);

	chkabort();

	ENTER();

	if(SIGABRT <= which && which <= SIGTERM)
		result = signal(which,action);
	else
		result = SIG_DFL;

	RETURN(result);
	return(result);
}

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

VOID
amiga_alarm(int seconds)	/* dummy */
{
	chkabort();

	ENTER();

	LEAVE();
}

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

int
amiga_waitpid(pid_t pid,int *status,int options)	/* dummy */
{
	int result;

	chkabort();

	ENTER();

	result = ERROR;
	errno = ENOSYS;

	RETURN(result);
	return(result);
}

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

long
amiga_setsid(VOID)
{
	long result;

	chkabort();

	ENTER();

	result = (long)setsid();

	RETURN(result);
	return(result);
}

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

int
amiga_setreuid(uid_t real, uid_t eff)
{
	int result;

	chkabort();

	ENTER();

	result = setreuid(real,eff);

	RETURN(result);
	return(result);
}

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

int
amiga_setregid(gid_t real, gid_t eff)
{
	int result;

	chkabort();

	ENTER();

	result = setregid(real,eff);

	RETURN(result);
	return(result);
}

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

int
amiga_getsockname(int sockfd,struct sockaddr *name,int *namelen)
{
	struct UFB * ufb;
	int result = ERROR;

	chkabort();

	ASSERT(name != NULL && namelen != NULL);

	ENTER();

	ufb = chkufb(sockfd);
	if(ufb != NULL && FLAG_IS_SET(ufb->ufbflg,UFB_IS_SOCKET))
		result = getsockname(ufb->ufbfh,name,(LONG *)namelen);
	else
		errno = ENOTSOCK;

	RETURN(result);
	return(result);
}

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

int
amiga_statfs(char *name,struct statfs *f)
{
	char localName[MAX_FILENAME_LEN];
	int result = ERROR;

	chkabort();

	ASSERT(name != NULL && f != NULL);

	ENTER();

	SHOWSTRING(name);
	SHOWVALUE(f);

	if(TranslateRelativePath(&name,localName,sizeof(localName)) == OK)
	{
		/* Make up something for the virtual root directory. */
		if(strcmp(name,"/") == SAME)
		{
			memset(f,0,sizeof(*f));

			f->f_fsize = 512;

			/* Try to reuse the data from our last scan. */
			if(RootBlocks > 0 && RootBlocksUsed > 0)
			{
				f->f_blocks	= RootBlocks;
				f->f_bfree	= RootBlocks - RootBlocksUsed;
			}
			else
			{
				f->f_blocks	= 0x7FFFFFFF/f->f_fsize;
				f->f_bfree	= f->f_blocks - 1;
			}

			f->f_bsize	= f->f_fsize;
			f->f_bavail	= f->f_bfree;

			SHOWVALUE(f->f_fsize);
			SHOWVALUE(f->f_bsize);
			SHOWVALUE(f->f_blocks);
			SHOWVALUE(f->f_bfree);
			SHOWVALUE(f->f_bavail);

			result = OK;
		}
		else
		{
			struct MangleInfo mi;
			BPTR fileLock;

			if(MangleName(&name,&mi) == OK)
			{
				ForbidDOS();

				fileLock = Lock(name,SHARED_LOCK);
				if(fileLock != ZERO)
				{
					struct InfoData __aligned id;

					if(Info(fileLock,&id))
					{
						memset(f,0,sizeof(*f));

						/* Make sure that these never drop to zero. */
						if(id.id_NumBlocks == 0)
							id.id_NumBlocks = 1;

						if(id.id_BytesPerBlock == 0)
							id.id_BytesPerBlock = 512;

						f->f_fsize	= id.id_BytesPerBlock;						/* fundamental file system block size */
						f->f_blocks	= id.id_NumBlocks;							/* total data blocks in file system */
						f->f_bfree	= id.id_NumBlocks - id.id_NumBlocksUsed;	/* free blocks in fs */
						f->f_bsize	= f->f_fsize;								/* optimal transfer block size */
						f->f_bavail	= f->f_bfree;								/* free blocks avail to non-superuser */

						SHOWVALUE(f->f_fsize);
						SHOWVALUE(f->f_bsize);
						SHOWVALUE(f->f_blocks);
						SHOWVALUE(f->f_bfree);
						SHOWVALUE(f->f_bavail);

						result = OK;
					}
					else
					{
						MapIoErrToErrno();
					}

					UnLock(fileLock);
				}
				else
				{
					MapIoErrToErrno();
				}

				PermitDOS();

				UnmangleName(&name,&mi);
			}
		}
	}

	RETURN(result);
	return(result);
}

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

int
amiga_execl(char *path,char *arg,...)	/* dummy */
{
	int result;

	chkabort();

	ENTER();

	result = ERROR;
	errno = ENOSYS;

	RETURN(result);
	return(result);
}

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

char *
amiga_strerror(int error)
{
	struct TagItem tags[2];
	char *result;

	chkabort();

	ENTER();

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

	SocketBaseTagList(tags);

	result = (char *)tags[0].ti_Data;

	RETURN(result);
	return(result);
}

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

int
amiga_access(char *name,int modes)
{
	struct MangleInfo mi;
	int result = ERROR;

	chkabort();

	ASSERT(name != NULL);

	ENTER();

	if(MangleName(&name,&mi) == OK)
	{
		ForbidDOS();
		result = access(name,modes);
		PermitDOS();

		UnmangleName(&name,&mi);
	}

	RETURN(result);
	return(result);
}

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

STATIC VOID
MapIoErrToErrno(VOID)
{
	/* This routine maps AmigaDOS error codes to
	 * Unix error codes, as far as this is possible.
	 * This table contains AmigaDOS error codes
	 * the emulated routines won't generate. I have
	 * included them for the sake of completeness.
	 */
	struct { LONG IoErr; LONG errno; } MapTable[] =
	{
		ERROR_NO_FREE_STORE,			ENOMEM,
		ERROR_TASK_TABLE_FULL,			ENOMEM,
		ERROR_BAD_TEMPLATE,				EINVAL,
		ERROR_BAD_NUMBER,				EINVAL,
		ERROR_REQUIRED_ARG_MISSING,		EINVAL,
		ERROR_KEY_NEEDS_ARG,			EINVAL,
		ERROR_TOO_MANY_ARGS,			EINVAL,
		ERROR_UNMATCHED_QUOTES,			EINVAL,
		ERROR_LINE_TOO_LONG,			ENAMETOOLONG,
		ERROR_FILE_NOT_OBJECT,			ENOEXEC,
		ERROR_INVALID_RESIDENT_LIBRARY,	EIO,
		ERROR_NO_DEFAULT_DIR,			EIO,
		ERROR_OBJECT_IN_USE,			EBUSY,
		ERROR_OBJECT_EXISTS,			EBUSY,
		ERROR_DIR_NOT_FOUND,			ENOENT,
		ERROR_OBJECT_NOT_FOUND,			ENOENT,
		ERROR_BAD_STREAM_NAME,			EINVAL,
		ERROR_OBJECT_TOO_LARGE,			EFBIG,
		ERROR_ACTION_NOT_KNOWN,			ENOSYS,
		ERROR_INVALID_COMPONENT_NAME,	EINVAL,
		ERROR_INVALID_LOCK,				EBADF,
		ERROR_OBJECT_WRONG_TYPE,		EFTYPE,
		ERROR_DISK_NOT_VALIDATED,		EROFS,
		ERROR_DISK_WRITE_PROTECTED,		EROFS,
		ERROR_RENAME_ACROSS_DEVICES,	EXDEV,
		ERROR_DIRECTORY_NOT_EMPTY,		ENOTEMPTY,
		ERROR_TOO_MANY_LEVELS,			ENAMETOOLONG,
		ERROR_DEVICE_NOT_MOUNTED,		ENXIO,
		ERROR_SEEK_ERROR,				EIO,
		ERROR_COMMENT_TOO_BIG,			ENAMETOOLONG,
		ERROR_DISK_FULL,				ENOSPC,
		ERROR_DELETE_PROTECTED,			EACCES,
		ERROR_WRITE_PROTECTED,			EACCES,
		ERROR_READ_PROTECTED,			EACCES,
		ERROR_NOT_A_DOS_DISK,			EFTYPE,
		ERROR_NO_DISK,					EACCES,
		ERROR_NO_MORE_ENTRIES,			EIO,
		ERROR_IS_SOFT_LINK,				EFTYPE,
		ERROR_OBJECT_LINKED,			EIO,
		ERROR_BAD_HUNK,					ENOEXEC,
		ERROR_NOT_IMPLEMENTED,			ENOSYS,
		ERROR_RECORD_NOT_LOCKED,		EIO,
		ERROR_LOCK_COLLISION,			EACCES,
		ERROR_LOCK_TIMEOUT,				EIO,
		ERROR_UNLOCK_ERROR,				EIO,
		ERROR_BUFFER_OVERFLOW,			EIO,
		ERROR_BREAK,					EINTR,
		ERROR_NOT_EXECUTABLE,			ENOEXEC
	};

	LONG ioErr = IoErr();
	int i;

	/* If nothing else matches, we can always
	 * flag it as an I/O error.
	 */
	errno = EIO;

	for(i = 0 ; i < NUM_ENTRIES(MapTable) ; i++)
	{
		if(MapTable[i].IoErr == ioErr)
		{
			errno = MapTable[i].errno;
			break;
		}
	}
}

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

off_t
amiga_lseek(int fd,off_t offset,int mode)
{
	struct UFB * ufb;
	off_t result = ERROR;

	ENTER();
	SHOWVALUE(fd);
	SHOWVALUE(offset);
	SHOWVALUE(mode);

	ForbidDOS();

	/* Make sure that we operate on a file. */
	ufb = chkufb(fd);
	if(ufb != NULL && FLAG_IS_CLEAR(ufb->ufbflg,UFB_IS_SOCKET))
		result = lseek(fd,offset,mode);
	else
		errno = EBADF;

	PermitDOS();

	RETURN(result);
	return(result);
}

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

int
amiga_chroot(char *name)	/* dummy */
{
	int result = OK;

	ENTER();

	SHOWSTRING(name);

	RETURN(result);
	return(result);
}

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

STATIC int
MapFileNameAmigaToUnix(
	const char *	amiga,
	char *			unix,
	int				maxUnixLen)
{
	int len,destLen;
	int result = ERROR;

	/* This routine makes a Unix file
	 * name generated from an AmigaDOS
	 * file name. This involves a slight
	 * change of syntax...
	 */
	D(("amiga name |%s|",amiga));

	len = destLen = strlen(amiga);
	if(amiga[0] == '/')
	{
		destLen = 2 + len;
	}
	else
	{
		int i;

		for(i = 0 ; i < len ; i++)
		{
			if(amiga[i] == ':')
			{
				if(amiga[i+1] != '\0')
					destLen = 1 + len;

				break;
			}
		}
	}

	if(destLen < maxUnixLen)
	{
		if(amiga[0] == '/')
		{
			/* `/foo\0' -> `../foo\0' */
			memmove(&unix[2],amiga,len+1);
			strncpy(unix,"..",2);
		}
		else
		{
			BOOL done = FALSE;
			int i;

			for(i = 0 ; i < len ; i++)
			{
				if(amiga[i] == ':')
				{
					if(amiga[i+1] == '\0')
					{
						/* `foo:\0' -> `/foo\0' */
						memmove(&unix[1],amiga,i);
						unix[0] = '/';
						unix[i+1] = '\0';
					}
					else
					{
						/* `foo:bar\0' -> `/foo/bar\0' */
						memmove(&unix[1],amiga,len+1);
						unix[0] = '/';
						unix[i+1] = '/';
					}

					done = TRUE;
					break;
				}
			}

			if(NOT done && unix != amiga)
				strcpy(unix,amiga);
		}

		D(("unix name |%s|",unix));

		result = OK;
	}
	else
	{
		SHOWMSG("unix name is too long to fit");

		errno = ENAMETOOLONG;
	}

	return(result);
}

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

STATIC VOID
FlushSTDOUT(VOID)
{
	/* Flush the standard output streams so that
	 * any following output will be printed after
	 * any buffered stdio output.
	 */
	if(WBenchMsg == NULL)
	{
		struct UFB * ufb;

		/* Don't let anybody stop us. */
		signal(SIGINT,SIG_IGN);
		signal(SIGTERM,SIG_IGN);

		ufb = chkufb(fileno(stdout));
		if(ufb != NULL && FLAG_IS_CLEAR(ufb->ufbflg,UFB_IS_SOCKET))
			fflush(stdout);

		ufb = chkufb(fileno(stderr));
		if(ufb != NULL && FLAG_IS_CLEAR(ufb->ufbflg,UFB_IS_SOCKET))
			fflush(stderr);
	}
}

VOID
_CXOVF(VOID)
{
	/* This routine is called when a stack overflow occurs. */
	FlushSTDOUT();
	ReportProblem("Stack overflow");

	exit(RETURN_ERROR);
}

VOID __regargs
_CXBRK(VOID)
{
	extern STRPTR _ProgramName;

	FlushSTDOUT();

	/* This routine is called when the program is interrupted. */
	if(DOSBase->lib_Version >= 37)
	{
		PrintFault(ERROR_BREAK,_ProgramName);
	}
	else
	{
		const char *famousLastWords = ": *** Break";
		BPTR output = Output();

		Write(output,(APTR)famousLastWords,strlen(famousLastWords));
		Write(output,_ProgramName,strlen(_ProgramName));
		Write(output,"\n",1);
	}

	exit(RETURN_WARN);
}

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

STATIC STRPTR SambaSemaphoreName = "Amiga Samba";

struct SambaClientNode
{
	struct MinNode	scn_MinNode;
	struct Task *	scn_Task;
	pid_t			scn_PID;
};

struct SambaSemaphore
{
	struct SignalSemaphore	ss_Semaphore;
	struct List				ss_ClientList;
	pid_t					ss_PID;
	struct List				ss_FileLockList;
};

STATIC struct SambaSemaphore *	SambaSemaphore;
STATIC struct SambaClientNode *	ThisClient;

STATIC VOID
CleanupSambaSemaphore(VOID)
{
	if(ThisClient != NULL)
	{
		/* Unregister the program. */
		ObtainSemaphore((struct SignalSemaphore *)SambaSemaphore);
		Remove((struct Node *)ThisClient);
		ReleaseSemaphore((struct SignalSemaphore *)SambaSemaphore);

		FreeVec(ThisClient);
		ThisClient = NULL;
	}
}

STATIC BOOL
SetupSambaSemaphore(VOID)
{
	BOOL success = FALSE;

	/* We have to have the timer open to serialize the process IDs properly. */
	if(TimerBase != NULL)
	{
		Forbid();

		/* Try to find the global rendezvous semaphore. */
		SambaSemaphore = (struct SambaSemaphore *)FindSemaphore(SambaSemaphoreName);
		if(SambaSemaphore == NULL)
		{
			/* Couldn't find it. So, we create it... */
			SambaSemaphore = AllocMem(sizeof(*SambaSemaphore) + strlen(SambaSemaphoreName)+1,MEMF_ANY|MEMF_PUBLIC|MEMF_CLEAR);
			if(SambaSemaphore != NULL)
			{
				/* Set up the name. */
				SambaSemaphore->ss_Semaphore.ss_Link.ln_Name = (char *)(SambaSemaphore+1);
				strcpy(SambaSemaphore->ss_Semaphore.ss_Link.ln_Name,SambaSemaphoreName);

				/* Set the base value for all process IDs. */
				SambaSemaphore->ss_PID = 936;

				/* Clear the client list tasks are going to be registered with. */
				NewList(&SambaSemaphore->ss_ClientList);

				/* Clear the list we will use for keeping
				 * track of file segment locks.
				 */
				NewList(&SambaSemaphore->ss_FileLockList);

				AddSemaphore((struct SignalSemaphore *)SambaSemaphore);
			}
		}

		Permit();
	}

	/* If possible, register this task. */
	if(SambaSemaphore != NULL)
	{
		struct SambaClientNode * scn;

		scn = AllocVec(sizeof(*scn),MEMF_ANY|MEMF_PUBLIC|MEMF_CLEAR);
		if(scn != NULL)
		{
			/* That's me. */
			scn->scn_Task = FindTask(NULL);

			ObtainSemaphore((struct SignalSemaphore *)SambaSemaphore);

			/* Register this program with a new process ID. */
			scn->scn_PID = SambaSemaphore->ss_PID++;
			AddTail(&SambaSemaphore->ss_ClientList,(struct Node *)scn);

			ReleaseSemaphore((struct SignalSemaphore *)SambaSemaphore);

			ThisClient = scn;

			success = TRUE;
		}
	}

	return(success);
}

int
amiga_kill(pid_t pid,int sig)
{
	struct SambaClientNode * scn;
	struct Task * found = NULL;
	int result = ERROR;

	chkabort();

	ENTER();

	ObtainSemaphore((struct SignalSemaphore *)SambaSemaphore);

	/* Try to find the Samba program that responds
	 * to the given process ID.
	 */
	for(scn = (struct SambaClientNode *)SambaSemaphore->ss_ClientList.lh_Head ;
	    scn->scn_MinNode.mln_Succ != NULL ;
	    scn = (struct SambaClientNode *)scn->scn_MinNode.mln_Succ)
	{
		if(scn->scn_PID == pid)
		{
			found = scn->scn_Task;
			break;
		}
	}

	/* Did we find one? */
	if(found != NULL)
	{
		/* Kill the task or just do nothing? */
		if(sig == SIGTERM)
			Signal(found,SIGBREAKF_CTRL_C);

		result = OK;
	}
	else
	{
		errno = ESRCH;
	}

	ReleaseSemaphore((struct SignalSemaphore *)SambaSemaphore);

	RETURN(result);
	return(result);
}

pid_t
amiga_getpid(VOID)
{
	pid_t result;

	chkabort();

	ENTER();

	/* Return this program's process ID. */
	result = ThisClient->scn_PID;

	RETURN(result);
	return(result);
}

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

struct LockedRegionNode
{
	struct MinNode	lrn_MinNode;
	LONG			lrn_Start;
	LONG			lrn_Stop;
	pid_t			lrn_Owner;
	BOOL			lrn_Shared;
};

struct FileLockNode
{
	struct MinNode	fln_MinNode;
	struct List		fln_LockedRegionList;
	BPTR			fln_FileParentDir;
	UBYTE			fln_FileName[1];
};

STATIC VOID
RemoveLockedRegionNode(struct UFB * ufb,LONG start,LONG stop)
{
	if(FLAG_IS_SET(ufb->ufbflg,UFB_LOCKED))
	{
		BPTR fileHandle = (BPTR)ufb->ufbfh;
		struct FileLockNode * whichLock;

		/* Find the locked file this descriptor
		 * buffer belongs to.
		 */
		if(FindFileLockNodeByFileHandle(fileHandle,&whichLock) == OK && whichLock != NULL)
		{
			struct LockedRegionNode * lrn;

			/* Find the region to unlock and remove it. */
			for(lrn = (struct LockedRegionNode *)whichLock->fln_LockedRegionList.lh_Head ;
			    lrn->lrn_MinNode.mln_Succ != NULL ;
			    lrn = (struct LockedRegionNode *)lrn->lrn_MinNode.mln_Succ)
			{
				if(lrn->lrn_Owner == ThisClient->scn_PID &&
				   lrn->lrn_Start == start &&
				   lrn->lrn_Stop  == stop)
				{
					SHOWMSG("unlocking all regions on this file");

					Remove((struct Node *)lrn);
					DeleteLockedRegionNode(lrn);
					break;
				}
			}

			/* Check if there are any locked regions left.
			 * If not, mark the entire file as unlocked.
			 */
			if(IsListEmpty(&whichLock->fln_LockedRegionList))
			{
				SHOWMSG("no more regions are locked; removing the file lock node");

				Remove((struct Node *)whichLock);

				DeleteFileLockNode(whichLock);

				CLEAR_FLAG(ufb->ufbflg,UFB_LOCKED);
			}
		}
	}
}

STATIC VOID
DeleteLockedRegionNode(struct LockedRegionNode * lrn)
{
	if(lrn != NULL)
		FreeMem(lrn,sizeof(*lrn));
}

STATIC LONG
CreateLockedRegionNode(struct LockedRegionNode ** resultPtr)
{
	struct LockedRegionNode * lrn;
	LONG error;

	lrn = AllocMem(sizeof(*lrn),MEMF_ANY|MEMF_PUBLIC|MEMF_CLEAR);
	if(lrn != NULL)
	{
		lrn->lrn_Owner = ThisClient->scn_PID;

		error = OK;
	}
	else
	{
		error = ERROR_NO_FREE_STORE;
	}

	(*resultPtr) = lrn;

	return(error);
}

STATIC VOID
DeleteFileLockNode(struct FileLockNode * fln)
{
	if(fln != NULL)
	{
		UnLock(fln->fln_FileParentDir);
		FreeVec(fln);
	}
}

STATIC LONG
CreateFileLockNode(struct UFB * ufb,struct FileLockNode ** resultPtr)
{
	struct FileLockNode * result = NULL;
	BPTR fileHandle = (BPTR)ufb->ufbfh;
	struct FileInfoBlock __aligned fib;
	LONG error = OK;

	/* We store a lock on the file's parent directory
	 * and the name of the file for later use in
	 * comparisons.
	 */
	if(ExamineFH(fileHandle,&fib))
	{
		struct FileLockNode * fln;

		fln = AllocVec(sizeof(*fln) + strlen(fib.fib_FileName),MEMF_ANY|MEMF_PUBLIC|MEMF_CLEAR);
		if(fln != NULL)
		{
			fln->fln_FileParentDir = ParentOfFH(fileHandle);
			if(fln->fln_FileParentDir != ZERO)
			{
				strcpy(fln->fln_FileName,fib.fib_FileName);

				NewList(&fln->fln_LockedRegionList);

				result = fln;
				fln = NULL;
			}
			else
			{
				error = IoErr();
			}

			DeleteFileLockNode(fln);
		}
		else
		{
			error = ERROR_NO_FREE_STORE;
		}
	}
	else
	{
		error = IoErr();
	}

	(*resultPtr) = result;

	return(error);
}

STATIC LONG
FindFileLockNodeByFileHandle(BPTR fileHandle,struct FileLockNode ** resultPtr)
{
	struct FileLockNode * result = NULL;
	BPTR parentDir;
	LONG error;

	/* Determine the file's parent directory and
	 * name. These will be compared against the
	 * global file lock data.
	 */
	parentDir = ParentOfFH(fileHandle);
	if(parentDir != ZERO)
	{
		struct FileInfoBlock __aligned this_fib;

		if(ExamineFH(fileHandle,&this_fib))
		{
			struct FileLockNode * fln;

			#if DEBUG
			{
				char name[MAX_FILENAME_LEN];

				if(NameFromFH(fileHandle,name,sizeof(name)))
					D(("Looking for a lock on file |%s|",name));
			}
			#endif /* DEBUG */

			error = OK;

			for(fln = (struct FileLockNode *)SambaSemaphore->ss_FileLockList.lh_Head ;
			    fln->fln_MinNode.mln_Succ != NULL ;
			    fln = (struct FileLockNode *)fln->fln_MinNode.mln_Succ)
			{
				/* To be identical, the files must reside in the
				 * same directories and bear the same names.
				 */
				if(SameLock(fln->fln_FileParentDir,parentDir) == LOCK_SAME)
				{
					if(Stricmp(fln->fln_FileName,this_fib.fib_FileName) == SAME)
					{
						result = fln;
						error = OK;

						break;
					}
				}
			}
		}
		else
		{
			error = IoErr();
		}

		if(result != NULL)
			SHOWMSG("found one");
		else
			SHOWMSG("didn't find one");

		UnLock(parentDir);
	}
	else
	{
		error = IoErr();
	}

	(*resultPtr) = result;

	return(error);
}

STATIC VOID
FindFileLockNodeByDrawerAndName(BPTR dirLock,STRPTR fileName,struct FileLockNode ** resultPtr)
{
	struct FileLockNode * result = NULL;
	struct FileLockNode * fln;

	/* This is a somewhat simplied version of FindFileLockNodeByFileHandle()
	 * which works with preset drawer and name data.
	 */

	#if DEBUG
	{
		char name[MAX_FILENAME_LEN];

		if(NameFromLock(dirLock,name,sizeof(name)))
		{
			if(AddPart(name,fileName,sizeof(name)))
				D(("Looking for a lock on file |%s|",name));
		}
	}
	#endif /* DEBUG */

	for(fln = (struct FileLockNode *)SambaSemaphore->ss_FileLockList.lh_Head ;
	    fln->fln_MinNode.mln_Succ != NULL ;
	    fln = (struct FileLockNode *)fln->fln_MinNode.mln_Succ)
	{
		if(SameLock(fln->fln_FileParentDir,dirLock) == LOCK_SAME)
		{
			if(Stricmp(fln->fln_FileName,fileName) == SAME)
			{
				result = fln;

				break;
			}
		}
	}

	if(result != NULL)
		SHOWMSG("found one");
	else
		SHOWMSG("didn't find one");

	(*resultPtr) = result;
}

STATIC struct LockedRegionNode *
FindCollidingRegion(struct FileLockNode * fln,LONG start,LONG stop,BOOL shared)
{
	struct LockedRegionNode * result = NULL;
	struct LockedRegionNode * lrn;

	/* This routine looks for a locked region that overlaps
	 * with the specified region. It returns a pointer to the
	 * region that would collide with the specified region if
	 * a new lock were to be added.
	 */
	for(lrn = (struct LockedRegionNode *)fln->fln_LockedRegionList.lh_Head ;
	    lrn->lrn_MinNode.mln_Succ != NULL ;
	    lrn = (struct LockedRegionNode *)lrn->lrn_MinNode.mln_Succ)
	{
		/* Do the regions overlap? */
		if(lrn->lrn_Start <= stop && start <= lrn->lrn_Stop)
		{
			/* Two shared regions may always overlap.
			 * How about the rest?
			 */
			if(NOT shared || NOT lrn->lrn_Shared)
			{
				/* The lock owner may add as many exclusive
				 * or shared locks to the same region as
				 * necessary.
				 */
				if(lrn->lrn_Owner == ThisClient->scn_PID)
					continue;

				/* So we found a region that would
				 * cause a collision.
				 */
				result = lrn;
				break;
			}
		}
	}

	return(result);
}

STATIC VOID
CleanupFileLocks(int fd)
{
	struct UFB * ufb;

	/* This routine removes all locked regions from a file
	 * before it is eventually closed.
	 */

	ufb = chkufb(fd);
	if(ufb != NULL && FLAG_IS_SET(ufb->ufbflg,UFB_LOCKED))
	{
		BPTR fileHandle = (BPTR)ufb->ufbfh;
		struct FileLockNode * whichLock;

		ObtainSemaphore((struct SignalSemaphore *)SambaSemaphore);

		if(FindFileLockNodeByFileHandle(fileHandle,&whichLock) == OK && whichLock != NULL)
		{
			struct LockedRegionNode * lrn_this;
			struct LockedRegionNode * lrn_next;

			SHOWMSG("unlocking all regions on this file");

			for(lrn_this = (struct LockedRegionNode *)whichLock->fln_LockedRegionList.lh_Head ;
			   (lrn_next = (struct LockedRegionNode *)lrn_this->lrn_MinNode.mln_Succ) != NULL ;
			    lrn_this = lrn_next)
			{
				if(lrn_this->lrn_Owner == ThisClient->scn_PID)
				{
					Remove((struct Node *)lrn_this);
					DeleteLockedRegionNode(lrn_this);
				}
			}

			if(IsListEmpty(&whichLock->fln_LockedRegionList))
			{
				SHOWMSG("no more regions are locked; removing the file lock node");

				Remove((struct Node *)whichLock);

				DeleteFileLockNode(whichLock);
			}
		}

		CLEAR_FLAG(ufb->ufbflg,UFB_LOCKED);

		ReleaseSemaphore((struct SignalSemaphore *)SambaSemaphore);
	}
}

#define SEEK_ERROR (-1)

STATIC int
HandleFileLocking(int cmd,struct flock * l,struct UFB * ufb)
{
	int result = ERROR;

	/* This routine implements advisory file segment locking
	 * similar to 4.4BSD, but not quite the same. The functionality
	 * is a subset, somewhat similar to the functionality offered
	 * by the AmigaDOS LockRecord() and UnlockRecord() functions.
	 * This means for example that every unlock request must
	 * match the size and position of the corresponding locking
	 * request.
	 *
	 * This implementation was chosen because not every Amiga
	 * filing system implements record locking and Samba
	 * absolutely requires this functionality to work.
	 */
	if(l != NULL && ufb != NULL)
	{
		/* Can we make sense of the input parameters? */
		if(F_RDLCK <= l->l_type && l->l_type <= F_WRLCK &&
		  (l->l_whence == SEEK_SET || l->l_whence == SEEK_CUR || l->l_whence == SEEK_END))
		{
			struct LockedRegionNode * lrn = NULL;
			struct FileLockNode * fln = NULL;
			LONG error;

			if((cmd == F_SETLK || cmd == F_SETLKW) && (l->l_type != F_UNLCK))
			{
				SHOWMSG("this is a lock request");
				error = CreateFileLockNode(ufb,&fln);
				if(error == OK)
					error = CreateLockedRegionNode(&lrn);
			}
			else
			{
				SHOWMSG("this is not a lock request");
				error = OK;
			}

			if(error == OK)
			{
				struct FileInfoBlock __aligned fib;
				BPTR fileHandle = (BPTR)ufb->ufbfh;
				BOOL dataIsValid = TRUE;
				LONG currentPosition;
				LONG start = 0;
				LONG len = 0;

				/* Now calculate the position of the
				 * first byte to lock and the number
				 * of bytes to lock.
				 */
				switch(l->l_whence)
				{
					case SEEK_SET:

						start = l->l_start;

						if(l->l_len == 0)
						{
							if(ExamineFH(fileHandle,&fib))
								len = fib.fib_Size - start;
							else
								dataIsValid = FALSE;
						}
						else
						{
							len = l->l_len;
						}

						break;

					case SEEK_CUR:

						currentPosition = Seek(fileHandle,0,OFFSET_CURRENT);
						if(currentPosition != SEEK_ERROR)
						{
							start = currentPosition + l->l_start;

							if(l->l_len == 0)
							{
								if(ExamineFH(fileHandle,&fib))
									len = fib.fib_Size - start;
								else
									dataIsValid = FALSE;
							}
							else
							{
								len = l->l_len;
							}
						}
						else
						{
							dataIsValid = FALSE;
						}

						break;

					case SEEK_END:
					default:

						if(ExamineFH(fileHandle,&fib))
						{
							start = fib.fib_Size + l->l_start;

							if(l->l_len == 0)
								len = fib.fib_Size - start;
							else
								len = l->l_len;
						}
						else
						{
							dataIsValid = FALSE;
						}

						break;
				}

				SHOWVALUE(start);
				SHOWVALUE(len);

				/* Did we get everything we needed? */
				if(dataIsValid)
				{
					if(start >= 0 && len >= 0)
					{
						if(len > 0)
						{
							if(l->l_type == F_UNLCK)
							{
								D(("unlocking %ld..%ld",start,start+len-1));

								ObtainSemaphore((struct SignalSemaphore *)SambaSemaphore);

								RemoveLockedRegionNode(ufb,start,start+len-1);

								ReleaseSemaphore((struct SignalSemaphore *)SambaSemaphore);

								result = OK;
							}
							else if(cmd == F_SETLKW)
							{
								BPTR parentDir;

								D(("  locking %ld..%ld",start,start+len-1));

								parentDir = ParentOfFH(fileHandle);
								if(parentDir != ZERO)
								{
									struct FileInfoBlock __aligned fib;

									if(ExamineFH(fileHandle,&fib))
									{
										BOOL shared = (BOOL)(l->l_type == F_RDLCK);
										struct FileLockNode * existing_fln;
										BOOL stopped = FALSE;
										BOOL locked;

										if(shared)
											D(("this is a shared lock; waiting for completion"));
										else
											D(("this is an exclusive lock; waiting for completion"));

										lrn->lrn_Start	= start;
										lrn->lrn_Stop	= start+len-1;
										lrn->lrn_Shared	= shared;

										/* Retry until we manage to lock the record. */
										locked = FALSE;
										do
										{
											ObtainSemaphore((struct SignalSemaphore *)SambaSemaphore);

											FindFileLockNodeByDrawerAndName(parentDir,fib.fib_FileName,&existing_fln);
											if(existing_fln != NULL)
											{
												SHOWMSG("that file is already locked by someone");

												if(FindCollidingRegion(existing_fln,start,start+len-1,shared) == NULL)
												{
													SHOWMSG("but the locks don't collide");

													AddTail(&existing_fln->fln_LockedRegionList,(struct Node *)lrn);
													lrn = NULL;

													locked = TRUE;
												}
												else
												{
													SHOWMSG("and the locks collide");
												}
											}
											else
											{
												SHOWMSG("nobody has any locks on this file");

												AddTail(&SambaSemaphore->ss_FileLockList,(struct Node *)fln);
												AddTail(&fln->fln_LockedRegionList,(struct Node *)lrn);

												fln = NULL;
												lrn = NULL;

												locked = TRUE;
											}

											ReleaseSemaphore((struct SignalSemaphore *)SambaSemaphore);

											if(NOT locked)
											{
												const int randMax = RAND_MAX / 65536;
												LONG numRandomTicks;

												if(CheckAbort())
												{
													stopped = TRUE;
													break;
												}

												/* Wait a little before retrying
												 * the locking operation. We add
												 * a little randomness here to
												 * reduce the likelihood of two
												 * competing processes trying to
												 * lock the same file at the
												 * same time.
												 */

												numRandomTicks = ((TICKS_PER_SECOND / 2) * (rand() / 65536)) / randMax;

												if(numRandomTicks > 0)
													Delay(numRandomTicks);
											}
										}
										while(NOT locked);

										if(stopped)
										{
											SHOWMSG("lock polling loop stopped");

											DeleteFileLockNode(fln);
											fln = NULL;

											DeleteLockedRegionNode(lrn);
											lrn = NULL;

											UnLock(parentDir);
											parentDir = ZERO;

											errno = EINTR;

											raise(SIGINT);
										}

										if(locked)
										{
											SHOWMSG("the file now has a lock set");
											SET_FLAG(ufb->ufbflg,UFB_LOCKED);
											result = OK;
										}
									}
									else
									{
										SHOWMSG("couldn't read this file's name");
										MapIoErrToErrno();
									}

									UnLock(parentDir);
								}
								else
								{
									SHOWMSG("couldn't get a lock on the file's parent directory");
									MapIoErrToErrno();
								}
							}
							else if(cmd == F_SETLK)
							{
								BOOL shared = (BOOL)(l->l_type == F_RDLCK);
								struct FileLockNode * existing_fln;
								BOOL locked = FALSE;

								if(shared)
									D(("this is a shared lock"));
								else
									D(("this is an exclusive lock"));

								lrn->lrn_Start	= start;
								lrn->lrn_Stop	= start+len-1;
								lrn->lrn_Shared	= shared;

								ObtainSemaphore((struct SignalSemaphore *)SambaSemaphore);

								if(FindFileLockNodeByFileHandle(fileHandle,&existing_fln) == OK)
								{
									if(existing_fln != NULL)
									{
										SHOWMSG("that file is already locked by someone else");

										if(FindCollidingRegion(existing_fln,start,start+len-1,shared) == NULL)
										{
											SHOWMSG("but the locks don't collide");

											AddTail(&existing_fln->fln_LockedRegionList,(struct Node *)lrn);
											lrn = NULL;

											locked = TRUE;
										}
										else
										{
											SHOWMSG("and the locks collide");
										}
									}
									else
									{
										SHOWMSG("nobody has any locks on this file");

										AddTail(&SambaSemaphore->ss_FileLockList,(struct Node *)fln);
										AddTail(&fln->fln_LockedRegionList,(struct Node *)lrn);

										fln = NULL;
										lrn = NULL;

										locked = TRUE;
									}
								}

								ReleaseSemaphore((struct SignalSemaphore *)SambaSemaphore);

								if(locked)
								{
									SHOWMSG("the file now has a lock set");

									SET_FLAG(ufb->ufbflg,UFB_LOCKED);

									result = OK;
								}
								else
								{
									errno = EACCES;
								}
							}
							else if (cmd == F_GETLK)
							{
								struct FileLockNode * fln;
								LONG error;

								SafeObtainSemaphoreShared((struct SignalSemaphore *)SambaSemaphore);

								SHOWMSG("checking for possible lock collision");

								error = FindFileLockNodeByFileHandle(fileHandle,&fln);
								if(error == OK)
								{
									if(fln != NULL)
									{
										struct LockedRegionNode * lrn;
										BOOL shared;

										SHOWMSG("somebody has locked this file");

										shared = (BOOL)(l->l_type == F_RDLCK);

										lrn = FindCollidingRegion(fln,start,start+len-1,shared);
										if(lrn != NULL)
										{
											SHOWMSG("there is a possible lock collision");

											l->l_type	= (lrn->lrn_Shared ? F_RDLCK : F_WRLCK);
											l->l_whence	= SEEK_SET;
											l->l_start	= lrn->lrn_Start;
											l->l_len	= lrn->lrn_Stop - lrn->lrn_Start + 1;
											l->l_pid	= lrn->lrn_Owner;
										}
										else
										{
											SHOWMSG("there is no lock collision");

											l->l_type = F_UNLCK;
										}
									}
									else
									{
										SHOWMSG("nobody has locked this file");

										l->l_type = F_UNLCK;
									}

									result = OK;
								}
								else
								{
									SetIoErr(error);

									MapIoErrToErrno();
								}

								ReleaseSemaphore((struct SignalSemaphore *)SambaSemaphore);
							}
						}
						else
						{
							SHOWMSG("zero length lock/unlock");
							result = OK;
						}
					}
					else
					{
						SHOWMSG("invalid start/len");
						SHOWVALUE(start);
						SHOWVALUE(len);
						errno = EINVAL;
					}
				}
				else
				{
					SHOWMSG("couldn't determine start/len");
					MapIoErrToErrno();
				}
			}
			else
			{
				SHOWMSG("couldn't get the bookkeeping memory needed");
				MapIoErrToErrno();
			}

			DeleteFileLockNode(fln);
			DeleteLockedRegionNode(lrn);
		}
		else
		{
			SHOWMSG("invalid lock type or seek offset");
			SHOWVALUE(l->l_type);
			SHOWVALUE(l->l_whence);
			errno = EINVAL;
		}
	}
	else
	{
		SHOWMSG("no flock or no ufb");
		SHOWVALUE(l);
		SHOWVALUE(ufb);

		errno = EINVAL;
	}

	RETURN(result);
	return(result);
}

int
amiga_fcntl(int fd,int cmd,unsigned long arg)
{
	struct UFB * ufb;
	struct flock * l;
	int result = ERROR;

	chkabort();

	ENTER();

	/* Don't try to lock data in a socket... */
	ufb = chkufb(fd);
	if(ufb == NULL || FLAG_IS_SET(ufb->ufbflg,UFB_IS_SOCKET))
		ufb = NULL;

	l = (struct flock *)arg;

	switch(cmd)
	{
		/* Get the first lock that blocks the lock description pointed to
		 * by the third argument, arg, taken as a pointer to a struct
		 * flock (see above).  The information retrieved overwrites the
		 * information passed to fcntl in the flock structure.  If no
		 * lock is found that would prevent this lock from being created,
		 * the structure is left unchanged by this function call except
		 * for the lock type which is set to F_UNLCK.
		 */
		case F_GETLK:

			SHOWMSG("F_GETLK");
			result = HandleFileLocking(cmd,l,ufb);
			break;

		/* Set or clear a file segment lock according to the lock de-
		 * scription pointed to by the third argument, arg, taken as a
		 * pointer to a struct flock (see above).	F_SETLK is used to es-
		 * tablish shared (or read) locks (F_RDLCK) or exclusive (or
		 * write) locks, (F_WRLCK), as well as remove either type of lock
		 * (F_UNLCK). If a shared or exclusive lock cannot be set, fcntl
		 * returns immediately with EACCES.
		 */
		case F_SETLK:

			SHOWMSG("F_SETLK");
			result = HandleFileLocking(cmd,l,ufb);
			break;

		/* This command is the same as F_SETLK except that if a shared or
		 * exclusive lock is blocked by other locks, the process waits
		 * until the request can be satisfied.  If a signal that is to be
		 * caught is received while fcntl is waiting for a region, the
		 * fcntl will be interrupted if the signal handler has not speci-
		 * fied the SA_RESTART (see sigaction(2)).
		 */
		case F_SETLKW:

			SHOWMSG("F_SETLKW");
			result = HandleFileLocking(cmd,l,ufb);
			break;

		/* Get descriptor status flags, as described below (arg is ig-
		 * noted).
		 */
		case F_GETFL:

			SHOWMSG("F_GETFL");
			result = IsDescriptorNonblocking(fd) ? O_NONBLOCK : 0;
			break;

		/* Set descriptor status flags to arg. */
		case F_SETFL:

			SHOWMSG("F_SETFL");
			if(FLAG_IS_SET(arg,O_NONBLOCK))
				BlockDescriptor(fd);
			else
				UnblockDescriptor(fd);

			result = OK;
			break;

		default:

			D(("Unknown command %ld",cmd));
			errno = ENOSYS;
			break;
	}

	RETURN(result);
	return(result);
}

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

int
amiga_fgetc(FILE *in)
{
	struct UFB * ufb;
	int result = ERROR;

	PUSH_ASSERTS();

	chkabort();

	ufb = chkufb(fileno(in));
	if(ufb != NULL && FLAG_IS_SET(ufb->ufbflg,UFB_IS_SOCKET))
	{
		UBYTE c;

		ENTER();
		SHOWMSG("input from socket");

		if(recv(ufb->ufbfh,&c,1,0) == 1)
			result = c;
	}
	else
	{
		result = fgetc(in);
	}

	RETURN(result);
	POP();
	return(result);
}

char *
amiga_fgets(char *str,int n,FILE * in)
{
	struct UFB * ufb;
	char *result;

	ENTER();

	ufb = chkufb(fileno(in));
	if(ufb != NULL && FLAG_IS_SET(ufb->ufbflg,UFB_IS_SOCKET))
	{
		char *s = str;
		LONG rc;
		UBYTE c;

		SHOWMSG("input from socket");
		result = str;

		while(--n > 0)
		{
			chkabort();

			rc = recv(ufb->ufbfh,&c,1,0);
			if(rc == 1)
			{
				(*str++) = c;

				if(c == '\n')
					break;
			}
			else
			{
				/* End of stream or nothing read? */
				if(rc < 0 || str == s)
					result = NULL;

				break;
			}
		}

		if(result != NULL)
			(*str) = '\0';
	}
	else
	{
		result = fgets(str,n,in);
	}

	RETURN(result);
	return(result);
}

int
amiga_fputs(const char *str,FILE *out)
{
	struct UFB * ufb;
	int result;

	chkabort();

	ENTER();

	ufb = chkufb(fileno(out));
	if(ufb != NULL && FLAG_IS_SET(ufb->ufbflg,UFB_IS_SOCKET))
	{
		SHOWMSG("output to socket");

		if(NOT STRING_IS_EMPTY(str))
			result = send(ufb->ufbfh,str,strlen(str),0);
		else
			result = OK;

		if(result == OK)
			result = send(ufb->ufbfh,"\n",1,0);
	}
	else
	{
		result = fputs(str,out);
	}

	RETURN(result);
	return(result);
}

int
amiga_puts(const char *str)
{
	int result;

	result = amiga_fputs(str,stdout);

	return(result);
}

int
amiga_vfprintf(FILE *out,const char *fmt,va_list args)
{
	struct UFB * ufb;
	int result;

	chkabort();

	ENTER();

	ufb = chkufb(fileno(out));
	if(ufb != NULL && FLAG_IS_SET(ufb->ufbflg,UFB_IS_SOCKET))
	{
		char buffer[1024];

		SHOWMSG("output to socket");

		vsnprintf(buffer,sizeof(buffer)-1,fmt,args);
		buffer[sizeof(buffer)-1] = '\0';

		if(NOT STRING_IS_EMPTY(buffer))
			result = send(ufb->ufbfh,buffer,strlen(buffer),0);
		else
			result = 0;
	}
	else
	{
		result = vfprintf(out,fmt,args);
	}

	RETURN(result);
	return(result);
}

int
amiga_fprintf(FILE *out,const char *fmt,...)
{
	va_list args;
	int result;

	va_start(args,fmt);
	result = amiga_vfprintf(out,fmt,args);
	va_end(args);

	return(result);
}

int
amiga_printf(const char *fmt,...)
{
	va_list args;
	int result;

	va_start(args,fmt);
	result = amiga_vfprintf(stdout,fmt,args);
	va_end(args);

	return(result);
}

size_t
amiga_fwrite(const void *data,size_t blockSize,size_t numBlocks,FILE *out)
{
	struct UFB * ufb;
	size_t result;

	chkabort();

	ENTER();

	ufb = chkufb(fileno(out));
	if(ufb != NULL && FLAG_IS_SET(ufb->ufbflg,UFB_IS_SOCKET))
	{
		SHOWMSG("output to socket");

		result = send(ufb->ufbfh,(APTR)data,blockSize * numBlocks,0);
		if(result > 0)
			result = (result/blockSize);
	}
	else
	{
		result = fwrite(data,blockSize,numBlocks,out);
	}

	RETURN(result);
	return(result);
}

size_t
amiga_fread(void *data,size_t blockSize,size_t numBlocks,FILE *in)
{
	struct UFB * ufb;
	size_t result;

	chkabort();

	ENTER();

	ufb = chkufb(fileno(in));
	if(ufb != NULL && FLAG_IS_SET(ufb->ufbflg,UFB_IS_SOCKET))
	{
		SHOWMSG("input from socket");

		result = recv(ufb->ufbfh,(APTR)data,blockSize * numBlocks,0);
		if(result > 0)
			result = (result/blockSize);
	}
	else
	{
		result = fread(data,blockSize,numBlocks,in);
	}

	RETURN(result);
	return(result);
}

int
amiga_fclose(FILE * stream)
{
	struct UFB * ufb;
	int result;

	chkabort();

	ENTER();

	ufb = chkufb(fileno(stream));
	if(ufb != NULL && FLAG_IS_SET(ufb->ufbflg,UFB_IS_SOCKET))
		result = OK;
	else
		result = fclose(stream);

	RETURN(result);
	return(result);
}

int
amiga_fflush(FILE * stream)
{
	struct UFB * ufb;
	int result;

	chkabort();

	ENTER();

	ufb = chkufb(fileno(stream));
	if(ufb != NULL && FLAG_IS_SET(ufb->ufbflg,UFB_IS_SOCKET))
		result = OK;
	else
		result = fflush(stream);

	RETURN(result);
	return(result);
}

int
amiga_fseek(FILE * stream,long int offset,int mode)
{
	struct UFB * ufb;
	int result;

	chkabort();

	ENTER();

	ufb = chkufb(fileno(stream));
	if(ufb != NULL && FLAG_IS_SET(ufb->ufbflg,UFB_IS_SOCKET))
		result = OK;
	else
		result = fseek(stream,offset,mode);

	RETURN(result);
	return(result);
}

long int
amiga_ftell(FILE * stream)
{
	struct UFB * ufb;
	long int result;

	chkabort();

	ENTER();

	ufb = chkufb(fileno(stream));
	if(ufb != NULL && FLAG_IS_SET(ufb->ufbflg,UFB_IS_SOCKET))
		result = 0;
	else
		result = ftell(stream);

	RETURN(result);
	return(result);
}

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

int
amiga_setvbuf(FILE *stream,char *buff,int type,size_t size)
{
	struct UFB * ufb;
	int result;

	chkabort();

	ENTER();

	ufb = chkufb(fileno(stream));
	if(ufb != NULL && FLAG_IS_SET(ufb->ufbflg,UFB_IS_SOCKET))
		result = OK;
	else
		result = setvbuf(stream,buff,type,size);

	RETURN(result);
	return(result);
}

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

int
amiga_fputc(int c,FILE *stream)
{
	struct UFB * ufb;
	int result = ERROR;

	chkabort();

	ENTER();

	ufb = chkufb(fileno(stream));
	if(ufb != NULL && FLAG_IS_SET(ufb->ufbflg,UFB_IS_SOCKET))
	{
		UBYTE oneByte = c;

		if(send(ufb->ufbfh,&oneByte,1,0) > 0)
			result = OK;
	}
	else
	{
		result = fputc(c,stream);
	}

	RETURN(result);
	return(result);
}

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

VOID
amiga_setbuf(FILE *stream,char *buffer)
{
	struct UFB * ufb;

	chkabort();

	ENTER();

	ufb = chkufb(fileno(stream));
	if(ufb != NULL && FLAG_IS_SET(ufb->ufbflg,UFB_IS_SOCKET))
	{
		/* Do nothing */
	}
	else
	{
		setbuf(stream,buffer);
	}

	LEAVE();
}

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

int
amiga_recv(int fd,void *buff,size_t nbytes,int flags)
{
	struct UFB * ufb;
	int result = ERROR;

	ENTER();

	ufb = chkufb(fd);
	if(ufb != NULL && FLAG_IS_SET(ufb->ufbflg,UFB_IS_SOCKET))
		result = recv(ufb->ufbfh,buff,nbytes,flags);
	else
		errno = EBADF;

	RETURN(result);
	return(result);
}

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

int
amiga_send(int fd,void *buff,size_t nbytes,int flags)
{
	struct UFB * ufb;
	int result = ERROR;

	ENTER();

	ufb = chkufb(fd);
	if(ufb != NULL && FLAG_IS_SET(ufb->ufbflg,UFB_IS_SOCKET))
		result = send(ufb->ufbfh,buff,nbytes,flags);
	else
		errno = EBADF;

	RETURN(result);
	return(result);
}

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

int
amiga_smbrun(char *cmd,char *outfile,BOOL shared)
{
	struct MangleInfo mi_cmd;
	struct MangleInfo mi_outfile;
	int result = ERROR;

	ENTER();

	ForbidDOS();

	if(outfile == NULL)
		outfile = "/dev/null";

	if(MangleName(&cmd,&mi_cmd) == OK)
	{
		if(MangleName(&outfile,&mi_outfile) == OK)
		{
			BPTR in;

			in = Open("NIL:",MODE_OLDFILE);
			if(in != ZERO)
			{
				BPTR out;

				out = Open(outfile,MODE_NEWFILE);
				if(out != ZERO)
				{
					if(SystemTags(cmd,
						SYS_Input,		in,
						SYS_Output,		out,
						SYS_UserShell,	TRUE,
						NP_WindowPtr,	NULL,
					TAG_DONE) != -1)
					{
						result = OK;
					}
					else
					{
						MapIoErrToErrno();
					}

					Close(out);
				}
				else
				{
					MapIoErrToErrno();
				}

				Close(in);
			}
			else
			{
				MapIoErrToErrno();
			}

			UnmangleName(&outfile,&mi_outfile);
		}

		UnmangleName(&cmd,&mi_cmd);
	}

	PermitDOS();

	RETURN(result);
	return(result);
}

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

#define IFBSIZE 1024
#define max(a,b) ( (a) > (b) ? (a) : (b) )

int
amiga_get_interfaces(struct iface_struct * ifaces,int max_interfaces)
{
	struct ifreq * ifr_end;
	struct ifreq * ifr;
	struct ifconf * ifc;
	struct ifreq ifr_copy;
	int sockfd = -1;
	int result = -1;
	int total = 0;
	int len;

	/* Make room for the interface information. I hope
	 * that 1024 bytes will be sufficient.
	 */
	ifc = malloc(sizeof(*ifc) + IFBSIZE);
	if(ifc == NULL)
	{
		errno = ENOMEM;
		goto out;
	}

	sockfd = socket(AF_INET,SOCK_STREAM,0);
	if(sockfd < 0)
		goto out;

	/* Now attempt to copy the interface information into
	 * the buffer. As of this writing, support for the
	 * SIOCGIFCONF ioctl() action is undocumented in all
	 * currently existing TCP/IP stacks. Nevertheless,
	 * it appears to work.
	 */
	ifc->ifc_len = IFBSIZE;
	ifc->ifc_buf = (char *)(ifc+1);

	if(IoctlSocket(sockfd,SIOCGIFCONF,(char *)ifc) != OK)
		goto out;

	len = ifc->ifc_len;

	ifr = (struct ifreq *)ifc->ifc_buf;
	ifr_end = (struct ifreq *)((char *)ifr + len);

	/* Now check each interface, extracting the interface
	 * information.
	 */
	while(ifr < ifr_end && total < max_interfaces)
	{
		ifr_copy = (*ifr);

		/* Try to obtain the address information. */
		if(IoctlSocket(sockfd,SIOCGIFADDR,(char *)&ifr_copy) == OK)
		{
			struct in_addr ipaddr;

			/* We need to remember this for later. */
			ipaddr = (*(struct sockaddr_in *)&ifr_copy.ifr_addr).sin_addr;

			/* And query the interface flags; in particular, we are interested
			 * in whether this interface is currrently "up", i.e. "online".
			 */
			if(IoctlSocket(sockfd,SIOCGIFFLAGS,(char *)&ifr_copy) == OK)
			{
				if(ifr_copy.ifr_flags & IFF_UP)
				{
					/* And finally obtain the interface net mask. */
					if(IoctlSocket(sockfd,SIOCGIFNETMASK,(char *)&ifr_copy) == OK)
					{
						struct in_addr nmask;

						nmask = ((struct sockaddr_in *)&ifr_copy.ifr_addr)->sin_addr;

						strncpy(ifaces[total].name, ifr_copy.ifr_name, sizeof(ifaces[total].name)-1);
						ifaces[total].name[sizeof(ifaces[total].name)-1] = '\0';

						ifaces[total].ip = ipaddr;
						ifaces[total].netmask = nmask;

						total++;
					}
				}
			}
		}

		len = max(sizeof(struct sockaddr),ifr->ifr_addr.sa_len);
		ifr = (struct ifreq *)(((char *)ifr) + sizeof(ifr->ifr_name) + len);
	}

	result = total;

 out:

	if(ifc != NULL)
		free(ifc);

	if(sockfd != -1)
		CloseSocket(sockfd);

	return(result);
}
