/*
 * $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>
 * adapted to cvs-1.11.1p1 by Jens Langner <Jens.Langner@htw-dresden.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/debug_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 <time.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 <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"

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

// global debug macros
#define DEBUG_MAKRO_NOLF { kprintf("0x%08lx # "__FUNC__, (ULONG)FindTask(NULL)); }
#define DEBUG_MAKRO_LF   { kprintf("\n", NULL); }
#define DEBUG_MAKRO      { kprintf("0x%08lx # "__FUNC__"\n", (ULONG)FindTask(NULL)); }

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

#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	const char VersTag[] = "\0$VER: cvs 1.11.1p1 (29.04.2001) ported by Olaf Barthel and Jens Langner";

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

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(umask(mask));
}

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

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(getuid());
}

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

int
amiga_getgid(void)
{
    return(getgid());
}

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

long amiga_getgroups(long gidsetlen, long *gidset)
{
    return(getgroups(gidsetlen, gidset));
}

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

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) * 60 +   			/* jlangner +60 time fix */
          (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);
}

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

char *amiga_inet_ntoa(struct in_addr iaddr)
{
	return(Inet_NtoA(iaddr.s_addr));
}

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

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