/*
 * CDDL HEADER START
 *
 * The contents of this file are subject to the terms of the
 * Common Development and Distribution License (the "Licence").
 * You may not use this file except in compliance with the Licence.
 *
 * You can obtain a copy of the licence at RISC OS path @.^.LICENCE
 * or  http://www.riscosdev.com/lanman98/LICENCE.CDDL
 * See the Licence for the specific language governing permissions
 * and limitations under the Licence.
 *
 * When distributing Covered Code, include this CDDL HEADER in each
 * file and include the Licence file. If applicable, add the
 * following below this CDDL HEADER, with the fields enclosed by
 * brackets "[]" replaced with your own identifying information:
 * Portions Copyright [yyyy] [name of copyright owner]
 *
 * CDDL HEADER END
 */

/*
 *   Copyright 1996 Warm Silence Software Ltd.  All rights reserved.
 *   Use is subject to license terms.
 */

/*   PHBG 7/10/96: Initial version
 */


#include <ctype.h>
#include <string.h>
#include <stdio.h>
#include <stdlib.h>

// #include "MemCheck:MemCheck.h"


#include "swis.h"
#include "kernel.h"
#include "LanMan98BaseLib/error.h"
#include "nodebug.h"
#include "LanMan98BaseLib/memory.h"
#include "LanMan98BaseLib/callback.h"
#include "omni_swis.h"
//#include "LanMan98SWIs.h"
#include "LanMan98FSLib/fspatch.h"
#include "showfree.h"
#include "LanMan98FSLib/fsys.h"
#include "mount.h"
#include "netbios.h"
#include "logon.h"
#include "srvsvc.h"
#include "discover.h"
#include "vc.h"
#include "smb.h"
#include "mapping.h"
#include "omniheader.h"
/*
    Module_FullVersion carries the beta label where there is one; the
    version CMunge puts in the header is the number alone.

    CMunge stamps its own Module_Date from the clock it was run under,
    which is not the one the version file records; the definition is
    dropped rather than allowed to clash, since nothing here uses it.
*/
#undef Module_Date
#include "VersionNum"


#if 0
#define NC_ONLY
#endif

#define MIN(x, y) ((x) < (y) ? (x) : (y))

static char user_reg[512 + 16] = "unencrypted  regRO Devs Open Source Edition";

static void add_fs(void *pw)
{
    FsPatchAddFS(pw);
}

static void remove_fs(void)
{
    FsPatchRemoveFS();
}

static void omni_register(void)
{
    _kernel_swi_regs r;
    static char desc[256];

    /* Info box lines are 32 characters; this one is exactly that */
    sprintf(desc, "CIFS client v" Module_FullVersion "\n Gardiner, RISC OS Developments\n%s", user_reg + 16);
    r.r[0] = LanMan98_OmniOp;     /* Client ID            */
    r.r[1] = 0xf; /* Flags: RC_NEEDS_USERID | RC_NEEDS_PASSWD | RC_NEEDS_MOUNTPATH |
                            RC_DOES_FILES | RC_EXTN_CHAR('.') | RC_DOES_PRINT | RC_NEEDS_PRINTPWD */
    r.r[2] = (int) "omni_lm98";   /* Sprite name - 12 max */ 
    r.r[3] = (int) "LanMan98";    /* Title bar - 24 max   */
    r.r[4] = (int) desc;          /* Info box - 3x32 max  */
    r.r[5] = 0;                   /* Site ID (ignored)    */
    r.r[6] = (int) "LanMan98";    /* FilingSystemName     */
    _kernel_swi(Omni_RegisterClient, &r, &r);
    return;
    r.r[0] = LanMan98_LogonOp;
    r.r[1] = 0x5F;
    r.r[3] = (int) "Logon98";
    _kernel_swi(Omni_RegisterClient, &r, &r);
}

static void omni_deregister(void)
{
    _kernel_swi_regs r;

    r.r[0] = LanMan98_OmniOp;
    _kernel_swi(Omni_DeregisterClient, &r, &r);
    r.r[0] = LanMan98_LogonOp;
    _kernel_swi(Omni_DeregisterClient, &r, &r);
}

_kernel_oserror* finalise(int fatal, int podule, void *pw)
{
    CallBacksOff();
    omni_deregister();
    ShowFreeDeReg(FsysFSNumber);
    remove_fs();
    FsysShut();
//    MemCheck_OutputBlocksInfo();
    // ThreadLibraryFinalise();
    MemFinalise();
    return NULL;
}

#ifdef MemCheck_MEMCHECK
/*
#include "tcp.h"
#include "blocked.h"

static int mem_printf(void *reference, const char *format, va_list args)
{
    static tcp_conn_t conn = NULL;
    static char buf[1024];
    int res, time;

    MemCheck_SetChecking(0, 0);
    ExceptTry
    {
        if(conn == NULL)
        {
            conn = TcpCall(TCP_PORT_ANY, 1777, TcpResolve("10.0.0.1"));
            TcpSetNoDelay(conn);
        }
        res = vsprintf(buf, format, args);
        TcpWrite(buf, strlen(buf), conn);
        time = Time() + 100;
        while(Time() - time < 0)
            BlockedWait();
    }
    ExceptCatch
    {
        return 0;
    }
    MemCheck_SetChecking(1, 1);
    return res;
}
*/

static int mem_printf(void *reference, const char *format, va_list args)
{
    FILE *f;
    int n;

    f = fopen("<LanMan98$Dir>.MemCheck", "ab");
    if(f)
    {
        n = vfprintf(f, format, args);
        fclose(f);
    }
    return n;
}

#endif

#ifdef NC_ONLY
static void nc_check(char *mname, int mver)
{
    int mod_num = 0;
    int rom_sect = -1;
    int regs = _IN(0)|_IN(1)|_IN(2)|_OUT(1)|_OUT(2)|_OUT(3)|_OUT(6);
    char *name;
    int ver;

    while(rom_sect == -1 && NULL == _swix(OS_Module, regs, 20, mod_num, rom_sect, &mod_num, &rom_sect, &name, &ver))
    {
        if(strcmp(mname, name) == 0 && mver <= ver)
            return;
    }
    Error("This version of LanMan98 works only with NCs");
}
#endif


_kernel_oserror *initialise(const char *cmd_tail, int podule, void *pw)
{
//    MemCheck_Init();
    // MemCheck_RedirectToFilename("CIFS_Mem");
//    MemCheck_RedirectToFunction(mem_printf, NULL);
//    MemCheck_InterceptSCLStringFunctions();
//    MemCheck_SetQuitting(0, 0);
    // MemCheck_SetQuitting(1, 1);
//    MemCheck_OutputBlocksInfo();
    MemInitialise("LanMan98");
#ifdef NC_ONLY
    ExceptTry
    {
        nc_check("OSVersion", 0x00010600); // 111 aswell
    }
    ExceptCatch
    {
        return ExceptCaught();
    }
#endif
    CallBackInit(pw);
    add_fs(pw);
    ShowFreeReg(FsysFSNumber);
    omni_register();
    MapInit();
    FsysSweepStart();
#ifdef NC_ONLY
    printf("LanMan98 for NC v" Module_FullVersion " (c) Warm Silence Software Ltd.\n(c) RISC OS Developments 2019+\nAll rights reserved.\nAuthors: Paul Gardiner, RISC OS Developments\n\nRegistration: %s\n", user_reg+16);
#else
    printf("LanMan98 v" Module_FullVersion " (c) Warm Silence Software Ltd 1996-2019.\n(c) RISC OS Developments 2019+\nAll rights reserved.\nAuthors: Paul Gardiner, RISC OS Developments\n\nRegistration: %s\n", user_reg+16);
#endif
    return NULL;
}

void service(int service_number, _kernel_swi_regs *r, void *pw)
{
//    MemCheck_RegisterMiscBlock(r, sizeof(*r));
    switch(service_number)
    {
        case 0x40:
            add_fs(pw);
            break;
        case 0xa0:
            if(r->r[0] == 0)
                omni_register();
            break;
    }
//    MemCheck_UnRegisterMiscBlock(r);
}

static int poll_word;

static void connect(int argc, const char *args)
{
    char buf[80];
    int i, len;
    char *tok[5];

    if(argc < 3 || argc > 5)
       Error("Wrong number of arguments");
//    MemCheck_RegisterMiscBlock(args, 80);
    for(len = 0; len < 79 && !iscntrl(args[len]); len++)
        buf[len] = args[len];
    buf[len] = 0;
    tok[3] = "";
    tok[4] = "";
    for(i = 0; i < argc; i++)
        tok[i] = strtok(i == 0 ? buf : NULL, " \t");
    FsysMount(tok[0], tok[1], tok[2], tok[3], tok[4]);
    poll_word = 1;
    _swix(Omni_EnumerateMounts, _IN(0)|_IN(1), LanMan98_OmniOp, 1);
//    MemCheck_UnRegisterMiscBlock(args);
}

/*
    Turn a name into something that can be a file name here.

    Share names come from the other end and may hold characters that mean
    something to this filing system, or nothing at all.  Anything that is
    not plainly a letter or a digit becomes an underscore.
*/
static void safe_name(char *out, const char *in, int max)
{
    int i;

    for(i = 0; (i < max - 1) && in[i]; i++)
    {
        char c = in[i];

        if(((c >= 'A') && (c <= 'Z')) || ((c >= 'a') && (c <= 'z')) ||
           ((c >= '0') && (c <= '9')) || (c == '-') || (c == '_'))
            out[i] = c;
        else
            out[i] = '_';
    }
    out[i] = 0;
}

/*
    Find every server on the network, ask each what it shares, and leave an
    icon for each share that can be connected to.

    The icon is the same thing the connect window saves when a share is
    mounted by hand: a few lines saying where it is, with the password left
    out.  Double clicking one opens the connect window with everything but
    the password already filled in, which is the whole point.

    A share that already has an icon is left alone.  The one there may have
    a password kept in it, and finding the share again is no reason to
    throw that away.
*/
/*
    Where a share that has merely been found is put.

    Not in !LanMan98.Discs.  That directory is the ones the user keeps -
    named, and possibly with a password in - and writing every share on the
    network into it fills it with machines nobody asked for, on the local
    disc, permanently.  What is found goes in the scrap directory instead,
    is cleared out at the start of each look, and only becomes a kept icon
    if it is double clicked and the connection succeeds.

    The path is left in LanMan98$Found so the front end can open it without
    having to work it out again.  If there is no scrap directory to use,
    the old place is used rather than failing.
*/
static char *found_dir(void)
{
    static char dir[256];
    char *scrap;

    scrap = getenv("Wimp$ScrapDir");
    if((scrap == NULL) || (*scrap == 0) || (strlen(scrap) + 10 > sizeof(dir)))
    {
        strcpy(dir, "LM98:Discs");
        return dir;
    }
    sprintf(dir, "%s.LanMan98", scrap);
    return dir;
}

static void clear_found(const char *dir)
{
    char cmd[300];

    sprintf(cmd, "CDir %.250s", dir);
    _kernel_oscli(cmd);
    /*
        An empty directory makes Wipe complain that there is nothing to
        delete, which is not a problem worth reporting - the error is
        deliberately dropped.
    */
    sprintf(cmd, "Wipe %.240s.* ~C ~V F", dir);
    _kernel_oscli(cmd);
}

static void discover(int argc, const char *args)
{
    char buf[160];
    char *tok[2];
    char fname[256];
    char leaf[64];
    char *dir;
    found_t servers, f;
    share_t list, sh;
    int i, len, made, seen;
    FILE *fp;

    for(len = 0; len < 159 && !iscntrl(args[len]); len++)
        buf[len] = args[len];
    buf[len] = 0;
    tok[0] = ""; tok[1] = "";
    for(i = 0; i < argc && i < 2; i++)
        tok[i] = strtok(i == 0 ? buf : NULL, " \t");

    dir = found_dir();
    clear_found(dir);
    sprintf(fname, "Set LanMan98$Found %.230s", dir);
    _kernel_oscli(fname);

    printf("Looking for servers...\n");
    servers = DiscoverServers(300);
    if(servers == NULL)
    {
        printf("Nothing on this network announced itself\n");
        return;
    }

    made = 0;
    seen = 0;
    for(f = servers; f; f = f->next)
    {
        smb_server_t svr;

        seen++;
        printf("%s (%s)\n", f->name, f->addr);
        list = NULL;
        svr = NULL;
        ExceptTry
        {
            /*
                The port came back with the announcement, so there is no
                need to try one and then the other, and no need to wait
                long: a machine that will not answer promptly here is one
                to move past rather than one to sit on.
            */
            VCImpatient(200);
            svr = SMBConnectRAP(f->addr, f->port ? f->port : SMB_PORT_AUTO,
                                f->name,
                                (argc > 0) ? tok[0] : LoggedOnUser(f->name),
                                (argc > 1) ? tok[1] : LoggedOnPassword(f->name));
            list = SRVSVCEnumShares((smb2_conn_t) SMBServerSMB2(svr), f->name);
        }
        ExceptCatch
        {
            _kernel_oserror *why = ExceptCaught();

            /* One server that will not say is not a reason to stop: the
               next one may, and the list is worth having either way. */
            VCImpatient(0);
            if(svr) { SMBDropServer(svr); svr = NULL; }

            /*
                A machine nobody is logged on to usually still says what it
                shares if it is asked without a name at all - which is how
                a share list is got from a machine one has no account on.
                Worth one try before giving up on it, but only where the
                name was the thing it objected to.
            */
            if(why && (why->errnum == 0x20002))
            {
                ExceptTry
                {
                    VCImpatient(200);
                    svr = SMBConnectRAP(f->addr,
                                        f->port ? f->port : SMB_PORT_AUTO,
                                        f->name, "", "");
                    list = SRVSVCEnumShares((smb2_conn_t) SMBServerSMB2(svr),
                                            f->name);
                }
                ExceptCatch
                {
                    VCImpatient(0);
                    if(svr) SMBDropServer(svr);
                    printf("   (refused the name and password, and would "
                           "not say without one)\n");
                    continue;
                }
                VCImpatient(0);
            }
            else
            {
                printf("   (%s)\n",
                       (why && why->errmess[0]) ? why->errmess
                                                : "would not say what it shares");
                continue;
            }
        }
        VCImpatient(0);
        SMBDropServer(svr);

        for(sh = list; sh; sh = sh->next)
        {
            /* Only the ones that hold files, and only the ones the server
               means to be seen */
            if((sh->type & 0x0F) != SHARE_DISK) continue;
            if(sh->type & SHARE_HIDDEN) continue;

            safe_name(leaf, sh->name, sizeof(leaf));
            if(leaf[0] == 0) continue;
            /*
                A share already kept in Discs is left alone and not offered
                again: it may have a password in it, and finding it a
                second time is no reason to throw that away.
            */
            sprintf(fname, "LM98:Discs.%.200s", leaf);
            fp = fopen(fname, "r");
            if(fp)
            {
                fclose(fp);
                printf("   %-20s already kept\n", sh->name);
                continue;
            }

            sprintf(fname, "%.180s.%.60s", dir, leaf);
            fp = fopen(fname, "w");
            if(fp == NULL)
            {
                printf("   %-20s could not be written\n", sh->name);
                continue;
            }
            fprintf(fp, "Server: %s\n", f->addr);
            fprintf(fp, "Share: %s\n", sh->name);
            fprintf(fp, "Password: *****\n");
            fclose(fp);
            sprintf(buf, "SetType %.140s &069", fname);
            _kernel_oscli(buf);
            printf("   %-20s %s\n", sh->name,
                   (sh->remark && *sh->remark) ? sh->remark : "");
            made++;
        }
        SRVSVCFreeShares(list);
    }
    VCImpatient(0);
    DiscoverFree(servers);
    printf("\n%d server%s, %d share%s found\n",
           seen, (seen == 1) ? "" : "s", made, (made == 1) ? "" : "s");
    if(made > 0)
        printf("Double click one to connect; it is kept only if it "
               "connects\n");
}

/*
    List the servers that announce themselves on this network.
*/
static void servers(int argc, const char *args)
{
    char buf[32];
    found_t list, f;
    int wait, len;

    wait = 300;                         /* three seconds by default */
    if(argc >= 1)
    {
        for(len = 0; len < 31 && !iscntrl(args[len]); len++)
            buf[len] = args[len];
        buf[len] = 0;
        wait = atoi(buf) * 100;
        if(wait < 50) wait = 50;
        if(wait > 3000) wait = 3000;
    }

    list = DiscoverServers(wait);
    if(list == NULL)
    {
        printf("Nothing on this network announced itself\n");
        return;
    }
    for(f = list; f; f = f->next)
        printf("%-24s %-16s port %d\n", f->name, f->addr, f->port);
    DiscoverFree(list);
}

/*
    List what a server shares.

    The connection is made to IPC$, which is where the call that asks lives,
    and dropped again afterwards - this looks, it does not mount.
*/
static void shares(int argc, const char *args)
{
    char buf[160];
    char *tok[3];
    char *addr;
    smb_server_t svr;
    share_t list, sh;
    int i, len;

    if(argc < 1 || argc > 3)
        Error("Wrong number of arguments");
    for(len = 0; len < 159 && !iscntrl(args[len]); len++)
        buf[len] = args[len];
    buf[len] = 0;
    tok[1] = "";
    tok[2] = "";
    for(i = 0; i < argc; i++)
        tok[i] = strtok(i == 0 ? buf : NULL, " \t");

    addr = NetBIOSResolve(tok[0]);
    svr = SMBConnectRAP(addr, SMB_PORT_AUTO, tok[0],
                        (argc > 1) ? tok[1] : LoggedOnUser(tok[0]),
                        (argc > 2) ? tok[2] : LoggedOnPassword(tok[0]));
    list = NULL;
    ExceptTry
    {
        list = SRVSVCEnumShares((smb2_conn_t) SMBServerSMB2(svr), tok[0]);
    }
    ExceptCatch
    {
        SMBDropServer(svr);
        ExceptRethrow();
    }
    SMBDropServer(svr);

    if(list == NULL)
    {
        printf("%s offers nothing that can be connected to\n", tok[0]);
        return;
    }
    for(sh = list; sh; sh = sh->next)
    {
        const char *kind;

        switch(sh->type & 0x0F)
        {
            case SHARE_DISK:    kind = "disc";    break;
            case SHARE_PRINTER: kind = "printer"; break;
            case SHARE_DEVICE:  kind = "device";  break;
            case SHARE_IPC:     kind = "pipe";    break;
            default:            kind = "?";       break;
        }
        /* The top bit marks the ones a server does not mean to advertise */
        printf("%-16s %-8s%s%s%s\n", sh->name, kind,
               (sh->type & SHARE_HIDDEN) ? " (hidden)" : "",
               (sh->remark && *sh->remark) ? "  " : "",
               (sh->remark && *sh->remark) ? sh->remark : "");
    }
    SRVSVCFreeShares(list);
}

static void disconnect(int argc, const char *args)
{
    char buf[80];
    int len;
    mount_t mt;

    if(argc != 1)
       Error("Wrong number of arguments");
//    MemCheck_RegisterMiscBlock(args, 80);
    for(len = 0; len < 79 && !iscntrl(args[len]); len++)
        buf[len] = args[len];
    buf[len] = 0;
    mt = FsysFindMount(buf);
    if(mt == NULL)
        Error("Disc not mounted");
    FsysDismount(mt);
    poll_word = 1;
    _swix(Omni_EnumerateMounts, _IN(0)|_IN(1), LanMan98_OmniOp, 1);
//    MemCheck_UnRegisterMiscBlock(args);
}

static void logon(int argc, const char *args)
{
    char buf[80];
    int i, len;
    char *tok[3];

    if(argc < 2 || argc > 3)
       Error("Wrong number of arguments");
//    MemCheck_RegisterMiscBlock(args, 80);
    for(len = 0; len < 79 && !iscntrl(args[len]); len++)
        buf[len] = args[len];
    buf[len] = 0;
    tok[2] = "";
    for(i = 0; i < argc; i++)
        tok[i] = strtok(i == 0 ? buf : NULL, " \t");
    LogOn(tok[0], tok[1], tok[2]);
//    MemCheck_UnRegisterMiscBlock(args);
}

static void logoff(int argc, const char *args)
{
    LogOff();
}

static char *byte_str(int64 *x)
{
    static char buf[10];

    if(x->high >> 13)
        sprintf(buf, "%dTB", x->high >> 8);
    else if(x->high >> 3)
        sprintf(buf, "%dGB", (x->high << 2) | (x->low >> 30));
    else if(x->high || (x->low >> 25))
        sprintf(buf, "%dMB", (x->high << 12) | (x->low >> 20));
    else if(x->low >> 15)
        sprintf(buf, "%dKB", x->low >> 10);
    else
        sprintf(buf, "%dB", x->low);
    return buf;
}

static void display_free(int argc, const char *args)
{
    char buf[80];
    int len;
    int64 total, free;

    if(argc != 1)
       Error("Disc name not specified");
//    MemCheck_RegisterMiscBlock(args, 80);
    for(len = 0; len < 79 && !iscntrl(args[len]); len++)
        buf[len] = args[len];
    buf[len] = 0;
    FsysSpace(FsysImageFromDiscName(buf, NULL), &total, &free);
    if(total.high || free.high)
    {
        printf("Bytes free  &%04X%08X = %7s\n", free.high, free.low, byte_str(&free));
        printf("Bytes total &%04X%08X = %7s\n", total.high, total.low, byte_str(&total));
    }
    else
    {
        printf("Bytes free  &%08X = %7s\n", free.low, byte_str(&free));
        printf("Bytes total &%08X = %7s\n", total.low, byte_str(&total));
    }
//    MemCheck_UnRegisterMiscBlock(args);
}

static void enumerate_mounts(char **bufp, int size, int *indexp)
{
    mount_t mt;
    int len;

    while((mt = FsysNthMount(*indexp)) != NULL && (len = ((strlen(FsysMountName(mt)) + 12) & ~3)) <= size)
    {
        ((int *) *bufp)[0] = -1;
        ((int *) *bufp)[1] = (int) mt;
        strcpy(*bufp + 8, FsysMountName(mt));
        *bufp += len;
        size -= len;
        (*indexp)++;
    }
    if(mt == NULL)
        *indexp = 0;
}

_kernel_oserror *command(const char *arg_string, int argc, int cmd_no, void *pw)
{
    _kernel_swi_regs r;
    _kernel_oserror *err;

    /*
        Several of these talk to the network, and the sweep's callback can
        fire in the middle of one.  Keep it out of the way until the command
        has finished, whether it finished well or not.
    */
    err = NULL;
    FsysSweepHold(1);
    ExceptTry
    {
        switch(cmd_no)
        {
            case 0:
                r.r[0] = 14;
                r.r[1] = 0x76;
                _kernel_swi(OS_FSControl, &r, &r);
                break;
            case 1:
                connect(argc, arg_string);
                break;
            case 2:
                disconnect(argc, arg_string);
                break;
            case 3:
                discover(argc, arg_string);
                break;
            case 4:
                servers(argc, arg_string);
                break;
            case 5:
                shares(argc, arg_string);
                break;
            case 6:
                logon(argc, arg_string);
                break;
            case 7:
                logoff(argc, arg_string);
                break;
            case 8:
                display_free(argc, arg_string);
                break;
            case 9:
                MapCommand(arg_string);
                break;
            default:
                Error("Unknown command");
        }
    }
    ExceptCatch
    {
        err = ExceptCaught();
    }
    FsysSweepHold(0);
    return err;
}

/*
#include <stdarg.h>

static int var_printf(char *fmt, ...)
{
    static int i = 0;
    char buf1[32];
    char buf2[64];
    va_list args;
    int ret;

    va_start(args, fmt);
    ret = vsprintf(buf2, fmt, args);
    va_end(args);
    sprintf(buf1, "LanMan98$XXX%03d", i++);
    VarSet(buf1, buf2);
    return ret;
}

static void show(int reg, int val)
{
    if(val == 0)
    {
        var_printf("R%d is NULL", reg);
    }
    else if(strlen((char *) val) > 20)
    {
        var_printf("R%d too long", reg);
    }
    else
    {
        var_printf("R%d = %s", reg, (char *) val);
    }
}
*/

static void omni_op(int op, _kernel_swi_regs *r)
{
    mount_t mt;
    char *name, buf[64];

    switch(op)
    {
        case 0:
            ExceptTry
            {
//                MemCheck_RegisterMiscBlock_String((char *) r->r[1]);
//                MemCheck_RegisterMiscBlock_String((char *) r->r[2]);
//                MemCheck_RegisterMiscBlock_String((char *) r->r[3]);
//                MemCheck_RegisterMiscBlock_String((char *) r->r[4]);
//                MemCheck_RegisterMiscBlock_String((char *) r->r[5]);
                mt = FsysMount((char *) r->r[4],
                               (char *) r->r[1],
                     	       (char *) r->r[5],
                      	       (char *) r->r[2],
                      	       (char *) r->r[3]);
//                MemCheck_UnRegisterMiscBlock((char *) r->r[1]);
//                MemCheck_UnRegisterMiscBlock((char *) r->r[2]);
//                MemCheck_UnRegisterMiscBlock((char *) r->r[3]);
//                MemCheck_UnRegisterMiscBlock((char *) r->r[4]);
//                MemCheck_UnRegisterMiscBlock((char *) r->r[5]);
                r->r[1] = (int) mt;
                poll_word = 1;
                _swix(Omni_EnumerateMounts, _IN(0)|_IN(1), LanMan98_OmniOp, 1);
            }
            ExceptCatch
            {
//                MemCheck_UnRegisterMiscBlock((char *) r->r[1]);
//               MemCheck_UnRegisterMiscBlock((char *) r->r[2]);
//               MemCheck_UnRegisterMiscBlock((char *) r->r[3]);
//                MemCheck_UnRegisterMiscBlock((char *) r->r[4]);
//                MemCheck_UnRegisterMiscBlock((char *) r->r[5]);
                r->r[1] = 0;
                ExceptRethrow();
            }
            break;
        case 1:
            FsysDismount((mount_t) r->r[1]);
            MemMinimalise();
            poll_word = 1;
            _swix(Omni_EnumerateMounts, _IN(0)|_IN(1), LanMan98_OmniOp, 1);
            break;
        case 5:
            enumerate_mounts((char **) &r->r[1], r->r[2], &r->r[3]);
            break;
        case 6:
        case 7:
            name = FsysMountName((mount_t) r->r[1]);
            if(name)
            {
                sprintf(buf, "Filer_OpenDir LanMan98::%s.$", name);
                _kernel_oscli(buf);
            }
            break;
        case 8:
            Error("Not supported");
            r->r[1] = LoggedOn() ? 0x9 : 0xF;
            break;
        case 9:
            mt = (mount_t) r->r[1];
            r->r[1] = (int) FsysMountServer(mt);
            r->r[2] = (int) FsysMountUser(mt);
            r->r[3] = (int) FsysMountName(mt);
            r->r[4] = (int) FsysMountPath(mt);
            r->r[5] = 0;
            r->r[6] = 0;
            break;
        default:
            ErrorErr(_kernel_swi(LanMan_OmniOp, r, r));
    }
}

static void logon_op(int op, _kernel_swi_regs *r)
{
    switch(op)
    {
        case 0:
            LogOn((char *) r->r[1], (char *) r->r[2], (char *) r->r[3]);
            r->r[0] = -1;
            break;
        /*
        case 3:
            if(r->r[3] == 0)
            {
                ((int *) r->r[1])[0] = 0;
                strcpy((char *)(r->r[1] + 4), "Logon98");
                r->r[1] += 12;
                strcpy((char *)(r->r[1]), "logon98");
                r->r[1] += 8;
                strcpy((char *)(r->r[1]), "Logon Network");
                r->r[1] += 16;
            }
            r->r[3] = 0;
            break;
        */
        case 17:
            LogOff();
            break;
    }
}

static void sub64(int64 *res, int64 *op1, int64 *op2)
{
    res->high = op1->high - op2->high;
    res->low = op1->low - op2->low;
    if(op2->low > op1->low)
        res->high--;
}

static void free_op(_kernel_swi_regs *r)
{
    static int len;

    switch(r->r[0])
    {
        case 0:
            break;
        case 1:
            strcpy((char*) r->r[2], (char*) r->r[3]);
            r->r[0] = len = strlen((char*) r->r[2]);
            break;
        case 2:
            {
                fsys_image_t img;
                int64 total, free, used;
                int *buf;

                img = FsysImageFromDiscName((char *) r->r[3], NULL);
                if(img == NULL)
                    Error("Disc not found...");
                FsysSpace(img, &total, &free);
                sub64(&used, &total, &free);
                buf = (int *) r->r[2];
                buf[0] = total.high ? 0xffffffff : total.low;
                buf[1] = free.high ? 0xffffffff : free.low;
                buf[2] = used.high ? 0xffffffff : used.low;
            }
            break;
        case 3:
            Error("Impossible");
            break;
        case 4:
            {
                fsys_image_t img;
                int64 total, free, used;
                int64 *buf;

                img = FsysImageFromDiscName((char *) r->r[3], NULL);
                if(img == NULL)
                    Error("Disc not found...");
                FsysSpace(img, &total, &free);
                sub64(&used, &total, &free);
                buf = (int64 *) r->r[2];
                buf[0] = total;
                buf[1] = free;
                buf[2] = used;
                r->r[0] = 0;
            }
            break;
        default:
            Error("Bad \"Free\" reason code");
    }
}

_kernel_oserror *swi_handler(int swi_no, _kernel_swi_regs *r, void *pw)
{
    static _kernel_oserror error_ill_swi = {0x10000, "Illegal SWI"};
    _kernel_oserror *err;

//    MemCheck_RegisterMiscBlock(r, sizeof(*r));
    err = NULL;
    FsysSweepHold(1);
    ExceptTry
    {
        switch(swi_no + LanMan98_OmniOp)
        {
            case LanMan98_OmniOp:
                omni_op(r->r[0], r);
                break;
            case LanMan98_Bad1:
                free_op(r);
                break;
            case LanMan98_LogonOp:
                logon_op(r->r[0], r);
                break;
            case LanMan98_PollWord:
                poll_word = 0;
                r->r[0] = (int) &poll_word;
                break;
            default:
                err = &error_ill_swi;
        }
    }
    ExceptCatch
    {
        err = ExceptCaught();
        err->errnum = 0;
    }
    FsysSweepHold(0);
//    MemCheck_UnRegisterMiscBlock(r);
    return err;
}
