/*
 * $Id$
 *
 * :ts=4
 *
 * AmigaOS wrapper routines for GNU CVS, using the AmiTCP V3 API
 * and the SAS/C V6.58 compiler.
 *
 * Written and adapted 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 <exec/memory.h>

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

#include <libraries/locale.h>

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

#include <clib/exec_protos.h>
#include <clib/dos_protos.h>
#include <clib/utility_protos.h>
#include <clib/socket_protos.h>
#include <clib/usergroup_protos.h>
#include <clib/locale_protos.h>
#include <clib/alib_protos.h>

#include <pragmas/exec_sysbase_pragmas.h>
#include <pragmas/dos_pragmas.h>
#include <pragmas/utility_pragmas.h>
#include <pragmas/socket_pragmas.h>
#include <pragmas/usergroup_pragmas.h>
#include <pragmas/locale_pragmas.h>

#include <sys/types.h>
#include <sys/socket.h>
#include <sys/param.h>
#include <sys/ioctl.h>

#include <constructor.h>
#include <utime.h>
#include <stdio.h>
#include <errno.h>
#include <dirent.h>
#include <netdb.h>
#include <stat.h>
#include <signal.h>
#include <stdlib.h>
#include <time.h>
#include <pwd.h>
#include <grp.h>
#include <dos.h>

#include <netinet/ip.h>
#include <netinet/tcp.h>

#include <net/if.h>
#include <unistd.h>

#include <ios1.h>

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

#include "_assert.h"

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

#define const
#define NO_NAME_REPLACEMENT
#include "amiga.h"
#include "error.h"

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

#define UNIX_TIME_OFFSET 252460800

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

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

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

/* This macro lets us long-align structures on the stack */
#define D_S(type,name) \
	char a_##name[sizeof(type)+3]; \
	type *name = (type *)((LONG)(a_##name+3) & ~3)

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

#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 && \
							 (fib)->fib_DirEntryType != ST_LINKDIR)

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

#define FLAG_IS_SET(v,f)	(((v) & (f)) == (f))
#define SET_FLAG(v,f)		(v |= (f))

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

extern void * xmalloc(size_t size);
extern char * xstrdup(const char * const str);

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

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

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

static struct Library * SocketBase;
static struct Library * UserGroupBase;

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

static struct Library * LocaleBase;

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

long __stack = 20000;

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

static void map_ioerr_to_errno(void);
static int get_minutes_west(void);
static void get_next_buffer(char **buffer_ptr);
static void correct_name(char **name_ptr);
static void dispatch(void);
static void close_libs(void);
static void initialize_libraries(void);
static int recursive_unlink_file_dir(char *f);
static int compare(char **a, char **b);
static void convert_fileinfo_to_stat(struct FileInfoBlock *fib, struct stat *st);
static void restore_home_dir(void);
static int amiga_rcmd(char **remote_hostname, int remote_port, char *local_user, char *remote_user, char *command);

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

static void
map_ioerr_to_errno(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; int errno; } map_table[] =
	{
		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,			EEXIST,
		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 Error = IoErr();

	if(Error != OK)
	{
		int i;

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

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

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

static int
get_minutes_west(void)
{
	int minutes_west;

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

	if(LocaleBase != NULL)
	{
		struct Locale * loc;

		loc = OpenLocale(NULL);

		minutes_west = loc->loc_GMTOffset;

		CloseLocale(loc);
	}
	else
	{
		minutes_west = 0;
	}

	return(minutes_west);
}

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

#define MAX_FILENAME_LEN 1024

static void
get_next_buffer(char ** buffer_ptr)
{
	static char buffer_slots[8][MAX_FILENAME_LEN];
	static int buffer_index;

	(*buffer_ptr) = buffer_slots[buffer_index];
	buffer_index = (buffer_index + 1) % 8;
}

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

static void
correct_name(char ** name_ptr)
{
	char * buffer;
	int len,i;
	char * name;

	ENTER();

	name = (*name_ptr);
	if(name[0] == '/')
	{
		BOOL done;

		SHOWSTRING(name);

		get_next_buffer(&buffer);

		done = FALSE;

		len = strlen(name);
		for(i = 1 ; i <= len ; i++)
		{
			if(name[i] == '/' || name[i] == '\0')
			{
				memcpy(buffer,name+1,i-1);
				buffer[i-1] = ':';
				strcpy(&buffer[i],&name[i+1]);
				done = TRUE;
				break;
			}
		}

		if(NOT done)
			strcpy(buffer,name);

		SHOWSTRING(buffer);

		name = buffer;
	}
	else
	{
		len = strlen(name);

		SHOWSTRING(name);

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

				memcpy(buffer,name,i+1);
				strcpy(&buffer[i+1],&name[i+2]);

				SHOWSTRING(buffer);

				name = buffer;
				break;
			}
		}
	}

	if(strncmp(name,"./",2) == SAME)
	{
		get_next_buffer(&buffer);

		strcpy(buffer,name+2);
		name = buffer;
	}
	else if (strncmp(name,"../",3) == SAME)
	{
		get_next_buffer(&buffer);

		strcpy(buffer,name+3);
		name = buffer;
	}
	else if (strcmp(name,".") == SAME)
	{
		get_next_buffer(&buffer);

		strcpy(buffer,"");
		name = buffer;
	}
	else if (strcmp(name,"..") == SAME)
	{
		get_next_buffer(&buffer);

		strcpy(buffer,"/");
		name = buffer;
	}

	len = strlen(name);
	if(len > 0 && name[len-1] == '/')
		name[--len] = '\0';

	(*name_ptr) = name;

	LEAVE();
}

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

void *
amiga_valloc(size_t bytes)
{
	void * result;

	ENTER();
	SHOWVALUE(bytes);

	result = malloc(bytes);

	RETURN(result);
	return(result);
}

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

int
amiga_symlink(char *to,char *from)
{
	int result;

	ENTER();

	SHOWSTRING(to);
	SHOWSTRING(from);

	result = -1;
	errno = EINVAL;

	RETURN(result);
	return(result);
}

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

int
amiga_readlink(char *path,char *buf,int buf_size)
{
	int result;

	ENTER();

	SHOWSTRING(path);

	result = -1;
	errno = EINVAL;

	RETURN(result);
	return(result);
}

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

unsigned
amiga_sleep(unsigned seconds)
{
	Delay(TICKS_PER_SECOND * seconds);

	return(0);
}

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

unsigned long
amiga_umask(unsigned long mask)
{
	return(0);
}

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

unsigned long
amiga_waitpid(unsigned long pid,int *stat_loc,int options)
{
	return(0);
}

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

int
amiga_utime(char *name,struct utimbuf *time)
{
	struct DateStamp ds;
	int result = -1;

	ENTER();

	correct_name(&name);

	SHOWSTRING(name);

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

		/* Convert the time given. */
		if(time->modtime < (UNIX_TIME_OFFSET + 60 * minutes_west))
			seconds = 0;
		else
			seconds = time->modtime - (UNIX_TIME_OFFSET + 60 * minutes_west);	/* 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(SetFileDate((STRPTR)name,&ds))
		result = 0;

	RETURN(result);
	return(result);
}

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

int
amiga_geteuid(void)
{
	return(0);
}

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

int
amiga_getuid(void)
{
	return(0);
}

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

long
amiga_getpid(void)
{
	static long old_pid = -1;
	long result;

	ENTER();

	if(old_pid == -1)
	{
		struct Process * this_process;
		LONG max_cli;
		LONG which;
		LONG i;

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

		Forbid();

		which = max_cli = MaxCli();

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

		Permit();

		old_pid = which;
	}

	result = old_pid;

	RETURN(result);
	return(result);
}

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

char *
amiga_getlogin(void)
{
	static char name[256];
	int i;

	ENTER();

	if(GetVar("USER",name,sizeof(name),0) <= 0)
	{
		if(GetVar("LOGUSER",name,sizeof(name),0) <= 0)
		{
			if(GetVar("USERNAME",name,sizeof(name),0) <= 0)
				strcpy(name,"anonymous");
		}
	}

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

	SHOWSTRING(name);

	RETURN(name);
	return(name);
}

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

struct passwd *
amiga_getpwuid(int uid)
{
	static struct passwd pw;

	ENTER();

	SHOWVALUE(uid);

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

	pw.pw_dir	= "CVSHOME:";		/* pseudo-home directory */
	pw.pw_gid	= 1;				/* ZZZ wrong */
	pw.pw_name	= amiga_getlogin();
	pw.pw_uid	= uid;				/* ZZZ wrong */

	RETURN(&pw);
	return(&pw);
}

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

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

	ENTER();

	SHOWSTRING(name);

	result = amiga_getpwuid(1);

	RETURN(result);
	return(result);
}

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

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

	ENTER();

	result = NULL;

	RETURN(result);
	return(result);
}

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

char *
amiga_getpass(const char *prompt)
{
	struct UFB * ufb;
	char * result = NULL;

	ENTER();

	SHOWSTRING(prompt);

	ufb = chkufb(fileno(stdin));
	if(ufb != NULL)
	{
		void (*old_sig_handler)(int);

		old_sig_handler = signal(SIGINT,SIG_IGN);

		if(SetMode(ufb->ufbfh,DOSTRUE))
		{
			static char pwd_buf[128];
			int len,c;

			fputs(prompt, stderr);
			fflush(stderr);

			len = 0;
			while(TRUE)
			{
				c = -1;

				while(TRUE)
				{
					if(CheckSignal(SIGBREAKF_CTRL_C))
					{
						SetMode(ufb->ufbfh,DOSFALSE);
						signal(SIGINT,old_sig_handler);

						raise(SIGINT);

						signal(SIGINT,SIG_IGN);
						SetMode(ufb->ufbfh,DOSTRUE);
					}

					if(WaitForChar(ufb->ufbfh,TICKS_PER_SECOND / 2))
					{
						c = fgetc(stdin);
						if(c == '\003')
						{
							SetMode(ufb->ufbfh,DOSFALSE);
							signal(SIGINT,old_sig_handler);

							raise(SIGINT);

							signal(SIGINT,SIG_IGN);
							SetMode(ufb->ufbfh,DOSTRUE);
						}
						else
						{
							break;
						}
					}
				}

				if(c == '\r' || c == '\n')
					break;

				if(((c >= ' ' && c < 127) || (c >= 160)) && len < sizeof(pwd_buf)-1)
				{
					pwd_buf[len++] = c;
					pwd_buf[len] = '\0';
				}
			}

			SetMode(ufb->ufbfh,DOSFALSE);

			fputs("\n",stderr);

			SHOWSTRING(pwd_buf);

			result = pwd_buf;
		}

		signal(SIGINT,old_sig_handler);
	}

	RETURN(result);
	return(result);
}

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

int
amiga_gethostname(char * name,int namelen)
{
	static char hostname[256];
	int i,len;

	ENTER();

	if(GetVar("HOST",hostname,sizeof(hostname),0) <= 0)
	{
		if(GetVar("HOSTNAME",hostname,sizeof(hostname),0) <= 0)
			strcpy(hostname,"anonymous");
	}

	for(i = strlen(hostname)-1 ; i >= 0 ; i--)
	{
		if(hostname[i] == ' ' || hostname[i] == '\t' || hostname[i] == '\r' || hostname[i] == '\n')
			hostname[i] = '\0';
		else
			break;
	}

	len = strlen(hostname);
	if(len > namelen)
		len = namelen;

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

	SHOWSTRING(name);

	RETURN(0);
	return(0);
}

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

int
amiga_pclose(FILE * pipe)
{
	ENTER();

	fclose(pipe);

	RETURN(0);
	return(0);
}

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

FILE *
amiga_popen(char * command, const char * mode)
{
	FILE * result = NULL;
	char temp_name[40];
	BPTR output;

	ENTER();

	correct_name(&command);

	SHOWSTRING(command);
	SHOWSTRING(mode);

	sprintf(temp_name,"PIPE:%08x.%08x",FindTask(NULL),time(NULL));

	output = Open(temp_name,MODE_NEWFILE);
	if(output != ZERO)
	{
		LONG res;

		res = SystemTags(command,
			SYS_Input,		Input(),
			SYS_Output,		output,
			SYS_Asynch,		TRUE,
			SYS_UserShell,	TRUE,
			NP_CloseInput,	FALSE,
		TAG_END);

		switch(res)
		{
			case 0:
				result = fopen(temp_name,mode);
				break;

			case -1:
				errno = ENOMEM;
				Close(output);
				break;

			default:
				errno = EIO;
				break;
		}
	}
	else
	{
		errno = EIO;
	}

	RETURN(result);
	return(result);
}

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

static struct MsgPort magic_port;

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

#define ACTION_LOCATE_SOCKET	2050	/* arg1=domain, arg2=type, arg3=protocol -> lock (BPTR) */
#define ACTION_COPY_DIR_SOCKET	2051	/* arg1=socket -> lock (BPTR) */

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

static void
dispatch(void)
{
	struct FileHandle * fh;
	struct FileLock * fl;
	long fd;
	struct DosPacket * dp;
	struct Message * mn;
	LONG res1,res2;

	ENTER();

	/* We arrive here with interrupt processing disabled. We don't need
	 * that and we certainly don't want that either.
	 */
	Enable();

	/* There's always just one message waiting. */
	mn = GetMsg(&magic_port);

	dp = (struct DosPacket *)mn->mn_Node.ln_Name;

	res1 = DOSFALSE;
	res2 = 0;

	switch(dp->dp_Action)
	{
		case ACTION_FREE_LOCK: /* lock -> bool */

			SHOWMSG("ACTION_FREE_LOCK");

			fl = BADDR(dp->dp_Arg1);

			if(SocketBase != NULL)
				CloseSocket(fl->fl_Key);

			res1 = DOSTRUE;

			break;

		case ACTION_READ: /* socket,buffer (APTR),length -> length */

			SHOWMSG("ACTION_READ");
			SHOWVALUE(dp->dp_Arg3);

			if(SocketBase != NULL)
				res1 = recv(dp->dp_Arg1,(APTR)dp->dp_Arg2,dp->dp_Arg3,0);

			break;

		case ACTION_WRITE: /* socket,buffer (APTR),length -> length */

			SHOWMSG("ACTION_WRITE");
			SHOWVALUE(dp->dp_Arg3);

			if(SocketBase != NULL)
				res1 = send(dp->dp_Arg1,(APTR)dp->dp_Arg2,dp->dp_Arg3,0);

			break;

		case ACTION_END: /* socket -> bool */

			SHOWMSG("ACTION_END");

			if(SocketBase != NULL)
				CloseSocket(dp->dp_Arg1);

			res1 = DOSTRUE;

			break;

		case ACTION_FH_FROM_LOCK: /* file handle (BPTR), lock (BPTR) -> bool */

			SHOWMSG("ACTION_FH_FROM_LOCK");

			fh = BADDR(dp->dp_Arg1);
			fl = BADDR(dp->dp_Arg2);

			fh->fh_Arg1 = fl->fl_Key;

			FreeVec(fl);

			res1 = DOSTRUE;

			break;

		case ACTION_LOCATE_SOCKET: /* domain, type, protocol -> lock (BPTR) */

			SHOWMSG("ACTION_LOCATE_SOCKET");

			if(SocketBase != NULL)
				fd = socket(dp->dp_Arg1,dp->dp_Arg2,dp->dp_Arg3);
			else
				fd = -1;

			if(fd < 0)
			{
				res2 = ERROR_NO_FREE_STORE;
				break;
			}

			fl = AllocVec(sizeof(*fl),MEMF_ANY|MEMF_PUBLIC|MEMF_CLEAR);
			if(fl == NULL)
			{
				CloseSocket(fd);
				res2 = ERROR_NO_FREE_STORE;
				break;
			}

			fl->fl_Key		= fd;
			fl->fl_Access	= SHARED_LOCK;
			fl->fl_Task		= &magic_port;

			res1 = MKBADDR(fl);

			break;

		case ACTION_COPY_DIR_SOCKET: /* socket -> lock (BPTR) */

			SHOWMSG("ACTION_COPY_DIR_SOCKET");

			if(SocketBase != NULL)
				fd = Dup2Socket(dp->dp_Arg1,-1);
			else
				fd = -1;

			if(fd < 0)
			{
				res2 = ERROR_NO_FREE_STORE;
				break;
			}

			fl = AllocVec(sizeof(*fl),MEMF_ANY|MEMF_PUBLIC|MEMF_CLEAR);
			if(fl == NULL)
			{
				CloseSocket(fd);
				res2 = ERROR_NO_FREE_STORE;
				break;
			}

			fl->fl_Key		= fd;
			fl->fl_Access	= SHARED_LOCK;
			fl->fl_Task		= &magic_port;

			res1 = MKBADDR(fl);

			break;

		default:

			D(("ACTION_??? (type=%ld)",dp->dp_Action));

			res2 = ERROR_ACTION_NOT_KNOWN;
			break;
	}

	SHOWVALUE(res1);
	SHOWVALUE(res2);

	ReplyPkt(dp,res1,res2);

	/* Return to the entry state, that is with interrupts disabled
	 * since PutMsg() will turn them back on before it returns.
	 */
	Disable();

	LEAVE();
}

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

static void
close_libs(void)
{
	if(LocaleBase != NULL)
	{
		CloseLibrary(LocaleBase);
		LocaleBase = NULL;
	}

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

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

CBMLIB_DESTRUCTOR(close_libs)
{
	ENTER();

	close_libs();

	LEAVE();
}

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

CBMLIB_CONSTRUCTOR(init_magic_port)
{
	ENTER();

	/* Don't try this at home kids! We're all trained professionals here. */
	magic_port.mp_Flags	= 3;
	magic_port.mp_SigTask = (APTR)dispatch;
	NewList(&magic_port.mp_MsgList);

	RETURN(0);
	return(0);
}

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

static void
initialize_libraries(void)
{
	ENTER();

	if(SocketBase == NULL && UserGroupBase == NULL)
	{
		SocketBase = OpenLibrary("bsdsocket.library",3);
		if(SocketBase != NULL)
		{
			extern STRPTR _ProgramName;

			if(SocketBaseTags(
				SBTM_SETVAL(SBTC_ERRNOPTR(sizeof(errno))),	&errno,
				SBTM_SETVAL(SBTC_LOGTAGPTR),				_ProgramName,
			TAG_END) != 0)
			{
				CloseLibrary(SocketBase);
				SocketBase = NULL;
			}
		}

		UserGroupBase = OpenLibrary("usergroup.library",1);
		if(UserGroupBase != NULL)
		{
			if(ug_SetupContextTags(_ProgramName,
				UGT_ERRNOPTR(sizeof(errno)),&errno,
			TAG_END) != 0)
			{
				CloseLibrary(UserGroupBase);
				UserGroupBase = NULL;
			}
		}

		if(SocketBase == NULL)
		{
			fprintf(stderr,"Could not open 'bsdsocket.library' V3; TCP/IP stack not running?\n");
			exit(RETURN_FAIL);
		}
		else if (UserGroupBase == NULL)
		{
			fprintf(stderr,"Could not open 'usergroup.library' V1; TCP/IP stack not running?\n");
			exit(RETURN_FAIL);
		}
	}

	LEAVE();
}

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

void __regargs
__chkabort(void)
{
	if(SetSignal(0,0) & SIGBREAKF_CTRL_C)
		raise(SIGINT);
}

void __regargs
_CXBRK(void)
{
	extern STRPTR _ProgramName;

	/* Flush the standard output streams so that
	 * any following output will be printed after
	 * any buffered stdio output.
	 */
	if(WBenchMsg == NULL)
	{
		/* Don't let anybody stop us. */
		signal(SIGINT,SIG_IGN);
		signal(SIGTERM,SIG_IGN);

		fflush(stdout);
		fflush(stderr);
	}

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

	restore_home_dir();
	close_libs();

	exit(RETURN_WARN);
}

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

char *
amiga_strerror(int code)
{
	char * result;

	ENTER();

	if(SocketBase != NULL)
	{
		struct TagItem tags[2];

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

		SocketBaseTagList(tags);

		result = (char *)tags[0].ti_Data;
	}
	else
	{
		result = strerror(code);
	}

	SHOWSTRING(result);

	RETURN(result);
	return(result);
}

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

int
amiga_mkdir(char *name,int mode)
{
	int result = OK;
	BPTR lock;

	ENTER();

	correct_name(&name);

	SHOWSTRING(name);

	lock = CreateDir(name);
	if(lock != ZERO)
	{
		UnLock(lock);

		amiga_chmod(name,mode);
	}
	else
	{
		LONG error = IoErr();

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

			map_ioerr_to_errno();

			result = -1;
		}
	}

	RETURN(result);
	return(result);
}

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

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

	ENTER();
	SHOWSTRING(name);

	initialize_libraries();

	result = gethostbyname(name);

	RETURN(result);
	return(result);
}

struct servent *
amiga_getservbyname(char *name,char *proto)
{
	struct servent *result;

	ENTER();

	SHOWSTRING(name);
	SHOWSTRING(proto);

	initialize_libraries();

	result = getservbyname(name,proto);

	RETURN(result);
	return(result);
}

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

int
amiga_bind(int fd,struct sockaddr *name,int namelen)
{
	struct FileHandle * fh;
	struct UFB * ufb;
	int result = -1;

	ENTER();

	initialize_libraries();

	ufb = chkufb(fd);
	if(ufb == NULL)
	{
		errno = EIO;
		goto out;
	}

	fh = (struct FileHandle *)BADDR(ufb->ufbfh);
	if(fh->fh_Type != &magic_port)
	{
		errno = EBADF;
		goto out;
	}

	result = bind(fh->fh_Arg1,name,namelen);

 out:

	RETURN(result);
	return(result);
}

int
amiga_connect(int fd,struct sockaddr *name,int namelen)
{
	struct FileHandle * fh;
	struct UFB * ufb;
	int result = -1;

	ENTER();

	initialize_libraries();

	ufb = chkufb(fd);
	if(ufb == NULL)
	{
		errno = EIO;
		goto out;
	}

	fh = (struct FileHandle *)BADDR(ufb->ufbfh);
	if(fh->fh_Type != &magic_port)
	{
		errno = EBADF;
		goto out;
	}

	result = connect(fh->fh_Arg1,name,namelen);

 out:

	RETURN(result);
	return(result);
}

int
amiga_dup(int socketfd)
{
	struct FileHandle * fh;
	BPTR socket_lock = ZERO;
	BPTR socket_handle = ZERO;
	int fd = -1;
	struct UFB * ufb;

	ENTER();

	initialize_libraries();

	ufb = chkufb(socketfd);
	if(ufb == NULL)
	{
		errno = EIO;
		goto out;
	}

	fh = (struct FileHandle *)BADDR(ufb->ufbfh);
	if(fh->fh_Type != &magic_port)
	{
		errno = EBADF;
		goto out;
	}

	socket_lock = DoPkt(&magic_port,ACTION_COPY_DIR_SOCKET,fh->fh_Arg1,	0,0,0,0);
	if(socket_lock == ZERO)
	{
		errno = EIO;
		goto out;
	}

	socket_handle = OpenFromLock(socket_lock);
	if(socket_handle == ZERO)
	{
		errno = EIO;
		goto out;
	}

	socket_lock = ZERO;

	fd = open("NIL:",O_RDWR,0777);
	if(fd < 0)
		goto out;

	ufb = chkufb(fd);
	if(ufb == NULL)
	{
		int error;

		error = errno;

		close(fd);
		fd = -1;

		errno = error;
		goto out;
	}

	Close(ufb->ufbfh);
	ufb->ufbfh = socket_handle;

	socket_handle = ZERO;

 out:

	if(fd < 0)
	{
		if(socket_handle != ZERO)
			Close(socket_handle);

		UnLock(socket_lock);
	}

	RETURN(fd);
	return(fd);
}

int
amiga_recv(int fd,void *buff,int nbytes,int flags)
{
	struct FileHandle * fh;
	struct UFB * ufb;
	int result = -1;

	ENTER();

	initialize_libraries();

	ufb = chkufb(fd);
	if(ufb == NULL)
	{
		errno = EIO;
		goto out;
	}

	fh = (struct FileHandle *)BADDR(ufb->ufbfh);
	if(fh->fh_Type != &magic_port)
	{
		errno = EBADF;
		goto out;
	}

	result = recv(fh->fh_Arg1,buff,nbytes,flags);

 out:

	RETURN(result);
	return(result);
}

int
amiga_send(int fd,void *buff,int nbytes,int flags)
{
	struct FileHandle * fh;
	struct UFB * ufb;
	int result = -1;

	ENTER();

	initialize_libraries();

	ufb = chkufb(fd);
	if(ufb == NULL)
	{
		errno = EIO;
		goto out;
	}

	fh = (struct FileHandle *)BADDR(ufb->ufbfh);
	if(fh->fh_Type != &magic_port)
	{
		errno = EBADF;
		goto out;
	}

	result = send(fh->fh_Arg1,buff,nbytes,flags);

 out:

	RETURN(result);
	return(result);
}

int
amiga_shutdown(int fd,int how)
{
	struct FileHandle * fh;
	struct UFB * ufb;
	int result = -1;

	ENTER();

	initialize_libraries();

	ufb = chkufb(fd);
	if(ufb == NULL)
	{
		errno = EIO;
		goto out;
	}

	fh = (struct FileHandle *)BADDR(ufb->ufbfh);
	if(fh->fh_Type != &magic_port)
	{
		errno = EBADF;
		goto out;
	}

	result = shutdown(fh->fh_Arg1,how);

 out:

	RETURN(result);
	return(result);
}

int
amiga_socket(int domain,int type,int protocol)
{
	BPTR socket_lock;
	BPTR socket_handle = ZERO;
	int fd = -1;
	struct UFB * ufb;

	ENTER();

	initialize_libraries();

	socket_lock = DoPkt(&magic_port,ACTION_LOCATE_SOCKET,domain,type,protocol,	0,0);
	if(socket_lock == ZERO)
	{
		errno = EIO;
		goto out;
	}

	socket_handle = OpenFromLock(socket_lock);
	if(socket_handle == ZERO)
	{
		errno = EIO;
		goto out;
	}

	socket_lock = ZERO;

	fd = open("NIL:",O_RDWR,0777);
	if(fd < 0)
		goto out;

	ufb = chkufb(fd);
	if(ufb == NULL)
	{
		int error;

		error = errno;

		close(fd);
		fd = -1;

		errno = error;
		goto out;
	}

	Close(ufb->ufbfh);
	ufb->ufbfh = socket_handle;

	socket_handle = ZERO;

 out:

	if(fd < 0)
	{
		if(socket_handle != ZERO)
			Close(socket_handle);

		UnLock(socket_lock);
	}

	RETURN(fd);
	return(fd);
}

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

int
amiga_piped_child(char ** argv,int * to_fd_ptr,int * from_fd_ptr)
{
	int len,total_len,quotes,escape,argc,i,j;
	char * s;
	char * arg;
	char * command;
	BPTR input = ZERO;
	BPTR output = ZERO;
	char in_name[40];
	char out_name[40];
	int result = -1;

	ENTER();

	argc = 0;
	total_len = 0;
	for (i = 0 ; argv[i] != NULL ; i++)
	{
		argc++;
		arg = argv[i];
		len = strlen(arg);
		quotes = 0;

		for(j = 0 ; j < len ; j++)
		{
			if(arg[j] == ' ' && quotes == 0)
				quotes = 2;
			else if (arg[j] == '\"')
				total_len++;
		}

		total_len += len + quotes + 1;
	}

	command = malloc(total_len+1);
	if(command == NULL)
	{
		errno = ENOMEM;
		return(-1);
	}

	s = command;

	for (i = 0 ; i < argc ; i++)
	{
		arg = argv[i];
		len = strlen(arg);
		quotes = escape = 0;

		for(j = 0 ; j < len ; j++)
		{
			if(arg[j] == ' ')
				quotes = 1;
			else if (arg[j] == '\"')
				escape = 1;

			if(quotes && escape)
				break;
		}

		if(quotes)
			(*s++) = '\"';

		for(j = 0 ; j < len ; j++)
		{
			if(arg[j] == '\"')
				(*s++) = '*';

			(*s++) = arg[j];
		}

		if(quotes)
			(*s++) = '\"';

		if(i < argc-1)
			(*s++) = ' ';
	}

	(*s) = '\0';

	SHOWSTRING(command);

	sprintf(in_name,"PIPE:in_%08x.%08x",FindTask(NULL),time(NULL));
	sprintf(out_name,"PIPE:out_%08x.%08x",FindTask(NULL),time(NULL));

	input = Open(in_name,MODE_OLDFILE);
	output = Open(out_name,MODE_NEWFILE);
	if(input != ZERO && output != ZERO)
	{
		LONG res;

		res = SystemTags(command,
			SYS_Input,		input,
			SYS_Output,		output,
			SYS_Asynch,		TRUE,
			SYS_UserShell,	TRUE,
		TAG_END);

		switch(res)
		{
			case 0:
				(*to_fd_ptr) = open(in_name,O_WRONLY,0777);
				if((*to_fd_ptr) == -1)
					break;

				(*from_fd_ptr) = open(out_name,O_RDONLY,0777);
				if((*from_fd_ptr) == -1)
					break;

				result = 0;
				break;

			case -1:
				errno = ENOMEM;
				Close(input);
				Close(output);
				break;

			default:
				errno = EIO;
				break;
		}
	}
	else
	{
		if(input != ZERO)
			Close(input);

		if(output != ZERO)
			Close(output);

		errno = EIO;
	}

	RETURN(result);
	return(result);
}

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

int
amiga_isabsolute(char *filename)
{
	int result = 0;
	int i;

	ENTER();

	SHOWSTRING(filename);

	for(i = 0 ; i < strlen(filename) ; i++)
	{
		if(filename[i] == ':')
		{
			result = 1;
			break;
		}
	}

	RETURN(result);
	return(result);
}

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

char *
amiga_last_component(char *path)
{
	char * result;

	ENTER();

	SHOWSTRING(path);

	result = FilePart(path);

	RETURN(result);
	return(result);
}

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

static int
recursive_unlink_file_dir(char *f)
{
	D_S(struct FileInfoBlock,fib);
	BPTR lock;
	int res = 0;

	lock = Lock(f,SHARED_LOCK);
	if(lock != ZERO)
	{
		if(Examine(lock,fib))
		{
			if(FIB_IS_DRAWER(fib))
			{
				char name[110];

				name[0] = '\0';

				while(ExNext(lock,fib))
				{
					if(SetSignal(0,0) & SIGBREAKF_CTRL_C)
					{
						res = -1;
						break;
					}

					if(name[0] != '\0')
					{
						if(DeleteFile(name))
						{
							res = -1;
							break;
						}

						name[0] = '\0';
					}

					if(FIB_IS_DRAWER(fib))
					{
						BPTR old_dir;

						old_dir = CurrentDir(lock);
						res = recursive_unlink_file_dir(fib->fib_FileName);
						CurrentDir(old_dir);

						if(res != 0)
							break;
					}

					strcpy(name,fib->fib_FileName);
				}

				if(res == 0 && name[0] != '\0')
				{
					if(CANNOT DeleteFile(name))
						res = -1;
				}
			}

			UnLock(lock);

			if(res == 0)
			{
				if(CANNOT DeleteFile(f))
					res = -1;
			}
		}
		else
		{
			UnLock(lock);
		}
	}

	return(res);
}

int
amiga_unlink_file_dir(char * f)
{
	int res;

	ENTER();

	correct_name(&f);

	SHOWSTRING(f);

	res = recursive_unlink_file_dir(f);

	RETURN(res);
	return(res);
}

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

int
amiga_fncmp(char *n1,char *n2)
{
	int result;

	ENTER();

	SHOWSTRING(n1);
	SHOWSTRING(n2);

	result = Stricmp(n1,n2);

	RETURN(result);
	return(result);
}

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

void
amiga_fnfold(char *name)
{
	int c;

	while((c = (*(unsigned char *)name)) != '\0')
		(*name++) = ToLower(c);
}

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

int
amiga_fold_fn_char(int c)
{
	int result;

	result = ToLower(c);

	return(result);
}

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

typedef struct name_node
{
	struct name_node *	nn_next;
	char *				nn_name;
	BOOL				nn_wild;
} name_node_t;

static int
compare(char **a,char **b)
{
	return(Stricmp(*a,*b));
}

void
amiga_expand_wild(int argc,char ** argv,int * _argc,char *** _argv)
{
	struct AnchorPath * anchor;
	name_node_t * root;
	name_node_t * node;
	LONG name_plus;
	LONG name_total;
	LONG i;

	ENTER();

	anchor		= (struct AnchorPath *)xmalloc(sizeof(*anchor) + 2 * MAX_FILENAME_LEN);
	root		= NULL;
	name_plus	= 0;
	name_total	= 0;

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

	anchor->ap_Strlen		= MAX_FILENAME_LEN;
	anchor->ap_BreakBits	= SIGBREAKF_CTRL_C;

	for(i = 0 ; i < argc ; i++)
	{
		if(i > 0 && ParsePatternNoCase(argv[i],anchor->ap_Buf,2 * MAX_FILENAME_LEN) > 0)
		{
			LONG result;

			result = MatchFirst(argv[i],anchor);

			while(result == 0)
			{
				node = (name_node_t *)malloc(sizeof(*node) + strlen(anchor->ap_Buf) + 1);
				if(node == NULL)
				{
					char buf[80];

					MatchEnd(anchor);

					sprintf(buf,"out of memory; can not allocate %lu bytes",
						(unsigned long)(sizeof(*node) + strlen(anchor->ap_Buf) + 1));

					error(1,0,buf);
				}

				node->nn_name = (char *)(node + 1);
				node->nn_next = root;
				node->nn_wild = TRUE;

				strcpy(node->nn_name,anchor->ap_Buf);

				root = node;

				name_plus++;
				name_total++;

				result = MatchNext(anchor);
			}

			MatchEnd(anchor);
		}
		else
		{
			node = (name_node_t *)xmalloc(sizeof(*node));

			node->nn_name = argv[i];
			node->nn_next = root;
			node->nn_wild = FALSE;

			root = node;

			name_total++;
		}
	}

	if(name_plus > 0)
	{
		char ** last_wild;
		char ** index;

		index = (char **)xmalloc(sizeof(char *) * (name_total + 1));

		(*_argc) = name_total;
		(*_argv) = index;

		index = &(index[name_total]);

		(*index--) = NULL;

		node		= root;
		last_wild	= NULL;

		while(node != NULL)
		{
			if(node->nn_wild)
			{
				if(last_wild == NULL)
					last_wild = index;
			}
			else
			{
				if(last_wild)
				{
					if((ULONG)last_wild - (ULONG)index > sizeof(char **))
						qsort(index + 1,((ULONG)last_wild - (ULONG)index) / sizeof(char **),sizeof(char *),compare);

					last_wild = NULL;
				}
			}

			(*index--) = node->nn_name;

			node = node->nn_next;
		}
	}
	else
	{
		name_node_t * next;

		node = root;

		while(node != NULL)
		{
			next = node->nn_next;

			free(node);

			node = next;
		}
	}

	free(anchor);

	LEAVE();
}

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

static void
convert_fileinfo_to_stat(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_mode		= mode;
	st->st_mtime	= UNIX_TIME_OFFSET + time + 60 * get_minutes_west();	/* translate from local time to UTC */
	st->st_atime	= st->st_mtime;
	st->st_ctime	= st->st_mtime;

	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)
{
	int result = -1;
	BPTR fileLock;
	int len;

	chkabort();

	correct_name(&name);

	len = strlen(name);

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

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

			/* Check if this is a root directory. */
			parentDir = ParentDir(fileLock);
			if(parentDir != ZERO)
			{
				/* This is not the root directory. */
				UnLock(parentDir);
			}
			else
			{
				/* So this is a 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;
			}

			convert_fileinfo_to_stat(fib,st);

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

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

	return(result);
}

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

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

	result = amiga_stat(name,statstruct);

	return(result);
}

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

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

	chkabort();

	ufb = chkufb(fd);
	if(ufb != NULL)
	{
		D_S(struct FileInfoBlock,fib);

		if(ExamineFH(ufb->ufbfh,fib))
		{
			convert_fileinfo_to_stat(fib,st);

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

	return(result);
}

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

int
amiga_chmod(char *name,int mode)
{
	int result = OK;
	ULONG flags = 0;

	chkabort();

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

	correct_name(&name);

	if(CANNOT SetProtection(name,flags))
	{
		LONG error;

		error = IoErr();
		if(error != ERROR_OBJECT_IN_USE)
		{
			SetIoErr(error);

			map_ioerr_to_errno();

			result = -1;
		}
	}

	return(result);
}

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

int
amiga_access(char *name,int modes)
{
	int result;

	ENTER();

	correct_name(&name);

	SHOWSTRING(name);
	SHOWVALUE(modes);

	/* We ignore the 'x' bit since it doesn't matter
	 * on the Amiga.
	 */
	result = access(name,modes & ~X_OK);

	RETURN(result);
	return(result);
}

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

static BPTR home_dir;

static void
restore_home_dir(void)
{
	if(home_dir != ZERO)
	{
		UnLock(CurrentDir(home_dir));
		home_dir = ZERO;
	}
}

DEFAULT_DESTRUCTOR(restore_home_dir)
{
	restore_home_dir();
}

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

int
amiga_chdir(char *path)
{
	int result;

	ENTER();

	if(home_dir == ZERO)
	{
		BPTR old_dir;

		/* This is tricky at best. chdir() will change the
		 * current directory of this process and unlock the
		 * previously active current directory lock. However,
		 * the current directory lock with which this program
		 * was launched *must not* be unlocked; the same lock
		 * the program was launched with must be the one the
		 * program exits with. This is what we are trying to
		 * achieve here.
		 */
		old_dir = Lock("",SHARED_LOCK);
		if(old_dir == ZERO)
		{
			errno = EIO;
			result = -1;
			goto out;
		}

		home_dir = CurrentDir(old_dir);
	}

	correct_name(&path);

	SHOWSTRING(path);

	result = chdir(path);

 out:

	RETURN(result);
	return(result);
}

int
amiga_creat(char *name,int prot)
{
	int result;

	ENTER();

	correct_name(&name);

	SHOWSTRING(name);
	SHOWVALUE(prot);

	result = creat(name,prot);

	RETURN(result);
	return(result);
}

FILE *
amiga_fopen(char *name,const char *modes)
{
	FILE * result;

	ENTER();

	correct_name(&name);

	SHOWSTRING(name);
	SHOWSTRING(modes);

	result = fopen(name,modes);

	RETURN(result);
	return(result);
}

int
amiga_open(char *name,int mode,int prot)
{
	int result;

	ENTER();

	correct_name(&name);

	SHOWSTRING(name);
	SHOWVALUE(mode);

	result = open(name,mode);

	RETURN(result);
	return(result);
}

void *
amiga_opendir(char *dir_name)
{
	void * result;

	ENTER();

	correct_name(&dir_name);

	SHOWSTRING(dir_name);

	result = opendir(dir_name);

	RETURN(result);
	return(result);
}

int
amiga_rename(char *old,char *new)
{
	int result;

	ENTER();

	correct_name(&old);
	correct_name(&new);

	SHOWSTRING(old);
	SHOWSTRING(new);

	result = rename(old,new);
	if(result == -1 && errno == EEXIST)
	{
		unlink(new);
		result = rename(old,new);
	}

	RETURN(result);
	return(result);
}

int
amiga_rmdir(char *name)
{
	int result;

	ENTER();

	correct_name(&name);

	SHOWSTRING(name);

	result = rmdir(name);

	RETURN(result);
	return(result);
}

int
amiga_unlink(char *name)
{
	int result;

	ENTER();

	correct_name(&name);

	SHOWSTRING(name);

	result = unlink(name);

	RETURN(result);
	return(result);
}

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

char *
amiga_cvs_temp_name(void)
{
	extern char *Tmpdir;
	char *value;
	char *retval;
	int max_len;
	int len;

	ENTER();

	max_len = strlen (Tmpdir) + 40;

	value = xmalloc (max_len);

	strcpy(value,Tmpdir);
	len = strlen(value);
	while(len > 0 && value[len-1] == '/')
		value[--len] = '\0';

	AddPart(value,"cvsXXXXXX",max_len);

	retval = mktemp (value);

	if (retval == NULL)
		error (1, errno, "cannot generate temporary filename");

	SHOWSTRING(value);

	RETURN(value);
	return(value);
}

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

static int amiga_rcmd(char **remote_hostname, int remote_port,char *local_user, char *remote_user,char *command)
{
  struct hostent *remote_hp;
  struct hostent *local_hp;
  struct sockaddr_in remote_isa;
  struct sockaddr_in local_isa;
  char local_hostname[80];
  char ch;
  int s;
  int local_port;
  int rs;

  remote_hp = amiga_gethostbyname(*remote_hostname);
  if(!remote_hp)
    {
    perror("couldn't get remote host address");
    exit(1);
    }

  /* Copy remote IP address into socket address structure */
  memset(&remote_isa,0,sizeof(remote_isa));
  remote_isa.sin_family = AF_INET;
  remote_isa.sin_port = htons(remote_port);
  memcpy(&remote_isa.sin_addr, remote_hp->h_addr, sizeof(remote_isa.sin_addr));

  amiga_gethostname(local_hostname, 80);
  local_hp = amiga_gethostbyname(local_hostname);
  if(!local_hp)
    {
    perror("couldn't get local host address");
    exit(1);
    }

  /* Copy local IP address into socket address structure */
  memset(&local_isa,0,sizeof(local_isa));
  local_isa.sin_family = AF_INET;
  memcpy(&local_isa.sin_addr, local_hp->h_addr, sizeof(local_isa.sin_addr));

  /* Create the local socket */
  s = amiga_socket(AF_INET, SOCK_STREAM, 0);
  if(s < 0)
    {
    perror("socket failed\n");
    exit(1);
    }

  /* Bind local socket with a port from IPPORT_RESERVED/2 to IPPORT_RESERVED - 1
     this requires the OPER privilege under VMS -- to allow communication with
     a stock rshd under UNIX */

  for(local_port = IPPORT_RESERVED - 1; local_port >= IPPORT_RESERVED/2; local_port--)
    {
    local_isa.sin_port = htons(local_port);
    rs = amiga_bind(s, (struct sockaddr *)&local_isa, sizeof(local_isa));
    if(rs == 0)
      break;
    }

  /* Bind local socket to an unprivileged port.  A normal rshd will drop the
     connection; you must be running a patched rshd invoked through inetd for
     this connection method to work */

  if (rs != 0)
    for(local_port = IPPORT_USERRESERVED - 1;
        local_port > IPPORT_RESERVED;
        local_port--)
      {
      local_isa.sin_port = htons(local_port);
      rs = amiga_bind(s, (struct sockaddr *)&local_isa, sizeof(local_isa));
      if(rs == 0)
        break;
      }

  rs = amiga_connect(s, (struct sockaddr *) &remote_isa, sizeof(remote_isa));
  if(rs == -1)
    {
    fprintf(stderr, "connect: errno = %d\n", errno);
    close(s);
    exit(2);
    }

  /* Now supply authentication information */

  /* Auxiliary port number for error messages, we don't use it */
  write(s, "0\0", 2);

  /* Who are we */
  write(s, local_user, strlen(local_user) + 1);

  /* Who do we want to be */
  write(s, remote_user, strlen(remote_user) + 1);

  /* What do we want to run */
  write(s, command, strlen(command) + 1);

  /* NUL is sent back to us if information is acceptable */
  read(s, &ch, 1);
  if(ch != '\0')
    {
    errno = EPERM;
    return -1;
    }

  return s;
}

static char *cvs_server;
static char *command;

extern int trace;

void
amiga_start_server (int *tofd, int *fromfd,char *client_user, char *server_user,char *server_host, char *server_cvsroot)
{
  int fd, port;
  char *portenv;
  struct servent *sptr;

  if (! (cvs_server = getenv ("CVS_SERVER")))
      cvs_server = "cvs";
  command = xmalloc (strlen (cvs_server)
		     + strlen (server_cvsroot)
		     + 50);
  sprintf(command, "%s server", cvs_server);

  portenv = getenv("CVS_RCMD_PORT");
  if (portenv)
      port = atoi(portenv);
  else if ((sptr = amiga_getservbyname("shell", "tcp")) != NULL)
      port = sptr->s_port;
  else
      port = 514; /* shell/tcp */

  if(trace)
    {
    fprintf(stderr, "amiga_start_server(): connecting to %s:%d\n",
            server_host, port);
    fprintf(stderr, "local_user = %s, remote_user = %s, CVSROOT = %s\n",
            client_user, (server_user ? server_user : client_user),
            server_cvsroot);
    }

  fd = amiga_rcmd(&server_host, port,
            client_user,
            (server_user ? server_user : client_user),
            command);

  if (fd < 0)
     error (1, errno, "cannot start server via rcmd()");

  (*tofd) = fd;
  (*fromfd) = fd;

  free (command);
}

void
amiga_shutdown_server (int fd)
{
    /* FIXME: shutdown on files seems to have no bad effects */
    if (amiga_shutdown (fd, 2) < 0 && errno != ENOTSOCK)
        error (1, 0, "couldn't shutdown server connection");
    if (close (fd) < 0)
        error (1, 0, "couldn't close server connection");
}

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

void
amiga_system_initialize(int * _argc,char *** _argv)
{
	amiga_expand_wild((*_argc),(*_argv),_argc,_argv);
}
