The Perl Toolchain Summit needs more sponsors. If your company depends on Perl, please support this very important event.
/* amigaos.c uses only amigaos APIs,
 * as opposed to amigaio.c which mixes amigaos and perl APIs */

#include <string.h>

#include <sys/stat.h>
#include <unistd.h>
#include <assert.h>

#include <errno.h>
#include <stdio.h>
#include <stdlib.h>
#if defined(__CLIB2__)
#  include <dos.h>
#endif
#if defined(__NEWLIB__)
#  include <amiga_platform.h>
#endif
#include <fcntl.h>
#include <ctype.h>
#include <stdarg.h>
#include <stdbool.h>
#undef WORD
#define WORD int16

#include <dos/dos.h>
#include <proto/dos.h>
#include <proto/exec.h>
#include <proto/utility.h>

#include "amigaos.h"

struct UtilityIFace *IUtility = NULL;

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

struct Interface *OpenInterface(CONST_STRPTR libname, uint32 libver)
{
	struct Library *base = IExec->OpenLibrary(libname, libver);
	struct Interface *iface = IExec->GetInterface(base, "main", 1, NULL);
	if (iface == NULL)
	{
		// We should probably post some kind of error message here.

		IExec->CloseLibrary(base);
	}

	return iface;
}

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

void CloseInterface(struct Interface *iface)
{
	if (iface != NULL)
	{
		struct Library *base = iface->Data.LibBase;
		IExec->DropInterface(iface);
		IExec->CloseLibrary(base);
	}
}

BOOL __unlink_retries = FALSE;

void ___makeenviron() __attribute__((constructor));
void ___freeenviron() __attribute__((destructor));

void ___openinterfaces() __attribute__((constructor));
void ___closeinterfaces() __attribute__((destructor));

void ___openinterfaces()
{
	if (!IDOS)
		IDOS = (struct DOSIFace *)OpenInterface("dos.library", 53);
	if (!IUtility)
		IUtility =
		    (struct UtilityIFace *)OpenInterface("utility.library", 53);
}

void ___closeinterfaces()
{
	CloseInterface((struct Interface *)IDOS);
	CloseInterface((struct Interface *)IUtility);
}
int VARARGS68K araddebug(UBYTE *fmt, ...);
int VARARGS68K adebug(UBYTE *fmt, ...);

#define __USE_RUNCOMMAND__

char **myenviron = NULL;
char **origenviron = NULL;

static void createvars(char **envp);

struct args
{
	BPTR seglist;
	int stack;
	char *command;
	int length;
	int result;
	char **envp;
};

int __myrc(char *arg)
{
	struct Task *thisTask = IExec->FindTask(0);
	struct args *myargs = (struct args *)thisTask->tc_UserData;
	if (myargs->envp)
		createvars(myargs->envp);
	// adebug("%s %ld %s \n",__FUNCTION__,__LINE__,myargs->command);
	myargs->result = IDOS->RunCommand(myargs->seglist, myargs->stack,
	                                  myargs->command, myargs->length);
	return 0;
}

int32 myruncommand(
    BPTR seglist, int stack, char *command, int length, char **envp)
{
	struct args myargs;
	struct Task *thisTask = IExec->FindTask(0);
	struct Process *proc;

	// adebug("%s %ld  %s\n",__FUNCTION__,__LINE__,command?command:"NULL");

	myargs.seglist = seglist;
	myargs.stack = stack;
	myargs.command = command;
	myargs.length = length;
	myargs.result = -1;
	myargs.envp = envp;

	if ((proc = IDOS->CreateNewProcTags(
	                NP_Entry, __myrc, NP_Child, TRUE, NP_Input, IDOS->Input(),
	                NP_Output, IDOS->Output(), NP_Error, IDOS->ErrorOutput(),
	                NP_CloseInput, FALSE, NP_CloseOutput, FALSE, NP_CloseError,
	                FALSE, NP_CopyVars, FALSE,

	                //           NP_StackSize,           ((struct Process
	                //           *)myargs.parent)->pr_StackSize,
	                NP_Cli, TRUE, NP_UserData, (int)&myargs,
	                NP_NotifyOnDeathSigTask, thisTask, TAG_DONE)))

	{
		IExec->Wait(SIGF_CHILD);
	}
	return myargs.result;
}

char *mystrdup(const char *s)
{
	char *result = NULL;
	size_t size;

	size = strlen(s) + 1;

	if ((result = (char *)IExec->AllocVec(size, MEMF_ANY)))
	{
		memmove(result, s, size);
	}
	return result;
}

static int pipenum = 0;

int pipe(int filedes[2])
{
	char pipe_name[1024];

//   adebug("%s %ld \n",__FUNCTION__,__LINE__);
#ifdef USE_TEMPFILES
	sprintf(pipe_name, "/T/%x.%08lx", pipenum++, IUtility->GetUniqueID());
#else
	sprintf(pipe_name, "/PIPE/%x%08lx/4096/0", pipenum++,
	        IUtility->GetUniqueID());
#endif

	/*      printf("pipe: %s \n", pipe_name);*/

	filedes[1] = open(pipe_name, O_WRONLY | O_CREAT);
	filedes[0] = open(pipe_name, O_RDONLY);
	if (filedes[0] == -1 || filedes[1] == -1)
	{
		if (filedes[0] != -1)
			close(filedes[0]);
		if (filedes[1] != -1)
			close(filedes[1]);
		return -1;
	}
	/*      printf("filedes %d %d\n", filedes[0],
	 * filedes[1]);fflush(stdout);*/

	return 0;
}

int fork(void)
{
	fprintf(stderr, "Can not bloody fork\n");
	errno = ENOMEM;
	return -1;
}

int wait(int *status)
{
	fprintf(stderr, "No wait try waitpid instead\n");
	errno = ECHILD;
	return -1;
}

char *convert_path_a2u(const char *filename)
{
	struct NameTranslationInfo nti;

	if (!filename)
	{
		return 0;
	}

	__translate_amiga_to_unix_path_name(&filename, &nti);

	return mystrdup(filename);
}
char *convert_path_u2a(const char *filename)
{
	struct NameTranslationInfo nti;

	if (!filename)
	{
		return 0;
	}

	if (strcmp(filename, "/dev/tty") == 0)
	{
		return mystrdup("CONSOLE:");
		;
	}

	__translate_unix_to_amiga_path_name(&filename, &nti);

	return mystrdup(filename);
}

static struct SignalSemaphore environ_sema;

void amigaos4_init_environ_sema()
{
	IExec->InitSemaphore(&environ_sema);
}

void amigaos4_obtain_environ()
{
	IExec->ObtainSemaphore(&environ_sema);
}

void amigaos4_release_environ()
{
	IExec->ReleaseSemaphore(&environ_sema);
}

static void createvars(char **envp)
{
	if (envp)
	{
		/* Set a local var to indicate to any subsequent sh that it is
		* not
		* the top level shell and so should only inherit local amigaos
		* vars */
		IDOS->SetVar("ABCSH_IMPORT_LOCAL", "TRUE", 5, GVF_LOCAL_ONLY);

		amigaos4_obtain_environ();

		envp = myenviron;

		while ((envp != NULL) && (*envp != NULL))
		{
			int len;
			char *var;
			char *val;
			if ((len = strlen(*envp)))
			{
				if ((var = (char *)IExec->AllocVec(
				               len + 1, MEMF_ANY | MEMF_CLEAR)))
				{
					strcpy(var, *envp);

					val = strchr(var, '=');
					if (val)
					{
						*val++ = '\0';
						if (*val)
						{
							IDOS->SetVar(
							    var, val,
							    strlen(val) + 1,
							    GVF_LOCAL_ONLY);
						}
					}
					IExec->FreeVec(var);
				}
			}
			envp++;
		}
		amigaos4_release_environ();
	}
}

static BOOL contains_whitespace(char *string)
{

	if (string)
	{

		if (strchr(string, ' '))
			return TRUE;
		if (strchr(string, '\t'))
			return TRUE;
		if (strchr(string, '\n'))
			return TRUE;
		if (strchr(string, 0xA0))
			return TRUE;
		if (strchr(string, '"'))
			return TRUE;
	}
	return FALSE;
}

static int no_of_escapes(char *string)
{
	int cnt = 0;
	char *p;
	for (p = string; p < string + strlen(string); p++)
	{
		if (*p == '"')
			cnt++;
		if (*p == '*')
			cnt++;
		if (*p == '\n')
			cnt++;
		if (*p == '\t')
			cnt++;
	}
	return cnt;
}

struct command_data
{
	STRPTR args;
	BPTR seglist;
	struct Task *parent;
};

int myexecvp(bool isperlthread, const char *filename, char *argv[])
{
	//	adebug("%s %ld
	//%s\n",__FUNCTION__,__LINE__,filename?filename:"NULL");
	/* if there's a slash or a colon consider filename a path and skip
	 * search */
	int res;
	if ((strchr(filename, '/') == NULL) && (strchr(filename, ':') == NULL))
	{
		char *path;
		char *name;
		char *pathpart;
		char *p;
		size_t len;
		struct stat st;

		if (!(path = getenv("PATH")))
		{
			path = ".:/bin:/usr/bin:/c";
		}

		len = strlen(filename) + 1;
		name = (char *)alloca(strlen(path) + len);
		pathpart = (char *)alloca(strlen(path) + 1);
		p = path;
		do
		{
			path = p;

			if (!(p = strchr(path, ':')))
			{
				p = strchr(path, '\0');
			}

			memcpy(pathpart, path, p - path);
			pathpart[p - path] = '\0';
			if (!(strlen(pathpart) == 0))
			{
				sprintf(name, "%s/%s", pathpart, filename);
			}
			else
				sprintf(name, "%s", filename);

			if ((stat(name, &st) == 0) && (S_ISREG(st.st_mode)))
			{
				/* we stated it and it's a regular file */
				/* let's boogie! */
				filename = name;
				break;
			}

		}
		while (*p++ != '\0');
	}
	res = myexecve(isperlthread, filename, argv, myenviron);
	return res;
}

int myexecv(bool isperlthread, const char *path, char *argv[])
{
	return myexecve(isperlthread, path, argv, myenviron);
}

int myexecl(bool isperlthread, const char *path, ...)
{
	va_list va;
	char *argv[1024]; /* 1024 enough? let's hope so! */
	int i = 0;
	// adebug("%s %ld\n",__FUNCTION__,__LINE__);

	va_start(va, path);
	i = 0;

	do
	{
		argv[i] = va_arg(va, char *);
	}
	while (argv[i++] != NULL);

	va_end(va);
	return myexecve(isperlthread, path, argv, myenviron);
}

#if 0

int myexecve(const char *filename, char *argv[], char *envp[])
{
	FILE *fh;
	char buffer[1000];
	int size = 0;
	char **cur;
	char *interpreter = 0;
	char *interpreter_args = 0;
	char *full = 0;
	char *filename_conv = 0;
	char *interpreter_conv = 0;
	//        char *tmp = 0;
	char *fname;
	//        int tmpint;
	//        struct Task *thisTask = IExec->FindTask(0);
	int result = -1;

	StdioStore store;

	dTHX;
	if(aTHX) // I hope this is NULL when not on a interpreteer thread nor to level.
	{
		/* Save away our stdio */
		amigaos_stdio_save(aTHX_ & store);
	}

	// adebug("%s %ld %s\n",__FUNCTION__,__LINE__,filename?filename:"NULL");

	/* Calculate the size of filename and all args, including spaces and
	 * quotes */
	size = 0; // strlen(filename) + 1;
	for (cur = (char **)argv /* +1 */; *cur; cur++)
	{
		size +=
		    strlen(*cur) + 1 +
		    (contains_whitespace(*cur) ? (2 + no_of_escapes(*cur)) : 0);
	}
	/* Check if it's a script file */

	fh = fopen(filename, "r");
	if (fh)
	{
		if (fgetc(fh) == '#' && fgetc(fh) == '!')
		{
			char *p;
			char *q;
			fgets(buffer, 999, fh);
			p = buffer;
			while (*p == ' ' || *p == '\t')
				p++;
			if (buffer[strlen(buffer) - 1] == '\n')
				buffer[strlen(buffer) - 1] = '\0';
			if ((q = strchr(p, ' ')))
			{
				*q++ = '\0';
				if (*q != '\0')
				{
					interpreter_args = mystrdup(q);
				}
			}
			else
				interpreter_args = mystrdup("");

			interpreter = mystrdup(p);
			size += strlen(interpreter) + 1;
			size += strlen(interpreter_args) + 1;
		}

		fclose(fh);
	}
	else
	{
		/* We couldn't open this why not? */
		if (errno == ENOENT)
		{
			/* file didn't exist! */
			goto out;
		}
	}

	/* Allocate the command line */
	filename_conv = convert_path_u2a(filename);

	if (filename_conv)
		size += strlen(filename_conv);
	size += 1;
	full = (char *)IExec->AllocVec(size + 10, MEMF_ANY | MEMF_CLEAR);
	if (full)
	{
		if (interpreter)
		{
			interpreter_conv = convert_path_u2a(interpreter);
#if !defined(__USE_RUNCOMMAND__)
#warning(using system!)
			sprintf(full, "%s %s %s ", interpreter_conv,
			        interpreter_args, filename_conv);
#else
			sprintf(full, "%s %s ", interpreter_args,
			        filename_conv);
#endif
			IExec->FreeVec(interpreter);
			IExec->FreeVec(interpreter_args);

			if (filename_conv)
				IExec->FreeVec(filename_conv);
			fname = mystrdup(interpreter_conv);

			if (interpreter_conv)
				IExec->FreeVec(interpreter_conv);
		}
		else
		{
#ifndef __USE_RUNCOMMAND__
			sprintf(full, "%s ", filename_conv);
#else
			sprintf(full, "");
#endif
			fname = mystrdup(filename_conv);
			if (filename_conv)
				IExec->FreeVec(filename_conv);
		}

		for (cur = (char **)(argv + 1); *cur != 0; cur++)
		{
			if (contains_whitespace(*cur))
			{
				int esc = no_of_escapes(*cur);

				if (esc > 0)
				{
					char *buff = IExec->AllocVec(
					                 strlen(*cur) + 4 + esc,
					                 MEMF_ANY | MEMF_CLEAR);
					char *p = *cur;
					char *q = buff;

					*q++ = '"';
					while (*p != '\0')
					{

						if (*p == '\n')
						{
							*q++ = '*';
							*q++ = 'N';
							p++;
							continue;
						}
						else if (*p == '"')
						{
							*q++ = '*';
							*q++ = '"';
							p++;
							continue;
						}
						else if (*p == '*')
						{
							*q++ = '*';
						}
						*q++ = *p++;
					}
					*q++ = '"';
					*q++ = ' ';
					*q = '\0';
					strcat(full, buff);
					IExec->FreeVec(buff);
				}
				else
				{
					strcat(full, "\"");
					strcat(full, *cur);
					strcat(full, "\" ");
				}
			}
			else
			{
				strcat(full, *cur);
				strcat(full, " ");
			}
		}
		strcat(full, "\n");

//            if(envp)
//                 createvars(envp);

#ifndef __USE_RUNCOMMAND__
		result = IDOS->SystemTags(
		             full, SYS_UserShell, TRUE, NP_StackSize,
		             ((struct Process *)thisTask)->pr_StackSize, SYS_Input,
		             ((struct Process *)thisTask)->pr_CIS, SYS_Output,
		             ((struct Process *)thisTask)->pr_COS, SYS_Error,
		             ((struct Process *)thisTask)->pr_CES, TAG_DONE);
#else

		if (fname)
		{
			BPTR seglist = IDOS->LoadSeg(fname);
			if (seglist)
			{
				/* check if we have an executable! */
				struct PseudoSegList *ps = NULL;
				if (!IDOS->GetSegListInfoTags(
				            seglist, GSLI_Native, &ps, TAG_DONE))
				{
					IDOS->GetSegListInfoTags(
					    seglist, GSLI_68KPS, &ps, TAG_DONE);
				}
				if (ps != NULL)
				{
					//                    adebug("%s %ld %s
					//                    %s\n",__FUNCTION__,__LINE__,fname,full);
					IDOS->SetCliProgramName(fname);
					//                        result=RunCommand(seglist,8*1024,full,strlen(full));
					//                        result=myruncommand(seglist,8*1024,full,strlen(full),envp);
					result = myruncommand(seglist, 8 * 1024,
					                      full, -1, envp);
					errno = 0;
				}
				else
				{
					errno = ENOEXEC;
				}
				IDOS->UnLoadSeg(seglist);
			}
			else
			{
				errno = ENOEXEC;
			}
			IExec->FreeVec(fname);
		}

#endif /* USE_RUNCOMMAND */

		IExec->FreeVec(full);
		if (errno == ENOEXEC)
		{
			result = -1;
		}
		goto out;
	}

	if (interpreter)
		IExec->FreeVec(interpreter);
	if (filename_conv)
		IExec->FreeVec(filename_conv);

	errno = ENOMEM;

out:

	amigaos_stdio_restore(aTHX_ &store);
	STATUS_NATIVE_CHILD_SET(result);
	PL_exit_flags |= PERL_EXIT_EXPECTED;
	if (result != -1) my_exit(result);

	return(result);
}

#endif

int pause(void)
{
	fprintf(stderr, "Pause not implemented\n");

	errno = EINTR;
	return -1;
}

uint32 size_env(struct Hook *hook, APTR userdata, struct ScanVarsMsg *message)
{
	if (strlen(message->sv_GDir) <= 4)
	{
		hook->h_Data = (APTR)(((uint32)hook->h_Data) + 1);
	}
	return 0;
}

uint32 copy_env(struct Hook *hook, APTR userdata, struct ScanVarsMsg *message)
{
	if (strlen(message->sv_GDir) <= 4)
	{
		char **env = (char **)hook->h_Data;
		uint32 size =
		    strlen(message->sv_Name) + 1 + message->sv_VarLen + 1 + 1;
		char *buffer = (char *)IExec->AllocVec((uint32)size,
		                                       MEMF_ANY | MEMF_CLEAR);

		snprintf(buffer, size - 1, "%s=%s", message->sv_Name,
		         message->sv_Var);

		*env = buffer;
		env++;
		hook->h_Data = env;
	}
	return 0;
}

void ___makeenviron()
{
	struct Hook hook;

	char varbuf[8];
	uint32 flags = 0;

	struct DOSIFace *myIDOS =
	    (struct DOSIFace *)OpenInterface("dos.library", 53);
	if (myIDOS)
	{
		if (myIDOS->GetVar("ABCSH_IMPORT_LOCAL", varbuf, 8,
		                   GVF_LOCAL_ONLY) > 0)
		{
			flags = GVF_LOCAL_ONLY;
		}
		else
		{
			flags = GVF_GLOBAL_ONLY;
		}

		hook.h_Entry = size_env;
		hook.h_Data = 0;

		myIDOS->ScanVars(&hook, flags, 0);
		hook.h_Data = (APTR)(((uint32)hook.h_Data) + 1);

		myenviron = (char **)IExec->AllocVec((uint32)hook.h_Data *
		                                     sizeof(char **),
		                                     MEMF_ANY | MEMF_CLEAR);
		origenviron = myenviron;
		if (!myenviron)
		{
			return;
		}
		hook.h_Entry = copy_env;
		hook.h_Data = myenviron;

		myIDOS->ScanVars(&hook, flags, 0);
		CloseInterface((struct Interface *)myIDOS);
	}
}

void ___freeenviron()
{
	char **i;
	/* perl might change environ, it puts it back except for ctrl-c */
	/* so restore our own copy here */
	struct DOSIFace *myIDOS =
	    (struct DOSIFace *)OpenInterface("dos.library", 53);
	if (myIDOS)
	{
		myenviron = origenviron;

		if (myenviron)
		{
			for (i = myenviron; *i != NULL; i++)
			{
				IExec->FreeVec(*i);
			}
			IExec->FreeVec(myenviron);
			myenviron = NULL;
		}
		CloseInterface((struct Interface *)myIDOS);
	}
}

/* reimplementation of popen, clib2's doesn't do all we want */

static BOOL is_final_quote_character(const char *str)
{
	BOOL result;

	result = (BOOL)(str[0] == '\"' && (str[1] == '\0' || isspace(str[1])));

	return (result);
}

static BOOL is_final_squote_character(const char *str)
{
	BOOL result;

	result = (BOOL)(str[0] == '\'' && (str[1] == '\0' || isspace(str[1])));

	return (result);
}

int popen_child()
{
	struct Task *thisTask = IExec->FindTask(0);

	char *command = (char *)thisTask->tc_UserData;
	size_t len;
	char *str;
	int argc;
	int number_of_arguments;
	char *argv[4];

	argv[0] = "sh";
	argv[1] = "-c";
	argv[2] = command ? command : NULL;
	argv[3] = NULL;

	// adebug("%s %ld  %s\n",__FUNCTION__,__LINE__,command?command:"NULL");

	/* We need to give this to sh via execvp, execvp expects filename,
	 * argv[]
	 */

	myexecvp(FALSE, argv[0], argv);
	if (command)
		IExec->FreeVec(command);

	IExec->Forbid();
	return 0;
}

FILE *amigaos_popen(const char *cmd, const char *mode)
{
	FILE *result = NULL;
	char pipe_name[50];
	char unix_pipe[50];
	char ami_pipe[50];
	char *cmd_copy;
	BPTR input = 0;
	BPTR output = 0;
	struct Process *proc = NULL;
	struct Task *thisTask = IExec->FindTask(0);

	/* First we need to check the mode
	 * We can only have unidirectional pipes
	 */
	//    adebug("%s %ld cmd %s mode %s \n",__FUNCTION__,__LINE__,cmd,
	//    mode);

	switch (mode[0])
	{
	case 'r':
	case 'w':
		break;

	default:

		errno = EINVAL;
		return result;
	}

	/* Make a unique pipe name
	 * we need a unix one and an amigaos version (of the same pipe!)
	 * as were linking with libunix.
	 */

	sprintf(pipe_name, "%x%08lx/4096/0", pipenum++,
	        IUtility->GetUniqueID());
	sprintf(unix_pipe, "/PIPE/%s", pipe_name);
	sprintf(ami_pipe, "PIPE:%s", pipe_name);

	/* Now we open the AmigaOs Filehandles That we wil pass to our
	 * Sub process
	 */

	if (mode[0] == 'r')
	{
		/* A read mode pipe: Output from pipe input from NIL:*/
		input = IDOS->Open("NIL:", MODE_NEWFILE);
		if (input != 0)
		{
			output = IDOS->Open(ami_pipe, MODE_NEWFILE);
		}
	}
	else
	{

		input = IDOS->Open(ami_pipe, MODE_NEWFILE);
		if (input != 0)
		{
			output = IDOS->Open("NIL:", MODE_NEWFILE);
		}
	}
	if ((input == 0) || (output == 0))
	{
		/* Ouch stream opening failed */
		/* Close and bail */
		if (input)
			IDOS->Close(input);
		if (output)
			IDOS->Close(output);
		return result;
	}

	/* We have our streams now start our new process
	 * We're using a new process so that execve can modify the environment
	 * with messing things up for the shell that launched perl
	 * Copy cmd before we launch the subprocess as perl seems to waste
	 * no time in overwriting it! The subprocess will free the copy.
	 */

	if ((cmd_copy = mystrdup(cmd)))
	{
		// adebug("%s %ld
		// %s\n",__FUNCTION__,__LINE__,cmd_copy?cmd_copy:"NULL");
		proc = IDOS->CreateNewProcTags(
		           NP_Entry, popen_child, NP_Child, TRUE, NP_StackSize,
		           ((struct Process *)thisTask)->pr_StackSize, NP_Input, input,
		           NP_Output, output, NP_Error, IDOS->ErrorOutput(),
		           NP_CloseError, FALSE, NP_Cli, TRUE, NP_Name,
		           "Perl: popen process", NP_UserData, (int)cmd_copy,
		           TAG_DONE);
	}
	if (!proc)
	{
		/* New Process Failed to start
		 * Close and bail out
		 */
		if (input)
			IDOS->Close(input);
		if (output)
			IDOS->Close(output);
		if (cmd_copy)
			IExec->FreeVec(cmd_copy);
	}

	/* Our new process is running and will close it streams etc
	 * once its done. All we need to is open the pipe via stdio
	 */

	return fopen(unix_pipe, mode);
}

/* Work arround for clib2 fstat */
#ifndef S_IFCHR
#define S_IFCHR 0x0020000
#endif

#define SET_FLAG(u, v) ((void)((u) |= (v)))

int afstat(int fd, struct stat *statb)
{
	int result;
	BPTR fh;
	int mode;
	BOOL input;
	/* In the first instance pass it to fstat */
	// adebug("fd %ld ad %ld\n",fd,amigaos_get_file(fd));

	if ((result = fstat(fd, statb) >= 0))
		return result;

	/* Now we've got a file descriptor but we failed to stat it */
	/* Could be a nil: or could be a std#? */

	/* if get_default_file fails we had a dud fd so return failure */
#if !defined(__CLIB2__)

	fh = amigaos_get_file(fd);

	/* if nil: return failure*/
	if (fh == 0)
		return -1;

	/* Now compare with our process Input() Output() etc */
	/* if these were regular files sockets or pipes we had already
	 * succeeded */
	/* so we can guess they a character special console.... I hope */

	struct ExamineData *data;
	char name[120];
	name[0] = '\0';

	data = IDOS->ExamineObjectTags(EX_FileHandleInput, fh, TAG_END);
	if (data != NULL)
	{

		IUtility->Strlcpy(name, data->Name, sizeof(name));

		IDOS->FreeDosObject(DOS_EXAMINEDATA, data);
	}

	// adebug("ad %ld '%s'\n",amigaos_get_file(fd),name);
	mode = S_IFCHR;

	if (fh == IDOS->Input())
	{
		input = TRUE;
		SET_FLAG(mode, S_IRUSR);
		SET_FLAG(mode, S_IRGRP);
		SET_FLAG(mode, S_IROTH);
	}
	else if (fh == IDOS->Output() || fh == IDOS->ErrorOutput())
	{
		input = FALSE;
		SET_FLAG(mode, S_IWUSR);
		SET_FLAG(mode, S_IWGRP);
		SET_FLAG(mode, S_IWOTH);
	}
	else
	{
		/* we got a filehandle not handle by fstat or the above */
		/* most likely it's NIL: but lets check */
		struct ExamineData *exd = NULL;
		if ((exd = IDOS->ExamineObjectTags(EX_FileHandleInput, fh,
		                                   TAG_DONE)))
		{
			BOOL isnil = FALSE;
			if (exd->Type ==
			        (20060920)) // Ugh yes I know nasty.....
			{
				isnil = TRUE;
			}
			IDOS->FreeDosObject(DOS_EXAMINEDATA, exd);
			if (isnil)
			{
				/* yep we got NIL: */
				SET_FLAG(mode, S_IRUSR);
				SET_FLAG(mode, S_IRGRP);
				SET_FLAG(mode, S_IROTH);
				SET_FLAG(mode, S_IWUSR);
				SET_FLAG(mode, S_IWGRP);
				SET_FLAG(mode, S_IWOTH);
			}
			else
			{
				IExec->DebugPrintF(
				    "unhandled filehandle in afstat()\n");
				return -1;
			}
		}
	}

	memset(statb, 0, sizeof(statb));

	statb->st_mode = mode;

#endif
	return 0;
}

BPTR amigaos_get_file(int fd)
{
	BPTR fh = (BPTR)NULL;
	if (!(fh = _get_osfhandle(fd)))
	{
		switch (fd)
		{
		case 0:
			fh = IDOS->Input();
			break;
		case 1:
			fh = IDOS->Output();
			break;
		case 2:
			fh = IDOS->ErrorOutput();
			break;
		default:
			break;
		}
	}
	return fh;
}

/*########################################################################*/

#define LOCK_START 0xFFFFFFFFFFFFFFFELL
#define LOCK_LENGTH 1LL

// No wait forever option so lets wait for a loooong time.
#define TIMEOUT 0x7FFFFFFF

int amigaos_flock(int fd, int oper)
{
	BPTR fh;
	int32 success = -1;

	if (!(fh = amigaos_get_file(fd)))
	{
		errno = EBADF;
		return -1;
	}

	switch (oper)
	{
	case LOCK_SH:
	{
		if (IDOS->LockRecord(fh, LOCK_START, LOCK_LENGTH,
		                     REC_SHARED | RECF_DOS_METHOD_ONLY,
		                     TIMEOUT))
		{
			success = 0;
		}
		break;
	}
	case LOCK_EX:
	{
		if (IDOS->LockRecord(fh, LOCK_START, LOCK_LENGTH,
		                     REC_EXCLUSIVE | RECF_DOS_METHOD_ONLY,
		                     TIMEOUT))
		{
			success = 0;
		}
		break;
	}
	case LOCK_SH | LOCK_NB:
	{
		if (IDOS->LockRecord(fh, LOCK_START, LOCK_LENGTH,
		                     REC_SHARED_IMMED | RECF_DOS_METHOD_ONLY,
		                     TIMEOUT))
		{
			success = 0;
		}
		else
		{
			errno = EWOULDBLOCK;
		}
		break;
	}
	case LOCK_EX | LOCK_NB:
	{
		if (IDOS->LockRecord(fh, LOCK_START, LOCK_LENGTH,
		                     REC_EXCLUSIVE_IMMED | RECF_DOS_METHOD_ONLY,
		                     TIMEOUT))
		{
			success = 0;
		}
		else
		{
			errno = EWOULDBLOCK;
		}
		break;
	}
	case LOCK_UN:
	{
		if (IDOS->UnLockRecord(fh, LOCK_START, LOCK_LENGTH))
		{
			success = 0;
		}
		break;
	}
	default:
	{
		errno = EINVAL;
		return -1;
	}
	}
	return success;
}